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