1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/cpu/sam3/periph/pwm.c
2020-10-23 00:46:26 +02:00

132 lines
3.6 KiB
C

/*
* Copyright (C) 2015 Hamburg University of Applied Sciences
* 2016 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup cpu_sam3
* @ingroup drivers_periph_pwm
* @{
*
* @file
* @brief CPU specific low-level PWM driver implementation for the SAM3X8E
*
* The SAM3 has only support for a single PWM device, so we accept only
* PWM_DEV(0) for this driver.
*
* @author Andreas "Paul" Pauli <andreas.pauli@haw-hamburg.de>
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
*
* @}
*/
#include "cpu.h"
#include "assert.h"
#include "periph/pwm.h"
#include "periph/gpio.h"
#define ENABLE_DEBUG 0
#include "debug.h"
#define PREA_MAX (10U)
static uint16_t pwm_period;
static uint8_t pwm_chan_mask;
uint32_t pwm_init(pwm_t dev, pwm_mode_t mode, uint32_t freq, uint16_t res)
{
uint32_t pwm_clk = freq * res; /* Desired/real pwm_clock */
uint32_t diva = 1; /* Candidate for 8bit divider */
uint32_t prea = 0; /* Candidate for clock select */
/* check for valid device and mode (left-aligned PWM only so far) */
if ((dev != PWM_DEV(0)) || (mode != PWM_LEFT)) {
return 0;
}
/* check if target frequency and resolution is applicable */
if (pwm_clk > CLOCK_CORECLOCK) {
return 0;
}
/* calculate pre-scalers for targeted frequency and resolution:
* clk = CORECLOCK / (2 ^ prea) / diva
* width prea := [0, 10] and diva [1, 255]
*/
while ((diva = (CLOCK_CORECLOCK / pwm_clk / (1 << prea))) > 255) {
++prea;
}
/* make sure PREA does not exceed its limit */
if (prea > PREA_MAX) {
return 0;
}
/* activate PWM block by enabling it's clock. */
PMC->PMC_PCER1 = PMC_PCER1_PID36;
/* disable all channels to allow CPRD updates. */
PWM->PWM_DIS = 0xff;
/* configure clock generator */
PWM->PWM_CLK = PWM_CLK_PREA(prea) | PWM_CLK_DIVA(diva);
/* remember the used resolution (for cropping inputs later) */
pwm_period = res - 1;
/* setup the configured channels */
pwm_chan_mask = 0;
for (unsigned i = 0; i < PWM_CHAN_NUMOF; i++) {
/* configure the use pin */
gpio_init_mux(pwm_chan[i].pin, GPIO_MUX_B);
/* and setup the channel */
pwm_chan_mask |= (1 << pwm_chan[i].hwchan);
PWM->PWM_CH_NUM[pwm_chan[i].hwchan].PWM_CMR = PWM_CMR_CPRE_CLKA;
PWM->PWM_CH_NUM[pwm_chan[i].hwchan].PWM_CPRD = pwm_period;
PWM->PWM_CH_NUM[pwm_chan[i].hwchan].PWM_CDTY = 0;
}
/* enable all configured channels */
PWM->PWM_ENA = pwm_chan_mask;
/* and return the actual configured frequency */
return (CLOCK_CORECLOCK / (1 << prea) / diva / res);
}
uint8_t pwm_channels(pwm_t pwm)
{
(void)pwm;
assert(pwm == PWM_DEV(0));
return (uint8_t)PWM_CHAN_NUMOF;
}
/*
* Update duty-cycle in channel with value.
* If value is larger than resolution set by pwm_init() it is cropped.
*/
void pwm_set(pwm_t pwm, uint8_t channel, uint16_t value)
{
(void)pwm;
assert((pwm == PWM_DEV(0)) && (channel < PWM_CHAN_NUMOF));
/* clip and set new value */
value = (value > pwm_period) ? pwm_period : value;
PWM->PWM_CH_NUM[pwm_chan[channel].hwchan].PWM_CDTYUPD = value;
}
void pwm_poweron(pwm_t pwm)
{
(void)pwm;
assert(pwm == PWM_DEV(0));
PMC->PMC_PCER1 = PMC_PCDR1_PID36;
PWM->PWM_ENA = pwm_chan_mask;
}
void pwm_poweroff(pwm_t pwm)
{
(void)pwm;
assert(pwm == PWM_DEV(0));
PWM->PWM_ENA = 0;
PMC->PMC_PCDR1 = PMC_PCDR1_PID36;
}