adc: Add driver for STM32 ADC

This patch adds support for STMicroelectronics STM32 ADC (analog to
digital converter). It's originally based on Linux kernel v4.18-rcs
drivers/iio/adc/stm32-adc*. It's composed of:
- core driver (UCLASS_SIMPLE_BUS) manages common resources (clk, regu).
- child drivers (UCLASS_ADC) declare each ADC, channels and handle
  conversions.
This driver currently supports STM32H7 and STM32MP1 ADC.

Signed-off-by: Fabrice Gasnier <fabrice.gasnier@st.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Fabrice Gasnier 2018-07-24 16:31:31 +02:00 committed by Tom Rini
parent fb4e674a4b
commit a466ecec48
5 changed files with 534 additions and 0 deletions

View file

@ -47,3 +47,19 @@ config SARADC_ROCKCHIP
- 2~6 analog input channels
- 1O or 12 bits resolution
- Up to 1MSPS of sample rate
config STM32_ADC
bool "Enable STMicroelectronics STM32 ADC driver"
depends on ADC && (STM32H7 || ARCH_STM32MP)
help
This enables driver for STMicroelectronics STM32 analog-to-digital
converter (ADC).
A STM32 ADC block can be composed of several individual ADCs.
Each has its own private registers, but shares some resources:
- clock selection and prescaler
- voltage reference
- common registers area.
STM32 ADC driver is composed of:
- core driver to deal with common resources
- child driver to deal with individual ADC resources (declare ADC
device and associated channels, start/stop conversions)

View file

@ -10,3 +10,4 @@ obj-$(CONFIG_ADC_EXYNOS) += exynos-adc.o
obj-$(CONFIG_ADC_SANDBOX) += sandbox.o
obj-$(CONFIG_SARADC_ROCKCHIP) += rockchip-saradc.o
obj-$(CONFIG_SARADC_MESON) += meson-saradc.o
obj-$(CONFIG_STM32_ADC) += stm32-adc.o stm32-adc-core.o

View file

