diff --git a/boards/atmel/sam0/same54_xpro/same54_xpro-pinctrl.dtsi b/boards/atmel/sam0/same54_xpro/same54_xpro-pinctrl.dtsi index 59f6915821d..ac156f234ee 100644 --- a/boards/atmel/sam0/same54_xpro/same54_xpro-pinctrl.dtsi +++ b/boards/atmel/sam0/same54_xpro/same54_xpro-pinctrl.dtsi @@ -60,4 +60,11 @@ ; }; }; + + dac0_default: dac0_default { + group1 { + pinmux = , + ; + }; + }; }; diff --git a/boards/atmel/sam0/same54_xpro/same54_xpro.dts b/boards/atmel/sam0/same54_xpro/same54_xpro.dts index b6b9ff9d78d..7c70da060be 100644 --- a/boards/atmel/sam0/same54_xpro/same54_xpro.dts +++ b/boards/atmel/sam0/same54_xpro/same54_xpro.dts @@ -122,6 +122,25 @@ status = "okay"; }; +&dac0 { + status = "okay"; + reference = "intref"; + + channel_0 { + oversampling = "OSR_1"; + refresh-period = <1>; + current-control = "CC1M"; + }; + channel_1 { + oversampling = "OSR_1"; + refresh-period = <1>; + current-control = "CC1M"; + }; + + pinctrl-0 = <&dac0_default>; + pinctrl-names = "default"; +}; + zephyr_udc0: &usb0 { status = "okay"; diff --git a/boards/atmel/sam0/same54_xpro/same54_xpro.yaml b/boards/atmel/sam0/same54_xpro/same54_xpro.yaml index 346c7fe0869..d9a73d53e84 100644 --- a/boards/atmel/sam0/same54_xpro/same54_xpro.yaml +++ b/boards/atmel/sam0/same54_xpro/same54_xpro.yaml @@ -12,6 +12,7 @@ flash: 1024 ram: 256 supported: - adc + - dac - dma - flash - gpio diff --git a/drivers/dac/CMakeLists.txt b/drivers/dac/CMakeLists.txt index a190bd0885d..4e69815f4ec 100644 --- a/drivers/dac/CMakeLists.txt +++ b/drivers/dac/CMakeLists.txt @@ -11,6 +11,7 @@ zephyr_library_sources_ifdef(CONFIG_DAC_MCUX_DAC32 dac_mcux_dac32.c) zephyr_library_sources_ifdef(CONFIG_DAC_STM32 dac_stm32.c) zephyr_library_sources_ifdef(CONFIG_DAC_SAM dac_sam.c) zephyr_library_sources_ifdef(CONFIG_DAC_SAM0 dac_sam0.c) +zephyr_library_sources_ifdef(CONFIG_DAC_SAMD5X dac_samd5x.c) zephyr_library_sources_ifdef(CONFIG_DAC_DAC161S997 dac_dac161s997.c) zephyr_library_sources_ifdef(CONFIG_DAC_DACX0501 dac_dacx0501.c) zephyr_library_sources_ifdef(CONFIG_DAC_DACX0508 dac_dacx0508.c) diff --git a/drivers/dac/Kconfig b/drivers/dac/Kconfig index 21c2f174c9c..83051899871 100644 --- a/drivers/dac/Kconfig +++ b/drivers/dac/Kconfig @@ -67,4 +67,6 @@ source "drivers/dac/Kconfig.max22017" source "drivers/dac/Kconfig.renesas_ra" +source "drivers/dac/Kconfig.samd5x" + endif # DAC diff --git a/drivers/dac/Kconfig.samd5x b/drivers/dac/Kconfig.samd5x new file mode 100644 index 00000000000..0ae4ca92535 --- /dev/null +++ b/drivers/dac/Kconfig.samd5x @@ -0,0 +1,10 @@ +# Copyright(c) 2025 Daikin Comfort Technologies North America, Inc. +# SPDX-License-Identifier: Apache-2.0 + +config DAC_SAMD5X + bool "Atmel SAMD5x series DAC Driver" + default y + depends on DT_HAS_ATMEL_SAMD5X_DAC_ENABLED + select PINCTRL + help + Enables the Atmel SAMD5x MCU Family Digital-to-Analog Converter(DAC) driver. diff --git a/drivers/dac/dac_samd5x.c b/drivers/dac/dac_samd5x.c new file mode 100644 index 00000000000..9e5e338d7d0 --- /dev/null +++ b/drivers/dac/dac_samd5x.c @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2025 Daikin Comfort Technologies North America, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT atmel_samd5x_dac + +#include + +#include +#include +#include +#include +LOG_MODULE_REGISTER(dac_samd5x, CONFIG_DAC_LOG_LEVEL); + +#define DAC_CHANNEL_NO 2 +#define DAC_RESOLUTION 12 + +#define SAMD5X_DAC_REFSEL_0 DAC_CTRLB_REFSEL_VREFPU +#define SAMD5X_DAC_REFSEL_1 DAC_CTRLB_REFSEL_VDDANA +#define SAMD5X_DAC_REFSEL_2 DAC_CTRLB_REFSEL_VREFPB +#define SAMD5X_DAC_REFSEL_3 DAC_CTRLB_REFSEL_INTREF + +struct dac_samd5x_channel_cfg { + uint8_t oversampling; /* Oversampling ratio */ + uint8_t refresh_period; /* Refresh period */ + bool run_in_standby; /* Run in standby mode */ + uint8_t current_control; /* Current control */ +}; + +struct dac_samd5x_cfg { + Dac *regs; + const struct pinctrl_dev_config *pcfg; + volatile uint32_t *mclk; + uint32_t mclk_mask; + uint32_t gclk_gen; + uint16_t gclk_id; + uint8_t refsel; + bool diff_mode; + struct dac_samd5x_channel_cfg channel_cfg[DAC_CHANNEL_NO]; +}; + +struct dac_samd5x_data { + uint8_t resolution[DAC_CHANNEL_NO]; +}; + +/* Write to the DAC. */ +static int dac_samd5x_write_value(const struct device *dev, uint8_t channel, uint32_t value) +{ + const struct dac_samd5x_cfg *const cfg = dev->config; + struct dac_samd5x_data *data = dev->data; + Dac *regs = cfg->regs; + + if (channel >= DAC_CHANNEL_NO) { + return -EINVAL; + } + + if (data->resolution[channel] > 12) { + if (value >= BIT(16)) { + LOG_ERR("value %d out of range", value); + return -EINVAL; + } + } else { + if (value >= BIT(12)) { + LOG_ERR("value %d out of range", value); + return -EINVAL; + } + } + + regs->DATA[channel].reg = (uint16_t)value; + + if (channel == 0) { + while ((regs->SYNCBUSY.reg & DAC_SYNCBUSY_DATA0) == DAC_SYNCBUSY_DATA0) { + /* Wait for synchronization */ + } + } else { + while ((regs->SYNCBUSY.reg & DAC_SYNCBUSY_DATA1) == DAC_SYNCBUSY_DATA1) { + /* Wait for synchronization */ + } + } + + return 0; +} + +/* + * Setup the channel. Validates the input id and resolution to match within + * the samd5x/e5x parameters. + */ +static int dac_samd5x_channel_setup(const struct device *dev, + const struct dac_channel_cfg *channel_cfg) +{ + const struct dac_samd5x_cfg *const cfg = dev->config; + struct dac_samd5x_data *data = dev->data; + Dac *regs = cfg->regs; + + if (channel_cfg->channel_id >= DAC_CHANNEL_NO) { + return -EINVAL; + } + if ((channel_cfg->resolution != 12) && (channel_cfg->resolution != 16)) { + return -ENOTSUP; + } + if (channel_cfg->internal) { + return -ENOSYS; + } + + /* Disable DAC */ + regs->CTRLA.reg = DAC_CTRLA_RESETVALUE; + while (((regs->SYNCBUSY.reg & DAC_SYNCBUSY_ENABLE) == DAC_SYNCBUSY_ENABLE)) { + /* Wait for synchronization */ + } + + /* Enable dithering for 16-bit resolution */ + if (channel_cfg->resolution == 16) { + regs->DACCTRL[channel_cfg->channel_id].reg |= DAC_DACCTRL_DITHER; + data->resolution[channel_cfg->channel_id] = 16; + } + + /* Enable channel */ + regs->DACCTRL[channel_cfg->channel_id].reg |= DAC_DACCTRL_ENABLE; + + /* Enable DAC */ + regs->CTRLA.reg = DAC_CTRLA_ENABLE; + while (((regs->SYNCBUSY.reg & DAC_SYNCBUSY_ENABLE) == DAC_SYNCBUSY_ENABLE)) { + /* Wait for synchronization */ + } + + return 0; +} + +/* Initialize and enable DAC and channels properties. */ +static int dac_samd5x_init(const struct device *dev) +{ + const struct dac_samd5x_cfg *const cfg = dev->config; + Dac *regs = cfg->regs; + int retval; + + *cfg->mclk |= cfg->mclk_mask; + +#ifdef MCLK + GCLK->PCHCTRL[cfg->gclk_id].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN(cfg->gclk_gen); +#else + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN(cfg->gclk_gen) | + GCLK_CLKCTRL_ID(cfg->gclk_id); +#endif + + retval = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); + if (retval < 0) { + return retval; + } + + /* Reset then configure the DAC */ + regs->CTRLA.reg = DAC_CTRLA_SWRST; + while (((regs->CTRLA.reg & DAC_CTRLA_SWRST) == DAC_CTRLA_SWRST) && + ((regs->SYNCBUSY.reg & DAC_SYNCBUSY_SWRST) == DAC_SYNCBUSY_SWRST)) { + /* Wait for synchronization */ + } + + regs->CTRLB.reg = cfg->refsel; + if (cfg->diff_mode) { + regs->CTRLB.reg |= DAC_CTRLB_DIFF; + } + + /* Configure each channel */ + for (int i = 0; i < DAC_CHANNEL_NO; i++) { + regs->DACCTRL[i].reg = ( + DAC_DACCTRL_OSR(cfg->channel_cfg[i].oversampling) | + DAC_DACCTRL_REFRESH(cfg->channel_cfg[i].refresh_period) | + (cfg->channel_cfg[i].run_in_standby ? DAC_DACCTRL_RUNSTDBY : 0) | + DAC_DACCTRL_CCTRL(cfg->channel_cfg[i].current_control)); + } + + /* Enable DAC */ + regs->CTRLA.reg = DAC_CTRLA_ENABLE; + while (((regs->SYNCBUSY.reg & DAC_SYNCBUSY_ENABLE) == DAC_SYNCBUSY_ENABLE)) { + /* Wait for synchronization */ + } + + return 0; +} + +static DEVICE_API(dac, dac_samd5x_driver_api) = { + .channel_setup = dac_samd5x_channel_setup, + .write_value = dac_samd5x_write_value +}; + +/* Helper macros for device tree properties */ +#define SAMD5X_DAC_REFSEL(n) \ + COND_CODE_1(DT_INST_NODE_HAS_PROP(n, reference), \ + (DT_INST_ENUM_IDX(n, reference)), (0)) + +#define SAMD5X_DAC_DIFF_MODE(n) DT_INST_PROP_OR(n, differential - mode, 0) + +/* Channel configuration macros */ +#define CHANNEL_CFG_DEF(n) \ + {.oversampling = DT_INST_ENUM_IDX_OR(n, oversampling, 0), \ + .refresh_period = DT_PROP_OR(n, refresh_period, 0), \ + .run_in_standby = DT_PROP_OR(n, run_in_standby, 0), \ + .current_control = DT_INST_ENUM_IDX_OR(n, current_control, 0)} + +/* Device initialization macro */ +#define SAMD5X_DAC_INIT(n) \ + PINCTRL_DT_INST_DEFINE(n); \ + static struct dac_samd5x_data dac_samd5x_data_##n; \ + static const struct dac_samd5x_cfg dac_samd5x_cfg_##n = { \ + .regs = (Dac *)DT_INST_REG_ADDR(n), \ + .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ + .gclk_gen = ATMEL_SAM0_DT_INST_ASSIGNED_CLOCKS_CELL_BY_NAME(n, gclk, gen), \ + .gclk_id = DT_INST_CLOCKS_CELL_BY_NAME(n, gclk, id), \ + .mclk = ATMEL_SAM0_DT_INST_MCLK_PM_REG_ADDR_OFFSET(n), \ + .mclk_mask = ATMEL_SAM0_DT_INST_MCLK_PM_PERIPH_MASK(n, bit), \ + .refsel = UTIL_CAT(SAMD5X_DAC_REFSEL_, SAMD5X_DAC_REFSEL(n)), \ + .diff_mode = SAMD5X_DAC_DIFF_MODE(n), \ + .channel_cfg = {DT_INST_FOREACH_CHILD_STATUS_OKAY_SEP(n, CHANNEL_CFG_DEF, (,))}, \ + }; \ + DEVICE_DT_INST_DEFINE(n, &dac_samd5x_init, NULL, &dac_samd5x_data_##n, \ + &dac_samd5x_cfg_##n, POST_KERNEL, CONFIG_DAC_INIT_PRIORITY, \ + &dac_samd5x_driver_api) + +/* Create all instances */ +DT_INST_FOREACH_STATUS_OKAY(SAMD5X_DAC_INIT); diff --git a/dts/arm/atmel/samd5x.dtsi b/dts/arm/atmel/samd5x.dtsi index 65123ef0e1d..0c7a14b12d9 100644 --- a/dts/arm/atmel/samd5x.dtsi +++ b/dts/arm/atmel/samd5x.dtsi @@ -388,6 +388,19 @@ calib-offset = <14>; }; + dac0: dac@43002400{ + compatible = "atmel,samd5x-dac"; + reg = <0x43002400 0x20>; + + clocks = <&gclk 42>, <&mclk 0x20 9>; + clock-names = "GCLK", "MCLK"; + atmel,assigned-clocks = <&gclk 0>; + atmel,assigned-clock-names = "GCLK"; + status = "disabled"; + + #io-channel-cells = <1>; + }; + tc0: tc@40003800 { compatible = "atmel,sam0-tc32"; reg = <0x40003800 0x34>; diff --git a/dts/bindings/dac/atmel,samd5x-dac.yaml b/dts/bindings/dac/atmel,samd5x-dac.yaml new file mode 100644 index 00000000000..ffb657229fa --- /dev/null +++ b/dts/bindings/dac/atmel,samd5x-dac.yaml @@ -0,0 +1,120 @@ +# Copyright (c) 2025 Daikin Comfort Technologies North America, Inc. +# SPDX-License-Identifier: Apache-2.0 + +description: | + Atmel SAMD5x/E5x family DAC + + To enable the DAC peripheral, it must be enabled in the board dts, or in + subsequent overlay file. In addition, each channel must be configured + using the child-binding properties. + + &dac0 { + status = "okay"; + reference = "intref"; + + channel_0 { + oversampling = <0>; + refresh-period = <1>; + current-control = <2>; + }; + + channel_1 { + oversampling = <0>; + refresh-period = <1>; + current-control = <2>; + }; + }; + +compatible: "atmel,samd5x-dac" + +include: + - name: dac-controller.yaml + - name: pinctrl-device.yaml + - name: atmel,assigned-clocks.yaml + +properties: + reg: + required: true + + clocks: + required: true + + clock-names: + required: true + + atmel,assigned-clocks: + required: true + + atmel,assigned-clock-names: + required: true + + reference: + type: string + description: Reference voltage source + enum: + - "vrefau" + - "vddana" + - "vrefab" + - "intref" + + differential-mode: + type: boolean + description: | + Enable differential mode. When enabled, the DAC outputs the difference + between channel 0 and channel 1 on the VOUT0 pin. + + "#io-channel-cells": + const: 1 + +child-binding: + description: | + DAC Channel configuration. Each channel can be individually configured + with the following properties: + + properties: + oversampling: + type: string + description: | + This field defines the oversampling ratio/interpolation depth: + - 0: 1x oversampling (no interpolation) + - 1: 2x oversampling + - 2: 4x oversampling + - 3: 8x oversampling + - 4: 16x oversampling + - 5: 32x oversampling + Higher oversampling ratios provide better noise reduction but + reduce the maximum update rate. + enum: + - "OSR_1" + - "OSR_2" + - "OSR_4" + - "OSR_8" + - "OSR_16" + - "OSR_32" + + refresh-period: + type: int + description: | + Defines the refresh period for the DAC output. Only work when the + input data is not interpolated, i.e. the oversampling rate is zero. + - 0: Refresh mode disabled + - 1-15: Refresh period = REFRESH * 30µs + + run-in-standby: + type: boolean + description: | + When enabled, the DAC channel continues to operate in standby mode. + + current-control: + type: string + description: | + Select the output buffer current according to data rate, power + consumption can be reduced by controlling the output buffer + current, according to conversion rate. + - 0: GCLK_DAC ≤ 1.2MHz (100kSPS) + - 1: 1.2MHz < GCLK_DAC ≤ 6MHz (500kSPS) + - 2: 6MHz < GCLK_DAC ≤ 12MHz (1MSPS) + enum: + - "CC100K" + - "CC1M" + - "CC12M" diff --git a/tests/drivers/dac/dac_api/src/test_dac.c b/tests/drivers/dac/dac_api/src/test_dac.c index 77174445e4c..0c4bb855804 100644 --- a/tests/drivers/dac/dac_api/src/test_dac.c +++ b/tests/drivers/dac/dac_api/src/test_dac.c @@ -55,7 +55,8 @@ defined(CONFIG_BOARD_SEEEDUINO_XIAO) || \ defined(CONFIG_BOARD_ARDUINO_MKRZERO) || \ defined(CONFIG_BOARD_ARDUINO_ZERO) || \ - defined(CONFIG_BOARD_LPCXPRESSO55S36) + defined(CONFIG_BOARD_LPCXPRESSO55S36) || \ + defined(CONFIG_BOARD_SAME54_XPRO) #define DAC_DEVICE_NODE DT_NODELABEL(dac0) #define DAC_RESOLUTION 12