summaryrefslogtreecommitdiff
path: root/drivers/adc/stm32-adc-core.c
diff options
context:
space:
mode:
authorFabrice Gasnier <fabrice.gasnier@st.com>2018-07-24 16:31:31 +0200
committerTom Rini <trini@konsulko.com>2018-08-03 19:53:10 -0400
commita466ecec483a94ac5c66d3ce6793e1575f333d23 (patch)
treec8381d1205420220f91ffd627426b322d35c17d9 /drivers/adc/stm32-adc-core.c
parentfb4e674a4b01d44fe15ea72c31c84598db11ebc1 (diff)
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>
Diffstat (limited to 'drivers/adc/stm32-adc-core.c')
-rw-r--r--drivers/adc/stm32-adc-core.c209
1 files changed, 209 insertions, 0 deletions
diff --git a/drivers/adc/stm32-adc-core.c b/drivers/adc/stm32-adc-core.c
new file mode 100644
index 0000000000..a9aa143bfe
--- /dev/null
+++ b/drivers/adc/stm32-adc-core.c
@@ -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),
+};