164 lines
3.6 KiB
C
164 lines
3.6 KiB
C
|
/* 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);
|