From 2e30bbca007ded1bc1dfa0454db6dafc162c07d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Aldo=20Hern=C3=A1ndez=20Luna?= Date: Thu, 10 Apr 2025 18:02:09 -0600 Subject: [PATCH] drivers: dac: Added dac driver for samd5x MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added driver and binding file for samd5x dac peripheral, the already implemented dac_sam0.c lacks the configuration registers for this microcontroller family and is fixed to only have one dac channel output, also, the code gets too bulky when adding the samd5x dac configuration using preprocessor directives that’s why I moved the implementation to its own file. Added dac to the supported list of same54_xpro.yaml, fixed Kconfig.samd5x help spacing, added board defines to test_dac.c and test it out with twister script on board. Signed-off-by: Rafael Aldo Hernández Luna --- .../sam0/same54_xpro/same54_xpro-pinctrl.dtsi | 7 + boards/atmel/sam0/same54_xpro/same54_xpro.dts | 19 ++ .../atmel/sam0/same54_xpro/same54_xpro.yaml | 1 + drivers/dac/CMakeLists.txt | 1 + drivers/dac/Kconfig | 2 + drivers/dac/Kconfig.samd5x | 10 + drivers/dac/dac_samd5x.c | 220 ++++++++++++++++++ dts/arm/atmel/samd5x.dtsi | 13 ++ dts/bindings/dac/atmel,samd5x-dac.yaml | 120 ++++++++++ tests/drivers/dac/dac_api/src/test_dac.c | 3 +- 10 files changed, 395 insertions(+), 1 deletion(-) create mode 100644 drivers/dac/Kconfig.samd5x create mode 100644 drivers/dac/dac_samd5x.c create mode 100644 dts/bindings/dac/atmel,samd5x-dac.yaml 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