Commit 3a81bde5 authored by Fabrice Gasnier's avatar Fabrice Gasnier Committed by Jonathan Cameron

iio: adc: stm32-adc: add analog switches supply control

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: default avatarFabrice Gasnier <fabrice.gasnier@st.com>
Signed-off-by: default avatarJonathan Cameron <Jonathan.Cameron@huawei.com>
parent a85a43e0
...@@ -14,9 +14,11 @@ ...@@ -14,9 +14,11 @@
#include <linux/irqchip/chained_irq.h> #include <linux/irqchip/chained_irq.h>
#include <linux/irqdesc.h> #include <linux/irqdesc.h>
#include <linux/irqdomain.h> #include <linux/irqdomain.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of_device.h> #include <linux/of_device.h>
#include <linux/pm_runtime.h> #include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h> #include <linux/regulator/consumer.h>
#include <linux/slab.h> #include <linux/slab.h>
...@@ -51,6 +53,17 @@ ...@@ -51,6 +53,17 @@
#define STM32_ADC_CORE_SLEEP_DELAY_MS 2000 #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 * stm32_adc_common_regs - stm32 common registers, compatible dependent data
* @csr: common status register offset * @csr: common status register offset
...@@ -74,11 +87,13 @@ struct stm32_adc_priv; ...@@ -74,11 +87,13 @@ struct stm32_adc_priv;
* @regs: common registers for all instances * @regs: common registers for all instances
* @clk_sel: clock selection routine * @clk_sel: clock selection routine
* @max_clk_rate_hz: maximum analog clock rate (Hz, from datasheet) * @max_clk_rate_hz: maximum analog clock rate (Hz, from datasheet)
* @has_syscfg: SYSCFG capability flags
*/ */
struct stm32_adc_priv_cfg { struct stm32_adc_priv_cfg {
const struct stm32_adc_common_regs *regs; const struct stm32_adc_common_regs *regs;
int (*clk_sel)(struct platform_device *, struct stm32_adc_priv *); int (*clk_sel)(struct platform_device *, struct stm32_adc_priv *);
u32 max_clk_rate_hz; u32 max_clk_rate_hz;
unsigned int has_syscfg;
}; };
/** /**
...@@ -87,22 +102,32 @@ struct stm32_adc_priv_cfg { ...@@ -87,22 +102,32 @@ struct stm32_adc_priv_cfg {
* @domain: irq domain reference * @domain: irq domain reference
* @aclk: clock reference for the analog circuitry * @aclk: clock reference for the analog circuitry
* @bclk: bus clock common for all ADCs, depends on part used * @bclk: bus clock common for all ADCs, depends on part used
* @booster: booster supply reference
* @vdd: vdd supply reference
* @vdda: vdda analog supply reference * @vdda: vdda analog supply reference
* @vref: regulator reference * @vref: regulator reference
* @vdd_uv: vdd supply voltage (microvolts)
* @vdda_uv: vdda supply voltage (microvolts)
* @cfg: compatible configuration data * @cfg: compatible configuration data
* @common: common data for all ADC instances * @common: common data for all ADC instances
* @ccr_bak: backup CCR in low power mode * @ccr_bak: backup CCR in low power mode
* @syscfg: reference to syscon, system control registers
*/ */
struct stm32_adc_priv { struct stm32_adc_priv {
int irq[STM32_ADC_MAX_ADCS]; int irq[STM32_ADC_MAX_ADCS];
struct irq_domain *domain; struct irq_domain *domain;
struct clk *aclk; struct clk *aclk;
struct clk *bclk; struct clk *bclk;
struct regulator *booster;
struct regulator *vdd;
struct regulator *vdda; struct regulator *vdda;
struct regulator *vref; struct regulator *vref;
int vdd_uv;
int vdda_uv;
const struct stm32_adc_priv_cfg *cfg; const struct stm32_adc_priv_cfg *cfg;
struct stm32_adc_common common; struct stm32_adc_common common;
u32 ccr_bak; u32 ccr_bak;
struct regmap *syscfg;
}; };
static struct stm32_adc_priv *to_stm32_adc_priv(struct stm32_adc_common *com) 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, ...@@ -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) static int stm32_adc_core_hw_start(struct device *dev)
{ {
struct stm32_adc_common *common = dev_get_drvdata(dev); struct stm32_adc_common *common = dev_get_drvdata(dev);
...@@ -402,10 +503,21 @@ static int stm32_adc_core_hw_start(struct device *dev) ...@@ -402,10 +503,21 @@ static int stm32_adc_core_hw_start(struct device *dev)
return ret; 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); ret = regulator_enable(priv->vref);
if (ret < 0) { if (ret < 0) {
dev_err(dev, "vref enable failed\n"); dev_err(dev, "vref enable failed\n");
goto err_vdda_disable; goto err_switches_dis;
} }
if (priv->bclk) { if (priv->bclk) {
...@@ -433,6 +545,8 @@ static int stm32_adc_core_hw_start(struct device *dev) ...@@ -433,6 +545,8 @@ static int stm32_adc_core_hw_start(struct device *dev)
clk_disable_unprepare(priv->bclk); clk_disable_unprepare(priv->bclk);
err_regulator_disable: err_regulator_disable:
regulator_disable(priv->vref); regulator_disable(priv->vref);
err_switches_dis:
stm32_adc_core_switches_supply_dis(priv);
err_vdda_disable: err_vdda_disable:
regulator_disable(priv->vdda); regulator_disable(priv->vdda);
...@@ -451,9 +565,80 @@ static void stm32_adc_core_hw_stop(struct device *dev) ...@@ -451,9 +565,80 @@ static void stm32_adc_core_hw_stop(struct device *dev)
if (priv->bclk) if (priv->bclk)
clk_disable_unprepare(priv->bclk); clk_disable_unprepare(priv->bclk);
regulator_disable(priv->vref); regulator_disable(priv->vref);
stm32_adc_core_switches_supply_dis(priv);
regulator_disable(priv->vdda); 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) static int stm32_adc_probe(struct platform_device *pdev)
{ {
struct stm32_adc_priv *priv; struct stm32_adc_priv *priv;
...@@ -514,6 +699,10 @@ static int stm32_adc_probe(struct platform_device *pdev) ...@@ -514,6 +699,10 @@ static int stm32_adc_probe(struct platform_device *pdev)
priv->bclk = NULL; priv->bclk = NULL;
} }
ret = stm32_adc_core_switches_probe(dev, priv);
if (ret)
return ret;
pm_runtime_get_noresume(dev); pm_runtime_get_noresume(dev);
pm_runtime_set_active(dev); pm_runtime_set_active(dev);
pm_runtime_set_autosuspend_delay(dev, STM32_ADC_CORE_SLEEP_DELAY_MS); 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 = { ...@@ -611,12 +800,14 @@ static const struct stm32_adc_priv_cfg stm32h7_adc_priv_cfg = {
.regs = &stm32h7_adc_common_regs, .regs = &stm32h7_adc_common_regs,
.clk_sel = stm32h7_adc_clk_sel, .clk_sel = stm32h7_adc_clk_sel,
.max_clk_rate_hz = 36000000, .max_clk_rate_hz = 36000000,
.has_syscfg = HAS_VBOOSTER,
}; };
static const struct stm32_adc_priv_cfg stm32mp1_adc_priv_cfg = { static const struct stm32_adc_priv_cfg stm32mp1_adc_priv_cfg = {
.regs = &stm32h7_adc_common_regs, .regs = &stm32h7_adc_common_regs,
.clk_sel = stm32h7_adc_clk_sel, .clk_sel = stm32h7_adc_clk_sel,
.max_clk_rate_hz = 40000000, .max_clk_rate_hz = 40000000,
.has_syscfg = HAS_VBOOSTER | HAS_ANASWVDD,
}; };
static const struct of_device_id stm32_adc_of_match[] = { static const struct of_device_id stm32_adc_of_match[] = {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment