/* Copyright 2013 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ /* PWM control module for STM32 */ #include "clock.h" #include "gpio.h" #include "hooks.h" #include "hwtimer.h" #include "pwm.h" #include "pwm_chip.h" #include "registers.h" #include "system.h" #include "util.h" /* Bitmap of currently active PWM channels. 1 bit per channel. */ static uint32_t using_pwm; void pwm_set_duty(enum pwm_channel ch, int percent) { const struct pwm_t *pwm = pwm_channels + ch; timer_ctlr_t *tim = (timer_ctlr_t *)(pwm->tim.base); ASSERT((percent >= 0) && (percent <= 100)); tim->ccr[pwm->channel] = percent; } int pwm_get_duty(enum pwm_channel ch) { const struct pwm_t *pwm = pwm_channels + ch; timer_ctlr_t *tim = (timer_ctlr_t *)(pwm->tim.base); return tim->ccr[pwm->channel]; } static void pwm_configure(enum pwm_channel ch) { const struct pwm_t *pwm = pwm_channels + ch; timer_ctlr_t *tim = (timer_ctlr_t *)(pwm->tim.base); volatile unsigned *ccmr = NULL; /* Default frequency = 100 Hz */ int frequency = pwm->frequency ? pwm->frequency : 100; uint16_t ccer; if (using_pwm & BIT(ch)) return; /* Enable timer */ __hw_timer_enable_clock(pwm->tim.id, 1); /* Disable counter during setup */ tim->cr1 = 0x0000; /* * CPU clock / PSC determines how fast the counter operates. * ARR determines the wave period, CCRn determines duty cycle. * Thus, frequency = cpu_freq / PSC / ARR. so: * * frequency = cpu_freq / (cpu_freq/10000 + 1) / (99 + 1) = 100 Hz. */ tim->psc = clock_get_freq() / (frequency * 100) - 1; tim->arr = 99; if (pwm->channel <= 2) /* Channel ID starts from 1 */ ccmr = &tim->ccmr1; else ccmr = &tim->ccmr2; /* Output, PWM mode 1, preload enable */ if (pwm->channel & 0x1) *ccmr = (6 << 4) | BIT(3); else *ccmr = (6 << 12) | BIT(11); /* Output enable. Set active high/low. */ if (pwm->flags & PWM_CONFIG_ACTIVE_LOW) ccer = 3 << (pwm->channel * 4 - 4); else ccer = 1 << (pwm->channel * 4 - 4); /* Enable complementary output, if present. */ if (pwm->flags & PWM_CONFIG_COMPLEMENTARY_OUTPUT) ccer |= (ccer << 2); tim->ccer = ccer; /* * Main output enable. * TODO(shawnn): BDTR is undocumented on STM32L. Verify this isn't * harmful on STM32L. */ tim->bdtr |= BIT(15); /* Generate update event to force loading of shadow registers */ tim->egr |= 1; /* Enable auto-reload preload, start counting */ tim->cr1 |= BIT(7) | BIT(0); atomic_or(&using_pwm, 1 << ch); /* Prevent sleep */ disable_sleep(SLEEP_MASK_PWM); } static void pwm_disable(enum pwm_channel ch) { const struct pwm_t *pwm = pwm_channels + ch; timer_ctlr_t *tim = (timer_ctlr_t *)(pwm->tim.base); if ((using_pwm & BIT(ch)) == 0) return; /* Main output disable */ tim->bdtr &= ~BIT(15); /* Disable counter */ tim->cr1 &= ~0x1; /* Disable timer clock */ __hw_timer_enable_clock(pwm->tim.id, 0); /* Allow sleep */ enable_sleep(SLEEP_MASK_PWM); atomic_clear(&using_pwm, 1 << ch); /* Unless another PWM is active... Then prevent sleep */ if (using_pwm) disable_sleep(SLEEP_MASK_PWM); } void pwm_enable(enum pwm_channel ch, int enabled) { if (enabled) pwm_configure(ch); else pwm_disable(ch); } int pwm_get_enabled(enum pwm_channel ch) { return using_pwm & BIT(ch); } static void pwm_reconfigure(enum pwm_channel ch) { atomic_clear(&using_pwm, 1 << ch); pwm_configure(ch); } /** * Handle clock frequency change */ static void pwm_freq_change(void) { int i; for (i = 0; i < PWM_CH_COUNT; ++i) if (pwm_get_enabled(i)) pwm_reconfigure(i); } DECLARE_HOOK(HOOK_FREQ_CHANGE, pwm_freq_change, HOOK_PRIO_DEFAULT);