aboutsummaryrefslogtreecommitdiffstats
path: root/os/hal/platforms/AT91SAM7/pwm_lld.c
diff options
context:
space:
mode:
Diffstat (limited to 'os/hal/platforms/AT91SAM7/pwm_lld.c')
-rw-r--r--os/hal/platforms/AT91SAM7/pwm_lld.c474
1 files changed, 474 insertions, 0 deletions
diff --git a/os/hal/platforms/AT91SAM7/pwm_lld.c b/os/hal/platforms/AT91SAM7/pwm_lld.c
new file mode 100644
index 000000000..f2314e6a3
--- /dev/null
+++ b/os/hal/platforms/AT91SAM7/pwm_lld.c
@@ -0,0 +1,474 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+
+ ---
+
+ 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. */
+/*===========================================================================*/
+
+#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 */
+
+/** @} */