linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Fabrice Gasnier <fabrice.gasnier@st.com>
To: <jic23@kernel.org>, <robh+dt@kernel.org>, <alexandre.torgue@st.com>
Cc: <mark.rutland@arm.com>, <mcoquelin.stm32@gmail.com>,
	<lars@metafoo.de>, <knaack.h@gmx.de>, <pmeerw@pmeerw.net>,
	<fabrice.gasnier@st.com>, <linux-iio@vger.kernel.org>,
	<devicetree@vger.kernel.org>,
	<linux-stm32@st-md-mailman.stormreply.com>,
	<linux-arm-kernel@lists.infradead.org>,
	<linux-kernel@vger.kernel.org>
Subject: [PATCH v2 2/3] iio: adc: stm32-adc: add analog switches supply control
Date: Wed, 3 Jul 2019 12:08:15 +0200	[thread overview]
Message-ID: <1562148496-26789-3-git-send-email-fabrice.gasnier@st.com> (raw)
In-Reply-To: <1562148496-26789-1-git-send-email-fabrice.gasnier@st.com>

On stm32h7 and stm32mp1, the ADC inputs are multiplexed with analog
switches which have reduced performances when their supply is below 2.7V
(vdda by default):
- 3.3V embedded booster can be used, to get full ADC performances
  (increases power consumption).
- vdd supply can be selected if above 2.7V by setting ANASWVDD syscfg bit,
  on STM32MP1 only.

Make this optional, since this is a trade-off between analog performance
and power consumption.

Signed-off-by: Fabrice Gasnier <fabrice.gasnier@st.com>
---
Changes in v2:
- Only enable vdd regulator when needed.
- Rework vdda enabling since: "Add missing vdda-supply to STM32 ADC".
- Booster has been added to the regulator framework. This helps also when
  there are several ADC instances like on stm32h7 (e.g. ADC12 and ADC3), to
  benefit from the use count.
---
 drivers/iio/adc/stm32-adc-core.c | 193 ++++++++++++++++++++++++++++++++++++++-
 1 file changed, 192 insertions(+), 1 deletion(-)

diff --git a/drivers/iio/adc/stm32-adc-core.c b/drivers/iio/adc/stm32-adc-core.c
index 1f7ce51..4299cef 100644
--- a/drivers/iio/adc/stm32-adc-core.c
+++ b/drivers/iio/adc/stm32-adc-core.c
@@ -14,9 +14,11 @@
 #include <linux/irqchip/chained_irq.h>
 #include <linux/irqdesc.h>
 #include <linux/irqdomain.h>
+#include <linux/mfd/syscon.h>
 #include <linux/module.h>
 #include <linux/of_device.h>
 #include <linux/pm_runtime.h>
+#include <linux/regmap.h>
 #include <linux/regulator/consumer.h>
 #include <linux/slab.h>
 
@@ -51,6 +53,17 @@
 
 #define STM32_ADC_CORE_SLEEP_DELAY_MS	2000
 
