diff --git a/boards/ti/lp_mspm0g3507/lp_mspm0g3507.dts b/boards/ti/lp_mspm0g3507/lp_mspm0g3507.dts index d253d0d4d950..52c0b0a7f6f7 100644 --- a/boards/ti/lp_mspm0g3507/lp_mspm0g3507.dts +++ b/boards/ti/lp_mspm0g3507/lp_mspm0g3507.dts @@ -48,6 +48,12 @@ pwms = <&pwma0 0 PWM_MSEC(100) PWM_POLARITY_NORMAL>; }; }; + + zephyr,user { + dac = <&dac0>; + dac-channel-id = <0>; + dac-resolution = <12>; + }; }; &cpu0 { @@ -68,6 +74,10 @@ status = "okay"; }; +&mfpclk { + status = "okay"; +}; + &canclk { clocks = <&syspll>; status = "okay"; @@ -134,6 +144,12 @@ pinctrl-names = "default"; }; +&dac0 { + status = "okay"; + fifo-enable; + sample-rate-hz = <1000>; +}; + &vref0 { status = "okay"; pinctrl-0 = <&analog_pa23>; diff --git a/boards/ti/lp_mspm0g3507/lp_mspm0g3507.yaml b/boards/ti/lp_mspm0g3507/lp_mspm0g3507.yaml index e60175a52833..a3c222caf92b 100644 --- a/boards/ti/lp_mspm0g3507/lp_mspm0g3507.yaml +++ b/boards/ti/lp_mspm0g3507/lp_mspm0g3507.yaml @@ -13,6 +13,7 @@ supported: - uart - gpio - can + - dac - vref - dma - trng diff --git a/boards/ti/lp_mspm0g3519/lp_mspm0g3519.dts b/boards/ti/lp_mspm0g3519/lp_mspm0g3519.dts index 8068b09acfa8..48e4cf2c890f 100644 --- a/boards/ti/lp_mspm0g3519/lp_mspm0g3519.dts +++ b/boards/ti/lp_mspm0g3519/lp_mspm0g3519.dts @@ -63,6 +63,12 @@ zephyr,code = ; }; }; + + zephyr,user { + dac = <&dac0>; + dac-channel-id = <0>; + dac-resolution = <12>; + }; }; &cpu0 { @@ -83,6 +89,10 @@ status = "okay"; }; +&mfpclk { + status = "okay"; +}; + &canclk { clocks = <&syspll>; status = "okay"; @@ -124,6 +134,12 @@ pinctrl-names = "default"; }; +&dac0 { + status = "okay"; + fifo-enable; + sample-rate-hz = <1000>; +}; + &vref0 { status = "okay"; pinctrl-0 = <&analog_pa23>; diff --git a/boards/ti/lp_mspm0g3519/lp_mspm0g3519.yaml b/boards/ti/lp_mspm0g3519/lp_mspm0g3519.yaml index cf8ab53c372f..593074095191 100644 --- a/boards/ti/lp_mspm0g3519/lp_mspm0g3519.yaml +++ b/boards/ti/lp_mspm0g3519/lp_mspm0g3519.yaml @@ -13,6 +13,7 @@ supported: - uart - gpio - can + - dac - vref - dma - trng diff --git a/drivers/dac/CMakeLists.txt b/drivers/dac/CMakeLists.txt index 19e10ecd8fe8..7b588bf6ca7f 100644 --- a/drivers/dac/CMakeLists.txt +++ b/drivers/dac/CMakeLists.txt @@ -30,6 +30,7 @@ zephyr_library_sources_ifdef(CONFIG_DAC_MCUX_DAC12 dac_mcux_dac12.c) zephyr_library_sources_ifdef(CONFIG_DAC_MCUX_DAC32 dac_mcux_dac32.c) zephyr_library_sources_ifdef(CONFIG_DAC_MCUX_GAU dac_mcux_gau.c) zephyr_library_sources_ifdef(CONFIG_DAC_MCUX_LPDAC dac_mcux_lpdac.c) +zephyr_library_sources_ifdef(CONFIG_DAC_MSPM0 dac_mspm0.c) zephyr_library_sources_ifdef(CONFIG_DAC_NXP_HPDAC dac_nxp_hpdac.c) zephyr_library_sources_ifdef(CONFIG_DAC_RENESAS_RA dac_renesas_ra.c) zephyr_library_sources_ifdef(CONFIG_DAC_SAM dac_sam.c) diff --git a/drivers/dac/Kconfig b/drivers/dac/Kconfig index 0fb0934215bc..5a1245a8c466 100644 --- a/drivers/dac/Kconfig +++ b/drivers/dac/Kconfig @@ -48,6 +48,7 @@ source "drivers/dac/Kconfig.mchp" source "drivers/dac/Kconfig.mcp4725" source "drivers/dac/Kconfig.mcp4728" source "drivers/dac/Kconfig.mcux" +source "drivers/dac/Kconfig.mspm0" source "drivers/dac/Kconfig.renesas_ra" source "drivers/dac/Kconfig.sam" source "drivers/dac/Kconfig.sam0" diff --git a/drivers/dac/Kconfig.mspm0 b/drivers/dac/Kconfig.mspm0 new file mode 100644 index 000000000000..8365a1bceb4f --- /dev/null +++ b/drivers/dac/Kconfig.mspm0 @@ -0,0 +1,13 @@ +# TI MSPM0 DAC Driver Configuration + +# Copyright (c) 2026 Linumiz +# Copyright (c) 2026 Texas Instruments Incorporated +# SPDX-License-Identifier: Apache-2.0 + +config DAC_MSPM0 + bool "TI MSPM0 Digital To Analog Converter (DAC) Driver" + default y + depends on DT_HAS_TI_MSPM0_DAC_ENABLED + help + Enable support for the DAC (Digital-to-Analog Converter) + peripheral driver for Texas Instruments MSPM0 G-Series MCUs. diff --git a/drivers/dac/dac_mspm0.c b/drivers/dac/dac_mspm0.c new file mode 100644 index 000000000000..1d6f06698e82 --- /dev/null +++ b/drivers/dac/dac_mspm0.c @@ -0,0 +1,443 @@ +/* + * Copyright (c) 2026 Linumiz + * Copyright (c) 2026 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT ti_mspm0_dac + +#include +#include +#include +#include +#include +#include + +struct dac12_gen_event_regs { + volatile uint32_t iidx; /* !< (@ 0x00001050) Interrupt index */ + uint32_t reserved0; /* !< Reserved*/ + volatile uint32_t imask; /* !< (@ 0x00001058) Interrupt mask */ + uint32_t reserved1; /* !< Reserved*/ + volatile uint32_t ris; /* !< (@ 0x00001060) Raw interrupt status */ + uint32_t reserved2; /* !< Reserved*/ + volatile uint32_t mis; /* !< (@ 0x00001068) Masked interrupt status */ + uint32_t reserved3; /* !< Reserved*/ + volatile uint32_t iset; /* !< (@ 0x00001070) Interrupt set */ + uint32_t reserved4; /* !< Reserved*/ + volatile uint32_t iclr; /* !< (@ 0x00001078) Interrupt clear */ +}; + +struct dac12_cpu_init_regs { + volatile uint32_t iidx; /* !< (@ 0x00001020) Interrupt index */ + uint32_t reserved0; /* !< Reserved*/ + volatile uint32_t imask; /* !< (@ 0x00001028) Interrupt mask */ + uint32_t reserved1; /* !< Reserved*/ + volatile uint32_t ris; /* !< (@ 0x00001030) Raw interrupt status */ + uint32_t reserved2; /* !< Reserved*/ + volatile uint32_t mis; /* !< (@ 0x00001038) Masked interrupt status */ + uint32_t reserved3; /* !< Reserved*/ + volatile uint32_t iset; /* !< (@ 0x00001040) Interrupt set */ + uint32_t reserved4; /* !< Reserved*/ + volatile uint32_t iclr; /* !< (@ 0x00001048) Interrupt clear */ +}; + +struct dac12_gprcm_regs { + volatile uint32_t pwren; /* !< (@ 0x00000800) Power enable */ + volatile uint32_t rstctl; /* !< (@ 0x00000804) Reset Control */ + uint32_t reserved0[3]; /* !< Reserved*/ + volatile uint32_t stat; /* !< (@ 0x00000814) Status Register */ +}; + +struct dac12_regs { + uint32_t reserved0[256]; + volatile uint32_t fsub_0; /* !< (@ 0x00000400) Subscriber Port 0 */ + uint32_t reserved1[16]; /* !< Reserved*/ + volatile uint32_t fpub_1; /* !< (@ 0x00000444) Publisher port 1 */ + uint32_t reserved2[238]; /* !< Reserved*/ + struct dac12_gprcm_regs gprcm; /* !< (@ 0x00000800) */ + uint32_t reserved3[514]; /* !< Reserved*/ + struct dac12_cpu_init_regs cpu_int; /* !< (@ 0x00001020) */ + uint32_t reserved4; /* !< Reserved*/ + struct dac12_gen_event_regs gen_event; /* !< (@ 0x00001050) */ + uint32_t reserved5[25]; /* !< Reserved*/ + volatile uint32_t evt_mode; /* !< (@ 0x000010E0) Event Mode */ + uint32_t reserved6[6]; /* !< Reserved*/ + volatile uint32_t desc; /* !< (@ 0x000010FC) Module Description */ + volatile uint32_t ctl0; /* !< (@ 0x00001100) Control 0 */ + uint32_t reserved7[3]; /* !< Reserved*/ + volatile uint32_t ctl1; /* !< (@ 0x00001110) Control 1 */ + uint32_t reserved8[3]; /* !< Reserved*/ + volatile uint32_t ctl2; /* !< (@ 0x00001120) Control 2 */ + uint32_t reserved9[3]; /* !< Reserved*/ + volatile uint32_t ctl3; /* !< (@ 0x00001130) Control 3 */ + uint32_t reserved10[3]; /* !< Reserved*/ + volatile uint32_t calctl; /* !< (@ 0x00001140) Calibration control */ + uint32_t reserved11[7]; /* !< Reserved*/ + volatile uint32_t caldata; /* !< (@ 0x00001160) Calibration data */ + uint32_t reserved12[39]; /* !< Reserved*/ + volatile uint32_t data0; /* !< (@ 0x00001200) Data 0 */ +}; + +/* + * Compile-time checks + */ +BUILD_ASSERT(offsetof(struct dac12_regs, gprcm) == 0x0800U); +BUILD_ASSERT(offsetof(struct dac12_regs, cpu_int) == 0x1020U); +BUILD_ASSERT(offsetof(struct dac12_regs, gen_event) == 0x1050U); +BUILD_ASSERT(offsetof(struct dac12_regs, ctl0) == 0x1100U); +BUILD_ASSERT(offsetof(struct dac12_regs, ctl1) == 0x1110U); +BUILD_ASSERT(offsetof(struct dac12_regs, calctl) == 0x1140U); +BUILD_ASSERT(offsetof(struct dac12_regs, data0) == 0x1200U); + +/* + * Bit-field constants + */ + +#ifndef CONFIG_HAS_MSPM0_SDK +/* DAC12_RSTCTL Bits */ +#define DAC12_RSTCTL_RESETSTKYCLR_CLR BIT(1) /* !< Clear reset sticky bit */ +#define DAC12_RSTCTL_RESETASSERT_ASSERT BIT(0) /* !< Assert reset */ +#define DAC12_RSTCTL_KEY_UNLOCK_W 0xB1000000U /* !< KEY to allow write access */ + +/* GPRCM.PWREN — writing the unlock key + enable bit powers the peripheral on */ +#define DAC12_PWREN_KEY_UNLOCK_W 0x26000000U +#define DAC12_PWREN_ENABLE_ENABLE BIT(0) + +/* CTL0 — main DAC enable/disable, resolution, and data format */ +#define DAC12_CTL0_ENABLE_SET BIT(0) /* bit 0: DAC on */ +#define DAC12_CTL0_ENABLE_MASK BIT(0) +#define DAC12_CTL0_RES_MASK BIT(8) /* bit 8: resolution select */ +#define DAC12_CTL0_RES__8BITS FIELD_PREP(DAC12_CTL0_RES_MASK, 0) +#define DAC12_CTL0_RES__12BITS FIELD_PREP(DAC12_CTL0_RES_MASK, 1) +#define DAC12_CTL0_DFM_MASK BIT(16) /* bit 16: data format (binary vs 2s-comp) */ +#define DAC12_CTL0_DFM_BINARY FIELD_PREP(DAC12_CTL0_DFM_MASK, 0) + +/* CTL1 — output amplifier, voltage reference, and output pin routing */ +#define DAC12_CTL1_AMPEN_MASK BIT(0) +#define DAC12_CTL1_AMPEN_ENABLE FIELD_PREP(DAC12_CTL1_AMPEN_MASK, 1) /* bit 0: amp enable */ +#define DAC12_CTL1_AMPHIZ_MASK BIT(1) /* bit 1: amp-off output state */ +#define DAC12_CTL1_AMPHIZ_PULLDOWN \ + FIELD_PREP(DAC12_CTL1_AMPHIZ_MASK, 1) /* pull DAC_OUT to 0 V when amp is off */ +#define DAC12_CTL1_REFSP_MASK BIT(8) /* bit 8: positive reference select */ +#define DAC12_CTL1_REFSP_VDDA FIELD_PREP(DAC12_CTL1_REFSP_MASK, 0) /* use VDDA as VR+ */ +#define DAC12_CTL1_REFSP_VEREFP FIELD_PREP(DAC12_CTL1_REFSP_MASK, 1) /* use VEREFP pin as VR+ */ +#define DAC12_CTL1_REFSN_MASK BIT(9) /* bit 9: negative reference select */ +#define DAC12_CTL1_REFSN_VEREFN FIELD_PREP(DAC12_CTL1_REFSN_MASK, 0) /* use VEREFN pin as VR- */ +#define DAC12_CTL1_REFSN_VSSA FIELD_PREP(DAC12_CTL1_REFSP_MASK, 1) /* use VSSA as VR- */ +#define DAC12_CTL1_OPS_MASK BIT(24) /* bit 24: output pin select */ +#define DAC12_CTL1_OPS_OUT0 FIELD_PREP(DAC12_CTL1_OPS_MASK, 1) /* route output to DAC_OUT pin */ + +/* CTL2 — FIFO control */ +#define DAC12_CTL2_FIFOEN_SET BIT(0) /* FIFO enabled */ +#define DAC12_CTL2_FIFOTRIGSEL_STIM 0x0 /* sample time generator trigger */ + +/* CTL3 — sample time generator */ +#define DAC12_CTL3_STIMEN_SET BIT(0) /* sample time generator enabled */ +#define DAC12_CTL3_STIMCONFIG__500SPS 0x0 +#define DAC12_CTL3_STIMCONFIG__1KSPS BIT(8) +#define DAC12_CTL3_STIMCONFIG__2KSPS BIT(9) +#define DAC12_CTL3_STIMCONFIG__4KSPS 0x300 +#define DAC12_CTL3_STIMCONFIG__8KSPS BIT(10) +#define DAC12_CTL3_STIMCONFIG__16KSPS 0x500 +#define DAC12_CTL3_STIMCONFIG__100KSPS 0x600 +#define DAC12_CTL3_STIMCONFIG__200KSPS 0x700 +#define DAC12_CTL3_STIMCONFIG__500KSPS BIT(11) +#define DAC12_CTL3_STIMCONFIG__1MSPS 0x900 + +/* CPU_INT — FIFO interrupt mask and clear */ +; +#define DAC12_CPU_INT_IMASK_FIFOEMPTYIFG_SET BIT(12) +#define DAC12_CPU_INT_MIS_FIFOEMPTYIFG_SET BIT(12) +#define DAC12_CPU_INT_ICLR_FIFOEMPTYIFG_CLR BIT(12) +#define DAC12_CPU_INT_IMASK_FIFOFULLIFG_SET BIT(8) +#define DAC12_CPU_INT_MIS_FIFOFULLIFG_SET BIT(8) +#define DAC12_CPU_INT_ICLR_FIFOFULLIFG_CLR BIT(8) +#define DAC12_CPU_INT_RIS_FIFOFULLIFG_MASK BIT(8) + +/* CALCTL — self-calibration trigger and trim source select */ +#define DAC12_CALCTL_CALON_MASK BIT(0) /* bit 0: calibration state bit */ +#define DAC12_CALCTL_CALON_ACTIVE \ + FIELD_PREP(DAC12_CALCTL_CALON_MASK, 1) /* bit 0: calibration running */ +#define DAC12_CALCTL_CALSEL_SELFCALIBRATIONTRIM BIT(1) /* bit 1: use self-cal trim */ + +/* GEN_EVENT.RIS — raw interrupt status flags */ +#define DAC12_GEN_EVENT_RIS_MODRDYIFG_SET BIT(1) /* bit 1: DAC core is ready */ + +/* DATA0 — the value written here appears on the DAC output */ +#define DAC12_DATA0_DATA_VALUE_MASK GENMASK(11, 0) /* bits [11:0]: 12-bit data field */ +#endif + +#define DAC_RESOLUTION_8BIT 8 +#define DAC_RESOLUTION_12BIT 12 + +#define DAC8_MAX_VALUE 255 +#define DAC12_MAX_VALUE 4095 + +#define DAC_PRIMARY_CHANNEL_ID 0 +#define DAC_READY_TIMEOUT_US 1000 + +#define DAC_FIFO_DEPTH 4 + +#define DAC12_VREF_SOURCE_VEREFP_VEREFN (DAC12_CTL1_REFSP_VEREFP | DAC12_CTL1_REFSN_VEREFN) +#define DAC12_VREF_SOURCE_VDDA_VSSA (DAC12_CTL1_REFSP_VDDA | DAC12_CTL1_REFSN_VSSA) + +/* Startup delay in cycles */ +#define POWER_STARTUP_DELAY 16 + +struct dac_mspm0_config { + DEVICE_MMIO_ROM; + uint32_t vref_ctl1_bits; + bool fifo_enabled; + uint32_t sample_rate_hz; +}; + +struct dac_mspm0_data { + DEVICE_MMIO_RAM; + struct k_mutex lock; + struct k_sem fifo_sem; /* flow-control gate */ + uint8_t resolution; +}; + +static inline struct dac12_regs *dac_mspm0_regs(const struct device *dev) +{ + return (struct dac12_regs *)DEVICE_MMIO_GET(dev); +} + +static void dac_mspm0_isr(const struct device *dev) +{ + struct dac_mspm0_data *data = dev->data; + struct dac12_regs *regs = dac_mspm0_regs(dev); + uint32_t mis = regs->cpu_int.mis; + + if (mis & DAC12_CPU_INT_MIS_FIFOEMPTYIFG_SET) { + regs->cpu_int.imask &= ~(DAC12_CPU_INT_IMASK_FIFOEMPTYIFG_SET); + /* Release the gate — unblocks the thread waiting in write_value */ + k_sem_give(&data->fifo_sem); + } +} + +static int dac_mspm0_channel_setup(const struct device *dev, + const struct dac_channel_cfg *channel_cfg) +{ + const struct dac_mspm0_config *config = dev->config; + struct dac_mspm0_data *data = dev->data; + struct dac12_regs *regs = dac_mspm0_regs(dev); + + if (channel_cfg->channel_id != DAC_PRIMARY_CHANNEL_ID) { + return -EINVAL; + } + + if (channel_cfg->resolution != DAC_RESOLUTION_8BIT && + channel_cfg->resolution != DAC_RESOLUTION_12BIT) { + return -ENOTSUP; + } + + k_mutex_lock(&data->lock, K_FOREVER); + + /* disable DAC before reconfiguring */ + regs->ctl0 &= ~DAC12_CTL0_ENABLE_MASK; + + /* set data format (binary) and resolution in ctl0 */ + uint32_t res_bits = (channel_cfg->resolution == DAC_RESOLUTION_12BIT) + ? DAC12_CTL0_RES__12BITS + : DAC12_CTL0_RES__8BITS; + + regs->ctl0 = + (regs->ctl0 & ~(DAC12_CTL0_DFM_MASK | DAC12_CTL0_RES_MASK)) | + ((DAC12_CTL0_DFM_BINARY | res_bits) & (DAC12_CTL0_DFM_MASK | DAC12_CTL0_RES_MASK)); + + /* configure amplifier, voltage reference, and output routing in ctl1 */ + uint32_t amp_bits = + channel_cfg->buffered ? DAC12_CTL1_AMPEN_ENABLE : DAC12_CTL1_AMPHIZ_PULLDOWN; + uint32_t ops_bits = channel_cfg->internal ? 0U : DAC12_CTL1_OPS_OUT0; + uint32_t ctl1_mask = DAC12_CTL1_AMPEN_MASK | DAC12_CTL1_AMPHIZ_MASK | + DAC12_CTL1_REFSP_MASK | DAC12_CTL1_REFSN_MASK | DAC12_CTL1_OPS_MASK; + + regs->ctl1 = (regs->ctl1 & ~ctl1_mask) | + ((amp_bits | config->vref_ctl1_bits | ops_bits) & ctl1_mask); + + /* re-enable the DAC */ + regs->ctl0 |= DAC12_CTL0_ENABLE_SET; + + /* Wait for the DAC core and amplifier to settle */ + if (!WAIT_FOR(regs->gen_event.ris & DAC12_GEN_EVENT_RIS_MODRDYIFG_SET, DAC_READY_TIMEOUT_US, + k_busy_wait(1))) { + k_mutex_unlock(&data->lock); + return -ETIMEDOUT; + } + + data->resolution = channel_cfg->resolution; + + /* self-calibrate offset error if amplifier is active */ + if (channel_cfg->buffered) { + regs->calctl = DAC12_CALCTL_CALON_ACTIVE | DAC12_CALCTL_CALSEL_SELFCALIBRATIONTRIM; + if (!WAIT_FOR((!(regs->calctl & DAC12_CALCTL_CALON_MASK)) & + DAC12_CALCTL_CALON_ACTIVE, + DAC_READY_TIMEOUT_US, k_busy_wait(1))) { + k_mutex_unlock(&data->lock); + return -ETIMEDOUT; + } + } + + if (config->fifo_enabled) { + uint32_t stimconfig; + + switch (config->sample_rate_hz) { + case 500: + stimconfig = DAC12_CTL3_STIMCONFIG__500SPS; + break; + case 1000: + stimconfig = DAC12_CTL3_STIMCONFIG__1KSPS; + break; + case 2000: + stimconfig = DAC12_CTL3_STIMCONFIG__2KSPS; + break; + case 4000: + stimconfig = DAC12_CTL3_STIMCONFIG__4KSPS; + break; + case 8000: + stimconfig = DAC12_CTL3_STIMCONFIG__8KSPS; + break; + case 16000: + stimconfig = DAC12_CTL3_STIMCONFIG__16KSPS; + break; + case 100000: + stimconfig = DAC12_CTL3_STIMCONFIG__100KSPS; + break; + case 200000: + stimconfig = DAC12_CTL3_STIMCONFIG__200KSPS; + break; + case 500000: + stimconfig = DAC12_CTL3_STIMCONFIG__500KSPS; + break; + case 1000000: + stimconfig = DAC12_CTL3_STIMCONFIG__1MSPS; + break; + default: + k_mutex_unlock(&data->lock); + return -EINVAL; + } + + /* enable sample time generator with selected rate */ + regs->ctl3 = DAC12_CTL3_STIMEN_SET | stimconfig; + + /* enable FIFO and select sample time generator trigger. + * FIFOTH is don't care in CPU mode; FIFO level is reported + * directly through the RIS interrupt flags. + */ + regs->ctl2 = DAC12_CTL2_FIFOEN_SET | DAC12_CTL2_FIFOTRIGSEL_STIM; + + regs->cpu_int.iclr = + DAC12_CPU_INT_ICLR_FIFOEMPTYIFG_CLR | DAC12_CPU_INT_ICLR_FIFOFULLIFG_CLR; + + /* Binary semaphore: count=0, limit=1 */ + k_sem_init(&data->fifo_sem, 0, 1); + } + + k_mutex_unlock(&data->lock); + + return 0; +} + +static int dac_mspm0_write_value(const struct device *dev, uint8_t channel, uint32_t value) +{ + const struct dac_mspm0_config *config = dev->config; + struct dac_mspm0_data *data = dev->data; + struct dac12_regs *regs = dac_mspm0_regs(dev); + int ret = 0; + + /* + * Block here (without holding the mutex) if the FIFO-full ISR has + * closed the gate. The empty ISR will give the semaphore back, + * waking this thread when there is space to write again. + */ + if (config->fifo_enabled) { + if (regs->cpu_int.ris & DAC12_CPU_INT_RIS_FIFOFULLIFG_MASK) { + k_sem_reset(&data->fifo_sem); + regs->cpu_int.iclr |= DAC12_CPU_INT_ICLR_FIFOEMPTYIFG_CLR; + regs->cpu_int.imask |= DAC12_CPU_INT_IMASK_FIFOEMPTYIFG_SET; + k_sem_take(&data->fifo_sem, K_FOREVER); + regs->cpu_int.iclr |= DAC12_CPU_INT_ICLR_FIFOFULLIFG_CLR; + } + } + + k_mutex_lock(&data->lock, K_FOREVER); + + /* Validate channel and resolution */ + if (channel != DAC_PRIMARY_CHANNEL_ID || data->resolution == 0) { + ret = -EINVAL; + goto unlock; + } + + if (data->resolution == DAC_RESOLUTION_12BIT) { + if (value > DAC12_MAX_VALUE) { + ret = -EINVAL; + goto unlock; + } + regs->data0 = value & DAC12_DATA0_DATA_VALUE_MASK; + } else { + if (value > DAC8_MAX_VALUE) { + ret = -EINVAL; + goto unlock; + } + regs->data0 = (uint8_t)value; + } + +unlock: + k_mutex_unlock(&data->lock); + return ret; +} + +static int dac_mspm0_init(const struct device *dev) +{ + DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE); + + struct dac12_regs *regs = dac_mspm0_regs(dev); + + regs->gprcm.rstctl = DAC12_RSTCTL_KEY_UNLOCK_W | DAC12_RSTCTL_RESETSTKYCLR_CLR | + DAC12_RSTCTL_RESETASSERT_ASSERT; + + regs->gprcm.pwren = DAC12_PWREN_KEY_UNLOCK_W | DAC12_PWREN_ENABLE_ENABLE; + + return 0; +} + +static DEVICE_API(dac, dac_mspm0_driver_api) = { + .channel_setup = dac_mspm0_channel_setup, + .write_value = dac_mspm0_write_value +}; + +#define DAC_MSPM0_IRQ_INIT(id) \ + IRQ_CONNECT(DT_INST_IRQN(id), DT_INST_IRQ(id, priority), dac_mspm0_isr, \ + DEVICE_DT_INST_GET(id), 0); \ + irq_enable(DT_INST_IRQN(id)); + +#define DAC_MSPM0_DEFINE(id) \ + \ + static const struct dac_mspm0_config dac_mspm0_config_##id = { \ + DEVICE_MMIO_ROM_INIT(DT_DRV_INST(id)), \ + .vref_ctl1_bits = COND_CODE_1(DT_INST_NODE_HAS_PROP(id, vref), \ + DAC12_VREF_SOURCE_VEREFP_VEREFN, \ + DAC12_VREF_SOURCE_VDDA_VSSA), \ + .fifo_enabled = DT_INST_PROP(id, fifo_enable), \ + .sample_rate_hz = DT_INST_PROP_OR(id, sample_rate_hz, 0), \ + }; \ + \ + static struct dac_mspm0_data dac_mspm0_data_##id = { \ + .lock = Z_MUTEX_INITIALIZER(dac_mspm0_data_##id.lock), \ + }; \ + \ + static int dac_mspm0_init_##id(const struct device *dev) \ + { \ + dac_mspm0_init(dev); \ + IF_ENABLED(DT_INST_PROP(id, fifo_enable), \ + (DAC_MSPM0_IRQ_INIT(id))); \ + return 0; \ + } \ + \ + DEVICE_DT_INST_DEFINE(id, &dac_mspm0_init_##id, NULL, &dac_mspm0_data_##id, \ + &dac_mspm0_config_##id, POST_KERNEL, CONFIG_DAC_INIT_PRIORITY, \ + &dac_mspm0_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(DAC_MSPM0_DEFINE) diff --git a/dts/arm/ti/mspm0/g/mspm0g150x.dtsi b/dts/arm/ti/mspm0/g/mspm0g150x.dtsi index c029604a0615..4f8e6ea0b320 100644 --- a/dts/arm/ti/mspm0/g/mspm0g150x.dtsi +++ b/dts/arm/ti/mspm0/g/mspm0g150x.dtsi @@ -8,6 +8,13 @@ / { soc { + dac0: dac@40018000 { + compatible = "ti,mspm0-dac"; + reg = <0x40018000 0x2000>; + status = "disabled"; + #io-channel-cells = <1>; + }; + comp0: comp@40008000 { compatible = "ti,mspm0-comparator"; reg = <0x40008000 0x2000>; diff --git a/dts/arm/ti/mspm0/g/mspm0g350x.dtsi b/dts/arm/ti/mspm0/g/mspm0g350x.dtsi index da514638e93f..6b57dd0205c1 100644 --- a/dts/arm/ti/mspm0/g/mspm0g350x.dtsi +++ b/dts/arm/ti/mspm0/g/mspm0g350x.dtsi @@ -8,6 +8,14 @@ / { soc { + dac0: dac@40018000 { + compatible = "ti,mspm0-dac"; + reg = <0x40018000 0x2000>; + interrupts = <7 0>; + status = "disabled"; + #io-channel-cells = <1>; + }; + comp0: comp@40008000 { compatible = "ti,mspm0-comparator"; reg = <0x40008000 0x2000>; diff --git a/dts/arm/ti/mspm0/g/mspm0gx51x.dtsi b/dts/arm/ti/mspm0/g/mspm0gx51x.dtsi index bbbde016b354..df8ed8d218da 100644 --- a/dts/arm/ti/mspm0/g/mspm0gx51x.dtsi +++ b/dts/arm/ti/mspm0/g/mspm0gx51x.dtsi @@ -8,6 +8,14 @@ / { soc { + dac0: dac@40018000 { + compatible = "ti,mspm0-dac"; + reg = <0x40018000 0x2000>; + interrupts = <7 0>; + status = "disabled"; + #io-channel-cells = <1>; + }; + comp0: comp@40008000 { compatible = "ti,mspm0-comparator"; reg = <0x40008000 0x2000>; diff --git a/dts/bindings/dac/ti,mspm0-dac.yaml b/dts/bindings/dac/ti,mspm0-dac.yaml new file mode 100644 index 000000000000..475fc0ab7f71 --- /dev/null +++ b/dts/bindings/dac/ti,mspm0-dac.yaml @@ -0,0 +1,35 @@ +# Copyright (c) 2026 Linumiz +# SPDX-License-Identifier: Apache-2.0 + +description: Texas Instruments MSPM0 G-Series Digital-to-Analog Converter (DAC) + +compatible: "ti,mspm0-dac" + +include: dac-controller.yaml + +properties: + reg: + required: true + + vref: + type: phandle + + fifo-enable: + type: boolean + description: | + Enable the 4-slot hardware FIFO. When set, the sample time generator + clocks samples out of the FIFO at sample-rate-hz, decoupling DAC output + timing from CPU write timing. + + sample-rate-hz: + type: int + enum: [500, 1000, 2000, 4000, 8000, 16000, 100000, 200000, 500000, 1000000] + description: | + DAC output sample rate in Hz. Maps directly to the sample time generator + STIMCONFIG preset. Requires fifo-enable. + + "#io-channel-cells": + const: 1 + +io-channel-cells: + - output diff --git a/modules/Kconfig.mspm0 b/modules/Kconfig.mspm0 index cef1043ed1ad..63ec8cdd0d37 100644 --- a/modules/Kconfig.mspm0 +++ b/modules/Kconfig.mspm0 @@ -18,6 +18,9 @@ config USE_MSPM0_DL_TIMER config USE_MSPM0_DL_MCAN bool +config USE_MSPM0_DL_DAC12 + bool + config USE_MSPM0_DL_VREF bool diff --git a/samples/drivers/dac/Kconfig b/samples/drivers/dac/Kconfig index ce025e047cf4..1f81e1763c89 100644 --- a/samples/drivers/dac/Kconfig +++ b/samples/drivers/dac/Kconfig @@ -20,3 +20,12 @@ config DAC_BUFFER_NOT_SUPPORT bool "DAC on board/SoC does not support output buffer mode" help If this config is turned on, the sample will run with no output buffer enabled + +config DAC_SAMPLE_SLEEP_MS + int "Sleep between DAC writes in ms (-1 = auto from resolution, 0 = disabled)" + default -1 + help + Delay inserted between successive dac_write_value() calls. + -1: use the original computed value (4096 / dac_values, min 1 ms). + 0: disable sleep entirely (e.g. when FIFO flow control handles pacing). + >0: fixed delay in milliseconds. diff --git a/samples/drivers/dac/boards/lp_mspm0g3507.conf b/samples/drivers/dac/boards/lp_mspm0g3507.conf new file mode 100644 index 000000000000..08474070b7aa --- /dev/null +++ b/samples/drivers/dac/boards/lp_mspm0g3507.conf @@ -0,0 +1 @@ +CONFIG_DAC_SAMPLE_SLEEP_MS=0 diff --git a/samples/drivers/dac/boards/lp_mspm0g3519.conf b/samples/drivers/dac/boards/lp_mspm0g3519.conf new file mode 100644 index 000000000000..08474070b7aa --- /dev/null +++ b/samples/drivers/dac/boards/lp_mspm0g3519.conf @@ -0,0 +1 @@ +CONFIG_DAC_SAMPLE_SLEEP_MS=0 diff --git a/samples/drivers/dac/src/main.c b/samples/drivers/dac/src/main.c index 71657a193418..6be78448b76d 100644 --- a/samples/drivers/dac/src/main.c +++ b/samples/drivers/dac/src/main.c @@ -55,23 +55,26 @@ int main(void) /* Number of valid DAC values, e.g. 4096 for 12-bit DAC */ const int dac_values = 1U << DAC_RESOLUTION; - /* - * 1 msec sleep leads to about 4 sec signal period for 12-bit - * DACs. For DACs with lower resolution, sleep time needs to - * be increased. - * Make sure to sleep at least 1 msec even for future 16-bit - * DACs (lowering signal frequency). - */ - const int sleep_time = 4096 / dac_values > 0 ? - 4096 / dac_values : 1; - for (int i = 0; i < dac_values; i++) { ret = dac_write_value(dac_dev, DAC_CHANNEL_ID, i); if (ret != 0) { printk("dac_write_value() failed with code %d\n", ret); return 0; } +#if CONFIG_DAC_SAMPLE_SLEEP_MS == -1 + /* + * 1 msec sleep leads to about 4 sec signal period for 12-bit + * DACs. For DACs with lower resolution, sleep time needs to + * be increased. + * Make sure to sleep at least 1 msec even for future 16-bit + * DACs (lowering signal frequency). + */ + const int sleep_time = 4096 / dac_values > 0 ? + 4096 / dac_values : 1; k_sleep(K_MSEC(sleep_time)); +#elif CONFIG_DAC_SAMPLE_SLEEP_MS > 0 + k_sleep(K_MSEC(CONFIG_DAC_SAMPLE_SLEEP_MS)); +#endif } } return 0; diff --git a/tests/drivers/dac/dac_api/dac0-channel0-buffered.overlay b/tests/drivers/dac/dac_api/dac0-channel0-buffered.overlay new file mode 100644 index 000000000000..3d18827df636 --- /dev/null +++ b/tests/drivers/dac/dac_api/dac0-channel0-buffered.overlay @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2026 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + zephyr,user { + io-channels = <&dac0 0>; + io-channel-names = "dac1"; + }; +}; + +&dac0 { + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,resolution = <12>; + zephyr,buffered; + }; +}; diff --git a/tests/drivers/dac/dac_api/testcase.yaml b/tests/drivers/dac/dac_api/testcase.yaml index 4e21f19129fb..f21ccc7163ee 100644 --- a/tests/drivers/dac/dac_api/testcase.yaml +++ b/tests/drivers/dac/dac_api/testcase.yaml @@ -59,6 +59,12 @@ tests: - bl652_dvk - bl653_dvk - bl5340_dvk/nrf5340/cpuapp + drivers.dac.api.dac0_channel0_buffered: + extra_args: + - EXTRA_DTC_OVERLAY_FILE="dac0-channel0-buffered.overlay" + platform_allow: + - lp_mspm0g3507 + - lp_mspm0g3519 drivers.dac.api.dac1_channel1: extra_args: - EXTRA_DTC_OVERLAY_FILE="dac1-channel1.overlay" diff --git a/tests/drivers/dac/dac_loopback/testcase.yaml b/tests/drivers/dac/dac_loopback/testcase.yaml index 64113bc73cf7..182b0f091dbe 100644 --- a/tests/drivers/dac/dac_loopback/testcase.yaml +++ b/tests/drivers/dac/dac_loopback/testcase.yaml @@ -39,6 +39,12 @@ tests: - frdm_k22f - frdm_k64f - twr_ke18f + drivers.dac.loopback.ti_dac0_chan0_adc0_chan4: + extra_args: + - EXTRA_DTC_OVERLAY_FILE="ti-dac0-channel0-adc0-channel4.overlay" + platform_allow: + - lp_mspm0g3507 + - lp_mspm0g3519 drivers.dac.loopback.blxxx_dvk: extra_args: - EXTRA_DTC_OVERLAY_FILE="blxxx_dvk.overlay" diff --git a/tests/drivers/dac/dac_loopback/ti-dac0-channel0-adc0-channel4.overlay b/tests/drivers/dac/dac_loopback/ti-dac0-channel0-adc0-channel4.overlay new file mode 100644 index 000000000000..174eb5fe3efd --- /dev/null +++ b/tests/drivers/dac/dac_loopback/ti-dac0-channel0-adc0-channel4.overlay @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2026 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/ { + zephyr,user { + io-channels = <&dac0 0 &adc0 4>; + io-channel-names = "dac1", "adc1"; + }; +}; + +&dac0 { + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,resolution = <12>; + zephyr,buffered; + }; +}; + +&adc0 { + pinctrl-0 = <&analog_pb25>; + pinctrl-names = "default"; + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + channel@4 { + reg = <4>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_VDD_1"; + zephyr,vref-mv = <3300>; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; +};