1 /* Copyright 2020 SiFive, Inc */
2 /* SPDX-License-Identifier: Apache-2.0 */
4 #include <metal/machine/platform.h>
6 #ifdef METAL_SIFIVE_PWM0
7 #include <metal/clock.h>
8 #include <metal/compiler.h>
9 #include <metal/drivers/sifive_gpio0.h>
10 #include <metal/drivers/sifive_pwm0.h>
12 #include <metal/machine.h>
13 #include <metal/time.h>
17 #define METAL_PWMCFG_STICKY (1UL << 8)
18 #define METAL_PWMCFG_ZEROCMP (1UL << 9)
19 #define METAL_PWMCFG_DEGLITCH (1UL << 10)
20 #define METAL_PWMCFG_ENALWAYS (1UL << 12)
21 #define METAL_PWMCFG_ENONESHOT (1UL << 13)
22 #define METAL_PWMCFG_CMPCENTER(x) (1UL << (16 + x))
23 #define METAL_PWMCFG_CMPIP(x) (1UL << (28 + x))
24 #define METAL_SIFIVE_PWM0_PWMCMP(x) (METAL_SIFIVE_PWM0_PWMCMP0 + (x * 4))
26 /* Macros to access registers */
27 #define METAL_PWM_REG(offset) ((base + offset))
28 #define METAL_PWM_REGW(offset) \
29 (__METAL_ACCESS_ONCE((__metal_io_u32 *)METAL_PWM_REG(offset)))
31 /* Macro to get PWM compare count */
32 #define METAL_PWM_GETCMPVAL(duty) (duty * pwm->count_val) / 100U
33 /* Max duty cycle value */
34 #define METAL_PWM_MAXDUTY 100UL
35 /* Max pre-scalar value */
36 #define METAL_PWM_MAXPRESCAL 15UL
39 #if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
40 #error *** Unsupported endianess ***
43 #if (METAL_MAX_PWM0_NCMP > METAL_MAX_PWM_CHANNELS)
44 #error *** METAL_MAX_PWM_CHANNELS exceeded ***
48 #define METAL_PWM_RET_OK 0
49 #define METAL_PWM_RET_ERR -1
51 static void pre_rate_change_callback(void *priv) {
52 struct metal_pwm *gpwm = priv;
53 /* Disable active PWM instance. */
54 gpwm->vtable->stop(gpwm, 0);
57 static void post_rate_change_callback(void *priv) {
58 struct __metal_driver_sifive_pwm0 *pwm = priv;
59 struct metal_pwm *gpwm = priv;
60 unsigned long base = __metal_driver_sifive_pwm0_control_base(gpwm);
61 unsigned int cmp_count = __metal_driver_sifive_pwm0_comparator_count(gpwm);
65 /* Check if PWM frequency was set */
67 /* Set frequency after clock rate change */
68 gpwm->vtable->set_freq(gpwm, 0, pwm->freq);
70 /* Set duty cycle after clock rate change */
71 while (++idx < cmp_count) {
72 duty = pwm->duty[idx];
74 METAL_PWM_REGW(METAL_SIFIVE_PWM0_PWMCMP(idx)) =
75 METAL_PWM_GETCMPVAL(duty);
81 static int __metal_driver_sifive_pwm0_enable(struct metal_pwm *gpwm) {
82 struct __metal_driver_sifive_gpio0 *pinmux =
83 __metal_driver_sifive_pwm0_pinmux(gpwm);
84 unsigned long base = __metal_driver_sifive_pwm0_control_base(gpwm);
85 struct __metal_driver_sifive_pwm0 *pwm = (void *)gpwm;
86 int ret = METAL_PWM_RET_ERR;
90 if ((pinmux != NULL) && (gpwm != NULL)) {
91 /* Configure PWM I/O pins */
92 long pinmux_output_selector =
93 __metal_driver_sifive_pwm0_pinmux_output_selector(gpwm);
94 long pinmux_source_selector =
95 __metal_driver_sifive_pwm0_pinmux_source_selector(gpwm);
97 pinmux->gpio.vtable->enable_io((struct metal_gpio *)pinmux,
98 pinmux_output_selector,
99 pinmux_source_selector);
102 /* Initialize default values */
104 (1UL << __metal_driver_sifive_pwm0_compare_width(gpwm)) - 1;
107 METAL_PWM_REGW(METAL_SIFIVE_PWM0_PWMCFG) = 0;
108 ret = METAL_PWM_RET_OK;
113 static int __metal_driver_sifive_pwm0_disable(struct metal_pwm *gpwm) {
114 struct __metal_driver_sifive_gpio0 *pinmux =
115 __metal_driver_sifive_pwm0_pinmux(gpwm);
116 int ret = METAL_PWM_RET_ERR;
120 if (pinmux != NULL) {
121 /* Disable PWM I/O pins */
122 long pinmux_source_selector =
123 __metal_driver_sifive_pwm0_pinmux_source_selector(gpwm);
124 pinmux->gpio.vtable->disable_io((struct metal_gpio *)pinmux,
125 pinmux_source_selector);
128 ret = METAL_PWM_RET_OK;
133 static int __metal_driver_sifive_pwm0_set_freq(struct metal_pwm *gpwm,
136 struct metal_clock *clock = __metal_driver_sifive_pwm0_clock(gpwm);
137 unsigned long base = __metal_driver_sifive_pwm0_control_base(gpwm);
138 unsigned int cmp_count = __metal_driver_sifive_pwm0_comparator_count(gpwm);
139 struct __metal_driver_sifive_pwm0 *pwm = (void *)gpwm;
140 unsigned int clock_rate;
142 unsigned int prescale = 0;
143 int ret = METAL_PWM_RET_ERR;
145 if ((clock != NULL) && (gpwm != NULL) && (idx < cmp_count)) {
146 clock_rate = clock->vtable->get_rate_hz(clock);
147 /* Register clock rate change call-backs */
148 if (pwm->freq == 0) {
149 pwm->pre_rate_change_callback.callback = &pre_rate_change_callback;
150 pwm->pre_rate_change_callback.priv = pwm;
151 metal_clock_register_pre_rate_change_callback(
152 clock, &(pwm->pre_rate_change_callback));
154 pwm->post_rate_change_callback.callback =
155 &post_rate_change_callback;
156 pwm->post_rate_change_callback.priv = pwm;
157 metal_clock_register_post_rate_change_callback(
158 clock, &(pwm->post_rate_change_callback));
161 /* Calculate count value for given PWM frequency */
163 count = (clock_rate / (1UL << prescale)) / freq;
164 } while ((count > pwm->max_count) &&
165 (prescale++ < METAL_PWM_MAXPRESCAL));
167 pwm->freq = (clock_rate / (1UL << prescale)) / count;
168 pwm->count_val = --count;
170 /* Update values into registers */
171 METAL_PWM_REGW(METAL_SIFIVE_PWM0_PWMCMP0) = count;
172 METAL_PWM_REGW(METAL_SIFIVE_PWM0_PWMCFG) |= (prescale & 0x0FUL);
173 ret = METAL_PWM_RET_OK;
175 #if defined(METAL_PWM_DEBUG)
176 printf("PWM requested freq:%u set freq:%u \n", freq, pwm->freq);
177 printf("CPU Clk:%u Prescale:%u Count:%u \n", clock_rate, prescale,
185 __metal_driver_sifive_pwm0_set_duty(struct metal_pwm *gpwm, unsigned int idx,
187 metal_pwm_phase_correct_t phase_corr) {
188 struct __metal_driver_sifive_pwm0 *pwm = (void *)gpwm;
189 unsigned long base = __metal_driver_sifive_pwm0_control_base(gpwm);
190 unsigned int cmp_count = __metal_driver_sifive_pwm0_comparator_count(gpwm);
191 int ret = METAL_PWM_RET_ERR;
193 /* Check if duty value is within limits, duty cycle cannot be set for
195 if ((idx > 0) && (idx < cmp_count) && (duty <= METAL_PWM_MAXDUTY)) {
196 /* Calculate PWM compare count value for given duty cycle */
197 METAL_PWM_REGW(METAL_SIFIVE_PWM0_PWMCMP(idx)) =
198 METAL_PWM_GETCMPVAL(duty);
199 pwm->duty[idx] = duty;
201 /* Enable / Disable phase correct PWM mode */
202 if (phase_corr == METAL_PWM_PHASE_CORRECT_ENABLE) {
203 METAL_PWM_REGW(METAL_SIFIVE_PWM0_PWMCFG) |=
204 METAL_PWMCFG_CMPCENTER(idx);
206 METAL_PWM_REGW(METAL_SIFIVE_PWM0_PWMCFG) &=
207 ~METAL_PWMCFG_CMPCENTER(idx);
209 ret = METAL_PWM_RET_OK;
214 static unsigned int __metal_driver_sifive_pwm0_get_duty(struct metal_pwm *gpwm,
216 struct __metal_driver_sifive_pwm0 *pwm = (void *)gpwm;
217 unsigned int cmp_count = __metal_driver_sifive_pwm0_comparator_count(gpwm);
218 unsigned int duty = 0;
220 /* Check for valid parameters and get configured duty cycle value */
221 if ((pwm != NULL) && (idx > 0) && (idx < cmp_count)) {
222 duty = pwm->duty[idx];
227 static unsigned int __metal_driver_sifive_pwm0_get_freq(struct metal_pwm *gpwm,
229 struct __metal_driver_sifive_pwm0 *pwm = (void *)gpwm;
230 unsigned int freq = 0;
232 (void)idx; /* Unused parameter, no support for per channel frequency */
234 /* Check for valid parameters and get configured PWM frequency value */
241 static int __metal_driver_sifive_pwm0_trigger(struct metal_pwm *gpwm,
243 metal_pwm_run_mode_t mode) {
244 unsigned long base = __metal_driver_sifive_pwm0_control_base(gpwm);
245 int ret = METAL_PWM_RET_ERR;
247 (void)idx; /* Unused parameter,for later use */
250 /* Configure for requested PWM run mode */
251 if (mode == METAL_PWM_CONTINUOUS) {
252 METAL_PWM_REGW(METAL_SIFIVE_PWM0_PWMCFG) |= METAL_PWMCFG_DEGLITCH |
253 METAL_PWMCFG_ZEROCMP |
254 METAL_PWMCFG_ENALWAYS;
256 METAL_PWM_REGW(METAL_SIFIVE_PWM0_PWMCFG) |= METAL_PWMCFG_DEGLITCH |
257 METAL_PWMCFG_ZEROCMP |
258 METAL_PWMCFG_ENONESHOT;
260 ret = METAL_PWM_RET_OK;
265 static int __metal_driver_sifive_pwm0_stop(struct metal_pwm *gpwm,
267 unsigned long base = __metal_driver_sifive_pwm0_control_base(gpwm);
268 int ret = METAL_PWM_RET_ERR;
270 (void)idx; /* Unused parameter,for later use */
273 /* Disable always running mode */
274 METAL_PWM_REGW(METAL_SIFIVE_PWM0_PWMCFG) &= ~METAL_PWMCFG_ENALWAYS;
275 ret = METAL_PWM_RET_OK;
281 __metal_driver_sifive_pwm0_cfg_interrupt(struct metal_pwm *gpwm,
282 metal_pwm_interrupt_t flag) {
283 unsigned long base = __metal_driver_sifive_pwm0_control_base(gpwm);
284 int ret = METAL_PWM_RET_ERR;
287 if (flag == METAL_PWM_INTERRUPT_ENABLE) {
288 /* Enable sticky bit, to make sure interrupts are not forgotten */
289 METAL_PWM_REGW(METAL_SIFIVE_PWM0_PWMCFG) |= METAL_PWMCFG_STICKY;
291 METAL_PWM_REGW(METAL_SIFIVE_PWM0_PWMCFG) &= ~METAL_PWMCFG_STICKY;
293 ret = METAL_PWM_RET_OK;
298 static int __metal_driver_sifive_pwm0_clr_interrupt(struct metal_pwm *gpwm,
300 unsigned long base = __metal_driver_sifive_pwm0_control_base(gpwm);
301 unsigned int cmp_count = __metal_driver_sifive_pwm0_comparator_count(gpwm);
302 int ret = METAL_PWM_RET_ERR;
304 if ((base != 0) && (idx < cmp_count)) {
305 /* Clear interrupt pending bit for given PWM comparator */
306 METAL_PWM_REGW(METAL_SIFIVE_PWM0_PWMCFG) &= ~METAL_PWMCFG_CMPIP(idx);
307 ret = METAL_PWM_RET_OK;
312 static struct metal_interrupt *
313 __metal_driver_sifive_pwm0_interrupt_controller(struct metal_pwm *gpwm) {
314 return __metal_driver_sifive_pwm0_interrupt_parent(gpwm);
317 static int __metal_driver_sifive_pwm0_interrupt_id(struct metal_pwm *gpwm,
319 return __metal_driver_sifive_pwm0_interrupt_lines(gpwm, idx);
322 __METAL_DEFINE_VTABLE(__metal_driver_vtable_sifive_pwm0) = {
323 .pwm.enable = __metal_driver_sifive_pwm0_enable,
324 .pwm.disable = __metal_driver_sifive_pwm0_disable,
325 .pwm.set_duty = __metal_driver_sifive_pwm0_set_duty,
326 .pwm.set_freq = __metal_driver_sifive_pwm0_set_freq,
327 .pwm.get_duty = __metal_driver_sifive_pwm0_get_duty,
328 .pwm.get_freq = __metal_driver_sifive_pwm0_get_freq,
329 .pwm.trigger = __metal_driver_sifive_pwm0_trigger,
330 .pwm.stop = __metal_driver_sifive_pwm0_stop,
331 .pwm.cfg_interrupt = __metal_driver_sifive_pwm0_cfg_interrupt,
332 .pwm.clr_interrupt = __metal_driver_sifive_pwm0_clr_interrupt,
333 .pwm.get_interrupt_controller =
334 __metal_driver_sifive_pwm0_interrupt_controller,
335 .pwm.get_interrupt_id = __metal_driver_sifive_pwm0_interrupt_id,
338 #endif /* METAL_SIFIVE_PWM0 */
340 typedef int no_empty_translation_units;