@ -0,0 +1,209 @@
// 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-core.c.
*/
#include <common.h>
#include <asm/io.h>
#include <power/regulator.h>
#include "stm32-adc-core.h"
/* STM32H7 - common registers for all ADC instances */
#define STM32H7_ADC_CCR (STM32_ADCX_COMN_OFFSET + 0x08)
/* STM32H7_ADC_CCR - bit fields */
#define STM32H7_PRESC_SHIFT 18
#define STM32H7_PRESC_MASK GENMASK(21, 18)
#define STM32H7_CKMODE_SHIFT 16
#define STM32H7_CKMODE_MASK GENMASK(17, 16)
/* STM32 H7 maximum analog clock rate (from datasheet) */
#define STM32H7_ADC_MAX_CLK_RATE 36000000
/**
* struct stm32h7_adc_ck_spec - specification for stm32h7 adc clock
* @ckmode: ADC clock mode, Async or sync with prescaler.
* @presc: prescaler bitfield for async clock mode
* @div: prescaler division ratio
*/
struct stm32h7_adc_ck_spec {
u32 ckmode;
u32 presc;
int div;
};
static const struct stm32h7_adc_ck_spec stm32h7_adc_ckmodes_spec[] = {
/* 00: CK_ADC[1..3]: Asynchronous clock modes */
{ 0, 0, 1 },
{ 0, 1, 2 },
{ 0, 2, 4 },
{ 0, 3, 6 },
{ 0, 4, 8 },
{ 0, 5, 10 },
{ 0, 6, 12 },
{ 0, 7, 16 },
{ 0, 8, 32 },
{ 0, 9, 64 },
{ 0, 10, 128 },
{ 0, 11, 256 },
/* HCLK used: Synchronous clock modes (1, 2 or 4 prescaler) */
{ 1, 0, 1 },
{ 2, 0, 2 },
{ 3, 0, 4 },
};
static int stm32h7_adc_clk_sel(struct udevice *dev,
struct stm32_adc_common *common)
{
u32 ckmode, presc;
unsigned long rate;
int i, div;
/* stm32h7 bus clock is common for all ADC instances (mandatory) */
if (!clk_valid(&common->bclk)) {
dev_err(dev, "No bclk clock found\n");
return -ENOENT;
}
/*
* stm32h7 can use either 'bus' or 'adc' clock for analog circuitry.
* So, choice is to have bus clock mandatory and adc clock optional.
* If optional 'adc' clock has been found, then try to use it first.
*/
if (clk_valid(&common->aclk)) {
/*
* Asynchronous clock modes (e.g. ckmode == 0)
* From spec: PLL output musn't exceed max rate
*/
rate = clk_get_rate(&common->aclk);
if (!rate) {
dev_err(dev, "Invalid aclk rate: 0\n");
return -EINVAL;
}
for (i = 0; i < ARRAY_SIZE(stm32h7_adc_ckmodes_spec); i++) {
ckmode = stm32h7_adc_ckmodes_spec[i].ckmode;
presc = stm32h7_adc_ckmodes_spec[i].presc;
div = stm32h7_adc_ckmodes_spec[i].div;
if (ckmode)
continue;
if ((rate / div) <= STM32H7_ADC_MAX_CLK_RATE)
goto out;
}
}
/* Synchronous clock modes (e.g. ckmode is 1, 2 or 3) */
rate = clk_get_rate(&common->bclk);
if (!rate) {
dev_err(dev, "Invalid bus clock rate: 0\n");
return -EINVAL;
}
for (i = 0; i < ARRAY_SIZE(stm32h7_adc_ckmodes_spec); i++) {
ckmode = stm32h7_adc_ckmodes_spec[i].ckmode;
presc = stm32h7_adc_ckmodes_spec[i].presc;
div = stm32h7_adc_ckmodes_spec[i].div;
if (!ckmode)
continue;
if ((rate / div) <= STM32H7_ADC_MAX_CLK_RATE)
goto out;
}
dev_err(dev, "clk selection failed\n");
return -EINVAL;
out:
/* rate used later by each ADC instance to control BOOST mode */
common->rate = rate / div;
/* Set common clock mode and prescaler */
clrsetbits_le32(common->base + STM32H7_ADC_CCR,
STM32H7_CKMODE_MASK | STM32H7_PRESC_MASK,
ckmode << STM32H7_CKMODE_SHIFT |
presc << STM32H7_PRESC_SHIFT);
dev_dbg(dev, "Using %s clock/%d source at %ld kHz\n",
ckmode ? "bus" : "adc", div, common->rate / 1000);
return 0;
}
static int stm32_adc_core_probe(struct udevice *dev)
{
struct stm32_adc_common *common = dev_get_priv(dev);
int ret;
common->base = dev_read_addr_ptr(dev);
if (!common->base) {
dev_err(dev, "can't get address\n");
return -ENOENT;
}
ret = device_get_supply_regulator(dev, "vref-supply", &common->vref);
if (ret) {
dev_err(dev, "can't get vref-supply: %d\n", ret);
return ret;
}
ret = regulator_get_value(common->vref);
if (ret < 0) {
dev_err(dev, "can't get vref-supply value: %d\n", ret);
return ret;
}
common->vref_uv = ret;
ret = clk_get_by_name(dev, "adc", &common->aclk);
if (!ret) {
ret = clk_enable(&common->aclk);
if (ret) {
dev_err(dev, "Can't enable aclk: %d\n", ret);
return ret;
}
}
ret = clk_get_by_name(dev, "bus", &common->bclk);
if (!ret) {
ret = clk_enable(&common->bclk);
if (ret) {
dev_err(dev, "Can't enable bclk: %d\n", ret);
goto err_aclk_disable;
}
}
ret = stm32h7_adc_clk_sel(dev, common);
if (ret)
goto err_bclk_disable;
return ret;
err_bclk_disable:
if (clk_valid(&common->bclk))
clk_disable(&common->bclk);
err_aclk_disable:
if (clk_valid(&common->aclk))
clk_disable(&common->aclk);
return ret;
}
static const struct udevice_id stm32_adc_core_ids[] = {
{ .compatible = "st,stm32h7-adc-core" },
{ .compatible = "st,stm32mp1-adc-core" },
{}
};
U_BOOT_DRIVER(stm32_adc_core) = {
.name = "stm32-adc-core",
.id = UCLASS_SIMPLE_BUS,
.of_match = stm32_adc_core_ids,
.probe = stm32_adc_core_probe,
.priv_auto_alloc_size = sizeof(struct stm32_adc_common),
};

View file

@ -0,0 +1,51 @@
/* 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-core.h.
*/
#ifndef __STM32_ADC_H
#define __STM32_ADC_H
/*
* STM32 - ADC global register map
* ________________________________________________________
* | Offset | Register |
* --------------------------------------------------------
* | 0x000 | Master ADC1 |
* --------------------------------------------------------
* | 0x100 | Slave ADC2 |
* --------------------------------------------------------
* | 0x200 | Slave ADC3 |
* --------------------------------------------------------
* | 0x300 | Master & Slave common regs |
* --------------------------------------------------------
*/
#define STM32_ADC_MAX_ADCS 3
#define STM32_ADCX_COMN_OFFSET 0x300
#include <common.h>
#include <clk.h>
#include <dm.h>
/**
* struct stm32_adc_common - stm32 ADC driver common data (for all instances)
* @base: control registers base cpu addr
* @rate: clock rate used for analog circuitry
* @aclk: clock for the analog circuitry
* @bclk: bus clock common for all ADCs
* @vref: regulator reference
* @vref_uv: reference supply voltage (uV)
*/
struct stm32_adc_common {
void __iomem *base;
unsigned long rate;
struct clk aclk;
struct clk bclk;
struct udevice *vref;
int vref_uv;
};
#endif

257
drivers/adc/stm32-adc.c Normal file
View file

@ -0,0 +1,257 @@
// 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>
#include <asm/io.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];
int i, num_channels, ret;
/* Retrieve single ended channels listed in device tree */
num_channels = dev_read_size(dev, "st,adc-channels");
if (num_channels < 0) {
dev_err(dev, "can't get st,adc-channels: %d\n", num_channels);
return num_channels;
}
num_channels /= 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),
};