summaryrefslogtreecommitdiff
path: root/drivers/adc/stm32-adc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/adc/stm32-adc.c')
-rw-r--r--drivers/adc/stm32-adc.c134
1 files changed, 116 insertions, 18 deletions
diff --git a/drivers/adc/stm32-adc.c b/drivers/adc/stm32-adc.c
index 85efc119db..1fba707c6f 100644
--- a/drivers/adc/stm32-adc.c
+++ b/drivers/adc/stm32-adc.c
@@ -33,8 +33,11 @@
#define STM32H7_ADRDY BIT(0)
/* STM32H7_ADC_CR - bit fields */
+#define STM32H7_ADCAL BIT(31)
+#define STM32H7_ADCALDIF BIT(30)
#define STM32H7_DEEPPWD BIT(29)
#define STM32H7_ADVREGEN BIT(28)
+#define STM32H7_ADCALLIN BIT(16)
#define STM32H7_BOOST BIT(8)
#define STM32H7_ADSTART BIT(2)
#define STM32H7_ADDIS BIT(1)
@@ -65,47 +68,72 @@ struct stm32_adc {
const struct stm32_adc_cfg *cfg;
};
-static int stm32_adc_stop(struct udevice *dev)
+static void stm32_adc_enter_pwr_down(struct udevice *dev)
{
struct stm32_adc *adc = dev_get_priv(dev);
- setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADDIS);
clrbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_BOOST);
/* Setting DEEPPWD disables ADC vreg and clears ADVREGEN */
setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_DEEPPWD);
- adc->active_channel = -1;
-
- return 0;
}
-static int stm32_adc_start_channel(struct udevice *dev, int channel)
+static int stm32_adc_exit_pwr_down(struct udevice *dev)
{
- struct adc_uclass_plat *uc_pdata = dev_get_uclass_plat(dev);
struct stm32_adc_common *common = dev_get_priv(dev_get_parent(dev));
struct stm32_adc *adc = dev_get_priv(dev);
int ret;
u32 val;
+ /* return immediately if ADC is not in deep power down mode */
+ if (!(readl(adc->regs + STM32H7_ADC_CR) & STM32H7_DEEPPWD))
+ return 0;
+
/* Exit deep power down, then enable ADC voltage regulator */
clrbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_DEEPPWD);
setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADVREGEN);
+
if (common->rate > STM32H7_BOOST_CLKRATE)
setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_BOOST);
/* Wait for startup time */
if (!adc->cfg->has_vregready) {
udelay(20);
- } else {
- ret = readl_poll_timeout(adc->regs + STM32H7_ADC_ISR, val,
- val & STM32MP1_VREGREADY,
- STM32_ADC_TIMEOUT_US);
- if (ret < 0) {
- stm32_adc_stop(dev);
- dev_err(dev, "Failed to enable vreg: %d\n", ret);
- return ret;
- }
+ return 0;
+ }
+
+ ret = readl_poll_timeout(adc->regs + STM32H7_ADC_ISR, val,
+ val & STM32MP1_VREGREADY,
+ STM32_ADC_TIMEOUT_US);
+ if (ret < 0) {
+ stm32_adc_enter_pwr_down(dev);
+ dev_err(dev, "Failed to enable vreg: %d\n", ret);
}
+ return ret;
+}
+
+static int stm32_adc_stop(struct udevice *dev)
+{
+ struct stm32_adc *adc = dev_get_priv(dev);
+
+ setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADDIS);
+ stm32_adc_enter_pwr_down(dev);
+ adc->active_channel = -1;
+
+ return 0;
+}
+
+static int stm32_adc_start_channel(struct udevice *dev, int channel)
+{
+ struct adc_uclass_plat *uc_pdata = dev_get_uclass_plat(dev);
+ struct stm32_adc *adc = dev_get_priv(dev);
+ int ret;
+ u32 val;
+
+ ret = stm32_adc_exit_pwr_down(dev);
+ if (ret < 0)
+ return ret;
+
/* Only use single ended channels */
writel(0, adc->regs + STM32H7_ADC_DIFSEL);
@@ -162,6 +190,64 @@ static int stm32_adc_channel_data(struct udevice *dev, int channel,
return 0;
}
+/**
+ * Fixed timeout value for ADC calibration.
+ * worst cases:
+ * - low clock frequency (0.12 MHz min)
+ * - maximum prescalers
+ * Calibration requires:
+ * - 16384 ADC clock cycle for the linear calibration
+ * - 20 ADC clock cycle for the offset calibration
+ *
+ * Set to 100ms for now
+ */
+#define STM32H7_ADC_CALIB_TIMEOUT_US 100000
+
+static int stm32_adc_selfcalib(struct udevice *dev)
+{
+ struct stm32_adc *adc = dev_get_priv(dev);
+ int ret;
+ u32 val;
+
+ /*
+ * Select calibration mode:
+ * - Offset calibration for single ended inputs
+ * - No linearity calibration. Done in next step.
+ */
+ clrbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADCALDIF | STM32H7_ADCALLIN);
+
+ /* Start calibration, then wait for completion */
+ setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADCAL);
+ ret = readl_poll_sleep_timeout(adc->regs + STM32H7_ADC_CR, val,
+ !(val & STM32H7_ADCAL), 100,
+ STM32H7_ADC_CALIB_TIMEOUT_US);
+ if (ret) {
+ dev_err(dev, "calibration failed\n");
+ goto out;
+ }
+
+ /*
+ * Select calibration mode, then start calibration:
+ * - Offset calibration for differential input
+ * - Linearity calibration (needs to be done only once for single/diff)
+ * will run simultaneously with offset calibration.
+ */
+ setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADCALDIF | STM32H7_ADCALLIN);
+
+ /* Start calibration, then wait for completion */
+ setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADCAL);
+ ret = readl_poll_sleep_timeout(adc->regs + STM32H7_ADC_CR, val,
+ !(val & STM32H7_ADCAL), 100,
+ STM32H7_ADC_CALIB_TIMEOUT_US);
+ if (ret)
+ dev_err(dev, "calibration failed\n");
+
+out:
+ clrbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADCALDIF | STM32H7_ADCALLIN);
+
+ return ret;
+}
+
static int stm32_adc_get_legacy_chan_count(struct udevice *dev)
{
int ret;
@@ -272,7 +358,7 @@ static int stm32_adc_probe(struct udevice *dev)
struct adc_uclass_plat *uc_pdata = dev_get_uclass_plat(dev);
struct stm32_adc_common *common = dev_get_priv(dev_get_parent(dev));
struct stm32_adc *adc = dev_get_priv(dev);
- int offset;
+ int offset, ret;
offset = dev_read_u32_default(dev, "reg", -ENODATA);
if (offset < 0) {
@@ -287,7 +373,19 @@ static int stm32_adc_probe(struct udevice *dev)
uc_pdata->vdd_microvolts = common->vref_uv;
uc_pdata->vss_microvolts = 0;
- return stm32_adc_chan_of_init(dev);
+ ret = stm32_adc_chan_of_init(dev);
+ if (ret < 0)
+ return ret;
+
+ ret = stm32_adc_exit_pwr_down(dev);
+ if (ret < 0)
+ return ret;
+
+ ret = stm32_adc_selfcalib(dev);
+ if (ret)
+ stm32_adc_enter_pwr_down(dev);
+
+ return ret;
}
static const struct adc_ops stm32_adc_ops = {