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>
221 lines
6.3 KiB
C
221 lines
6.3 KiB
C
/*
|
|
* 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);
|