| /* |
| * Copyright (c) 2019 Derek Hageman <hageman@inthat.cloud> |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT atmel_sam0_adc |
| |
| #include <soc.h> |
| #include <zephyr/drivers/adc.h> |
| #include <zephyr/drivers/pinctrl.h> |
| |
| #include <zephyr/logging/log.h> |
| #include <zephyr/irq.h> |
| LOG_MODULE_REGISTER(adc_sam0, CONFIG_ADC_LOG_LEVEL); |
| |
| #define ADC_CONTEXT_USES_KERNEL_TIMER |
| #include "adc_context.h" |
| |
| #if defined(CONFIG_SOC_SERIES_SAMD21) || defined(CONFIG_SOC_SERIES_SAMR21) || \ |
| defined(CONFIG_SOC_SERIES_SAMD20) |
| /* |
| * SAMD21 Manual 33.6.2.1: The first conversion after changing the reference |
| * is invalid, so we have to discard it. |
| */ |
| #define ADC_SAM0_REFERENCE_GLITCH 1 |
| #endif |
| |
| struct adc_sam0_data { |
| struct adc_context ctx; |
| const struct device *dev; |
| |
| uint16_t *buffer; |
| |
| /* |
| * Saved initial start, so we can reset the advances we've done |
| * if required |
| */ |
| uint16_t *repeat_buffer; |
| |
| #ifdef ADC_SAM0_REFERENCE_GLITCH |
| uint8_t reference_changed; |
| #endif |
| }; |
| |
| struct adc_sam0_cfg { |
| Adc *regs; |
| const struct pinctrl_dev_config *pcfg; |
| |
| #ifdef MCLK |
| uint32_t mclk_mask; |
| uint32_t gclk_mask; |
| uint16_t gclk_id; |
| #else |
| uint32_t gclk; |
| #endif |
| |
| uint32_t freq; |
| uint16_t prescaler; |
| |
| void (*config_func)(const struct device *dev); |
| }; |
| |
| static void wait_synchronization(Adc *const adc) |
| { |
| while ((ADC_SYNC(adc) & ADC_SYNC_MASK) != 0) { |
| } |
| } |
| |
| static int adc_sam0_acquisition_to_clocks(const struct device *dev, |
| uint16_t acquisition_time) |
| { |
| const struct adc_sam0_cfg *const cfg = dev->config; |
| uint64_t scaled_acq; |
| |
| switch (ADC_ACQ_TIME_UNIT(acquisition_time)) { |
| case ADC_ACQ_TIME_TICKS: |
| if (ADC_ACQ_TIME_VALUE(acquisition_time) > 64U) { |
| return -EINVAL; |
| } |
| |
| return (int)ADC_ACQ_TIME_VALUE(acquisition_time) - 1; |
| case ADC_ACQ_TIME_MICROSECONDS: |
| scaled_acq = (uint64_t)ADC_ACQ_TIME_VALUE(acquisition_time) * |
| 1000000U; |
| break; |
| case ADC_ACQ_TIME_NANOSECONDS: |
| scaled_acq = (uint64_t)ADC_ACQ_TIME_VALUE(acquisition_time) * |
| 1000U; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| /* |
| * sample_time = (sample_length+1) * (clk_adc / 2) |
| * sample_length = sample_time * (2/clk_adc) - 1, |
| */ |
| |
| scaled_acq *= 2U; |
| scaled_acq += cfg->freq / 2U; |
| scaled_acq /= cfg->freq; |
| if (scaled_acq <= 1U) { |
| return 0; |
| } |
| |
| scaled_acq -= 1U; |
| if (scaled_acq >= 64U) { |
| return -EINVAL; |
| } |
| |
| return (int)scaled_acq; |
| } |
| |
| static int adc_sam0_channel_setup(const struct device *dev, |
| const struct adc_channel_cfg *channel_cfg) |
| { |
| const struct adc_sam0_cfg *const cfg = dev->config; |
| Adc *const adc = cfg->regs; |
| int retval; |
| uint8_t sampctrl = 0; |
| |
| if (channel_cfg->acquisition_time != ADC_ACQ_TIME_DEFAULT) { |
| retval = adc_sam0_acquisition_to_clocks(dev, |
| channel_cfg->acquisition_time); |
| if (retval < 0) { |
| LOG_ERR("Selected ADC acquisition time is not valid"); |
| return retval; |
| } |
| |
| sampctrl |= ADC_SAMPCTRL_SAMPLEN(retval); |
| } |
| |
| adc->SAMPCTRL.reg = sampctrl; |
| wait_synchronization(adc); |
| |
| uint8_t refctrl; |
| |
| switch (channel_cfg->reference) { |
| case ADC_REF_INTERNAL: |
| refctrl = ADC_REFCTRL_REFSEL_INTERNAL | ADC_REFCTRL_REFCOMP; |
| /* Enable the internal bandgap reference */ |
| ADC_BGEN = 1; |
| break; |
| #ifdef ADC_REFCTRL_REFSEL_VDD_1 |
| case ADC_REF_VDD_1: |
| refctrl = ADC_REFCTRL_REFSEL_VDD_1 | ADC_REFCTRL_REFCOMP; |
| break; |
| #endif |
| case ADC_REF_VDD_1_2: |
| refctrl = ADC_REFCTRL_REFSEL_VDD_1_2 | ADC_REFCTRL_REFCOMP; |
| break; |
| case ADC_REF_EXTERNAL0: |
| refctrl = ADC_REFCTRL_REFSEL_AREFA; |
| break; |
| #ifdef ADC_REFCTRL_REFSEL_AREFB |
| case ADC_REF_EXTERNAL1: |
| refctrl = ADC_REFCTRL_REFSEL_AREFB; |
| break; |
| #endif |
| default: |
| LOG_ERR("Selected reference is not valid"); |
| return -EINVAL; |
| } |
| if (adc->REFCTRL.reg != refctrl) { |
| #ifdef ADC_SAM0_REFERENCE_ENABLE_PROTECTED |
| adc->CTRLA.bit.ENABLE = 0; |
| wait_synchronization(adc); |
| #endif |
| adc->REFCTRL.reg = refctrl; |
| wait_synchronization(adc); |
| #ifdef ADC_SAM0_REFERENCE_ENABLE_PROTECTED |
| adc->CTRLA.bit.ENABLE = 1; |
| wait_synchronization(adc); |
| #endif |
| #ifdef ADC_SAM0_REFERENCE_GLITCH |
| struct adc_sam0_data *data = dev->data; |
| |
| data->reference_changed = 1; |
| #endif |
| } |
| |
| |
| uint32_t inputctrl = 0; |
| |
| switch (channel_cfg->gain) { |
| case ADC_GAIN_1: |
| #ifdef ADC_INPUTCTRL_GAIN_1X |
| inputctrl = ADC_INPUTCTRL_GAIN_1X; |
| #endif |
| break; |
| #ifdef ADC_INPUTCTRL_GAIN_DIV2 |
| case ADC_GAIN_1_2: |
| inputctrl = ADC_INPUTCTRL_GAIN_DIV2; |
| break; |
| #endif |
| #ifdef ADC_INPUTCTRL_GAIN_2X |
| case ADC_GAIN_2: |
| inputctrl = ADC_INPUTCTRL_GAIN_2X; |
| break; |
| #endif |
| #ifdef ADC_INPUTCTRL_GAIN_4X |
| case ADC_GAIN_4: |
| inputctrl = ADC_INPUTCTRL_GAIN_4X; |
| break; |
| #endif |
| #ifdef ADC_INPUTCTRL_GAIN_8X |
| case ADC_GAIN_8: |
| inputctrl = ADC_INPUTCTRL_GAIN_8X; |
| break; |
| #endif |
| #ifdef ADC_INPUTCTRL_GAIN_16X |
| case ADC_GAIN_16: |
| inputctrl = ADC_INPUTCTRL_GAIN_16X; |
| break; |
| #endif |
| default: |
| LOG_ERR("Selected ADC gain is not valid"); |
| return -EINVAL; |
| } |
| |
| inputctrl |= ADC_INPUTCTRL_MUXPOS(channel_cfg->input_positive); |
| if (channel_cfg->differential) { |
| inputctrl |= ADC_INPUTCTRL_MUXNEG(channel_cfg->input_negative); |
| |
| ADC_DIFF(adc) |= ADC_DIFF_MASK; |
| } else { |
| inputctrl |= ADC_INPUTCTRL_MUXNEG_GND; |
| |
| ADC_DIFF(adc) &= ~ADC_DIFF_MASK; |
| } |
| wait_synchronization(adc); |
| |
| adc->INPUTCTRL.reg = inputctrl; |
| wait_synchronization(adc); |
| |
| /* Enable references if they're selected */ |
| switch (channel_cfg->input_positive) { |
| #ifdef ADC_INPUTCTRL_MUXPOS_TEMP_Val |
| case ADC_INPUTCTRL_MUXPOS_TEMP_Val: |
| ADC_TSEN = 1; |
| break; |
| #endif |
| #ifdef ADC_INPUTCTRL_MUXPOS_PTAT_Val |
| case ADC_INPUTCTRL_MUXPOS_PTAT_Val: |
| ADC_TSEN = 1; |
| break; |
| #endif |
| #ifdef ADC_INPUTCTRL_MUXPOS_CTAT_Val |
| case ADC_INPUTCTRL_MUXPOS_CTAT_Val: |
| ADC_TSEN = 1; |
| break; |
| #endif |
| case ADC_INPUTCTRL_MUXPOS_BANDGAP_Val: |
| ADC_BGEN = 1; |
| break; |
| default: |
| break; |
| } |
| |
| |
| return 0; |
| } |
| |
| static void adc_sam0_start_conversion(const struct device *dev) |
| { |
| const struct adc_sam0_cfg *const cfg = dev->config; |
| Adc *const adc = cfg->regs; |
| |
| LOG_DBG("Starting conversion"); |
| |
| adc->SWTRIG.reg = ADC_SWTRIG_START; |
| /* |
| * Should be safe to not synchronize here because the only things |
| * that might access the ADC after this will wait for it to complete |
| * (synchronize finished implicitly) |
| */ |
| } |
| |
| static void adc_context_start_sampling(struct adc_context *ctx) |
| { |
| struct adc_sam0_data *data = |
| CONTAINER_OF(ctx, struct adc_sam0_data, ctx); |
| |
| adc_sam0_start_conversion(data->dev); |
| } |
| |
| static void adc_context_update_buffer_pointer(struct adc_context *ctx, |
| bool repeat_sampling) |
| { |
| struct adc_sam0_data *data = |
| CONTAINER_OF(ctx, struct adc_sam0_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 *= (1U + 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) |
| { |
| const struct adc_sam0_cfg *const cfg = dev->config; |
| struct adc_sam0_data *data = dev->data; |
| Adc *const adc = cfg->regs; |
| int error; |
| |
| if (sequence->oversampling > 10U) { |
| LOG_ERR("Invalid oversampling"); |
| return -EINVAL; |
| } |
| |
| adc->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM(sequence->oversampling); |
| /* AVGCTRL is not synchronized */ |
| |
| #ifdef CONFIG_SOC_SERIES_SAMD20 |
| /* |
| * Errata: silicon revisions B and C do not perform the automatic right |
| * shifts in accumulation |
| */ |
| if (sequence->oversampling > 4U && DSU->DID.bit.REVISION < 3) { |
| adc->AVGCTRL.bit.ADJRES = sequence->oversampling - 4U; |
| } |
| #endif |
| |
| switch (sequence->resolution) { |
| case 8: |
| if (sequence->oversampling) { |
| LOG_ERR("Oversampling requires 12 bit resolution"); |
| return -EINVAL; |
| } |
| |
| ADC_RESSEL(adc) = ADC_RESSEL_8BIT; |
| break; |
| case 10: |
| if (sequence->oversampling) { |
| LOG_ERR("Oversampling requires 12 bit resolution"); |
| return -EINVAL; |
| } |
| |
| ADC_RESSEL(adc) = ADC_RESSEL_10BIT; |
| break; |
| case 12: |
| if (sequence->oversampling) { |
| ADC_RESSEL(adc) = ADC_RESSEL_16BIT; |
| } else { |
| ADC_RESSEL(adc) = ADC_RESSEL_12BIT; |
| } |
| break; |
| default: |
| LOG_ERR("ADC resolution value %d is not valid", |
| sequence->resolution); |
| return -EINVAL; |
| } |
| |
| wait_synchronization(adc); |
| |
| if ((sequence->channels == 0) |
| || ((sequence->channels & (sequence->channels - 1)) != 0)) { |
| /* The caller is expected to identify a single input channel, which will |
| * typically be the positive input, though no check is made for this... |
| * |
| * While ensuring that the channels bitfield matches the positive input |
| * might be sensible, this will likely break users before this revision |
| * was put in place. |
| */ |
| LOG_ERR("Channel scanning is not supported"); |
| return -ENOTSUP; |
| } |
| |
| error = check_buffer_size(sequence, 1); |
| 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); |
| |
| error = adc_context_wait_for_completion(&data->ctx); |
| return error; |
| } |
| |
| static int adc_sam0_read(const struct device *dev, |
| const struct adc_sequence *sequence) |
| { |
| struct adc_sam0_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_sam0_isr(const struct device *dev) |
| { |
| struct adc_sam0_data *data = dev->data; |
| const struct adc_sam0_cfg *const cfg = dev->config; |
| Adc *const adc = cfg->regs; |
| uint16_t result; |
| |
| adc->INTFLAG.reg = ADC_INTFLAG_MASK; |
| |
| result = (uint16_t)(adc->RESULT.reg); |
| |
| #ifdef ADC_SAM0_REFERENCE_GLITCH |
| if (data->reference_changed) { |
| data->reference_changed = 0; |
| LOG_DBG("Discarded initial conversion due to reference change"); |
| |
| adc_sam0_start_conversion(dev); |
| return; |
| } |
| #endif |
| |
| *data->buffer++ = result; |
| adc_context_on_sampling_done(&data->ctx, dev); |
| } |
| |
| static int adc_sam0_init(const struct device *dev) |
| { |
| const struct adc_sam0_cfg *const cfg = dev->config; |
| struct adc_sam0_data *data = dev->data; |
| Adc *const adc = cfg->regs; |
| int retval; |
| |
| #ifdef MCLK |
| GCLK->PCHCTRL[cfg->gclk_id].reg = cfg->gclk_mask | GCLK_PCHCTRL_CHEN; |
| |
| MCLK_ADC |= cfg->mclk_mask; |
| #else |
| PM->APBCMASK.bit.ADC_ = 1; |
| |
| GCLK->CLKCTRL.reg = cfg->gclk | GCLK_CLKCTRL_CLKEN; |
| #endif |
| |
| retval = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); |
| if (retval < 0) { |
| return retval; |
| } |
| |
| ADC_PRESCALER(adc) = cfg->prescaler; |
| wait_synchronization(adc); |
| |
| adc->INTENCLR.reg = ADC_INTENCLR_MASK; |
| adc->INTFLAG.reg = ADC_INTFLAG_MASK; |
| |
| cfg->config_func(dev); |
| |
| adc->INTENSET.reg = ADC_INTENSET_RESRDY; |
| |
| data->dev = dev; |
| #ifdef ADC_SAM0_REFERENCE_GLITCH |
| data->reference_changed = 1; |
| #endif |
| |
| adc->CTRLA.bit.ENABLE = 1; |
| wait_synchronization(adc); |
| |
| adc_context_unlock_unconditionally(&data->ctx); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_ADC_ASYNC |
| static int adc_sam0_read_async(const struct device *dev, |
| const struct adc_sequence *sequence, |
| struct k_poll_signal *async) |
| { |
| struct adc_sam0_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_sam0_api = { |
| .channel_setup = adc_sam0_channel_setup, |
| .read = adc_sam0_read, |
| #ifdef CONFIG_ADC_ASYNC |
| .read_async = adc_sam0_read_async, |
| #endif |
| }; |
| |
| |
| #ifdef MCLK |
| |
| #define ADC_SAM0_CLOCK_CONTROL(n) \ |
| .mclk_mask = BIT(DT_INST_CLOCKS_CELL_BY_NAME(n, mclk, bit)), \ |
| .gclk_mask = UTIL_CAT(GCLK_PCHCTRL_GEN_GCLK, \ |
| DT_INST_PROP(n, gclk)), \ |
| .gclk_id = DT_INST_CLOCKS_CELL_BY_NAME(n, gclk, periph_ch), \ |
| .prescaler = UTIL_CAT(ADC_CTRLx_PRESCALER_DIV, \ |
| UTIL_CAT(DT_INST_PROP(n, prescaler), _Val)), |
| |
| #define ADC_SAM0_CONFIGURE(n) \ |
| do { \ |
| const struct adc_sam0_cfg *const cfg = dev->config; \ |
| Adc * const adc = cfg->regs; \ |
| adc->CALIB.reg = ADC_SAM0_BIASCOMP(n) \ |
| | ADC_SAM0_BIASR2R(n) \ |
| | ADC_SAM0_BIASREFBUF(n); \ |
| } while (false) |
| |
| #else |
| |
| #define ADC_SAM0_CLOCK_CONTROL(n) \ |
| .gclk = UTIL_CAT(GCLK_CLKCTRL_GEN_GCLK, DT_INST_PROP(n, gclk)) |\ |
| GCLK_CLKCTRL_ID_ADC, \ |
| .prescaler = UTIL_CAT(ADC_CTRLx_PRESCALER_DIV, \ |
| UTIL_CAT(DT_INST_PROP(n, prescaler), _Val)), |
| |
| #define ADC_SAM0_CONFIGURE(n) \ |
| do { \ |
| const struct adc_sam0_cfg *const cfg = dev->config; \ |
| Adc * const adc = cfg->regs; \ |
| /* Linearity is split across two words */ \ |
| uint32_t lin = ((*(uint32_t *)ADC_FUSES_LINEARITY_0_ADDR) & \ |
| ADC_FUSES_LINEARITY_0_Msk) >> \ |
| ADC_FUSES_LINEARITY_0_Pos; \ |
| lin |= (((*(uint32_t *)ADC_FUSES_LINEARITY_1_ADDR) & \ |
| ADC_FUSES_LINEARITY_1_Msk) >> \ |
| ADC_FUSES_LINEARITY_1_Pos) << 4; \ |
| uint32_t bias = ((*(uint32_t *)ADC_FUSES_BIASCAL_ADDR) & \ |
| ADC_FUSES_BIASCAL_Msk) >> ADC_FUSES_BIASCAL_Pos; \ |
| adc->CALIB.reg = ADC_CALIB_BIAS_CAL(bias) | \ |
| ADC_CALIB_LINEARITY_CAL(lin); \ |
| } while (false) |
| |
| #endif |
| |
| #define ADC_SAM0_DEVICE(n) \ |
| PINCTRL_DT_INST_DEFINE(n); \ |
| static void adc_sam0_config_##n(const struct device *dev); \ |
| static const struct adc_sam0_cfg adc_sam_cfg_##n = { \ |
| .regs = (Adc *)DT_INST_REG_ADDR(n), \ |
| ADC_SAM0_CLOCK_CONTROL(n) \ |
| .freq = UTIL_CAT(UTIL_CAT(SOC_ATMEL_SAM0_GCLK, \ |
| DT_INST_PROP(n, gclk)), \ |
| _FREQ_HZ) / \ |
| DT_INST_PROP(n, prescaler), \ |
| .config_func = &adc_sam0_config_##n, \ |
| .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ |
| }; \ |
| static struct adc_sam0_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), \ |
| }; \ |
| DEVICE_DT_INST_DEFINE(n, adc_sam0_init, NULL, \ |
| &adc_sam_data_##n, \ |
| &adc_sam_cfg_##n, POST_KERNEL, \ |
| CONFIG_ADC_INIT_PRIORITY, \ |
| &adc_sam0_api); \ |
| static void adc_sam0_config_##n(const struct device *dev) \ |
| { \ |
| IRQ_CONNECT(DT_INST_IRQ_BY_NAME(n, resrdy, irq), \ |
| DT_INST_IRQ_BY_NAME(n, resrdy, priority), \ |
| adc_sam0_isr, \ |
| DEVICE_DT_INST_GET(n), 0); \ |
| irq_enable(DT_INST_IRQ_BY_NAME(n, resrdy, irq)); \ |
| ADC_SAM0_CONFIGURE(n); \ |
| } |
| |
| DT_INST_FOREACH_STATUS_OKAY(ADC_SAM0_DEVICE) |