iio: adc: stm32-adc: add analog switches supply control
authorFabrice Gasnier <fabrice.gasnier@st.com>
Wed, 3 Jul 2019 10:08:15 +0000 (12:08 +0200)
committerJonathan Cameron <Jonathan.Cameron@huawei.com>
Sat, 27 Jul 2019 19:11:16 +0000 (20:11 +0100)
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>
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
drivers/iio/adc/stm32-adc-core.c

index 1f7ce51..4299cef 100644 (file)
 #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>
 
 
 #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 @@ err_bclk_disable:
                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[] = {