nes-proj/arch/cpu/cc2538/dev/pwm.c

370 lines
12 KiB
C

/*
* 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 <jsanchez@zolertia.com>
* Antonio Lignan <alinan@zolertia.com>
*/
/*---------------------------------------------------------------------------*/
#include "contiki.h"
#include "dev/ioc.h"
#include "dev/gpio.h"
#include "dev/sys-ctrl.h"
#include "dev/pwm.h"
#include "lpm.h"
#include <stdio.h>
#include <stdlib.h>
/*---------------------------------------------------------------------------*/
#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, uint32_t count, 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%%/%lu on GPT%u-%u\n", freq, duty, count, 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) && (!count)) {
REG(gpt_base + GPTIMER_CTL) |= (copy | gpt_dir);
return PWM_SUCCESS;
}
/* Get the peripheral clock and equivalent deassert count, depending on the
* value given by the user, either use the count number of the duty cycle in
* percentage
*/
interval_load = sys_ctrl_get_sys_clock() / freq;
if(duty) {
duty_count = ((interval_load * duty) + 1) / 100;
} else {
duty_count = count;
}
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;
}
/*---------------------------------------------------------------------------*/
/** @} */