mirror of
				https://xff.cz/git/u-boot/
				synced 2025-10-31 18:35:42 +01:00 
			
		
		
		
	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>
		
			
				
	
	
		
			210 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			210 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // 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),
 | |
| };
 |