/* Copyright (c) 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 "util.h" static int using_pwm[PWM_CH_COUNT]; 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; const struct gpio_info *gpio = gpio_list + pwm->pin; timer_ctlr_t *tim = (timer_ctlr_t *)(pwm->tim.base); volatile unsigned *ccmr = NULL; #ifdef CHIP_FAMILY_stm32f int mask = gpio->mask; volatile uint32_t *gpio_cr = NULL; uint32_t val; #endif if (using_pwm[ch]) return; #ifdef CHIP_FAMILY_stm32f if (mask < 0x100) { gpio_cr = &STM32_GPIO_CRL(gpio->port); } else { gpio_cr = &STM32_GPIO_CRH(gpio->port); mask >>= 8; } /* Expand mask from 8-bit to 32-bit */ mask = mask * mask; mask = mask * mask; /* Set alternate function */ val = *gpio_cr & ~(mask * 0xf); val |= mask * 0x9; *gpio_cr = val; #else /* stm32l */ gpio_set_alternate_function(gpio->port, gpio->mask, GPIO_ALT_TIM(pwm->tim.id)); #endif /* 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) / 100 = 100 Hz. */ tim->psc = clock_get_freq() / 10000; tim->arr = 100; 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) | (1 << 3); else *ccmr = (6 << 12) | (1 << 11); /* Output enable. Set active high/low. */ if (pwm->flags & PWM_CONFIG_ACTIVE_LOW) tim->ccer = 3 << (pwm->channel * 4 - 4); else tim->ccer = 1 << (pwm->channel * 4 - 4); /* Generate update event to force loading of shadow registers */ tim->egr |= 1; /* Enable auto-reload preload, start counting */ tim->cr1 |= (1 << 7) | (1 << 0); using_pwm[ch] = 1; } 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[ch] == 0) return; /* Disable counter */ tim->cr1 &= ~0x1; /* Disable timer clock */ __hw_timer_enable_clock(pwm->tim.id, 0); using_pwm[ch] = 0; } 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[ch]; } static void pwm_reconfigure(enum pwm_channel ch) { using_pwm[ch] = 0; pwm_configure(ch); } /** * Handle clock frequency change */ static void pwm_freq_change(void) { int i; for (i = 0; i < PWM_CH_COUNT; ++i) if (using_pwm[i]) pwm_reconfigure(i); } DECLARE_HOOK(HOOK_FREQ_CHANGE, pwm_freq_change, HOOK_PRIO_DEFAULT);