nes-proj/cpu/cc2538/clock.c

262 lines
8.5 KiB
C

/*
* Copyright (c) 2012, Texas Instruments Incorporated - http://www.ti.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
* @{
*
* \defgroup cc2538-clock cc2538 Clock
*
* Implementation of the clock module for the cc2538
*
* To implement the clock functionality, we use the SysTick peripheral on the
* cortex-M3. We run the system clock at a configurable speed and set the
* SysTick to give us 128 interrupts / sec. However, the Sleep Timer counter
* value is used for the number of elapsed ticks in order to avoid a
* significant time drift caused by PM1/2. Contrary to the Sleep Timer, the
* SysTick peripheral is indeed frozen during PM1/2, so adjusting upon wake-up
* a tick counter based on this peripheral would hardly be accurate.
* @{
*
* \file
* Clock driver implementation for the TI cc2538
*/
#include "contiki.h"
#include "systick.h"
#include "reg.h"
#include "cpu.h"
#include "dev/gptimer.h"
#include "dev/sys-ctrl.h"
#include "sys/energest.h"
#include "sys/etimer.h"
#include "sys/rtimer.h"
#include <stdint.h>
/*---------------------------------------------------------------------------*/
#define RTIMER_CLOCK_TICK_RATIO (RTIMER_SECOND / CLOCK_SECOND)
/* Prescaler for GPT0:Timer A used for clock_delay_usec(). */
#if SYS_CTRL_SYS_CLOCK < SYS_CTRL_1MHZ
#error System clock speeds below 1MHz are not supported
#endif
#define PRESCALER_VALUE (SYS_CTRL_SYS_CLOCK / SYS_CTRL_1MHZ - 1)
/* Reload value for SysTick counter */
#if SYS_CTRL_SYS_CLOCK % CLOCK_SECOND
/* Too low clock speeds will lead to reduced accurracy */
#error System clock speed too slow for CLOCK_SECOND, accuracy reduced
#endif
#define RELOAD_VALUE (SYS_CTRL_SYS_CLOCK / CLOCK_SECOND - 1)
static volatile uint64_t rt_ticks_startup = 0, rt_ticks_epoch = 0;
/*---------------------------------------------------------------------------*/
/**
* \brief Arch-specific implementation of clock_init for the cc2538
*
* We initialise the SysTick to fire 128 interrupts per second, giving us a
* value of 128 for CLOCK_SECOND
*
* We also initialise GPT0:Timer A, which is used by clock_delay_usec().
* We use 16-bit range (individual), count-down, one-shot, no interrupts.
* The prescaler is computed according to the system clock in order to get 1
* tick per usec.
*/
void
clock_init(void)
{
REG(SYSTICK_STRELOAD) = RELOAD_VALUE;
/* System clock source, Enable */
REG(SYSTICK_STCTRL) |= SYSTICK_STCTRL_CLK_SRC | SYSTICK_STCTRL_ENABLE;
/* Enable the SysTick Interrupt */
REG(SYSTICK_STCTRL) |= SYSTICK_STCTRL_INTEN;
/*
* Remove the clock gate to enable GPT0 and then initialise it
* We only use GPT0 for clock_delay_usec. We initialise it here so we can
* have it ready when it's needed
*/
REG(SYS_CTRL_RCGCGPT) |= SYS_CTRL_RCGCGPT_GPT0;
/* Make sure GPT0 is off */
REG(GPT_0_BASE + GPTIMER_CTL) = 0;
/* 16-bit */
REG(GPT_0_BASE + GPTIMER_CFG) = 0x04;
/* One-Shot, Count Down, No Interrupts */
REG(GPT_0_BASE + GPTIMER_TAMR) = GPTIMER_TAMR_TAMR_ONE_SHOT;
/* Prescale depending on system clock used */
REG(GPT_0_BASE + GPTIMER_TAPR) = PRESCALER_VALUE;
}
/*---------------------------------------------------------------------------*/
CCIF clock_time_t
clock_time(void)
{
return rt_ticks_startup / RTIMER_CLOCK_TICK_RATIO;
}
/*---------------------------------------------------------------------------*/
void
clock_set_seconds(unsigned long sec)
{
rt_ticks_epoch = (uint64_t)sec * RTIMER_SECOND;
}
/*---------------------------------------------------------------------------*/
CCIF unsigned long
clock_seconds(void)
{
return rt_ticks_epoch / RTIMER_SECOND;
}
/*---------------------------------------------------------------------------*/
void
clock_wait(clock_time_t i)
{
clock_time_t start;
start = clock_time();
while(clock_time() - start < (clock_time_t)i);
}
/*---------------------------------------------------------------------------*/
/*
* Arch-specific implementation of clock_delay_usec for the cc2538
*
* See clock_init() for GPT0 Timer A's configuration
*/
void
clock_delay_usec(uint16_t dt)
{
REG(GPT_0_BASE + GPTIMER_TAILR) = dt;
REG(GPT_0_BASE + GPTIMER_CTL) |= GPTIMER_CTL_TAEN;
/* One-Shot mode: TAEN will be cleared when the timer reaches 0 */
while(REG(GPT_0_BASE + GPTIMER_CTL) & GPTIMER_CTL_TAEN);
}
/*---------------------------------------------------------------------------*/
/**
* \brief Obsolete delay function but we implement it here since some code
* still uses it
*/
void
clock_delay(unsigned int i)
{
clock_delay_usec(i);
}
/*---------------------------------------------------------------------------*/
/**
* \brief Update the software clock ticks and seconds
*
* This function is used to update the software tick counters whenever the
* system clock might have changed, which can occur upon a SysTick ISR or upon
* wake-up from PM1/2.
*
* For the software clock ticks counter, the Sleep Timer counter value is used
* as the base tick value, and extended to a 64-bit value thanks to a detection
* of wraparounds.
*
* For the seconds counter, the changes of the Sleep Timer counter value are
* added to the reference time, which is either the startup time or the value
* passed to clock_set_seconds().
*
* This function polls the etimer process if an etimer has expired.
*/
static void
update_ticks(void)
{
rtimer_clock_t now;
uint64_t prev_rt_ticks_startup, cur_rt_ticks_startup;
uint32_t cur_rt_ticks_startup_hi;
now = RTIMER_NOW();
prev_rt_ticks_startup = rt_ticks_startup;
cur_rt_ticks_startup_hi = prev_rt_ticks_startup >> 32;
if(now < (rtimer_clock_t)prev_rt_ticks_startup) {
cur_rt_ticks_startup_hi++;
}
cur_rt_ticks_startup = (uint64_t)cur_rt_ticks_startup_hi << 32 | now;
rt_ticks_startup = cur_rt_ticks_startup;
rt_ticks_epoch += cur_rt_ticks_startup - prev_rt_ticks_startup;
/*
* Inform the etimer library that the system clock has changed and that an
* etimer might have expired.
*/
if(etimer_pending()) {
etimer_request_poll();
}
}
/*---------------------------------------------------------------------------*/
/**
* \brief Adjust the clock following missed SysTick ISRs
*
* This function is useful when coming out of PM1/2, during which the system
* clock is stopped. We adjust the clock counters like after any SysTick ISR.
*
* \note This function is only meant to be used by lpm_exit(). Applications
* should really avoid calling this
*/
void
clock_adjust(void)
{
/* Halt the SysTick while adjusting */
REG(SYSTICK_STCTRL) &= ~SYSTICK_STCTRL_ENABLE;
update_ticks();
/* Re-Start the SysTick */
REG(SYSTICK_STCTRL) |= SYSTICK_STCTRL_ENABLE;
}
/*---------------------------------------------------------------------------*/
/**
* \brief The clock Interrupt Service Routine
*
* It polls the etimer process if an etimer has expired. It also updates the
* software clock tick and seconds counter.
*/
void
clock_isr(void)
{
ENERGEST_ON(ENERGEST_TYPE_IRQ);
update_ticks();
ENERGEST_OFF(ENERGEST_TYPE_IRQ);
}
/*---------------------------------------------------------------------------*/
/**
* @}
* @}
*/