Refine offset and linear calibration strategy for STM32MP15 and
STM32MP13 SoCs:
- offset calibration
This calibration depends on factors such as temperature and voltage.
As it is not time consuming, it's worth doing it on each ADC
start, to get the best accuracy. There is no need to save these data.
- linear calibration
This calibration is basically SoC dependent, so it can be done only once.
When this calibration has been performed at boot stage, the ADC kernel
driver can retrieve the calibration data from the ADC registers.
Otherwise, the linear calibration is performed once by the ADC driver.
The backup of these data, allows to restore them on successive ADC starts.
Olivier Moysan (3):
iio: adc: stm32-adc: smart calibration support
iio: adc: stm32-adc: improve calibration error log
iio: adc: stm32-adc: add debugfs to read raw calibration result
drivers/iio/adc/stm32-adc-core.h | 1 +
drivers/iio/adc/stm32-adc.c | 135 ++++++++++++++++++-------------
2 files changed, 78 insertions(+), 58 deletions(-)
--
2.25.1
Add smart calibration support for STM32MP1.
- STM32MP15x: both linear & offset calibration are supported
- STM32MP13x: Only offset calibration is supported
Linear calibration:
Linear calibration is SoC dependent and does not change over time
so it can be done only once.
Linear calibration may have already been done in u-boot.
Skip calibration execution if calibration data are already available.
Save calibration factors in private data and restore them from private
data on next ADC start.
Offset calibration:
This calibration may vary over time, depending on temperature or voltage.
Run offset single-ended and differential calibration on each ADC start,
as it is not time consuming. This calibration do not need to be saved.
So, remove calfact_s and calfact_d value and bitfields that are no
longer used.
Signed-off-by: Olivier Moysan <[email protected]>
---
drivers/iio/adc/stm32-adc-core.h | 1 +
drivers/iio/adc/stm32-adc.c | 108 +++++++++++++++----------------
2 files changed, 53 insertions(+), 56 deletions(-)
diff --git a/drivers/iio/adc/stm32-adc-core.h b/drivers/iio/adc/stm32-adc-core.h
index 9d6dfa1c03fa..73b2c2e91c08 100644
--- a/drivers/iio/adc/stm32-adc-core.h
+++ b/drivers/iio/adc/stm32-adc-core.h
@@ -142,6 +142,7 @@
#define STM32H7_LINCALRDYW3 BIT(24)
#define STM32H7_LINCALRDYW2 BIT(23)
#define STM32H7_LINCALRDYW1 BIT(22)
+#define STM32H7_LINCALRDYW_MASK GENMASK(27, 22)
#define STM32H7_ADCALLIN BIT(16)
#define STM32H7_BOOST BIT(8)
#define STM32H7_ADSTP BIT(4)
diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c
index 8d03d21a33d6..2b2b55eb130e 100644
--- a/drivers/iio/adc/stm32-adc.c
+++ b/drivers/iio/adc/stm32-adc.c
@@ -119,16 +119,12 @@ struct stm32_adc_trig_info {
/**
* struct stm32_adc_calib - optional adc calibration data
- * @calfact_s: Calibration offset for single ended channels
- * @calfact_d: Calibration offset in differential
* @lincalfact: Linearity calibration factor
- * @calibrated: Indicates calibration status
+ * @lincal_saved: Indicates that linear calibration factors are saved
*/
struct stm32_adc_calib {
- u32 calfact_s;
- u32 calfact_d;
u32 lincalfact[STM32H7_LINCALFACT_NUM];
- bool calibrated;
+ bool lincal_saved;
};
/**
@@ -165,8 +161,6 @@ struct stm32_adc_vrefint {
* @extsel: trigger selection register & bitfield
* @res: resolution selection register & bitfield
* @difsel: differential mode selection register & bitfield
- * @calfact_s: single-ended calibration factors register & bitfield
- * @calfact_d: differential calibration factors register & bitfield
* @smpr: smpr1 & smpr2 registers offset array
* @smp_bits: smpr1 & smpr2 index and bitfields
* @or_vddcore: option register & vddcore bitfield
@@ -186,8 +180,6 @@ struct stm32_adc_regspec {
const struct stm32_adc_regs extsel;
const struct stm32_adc_regs res;
const struct stm32_adc_regs difsel;
- const struct stm32_adc_regs calfact_s;
- const struct stm32_adc_regs calfact_d;
const u32 smpr[2];
const struct stm32_adc_regs *smp_bits;
const struct stm32_adc_regs or_vddcore;
@@ -525,10 +517,6 @@ static const struct stm32_adc_regspec stm32h7_adc_regspec = {
STM32H7_EXTSEL_SHIFT },
.res = { STM32H7_ADC_CFGR, STM32H7_RES_MASK, STM32H7_RES_SHIFT },
.difsel = { STM32H7_ADC_DIFSEL, STM32H7_DIFSEL_MASK},
- .calfact_s = { STM32H7_ADC_CALFACT, STM32H7_CALFACT_S_MASK,
- STM32H7_CALFACT_S_SHIFT },
- .calfact_d = { STM32H7_ADC_CALFACT, STM32H7_CALFACT_D_MASK,
- STM32H7_CALFACT_D_SHIFT },
.smpr = { STM32H7_ADC_SMPR1, STM32H7_ADC_SMPR2 },
.smp_bits = stm32h7_smp_bits,
};
@@ -550,10 +538,6 @@ static const struct stm32_adc_regspec stm32mp13_adc_regspec = {
STM32H7_EXTSEL_SHIFT },
.res = { STM32H7_ADC_CFGR, STM32MP13_RES_MASK, STM32MP13_RES_SHIFT },
.difsel = { STM32MP13_ADC_DIFSEL, STM32MP13_DIFSEL_MASK},
- .calfact_s = { STM32MP13_ADC_CALFACT, STM32MP13_CALFACT_S_MASK,
- STM32MP13_CALFACT_S_SHIFT },
- .calfact_d = { STM32MP13_ADC_CALFACT, STM32MP13_CALFACT_D_MASK,
- STM32MP13_CALFACT_D_SHIFT },
.smpr = { STM32H7_ADC_SMPR1, STM32H7_ADC_SMPR2 },
.smp_bits = stm32h7_smp_bits,
.or_vddcore = { STM32MP13_ADC2_OR, STM32MP13_OP0 },
@@ -575,10 +559,6 @@ static const struct stm32_adc_regspec stm32mp1_adc_regspec = {
STM32H7_EXTSEL_SHIFT },
.res = { STM32H7_ADC_CFGR, STM32H7_RES_MASK, STM32H7_RES_SHIFT },
.difsel = { STM32H7_ADC_DIFSEL, STM32H7_DIFSEL_MASK},
- .calfact_s = { STM32H7_ADC_CALFACT, STM32H7_CALFACT_S_MASK,
- STM32H7_CALFACT_S_SHIFT },
- .calfact_d = { STM32H7_ADC_CALFACT, STM32H7_CALFACT_D_MASK,
- STM32H7_CALFACT_D_SHIFT },
.smpr = { STM32H7_ADC_SMPR1, STM32H7_ADC_SMPR2 },
.smp_bits = stm32h7_smp_bits,
.or_vddcore = { STM32MP1_ADC2_OR, STM32MP1_VDDCOREEN },
@@ -1000,9 +980,6 @@ static int stm32h7_adc_read_selfcalib(struct iio_dev *indio_dev)
int i, ret;
u32 lincalrdyw_mask, val;
- if (!adc->cfg->has_linearcal)
- goto skip_linearcal;
-
/* Read linearity calibration */
lincalrdyw_mask = STM32H7_LINCALRDYW6;
for (i = STM32H7_LINCALFACT_NUM - 1; i >= 0; i--) {
@@ -1024,15 +1001,7 @@ static int stm32h7_adc_read_selfcalib(struct iio_dev *indio_dev)
lincalrdyw_mask >>= 1;
}
-
-skip_linearcal:
- /* Read offset calibration */
- val = stm32_adc_readl(adc, adc->cfg->regs->calfact_s.reg);
- adc->cal.calfact_s = (val & adc->cfg->regs->calfact_s.mask);
- adc->cal.calfact_s >>= adc->cfg->regs->calfact_s.shift;
- adc->cal.calfact_d = (val & adc->cfg->regs->calfact_d.mask);
- adc->cal.calfact_d >>= adc->cfg->regs->calfact_d.shift;
- adc->cal.calibrated = true;
+ adc->cal.lincal_saved = true;
return 0;
}
@@ -1048,13 +1017,6 @@ static int stm32h7_adc_restore_selfcalib(struct iio_dev *indio_dev)
int i, ret;
u32 lincalrdyw_mask, val;
- val = (adc->cal.calfact_s << adc->cfg->regs->calfact_s.shift) |
- (adc->cal.calfact_d << adc->cfg->regs->calfact_d.shift);
- stm32_adc_writel(adc, adc->cfg->regs->calfact_s.reg, val);
-
- if (!adc->cfg->has_linearcal)
- return 0;
-
lincalrdyw_mask = STM32H7_LINCALRDYW6;
for (i = STM32H7_LINCALFACT_NUM - 1; i >= 0; i--) {
/*
@@ -1116,19 +1078,20 @@ static int stm32h7_adc_restore_selfcalib(struct iio_dev *indio_dev)
/**
* stm32h7_adc_selfcalib() - Procedure to calibrate ADC
* @indio_dev: IIO device instance
+ * @do_lincal: linear calibration request flag
* Note: Must be called once ADC is out of power down.
+ *
+ * Run offset calibration unconditionally.
+ * Run linear calibration if requested & supported.
*/
-static int stm32h7_adc_selfcalib(struct iio_dev *indio_dev)
+static int stm32h7_adc_selfcalib(struct iio_dev *indio_dev, int do_lincal)
{
struct stm32_adc *adc = iio_priv(indio_dev);
int ret;
u32 msk = STM32H7_ADCALDIF;
u32 val;
- if (adc->cal.calibrated)
- return true;
-
- if (adc->cfg->has_linearcal)
+ if (adc->cfg->has_linearcal && do_lincal)
msk |= STM32H7_ADCALLIN;
/* ADC must be disabled for calibration */
stm32h7_adc_disable(indio_dev);
@@ -1172,6 +1135,33 @@ static int stm32h7_adc_selfcalib(struct iio_dev *indio_dev)
return ret;
}
+/**
+ * stm32h7_adc_check_selfcalib() - Check linear calibration status
+ * @indio_dev: IIO device instance
+ *
+ * Used to check if linear calibration has been done.
+ * Return true if linear calibration factors are already saved in private data
+ * or if a linear calibration has been done at boot stage.
+ */
+static int stm32h7_adc_check_selfcalib(struct iio_dev *indio_dev)
+{
+ struct stm32_adc *adc = iio_priv(indio_dev);
+ u32 val;
+
+ if (adc->cal.lincal_saved)
+ return true;
+
+ /*
+ * Check if linear calibration factors are available in ADC registers,
+ * by checking that all LINCALRDYWx bits are set.
+ */
+ val = stm32_adc_readl(adc, STM32H7_ADC_CR) & STM32H7_LINCALRDYW_MASK;
+ if (val == STM32H7_LINCALRDYW_MASK)
+ return true;
+
+ return false;
+}
+
/**
* stm32h7_adc_prepare() - Leave power down mode to enable ADC.
* @indio_dev: IIO device instance
@@ -1186,16 +1176,20 @@ static int stm32h7_adc_selfcalib(struct iio_dev *indio_dev)
static int stm32h7_adc_prepare(struct iio_dev *indio_dev)
{
struct stm32_adc *adc = iio_priv(indio_dev);
- int calib, ret;
+ int lincal_done = false;
+ int ret;
ret = stm32h7_adc_exit_pwr_down(indio_dev);
if (ret)
return ret;
- ret = stm32h7_adc_selfcalib(indio_dev);
+ if (adc->cfg->has_linearcal)
+ lincal_done = stm32h7_adc_check_selfcalib(indio_dev);
+
+ /* Always run offset calibration. Run linear calibration only once */
+ ret = stm32h7_adc_selfcalib(indio_dev, !lincal_done);
if (ret < 0)
goto pwr_dwn;
- calib = ret;
stm32_adc_int_ch_enable(indio_dev);
@@ -1205,13 +1199,15 @@ static int stm32h7_adc_prepare(struct iio_dev *indio_dev)
if (ret)
goto ch_disable;
- /* Either restore or read calibration result for future reference */
- if (calib)
- ret = stm32h7_adc_restore_selfcalib(indio_dev);
- else
- ret = stm32h7_adc_read_selfcalib(indio_dev);
- if (ret)
- goto disable;
+ if (adc->cfg->has_linearcal) {
+ if (!adc->cal.lincal_saved)
+ ret = stm32h7_adc_read_selfcalib(indio_dev);
+ else
+ ret = stm32h7_adc_restore_selfcalib(indio_dev);
+
+ if (ret)
+ goto disable;
+ }
if (adc->cfg->has_presel)
stm32_adc_writel(adc, STM32H7_ADC_PCSEL, adc->pcsel);
--
2.25.1
Add debugfs to read linear ADC STM32 self calibration results.
Signed-off-by: Fabrice Gasnier <[email protected]>
Signed-off-by: Olivier Moysan <[email protected]>
---
drivers/iio/adc/stm32-adc.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c
index 65dd45537505..45d4e79f8e55 100644
--- a/drivers/iio/adc/stm32-adc.c
+++ b/drivers/iio/adc/stm32-adc.c
@@ -7,6 +7,7 @@
*/
#include <linux/clk.h>
+#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
@@ -1879,6 +1880,23 @@ static const struct iio_chan_spec_ext_info stm32_adc_ext_info[] = {
{},
};
+static void stm32_adc_debugfs_init(struct iio_dev *indio_dev)
+{
+ struct stm32_adc *adc = iio_priv(indio_dev);
+ struct dentry *d = iio_get_debugfs_dentry(indio_dev);
+ struct stm32_adc_calib *cal = &adc->cal;
+ char buf[16];
+ unsigned int i;
+
+ if (!adc->cfg->has_linearcal)
+ return;
+
+ for (i = 0; i < STM32H7_LINCALFACT_NUM; i++) {
+ snprintf(buf, sizeof(buf), "lincalfact%d", i + 1);
+ debugfs_create_u32(buf, 0444, d, &cal->lincalfact[i]);
+ }
+}
+
static int stm32_adc_fw_get_resolution(struct iio_dev *indio_dev)
{
struct device *dev = &indio_dev->dev;
@@ -2465,6 +2483,9 @@ static int stm32_adc_probe(struct platform_device *pdev)
pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
+ if (IS_ENABLED(CONFIG_DEBUG_FS))
+ stm32_adc_debugfs_init(indio_dev);
+
return 0;
err_hw_stop:
@@ -2493,6 +2514,7 @@ static int stm32_adc_remove(struct platform_device *pdev)
struct stm32_adc *adc = iio_priv(indio_dev);
pm_runtime_get_sync(&pdev->dev);
+ /* iio_device_unregister() also removes debugfs entries */
iio_device_unregister(indio_dev);
stm32_adc_hw_stop(&pdev->dev);
pm_runtime_disable(&pdev->dev);
--
2.25.1
Add more information in calibration error log to differentiate
single-ended and differential calibration.
Signed-off-by: Olivier Moysan <[email protected]>
---
drivers/iio/adc/stm32-adc.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c
index 2b2b55eb130e..65dd45537505 100644
--- a/drivers/iio/adc/stm32-adc.c
+++ b/drivers/iio/adc/stm32-adc.c
@@ -1109,7 +1109,7 @@ static int stm32h7_adc_selfcalib(struct iio_dev *indio_dev, int do_lincal)
!(val & STM32H7_ADCAL), 100,
STM32H7_ADC_CALIB_TIMEOUT_US);
if (ret) {
- dev_err(&indio_dev->dev, "calibration failed\n");
+ dev_err(&indio_dev->dev, "calibration (single-ended) error %d\n", ret);
goto out;
}
@@ -1125,7 +1125,8 @@ static int stm32h7_adc_selfcalib(struct iio_dev *indio_dev, int do_lincal)
!(val & STM32H7_ADCAL), 100,
STM32H7_ADC_CALIB_TIMEOUT_US);
if (ret) {
- dev_err(&indio_dev->dev, "calibration failed\n");
+ dev_err(&indio_dev->dev, "calibration (diff%s) error %d\n",
+ (msk & STM32H7_ADCALLIN) ? "+linear" : "", ret);
goto out;
}
--
2.25.1
On 11/15/22 11:31, Olivier Moysan wrote:
> Refine offset and linear calibration strategy for STM32MP15 and
> STM32MP13 SoCs:
>
> - offset calibration
> This calibration depends on factors such as temperature and voltage.
> As it is not time consuming, it's worth doing it on each ADC
> start, to get the best accuracy. There is no need to save these data.
>
> - linear calibration
> This calibration is basically SoC dependent, so it can be done only once.
> When this calibration has been performed at boot stage, the ADC kernel
> driver can retrieve the calibration data from the ADC registers.
> Otherwise, the linear calibration is performed once by the ADC driver.
> The backup of these data, allows to restore them on successive ADC starts.
>
> Olivier Moysan (3):
> iio: adc: stm32-adc: smart calibration support
> iio: adc: stm32-adc: improve calibration error log
> iio: adc: stm32-adc: add debugfs to read raw calibration result
Hi Olivier,
For the series, you can add my:
Reviewed-by: Fabrice Gasnier <[email protected]>
Thanks,
Fabrice
>
> drivers/iio/adc/stm32-adc-core.h | 1 +
> drivers/iio/adc/stm32-adc.c | 135 ++++++++++++++++++-------------
> 2 files changed, 78 insertions(+), 58 deletions(-)
>
On Wed, 23 Nov 2022 12:04:36 +0100
Fabrice Gasnier <[email protected]> wrote:
> On 11/15/22 11:31, Olivier Moysan wrote:
> > Refine offset and linear calibration strategy for STM32MP15 and
> > STM32MP13 SoCs:
> >
> > - offset calibration
> > This calibration depends on factors such as temperature and voltage.
> > As it is not time consuming, it's worth doing it on each ADC
> > start, to get the best accuracy. There is no need to save these data.
> >
> > - linear calibration
> > This calibration is basically SoC dependent, so it can be done only once.
> > When this calibration has been performed at boot stage, the ADC kernel
> > driver can retrieve the calibration data from the ADC registers.
> > Otherwise, the linear calibration is performed once by the ADC driver.
> > The backup of these data, allows to restore them on successive ADC starts.
> >
> > Olivier Moysan (3):
> > iio: adc: stm32-adc: smart calibration support
> > iio: adc: stm32-adc: improve calibration error log
> > iio: adc: stm32-adc: add debugfs to read raw calibration result
>
> Hi Olivier,
>
> For the series, you can add my:
> Reviewed-by: Fabrice Gasnier <[email protected]>
Applied to the togreg branch of iio.git and pushed out as testing to let
0-day have a first look at it.
Thanks,
Jonathan
>
> Thanks,
> Fabrice
>
> >
> > drivers/iio/adc/stm32-adc-core.h | 1 +
> > drivers/iio/adc/stm32-adc.c | 135 ++++++++++++++++++-------------
> > 2 files changed, 78 insertions(+), 58 deletions(-)
> >