* [PATCH 2/3] irqchip/gic-v3: Add Renesas R-Car Gen4 erratum workaround
From: Marek Vasut @ 2026-06-17 2:59 UTC (permalink / raw)
To: linux-pci
Cc: Marek Vasut, Yoshihiro Shimoda, Krzysztof Wilczyński,
Bjorn Helgaas, Catalin Marinas, Conor Dooley, Geert Uytterhoeven,
Krzysztof Kozlowski, Lorenzo Pieralisi, Manivannan Sadhasivam,
Marc Zyngier, Rob Herring, devicetree, linux-arm-kernel,
linux-doc, linux-kernel, linux-renesas-soc
In-Reply-To: <20260617030008.154449-1-marek.vasut+renesas@mailbox.org>
Renesas R-Car S4/V4H/V4M GIC600 integration has address width for AXI
or APB interface configured to 32 bit, it can therefore access only
the first 4 GiB of physical address space. This information comes from
R-Car V4H Interface Specification sheet, there is currently no technical
update number assigned to this limitation. Further input from hardware
engineer indicates that this limitation also applies to R-Car S4 and V4M.
Name the limitation GEN4GICITS1, and add a driver quirk to mitigate this
limitation.
Note that the 0x0201743b GIC600 ID is not Renesas-specific, it is
common for many ARM GICv3 implementations. Therefore, add an extra
of_machine_is_compatible() check.
The GIC600 implementation in R-Car S4/V4H/V4M is r1p6.
Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
Signed-off-by: Marek Vasut <marek.vasut+renesas@mailbox.org>
---
NOTE: This would not be possible without prior work from Shimoda-san
https://lore.kernel.org/all/20240214052050.1966439-1-yoshihiro.shimoda.uh@renesas.com/
---
Cc: "Krzysztof Wilczyński" <kwilczynski@kernel.org>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Conor Dooley <conor+dt@kernel.org>
Cc: Geert Uytterhoeven <geert+renesas@glider.be>
Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
Cc: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Manivannan Sadhasivam <mani@kernel.org>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Rob Herring <robh@kernel.org>
Cc: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
Cc: devicetree@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-doc@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-pci@vger.kernel.org
Cc: linux-renesas-soc@vger.kernel.org
---
Documentation/arch/arm64/silicon-errata.rst | 1 +
arch/arm64/Kconfig | 9 +++++++++
drivers/irqchip/irq-gic-v3-its.c | 20 ++++++++++++++++++++
3 files changed, 30 insertions(+)
diff --git a/Documentation/arch/arm64/silicon-errata.rst b/Documentation/arch/arm64/silicon-errata.rst
index 014aa1c215a16..b0c68b64f5ac2 100644
--- a/Documentation/arch/arm64/silicon-errata.rst
+++ b/Documentation/arch/arm64/silicon-errata.rst
@@ -352,6 +352,7 @@ stable kernels.
+----------------+-----------------+-----------------+-----------------------------+
| Qualcomm Tech. | Kryo4xx Gold | N/A | ARM64_ERRATUM_1286807 |
+----------------+-----------------+-----------------+-----------------------------+
+| Renesas | S4/V4H/V4M | N/A | RENESAS_ERRATUM_GEN4GICITS1 |
+----------------+-----------------+-----------------+-----------------------------+
| Rockchip | RK3588 | #3588001 | ROCKCHIP_ERRATUM_3588001 |
+----------------+-----------------+-----------------+-----------------------------+
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index b3afe0688919b..b9e17ce475e61 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -1382,6 +1382,15 @@ config NVIDIA_CARMEL_CNP_ERRATUM
If unsure, say Y.
+config RENESAS_ERRATUM_GEN4GICITS1
+ bool "Renesas R-Car Gen4: GIC600 can not access physical addresses above 4 GiB"
+ default y
+ help
+ The Renesas R-Car Gen4 S4/V4H/V4M GIC600 SoC integrations have AXI
+ addressing limited to the first 32-bit of physical address space.
+
+ If unsure, say Y.
+
config ROCKCHIP_ERRATUM_3568002
bool "Rockchip 3568002: GIC600 can not access physical addresses higher than 4GB"
default y
diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c
index b57d81ad33a0a..ec3756f29cf1a 100644
--- a/drivers/irqchip/irq-gic-v3-its.c
+++ b/drivers/irqchip/irq-gic-v3-its.c
@@ -4901,6 +4901,18 @@ static bool __maybe_unused its_enable_rk3568002(void *data)
return true;
}
+static bool __maybe_unused its_enable_renesas_gen4(void *data)
+{
+ if (!of_machine_is_compatible("renesas,r8a779f0") &&
+ !of_machine_is_compatible("renesas,r8a779g0") &&
+ !of_machine_is_compatible("renesas,r8a779h0"))
+ return false;
+
+ gfp_flags_quirk |= GFP_DMA32;
+
+ return true;
+}
+
static const struct gic_quirk its_quirks[] = {
#ifdef CONFIG_CAVIUM_ERRATUM_22375
{
@@ -4975,6 +4987,14 @@ static const struct gic_quirk its_quirks[] = {
.mask = 0xffffffff,
.init = its_enable_rk3568002,
},
+#endif
+#ifdef CONFIG_RENESAS_ERRATUM_GEN4GICITS1
+ {
+ .desc = "ITS: Renesas R-Car Gen4 GIC600 32-bit limit",
+ .iidr = 0x0201743b,
+ .mask = 0xffffffff,
+ .init = its_enable_renesas_gen4,
+ },
#endif
{
}
--
2.53.0
^ permalink raw reply related
* [PATCH 1/3] PCI: rcar-gen4: Configure AXIINTC if iMSI-RX not used
From: Marek Vasut @ 2026-06-17 2:59 UTC (permalink / raw)
To: linux-pci
Cc: Marek Vasut, Yoshihiro Shimoda, Krzysztof Wilczyński,
Bjorn Helgaas, Catalin Marinas, Conor Dooley, Geert Uytterhoeven,
Krzysztof Kozlowski, Lorenzo Pieralisi, Manivannan Sadhasivam,
Marc Zyngier, Rob Herring, devicetree, linux-arm-kernel,
linux-doc, linux-kernel, linux-renesas-soc
In case MSI are enabled, but DWC built-in iMSI-RX is not in use, the
MSI are handled via GIC ITS. Configure all controller MSI registers
fully.
Set or clear MSI capability register MSICAP0 MSI enable MSIE bit and
PCIe Interrupt Status 0 Enable register PCIEINTSTS0EN MSI interrupt
enable MSI_CTRL_INT bit according to MSI enable state, set both bits
if MSI are enabled, clear both bits if MSI are disabled.
If MSI are disabled, or MSI are enabled and iMSI-RX is used, then
deconfigure AXIINTCADDR and AXIINTCCONT to 0, which disables any
pass through of MSI TLPs onto the AXI bus and then further into
GIC ITS translation registers.
If MSI are enabled and iMSI-RX is not used, the configure AXIINTCADDR
with target address of GIC ITS translation registers, and configure
AXIINTCCONT to enable MSI TLP pass through onto AXI bus and into the
GIC ITS. This specific configuration allows handling of MSI via the
GIC ITS instead of integrated iMSI-RX.
Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
Signed-off-by: Marek Vasut <marek.vasut+renesas@mailbox.org>
---
NOTE: This would not be possible without prior work from Shimoda-san
---
Cc: "Krzysztof Wilczyński" <kwilczynski@kernel.org>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Conor Dooley <conor+dt@kernel.org>
Cc: Geert Uytterhoeven <geert+renesas@glider.be>
Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
Cc: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Manivannan Sadhasivam <mani@kernel.org>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Rob Herring <robh@kernel.org>
Cc: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
Cc: devicetree@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-doc@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-pci@vger.kernel.org
Cc: linux-renesas-soc@vger.kernel.org
---
drivers/pci/controller/dwc/pcie-rcar-gen4.c | 53 +++++++++++++++++++--
1 file changed, 48 insertions(+), 5 deletions(-)
diff --git a/drivers/pci/controller/dwc/pcie-rcar-gen4.c b/drivers/pci/controller/dwc/pcie-rcar-gen4.c
index 485cfa8bd9692..ba6e3bedd6d0a 100644
--- a/drivers/pci/controller/dwc/pcie-rcar-gen4.c
+++ b/drivers/pci/controller/dwc/pcie-rcar-gen4.c
@@ -31,6 +31,10 @@
#define DEVICE_TYPE_RC BIT(4)
#define BIFUR_MOD_SET_ON BIT(0)
+/* MSI Capability */
+#define MSICAP0 0x0050
+#define MSICAP0_MSIE BIT(16)
+
/* PCIe Interrupt Status 0 */
#define PCIEINTSTS0 0x0084
@@ -55,6 +59,16 @@
#define APP_HOLD_PHY_RST BIT(16)
#define APP_LTSSM_ENABLE BIT(0)
+/* INTC address */
+#define AXIINTCADDR 0x0a00
+/* GITS GIC ITS translation register */
+#define AXIINTCADDR_VAL 0xf1050000
+
+/* INTC control & mask */
+#define AXIINTCCONT 0x0a04
+#define INTC_EN BIT(31)
+#define INTC_MASK GENMASK(11, 2)
+
/* PCIe Power Management Control */
#define PCIEPWRMNGCTRL 0x0070
#define APP_CLK_REQ_N BIT(11)
@@ -305,6 +319,39 @@ static struct rcar_gen4_pcie *rcar_gen4_pcie_alloc(struct platform_device *pdev)
return rcar;
}
+static void rcar_gen4_pcie_host_msi_init(struct dw_pcie_rp *pp)
+{
+ struct dw_pcie *dw = to_dw_pcie_from_pp(pp);
+ struct rcar_gen4_pcie *rcar = to_rcar_gen4_pcie(dw);
+ u32 val;
+
+ /* Make sure MSICAP0 MSIE is configured. */
+ val = dw_pcie_readl_dbi(dw, MSICAP0);
+ if (pci_msi_enabled())
+ val |= MSICAP0_MSIE;
+ else
+ val &= ~MSICAP0_MSIE;
+ dw_pcie_writel_dbi(dw, MSICAP0, val);
+
+ if (!pci_msi_enabled() || pp->use_imsi_rx) {
+ /* Clear AXIINTC mapping. */
+ writel(0, rcar->base + AXIINTCADDR);
+ writel(0, rcar->base + AXIINTCCONT);
+ } else {
+ /* Point AXIINTC to GIC ITS and enable. */
+ writel(AXIINTCADDR_VAL, rcar->base + AXIINTCADDR);
+ writel(INTC_EN | INTC_MASK, rcar->base + AXIINTCCONT);
+ }
+
+ /* Configure MSI interrupt signal */
+ val = readl(rcar->base + PCIEINTSTS0EN);
+ if (pci_msi_enabled())
+ val |= MSI_CTRL_INT;
+ else
+ val &= ~MSI_CTRL_INT;
+ writel(val, rcar->base + PCIEINTSTS0EN);
+}
+
static int rcar_gen4_pcie_enable_device(struct pci_host_bridge *bridge,
struct pci_dev *dev)
{
@@ -359,7 +406,6 @@ static int rcar_gen4_pcie_host_init(struct dw_pcie_rp *pp)
struct dw_pcie *dw = to_dw_pcie_from_pp(pp);
struct rcar_gen4_pcie *rcar = to_rcar_gen4_pcie(dw);
int ret;
- u32 val;
if (pp->bridge)
pp->bridge->enable_device = rcar_gen4_pcie_enable_device;
@@ -379,10 +425,7 @@ static int rcar_gen4_pcie_host_init(struct dw_pcie_rp *pp)
dw_pcie_writel_dbi2(dw, PCI_BASE_ADDRESS_0, 0x0);
dw_pcie_writel_dbi2(dw, PCI_BASE_ADDRESS_1, 0x0);
- /* Enable MSI interrupt signal */
- val = readl(rcar->base + PCIEINTSTS0EN);
- val |= MSI_CTRL_INT;
- writel(val, rcar->base + PCIEINTSTS0EN);
+ rcar_gen4_pcie_host_msi_init(pp);
msleep(PCIE_T_PVPERL_MS); /* pe_rst requires 100msec delay */
--
2.53.0
^ permalink raw reply related
* [PATCH v4 2/2] pwm: Add Nuvoton MA35D1 PWM controller support
From: Chi-Wen Weng @ 2026-06-17 2:59 UTC (permalink / raw)
To: ukleinek, robh, krzk+dt, conor+dt
Cc: linux-arm-kernel, linux-pwm, devicetree, linux-kernel, cwweng,
cwweng.linux, Trevor Gamblin
In-Reply-To: <20260617025925.2539334-1-cwweng.linux@gmail.com>
From: Chi-Wen Weng <cwweng@nuvoton.com>
Add a PWM framework driver for the Nuvoton MA35D1 PWM controller.
The MA35D1 PWM controller provides 6 PWM channels. The hardware supports
up, down and up-down counter types, auto-reload and one-shot modes, and
independent and complementary output modes. This driver configures all
channels to up-counting mode, auto-reload mode and independent output mode.
The waveform generator is configured to drive the output high at the zero
point and low at the compare-up point. In up-counting mode the counter
counts from 0 to PERIOD inclusive, so the PWM period is PERIOD + 1 cycles.
With the selected waveform actions, CMPDAT = 0 generates 0% duty cycle and
CMPDAT > PERIOD generates 100% duty cycle. Limit PERIOD to 0xfffe so that
CMPDAT = 0xffff can be used for the full-duty case.
PERIOD and CMPDAT updates are buffered by the hardware and take effect at
the end of the current period because IMMLDENn is left disabled. When the
PWM output is disabled, POENn is cleared and the output pin is put into
tri-state.
Reviewed-by: Trevor Gamblin <tgamblin@baylibre.com>
Signed-off-by: Chi-Wen Weng <cwweng@nuvoton.com>
---
drivers/pwm/Kconfig | 9 +
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-ma35d1.c | 344 +++++++++++++++++++++++++++++++++++++++
3 files changed, 354 insertions(+)
create mode 100644 drivers/pwm/pwm-ma35d1.c
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index e8886a9b64d9..355131e6efac 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -463,6 +463,15 @@ config PWM_LPSS_PLATFORM
To compile this driver as a module, choose M here: the module
will be called pwm-lpss-platform.
+config PWM_MA35D1
+ tristate "Nuvoton MA35D1 PWM support"
+ depends on ARCH_MA35 || COMPILE_TEST
+ help
+ Generic PWM framework driver for Nuvoton MA35D1.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-ma35d1.
+
config PWM_MAX7360
tristate "MAX7360 PWMs"
depends on MFD_MAX7360
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 5630a521a7cf..7ad761ea27d1 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -40,6 +40,7 @@ obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o
obj-$(CONFIG_PWM_LPSS) += pwm-lpss.o
obj-$(CONFIG_PWM_LPSS_PCI) += pwm-lpss-pci.o
obj-$(CONFIG_PWM_LPSS_PLATFORM) += pwm-lpss-platform.o
+obj-$(CONFIG_PWM_MA35D1) += pwm-ma35d1.o
obj-$(CONFIG_PWM_MAX7360) += pwm-max7360.o
obj-$(CONFIG_PWM_MC33XS2410) += pwm-mc33xs2410.o
obj-$(CONFIG_PWM_MEDIATEK) += pwm-mediatek.o
diff --git a/drivers/pwm/pwm-ma35d1.c b/drivers/pwm/pwm-ma35d1.c
new file mode 100644
index 000000000000..c07eedeca035
--- /dev/null
+++ b/drivers/pwm/pwm-ma35d1.c
@@ -0,0 +1,344 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for the Nuvoton MA35D1 PWM controller
+ *
+ * Copyright (C) 2026 Nuvoton Corporation
+ * Chi-Wen Weng <cwweng@nuvoton.com>
+ *
+ * Reference Manual:
+ * https://www.nuvoton.com.cn/resource-download.jsp?tp_GUID=DA05-MA35D16
+ *
+ * Limitations:
+ * - The hardware supports 6 PWM channels.
+ * - The hardware supports up, down and up-down counter types. This driver
+ * configures all channels to up-counting mode.
+ * - The hardware supports auto-reload and one-shot counter modes. This driver
+ * configures all channels to auto-reload mode.
+ * - The hardware supports independent and complementary output modes. This
+ * driver configures all channels to independent output mode.
+ * - The hardware supports programmable waveform actions at zero, period and
+ * compare points. This driver uses zero point high and compare-up point low
+ * actions for normal PWM output.
+ * - In up-counting mode, the counter counts from 0 to PERIOD inclusive. With
+ * zero point high and compare-up point low actions, CMPDAT = 0 produces 0%
+ * duty and CMPDAT > PERIOD produces 100% duty.
+ * - The driver limits PERIOD to 0xfffe so that CMPDAT can be set greater than
+ * PERIOD to generate a 100% duty cycle.
+ * - Period and duty cycle changes are buffered by hardware and take effect at
+ * the end of the current period because IMMLDENn is left disabled.
+ * - Polarity changes are applied directly and may cause a transient output
+ * change if the PWM output is running.
+ * - When disabled, the output pin is put in tri-state by clearing POENn.
+ */
+
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/math64.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+
+#define MA35D1_REG_PWM_CTL0 0x00
+#define MA35D1_REG_PWM_CTL1 0x04
+#define MA35D1_REG_PWM_CNTEN 0x20
+#define MA35D1_REG_PWM_PERIOD(ch) (0x30 + 4 * (ch))
+#define MA35D1_REG_PWM_CMPDAT(ch) (0x50 + 4 * (ch))
+#define MA35D1_REG_PWM_WGCTL0 0xb0
+#define MA35D1_REG_PWM_WGCTL1 0xb4
+#define MA35D1_REG_PWM_POLCTL 0xd4
+#define MA35D1_REG_PWM_POEN 0xd8
+
+#define MA35D1_PWM_CTL1_CNTMODE_MASK(ch) BIT(16 + (ch))
+#define MA35D1_PWM_CTL1_OUTMODE_MASK(ch) BIT(24 + ((ch) / 2))
+
+#define MA35D1_PWM_WGCTL_ACTION_MASK 0x3
+#define MA35D1_PWM_WGCTL_ACTION_LOW 1
+#define MA35D1_PWM_WGCTL_ACTION_HIGH 2
+
+#define MA35D1_PWM_WGCTL_ZERO_HIGH(ch) \
+ (MA35D1_PWM_WGCTL_ACTION_HIGH << (2 * (ch)))
+#define MA35D1_PWM_WGCTL_CMP_UP_LOW(ch) \
+ (MA35D1_PWM_WGCTL_ACTION_LOW << (2 * (ch)))
+
+#define MA35D1_PWM_CNTEN_EN(ch) BIT(ch)
+#define MA35D1_PWM_POEN_EN(ch) BIT(ch)
+#define MA35D1_PWM_POLCTL_INV(ch) BIT(ch)
+
+#define MA35D1_PWM_MAX_CMPDAT 0xffff
+#define MA35D1_PWM_MAX_PERIOD 0xfffe
+#define MA35D1_PWM_MAX_PERIOD_CYCLES (MA35D1_PWM_MAX_PERIOD + 1)
+#define MA35D1_PWM_NUM_CHANNELS 6
+
+struct nuvoton_pwm {
+ void __iomem *base;
+ unsigned long clkrate;
+};
+
+static inline struct nuvoton_pwm *nuvoton_pwm_from_chip(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static inline u32 nuvoton_pwm_ctl1_cnttype_mask(unsigned int ch)
+{
+ return MA35D1_PWM_WGCTL_ACTION_MASK << (2 * ch);
+}
+
+static inline u32 nuvoton_pwm_wgctl_zero_mask(unsigned int ch)
+{
+ return MA35D1_PWM_WGCTL_ACTION_MASK << (2 * ch);
+}
+
+static inline u32 nuvoton_pwm_wgctl_period_mask(unsigned int ch)
+{
+ return MA35D1_PWM_WGCTL_ACTION_MASK << (16 + 2 * ch);
+}
+
+static inline u32 nuvoton_pwm_wgctl_cmp_up_mask(unsigned int ch)
+{
+ return MA35D1_PWM_WGCTL_ACTION_MASK << (2 * ch);
+}
+
+static inline u32 nuvoton_pwm_wgctl_cmp_down_mask(unsigned int ch)
+{
+ return MA35D1_PWM_WGCTL_ACTION_MASK << (16 + 2 * ch);
+}
+
+static inline u32 nuvoton_pwm_readl(struct nuvoton_pwm *nvtpwm,
+ unsigned int offset)
+{
+ return readl(nvtpwm->base + offset);
+}
+
+static inline void nuvoton_pwm_writel(struct nuvoton_pwm *nvtpwm,
+ unsigned int offset, u32 value)
+{
+ writel(value, nvtpwm->base + offset);
+}
+
+static inline void nuvoton_pwm_rmw(struct nuvoton_pwm *nvtpwm,
+ unsigned int offset, u32 mask, u32 value)
+{
+ u32 reg;
+
+ reg = nuvoton_pwm_readl(nvtpwm, offset);
+ reg &= ~mask;
+ reg |= value & mask;
+ nuvoton_pwm_writel(nvtpwm, offset, reg);
+}
+
+static void nuvoton_pwm_init(struct nuvoton_pwm *nvtpwm)
+{
+ u32 ctl1_mask = 0;
+ u32 wgctl0_mask = 0;
+ u32 wgctl0_val = 0;
+ u32 wgctl1_mask = 0;
+ u32 wgctl1_val = 0;
+ int ch;
+
+ for (ch = 0; ch < MA35D1_PWM_NUM_CHANNELS; ch++) {
+ /* CNTTYPEn = 00: up counter type */
+ ctl1_mask |= nuvoton_pwm_ctl1_cnttype_mask(ch);
+
+ /* CNTMODEn = 0: auto-reload mode */
+ ctl1_mask |= MA35D1_PWM_CTL1_CNTMODE_MASK(ch);
+
+ /* ZPCTLn = 10: output high at zero point */
+ wgctl0_mask |= nuvoton_pwm_wgctl_zero_mask(ch);
+ wgctl0_val |= MA35D1_PWM_WGCTL_ZERO_HIGH(ch);
+
+ /* PRDPCTLn = 00: do nothing at period point */
+ wgctl0_mask |= nuvoton_pwm_wgctl_period_mask(ch);
+
+ /* CMPUCTLn = 01: output low at compare up point */
+ wgctl1_mask |= nuvoton_pwm_wgctl_cmp_up_mask(ch);
+ wgctl1_val |= MA35D1_PWM_WGCTL_CMP_UP_LOW(ch);
+
+ /* CMPDCTLn = 00: do nothing at compare down point */
+ wgctl1_mask |= nuvoton_pwm_wgctl_cmp_down_mask(ch);
+ }
+
+ for (ch = 0; ch < MA35D1_PWM_NUM_CHANNELS; ch += 2) {
+ /* OUTMODEn = 0: independent mode */
+ ctl1_mask |= MA35D1_PWM_CTL1_OUTMODE_MASK(ch);
+ }
+
+ nuvoton_pwm_rmw(nvtpwm, MA35D1_REG_PWM_CTL1, ctl1_mask, 0);
+ nuvoton_pwm_rmw(nvtpwm, MA35D1_REG_PWM_WGCTL0,
+ wgctl0_mask, wgctl0_val);
+ nuvoton_pwm_rmw(nvtpwm, MA35D1_REG_PWM_WGCTL1,
+ wgctl1_mask, wgctl1_val);
+}
+
+static int nuvoton_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct nuvoton_pwm *nvtpwm = nuvoton_pwm_from_chip(chip);
+ u32 ch = pwm->hwpwm;
+ u64 duty_cycles, period_cycles;
+ u32 cmpdat, period;
+
+ if (!state->enabled) {
+ nuvoton_pwm_rmw(nvtpwm, MA35D1_REG_PWM_POEN,
+ MA35D1_PWM_POEN_EN(ch), 0);
+ nuvoton_pwm_rmw(nvtpwm, MA35D1_REG_PWM_CNTEN,
+ MA35D1_PWM_CNTEN_EN(ch), 0);
+
+ return 0;
+ }
+
+ period_cycles = mul_u64_u64_div_u64(nvtpwm->clkrate,
+ state->period,
+ NSEC_PER_SEC);
+ if (!period_cycles)
+ return -EINVAL;
+
+ if (period_cycles > MA35D1_PWM_MAX_PERIOD_CYCLES)
+ period_cycles = MA35D1_PWM_MAX_PERIOD_CYCLES;
+
+ duty_cycles = mul_u64_u64_div_u64(nvtpwm->clkrate,
+ state->duty_cycle,
+ NSEC_PER_SEC);
+ if (duty_cycles > period_cycles)
+ duty_cycles = period_cycles;
+
+ if (state->polarity == PWM_POLARITY_NORMAL)
+ nuvoton_pwm_rmw(nvtpwm, MA35D1_REG_PWM_POLCTL,
+ MA35D1_PWM_POLCTL_INV(ch), 0);
+ else
+ nuvoton_pwm_rmw(nvtpwm, MA35D1_REG_PWM_POLCTL,
+ MA35D1_PWM_POLCTL_INV(ch),
+ MA35D1_PWM_POLCTL_INV(ch));
+
+ /*
+ * In up-counting mode the counter counts from 0 to PERIOD inclusive.
+ * With zero point high and compare-up point low actions:
+ * - CMPDAT = 0 produces 0% duty.
+ * - CMPDAT > PERIOD produces 100% duty.
+ * PERIOD is limited to 0xfffe, so duty_cycles can be written directly
+ * to CMPDAT and still fit in the 16-bit compare field for 100% duty.
+ */
+ period = period_cycles - 1;
+ cmpdat = duty_cycles;
+
+ nuvoton_pwm_writel(nvtpwm, MA35D1_REG_PWM_PERIOD(ch), period);
+ nuvoton_pwm_writel(nvtpwm, MA35D1_REG_PWM_CMPDAT(ch), cmpdat);
+
+ nuvoton_pwm_rmw(nvtpwm, MA35D1_REG_PWM_CNTEN,
+ MA35D1_PWM_CNTEN_EN(ch), MA35D1_PWM_CNTEN_EN(ch));
+ nuvoton_pwm_rmw(nvtpwm, MA35D1_REG_PWM_POEN,
+ MA35D1_PWM_POEN_EN(ch), MA35D1_PWM_POEN_EN(ch));
+
+ return 0;
+}
+
+static int nuvoton_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct nuvoton_pwm *nvtpwm = nuvoton_pwm_from_chip(chip);
+ u32 ch = pwm->hwpwm;
+ u32 cmpdat, cnten, period, poen, polctl;
+ u64 duty_cycles, period_cycles;
+
+ cnten = nuvoton_pwm_readl(nvtpwm, MA35D1_REG_PWM_CNTEN);
+ poen = nuvoton_pwm_readl(nvtpwm, MA35D1_REG_PWM_POEN);
+ polctl = nuvoton_pwm_readl(nvtpwm, MA35D1_REG_PWM_POLCTL);
+ period = nuvoton_pwm_readl(nvtpwm, MA35D1_REG_PWM_PERIOD(ch)) &
+ MA35D1_PWM_MAX_CMPDAT;
+ cmpdat = nuvoton_pwm_readl(nvtpwm, MA35D1_REG_PWM_CMPDAT(ch)) &
+ MA35D1_PWM_MAX_CMPDAT;
+
+ period_cycles = period + 1;
+ if (cmpdat > period)
+ duty_cycles = period_cycles;
+ else
+ duty_cycles = cmpdat;
+
+ state->enabled = (cnten & MA35D1_PWM_CNTEN_EN(ch)) &&
+ (poen & MA35D1_PWM_POEN_EN(ch));
+ state->polarity = (polctl & MA35D1_PWM_POLCTL_INV(ch)) ?
+ PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL;
+ state->period = DIV64_U64_ROUND_UP(period_cycles * NSEC_PER_SEC,
+ nvtpwm->clkrate);
+ state->duty_cycle = DIV64_U64_ROUND_UP(duty_cycles * NSEC_PER_SEC,
+ nvtpwm->clkrate);
+
+ return 0;
+}
+
+static const struct pwm_ops nuvoton_pwm_ops = {
+ .apply = nuvoton_pwm_apply,
+ .get_state = nuvoton_pwm_get_state,
+};
+
+static int nuvoton_pwm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pwm_chip *chip;
+ struct nuvoton_pwm *nvtpwm;
+ struct clk *clk;
+ int ret;
+
+ chip = devm_pwmchip_alloc(dev, MA35D1_PWM_NUM_CHANNELS,
+ sizeof(*nvtpwm));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+
+ nvtpwm = nuvoton_pwm_from_chip(chip);
+
+ nvtpwm->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(nvtpwm->base))
+ return PTR_ERR(nvtpwm->base);
+
+ clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk),
+ "Unable to get the clock\n");
+
+ ret = devm_clk_rate_exclusive_get(dev, clk);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Unable to get exclusive clock rate\n");
+
+ nvtpwm->clkrate = clk_get_rate(clk);
+ if (!nvtpwm->clkrate)
+ return dev_err_probe(dev, -EINVAL,
+ "PWM clock rate is zero\n");
+
+ if (nvtpwm->clkrate > NSEC_PER_SEC)
+ return dev_err_probe(dev, -EINVAL,
+ "PWM clock out of range (%lu)\n",
+ nvtpwm->clkrate);
+
+ nuvoton_pwm_init(nvtpwm);
+
+ chip->ops = &nuvoton_pwm_ops;
+ chip->atomic = true;
+
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret)
+ return dev_err_probe(dev, ret, "Unable to add PWM chip\n");
+
+ return 0;
+}
+
+static const struct of_device_id nuvoton_pwm_of_match[] = {
+ { .compatible = "nuvoton,ma35d1-pwm" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, nuvoton_pwm_of_match);
+
+static struct platform_driver nuvoton_pwm_driver = {
+ .probe = nuvoton_pwm_probe,
+ .driver = {
+ .name = "nuvoton-pwm",
+ .of_match_table = nuvoton_pwm_of_match,
+ },
+};
+module_platform_driver(nuvoton_pwm_driver);
+
+MODULE_AUTHOR("Chi-Wen Weng <cwweng@nuvoton.com>");
+MODULE_DESCRIPTION("Nuvoton MA35D1 PWM driver");
+MODULE_LICENSE("GPL");
--
2.25.1
^ permalink raw reply related
* [PATCH v4 1/2] dt-bindings: pwm: Add Nuvoton MA35D1 PWM controller
From: Chi-Wen Weng @ 2026-06-17 2:59 UTC (permalink / raw)
To: ukleinek, robh, krzk+dt, conor+dt
Cc: linux-arm-kernel, linux-pwm, devicetree, linux-kernel, cwweng,
cwweng.linux, Krzysztof Kozlowski
In-Reply-To: <20260617025925.2539334-1-cwweng.linux@gmail.com>
From: Chi-Wen Weng <cwweng@nuvoton.com>
Add device tree binding for the Nuvoton MA35D1 PWM controller.
The MA35D1 PWM controller provides 6 PWM channels and uses one register
region and one functional clock. The binding uses the standard PWM binding
with three PWM cells.
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Signed-off-by: Chi-Wen Weng <cwweng@nuvoton.com>
---
.../bindings/pwm/nuvoton,ma35d1-pwm.yaml | 45 +++++++++++++++++++
1 file changed, 45 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pwm/nuvoton,ma35d1-pwm.yaml
diff --git a/Documentation/devicetree/bindings/pwm/nuvoton,ma35d1-pwm.yaml b/Documentation/devicetree/bindings/pwm/nuvoton,ma35d1-pwm.yaml
new file mode 100644
index 000000000000..47a59bdd14d0
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/nuvoton,ma35d1-pwm.yaml
@@ -0,0 +1,45 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pwm/nuvoton,ma35d1-pwm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Nuvoton MA35D1 PWM controller
+
+maintainers:
+ - Chi-Wen Weng <cwweng@nuvoton.com>
+
+allOf:
+ - $ref: pwm.yaml#
+
+properties:
+ compatible:
+ enum:
+ - nuvoton,ma35d1-pwm
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ "#pwm-cells":
+ const: 3
+
+required:
+ - compatible
+ - reg
+ - clocks
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
+
+ pwm@40580000 {
+ compatible = "nuvoton,ma35d1-pwm";
+ reg = <0x40580000 0x400>;
+ clocks = <&clk EPWM0_GATE>;
+ #pwm-cells = <3>;
+ };
--
2.25.1
^ permalink raw reply related
* [PATCH v4 0/2] pwm: Add Nuvoton MA35D1 PWM controller support
From: Chi-Wen Weng @ 2026-06-17 2:59 UTC (permalink / raw)
To: ukleinek, robh, krzk+dt, conor+dt
Cc: linux-arm-kernel, linux-pwm, devicetree, linux-kernel, cwweng,
cwweng.linux
From: Chi-Wen Weng <cwweng@nuvoton.com>
This series adds support for the Nuvoton MA35D1 PWM controller.
The MA35D1 PWM controller provides 6 PWM channels. The hardware supports
several counter types and output modes. This driver configures the controller
to use up-counting mode, auto-reload mode and independent output mode. The
waveform generator is configured to drive the output high at the zero point
and low at the compare-up point.
For the up-counting mode used by this driver, the counter counts from 0 to
PERIOD inclusive. Therefore, the programmed period is PERIOD + 1 cycles. The
hardware can generate 0% duty cycle with CMPDAT = 0 and 100% duty cycle with
CMPDAT > PERIOD. To keep 100% duty cycle representable, the driver limits the
maximum PERIOD value to 0xfffe and reserves CMPDAT = 0xffff for the full-duty
case.
The hardware buffers PERIOD and CMPDAT updates when IMMLDENn is disabled. The
driver keeps IMMLDENn disabled, so period and duty cycle updates take effect
at the end of the current period. Polarity and waveform-control changes are
applied directly and may cause transient output changes if the PWM is running.
When the PWM output is disabled by clearing POENn, the output pin is put into
tri-state according to the MA35D1 reference manual.
Changes in v4:
- Add a Limitations section to describe the hardware capabilities and driver
limitations.
- Add a link to the MA35D1 reference manual.
- Replace register address macros containing the base pointer with register
offset macros.
- Add readl/writel/rmw helper functions.
- Rename TOTAL_CHANNELS to NUM_CHANNELS.
- Use unsigned long for the cached clock rate.
- Use devm_clk_rate_exclusive_get().
- Configure polarity before enabling the counter and output.
- Add controller initialization for up-counting, auto-reload and independent
output mode.
- Configure the waveform generator for zero-point-high and compare-up-low
output.
- Fix the period conversion because the hardware period is PERIOD + 1 cycles.
- Limit the maximum PERIOD value to 0xfffe so that CMPDAT = 0xffff can be used
to generate 100% duty cycle.
- Use CNTEN and POEN bits to report the enabled state in .get_state().
- Disable the PWM by clearing POENn and CNTENn.
- Fix error message capitalization and trailing newlines.
- Fix coding style issues reported by review/checkpatch-style tools.
Changes in v3:
- Update nuvoton,ma35d1-pwm.yaml
- Add maintainers entry
- Increse "#pwm-cells" to 3
- Update ma35d1 pwm driver
- Make include header and macros definitions organized alphabetically
- Rename macros REG_PWM_XXXX to MA35D1_REG_PWM_XXXX
- Add macros for register address
v2 resend:
- Remove wrong 'Reviewed-by' tags.
Changes in v2:
- Update nuvoton,ma35d1-pwm.yaml
- Fix 'maxItems' of 'reg' to 1.
- Remove unused label
- Update ma35d1 pwm driver
- Remove MODULE_ALIAS()
- Add chip->atomic = true
Chi-Wen Weng (2):
dt-bindings: pwm: Add Nuvoton MA35D1 PWM controller
pwm: Add Nuvoton MA35D1 PWM controller support
.../bindings/pwm/nuvoton,ma35d1-pwm.yaml | 45 +++
drivers/pwm/Kconfig | 9 +
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-ma35d1.c | 344 ++++++++++++++++++
4 files changed, 399 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pwm/nuvoton,ma35d1-pwm.yaml
create mode 100644 drivers/pwm/pwm-ma35d1.c
--
2.25.1
^ permalink raw reply
* RE: [PATCH v32 4/5] i2c: ast2600: Add controller driver for AST2600 new register set
From: Ryan Chen @ 2026-06-17 2:44 UTC (permalink / raw)
To: sashiko-reviews@lists.linux.dev
Cc: conor+dt@kernel.org, devicetree@vger.kernel.org, robh@kernel.org
In-Reply-To: <20260611054624.25BC51F00893@smtp.kernel.org>
Hello,
> Subject: Re: [PATCH v32 4/5] i2c: ast2600: Add controller driver for AST2600
> new register set
>
> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to
> consider:
> - [High] The driver fails to send a NAK on the last byte of an intermediate read
> message, causing the slave to hold SDA low and block the subsequent Repeated
> Start.
> - [High] The interrupt handler does not clear error status bits in the
> Write-1-to-Clear (W1C) ISR register, permanently wedging the controller after
> a single error.
> - [Medium] Bus recovery failure and other error interrupts are not enabled in
> the Interrupt Enable Register (IER), leading to needless blocking timeouts.
> --
>
> commit 01c0588191421a92b3d96b7bc246be1fe0958d9f
> Author: Ryan Chen <ryan_chen@aspeedtech.com>
>
> i2c: ast2600: Add controller driver for AST2600 new register set
>
> The AST2600 introduces a new I2C controller register layout, selectable at
> runtime via global control registers. Compared to the legacy layout used on
> AST2400/AST2500, the new layout separates controller (master) and target
> (slave) registers and adds support for packet-based transfers
>
> > diff --git a/drivers/i2c/busses/i2c-ast2600.c
> > b/drivers/i2c/busses/i2c-ast2600.c
> > new file mode 100644
> > --- /dev/null
> > +++ b/drivers/i2c/busses/i2c-ast2600.c
>
> [ ... ]
>
> > +static int ast2600_i2c_setup_buff_rx(u32 cmd, struct ast2600_i2c_bus
> > +*i2c_bus) {
> > + struct i2c_msg *msg = &i2c_bus->msgs[i2c_bus->msgs_index];
> > + int xfer_len = msg->len - i2c_bus->controller_xfer_cnt;
> > +
> > + cmd |= AST2600_I2CM_PKT_EN | AST2600_I2CM_RX_BUFF_EN |
> > +AST2600_I2CM_RX_CMD;
> > +
> > + if (cmd & AST2600_I2CM_START_CMD)
> > + cmd |= AST2600_I2CM_PKT_ADDR(msg->addr);
> > +
> > + if (msg->flags & I2C_M_RECV_LEN) {
> > + dev_dbg(i2c_bus->dev, "smbus read\n");
> > + xfer_len = 1;
> > + } else if (xfer_len > i2c_bus->buf_size) {
> > + xfer_len = i2c_bus->buf_size;
> > + } else if (i2c_bus->msgs_index + 1 == i2c_bus->msgs_count) {
> > + cmd |= CONTROLLER_TRIGGER_LAST_STOP;
> > + }
>
> [Severity: High]
> Does this logic omit the NAK for intermediate read messages?
>
> The CONTROLLER_TRIGGER_LAST_STOP flag (which applies the hardware NAK
> via
> AST2600_I2CM_RX_CMD_LAST) is strictly tied to the absolute final message in
> the array. If the transfer contains an intermediate read message, such as a read
> before a Repeated Start, the last byte will be ACKed.
>
> This will cause the slave to expect another byte and hold SDA low, leading to
> an arbitration loss or bus hang when attempting the Repeated Start.
Will update with following.
} else {
cmd |= AST2600_I2CM_RX_CMD_LAST;
if (i2c_bus->msgs_index + 1 == i2c_bus->msgs_count)
cmd |= AST2600_I2CM_STOP_CMD;
}
>
> [ ... ]
>
> > +static int ast2600_i2c_controller_irq(struct ast2600_i2c_bus
> > +*i2c_bus) {
> > + u32 sts = readl(i2c_bus->reg_base + AST2600_I2CM_ISR);
> > + u32 ctrl;
> > +
> > + sts &= ~AST2600_I2CM_SMBUS_ALERT;
> > +
> > + if (sts & AST2600_I2CM_BUS_RECOVER_FAIL) {
> > + writel(AST2600_I2CM_BUS_RECOVER_FAIL, i2c_bus->reg_base +
> AST2600_I2CM_ISR);
> > + ctrl = readl(i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL);
> > + writel(0, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL);
> > + writel(ctrl, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL);
> > + WRITE_ONCE(i2c_bus->msgs, NULL);
> > + i2c_bus->cmd_err = -EPROTO;
> > + complete(&i2c_bus->cmd_complete);
> > + return 1;
> > + }
> > +
> > + if (sts & AST2600_I2CM_BUS_RECOVER) {
> > + writel(AST2600_I2CM_BUS_RECOVER, i2c_bus->reg_base +
> AST2600_I2CM_ISR);
> > + WRITE_ONCE(i2c_bus->msgs, NULL);
> > + i2c_bus->cmd_err = 0;
> > + complete(&i2c_bus->cmd_complete);
> > + return 1;
> > + }
> > +
> > + i2c_bus->cmd_err = ast2600_i2c_irq_err_to_errno(sts);
> > + if (i2c_bus->cmd_err) {
> > + WRITE_ONCE(i2c_bus->msgs, NULL);
> > + writel(AST2600_I2CM_PKT_DONE, i2c_bus->reg_base +
> AST2600_I2CM_ISR);
> > + complete(&i2c_bus->cmd_complete);
> > + return 1;
> > + }
>
> [Severity: High]
> Will this permanently wedge the controller after a single error?
>
> When an error occurs, the error path explicitly writes only
> AST2600_I2CM_PKT_DONE to the ISR. W1C error bits like
> AST2600_I2CM_ARBIT_LOSS or AST2600_I2CM_TX_NAK are never written
> back and will remain set.
>
> Because the error path calls complete() successfully, the timeout fallback path
> that could reset the controller is bypassed. On the very next I2C transaction, a
> new PKT_DONE interrupt will cause the handler to read the stale error bits,
> instantly fail the transaction, and loop in this state.
>
> The same issue appears to exist in ast2600_i2c_controller_packet_irq() for bits
> like AST2600_I2CM_PKT_ERROR.
In packet mode the error status is not cleared bit-by-bit. Per the
datasheet description of I2CM14[16] (PKT_DONE), clearing PKT_DONE also
clears bit[6:0], bit[15:13] and bit[18:17] — which covers arbitration
loss, the timeout bits, ABNORMAL and PKT_ERROR. Writing back only
PKT_DONE therefore clears all pending error status as well; the
controller is not wedged. Writing the full ISR value back would be
functionally equivalent.
>
> [ ... ]
>
> > +static int ast2600_i2c_probe(struct platform_device *pdev) {
>
> [ ... ]
>
> > + ret = devm_request_irq(dev, i2c_bus->irq, ast2600_i2c_bus_irq, 0,
> > + dev_name(dev), i2c_bus);
> > + if (ret < 0) {
> > + ret = dev_err_probe(dev, ret, "Unable to request irq %d\n",
> > + i2c_bus->irq);
> > + goto err;
> > + }
> > +
> > + writel(AST2600_I2CM_PKT_DONE | AST2600_I2CM_BUS_RECOVER,
> > + i2c_bus->reg_base + AST2600_I2CM_IER);
> > +
> > + ret = i2c_add_adapter(&i2c_bus->adap);
>
> [Severity: Medium]
> Are bus recovery failure interrupts intentionally left disabled?
>
> The interrupt enable register is configured here without including the
> AST2600_I2CM_BUS_RECOVER_FAIL bit. If a bus recovery attempt fails, the
> hardware will assert the bit in the ISR, but the interrupt handler will never be
> triggered.
>
> This will cause the driver to needlessly block for the entire duration of
> wait_for_completion_timeout() instead of handling the failure immediately.
Per the datasheet description of I2CM10[16], in packet mode only bit 16
(PKT_DONE) and optionally bit 12 (SMBus Alert) need to be enabled; the
other enable bits have no effect. All error conditions (arbitration loss,
timeouts, ABNORMAL) are reported through PKT_ERROR folded into PKT_DONE,
so they are delivered without a separate enable and do not rely on the
xfer timeout. In addition, I2CM10[15] (Bus Recover Fail enable) is
RO/Reserved, so it cannot be enabled in the first place.
>
> --
> Sashiko AI review ·
> https://sashiko.dev/#/patchset/20260611-upstream_i2c-v32-0-b66eba921d01
> @aspeedtech.com?part=4
^ permalink raw reply
* Re: [PATCH 3/4] input: misc: Add Qualcomm SPMI PMIC haptics driver
From: Fenglin Wu @ 2026-06-17 2:31 UTC (permalink / raw)
To: Konrad Dybcio, linux-arm-msm, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lee Jones, Stephen Boyd,
Bjorn Andersson, Konrad Dybcio
Cc: David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa, kernel,
linux-input, devicetree, linux-kernel
In-Reply-To: <eb693705-c0c3-427b-a924-5aa907fd65bb@oss.qualcomm.com>
>> + ret = ptn_bulk_write(h, HAP_PTN_FIFO_DIN_0_REG, &data[i], 4);
>> + if (ret)
>> + return ret;
>> + }
>> +
>> + for (; i < len; i++) {
>> + ret = ptn_write(h, HAP_PTN_FIFO_DIN_1B_REG, (u8)data[i]);
>> + if (ret)
>> + return ret;
>> + }
> So if i'm reading this right, the first loop will always write
> 4*(len//4) bytes and the second one will be entered at most once,
> to write len rem 4 bytes.. should this be an if instead?
I should put a comment for clarification. Here’s some background: FIFO
data writing supports both 4-byte bulk writes using registers
[HAP_PTN_FIFO_DIN_0_REG ... HAP_PTN_FIFO_DIN_3_REG], and 1-byte writes
using the HAP_PTN_FIFO_DIN_1B_REG register. The 4-byte bulk write is
more efficient, especially for waveform which has several Kb data, and
it helps to reduce software latency when loading effects and reduce the
delay in triggering vibration. It also helps prevent the FIFO from
running dry during data refill in FIFO-empty interrupts. Typically, we
use 4-byte writes for the initial 4-byte aligned data, and 1-byte writes
for any trailing remainder.
So it still needs a 'for' loop here since the remainder could be more
than 1 byte.
>> +
>> + return 0;
>> +}
>> +
>> +/*
>> + * Configure the hardware FIFO memory boundary.
>> + * FIFO occupies addresses [0, fifo_len).
>> + */
>> +static int haptics_configure_fifo_mmap(struct qcom_haptics *h)
>> +{
>> + u32 fifo_len, fifo_units;
>> +
>> + /* Config all memory space for FIFO usage for now */
> What's the not-"for now" endgame for this?
The hardware supports more modes than the two currently supported in the
driver. One of these, called 'PAT_MEM' mode, also shares memory space
with FIFO mode. However, 'PAT_MEM' requires memory to be pre-reserved
and waveform data to be pre-loaded. The entire 8K bytes of memory can be
divided into partitions, and it is configurable, with FIFO mode always
using the first partition [0, fifo_len], where 'fifo_len' is set via the
'MMAP_FIFO_REG' register. 'PAT_MEM' mode plays waveform using data
preloaded in a memory bank defined by the registers
'PATX_MEM_START_ADDR_REG' and 'PATTERN_SPMI_PATX_LEN_REG' (they are not
defined in the driver). Since PAT_MEM is mainly intended for
hardware-triggered vibrations, such as a signal from a dedicated GPIO
triggering a short vibration with a preloaded waveform, and although it
also supports software triggers, I haven't found a suitable way to
support it well into the driver under input FF framework yet. So, I am
currently allocating the entire 8K FIFO memory for FIFO mode only. We
can adjust this later if we find a better way to incorporate 'PAT_MEM'
mode into the driver.
^ permalink raw reply
* Re: [PATCH 2/5] ARM: dts: mediatek: mt8127: Add watchdog support
From: sashiko-bot @ 2026-06-17 2:30 UTC (permalink / raw)
To: Zakariya Hadrami via B4 Relay; +Cc: robh, linux-watchdog, conor+dt, devicetree
In-Reply-To: <20260617-mt8127-amazon-ford-basic-v1-2-d02ad15ac359@proton.me>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Device tree nodes are not sorted by unit address.
--
commit 7452ca22e20f1d82f0774e2f81b86b3196a78313
Author: Zakariya Hadrami <zkh1@proton.me>
ARM: dts: mediatek: mt8127: Add watchdog support
This commit adds the watchdog node to the mt8127 device tree. It is
disabled by default since it was not present initially.
> diff --git a/arch/arm/boot/dts/mediatek/mt8127.dtsi b/arch/arm/boot/dts/mediatek/mt8127.dtsi
> index bd61ec7e70c0c..1855dda42710f 100644
> --- a/arch/arm/boot/dts/mediatek/mt8127.dtsi
> +++ b/arch/arm/boot/dts/mediatek/mt8127.dtsi
> @@ -159,5 +159,12 @@ uart3: serial@11005000 {
> clocks = <&uart_clk>;
> status = "disabled";
> };
> +
> + watchdog: watchdog@10007000 {
[Severity: Low]
Should this node be sorted by its unit address?
It looks like watchdog@10007000 is being placed after serial@11005000 at the
end of the soc node. Device tree styling conventions usually require sibling
nodes to be sorted in ascending order of their unit address, which would
place this earlier in the file (likely before timer@10008000).
> + compatible = "mediatek,mt8127-wdt","mediatek,mt6589-wdt";
> + reg = <0 0x10007000 0 0x100>;
> + interrupts = <GIC_SPI 88 IRQ_TYPE_LEVEL_LOW>;
> + status = "disabled";
> + };
> };
> };
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260617-mt8127-amazon-ford-basic-v1-0-d02ad15ac359@proton.me?part=2
^ permalink raw reply
* Re: [PATCH 4/5] dt-bindings: arm: mediatek: Add MT8127 Amazon ford
From: sashiko-bot @ 2026-06-17 2:29 UTC (permalink / raw)
To: Zakariya Hadrami via B4 Relay; +Cc: devicetree, linux-watchdog, robh, conor+dt
In-Reply-To: <20260617-mt8127-amazon-ford-basic-v1-4-d02ad15ac359@proton.me>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The device tree compatible block for `amazon,ford` is incorrectly sorted. The file specifies that entries should be sorted by SoC compatible and then alphabetically by board compatible. Because `amazon,ford` and `mediatek,mt8127-moose` share the same SoC compatible (`mediatek,mt8127`), `amazon,ford` should be placed before `mediatek,mt8127-moose`.
--
commit 5d4dd6329b44b144dcfbe78c4a1cf82524b51ef6
Author: Zakariya Hadrami <zkh1@proton.me>
dt-bindings: arm: mediatek: Add MT8127 Amazon ford
This commit adds the device tree binding for the Amazon ford tablet based
on the MediaTek MT8127 SoC.
> diff --git a/Documentation/devicetree/bindings/arm/mediatek.yaml b/Documentation/devicetree/bindings/arm/mediatek.yaml
> index 382d0eb4d0af6..5ddc79689df91 100644
> --- a/Documentation/devicetree/bindings/arm/mediatek.yaml
> +++ b/Documentation/devicetree/bindings/arm/mediatek.yaml
> @@ -124,6 +124,10 @@ properties:
> - enum:
> - mediatek,mt8127-moose
> - const: mediatek,mt8127
> + - items:
> + - enum:
> + - amazon,ford
> + - const: mediatek,mt8127
[Severity: Low]
This isn't a bug, but should the amazon,ford board compatible block be
inserted alphabetically before mediatek,mt8127-moose? Since both share the
mediatek,mt8127 SoC compatible, sorting alphabetically by the board
compatible would usually place amazon,ford first.
> - items:
> - enum:
> - mediatek,mt8135-evbp1
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260617-mt8127-amazon-ford-basic-v1-0-d02ad15ac359@proton.me?part=4
^ permalink raw reply
* [PATCH 5/5] dt-bindings: watchdog: mediatek: Add MT8127
From: Zakariya Hadrami via B4 Relay @ 2026-06-17 2:20 UTC (permalink / raw)
To: Matthias Brugger, AngeloGioacchino Del Regno, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sean Wang, Wim Van Sebroeck,
Guenter Roeck
Cc: linux-kernel, linux-arm-kernel, linux-mediatek, devicetree,
linux-watchdog, Zakariya Hadrami
In-Reply-To: <20260617-mt8127-amazon-ford-basic-v1-0-d02ad15ac359@proton.me>
From: Zakariya Hadrami <zkh1@proton.me>
Add entry for MT8127 SoC's watchdog which is compatible with MT6589's
one.
Signed-off-by: Zakariya Hadrami <zkh1@proton.me>
---
Documentation/devicetree/bindings/watchdog/mediatek,mtk-wdt.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/watchdog/mediatek,mtk-wdt.yaml b/Documentation/devicetree/bindings/watchdog/mediatek,mtk-wdt.yaml
index 953629cb9558..e6e4546da0aa 100644
--- a/Documentation/devicetree/bindings/watchdog/mediatek,mtk-wdt.yaml
+++ b/Documentation/devicetree/bindings/watchdog/mediatek,mtk-wdt.yaml
@@ -40,6 +40,7 @@ properties:
- mediatek,mt7622-wdt
- mediatek,mt7623-wdt
- mediatek,mt7629-wdt
+ - mediatek,mt8127-wdt
- mediatek,mt8173-wdt
- mediatek,mt8188-wdt
- mediatek,mt8189-wdt
--
2.54.0
^ permalink raw reply related
* [PATCH 4/5] dt-bindings: arm: mediatek: Add MT8127 Amazon ford
From: Zakariya Hadrami via B4 Relay @ 2026-06-17 2:20 UTC (permalink / raw)
To: Matthias Brugger, AngeloGioacchino Del Regno, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sean Wang, Wim Van Sebroeck,
Guenter Roeck
Cc: linux-kernel, linux-arm-kernel, linux-mediatek, devicetree,
linux-watchdog, Zakariya Hadrami
In-Reply-To: <20260617-mt8127-amazon-ford-basic-v1-0-d02ad15ac359@proton.me>
From: Zakariya Hadrami <zkh1@proton.me>
Add entry for the MT8127 based Amazon ford tablet.
Signed-off-by: Zakariya Hadrami <zkh1@proton.me>
---
Documentation/devicetree/bindings/arm/mediatek.yaml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/Documentation/devicetree/bindings/arm/mediatek.yaml b/Documentation/devicetree/bindings/arm/mediatek.yaml
index 382d0eb4d0af..5ddc79689df9 100644
--- a/Documentation/devicetree/bindings/arm/mediatek.yaml
+++ b/Documentation/devicetree/bindings/arm/mediatek.yaml
@@ -124,6 +124,10 @@ properties:
- enum:
- mediatek,mt8127-moose
- const: mediatek,mt8127
+ - items:
+ - enum:
+ - amazon,ford
+ - const: mediatek,mt8127
- items:
- enum:
- mediatek,mt8135-evbp1
--
2.54.0
^ permalink raw reply related
* [PATCH 0/5] ARM: Basic support for Amazon ford tablet (MT8127)
From: Zakariya Hadrami via B4 Relay @ 2026-06-17 2:20 UTC (permalink / raw)
To: Matthias Brugger, AngeloGioacchino Del Regno, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sean Wang, Wim Van Sebroeck,
Guenter Roeck
Cc: linux-kernel, linux-arm-kernel, linux-mediatek, devicetree,
linux-watchdog, Zakariya Hadrami
This series of patches adds basic support for MT8127 SoC based Amazon ford
tablet and fixes a small indentation error in the dtsi file.
Signed-off-by: Zakariya Hadrami <zkh1@proton.me>
---
Zakariya Hadrami (5):
ARM: dts: mediatek: mt8127: Fix indentation error
ARM: dts: mediatek: mt8127: Add watchdog support
ARM: dts: mediatek: Add basic support for Amazon ford board
dt-bindings: arm: mediatek: Add MT8127 Amazon ford
dt-bindings: watchdog: mediatek: Add MT8127
.../devicetree/bindings/arm/mediatek.yaml | 4 ++
.../bindings/watchdog/mediatek,mtk-wdt.yaml | 1 +
arch/arm/boot/dts/mediatek/Makefile | 1 +
arch/arm/boot/dts/mediatek/mt8127-amazon-ford.dts | 46 ++++++++++++++++++++++
arch/arm/boot/dts/mediatek/mt8127.dtsi | 9 ++++-
5 files changed, 60 insertions(+), 1 deletion(-)
---
base-commit: 8cd9520d35a6c38db6567e97dd93b1f11f185dc6
change-id: 20260616-mt8127-amazon-ford-basic-1509d7052f7e
Best regards,
--
Zakariya Hadrami <zkh1@proton.me>
^ permalink raw reply
* [PATCH 2/5] ARM: dts: mediatek: mt8127: Add watchdog support
From: Zakariya Hadrami via B4 Relay @ 2026-06-17 2:20 UTC (permalink / raw)
To: Matthias Brugger, AngeloGioacchino Del Regno, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sean Wang, Wim Van Sebroeck,
Guenter Roeck
Cc: linux-kernel, linux-arm-kernel, linux-mediatek, devicetree,
linux-watchdog, Zakariya Hadrami
In-Reply-To: <20260617-mt8127-amazon-ford-basic-v1-0-d02ad15ac359@proton.me>
From: Zakariya Hadrami <zkh1@proton.me>
Add watchdog node and disable it by default as it was not present
initially.
Signed-off-by: Zakariya Hadrami <zkh1@proton.me>
---
arch/arm/boot/dts/mediatek/mt8127.dtsi | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/arch/arm/boot/dts/mediatek/mt8127.dtsi b/arch/arm/boot/dts/mediatek/mt8127.dtsi
index bd61ec7e70c0..1855dda42710 100644
--- a/arch/arm/boot/dts/mediatek/mt8127.dtsi
+++ b/arch/arm/boot/dts/mediatek/mt8127.dtsi
@@ -159,5 +159,12 @@ uart3: serial@11005000 {
clocks = <&uart_clk>;
status = "disabled";
};
+
+ watchdog: watchdog@10007000 {
+ compatible = "mediatek,mt8127-wdt","mediatek,mt6589-wdt";
+ reg = <0 0x10007000 0 0x100>;
+ interrupts = <GIC_SPI 88 IRQ_TYPE_LEVEL_LOW>;
+ status = "disabled";
+ };
};
};
--
2.54.0
^ permalink raw reply related
* [PATCH 1/5] ARM: dts: mediatek: mt8127: Fix indentation error
From: Zakariya Hadrami via B4 Relay @ 2026-06-17 2:20 UTC (permalink / raw)
To: Matthias Brugger, AngeloGioacchino Del Regno, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sean Wang, Wim Van Sebroeck,
Guenter Roeck
Cc: linux-kernel, linux-arm-kernel, linux-mediatek, devicetree,
linux-watchdog, Zakariya Hadrami
In-Reply-To: <20260617-mt8127-amazon-ford-basic-v1-0-d02ad15ac359@proton.me>
From: Zakariya Hadrami <zkh1@proton.me>
Fix an indentation error caused by a space at the start of a line.
Signed-off-by: Zakariya Hadrami <zkh1@proton.me>
---
arch/arm/boot/dts/mediatek/mt8127.dtsi | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/arm/boot/dts/mediatek/mt8127.dtsi b/arch/arm/boot/dts/mediatek/mt8127.dtsi
index aced173c2a52..bd61ec7e70c0 100644
--- a/arch/arm/boot/dts/mediatek/mt8127.dtsi
+++ b/arch/arm/boot/dts/mediatek/mt8127.dtsi
@@ -75,7 +75,7 @@ uart_clk: dummy26m {
compatible = "fixed-clock";
clock-frequency = <26000000>;
#clock-cells = <0>;
- };
+ };
};
timer {
--
2.54.0
^ permalink raw reply related
* [PATCH 3/5] ARM: dts: mediatek: Add basic support for Amazon ford board
From: Zakariya Hadrami via B4 Relay @ 2026-06-17 2:20 UTC (permalink / raw)
To: Matthias Brugger, AngeloGioacchino Del Regno, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sean Wang, Wim Van Sebroeck,
Guenter Roeck
Cc: linux-kernel, linux-arm-kernel, linux-mediatek, devicetree,
linux-watchdog, Zakariya Hadrami
In-Reply-To: <20260617-mt8127-amazon-ford-basic-v1-0-d02ad15ac359@proton.me>
From: Zakariya Hadrami <zkh1@proton.me>
This tablet uses a MediaTek MT8127 system-on-chip with 1GB of RAM.
It can currently boot into initramfs with a working UART and
Simple Framebuffer using already initialized panel by the bootloader.
Signed-off-by: Zakariya Hadrami <zkh1@proton.me>
---
arch/arm/boot/dts/mediatek/Makefile | 1 +
arch/arm/boot/dts/mediatek/mt8127-amazon-ford.dts | 46 +++++++++++++++++++++++
2 files changed, 47 insertions(+)
diff --git a/arch/arm/boot/dts/mediatek/Makefile b/arch/arm/boot/dts/mediatek/Makefile
index 37c4cded0eae..a610bc75c7d9 100644
--- a/arch/arm/boot/dts/mediatek/Makefile
+++ b/arch/arm/boot/dts/mediatek/Makefile
@@ -14,5 +14,6 @@ dtb-$(CONFIG_ARCH_MEDIATEK) += \
mt7623n-rfb-emmc.dtb \
mt7623n-bananapi-bpi-r2.dtb \
mt7629-rfb.dtb \
+ mt8127-amazon-ford.dtb \
mt8127-moose.dtb \
mt8135-evbp1.dtb
diff --git a/arch/arm/boot/dts/mediatek/mt8127-amazon-ford.dts b/arch/arm/boot/dts/mediatek/mt8127-amazon-ford.dts
new file mode 100644
index 000000000000..21bdab0e43f8
--- /dev/null
+++ b/arch/arm/boot/dts/mediatek/mt8127-amazon-ford.dts
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/dts-v1/;
+#include "mt8127.dtsi"
+
+/ {
+ model = "MediaTek MT8127 Amazon Ford";
+ compatible = "amazon,ford", "mediatek,mt8127";
+
+ aliases {
+ serial0 = &uart0;
+ };
+
+ chosen {
+ stdout-path = "serial0:921600n8";
+
+ framebuffer0: framebuffer@b7a00000 {
+ compatible = "simple-framebuffer";
+ memory-region = <&framebuffer_reserved>;
+ width = <1024>;
+ height = <600>;
+ stride = <(1024 * 2)>;
+ format = "r5g6b5";
+ };
+ };
+
+ memory@80000000 {
+ device_type = "memory";
+ reg = <0 0x80000000 0 0x40000000>;
+ };
+
+ reserved-memory {
+ framebuffer_reserved: framebuffer@b7a00000 {
+ reg = <0 0xb7a00000 0 0x1000000>;
+ no-map;
+ };
+ };
+};
+
+&uart0 {
+ status = "okay";
+};
+
+&watchdog {
+ status = "okay";
+};
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v3 5/5] iio: adc: ltc2378: Enable triggered buffer data capture
From: sashiko-bot @ 2026-06-17 2:18 UTC (permalink / raw)
To: Marcelo Schmitt; +Cc: conor+dt, robh, devicetree
In-Reply-To: <22248e8ba646fbe8edfc87e2b9be527632d5b48c.1781661028.git.marcelo.schmitt@analog.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Triggered buffer setup fallback is unreachable when SPI offload support is compiled in, causing driver probe to fail on systems without SPI offload hardware.
- [High] The triggered buffer pushes misaligned data on big-endian architectures, yielding constant zero readings for 16-bit parts.
- [Low] The newly added file `ltc2378-triggered-buffer.c` uses `#include <ltc2378.h>` instead of `#include "ltc2378.h"`, causing a compile error.
--
commit 975300646e8809a19fe7c482edd698d128476317
Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
iio: adc: ltc2378: Enable triggered buffer data capture
Enable users to run triggered data captures with LTC2378 and similar ADCs.
> diff --git a/drivers/iio/adc/ltc2378-lib-core.c b/drivers/iio/adc/ltc2378-lib-core.c
> index 1160f4324d019..ec83e9f2ae81c 100644
> --- a/drivers/iio/adc/ltc2378-lib-core.c
> +++ b/drivers/iio/adc/ltc2378-lib-core.c
> @@ -18,7 +18,7 @@ int ltc2378_lib_buffer_setup(struct iio_dev *indio_dev, struct ltc2378_state *st
>
> ret = __ltc2378_set_offload_ops(st);
> if (ret == -EOPNOTSUPP)
> - return 0; /* Let device setup complete without buffer support */
> + goto trigger_buf_setup;
>
> if (!ret)
> ret = st->ops->buffer_setup(indio_dev, st);
>
> if (ret)
> return dev_err_probe(dev, ret, "error on SPI offload setup\n");
[Severity: High]
If CONFIG_LTC2378_LIB_OFFLOAD_BUFFER is enabled, does __ltc2378_set_offload_ops()
unconditionally return 0?
If so, the fallback condition if (ret == -EOPNOTSUPP) is never met. The driver
then calls st->ops->buffer_setup(), which maps to ltc2378_offload_buffer_setup.
If the device lacks an SPI offload engine in the device tree,
devm_spi_offload_get() will return -ENODEV, failing the probe entirely.
Does this code prevent the driver from gracefully falling back to triggered
buffers on non-offload setups?
[ ... ]
> diff --git a/drivers/iio/adc/ltc2378-triggered-buffer.c b/drivers/iio/adc/ltc2378-triggered-buffer.c
> new file mode 100644
> index 0000000000000..d1d788fb5cb4f
> --- /dev/null
> +++ b/drivers/iio/adc/ltc2378-triggered-buffer.c
> @@ -0,0 +1,49 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2026 Analog Devices, Inc.
> + * Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
> + */
> +
> +#include <linux/iio/buffer.h>
> +#include <linux/iio/triggered_buffer.h>
> +#include <linux/iio/trigger_consumer.h>
> +
> +#include <ltc2378.h>
[Severity: Low]
Does this include directive cause a compile error because it uses angle
brackets?
In C, #include <...> instructs the preprocessor to search standard system
directories. Because ltc2378.h resides in the local subsystem directory
drivers/iio/adc/ and the IIO Makefile does not add -I$(src), compilation
fails with a missing file error.
> +static irqreturn_t ltc2378_trigger_handler(int irq, void *p)
> +{
> + struct iio_poll_func *pf = p;
> + struct iio_dev *indio_dev = pf->indio_dev;
> + struct ltc2378_state *st = iio_priv(indio_dev);
> + int ret;
> +
> + ret = ltc2378_convert_and_acquire(st);
> + if (ret < 0)
> + goto err_out;
> +
> + iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan),
> + pf->timestamp);
[Severity: High]
Does this code push misaligned data on big-endian architectures, resulting
in constant zero readings for 16-bit parts?
The IIO channel specifies storagebits = 32 and shift = 0, so consumers expect
a 32-bit native-endian value with data in the lowest 16 bits.
During ltc2378_trigger_handler(), spi_sync_transfer() with bits_per_word = 16
stores the 16-bit sample in CPU byte order at the start of the st->scan.data
union (sample_buf16).
On little-endian systems, this natively aligns with the lower 16 bits of the
32-bit container. However, on big-endian systems, the 16-bit sample occupies
the most significant bytes of the 32-bit word.
When the IIO core processes this as a 32-bit word, the data sits in the upper
16 bits while the lower 16 bits remain zero. Since scan_type.shift is 0,
masking the lower 16 bits results in the consumer incorrectly receiving zero
for every sample.
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1781661028.git.marcelo.schmitt@analog.com?part=5
^ permalink raw reply
* Re: [PATCH v3 4/5] iio: adc: ltc2378: Enable high-speed data capture
From: sashiko-bot @ 2026-06-17 2:17 UTC (permalink / raw)
To: Marcelo Schmitt; +Cc: devicetree, conor+dt, robh
In-Reply-To: <9f173c47928446aa3e900cf0becb6130dd76846b.1781661028.git.marcelo.schmitt@analog.com>
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] Unconditional propagation of -ENODEV breaks driver probe on systems without SPI offload
- [High] Silent failure in SPI offload trigger offset validation loop risks data corruption
- [Medium] Incomplete state rollback on failed conversion rate validation corrupts driver state
- [Low] Missing source file causes build failure
- [Low] Accumulative drift of PWM period due to reusing modified waveform structure
--
commit 0bb089e96af6d65eeaa13775a0321f6c17a80b75
Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
iio: adc: ltc2378: Enable high-speed data capture
Make use of SPI transfer offloading to speed up data capture, enabling data
acquisition at faster sample rates (up to 2 MSPS).
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 2b82034513673..f96d9262b891d 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -944,6 +944,9 @@ config LTC2378
> tristate "Analog Devices LTC2378 ADC driver"
> depends on SPI
> depends on GPIOLIB
> + select LTC2378_LIB
> + select LTC2378_LIB_OFFLOAD_BUFFER if SPI_OFFLOAD && PWM && SPI_OFFLOAD_TRIGGER_PWM && IIO_BUFFER && IIO_BUFFER_DMAENGINE
> + select LTC2378_LIB_TRIGGERED_BUFFER if IIO_BUFFER
[ ... ]
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index 1814fb78dde30..109cd39237c9d 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -82,6 +82,12 @@ obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o
> obj-$(CONFIG_LPC32XX_ADC) += lpc32xx_adc.o
> obj-$(CONFIG_LTC2309) += ltc2309.o
> obj-$(CONFIG_LTC2378) += ltc2378.o
> +
> +ltc2378_lib-y += ltc2378-lib-core.o
> +ltc2378_lib-$(CONFIG_LTC2378_LIB_OFFLOAD_BUFFER) += ltc2378-offload-buffer.o
> +ltc2378_lib-$(CONFIG_LTC2378_LIB_TRIGGERED_BUFFER) += ltc2378-triggered-buffer.o
[Severity: Low]
Will this break the build since ltc2378-triggered-buffer.c is not included
in this patch?
The patch adds CONFIG_LTC2378_LIB_TRIGGERED_BUFFER and references
ltc2378-triggered-buffer.o in the Makefile, which is automatically selected if
IIO_BUFFER is enabled. However, the corresponding ltc2378-triggered-buffer.c
file is missing.
This regression was fixed later in the series in commit
975300646e8809a19fe7c482edd698d128476317 ("iio: adc: ltc2378: Enable triggered
buffer data capture"), but shouldn't each patch in the series compile
independently to preserve bisectability?
[ ... ]
> diff --git a/drivers/iio/adc/ltc2378-lib-core.c b/drivers/iio/adc/ltc2378-lib-core.c
> new file mode 100644
> index 0000000000000..1160f4324d019
> --- /dev/null
> +++ b/drivers/iio/adc/ltc2378-lib-core.c
> @@ -0,0 +1,35 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Analog Devices LTC2378 ADC series driver
> + *
> + * Copyright (C) 2026 Analog Devices Inc.
> + * Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
> + */
> +
> +#include <linux/err.h>
> +#include <linux/iio/iio.h>
> +
> +#include "ltc2378.h"
> +
> +int ltc2378_lib_buffer_setup(struct iio_dev *indio_dev, struct ltc2378_state *st)
> +{
> + struct device *dev = &st->spi->dev;
> + int ret;
> +
> + ret = __ltc2378_set_offload_ops(st);
> + if (ret == -EOPNOTSUPP)
> + return 0; /* Let device setup complete without buffer support */
> +
> + if (!ret)
> + ret = st->ops->buffer_setup(indio_dev, st);
> +
> + if (ret)
> + return dev_err_probe(dev, ret, "error on SPI offload setup\n");
> +
> + return 0;
> +}
[ ... ]
> diff --git a/drivers/iio/adc/ltc2378-offload-buffer.c b/drivers/iio/adc/ltc2378-offload-buffer.c
> new file mode 100644
> index 0000000000000..3e8c7ab64a54b
> --- /dev/null
> +++ b/drivers/iio/adc/ltc2378-offload-buffer.c
> @@ -0,0 +1,305 @@
[ ... ]
> +static int ltc2378_offload_buffer_setup(struct iio_dev *indio_dev,
> + struct ltc2378_state *st)
> +{
> + struct spi_device *spi = st->spi;
> + struct device *dev = &spi->dev;
> + int ret;
> +
> + st->offload = devm_spi_offload_get(dev, spi, <c2378_offload_config);
> + ret = PTR_ERR_OR_ZERO(st->offload);
> + if (ret)
> + return ret;
[Severity: High]
Does this unconditionally propagate -ENODEV and break the driver probe on
systems without SPI offload?
When CONFIG_LTC2378_LIB_OFFLOAD_BUFFER is enabled, the driver attempts to set
up the SPI offload buffer. If the system hardware does not support SPI offload,
devm_spi_offload_get() returns -ENODEV.
Because ltc2378_offload_buffer_setup() returns this error,
ltc2378_lib_buffer_setup() will pass it to dev_err_probe() and fail the entire
ADC probe. Should the driver treat -ENODEV as a signal to bypass offload setup
and gracefully fall back to triggered buffer or direct mode?
[ ... ]
> +static int ltc2378_update_conversion_rate(struct ltc2378_state *st, int freq_Hz)
> +{
> + struct spi_offload_trigger_config *config = &st->offload_trigger_config;
> + unsigned int min_read_offset, offload_period_ns;
> + struct pwm_waveform cnv_wf = { };
> + u64 target = LTC2378_TCNV_HIGH_NS;
> + unsigned int count = 0;
> + u64 offload_offset_ns;
> + int ret;
> +
> + if (freq_Hz == 0)
> + return -EINVAL;
> +
> + if (freq_Hz < 1 || freq_Hz > st->info->max_sample_rate_hz)
> + return -ERANGE;
> +
> + /* Configure CNV PWM waveform */
> + cnv_wf.period_length_ns = DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq_Hz);
> +
> + /*
> + * Ensure CNV high time meets minimum requirement (20ns). The PWM
> + * hardware may round the duty cycle, so iterate until we get at least
> + * the minimum required high time.
> + */
> + do {
> + cnv_wf.duty_length_ns = target;
> + ret = pwm_round_waveform_might_sleep(st->cnv_trigger, &cnv_wf);
> + if (ret)
> + return ret;
> + target += 10; /* Increment by PWM duty cycle period */
> + } while (count++ < 100 && cnv_wf.duty_length_ns < LTC2378_TCNV_HIGH_NS);
[Severity: Low]
Could this cause an accumulative drift of the PWM period?
In this loop, pwm_round_waveform_might_sleep(&cnv_wf) can modify fields in
cnv_wf based on hardware rounding capabilities. Because cnv_wf.period_length_ns
is not reset to the original target period at the beginning of each iteration,
any rounding applied to the period in one iteration is passed into the next,
which might drift the PWM period away from the requested frequency.
> +
> + /* Double check the minimum CNV high time is met */
> + if (cnv_wf.duty_length_ns < LTC2378_TCNV_HIGH_NS)
> + return -EIO;
> +
> + /*
> + * Configure SPI offload PWM trigger.
> + * The trigger should fire after tBUSYLH + tCONV + tDSDOBUSYL.
> + * Minimum time needed: TBUSYLH (13ns) + TCONV (part-specific) + TDSDOBUSYL (5ns)
> + *
> + * Use the same period as CNV PWM to avoid timing issues.
> + * Convert back from period to frequency for the SPI offload API.
> + */
> + offload_period_ns = cnv_wf.period_length_ns;
> + config->periodic.frequency_hz = DIV_ROUND_UP(HZ_PER_GHZ, offload_period_ns);
> + min_read_offset = LTC2378_TBUSYLH_NS + st->info->tconv_ns + LTC2378_TDSDOBUSYL_NS;
> + offload_offset_ns = min_read_offset;
> + count = 0;
> + do {
> + config->periodic.offset_ns = offload_offset_ns;
> + ret = spi_offload_trigger_validate(st->offload_trigger, config);
> + if (ret)
> + return ret;
[Severity: Medium]
Does returning here leave the driver in an inconsistent state?
The pointer config directly points to the device state
st->offload_trigger_config. If validation fails, returning an error immediately
leaves st->offload_trigger_config containing the rejected values, while
st->cnv_wf and st->cnv_Hz retain their old values.
Subsequent buffer enable operations might try to pair the old PWM waveform
with the corrupted offload trigger config. Shouldn't the state be modified
on a local copy and only committed upon success?
> + offload_offset_ns += 10;
> + } while (count++ < 100 && config->periodic.offset_ns < min_read_offset);
> +
> + st->cnv_wf = cnv_wf;
> + st->cnv_Hz = DIV_ROUND_CLOSEST_ULL(HZ_PER_GHZ, cnv_wf.period_length_ns);
> +
> + return 0;
[Severity: High]
Is it possible for this to silently return 0 on a failed loop condition?
The loop iterates up to 100 times to find an SPI offload trigger offset >=
min_read_offset. If the underlying hardware driver continually rounds the
offset down such that it never reaches min_read_offset, the loop drops out
after 100 iterations.
The function then unconditionally returns 0 (success). This would configure
the SPI offload to trigger an SPI read before the ADC finishes its conversion,
leading to corrupted data. Should this check if
config->periodic.offset_ns >= min_read_offset after the loop and return an
error if it fails?
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1781661028.git.marcelo.schmitt@analog.com?part=4
^ permalink raw reply
* Re: [PATCH v3 2/5] iio: adc: ltc2378: Add support for LTC2378-20 and similar ADCs
From: sashiko-bot @ 2026-06-17 2:16 UTC (permalink / raw)
To: Marcelo Schmitt; +Cc: robh, conor+dt, devicetree
In-Reply-To: <5c18e7a370119ddfd5faefe147b294b39f78894a.1781661028.git.marcelo.schmitt@analog.com>
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] Unsupported SPI bits_per_word values for 18-bit and 20-bit ADCs.
- [High] Logic Error: Keeping CNV high during SPI transfer outputs BUSY flags instead of data.
- [High] Data Corruption: Buffered reads for 16-bit parts always return 0 on big-endian architectures.
- [Medium] ABI Violation: Non-standard sysfs attribute name without channel index.
- [Medium] Missing Kconfig dependency on REGULATOR causes unconditional probe failure.
--
commit 30af41465781d1df468bbb782a984208c0789b78
Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
iio: adc: ltc2378: Add support for LTC2378-20 and similar ADCs
Support for LTC2378-20 and similar analog-to-digital converters. Co-developed
by Ioan-Daniel Pop.
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index a3a93a47b43d9..2b82034513673 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
[ ... ]
> @@ -940,6 +940,17 @@ config LTC2309
> This driver can also be built as a module. If so, the module will
> be called ltc2309.
>
> +config LTC2378
> + tristate "Analog Devices LTC2378 ADC driver"
> + depends on SPI
> + depends on GPIOLIB
[Severity: Medium]
Should this Kconfig entry also depend on REGULATOR?
The probe function unconditionally calls devm_regulator_get_enable_read_voltage()
and fails if it returns an error. If the kernel is built with CONFIG_REGULATOR
disabled, the regulator API is stubbed out to return -ENODEV, making it
structurally impossible for the driver to successfully probe.
> diff --git a/drivers/iio/adc/ltc2378.c b/drivers/iio/adc/ltc2378.c
> new file mode 100644
> index 0000000000000..88582bdcd6a62
> --- /dev/null
> +++ b/drivers/iio/adc/ltc2378.c
[ ... ]
> +static int ltc2378_probe(struct spi_device *spi)
> +{
[ ... ]
> + st->chans[st->num_iio_chans++] = (struct iio_chan_spec) {
> + .type = IIO_VOLTAGE,
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> + BIT(IIO_CHAN_INFO_SCALE),
> + .scan_index = 0,
[Severity: Medium]
Does this channel specification need .indexed = 1 and .channel = 0?
Without the index, the IIO core exposes the sysfs attribute as in_voltage_raw
instead of in_voltage0_raw. The IIO ABI documentation strictly mandates that
the number must always be specified and unique to allow association with event
codes, so this non-standard naming breaks compatibility with generic userspace
clients.
> + .scan_type = {
> + .format = st->info->bipolar ? IIO_SCAN_FORMAT_SIGNED_INT :
> + IIO_SCAN_FORMAT_UNSIGNED_INT,
> + .realbits = st->info->resolution,
> + /*
> + * Buffer elements could be 16-bit for low precision
> + * parts. Though, using more storage bits allows keeping
> + * the same scan_type configuration for both types of
> + * buffer support.
> + */
> + .storagebits = 32,
> + },
[Severity: High]
Will this configuration cause buffered reads for 16-bit parts to return 0 on
big-endian architectures?
For 16-bit parts, the SPI transfer writes 2 bytes to the 32-bit union. On
big-endian architectures, these 2 bytes are written to the most significant
bytes of the 32-bit space. However, setting storagebits = 32 with an implicit
shift of 0 causes the IIO core to extract the lower 16 bits (which remain 0).
This causes the IIO core to persistently push 0 to the buffers.
> + };
> +
> + st->xfer.rx_buf = &st->scan.data;
> + st->xfer.len = st->info->resolution > 16 ? 4 : 2;
> + st->xfer.bits_per_word = st->info->resolution;
[Severity: High]
Is it safe to set bits_per_word directly to 18 or 20?
The SPI core validates bits_per_word against the controller's bits_per_word_mask
during transfer. Since the vast majority of SPI controllers only support 8, 16,
or 32 bits per word, setting this to 18 or 20 will cause spi_sync_transfer() to
immediately fail with -EINVAL, breaking data capture on standard hardware.
> diff --git a/drivers/iio/adc/ltc2378.h b/drivers/iio/adc/ltc2378.h
> new file mode 100644
> index 0000000000000..a3a69351de6ce
> --- /dev/null
> +++ b/drivers/iio/adc/ltc2378.h
[ ... ]
> +static inline int ltc2378_convert_and_acquire(struct ltc2378_state *st)
> +{
> + int ret;
> +
> + /* Cause a rising edge of CNV to initiate a new ADC conversion */
> + gpiod_set_value_cansleep(st->cnv_gpio, 1);
> + fsleep(4);
> + ret = spi_sync_transfer(st->spi, &st->xfer, 1);
> + gpiod_set_value_cansleep(st->cnv_gpio, 0);
> +
> + return ret;
> +}
[Severity: High]
Does keeping the CNV pin high during the SPI transfer cause the ADC to output
BUSY flags instead of conversion data?
For the LTC2378 series, bringing the Chip Select low while CNV is still high
places the ADC into a BUSY indicator mode instead of shifting out the sampled
data. Should CNV be brought low before initiating the SPI transfer?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1781661028.git.marcelo.schmitt@analog.com?part=2
^ permalink raw reply
* Re: [RFC PATCH v3 3/5] iio: buffer: Extend DMAengine buffer interfaces to take extra sysfs attributes
From: sashiko-bot @ 2026-06-17 2:13 UTC (permalink / raw)
To: Marcelo Schmitt; +Cc: robh, conor+dt, devicetree
In-Reply-To: <a09f2cac2d26fe385f1f7790b0db64b7d9953ac6.1781661028.git.marcelo.schmitt@analog.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Critical] Unconditional assignment of `buffer->attrs` overwrites the default DMAengine buffer attributes, removing `length_align_bytes` from sysfs and breaking the userspace ABI.
Pre-existing issues:
- [High] The ad4691 driver manually overwrites the IIO buffer's `attrs` field after setup, clobbering the generic DMAengine buffer attributes.
--
commit e15805cbfeb85d6ab2a3f8819279738d4402da62
Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
iio: buffer: Extend DMAengine buffer interfaces to take extra sysfs attributes
This patch extends the IIO DMAengine buffer setup interfaces to accept an
additional parameter for extra sysfs attributes. It updates all existing
callers to pass NULL for this new parameter.
> diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
> --- a/drivers/iio/adc/ad4691.c
> +++ b/drivers/iio/adc/ad4691.c
> @@ -1980,7 +1980,8 @@ static int ad4691_setup_offload(struct iio_dev *indio_dev,
> indio_dev->setup_ops = &ad4691_cnv_burst_offload_buffer_setup_ops;
>
> ret = devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, rx_dma,
> - IIO_BUFFER_DIRECTION_IN);
> + IIO_BUFFER_DIRECTION_IN,
> + NULL);
[Severity: High]
This is a pre-existing issue, but does manually assigning the buffer attributes
further down in this function clobber the generic DMAengine buffer attributes?
Following this setup call in ad4691_setup_offload(), the driver manually
assigns:
indio_dev->buffer->attrs = ad4691_buffer_attrs;
Could this overwrite the default DMAengine buffer attributes like
length_align_bytes? Since the new API allows injecting custom attributes,
should this driver be updated to pass ad4691_buffer_attrs into
devm_iio_dmaengine_buffer_setup_with_handle() rather than passing NULL and
manually overwriting it afterwards?
> if (ret)
> return ret;
>
[ ... ]
> diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
> --- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c
> +++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
> @@ -289,7 +289,8 @@ EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_teardown, "IIO_DMAENGINE_BUFFER");
> static struct iio_buffer
> *__iio_dmaengine_buffer_setup_ext(struct iio_dev *indio_dev,
> struct dma_chan *chan,
> - enum iio_buffer_direction dir)
> + enum iio_buffer_direction dir,
> + const struct iio_dev_attr **buffer_attrs)
> {
> struct iio_buffer *buffer;
> int ret;
> @@ -301,6 +302,7 @@ static struct iio_buffer
> indio_dev->modes |= INDIO_BUFFER_HARDWARE;
>
> buffer->direction = dir;
> + buffer->attrs = buffer_attrs;
[Severity: Critical]
Does unconditionally overwriting buffer->attrs here remove the default
DMAengine buffer attributes?
Since iio_dmaengine_buffer_alloc() sets up the default attributes array
(which exposes length_align_bytes to userspace), and all current callers
updated in this patch pass NULL for buffer_attrs, it appears this will
silently delete length_align_bytes from sysfs for all IIO DMAengine buffers.
Could this break the userspace ABI, as applications might rely on
length_align_bytes to determine the required memory alignment for DMA
buffers?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1781661028.git.marcelo.schmitt@analog.com?part=3
^ permalink raw reply
* Re: [PATCH v3 2/2] drm/tiny: add support for PIXPAPER 4.26 monochrome e-ink panel
From: LiangCheng Wang @ 2026-06-17 2:09 UTC (permalink / raw)
To: Devarsh Thakkar, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Wig Cheng
Cc: dri-devel, devicetree, linux-kernel, Tomi Valkeinen,
LiangCheng Wang
In-Reply-To: <423c3dca-9589-488d-8462-c3d51f05ee4a@ti.com>
Hi Devarsh,
Thanks, that sounds great -- let's converge on panel-ssd16xx.c.
> Yes, I will be adding SSD1677 controller support in V2 of my series,
> hopefully that should help and after that it's just a matter of adding
> panel entry for your pixpaper panel. I can share you my branch with
> ssd1677 support once I have it ready.
That would be very helpful, please do share the branch once it's ready. I
have the PIXPAPER 4.26 hardware here, so I can validate the SSD1677 support
and the panel on real hardware and report back any quirks.
> additionally I can quickly add boilerplate pixpaper 4.26 panel entries on
> top of my V2 series referring from your patch so that it switches to using
> standard ssd16xx commands and ssd1677 quirks wherever necessary and you can
> then validate and modify.
Thanks for offering. If it's alright with you, I'd like to take the
pixpaper-426m panel entry through review myself on top of your series, using
your boilerplate as a starting point, since I can carry the validation on
real hardware. I'll credit your help with Co-developed-by/Suggested-by as
appropriate. Happy to arrange it whichever way is easiest for you.
I'll also follow up with the 0xF7/0xFF test result so we can capture the
correct SSD1677 update-sequence quirk.
Regards,
LiangCheng
^ permalink raw reply
* [PATCH v3 5/5] iio: adc: ltc2378: Enable triggered buffer data capture
From: Marcelo Schmitt @ 2026-06-17 2:04 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel
Cc: jic23, nuno.sa, Michael.Hennerich, dlechner, andy, robh, krzk+dt,
conor+dt, pop.ioan-daniel, marcelo.schmitt1
In-Reply-To: <cover.1781661028.git.marcelo.schmitt@analog.com>
Enable users to run triggered data captures with LTC2378 and similar ADCs.
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
Change log v2 -> v3:
- Extracted from main driver file into a separate buffer-specific containment.
drivers/iio/adc/ltc2378-lib-core.c | 17 +++++++-
drivers/iio/adc/ltc2378-triggered-buffer.c | 49 ++++++++++++++++++++++
drivers/iio/adc/ltc2378.h | 15 +++++++
3 files changed, 80 insertions(+), 1 deletion(-)
create mode 100644 drivers/iio/adc/ltc2378-triggered-buffer.c
diff --git a/drivers/iio/adc/ltc2378-lib-core.c b/drivers/iio/adc/ltc2378-lib-core.c
index 1160f4324d01..ec83e9f2ae81 100644
--- a/drivers/iio/adc/ltc2378-lib-core.c
+++ b/drivers/iio/adc/ltc2378-lib-core.c
@@ -18,7 +18,7 @@ int ltc2378_lib_buffer_setup(struct iio_dev *indio_dev, struct ltc2378_state *st
ret = __ltc2378_set_offload_ops(st);
if (ret == -EOPNOTSUPP)
- return 0; /* Let device setup complete without buffer support */
+ goto trigger_buf_setup;
if (!ret)
ret = st->ops->buffer_setup(indio_dev, st);
@@ -27,6 +27,21 @@ int ltc2378_lib_buffer_setup(struct iio_dev *indio_dev, struct ltc2378_state *st
return dev_err_probe(dev, ret, "error on SPI offload setup\n");
return 0;
+
+trigger_buf_setup:
+ ret = __ltc2378_set_triggered_buf_ops(st);
+ if (ret == -EOPNOTSUPP)
+ return 0; /* Let device setup complete without buffer support */
+
+ if (!ret)
+ ret = st->ops->buffer_setup(indio_dev, st);
+
+ if (ret)
+ return dev_err_probe(dev, ret, "error on buffer setup\n");
+
+ st->chans[st->num_iio_chans++] = IIO_CHAN_SOFT_TIMESTAMP(1);
+
+ return 0;
}
EXPORT_SYMBOL_NS_GPL(ltc2378_lib_buffer_setup, "IIO_LTC2378");
diff --git a/drivers/iio/adc/ltc2378-triggered-buffer.c b/drivers/iio/adc/ltc2378-triggered-buffer.c
new file mode 100644
index 000000000000..d1d788fb5cb4
--- /dev/null
+++ b/drivers/iio/adc/ltc2378-triggered-buffer.c
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2026 Analog Devices, Inc.
+ * Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
+ */
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
+
+#include <ltc2378.h>
+
+static irqreturn_t ltc2378_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct ltc2378_state *st = iio_priv(indio_dev);
+ int ret;
+
+ ret = ltc2378_convert_and_acquire(st);
+ if (ret < 0)
+ goto err_out;
+
+ iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan),
+ pf->timestamp);
+
+err_out:
+ iio_trigger_notify_done(indio_dev->trig);
+ return IRQ_HANDLED;
+}
+
+static int ltc2378_triggered_buffer_setup(struct iio_dev *indio_dev, struct ltc2378_state *st)
+{
+ return devm_iio_triggered_buffer_setup(&st->spi->dev, indio_dev,
+ &iio_pollfunc_store_time,
+ <c2378_trigger_handler,
+ NULL);
+}
+
+static const struct ltc2378_ops ltc2378_triggered_buf_ops = {
+ .buffer_setup = ltc2378_triggered_buffer_setup,
+};
+
+int ltc2378_set_triggered_buf_ops(struct ltc2378_state *st)
+{
+ st->ops = <c2378_triggered_buf_ops;
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(ltc2378_set_triggered_buf_ops, "IIO_LTC2378");
diff --git a/drivers/iio/adc/ltc2378.h b/drivers/iio/adc/ltc2378.h
index f6e10f9a83e0..4241b1e79ac0 100644
--- a/drivers/iio/adc/ltc2378.h
+++ b/drivers/iio/adc/ltc2378.h
@@ -89,6 +89,8 @@ int ltc2378_lib_buffer_setup(struct iio_dev *indio_dev, struct ltc2378_state *st
#define __ltc2378_set_offload_ops(st) ltc2378_set_offload_ops((st))
+#define __ltc2378_set_triggered_buf_ops(st) ltc2378_set_triggered_buf_ops((st))
+
#ifdef CONFIG_LTC2378_LIB_OFFLOAD_BUFFER
int ltc2378_set_offload_ops(struct ltc2378_state *st);
@@ -102,4 +104,17 @@ static inline int ltc2378_set_offload_ops(struct ltc2378_state *st)
#endif /* CONFIG_LTC2378_LIB_OFFLOAD_BUFFER */
+#ifdef CONFIG_LTC2378_LIB_TRIGGERED_BUFFER
+
+int ltc2378_set_triggered_buf_ops(struct ltc2378_state *st);
+
+#else
+
+static inline int ltc2378_set_triggered_buf_ops(struct ltc2378_state *st)
+{
+ return -EOPNOTSUPP;
+}
+
+#endif /* CONFIG_LTC2378_LIB_TRIGGERED_BUFFER */
+
#endif /* __DRIVERS_IIO_ADC_LTC2378_H__ */
--
2.53.0
^ permalink raw reply related
* [PATCH v3 4/5] iio: adc: ltc2378: Enable high-speed data capture
From: Marcelo Schmitt @ 2026-06-17 2:04 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel
Cc: jic23, nuno.sa, Michael.Hennerich, dlechner, andy, robh, krzk+dt,
conor+dt, pop.ioan-daniel, marcelo.schmitt1
In-Reply-To: <cover.1781661028.git.marcelo.schmitt@analog.com>
Make use of SPI transfer offloading to speed up data capture, enabling data
acquisition at faster sample rates (up to 2 MSPS).
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
Change log v2 -> v3:
- Fixed the evaluation loop conditions for CNV PWM and SPI Engine trigger PWM,
avoiding potential infinite loop if and CPU stall.
- Added comment to about initial PWM disable.
- Adjusted SPI offload setup initialization to not print error on a valid condition.
- Fully initialize IIO channel scan_type.
- Reworked to make offload support not imply all dependencies to be built in.
- Made sampling_frequency a buffer attribute.
- Made offload support not require DMA and other features to be built in.
- Now using same scan_type configuration for all use cases.
drivers/iio/adc/Kconfig | 19 ++
drivers/iio/adc/Makefile | 6 +
drivers/iio/adc/ltc2378-lib-core.c | 35 +++
drivers/iio/adc/ltc2378-offload-buffer.c | 305 +++++++++++++++++++++++
drivers/iio/adc/ltc2378.c | 46 ++++
drivers/iio/adc/ltc2378.h | 42 ++++
6 files changed, 453 insertions(+)
create mode 100644 drivers/iio/adc/ltc2378-lib-core.c
create mode 100644 drivers/iio/adc/ltc2378-offload-buffer.c
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 2b8203451367..f96d9262b891 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -944,6 +944,9 @@ config LTC2378
tristate "Analog Devices LTC2378 ADC driver"
depends on SPI
depends on GPIOLIB
+ select LTC2378_LIB
+ select LTC2378_LIB_OFFLOAD_BUFFER if SPI_OFFLOAD && PWM && SPI_OFFLOAD_TRIGGER_PWM && IIO_BUFFER && IIO_BUFFER_DMAENGINE
+ select LTC2378_LIB_TRIGGERED_BUFFER if IIO_BUFFER
help
Say yes here to build support for Analog Devices LTC2378-20 and
similar analog to digital converters.
@@ -2027,3 +2030,19 @@ config XILINX_AMS
xilinx-ams.
endmenu
+
+config LTC2378_LIB
+ tristate
+ help
+ Say yes here to build support for buffered data capture with LTC2378
+
+config LTC2378_LIB_OFFLOAD_BUFFER
+ bool
+ help
+ Say yes here to build support for high speed data capture with LTC2378
+
+config LTC2378_LIB_TRIGGERED_BUFFER
+ bool
+ select IIO_TRIGGERED_BUFFER
+ help
+ Say yes here to build support for buffered data capture with LTC2378
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 1814fb78dde3..109cd39237c9 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -82,6 +82,12 @@ obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o
obj-$(CONFIG_LPC32XX_ADC) += lpc32xx_adc.o
obj-$(CONFIG_LTC2309) += ltc2309.o
obj-$(CONFIG_LTC2378) += ltc2378.o
+
+ltc2378_lib-y += ltc2378-lib-core.o
+ltc2378_lib-$(CONFIG_LTC2378_LIB_OFFLOAD_BUFFER) += ltc2378-offload-buffer.o
+ltc2378_lib-$(CONFIG_LTC2378_LIB_TRIGGERED_BUFFER) += ltc2378-triggered-buffer.o
+obj-$(CONFIG_LTC2378_LIB) += ltc2378_lib.o
+
obj-$(CONFIG_LTC2471) += ltc2471.o
obj-$(CONFIG_LTC2485) += ltc2485.o
obj-$(CONFIG_LTC2496) += ltc2496.o ltc2497-core.o
diff --git a/drivers/iio/adc/ltc2378-lib-core.c b/drivers/iio/adc/ltc2378-lib-core.c
new file mode 100644
index 000000000000..1160f4324d01
--- /dev/null
+++ b/drivers/iio/adc/ltc2378-lib-core.c
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Analog Devices LTC2378 ADC series driver
+ *
+ * Copyright (C) 2026 Analog Devices Inc.
+ * Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
+ */
+
+#include <linux/err.h>
+#include <linux/iio/iio.h>
+
+#include "ltc2378.h"
+
+int ltc2378_lib_buffer_setup(struct iio_dev *indio_dev, struct ltc2378_state *st)
+{
+ struct device *dev = &st->spi->dev;
+ int ret;
+
+ ret = __ltc2378_set_offload_ops(st);
+ if (ret == -EOPNOTSUPP)
+ return 0; /* Let device setup complete without buffer support */
+
+ if (!ret)
+ ret = st->ops->buffer_setup(indio_dev, st);
+
+ if (ret)
+ return dev_err_probe(dev, ret, "error on SPI offload setup\n");
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(ltc2378_lib_buffer_setup, "IIO_LTC2378");
+
+MODULE_AUTHOR("Marcelo Schmitt <marcelo.schmitt@analog.com>");
+MODULE_DESCRIPTION("Analog Devices LTC2378 ADC series driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/adc/ltc2378-offload-buffer.c b/drivers/iio/adc/ltc2378-offload-buffer.c
new file mode 100644
index 000000000000..3e8c7ab64a54
--- /dev/null
+++ b/drivers/iio/adc/ltc2378-offload-buffer.c
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2026 Analog Devices, Inc.
+ * Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
+ */
+
+#include <linux/cleanup.h>
+#include <linux/err.h>
+#include <linux/math.h>
+#include <linux/math64.h>
+#include <linux/pwm.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/offload/consumer.h>
+#include <linux/spi/offload/types.h>
+#include <linux/time64.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/buffer-dmaengine.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/types.h>
+
+#include "ltc2378.h"
+
+/*
+ * SPI offload wiring schema
+ *
+ * +-------------+ +-------------+
+ * | CNV |<-----+--| GPIO |
+ * | | +--| PWM0 |
+ * | | | |
+ * | | +--| PWM1 |
+ * | | | +-------------+
+ * | | +->| TRIGGER |
+ * | | | |
+ * | ADC | | SPI |
+ * | | | controller |
+ * | | | |
+ * | SDI |<--------| SDO |
+ * | SDO |-------->| SDI |
+ * | SCLK |<--------| SCLK |
+ * +-------------+ +-------------+
+ *
+ */
+static int ltc2378_update_conversion_rate(struct ltc2378_state *st, int freq_Hz)
+{
+ struct spi_offload_trigger_config *config = &st->offload_trigger_config;
+ unsigned int min_read_offset, offload_period_ns;
+ struct pwm_waveform cnv_wf = { };
+ u64 target = LTC2378_TCNV_HIGH_NS;
+ unsigned int count = 0;
+ u64 offload_offset_ns;
+ int ret;
+
+ if (freq_Hz == 0)
+ return -EINVAL;
+
+ if (freq_Hz < 1 || freq_Hz > st->info->max_sample_rate_hz)
+ return -ERANGE;
+
+ /* Configure CNV PWM waveform */
+ cnv_wf.period_length_ns = DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq_Hz);
+
+ /*
+ * Ensure CNV high time meets minimum requirement (20ns). The PWM
+ * hardware may round the duty cycle, so iterate until we get at least
+ * the minimum required high time.
+ */
+ do {
+ cnv_wf.duty_length_ns = target;
+ ret = pwm_round_waveform_might_sleep(st->cnv_trigger, &cnv_wf);
+ if (ret)
+ return ret;
+ target += 10; /* Increment by PWM duty cycle period */
+ } while (count++ < 100 && cnv_wf.duty_length_ns < LTC2378_TCNV_HIGH_NS);
+
+ /* Double check the minimum CNV high time is met */
+ if (cnv_wf.duty_length_ns < LTC2378_TCNV_HIGH_NS)
+ return -EIO;
+
+ /*
+ * Configure SPI offload PWM trigger.
+ * The trigger should fire after tBUSYLH + tCONV + tDSDOBUSYL.
+ * Minimum time needed: TBUSYLH (13ns) + TCONV (part-specific) + TDSDOBUSYL (5ns)
+ *
+ * Use the same period as CNV PWM to avoid timing issues.
+ * Convert back from period to frequency for the SPI offload API.
+ */
+ offload_period_ns = cnv_wf.period_length_ns;
+ config->periodic.frequency_hz = DIV_ROUND_UP(HZ_PER_GHZ, offload_period_ns);
+ min_read_offset = LTC2378_TBUSYLH_NS + st->info->tconv_ns + LTC2378_TDSDOBUSYL_NS;
+ offload_offset_ns = min_read_offset;
+ count = 0;
+ do {
+ config->periodic.offset_ns = offload_offset_ns;
+ ret = spi_offload_trigger_validate(st->offload_trigger, config);
+ if (ret)
+ return ret;
+ offload_offset_ns += 10;
+ } while (count++ < 100 && config->periodic.offset_ns < min_read_offset);
+
+ st->cnv_wf = cnv_wf;
+ st->cnv_Hz = DIV_ROUND_CLOSEST_ULL(HZ_PER_GHZ, cnv_wf.period_length_ns);
+
+ return 0;
+}
+
+static ssize_t sampling_frequency_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ltc2378_state *st = iio_priv(dev_to_iio_dev(dev));
+
+ return sysfs_emit(buf, "%u\n", st->cnv_Hz);
+}
+
+static ssize_t sampling_frequency_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct ltc2378_state *st = iio_priv(indio_dev);
+ unsigned int val;
+ int ret;
+
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ ret = kstrtouint(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ ret = ltc2378_update_conversion_rate(st, val);
+
+ return ret ?: len;
+}
+
+static ssize_t sampling_frequency_available_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct ltc2378_state *st = iio_priv(indio_dev);
+
+ return sysfs_emit(buf, "[%u %u %u]\n",
+ 1, 1, st->info->max_sample_rate_hz);
+}
+
+static IIO_DEVICE_ATTR_RO(sampling_frequency_available, 0);
+static IIO_DEVICE_ATTR(sampling_frequency, 0644,
+ sampling_frequency_show, sampling_frequency_store, 0);
+
+static const struct iio_dev_attr *ltc2378_offload_buffer_attrs[] = {
+ &iio_dev_attr_sampling_frequency_available,
+ &iio_dev_attr_sampling_frequency,
+ NULL
+};
+
+static int ltc2378_prepare_offload_message(struct device *dev,
+ struct ltc2378_state *st)
+{
+ st->offload_xfer.bits_per_word = st->info->resolution;
+ st->offload_xfer.len = st->info->resolution > 16 ? 4 : 2;
+ st->offload_xfer.offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+
+ /* Initialize message with offload */
+ spi_message_init_with_transfers(&st->offload_msg, &st->offload_xfer, 1);
+ st->offload_msg.offload = st->offload;
+
+ return devm_spi_optimize_message(dev, st->spi, &st->offload_msg);
+}
+
+static int ltc2378_offload_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct ltc2378_state *st = iio_priv(indio_dev);
+ int ret;
+
+ ret = pwm_set_waveform_might_sleep(st->cnv_trigger, &st->cnv_wf, false);
+ if (ret)
+ return ret;
+
+ ret = spi_offload_trigger_enable(st->offload, st->offload_trigger,
+ &st->offload_trigger_config);
+ if (ret)
+ goto out_pwm_disable;
+
+ return 0;
+
+out_pwm_disable:
+ pwm_disable(st->cnv_trigger);
+ return ret;
+}
+
+static int ltc2378_offload_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct ltc2378_state *st = iio_priv(indio_dev);
+
+ spi_offload_trigger_disable(st->offload, st->offload_trigger);
+ pwm_disable(st->cnv_trigger);
+
+ return 0;
+}
+
+static const struct iio_buffer_setup_ops ltc2378_offload_buffer_ops = {
+ .postenable = <c2378_offload_buffer_postenable,
+ .predisable = <c2378_offload_buffer_predisable,
+};
+
+static int ltc2378_spi_offload_setup(struct iio_dev *indio_dev,
+ struct ltc2378_state *st)
+{
+ struct device *dev = &st->spi->dev;
+ struct dma_chan *rx_dma;
+
+ indio_dev->setup_ops = <c2378_offload_buffer_ops;
+
+ st->offload_trigger = devm_spi_offload_trigger_get(dev, st->offload,
+ SPI_OFFLOAD_TRIGGER_PERIODIC);
+ if (IS_ERR(st->offload_trigger))
+ return dev_err_probe(dev, PTR_ERR(st->offload_trigger),
+ "failed to get offload trigger\n");
+
+ st->offload_trigger_config.type = SPI_OFFLOAD_TRIGGER_PERIODIC;
+
+ rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev, st->offload);
+ if (IS_ERR(rx_dma))
+ return dev_err_probe(dev, PTR_ERR(rx_dma), "failed to get offload RX DMA\n");
+
+ return devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, rx_dma,
+ IIO_BUFFER_DIRECTION_IN,
+ ltc2378_offload_buffer_attrs);
+}
+
+static int ltc2378_pwm_get(struct ltc2378_state *st)
+{
+ struct device *dev = &st->spi->dev;
+
+ st->cnv_trigger = devm_pwm_get(dev, NULL);
+ if (IS_ERR(st->cnv_trigger))
+ return dev_err_probe(dev, PTR_ERR(st->cnv_trigger),
+ "failed to get cnv pwm\n");
+
+ /*
+ * Disable the PWM connected to CNV in case it was left running by
+ * something else.
+ */
+ pwm_disable(st->cnv_trigger);
+
+ return 0;
+}
+
+static const struct spi_offload_config ltc2378_offload_config = {
+ .capability_flags = SPI_OFFLOAD_CAP_TRIGGER |
+ SPI_OFFLOAD_CAP_RX_STREAM_DMA,
+};
+
+static int ltc2378_offload_buffer_setup(struct iio_dev *indio_dev,
+ struct ltc2378_state *st)
+{
+ struct spi_device *spi = st->spi;
+ struct device *dev = &spi->dev;
+ int ret;
+
+ st->offload = devm_spi_offload_get(dev, spi, <c2378_offload_config);
+ ret = PTR_ERR_OR_ZERO(st->offload);
+ if (ret)
+ return ret;
+
+ ret = ltc2378_spi_offload_setup(indio_dev, st);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to setup SPI offload\n");
+
+ ret = ltc2378_pwm_get(st);
+ if (ret)
+ return ret;
+
+ /*
+ * Start with a slower sampling rate so there is some room for
+ * adjusting the sampling frequency without hitting the maximum
+ * conversion rate.
+ */
+ ret = ltc2378_update_conversion_rate(st, st->info->max_sample_rate_hz >> 4);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to sampling frequency\n");
+
+ ret = ltc2378_prepare_offload_message(&spi->dev, st);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to optimize SPI message\n");
+
+ return 0;
+}
+
+static const struct ltc2378_ops ltc2378_offload_ops = {
+ .buffer_setup = ltc2378_offload_buffer_setup,
+};
+
+int ltc2378_set_offload_ops(struct ltc2378_state *st)
+{
+ st->ops = <c2378_offload_ops;
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(ltc2378_set_offload_ops, "IIO_LTC2378");
+
+MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER");
diff --git a/drivers/iio/adc/ltc2378.c b/drivers/iio/adc/ltc2378.c
index 88582bdcd6a6..bf17b202230b 100644
--- a/drivers/iio/adc/ltc2378.c
+++ b/drivers/iio/adc/ltc2378.c
@@ -17,6 +17,7 @@
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
#include <linux/types.h>
+#include <linux/units.h>
#include <linux/iio/iio.h>
#include <linux/iio/types.h>
@@ -26,120 +27,160 @@
static const struct ltc2378_chip_info ltc2338_18_chip_info = {
.name = "ltc2338-18",
.resolution = 18,
+ .max_sample_rate_hz = HZ_PER_MHZ,
+ .tconv_ns = 527,
.bipolar = true,
};
static const struct ltc2378_chip_info ltc2364_16_chip_info = {
.name = "ltc2364-16",
.resolution = 16,
+ .max_sample_rate_hz = 250 * HZ_PER_KHZ,
+ .tconv_ns = 3000,
.bipolar = false,
};
static const struct ltc2378_chip_info ltc2364_18_chip_info = {
.name = "ltc2364-18",
.resolution = 18,
+ .max_sample_rate_hz = 250 * HZ_PER_KHZ,
+ .tconv_ns = 3000,
.bipolar = false,
};
static const struct ltc2378_chip_info ltc2367_16_chip_info = {
.name = "ltc2367-16",
.resolution = 16,
+ .max_sample_rate_hz = 500 * HZ_PER_KHZ,
+ .tconv_ns = 1500,
.bipolar = false,
};
static const struct ltc2378_chip_info ltc2367_18_chip_info = {
.name = "ltc2367-18",
.resolution = 18,
+ .max_sample_rate_hz = 500 * HZ_PER_KHZ,
+ .tconv_ns = 1500,
.bipolar = false,
};
static const struct ltc2378_chip_info ltc2368_16_chip_info = {
.name = "ltc2368-16",
.resolution = 16,
+ .max_sample_rate_hz = HZ_PER_MHZ,
+ .tconv_ns = 527,
.bipolar = false,
};
static const struct ltc2378_chip_info ltc2368_18_chip_info = {
.name = "ltc2368-18",
.resolution = 18,
+ .max_sample_rate_hz = HZ_PER_MHZ,
+ .tconv_ns = 527,
.bipolar = false,
};
static const struct ltc2378_chip_info ltc2369_18_chip_info = {
.name = "ltc2369-18",
.resolution = 18,
+ .max_sample_rate_hz = 1600 * HZ_PER_KHZ,
+ .tconv_ns = 412,
.bipolar = false,
};
static const struct ltc2378_chip_info ltc2370_16_chip_info = {
.name = "ltc2370-16",
.resolution = 16,
+ .max_sample_rate_hz = 2 * HZ_PER_MHZ,
+ .tconv_ns = 322,
.bipolar = false,
};
static const struct ltc2378_chip_info ltc2376_16_chip_info = {
.name = "ltc2376-16",
.resolution = 16,
+ .max_sample_rate_hz = 250 * HZ_PER_KHZ,
+ .tconv_ns = 3000,
.bipolar = true,
};
static const struct ltc2378_chip_info ltc2376_18_chip_info = {
.name = "ltc2376-18",
.resolution = 18,
+ .max_sample_rate_hz = 250 * HZ_PER_KHZ,
+ .tconv_ns = 3000,
.bipolar = true,
};
static const struct ltc2378_chip_info ltc2376_20_chip_info = {
.name = "ltc2376-20",
.resolution = 20,
+ .max_sample_rate_hz = 250 * HZ_PER_KHZ,
+ .tconv_ns = 3000,
.bipolar = true,
};
static const struct ltc2378_chip_info ltc2377_16_chip_info = {
.name = "ltc2377-16",
.resolution = 16,
+ .max_sample_rate_hz = 500 * HZ_PER_KHZ,
+ .tconv_ns = 1500,
.bipolar = true,
};
static const struct ltc2378_chip_info ltc2377_18_chip_info = {
.name = "ltc2377-18",
.resolution = 18,
+ .max_sample_rate_hz = 500 * HZ_PER_KHZ,
+ .tconv_ns = 1500,
.bipolar = true,
};
static const struct ltc2378_chip_info ltc2377_20_chip_info = {
.name = "ltc2377-20",
.resolution = 20,
+ .max_sample_rate_hz = 500 * HZ_PER_KHZ,
+ .tconv_ns = 1500,
.bipolar = true,
};
static const struct ltc2378_chip_info ltc2378_16_chip_info = {
.name = "ltc2378-16",
.resolution = 16,
+ .max_sample_rate_hz = HZ_PER_MHZ,
+ .tconv_ns = 527,
.bipolar = true,
};
static const struct ltc2378_chip_info ltc2378_18_chip_info = {
.name = "ltc2378-18",
.resolution = 18,
+ .max_sample_rate_hz = HZ_PER_MHZ,
+ .tconv_ns = 527,
.bipolar = true,
};
static const struct ltc2378_chip_info ltc2378_20_chip_info = {
.name = "ltc2378-20",
.resolution = 20,
+ .max_sample_rate_hz = HZ_PER_MHZ,
+ .tconv_ns = 675,
.bipolar = true,
};
static const struct ltc2378_chip_info ltc2379_18_chip_info = {
.name = "ltc2379-18",
.resolution = 18,
+ .max_sample_rate_hz = 1600 * HZ_PER_KHZ,
+ .tconv_ns = 412,
.bipolar = true,
};
static const struct ltc2378_chip_info ltc2380_16_chip_info = {
.name = "ltc2380-16",
.resolution = 16,
+ .max_sample_rate_hz = 2 * HZ_PER_MHZ,
+ .tconv_ns = 322,
.bipolar = true,
};
@@ -266,6 +307,10 @@ static int ltc2378_probe(struct spi_device *spi)
st->xfer.len = st->info->resolution > 16 ? 4 : 2;
st->xfer.bits_per_word = st->info->resolution;
+ ret = ltc2378_lib_buffer_setup(indio_dev, st);
+ if (ret)
+ return ret;
+
indio_dev->channels = st->chans;
indio_dev->num_channels = st->num_iio_chans;
@@ -336,3 +381,4 @@ MODULE_AUTHOR("Ioan-Daniel Pop <pop.ioan-daniel@analog.com>");
MODULE_AUTHOR("Marcelo Schmitt <marcelo.schmitt@analog.com>");
MODULE_DESCRIPTION("Analog Devices LTC2378 ADC series driver");
MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_LTC2378");
diff --git a/drivers/iio/adc/ltc2378.h b/drivers/iio/adc/ltc2378.h
index a3a69351de6c..f6e10f9a83e0 100644
--- a/drivers/iio/adc/ltc2378.h
+++ b/drivers/iio/adc/ltc2378.h
@@ -8,10 +8,14 @@
#ifndef __DRIVERS_IIO_ADC_LTC2378_H__
#define __DRIVERS_IIO_ADC_LTC2378_H__
+#include <linux/errno.h>
#include <linux/iio/iio.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
+#include <linux/pwm.h>
#include <linux/spi/spi.h>
+#include <linux/spi/offload/consumer.h>
+#include <linux/spi/offload/types.h>
#include <linux/types.h>
#include <linux/units.h>
@@ -19,12 +23,24 @@
#define LTC2378_TBUSYLH_NS 13
#define LTC2378_TCNV_HIGH_NS 20
+struct ltc2378_state;
+
struct ltc2378_chip_info {
const char *name;
int resolution;
+ unsigned int max_sample_rate_hz;
+ unsigned int tconv_ns;
bool bipolar;
};
+/**
+ * struct ltc2378_ops: Setup specific procedures for ltc2378 devices.
+ * @ltc2378_buffer_setup: Custom buffer setup implementation.
+ */
+struct ltc2378_ops {
+ int (*buffer_setup)(struct iio_dev *indio_dev, struct ltc2378_state *st);
+};
+
struct ltc2378_state {
const struct ltc2378_chip_info *info;
struct gpio_desc *cnv_gpio;
@@ -33,6 +49,15 @@ struct ltc2378_state {
unsigned int num_iio_chans;
struct iio_chan_spec chans[2]; /* 1 physical chan + 1 timestamp chan */
int ref_uV;
+ const struct ltc2378_ops *ops;
+ unsigned int cnv_Hz;
+ struct pwm_waveform cnv_wf;
+ struct spi_offload *offload;
+ struct spi_offload_trigger *offload_trigger;
+ struct spi_message offload_msg;
+ struct spi_transfer offload_xfer;
+ struct spi_offload_trigger_config offload_trigger_config;
+ struct pwm_device *cnv_trigger;
/*
* DMA (thus cache coherency maintenance) requires the
@@ -60,4 +85,21 @@ static inline int ltc2378_convert_and_acquire(struct ltc2378_state *st)
return ret;
}
+int ltc2378_lib_buffer_setup(struct iio_dev *indio_dev, struct ltc2378_state *st);
+
+#define __ltc2378_set_offload_ops(st) ltc2378_set_offload_ops((st))
+
+#ifdef CONFIG_LTC2378_LIB_OFFLOAD_BUFFER
+
+int ltc2378_set_offload_ops(struct ltc2378_state *st);
+
+#else
+
+static inline int ltc2378_set_offload_ops(struct ltc2378_state *st)
+{
+ return -EOPNOTSUPP;
+}
+
+#endif /* CONFIG_LTC2378_LIB_OFFLOAD_BUFFER */
+
#endif /* __DRIVERS_IIO_ADC_LTC2378_H__ */
--
2.53.0
^ permalink raw reply related
* [RFC PATCH v3 3/5] iio: buffer: Extend DMAengine buffer interfaces to take extra sysfs attributes
From: Marcelo Schmitt @ 2026-06-17 2:03 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel
Cc: jic23, nuno.sa, Michael.Hennerich, dlechner, andy, robh, krzk+dt,
conor+dt, pop.ioan-daniel, marcelo.schmitt1
In-Reply-To: <cover.1781661028.git.marcelo.schmitt@analog.com>
Some devices using DMAengine buffers are connected to extra hardware that
allows setting how fast data is transferred to/from the buffer. However,
those extra pieces of harwdware are external to the sensor chip such that
supporting the transfer speed as a sensor property is a bit of an
inaccuracy. Expand IIO DMAengine buffer interfaces to take arguments for
extra sysfs attributes, enabling the transfer speed to be configured
through the buffer interface.
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
New patch.
Now that I've come to this buffer "solution", I have pretty much convinced
myself it would be better to instead have some sort of IIO trigger to control
the signal source connected to SPI offloading trigger module.
drivers/iio/adc/ad4000.c | 3 ++-
drivers/iio/adc/ad4030.c | 3 ++-
drivers/iio/adc/ad4691.c | 3 ++-
drivers/iio/adc/ad4695.c | 2 +-
drivers/iio/adc/ad7380.c | 2 +-
drivers/iio/adc/ad7606_spi.c | 2 +-
drivers/iio/adc/ad7768-1.c | 3 ++-
drivers/iio/adc/ad7944.c | 2 +-
drivers/iio/adc/ad_sigma_delta.c | 2 +-
.../buffer/industrialio-buffer-dmaengine.c | 19 ++++++++++++-------
drivers/iio/dac/ad5791.c | 2 +-
drivers/iio/dac/ad8460.c | 2 +-
drivers/iio/dac/adi-axi-dac.c | 2 +-
include/linux/iio/buffer-dmaengine.h | 16 ++++++++++------
14 files changed, 38 insertions(+), 25 deletions(-)
diff --git a/drivers/iio/adc/ad4000.c b/drivers/iio/adc/ad4000.c
index fd3d79fca785..b79b627310af 100644
--- a/drivers/iio/adc/ad4000.c
+++ b/drivers/iio/adc/ad4000.c
@@ -871,7 +871,8 @@ static int ad4000_spi_offload_setup(struct iio_dev *indio_dev,
"Failed to get offload RX DMA\n");
ret = devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, rx_dma,
- IIO_BUFFER_DIRECTION_IN);
+ IIO_BUFFER_DIRECTION_IN,
+ NULL);
if (ret)
return dev_err_probe(dev, ret, "Failed to setup DMA buffer\n");
diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c
index 9c5f19321e3b..713fe95176c0 100644
--- a/drivers/iio/adc/ad4030.c
+++ b/drivers/iio/adc/ad4030.c
@@ -1400,7 +1400,8 @@ static int ad4030_spi_offload_setup(struct iio_dev *indio_dev,
"failed to get offload RX DMA\n");
return devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, rx_dma,
- IIO_BUFFER_DIRECTION_IN);
+ IIO_BUFFER_DIRECTION_IN,
+ NULL);
}
static int ad4030_setup_pga(struct device *dev, struct iio_dev *indio_dev,
diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
index 548678adc2a4..9845fe9c4725 100644
--- a/drivers/iio/adc/ad4691.c
+++ b/drivers/iio/adc/ad4691.c
@@ -1980,7 +1980,8 @@ static int ad4691_setup_offload(struct iio_dev *indio_dev,
indio_dev->setup_ops = &ad4691_cnv_burst_offload_buffer_setup_ops;
ret = devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, rx_dma,
- IIO_BUFFER_DIRECTION_IN);
+ IIO_BUFFER_DIRECTION_IN,
+ NULL);
if (ret)
return ret;
diff --git a/drivers/iio/adc/ad4695.c b/drivers/iio/adc/ad4695.c
index 53642de7330d..78faeade19f5 100644
--- a/drivers/iio/adc/ad4695.c
+++ b/drivers/iio/adc/ad4695.c
@@ -1779,7 +1779,7 @@ static int ad4695_probe_spi_offload(struct iio_dev *indio_dev,
}
return devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev,
- rx_dma, IIO_BUFFER_DIRECTION_IN);
+ rx_dma, IIO_BUFFER_DIRECTION_IN, NULL);
}
static const struct spi_offload_config ad4695_spi_offload_config = {
diff --git a/drivers/iio/adc/ad7380.c b/drivers/iio/adc/ad7380.c
index 9f77990a03f9..415b8e1be920 100644
--- a/drivers/iio/adc/ad7380.c
+++ b/drivers/iio/adc/ad7380.c
@@ -1874,7 +1874,7 @@ static int ad7380_probe_spi_offload(struct iio_dev *indio_dev,
"failed to get offload RX DMA\n");
ret = devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev,
- rx_dma, IIO_BUFFER_DIRECTION_IN);
+ rx_dma, IIO_BUFFER_DIRECTION_IN, NULL);
if (ret)
return dev_err_probe(dev, ret, "cannot setup dma buffer\n");
diff --git a/drivers/iio/adc/ad7606_spi.c b/drivers/iio/adc/ad7606_spi.c
index 7e17ccbcedd0..760b54bfe208 100644
--- a/drivers/iio/adc/ad7606_spi.c
+++ b/drivers/iio/adc/ad7606_spi.c
@@ -317,7 +317,7 @@ static int ad7606_spi_offload_probe(struct device *dev,
"failed to get offload RX DMA\n");
ret = devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev,
- rx_dma, IIO_BUFFER_DIRECTION_IN);
+ rx_dma, IIO_BUFFER_DIRECTION_IN, NULL);
if (ret)
return dev_err_probe(dev, ret,
"failed to setup offload RX DMA\n");
diff --git a/drivers/iio/adc/ad7768-1.c b/drivers/iio/adc/ad7768-1.c
index 598936e47fd2..a13ac31281d8 100644
--- a/drivers/iio/adc/ad7768-1.c
+++ b/drivers/iio/adc/ad7768-1.c
@@ -1792,7 +1792,8 @@ static int ad7768_spi_offload_probe(struct iio_dev *indio_dev,
return dev_err_probe(dev, PTR_ERR(rx_dma), "failed to get offload RX DMA\n");
ret = devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, rx_dma,
- IIO_BUFFER_DIRECTION_IN);
+ IIO_BUFFER_DIRECTION_IN,
+ NULL);
if (ret)
return dev_err_probe(dev, ret, "failed to setup offload RX DMA\n");
diff --git a/drivers/iio/adc/ad7944.c b/drivers/iio/adc/ad7944.c
index 7722cf9e8214..63852a6c2acc 100644
--- a/drivers/iio/adc/ad7944.c
+++ b/drivers/iio/adc/ad7944.c
@@ -843,7 +843,7 @@ static int ad7944_probe(struct spi_device *spi)
*/
ret = devm_iio_dmaengine_buffer_setup_with_handle(dev,
- indio_dev, rx_dma, IIO_BUFFER_DIRECTION_IN);
+ indio_dev, rx_dma, IIO_BUFFER_DIRECTION_IN, NULL);
if (ret)
return ret;
diff --git a/drivers/iio/adc/ad_sigma_delta.c b/drivers/iio/adc/ad_sigma_delta.c
index a955556f9ec8..4b88e1c72c87 100644
--- a/drivers/iio/adc/ad_sigma_delta.c
+++ b/drivers/iio/adc/ad_sigma_delta.c
@@ -820,7 +820,7 @@ int devm_ad_sd_setup_buffer_and_trigger(struct device *dev, struct iio_dev *indi
"Failed to get RX DMA channel\n");
ret = devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev,
- rx_dma, IIO_BUFFER_DIRECTION_IN);
+ rx_dma, IIO_BUFFER_DIRECTION_IN, NULL);
if (ret)
return dev_err_probe(dev, ret, "Cannot setup DMA buffer\n");
diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
index 98acce909854..343dbb6d1446 100644
--- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c
+++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
@@ -289,7 +289,8 @@ EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_teardown, "IIO_DMAENGINE_BUFFER");
static struct iio_buffer
*__iio_dmaengine_buffer_setup_ext(struct iio_dev *indio_dev,
struct dma_chan *chan,
- enum iio_buffer_direction dir)
+ enum iio_buffer_direction dir,
+ const struct iio_dev_attr **buffer_attrs)
{
struct iio_buffer *buffer;
int ret;
@@ -301,6 +302,7 @@ static struct iio_buffer
indio_dev->modes |= INDIO_BUFFER_HARDWARE;
buffer->direction = dir;
+ buffer->attrs = buffer_attrs;
ret = iio_device_attach_buffer(indio_dev, buffer);
if (ret) {
@@ -329,7 +331,8 @@ static struct iio_buffer
struct iio_buffer *iio_dmaengine_buffer_setup_ext(struct device *dev,
struct iio_dev *indio_dev,
const char *channel,
- enum iio_buffer_direction dir)
+ enum iio_buffer_direction dir,
+ const struct iio_dev_attr **buffer_attrs)
{
struct dma_chan *chan;
struct iio_buffer *buffer;
@@ -338,7 +341,7 @@ struct iio_buffer *iio_dmaengine_buffer_setup_ext(struct device *dev,
if (IS_ERR(chan))
return ERR_CAST(chan);
- buffer = __iio_dmaengine_buffer_setup_ext(indio_dev, chan, dir);
+ buffer = __iio_dmaengine_buffer_setup_ext(indio_dev, chan, dir, buffer_attrs);
if (IS_ERR(buffer))
dma_release_channel(chan);
@@ -366,11 +369,12 @@ static void devm_iio_dmaengine_buffer_teardown(void *buffer)
int devm_iio_dmaengine_buffer_setup_ext(struct device *dev,
struct iio_dev *indio_dev,
const char *channel,
- enum iio_buffer_direction dir)
+ enum iio_buffer_direction dir,
+ const struct iio_dev_attr **buffer_attrs)
{
struct iio_buffer *buffer;
- buffer = iio_dmaengine_buffer_setup_ext(dev, indio_dev, channel, dir);
+ buffer = iio_dmaengine_buffer_setup_ext(dev, indio_dev, channel, dir, buffer_attrs);
if (IS_ERR(buffer))
return PTR_ERR(buffer);
@@ -403,11 +407,12 @@ static void devm_iio_dmaengine_buffer_free(void *buffer)
int devm_iio_dmaengine_buffer_setup_with_handle(struct device *dev,
struct iio_dev *indio_dev,
struct dma_chan *chan,
- enum iio_buffer_direction dir)
+ enum iio_buffer_direction dir,
+ const struct iio_dev_attr **buffer_attrs)
{
struct iio_buffer *buffer;
- buffer = __iio_dmaengine_buffer_setup_ext(indio_dev, chan, dir);
+ buffer = __iio_dmaengine_buffer_setup_ext(indio_dev, chan, dir, buffer_attrs);
if (IS_ERR(buffer))
return PTR_ERR(buffer);
diff --git a/drivers/iio/dac/ad5791.c b/drivers/iio/dac/ad5791.c
index ae7297f08398..024607adb915 100644
--- a/drivers/iio/dac/ad5791.c
+++ b/drivers/iio/dac/ad5791.c
@@ -448,7 +448,7 @@ static int ad5791_offload_setup(struct iio_dev *indio_dev)
"failed to get offload TX DMA\n");
ret = devm_iio_dmaengine_buffer_setup_with_handle(&spi->dev,
- indio_dev, tx_dma, IIO_BUFFER_DIRECTION_OUT);
+ indio_dev, tx_dma, IIO_BUFFER_DIRECTION_OUT, NULL);
if (ret)
return ret;
diff --git a/drivers/iio/dac/ad8460.c b/drivers/iio/dac/ad8460.c
index 6e45686902dd..3c5fd71d5add 100644
--- a/drivers/iio/dac/ad8460.c
+++ b/drivers/iio/dac/ad8460.c
@@ -921,7 +921,7 @@ static int ad8460_probe(struct spi_device *spi)
indio_dev->setup_ops = &ad8460_buffer_setup_ops;
ret = devm_iio_dmaengine_buffer_setup_ext(dev, indio_dev, "tx",
- IIO_BUFFER_DIRECTION_OUT);
+ IIO_BUFFER_DIRECTION_OUT, NULL);
if (ret)
return dev_err_probe(dev, ret,
"Failed to get DMA buffer\n");
diff --git a/drivers/iio/dac/adi-axi-dac.c b/drivers/iio/dac/adi-axi-dac.c
index 451fad34e7ee..23028a92ceba 100644
--- a/drivers/iio/dac/adi-axi-dac.c
+++ b/drivers/iio/dac/adi-axi-dac.c
@@ -162,7 +162,7 @@ static struct iio_buffer *axi_dac_request_buffer(struct iio_backend *back,
dma_name = "tx";
return iio_dmaengine_buffer_setup_ext(st->dev, indio_dev, dma_name,
- IIO_BUFFER_DIRECTION_OUT);
+ IIO_BUFFER_DIRECTION_OUT, NULL);
}
static void axi_dac_free_buffer(struct iio_backend *back,
diff --git a/include/linux/iio/buffer-dmaengine.h b/include/linux/iio/buffer-dmaengine.h
index 37f27545f69f..bb7a348db72d 100644
--- a/include/linux/iio/buffer-dmaengine.h
+++ b/include/linux/iio/buffer-dmaengine.h
@@ -10,6 +10,7 @@
#include <linux/iio/buffer.h>
struct iio_dev;
+struct iio_dev_attr;
struct device;
struct dma_chan;
@@ -17,23 +18,26 @@ void iio_dmaengine_buffer_teardown(struct iio_buffer *buffer);
struct iio_buffer *iio_dmaengine_buffer_setup_ext(struct device *dev,
struct iio_dev *indio_dev,
const char *channel,
- enum iio_buffer_direction dir);
+ enum iio_buffer_direction dir,
+ const struct iio_dev_attr **buffer_attrs);
#define iio_dmaengine_buffer_setup(dev, indio_dev, channel) \
- iio_dmaengine_buffer_setup_ext(dev, indio_dev, channel, \
- IIO_BUFFER_DIRECTION_IN)
+ iio_dmaengine_buffer_setup_ext(dev, indio_dev, channel, \
+ IIO_BUFFER_DIRECTION_IN, NULL)
int devm_iio_dmaengine_buffer_setup_ext(struct device *dev,
struct iio_dev *indio_dev,
const char *channel,
- enum iio_buffer_direction dir);
+ enum iio_buffer_direction dir,
+ const struct iio_dev_attr **buffer_attrs);
int devm_iio_dmaengine_buffer_setup_with_handle(struct device *dev,
struct iio_dev *indio_dev,
struct dma_chan *chan,
- enum iio_buffer_direction dir);
+ enum iio_buffer_direction dir,
+ const struct iio_dev_attr **buffer_attrs);
#define devm_iio_dmaengine_buffer_setup(dev, indio_dev, channel) \
devm_iio_dmaengine_buffer_setup_ext(dev, indio_dev, channel, \
- IIO_BUFFER_DIRECTION_IN)
+ IIO_BUFFER_DIRECTION_IN, NULL)
#endif
--
2.53.0
^ permalink raw reply related
* [PATCH v3 2/5] iio: adc: ltc2378: Add support for LTC2378-20 and similar ADCs
From: Marcelo Schmitt @ 2026-06-17 2:03 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel
Cc: jic23, nuno.sa, Michael.Hennerich, dlechner, andy, robh, krzk+dt,
conor+dt, pop.ioan-daniel, marcelo.schmitt1
In-Reply-To: <cover.1781661028.git.marcelo.schmitt@analog.com>
Support for LTC2378-20 and similar analog-to-digital converters.
Co-developed-by: Ioan-Daniel Pop <pop.ioan-daniel@analog.com>
Signed-off-by: Ioan-Daniel Pop <pop.ioan-daniel@analog.com>
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
Change log v2 -> v3:
- Restricted LTC2378 dependency list to require GPIO
- Use datasheet terminology to indicate polarity/output code.
- Added missing block scope to IIO_DEV_ACQUIRE_DIRECT_MODE.
MAINTAINERS | 1 +
drivers/iio/adc/Kconfig | 11 ++
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/ltc2378.c | 338 ++++++++++++++++++++++++++++++++++++++
drivers/iio/adc/ltc2378.h | 63 +++++++
5 files changed, 414 insertions(+)
create mode 100644 drivers/iio/adc/ltc2378.c
create mode 100644 drivers/iio/adc/ltc2378.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 205acb4b0789..a11c00a78c13 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15226,6 +15226,7 @@ L: linux-iio@vger.kernel.org
S: Supported
W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
+F: drivers/iio/adc/ltc2378*
LTC2664 IIO DAC DRIVER
M: Michael Hennerich <michael.hennerich@analog.com>
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index a3a93a47b43d..2b8203451367 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -940,6 +940,17 @@ config LTC2309
This driver can also be built as a module. If so, the module will
be called ltc2309.
+config LTC2378
+ tristate "Analog Devices LTC2378 ADC driver"
+ depends on SPI
+ depends on GPIOLIB
+ help
+ Say yes here to build support for Analog Devices LTC2378-20 and
+ similar analog to digital converters.
+
+ This driver can also be built as a module. If so, the module will
+ be called ltc2378.
+
config LTC2471
tristate "Linear Technology LTC2471 and LTC2473 ADC driver"
depends on I2C
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 707dd708912f..1814fb78dde3 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -81,6 +81,7 @@ obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o
obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o
obj-$(CONFIG_LPC32XX_ADC) += lpc32xx_adc.o
obj-$(CONFIG_LTC2309) += ltc2309.o
+obj-$(CONFIG_LTC2378) += ltc2378.o
obj-$(CONFIG_LTC2471) += ltc2471.o
obj-$(CONFIG_LTC2485) += ltc2485.o
obj-$(CONFIG_LTC2496) += ltc2496.o ltc2497-core.o
diff --git a/drivers/iio/adc/ltc2378.c b/drivers/iio/adc/ltc2378.c
new file mode 100644
index 000000000000..88582bdcd6a6
--- /dev/null
+++ b/drivers/iio/adc/ltc2378.c
@@ -0,0 +1,338 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Analog Devices LTC2378 ADC series driver
+ *
+ * Copyright (C) 2026 Analog Devices Inc.
+ * Author: Ioan-Daniel Pop <pop.ioan-daniel@analog.com>
+ * Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/types.h>
+
+#include "ltc2378.h"
+
+static const struct ltc2378_chip_info ltc2338_18_chip_info = {
+ .name = "ltc2338-18",
+ .resolution = 18,
+ .bipolar = true,
+};
+
+static const struct ltc2378_chip_info ltc2364_16_chip_info = {
+ .name = "ltc2364-16",
+ .resolution = 16,
+ .bipolar = false,
+};
+
+static const struct ltc2378_chip_info ltc2364_18_chip_info = {
+ .name = "ltc2364-18",
+ .resolution = 18,
+ .bipolar = false,
+};
+
+static const struct ltc2378_chip_info ltc2367_16_chip_info = {
+ .name = "ltc2367-16",
+ .resolution = 16,
+ .bipolar = false,
+};
+
+static const struct ltc2378_chip_info ltc2367_18_chip_info = {
+ .name = "ltc2367-18",
+ .resolution = 18,
+ .bipolar = false,
+};
+
+static const struct ltc2378_chip_info ltc2368_16_chip_info = {
+ .name = "ltc2368-16",
+ .resolution = 16,
+ .bipolar = false,
+};
+
+static const struct ltc2378_chip_info ltc2368_18_chip_info = {
+ .name = "ltc2368-18",
+ .resolution = 18,
+ .bipolar = false,
+};
+
+static const struct ltc2378_chip_info ltc2369_18_chip_info = {
+ .name = "ltc2369-18",
+ .resolution = 18,
+ .bipolar = false,
+};
+
+static const struct ltc2378_chip_info ltc2370_16_chip_info = {
+ .name = "ltc2370-16",
+ .resolution = 16,
+ .bipolar = false,
+};
+
+static const struct ltc2378_chip_info ltc2376_16_chip_info = {
+ .name = "ltc2376-16",
+ .resolution = 16,
+ .bipolar = true,
+};
+
+static const struct ltc2378_chip_info ltc2376_18_chip_info = {
+ .name = "ltc2376-18",
+ .resolution = 18,
+ .bipolar = true,
+};
+
+static const struct ltc2378_chip_info ltc2376_20_chip_info = {
+ .name = "ltc2376-20",
+ .resolution = 20,
+ .bipolar = true,
+};
+
+static const struct ltc2378_chip_info ltc2377_16_chip_info = {
+ .name = "ltc2377-16",
+ .resolution = 16,
+ .bipolar = true,
+};
+
+static const struct ltc2378_chip_info ltc2377_18_chip_info = {
+ .name = "ltc2377-18",
+ .resolution = 18,
+ .bipolar = true,
+};
+
+static const struct ltc2378_chip_info ltc2377_20_chip_info = {
+ .name = "ltc2377-20",
+ .resolution = 20,
+ .bipolar = true,
+};
+
+static const struct ltc2378_chip_info ltc2378_16_chip_info = {
+ .name = "ltc2378-16",
+ .resolution = 16,
+ .bipolar = true,
+};
+
+static const struct ltc2378_chip_info ltc2378_18_chip_info = {
+ .name = "ltc2378-18",
+ .resolution = 18,
+ .bipolar = true,
+};
+
+static const struct ltc2378_chip_info ltc2378_20_chip_info = {
+ .name = "ltc2378-20",
+ .resolution = 20,
+ .bipolar = true,
+};
+
+static const struct ltc2378_chip_info ltc2379_18_chip_info = {
+ .name = "ltc2379-18",
+ .resolution = 18,
+ .bipolar = true,
+};
+
+static const struct ltc2378_chip_info ltc2380_16_chip_info = {
+ .name = "ltc2380-16",
+ .resolution = 16,
+ .bipolar = true,
+};
+
+static int ltc2378_channel_single_read(const struct iio_chan_spec *chan,
+ struct ltc2378_state *st, int *val)
+{
+ const struct iio_scan_type *scan_type = &chan->scan_type;
+ u32 sample;
+ int ret;
+
+ ret = ltc2378_convert_and_acquire(st);
+ if (ret)
+ return ret;
+
+ if (scan_type->realbits > 16)
+ sample = st->scan.data.sample_buf32;
+ else
+ sample = st->scan.data.sample_buf16;
+
+ if (scan_type->format == IIO_SCAN_FORMAT_SIGNED_INT)
+ *val = sign_extend32(sample, scan_type->realbits - 1);
+ else
+ *val = sample;
+
+ return 0;
+}
+
+static int ltc2378_read_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2, long info)
+{
+ struct ltc2378_state *st = iio_priv(indio_dev);
+ int ret;
+
+ switch (info) {
+ case IIO_CHAN_INFO_RAW: {
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ ret = ltc2378_channel_single_read(chan, st, val);
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ *val = st->ref_uV / MILLI;
+ /*
+ * For all LTC2378-like devices, the amount of bits that express
+ * voltage magnitude depend on the polarity / output code format:
+ * - straight binary: All precision/resolution bits are used.
+ * - 2's complement: One of the precision bits is used for sign.
+ */
+ if (st->info->bipolar)
+ *val2 = st->info->resolution - 1;
+ else
+ *val2 = st->info->resolution;
+
+ return IIO_VAL_FRACTIONAL_LOG2;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info ltc2378_iio_info = {
+ .read_raw = <c2378_read_raw,
+};
+
+static int ltc2378_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct iio_dev *indio_dev;
+ struct ltc2378_state *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ st->spi = spi;
+
+ ret = devm_regulator_get_enable_read_voltage(dev, "ref");
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to read ref regulator\n");
+
+ st->ref_uV = ret;
+ st->info = spi_get_device_match_data(spi);
+ if (!st->info)
+ return -EINVAL;
+
+ indio_dev->name = st->info->name;
+ indio_dev->info = <c2378_iio_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ st->cnv_gpio = devm_gpiod_get(dev, "cnv", GPIOD_OUT_LOW);
+ if (IS_ERR(st->cnv_gpio))
+ return dev_err_probe(dev, PTR_ERR(st->cnv_gpio),
+ "failed to get CNV GPIO");
+
+ st->num_iio_chans = 0;
+ st->chans[st->num_iio_chans++] = (struct iio_chan_spec) {
+ .type = IIO_VOLTAGE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .scan_index = 0,
+ .scan_type = {
+ .format = st->info->bipolar ? IIO_SCAN_FORMAT_SIGNED_INT :
+ IIO_SCAN_FORMAT_UNSIGNED_INT,
+ .realbits = st->info->resolution,
+ /*
+ * Buffer elements could be 16-bit for low precision
+ * parts. Though, using more storage bits allows keeping
+ * the same scan_type configuration for both types of
+ * buffer support.
+ */
+ .storagebits = 32,
+ },
+ };
+
+ st->xfer.rx_buf = &st->scan.data;
+ st->xfer.len = st->info->resolution > 16 ? 4 : 2;
+ st->xfer.bits_per_word = st->info->resolution;
+
+ indio_dev->channels = st->chans;
+ indio_dev->num_channels = st->num_iio_chans;
+
+ return devm_iio_device_register(&spi->dev, indio_dev);
+}
+
+static const struct of_device_id ltc2378_of_match[] = {
+ { .compatible = "adi,ltc2338-18", .data = <c2338_18_chip_info },
+ { .compatible = "adi,ltc2364-16", .data = <c2364_16_chip_info },
+ { .compatible = "adi,ltc2364-18", .data = <c2364_18_chip_info },
+ { .compatible = "adi,ltc2367-16", .data = <c2367_16_chip_info },
+ { .compatible = "adi,ltc2367-18", .data = <c2367_18_chip_info },
+ { .compatible = "adi,ltc2368-16", .data = <c2368_16_chip_info },
+ { .compatible = "adi,ltc2368-18", .data = <c2368_18_chip_info },
+ { .compatible = "adi,ltc2369-18", .data = <c2369_18_chip_info },
+ { .compatible = "adi,ltc2370-16", .data = <c2370_16_chip_info },
+ { .compatible = "adi,ltc2376-16", .data = <c2376_16_chip_info },
+ { .compatible = "adi,ltc2376-18", .data = <c2376_18_chip_info },
+ { .compatible = "adi,ltc2376-20", .data = <c2376_20_chip_info },
+ { .compatible = "adi,ltc2377-16", .data = <c2377_16_chip_info },
+ { .compatible = "adi,ltc2377-18", .data = <c2377_18_chip_info },
+ { .compatible = "adi,ltc2377-20", .data = <c2377_20_chip_info },
+ { .compatible = "adi,ltc2378-16", .data = <c2378_16_chip_info },
+ { .compatible = "adi,ltc2378-18", .data = <c2378_18_chip_info },
+ { .compatible = "adi,ltc2378-20", .data = <c2378_20_chip_info },
+ { .compatible = "adi,ltc2379-18", .data = <c2379_18_chip_info },
+ { .compatible = "adi,ltc2380-16", .data = <c2380_16_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ltc2378_of_match);
+
+static const struct spi_device_id ltc2378_spi_id[] = {
+ { .name = "ltc2338-18", .driver_data = (kernel_ulong_t)<c2338_18_chip_info },
+ { .name = "ltc2364-16", .driver_data = (kernel_ulong_t)<c2364_16_chip_info },
+ { .name = "ltc2364-18", .driver_data = (kernel_ulong_t)<c2364_18_chip_info },
+ { .name = "ltc2367-16", .driver_data = (kernel_ulong_t)<c2367_16_chip_info },
+ { .name = "ltc2367-18", .driver_data = (kernel_ulong_t)<c2367_18_chip_info },
+ { .name = "ltc2368-16", .driver_data = (kernel_ulong_t)<c2368_16_chip_info },
+ { .name = "ltc2368-18", .driver_data = (kernel_ulong_t)<c2368_18_chip_info },
+ { .name = "ltc2369-18", .driver_data = (kernel_ulong_t)<c2369_18_chip_info },
+ { .name = "ltc2370-16", .driver_data = (kernel_ulong_t)<c2370_16_chip_info },
+ { .name = "ltc2376-16", .driver_data = (kernel_ulong_t)<c2376_16_chip_info },
+ { .name = "ltc2376-18", .driver_data = (kernel_ulong_t)<c2376_18_chip_info },
+ { .name = "ltc2376-20", .driver_data = (kernel_ulong_t)<c2376_20_chip_info },
+ { .name = "ltc2377-16", .driver_data = (kernel_ulong_t)<c2377_16_chip_info },
+ { .name = "ltc2377-18", .driver_data = (kernel_ulong_t)<c2377_18_chip_info },
+ { .name = "ltc2377-20", .driver_data = (kernel_ulong_t)<c2377_20_chip_info },
+ { .name = "ltc2378-16", .driver_data = (kernel_ulong_t)<c2378_16_chip_info },
+ { .name = "ltc2378-18", .driver_data = (kernel_ulong_t)<c2378_18_chip_info },
+ { .name = "ltc2378-20", .driver_data = (kernel_ulong_t)<c2378_20_chip_info },
+ { .name = "ltc2379-18", .driver_data = (kernel_ulong_t)<c2379_18_chip_info },
+ { .name = "ltc2380-16", .driver_data = (kernel_ulong_t)<c2380_16_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ltc2378_spi_id);
+
+static struct spi_driver ltc2378_driver = {
+ .driver = {
+ .name = "ltc2378",
+ .of_match_table = ltc2378_of_match
+ },
+ .probe = ltc2378_probe,
+ .id_table = ltc2378_spi_id,
+};
+module_spi_driver(ltc2378_driver);
+
+MODULE_AUTHOR("Ioan-Daniel Pop <pop.ioan-daniel@analog.com>");
+MODULE_AUTHOR("Marcelo Schmitt <marcelo.schmitt@analog.com>");
+MODULE_DESCRIPTION("Analog Devices LTC2378 ADC series driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/adc/ltc2378.h b/drivers/iio/adc/ltc2378.h
new file mode 100644
index 000000000000..a3a69351de6c
--- /dev/null
+++ b/drivers/iio/adc/ltc2378.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Analog Devices LTC2378 and similar ADCs common definitions and properties
+ * Copyright (C) 2026 Analog Devices, Inc.
+ * Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
+ */
+
+#ifndef __DRIVERS_IIO_ADC_LTC2378_H__
+#define __DRIVERS_IIO_ADC_LTC2378_H__
+
+#include <linux/iio/iio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/delay.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#define LTC2378_TDSDOBUSYL_NS 5
+#define LTC2378_TBUSYLH_NS 13
+#define LTC2378_TCNV_HIGH_NS 20
+
+struct ltc2378_chip_info {
+ const char *name;
+ int resolution;
+ bool bipolar;
+};
+
+struct ltc2378_state {
+ const struct ltc2378_chip_info *info;
+ struct gpio_desc *cnv_gpio;
+ struct spi_device *spi;
+ struct spi_transfer xfer;
+ unsigned int num_iio_chans;
+ struct iio_chan_spec chans[2]; /* 1 physical chan + 1 timestamp chan */
+ int ref_uV;
+
+ /*
+ * DMA (thus cache coherency maintenance) requires the
+ * transfer buffers to live in their own cache lines.
+ */
+ struct {
+ union {
+ u16 sample_buf16;
+ u32 sample_buf32;
+ } data;
+ aligned_s64 timestamp;
+ } scan __aligned(IIO_DMA_MINALIGN);
+};
+
+static inline int ltc2378_convert_and_acquire(struct ltc2378_state *st)
+{
+ int ret;
+
+ /* Cause a rising edge of CNV to initiate a new ADC conversion */
+ gpiod_set_value_cansleep(st->cnv_gpio, 1);
+ fsleep(4);
+ ret = spi_sync_transfer(st->spi, &st->xfer, 1);
+ gpiod_set_value_cansleep(st->cnv_gpio, 0);
+
+ return ret;
+}
+
+#endif /* __DRIVERS_IIO_ADC_LTC2378_H__ */
--
2.53.0
^ permalink raw reply related
* [PATCH v3 1/5] dt-bindings: iio: adc: Add ltc2378
From: Marcelo Schmitt @ 2026-06-17 2:03 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel
Cc: jic23, nuno.sa, Michael.Hennerich, dlechner, andy, robh, krzk+dt,
conor+dt, pop.ioan-daniel, marcelo.schmitt1
In-Reply-To: <cover.1781661028.git.marcelo.schmitt@analog.com>
Document how to describe LTC2378-20 and similar ADCs in device tree.
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
Change log v2 -> v3:
- Re-added device tree fallback compatibles for LTC2378 chips, now with options
to provide a single compatible string or a pair of single compatible string
plus a fallback string to a slower sample rate spec in case a driver for the
specific part is not found.
.../bindings/iio/adc/adi,ltc2378.yaml | 160 ++++++++++++++++++
MAINTAINERS | 7 +
2 files changed, 167 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
new file mode 100644
index 000000000000..7d30a2cade8f
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
@@ -0,0 +1,160 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/adi,ltc2378.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices LTC2378 and similar Analog to Digital Converters
+
+maintainers:
+ - Marcelo Schmitt <marcelo.schmitt@analog.com>
+
+description: |
+ Analog Devices LTC2378 series of ADCs.
+ Specifications can be found at:
+ https://www.analog.com/media/en/technical-documentation/data-sheets/233818fa.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/236416fa.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/236418f.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/236716fa.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/236718f.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/236816f.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/236818f.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/236918fa.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/237016fa.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/237616fa.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/237618fa.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/237620fb.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/237716fa.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/237718fa.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/237720fb.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/237816fa.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/237818fa.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/237820fb.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/237918fb.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/238016fb.pdf
+
+$ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+ compatible:
+ oneOf:
+ # Single compatible string match.
+ - enum:
+ - adi,ltc2338-18
+ - adi,ltc2364-16
+ - adi,ltc2364-18
+ - adi,ltc2367-16
+ - adi,ltc2367-18
+ - adi,ltc2368-16
+ - adi,ltc2368-18
+ - adi,ltc2369-18
+ - adi,ltc2370-16
+ - adi,ltc2376-16
+ - adi,ltc2376-18
+ - adi,ltc2376-20
+ - adi,ltc2377-16
+ - adi,ltc2377-18
+ - adi,ltc2377-20
+ - adi,ltc2378-16
+ - adi,ltc2378-18
+ - adi,ltc2378-20
+ - adi,ltc2379-18
+ - adi,ltc2380-16
+
+ # Low sample rate fallback for 16-bit unipolar sensors.
+ - items:
+ - enum:
+ - adi,ltc2370-16 # 2 MSPS
+ - adi,ltc2368-16 # 1 MSPS
+ - adi,ltc2367-16 # 500 kSPS
+ - const: adi,ltc2364-16 # fallback (250 kSPS)
+
+ # Low sample rate fallback for 18-bit unipolar sensors.
+ - items:
+ - enum:
+ - adi,ltc2369-18 # 1.6 MSPS
+ - adi,ltc2368-18 # 1 MSPS
+ - adi,ltc2367-18 # 500 kSPS
+ - const: adi,ltc2364-18 # fallback (250 kSPS)
+
+ # Low sample rate fallback for 16-bit bipolar sensors.
+ - items:
+ - enum:
+ - adi,ltc2380-16 # 2 MSPS
+ - adi,ltc2378-16 # 1 MSPS
+ - adi,ltc2377-16 # 500 kSPS
+ - const: adi,ltc2376-16 # fallback (250 kSPS)
+
+ # Low sample rate fallback for 18-bit bipolar sensors.
+ - items:
+ - enum:
+ - adi,ltc2379-18 # 1.6 MSPS
+ - adi,ltc2338-18 # 1 MSPS
+ - adi,ltc2378-18 # 1 MSPS
+ - adi,ltc2377-18 # 500 kSPS
+ - const: adi,ltc2376-18 # fallback (250 kSPS)
+
+ # Low sample rate fallback for 20-bit bipolar sensors.
+ - items:
+ - enum:
+ - adi,ltc2378-20 # 1 MSPS
+ - adi,ltc2377-20 # 500 kSPS
+ - const: adi,ltc2376-20 # fallback (250 kSPS)
+
+ reg:
+ maxItems: 1
+
+ spi-max-frequency:
+ maximum: 100000000
+
+ vdd-supply:
+ description: A 2.5V supply that powers the chip (VDD).
+
+ ovdd-supply:
+ description:
+ A 1.71V to 5.25V supply that sets the logic level for digital interface.
+
+ ref-supply:
+ description:
+ A 2.5V to 5.1V supply for the reference input (REF).
+
+ cnv-gpios:
+ description:
+ When provided, this property indicates the GPIO that is connected to the
+ CNV pin.
+ maxItems: 1
+
+ interrupts:
+ description:
+ Interrupt for signaling the completion of conversion results. The active
+ low signal provided on the BUSY pin asserts when ADC conversions finish.
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - vdd-supply
+ - ovdd-supply
+ - ref-supply
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ adc@0 {
+ compatible = "adi,ltc2378-20", "adi,ltc2376-20";
+ reg = <0>;
+ spi-max-frequency = <71000000>;
+ vdd-supply = <&supply_2_5V>;
+ ovdd-supply = <&supply_3_3V>;
+ ref-supply = <&supply_5V>;
+ cnv-gpios = <&gpio0 88 GPIO_ACTIVE_HIGH>;
+ interrupts = <7 IRQ_TYPE_EDGE_FALLING>;
+ interrupt-parent = <&gpio>;
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index b051eccafa60..205acb4b0789 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15220,6 +15220,13 @@ S: Maintained
F: Documentation/devicetree/bindings/iio/dac/lltc,ltc1660.yaml
F: drivers/iio/dac/ltc1660.c
+LTC2378 IIO ADC DRIVER
+M: Marcelo Schmitt <marcelo.schmitt@analog.com>
+L: linux-iio@vger.kernel.org
+S: Supported
+W: https://ez.analog.com/linux-software-drivers
+F: Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
+
LTC2664 IIO DAC DRIVER
M: Michael Hennerich <michael.hennerich@analog.com>
M: Kim Seer Paller <kimseer.paller@analog.com>
--
2.53.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox