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 <zephyr/device.h>
15 #include <errno.h>
16 #include <zephyr/drivers/pwm.h>
17 #include <zephyr/drivers/pinctrl.h>
18 #include <soc.h>
19 
20 /* Static configuration */
21 struct pwm_sam0_config {
22 	Tcc *regs;
23 	const struct pinctrl_dev_config *pcfg;
24 	uint8_t channels;
25 	uint8_t counter_size;
26 	uint16_t prescaler;
27 	uint32_t freq;
28 
29 #ifdef MCLK
30 	volatile uint32_t *mclk;
31 	uint32_t mclk_mask;
32 	uint16_t gclk_id;
33 #else
34 	uint32_t pm_apbcmask;
35 	uint16_t gclk_clkctrl_id;
36 #endif
37 };
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 channel,uint64_t * cycles)46 static int pwm_sam0_get_cycles_per_sec(const struct device *dev,
47 				       uint32_t channel, uint64_t *cycles)
48 {
49 	const struct pwm_sam0_config *const cfg = dev->config;
50 
51 	if (channel >= cfg->channels) {
52 		return -EINVAL;
53 	}
54 	*cycles = cfg->freq;
55 
56 	return 0;
57 }
58 
pwm_sam0_set_cycles(const struct device * dev,uint32_t channel,uint32_t period_cycles,uint32_t pulse_cycles,pwm_flags_t flags)59 static int pwm_sam0_set_cycles(const struct device *dev, uint32_t channel,
60 			       uint32_t period_cycles, uint32_t pulse_cycles,
61 			       pwm_flags_t flags)
62 {
63 	const struct pwm_sam0_config *const cfg = dev->config;
64 	Tcc *regs = cfg->regs;
65 	uint32_t top = 1 << cfg->counter_size;
66 	uint32_t invert_mask = 1 << channel;
67 	bool invert = ((flags & PWM_POLARITY_INVERTED) != 0);
68 	bool inverted = ((regs->DRVCTRL.vec.INVEN & invert_mask) != 0);
69 
70 	if (channel >= 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[channel].reg = TCC_CCBUF_CCBUF(pulse_cycles);
84 	regs->PERBUF.reg = TCC_PERBUF_PERBUF(period_cycles);
85 #else
86 	/* SAMD21 naming */
87 	regs->CCB[channel].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->config;
106 	Tcc *regs = cfg->regs;
107 	int retval;
108 
109 	/* Enable the clocks */
110 #ifdef MCLK
111 	GCLK->PCHCTRL[cfg->gclk_id].reg =
112 		GCLK_PCHCTRL_GEN_GCLK0 | GCLK_PCHCTRL_CHEN;
113 	*cfg->mclk |= cfg->mclk_mask;
114 #else
115 	GCLK->CLKCTRL.reg = cfg->gclk_clkctrl_id | GCLK_CLKCTRL_GEN_GCLK0 |
116 			    GCLK_CLKCTRL_CLKEN;
117 	PM->APBCMASK.reg |= cfg->pm_apbcmask;
118 #endif
119 
120 	retval = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT);
121 	if (retval < 0) {
122 		return retval;
123 	}
124 
125 	regs->CTRLA.bit.SWRST = 1;
126 	wait_synchronization(regs);
127 
128 	regs->CTRLA.reg = cfg->prescaler;
129 	regs->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;
130 	regs->PER.reg = TCC_PER_PER(1);
131 
132 	regs->CTRLA.bit.ENABLE = 1;
133 	wait_synchronization(regs);
134 
135 	return 0;
136 }
137 
138 static const struct pwm_driver_api pwm_sam0_driver_api = {
139 	.set_cycles = pwm_sam0_set_cycles,
140 	.get_cycles_per_sec = pwm_sam0_get_cycles_per_sec,
141 };
142 
143 #ifdef MCLK
144 #define PWM_SAM0_INIT_CLOCKS(inst)					       \
145 	.mclk = (volatile uint32_t *)MCLK_MASK_DT_INT_REG_ADDR(inst),	       \
146 	.mclk_mask = BIT(DT_INST_CLOCKS_CELL_BY_NAME(inst, mclk, bit)),	       \
147 	.gclk_id = DT_INST_CLOCKS_CELL_BY_NAME(inst, gclk, periph_ch)
148 #else
149 #define PWM_SAM0_INIT_CLOCKS(inst)					       \
150 	.pm_apbcmask = BIT(DT_INST_CLOCKS_CELL_BY_NAME(inst, pm, bit)),	       \
151 	.gclk_clkctrl_id = DT_INST_CLOCKS_CELL_BY_NAME(inst, gclk, clkctrl_id)
152 #endif
153 
154 #define PWM_SAM0_INIT(inst)						       \
155 	PINCTRL_DT_INST_DEFINE(inst);					       \
156 	static const struct pwm_sam0_config pwm_sam0_config_##inst = {	       \
157 		.regs = (Tcc *)DT_INST_REG_ADDR(inst),			       \
158 		.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst),		       \
159 		.channels = DT_INST_PROP(inst, channels),		       \
160 		.counter_size = DT_INST_PROP(inst, counter_size),	       \
161 		.prescaler = UTIL_CAT(TCC_CTRLA_PRESCALER_DIV,		       \
162 				      DT_INST_PROP(inst, prescaler)),	       \
163 		.freq = SOC_ATMEL_SAM0_GCLK0_FREQ_HZ /			       \
164 			DT_INST_PROP(inst, prescaler),			       \
165 		PWM_SAM0_INIT_CLOCKS(inst),				       \
166 	};								       \
167 									       \
168 	DEVICE_DT_INST_DEFINE(inst, &pwm_sam0_init, NULL,		       \
169 			    NULL, &pwm_sam0_config_##inst,		       \
170 			    POST_KERNEL, CONFIG_PWM_INIT_PRIORITY,	       \
171 			    &pwm_sam0_driver_api);
172 
173 DT_INST_FOREACH_STATUS_OKAY(PWM_SAM0_INIT)
174