diff options
Diffstat (limited to 'drivers/adc')
-rw-r--r-- | drivers/adc/stm32-adc.c | 134 |
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 = { |