2025-10-16 15:57:18 -07:00
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2025 Infineon Technologies AG,
|
|
|
|
|
* or an affiliate of Infineon Technologies AG.
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief ADC driver for Infineon AutAnalog SAR ADC used by Edge MCU family.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#define DT_DRV_COMPAT infineon_autanalog_sar_adc
|
|
|
|
|
|
|
|
|
|
#include <zephyr/dt-bindings/clock/ifx_clock_source_common.h>
|
|
|
|
|
#include <zephyr/drivers/adc.h>
|
|
|
|
|
#include <zephyr/device.h>
|
|
|
|
|
#include <zephyr/devicetree.h>
|
|
|
|
|
#include <zephyr/logging/log.h>
|
|
|
|
|
#include <ifx_autanalog.h>
|
|
|
|
|
|
|
|
|
|
#define ADC_CONTEXT_USES_KERNEL_TIMER
|
|
|
|
|
#include "adc_context.h"
|
|
|
|
|
|
|
|
|
|
#include "cy_pdl.h"
|
|
|
|
|
|
|
|
|
|
LOG_MODULE_REGISTER(ifx_autanalog_sar_adc, CONFIG_ADC_LOG_LEVEL);
|
|
|
|
|
|
|
|
|
|
#define ADC_AUTANALOG_SAR_DEFAULT_ACQUISITION_NS (1000u)
|
|
|
|
|
#define ADC_AUTANALOG_SAR_RESOLUTION 12
|
|
|
|
|
|
|
|
|
|
#define IFX_AUTANALOG_SAR_MAX_NUM_CHANNELS 32
|
|
|
|
|
#define IFX_AUTANALOG_SAR_NUM_SEQUENCERS 1
|
|
|
|
|
#define IFX_AUTANALOG_SAR_NUM_ENABLED_CHANNELS(inst) DT_NUM_CHILDREN(DT_DRV_INST(inst))
|
|
|
|
|
|
|
|
|
|
#define IFX_AUTANALOG_SAR_SAMPLETIME_COUNT 4
|
|
|
|
|
|
|
|
|
|
#define IFX_AUTANALOG_HF_CLK_SRC 9
|
|
|
|
|
|
|
|
|
|
/* Reference Voltage Source definitions from Device Tree Bindings */
|
|
|
|
|
enum ifx_autanalog_sar_vref_source {
|
|
|
|
|
IFX_AUTANALOG_SAR_VREF_VDDA = 0,
|
|
|
|
|
IFX_AUTANALOG_SAR_VREF_EXT = 1,
|
|
|
|
|
IFX_AUTANALOG_SAR_VREF_VBGR = 2,
|
|
|
|
|
IFX_AUTANALOG_SAR_VREF_VDDA_BY_2 = 3,
|
|
|
|
|
IFX_AUTANALOG_SAR_VREF_PRB_OUT0 = 4,
|
|
|
|
|
IFX_AUTANALOG_SAR_VREF_PRB_OUT1 = 5,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct ifx_autanalog_sar_adc_config {
|
|
|
|
|
void (*irq_func)(void);
|
|
|
|
|
enum ifx_autanalog_sar_vref_source vref_source;
|
|
|
|
|
bool linear_cal;
|
|
|
|
|
bool offset_cal;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct ifx_autanalog_sar_adc_channel_config {
|
|
|
|
|
/* Hardware supports 4 sample times, so map this channel to one of the sample times
|
|
|
|
|
* configured in HW.
|
|
|
|
|
*/
|
|
|
|
|
uint8_t sample_time_idx;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct ifx_autanalog_sar_adc_data {
|
|
|
|
|
struct adc_context ctx;
|
|
|
|
|
const struct device *dev;
|
|
|
|
|
/* Conversion Buffer */
|
|
|
|
|
uint16_t *conversion_buffer;
|
|
|
|
|
/* Repeat buffer for continuous sampling */
|
|
|
|
|
uint16_t *repeat_buffer;
|
|
|
|
|
/* Conversion result */
|
|
|
|
|
int conversion_result;
|
|
|
|
|
/* Bitmask of enabled channels */
|
|
|
|
|
uint32_t enabled_channels;
|
|
|
|
|
|
|
|
|
|
struct ifx_autanalog_sar_adc_channel_config
|
|
|
|
|
autanalog_channel_cfg[IFX_AUTANALOG_SAR_MAX_NUM_CHANNELS];
|
|
|
|
|
|
|
|
|
|
/* The following structures are used by the Infineon PDL API for configuring the ADC. */
|
|
|
|
|
cy_stc_autanalog_sar_t pdl_adc_top_obj;
|
|
|
|
|
cy_stc_autanalog_sar_sta_t pdl_adc_top_static_obj;
|
|
|
|
|
|
|
|
|
|
/* PDL structures to initialize the High Speed ADC */
|
|
|
|
|
cy_stc_autanalog_sar_hs_chan_t
|
|
|
|
|
pdl_adc_hs_channel_cfg_obj_arr[IFX_AUTANALOG_SAR_MAX_NUM_CHANNELS];
|
|
|
|
|
cy_stc_autanalog_sar_sta_hs_t pdl_adc_hs_static_obj;
|
|
|
|
|
cy_stc_autanalog_sar_seq_tab_hs_t pdl_adc_seq_hs_cfg_obj[IFX_AUTANALOG_SAR_NUM_SEQUENCERS];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Initialize PDL structures for the ADC
|
|
|
|
|
*
|
|
|
|
|
* @param data Pointer to driver data structure
|
|
|
|
|
* @param cfg Pointer to driver configuration structure
|
|
|
|
|
*
|
|
|
|
|
* Initializes the PDL structures using default values and values for calibration and vref values
|
|
|
|
|
* derived from the device tree.
|
|
|
|
|
*/
|
|
|
|
|
static void ifx_init_pdl_structs(struct ifx_autanalog_sar_adc_data *data,
|
|
|
|
|
const struct ifx_autanalog_sar_adc_config *cfg)
|
|
|
|
|
{
|
|
|
|
|
data->pdl_adc_top_obj = (cy_stc_autanalog_sar_t){
|
|
|
|
|
.sarStaCfg = &data->pdl_adc_top_static_obj,
|
|
|
|
|
/* This driver implementation uses only a single sequencer. The sequencer is
|
|
|
|
|
* reconfigured every time an adc read is started. Hardware supports up to 32
|
|
|
|
|
* sequencers, which can be used for more advanced ADC configurations.
|
|
|
|
|
*/
|
|
|
|
|
.hsSeqTabNum = IFX_AUTANALOG_SAR_NUM_SEQUENCERS,
|
|
|
|
|
.hsSeqTabArr = &data->pdl_adc_seq_hs_cfg_obj[0],
|
|
|
|
|
.lpSeqTabNum = 0U,
|
|
|
|
|
.lpSeqTabArr = NULL,
|
|
|
|
|
.firNum = 0U,
|
|
|
|
|
.firCfg = NULL,
|
|
|
|
|
.fifoCfg = NULL,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
data->pdl_adc_seq_hs_cfg_obj[0] = (cy_stc_autanalog_sar_seq_tab_hs_t){
|
|
|
|
|
.chanEn = CY_AUTANALOG_SAR_CHAN_MASK_GPIO_DISABLED,
|
|
|
|
|
.muxMode = CY_AUTANALOG_SAR_CHAN_CFG_MUX_DISABLED,
|
|
|
|
|
.mux0Sel = CY_AUTANALOG_SAR_CHAN_CFG_MUX0,
|
|
|
|
|
.mux1Sel = CY_AUTANALOG_SAR_CHAN_CFG_MUX0,
|
|
|
|
|
.sampleTimeEn = true,
|
|
|
|
|
.sampleTime = CY_AUTANALOG_SAR_SAMPLE_TIME0,
|
|
|
|
|
.accEn = false,
|
|
|
|
|
.accCount = CY_AUTANALOG_SAR_ACC_CNT2,
|
|
|
|
|
.calReq = CY_AUTANALOG_SAR_CAL_DISABLED,
|
|
|
|
|
.nextAction = CY_AUTANALOG_SAR_NEXT_ACTION_STATE_STOP, /* Single Shot mode */
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
data->pdl_adc_top_static_obj = (cy_stc_autanalog_sar_sta_t){
|
|
|
|
|
.lpStaCfg = NULL, /* This driver implementation only implements HS mode.*/
|
|
|
|
|
.hsStaCfg = &data->pdl_adc_hs_static_obj,
|
|
|
|
|
.posBufPwr = CY_AUTANALOG_SAR_BUF_PWR_OFF,
|
|
|
|
|
.negBufPwr = CY_AUTANALOG_SAR_BUF_PWR_OFF,
|
|
|
|
|
/* Note: This setting chooses "accumulate and dump" vs. "interleaved" for channels
|
|
|
|
|
* where averaging is enabled. The selection for "accumulate" vs. "accumlate and
|
|
|
|
|
* divide" is tracked by adc->average_is_accumulate and is applied in the hardware
|
|
|
|
|
* on a per-channel basis.
|
|
|
|
|
*/
|
|
|
|
|
.accMode = CY_AUTANALOG_SAR_ACC_DISABLED,
|
|
|
|
|
.startupCal = (cfg->offset_cal ? CY_AUTANALOG_SAR_CAL_OFFSET
|
|
|
|
|
: CY_AUTANALOG_SAR_CAL_DISABLED) |
|
|
|
|
|
(cfg->linear_cal ? CY_AUTANALOG_SAR_CAL_LINEARITY
|
|
|
|
|
: CY_AUTANALOG_SAR_CAL_DISABLED),
|
|
|
|
|
.chanID = false, /* We don't use the FIFO features */
|
|
|
|
|
/* When accShift is set for a channel, shift back down to 12 bits */
|
|
|
|
|
.shiftMode = false,
|
|
|
|
|
.intMuxChan = {NULL}, /* We don't expose mux channels */
|
|
|
|
|
.limitCond = {NULL}, /* We don't expose the range detection */
|
|
|
|
|
.muxResultMask = 0u, /* We don't expose mux channels */
|
|
|
|
|
.firResultMask = 0u, /* We don't expose FIR functionality */
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
data->pdl_adc_hs_static_obj = (cy_stc_autanalog_sar_sta_hs_t){
|
|
|
|
|
.hsVref = cfg->vref_source,
|
|
|
|
|
.hsSampleTime = {
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
},
|
|
|
|
|
/* These will be configured during channel setup */
|
|
|
|
|
.hsGpioChan = {
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
},
|
|
|
|
|
.hsGpioResultMask = 0,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* Map the vref_source from the device tree to the PDL enum */
|
|
|
|
|
switch (cfg->vref_source) {
|
|
|
|
|
case IFX_AUTANALOG_SAR_VREF_VDDA:
|
|
|
|
|
data->pdl_adc_hs_static_obj.hsVref = CY_AUTANALOG_SAR_VREF_VDDA;
|
|
|
|
|
break;
|
|
|
|
|
case IFX_AUTANALOG_SAR_VREF_EXT:
|
|
|
|
|
data->pdl_adc_hs_static_obj.hsVref = CY_AUTANALOG_SAR_VREF_EXT;
|
|
|
|
|
break;
|
|
|
|
|
case IFX_AUTANALOG_SAR_VREF_VDDA_BY_2:
|
|
|
|
|
data->pdl_adc_hs_static_obj.hsVref = CY_AUTANALOG_SAR_VREF_VDDA_BY_2;
|
|
|
|
|
break;
|
|
|
|
|
case IFX_AUTANALOG_SAR_VREF_VBGR:
|
|
|
|
|
data->pdl_adc_hs_static_obj.hsVref = CY_AUTANALOG_SAR_VREF_VBGR;
|
|
|
|
|
break;
|
|
|
|
|
case IFX_AUTANALOG_SAR_VREF_PRB_OUT0:
|
|
|
|
|
data->pdl_adc_hs_static_obj.hsVref = CY_AUTANALOG_SAR_VREF_PRB_OUT0;
|
|
|
|
|
break;
|
|
|
|
|
case IFX_AUTANALOG_SAR_VREF_PRB_OUT1:
|
|
|
|
|
data->pdl_adc_hs_static_obj.hsVref = CY_AUTANALOG_SAR_VREF_PRB_OUT1;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
LOG_ERR("Unsupported VREF source, using VDDA");
|
|
|
|
|
data->pdl_adc_hs_static_obj.hsVref = CY_AUTANALOG_SAR_VREF_VDDA;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} /* ifx_init_pdl_struct() */
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Read results from the ADC
|
|
|
|
|
*
|
|
|
|
|
* @param channels Bitmask of channels to read
|
|
|
|
|
* @param data Pointer to driver data structure
|
|
|
|
|
*
|
|
|
|
|
* Reads the conversion results for the specified channels from the ADC and stores them in the
|
|
|
|
|
* buffer pointed to by data->buffer. The buffer pointer is incremented as results are stored.
|
|
|
|
|
* It is assumed that the buffer is large enough to hold all requested results.
|
|
|
|
|
*/
|
|
|
|
|
static void ifx_autanalog_sar_get_results(uint32_t channels,
|
|
|
|
|
struct ifx_autanalog_sar_adc_data *data)
|
|
|
|
|
{
|
|
|
|
|
if (data->conversion_buffer == NULL) {
|
|
|
|
|
LOG_ERR("ADC data buffer is NULL");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < IFX_AUTANALOG_SAR_MAX_NUM_CHANNELS; i++) {
|
|
|
|
|
if ((channels & (1 << i)) != 0) {
|
|
|
|
|
*data->conversion_buffer++ =
|
|
|
|
|
Cy_AutAnalog_SAR_ReadResult(0, CY_AUTANALOG_SAR_INPUT_GPIO, i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} /* ifx_autanalog_sar_get_results() */
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Build a sequencer entry for the specified channels
|
|
|
|
|
*
|
|
|
|
|
* @param channels Bitmask of channels to include in the sequencer entry
|
|
|
|
|
* @param data Pointer to driver data structure
|
|
|
|
|
*
|
|
|
|
|
* Builds a sequencer entry for the specified channels. All channels in the entry must have the
|
|
|
|
|
* same acquisition time and must map to one of the four sample times configured in hardware.
|
|
|
|
|
*
|
|
|
|
|
* @return 0 on success, -EINVAL if an error occurs.
|
|
|
|
|
*/
|
|
|
|
|
static int ifx_build_hs_sequencer_entry(uint32_t channels, struct ifx_autanalog_sar_adc_data *data)
|
|
|
|
|
{
|
|
|
|
|
uint8_t timer_index = IFX_AUTANALOG_SAR_SAMPLETIME_COUNT;
|
|
|
|
|
cy_stc_autanalog_sar_seq_tab_hs_t *seq_entry = &data->pdl_adc_seq_hs_cfg_obj[0];
|
|
|
|
|
|
|
|
|
|
/* Verify that all channels in the sequence have the same acquisition time and the sample
|
|
|
|
|
* time is configured in hardware.
|
|
|
|
|
*/
|
|
|
|
|
for (uint8_t i = 0; i < IFX_AUTANALOG_SAR_MAX_NUM_CHANNELS; i++) {
|
|
|
|
|
if ((channels & (1 << i)) != 0) {
|
|
|
|
|
if (data->autanalog_channel_cfg[i].sample_time_idx >=
|
|
|
|
|
IFX_AUTANALOG_SAR_SAMPLETIME_COUNT) {
|
|
|
|
|
LOG_ERR("Invalid sample time index for channel %d", i);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (timer_index == IFX_AUTANALOG_SAR_SAMPLETIME_COUNT) {
|
|
|
|
|
timer_index = data->autanalog_channel_cfg[i].sample_time_idx;
|
|
|
|
|
} else if (timer_index != data->autanalog_channel_cfg[i].sample_time_idx) {
|
|
|
|
|
LOG_ERR("All channels in a sequence must have the same sample "
|
|
|
|
|
"time");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (timer_index >= IFX_AUTANALOG_SAR_SAMPLETIME_COUNT) {
|
|
|
|
|
LOG_ERR("No sample time configured for selected channels");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
seq_entry->sampleTime = (cy_en_autanalog_sar_sample_time_t)timer_index;
|
|
|
|
|
seq_entry->chanEn = channels;
|
|
|
|
|
seq_entry->muxMode = CY_AUTANALOG_SAR_CHAN_CFG_MUX_DISABLED;
|
|
|
|
|
seq_entry->mux0Sel = CY_AUTANALOG_SAR_CHAN_CFG_MUX0;
|
|
|
|
|
seq_entry->mux1Sel = CY_AUTANALOG_SAR_CHAN_CFG_MUX0;
|
|
|
|
|
seq_entry->sampleTimeEn = true;
|
|
|
|
|
seq_entry->accEn = false;
|
|
|
|
|
seq_entry->accCount = CY_AUTANALOG_SAR_ACC_CNT2;
|
|
|
|
|
seq_entry->calReq = CY_AUTANALOG_SAR_CAL_DISABLED;
|
|
|
|
|
seq_entry->nextAction = CY_AUTANALOG_SAR_NEXT_ACTION_STATE_STOP;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
} /* ifx_build_hs_sequencer_entry() */
|
|
|
|
|
|
|
|
|
|
/** @brief Start ADC sampling
|
|
|
|
|
*
|
|
|
|
|
* @param ctx Pointer to ADC context
|
|
|
|
|
*
|
|
|
|
|
* This function is called by the ADC context. Configures ADC Sequencer and starts the ADC
|
|
|
|
|
* sampling.
|
|
|
|
|
*/
|
|
|
|
|
static void adc_context_start_sampling(struct adc_context *ctx)
|
|
|
|
|
{
|
|
|
|
|
struct ifx_autanalog_sar_adc_data *data =
|
|
|
|
|
CONTAINER_OF(ctx, struct ifx_autanalog_sar_adc_data, ctx);
|
|
|
|
|
const struct adc_sequence *sequence = &ctx->sequence;
|
|
|
|
|
uint32_t result_status;
|
|
|
|
|
|
|
|
|
|
data->repeat_buffer = data->conversion_buffer;
|
|
|
|
|
if (data->conversion_buffer == NULL || sequence->buffer_size == 0) {
|
|
|
|
|
data->conversion_result = -ENOMEM;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sequence->channels == 0) {
|
|
|
|
|
LOG_ERR("No channels specified");
|
|
|
|
|
data->conversion_result = -EINVAL;
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* This implementation uses a single sequencer which is reconfigured for every ADC
|
|
|
|
|
* read operation. If needed, this can be extended to use multiple sequencers.
|
|
|
|
|
*/
|
|
|
|
|
if (ifx_build_hs_sequencer_entry(sequence->channels, data) != 0) {
|
|
|
|
|
LOG_ERR("Error building ADC Sequencer Configuration");
|
|
|
|
|
data->conversion_result = -EINVAL;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Stop the Autonomous Controller while we reconfigure the sequencer */
|
|
|
|
|
ifx_autanalog_pause_sar_autonomous_control();
|
|
|
|
|
result_status = Cy_AutAnalog_SAR_LoadHSseqTable(0, IFX_AUTANALOG_SAR_NUM_SEQUENCERS,
|
|
|
|
|
&data->pdl_adc_seq_hs_cfg_obj[0]);
|
|
|
|
|
if (result_status != CY_AUTANALOG_SUCCESS) {
|
|
|
|
|
LOG_ERR("Error Loading ADC Sequencer Configuration: %u",
|
|
|
|
|
(unsigned int)result_status);
|
|
|
|
|
data->conversion_result = -EIO;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ifx_autanalog_start_sar_autonomous_control();
|
|
|
|
|
Cy_AutAnalog_SAR_ClearHSchanResultStatus(0, sequence->channels);
|
|
|
|
|
Cy_AutAnalog_FwTrigger(CY_AUTANALOG_FW_TRIGGER0);
|
|
|
|
|
|
|
|
|
|
#if defined(CONFIG_ADC_ASYNC)
|
|
|
|
|
if (!data->ctx.asynchronous) {
|
|
|
|
|
#endif
|
|
|
|
|
/* Wait for conversion to complete */
|
|
|
|
|
do {
|
|
|
|
|
result_status = Cy_AutAnalog_SAR_GetHSchanResultStatus(0);
|
|
|
|
|
} while ((result_status & sequence->channels) != sequence->channels);
|
|
|
|
|
|
|
|
|
|
ifx_autanalog_sar_get_results(sequence->channels, data);
|
|
|
|
|
adc_context_on_sampling_done(&data->ctx, data->dev);
|
|
|
|
|
#if defined(CONFIG_ADC_ASYNC)
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
data->conversion_result = 0;
|
|
|
|
|
|
|
|
|
|
} /* adc_context_start_sampling() */
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Update buffer pointer for next read
|
|
|
|
|
*
|
|
|
|
|
* @param ctx Pointer to ADC context
|
|
|
|
|
* @param repeat_sampling True if the current sampling is to be repeated
|
|
|
|
|
*/
|
|
|
|
|
static void adc_context_update_buffer_pointer(struct adc_context *ctx, bool repeat_sampling)
|
|
|
|
|
{
|
|
|
|
|
struct ifx_autanalog_sar_adc_data *data =
|
|
|
|
|
CONTAINER_OF(ctx, struct ifx_autanalog_sar_adc_data, ctx);
|
|
|
|
|
|
|
|
|
|
if (repeat_sampling) {
|
|
|
|
|
data->conversion_buffer = data->repeat_buffer;
|
|
|
|
|
}
|
|
|
|
|
} /* adc_context_update_buffer_pointer() */
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Start an ADC read operation
|
|
|
|
|
*
|
|
|
|
|
* @param dev Pointer to device structure
|
|
|
|
|
* @param sequence Pointer to ADC sequence structure
|
|
|
|
|
*
|
|
|
|
|
* Validates the requested sequence is supported by hardware and starts the ADC read operation.
|
|
|
|
|
*
|
|
|
|
|
* @return 0 on success, negative error code on failure.
|
|
|
|
|
*/
|
|
|
|
|
static int start_read(const struct device *dev, const struct adc_sequence *sequence)
|
|
|
|
|
{
|
|
|
|
|
struct ifx_autanalog_sar_adc_data *data = dev->data;
|
|
|
|
|
|
|
|
|
|
if (sequence->buffer_size < (sizeof(int16_t) * POPCOUNT(sequence->channels))) {
|
|
|
|
|
LOG_ERR("Buffer too small");
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sequence->resolution != ADC_AUTANALOG_SAR_RESOLUTION) {
|
|
|
|
|
LOG_ERR("Unsupported resolution: %d", sequence->resolution);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sequence->channels == 0) {
|
|
|
|
|
LOG_ERR("No channels specified");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((sequence->channels ^ (data->enabled_channels & sequence->channels)) != 0) {
|
|
|
|
|
LOG_ERR("Channels not configured");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sequence->oversampling != 0) {
|
|
|
|
|
LOG_ERR("Oversampling not supported");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data->conversion_buffer = sequence->buffer;
|
|
|
|
|
adc_context_start_read(&data->ctx, sequence);
|
|
|
|
|
|
|
|
|
|
return adc_context_wait_for_completion(&data->ctx);
|
|
|
|
|
} /* start_read() */
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief ADC interrupt handler
|
|
|
|
|
*
|
|
|
|
|
* @param dev Pointer to the device structure for the driver instance.
|
|
|
|
|
*
|
|
|
|
|
* All interrupts for the AutAnalog subsystem are handled by a single IRQ. This function implements
|
|
|
|
|
* the AutAnalog SAR ADC interrupt handling, and is expected to be called from the AutAnalog ISR.
|
|
|
|
|
*/
|
|
|
|
|
static void ifx_autanalog_sar_adc_isr(const struct device *dev)
|
|
|
|
|
{
|
|
|
|
|
#if defined(CONFIG_ADC_ASYNC)
|
|
|
|
|
struct ifx_autanalog_sar_adc_data *data = dev->data;
|
|
|
|
|
const struct adc_sequence *sequence = &data->ctx.sequence;
|
|
|
|
|
uint32_t result_status;
|
|
|
|
|
|
|
|
|
|
if (data->ctx.asynchronous) {
|
|
|
|
|
result_status = Cy_AutAnalog_SAR_GetHSchanResultStatus(0);
|
|
|
|
|
|
|
|
|
|
if ((result_status & sequence->channels) == sequence->channels) {
|
|
|
|
|
Cy_AutAnalog_SAR_ClearHSchanResultStatus(0, sequence->channels);
|
|
|
|
|
ifx_autanalog_sar_get_results(sequence->channels, data);
|
|
|
|
|
adc_context_on_sampling_done(&data->ctx, data->dev);
|
|
|
|
|
} else {
|
|
|
|
|
/* Not all channels have completed yet. This shouldn't happen in
|
|
|
|
|
* normal operation
|
|
|
|
|
*/
|
|
|
|
|
LOG_ERR("ADC ISR: Not all channels completed yet.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
ARG_UNUSED(dev);
|
|
|
|
|
#endif
|
|
|
|
|
} /* ifx_autanalog_sar_adc_isr() */
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Calculate the sample time register value based on requested acquisition
|
|
|
|
|
* time
|
|
|
|
|
*
|
|
|
|
|
* @param acquisition_time_ns Requested acquisition time in nanoseconds
|
|
|
|
|
*
|
|
|
|
|
* @return Acquisition clock cycles, 0 if error
|
|
|
|
|
*/
|
|
|
|
|
static uint16_t ifx_calc_acquisition_timer_val(uint32_t acquisition_time_ns)
|
|
|
|
|
{
|
|
|
|
|
const uint32_t ACQUISITION_CLOCKS_MIN = 1;
|
|
|
|
|
const uint32_t ACQUISITION_CLOCKS_MAX = 1024;
|
|
|
|
|
|
|
|
|
|
uint32_t timer_clock_cycles;
|
|
|
|
|
uint32_t clock_frequency_hz;
|
|
|
|
|
uint32_t clock_period_ns;
|
|
|
|
|
|
|
|
|
|
clock_frequency_hz = Cy_SysClk_ClkHfGetFrequency(IFX_AUTANALOG_HF_CLK_SRC);
|
|
|
|
|
if (clock_frequency_hz == 0) {
|
|
|
|
|
LOG_ERR("Failed to get AutAnalog clock frequency");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
clock_period_ns = NSEC_PER_SEC / clock_frequency_hz;
|
|
|
|
|
timer_clock_cycles = (acquisition_time_ns + (clock_period_ns - 1)) / clock_period_ns;
|
|
|
|
|
if (timer_clock_cycles < ACQUISITION_CLOCKS_MIN) {
|
|
|
|
|
timer_clock_cycles = ACQUISITION_CLOCKS_MIN;
|
|
|
|
|
LOG_WRN("ADC acquisition time too short, using minimum");
|
|
|
|
|
} else if (timer_clock_cycles > ACQUISITION_CLOCKS_MAX) {
|
|
|
|
|
timer_clock_cycles = ACQUISITION_CLOCKS_MAX;
|
|
|
|
|
LOG_WRN("ADC acquisition time too long, using maximum");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Per the register map, the timer value should be one less than the actual
|
|
|
|
|
* desired sampling cycle count
|
|
|
|
|
*/
|
|
|
|
|
return (uint16_t)(timer_clock_cycles - 1);
|
|
|
|
|
} /* ifx_calc_acquisition_timer_val() */
|
|
|
|
|
|
|
|
|
|
/* Zephyr Driver API Functions
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Autanalog SAR ADC read function
|
|
|
|
|
*
|
|
|
|
|
* @param dev Pointer to device structure
|
|
|
|
|
* @param sequence Pointer to ADC sequence structure
|
|
|
|
|
*
|
|
|
|
|
* @return 0 on success, error code on failure
|
|
|
|
|
*/
|
|
|
|
|
static int ifx_autanalog_sar_adc_read(const struct device *dev, const struct adc_sequence *sequence)
|
|
|
|
|
{
|
|
|
|
|
struct ifx_autanalog_sar_adc_data *data = dev->data;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
adc_context_lock(&data->ctx, false, NULL);
|
|
|
|
|
ret = start_read(dev, sequence);
|
|
|
|
|
adc_context_release(&data->ctx, ret);
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
} /* ifx_autanalog_sar_adc_read() */
|
|
|
|
|
|
|
|
|
|
#ifdef CONFIG_ADC_ASYNC
|
|
|
|
|
/**
|
|
|
|
|
* @brief Autanalog SAR ADC asynchronous read function
|
|
|
|
|
*
|
|
|
|
|
* @param dev Pointer to device structure
|
|
|
|
|
* @param sequence Pointer to ADC sequence structure
|
|
|
|
|
* @param async Pointer to poll signal structure for asynchronous operation
|
|
|
|
|
*
|
|
|
|
|
* @return 0 on success, error code on failure
|
|
|
|
|
*/
|
|
|
|
|
static int ifx_autanalog_sar_adc_read_async(const struct device *dev,
|
|
|
|
|
const struct adc_sequence *sequence,
|
|
|
|
|
struct k_poll_signal *async)
|
|
|
|
|
{
|
|
|
|
|
struct ifx_autanalog_sar_adc_data *data = dev->data;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
adc_context_lock(&data->ctx, true, async);
|
|
|
|
|
ret = start_read(dev, sequence);
|
|
|
|
|
adc_context_release(&data->ctx, ret);
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
} /* ifx_autanalog_sar_adc_read_async() */
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief API Function to configure an ADC channel
|
|
|
|
|
*
|
|
|
|
|
* @param dev Pointer to device structure
|
|
|
|
|
* @param channel_cfg Pointer to channel configuration structure
|
|
|
|
|
*
|
|
|
|
|
* @return 0 on success, negative error code on failure
|
|
|
|
|
*/
|
|
|
|
|
static int ifx_autanalog_sar_adc_channel_setup(const struct device *dev,
|
|
|
|
|
const struct adc_channel_cfg *channel_cfg)
|
|
|
|
|
{
|
|
|
|
|
struct ifx_autanalog_sar_adc_data *data = dev->data;
|
|
|
|
|
struct ifx_autanalog_sar_adc_channel_config *channel_zephyr_cfg =
|
|
|
|
|
&data->autanalog_channel_cfg[channel_cfg->channel_id];
|
|
|
|
|
cy_stc_autanalog_sar_hs_chan_t *pdl_channel =
|
|
|
|
|
&data->pdl_adc_hs_channel_cfg_obj_arr[channel_cfg->channel_id];
|
|
|
|
|
data->pdl_adc_hs_static_obj.hsGpioChan[channel_cfg->channel_id] = pdl_channel;
|
|
|
|
|
uint8_t sample_time_idx;
|
|
|
|
|
uint16_t timer_clock_cycles;
|
|
|
|
|
|
|
|
|
|
if (channel_cfg->channel_id >= IFX_AUTANALOG_SAR_MAX_NUM_CHANNELS) {
|
|
|
|
|
LOG_ERR("Invalid channel ID: %d", channel_cfg->channel_id);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (channel_cfg->differential) {
|
|
|
|
|
LOG_ERR("Differential channels not supported");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (channel_cfg->gain != ADC_GAIN_1) {
|
|
|
|
|
LOG_ERR("AutAnalog SAR ADC Hardware only supports unity gain.");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* NOTE: This ADC hardware does not support reference settings per channel.
|
|
|
|
|
* Reference must be set for all channels. Use vref-source property in devicetree
|
|
|
|
|
* for the adc instance.
|
|
|
|
|
*/
|
|
|
|
|
if (channel_cfg->reference != ADC_REF_INTERNAL &&
|
|
|
|
|
channel_cfg->reference != ADC_REF_EXTERNAL0 &&
|
|
|
|
|
channel_cfg->reference != ADC_REF_VDD_1_2) {
|
|
|
|
|
LOG_ERR("Reference setting not supported.");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* This driver implementation only supports direct GPIO channel inputs and not the
|
|
|
|
|
* MUXed inputs
|
|
|
|
|
*/
|
|
|
|
|
if (channel_cfg->input_positive >= PASS_SAR_SAR_GPIO_CHANNELS) {
|
|
|
|
|
LOG_ERR("Invalid ADC input pin for channel %d: %d", channel_cfg->channel_id,
|
|
|
|
|
channel_cfg->input_positive);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Calculate sample time and try to map it to one of the 4 available sample time
|
|
|
|
|
* configurations. If all sample time slots have been used and don't match the
|
|
|
|
|
* requested time, return an error and stop configuring the ADC channel.
|
|
|
|
|
*/
|
|
|
|
|
timer_clock_cycles = ifx_calc_acquisition_timer_val(channel_cfg->acquisition_time);
|
|
|
|
|
sample_time_idx = 0xFF;
|
|
|
|
|
for (size_t i = 0; i < IFX_AUTANALOG_SAR_SAMPLETIME_COUNT; i++) {
|
|
|
|
|
if (data->pdl_adc_hs_static_obj.hsSampleTime[i] == timer_clock_cycles) {
|
|
|
|
|
sample_time_idx = i;
|
|
|
|
|
break;
|
|
|
|
|
} else if (data->pdl_adc_hs_static_obj.hsSampleTime[i] == 0) {
|
|
|
|
|
data->pdl_adc_hs_static_obj.hsSampleTime[i] = timer_clock_cycles;
|
|
|
|
|
sample_time_idx = i;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sample_time_idx == 0xFF) {
|
|
|
|
|
LOG_ERR("No available sample time slots for requested acquisition time");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
channel_zephyr_cfg->sample_time_idx = sample_time_idx;
|
|
|
|
|
|
|
|
|
|
pdl_channel->posPin = channel_cfg->input_positive;
|
|
|
|
|
pdl_channel->hsDiffEn = false;
|
|
|
|
|
pdl_channel->sign = false;
|
|
|
|
|
pdl_channel->posCoeff = CY_AUTANALOG_SAR_CH_COEFF_DISABLED;
|
|
|
|
|
pdl_channel->negPin = CY_AUTANALOG_SAR_PIN_GPIO0;
|
|
|
|
|
pdl_channel->accShift = false;
|
|
|
|
|
pdl_channel->negCoeff = CY_AUTANALOG_SAR_CH_COEFF_DISABLED;
|
|
|
|
|
pdl_channel->hsLimit = CY_AUTANALOG_SAR_LIMIT_STATUS_DISABLED;
|
|
|
|
|
pdl_channel->fifoSel = CY_AUTANALOG_FIFO_DISABLED;
|
|
|
|
|
|
|
|
|
|
data->pdl_adc_hs_static_obj.hsGpioResultMask |= (1 << channel_cfg->channel_id);
|
|
|
|
|
|
|
|
|
|
if (Cy_AutAnalog_SAR_LoadStaticConfig(0, &data->pdl_adc_top_static_obj) !=
|
|
|
|
|
CY_AUTANALOG_SUCCESS) {
|
|
|
|
|
data->pdl_adc_hs_static_obj.hsGpioChan[channel_cfg->channel_id] = NULL;
|
|
|
|
|
data->pdl_adc_hs_static_obj.hsGpioResultMask &= ~(1 << channel_cfg->channel_id);
|
|
|
|
|
LOG_ERR("Failed to configure ADC Channel %d", channel_cfg->channel_id);
|
|
|
|
|
|
|
|
|
|
return -EIO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data->enabled_channels |= BIT(channel_cfg->channel_id);
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
} /* ifx_autanalog_sar_adc_channel_setup() */
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Initialize the ADC driver
|
|
|
|
|
*
|
|
|
|
|
* @param dev Pointer to device structure
|
|
|
|
|
*
|
|
|
|
|
* Initializes the ADC driver, configures the Infineon PDL data structures, and initializes the
|
|
|
|
|
* AutAnalog SAR ADC hardware.
|
|
|
|
|
*
|
|
|
|
|
* @return 0 on success, negative error code on failure
|
|
|
|
|
*/
|
|
|
|
|
static int ifx_autanalog_sar_adc_init(const struct device *dev)
|
|
|
|
|
{
|
|
|
|
|
const struct ifx_autanalog_sar_adc_config *cfg = dev->config;
|
|
|
|
|
struct ifx_autanalog_sar_adc_data *data = dev->data;
|
|
|
|
|
cy_en_autanalog_status_t result_val;
|
|
|
|
|
|
|
|
|
|
memset(data->autanalog_channel_cfg, 0xFF, sizeof(data->autanalog_channel_cfg));
|
|
|
|
|
|
|
|
|
|
data->dev = dev;
|
|
|
|
|
data->enabled_channels = 0;
|
|
|
|
|
|
|
|
|
|
/* Initialize the pdl data structures based on the device tree configuration, then
|
|
|
|
|
* use Infineon PDL APIs to initialize the ADC.
|
|
|
|
|
*/
|
|
|
|
|
ifx_init_pdl_structs(data, cfg);
|
|
|
|
|
result_val = Cy_AutAnalog_SAR_LoadConfig(0, &data->pdl_adc_top_obj);
|
|
|
|
|
if (result_val != CY_AUTANALOG_SUCCESS) {
|
|
|
|
|
LOG_ERR("Failed to initialize AutAnalog SAR ADC");
|
|
|
|
|
return -EIO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Note: We can only partially initialize the AutAnalog system here. If we try to
|
|
|
|
|
* run the Autonomous Controller here, the ADC will not function correctly. We need
|
|
|
|
|
* to wait until at least one channel is configured before starting the AC.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#if defined(CONFIG_ADC_ASYNC)
|
|
|
|
|
cfg->irq_func();
|
|
|
|
|
#endif /* CONFIG_ADC_ASYNC */
|
|
|
|
|
|
|
|
|
|
adc_context_unlock_unconditionally(&data->ctx);
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
} /* ifx_autanalog_sar_adc_init() */
|
|
|
|
|
|
|
|
|
|
#ifdef CONFIG_ADC_ASYNC
|
|
|
|
|
#define ADC_IFX_AUTANALOG_SAR_DRIVER_API(n) \
|
2026-02-10 16:11:27 +01:00
|
|
|
static DEVICE_API(adc, ifx_autanalog_sar_adc_api_##n) = { \
|
2025-10-16 15:57:18 -07:00
|
|
|
.channel_setup = ifx_autanalog_sar_adc_channel_setup, \
|
|
|
|
|
.read = ifx_autanalog_sar_adc_read, \
|
|
|
|
|
.read_async = ifx_autanalog_sar_adc_read_async, \
|
|
|
|
|
.ref_internal = DT_INST_PROP(n, vref_mv), \
|
|
|
|
|
};
|
|
|
|
|
#else
|
|
|
|
|
#define ADC_IFX_AUTANALOG_SAR_DRIVER_API(n) \
|
2026-02-10 16:11:27 +01:00
|
|
|
static DEVICE_API(adc, ifx_autanalog_sar_adc_api_##n) = { \
|
2025-10-16 15:57:18 -07:00
|
|
|
.channel_setup = ifx_autanalog_sar_adc_channel_setup, \
|
|
|
|
|
.read = ifx_autanalog_sar_adc_read, \
|
|
|
|
|
.ref_internal = DT_INST_PROP(n, vref_mv), \
|
|
|
|
|
};
|
|
|
|
|
#endif /* CONFIG_ADC_ASYNC */
|
|
|
|
|
|
|
|
|
|
/* Device Instantiation */
|
|
|
|
|
#define IFX_AUTANALOG_SAR_ADC_INIT(n) \
|
|
|
|
|
ADC_IFX_AUTANALOG_SAR_DRIVER_API(n); \
|
|
|
|
|
static void ifx_autanalog_sar_adc_config_func_##n(void); \
|
|
|
|
|
static const struct ifx_autanalog_sar_adc_config ifx_autanalog_sar_adc_config_##n = { \
|
|
|
|
|
.irq_func = ifx_autanalog_sar_adc_config_func_##n, \
|
|
|
|
|
.vref_source = DT_INST_ENUM_IDX(n, vref_source), \
|
|
|
|
|
.linear_cal = DT_INST_PROP(n, linear_cal), \
|
|
|
|
|
.offset_cal = DT_INST_PROP(n, offset_cal)}; \
|
|
|
|
|
static struct ifx_autanalog_sar_adc_data ifx_autanalog_sar_adc_data_##n = { \
|
|
|
|
|
ADC_CONTEXT_INIT_LOCK(ifx_autanalog_sar_adc_data_##n, ctx), \
|
|
|
|
|
ADC_CONTEXT_INIT_TIMER(ifx_autanalog_sar_adc_data_##n, ctx), \
|
|
|
|
|
ADC_CONTEXT_INIT_SYNC(ifx_autanalog_sar_adc_data_##n, ctx), \
|
|
|
|
|
}; \
|
|
|
|
|
DEVICE_DT_INST_DEFINE(n, &ifx_autanalog_sar_adc_init, NULL, \
|
|
|
|
|
&ifx_autanalog_sar_adc_data_##n, &ifx_autanalog_sar_adc_config_##n, \
|
|
|
|
|
POST_KERNEL, CONFIG_ADC_INIT_PRIORITY, \
|
|
|
|
|
&ifx_autanalog_sar_adc_api_##n); \
|
|
|
|
|
\
|
|
|
|
|
static void ifx_autanalog_sar_adc_config_func_##n(void) \
|
|
|
|
|
{ \
|
|
|
|
|
ifx_autanalog_register_adc_handler(ifx_autanalog_sar_adc_isr, \
|
|
|
|
|
DEVICE_DT_INST_GET(n)); \
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(IFX_AUTANALOG_SAR_ADC_INIT)
|