/* * Copyright (c) 2017 comsuisse AG * Copyright (c) 2018 Justin Watson * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT atmel_sam_afec /** @file * @brief Atmel SAM MCU family ADC (AFEC) driver. * * This is an implementation of the Zephyr ADC driver using the SAM Analog * Front-End Controller (AFEC) peripheral. */ #include #include #include #include #include #include #include #include #define ADC_CONTEXT_USES_KERNEL_TIMER #include "adc_context.h" #define LOG_LEVEL CONFIG_ADC_LOG_LEVEL #include LOG_MODULE_REGISTER(adc_sam_afec); #define NUM_CHANNELS 12 #define CONF_ADC_PRESCALER ((SOC_ATMEL_SAM_MCK_FREQ_HZ / 15000000) - 1) typedef void (*cfg_func_t)(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; /* Bit mask of the channels to be sampled. */ uint32_t channels; /* Index of the channel being sampled. */ uint8_t channel_id; }; struct adc_sam_cfg { Afec *regs; cfg_func_t cfg_func; uint32_t periph_id; const struct pinctrl_dev_config *pcfg; }; static int adc_sam_channel_setup(const struct device *dev, const struct adc_channel_cfg *channel_cfg) { const struct adc_sam_cfg * const cfg = dev->config; Afec *const afec = cfg->regs; uint8_t channel_id = channel_cfg->channel_id; /* Clear the gain bits for the channel. */ afec->AFEC_CGR &= ~(3 << channel_id * 2U); switch (channel_cfg->gain) { case ADC_GAIN_1: /* A value of 0 in this register is a gain of 1. */ break; case ADC_GAIN_1_2: afec->AFEC_CGR |= (1 << (channel_id * 2U)); break; case ADC_GAIN_1_4: afec->AFEC_CGR |= (2 << (channel_id * 2U)); break; default: LOG_ERR("Selected ADC gain is not valid"); return -EINVAL; } if (channel_cfg->acquisition_time != ADC_ACQ_TIME_DEFAULT) { LOG_ERR("Selected ADC acquisition time is not valid"); return -EINVAL; } if (channel_cfg->reference != ADC_REF_EXTERNAL0) { LOG_ERR("Selected reference is not valid"); return -EINVAL; } if (channel_cfg->differential) { LOG_ERR("Differential input is not supported"); return -EINVAL; } /* Set single ended channels to unsigned and differential channels * to signed conversions. */ afec->AFEC_EMR &= ~(AFEC_EMR_SIGNMODE( AFEC_EMR_SIGNMODE_SE_UNSG_DF_SIGN_Val)); return 0; } static void adc_sam_start_conversion(const struct device *dev) { const struct adc_sam_cfg *const cfg = dev->config; struct adc_sam_data *data = dev->data; Afec *const afec = cfg->regs; data->channel_id = find_lsb_set(data->channels) - 1; LOG_DBG("Starting channel %d", data->channel_id); /* Disable all channels. */ afec->AFEC_CHDR = 0xfff; afec->AFEC_IDR = 0xfff; /* Enable the ADC channel. This also enables/selects the channel pin as * an input to the AFEC (50.5.1 SAM E70 datasheet). */ afec->AFEC_CHER = (1 << data->channel_id); /* Enable the interrupt for the channel. */ afec->AFEC_IER = (1 << data->channel_id); /* Start the conversions. */ afec->AFEC_CR = AFEC_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); data->channels = ctx->sequence.channels; adc_sam_start_conversion(data->dev); } static void adc_context_update_buffer_pointer(struct adc_context *ctx, bool repeat_sampling) { struct adc_sam_data *data = CONTAINER_OF(ctx, struct adc_sam_data, ctx); if (repeat_sampling) { data->buffer = data->repeat_buffer; } } static int 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_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; int error = 0; uint32_t channels = sequence->channels; data->channels = 0U; /* Signal an error if the channel selection is invalid (no channels or * a non-existing one is selected). */ if (channels == 0U || (channels & (~0UL << 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) { /* TODO JKW: Support the Enhanced Resolution Mode 50.6.3 page * 1544. */ LOG_ERR("ADC resolution value %d is not valid", sequence->resolution); return -EINVAL; } uint8_t num_active_channels = 0U; uint8_t channel = 0U; while (channels > 0) { if (channels & 1) { ++num_active_channels; } channels >>= 1; ++channel; } error = check_buffer_size(sequence, num_active_channels); if (error) { return error; } /* In the context you have a pointer to the adc_sam_data structure * only. */ 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); error = adc_context_wait_for_completion(&data->ctx); return error; } 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 int adc_sam_init(const struct device *dev) { const struct adc_sam_cfg *const cfg = dev->config; struct adc_sam_data *data = dev->data; Afec *const afec = cfg->regs; int retval; /* Reset the AFEC. */ afec->AFEC_CR = AFEC_CR_SWRST; afec->AFEC_MR = AFEC_MR_TRGEN_DIS | AFEC_MR_SLEEP_NORMAL | AFEC_MR_FWUP_OFF | AFEC_MR_FREERUN_OFF | AFEC_MR_PRESCAL(CONF_ADC_PRESCALER) | AFEC_MR_STARTUP_SUT96 | AFEC_MR_ONE | AFEC_MR_USEQ_NUM_ORDER; /* Set all channels CM voltage to Vrefp/2 (512). */ for (int i = 0; i < NUM_CHANNELS; i++) { afec->AFEC_CSELR = i; afec->AFEC_COCR = 512; } /* Enable PGA and Current Bias. */ afec->AFEC_ACR = AFEC_ACR_PGA0EN | AFEC_ACR_PGA1EN | AFEC_ACR_IBCTL(1); soc_pmc_peripheral_enable(cfg->periph_id); /* Connect pins to the peripheral */ retval = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); if (retval < 0) { return retval; } cfg->cfg_func(dev); data->dev = dev; adc_context_unlock_unconditionally(&data->ctx); return retval; } #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 }; static void adc_sam_isr(const struct device *dev) { struct adc_sam_data *data = dev->data; const struct adc_sam_cfg *const cfg = dev->config; Afec *const afec = cfg->regs; uint16_t result; afec->AFEC_CHDR |= BIT(data->channel_id); afec->AFEC_IDR |= BIT(data->channel_id); afec->AFEC_CSELR = AFEC_CSELR_CSEL(data->channel_id); result = (uint16_t)(afec->AFEC_CDR); *data->buffer++ = result; data->channels &= ~BIT(data->channel_id); if (data->channels) { adc_sam_start_conversion(dev); } else { /* Called once all conversions have completed.*/ adc_context_on_sampling_done(&data->ctx, dev); } } #define ADC_SAM_INIT(n) \ PINCTRL_DT_INST_DEFINE(n); \ static void adc##n##_sam_cfg_func(const struct device *dev); \ \ static const struct adc_sam_cfg adc##n##_sam_cfg = { \ .regs = (Afec *)DT_INST_REG_ADDR(n), \ .cfg_func = adc##n##_sam_cfg_func, \ .periph_id = DT_INST_PROP(n, peripheral_id), \ .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ }; \ \ static struct adc_sam_data adc##n##_sam_data = { \ ADC_CONTEXT_INIT_TIMER(adc##n##_sam_data, ctx), \ ADC_CONTEXT_INIT_LOCK(adc##n##_sam_data, ctx), \ ADC_CONTEXT_INIT_SYNC(adc##n##_sam_data, ctx), \ }; \ \ DEVICE_DT_INST_DEFINE(n, adc_sam_init, NULL, \ &adc##n##_sam_data, \ &adc##n##_sam_cfg, POST_KERNEL, \ CONFIG_ADC_INIT_PRIORITY, \ &adc_sam_api); \ \ static void adc##n##_sam_cfg_func(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)); \ } DT_INST_FOREACH_STATUS_OKAY(ADC_SAM_INIT)