drivers: dac: Added dac driver for samd5x

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 <aldo.hernandez@daikincomfort.com>
This commit is contained in:
Rafael Aldo Hernández Luna 2025-04-10 18:02:09 -06:00 committed by Alberto Escolar
parent 7d9fffaeb9
commit 2e30bbca00
10 changed files with 395 additions and 1 deletions

View File

@ -60,4 +60,11 @@
<PA24H_USB_DM>;
};
};
dac0_default: dac0_default {
group1 {
pinmux = <PA2B_DAC_VOUT0>,
<PA5B_DAC_VOUT1>;
};
};
};

View File

@ -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";

View File

@ -12,6 +12,7 @@ flash: 1024
ram: 256
supported:
- adc
- dac
- dma
- flash
- gpio

View File

@ -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)

View File

@ -67,4 +67,6 @@ source "drivers/dac/Kconfig.max22017"
source "drivers/dac/Kconfig.renesas_ra"
source "drivers/dac/Kconfig.samd5x"
endif # DAC

View File

@ -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.

220
drivers/dac/dac_samd5x.c Normal file
View File

@ -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 <errno.h>
#include <zephyr/drivers/dac.h>
#include <zephyr/drivers/pinctrl.h>
#include <soc.h>
#include <zephyr/logging/log.h>
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);

View File

@ -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>;

View File

@ -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"

View File

@ -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