468 lines
15 KiB
C
468 lines
15 KiB
C
/*
|
|
ChibiOS/RT - Copyright (C) 2006-2013 Giovanni Di Sirio
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
/*
|
|
This file has been contributed by:
|
|
Andrew Hannam aka inmarket.
|
|
*/
|
|
|
|
/**
|
|
* @file AT91SAM7/pwm_lld.c
|
|
* @brief AT91SAM7 PWM Driver subsystem low level driver source.
|
|
*
|
|
* @addtogroup PWM
|
|
* @{
|
|
*/
|
|
|
|
#include "ch.h"
|
|
#include "hal.h"
|
|
|
|
#if HAL_USE_PWM || defined(__DOXYGEN__)
|
|
|
|
/*===========================================================================*/
|
|
/* Driver local definitions. */
|
|
/*===========================================================================*/
|
|
|
|
#ifdef UNUSED
|
|
#elif defined(__GNUC__)
|
|
# define UNUSED(x) UNUSED_ ## x __attribute__((unused))
|
|
#elif defined(__LCLINT__)
|
|
# define UNUSED(x) /*@unused@*/ x
|
|
#else
|
|
# define UNUSED(x) x
|
|
#endif
|
|
|
|
#define PWMC_M ((AT91S_PWMC *)AT91C_PWMC_MR)
|
|
|
|
#define PWM_MCK_MASK 0x0F00
|
|
#define PWM_MCK_SHIFT 8
|
|
|
|
typedef struct pindef {
|
|
uint32_t portpin; /* Set to 0 if this pin combination is invalid */
|
|
AT91S_PIO *pio;
|
|
AT91_REG *perab;
|
|
} pindef_t;
|
|
|
|
typedef struct pwmpindefs {
|
|
pindef_t pin[3];
|
|
} pwmpindefs_t;
|
|
|
|
/*===========================================================================*/
|
|
/* Driver exported variables. */
|
|
/*===========================================================================*/
|
|
|
|
#if PWM_USE_PWM1 && !defined(__DOXYGEN__)
|
|
PWMDriver PWMD1;
|
|
#endif
|
|
|
|
#if PWM_USE_PWM2 && !defined(__DOXYGEN__)
|
|
PWMDriver PWMD2;
|
|
#endif
|
|
|
|
#if PWM_USE_PWM3 && !defined(__DOXYGEN__)
|
|
PWMDriver PWMD3;
|
|
#endif
|
|
|
|
#if PWM_USE_PWM4 && !defined(__DOXYGEN__)
|
|
PWMDriver PWMD4;
|
|
#endif
|
|
|
|
/*===========================================================================*/
|
|
/* Driver local variables and types. */
|
|
/*===========================================================================*/
|
|
|
|
#if (SAM7_PLATFORM == SAM7S64) || (SAM7_PLATFORM == SAM7S128) || \
|
|
(SAM7_PLATFORM == SAM7S256) || (SAM7_PLATFORM == SAM7S512)
|
|
|
|
#if PWM_USE_PWM1 && !defined(__DOXYGEN__)
|
|
static const pwmpindefs_t PWMP1 = {{
|
|
{ AT91C_PA0_PWM0 , AT91C_BASE_PIOA, &AT91C_BASE_PIOA->PIO_ASR },
|
|
{ AT91C_PA11_PWM0, AT91C_BASE_PIOA, &AT91C_BASE_PIOA->PIO_BSR },
|
|
{ AT91C_PA23_PWM0, AT91C_BASE_PIOA, &AT91C_BASE_PIOA->PIO_BSR },
|
|
}};
|
|
#endif
|
|
#if PWM_USE_PWM2 && !defined(__DOXYGEN__)
|
|
static const pwmpindefs_t PWMP2 = {{
|
|
{ AT91C_PA1_PWM1 , AT91C_BASE_PIOA, &AT91C_BASE_PIOA->PIO_ASR },
|
|
{ AT91C_PA12_PWM1, AT91C_BASE_PIOA, &AT91C_BASE_PIOA->PIO_BSR },
|
|
{ AT91C_PA24_PWM1, AT91C_BASE_PIOA, &AT91C_BASE_PIOA->PIO_BSR },
|
|
}};
|
|
#endif
|
|
#if PWM_USE_PWM3 && !defined(__DOXYGEN__)
|
|
static const pwmpindefs_t PWMP3 = {{
|
|
{ AT91C_PA2_PWM2 , AT91C_BASE_PIOA, &AT91C_BASE_PIOA->PIO_ASR },
|
|
{ AT91C_PA13_PWM2, AT91C_BASE_PIOA, &AT91C_BASE_PIOA->PIO_BSR },
|
|
{ AT91C_PA25_PWM2, AT91C_BASE_PIOA, &AT91C_BASE_PIOA->PIO_BSR },
|
|
}};
|
|
#endif
|
|
#if PWM_USE_PWM4 && !defined(__DOXYGEN__)
|
|
static const pwmpindefs_t PWMP4 = {{
|
|
{ AT91C_PA7_PWM3 , AT91C_BASE_PIOA, &AT91C_BASE_PIOA->PIO_BSR },
|
|
{ AT91C_PA14_PWM3, AT91C_BASE_PIOA, &AT91C_BASE_PIOA->PIO_BSR },
|
|
{ 0, 0, 0 },
|
|
}};
|
|
#endif
|
|
|
|
#elif (SAM7_PLATFORM == SAM7X128) || (SAM7_PLATFORM == SAM7X256) || \
|
|
(SAM7_PLATFORM == SAM7X512)
|
|
|
|
#if PWM_USE_PWM1 && !defined(__DOXYGEN__)
|
|
static const pwmpindefs_t PWMP1 = {{
|
|
{ AT91C_PB19_PWM0, AT91C_BASE_PIOB, &AT91C_BASE_PIOB->PIO_ASR },
|
|
{ AT91C_PB27_PWM0, AT91C_BASE_PIOB, &AT91C_BASE_PIOB->PIO_BSR },
|
|
{ 0, 0, 0 },
|
|
}};
|
|
#endif
|
|
#if PWM_USE_PWM2 && !defined(__DOXYGEN__)
|
|
static const pwmpindefs_t PWMP2 = {{
|
|
{ AT91C_PB20_PWM1, AT91C_BASE_PIOB, &AT91C_BASE_PIOB->PIO_ASR },
|
|
{ AT91C_PB28_PWM1, AT91C_BASE_PIOB, &AT91C_BASE_PIOB->PIO_BSR },
|
|
{ 0, 0, 0 },
|
|
}};
|
|
#endif
|
|
#if PWM_USE_PWM3 && !defined(__DOXYGEN__)
|
|
static const pwmpindefs_t PWMP3 = {{
|
|
{ AT91C_PB21_PWM2, AT91C_BASE_PIOB, &AT91C_BASE_PIOB->PIO_ASR },
|
|
{ AT91C_PB29_PWM2, AT91C_BASE_PIOB, &AT91C_BASE_PIOB->PIO_BSR },
|
|
{ 0, 0, 0 },
|
|
}};
|
|
#endif
|
|
#if PWM_USE_PWM4 && !defined(__DOXYGEN__)
|
|
static const pwmpindefs_t PWMP4 = {{
|
|
{ AT91C_PB22_PWM3, AT91C_BASE_PIOB, &AT91C_BASE_PIOB->PIO_ASR },
|
|
{ AT91C_PB30_PWM3, AT91C_BASE_PIOB, &AT91C_BASE_PIOB->PIO_BSR },
|
|
{ 0, 0, 0 },
|
|
}};
|
|
#endif
|
|
|
|
#else
|
|
#error "PWM pins not defined for this SAM7 version"
|
|
#endif
|
|
|
|
#if PWM_USE_PWM2 && !defined(__DOXYGEN__)
|
|
PWMDriver PWMD2;
|
|
#endif
|
|
|
|
#if PWM_USE_PWM3 && !defined(__DOXYGEN__)
|
|
PWMDriver PWMD3;
|
|
#endif
|
|
|
|
#if PWM_USE_PWM4 && !defined(__DOXYGEN__)
|
|
PWMDriver PWMD4;
|
|
#endif
|
|
|
|
/*===========================================================================*/
|
|
/* Driver local functions. */
|
|
/*===========================================================================*/
|
|
|
|
/*===========================================================================*/
|
|
/* Driver interrupt handlers. */
|
|
/*===========================================================================*/
|
|
|
|
#if defined(__GNUC__)
|
|
__attribute__((noinline))
|
|
#endif
|
|
/**
|
|
* @brief Common IRQ handler.
|
|
*/
|
|
static void pwm_lld_serve_interrupt(void) {
|
|
uint32_t isr;
|
|
|
|
isr = PWMC_M->PWMC_ISR;
|
|
#if PWM_USE_PWM1
|
|
if ((isr & 1) && PWMD1.config->channels[0].callback)
|
|
PWMD1.config->channels[0].callback(&PWMD1);
|
|
#endif
|
|
#if PWM_USE_PWM2
|
|
if ((isr & 2) && PWMD2.config->channels[0].callback)
|
|
PWMD2.config->channels[0].callback(&PWMD2);
|
|
#endif
|
|
#if PWM_USE_PWM3
|
|
if ((isr & 4) && PWMD3.config->channels[0].callback)
|
|
PWMD3.config->channels[0].callback(&PWMD3);
|
|
#endif
|
|
#if PWM_USE_PWM4
|
|
if ((isr & 8) && PWMD4.config->channels[0].callback)
|
|
PWMD4.config->channels[0].callback(&PWMD4);
|
|
#endif
|
|
}
|
|
|
|
CH_IRQ_HANDLER(PWMIrqHandler) {
|
|
CH_IRQ_PROLOGUE();
|
|
pwm_lld_serve_interrupt();
|
|
AT91C_BASE_AIC->AIC_EOICR = 0;
|
|
CH_IRQ_EPILOGUE();
|
|
}
|
|
|
|
/*===========================================================================*/
|
|
/* Driver exported functions. */
|
|
/*===========================================================================*/
|
|
|
|
/**
|
|
* @brief Low level PWM driver initialization.
|
|
*
|
|
* @notapi
|
|
*/
|
|
void pwm_lld_init(void) {
|
|
|
|
/* Driver initialization.*/
|
|
#if PWM_USE_PWM1 && !defined(__DOXYGEN__)
|
|
pwmObjectInit(&PWMD1);
|
|
PWMD1.chbit = 1;
|
|
PWMD1.reg = AT91C_BASE_PWMC_CH0;
|
|
PWMD1.pins = &PWMP1;
|
|
#endif
|
|
|
|
#if PWM_USE_PWM2 && !defined(__DOXYGEN__)
|
|
pwmObjectInit(&PWMD2);
|
|
PWMD2.chbit = 2;
|
|
PWMD2.reg = AT91C_BASE_PWMC_CH1;
|
|
PWMD2.pins = &PWMP2;
|
|
#endif
|
|
|
|
#if PWM_USE_PWM3 && !defined(__DOXYGEN__)
|
|
pwmObjectInit(&PWMD3);
|
|
PWMD3.chbit = 4;
|
|
PWMD3.reg = AT91C_BASE_PWMC_CH2;
|
|
PWMD3.pins = &PWMP3;
|
|
#endif
|
|
|
|
#if PWM_USE_PWM4 && !defined(__DOXYGEN__)
|
|
pwmObjectInit(&PWMD4);
|
|
PWMD4.chbit = 8;
|
|
PWMD4.reg = AT91C_BASE_PWMC_CH3;
|
|
PWMD4.pins = &PWMP4;
|
|
#endif
|
|
|
|
/* Turn on PWM in the power management controller */
|
|
AT91C_BASE_PMC->PMC_PCER = (1 << AT91C_ID_PWMC);
|
|
|
|
/* Setup interrupt handler */
|
|
PWMC_M->PWMC_IDR = 0xFFFFFFFF;
|
|
AIC_ConfigureIT(AT91C_ID_PWMC,
|
|
AT91C_AIC_SRCTYPE_HIGH_LEVEL | AT91SAM7_PWM_PRIORITY,
|
|
PWMIrqHandler);
|
|
AIC_EnableIT(AT91C_ID_PWMC);
|
|
}
|
|
|
|
/**
|
|
* @brief Configures and activates the PWM peripheral.
|
|
*
|
|
* @param[in] pwmp pointer to the @p PWMDriver object
|
|
*
|
|
* @notapi
|
|
*/
|
|
void pwm_lld_start(PWMDriver *pwmp) {
|
|
uint32_t mode, mr, div, pre;
|
|
|
|
/* Steps:
|
|
1. Turn the IO pin to a PWM output
|
|
2. Configuration of Clock if DIVA or DIVB used
|
|
3. Selection of the clock for each channel (CPRE field in the PWM_CMRx register)
|
|
4. Configuration of the waveform alignment for each channel (CALG field in the PWM_CMRx register)
|
|
5. Configuration of the output waveform polarity for each channel (CPOL in the PWM_CMRx register)
|
|
6. Configuration of the period for each channel (CPRD in the PWM_CPRDx register). Writing in
|
|
PWM_CPRDx Register is possible while the channel is disabled. After validation of the
|
|
channel, the user must use PWM_CUPDx Register to update PWM_CPRDx
|
|
7. Enable Interrupts (Writing CHIDx in the PWM_IER register)
|
|
*/
|
|
|
|
/* Make sure it is off first */
|
|
pwm_lld_disable_channel(pwmp, 0);
|
|
|
|
/* Configuration.*/
|
|
mode = pwmp->config->channels[0].mode;
|
|
|
|
/* Step 1 */
|
|
if (mode & PWM_OUTPUT_PIN1) {
|
|
pwmp->pins->pin[0].perab[0] = pwmp->pins->pin[0].portpin; /* Select A or B peripheral */
|
|
pwmp->pins->pin[0].pio->PIO_PDR = pwmp->pins->pin[0].portpin; /* Turn PIO into PWM output */
|
|
pwmp->pins->pin[0].pio->PIO_MDDR = pwmp->pins->pin[0].portpin; /* Turn off PIO multi-drive */
|
|
if (mode & PWM_DISABLEPULLUP_PIN1)
|
|
pwmp->pins->pin[0].pio->PIO_PPUDR = pwmp->pins->pin[0].portpin; /* Turn off PIO pullup */
|
|
else
|
|
pwmp->pins->pin[0].pio->PIO_PPUER = pwmp->pins->pin[0].portpin; /* Turn on PIO pullup */
|
|
}
|
|
if (mode & PWM_OUTPUT_PIN2) {
|
|
pwmp->pins->pin[1].perab[0] = pwmp->pins->pin[1].portpin;
|
|
pwmp->pins->pin[1].pio->PIO_PDR = pwmp->pins->pin[1].portpin;
|
|
pwmp->pins->pin[1].pio->PIO_MDDR = pwmp->pins->pin[1].portpin;
|
|
if (mode & PWM_DISABLEPULLUP_PIN2)
|
|
pwmp->pins->pin[1].pio->PIO_PPUDR = pwmp->pins->pin[1].portpin;
|
|
else
|
|
pwmp->pins->pin[1].pio->PIO_PPUER = pwmp->pins->pin[1].portpin;
|
|
}
|
|
if ((mode & PWM_OUTPUT_PIN3) && pwmp->pins->pin[2].portpin) {
|
|
pwmp->pins->pin[2].perab[0] = pwmp->pins->pin[2].portpin;
|
|
pwmp->pins->pin[2].pio->PIO_PDR = pwmp->pins->pin[2].portpin;
|
|
pwmp->pins->pin[2].pio->PIO_MDDR = pwmp->pins->pin[2].portpin;
|
|
if (mode & PWM_DISABLEPULLUP_PIN3)
|
|
pwmp->pins->pin[2].pio->PIO_PPUDR = pwmp->pins->pin[2].portpin;
|
|
else
|
|
pwmp->pins->pin[2].pio->PIO_PPUER = pwmp->pins->pin[2].portpin;
|
|
}
|
|
|
|
/* Step 2 */
|
|
if ((mode & PWM_MCK_MASK) == PWM_MCK_DIV_CLKA) {
|
|
if (!pwmp->config->frequency) {
|
|
/* As slow as we go */
|
|
PWMC_M->PWMC_MR = (PWMC_M->PWMC_MR & 0xFFFF0000) | (10 << 8) | (255 << 0);
|
|
} else if (pwmp->config->frequency > MCK) {
|
|
/* Just use MCLK */
|
|
mode &= ~PWM_MCK_MASK;
|
|
} else {
|
|
div = MCK / pwmp->config->frequency;
|
|
if (mode & PWM_OUTPUT_CENTER) div >>= 1;
|
|
for(pre = 0; div > 255 && pre < 10; pre++) div >>= 1;
|
|
if (div > 255) div = 255;
|
|
PWMC_M->PWMC_MR = (PWMC_M->PWMC_MR & 0xFFFF0000) | (pre << 8) | (div << 0);
|
|
}
|
|
} else if ((mode & PWM_MCK_MASK) == PWM_MCK_DIV_CLKB) {
|
|
if (!pwmp->config->frequency) {
|
|
/* As slow as we go */
|
|
PWMC_M->PWMC_MR = (PWMC_M->PWMC_MR & 0x0000FFFF) | (10 << 24) | (255 << 16);
|
|
} else if (pwmp->config->frequency > MCK) {
|
|
/* Just use MCLK */
|
|
mode &= ~PWM_MCK_MASK;
|
|
} else {
|
|
div = MCK / pwmp->config->frequency;
|
|
if (mode & PWM_OUTPUT_CENTER) div >>= 1;
|
|
for(pre = 0; div > 255 && pre < 10; pre++) div >>= 1;
|
|
if (div > 255) div = 255;
|
|
PWMC_M->PWMC_MR = (PWMC_M->PWMC_MR & 0x0000FFFF) | (pre << 24) | (div << 16);
|
|
}
|
|
}
|
|
|
|
/* Step 3 -> 5 */
|
|
mr = (mode & PWM_MCK_MASK) >> PWM_MCK_SHIFT;
|
|
if (mode & PWM_OUTPUT_CENTER) mr |= AT91C_PWMC_CALG;
|
|
if (mode & PWM_OUTPUT_ACTIVE_HIGH) mr |= AT91C_PWMC_CPOL;
|
|
pwmp->reg->PWMC_CMR = mr;
|
|
|
|
/* Step 6 */
|
|
pwmp->reg->PWMC_CPRDR = pwmp->period;
|
|
|
|
/* Step 7 */
|
|
if (pwmp->config->channels[0].callback)
|
|
PWMC_M->PWMC_IER = pwmp->chbit;
|
|
}
|
|
|
|
/**
|
|
* @brief Deactivates the PWM peripheral.
|
|
*
|
|
* @param[in] pwmp pointer to the @p PWMDriver object
|
|
*
|
|
* @notapi
|
|
*/
|
|
void pwm_lld_stop(PWMDriver *pwmp) {
|
|
/* Make sure it is off */
|
|
pwm_lld_disable_channel(pwmp, 0);
|
|
|
|
/* Turn the pin back to a PIO pin - we have forgotten pull-up and multi-drive state for the pin though */
|
|
if (pwmp->config->channels[0].mode & PWM_OUTPUT_PIN1)
|
|
pwmp->pins->pin[0].pio->PIO_PER = pwmp->pins->pin[0].portpin;
|
|
if (pwmp->config->channels[0].mode & PWM_OUTPUT_PIN2)
|
|
pwmp->pins->pin[1].pio->PIO_PER = pwmp->pins->pin[1].portpin;
|
|
if ((pwmp->config->channels[0].mode & PWM_OUTPUT_PIN3) && pwmp->pins->pin[2].portpin)
|
|
pwmp->pins->pin[2].pio->PIO_PER = pwmp->pins->pin[2].portpin;
|
|
}
|
|
|
|
/**
|
|
* @brief Changes the period the PWM peripheral.
|
|
* @details This function changes the period of a PWM unit that has already
|
|
* been activated using @p pwmStart().
|
|
* @pre The PWM unit must have been activated using @p pwmStart().
|
|
* @post The PWM unit period is changed to the new value.
|
|
* @note The function has effect at the next cycle start.
|
|
* @note If a period is specified that is shorter than the pulse width
|
|
* programmed in one of the channels then the behavior is not
|
|
* guaranteed.
|
|
*
|
|
* @param[in] pwmp pointer to a @p PWMDriver object
|
|
* @param[in] period new cycle time in ticks
|
|
*
|
|
* @notapi
|
|
*/
|
|
void pwm_lld_change_period(PWMDriver *pwmp, pwmcnt_t period) {
|
|
pwmp->period = period;
|
|
|
|
if (PWMC_M->PWMC_SR & pwmp->chbit) {
|
|
pwmp->reg->PWMC_CMR |= AT91C_PWMC_CPD;
|
|
pwmp->reg->PWMC_CUPDR = period;
|
|
} else {
|
|
pwmp->reg->PWMC_CPRDR = period;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Enables a PWM channel.
|
|
* @pre The PWM unit must have been activated using @p pwmStart().
|
|
* @post The channel is active using the specified configuration.
|
|
* @note Depending on the hardware implementation this function has
|
|
* effect starting on the next cycle (recommended implementation)
|
|
* or immediately (fallback implementation).
|
|
*
|
|
* @param[in] pwmp pointer to a @p PWMDriver object
|
|
* @param[in] channel PWM channel identifier (0...PWM_CHANNELS-1)
|
|
* @param[in] width PWM pulse width as clock pulses number
|
|
*
|
|
* @notapi
|
|
*/
|
|
void pwm_lld_enable_channel(PWMDriver *pwmp,
|
|
pwmchannel_t UNUSED(channel),
|
|
pwmcnt_t width) {
|
|
/*
|
|
6. Configuration of the duty cycle for each channel (CDTY in the PWM_CDTYx register).
|
|
Writing in PWM_CDTYx Register is possible while the channel is disabled. After validation of
|
|
the channel, the user must use PWM_CUPDx Register to update PWM_CDTYx.
|
|
7. Enable the PWM channel (Writing CHIDx in the PWM_ENA register)
|
|
*/
|
|
|
|
/* Step 6 */
|
|
if (PWMC_M->PWMC_SR & pwmp->chbit) {
|
|
pwmp->reg->PWMC_CMR &= ~AT91C_PWMC_CPD;
|
|
pwmp->reg->PWMC_CUPDR = width;
|
|
} else {
|
|
pwmp->reg->PWMC_CDTYR = width;
|
|
PWMC_M->PWMC_ENA = pwmp->chbit;
|
|
}
|
|
|
|
/* Step 7 */
|
|
PWMC_M->PWMC_ENA = pwmp->chbit;
|
|
}
|
|
|
|
/**
|
|
* @brief Disables a PWM channel.
|
|
* @pre The PWM unit must have been activated using @p pwmStart().
|
|
* @post The channel is disabled and its output line returned to the
|
|
* idle state.
|
|
* @note Depending on the hardware implementation this function has
|
|
* effect starting on the next cycle (recommended implementation)
|
|
* or immediately (fallback implementation).
|
|
*
|
|
* @param[in] pwmp pointer to a @p PWMDriver object
|
|
* @param[in] channel PWM channel identifier (0...PWM_CHANNELS-1)
|
|
*
|
|
* @notapi
|
|
*/
|
|
void pwm_lld_disable_channel(PWMDriver *pwmp, pwmchannel_t UNUSED(channel)) {
|
|
PWMC_M->PWMC_IDR = pwmp->chbit;
|
|
PWMC_M->PWMC_DIS = pwmp->chbit;
|
|
}
|
|
|
|
#endif /* HAL_USE_PWM */
|
|
|
|
/** @} */
|