Kevin O'Connor fe95ea221b build: Define DECL_CONSTANT mechanism for defining exported constants
Add a DECL_CONSTANT macro to allow the firmware to define constants
that are to be exported to the host during the "identify" phase.  This
replaces the existing hardcoded mechanism of scanning the Kconfig
header file for certain constants.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2016-12-23 17:06:10 -05:00

149 lines
3.7 KiB
C

// SAM3x8e timer interrupt scheduling
//
// Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include "autoconf.h" // CONFIG_CLOCK_FREQ
#include "board/misc.h" // timer_from_us
#include "command.h" // shutdown
#include "irq.h" // irq_disable
#include "sam3x8e.h" // TC0
#include "sched.h" // sched_timer_kick
/****************************************************************
* Low level timer code
****************************************************************/
DECL_CONSTANT(CLOCK_FREQ, CONFIG_CLOCK_FREQ);
DECL_CONSTANT(MCU, "sam3x8e");
// Return the number of clock ticks for a given number of microseconds
uint32_t
timer_from_us(uint32_t us)
{
return us * (CONFIG_CLOCK_FREQ / 1000000);
}
// IRQ handler
void __visible
TC0_Handler(void)
{
TC0->TC_CHANNEL[0].TC_SR; // clear irq pending
irq_disable();
sched_timer_kick();
irq_enable();
}
static void
timer_set(uint32_t value)
{
TC0->TC_CHANNEL[0].TC_RA = value;
}
static void
timer_init(void)
{
TcChannel *tc = &TC0->TC_CHANNEL[0];
// Reset the timer
tc->TC_CCR = TC_CCR_CLKDIS;
tc->TC_IDR = 0xFFFFFFFF;
tc->TC_SR;
// Enable it
PMC->PMC_PCER0 = 1 << ID_TC0;
tc->TC_CMR = TC_CMR_WAVE | TC_CMR_WAVSEL_UP | TC_CMR_TCCLKS_TIMER_CLOCK1;
tc->TC_IER = TC_IER_CPAS;
NVIC_SetPriority(TC0_IRQn, 1);
NVIC_EnableIRQ(TC0_IRQn);
timer_set(1);
tc->TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG;
}
DECL_INIT(timer_init);
// Called by main code once every millisecond. (IRQs disabled.)
void
timer_periodic(void)
{
}
// Return the current time (in absolute clock ticks).
uint32_t
timer_read_time(void)
{
return TC0->TC_CHANNEL[0].TC_CV;
}
#define TIMER_MIN_TICKS 100
// Set the next timer wake time (in absolute clock ticks). Caller
// must disable irqs. The caller should not schedule a time more than
// a few milliseconds in the future.
uint8_t
timer_set_next(uint32_t next)
{
uint32_t cur = timer_read_time();
uint32_t mintime = cur + TIMER_MIN_TICKS;
if (sched_is_before(mintime, next)) {
timer_set(next);
return 0;
}
timer_set(mintime);
return 1;
}
static uint32_t timer_repeat_until;
#define TIMER_IDLE_REPEAT_TICKS timer_from_us(500)
#define TIMER_REPEAT_TICKS timer_from_us(100)
#define TIMER_MIN_TRY_TICKS timer_from_us(1)
#define TIMER_DEFER_REPEAT_TICKS timer_from_us(5)
// Similar to timer_set_next(), but wait for the given time if it is
// in the near future.
uint8_t
timer_try_set_next(uint32_t next)
{
uint32_t now = timer_read_time();
int32_t diff = next - now;
if (diff > (int32_t)TIMER_MIN_TRY_TICKS)
// Schedule next timer normally.
goto done;
// Next timer is in the past or near future - can't reschedule to it
if (likely(sched_is_before(now, timer_repeat_until))) {
// Can run more timers from this irq; briefly allow irqs
irq_enable();
while (diff >= 0) {
// Next timer is in the near future - wait for time to occur
now = timer_read_time();
diff = next - now;
}
irq_disable();
return 0;
}
if (diff < (int32_t)(-timer_from_us(1000)))
goto fail;
// Too many repeat timers from a single interrupt - force a pause
timer_repeat_until = now + TIMER_REPEAT_TICKS;
next = now + TIMER_DEFER_REPEAT_TICKS;
done:
timer_set(next);
return 1;
fail:
shutdown("Rescheduled timer in the past");
}
// Periodic background task that temporarily boosts priority of
// timers. This helps prioritize timers when tasks are idling.
static void
timer_task(void)
{
irq_disable();
timer_repeat_until = timer_read_time() + TIMER_IDLE_REPEAT_TICKS;
irq_enable();
}
DECL_TASK(timer_task);