+/* SYSCFG registers */
+#define STM32MP1_SYSCFG_PMCSETR		0x04
+#define STM32MP1_SYSCFG_PMCCLRR		0x44
+
+/* SYSCFG bit fields */
+#define STM32MP1_SYSCFG_ANASWVDD_MASK	BIT(9)
+
+/* SYSCFG capability flags */
+#define HAS_VBOOSTER		BIT(0)
+#define HAS_ANASWVDD		BIT(1)
+
 /**
  * stm32_adc_common_regs - stm32 common registers, compatible dependent data
  * @csr:	common status register offset
@@ -74,11 +87,13 @@ struct stm32_adc_priv;
  * @regs:	common registers for all instances
  * @clk_sel:	clock selection routine
  * @max_clk_rate_hz: maximum analog clock rate (Hz, from datasheet)
+ * @has_syscfg: SYSCFG capability flags
  */
 struct stm32_adc_priv_cfg {
 	const struct stm32_adc_common_regs *regs;
 	int (*clk_sel)(struct platform_device *, struct stm32_adc_priv *);
 	u32 max_clk_rate_hz;
+	unsigned int has_syscfg;
 };
 
 /**
@@ -87,22 +102,32 @@ struct stm32_adc_priv_cfg {
  * @domain:		irq domain reference
  * @aclk:		clock reference for the analog circuitry
  * @bclk:		bus clock common for all ADCs, depends on part used
+ * @booster:		booster supply reference
+ * @vdd:		vdd supply reference
  * @vdda:		vdda analog supply reference
  * @vref:		regulator reference
+ * @vdd_uv:		vdd supply voltage (microvolts)
+ * @vdda_uv:		vdda supply voltage (microvolts)
  * @cfg:		compatible configuration data
  * @common:		common data for all ADC instances
  * @ccr_bak:		backup CCR in low power mode
+ * @syscfg:		reference to syscon, system control registers
  */
 struct stm32_adc_priv {
 	int				irq[STM32_ADC_MAX_ADCS];
 	struct irq_domain		*domain;
 	struct clk			*aclk;
 	struct clk			*bclk;
+	struct regulator		*booster;
+	struct regulator		*vdd;
 	struct regulator		*vdda;
 	struct regulator		*vref;
+	int				vdd_uv;
+	int				vdda_uv;
 	const struct stm32_adc_priv_cfg	*cfg;
 	struct stm32_adc_common		common;
 	u32				ccr_bak;
+	struct regmap			*syscfg;
 };
 
 static struct stm32_adc_priv *to_stm32_adc_priv(struct stm32_adc_common *com)
@@ -390,6 +415,82 @@ static void stm32_adc_irq_remove(struct platform_device *pdev,
 	}
 }
 
+static int stm32_adc_core_switches_supply_en(struct stm32_adc_priv *priv,
+					     struct device *dev)
+{
+	int ret;
+
+	/*
+	 * On STM32H7 and STM32MP1, the ADC inputs are multiplexed with analog
+	 * switches (via PCSEL) which have reduced performances when their
+	 * supply is below 2.7V (vdda by default):
+	 * - Voltage booster can be used, to get full ADC performances
+	 *   (increases power consumption).
+	 * - Vdd can be used to supply them, if above 2.7V (STM32MP1 only).
+	 *
+	 * Recommended settings for ANASWVDD and EN_BOOSTER:
+	 * - vdda < 2.7V but vdd > 2.7V: ANASWVDD = 1, EN_BOOSTER = 0 (stm32mp1)
+	 * - vdda < 2.7V and vdd < 2.7V: ANASWVDD = 0, EN_BOOSTER = 1
+	 * - vdda >= 2.7V:               ANASWVDD = 0, EN_BOOSTER = 0 (default)
+	 */
+	if (priv->vdda_uv < 2700000) {
+		if (priv->syscfg && priv->vdd_uv > 2700000) {
+			ret = regulator_enable(priv->vdd);
+			if (ret < 0) {
+				dev_err(dev, "vdd enable failed %d\n", ret);
+				return ret;
+			}
+
+			ret = regmap_write(priv->syscfg,
+					   STM32MP1_SYSCFG_PMCSETR,
+					   STM32MP1_SYSCFG_ANASWVDD_MASK);
+			if (ret < 0) {
+				regulator_disable(priv->vdd);
+				dev_err(dev, "vdd select failed, %d\n", ret);
+				return ret;
+			}
+			dev_dbg(dev, "analog switches supplied by vdd\n");
+
+			return 0;
+		}
+
+		if (priv->booster) {
+			/*
+			 * This is optional, as this is a trade-off between
+			 * analog performance and power consumption.
+			 */
+			ret = regulator_enable(priv->booster);
+			if (ret < 0) {
+				dev_err(dev, "booster enable failed %d\n", ret);
+				return ret;
+			}
+			dev_dbg(dev, "analog switches supplied by booster\n");
+
+			return 0;
+		}
+	}
+
+	/* Fallback using vdda (default), nothing to do */
+	dev_dbg(dev, "analog switches supplied by vdda (%d uV)\n",
+		priv->vdda_uv);
+
+	return 0;
+}
+
+static void stm32_adc_core_switches_supply_dis(struct stm32_adc_priv *priv)
+{
+	if (priv->vdda_uv < 2700000) {
+		if (priv->syscfg && priv->vdd_uv > 2700000) {
+			regmap_write(priv->syscfg, STM32MP1_SYSCFG_PMCCLRR,
+				     STM32MP1_SYSCFG_ANASWVDD_MASK);
+			regulator_disable(priv->vdd);
+			return;
+		}
+		if (priv->booster)
+			regulator_disable(priv->booster);
+	}
+}
+
 static int stm32_adc_core_hw_start(struct device *dev)
 {
 	struct stm32_adc_common *common = dev_get_drvdata(dev);
@@ -402,10 +503,21 @@ static int stm32_adc_core_hw_start(struct device *dev)
 		return ret;
 	}
 
