You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
594 lines
14 KiB
594 lines
14 KiB
/* |
|
* Copyright (c) 2019 Derek Hageman <hageman@inthat.cloud> |
|
* Copyright (c) 2024 Gerson Fernando Budke <nandojve@gmail.com> |
|
* |
|
* 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); |
|
|
|
/* clang-format off */ |
|
|
|
#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; |
|
volatile uint32_t *mclk; |
|
uint32_t mclk_mask; |
|
uint32_t gclk_gen; |
|
uint16_t gclk_id; |
|
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; |
|
|
|
*cfg->mclk |= cfg->mclk_mask; |
|
|
|
#ifdef MCLK |
|
GCLK->PCHCTRL[cfg->gclk_id].reg = GCLK_PCHCTRL_CHEN |
|
| GCLK_PCHCTRL_GEN(cfg->gclk_gen); |
|
#else |
|
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |
|
| GCLK_CLKCTRL_GEN(cfg->gclk_gen) |
|
| GCLK_CLKCTRL_ID(cfg->gclk_id); |
|
#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 DEVICE_API(adc, adc_sam0_api) = { |
|
.channel_setup = adc_sam0_channel_setup, |
|
.read = adc_sam0_read, |
|
.ref_internal = 1000U, /* Fixed 1.0 V reference */ |
|
#ifdef CONFIG_ADC_ASYNC |
|
.read_async = adc_sam0_read_async, |
|
#endif |
|
}; |
|
|
|
|
|
#ifdef MCLK |
|
|
|
#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_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 ASSIGNED_CLOCKS_CELL_BY_NAME \ |
|
ATMEL_SAM0_DT_INST_ASSIGNED_CLOCKS_CELL_BY_NAME |
|
|
|
#define ADC_SAM0_GCLK_FREQ(n) \ |
|
UTIL_CAT(UTIL_CAT(SOC_ATMEL_SAM0_GCLK, \ |
|
ASSIGNED_CLOCKS_CELL_BY_NAME(n, gclk, gen)), \ |
|
_FREQ_HZ) |
|
|
|
#define ADC_SAM0_FREQ(n) \ |
|
.prescaler = UTIL_CAT(ADC_CTRLx_PRESCALER_DIV, \ |
|
UTIL_CAT(DT_INST_PROP(n, prescaler), _Val)), \ |
|
.freq = ADC_SAM0_GCLK_FREQ(n) / DT_INST_PROP(n, prescaler) |
|
|
|
#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), \ |
|
.gclk_gen = ASSIGNED_CLOCKS_CELL_BY_NAME(n, gclk, gen), \ |
|
.gclk_id = DT_INST_CLOCKS_CELL_BY_NAME(n, gclk, id), \ |
|
.mclk = ATMEL_SAM0_DT_INST_MCLK_PM_REG_ADDR_OFFSET(n), \ |
|
.mclk_mask = ATMEL_SAM0_DT_INST_MCLK_PM_PERIPH_MASK(n, bit), \ |
|
ADC_SAM0_FREQ(n), \ |
|
.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) |
|
|
|
/* clang-format on */
|
|
|