diff options
Diffstat (limited to 'drivers/iio/adc/stm32-adc-core.c')
| -rw-r--r-- | drivers/iio/adc/stm32-adc-core.c | 194 | 
1 files changed, 192 insertions, 2 deletions
diff --git a/drivers/iio/adc/stm32-adc-core.c b/drivers/iio/adc/stm32-adc-core.c index 1f7ce5186dfc..9b85fefc0a96 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) @@ -349,7 +374,6 @@ static int stm32_adc_irq_probe(struct platform_device *pdev,  			 */  			if (i && priv->irq[i] == -ENXIO)  				continue; -			dev_err(&pdev->dev, "failed to get irq\n");  			return priv->irq[i];  		} @@ -390,6 +414,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 +502,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 +544,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 +564,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 +698,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 +799,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[] = {  |