diff --git a/drivers/adc/CMakeLists.txt b/drivers/adc/CMakeLists.txt index 7b1c0fe96b..676be802d1 100644 --- a/drivers/adc/CMakeLists.txt +++ b/drivers/adc/CMakeLists.txt @@ -13,6 +13,7 @@ zephyr_library_sources_ifdef(CONFIG_ADC_MCUX_LPADC adc_mcux_lpadc.c) zephyr_library_sources_ifdef(CONFIG_ADC_SAM_AFEC adc_sam_afec.c) zephyr_library_sources_ifdef(CONFIG_ADC_NRFX_ADC adc_nrfx_adc.c) zephyr_library_sources_ifdef(CONFIG_ADC_NRFX_SAADC adc_nrfx_saadc.c) +zephyr_library_sources_ifdef(CONFIG_ADC_SAM adc_sam.c) zephyr_library_sources_ifdef(CONFIG_ADC_SAM0 adc_sam0.c) zephyr_library_sources_ifdef(CONFIG_ADC_STM32 adc_stm32.c) zephyr_library_sources_ifdef(CONFIG_ADC_XEC adc_mchp_xec.c) diff --git a/drivers/adc/Kconfig b/drivers/adc/Kconfig index cbcd81eda7..149f5172c5 100644 --- a/drivers/adc/Kconfig +++ b/drivers/adc/Kconfig @@ -54,6 +54,8 @@ source "drivers/adc/Kconfig.nrfx" source "drivers/adc/Kconfig.sam_afec" +source "drivers/adc/Kconfig.sam" + source "drivers/adc/Kconfig.sam0" source "drivers/adc/Kconfig.stm32" diff --git a/drivers/adc/Kconfig.sam b/drivers/adc/Kconfig.sam new file mode 100644 index 0000000000..20c1bd4249 --- /dev/null +++ b/drivers/adc/Kconfig.sam @@ -0,0 +1,10 @@ +# Copyright (c) 2022, Basalte bv +# SPDX-License-Identifier: Apache-2.0 + +config ADC_SAM + bool "Atmel SAM series ADC Driver" + default y + depends on DT_HAS_ATMEL_SAM_ADC_ENABLED + select ADC_CONFIGURABLE_INPUTS + help + Enable Atmel SAM MCU Family Analog-to-Digital Converter (ADC) driver. diff --git a/drivers/adc/adc_sam.c b/drivers/adc/adc_sam.c new file mode 100644 index 0000000000..3536a88af4 --- /dev/null +++ b/drivers/adc/adc_sam.c @@ -0,0 +1,419 @@ +/* + * Copyright (c) 2022, Basalte bv + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT atmel_sam_adc + +#include +#include +#include +#include + +#define ADC_CONTEXT_USES_KERNEL_TIMER +#include "adc_context.h" + +#include +#include +LOG_MODULE_REGISTER(adc_sam, CONFIG_ADC_LOG_LEVEL); + +#define SAM_ADC_NUM_CHANNELS 16 +#define SAM_ADC_TEMP_CHANNEL 15 + +struct adc_sam_config { + Adc *regs; + const struct atmel_sam_pmc_config clock_cfg; + + uint8_t prescaler; + uint8_t startup_time; + uint8_t settling_time; + uint8_t tracking_time; + + const struct pinctrl_dev_config *pcfg; + void (*config_func)(const struct device *dev); +}; + +struct adc_sam_data { + struct adc_context ctx; + const struct device *dev; + + /* Pointer to the buffer in the sequence. */ + uint16_t *buffer; + + /* Pointer to the beginning of a sample. Consider the number of + * channels in the sequence: this buffer changes by that amount + * so all the channels would get repeated. + */ + uint16_t *repeat_buffer; + + /* Number of active channels to fill buffer */ + uint8_t num_active_channels; +}; + +static uint8_t count_bits(uint32_t val) +{ + uint8_t res = 0; + + while (val) { + res += val & 1U; + val >>= 1; + } + + return res; +} + +static int adc_sam_channel_setup(const struct device *dev, + const struct adc_channel_cfg *channel_cfg) +{ + const struct adc_sam_config *const cfg = dev->config; + Adc *const adc = cfg->regs; + + uint8_t channel_id = channel_cfg->channel_id; + + if (channel_cfg->differential) { + if (channel_id != (channel_cfg->input_positive / 2U) + || channel_id != (channel_cfg->input_negative / 2U)) { + LOG_ERR("Invalid ADC differential input for channel %u", channel_id); + return -EINVAL; + } + } else { + if (channel_id != channel_cfg->input_positive) { + LOG_ERR("Invalid ADC single-ended input for channel %u", channel_id); + return -EINVAL; + } + } + + if (channel_cfg->acquisition_time != ADC_ACQ_TIME_DEFAULT) { + LOG_ERR("Invalid ADC channel acquisition time"); + return -EINVAL; + } + + if (channel_cfg->reference != ADC_REF_EXTERNAL0) { + LOG_ERR("Invalid ADC channel reference (%d)", channel_cfg->reference); + return -EINVAL; + } + + /* Enable internal temperature sensor (channel 15 / single-ended) */ + if (channel_cfg->channel_id == SAM_ADC_TEMP_CHANNEL) { + adc->ADC_ACR |= ADC_ACR_TSON; + } + + /* Set channel mode, always on both inputs */ + if (channel_cfg->differential) { + adc->ADC_COR |= (ADC_COR_DIFF0 | ADC_COR_DIFF1) << (channel_id * 2U); + } else { + adc->ADC_COR &= ~((ADC_COR_DIFF0 | ADC_COR_DIFF1) << (channel_id * 2U)); + } + + /* Reset current gain */ + adc->ADC_CGR &= ~(ADC_CGR_GAIN0_Msk << (channel_id * 2U)); + + switch (channel_cfg->gain) { + case ADC_GAIN_1_2: + if (!channel_cfg->differential) { + LOG_ERR("ADC 1/2x gain only allowed for differential channel"); + return -EINVAL; + } + /* NOP */ + break; + case ADC_GAIN_1: + adc->ADC_CGR |= ADC_CGR_GAIN0(1) << (channel_id * 2U); + break; + case ADC_GAIN_2: + adc->ADC_CGR |= ADC_CGR_GAIN0(2) << (channel_id * 2U); + break; + case ADC_GAIN_4: + if (channel_cfg->differential) { + LOG_ERR("ADC 4x gain only allowed for single-ended channel"); + return -EINVAL; + } + adc->ADC_CGR |= ADC_CGR_GAIN0(3) << (channel_id * 2U); + break; + default: + LOG_ERR("Invalid ADC channel gain (%d)", channel_cfg->gain); + return -EINVAL; + } + + return 0; +} + +static void adc_sam_start_conversion(const struct device *dev) +{ + const struct adc_sam_config *const cfg = dev->config; + Adc *const adc = cfg->regs; + + adc->ADC_CR = ADC_CR_START; +} + +/** + * This is only called once at the beginning of all the conversions, + * all channels as a group. + */ +static void adc_context_start_sampling(struct adc_context *ctx) +{ + struct adc_sam_data *data = CONTAINER_OF(ctx, struct adc_sam_data, ctx); + const struct adc_sam_config *const cfg = data->dev->config; + Adc *const adc = cfg->regs; + + data->num_active_channels = count_bits(ctx->sequence.channels); + + /* Disable all */ + adc->ADC_CHDR = 0xffff; + + /* Enable selected */ + adc->ADC_CHER = ctx->sequence.channels; + + LOG_DBG("Starting conversion for %u channels", data->num_active_channels); + + adc_sam_start_conversion(data->dev); +} + +static void adc_context_update_buffer_pointer(struct adc_context *ctx, bool repeat) +{ + struct adc_sam_data *data = CONTAINER_OF(ctx, struct adc_sam_data, ctx); + + if (repeat) { + data->buffer = data->repeat_buffer; + } +} + +static int check_buffer_size(const struct adc_sequence *sequence, + uint8_t active_channels) +{ + size_t 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_ERR("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_sam_data *data = dev->data; + uint32_t channels = sequence->channels; + int error; + + /* Signal an error if the channel selection is invalid (no channels or + * a non-existing one is selected). + */ + if (channels == 0U || + (channels & (~0UL << SAM_ADC_NUM_CHANNELS))) { + LOG_ERR("Invalid selection of channels"); + return -EINVAL; + } + + if (sequence->oversampling != 0U) { + LOG_ERR("Oversampling is not supported"); + return -EINVAL; + } + + if (sequence->resolution != 12U) { + LOG_ERR("ADC resolution %d is not valid", sequence->resolution); + return -EINVAL; + } + + data->num_active_channels = count_bits(channels); + + error = check_buffer_size(sequence, data->num_active_channels); + if (error) { + return error; + } + + data->buffer = sequence->buffer; + data->repeat_buffer = sequence->buffer; + + /* At this point we allow the scheduler to do other things while + * we wait for the conversions to complete. This is provided by the + * adc_context functions. However, the caller of this function is + * blocked until the results are in. + */ + adc_context_start_read(&data->ctx, sequence); + + return adc_context_wait_for_completion(&data->ctx); +} + +static int adc_sam_read(const struct device *dev, + const struct adc_sequence *sequence) +{ + struct adc_sam_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 void adc_sam_isr(const struct device *dev) +{ + const struct adc_sam_config *const cfg = dev->config; + struct adc_sam_data *data = dev->data; + Adc *const adc = cfg->regs; + + uint16_t result; + + if (adc->ADC_ISR & ADC_ISR_DRDY) { + result = adc->ADC_LCDR & ADC_LCDR_LDATA_Msk; + + *data->buffer++ = result; + data->num_active_channels--; + + if (data->num_active_channels == 0) { + /* Called once all conversions have completed.*/ + adc_context_on_sampling_done(&data->ctx, dev); + } else { + adc_sam_start_conversion(dev); + } + } +} + +static int adc_sam_init(const struct device *dev) +{ + const struct adc_sam_config *const cfg = dev->config; + struct adc_sam_data *data = dev->data; + Adc *const adc = cfg->regs; + + int ret; + uint32_t frequency, conv_periods; + + /* Get peripheral clock frequency */ + ret = clock_control_get_rate(SAM_DT_PMC_CONTROLLER, + (clock_control_subsys_t)&cfg->clock_cfg, + &frequency); + if (ret < 0) { + LOG_ERR("Failed to get ADC peripheral clock rate (%d)", ret); + return -ENODEV; + } + + /* Calculate ADC clock frequency */ + frequency = frequency / 2U / (cfg->prescaler + 1U); + if (frequency < 1000000U || frequency > 22000000U) { + LOG_ERR("Invalid ADC clock frequency %d (1MHz < freq < 22Mhz)", frequency); + return -EINVAL; + } + + /* The number of ADC pulses for conversion */ + conv_periods = MAX(20U, cfg->tracking_time + 6U); + + /* Calculate the sampling frequency */ + frequency /= conv_periods; + + /* Reset ADC controller */ + adc->ADC_CR = ADC_CR_SWRST; + + /* Reset Mode */ + adc->ADC_MR = 0U; + + /* Reset PDC transfer */ + adc->ADC_PTCR = ADC_PTCR_RXTDIS | ADC_PTCR_TXTDIS; + adc->ADC_RCR = 0U; + adc->ADC_RNCR = 0U; + + /* Set prescaler, timings and allow different analog settings for each channel */ + adc->ADC_MR = ADC_MR_PRESCAL(cfg->prescaler) + | ADC_MR_STARTUP(cfg->startup_time) + | ADC_MR_SETTLING(cfg->settling_time) + | ADC_MR_TRACKTIM(cfg->tracking_time) + | ADC_MR_TRANSFER(2U) /* Should be 2 to guarantee the optimal hold time. */ + | ADC_MR_ANACH_ALLOWED; + + /** + * Set bias current control + * IBCTL = 00 is the required value for a sampling frequency below 500 kHz, + * and IBCTL = 01 for a sampling frequency between 500 kHz and 1 MHz. + */ + adc->ADC_ACR = ADC_ACR_IBCTL(frequency < 500000U ? 0U : 1U); + + /* Enable ADC clock in PMC */ + ret = clock_control_on(SAM_DT_PMC_CONTROLLER, + (clock_control_subsys_t)&cfg->clock_cfg); + if (ret < 0) { + LOG_ERR("Failed to enable ADC clock (%d)", ret); + return -ENODEV; + } + + ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); + if (ret < 0) { + return ret; + } + + cfg->config_func(dev); + + /* Enable data ready interrupt */ + adc->ADC_IER = ADC_IER_DRDY; + + data->dev = dev; + + adc_context_unlock_unconditionally(&data->ctx); + + return 0; +} + +#ifdef CONFIG_ADC_ASYNC +static int adc_sam_read_async(const struct device *dev, + const struct adc_sequence *sequence, + struct k_poll_signal *async) +{ + struct adc_sam_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 const struct adc_driver_api adc_sam_api = { + .channel_setup = adc_sam_channel_setup, + .read = adc_sam_read, +#ifdef CONFIG_ADC_ASYNC + .read_async = adc_sam_read_async, +#endif +}; + +#define ADC_SAM_DEVICE(n) \ + PINCTRL_DT_INST_DEFINE(n); \ + static void adc_sam_irq_config_##n(const struct device *dev) \ + { \ + IRQ_CONNECT(DT_INST_IRQN(n), \ + DT_INST_IRQ(n, priority), \ + adc_sam_isr, \ + DEVICE_DT_INST_GET(n), 0); \ + irq_enable(DT_INST_IRQN(n)); \ + } \ + static const struct adc_sam_config adc_sam_config_##n = { \ + .regs = (Adc *)DT_INST_REG_ADDR(n), \ + .clock_cfg = SAM_DT_INST_CLOCK_PMC_CFG(n), \ + .prescaler = DT_INST_PROP(n, prescaler), \ + .startup_time = DT_INST_ENUM_IDX(n, startup_time), \ + .settling_time = DT_INST_ENUM_IDX(n, settling_time), \ + .tracking_time = DT_INST_ENUM_IDX(n, tracking_time), \ + .config_func = &adc_sam_irq_config_##n, \ + .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ + }; \ + static struct adc_sam_data adc_sam_data_##n = { \ + ADC_CONTEXT_INIT_TIMER(adc_sam_data_##n, ctx), \ + ADC_CONTEXT_INIT_LOCK(adc_sam_data_##n, ctx), \ + ADC_CONTEXT_INIT_SYNC(adc_sam_data_##n, ctx), \ + .dev = DEVICE_DT_INST_GET(n), \ + }; \ + DEVICE_DT_INST_DEFINE(n, adc_sam_init, NULL, \ + &adc_sam_data_##n, \ + &adc_sam_config_##n, POST_KERNEL, \ + CONFIG_ADC_INIT_PRIORITY, \ + &adc_sam_api); + +DT_INST_FOREACH_STATUS_OKAY(ADC_SAM_DEVICE) diff --git a/drivers/adc/adc_shell.c b/drivers/adc/adc_shell.c index 342420719d..31f8c05f37 100644 --- a/drivers/adc/adc_shell.c +++ b/drivers/adc/adc_shell.c @@ -15,6 +15,8 @@ #define DT_DRV_COMPAT atmel_sam_afec #elif DT_HAS_COMPAT_STATUS_OKAY(espressif_esp32_adc) #define DT_DRV_COMPAT espressif_esp32_adc +#elif DT_HAS_COMPAT_STATUS_OKAY(atmel_sam_adc) +#define DT_DRV_COMPAT atmel_sam_adc #elif DT_HAS_COMPAT_STATUS_OKAY(atmel_sam0_adc) #define DT_DRV_COMPAT atmel_sam0_adc #elif DT_HAS_COMPAT_STATUS_OKAY(ite_it8xxx2_adc) diff --git a/dts/bindings/adc/atmel,sam-adc.yaml b/dts/bindings/adc/atmel,sam-adc.yaml new file mode 100644 index 0000000000..6f78a6b578 --- /dev/null +++ b/dts/bindings/adc/atmel,sam-adc.yaml @@ -0,0 +1,61 @@ +# Copyright (c) 2022, Basalte bv +# SPDX-License-Identifier: Apache-2.0 + +description: Atmel SAM family ADC + +compatible: "atmel,sam-adc" + +include: [adc-controller.yaml, pinctrl-device.yaml] + +properties: + reg: + required: true + + interrupts: + required: true + + clocks: + required: true + + prescaler: + type: int + required: true + description: CPU clock prescaler applied to get the ADC clock. + + startup-time: + type: int + required: true + description: | + ADC startup time in ADC clock cycles. + enum: [0, 8, 16, 24, 64, 80, 96, 112, 512, 576, 640, 704, 768, 832, 896, 960] + + settling-time: + type: int + required: true + description: | + ADC settling time in ADC clock cycles. When the gain, offset + or differential input parameters of the analog cell change + between two channels, the analog cell may need a specific + settling time before starting the tracking phase. + enum: [3, 5, 9, 17] + + tracking-time: + type: int + required: true + description: | + ADC tracking time in ADC clock cycles. A minimal tracking time + is necessary for the ADC to guarantee the best converted final + value between two channel selections. + enum: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] + + "#io-channel-cells": + const: 1 + + pinctrl-0: + required: true + + pinctrl-names: + required: true + +io-channel-cells: + - input