]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
iio: adc: ad7124: Implement internal calibration at probe time
authorUwe Kleine-König <u.kleine-koenig@baylibre.com>
Mon, 3 Mar 2025 11:47:05 +0000 (12:47 +0100)
committerJonathan Cameron <Jonathan.Cameron@huawei.com>
Tue, 11 Mar 2025 19:09:16 +0000 (19:09 +0000)
Use the calibration function provided by the ad_sigma_delta shim to
calibrate all channels at probe time.

For measurements with gain 1 (i.e. if CONFIG_x.PGA = 0) full-scale
calibrations are not supported and the reset default value of the GAIN
register is supposed to be used then.

Signed-off-by: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
Link: https://patch.msgid.link/20250303114659.1672695-17-u.kleine-koenig@baylibre.com
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
drivers/iio/adc/ad7124.c

index de90ecb5f6307e83bfa32ac770873febe6db1866..382f46ff2b5117ed443d0e4c56b2e125a732ce35 100644 (file)
 #define AD7124_ADC_CTRL_MODE_MSK       GENMASK(5, 2)
 #define AD7124_ADC_CTRL_MODE(x)        FIELD_PREP(AD7124_ADC_CTRL_MODE_MSK, x)
 
+#define AD7124_MODE_CAL_INT_ZERO       0x5 /* Internal Zero-Scale Calibration */
+#define AD7124_MODE_CAL_INT_FULL       0x6 /* Internal Full-Scale Calibration */
+#define AD7124_MODE_CAL_SYS_ZERO       0x7 /* System Zero-Scale Calibration */
+#define AD7124_MODE_CAL_SYS_FULL       0x8 /* System Full-Scale Calibration */
+
 /* AD7124 ID */
 #define AD7124_DEVICE_ID_MSK           GENMASK(7, 4)
 #define AD7124_DEVICE_ID_GET(x)                FIELD_GET(AD7124_DEVICE_ID_MSK, x)
@@ -166,6 +171,8 @@ struct ad7124_channel_config {
                unsigned int odr;
                unsigned int odr_sel_bits;
                unsigned int filter_type;
+               unsigned int calibration_offset;
+               unsigned int calibration_gain;
        );
 };
 
@@ -186,6 +193,12 @@ struct ad7124_state {
        unsigned int num_channels;
        struct mutex cfgs_lock; /* lock for configs access */
        unsigned long cfg_slots_status; /* bitmap with slot status (1 means it is used) */
+
+       /*
+        * Stores the power-on reset value for the GAIN(x) registers which are
+        * needed for measurements at gain 1 (i.e. CONFIG(x).PGA == 0)
+        */
+       unsigned int gain_default;
        DECLARE_KFIFO(live_cfgs_fifo, struct ad7124_channel_config *, AD7124_MAX_CONFIGS);
 };
 
@@ -359,6 +372,8 @@ static struct ad7124_channel_config *ad7124_find_similar_live_cfg(struct ad7124_
                                     unsigned int odr;
                                     unsigned int odr_sel_bits;
                                     unsigned int filter_type;
+                                    unsigned int calibration_offset;
+                                    unsigned int calibration_gain;
                             }));
 
        for (i = 0; i < st->num_channels; i++) {
@@ -373,7 +388,9 @@ static struct ad7124_channel_config *ad7124_find_similar_live_cfg(struct ad7124_
                    cfg->pga_bits == cfg_aux->pga_bits &&
                    cfg->odr == cfg_aux->odr &&
                    cfg->odr_sel_bits == cfg_aux->odr_sel_bits &&
-                   cfg->filter_type == cfg_aux->filter_type)
+                   cfg->filter_type == cfg_aux->filter_type &&
+                   cfg->calibration_offset == cfg_aux->calibration_offset &&
+                   cfg->calibration_gain == cfg_aux->calibration_gain)
                        return cfg_aux;
        }
 
@@ -429,6 +446,14 @@ static int ad7124_write_config(struct ad7124_state *st, struct ad7124_channel_co
 
        cfg->cfg_slot = cfg_slot;
 
+       ret = ad_sd_write_reg(&st->sd, AD7124_OFFSET(cfg->cfg_slot), 3, cfg->calibration_offset);
+       if (ret)
+               return ret;
+
+       ret = ad_sd_write_reg(&st->sd, AD7124_GAIN(cfg->cfg_slot), 3, cfg->calibration_gain);
+       if (ret)
+               return ret;
+
        tmp = (cfg->buf_positive << 1) + cfg->buf_negative;
        val = AD7124_CONFIG_BIPOLAR(cfg->bipolar) | AD7124_CONFIG_REF_SEL(cfg->refsel) |
              AD7124_CONFIG_IN_BUFF(tmp) | AD7124_CONFIG_PGA(cfg->pga_bits);
@@ -835,13 +860,22 @@ static int ad7124_soft_reset(struct ad7124_state *st)
                        return dev_err_probe(dev, ret, "Error reading status register\n");
 
                if (!(readval & AD7124_STATUS_POR_FLAG_MSK))
-                       return 0;
+                       break;
 
                /* The AD7124 requires typically 2ms to power up and settle */
                usleep_range(100, 2000);
        } while (--timeout);
 
