/* * Copyright (c) 2015, Zolertia - http://www.zolertia.com * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ /*---------------------------------------------------------------------------*/ /** * \addtogroup cc2538-pwm-driver * @{ * * \file * Driver for the CC2538 PWM * * \author * Javier Sanchez * Antonio Lignan */ /*---------------------------------------------------------------------------*/ #include "contiki.h" #include "dev/ioc.h" #include "dev/gpio.h" #include "dev/sys-ctrl.h" #include "dev/pwm.h" #include "lpm.h" #include #include /*---------------------------------------------------------------------------*/ #define DEBUG 0 #if DEBUG #define PRINTF(...) printf(__VA_ARGS__) #else #define PRINTF(...) #endif /*---------------------------------------------------------------------------*/ #define PWM_GPTIMER_NUM_TO_BASE(x) ((GPT_0_BASE) + ((x) << 12)) /*---------------------------------------------------------------------------*/ static uint8_t pwm_configured(uint8_t timer, uint8_t ab) { uint8_t offset; uint32_t gpt_base; gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer); offset = (ab) ? 4 : 0; if((REG(gpt_base + GPTIMER_TAMR + offset) & GPTIMER_TAMR_TAAMS) && (REG(gpt_base + GPTIMER_TAMR + offset) & GPTIMER_TAMR_TAMR_PERIODIC)) { return 1; } return 0; } /*---------------------------------------------------------------------------*/ static bool permit_pm1(void) { uint8_t timer, ab; for(timer = PWM_TIMER_0; timer <= PWM_TIMER_3; timer++) for(ab = PWM_TIMER_A; ab <= PWM_TIMER_B; ab++) if(pwm_configured(timer, ab) && REG(PWM_GPTIMER_NUM_TO_BASE(timer) + GPTIMER_CTL) & (ab == PWM_TIMER_A ? GPTIMER_CTL_TAEN : GPTIMER_CTL_TBEN)) return false; return true; } /*---------------------------------------------------------------------------*/ int8_t pwm_enable(uint32_t freq, uint8_t duty, uint8_t timer, uint8_t ab) { uint8_t offset = 0; uint32_t interval_load, duty_count, copy; uint32_t gpt_base, gpt_en, gpt_dir; if((freq < PWM_FREQ_MIN) || (freq > PWM_FREQ_MAX) || (duty < PWM_DUTY_MIN) || (duty > PWM_DUTY_MAX) || (timer > PWM_TIMER_MAX) || (timer < PWM_TIMER_MIN)) { PRINTF("PWM: Invalid PWM settings\n"); return PWM_ERROR; } /* GPT0 timer A is used for clock_delay_usec() in clock.c */ if((ab == PWM_TIMER_A) && (timer == PWM_TIMER_0)) { PRINTF("PWM: GPT0 (timer A) is reserved for clock_delay_usec()\n"); return PWM_ERROR; } PRINTF("PWM: F%08luHz: %u%% on GPT%u-%u\n", freq, duty, timer, ab); lpm_register_peripheral(permit_pm1); gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer); gpt_en = GPTIMER_CTL_TAEN; gpt_dir = GPTIMER_CTL_TAPWML; if(ab == PWM_TIMER_B) { offset = 4; gpt_en = GPTIMER_CTL_TBEN; gpt_dir = GPTIMER_CTL_TBPWML; } PRINTF("PWM: GPT_x_BASE 0x%08lX (%u)\n", gpt_base, offset); /* Restore later, ensure GPTIMER_CTL_TxEN and GPTIMER_CTL_TxPWML are clear */ copy = REG(gpt_base + GPTIMER_CTL); copy &= ~(gpt_en | gpt_dir); /* Enable module clock for the GPTx in Active mode */ REG(SYS_CTRL_RCGCGPT) |= (SYS_CTRL_RCGCGPT_GPT0 << timer); /* Enable module clock for the GPTx in Sleep mode */ REG(SYS_CTRL_SCGCGPT) |= (SYS_CTRL_SCGCGPT_GPT0 << timer); /* Enable module clock for the GPTx in PM0, in PM1 and below this doesn't matter */ REG(SYS_CTRL_DCGCGPT) |= (SYS_CTRL_DCGCGPT_GPT0 << timer); /* Stop the timer */ REG(gpt_base + GPTIMER_CTL) = 0; /* Use 16-bit timer */ REG(gpt_base + GPTIMER_CFG) = PWM_GPTIMER_CFG_SPLIT_MODE; /* Configure PWM mode */ REG(gpt_base + GPTIMER_TAMR + offset) = 0; REG(gpt_base + GPTIMER_TAMR + offset) |= GPTIMER_TAMR_TAAMS; REG(gpt_base + GPTIMER_TAMR + offset) |= GPTIMER_TAMR_TAMR_PERIODIC; /* If the duty cycle is zero, leave the GPTIMER configured as PWM to pass a next * configured check, but do nothing else */ if(!duty) { REG(gpt_base + GPTIMER_CTL) |= (copy | gpt_dir); return PWM_SUCCESS; } /* Get the peripheral clock and equivalent deassert count */ interval_load = sys_ctrl_get_sys_clock() / freq; duty_count = ((interval_load * duty) + 1) / 100; PRINTF("PWM: sys %luHz: %lu %lu\n", sys_ctrl_get_sys_clock(), interval_load, duty_count); /* Set the start value (period), count down */ REG(gpt_base + GPTIMER_TAILR + offset) = ((uint16_t *)&interval_load)[0] - 1; /* Set the deassert period */ REG(gpt_base + GPTIMER_TAMATCHR + offset) = ((uint16_t *)&duty_count)[0] - 1; /* Set the prescaler if required */ REG(gpt_base + GPTIMER_TAPR + offset) = ((uint8_t *)&interval_load)[2]; /* Set the prescaler match if required */ REG(gpt_base + GPTIMER_TAPMR + offset) = ((uint8_t *)&duty_count)[2]; /* Restore the register content */ REG(gpt_base + GPTIMER_CTL) |= (copy | gpt_dir); PRINTF("PWM: TnILR %lu ", REG(gpt_base + (GPTIMER_TAILR + offset))); PRINTF("TnMATCHR %lu ", REG(gpt_base + (GPTIMER_TAMATCHR + offset))); PRINTF("TnPR %lu ", REG(gpt_base + (GPTIMER_TAPR + offset))); PRINTF("TnPMR %lu\n", REG(gpt_base + (GPTIMER_TAPMR + offset))); return PWM_SUCCESS; } /*---------------------------------------------------------------------------*/ int8_t pwm_stop(uint8_t timer, uint8_t ab, uint8_t port, uint8_t pin, uint8_t state) { uint32_t gpt_base, gpt_dis; if((ab > PWM_TIMER_B) || (timer < PWM_TIMER_MIN) || (timer > PWM_TIMER_MAX)) { PRINTF("PWM: Invalid PWM values\n"); return PWM_ERROR; } if(!pwm_configured(timer, ab)) { PRINTF("PWM: GPTn not configured as PWM\n"); return PWM_ERROR; } /* CC2538 has 4 ports (A-D) and up to 8 pins (0-7) */ if((port > GPIO_D_NUM) || (pin > 7)) { PRINTF("PWM: Invalid pin/port settings\n"); return PWM_ERROR; } /* CC2538 has 4 ports (A-D) and up to 8 pins (0-7) */ if((state != PWM_OFF_WHEN_STOP) && (state != PWM_ON_WHEN_STOP)) { PRINTF("PWM: Invalid pin state when PWM is halt\n"); return PWM_ERROR; } gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer); gpt_dis = (ab == PWM_TIMER_B) ? GPTIMER_CTL_TBEN : GPTIMER_CTL_TAEN; REG(gpt_base + GPTIMER_CTL) &= ~gpt_dis; /* Configure the port/pin as GPIO, input */ ioc_set_over(port, pin, IOC_OVERRIDE_DIS); GPIO_SOFTWARE_CONTROL(GPIO_PORT_TO_BASE(port), GPIO_PIN_MASK(pin)); GPIO_SET_OUTPUT(GPIO_PORT_TO_BASE(port), GPIO_PIN_MASK(pin)); if(state) { GPIO_SET_PIN(GPIO_PORT_TO_BASE(port), GPIO_PIN_MASK(pin)); } else { GPIO_CLR_PIN(GPIO_PORT_TO_BASE(port), GPIO_PIN_MASK(pin)); } PRINTF("PWM: OFF -> Timer %u (%u)\n", timer, ab); return PWM_SUCCESS; } /*---------------------------------------------------------------------------*/ int8_t pwm_start(uint8_t timer, uint8_t ab, uint8_t port, uint8_t pin) { uint32_t gpt_base, gpt_en, gpt_sel; if((ab > PWM_TIMER_B) || (timer < PWM_TIMER_MIN) || (timer > PWM_TIMER_MAX)) { PRINTF("PWM: Invalid PWM values\n"); return PWM_ERROR; } if(!pwm_configured(timer, ab)) { PRINTF("PWM: GPTn not configured as PWM\n"); return PWM_ERROR; } /* CC2538 has 4 ports (A-D) and up to 8 pins (0-7) */ if((port > GPIO_D_NUM) || (pin > 7)) { PRINTF("PWM: Invalid pin/port settings\n"); return PWM_ERROR; } /* Map to given port/pin */ gpt_sel = IOC_PXX_SEL_GPT0_ICP1 + (timer * 2); if(ab == PWM_TIMER_B) { gpt_sel++; } ioc_set_sel(port, pin, gpt_sel); ioc_set_over(port, pin, IOC_OVERRIDE_OE); GPIO_PERIPHERAL_CONTROL(GPIO_PORT_TO_BASE(port), GPIO_PIN_MASK(pin)); gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer); gpt_en = (ab == PWM_TIMER_B) ? GPTIMER_CTL_TBEN : GPTIMER_CTL_TAEN; REG(gpt_base + GPTIMER_CTL) |= gpt_en; PRINTF("PWM: ON -> Timer %u (%u) IOC_PXX_SEL_GPTx_IPCx 0x%08lX\n", timer, ab, gpt_sel); return PWM_SUCCESS; } /*---------------------------------------------------------------------------*/ int8_t pwm_set_direction(uint8_t timer, uint8_t ab, uint8_t dir) { uint32_t gpt_base, gpt_dir; if((ab > PWM_TIMER_B) || (timer < PWM_TIMER_MIN) || (timer > PWM_TIMER_MAX) || (dir > PWM_SIGNAL_INVERTED)) { PRINTF("PWM: Invalid PWM values\n"); return PWM_ERROR; } if(!pwm_configured(timer, ab)) { PRINTF("PWM: GPTn not configured as PWM\n"); return PWM_ERROR; } gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer); gpt_dir = (ab == PWM_TIMER_B) ? GPTIMER_CTL_TBPWML : GPTIMER_CTL_TAPWML; if(dir) { REG(gpt_base + GPTIMER_CTL) |= gpt_dir; } else { REG(gpt_base + GPTIMER_CTL) &= ~gpt_dir; } PRINTF("PWM: Signal direction (%u) -> Timer %u (%u)\n", dir, timer, ab); return PWM_SUCCESS; } /*---------------------------------------------------------------------------*/ int8_t pwm_toggle_direction(uint8_t timer, uint8_t ab) { uint32_t gpt_base, gpt_dir; if((ab > PWM_TIMER_B) || (timer < PWM_TIMER_MIN) || (timer > PWM_TIMER_MAX)) { PRINTF("PWM: Invalid PWM values\n"); return PWM_ERROR; } if(!pwm_configured(timer, ab)) { PRINTF("PWM: GPTn not configured as PWM\n"); return PWM_ERROR; } gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer); gpt_dir = (ab == PWM_TIMER_B) ? GPTIMER_CTL_TBPWML : GPTIMER_CTL_TAPWML; if(REG(gpt_base + GPTIMER_CTL) & gpt_dir) { REG(gpt_base + GPTIMER_CTL) &= ~gpt_dir; } else { REG(gpt_base + GPTIMER_CTL) |= gpt_dir; } PRINTF("PWM: direction toggled -> Timer %u (%u)\n", timer, ab); return PWM_SUCCESS; } /*---------------------------------------------------------------------------*/ int8_t pwm_disable(uint8_t timer, uint8_t ab, uint8_t port, uint8_t pin) { uint32_t gpt_base; uint8_t offset = (ab == PWM_TIMER_B) ? 4 : 0; gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer); if((ab > PWM_TIMER_B) || (timer < PWM_TIMER_MIN) || (timer > PWM_TIMER_MAX)) { PRINTF("PWM: Invalid PWM values\n"); return PWM_ERROR; } /* CC2538 has 4 ports (A-D) and up to 8 pins (0-7) */ if((port > GPIO_D_NUM) || (pin > 7)) { PRINTF("PWM: Invalid pin/port settings\n"); return PWM_ERROR; } if(!pwm_configured(timer, ab)) { PRINTF("PWM: GPTn not configured as PWM\n"); return PWM_ERROR; } /* Stop the PWM */ pwm_stop(timer, ab, port, pin, PWM_OFF_WHEN_STOP); /* Disable the PWM mode */ REG(gpt_base + (GPTIMER_TAMR + offset)) = 0; /* Restart the interval load and deassert values */ REG(gpt_base + (GPTIMER_TAILR + offset)) = 0; REG(gpt_base + (GPTIMER_TAMATCHR + offset)) = 0; /* Configure the port/pin as GPIO, input */ ioc_set_over(port, pin, IOC_OVERRIDE_DIS); GPIO_SOFTWARE_CONTROL(GPIO_PORT_TO_BASE(port), GPIO_PIN_MASK(pin)); GPIO_SET_INPUT(GPIO_PORT_TO_BASE(port), GPIO_PIN_MASK(pin)); return PWM_SUCCESS; } /*---------------------------------------------------------------------------*/ /** @} */