mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
257 lines
7.0 KiB
257 lines
7.0 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* Copyright (C) 2018, STMicroelectronics - All Rights Reserved |
|
* Author: Fabrice Gasnier <[email protected]> |
|
* |
|
* Originally based on the Linux kernel v4.18 drivers/iio/adc/stm32-adc.c. |
|
*/ |
|
|
|
#include <common.h> |
|
#include <adc.h> |
|
#include <asm/io.h> |
|
#include <linux/iopoll.h> |
|
#include "stm32-adc-core.h" |
|
|
|
/* STM32H7 - Registers for each ADC instance */ |
|
#define STM32H7_ADC_ISR 0x00 |
|
#define STM32H7_ADC_CR 0x08 |
|
#define STM32H7_ADC_CFGR 0x0C |
|
#define STM32H7_ADC_SMPR1 0x14 |
|
#define STM32H7_ADC_SMPR2 0x18 |
|
#define STM32H7_ADC_PCSEL 0x1C |
|
#define STM32H7_ADC_SQR1 0x30 |
|
#define STM32H7_ADC_DR 0x40 |
|
#define STM32H7_ADC_DIFSEL 0xC0 |
|
|
|
/* STM32H7_ADC_ISR - bit fields */ |
|
#define STM32MP1_VREGREADY BIT(12) |
|
#define STM32H7_EOC BIT(2) |
|
#define STM32H7_ADRDY BIT(0) |
|
|
|
/* STM32H7_ADC_CR - bit fields */ |
|
#define STM32H7_DEEPPWD BIT(29) |
|
#define STM32H7_ADVREGEN BIT(28) |
|
#define STM32H7_BOOST BIT(8) |
|
#define STM32H7_ADSTART BIT(2) |
|
#define STM32H7_ADDIS BIT(1) |
|
#define STM32H7_ADEN BIT(0) |
|
|
|
/* STM32H7_ADC_CFGR bit fields */ |
|
#define STM32H7_EXTEN GENMASK(11, 10) |
|
#define STM32H7_DMNGT GENMASK(1, 0) |
|
|
|
/* STM32H7_ADC_SQR1 - bit fields */ |
|
#define STM32H7_SQ1_SHIFT 6 |
|
|
|
/* BOOST bit must be set on STM32H7 when ADC clock is above 20MHz */ |
|
#define STM32H7_BOOST_CLKRATE 20000000UL |
|
|
|
#define STM32_ADC_CH_MAX 20 /* max number of channels */ |
|
#define STM32_ADC_TIMEOUT_US 100000 |
|
|
|
struct stm32_adc_cfg { |
|
unsigned int max_channels; |
|
unsigned int num_bits; |
|
bool has_vregready; |
|
}; |
|
|
|
struct stm32_adc { |
|
void __iomem *regs; |
|
int active_channel; |
|
const struct stm32_adc_cfg *cfg; |
|
}; |
|
|
|
static int stm32_adc_stop(struct udevice *dev) |
|
{ |
|
struct stm32_adc *adc = dev_get_priv(dev); |
|
|
|
setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADDIS); |
|
clrbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_BOOST); |
|
/* Setting DEEPPWD disables ADC vreg and clears ADVREGEN */ |
|
setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_DEEPPWD); |
|
adc->active_channel = -1; |
|
|
|
return 0; |
|
} |
|
|
|
static int stm32_adc_start_channel(struct udevice *dev, int channel) |
|
{ |
|
struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev); |
|
struct stm32_adc_common *common = dev_get_priv(dev_get_parent(dev)); |
|
struct stm32_adc *adc = dev_get_priv(dev); |
|
int ret; |
|
u32 val; |
|
|
|
/* Exit deep power down, then enable ADC voltage regulator */ |
|
clrbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_DEEPPWD); |
|
setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADVREGEN); |
|
if (common->rate > STM32H7_BOOST_CLKRATE) |
|
setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_BOOST); |
|
|
|
/* Wait for startup time */ |
|
if (!adc->cfg->has_vregready) { |
|
udelay(20); |
|
} else { |
|
ret = readl_poll_timeout(adc->regs + STM32H7_ADC_ISR, val, |
|
val & STM32MP1_VREGREADY, |
|
STM32_ADC_TIMEOUT_US); |
|
if (ret < 0) { |
|
stm32_adc_stop(dev); |
|
dev_err(dev, "Failed to enable vreg: %d\n", ret); |
|
return ret; |
|
} |
|
} |
|
|
|
/* Only use single ended channels */ |
|
writel(0, adc->regs + STM32H7_ADC_DIFSEL); |
|
|
|
/* Enable ADC, Poll for ADRDY to be set (after adc startup time) */ |
|
setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADEN); |
|
ret = readl_poll_timeout(adc->regs + STM32H7_ADC_ISR, val, |
|
val & STM32H7_ADRDY, STM32_ADC_TIMEOUT_US); |
|
if (ret < 0) { |
|
stm32_adc_stop(dev); |
|
dev_err(dev, "Failed to enable ADC: %d\n", ret); |
|
return ret; |
|
} |
|
|
|
/* Preselect channels */ |
|
writel(uc_pdata->channel_mask, adc->regs + STM32H7_ADC_PCSEL); |
|
|
|
/* Set sampling time to max value by default */ |
|
writel(0xffffffff, adc->regs + STM32H7_ADC_SMPR1); |
|
writel(0xffffffff, adc->regs + STM32H7_ADC_SMPR2); |
|
|
|
/* Program regular sequence: chan in SQ1 & len = 0 for one channel */ |
|
writel(channel << STM32H7_SQ1_SHIFT, adc->regs + STM32H7_ADC_SQR1); |
|
|
|
/* Trigger detection disabled (conversion can be launched in SW) */ |
|
clrbits_le32(adc->regs + STM32H7_ADC_CFGR, STM32H7_EXTEN | |
|
STM32H7_DMNGT); |
|
adc->active_channel = channel; |
|
|
|
return 0; |
|
} |
|
|
|
static int stm32_adc_channel_data(struct udevice *dev, int channel, |
|
unsigned int *data) |
|
{ |
|
struct stm32_adc *adc = dev_get_priv(dev); |
|
int ret; |
|
u32 val; |
|
|
|
if (channel != adc->active_channel) { |
|
dev_err(dev, "Requested channel is not active!\n"); |
|
return -EINVAL; |
|
} |
|
|
|
setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADSTART); |
|
ret = readl_poll_timeout(adc->regs + STM32H7_ADC_ISR, val, |
|
val & STM32H7_EOC, STM32_ADC_TIMEOUT_US); |
|
if (ret < 0) { |
|
dev_err(dev, "conversion timed out: %d\n", ret); |
|
return ret; |
|
} |
|
|
|
*data = readl(adc->regs + STM32H7_ADC_DR); |
|
|
|
return 0; |
|
} |
|
|
|
static int stm32_adc_chan_of_init(struct udevice *dev) |
|
{ |
|
struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev); |
|
struct stm32_adc *adc = dev_get_priv(dev); |
|
u32 chans[STM32_ADC_CH_MAX]; |
|
int i, num_channels, ret; |
|
|
|
/* Retrieve single ended channels listed in device tree */ |
|
num_channels = dev_read_size(dev, "st,adc-channels"); |
|
if (num_channels < 0) { |
|
dev_err(dev, "can't get st,adc-channels: %d\n", num_channels); |
|
return num_channels; |
|
} |
|
num_channels /= sizeof(u32); |
|
|
|
if (num_channels > adc->cfg->max_channels) { |
|
dev_err(dev, "too many st,adc-channels: %d\n", num_channels); |
|
return -EINVAL; |
|
} |
|
|
|
ret = dev_read_u32_array(dev, "st,adc-channels", chans, num_channels); |
|
if (ret < 0) { |
|
dev_err(dev, "can't read st,adc-channels: %d\n", ret); |
|
return ret; |
|
} |
|
|
|
for (i = 0; i < num_channels; i++) { |
|
if (chans[i] >= adc->cfg->max_channels) { |
|
dev_err(dev, "bad channel %u\n", chans[i]); |
|
return -EINVAL; |
|
} |
|
uc_pdata->channel_mask |= 1 << chans[i]; |
|
} |
|
|
|
uc_pdata->data_mask = (1 << adc->cfg->num_bits) - 1; |
|
uc_pdata->data_format = ADC_DATA_FORMAT_BIN; |
|
uc_pdata->data_timeout_us = 100000; |
|
|
|
return 0; |
|
} |
|
|
|
static int stm32_adc_probe(struct udevice *dev) |
|
{ |
|
struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev); |
|
struct stm32_adc_common *common = dev_get_priv(dev_get_parent(dev)); |
|
struct stm32_adc *adc = dev_get_priv(dev); |
|
int offset; |
|
|
|
offset = dev_read_u32_default(dev, "reg", -ENODATA); |
|
if (offset < 0) { |
|
dev_err(dev, "Can't read reg property\n"); |
|
return offset; |
|
} |
|
adc->regs = common->base + offset; |
|
adc->cfg = (const struct stm32_adc_cfg *)dev_get_driver_data(dev); |
|
|
|
/* VDD supplied by common vref pin */ |
|
uc_pdata->vdd_supply = common->vref; |
|
uc_pdata->vdd_microvolts = common->vref_uv; |
|
uc_pdata->vss_microvolts = 0; |
|
|
|
return stm32_adc_chan_of_init(dev); |
|
} |
|
|
|
static const struct adc_ops stm32_adc_ops = { |
|
.start_channel = stm32_adc_start_channel, |
|
.channel_data = stm32_adc_channel_data, |
|
.stop = stm32_adc_stop, |
|
}; |
|
|
|
static const struct stm32_adc_cfg stm32h7_adc_cfg = { |
|
.num_bits = 16, |
|
.max_channels = STM32_ADC_CH_MAX, |
|
}; |
|
|
|
static const struct stm32_adc_cfg stm32mp1_adc_cfg = { |
|
.num_bits = 16, |
|
.max_channels = STM32_ADC_CH_MAX, |
|
.has_vregready = true, |
|
}; |
|
|
|
static const struct udevice_id stm32_adc_ids[] = { |
|
{ .compatible = "st,stm32h7-adc", |
|
.data = (ulong)&stm32h7_adc_cfg }, |
|
{ .compatible = "st,stm32mp1-adc", |
|
.data = (ulong)&stm32mp1_adc_cfg }, |
|
{} |
|
}; |
|
|
|
U_BOOT_DRIVER(stm32_adc) = { |
|
.name = "stm32-adc", |
|
.id = UCLASS_ADC, |
|
.of_match = stm32_adc_ids, |
|
.probe = stm32_adc_probe, |
|
.ops = &stm32_adc_ops, |
|
.priv_auto_alloc_size = sizeof(struct stm32_adc), |
|
};
|
|
|