| /* | 
 |  * 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); |