From nobody Sat Jul 25 22:03:00 2026 Received: from mail-pj1-f46.google.com (mail-pj1-f46.google.com [209.85.216.46]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2DC953A963E for ; Mon, 13 Jul 2026 08:12:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.46 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783930331; cv=none; b=Y9qOyFasH3TbgfuVtQW/L7kzefY3/xbqgW+5ljY8lDyyyXNZqrggFcpiOwxWjRfq8kPiRfBQRmhdSLH4k78joBomuEb+PrA2tkgs8gxO6Cd9hhDPORZMAz2E5z6o2UTju+jn2Xam+NEHSF6DydzCRvTSWJ9kFNPEXPg02hVrxbE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783930331; c=relaxed/simple; bh=gAwrYaW8vCsSAnNCmcchp0PHhs5DGs2/EkBdFCJYnAc=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=jbx57Pfr31q1igOUia1GQK3mBIACX3KbFOOJ0/+uwurvnswhQnKwPWJReEhEcxl/cbo5Y0b3G5MLAF3tTkL174CEUmEiZKp7KKvJANUJsDsD8c5Z+htG1RMElIArO6Lp1Ltr60HcLhuuyZ9cFtkb39xSRI1TK8zPxuTuVFbkkLg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=CO9nszE6; arc=none smtp.client-ip=209.85.216.46 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="CO9nszE6" Received: by mail-pj1-f46.google.com with SMTP id 98e67ed59e1d1-38dcbade417so1220313a91.1 for ; Mon, 13 Jul 2026 01:12:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1783930329; x=1784535129; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=eO0942WY587PlgQYOqbKcvLrhgs/k5YOSOmJGEVN3qc=; b=CO9nszE64xhN8Ol1AA4n8cbGm7Pi/b0YrPWkTiAE0Xc0NGtf8ytpwHt0mSXbaqqpJL 6ja6Ptb3WOjo4doFxOmqFc/R7Ezo3YRiTOmDy93hALFQm6d+HD9ykbpe57AavahXbuq6 /kIhN1KSLDAW1jPbNQStscH2qKaC8orae0yoAGW5FdxPLalAma8qyhpLGG2KyjVyH4Yl gfn81xLlTeMpCqqz7JeYTtUDrby4zQrjD8HzvyK8FJYAPjwjdjLOhl0KUgtyaVECq9SW WmzrunXJosjfHwEn6JyRj9pteKboBdp5KRzyil6X7KavJ9CqWEkoO2HZ4HcExdVjzjqF UJuw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783930329; x=1784535129; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=eO0942WY587PlgQYOqbKcvLrhgs/k5YOSOmJGEVN3qc=; b=fvWpA4qHKqwBa3gst9yHfliQI4tfcD/nDMxM+PLEZxSXLaWPC1ZFxLcSf+m7KxiquL XClPHVhcycIHrHiSTWUxkV9ZuWDX80D7W/0UPA5DjBPVjPg5Tf5/fKOK4ZaR7+MpAZfW miMtvCOHA7E9CabZRl6COuClCjpQsY2Yp12I1Wxq5ZC+ieMNkU2Zp3HpwCTq4plsJeAo hcuO3FN0qaSy860tQG/m/n31NlGpAyFwIhgC9ySO12Tlnl06CjgVecCG/98UU1tGkS7M PooubeXPkY1yYAocfn3DCTywzY6+VWSXZw0QwnHqgitWkLjU9HmxkKOjbamFiK0DbUmG gefw== X-Forwarded-Encrypted: i=1; AHgh+RqDsq1etFtSpNOqMF5rpnmHf9pITqC8jP/aHmcFijjpE2L+QHFCiu5UL7e4v61Yn3lHstqSEUL9TmRC3V8=@vger.kernel.org X-Gm-Message-State: AOJu0Yw5AGjXFcbY2kSjjpa654cafA5MRRPnhXKkkd70+aYHowf8jnnY ycy8K6e+eTMZtv+5xQlNp6g7uNNOqF1LNb3RHJfqd1eFW5tpLNYmKrAi X-Gm-Gg: AfdE7clliGvGA2M8YW7Bznq/oRKlXAHSQlOk7bhXbYJ1S6E+lmSDvwtXJ7w/LzK7H9z YTz3/EM+wTvGKzLfQ7i0XspOtqnhaSvGLk1mVpH048B5MdLcmU1d0p+9Z3ww/DOiMKqEk5tLLR0 dfF2K8CzJBzF+HftdJ5dtbKq2U2XYOWGtAHYst6x/3L5sserImqDLggXqN1l+dsHfoiywvW7FoH ByzDSSgCo/TfHQq9kxF3VzPDnH4xpqmtSLB/Hkbydg+STmLg3PQMxyCbzzH4R3QYvPtznEwkvBv GU6IH2QT2D9IIXjrbMSJmYBsdopOeqLD2zOOhKTxToH5029MbHpwE6KkjHoQ9cXn1qBTSUDdT9a i/2CxjzUSX5/gThmRWwPd00jeDYY3nlXVOL8zR910mvwbBUbj+yGhBJK+/eaAeRLwZYi2c56bJD 92zI2Ya11Lje4rfChzFervpPhFoylxrH3L2Oi2VUMr7ZV8uDKJtpkCSOoTijcdwD4FwmjOb31aK EM= X-Received: by 2002:a17:90b:2f47:b0:389:8f6c:4d70 with SMTP id 98e67ed59e1d1-38dc77f74d5mr7473535a91.28.1783930329361; Mon, 13 Jul 2026 01:12:09 -0700 (PDT) Received: from localhost.localdomain (60-250-196-139.hinet-ip.hinet.net. [60.250.196.139]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-38e0e033de6sm269636a91.13.2026.07.13.01.12.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 13 Jul 2026 01:12:08 -0700 (PDT) From: Chi-Wen Weng To: jic23@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org Cc: dlechner@baylibre.com, nuno.sa@analog.com, andy@kernel.org, linux-arm-kernel@lists.infradead.org, linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, cwweng@nuvoton.com, cwweng.linux@gmail.com Subject: [PATCH v2 1/2] dt-bindings: iio: adc: Add Nuvoton MA35D1 EADC Date: Mon, 13 Jul 2026 16:11:26 +0800 Message-Id: <20260713081127.115197-2-cwweng.linux@gmail.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260713081127.115197-1-cwweng.linux@gmail.com> References: <20260713081127.115197-1-cwweng.linux@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Chi-Wen Weng Add devicetree binding for the Enhanced ADC controller found on Nuvoton MA35D1 SoCs. The controller has one register region, up to four end-of-conversion interrupts, one functional clock, and an optional reset line. The binding also describes the optional external reference supply and the optional DMA request supported by the hardware. ADC input pins are described using standard channel child nodes. The external ADC input channels are 0 to 7. Differential inputs are described with diff-channels and are limited to the fixed hardware pairs. Signed-off-by: Chi-Wen Weng --- .../bindings/iio/adc/nuvoton,ma35d1-eadc.yaml | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d= 1-eadc.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.= yaml b/Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml new file mode 100644 index 000000000000..1be5470643d1 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/nuvoton,ma35d1-eadc.yaml @@ -0,0 +1,163 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/nuvoton,ma35d1-eadc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Nuvoton MA35D1 Enhanced Analog to Digital Converter + +maintainers: + - Chi-Wen Weng + +description: + The Nuvoton MA35D1 Enhanced Analog to Digital Converter is a 12-bit + ADC controller with 8 external input channels, 4 fixed differential + input pairs and 1 internal VBAT input. Board wired ADC inputs are + described using channel child nodes. + +properties: + compatible: + const: nuvoton,ma35d1-eadc + + reg: + maxItems: 1 + + interrupts: + minItems: 1 + maxItems: 4 + items: + - description: ADINT0 end-of-conversion interrupt + - description: ADINT1 end-of-conversion interrupt + - description: ADINT2 end-of-conversion interrupt + - description: ADINT3 end-of-conversion interrupt + + clocks: + maxItems: 1 + + resets: + maxItems: 1 + + vref-supply: + description: + External reference voltage supply connected to the VREF pin. If this + property is omitted, the internal 1.6 V reference is used. + + dmas: + maxItems: 1 + + dma-names: + const: rx + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + +patternProperties: + '^channel@[0-7]$': + type: object + $ref: adc.yaml + unevaluatedProperties: false + + properties: + reg: + maximum: 7 + + diff-channels: true + + allOf: + - if: + properties: + reg: + const: 0 + then: + properties: + diff-channels: + items: + - const: 0 + - const: 4 + - if: + properties: + reg: + const: 1 + then: + properties: + diff-channels: + items: + - const: 1 + - const: 5 + - if: + properties: + reg: + const: 2 + then: + properties: + diff-channels: + items: + - const: 2 + - const: 6 + - if: + properties: + reg: + const: 3 + then: + properties: + diff-channels: + items: + - const: 3 + - const: 7 + - if: + properties: + reg: + enum: [4, 5, 6, 7] + then: + not: + required: + - diff-channels + + required: + - reg + +required: + - compatible + - reg + - interrupts + - clocks + - '#address-cells' + - '#size-cells' + +additionalProperties: false + +examples: + - | + #include + #include + #include + #include + + soc { + #address-cells =3D <2>; + #size-cells =3D <2>; + + adc@40430000 { + compatible =3D "nuvoton,ma35d1-eadc"; + reg =3D <0x0 0x40430000 0x0 0x10000>; + interrupts =3D ; + clocks =3D <&clk EADC_GATE>; + resets =3D <&reset MA35D1_RESET_EADC0>; + + #address-cells =3D <1>; + #size-cells =3D <0>; + + channel@0 { + reg =3D <0>; + }; + + channel@2 { + reg =3D <2>; + diff-channels =3D <2 6>; + }; + }; + }; +... --=20 2.25.1 From nobody Sat Jul 25 22:03:00 2026 Received: from mail-pj1-f54.google.com (mail-pj1-f54.google.com [209.85.216.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C20ED3AB293 for ; Mon, 13 Jul 2026 08:12:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.54 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783930344; cv=none; b=Cs+6be5MVVlFwkGt7fHS/esyB9s+X1ERhD/VGnALELtM8TAQDjf+EYmibLgAY205HqPxJ9B+d/sfYR4lTVemV9JMNiY5KJ2UVrCFq3TV7yzc2JVhOEQnO7DkaJXWBoprfWWFXvqGl/XP/w11Akmf+bEsMUjFogfluleQ90wYyVI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783930344; c=relaxed/simple; bh=dkYR4nc6krKwFAPYh3kFdw+u58OK7fbrBXpQoH0PCVc=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=EcUohreOu7C8UR7YObRAOxlMHFC7btUw6H5bE1Se2zKubMNd8Bw+IL6LtsP+T9r/zcQ9cch4NuGmIJ/V2/cocnwV4APDRTbI5WTpCci0QMxX6jhlLRJQW1M1rNGx4CwtlDeYIQYfP2TlYtGSC8xjc0Ygom2qeZ+gxTiDt69Nv3I= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=FdV6c6XJ; arc=none smtp.client-ip=209.85.216.54 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="FdV6c6XJ" Received: by mail-pj1-f54.google.com with SMTP id 98e67ed59e1d1-38759bcd877so2284745a91.2 for ; Mon, 13 Jul 2026 01:12:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1783930341; x=1784535141; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=xKgxKCWrar3l4e+Y6xInmD7AMTeWU1vfBzWTKqdgZ8M=; b=FdV6c6XJ301g27o1LUPd9Y1THO0keeLmF0DCCKwieDAHkGqATfr2oTGJqjoU2DLai+ 74R9sRMa1Cb4f1rt4uQGHE9pBX07EAyumodX+rhXiZF/O9bFY91hnqEdpXUeAR/mw2NF Dh+2ouYpKjZmhbma2hYFJmjDleu/R8NSPIrH9nhHwHazm26lxaQsuCovF0fY2m5hRwsF uNBDoeAwYcYaozWiQhuT09vs8y4zsJvfiCCEceHysRycPPgmch9kcaR9drPOdVWB+hf1 oN/8H7TSENmKCMZkAtqXvNOKnCyrfcTBdzcG6wZ8o3LB4FW9t/ADtNspXmPAZjULc/ZE 0myQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783930341; x=1784535141; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=xKgxKCWrar3l4e+Y6xInmD7AMTeWU1vfBzWTKqdgZ8M=; b=ZLZS1wjr2Jk2wnzgx/XNcILRQ7GB2QB6qZF9ZCA1PNflzc33LNRfpT6Rx7GwFovHwr bqPrzx/rKNEGfx6AXyDoivVKWRSmIPXXSz8gRRHaSAP8inI/Q+AJaSAeC+PfaOttl4ON 9LxJyNVTPwx71viIyLiyvKGJcQyCzodDStiggcnKpHszhnyocpcF7u/blNeAXSVw7mhU zEnOEVAS4mL8p2E5mYj77gr9lnZS2s9bLHA5TXTHXGB1JoIFKLp50fSnmzQeArZnXJIX +O5Bce8eSTBDRBG1khAvXRfelNoCZti5qOTWtxFl59J/aUsudiZ+qB2iqO4IW+iCpT8m 8MTg== X-Forwarded-Encrypted: i=1; AHgh+Ronhd/jOijRxVyYiAvu6XiIZX3VRfSTmmxBZ3sv25jClN/xuGyiDziTyEJrIyWbgrpcOpfEPNxUQyuaCRc=@vger.kernel.org X-Gm-Message-State: AOJu0YyHW+de+TvJhZW5caEvlAzhXRTWA0J3rumHwA0kaCXmBt8K67D6 s/PxGu2AAj3Vet09OCUJda7GhmfUANr9QIend4F6Aztpqdy6WOZ9iMhP X-Gm-Gg: AfdE7cnmKxBVO8xxUMIkNgBPrhhv/JRcVOYYQGtQ7clJB/dL3uHvstFfLi24NsZUNV/ iAUhSUOdHB+lj/MokQVaJpXlTZyr6a8QaVgMYojEcLt6Wn//cfZwitFDPI4iEzhWMDndEnmYIsE 2KyAgKGyQ9LSgTZFjA12sMxRBhqBoCT434mAZ8YXHnooXKQLayKIcvdPM9NsF+InxUDUtMIZSV3 7hw2EK84L5TTyeIoMQcldD9OmiR0Ak92ltYR0+/i0l1GTuoGtxsB+7+gkm3mhYOpqxcyXUZjReG Lvgpnc9mrPELjqhq/i5xDeuN/hl2cCHv1g13+GT2H9HmoOUJqHaDX7s3+PYO/lgNYzIYOpijMlM p4jfkS9uRjb63RyhSSPpuTj6Wi765yHM4XqkVRnDgjONiWGO62xQOqDMPoUVTbKdgQFqhtPuT+h uwAP2OX54wAnxMgv1T0RGzv3QdKb2rM36qloHKJTdUNlZ6tJmWVj0mtZ98FGEmNhzk2PgLUEUyJ WY= X-Received: by 2002:a17:90b:510a:b0:36a:c4b:76cf with SMTP id 98e67ed59e1d1-38dc7b37b5emr8139235a91.23.1783930341019; Mon, 13 Jul 2026 01:12:21 -0700 (PDT) Received: from localhost.localdomain (60-250-196-139.hinet-ip.hinet.net. [60.250.196.139]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-38e0e033de6sm269636a91.13.2026.07.13.01.12.17 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 13 Jul 2026 01:12:19 -0700 (PDT) From: Chi-Wen Weng To: jic23@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org Cc: dlechner@baylibre.com, nuno.sa@analog.com, andy@kernel.org, linux-arm-kernel@lists.infradead.org, linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, cwweng@nuvoton.com, cwweng.linux@gmail.com Subject: [PATCH v2 2/2] iio: adc: Add Nuvoton MA35D1 EADC driver Date: Mon, 13 Jul 2026 16:11:27 +0800 Message-Id: <20260713081127.115197-3-cwweng.linux@gmail.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260713081127.115197-1-cwweng.linux@gmail.com> References: <20260713081127.115197-1-cwweng.linux@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Chi-Wen Weng Add an IIO driver for the Nuvoton MA35D1 Enhanced ADC controller. The driver supports interrupt-driven direct raw reads and triggered buffered capture paced by an external IIO trigger. Buffered capture is limited to a single enabled voltage channel per scan in this initial implementation. Channels are described by firmware child nodes. Single-ended external channels and the fixed hardware differential input pairs are supported. The driver reports IIO scale from either an optional external reference supply or the internal 1.6 V reference. The driver also handles the functional clock and optional reset line. DMA support is not used by this initial driver. Signed-off-by: Chi-Wen Weng --- drivers/iio/adc/Kconfig | 12 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ma35d1_eadc.c | 740 ++++++++++++++++++++++++++++++++++ 3 files changed, 753 insertions(+) create mode 100644 drivers/iio/adc/ma35d1_eadc.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 1c663c98c6c9..509118dc5643 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -981,6 +981,18 @@ config LTC2497 To compile this driver as a module, choose M here: the module will be called ltc2497. =20 +config MA35D1_EADC + tristate "Nuvoton MA35D1 EADC driver" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select REGMAP_MMIO + help + Say yes here to build support for the Nuvoton MA35D1 + Enhanced Analog to Digital Converter. + + To compile this driver as a module, choose M here: the module will be + called ma35d1_eadc. + config MAX1027 tristate "Maxim max1027 ADC driver" depends on SPI diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 707dd708912f..7b9b38688223 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -85,6 +85,7 @@ obj-$(CONFIG_LTC2471) +=3D ltc2471.o obj-$(CONFIG_LTC2485) +=3D ltc2485.o obj-$(CONFIG_LTC2496) +=3D ltc2496.o ltc2497-core.o obj-$(CONFIG_LTC2497) +=3D ltc2497.o ltc2497-core.o +obj-$(CONFIG_MA35D1_EADC) +=3D ma35d1_eadc.o obj-$(CONFIG_MAX1027) +=3D max1027.o obj-$(CONFIG_MAX11100) +=3D max11100.o obj-$(CONFIG_MAX1118) +=3D max1118.o diff --git a/drivers/iio/adc/ma35d1_eadc.c b/drivers/iio/adc/ma35d1_eadc.c new file mode 100644 index 000000000000..b2733e816c49 --- /dev/null +++ b/drivers/iio/adc/ma35d1_eadc.c @@ -0,0 +1,740 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Nuvoton MA35D1 EADC driver + * + * Copyright (c) 2026 Nuvoton Technology Corp. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define MA35D1_EADC_DAT(n) (0x00 + (n) * 0x04) +#define MA35D1_EADC_CTL 0x50 +#define MA35D1_EADC_SWTRG 0x54 +#define MA35D1_EADC_SCTL(n) (0x80 + (n) * 0x04) +#define MA35D1_EADC_INTSRC0 0xd0 +#define MA35D1_EADC_STATUS2 0xf8 +#define MA35D1_EADC_SELSMP0 0x140 +#define MA35D1_EADC_REFADJCTL 0x150 + +#define MA35D1_EADC_CTL_ADCEN BIT(0) +#define MA35D1_EADC_CTL_ADCIEN0 BIT(2) +#define MA35D1_EADC_CTL_DIFFEN BIT(8) +#define MA35D1_EADC_CTL_DMOF BIT(9) +#define MA35D1_EADC_CTL_VREFSEL_MASK GENMASK(11, 10) +#define MA35D1_EADC_CTL_VREFSEL_1V6 FIELD_PREP(MA35D1_EADC_CTL_VREFSEL_MAS= K, 0) + +#define MA35D1_EADC_SCTL_CHSEL_MASK GENMASK(3, 0) +#define MA35D1_EADC_SCTL_TRGSEL_MASK GENMASK(21, 16) + +#define MA35D1_EADC_DAT_RESULT_MASK GENMASK(15, 0) +#define MA35D1_EADC_DAT_RESULT_12BIT GENMASK(11, 0) +#define MA35D1_EADC_DAT_OV BIT(16) +#define MA35D1_EADC_DAT_VALID BIT(17) + +#define MA35D1_EADC_INTSRC0_SPLIEN(n) BIT(n) +#define MA35D1_EADC_STATUS2_ADIF0 BIT(0) +#define MA35D1_EADC_REFADJCTL_PDREF BIT(0) +#define MA35D1_EADC_SELSMP0_SMPT0_MASK GENMASK(1, 0) +#define MA35D1_EADC_SELSMP_LONG FIELD_PREP(MA35D1_EADC_SELSMP0_SMPT0_MASK= , 3) + +#define MA35D1_EADC_MAX_EXT_CHANNELS 8 +#define MA35D1_EADC_INTERNAL_VREF_MV 1600 +#define MA35D1_EADC_TIMEOUT msecs_to_jiffies(1000) +#define MA35D1_EADC_RESET_DELAY_US 10 +#define MA35D1_EADC_REF_STABLE_US 1000 + +struct ma35d1_adc { + struct regmap *regmap; + struct clk *clk; + struct reset_control *rst; + struct regulator *vref; + struct completion completion; + /* Protects conversion state and register accesses from PM transitions. */ + struct mutex lock; + const struct iio_chan_spec *scan_chan; + unsigned int vref_mv; + int irq; + bool suspended; +}; + +static const struct regmap_config ma35d1_adc_regmap_config =3D { + .reg_bits =3D 32, + .val_bits =3D 32, + .reg_stride =3D 4, + .max_register =3D MA35D1_EADC_REFADJCTL, +}; + +static bool ma35d1_adc_valid_diff_pair(unsigned int vinp, unsigned int vin= n) +{ + return (vinp =3D=3D 0 && vinn =3D=3D 4) || + (vinp =3D=3D 1 && vinn =3D=3D 5) || + (vinp =3D=3D 2 && vinn =3D=3D 6) || + (vinp =3D=3D 3 && vinn =3D=3D 7); +} + +static int ma35d1_adc_set_bits(struct ma35d1_adc *adc, unsigned int reg, + unsigned int bits) +{ + return regmap_set_bits(adc->regmap, reg, bits); +} + +static int ma35d1_adc_clear_bits(struct ma35d1_adc *adc, unsigned int reg, + unsigned int bits) +{ + return regmap_clear_bits(adc->regmap, reg, bits); +} + +static int ma35d1_adc_disable_irq(struct ma35d1_adc *adc) +{ + return ma35d1_adc_clear_bits(adc, MA35D1_EADC_CTL, + MA35D1_EADC_CTL_ADCIEN0); +} + +static int ma35d1_adc_setup_reference(struct ma35d1_adc *adc) +{ + int ret; + + if (adc->vref) { + ret =3D regmap_set_bits(adc->regmap, MA35D1_EADC_REFADJCTL, + MA35D1_EADC_REFADJCTL_PDREF); + if (ret) + return ret; + } else { + ret =3D regmap_clear_bits(adc->regmap, MA35D1_EADC_REFADJCTL, + MA35D1_EADC_REFADJCTL_PDREF); + if (ret) + return ret; + + ret =3D regmap_update_bits(adc->regmap, MA35D1_EADC_CTL, + MA35D1_EADC_CTL_VREFSEL_MASK, + MA35D1_EADC_CTL_VREFSEL_1V6); + if (ret) + return ret; + } + + fsleep(MA35D1_EADC_REF_STABLE_US); + + return 0; +} + +static int ma35d1_adc_hw_init(struct ma35d1_adc *adc) +{ + int ret; + + ret =3D ma35d1_adc_disable_irq(adc); + if (ret) + return ret; + + ret =3D ma35d1_adc_setup_reference(adc); + if (ret) + return ret; + + ret =3D regmap_update_bits(adc->regmap, MA35D1_EADC_SELSMP0, + MA35D1_EADC_SELSMP0_SMPT0_MASK, + MA35D1_EADC_SELSMP_LONG); + if (ret) + return ret; + + ret =3D regmap_write(adc->regmap, MA35D1_EADC_STATUS2, + MA35D1_EADC_STATUS2_ADIF0); + if (ret) + return ret; + + return ma35d1_adc_set_bits(adc, MA35D1_EADC_CTL, MA35D1_EADC_CTL_ADCEN); +} + +static int ma35d1_adc_hw_disable(struct ma35d1_adc *adc) +{ + int ret; + + ret =3D ma35d1_adc_disable_irq(adc); + if (ret) + return ret; + + return ma35d1_adc_clear_bits(adc, MA35D1_EADC_CTL, MA35D1_EADC_CTL_ADCEN); +} + +static void ma35d1_adc_hw_disable_action(void *data) +{ + ma35d1_adc_hw_disable(data); +} + +static void ma35d1_adc_vref_disable(void *data) +{ + regulator_disable(data); +} + +static int ma35d1_adc_reset(struct ma35d1_adc *adc) +{ + int ret; + + if (!adc->rst) + return 0; + + ret =3D reset_control_assert(adc->rst); + if (ret) + return ret; + + fsleep(MA35D1_EADC_RESET_DELAY_US); + + return reset_control_deassert(adc->rst); +} + +static int ma35d1_adc_config_channel(struct ma35d1_adc *adc, + const struct iio_chan_spec *chan) +{ + unsigned int ctl; + int ret; + + ctl =3D chan->differential ? MA35D1_EADC_CTL_DIFFEN | + MA35D1_EADC_CTL_DMOF : 0; + + ret =3D regmap_update_bits(adc->regmap, MA35D1_EADC_CTL, + MA35D1_EADC_CTL_DIFFEN | + MA35D1_EADC_CTL_DMOF, ctl); + if (ret) + return ret; + + ret =3D regmap_update_bits(adc->regmap, MA35D1_EADC_SCTL(0), + MA35D1_EADC_SCTL_CHSEL_MASK | + MA35D1_EADC_SCTL_TRGSEL_MASK, + FIELD_PREP(MA35D1_EADC_SCTL_CHSEL_MASK, + chan->channel)); + if (ret) + return ret; + + return regmap_update_bits(adc->regmap, MA35D1_EADC_INTSRC0, + MA35D1_EADC_INTSRC0_SPLIEN(0), + MA35D1_EADC_INTSRC0_SPLIEN(0)); +} + +static int ma35d1_adc_read_conversion(struct ma35d1_adc *adc, + const struct iio_chan_spec *chan, u16 *raw) +{ + unsigned int val; + long timeout; + int ret; + + reinit_completion(&adc->completion); + + ret =3D regmap_write(adc->regmap, MA35D1_EADC_STATUS2, + MA35D1_EADC_STATUS2_ADIF0); + if (ret) + return ret; + + ret =3D ma35d1_adc_config_channel(adc, chan); + if (ret) + return ret; + + ret =3D ma35d1_adc_set_bits(adc, MA35D1_EADC_CTL, MA35D1_EADC_CTL_ADCIEN0= ); + if (ret) + return ret; + + ret =3D regmap_write(adc->regmap, MA35D1_EADC_SWTRG, BIT(0)); + if (ret) + goto disable_irq; + + timeout =3D wait_for_completion_timeout(&adc->completion, + MA35D1_EADC_TIMEOUT); + + ret =3D ma35d1_adc_disable_irq(adc); + if (ret) + return ret; + + if (!timeout) + return -ETIMEDOUT; + + ret =3D regmap_read(adc->regmap, MA35D1_EADC_DAT(0), &val); + if (ret) + return ret; + + if (!(val & MA35D1_EADC_DAT_VALID)) + return -EIO; + + if (val & MA35D1_EADC_DAT_OV) + return -EOVERFLOW; + + *raw =3D FIELD_GET(MA35D1_EADC_DAT_RESULT_MASK, val); + + return 0; + +disable_irq: + ma35d1_adc_disable_irq(adc); + + return ret; +} + +static irqreturn_t ma35d1_adc_isr(int irq, void *data) +{ + struct iio_dev *indio_dev =3D data; + struct ma35d1_adc *adc =3D iio_priv(indio_dev); + unsigned int status; + int ret; + + ret =3D regmap_read(adc->regmap, MA35D1_EADC_STATUS2, &status); + if (ret || !(status & MA35D1_EADC_STATUS2_ADIF0)) + return IRQ_NONE; + + regmap_write(adc->regmap, MA35D1_EADC_STATUS2, + MA35D1_EADC_STATUS2_ADIF0); + complete(&adc->completion); + + return IRQ_HANDLED; +} + +static int ma35d1_adc_sign_extend(u16 raw) +{ + return sign_extend32(raw & MA35D1_EADC_DAT_RESULT_12BIT, 11); +} + +static int ma35d1_adc_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct ma35d1_adc *adc =3D iio_priv(indio_dev); + u16 raw; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + guard(mutex)(&adc->lock); + if (adc->suspended) + return -EBUSY; + + ret =3D ma35d1_adc_read_conversion(adc, chan, &raw); + if (ret) + return ret; + + if (chan->differential) + *val =3D ma35d1_adc_sign_extend(raw); + else + *val =3D raw & MA35D1_EADC_DAT_RESULT_12BIT; + + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: + *val =3D adc->vref_mv; + *val2 =3D chan->differential ? 11 : 12; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static int ma35d1_adc_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ma35d1_adc *adc =3D iio_priv(indio_dev); + const struct iio_chan_spec *scan_chan =3D NULL; + unsigned long bit; + unsigned int count; + + count =3D 0; + for_each_set_bit(bit, scan_mask, indio_dev->masklength) { + scan_chan =3D &indio_dev->channels[bit]; + count++; + } + + if (count !=3D 1 || scan_chan->type !=3D IIO_VOLTAGE) + return -EINVAL; + + adc->scan_chan =3D scan_chan; + + return 0; +} + +static int ma35d1_adc_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ma35d1_adc *adc =3D iio_priv(indio_dev); + + guard(mutex)(&adc->lock); + + if (adc->suspended) + return -EBUSY; + + return adc->scan_chan ? 0 : -EINVAL; +} + +static int ma35d1_adc_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ma35d1_adc *adc =3D iio_priv(indio_dev); + int ret; + + guard(mutex)(&adc->lock); + + ret =3D ma35d1_adc_disable_irq(adc); + if (ret) + return ret; + + return regmap_write(adc->regmap, MA35D1_EADC_STATUS2, + MA35D1_EADC_STATUS2_ADIF0); +} + +static irqreturn_t ma35d1_adc_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf =3D p; + struct iio_dev *indio_dev =3D pf->indio_dev; + struct ma35d1_adc *adc =3D iio_priv(indio_dev); + IIO_DECLARE_BUFFER_WITH_TS(u16, scan, 1) =3D { }; + u16 raw; + int ret; + + guard(mutex)(&adc->lock); + if (adc->suspended || !adc->scan_chan) + goto done; + + ret =3D ma35d1_adc_read_conversion(adc, adc->scan_chan, &raw); + if (ret) + goto done; + + scan[0] =3D raw & MA35D1_EADC_DAT_RESULT_12BIT; + iio_push_to_buffers_with_timestamp(indio_dev, scan, pf->timestamp); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_buffer_setup_ops ma35d1_adc_buffer_ops =3D { + .postenable =3D ma35d1_adc_buffer_postenable, + .predisable =3D ma35d1_adc_buffer_predisable, +}; + +static const struct iio_info ma35d1_adc_info =3D { + .read_raw =3D ma35d1_adc_read_raw, + .update_scan_mode =3D ma35d1_adc_update_scan_mode, +}; + +static void ma35d1_adc_init_channel(struct iio_chan_spec *chan, u32 vinp, + u32 vinn, unsigned int scan_index, + bool differential) +{ + chan->type =3D IIO_VOLTAGE; + chan->indexed =3D 1; + chan->channel =3D vinp; + chan->scan_index =3D scan_index; + chan->info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE); + chan->scan_type.format =3D differential ? IIO_SCAN_FORMAT_SIGNED_INT : + IIO_SCAN_FORMAT_UNSIGNED_INT; + chan->scan_type.realbits =3D 12; + chan->scan_type.storagebits =3D 16; + chan->scan_type.endianness =3D IIO_CPU; + + if (differential) { + chan->differential =3D 1; + chan->channel2 =3D vinn; + } +} + +static struct iio_chan_spec +ma35d1_adc_timestamp_channel(unsigned int scan_index) +{ + struct iio_chan_spec chan =3D IIO_CHAN_SOFT_TIMESTAMP(scan_index); + + return chan; +} + +static int ma35d1_adc_parse_channels(struct iio_dev *indio_dev, + struct device *dev) +{ + DECLARE_BITMAP(used_channels, MA35D1_EADC_MAX_EXT_CHANNELS); + struct iio_chan_spec *channels; + unsigned int scan_index; + int num_channels; + int ret; + + bitmap_zero(used_channels, MA35D1_EADC_MAX_EXT_CHANNELS); + + num_channels =3D device_get_child_node_count(dev); + if (!num_channels) + return dev_err_probe(dev, -ENODATA, + "no ADC channels configured\n"); + + if (num_channels > MA35D1_EADC_MAX_EXT_CHANNELS) + return dev_err_probe(dev, -EINVAL, "too many ADC channels\n"); + + channels =3D devm_kcalloc(dev, num_channels + 1, sizeof(*channels), GFP_K= ERNEL); + if (!channels) + return -ENOMEM; + + scan_index =3D 0; + device_for_each_child_node_scoped(dev, child) { + u32 diff[2]; + u32 reg; + u32 vinn; + bool differential; + + ret =3D fwnode_property_read_u32(child, "reg", ®); + if (ret) + return dev_err_probe(dev, ret, + "missing channel reg property\n"); + + if (reg >=3D MA35D1_EADC_MAX_EXT_CHANNELS) + return dev_err_probe(dev, -EINVAL, + "invalid ADC channel %u\n", reg); + + if (test_and_set_bit(reg, used_channels)) + return dev_err_probe(dev, -EINVAL, + "duplicate ADC channel %u\n", reg); + + differential =3D false; + vinn =3D 0; + if (fwnode_property_present(child, "diff-channels")) { + ret =3D fwnode_property_read_u32_array(child, "diff-channels", diff, + ARRAY_SIZE(diff)); + if (ret) + return dev_err_probe(dev, ret, + "invalid diff-channels for channel %u\n", + reg); + + if (diff[0] !=3D reg || + !ma35d1_adc_valid_diff_pair(diff[0], diff[1])) + return dev_err_probe(dev, -EINVAL, + "invalid differential ADC channel %u-%u\n", + diff[0], diff[1]); + + if (test_and_set_bit(diff[1], used_channels)) + return dev_err_probe(dev, -EINVAL, + "ADC channel %u already used\n", + diff[1]); + + vinn =3D diff[1]; + differential =3D true; + } + + ma35d1_adc_init_channel(&channels[scan_index], reg, vinn, + scan_index, differential); + scan_index++; + } + + channels[scan_index] =3D ma35d1_adc_timestamp_channel(scan_index); + + indio_dev->channels =3D channels; + indio_dev->num_channels =3D scan_index + 1; + indio_dev->masklength =3D indio_dev->num_channels; + + return 0; +} + +static int ma35d1_adc_init_vref(struct ma35d1_adc *adc, struct device *dev) +{ + int ret; + + adc->vref =3D devm_regulator_get_optional(dev, "vref"); + if (IS_ERR(adc->vref)) { + if (PTR_ERR(adc->vref) !=3D -ENODEV) + return dev_err_probe(dev, PTR_ERR(adc->vref), + "failed to get VREF supply\n"); + + adc->vref =3D NULL; + adc->vref_mv =3D MA35D1_EADC_INTERNAL_VREF_MV; + + return 0; + } + + ret =3D regulator_enable(adc->vref); + if (ret) + return dev_err_probe(dev, ret, "failed to enable VREF supply\n"); + + ret =3D devm_add_action_or_reset(dev, ma35d1_adc_vref_disable, adc->vref); + if (ret) + return ret; + + ret =3D regulator_get_voltage(adc->vref); + if (ret <=3D 0) + return dev_err_probe(dev, ret ?: -EINVAL, + "failed to get VREF voltage\n"); + + adc->vref_mv =3D ret / 1000; + + return 0; +} + +static int ma35d1_adc_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct iio_dev *indio_dev; + struct ma35d1_adc *adc; + void __iomem *regs; + int irq; + int ret; + + indio_dev =3D devm_iio_device_alloc(dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc =3D iio_priv(indio_dev); + init_completion(&adc->completion); + + ret =3D devm_mutex_init(dev, &adc->lock); + if (ret) + return ret; + + regs =3D devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return dev_err_probe(dev, PTR_ERR(regs), + "failed to map registers\n"); + + adc->regmap =3D devm_regmap_init_mmio(dev, regs, &ma35d1_adc_regmap_confi= g); + if (IS_ERR(adc->regmap)) + return dev_err_probe(dev, PTR_ERR(adc->regmap), + "failed to initialize regmap\n"); + + adc->clk =3D devm_clk_get_enabled(dev, NULL); + if (IS_ERR(adc->clk)) + return dev_err_probe(dev, PTR_ERR(adc->clk), + "failed to get and enable ADC clock\n"); + + adc->rst =3D devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(adc->rst)) + return dev_err_probe(dev, PTR_ERR(adc->rst), + "failed to get reset control\n"); + + ret =3D ma35d1_adc_reset(adc); + if (ret) + return dev_err_probe(dev, ret, "failed to reset ADC\n"); + + ret =3D ma35d1_adc_init_vref(adc, dev); + if (ret) + return ret; + + indio_dev->name =3D "ma35d1-eadc"; + indio_dev->modes =3D INDIO_DIRECT_MODE; + indio_dev->info =3D &ma35d1_adc_info; + + ret =3D ma35d1_adc_parse_channels(indio_dev, dev); + if (ret) + return ret; + + ret =3D ma35d1_adc_hw_init(adc); + if (ret) + return dev_err_probe(dev, ret, "failed to initialize ADC\n"); + + ret =3D devm_add_action_or_reset(dev, ma35d1_adc_hw_disable_action, adc); + if (ret) + return ret; + + irq =3D platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + adc->irq =3D irq; + ret =3D devm_request_irq(dev, irq, ma35d1_adc_isr, 0, dev_name(dev), + indio_dev); + if (ret) + return ret; + + ret =3D devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + ma35d1_adc_trigger_handler, + &ma35d1_adc_buffer_ops); + if (ret) + return ret; + + platform_set_drvdata(pdev, indio_dev); + + return devm_iio_device_register(dev, indio_dev); +} + +static int ma35d1_adc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev =3D dev_get_drvdata(dev); + struct ma35d1_adc *adc =3D iio_priv(indio_dev); + int ret; + + guard(mutex)(&adc->lock); + + ret =3D ma35d1_adc_disable_irq(adc); + if (ret) + return ret; + + synchronize_irq(adc->irq); + + ret =3D ma35d1_adc_hw_disable(adc); + if (ret) + return ret; + + clk_disable_unprepare(adc->clk); + adc->suspended =3D true; + + return 0; +} + +static int ma35d1_adc_resume(struct device *dev) +{ + struct iio_dev *indio_dev =3D dev_get_drvdata(dev); + struct ma35d1_adc *adc =3D iio_priv(indio_dev); + int ret; + + guard(mutex)(&adc->lock); + + ret =3D clk_prepare_enable(adc->clk); + if (ret) + return ret; + + ret =3D ma35d1_adc_hw_init(adc); + if (ret) { + clk_disable_unprepare(adc->clk); + return ret; + } + + adc->suspended =3D false; + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ma35d1_adc_pm_ops, + ma35d1_adc_suspend, ma35d1_adc_resume); + +static const struct of_device_id ma35d1_adc_of_match[] =3D { + { .compatible =3D "nuvoton,ma35d1-eadc" }, + { } +}; +MODULE_DEVICE_TABLE(of, ma35d1_adc_of_match); + +static struct platform_driver ma35d1_adc_driver =3D { + .probe =3D ma35d1_adc_probe, + .driver =3D { + .name =3D "ma35d1-eadc", + .of_match_table =3D ma35d1_adc_of_match, + .pm =3D pm_sleep_ptr(&ma35d1_adc_pm_ops), + }, +}; +module_platform_driver(ma35d1_adc_driver); + +MODULE_AUTHOR("Chi-Wen Weng "); +MODULE_DESCRIPTION("Nuvoton MA35D1 EADC driver"); +MODULE_LICENSE("GPL"); --=20 2.25.1