zephyr/drivers/adc/iadc_gecko.c
Pieter De Gendt 8442b6a83f drivers: adc: Place API into iterable section
Move all adc driver api structs into an iterable section, this allows us
to verify if an api pointer is located in compatible linker section.

Signed-off-by: Pieter De Gendt <pieter.degendt@basalte.be>
2024-11-29 14:50:40 +01:00

491 lines
12 KiB
C

/*
* Copyright (c) 2023 Antmicro <www.antmicro.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT silabs_gecko_iadc
#include <zephyr/drivers/adc.h>
#include <em_iadc.h>
#include <em_cmu.h>
#define ADC_CONTEXT_USES_KERNEL_TIMER
#include "adc_context.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(iadc_gecko, CONFIG_ADC_LOG_LEVEL);
/* Number of channels available. */
#define GECKO_CHANNEL_COUNT 16
#define GECKO_INTERNAL_REFERENCE_mV 1210
#define GECKO_DATA_RES12BIT(DATA) ((DATA) & 0x0FFF)
struct adc_gecko_channel_config {
IADC_CfgAnalogGain_t gain;
IADC_CfgReference_t reference;
IADC_PosInput_t input_positive;
IADC_NegInput_t input_negative;
bool initialized;
};
struct adc_gecko_data {
const struct device *dev;
struct adc_context ctx;
uint16_t *buffer;
uint16_t *repeat_buffer;
uint32_t channels;
uint8_t channel_id;
struct adc_gecko_channel_config channel_config[GECKO_CHANNEL_COUNT];
};
struct adc_gecko_config {
IADC_Config_t config;
IADC_TypeDef *base;
void (*irq_cfg_func)(void);
};
static void adc_gecko_set_config(const struct device *dev)
{
struct adc_gecko_data *data = dev->data;
struct adc_gecko_channel_config *channel_config = NULL;
const struct adc_gecko_config *config = dev->config;
IADC_TypeDef *iadc = (IADC_TypeDef *)config->base;
IADC_InitSingle_t sInit = IADC_INITSINGLE_DEFAULT;
IADC_SingleInput_t initSingleInput = IADC_SINGLEINPUT_DEFAULT;
IADC_Init_t init = IADC_INIT_DEFAULT;
IADC_AllConfigs_t initAllConfigs = IADC_ALLCONFIGS_DEFAULT;
channel_config = &data->channel_config[data->channel_id];
initSingleInput.posInput = channel_config->input_positive;
initSingleInput.negInput = channel_config->input_negative;
initAllConfigs.configs[0].analogGain = channel_config->gain;
initAllConfigs.configs[0].reference = channel_config->reference;
IADC_init(iadc, &init, &initAllConfigs);
IADC_initSingle(iadc, &sInit, &initSingleInput);
}
static int adc_gecko_check_buffer_size(const struct adc_sequence *sequence,
uint8_t active_channels)
{
size_t needed_buffer_size;
needed_buffer_size = active_channels * sizeof(uint16_t);
if (sequence->options) {
needed_buffer_size *= (1 + sequence->options->extra_samplings);
}
if (sequence->buffer_size < needed_buffer_size) {
LOG_DBG("Provided buffer is too small (%u/%u)",
sequence->buffer_size, needed_buffer_size);
return -ENOMEM;
}
return 0;
}
static int adc_gecko_check_resolution(const struct adc_sequence *sequence)
{
int value = sequence->resolution;
/* Base resolution is on 12, it can be changed only up by oversampling */
if (value != 12) {
return -EINVAL;
}
return value;
}
static int start_read(const struct device *dev, const struct adc_sequence *sequence)
{
struct adc_gecko_data *data = dev->data;
uint32_t channels;
uint8_t channel_count;
uint8_t index;
int res;
/* Check if at least 1 channel is requested */
if (sequence->channels == 0) {
LOG_DBG("No channel requested");
return -EINVAL;
}
if (sequence->oversampling) {
LOG_ERR("Oversampling is not supported");
return -ENOTSUP;
}
/* Check resolution setting */
res = adc_gecko_check_resolution(sequence);
if (res < 0) {
return -EINVAL;
}
/* Verify all requested channels are initialized and store resolution */
channels = sequence->channels;
channel_count = 0;
while (channels) {
/* Iterate through all channels and check if they are initialized */
index = find_lsb_set(channels) - 1;
if (index >= GECKO_CHANNEL_COUNT) {
LOG_DBG("Requested channel index not available: %d", index);
return -EINVAL;
}
if (!data->channel_config[index].initialized) {
LOG_DBG("Channel not initialized");
return -EINVAL;
}
channel_count++;
channels &= ~BIT(index);
}
/* Check buffer size */
res = adc_gecko_check_buffer_size(sequence, channel_count);
if (res < 0) {
return res;
}
data->buffer = sequence->buffer;
adc_context_start_read(&data->ctx, sequence);
res = adc_context_wait_for_completion(&data->ctx);
return res;
}
static void adc_gecko_start_channel(const struct device *dev)
{
const struct adc_gecko_config *config = dev->config;
struct adc_gecko_data *data = dev->data;
IADC_TypeDef *iadc = (IADC_TypeDef *)config->base;
data->channel_id = find_lsb_set(data->channels) - 1;
LOG_DBG("Starting channel %d", data->channel_id);
adc_gecko_set_config(data->dev);
/* Enable single conversion interrupt */
IADC_enableInt(iadc, IADC_IEN_SINGLEDONE);
/* Start single conversion */
IADC_command(iadc, iadcCmdStartSingle);
}
static void adc_context_start_sampling(struct adc_context *ctx)
{
struct adc_gecko_data *data =
CONTAINER_OF(ctx, struct adc_gecko_data, ctx);
data->channels = ctx->sequence.channels;
data->repeat_buffer = data->buffer;
adc_gecko_start_channel(data->dev);
}
static void adc_context_update_buffer_pointer(struct adc_context *ctx,
bool repeat_sampling)
{
struct adc_gecko_data *data =
CONTAINER_OF(ctx, struct adc_gecko_data, ctx);
if (repeat_sampling) {
data->buffer = data->repeat_buffer;
}
}
static void adc_gecko_isr(void *arg)
{
const struct device *dev = (const struct device *)arg;
const struct adc_gecko_config *config = dev->config;
struct adc_gecko_data *data = dev->data;
IADC_TypeDef *iadc = config->base;
IADC_Result_t sample;
uint32_t flags, err;
/*
* IRQ is enabled only for SINGLEDONE. However, other
* interrupt flags - the ones singaling an error - may be
* set simultaneously with SINGLEDONE. We read & clear them
* to determine if conversion is successful or not.
*/
flags = IADC_getInt(iadc);
__ASSERT(flags & IADC_IF_SINGLEDONE,
"unexpected IADC IRQ (flags=0x%08x)!", flags);
err = flags & (IADC_IF_PORTALLOCERR |
IADC_IF_POLARITYERR |
IADC_IF_EM23ABORTERROR);
if (!err) {
sample = IADC_readSingleResult(iadc);
*data->buffer++ = GECKO_DATA_RES12BIT((uint16_t)sample.data);
data->channels &= ~BIT(data->channel_id);
if (data->channels) {
adc_gecko_start_channel(dev);
} else {
adc_context_on_sampling_done(&data->ctx, dev);
}
} else {
LOG_ERR("IADC conversion error, flags=%08x", err);
adc_context_complete(&data->ctx, -EIO);
}
IADC_clearInt(iadc, IADC_IF_SINGLEDONE | err);
}
static int adc_gecko_read(const struct device *dev,
const struct adc_sequence *sequence)
{
struct adc_gecko_data *data = dev->data;
int error;
adc_context_lock(&data->ctx, false, NULL);
error = start_read(dev, sequence);
adc_context_release(&data->ctx, error);
return error;
}
#ifdef CONFIG_ADC_ASYNC
static int adc_gecko_read_async(const struct device *dev,
const struct adc_sequence *sequence,
struct k_poll_signal *async)
{
struct adc_gecko_data *data = dev->data;
int error;
adc_context_lock(&data->ctx, true, async);
error = start_read(dev, sequence);
adc_context_release(&data->ctx, error);
return error;
}
#endif
static void adc_gecko_gpio_busalloc_pos(IADC_PosInput_t input)
{
uint32_t port = ((input << _IADC_SCAN_PINPOS_SHIFT) &
_IADC_SCAN_PORTPOS_MASK) >> _IADC_SCAN_PORTPOS_SHIFT;
uint32_t pin = ((input << _IADC_SCAN_PINPOS_SHIFT) &
_IADC_SCAN_PINPOS_MASK) >> _IADC_SCAN_PINPOS_SHIFT;
switch (port) {
case _IADC_SCAN_PORTPOS_PORTA:
if (pin & 1) {
GPIO->ABUSALLOC |= GPIO_ABUSALLOC_AODD0_ADC0;
} else {
GPIO->ABUSALLOC |= GPIO_ABUSALLOC_AEVEN0_ADC0;
}
break;
case _IADC_SCAN_PORTPOS_PORTB:
if (pin & 1) {
GPIO->BBUSALLOC |= GPIO_BBUSALLOC_BODD0_ADC0;
} else {
GPIO->BBUSALLOC |= GPIO_BBUSALLOC_BEVEN0_ADC0;
}
break;
case _IADC_SCAN_PORTPOS_PORTC:
case _IADC_SCAN_PORTPOS_PORTD:
if (pin & 1) {
GPIO->CDBUSALLOC |= GPIO_CDBUSALLOC_CDODD0_ADC0;
} else {
GPIO->CDBUSALLOC |= GPIO_CDBUSALLOC_CDEVEN0_ADC0;
}
break;
default:
}
}
static void adc_gecko_gpio_busalloc_neg(IADC_NegInput_t input)
{
uint32_t port = ((input << _IADC_SCAN_PINNEG_SHIFT) &
_IADC_SCAN_PORTNEG_MASK) >> _IADC_SCAN_PORTNEG_SHIFT;
uint32_t pin = ((input << _IADC_SCAN_PINNEG_SHIFT) &
_IADC_SCAN_PINNEG_MASK) >> _IADC_SCAN_PINNEG_SHIFT;
switch (port) {
case _IADC_SCAN_PORTNEG_PORTA:
if (pin & 1) {
GPIO->ABUSALLOC |= GPIO_ABUSALLOC_AODD0_ADC0;
} else {
GPIO->ABUSALLOC |= GPIO_ABUSALLOC_AEVEN0_ADC0;
}
break;
case _IADC_SCAN_PORTNEG_PORTB:
if (pin & 1) {
GPIO->BBUSALLOC |= GPIO_BBUSALLOC_BODD0_ADC0;
} else {
GPIO->BBUSALLOC |= GPIO_BBUSALLOC_BEVEN0_ADC0;
}
break;
case _IADC_SCAN_PORTNEG_PORTC:
case _IADC_SCAN_PORTNEG_PORTD:
if (pin & 1) {
GPIO->CDBUSALLOC |= GPIO_CDBUSALLOC_CDODD0_ADC0;
} else {
GPIO->CDBUSALLOC |= GPIO_CDBUSALLOC_CDEVEN0_ADC0;
}
break;
default:
}
}
static int adc_gecko_channel_setup(const struct device *dev,
const struct adc_channel_cfg *channel_cfg)
{
struct adc_gecko_data *data = dev->data;
struct adc_gecko_channel_config *channel_config = NULL;
if (channel_cfg->channel_id < GECKO_CHANNEL_COUNT) {
channel_config = &data->channel_config[channel_cfg->channel_id];
} else {
LOG_DBG("Requested channel index not available: %d", channel_cfg->channel_id);
return -EINVAL;
}
channel_config->initialized = false;
channel_config->input_positive = channel_cfg->input_positive;
if (channel_cfg->differential) {
channel_config->input_negative = channel_cfg->input_negative;
} else {
channel_config->input_negative = iadcNegInputGnd;
}
/* Setup input */
switch (channel_cfg->gain) {
#if defined(_IADC_CFG_ANALOGGAIN_ANAGAIN0P25)
case ADC_GAIN_1_4:
channel_config->gain = iadcCfgAnalogGain0P25x;
break;
#endif
case ADC_GAIN_1_2:
channel_config->gain = iadcCfgAnalogGain0P5x;
break;
case ADC_GAIN_1:
channel_config->gain = iadcCfgAnalogGain1x;
break;
case ADC_GAIN_2:
channel_config->gain = iadcCfgAnalogGain2x;
break;
case ADC_GAIN_3:
channel_config->gain = iadcCfgAnalogGain3x;
break;
case ADC_GAIN_4:
channel_config->gain = iadcCfgAnalogGain4x;
break;
default:
LOG_ERR("unsupported channel gain '%d'", channel_cfg->gain);
return -ENOTSUP;
}
/* Setup reference */
switch (channel_cfg->reference) {
case ADC_REF_VDD_1:
channel_config->reference = iadcCfgReferenceVddx;
break;
case ADC_REF_INTERNAL:
channel_config->reference = iadcCfgReferenceInt1V2;
break;
#if defined(_IADC_CFG_REFSEL_VREF2P5)
case ADC_REF_EXTERNAL1:
channel_config->reference = iadcCfgReferenceExt2V5;
break;
#endif
case ADC_REF_EXTERNAL0:
channel_config->reference = iadcCfgReferenceExt1V25;
break;
default:
LOG_ERR("unsupported channel reference type '%d'",
channel_cfg->reference);
return -ENOTSUP;
}
/* Setup GPIO xBUSALLOC registers if channel uses GPIO pin */
adc_gecko_gpio_busalloc_pos(channel_config->input_positive);
adc_gecko_gpio_busalloc_neg(channel_config->input_negative);
channel_config->initialized = true;
LOG_DBG("Channel setup succeeded!");
return 0;
}
static int adc_gecko_init(const struct device *dev)
{
const struct adc_gecko_config *config = dev->config;
struct adc_gecko_data *data = dev->data;
CMU_ClockEnable(cmuClock_IADC0, true);
/* Select clock for IADC */
CMU_ClockSelectSet(cmuClock_IADCCLK, cmuSelect_FSRCO); /* FSRCO - 20MHz */
data->dev = dev;
config->irq_cfg_func();
adc_context_unlock_unconditionally(&data->ctx);
return 0;
}
static DEVICE_API(adc, api_gecko_adc_driver_api) = {
.channel_setup = adc_gecko_channel_setup,
.read = adc_gecko_read,
#ifdef CONFIG_ADC_ASYNC
.read_async = adc_gecko_read_async,
#endif
.ref_internal = GECKO_INTERNAL_REFERENCE_mV,
};
#define GECKO_IADC_INIT(n) \
\
static void adc_gecko_config_func_##n(void); \
\
const static struct adc_gecko_config adc_gecko_config_##n = { \
.base = (IADC_TypeDef *)DT_INST_REG_ADDR(n),\
.irq_cfg_func = adc_gecko_config_func_##n, \
}; \
static struct adc_gecko_data adc_gecko_data_##n = { \
ADC_CONTEXT_INIT_TIMER(adc_gecko_data_##n, ctx), \
ADC_CONTEXT_INIT_LOCK(adc_gecko_data_##n, ctx), \
ADC_CONTEXT_INIT_SYNC(adc_gecko_data_##n, ctx), \
}; \
static void adc_gecko_config_func_##n(void) \
{ \
IRQ_CONNECT(DT_INST_IRQN(n), \
DT_INST_IRQ(n, priority), \
adc_gecko_isr, DEVICE_DT_INST_GET(n), 0); \
irq_enable(DT_INST_IRQN(n)); \
}; \
DEVICE_DT_INST_DEFINE(n, \
&adc_gecko_init, NULL, \
&adc_gecko_data_##n, &adc_gecko_config_##n,\
POST_KERNEL, CONFIG_ADC_INIT_PRIORITY, \
&api_gecko_adc_driver_api);
DT_INST_FOREACH_STATUS_OKAY(GECKO_IADC_INIT)