/* ChibiOS/RT - Copyright (C) 2006,2007,2008,2009,2010, 2011,2012,2013 Giovanni Di Sirio. This file is part of ChibiOS/RT. ChibiOS/RT is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. ChibiOS/RT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . --- A special exception to the GPL can be applied should you wish to distribute a combined work that includes ChibiOS/RT, without being obliged to provide the source code for any proprietary components. See the file exception.txt for full details of how and when the exception can be applied. */ /** * @file templates/pwm_lld.c * @brief PWM Driver subsystem low level driver source template. * * @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 "serial lines 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); PWMD1.chbit = 2; PWMD1.reg = AT91C_BASE_PWMC_CH1; PWMD1.pins = &PWMP2; #endif #if PWM_USE_PWM3 && !defined(__DOXYGEN__) pwmObjectInit(&PWMD3); PWMD1.chbit = 4; PWMD1.reg = AT91C_BASE_PWMC_CH2; PWMD1.pins = &PWMP3; #endif #if PWM_USE_PWM4 && !defined(__DOXYGEN__) pwmObjectInit(&PWMD4); PWMD1.chbit = 8; PWMD1.reg = AT91C_BASE_PWMC_CH3; PWMD1.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. Configusration 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 */ /** @} */