drivers: adc: stm32: support multiple channels

Support sequencing multiple channels into a single read

Signed-off-by: Hein Wessels <heinwessels93@gmail.com>
This commit is contained in:
Hein Wessels 2022-12-15 12:37:36 +01:00 committed by Fabio Baltieri
commit cbe52e9027

View file

@ -4,6 +4,7 @@
* Copyright (c) 2019 Endre Karlson * Copyright (c) 2019 Endre Karlson
* Copyright (c) 2020 Teslabs Engineering S.L. * Copyright (c) 2020 Teslabs Engineering S.L.
* Copyright (c) 2021 Marius Scholtz, RIC Electronics * Copyright (c) 2021 Marius Scholtz, RIC Electronics
* Copyright (c) 2023 Hein Wessels, Nobleo Technology
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@ -69,6 +70,7 @@ static const uint32_t table_rank[] = {
}; };
#define SEQ_LEN(n) LL_ADC_REG_SEQ_SCAN_ENABLE_##n##RANKS #define SEQ_LEN(n) LL_ADC_REG_SEQ_SCAN_ENABLE_##n##RANKS
/* Length of this array signifies the maximum sequence length */
static const uint32_t table_seq_len[] = { static const uint32_t table_seq_len[] = {
LL_ADC_REG_SEQ_SCAN_DISABLE, LL_ADC_REG_SEQ_SCAN_DISABLE,
SEQ_LEN(2), SEQ_LEN(2),
@ -250,7 +252,9 @@ struct adc_stm32_data {
uint16_t *repeat_buffer; uint16_t *repeat_buffer;
uint8_t resolution; uint8_t resolution;
uint32_t channels;
uint8_t channel_count; uint8_t channel_count;
uint8_t samples_count;
#if defined(CONFIG_SOC_SERIES_STM32F0X) || \ #if defined(CONFIG_SOC_SERIES_STM32F0X) || \
defined(CONFIG_SOC_SERIES_STM32G0X) || \ defined(CONFIG_SOC_SERIES_STM32G0X) || \
defined(CONFIG_SOC_SERIES_STM32L0X) defined(CONFIG_SOC_SERIES_STM32L0X)
@ -534,7 +538,7 @@ static void adc_stm32_set_common_path(const struct device *dev, uint32_t PathInt
LL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(adc), PathInternal); LL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(adc), PathInternal);
} }
static void adc_stm32_setup_channels(const struct device *dev, uint8_t channel_id) static void adc_stm32_setup_channel(const struct device *dev, uint8_t channel_id)
{ {
const struct adc_stm32_cfg *config = dev->config; const struct adc_stm32_cfg *config = dev->config;
ADC_TypeDef *adc = (ADC_TypeDef *)config->base; ADC_TypeDef *adc = (ADC_TypeDef *)config->base;
@ -626,23 +630,29 @@ static void adc_stm32_unset_common_path(const struct device *dev, uint32_t PathI
LL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(adc), PathInternal); LL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(adc), PathInternal);
} }
static void adc_stm32_teardown_channels(const struct device *dev, uint8_t channel_id) static void adc_stm32_teardown_channels(const struct device *dev)
{ {
const struct adc_stm32_cfg *config = dev->config; const struct adc_stm32_cfg *config = dev->config;
struct adc_stm32_data *data = dev->data;
ADC_TypeDef *adc = (ADC_TypeDef *)config->base; ADC_TypeDef *adc = (ADC_TypeDef *)config->base;
uint8_t channel_id;
for (uint32_t channels = data->channels; channels; channels &= ~BIT(channel_id)) {
channel_id = find_lsb_set(channels) - 1;
#ifdef CONFIG_SOC_SERIES_STM32G4X #ifdef CONFIG_SOC_SERIES_STM32G4X
if (config->has_temp_channel) { if (config->has_temp_channel) {
#if DT_NODE_HAS_STATUS(DT_NODELABEL(adc1), okay) #if DT_NODE_HAS_STATUS(DT_NODELABEL(adc1), okay)
if ((__LL_ADC_CHANNEL_TO_DECIMAL_NB(LL_ADC_CHANNEL_TEMPSENSOR_ADC1) == channel_id) if ((__LL_ADC_CHANNEL_TO_DECIMAL_NB(LL_ADC_CHANNEL_TEMPSENSOR_ADC1) ==
&& (config->base == ADC1)) { channel_id) &&
(config->base == ADC1)) {
adc_stm32_disable(adc); adc_stm32_disable(adc);
adc_stm32_unset_common_path(dev, LL_ADC_PATH_INTERNAL_TEMPSENSOR); adc_stm32_unset_common_path(dev, LL_ADC_PATH_INTERNAL_TEMPSENSOR);
} }
#endif #endif
#if DT_NODE_HAS_STATUS(DT_NODELABEL(adc5), okay) #if DT_NODE_HAS_STATUS(DT_NODELABEL(adc5), okay)
if ((__LL_ADC_CHANNEL_TO_DECIMAL_NB(LL_ADC_CHANNEL_TEMPSENSOR_ADC5) == channel_id) if ((__LL_ADC_CHANNEL_TO_DECIMAL_NB(LL_ADC_CHANNEL_TEMPSENSOR_ADC5) ==
&& (config->base == ADC5)) { channel_id) &&
(config->base == ADC5)) {
adc_stm32_disable(adc); adc_stm32_disable(adc);
adc_stm32_unset_common_path(dev, LL_ADC_PATH_INTERNAL_TEMPSENSOR); adc_stm32_unset_common_path(dev, LL_ADC_PATH_INTERNAL_TEMPSENSOR);
} }
@ -651,15 +661,17 @@ static void adc_stm32_teardown_channels(const struct device *dev, uint8_t channe
#elif CONFIG_SOC_SERIES_STM32U5X #elif CONFIG_SOC_SERIES_STM32U5X
if (config->has_temp_channel) { if (config->has_temp_channel) {
#if DT_NODE_HAS_STATUS(DT_NODELABEL(adc1), okay) #if DT_NODE_HAS_STATUS(DT_NODELABEL(adc1), okay)
if ((__LL_ADC_CHANNEL_TO_DECIMAL_NB(LL_ADC_CHANNEL_TEMPSENSOR) == channel_id) if ((__LL_ADC_CHANNEL_TO_DECIMAL_NB(LL_ADC_CHANNEL_TEMPSENSOR) ==
&& (config->base == ADC1)) { channel_id) &&
(config->base == ADC1)) {
adc_stm32_disable(adc); adc_stm32_disable(adc);
adc_stm32_unset_common_path(dev, LL_ADC_PATH_INTERNAL_TEMPSENSOR); adc_stm32_unset_common_path(dev, LL_ADC_PATH_INTERNAL_TEMPSENSOR);
} }
#endif #endif
#if DT_NODE_HAS_STATUS(DT_NODELABEL(adc4), okay) #if DT_NODE_HAS_STATUS(DT_NODELABEL(adc4), okay)
if ((__LL_ADC_CHANNEL_TO_DECIMAL_NB(LL_ADC_CHANNEL_TEMPSENSOR_ADC4) == channel_id) if ((__LL_ADC_CHANNEL_TO_DECIMAL_NB(LL_ADC_CHANNEL_TEMPSENSOR_ADC4) ==
&& (config->base == ADC4)) { channel_id) &&
(config->base == ADC4)) {
adc_stm32_disable(adc); adc_stm32_disable(adc);
adc_stm32_unset_common_path(dev, LL_ADC_PATH_INTERNAL_TEMPSENSOR); adc_stm32_unset_common_path(dev, LL_ADC_PATH_INTERNAL_TEMPSENSOR);
} }
@ -689,6 +701,8 @@ static void adc_stm32_teardown_channels(const struct device *dev, uint8_t channe
adc_stm32_unset_common_path(dev, LL_ADC_PATH_INTERNAL_VBAT); adc_stm32_unset_common_path(dev, LL_ADC_PATH_INTERNAL_VBAT);
} }
#endif /* LL_ADC_CHANNEL_VBAT */ #endif /* LL_ADC_CHANNEL_VBAT */
}
adc_stm32_enable(adc); adc_stm32_enable(adc);
} }
@ -758,19 +772,51 @@ static int start_read(const struct device *dev,
return -EINVAL; return -EINVAL;
} }
uint32_t channels = sequence->channels; data->buffer = sequence->buffer;
uint8_t index = find_lsb_set(channels) - 1; data->channels = sequence->channels;
data->channel_count = POPCOUNT(data->channels);
data->samples_count = 0;
if (channels > BIT(index)) { if (data->channel_count == 0) {
LOG_ERR("Only single channel supported"); LOG_ERR("No channels selected");
return -ENOTSUP; return -EINVAL;
} }
adc_stm32_setup_channels(dev, index); if (data->channels > BIT(STM32_CHANNEL_COUNT) - 1) {
LOG_ERR("Channels bitmask uses out of range channel");
return -EINVAL;
}
data->buffer = sequence->buffer; #if !defined(CONFIG_SOC_SERIES_STM32F0X) && \
!defined(CONFIG_SOC_SERIES_STM32G0X) && \
!defined(CONFIG_SOC_SERIES_STM32L0X) && \
!defined(CONFIG_SOC_SERIES_STM32WLX)
if (data->channel_count > ARRAY_SIZE(table_seq_len)) {
LOG_ERR("Too many channels for sequencer. Max: %d", ARRAY_SIZE(table_seq_len));
return -EINVAL;
}
#else
if (data->channel_count > 1) {
LOG_ERR("This device only supports single channel sampling");
return -EINVAL;
}
#endif
uint8_t channel_id;
uint8_t channel_index = 0;
/* Iterate over selected channels in bitmask keeping track of:
* - channel_index: ranging from 0 -> ( data->channel_count - 1 )
* - channel_id: ordinal position of channel in data->channels bitmask
*/
for (uint32_t channels = data->channels; channels;
channels &= ~BIT(channel_id), channel_index++) {
channel_id = find_lsb_set(channels) - 1;
uint32_t channel = __LL_ADC_DECIMAL_NB_TO_CHANNEL(channel_id);
adc_stm32_setup_channel(dev, channel_id);
uint32_t channel = __LL_ADC_DECIMAL_NB_TO_CHANNEL(index);
#if defined(CONFIG_SOC_SERIES_STM32H7X) #if defined(CONFIG_SOC_SERIES_STM32H7X)
/* /*
* Each channel in the sequence must be previously enabled in PCSEL. * Each channel in the sequence must be previously enabled in PCSEL.
@ -787,6 +833,7 @@ static int start_read(const struct device *dev,
LL_ADC_SetChannelPreselection(adc, channel); LL_ADC_SetChannelPreselection(adc, channel);
} }
#endif #endif
#if defined(CONFIG_SOC_SERIES_STM32F0X) || \ #if defined(CONFIG_SOC_SERIES_STM32F0X) || \
defined(CONFIG_SOC_SERIES_STM32L0X) defined(CONFIG_SOC_SERIES_STM32L0X)
LL_ADC_REG_SetSequencerChannels(adc, channel); LL_ADC_REG_SetSequencerChannels(adc, channel);
@ -811,18 +858,18 @@ static int start_read(const struct device *dev,
LL_ADC_ClearFlag_CCRDY(adc); LL_ADC_ClearFlag_CCRDY(adc);
#elif defined(CONFIG_SOC_SERIES_STM32U5X) #elif defined(CONFIG_SOC_SERIES_STM32U5X)
if (adc != ADC4) { if (adc != ADC4) {
LL_ADC_REG_SetSequencerRanks(adc, table_rank[0], channel); LL_ADC_REG_SetSequencerRanks(adc, table_rank[channel_index], channel);
LL_ADC_REG_SetSequencerLength(adc, table_seq_len[0]); LL_ADC_REG_SetSequencerLength(adc, table_seq_len[channel_index]);
} else { } else {
LL_ADC_REG_SetSequencerConfigurable(adc, LL_ADC_REG_SEQ_FIXED); LL_ADC_REG_SetSequencerConfigurable(adc, LL_ADC_REG_SEQ_FIXED);
LL_ADC_REG_SetSequencerLength(adc, LL_ADC_REG_SetSequencerLength(adc,
BIT(__LL_ADC_CHANNEL_TO_DECIMAL_NB(channel))); BIT(__LL_ADC_CHANNEL_TO_DECIMAL_NB(channel)));
} }
#else #else
LL_ADC_REG_SetSequencerRanks(adc, table_rank[0], channel); LL_ADC_REG_SetSequencerRanks(adc, table_rank[channel_index], channel);
LL_ADC_REG_SetSequencerLength(adc, table_seq_len[0]); LL_ADC_REG_SetSequencerLength(adc, table_seq_len[channel_index]);
#endif #endif
data->channel_count = 1; }
err = check_buffer_size(sequence, data->channel_count); err = check_buffer_size(sequence, data->channel_count);
if (err) { if (err) {
@ -948,11 +995,9 @@ static int start_read(const struct device *dev,
adc_context_start_read(&data->ctx, sequence); adc_context_start_read(&data->ctx, sequence);
err = adc_context_wait_for_completion(&data->ctx); int result = adc_context_wait_for_completion(&data->ctx);
adc_stm32_teardown_channels(dev, index); return result;
return err;
} }
static void adc_context_start_sampling(struct adc_context *ctx) static void adc_context_start_sampling(struct adc_context *ctx)
@ -985,7 +1030,12 @@ static void adc_stm32_isr(const struct device *dev)
*data->buffer++ = LL_ADC_REG_ReadConversionData32(adc); *data->buffer++ = LL_ADC_REG_ReadConversionData32(adc);
/* ISR is triggered after each conversion, and at the end-of-sequence. */
if (++data->samples_count == data->channel_count) {
data->samples_count = 0;
adc_context_on_sampling_done(&data->ctx, dev); adc_context_on_sampling_done(&data->ctx, dev);
adc_stm32_teardown_channels(dev);
}
LOG_DBG("%s ISR triggered.", dev->name); LOG_DBG("%s ISR triggered.", dev->name);
} }