1 /*
2  * Copyright (c) 2020 Google LLC.
3  * Copyright (c) 2024 Gerson Fernando Budke <nandojve@gmail.com>
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #define DT_DRV_COMPAT atmel_sam0_dac
9 
10 #include <errno.h>
11 
12 #include <zephyr/drivers/dac.h>
13 #include <zephyr/drivers/pinctrl.h>
14 #include <soc.h>
15 #include <zephyr/logging/log.h>
16 LOG_MODULE_REGISTER(dac_sam0, CONFIG_DAC_LOG_LEVEL);
17 
18 /* clang-format off */
19 
20 /*
21  * Maps between the DTS reference property names and register values.  Note that
22  * the ASF uses the 09/2015 names which differ from the 03/2020 datasheet.
23  *
24  * TODO(#21273): replace once improved support for enum values lands.
25  */
26 #define SAM0_DAC_REFSEL_0 DAC_CTRLB_REFSEL_INT1V_Val
27 #define SAM0_DAC_REFSEL_1 DAC_CTRLB_REFSEL_AVCC_Val
28 #define SAM0_DAC_REFSEL_2 DAC_CTRLB_REFSEL_VREFP_Val
29 
30 struct dac_sam0_cfg {
31 	Dac *regs;
32 	const struct pinctrl_dev_config *pcfg;
33 	volatile uint32_t *mclk;
34 	uint32_t mclk_mask;
35 	uint32_t gclk_gen;
36 	uint16_t gclk_id;
37 	uint8_t refsel;
38 };
39 
40 /* Write to the DAC. */
dac_sam0_write_value(const struct device * dev,uint8_t channel,uint32_t value)41 static int dac_sam0_write_value(const struct device *dev, uint8_t channel,
42 				uint32_t value)
43 {
44 	const struct dac_sam0_cfg *const cfg = dev->config;
45 	Dac *regs = cfg->regs;
46 
47 	if (value >= BIT(12)) {
48 		LOG_ERR("value %d out of range", value);
49 		return -EINVAL;
50 	}
51 
52 	regs->DATA.reg = (uint16_t)value;
53 
54 	return 0;
55 }
56 
57 /*
58  * Setup the channel.  As the SAM0 has one fixed width channel, this validates
59  * the input and does nothing else.
60  */
dac_sam0_channel_setup(const struct device * dev,const struct dac_channel_cfg * channel_cfg)61 static int dac_sam0_channel_setup(const struct device *dev,
62 				  const struct dac_channel_cfg *channel_cfg)
63 {
64 	if (channel_cfg->channel_id != 0) {
65 		return -EINVAL;
66 	}
67 	if (channel_cfg->resolution != 10) {
68 		return -ENOTSUP;
69 	}
70 
71 	if (channel_cfg->internal) {
72 		return -ENOSYS;
73 	}
74 
75 	return 0;
76 }
77 
78 /* Initialise and enable the DAC. */
dac_sam0_init(const struct device * dev)79 static int dac_sam0_init(const struct device *dev)
80 {
81 	const struct dac_sam0_cfg *const cfg = dev->config;
82 	Dac *regs = cfg->regs;
83 	int retval;
84 
85 	*cfg->mclk |= cfg->mclk_mask;
86 
87 #ifdef MCLK
88 	GCLK->PCHCTRL[cfg->gclk_id].reg = GCLK_PCHCTRL_CHEN
89 					| GCLK_PCHCTRL_GEN(cfg->gclk_gen);
90 #else
91 	GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN
92 			  | GCLK_CLKCTRL_GEN(cfg->gclk_gen)
93 			  | GCLK_CLKCTRL_ID(cfg->gclk_id);
94 #endif
95 
96 	retval = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT);
97 	if (retval < 0) {
98 		return retval;
99 	}
100 
101 	/* Reset then configure the DAC */
102 	regs->CTRLA.bit.SWRST = 1;
103 	while (regs->STATUS.bit.SYNCBUSY) {
104 	}
105 
106 	regs->CTRLB.bit.REFSEL = cfg->refsel;
107 	regs->CTRLB.bit.EOEN = 1;
108 
109 	/* Enable */
110 	regs->CTRLA.bit.ENABLE = 1;
111 	while (regs->STATUS.bit.SYNCBUSY) {
112 	}
113 
114 	return 0;
115 }
116 
117 static DEVICE_API(dac, api_sam0_driver_api) = {
118 	.channel_setup = dac_sam0_channel_setup,
119 	.write_value = dac_sam0_write_value
120 };
121 
122 #define ASSIGNED_CLOCKS_CELL_BY_NAME						\
123 	ATMEL_SAM0_DT_INST_ASSIGNED_CLOCKS_CELL_BY_NAME
124 
125 #define SAM0_DAC_REFSEL(n)							\
126 	COND_CODE_1(DT_INST_NODE_HAS_PROP(n, reference),			\
127 		    (DT_INST_ENUM_IDX(n, reference)), (0))
128 
129 #define SAM0_DAC_INIT(n)							\
130 	PINCTRL_DT_INST_DEFINE(n);						\
131 	static const struct dac_sam0_cfg dac_sam0_cfg_##n = {			\
132 		.regs = (Dac *)DT_INST_REG_ADDR(n),				\
133 		.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),			\
134 		.gclk_gen = ASSIGNED_CLOCKS_CELL_BY_NAME(n, gclk, gen),		\
135 		.gclk_id = DT_INST_CLOCKS_CELL_BY_NAME(n, gclk, id),		\
136 		.mclk = ATMEL_SAM0_DT_INST_MCLK_PM_REG_ADDR_OFFSET(n),		\
137 		.mclk_mask = ATMEL_SAM0_DT_INST_MCLK_PM_PERIPH_MASK(n, bit),	\
138 		.refsel = UTIL_CAT(SAM0_DAC_REFSEL_, SAM0_DAC_REFSEL(n)),	\
139 	};									\
140 										\
141 	DEVICE_DT_INST_DEFINE(n, &dac_sam0_init, NULL, NULL,			\
142 			    &dac_sam0_cfg_##n, POST_KERNEL,			\
143 			    CONFIG_DAC_INIT_PRIORITY,				\
144 			    &api_sam0_driver_api)
145 
146 DT_INST_FOREACH_STATUS_OKAY(SAM0_DAC_INIT);
147 
148 /* clang-format on */
149