2018-07-24 14:31:31 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
/*
|
|
|
|
* Copyright (C) 2018, STMicroelectronics - All Rights Reserved
|
|
|
|
* Author: Fabrice Gasnier <fabrice.gasnier@st.com>
|
|
|
|
*
|
|
|
|
* Originally based on the Linux kernel v4.18 drivers/iio/adc/stm32-adc.c.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <common.h>
|
|
|
|
#include <adc.h>
|
2020-07-19 16:15:52 +00:00
|
|
|
#include <dm.h>
|
2018-07-24 14:31:31 +00:00
|
|
|
#include <asm/io.h>
|
2020-02-03 14:36:16 +00:00
|
|
|
#include <dm/device_compat.h>
|
2020-05-10 17:40:13 +00:00
|
|
|
#include <linux/bitops.h>
|
2020-05-10 17:40:11 +00:00
|
|
|
#include <linux/delay.h>
|
2018-07-24 14:31:31 +00:00
|
|
|
#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 */
|
2022-12-15 12:51:10 +00:00
|
|
|
#define STM32H7_ADCAL BIT(31)
|
|
|
|
#define STM32H7_ADCALDIF BIT(30)
|
2018-07-24 14:31:31 +00:00
|
|
|
#define STM32H7_DEEPPWD BIT(29)
|
|
|
|
#define STM32H7_ADVREGEN BIT(28)
|
2022-12-15 12:51:10 +00:00
|
|
|
#define STM32H7_ADCALLIN BIT(16)
|
2018-07-24 14:31:31 +00:00
|
|
|
#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;
|
|
|
|
};
|
|
|
|
|
2022-12-15 12:51:10 +00:00
|
|
|
static void stm32_adc_enter_pwr_down(struct udevice *dev)
|
2018-07-24 14:31:31 +00:00
|
|
|
{
|
|
|
|
struct stm32_adc *adc = dev_get_priv(dev);
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-12-15 12:51:10 +00:00
|
|
|
static int stm32_adc_exit_pwr_down(struct udevice *dev)
|
2018-07-24 14:31:31 +00:00
|
|
|
{
|
|
|
|
struct stm32_adc_common *common = dev_get_priv(dev_get_parent(dev));
|
|
|
|
struct stm32_adc *adc = dev_get_priv(dev);
|
|
|
|
int ret;
|
|
|
|
u32 val;
|
|
|
|
|
2022-12-15 12:51:10 +00:00
|
|
|
/* return immediately if ADC is not in deep power down mode */
|
|
|
|
if (!(readl(adc->regs + STM32H7_ADC_CR) & STM32H7_DEEPPWD))
|
|
|
|
return 0;
|
|
|
|
|
2018-07-24 14:31:31 +00:00
|
|
|
/* 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);
|
2022-12-15 12:51:10 +00:00
|
|
|
|
2018-07-24 14:31:31 +00:00
|
|
|
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);
|
2022-12-15 12:51:10 +00:00
|
|
|
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);
|
2018-07-24 14:31:31 +00:00
|
|
|
}
|
|
|
|
|
2022-12-15 12:51:10 +00:00
|
|
|
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;
|
|
|
|
|
2018-07-24 14:31:31 +00:00
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
2022-12-15 12:51:10 +00:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2022-11-23 15:20:15 +00:00
|
|
|
static int stm32_adc_get_legacy_chan_count(struct udevice *dev)
|
2018-07-24 14:31:31 +00:00
|
|
|
{
|
2019-06-21 13:26:44 +00:00
|
|
|
int ret;
|
2018-07-24 14:31:31 +00:00
|
|
|
|
|
|
|
/* Retrieve single ended channels listed in device tree */
|
2019-06-21 13:26:44 +00:00
|
|
|
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;
|
2018-07-24 14:31:31 +00:00
|
|
|
}
|
|
|
|
|
2022-11-23 15:20:15 +00:00
|
|
|
return (ret / sizeof(u32));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int stm32_adc_legacy_chan_init(struct udevice *dev, unsigned int num_channels)
|
|
|
|
{
|
|
|
|
struct adc_uclass_plat *uc_pdata = dev_get_uclass_plat(dev);
|
|
|
|
struct stm32_adc *adc = dev_get_priv(dev);
|
|
|
|
u32 chans[STM32_ADC_CH_MAX];
|
|
|
|
int i, ret;
|
2018-07-24 14:31:31 +00:00
|
|
|
|
|
|
|
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];
|
|
|
|
}
|
|
|
|
|
2022-11-23 15:20:15 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2022-11-23 15:20:16 +00:00
|
|
|
static int stm32_adc_generic_chan_init(struct udevice *dev, unsigned int num_channels)
|
|
|
|
{
|
|
|
|
struct adc_uclass_plat *uc_pdata = dev_get_uclass_plat(dev);
|
|
|
|
struct stm32_adc *adc = dev_get_priv(dev);
|
|
|
|
ofnode child;
|
|
|
|
int val, ret;
|
|
|
|
|
|
|
|
ofnode_for_each_subnode(child, dev_ofnode(dev)) {
|
|
|
|
ret = ofnode_read_u32(child, "reg", &val);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "Missing channel index %d\n", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (val >= adc->cfg->max_channels) {
|
|
|
|
dev_err(dev, "Invalid channel %d\n", val);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
uc_pdata->channel_mask |= 1 << val;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-11-23 15:20:15 +00:00
|
|
|
static int stm32_adc_chan_of_init(struct udevice *dev)
|
|
|
|
{
|
|
|
|
struct adc_uclass_plat *uc_pdata = dev_get_uclass_plat(dev);
|
|
|
|
struct stm32_adc *adc = dev_get_priv(dev);
|
|
|
|
unsigned int num_channels;
|
|
|
|
int ret;
|
2022-11-23 15:20:16 +00:00
|
|
|
bool legacy = false;
|
|
|
|
|
|
|
|
num_channels = dev_get_child_count(dev);
|
|
|
|
/* If no channels have been found, fallback to channels legacy properties. */
|
|
|
|
if (!num_channels) {
|
|
|
|
legacy = true;
|
|
|
|
|
|
|
|
ret = stm32_adc_get_legacy_chan_count(dev);
|
|
|
|
if (!ret) {
|
|
|
|
dev_err(dev, "No channel found\n");
|
|
|
|
return -ENODATA;
|
|
|
|
} else if (ret < 0) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
num_channels = ret;
|
|
|
|
}
|
2022-11-23 15:20:15 +00:00
|
|
|
|
|
|
|
if (num_channels > adc->cfg->max_channels) {
|
|
|
|
dev_err(dev, "too many st,adc-channels: %d\n", num_channels);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2022-11-23 15:20:16 +00:00
|
|
|
if (legacy)
|
|
|
|
ret = stm32_adc_legacy_chan_init(dev, num_channels);
|
|
|
|
else
|
|
|
|
ret = stm32_adc_generic_chan_init(dev, num_channels);
|
2022-11-23 15:20:15 +00:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
2018-07-24 14:31:31 +00:00
|
|
|
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)
|
|
|
|
{
|
2020-12-03 23:55:18 +00:00
|
|
|
struct adc_uclass_plat *uc_pdata = dev_get_uclass_plat(dev);
|
2018-07-24 14:31:31 +00:00
|
|
|
struct stm32_adc_common *common = dev_get_priv(dev_get_parent(dev));
|
|
|
|
struct stm32_adc *adc = dev_get_priv(dev);
|
2022-12-15 12:51:10 +00:00
|
|
|
int offset, ret;
|
2018-07-24 14:31:31 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2022-12-15 12:51:10 +00:00
|
|
|
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;
|
2018-07-24 14:31:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
2020-12-03 23:55:17 +00:00
|
|
|
.priv_auto = sizeof(struct stm32_adc),
|
2018-07-24 14:31:31 +00:00
|
|
|
};
|