adc: Add driver for STM32 ADC
authorFabrice Gasnier <fabrice.gasnier@st.com>
Tue, 24 Jul 2018 14:31:31 +0000 (16:31 +0200)
committerTom Rini <trini@konsulko.com>
Fri, 3 Aug 2018 23:53:10 +0000 (19:53 -0400)
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>
drivers/adc/Kconfig
drivers/adc/Makefile
drivers/adc/stm32-adc-core.c [new file with mode: 0644]
drivers/adc/stm32-adc-core.h [new file with mode: 0644]
drivers/adc/stm32-adc.c [new file with mode: 0644]

index 93e27f1..e719c38 100644 (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)
index 95c93d4..cca0fec 100644 (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
diff --git a/drivers/adc/stm32-adc-core.c b/drivers/adc/stm32-adc-core.c
new file mode 100644 (file)
index 0000000..a9aa143
--- /dev/null
@@ -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),
+};
diff --git a/drivers/adc/stm32-adc-core.h b/drivers/adc/stm32-adc-core.h
new file mode 100644 (file)
index 0000000..ba0e10e
--- /dev/null
@@ -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
diff --git a/drivers/adc/stm32-adc.c b/drivers/adc/stm32-adc.c
new file mode 100644 (file)
index 0000000..e108062
--- /dev/null
@@ -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),
+};