| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (C) 2018, STMicroelectronics - All Rights Reserved | |
| * Author: Fabrice Gasnier <[email protected]> | |
| * | |
| * Originally based on the Linux kernel v4.18 drivers/iio/adc/stm32-adc.c. | |
| */ | |
| #include <common.h> | |
| #include <adc.h> | |
| #include <asm/io.h> | |
| #include <dm/device_compat.h> | |
| #include <linux/iopoll.h> | |
| #include "stm32-adc-core.h" | |
| /* STM32H7 - Registers for each ADC instance */ | |
| #define STM32H7_ADC_ISR 0x00 | |
| #define STM32H7_ADC_CR 0x08 | |
| #define STM32H7_ADC_CFGR 0x0C | |
| #define STM32H7_ADC_SMPR1 0x14 | |
| #define STM32H7_ADC_SMPR2 0x18 | |
| #define STM32H7_ADC_PCSEL 0x1C | |
| #define STM32H7_ADC_SQR1 0x30 | |
| #define STM32H7_ADC_DR 0x40 | |
| #define STM32H7_ADC_DIFSEL 0xC0 | |
| /* STM32H7_ADC_ISR - bit fields */ | |
| #define STM32MP1_VREGREADY BIT(12) | |
| #define STM32H7_EOC BIT(2) | |
| #define STM32H7_ADRDY BIT(0) | |
| /* STM32H7_ADC_CR - bit fields */ | |
| #define STM32H7_DEEPPWD BIT(29) | |
| #define STM32H7_ADVREGEN BIT(28) | |
| #define STM32H7_BOOST BIT(8) | |
| #define STM32H7_ADSTART BIT(2) | |
| #define STM32H7_ADDIS BIT(1) | |
| #define STM32H7_ADEN BIT(0) | |
| /* STM32H7_ADC_CFGR bit fields */ | |
| #define STM32H7_EXTEN GENMASK(11, 10) | |
| #define STM32H7_DMNGT GENMASK(1, 0) | |
| /* STM32H7_ADC_SQR1 - bit fields */ | |
| #define STM32H7_SQ1_SHIFT 6 | |
| /* BOOST bit must be set on STM32H7 when ADC clock is above 20MHz */ | |
| #define STM32H7_BOOST_CLKRATE 20000000UL | |
| #define STM32_ADC_CH_MAX 20 /* max number of channels */ | |
| #define STM32_ADC_TIMEOUT_US 100000 | |
| struct stm32_adc_cfg { | |
| unsigned int max_channels; | |
| unsigned int num_bits; | |
| bool has_vregready; | |
| }; | |
| struct stm32_adc { | |
| void __iomem *regs; | |
| int active_channel; | |
| const struct stm32_adc_cfg *cfg; | |
| }; | |
| 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); | |
| 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) | |
| { | |
| struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(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; | |
| /* 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; | |
| } | |
| } | |
| /* Only use single ended channels */ | |
| writel(0, adc->regs + STM32H7_ADC_DIFSEL); | |
| /* Enable ADC, Poll for ADRDY to be set (after adc startup time) */ | |
| setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADEN); | |
| ret = readl_poll_timeout(adc->regs + STM32H7_ADC_ISR, val, | |
| val & STM32H7_ADRDY, STM32_ADC_TIMEOUT_US); | |
| if (ret < 0) { | |
| stm32_adc_stop(dev); | |
| dev_err(dev, "Failed to enable ADC: %d\n", ret); | |
| return ret; | |
| } | |
| /* Preselect channels */ | |
| writel(uc_pdata->channel_mask, adc->regs + STM32H7_ADC_PCSEL); | |
| /* Set sampling time to max value by default */ | |
| writel(0xffffffff, adc->regs + STM32H7_ADC_SMPR1); | |
| writel(0xffffffff, adc->regs + STM32H7_ADC_SMPR2); | |
| /* Program regular sequence: chan in SQ1 & len = 0 for one channel */ | |
| writel(channel << STM32H7_SQ1_SHIFT, adc->regs + STM32H7_ADC_SQR1); | |
| /* Trigger detection disabled (conversion can be launched in SW) */ | |
| clrbits_le32(adc->regs + STM32H7_ADC_CFGR, STM32H7_EXTEN | | |
| STM32H7_DMNGT); | |
| adc->active_channel = channel; | |
| return 0; | |
| } | |
| static int stm32_adc_channel_data(struct udevice *dev, int channel, | |
| unsigned int *data) | |
| { | |
| struct stm32_adc *adc = dev_get_priv(dev); | |
| int ret; | |
| u32 val; | |
| if (channel != adc->active_channel) { | |
| dev_err(dev, "Requested channel is not active!\n"); | |
| return -EINVAL; | |
| } | |
| setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADSTART); | |
| ret = readl_poll_timeout(adc->regs + STM32H7_ADC_ISR, val, | |
| val & STM32H7_EOC, STM32_ADC_TIMEOUT_US); | |
| if (ret < 0) { | |
| dev_err(dev, "conversion timed out: %d\n", ret); | |
| return ret; | |
| } | |
| *data = readl(adc->regs + STM32H7_ADC_DR); | |
| return 0; | |
| } | |
| static int stm32_adc_chan_of_init(struct udevice *dev) | |
| { | |
| struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev); | |
| struct stm32_adc *adc = dev_get_priv(dev); | |
| u32 chans[STM32_ADC_CH_MAX]; | |
| unsigned int i, num_channels; | |
| int ret; | |
| /* Retrieve single ended channels listed in device tree */ | |
| ret = dev_read_size(dev, "st,adc-channels"); | |
| if (ret < 0) { | |
| dev_err(dev, "can't get st,adc-channels: %d\n", ret); | |
| return ret; | |
| } | |
| num_channels = ret / sizeof(u32); | |
| if (num_channels > adc->cfg->max_channels) { | |
| dev_err(dev, "too many st,adc-channels: %d\n", num_channels); | |
| return -EINVAL; | |
| } | |
| ret = dev_read_u32_array(dev, "st,adc-channels", chans, num_channels); | |
| if (ret < 0) { | |
| dev_err(dev, "can't read st,adc-channels: %d\n", ret); | |
| return ret; | |
| } | |
| for (i = 0; i < num_channels; i++) { | |
| if (chans[i] >= adc->cfg->max_channels) { | |
| dev_err(dev, "bad channel %u\n", chans[i]); | |
| return -EINVAL; | |
| } | |
| uc_pdata->channel_mask |= 1 << chans[i]; | |
| } | |
| uc_pdata->data_mask = (1 << adc->cfg->num_bits) - 1; | |
| uc_pdata->data_format = ADC_DATA_FORMAT_BIN; | |
| uc_pdata->data_timeout_us = 100000; | |
| return 0; | |
| } | |
| static int stm32_adc_probe(struct udevice *dev) | |
| { | |
| struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev); | |
| struct stm32_adc_common *common = dev_get_priv(dev_get_parent(dev)); | |
| struct stm32_adc *adc = dev_get_priv(dev); | |
| int offset; | |
| offset = dev_read_u32_default(dev, "reg", -ENODATA); | |
| if (offset < 0) { | |
| dev_err(dev, "Can't read reg property\n"); | |
| return offset; | |
| } | |
| adc->regs = common->base + offset; | |
| adc->cfg = (const struct stm32_adc_cfg *)dev_get_driver_data(dev); | |
| /* VDD supplied by common vref pin */ | |
| uc_pdata->vdd_supply = common->vref; | |
| uc_pdata->vdd_microvolts = common->vref_uv; | |
| uc_pdata->vss_microvolts = 0; | |
| return stm32_adc_chan_of_init(dev); | |
| } | |
| static const struct adc_ops stm32_adc_ops = { | |
| .start_channel = stm32_adc_start_channel, | |
| .channel_data = stm32_adc_channel_data, | |
| .stop = stm32_adc_stop, | |
| }; | |
| static const struct stm32_adc_cfg stm32h7_adc_cfg = { | |
| .num_bits = 16, | |
| .max_channels = STM32_ADC_CH_MAX, | |
| }; | |
| static const struct stm32_adc_cfg stm32mp1_adc_cfg = { | |
| .num_bits = 16, | |
| .max_channels = STM32_ADC_CH_MAX, | |
| .has_vregready = true, | |
| }; | |
| static const struct udevice_id stm32_adc_ids[] = { | |
| { .compatible = "st,stm32h7-adc", | |
| .data = (ulong)&stm32h7_adc_cfg }, | |
| { .compatible = "st,stm32mp1-adc", | |
| .data = (ulong)&stm32mp1_adc_cfg }, | |
| {} | |
| }; | |
| U_BOOT_DRIVER(stm32_adc) = { | |
| .name = "stm32-adc", | |
| .id = UCLASS_ADC, | |
| .of_match = stm32_adc_ids, | |
| .probe = stm32_adc_probe, | |
| .ops = &stm32_adc_ops, | |
| .priv_auto_alloc_size = sizeof(struct stm32_adc), | |
| }; |