1 /*
2 * Copyright (c) 2020 Google LLC.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 /*
8 * PWM driver using the SAM0 Timer/Counter (TCC) in Normal PWM (NPWM) mode.
9 * Supports the SAMD21 and SAMD5x series.
10 */
11
12 #define DT_DRV_COMPAT atmel_sam0_tcc_pwm
13
14 #include <device.h>
15 #include <errno.h>
16 #include <drivers/pwm.h>
17 #include <soc.h>
18
19 /* Static configuration */
20 struct pwm_sam0_config {
21 Tcc *regs;
22 uint8_t channels;
23 uint8_t counter_size;
24 uint16_t prescaler;
25 uint32_t freq;
26
27 #ifdef MCLK
28 volatile uint32_t *mclk;
29 uint32_t mclk_mask;
30 uint16_t gclk_id;
31 #else
32 uint32_t pm_apbcmask;
33 uint16_t gclk_clkctrl_id;
34 #endif
35 };
36
37 #define DEV_CFG(dev) ((const struct pwm_sam0_config *const)(dev)->config)
38
39 /* Wait for the peripheral to finish all commands */
wait_synchronization(Tcc * regs)40 static void wait_synchronization(Tcc *regs)
41 {
42 while (regs->SYNCBUSY.reg != 0) {
43 }
44 }
45
pwm_sam0_get_cycles_per_sec(const struct device * dev,uint32_t ch,uint64_t * cycles)46 static int pwm_sam0_get_cycles_per_sec(const struct device *dev, uint32_t ch,
47 uint64_t *cycles)
48 {
49 const struct pwm_sam0_config *const cfg = DEV_CFG(dev);
50
51 if (ch >= cfg->channels) {
52 return -EINVAL;
53 }
54 *cycles = cfg->freq;
55
56 return 0;
57 }
58
pwm_sam0_pin_set(const struct device * dev,uint32_t ch,uint32_t period_cycles,uint32_t pulse_cycles,pwm_flags_t flags)59 static int pwm_sam0_pin_set(const struct device *dev, uint32_t ch,
60 uint32_t period_cycles, uint32_t pulse_cycles,
61 pwm_flags_t flags)
62 {
63 const struct pwm_sam0_config *const cfg = DEV_CFG(dev);
64 Tcc *regs = cfg->regs;
65 uint32_t top = 1 << cfg->counter_size;
66 uint32_t invert_mask = 1 << ch;
67 bool invert = ((flags & PWM_POLARITY_INVERTED) != 0);
68 bool inverted = ((regs->DRVCTRL.vec.INVEN & invert_mask) != 0);
69
70 if (ch >= cfg->channels) {
71 return -EINVAL;
72 }
73 if (period_cycles >= top || pulse_cycles >= top) {
74 return -EINVAL;
75 }
76
77 /*
78 * Update the buffered width and period. These will be automatically
79 * loaded on the next cycle.
80 */
81 #ifdef TCC_PERBUF_PERBUF
82 /* SAME51 naming */
83 regs->CCBUF[ch].reg = TCC_CCBUF_CCBUF(pulse_cycles);
84 regs->PERBUF.reg = TCC_PERBUF_PERBUF(period_cycles);
85 #else
86 /* SAMD21 naming */
87 regs->CCB[ch].reg = TCC_CCB_CCB(pulse_cycles);
88 regs->PERB.reg = TCC_PERB_PERB(period_cycles);
89 #endif
90
91 if (invert != inverted) {
92 regs->CTRLA.bit.ENABLE = 0;
93 wait_synchronization(regs);
94
95 regs->DRVCTRL.vec.INVEN ^= invert_mask;
96 regs->CTRLA.bit.ENABLE = 1;
97 wait_synchronization(regs);
98 }
99
100 return 0;
101 }
102
pwm_sam0_init(const struct device * dev)103 static int pwm_sam0_init(const struct device *dev)
104 {
105 const struct pwm_sam0_config *const cfg = DEV_CFG(dev);
106 Tcc *regs = cfg->regs;
107
108 /* Enable the clocks */
109 #ifdef MCLK
110 GCLK->PCHCTRL[cfg->gclk_id].reg =
111 GCLK_PCHCTRL_GEN_GCLK0 | GCLK_PCHCTRL_CHEN;
112 *cfg->mclk |= cfg->mclk_mask;
113 #else
114 GCLK->CLKCTRL.reg = cfg->gclk_clkctrl_id | GCLK_CLKCTRL_GEN_GCLK0 |
115 GCLK_CLKCTRL_CLKEN;
116 PM->APBCMASK.reg |= cfg->pm_apbcmask;
117 #endif
118
119 regs->CTRLA.bit.SWRST = 1;
120 wait_synchronization(regs);
121
122 regs->CTRLA.reg = cfg->prescaler;
123 regs->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;
124 regs->PER.reg = TCC_PER_PER(1);
125
126 regs->CTRLA.bit.ENABLE = 1;
127 wait_synchronization(regs);
128
129 return 0;
130 }
131
132 static const struct pwm_driver_api pwm_sam0_driver_api = {
133 .pin_set = pwm_sam0_pin_set,
134 .get_cycles_per_sec = pwm_sam0_get_cycles_per_sec,
135 };
136
137 #ifdef MCLK
138 #define PWM_SAM0_INIT_CLOCKS(inst) \
139 .mclk = (volatile uint32_t *)MCLK_MASK_DT_INT_REG_ADDR(inst), \
140 .mclk_mask = BIT(DT_INST_CLOCKS_CELL_BY_NAME(inst, mclk, bit)), \
141 .gclk_id = DT_INST_CLOCKS_CELL_BY_NAME(inst, gclk, periph_ch)
142 #else
143 #define PWM_SAM0_INIT_CLOCKS(inst) \
144 .pm_apbcmask = BIT(DT_INST_CLOCKS_CELL_BY_NAME(inst, pm, bit)), \
145 .gclk_clkctrl_id = DT_INST_CLOCKS_CELL_BY_NAME(inst, gclk, clkctrl_id)
146 #endif
147
148 #define PWM_SAM0_INIT(inst) \
149 static const struct pwm_sam0_config pwm_sam0_config_##inst = { \
150 .regs = (Tcc *)DT_INST_REG_ADDR(inst), \
151 .channels = DT_INST_PROP(inst, channels), \
152 .counter_size = DT_INST_PROP(inst, counter_size), \
153 .prescaler = UTIL_CAT(TCC_CTRLA_PRESCALER_DIV, \
154 DT_INST_PROP(inst, prescaler)), \
155 .freq = SOC_ATMEL_SAM0_GCLK0_FREQ_HZ / \
156 DT_INST_PROP(inst, prescaler), \
157 PWM_SAM0_INIT_CLOCKS(inst), \
158 }; \
159 \
160 DEVICE_DT_INST_DEFINE(inst, &pwm_sam0_init, NULL, \
161 NULL, &pwm_sam0_config_##inst, \
162 POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
163 &pwm_sam0_driver_api);
164
165 DT_INST_FOREACH_STATUS_OKAY(PWM_SAM0_INIT)
166