diff --git a/.github/workflows/dev_mspm33_build.yml b/.github/workflows/dev_mspm33_build.yml index cff30ac078174..5995ea4ed4a89 100644 --- a/.github/workflows/dev_mspm33_build.yml +++ b/.github/workflows/dev_mspm33_build.yml @@ -137,3 +137,31 @@ jobs: cd ~/zephyrproject/zephyr source ~/zephyrproject/zephyr/zephyr-env.sh west build -p always -b lp_mspm33c321a tests/drivers/dma/loop_transfer + + - name: Build ADC DT Sample + run: | + source ~/zephyrproject/.venv/bin/activate + cd ~/zephyrproject/zephyr + source ~/zephyrproject/zephyr/zephyr-env.sh + west build -p always -b lp_mspm33c321a samples/drivers/adc/adc_dt + + - name: Build ADC Sequence Sample + run: | + source ~/zephyrproject/.venv/bin/activate + cd ~/zephyrproject/zephyr + source ~/zephyrproject/zephyr/zephyr-env.sh + west build -p always -b lp_mspm33c321a samples/drivers/adc/adc_sequence + + - name: Build ADC API Test + run: | + source ~/zephyrproject/.venv/bin/activate + cd ~/zephyrproject/zephyr + source ~/zephyrproject/zephyr/zephyr-env.sh + west build -p always -b lp_mspm33c321a tests/drivers/adc/adc_api + + - name: Build ADC Error Cases Test + run: | + source ~/zephyrproject/.venv/bin/activate + cd ~/zephyrproject/zephyr + source ~/zephyrproject/zephyr/zephyr-env.sh + west build -p always -b lp_mspm33c321a tests/drivers/adc/adc_error_cases diff --git a/boards/ti/lp_mspm33c321a/doc/index.rst b/boards/ti/lp_mspm33c321a/doc/index.rst index 86983650a28d2..0e7faf3270b21 100644 --- a/boards/ti/lp_mspm33c321a/doc/index.rst +++ b/boards/ti/lp_mspm33c321a/doc/index.rst @@ -20,7 +20,7 @@ The LP_MSPM33C321A LaunchPad is a development platform for the MSPM33C321A micro Zephyr uses the ``lp_mspm33c321a`` board configuration for building applications for this platform. Board Features -============= +============== * MSPM33C321A microcontroller with Arm® Cortex®-M33 core running at up to 160 MHz * 1MB Flash memory with ECC @@ -31,7 +31,7 @@ Board Features Development Environment -********************** +======================= The following development environment was used while developing and testing: @@ -80,7 +80,7 @@ Future updates to the Zephyr support for this board will include flash-based pro and support for the west flash command. Serial Console -============= +============== The MSPM33C321A LaunchPad includes an on-board XDS110 debugger that also provides a virtual COM port over USB. This can be used for serial console output. diff --git a/drivers/adc/Kconfig.mspm33 b/drivers/adc/Kconfig.mspm33 index 277247b50feba..7b802cfb4f1e2 100644 --- a/drivers/adc/Kconfig.mspm33 +++ b/drivers/adc/Kconfig.mspm33 @@ -9,3 +9,29 @@ config ADC_MSP_HSADC select USE_MSP_DL_VREF help Enable support for HSADC on the TI MSPM33 series. + +config ADC_MSP_HSADC_SOC_MAX + int "Maximum number of Start of Conversion (SOC) entries" + depends on ADC_MSP_HSADC + default 16 + range 1 16 + help + Maximum number of SOCs supported by the HSADC hardware. + The MSPM33 HSADC supports up to 16 SOCs for sequence conversion. + +config ADC_MSP_HSADC_CHANNEL_MAX + int "Maximum number of ADC input channels" + depends on ADC_MSP_HSADC + default 32 + range 1 32 + help + Maximum number of ADC input channels supported by the HSADC hardware. + The MSPM33 HSADC supports up to 32 input channels. + +config ADC_MSP_HSADC_DMA + bool "DMA support for TI MSPM33 HSADC" + depends on ADC_MSP_HSADC + select DMA + help + DMA driven mode offloads ADC result transfer from the CPU. + Requires dmas property in the ADC device tree node. diff --git a/drivers/adc/adc_msp_hsadc.c b/drivers/adc/adc_msp_hsadc.c index c800b03ae79c6..559a0f840981c 100644 --- a/drivers/adc/adc_msp_hsadc.c +++ b/drivers/adc/adc_msp_hsadc.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025 Texas Instruments + * Copyright (c) 2026 Texas Instruments * * SPDX-License-Identifier: Apache-2.0 */ @@ -7,6 +7,7 @@ #define DT_DRV_COMPAT ti_msp_hsadc #include +#include #define LOG_LEVEL CONFIG_ADC_LOG_LEVEL #include @@ -18,6 +19,10 @@ LOG_MODULE_REGISTER(adc_msp_hsadc); #include #include +#ifdef CONFIG_ADC_MSP_HSADC_DMA +#include +#endif + /* Driverlib includes */ #include #include @@ -28,75 +33,176 @@ LOG_MODULE_REGISTER(adc_msp_hsadc); #define ADC_CONTEXT_USES_KERNEL_TIMER #include "adc_context.h" -/* This ADC implementation supports up to 16 SOCs for sequence conversion */ -#define ADC_MSP_HSADC_SOC_MAX (16) +/* HSADC hardware limits - configured via Kconfig for SoC-specific variants */ +#define ADC_MSP_HSADC_SOC_MAX CONFIG_ADC_MSP_HSADC_SOC_MAX +#define ADC_MSP_HSADC_CHANNEL_MAX CONFIG_ADC_MSP_HSADC_CHANNEL_MAX +#define ADC_MSP_HSADC_ACQ_TIME_DEFAULT 64 /* Default acquisition time in SYSCLK cycles */ +#define ADC_MSP_HSADC_ACQ_TIME_MIN minSampleWindow /* From driverlib (1 SYSCLK) */ +#define ADC_MSP_HSADC_ACQ_TIME_MAX maxSampleWindow /* From driverlib (1472 SYSCLK) */ -/* VREF source options */ -#define ADC_MSP_HSADC_VREF_VDDA 0 -#define ADC_MSP_HSADC_VREF_INTERNAL_2_5V 1 -#define ADC_MSP_HSADC_VREF_INTERNAL_1_4V 2 +/* Helper macros to reduce repeated type casts */ +#define ADC_REGS(cfg) ((hsadc_ADC_LITE_REGS_Regs *)(cfg)->config_base) +#define RESULT_REGS(cfg) ((hsadc_ADC_LITE_RESULT_REGS_Regs *)(cfg)->result_base) -/* VDDA reference voltage in mV */ -#define ADC_MSP_HSADC_VDDA_MV 3300 +/* Internal reference voltage values (mV) - used to auto-detect VREF source */ +#define ADC_MSP_HSADC_VREF_INTERNAL_2_5V_MV 2500 +#define ADC_MSP_HSADC_VREF_INTERNAL_1_4V_MV 1400 + +/* Helper to check if vref_mv indicates internal reference */ +static inline bool adc_msp_hsadc_uses_internal_vref(uint16_t vref_mv) +{ + return (vref_mv == ADC_MSP_HSADC_VREF_INTERNAL_2_5V_MV || + vref_mv == ADC_MSP_HSADC_VREF_INTERNAL_1_4V_MV); +} struct adc_msp_hsadc_data { struct adc_context ctx; const struct device *dev; - uint16_t *buffer; uint16_t *repeat_buffer; - uint8_t sequencer_mapping[ADC_MSP_HSADC_SOC_MAX]; /* Maps channels to sequencers */ - uint8_t soc_mapping[ADC_MSP_HSADC_SOC_MAX]; /* Maps SOCs to channels */ - uint8_t channel_to_soc[ADC_MSP_HSADC_SOC_MAX]; /* Maps channels to SOCs (reverse of - soc_mapping) */ + /* Per-channel acquisition time in SYSCLK cycles (indexed by channel_id) */ + uint16_t ch_acq_time[ADC_MSP_HSADC_CHANNEL_MAX]; + uint32_t ch_configured; /* Bitmask of configured channels */ - uint32_t channels; - uint16_t oversampling; - uint32_t active_sequencers; /* Bitmap of active sequencers */ +#ifdef CONFIG_ADC_MSP_HSADC_DMA + /* Temp buffer for 32-bit packed FIFO results (2 x 16-bit per word) */ + uint32_t dma_fifo_result[(ADC_MSP_HSADC_SOC_MAX + 1) / 2]; +#endif }; struct adc_msp_hsadc_cfg { - uint32_t config_base; /* Base address for ADC_LITE_REGS */ - uint32_t result_base; /* Base address for ADC_LITE_RESULT_REGS */ - uint32_t clockDivider; - uint32_t sampleWindow; /* Sample window in ADC clock cycles */ + uint32_t config_base; + uint32_t result_base; + uint32_t clock_divider; void (*irq_cfg_func)(void); - uint8_t vref_source; - uint16_t ref_internal; /* mV */ + uint16_t vref_mv; +#ifdef CONFIG_ADC_MSP_HSADC_DMA + const struct device *dma_dev; + uint8_t dma_channel; + uint8_t dma_trigsrc; +#endif }; static void adc_msp_hsadc_isr(const struct device *dev); +#ifdef CONFIG_ADC_MSP_HSADC_DMA +static void adc_msp_hsadc_dma_callback(const struct device *dma_dev, void *user_data, + uint32_t channel, int status) +{ + const struct device *dev = user_data; + struct adc_msp_hsadc_data *data = dev->data; + const struct adc_msp_hsadc_cfg *config = dev->config; + uint16_t *buffer = (uint16_t *)data->ctx.sequence.buffer; + int ch_count = POPCOUNT(data->ctx.sequence.channels); + int fifo_reads = (ch_count + 1) / 2; + + DL_HSADC_DMAInterruptStatusClear(ADC_REGS(config), DL_HSADC_DMA_INT_1); + DL_HSADC_disableDMAInterrupt(ADC_REGS(config), DL_HSADC_DMA_INT_1); + + if (status != DMA_STATUS_COMPLETE) { + LOG_ERR("DMA transfer error: %d", status); + adc_context_complete(&data->ctx, -EIO); + return; + } + + /* Unpack 32-bit FIFO words into uint16_t user buffer. + * FIFO packing: each 32-bit read returns 2 consecutive SOC results. + * LSB[15:0] = first result, MSB[31:16] = second result. + */ + for (int i = 0; i < fifo_reads; i++) { + uint32_t packed = data->dma_fifo_result[i]; + + buffer[2 * i] = (uint16_t)(packed & 0xFFFF); + if ((2 * i + 1) < ch_count) { + buffer[2 * i + 1] = (uint16_t)((packed >> 16) & 0xFFFF); + } + } + + adc_context_on_sampling_done(&data->ctx, dev); +} + +static int adc_msp_hsadc_dma_start(const struct device *dev, int ch_count) +{ + const struct adc_msp_hsadc_cfg *config = dev->config; + struct adc_msp_hsadc_data *data = dev->data; + int fifo_reads = (ch_count + 1) / 2; + int ret; + + struct dma_block_config blk_cfg = { + .source_address = (uint32_t)&RESULT_REGS(config)-> + ADC_LITE_RESULT_REGS.ADCSEQ1FIFORESULT, + .dest_address = (uint32_t)data->dma_fifo_result, + .source_addr_adj = DMA_ADDR_ADJ_NO_CHANGE, + .dest_addr_adj = DMA_ADDR_ADJ_INCREMENT, + .block_size = fifo_reads, + }; + struct dma_config dma_cfg = { + .dma_slot = config->dma_trigsrc, + .channel_direction = PERIPHERAL_TO_MEMORY, + .block_count = 1, + .head_block = &blk_cfg, + .source_data_size = 4, + .dest_data_size = 4, + .dma_callback = adc_msp_hsadc_dma_callback, + .user_data = (void *)dev, + }; + + ret = dma_config(config->dma_dev, config->dma_channel, &dma_cfg); + if (ret < 0) { + LOG_ERR("DMA config failed: %d", ret); + return ret; + } + + ret = dma_start(config->dma_dev, config->dma_channel); + if (ret < 0) { + LOG_ERR("DMA start failed: %d", ret); + return ret; + } + + return 0; +} +#endif /* CONFIG_ADC_MSP_HSADC_DMA */ + static void adc_context_start_sampling(struct adc_context *ctx) { struct adc_msp_hsadc_data *data = CONTAINER_OF(ctx, struct adc_msp_hsadc_data, ctx); const struct device *dev = data->dev; const struct adc_msp_hsadc_cfg *config = dev->config; - data->repeat_buffer = data->buffer; + data->repeat_buffer = (uint16_t *)ctx->sequence.buffer; - /* Clear and enable interrupts for all active sequencers */ - for (int seq = 0; seq < 4; seq++) { - if (data->active_sequencers & BIT(seq)) { - /* Clear any pending interrupts */ - DL_HSADC_InterruptStatusClear( - (hsadc_ADC_LITE_REGS_Regs *)config->config_base, - (DL_HSADC_InterruptNumber)seq); +#ifdef CONFIG_ADC_MSP_HSADC_DMA + if (config->dma_dev != NULL) { + int ch_count = POPCOUNT(ctx->sequence.channels); - /* Enable the interrupt */ - DL_HSADC_enableInterrupt((hsadc_ADC_LITE_REGS_Regs *)config->config_base, - (DL_HSADC_InterruptNumber)seq); - } - } + /* Re-enable HSADC DMA trigger for this sampling round. + * The DMA callback disables DMA_INT_1 after each transfer. + * For repeated samplings (extra_samplings > 0), we must + * re-enable it before each round. + */ + DL_HSADC_DMAInterruptStatusClear(ADC_REGS(config), DL_HSADC_DMA_INT_1); + DL_HSADC_enableDMAInterrupt(ADC_REGS(config), DL_HSADC_DMA_INT_1); + + /* Start DMA transfer before triggering ADC */ + int ret = adc_msp_hsadc_dma_start(dev, ch_count); - /* Trigger all active sequencers */ - for (int seq = 0; seq < 4; seq++) { - if (data->active_sequencers & BIT(seq)) { - DL_HSADC_triggerSequencerSoftwareForce( - (hsadc_ADC_LITE_REGS_Regs *)config->config_base, - (DL_HSADC_SEQNumber)seq); + if (ret < 0) { + adc_context_complete(ctx, ret); + return; } + + /* Software trigger: DMA handles result collection */ + DL_HSADC_triggerSequencerSoftwareForce(ADC_REGS(config), DL_HSADC_SEQ_NUMBER1); + return; } +#endif + + /* Non-DMA path: interrupt-driven result collection */ + DL_HSADC_InterruptStatusClear(ADC_REGS(config), DL_HSADC_INT_1); + DL_HSADC_enableInterrupt(ADC_REGS(config), DL_HSADC_INT_1); + + /* Software trigger: start conversion now */ + DL_HSADC_triggerSequencerSoftwareForce(ADC_REGS(config), DL_HSADC_SEQ_NUMBER1); } static void adc_context_update_buffer_pointer(struct adc_context *ctx, bool repeat) @@ -104,101 +210,54 @@ static void adc_context_update_buffer_pointer(struct adc_context *ctx, bool repe struct adc_msp_hsadc_data *data = CONTAINER_OF(ctx, struct adc_msp_hsadc_data, ctx); if (repeat) { - data->buffer = data->repeat_buffer; + /* Reset to beginning for ADC_ACTION_REPEAT */ + ctx->sequence.buffer = data->repeat_buffer; } else { - data->buffer++; + /* Advance buffer by number of active channels for next sampling */ + uint16_t *buffer = (uint16_t *)ctx->sequence.buffer; + + buffer += POPCOUNT(ctx->sequence.channels); + ctx->sequence.buffer = buffer; } } -/** - * @brief Configure the VREF module for internal reference - * - * This function configures the VREF module when an internal reference - * voltage is selected. It checks if VREF is already configured correctly, - * and if not, initializes it with the appropriate voltage level (2.5V or 1.4V). - * - * @param ref_internal Reference voltage value in mV (2500 or 1400) - * @return 0 on success, negative error code on failure - */ -static int adc_msp_hsadc_config_vref(int ref_internal) +static int adc_msp_hsadc_config_vref(uint16_t vref_mv) { - int error = 0; - bool init_vref = false; - bool use_2_5v = false; - - /* No need to initialize VREF for VDDA */ - if (ref_internal == ADC_MSP_HSADC_VDDA_MV) { - return 0; - } - - /* Determine which internal reference to use */ - if (ref_internal == 2500) { - use_2_5v = true; - } else if (ref_internal == 1400) { - use_2_5v = false; - } else { - LOG_ERR("Invalid VREF value: %d mV", ref_internal); - return -EINVAL; - } - - /* Check if VREF is already powered */ - if ((VREF->GPRCM.PWREN & VREF_PWREN_ENABLE_MASK) == VREF_PWREN_ENABLE_DISABLE) { - /* VREF not powered, need to initialize */ - init_vref = true; - } else { - /* VREF is already powered, check if it's configured correctly */ - if (DL_VREF_isEnabled(VREF)) { - /* VREF is enabled, check configuration */ - if (use_2_5v && ((VREF->CTL0 & VREF_CTL0_BUFCONFIG_MASK) == - VREF_CTL0_BUFCONFIG_OUTPUT2P5V)) { - /* Already configured for 2.5V */ - LOG_DBG("VREF already configured for 2.5V"); - return 0; - } else if (!use_2_5v && ((VREF->CTL0 & VREF_CTL0_BUFCONFIG_MASK) == - VREF_CTL0_BUFCONFIG_OUTPUT1P4V)) { - /* Already configured for 1.4V */ - LOG_DBG("VREF already configured for 1.4V"); - return 0; - } else { - /* VREF is configured but doesn't match requested configuration */ - LOG_ERR("VREF already configured with different voltage"); - return -EINVAL; - } - } else { - /* VREF is powered but not enabled, need to initialize */ - init_vref = true; + bool is_2p5v = (vref_mv == ADC_MSP_HSADC_VREF_INTERNAL_2_5V_MV); + + /* VREF is global (shared across all ADC instances). + * Only one internal VREF voltage active at a time. + */ + if (DL_VREF_isEnabled(VREF)) { + uint32_t current_config = VREF->CTL0 & VREF_CTL0_BUFCONFIG_MASK; + uint32_t expected_config = + is_2p5v ? VREF_CTL0_BUFCONFIG_OUTPUT2P5V : VREF_CTL0_BUFCONFIG_OUTPUT1P4V; + if (current_config == expected_config) { + return 0; /* Already configured correctly */ } + LOG_ERR("VREF conflict: already configured to different voltage"); + return -EINVAL; } - if (init_vref) { - /* Initialize VREF module */ - DL_VREF_reset(VREF); + /* Initialize VREF */ + DL_VREF_reset(VREF); DL_VREF_enablePower(VREF); delay_cycles(CONFIG_MSP_PERIPH_STARTUP_DELAY); - /* Configure VREF clock */ - DL_VREF_ClockConfig vref_clk_config = {.clockSel = DL_VREF_CLOCK_BUSCLK, - .divideRatio = DL_VREF_CLOCK_DIVIDE_1}; - DL_VREF_setClockConfig(VREF, &vref_clk_config); - - /* Configure VREF */ - DL_VREF_Config vref_config = {.vrefEnable = DL_VREF_ENABLE_ENABLE, - .bufConfig = use_2_5v ? DL_VREF_BUFCONFIG_OUTPUT_2_5V - : DL_VREF_BUFCONFIG_OUTPUT_1_4V, - .shModeEnable = DL_VREF_SHMODE_DISABLE, - .holdCycleCount = DL_VREF_HOLD_MIN, - .shCycleCount = DL_VREF_SH_MIN}; - DL_VREF_configReference(VREF, &vref_config); - - /* Wait for VREF to be ready */ - while (DL_VREF_getStatus(VREF) == DL_VREF_CTL1_READY_NOTRDY) { - /* Wait for VREF to be ready */ - } + DL_VREF_ClockConfig vref_clk_config = {.clockSel = DL_VREF_CLOCK_BUSCLK, + .divideRatio = DL_VREF_CLOCK_DIVIDE_1}; + DL_VREF_setClockConfig(VREF, &vref_clk_config); - LOG_DBG("VREF initialized with %s reference", use_2_5v ? "2.5V" : "1.4V"); - } + DL_VREF_Config vref_config = {.vrefEnable = DL_VREF_ENABLE_ENABLE, + .bufConfig = is_2p5v ? DL_VREF_BUFCONFIG_OUTPUT_2_5V + : DL_VREF_BUFCONFIG_OUTPUT_1_4V, + .shModeEnable = DL_VREF_SHMODE_DISABLE, + .holdCycleCount = DL_VREF_HOLD_MIN, + .shCycleCount = DL_VREF_SH_MIN}; + DL_VREF_configReference(VREF, &vref_config); - return error; + /* VREF settling is verified in init() after ADC power-up */ + return 0; } static int adc_msp_hsadc_init(const struct device *dev) @@ -206,210 +265,203 @@ static int adc_msp_hsadc_init(const struct device *dev) struct adc_msp_hsadc_data *data = dev->data; const struct adc_msp_hsadc_cfg *config = dev->config; - LOG_DBG("Initializing %s", dev->name); - data->dev = dev; - /* Init power */ - DL_HSADC_reset((hsadc_ADC_LITE_REGS_Regs *)config->config_base); - DL_HSADC_enablePower((hsadc_ADC_LITE_REGS_Regs *)config->config_base); - - delay_cycles(CONFIG_MSP_PERIPH_STARTUP_DELAY); // wait for power to stabilize + DL_HSADC_reset(ADC_REGS(config)); + DL_HSADC_enablePower(ADC_REGS(config)); + delay_cycles(CONFIG_MSP_PERIPH_STARTUP_DELAY); - /* Configure clock */ - DL_HSADC_setClockDivideRatio((hsadc_ADC_LITE_REGS_Regs *)config->config_base, - config->clockDivider); + DL_HSADC_setClockDivideRatio(ADC_REGS(config), config->clock_divider); - /* Initialize VREF if using internal reference voltage value */ - if (config->vref_source != ADC_MSP_HSADC_VREF_VDDA) { - /* Initialize VREF with the configured reference voltage */ - int ref_internal = - (config->vref_source == ADC_MSP_HSADC_VREF_INTERNAL_2_5V) ? 2500 : 1400; - int ret = adc_msp_hsadc_config_vref(ref_internal); + /* Configure internal VREF if vref_mv matches a known internal reference */ + if (adc_msp_hsadc_uses_internal_vref(config->vref_mv)) { + LOG_INF("Configuring internal VREF to %d mV", config->vref_mv); + int ret = adc_msp_hsadc_config_vref(config->vref_mv); if (ret < 0) { LOG_ERR("Failed to configure VREF: %d", ret); return ret; } } - /* Power up the ADC */ - DL_HSADC_PowerUp((hsadc_ADC_LITE_REGS_Regs *)config->config_base); + DL_HSADC_PowerUp(ADC_REGS(config)); + + if (adc_msp_hsadc_uses_internal_vref(config->vref_mv)) { + int timeout = 1000; + + while (DL_VREF_getStatus(VREF) == DL_VREF_CTL1_READY_NOTRDY) { + k_busy_wait(10); + if (--timeout == 0) { + LOG_ERR("VREF not ready after ADC power-up"); + return -ETIMEDOUT; + } + } + } - /* Reset active sequencers */ - data->active_sequencers = 0; + /* Initialize driver data */ + data->ch_configured = 0; + memset(data->ch_acq_time, 0, sizeof(data->ch_acq_time)); - /* Initialize sequencer mapping */ - for (int i = 0; i < ADC_MSP_HSADC_SOC_MAX; i++) { - data->sequencer_mapping[i] = 0xFF; /* Invalid sequencer */ - data->soc_mapping[i] = 0xFF; /* Invalid channel */ - data->channel_to_soc[i] = 0xFF; /* Invalid SOC */ +#ifdef CONFIG_ADC_MSP_HSADC_DMA + if (config->dma_dev != NULL && !device_is_ready(config->dma_dev)) { + LOG_ERR("DMA device not ready"); + return -ENODEV; } +#endif config->irq_cfg_func(); - adc_context_unlock_unconditionally(&data->ctx); + return 0; } static int adc_msp_hsadc_channel_setup(const struct device *dev, const struct adc_channel_cfg *channel_cfg) { + struct adc_msp_hsadc_data *data = dev->data; const struct adc_msp_hsadc_cfg *config = dev->config; const uint8_t ch = channel_cfg->channel_id; - if (ch >= 32) { /* HSADC supports up to 32 channels */ - LOG_ERR("Channel 0x%X is not supported, max 31", ch); + if (ch >= ADC_MSP_HSADC_CHANNEL_MAX) { + LOG_ERR("Channel %d not supported, max %d", ch, ADC_MSP_HSADC_CHANNEL_MAX - 1); return -EINVAL; } - /* Validate channel configuration */ if (channel_cfg->differential) { - LOG_ERR("Differential channels are not supported"); + LOG_ERR("Differential channels not supported"); return -EINVAL; } if (channel_cfg->gain != ADC_GAIN_1) { - LOG_ERR("Gain is not valid"); + LOG_ERR("Only ADC_GAIN_1 supported"); return -EINVAL; } - /* Check reference voltage configuration */ if (channel_cfg->reference == ADC_REF_VDD_1) { - /* Using VDD as reference is always supported */ - if (config->vref_source != ADC_MSP_HSADC_VREF_VDDA) { - LOG_WRN("Using VDDA reference despite internal reference being configured"); + if (adc_msp_hsadc_uses_internal_vref(config->vref_mv)) { + LOG_WRN("Channel uses ADC_REF_VDD_1 but internal reference configured"); } - LOG_DBG("Using VDDA as reference voltage"); } else if (channel_cfg->reference == ADC_REF_INTERNAL) { - /* Internal reference is only valid if configured */ - if (config->vref_source == ADC_MSP_HSADC_VREF_VDDA) { - LOG_ERR("Internal reference requested but not configured"); - return -EINVAL; + if (!adc_msp_hsadc_uses_internal_vref(config->vref_mv)) { + LOG_WRN("Channel uses ADC_REF_INTERNAL but VDDA configured"); } - LOG_DBG("Using internal reference: %d mV", config->ref_internal); } else { LOG_ERR("Unsupported reference voltage"); return -EINVAL; } - /* We don't configure the SOC here - that's done during sequence configuration */ - LOG_DBG("ADC Channel setup successful!"); + /* Decode acquisition time from zephyr,acquisition-time DT property. + * ACQPS valid range: 1-1472 SYSCLK cycles. + * Only ADC_ACQ_TIME_TICKS unit is supported. + */ + uint16_t acq_time = channel_cfg->acquisition_time; + uint16_t acq_ticks; + + if (acq_time == ADC_ACQ_TIME_DEFAULT) { + acq_ticks = ADC_MSP_HSADC_ACQ_TIME_DEFAULT; + } else if (ADC_ACQ_TIME_UNIT(acq_time) == ADC_ACQ_TIME_TICKS) { + acq_ticks = ADC_ACQ_TIME_VALUE(acq_time); + if (acq_ticks < ADC_MSP_HSADC_ACQ_TIME_MIN || + acq_ticks > ADC_MSP_HSADC_ACQ_TIME_MAX) { + LOG_ERR("Channel %d: acq_time %d out of range (%d-%d)", ch, acq_ticks, + ADC_MSP_HSADC_ACQ_TIME_MIN, ADC_MSP_HSADC_ACQ_TIME_MAX); + return -EINVAL; + } + } else { + LOG_ERR("Channel %d: only ADC_ACQ_TIME_TICKS supported", ch); + return -ENOTSUP; + } + + /* Store channel configuration */ + data->ch_acq_time[ch] = acq_ticks; + data->ch_configured |= BIT(ch); + + LOG_DBG("Channel %d: acq_time=%d SYSCLK cycles", ch, acq_ticks); + return 0; } -static int adc_msp_hsadc_configure_sequence(const struct device *dev) +static int adc_msp_hsadc_configure_sequence(const struct device *dev, + const struct adc_sequence *sequence) { - struct adc_msp_hsadc_data *data = dev->data; const struct adc_msp_hsadc_cfg *config = dev->config; - uint32_t channels; - uint8_t ch; - - /* Configure all enabled channels for the sequence */ - channels = data->channels; - - /* Reset active sequencers, channel mappings, and SOC assignments */ - data->active_sequencers = 0; - uint8_t next_soc = 0; - - for (int i = 0; i < ADC_MSP_HSADC_SOC_MAX; i++) { - data->sequencer_mapping[i] = 0xFF; /* Invalid sequencer */ - data->soc_mapping[i] = 0xFF; /* Invalid channel */ - data->channel_to_soc[i] = 0xFF; /* Invalid SOC */ - } - - /* Group channels by sequencer based on hardware requirements */ - uint8_t seq_positions[4] = {0, 0, 0, 0}; /* Track position in each sequencer */ - - /* Map channels to appropriate sequencers and SOCs */ - uint32_t temp_channels = channels; - while (temp_channels) { - ch = find_lsb_set(temp_channels) - 1; - - /* Check if we've reached the SOC limit */ - if (next_soc >= ADC_MSP_HSADC_SOC_MAX) { - LOG_ERR("Too many channels requested, max SOCs: %d", ADC_MSP_HSADC_SOC_MAX); + struct adc_msp_hsadc_data *data = dev->data; + uint32_t channels = sequence->channels; + uint32_t temp_channels; + uint16_t acq_time = 0; + uint8_t soc = 0; + uint8_t end_soc = 0; + + /* Design assumption: Single sequencer (SEQ_NUMBER1) used for all channels. + * All channels must have identical acquisition time (hardware constraint). + */ + temp_channels = channels; + while (temp_channels != 0) { + uint8_t ch = find_lsb_set(temp_channels) - 1; + + if (!(data->ch_configured & BIT(ch))) { + LOG_ERR("Channel %d not configured", ch); return -EINVAL; } - /* Determine which sequencer this SOC belongs to */ - uint8_t soc = next_soc; - uint8_t seq = soc / 4; /* Group SOCs in blocks of 4 */ - - /* Mark this sequencer as active */ - data->active_sequencers |= BIT(seq); - - /* Map this channel to this sequencer */ - data->sequencer_mapping[ch] = seq; - seq_positions[seq]++; /* Increment position counter for this sequencer */ - - /* Map this SOC to this channel and vice versa */ - data->soc_mapping[soc] = ch; - data->channel_to_soc[ch] = soc; - - /* Configure the SOC to use the specified channel */ - DL_HSADC_SOCChannelSelect((hsadc_ADC_LITE_REGS_Regs *)config->config_base, - (DL_HSADC_SOCNumber)soc, (DL_HSADC_ADCIN)ch); - - /* Move to next SOC */ - next_soc++; - + if (acq_time == 0) { + acq_time = data->ch_acq_time[ch]; + } else if (data->ch_acq_time[ch] != acq_time) { + LOG_ERR("Channel %d acq_time=%d differs from %d", ch, data->ch_acq_time[ch], + acq_time); + return -EINVAL; + } temp_channels &= ~BIT(ch); } - /* Configure each active sequencer */ - uint8_t last_seq_end_soc = 0; - for (int seq = 0; seq < 4; seq++) { - if (data->active_sequencers & BIT(seq)) { - /* Disable the sequencer first for configuration */ - DL_HSADC_disableSequencer((hsadc_ADC_LITE_REGS_Regs *)config->config_base, - (DL_HSADC_SEQNumber)seq); - - /* Calculate start and end SOCs for this sequencer */ - uint8_t seq_start_soc = seq * 4; - uint8_t seq_end_soc = seq_start_soc + seq_positions[seq] - 1; - last_seq_end_soc = seq_end_soc; - - /* Set up sequencer with proper trigger mode */ - DL_HSADC_setupSequencer((hsadc_ADC_LITE_REGS_Regs *)config->config_base, - (DL_HSADC_SEQNumber)seq, config->sampleWindow, - DL_HSADC_Trigger_TieLow_SW_Trig, - (DL_HSADC_SOCNumber)seq_start_soc); - - /* Configure interrupt to trigger on the last channel */ - DL_HSADC_InterruptSourceSelect( - (hsadc_ADC_LITE_REGS_Regs *)config->config_base, - (DL_HSADC_InterruptNumber)seq, (DL_HSADC_SOCNumber)seq_end_soc); - - /* Configure sample cap reset to half VREF for better accuracy */ - DL_HSADC_setSampleCapReset((hsadc_ADC_LITE_REGS_Regs *)config->config_base, - (DL_HSADC_SEQNumber)seq, - DL_HSADC_sampleCapResetSelect_half_vrefhi); - - /* Configure oversampling if requested */ - if (data->oversampling > 0) { - /* Configure oversampling for this sequencer */ - DL_HSADC_setPPBOversamplingLimit( - (hsadc_ADC_LITE_REGS_Regs *)config->config_base, - (DL_HSADC_SEQNumber)seq, - (DL_HSADC_OversamplingLimit)data->oversampling); - - /* Set right shift based on oversampling to maintain proper scaling - */ - DL_HSADC_setPPBRightShift( - (hsadc_ADC_LITE_REGS_Regs *)config->config_base, - (DL_HSADC_SEQNumber)seq, - (DL_HSADC_PPBRightShift)data->oversampling); - } + /* Assign SOCs to channels (lowest channel first) */ + temp_channels = channels; + while (temp_channels != 0) { + uint8_t ch = find_lsb_set(temp_channels) - 1; - /* Enable the sequencer */ - DL_HSADC_enableSequencer((hsadc_ADC_LITE_REGS_Regs *)config->config_base, - (DL_HSADC_SEQNumber)seq); - } + DL_HSADC_SOCChannelSelect(ADC_REGS(config), (DL_HSADC_SOC_NUMBER)soc, + (DL_HSADC_ADCIN)ch); + end_soc = soc; + soc++; + temp_channels &= ~BIT(ch); } - /* Set the end SOC for the sequencer using the last active sequencer's end SOC */ - DL_HSADC_setEndOfSequencer((hsadc_ADC_LITE_REGS_Regs *)config->config_base, - (DL_HSADC_SOCNumber)last_seq_end_soc); + /* Configure sequencer 0 (DL_HSADC_SEQ_NUMBER1 = seq 0) */ + DL_HSADC_disableSequencer(ADC_REGS(config), DL_HSADC_SEQ_NUMBER1); + + /* Only software triggers supported (no hardware triggers). + * All conversions initiated by DL_HSADC_triggerSequencerSoftwareForce(). + */ + DL_HSADC_setupSequencer(ADC_REGS(config), DL_HSADC_SEQ_NUMBER1, acq_time, + DL_HSADC_TRIGGER_TIELOW_SW, DL_HSADC_SOC_NUMBER0); + + DL_HSADC_InterruptSourceSelect(ADC_REGS(config), DL_HSADC_INT_1, + (DL_HSADC_SOC_NUMBER)end_soc); + + DL_HSADC_setSampleCapReset(ADC_REGS(config), DL_HSADC_SEQ_NUMBER1, + DL_HSADC_SAMPCAPRESET_HALF_VREFHI); + + /* Always configure PPB oversampling registers to ensure clean state. + * A previous read with oversampling leaves LIMIT set in hardware, + * causing SOCs to repeat even when oversampling is now disabled. + * When oversampling=0: LIMIT=NULL (no accumulation), SHIFT=0. + */ + DL_HSADC_setPPBOversamplingLimit(ADC_REGS(config), DL_HSADC_SEQ_NUMBER1, + (DL_HSADC_OVERSAMPLING_LIMIT)sequence->oversampling); + DL_HSADC_setPPBRightShift(ADC_REGS(config), DL_HSADC_SEQ_NUMBER1, + (DL_HSADC_PPB_RIGHTSHIFT)sequence->oversampling); + + DL_HSADC_enableSequencer(ADC_REGS(config), DL_HSADC_SEQ_NUMBER1); + DL_HSADC_setEndOfSequencer(ADC_REGS(config), (DL_HSADC_SOC_NUMBER)end_soc); + +#ifdef CONFIG_ADC_MSP_HSADC_DMA + if (config->dma_dev != NULL) { + /* Configure HSADC to generate DMA trigger at end-of-sequence */ + DL_HSADC_DMAInterruptStatusClear(ADC_REGS(config), DL_HSADC_DMA_INT_1); + DL_HSADC_DMAInterruptSourceSelect(ADC_REGS(config), DL_HSADC_DMA_INT_1, + (DL_HSADC_SOC_NUMBER)end_soc); + DL_HSADC_enableDMAInterrupt(ADC_REGS(config), DL_HSADC_DMA_INT_1); + } +#endif return 0; } @@ -420,24 +472,15 @@ static int adc_msp_hsadc_read_internal(const struct device *dev, struct adc_msp_hsadc_data *data = dev->data; const struct adc_msp_hsadc_cfg *config = dev->config; size_t exp_size; - int sequence_ret; + int ret; int ch_count; - /* Check if ADC is busy */ - if (DL_HSADC_isBusy((hsadc_ADC_LITE_REGS_Regs *)config->config_base)) { + if (DL_HSADC_isBusy(ADC_REGS(config))) { LOG_ERR("ADC is busy with another conversion"); return -EBUSY; } - /* Validate resolution - HSADC supports 12-bit */ - if (sequence->resolution != 12) { - LOG_ERR("ADC resolution %d not supported. Only 12 bits.", sequence->resolution); - return -EINVAL; - } - - /* Validate channel count */ - data->channels = sequence->channels; - ch_count = POPCOUNT(data->channels); + ch_count = POPCOUNT(sequence->channels); if (ch_count == 0) { LOG_ERR("No ADC channels selected"); return -EINVAL; @@ -447,44 +490,61 @@ static int adc_msp_hsadc_read_internal(const struct device *dev, return -EINVAL; } - /* Validate buffer size */ exp_size = ch_count * sizeof(uint16_t); if (sequence->options) { exp_size *= (1 + sequence->options->extra_samplings); } if (sequence->buffer_size < exp_size) { - LOG_ERR("Required buffer size is %u, but %u got", exp_size, sequence->buffer_size); + LOG_ERR("Buffer too small: need %u bytes, have %u", exp_size, + sequence->buffer_size); return -ENOMEM; } - data->buffer = sequence->buffer; + if (sequence->resolution != 12) { + LOG_ERR("ADC resolution %d not supported. Only 12 bits.", sequence->resolution); + return -EINVAL; + } - /* Configure oversampling if requested */ - data->oversampling = sequence->oversampling; + /* Validate oversampling ratio */ + if (sequence->oversampling > 3) { + LOG_ERR("Oversampling value %d not supported. Max is 3 (8x oversampling)", + sequence->oversampling); + return -EINVAL; + } - /* Validate oversampling value - hardware supports 2x, 4x, and 8x */ - if (data->oversampling > 0) { - /* Check if oversampling value is valid (0, 1, 2, or 3) */ - if (data->oversampling > 3) { - LOG_ERR("Oversampling value %d not supported. Max is 3 (8x oversampling)", - data->oversampling); - return -EINVAL; - } + /* Hardware oversampling (PPB) incompatible with multi-channel. + * PPB has single SUM register per sequencer; multiple SOCs overwrite each other. + * Only single-channel oversampling is supported. + */ + if (sequence->oversampling > 0 && ch_count > 1) { + LOG_ERR("Oversampling is supported for single channel only"); + return -EINVAL; } +#ifdef CONFIG_ADC_MSP_HSADC_DMA + /* DMA and oversampling use different data paths. + * DMA reads FIFO (packed 2x16-bit), oversampling reads PPB FinalSumResult. + * Cannot mix both in single read. + */ + if (config->dma_dev != NULL && sequence->oversampling > 0) { + LOG_ERR("DMA mode does not support oversampling"); + return -EINVAL; + } +#endif + if (sequence->calibrate) { LOG_ERR("Calibration not supported"); return -ENOTSUP; } - /* Configure the ADC sequence */ - sequence_ret = adc_msp_hsadc_configure_sequence(dev); - if (sequence_ret < 0) { - LOG_ERR("Error in ADC sequence configuration"); - return sequence_ret; + /* Configure the ADC sequencers and SOCs BEFORE starting sampling */ + ret = adc_msp_hsadc_configure_sequence(dev, sequence); + if (ret < 0) { + return ret; } + /* Now start the read operation (this will call adc_context_start_sampling) */ adc_context_start_read(&data->ctx, sequence); return adc_context_wait_for_completion(&data->ctx); } @@ -520,92 +580,60 @@ static void adc_msp_hsadc_isr(const struct device *dev) { struct adc_msp_hsadc_data *data = dev->data; const struct adc_msp_hsadc_cfg *config = dev->config; - uint32_t channels = data->channels; + uint32_t channels = data->ctx.sequence.channels; + uint16_t *buffer = (uint16_t *)data->ctx.sequence.buffer; + uint8_t soc = 0; + uint32_t temp_channels = channels; uint8_t ch; - uint16_t result; - bool interrupt_triggered = false; - uint32_t processed_channels = 0; - - /* Check if any of our interrupts are triggered */ - for (int seq = 0; seq < 4; seq++) { - if ((data->active_sequencers & BIT(seq)) && - DL_HSADC_getInterruptStatus((hsadc_ADC_LITE_REGS_Regs *)config->config_base, - (DL_HSADC_InterruptNumber)seq)) { - interrupt_triggered = true; - - /* Process all channels for this sequencer */ - uint32_t seq_channels = channels; - while (seq_channels) { - ch = find_lsb_set(seq_channels) - 1; - - /* Only process channels mapped to this sequencer */ - if (data->sequencer_mapping[ch] == seq) { - /* Get the SOC for this channel using direct lookup */ - uint8_t soc = data->channel_to_soc[ch]; - - if (soc != 0xFF) { - if (data->oversampling > 0) { - /* When oversampling is enabled, read the - * final sum result */ - result = DL_HSADC_getFinalSumResult( - (hsadc_ADC_LITE_RESULT_REGS_Regs *) - config->result_base, - (DL_HSADC_SEQNumber)seq); - } else { - /* Normal mode - read the direct ADC result - */ - result = DL_HSADC_getResult( - (hsadc_ADC_LITE_RESULT_REGS_Regs *) - config->result_base, - (DL_HSADC_SOCNumber)soc); - } - - *data->buffer++ = result; - processed_channels |= BIT(ch); - } - } - - seq_channels &= ~BIT(ch); - } - /* Clear the interrupt */ - DL_HSADC_InterruptStatusClear( - (hsadc_ADC_LITE_REGS_Regs *)config->config_base, - (DL_HSADC_InterruptNumber)seq); + /* Check for sequencer 0 overflow (DL_HSADC_INT_1 = seq 0) */ + if (DL_HSADC_InterruptOverflowStatus(ADC_REGS(config), DL_HSADC_INT_1)) { + DL_HSADC_InterruptOverflowStatusClear(ADC_REGS(config), DL_HSADC_INT_1); + DL_HSADC_disableInterrupt(ADC_REGS(config), DL_HSADC_INT_1); + DL_HSADC_InterruptStatusClear(ADC_REGS(config), DL_HSADC_INT_1); + LOG_ERR("Sequencer overflow - data loss"); + adc_context_complete(&data->ctx, -EIO); + return; + } + + /* Read results from SOCs in channel order (lowest channel first). + * SOC N contains result for the Nth channel in the bitmask. + * + * When oversampling is active, read from the PPB FinalSumResult which + * contains the hardware-averaged value (PSUM >> SHIFT). Only single-channel + * sequences are allowed with oversampling (validated in read_internal), + * so the PPB SUM register holds the correct averaged result. + */ + while (temp_channels != 0) { + ch = find_lsb_set(temp_channels) - 1; - /* Disable interrupt */ - DL_HSADC_disableInterrupt((hsadc_ADC_LITE_REGS_Regs *)config->config_base, - (DL_HSADC_InterruptNumber)seq); + if (data->ctx.sequence.oversampling > 0) { + *buffer++ = DL_HSADC_getFinalSumResult(RESULT_REGS(config), + DL_HSADC_SEQ_NUMBER1); + } else { + *buffer++ = DL_HSADC_getResult(RESULT_REGS(config), + (DL_HSADC_SOC_NUMBER)soc); } + soc++; + temp_channels &= ~BIT(ch); } - /* Signal completion when all channels have been processed */ - if (interrupt_triggered && processed_channels == channels) { - adc_context_on_sampling_done(&data->ctx, dev); - } -} + DL_HSADC_InterruptStatusClear(ADC_REGS(config), DL_HSADC_INT_1); + DL_HSADC_disableInterrupt(ADC_REGS(config), DL_HSADC_INT_1); -static DEVICE_API(adc, msp_hsadc_driver_api) = { - .channel_setup = adc_msp_hsadc_channel_setup, - .read = adc_msp_hsadc_read, -#ifdef CONFIG_ADC_ASYNC - .read_async = adc_msp_hsadc_read_async, -#endif /* CONFIG_ADC_ASYNC */ -}; + adc_context_on_sampling_done(&data->ctx, dev); +} #define ADC_DT_CLOCK_DIVIDER(x) DT_INST_PROP(x, ti_clk_divider) -#define ADC_DT_SAMPLE_WINDOW(x) DT_INST_PROP(x, ti_sample_window) - -/* Helper macro to convert DT VREF source string to enum value */ -#define ADC_MSP_HSADC_VREF_SOURCE(index) \ - COND_CODE_1(DT_INST_NODE_HAS_PROP(index, ti_vref_source), \ - (COND_CODE_1(DT_INST_STRING_TOKEN_EQUAL(index, ti_vref_source, vdda), \ - (ADC_MSP_HSADC_VREF_VDDA), \ - (COND_CODE_1(DT_INST_STRING_TOKEN_EQUAL(index, ti_vref_source, \ - internal_2_5v), \ - (ADC_MSP_HSADC_VREF_INTERNAL_2_5V), \ - (ADC_MSP_HSADC_VREF_INTERNAL_1_4V))))), \ - (ADC_MSP_HSADC_VREF_VDDA)) + +#ifdef CONFIG_ADC_MSP_HSADC_DMA +#define ADC_MSP_HSADC_DMA_INIT(index) \ + .dma_dev = DEVICE_DT_GET_OR_NULL(DT_INST_DMAS_CTLR_BY_IDX(index, 0)), \ + .dma_channel = DT_INST_DMAS_CELL_BY_IDX(index, 0, channel), \ + .dma_trigsrc = DT_INST_DMAS_CELL_BY_IDX(index, 0, trigger), +#else +#define ADC_MSP_HSADC_DMA_INIT(index) +#endif #define MSP_HSADC_ADC_INIT(index) \ \ @@ -615,19 +643,23 @@ static DEVICE_API(adc, msp_hsadc_driver_api) = { .config_base = DT_INST_REG_ADDR_BY_NAME(index, config), \ .result_base = DT_INST_REG_ADDR_BY_NAME(index, result), \ .irq_cfg_func = adc_msp_hsadc_cfg_func_##index, \ - .clockDivider = ADC_DT_CLOCK_DIVIDER(index), \ - .sampleWindow = ADC_DT_SAMPLE_WINDOW(index), \ - .vref_source = ADC_MSP_HSADC_VREF_SOURCE(index), \ - .ref_internal = DT_INST_PROP(index, vref_mv), \ - }; \ + .clock_divider = ADC_DT_CLOCK_DIVIDER(index), \ + .vref_mv = DT_INST_PROP(index, vref_mv), \ + ADC_MSP_HSADC_DMA_INIT(index)}; \ static struct adc_msp_hsadc_data adc_msp_hsadc_data_##index = { \ ADC_CONTEXT_INIT_TIMER(adc_msp_hsadc_data_##index, ctx), \ ADC_CONTEXT_INIT_LOCK(adc_msp_hsadc_data_##index, ctx), \ ADC_CONTEXT_INIT_SYNC(adc_msp_hsadc_data_##index, ctx), \ }; \ + static DEVICE_API(adc, msp_hsadc_driver_api_##index) = { \ + .channel_setup = adc_msp_hsadc_channel_setup, \ + .read = adc_msp_hsadc_read, \ + .ref_internal = DT_INST_PROP(index, vref_mv), \ + IF_ENABLED(CONFIG_ADC_ASYNC, \ + (.read_async = adc_msp_hsadc_read_async,)) }; \ DEVICE_DT_INST_DEFINE(index, &adc_msp_hsadc_init, NULL, &adc_msp_hsadc_data_##index, \ &adc_msp_hsadc_cfg_##index, POST_KERNEL, CONFIG_ADC_INIT_PRIORITY, \ - &msp_hsadc_driver_api); \ + &msp_hsadc_driver_api_##index); \ \ static void adc_msp_hsadc_cfg_func_##index(void) \ { \ diff --git a/drivers/dma/dma_ti_msp.c b/drivers/dma/dma_ti_msp.c index 7fd6e56eac69b..4900f7750d665 100644 --- a/drivers/dma/dma_ti_msp.c +++ b/drivers/dma/dma_ti_msp.c @@ -39,6 +39,7 @@ struct dma_ti_msp_channel_data { void *user_data; uint8_t direction; bool busy; + bool external_trigger; }; struct dma_ti_msp_data { @@ -193,10 +194,29 @@ static int dma_ti_msp_configure(const struct device *dev, uint32_t channel, chan_data->direction = config->channel_direction; chan_data->dma_callback = config->dma_callback; chan_data->user_data = config->user_data; - dma_cfg.transferMode = DL_DMA_SINGLE_BLOCK_TRANSFER_MODE, - dma_cfg.extendedMode = DL_DMA_NORMAL_MODE, - dma_cfg.triggerType = DL_DMA_TRIGGER_TYPE_EXTERNAL; + dma_cfg.extendedMode = DL_DMA_NORMAL_MODE; dma_cfg.trigger = config->dma_slot; + /* All triggers (software and peripheral hardware) are routed + * through the external trigger mux. INTERNAL type is only for + * inter-channel DMA chaining. + */ + dma_cfg.triggerType = DL_DMA_TRIGGER_TYPE_EXTERNAL; + + switch (config->dma_slot) { + case 0: + /* Software trigger (DMAREQ): single block transfer */ + dma_cfg.transferMode = DL_DMA_SINGLE_BLOCK_TRANSFER_MODE; + chan_data->external_trigger = false; + break; + default: + /* Peripheral hardware trigger: use repeat-single mode where + * each trigger transfers one element. Channel stays armed + * until all transfers complete. + */ + dma_cfg.transferMode = DL_DMA_FULL_CH_REPEAT_SINGLE_TRANSFER_MODE; + chan_data->external_trigger = true; + break; + } /* Configure burst size based on source_burst_length */ DL_DMA_BURST_SIZE burstSize; @@ -218,12 +238,12 @@ static int dma_ti_msp_configure(const struct device *dev, uint32_t channel, key = irq_lock(); DL_DMA_setBurstSize(cfg->regs, burstSize); - DL_DMA_clearInterruptStatus(cfg->regs, (channel + DMA_TI_MSP_BASE_CHANNEL_NUM)); + DL_DMA_clearInterruptStatus(cfg->regs, BIT(channel)); DL_DMA_setTransferSize(cfg->regs, channel, blk_cfg->block_size); DL_DMA_initChannel(cfg->regs, channel, &dma_cfg); DL_DMA_setSrcAddr(cfg->regs, channel, blk_cfg->source_address); DL_DMA_setDestAddr(cfg->regs, channel, blk_cfg->dest_address); - DL_DMA_enableInterrupt(cfg->regs, (channel + DMA_TI_MSP_BASE_CHANNEL_NUM)); + DL_DMA_enableInterrupt(cfg->regs, BIT(channel)); chan_data->busy = true; irq_unlock(key); @@ -242,16 +262,19 @@ static int dma_ti_msp_start(const struct device *dev, const uint32_t channel) return -EINVAL; } - /* Software trigger to start the DMA transfer */ DL_DMA_enableChannel(cfg->regs, channel); - /* Verify channel was enabled successfully */ if (!DL_DMA_isChannelEnabled(cfg->regs, channel)) { LOG_ERR("Failed to enable DMA channel %u", channel); return -EINVAL; } - DL_DMA_startTransfer(cfg->regs, channel); + /* Only issue software trigger for non-external channels. + * External trigger channels wait for the peripheral hardware pulse. + */ + if (!dma_data->ch_data[channel].external_trigger) { + DL_DMA_startTransfer(cfg->regs, channel); + } return 0; } @@ -346,7 +369,7 @@ static inline void dma_ti_msp_isr(const struct device *dev) DL_DMA_disableChannel(cfg->regs, channel); /* Clear interrupt status */ - DL_DMA_clearInterruptStatus(cfg->regs, channel + DMA_TI_MSP_BASE_CHANNEL_NUM); + DL_DMA_clearInterruptStatus(cfg->regs, BIT(channel)); /* Mark channel as not busy before callback, as callback might want to reuse it */ chan_data->busy = false; diff --git a/dts/arm/ti/mspm33/mspm33c321a.dtsi b/dts/arm/ti/mspm33/mspm33c321a.dtsi index 7b9633e72ef36..fb15d8e03d065 100644 --- a/dts/arm/ti/mspm33/mspm33c321a.dtsi +++ b/dts/arm/ti/mspm33/mspm33c321a.dtsi @@ -173,6 +173,7 @@ reg-names = "config", "result"; interrupts = <9 0>; ti,clk-divider = <2>; + dmas = <&dma0 0 5>; status = "disabled"; #io-channel-cells = <1>; }; @@ -184,6 +185,7 @@ reg-names = "config", "result"; interrupts = <14 0>; ti,clk-divider = <2>; + dmas = <&dma0 1 9>; status = "disabled"; #io-channel-cells = <1>; }; diff --git a/dts/bindings/adc/ti,msp-hsadc.yaml b/dts/bindings/adc/ti,msp-hsadc.yaml index fd00ce9a743b9..d544e56d7610a 100644 --- a/dts/bindings/adc/ti,msp-hsadc.yaml +++ b/dts/bindings/adc/ti,msp-hsadc.yaml @@ -2,7 +2,7 @@ description: TI MSP HSADC compatible: "ti,msp-hsadc" -include: [adc-controller.yaml, pinctrl-device.yaml] +include: [adc-controller.yaml] properties: reg: @@ -22,12 +22,6 @@ properties: interrupts: required: true - pinctrl-0: - required: false - - pinctrl-names: - required: false - "#io-channel-cells": const: 1 @@ -55,28 +49,25 @@ properties: ADC clock divider: Applies to the internal ADC clock. - ti,sample-window: - type: int - default: 64 - description: Sample window in ADC clock cycles - vref-mv: type: int default: 3300 - description: Voltage reference in mV + description: | + Reference voltage in millivolts. This property controls both the hardware + VREF source and the value used for raw-to-voltage conversion: - ti,vref-source: - type: string - default: "vdda" - enum: - - "vdda" # Use VDDA as reference voltage (default) - - "internal-2.5v" # Use internal 2.5V reference - - "internal-1.4v" # Use internal 1.4V reference + - 2500: Enables internal 2.5V reference (VREF peripheral) + - 1400: Enables internal 1.4V reference (VREF peripheral) + - Any other value: Uses VDDA as reference (set to board VDD voltage) + + This property is used by adc_ref_internal() API and raw-to-millivolts + conversion functions. + + dmas: description: | - Selects the voltage reference source for the ADC. - - vdda: Use VDDA (3.3V) as reference - - internal-2.5v: Use internal 2.5V reference - - internal-1.4v: Use internal 1.4V reference + DMA specifier for ADC result transfer. References a DMA controller + with channel and trigger cells. + Example: dmas = <&dma0 0 5>; /* DMA0 ch0, ADC0 DMA trigger 1 */ io-channel-cells: diff --git a/samples/drivers/adc/adc_dt/boards/lp_mspm33c321a.conf b/samples/drivers/adc/adc_dt/boards/lp_mspm33c321a.conf new file mode 100644 index 0000000000000..75e62e63b4644 --- /dev/null +++ b/samples/drivers/adc/adc_dt/boards/lp_mspm33c321a.conf @@ -0,0 +1 @@ +CONFIG_ADC_MSP_HSADC_DMA=y diff --git a/samples/drivers/adc/adc_dt/boards/lp_mspm33c321a.overlay b/samples/drivers/adc/adc_dt/boards/lp_mspm33c321a.overlay new file mode 100644 index 0000000000000..c0c76a45a8535 --- /dev/null +++ b/samples/drivers/adc/adc_dt/boards/lp_mspm33c321a.overlay @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2026 Texas Instruments + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/ { + zephyr,user { + io-channels = <&adc0 0>, <&adc0 5>; + }; +}; + +&dma0 { + status = "okay"; +}; + +&adc0 { + status = "okay"; + ti,clk-divider = <2>; + vref-mv = <1400>; /* Internal 1.4V reference */ + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; + + channel@5 { + reg = <5>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; +}; diff --git a/samples/drivers/adc/adc_sequence/boards/lp_mspm33c321a.overlay b/samples/drivers/adc/adc_sequence/boards/lp_mspm33c321a.overlay index 98b27a1455e47..64cfffef308f3 100644 --- a/samples/drivers/adc/adc_sequence/boards/lp_mspm33c321a.overlay +++ b/samples/drivers/adc/adc_sequence/boards/lp_mspm33c321a.overlay @@ -1,4 +1,6 @@ /* + * Copyright (c) 2026 Texas Instruments + * * SPDX-License-Identifier: Apache-2.0 */ @@ -11,19 +13,25 @@ }; &adc0 { + status = "okay"; ti,clk-divider = <2>; - vref-mv = <3300>; - pinctrl-0 = <&analog_pb24>; - pinctrl-names = "default"; + vref-mv = <1400>; /* Internal 1.4V reference (auto-detected from value) */ #address-cells = <1>; #size-cells = <0>; + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; + channel@5 { reg = <5>; zephyr,gain = "ADC_GAIN_1"; - zephyr,reference = "ADC_REF_VDD_1"; + zephyr,reference = "ADC_REF_INTERNAL"; zephyr,acquisition-time = ; zephyr,resolution = <12>; - zephyr,oversampling = <3>; }; }; diff --git a/tests/drivers/adc/adc_api/boards/lp_mspm33c321a.overlay b/tests/drivers/adc/adc_api/boards/lp_mspm33c321a.overlay new file mode 100644 index 0000000000000..8bbb26199a212 --- /dev/null +++ b/tests/drivers/adc/adc_api/boards/lp_mspm33c321a.overlay @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2026 Texas Instruments + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/ { + zephyr,user { + io-channels = <&adc0 0>, <&adc0 5>; + }; +}; + +&adc0 { + ti,clk-divider = <2>; + vref-mv = <3300>; /* VDDA reference */ + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_VDD_1"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; + + channel@5 { + reg = <5>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_VDD_1"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; +}; diff --git a/tests/drivers/adc/adc_error_cases/boards/lp_mspm33c321a.overlay b/tests/drivers/adc/adc_error_cases/boards/lp_mspm33c321a.overlay new file mode 100644 index 0000000000000..52e0e890f0d07 --- /dev/null +++ b/tests/drivers/adc/adc_error_cases/boards/lp_mspm33c321a.overlay @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2026 Texas Instruments + * + * SPDX-License-Identifier: Apache-2.0 + */ + + +#include + +/ { + zephyr,user { + io-channels = <&adc0 0>; + }; + + + aliases { + adc = &adc0; + }; +}; + + +&adc0 { + status = "okay"; + ti,clk-divider = <2>; + vref-mv = <3300>; /* VDDA */ + #address-cells = <1>; + #size-cells = <0>; + + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_VDD_1"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; +};