1 /*
2  * Copyright (c) 2020 Google LLC.
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT atmel_sam0_dac
8 
9 #include <errno.h>
10 
11 #include <drivers/dac.h>
12 #include <soc.h>
13 
14 /*
15  * Maps between the DTS reference property names and register values.  Note that
16  * the ASF uses the 09/2015 names which differ from the 03/2020 datasheet.
17  *
18  * TODO(#21273): replace once improved support for enum values lands.
19  */
20 #define SAM0_DAC_REFSEL_0 DAC_CTRLB_REFSEL_INT1V_Val
21 #define SAM0_DAC_REFSEL_1 DAC_CTRLB_REFSEL_AVCC_Val
22 #define SAM0_DAC_REFSEL_2 DAC_CTRLB_REFSEL_VREFP_Val
23 
24 struct dac_sam0_cfg {
25 	Dac *regs;
26 	uint8_t pm_apbc_bit;
27 	uint8_t gclk_clkctrl_id;
28 	uint8_t refsel;
29 };
30 
31 #define DEV_CFG(dev) ((const struct dac_sam0_cfg *const)(dev)->config)
32 
33 /* Write to the DAC. */
dac_sam0_write_value(const struct device * dev,uint8_t channel,uint32_t value)34 static int dac_sam0_write_value(const struct device *dev, uint8_t channel,
35 				uint32_t value)
36 {
37 	const struct dac_sam0_cfg *const cfg = DEV_CFG(dev);
38 	Dac *regs = cfg->regs;
39 
40 	regs->DATA.reg = (uint16_t)value;
41 
42 	return 0;
43 }
44 
45 /*
46  * Setup the channel.  As the SAM0 has one fixed width channel, this validates
47  * the input and does nothing else.
48  */
dac_sam0_channel_setup(const struct device * dev,const struct dac_channel_cfg * channel_cfg)49 static int dac_sam0_channel_setup(const struct device *dev,
50 				  const struct dac_channel_cfg *channel_cfg)
51 {
52 	if (channel_cfg->channel_id != 0) {
53 		return -EINVAL;
54 	}
55 	if (channel_cfg->resolution != 10) {
56 		return -ENOTSUP;
57 	}
58 
59 	return 0;
60 }
61 
62 /* Initialise and enable the DAC. */
dac_sam0_init(const struct device * dev)63 static int dac_sam0_init(const struct device *dev)
64 {
65 	const struct dac_sam0_cfg *const cfg = DEV_CFG(dev);
66 	Dac *regs = cfg->regs;
67 
68 	/* Enable the GCLK */
69 	GCLK->CLKCTRL.reg = cfg->gclk_clkctrl_id | GCLK_CLKCTRL_GEN_GCLK0 |
70 			    GCLK_CLKCTRL_CLKEN;
71 
72 	/* Enable the clock in PM */
73 	PM->APBCMASK.reg |= 1 << cfg->pm_apbc_bit;
74 
75 	/* Reset then configure the DAC */
76 	regs->CTRLA.bit.SWRST = 1;
77 	while (regs->STATUS.bit.SYNCBUSY) {
78 	}
79 
80 	regs->CTRLB.bit.REFSEL = cfg->refsel;
81 	regs->CTRLB.bit.EOEN = 1;
82 
83 	/* Enable */
84 	regs->CTRLA.bit.ENABLE = 1;
85 	while (regs->STATUS.bit.SYNCBUSY) {
86 	}
87 
88 	return 0;
89 }
90 
91 static const struct dac_driver_api api_sam0_driver_api = {
92 	.channel_setup = dac_sam0_channel_setup,
93 	.write_value = dac_sam0_write_value
94 };
95 
96 #define SAM0_DAC_REFSEL(n)						       \
97 	COND_CODE_1(DT_INST_NODE_HAS_PROP(n, reference),		       \
98 		    (DT_ENUM_IDX(DT_DRV_INST(n), reference)), (0))
99 
100 #define SAM0_DAC_INIT(n)						       \
101 	static const struct dac_sam0_cfg dac_sam0_cfg_##n = {		       \
102 		.regs = (Dac *)DT_INST_REG_ADDR(n),			       \
103 		.pm_apbc_bit = DT_INST_CLOCKS_CELL_BY_NAME(n, pm, bit),	       \
104 		.gclk_clkctrl_id =					       \
105 			DT_INST_CLOCKS_CELL_BY_NAME(n, gclk, clkctrl_id),      \
106 		.refsel = UTIL_CAT(SAM0_DAC_REFSEL_, SAM0_DAC_REFSEL(n)),      \
107 	};								       \
108 									       \
109 	DEVICE_DT_INST_DEFINE(n, &dac_sam0_init, NULL, NULL,		       \
110 			    &dac_sam0_cfg_##n, POST_KERNEL,		       \
111 			    CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,	       \
112 			    &api_sam0_driver_api)
113 
114 DT_INST_FOREACH_STATUS_OKAY(SAM0_DAC_INIT);
115