diff --git a/drivers/adc/CMakeLists.txt b/drivers/adc/CMakeLists.txt index eded385c80c..daf2dff347e 100644 --- a/drivers/adc/CMakeLists.txt +++ b/drivers/adc/CMakeLists.txt @@ -37,6 +37,7 @@ zephyr_library_sources_ifdef(CONFIG_ADC_ADS114S0X adc_ads114s0x.c) zephyr_library_sources_ifdef(CONFIG_ADC_RPI_PICO adc_rpi_pico.c) zephyr_library_sources_ifdef(CONFIG_ADC_XMC4XXX adc_xmc4xxx.c) zephyr_library_sources_ifdef(CONFIG_ADC_ESP32 adc_esp32.c) +zephyr_library_sources_ifdef(CONFIG_ADC_GECKO_ADC adc_gecko.c) zephyr_library_sources_ifdef(CONFIG_ADC_GECKO_IADC iadc_gecko.c) zephyr_library_sources_ifdef(CONFIG_ADC_INFINEON_CAT1 adc_ifx_cat1.c) zephyr_library_sources_ifdef(CONFIG_ADC_SMARTBOND_GPADC adc_smartbond_gpadc.c) diff --git a/drivers/adc/Kconfig.gecko b/drivers/adc/Kconfig.gecko index 72de957c9aa..693803b26c9 100644 --- a/drivers/adc/Kconfig.gecko +++ b/drivers/adc/Kconfig.gecko @@ -11,3 +11,12 @@ config ADC_GECKO_IADC select ADC_CONFIGURABLE_INPUTS help Enable the driver implementation for the Silabs GeckoEXX32 Incremental ADC + +config ADC_GECKO_ADC + bool "Gecko ADC driver" + default y + depends on DT_HAS_SILABS_GECKO_ADC_ENABLED + select SOC_GECKO_ADC + select ADC_CONFIGURABLE_INPUTS + help + Enable the driver implementation for the Silabs GeckoEFM32 ADC diff --git a/drivers/adc/adc_gecko.c b/drivers/adc/adc_gecko.c new file mode 100644 index 00000000000..d8544cebd61 --- /dev/null +++ b/drivers/adc/adc_gecko.c @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT silabs_gecko_adc + +#include + +#include +#include + +#define ADC_CONTEXT_USES_KERNEL_TIMER +#include "adc_context.h" + +#include +LOG_MODULE_REGISTER(adc_gecko, CONFIG_ADC_LOG_LEVEL); + +/* Number of channels available. */ +#define GECKO_CHANNEL_COUNT 16 + +struct adc_gecko_channel_config { + bool initialized; + ADC_Ref_TypeDef reference; + ADC_PosSel_TypeDef input_select; +}; + +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; + ADC_Res_TypeDef resolution; + struct adc_gecko_channel_config channel_config[GECKO_CHANNEL_COUNT]; +}; + +struct adc_gecko_config { + ADC_TypeDef *base; + void (*irq_cfg_func)(void); + uint32_t frequency; +}; + +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; + ADC_TypeDef *adc_base = (ADC_TypeDef *)config->base; + + ADC_Init_TypeDef init = ADC_INIT_DEFAULT; + ADC_InitSingle_TypeDef initSingle = ADC_INITSINGLE_DEFAULT; + + channel_config = &data->channel_config[data->channel_id]; + + init.prescale = ADC_PrescaleCalc(config->frequency, 0); + init.timebase = ADC_TimebaseCalc(0); + + initSingle.diff = false; + initSingle.reference = channel_config->reference; + initSingle.resolution = data->resolution; + initSingle.acqTime = adcAcqTime4; + + initSingle.posSel = channel_config->input_select; + + ADC_Init(adc_base, &init); + ADC_InitSingle(adc_base, &initSingle); +} + +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 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; + } + + /* 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); + } + + 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; + ADC_TypeDef *adc_base = (ADC_TypeDef *)config->base; + + data->channel_id = find_lsb_set(data->channels) - 1; + adc_gecko_set_config(data->dev); + + ADC_IntEnable(adc_base, ADC_IEN_SINGLE); + ADC_Start(adc_base, adcStartSingle); +} + +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; + 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; + ADC_TypeDef *adc_base = config->base; + + uint32_t sample = 0; + uint32_t flags, err; + + flags = ADC_IntGet(adc_base); + + __ASSERT(flags & ADC_IF_SINGLE, "unexpected ADC IRQ (flags=0x%08x)!", flags); + + err = flags & (ADC_IF_EM23ERR | ADC_IF_PROGERR | ADC_IF_VREFOV | ADC_IF_SINGLEOF); + + if (!err) { + sample = ADC_DataSingleGet(adc_base); + *data->buffer++ = (uint16_t)sample; + 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("ADC conversion error, flags=%08x", err); + adc_context_complete(&data->ctx, -EIO); + } + ADC_IntClear(adc_base, ADC_IF_SINGLE | 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; +} + +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_select = channel_cfg->input_positive; + + switch (channel_cfg->gain) { + case ADC_GAIN_1: + break; + default: + LOG_ERR("unsupported channel gain '%d'", channel_cfg->gain); + return -ENOTSUP; + } + + switch (channel_cfg->reference) { + case ADC_REF_VDD_1: + channel_config->reference = adcRef5V; + break; + case ADC_REF_VDD_1_2: + channel_config->reference = adcRef2V5; + break; + case ADC_REF_VDD_1_4: + channel_config->reference = adcRef1V25; + break; + default: + LOG_ERR("unsupported channel reference type '%d'", channel_cfg->reference); + return -ENOTSUP; + } + + channel_config->initialized = true; + 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_HFPER, true); + CMU_ClockEnable(cmuClock_ADC0, true); + + data->dev = dev; + data->resolution = adcRes12Bit; + + config->irq_cfg_func(); + + adc_context_unlock_unconditionally(&data->ctx); + + return 0; +} + +static const struct adc_driver_api api_gecko_adc_driver_api = { + .channel_setup = adc_gecko_channel_setup, + .read = adc_gecko_read, +}; + +#define GECKO_ADC_INIT(n) \ + \ + static void adc_gecko_config_func_##n(void); \ + \ + const static struct adc_gecko_config adc_gecko_config_##n = { \ + .base = (ADC_TypeDef *)DT_INST_REG_ADDR(n), \ + .irq_cfg_func = adc_gecko_config_func_##n, \ + .frequency = DT_INST_PROP(n, frequency), \ + }; \ + 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_ADC_INIT) diff --git a/dts/bindings/adc/silabs,gecko-adc.yaml b/dts/bindings/adc/silabs,gecko-adc.yaml new file mode 100644 index 00000000000..33327498e6c --- /dev/null +++ b/dts/bindings/adc/silabs,gecko-adc.yaml @@ -0,0 +1,25 @@ +# Copyright (c) 2023 Antmicro +# SPDX-License-Identifier: Apache-2.0 + +description: Silicon Labs Gecko Series 1 ADC + +compatible: "silabs,gecko-adc" + +include: adc-controller.yaml + +properties: + reg: + required: true + + interrupts: + required: true + + frequency: + type: int + required: true + + "#io-channel-cells": + const: 1 + +io-channel-cells: + - input diff --git a/soc/arm/silabs_exx32/Kconfig b/soc/arm/silabs_exx32/Kconfig index 95b2be39f1b..af050de8109 100644 --- a/soc/arm/silabs_exx32/Kconfig +++ b/soc/arm/silabs_exx32/Kconfig @@ -39,6 +39,11 @@ config SOC_GECKO_CORE help Set if the Core interrupt handling (CORE) HAL module is used. +config SOC_GECKO_ADC + bool + help + Set if the Analog to Digital Converter (ADC) HAL module is used. + config SOC_GECKO_IADC bool help diff --git a/soc/arm/silabs_exx32/efm32pg12b/Kconfig.series b/soc/arm/silabs_exx32/efm32pg12b/Kconfig.series index 7f99d6d64b8..3f3a3ca39f6 100644 --- a/soc/arm/silabs_exx32/efm32pg12b/Kconfig.series +++ b/soc/arm/silabs_exx32/efm32pg12b/Kconfig.series @@ -19,5 +19,6 @@ config SOC_SERIES_EFM32PG12B select SOC_GECKO_EMU select SOC_GECKO_GPIO select SOC_GECKO_TRNG + select SOC_GECKO_ADC help Enable support for EFM32 PearlGecko MCU series