+	ret = regulator_get_voltage(priv->vdda);
+	if (ret < 0) {
+		dev_err(dev, "vdda get voltage failed, %d\n", ret);
+		goto err_vdda_disable;
+	}
+	priv->vdda_uv = ret;
+
+	ret = stm32_adc_core_switches_supply_en(priv, dev);
+	if (ret < 0)
+		goto err_vdda_disable;
+
 	ret = regulator_enable(priv->vref);
 	if (ret < 0) {
 		dev_err(dev, "vref enable failed\n");
-		goto err_vdda_disable;
+		goto err_switches_dis;
 	}
 
 	if (priv->bclk) {
@@ -433,6 +545,8 @@ static int stm32_adc_core_hw_start(struct device *dev)
 		clk_disable_unprepare(priv->bclk);
 err_regulator_disable:
 	regulator_disable(priv->vref);
+err_switches_dis:
+	stm32_adc_core_switches_supply_dis(priv);
 err_vdda_disable:
 	regulator_disable(priv->vdda);
 
@@ -451,9 +565,80 @@ static void stm32_adc_core_hw_stop(struct device *dev)
 	if (priv->bclk)
 		clk_disable_unprepare(priv->bclk);
 	regulator_disable(priv->vref);
+	stm32_adc_core_switches_supply_dis(priv);
 	regulator_disable(priv->vdda);
 }
 
+static int stm32_adc_core_switches_probe(struct device *dev,
+					 struct stm32_adc_priv *priv)
+{
+	struct device_node *np = dev->of_node;
+	int ret;
+
+	/* Analog switches supply can be controlled by syscfg (optional) */
+	priv->syscfg = syscon_regmap_lookup_by_phandle(np, "st,syscfg");
+	if (IS_ERR(priv->syscfg)) {
+		ret = PTR_ERR(priv->syscfg);
+		if (ret != -ENODEV) {
+			if (ret != -EPROBE_DEFER)
+				dev_err(dev, "Can't probe syscfg: %d\n", ret);
+			return ret;
+		}
+		priv->syscfg = NULL;
+	}
+
+	/* Booster can be used to supply analog switches (optional) */
+	if (priv->cfg->has_syscfg & HAS_VBOOSTER &&
+	    of_property_read_bool(np, "booster-supply")) {
+		priv->booster = devm_regulator_get_optional(dev, "booster");
+		if (IS_ERR(priv->booster)) {
+			ret = PTR_ERR(priv->booster);
+			if (ret != -ENODEV) {
+				if (ret != -EPROBE_DEFER)
+					dev_err(dev, "can't get booster %d\n",
+						ret);
+				return ret;
+			}
+			priv->booster = NULL;
+		}
+	}
+
+	/* Vdd can be used to supply analog switches (optional) */
+	if (priv->cfg->has_syscfg & HAS_ANASWVDD &&
+	    of_property_read_bool(np, "vdd-supply")) {
+		priv->vdd = devm_regulator_get_optional(dev, "vdd");
+		if (IS_ERR(priv->vdd)) {
+			ret = PTR_ERR(priv->vdd);
+			if (ret != -ENODEV) {
+				if (ret != -EPROBE_DEFER)
+					dev_err(dev, "can't get vdd %d\n", ret);
+				return ret;
+			}
+			priv->vdd = NULL;
+		}
+	}
+
+	if (priv->vdd) {
+		ret = regulator_enable(priv->vdd);
+		if (ret < 0) {
+			dev_err(dev, "vdd enable failed %d\n", ret);
+			return ret;
+		}
+
+		ret = regulator_get_voltage(priv->vdd);
+		if (ret < 0) {
+			dev_err(dev, "vdd get voltage failed %d\n", ret);
+			regulator_disable(priv->vdd);
+			return ret;
+		}
+		priv->vdd_uv = ret;
+
+		regulator_disable(priv->vdd);
+	}
+
+	return 0;
+}
+
 static int stm32_adc_probe(struct platform_device *pdev)
 {
 	struct stm32_adc_priv *priv;
@@ -514,6 +699,10 @@ static int stm32_adc_probe(struct platform_device *pdev)
 		priv->bclk = NULL;
 	}
 
