[PATCH v3 08/15] iio: adc: stm32-adc: add support for stm32mp25
From: Fabrice Gasnier
Date: Wed Sep 30 2026 - 13:05:54 EST
Add support for ADC on STM32MP25 SoC. It has 3 ADCs, split into two blocks:
- ADC1 & ADC2 are tightly coupled.
- ADC3 is managed independently.
Trigger list slightly changes between these blocks (ADC1 & ADC2). Other
differences are found on channels interconnects (similar between ADC2
and ADC3):
- ADC1 is connected to 18 external channels + 2 internal channels
- ADC2 is connected to 14 external channels + 6 internal channels
- ADC3 is connected to 14 external channels + 6 internal channels
Each ADC is a 12-bits successive approximation analog-to-digital converter,
with up to 20 multiplexed channels that can be configured as single ended
or differential. ADC resolution ranges from 6 to 12 bits.
It introduces diversity regarding IRQs, clocks, software calibration
procedure, internal voltage channels, sampling time (prescaler) and
trigger list. Most of the architecture, and the driver engine remains
similar. So, handle the differences w.r.t. other STM32 ADCs family with
a dedicated compatible and compatible data.
Co-developed-by: Cheick Traore <cheick.traore-ext@xxxxxx>
Signed-off-by: Cheick Traore <cheick.traore-ext@xxxxxx>
Co-developed-by: Olivier Moysan <olivier.moysan@xxxxxxxxxxx>
Signed-off-by: Olivier Moysan <olivier.moysan@xxxxxxxxxxx>
Signed-off-by: Fabrice Gasnier <fabrice.gasnier@xxxxxxxxxxx>
---
Changes in v3:
- Andy review comments: Refactor the calibration routine, to get rid of
retry labels. So add a dedicated differential calibration routine. Each
routine becomes more linear. Adopt positive conditional checks.
- Fix style (trailing commas).
Changes in v2:
- Split as advised by Jonathan and Andy. Added precursor patches to
prepare all the infrastructure. Keep only the new hardware support.
- Adopt separate compatible for ADC1, ADC2 and ADC3 to better describe
internal channels (split regs descriptions) and trigger list. Drop
"st,adc-trigger-sel".
- Cosmetic updates whilst at it: remove trailing comma
- Refactor calibration routine, to avoid twisted goto chain, as advised
by Andy
- One patch per new SoC
---
drivers/iio/adc/stm32-adc-core.c | 38 ++++
drivers/iio/adc/stm32-adc-core.h | 20 ++
drivers/iio/adc/stm32-adc.c | 397 +++++++++++++++++++++++++++++++++++++++
3 files changed, 455 insertions(+)
diff --git a/drivers/iio/adc/stm32-adc-core.c b/drivers/iio/adc/stm32-adc-core.c
index dcaf305edec2..bb8b53f52914 100644
--- a/drivers/iio/adc/stm32-adc-core.c
+++ b/drivers/iio/adc/stm32-adc-core.c
@@ -136,6 +136,9 @@ static struct stm32_adc_priv *to_stm32_adc_priv(struct stm32_adc_common *com)
/* STM32F4 ADC internal common clock prescaler division ratios */
static unsigned int stm32f4_pclk_div[] = { 2, 4, 6, 8 };
+/* STM32MP25 ADC internal common clock prescaler division ratios */
+static unsigned int stm32mp25_presc_div[] = { 1, 2, 4, 6, 8, 10, 12, 16, 32, 64, 128, 256 };
+
/**
* stm32_adc_clk_sel() - Select stm32f4 ADC common clock prescaler
* @pdev: platform device
@@ -345,6 +348,17 @@ static const struct stm32_adc_common_regs stm32mp13_adc_common_regs = {
.eocie_msk = STM32H7_EOCIE,
};
+/* STM32MP25 common registers definitions */
+static const struct stm32_adc_common_regs stm32mp25_adc_common_regs = {
+ .csr = STM32H7_ADC_CSR,
+ .ccr = STM32H7_ADC_CCR,
+ .eoc_msk = { STM32H7_EOC_MST, STM32H7_EOC_SLV},
+ .ovr_msk = { STM32H7_OVR_MST, STM32H7_OVR_SLV},
+ .ier = STM32H7_ADC_IER,
+ .eocie_msk = STM32H7_EOCIE,
+ .presc_msk = STM32H7_PRESC_MASK,
+};
+
static const unsigned int stm32_adc_offset[STM32_ADC_MAX_ADCS] = {
0, STM32_ADC_OFFSET, STM32_ADC_OFFSET * 2,
};
@@ -422,6 +436,8 @@ static int stm32_adc_irq_probe(struct platform_device *pdev,
* Interrupt(s) must be provided, depending on the compatible:
* - stm32f4/h7 shares a common interrupt line.
* - stm32mp1, has one line per ADC
+ * - stm32mp25, has a dual ADC1/ADC2 and standalone ADC3. Each ADC has an IRQ
+ * (two for ADC1/ADC2, one for ADC3)
*/
for (i = 0; i < priv->nb_irqs; i++) {
priv->irq[i] = platform_get_irq(pdev, i);
@@ -702,6 +718,17 @@ static int stm32_adc_probe_identification(struct platform_device *pdev,
return -EINVAL;
}
+ if (priv->cfg->ipid == STM32MP25_IPIDR_NUMBER) {
+ /*
+ * ADC diversity on STM32MP25 is handled here. There may be:
+ * - 2 IRQs for ADC1/ADC2 block
+ * - 1 IRQ for ADC3 block.
+ * There cannot be more IRQs than the actual number of ADCs.
+ */
+ if (priv->nb_irqs > priv->nb_adc_max)
+ priv->nb_irqs = priv->nb_adc_max;
+ }
+
val = readl_relaxed(priv->common.base + STM32MP1_ADC_VERR);
dev_dbg(&pdev->dev, "ADC version: %lu.%lu\n",
FIELD_GET(STM32MP1_MAJREV_MASK, val),
@@ -894,11 +921,22 @@ static const struct stm32_adc_priv_cfg stm32mp13_adc_priv_cfg = {
.num_irqs = 1,
};
+static const struct stm32_adc_priv_cfg stm32mp25_adc_priv_cfg = {
+ .regs = &stm32mp25_adc_common_regs,
+ .clk_sel = stm32_adc_clk_sel,
+ .presc = stm32mp25_presc_div,
+ .num_presc = ARRAY_SIZE(stm32mp25_presc_div),
+ .max_clk_rate_hz = 70000000,
+ .ipid = STM32MP25_IPIDR_NUMBER,
+ .num_irqs = 2, /* 2 IRQs for ADC1/ADC2, 1 irq for ADC3 */
+};
+
static const struct of_device_id stm32_adc_of_match[] = {
{ .compatible = "st,stm32f4-adc-core", .data = (void *)&stm32f4_adc_priv_cfg },
{ .compatible = "st,stm32h7-adc-core", .data = (void *)&stm32h7_adc_priv_cfg },
{ .compatible = "st,stm32mp1-adc-core", .data = (void *)&stm32mp1_adc_priv_cfg },
{ .compatible = "st,stm32mp13-adc-core", .data = (void *)&stm32mp13_adc_priv_cfg },
+ { .compatible = "st,stm32mp25-adc-core", .data = (void *)&stm32mp25_adc_priv_cfg },
{ }
};
MODULE_DEVICE_TABLE(of, stm32_adc_of_match);
diff --git a/drivers/iio/adc/stm32-adc-core.h b/drivers/iio/adc/stm32-adc-core.h
index db50a9f3b922..68b937e3819f 100644
--- a/drivers/iio/adc/stm32-adc-core.h
+++ b/drivers/iio/adc/stm32-adc-core.h
@@ -121,6 +121,9 @@
#define STM32MP13_ADC_CALFACT 0xB4
#define STM32MP13_ADC2_OR 0xC8
+/* STM32MP25 - ADC2 and ADC3 option register */
+#define STM32MP25_ADC23_OR 0xD0
+
/* STM32H7 - common registers for all ADC instances */
#define STM32H7_ADC_CSR (STM32_ADCX_COMN_OFFSET + 0x00)
#define STM32H7_ADC_CCR (STM32_ADCX_COMN_OFFSET + 0x08)
@@ -257,8 +260,25 @@ enum stm32h7_adc_dmngt {
#define STM32MP13_OP1 BIT(1)
#define STM32MP13_OP0 BIT(0)
+/* STM32MP25 - Registers for each ADC instance */
+#define STM32MP25_ADC_CALFACT 0xC4
+
+/* STM32MP25_ADC_CALFACT - bit fields */
+#define STM32MP25_CALFACT_CALADDOS BIT(31)
+#define STM32MP25_CALFACT_D_MASK GENMASK(24, 16)
+#define STM32MP25_CALFACT_S_MASK GENMASK(8, 0)
+
+/* STM32MP25_ADC_CFGR specific bit fields */
+#define STM32MP25_RES_SHIFT 2
+#define STM32MP25_RES_MASK GENMASK(3, 2)
+
+/* STM32MP25_ADC23_OR - specific bit fields */
+#define STM32MP25_VDDCPUEN BIT(3)
+#define STM32MP25_VDDCOREEN BIT(2)
+
#define STM32MP15_IPIDR_NUMBER 0x00110005
#define STM32MP13_IPIDR_NUMBER 0x00110006
+#define STM32MP25_IPIDR_NUMBER 0x00110008
/**
* struct stm32_adc_common - stm32 ADC driver common data (for all instances)
diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c
index c1979f1f1738..33e41505b2e9 100644
--- a/drivers/iio/adc/stm32-adc.c
+++ b/drivers/iio/adc/stm32-adc.c
@@ -33,6 +33,9 @@
/* Number of linear calibration shadow registers / LINCALRDYW control bits */
#define STM32H7_LINCALFACT_NUM 6
+/* Number of loops in the calibration procedure to average data on STM32MP25 */
+#define STM32MP25_CALIB_LOOP 8
+
/* BOOST bit must be set on STM32H7 when ADC clock is above 20MHz */
#define STM32H7_BOOST_CLKRATE 20000000UL
@@ -77,6 +80,14 @@ enum stm32_adc_extsel {
STM32_EXT18,
STM32_EXT19,
STM32_EXT20,
+ STM32_EXT21,
+ STM32_EXT22,
+ STM32_EXT23,
+ STM32_EXT24,
+ STM32_EXT25,
+ STM32_EXT26,
+ STM32_EXT27,
+ STM32_EXT28,
};
enum stm32_adc_int_ch {
@@ -348,6 +359,20 @@ static const struct stm32_adc_info stm32mp13_adc_info = {
.num_ovs = ARRAY_SIZE(stm32mp13_adc_oversampling_avail),
};
+/*
+ * stm32mp25 can have up to 20 channels.
+ * Keep channel 19 (VREF+) hidden on STM32MP25: it is the ADC reference
+ * supply itself, so exposing it as a regular input is meaningless.
+ * Reference voltage must be derived from VREFINT with calibration data.
+ */
+static const struct stm32_adc_info stm32mp25_adc_info = {
+ .max_channels = STM32_ADC_CH_MAX - 1,
+ .resolutions = stm32f4_adc_resolutions,
+ .oversampling = stm32h7_adc_oversampling_avail,
+ .num_res = ARRAY_SIZE(stm32f4_adc_resolutions),
+ .num_ovs = ARRAY_SIZE(stm32h7_adc_oversampling_avail),
+};
+
/*
* stm32f4_sq - describe regular sequence registers
* - L: sequence len (register & bit field)
@@ -538,6 +563,114 @@ static const struct stm32_adc_regspec stm32h7_adc_regspec = {
.smp_bits = stm32h7_smp_bits,
};
+/* STM32MP25 external trigger sources for ADC1 and ADC2 */
+static struct stm32_adc_trig_info stm32mp25_adc1_adc2_trigs[] = {
+ { TIM1_TRGO, STM32_EXT0 },
+ { TIM1_TRGO2, STM32_EXT1 },
+ { TIM8_TRGO, STM32_EXT2 },
+ { TIM8_TRGO2, STM32_EXT3 },
+ { TIM20_TRGO, STM32_EXT4 },
+ { TIM20_TRGO2, STM32_EXT5 },
+ { TIM2_TRGO, STM32_EXT6 },
+ { TIM3_TRGO, STM32_EXT7 },
+ { TIM4_TRGO, STM32_EXT8 },
+ { TIM5_TRGO, STM32_EXT9 },
+ { TIM6_TRGO, STM32_EXT10 },
+ { TIM15_TRGO, STM32_EXT11 },
+ { TIM1_CH1, STM32_EXT12 },
+ { TIM1_CH2, STM32_EXT13 },
+ { TIM1_CH3, STM32_EXT14 },
+ { TIM20_OC1, STM32_EXT15 },
+ { TIM20_OC2, STM32_EXT16 },
+ { TIM20_OC3, STM32_EXT17 },
+ { TIM2_CH2, STM32_EXT18 },
+ { TIM3_CH4, STM32_EXT19 },
+ { TIM4_CH4, STM32_EXT20 },
+ { TIM5_CH1, STM32_EXT21 },
+ { TIM12_CH1, STM32_EXT22 },
+ { LPTIM1_CH1, STM32_EXT24 },
+ { LPTIM2_CH1, STM32_EXT25 },
+ { LPTIM3_CH1, STM32_EXT26 },
+ { LPTIM4_CH1, STM32_EXT27 },
+ { LPTIM5_OUT, STM32_EXT28 },
+ { }
+};
+
+/* STM32MP25 external trigger sources for ADC3 */
+static struct stm32_adc_trig_info stm32mp25_adc3_trigs[] = {
+ { TIM1_TRGO, STM32_EXT0 },
+ { TIM1_TRGO2, STM32_EXT1 },
+ { TIM8_TRGO, STM32_EXT2 },
+ { TIM8_TRGO2, STM32_EXT3 },
+ { TIM20_TRGO, STM32_EXT4 },
+ { TIM20_TRGO2, STM32_EXT5 },
+ { TIM2_TRGO, STM32_EXT6 },
+ { TIM3_TRGO, STM32_EXT7 },
+ { TIM4_TRGO, STM32_EXT8 },
+ { TIM5_TRGO, STM32_EXT9 },
+ { TIM6_TRGO, STM32_EXT10 },
+ { TIM7_TRGO, STM32_EXT11 },
+ { TIM15_TRGO, STM32_EXT12 },
+ { TIM17_OC1, STM32_EXT13 },
+ { TIM1_CH3, STM32_EXT14 },
+ { TIM8_CH1, STM32_EXT15 },
+ { TIM20_OC1, STM32_EXT16 },
+ { TIM2_CH1, STM32_EXT17 },
+ { TIM2_CH3, STM32_EXT18 },
+ { TIM3_CH1, STM32_EXT19 },
+ { TIM4_CH1, STM32_EXT20 },
+ { TIM5_CH3, STM32_EXT21 },
+ { TIM12_CH1, STM32_EXT22 },
+ { LPTIM1_CH1, STM32_EXT24 },
+ { LPTIM2_CH1, STM32_EXT25 },
+ { LPTIM3_CH1, STM32_EXT26 },
+ { LPTIM4_CH1, STM32_EXT27 },
+ { LPTIM5_OUT, STM32_EXT28 },
+ { }
+};
+
+/* STM32MP25 programmable sampling time (ADC clock cycles, rounded down) */
+static const unsigned int stm32mp25_adc_smp_cycles[STM32_ADC_MAX_SMP + 1] = {
+ 2, 3, 7, 12, 24, 47, 247, 1501,
+};
+
+static const struct stm32_adc_regspec stm32mp25_adc1_regspec = {
+ .dr = STM32H7_ADC_DR,
+ .ier_eoc = { STM32H7_ADC_IER, STM32H7_EOCIE },
+ .ier_ovr = { STM32H7_ADC_IER, STM32H7_OVRIE },
+ .isr_eoc = { STM32H7_ADC_ISR, STM32H7_EOC },
+ .isr_ovr = { STM32H7_ADC_ISR, STM32H7_OVR },
+ .sqr = stm32h7_sq,
+ .exten = { STM32H7_ADC_CFGR, STM32H7_EXTEN_MASK, STM32H7_EXTEN_SHIFT },
+ .extsel = { STM32H7_ADC_CFGR, STM32H7_EXTSEL_MASK,
+ STM32H7_EXTSEL_SHIFT },
+ .res = { STM32H7_ADC_CFGR, STM32MP25_RES_MASK, STM32MP25_RES_SHIFT },
+ .difsel = { STM32H7_ADC_DIFSEL, STM32H7_DIFSEL_MASK},
+ .smpr = { STM32H7_ADC_SMPR1, STM32H7_ADC_SMPR2 },
+ .smp_bits = stm32h7_smp_bits,
+ .ccr_vref = { STM32H7_ADC_CCR, STM32H7_VREFEN },
+};
+
+static const struct stm32_adc_regspec stm32mp25_adc2_adc3_regspec = {
+ .dr = STM32H7_ADC_DR,
+ .ier_eoc = { STM32H7_ADC_IER, STM32H7_EOCIE },
+ .ier_ovr = { STM32H7_ADC_IER, STM32H7_OVRIE },
+ .isr_eoc = { STM32H7_ADC_ISR, STM32H7_EOC },
+ .isr_ovr = { STM32H7_ADC_ISR, STM32H7_OVR },
+ .sqr = stm32h7_sq,
+ .exten = { STM32H7_ADC_CFGR, STM32H7_EXTEN_MASK, STM32H7_EXTEN_SHIFT },
+ .extsel = { STM32H7_ADC_CFGR, STM32H7_EXTSEL_MASK,
+ STM32H7_EXTSEL_SHIFT },
+ .res = { STM32H7_ADC_CFGR, STM32MP25_RES_MASK, STM32MP25_RES_SHIFT },
+ .difsel = { STM32H7_ADC_DIFSEL, STM32H7_DIFSEL_MASK},
+ .smpr = { STM32H7_ADC_SMPR1, STM32H7_ADC_SMPR2 },
+ .smp_bits = stm32h7_smp_bits,
+ .or_vddcore = { STM32MP25_ADC23_OR, STM32MP25_VDDCOREEN },
+ .or_vddcpu = { STM32MP25_ADC23_OR, STM32MP25_VDDCPUEN },
+ .ccr_vbat = { STM32H7_ADC_CCR, STM32H7_VBATEN },
+ .ccr_vref = { STM32H7_ADC_CCR, STM32H7_VREFEN },
+};
+
/* STM32MP13 programmable sampling time (ADC clock cycles, rounded down) */
static const unsigned int stm32mp13_adc_smp_cycles[STM32_ADC_MAX_SMP + 1] = {
2, 6, 12, 24, 47, 92, 247, 640,
@@ -1304,6 +1437,209 @@ static void stm32h7_adc_unprepare(struct iio_dev *indio_dev)
stm32h7_adc_enter_pwr_down(adc);
}
+/*
+ * STM32MP25 offset calibration software procedure. Basically the calibration routine is
+ * expected to average (for example) 8 samples in calibration mode, for single-ended and
+ * differential channels, to calibrate the zero offset. In case offset is "negative", an
+ * additional offset can be added, to determine calibration factor. It must be kept later
+ * for all conversions.
+ */
+static int stm32mp25_adc_calib_get_average_data(struct iio_dev *indio_dev, u32 *average)
+{
+ struct stm32_adc *adc = iio_priv(indio_dev);
+ u32 avg = 0;
+
+ /* Repeat several conversions in calibration mode, average the results */
+ for (unsigned int i = 0; i < STM32MP25_CALIB_LOOP; i++) {
+ int ret;
+ u32 val;
+
+ stm32_adc_set_bits(adc, STM32H7_ADC_CR, STM32H7_ADSTART);
+ ret = stm32_adc_readl_poll_timeout(STM32H7_ADC_CR, val,
+ !(val & (STM32H7_ADSTART)),
+ 100, STM32_ADC_TIMEOUT_US);
+ if (ret) {
+ dev_err(&indio_dev->dev, "calibration average error %d\n", ret);
+ return ret;
+ }
+
+ val = stm32_adc_readl(adc, adc->cfg->regs->dr);
+ dev_vdbg(&indio_dev->dev, "dr[%d]=0x%08x\n", i, val);
+ avg += val;
+ }
+
+ *average = DIV_ROUND_CLOSEST(avg, STM32MP25_CALIB_LOOP);
+ dev_vdbg(&indio_dev->dev, "average=0x%08x\n", *average);
+
+ return 0;
+}
+
+static int stm32mp25_adc_calib_single_ended(struct iio_dev *indio_dev, u32 *calfact)
+{
+ bool additional_offset = FIELD_GET(STM32MP25_CALFACT_CALADDOS, *calfact);
+ struct stm32_adc *adc = iio_priv(indio_dev);
+ u32 average;
+ int ret;
+
+ /* Select single ended input calibration */
+ stm32_adc_clr_bits(adc, STM32H7_ADC_CR, STM32H7_ADCALDIF);
+ ret = stm32mp25_adc_calib_get_average_data(indio_dev, &average);
+ if (ret)
+ return ret;
+
+ /* Add offset and re-run single-ended calibration if the averaged data is zero */
+ if (!average && !additional_offset) {
+ additional_offset = true;
+ *calfact = STM32MP25_CALFACT_CALADDOS;
+ stm32_adc_writel(adc, STM32MP25_ADC_CALFACT, *calfact);
+
+ ret = stm32mp25_adc_calib_get_average_data(indio_dev, &average);
+ if (ret)
+ return ret;
+ }
+
+ if (average) {
+ *calfact |= FIELD_PREP(STM32MP25_CALFACT_S_MASK, average);
+ stm32_adc_writel(adc, STM32MP25_ADC_CALFACT, *calfact);
+ } else if (additional_offset) {
+ /* If average data is still zero with additional offset, just warn about it */
+ dev_warn(&indio_dev->dev, "Single-ended calibration average: 0\n");
+ }
+
+ return 0;
+}
+
+static int stm32mp25_adc_calib_differential(struct iio_dev *indio_dev, u32 *calfact)
+{
+ bool additional_offset = FIELD_GET(STM32MP25_CALFACT_CALADDOS, *calfact);
+ struct stm32_adc *adc = iio_priv(indio_dev);
+ bool diff_below_zero;
+ u32 average;
+ int ret;
+
+ /* Select differential input calibration (keep previous CALADDOS value) */
+ stm32_adc_set_bits(adc, STM32H7_ADC_CR, STM32H7_ADCALDIF);
+ ret = stm32mp25_adc_calib_get_average_data(indio_dev, &average);
+ if (ret)
+ return ret;
+
+ /*
+ * If averaged differential data is negative (below 0x800, half value in 12-bits mode)
+ * and an offset can be added, re-run single-ended and differential calibration with an
+ * additional offset.
+ */
+ diff_below_zero = average < BIT(adc->cfg->adc_info->resolutions[0] - 1);
+ if (diff_below_zero && !additional_offset) {
+ /* Clear previous calibration result and add an offset */
+ *calfact = STM32MP25_CALFACT_CALADDOS;
+ stm32_adc_writel(adc, STM32MP25_ADC_CALFACT, *calfact);
+
+ ret = stm32mp25_adc_calib_single_ended(indio_dev, calfact);
+ if (ret)
+ return ret;
+
+ stm32_adc_set_bits(adc, STM32H7_ADC_CR, STM32H7_ADCALDIF);
+ ret = stm32mp25_adc_calib_get_average_data(indio_dev, &average);
+ if (ret)
+ return ret;
+
+ diff_below_zero = average < BIT(adc->cfg->adc_info->resolutions[0] - 1);
+ }
+
+ if (!diff_below_zero) {
+ *calfact |= FIELD_PREP(STM32MP25_CALFACT_D_MASK, average);
+ stm32_adc_writel(adc, STM32MP25_ADC_CALFACT, *calfact);
+ } else {
+ /* Clamp differential data to zero (don't use the result), warn about it. */
+ dev_warn(&indio_dev->dev, "Differential calibration clamped(0): 0x%x\n", average);
+ }
+
+ return 0;
+}
+
+static int stm32mp25_adc_calib(struct iio_dev *indio_dev)
+{
+ struct stm32_adc *adc = iio_priv(indio_dev);
+ u32 calfact = 0;
+ int ret;
+
+ stm32_adc_set_bits(adc, STM32H7_ADC_CR, STM32H7_ADCAL);
+ /* Use default resolution (e.g. 12 bits) */
+ stm32_adc_clr_bits(adc, STM32H7_ADC_CFGR, STM32MP25_RES_MASK);
+ /* Clear calfact (additional offset and old calibration data if any) */
+ stm32_adc_writel(adc, STM32MP25_ADC_CALFACT, calfact);
+
+ ret = stm32mp25_adc_calib_single_ended(indio_dev, &calfact);
+ if (ret) {
+ stm32_adc_clr_bits(adc, STM32H7_ADC_CR, STM32H7_ADCAL);
+ return ret;
+ }
+
+ ret = stm32mp25_adc_calib_differential(indio_dev, &calfact);
+ if (ret) {
+ stm32_adc_clr_bits(adc, STM32H7_ADC_CR, STM32H7_ADCAL);
+ return ret;
+ }
+
+ dev_dbg(&indio_dev->dev, "set calfact_s=0x%03lx, calfact_d=0x%03lx, caladdos=%ld\n",
+ FIELD_GET(STM32MP25_CALFACT_S_MASK, calfact),
+ FIELD_GET(STM32MP25_CALFACT_D_MASK, calfact),
+ FIELD_GET(STM32MP25_CALFACT_CALADDOS, calfact));
+
+ stm32_adc_clr_bits(adc, STM32H7_ADC_CR, STM32H7_ADCAL);
+ stm32_adc_set_res(adc);
+
+ return 0;
+}
+
+static int stm32mp25_adc_prepare(struct iio_dev *indio_dev)
+{
+ struct stm32_adc *adc = iio_priv(indio_dev);
+ int ret;
+
+ ret = stm32h7_adc_exit_pwr_down(indio_dev);
+ if (ret)
+ return ret;
+
+ /* Must enable the ADC before running software-assisted calibration */
+ ret = stm32h7_adc_enable(indio_dev);
+ if (ret)
+ goto pwr_dwn;
+
+ /* Always run offset calibration */
+ ret = stm32mp25_adc_calib(indio_dev);
+ if (ret)
+ goto adc_dis;
+
+ stm32_adc_int_ch_enable(indio_dev);
+
+ stm32_adc_writel(adc, adc->cfg->regs->difsel.reg, adc->difsel);
+
+ if (adc->cfg->has_presel)
+ stm32_adc_writel(adc, STM32H7_ADC_PCSEL, adc->pcsel);
+
+ return 0;
+
+adc_dis:
+ stm32h7_adc_disable(indio_dev);
+pwr_dwn:
+ stm32h7_adc_enter_pwr_down(adc);
+
+ return ret;
+}
+
+static void stm32mp25_adc_unprepare(struct iio_dev *indio_dev)
+{
+ struct stm32_adc *adc = iio_priv(indio_dev);
+
+ /* Undo things in the reverse order */
+ if (adc->cfg->has_presel)
+ stm32_adc_writel(adc, STM32H7_ADC_PCSEL, 0);
+ stm32_adc_int_ch_disable(adc);
+ stm32h7_adc_disable(indio_dev);
+ stm32h7_adc_enter_pwr_down(adc);
+}
+
/**
* stm32_adc_conf_scan_seq() - Build regular channels scan sequence
* @indio_dev: IIO device
@@ -2788,11 +3124,72 @@ static const struct stm32_adc_cfg stm32mp13_adc_cfg = {
.vref_charac_mv = 3300,
};
+static const unsigned int stm32_adc_min_ts_mp25[STM32_ADC_INT_CH_NB] = {
+ [STM32_ADC_INT_CH_VDDCORE] = 34,
+ [STM32_ADC_INT_CH_VDDCPU] = 34,
+ [STM32_ADC_INT_CH_VREFINT] = 34,
+ [STM32_ADC_INT_CH_VBAT] = 34,
+};
+
+static const struct stm32_adc_cfg stm32mp25_adc1_cfg = {
+ .regs = &stm32mp25_adc1_regspec,
+ .adc_info = &stm32mp25_adc_info,
+ .trigs = stm32mp25_adc1_adc2_trigs,
+ .has_oversampling = true,
+ .has_presel = true,
+ .start_conv = stm32h7_adc_start_conv,
+ .stop_conv = stm32h7_adc_stop_conv,
+ .prepare = stm32mp25_adc_prepare,
+ .unprepare = stm32mp25_adc_unprepare,
+ .smp_cycles = stm32mp25_adc_smp_cycles,
+ .irq_clear = stm32h7_adc_irq_clear,
+ .set_ovs = stm32h7_adc_set_ovs,
+ .ts_int_ch = stm32_adc_min_ts_mp25,
+ .vref_charac_mv = 1800,
+};
+
+static const struct stm32_adc_cfg stm32mp25_adc2_cfg = {
+ .regs = &stm32mp25_adc2_adc3_regspec,
+ .adc_info = &stm32mp25_adc_info,
+ .trigs = stm32mp25_adc1_adc2_trigs,
+ .has_oversampling = true,
+ .has_presel = true,
+ .start_conv = stm32h7_adc_start_conv,
+ .stop_conv = stm32h7_adc_stop_conv,
+ .prepare = stm32mp25_adc_prepare,
+ .unprepare = stm32mp25_adc_unprepare,
+ .smp_cycles = stm32mp25_adc_smp_cycles,
+ .irq_clear = stm32h7_adc_irq_clear,
+ .set_ovs = stm32h7_adc_set_ovs,
+ .ts_int_ch = stm32_adc_min_ts_mp25,
+ .vref_charac_mv = 1800,
+};
+
+static const struct stm32_adc_cfg stm32mp25_adc3_cfg = {
+ .regs = &stm32mp25_adc2_adc3_regspec,
+ .adc_info = &stm32mp25_adc_info,
+ .trigs = stm32mp25_adc3_trigs,
+ .has_oversampling = true,
+ .has_presel = true,
+ .start_conv = stm32h7_adc_start_conv,
+ .stop_conv = stm32h7_adc_stop_conv,
+ .prepare = stm32mp25_adc_prepare,
+ .unprepare = stm32mp25_adc_unprepare,
+ .smp_cycles = stm32mp25_adc_smp_cycles,
+ .irq_clear = stm32h7_adc_irq_clear,
+ .set_ovs = stm32h7_adc_set_ovs,
+ .ts_int_ch = stm32_adc_min_ts_mp25,
+ .vref_charac_mv = 1800,
+};
+
static const struct of_device_id stm32_adc_of_match[] = {
{ .compatible = "st,stm32f4-adc", .data = (void *)&stm32f4_adc_cfg },
{ .compatible = "st,stm32h7-adc", .data = (void *)&stm32h7_adc_cfg },
{ .compatible = "st,stm32mp1-adc", .data = (void *)&stm32mp1_adc_cfg },
{ .compatible = "st,stm32mp13-adc", .data = (void *)&stm32mp13_adc_cfg },
+ { .compatible = "st,stm32mp25-adc1", .data = (void *)&stm32mp25_adc1_cfg },
+ { .compatible = "st,stm32mp25-adc2", .data = (void *)&stm32mp25_adc2_cfg },
+ { .compatible = "st,stm32mp25-adc3", .data = (void *)&stm32mp25_adc3_cfg },
{ }
};
MODULE_DEVICE_TABLE(of, stm32_adc_of_match);
--
2.43.0