-       return dev_err_probe(dev, -EIO, "Soft reset failed\n");
+       if (readval & AD7124_STATUS_POR_FLAG_MSK)
+               return dev_err_probe(dev, -EIO, "Soft reset failed\n");
+
+       ret = ad_sd_read_reg(&st->sd, AD7124_GAIN(0), 3, &st->gain_default);
+       if (ret < 0)
+               return dev_err_probe(dev, ret, "Error reading gain register\n");
+
+       dev_dbg(dev, "Reset value of GAIN register is 0x%x\n", st->gain_default);
+
+       return 0;
 }
 
 static int ad7124_check_chip_id(struct ad7124_state *st)
@@ -1054,6 +1088,91 @@ static int ad7124_setup(struct ad7124_state *st)
        return ret;
 }
 
+static int __ad7124_calibrate_all(struct ad7124_state *st, struct iio_dev *indio_dev)
+{
+       struct device *dev = &st->sd.spi->dev;
+       int ret, i;
+
+       for (i = 0; i < st->num_channels; i++) {
+
+               if (indio_dev->channels[i].type != IIO_VOLTAGE)
+                       continue;
+
+               /*
+                * For calibration the OFFSET register should hold its reset default
+                * value. For the GAIN register there is no such requirement but
+                * for gain 1 it should hold the reset default value, too. So to
+                * simplify matters use the reset default value for both.
+                */
+               st->channels[i].cfg.calibration_offset = 0x800000;
+               st->channels[i].cfg.calibration_gain = st->gain_default;
+
+               /*
+                * Full-scale calibration isn't supported at gain 1, so skip in
+                * that case. Note that untypically full-scale calibration has
+                * to happen before zero-scale calibration. This only applies to
+                * the internal calibration. For system calibration it's as
+                * usual: first zero-scale then full-scale calibration.
+                */
+               if (st->channels[i].cfg.pga_bits > 0) {
+                       ret = ad_sd_calibrate(&st->sd, AD7124_MODE_CAL_INT_FULL, i);
+                       if (ret < 0)
+                               return ret;
+
+                       /*
+                        * read out the resulting value of GAIN
+                        * after full-scale calibration because the next
+                        * ad_sd_calibrate() call overwrites this via
+                        * ad_sigma_delta_set_channel() -> ad7124_set_channel()
+                        * ... -> ad7124_enable_channel().
+                        */
+                       ret = ad_sd_read_reg(&st->sd, AD7124_GAIN(st->channels[i].cfg.cfg_slot), 3,
+                                            &st->channels[i].cfg.calibration_gain);
+                       if (ret < 0)
+                               return ret;
+               }
+
+               ret = ad_sd_calibrate(&st->sd, AD7124_MODE_CAL_INT_ZERO, i);
+               if (ret < 0)
+                       return ret;
+
+               ret = ad_sd_read_reg(&st->sd, AD7124_OFFSET(st->channels[i].cfg.cfg_slot), 3,
+                                    &st->channels[i].cfg.calibration_offset);
+               if (ret < 0)
+                       return ret;
+
+               dev_dbg(dev, "offset and gain for channel %d = 0x%x + 0x%x\n", i,
+                       st->channels[i].cfg.calibration_offset,
+                       st->channels[i].cfg.calibration_gain);
+       }
+
+       return 0;
+}
+
+static int ad7124_calibrate_all(struct ad7124_state *st, struct iio_dev *indio_dev)
+{
+       int ret;
+       unsigned int adc_control = st->adc_control;
+
+       /*
+        * Calibration isn't supported at full power, so speed down a bit.
+        * Setting .adc_control is enough here because the control register is
+        * written as part of ad_sd_calibrate() -> ad_sigma_delta_set_mode().
+        * The resulting calibration is then also valid for high-speed, so just
+        * restore adc_control afterwards.
+        */
+       if (FIELD_GET(AD7124_ADC_CTRL_PWR_MSK, adc_control) >= AD7124_FULL_POWER) {
+               st->adc_control &= ~AD7124_ADC_CTRL_PWR_MSK;
+               st->adc_control |= AD7124_ADC_CTRL_PWR(AD7124_MID_POWER);
+       }
+
+       ret = __ad7124_calibrate_all(st, indio_dev);
+
+       st->adc_control = adc_control;
+
+       return ret;
+}
+
 static void ad7124_reg_disable(void *r)
 {
        regulator_disable(r);
@@ -1132,6 +1251,10 @@ static int ad7124_probe(struct spi_device *spi)
        if (ret < 0)
                return dev_err_probe(dev, ret, "Failed to setup triggers\n");
 
+       ret = ad7124_calibrate_all(st, indio_dev);
+       if (ret)
+               return ret;
+
        ret = devm_iio_device_register(&spi->dev, indio_dev);
        if (ret < 0)
                return dev_err_probe(dev, ret, "Failed to register iio device\n");