+	ret = stm32_adc_core_switches_probe(dev, priv);
+	if (ret)
+		return ret;
+
 	pm_runtime_get_noresume(dev);
 	pm_runtime_set_active(dev);
 	pm_runtime_set_autosuspend_delay(dev, STM32_ADC_CORE_SLEEP_DELAY_MS);
@@ -611,12 +800,14 @@ static const struct stm32_adc_priv_cfg stm32h7_adc_priv_cfg = {
 	.regs = &stm32h7_adc_common_regs,
 	.clk_sel = stm32h7_adc_clk_sel,
 	.max_clk_rate_hz = 36000000,
+	.has_syscfg = HAS_VBOOSTER,
 };
 
 static const struct stm32_adc_priv_cfg stm32mp1_adc_priv_cfg = {
 	.regs = &stm32h7_adc_common_regs,
 	.clk_sel = stm32h7_adc_clk_sel,
 	.max_clk_rate_hz = 40000000,
+	.has_syscfg = HAS_VBOOSTER | HAS_ANASWVDD,
 };
 
 static const struct of_device_id stm32_adc_of_match[] = {
-- 
2.7.4


  parent reply	other threads:[~2019-07-03 10:09 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-07-03 10:08 [PATCH v2 0/3] STM32 ADC analog switches supply control Fabrice Gasnier
2019-07-03 10:08 ` [PATCH v2 1/3] dt-bindings: iio: adc: stm32: add " Fabrice Gasnier
2019-07-14 16:11   ` Jonathan Cameron
2019-07-03 10:08 ` Fabrice Gasnier [this message]
2019-07-14 16:12   ` [PATCH v2 2/3] iio: adc: stm32-adc: " Jonathan Cameron
2019-07-03 10:08 ` [PATCH v2 3/3] ARM: dts: stm32: add syscfg to ADC on stm32mp157c Fabrice Gasnier
2019-07-14 16:13   ` Jonathan Cameron
2019-07-15  7:01     ` Fabrice Gasnier
2019-07-29  7:23   ` Alexandre Torgue

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1562148496-26789-3-git-send-email-fabrice.gasnier@st.com \
    --to=fabrice.gasnier@st.com \
    --cc=alexandre.torgue@st.com \
    --cc=devicetree@vger.kernel.org \
    --cc=jic23@kernel.org \
    --cc=knaack.h@gmx.de \
    --cc=lars@metafoo.de \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-iio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-stm32@st-md-mailman.stormreply.com \
    --cc=mark.rutland@arm.com \
    --cc=mcoquelin.stm32@gmail.com \
    --cc=pmeerw@pmeerw.net \
    --cc=robh+dt@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).