266 lines
7.0 KiB
C
266 lines
7.0 KiB
C
/* Copyright 2016 The Chromium OS Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
/* Hardware timers driver for ISH High Precision Event Timers (HPET) */
|
|
|
|
#include "console.h"
|
|
#include "hpet.h"
|
|
#include "hwtimer.h"
|
|
#include "timer.h"
|
|
#include "registers.h"
|
|
#include "task.h"
|
|
#include "util.h"
|
|
|
|
#define CPUTS(outstr) cputs(CC_CLOCK, outstr)
|
|
#define CPRINTS(format, args...) cprints(CC_CLOCK, format, ## args)
|
|
#define CPRINTF(format, args...) cprintf(CC_CLOCK, format, ## args)
|
|
|
|
static uint32_t last_deadline;
|
|
|
|
/*
|
|
* The ISH hardware needs at least 25 ticks of leeway to arms the timer.
|
|
* ISH4/5 are the slowest with 32kHz timers, so we wait at least 800us when
|
|
* scheduling events in the future
|
|
*/
|
|
#define MINIMUM_EVENT_DELAY_US 800
|
|
|
|
/*
|
|
* ISH HPET timer HW has latency for interrupt, on ISH5, this latency is about
|
|
* 3 ticks, defined this configuration to calibrate the 'last_deadline' which is
|
|
* updated in event timer interrupt ISR. Without this calibration, we could
|
|
* get negative sleep time in idle task for low power sleep process.
|
|
*/
|
|
#define HPET_INT_LATENCY_TICKS 3
|
|
|
|
/* Scaling helper methods for different ISH chip variants */
|
|
#ifdef CHIP_FAMILY_ISH3
|
|
#define CLOCK_FACTOR 12
|
|
BUILD_ASSERT(CLOCK_FACTOR * SECOND == ISH_HPET_CLK_FREQ);
|
|
|
|
static inline uint64_t scale_us2ticks(uint64_t us)
|
|
{
|
|
return us * CLOCK_FACTOR;
|
|
}
|
|
|
|
static inline uint32_t scale_us2ticks_32(uint32_t us)
|
|
{
|
|
/* no optimization for ISH3 */
|
|
return us * CLOCK_FACTOR;
|
|
}
|
|
|
|
static inline uint64_t scale_ticks2us(uint64_t ticks)
|
|
{
|
|
return ticks / CLOCK_FACTOR;
|
|
}
|
|
|
|
static inline void wait_while_settling(uint32_t mask)
|
|
{
|
|
/* Do nothing on ISH3, only ISH4 and ISH5 need settling */
|
|
}
|
|
|
|
#elif defined(CHIP_FAMILY_ISH4) || defined(CHIP_FAMILY_ISH5)
|
|
#define CLOCK_SCALE_BITS 15
|
|
BUILD_ASSERT(BIT(CLOCK_SCALE_BITS) == ISH_HPET_CLK_FREQ);
|
|
|
|
/* Slow version, for 64-bit precision */
|
|
static inline uint64_t scale_us2ticks(uint64_t us)
|
|
{
|
|
/* ticks = us * ISH_HPET_CLK_FREQ / SECOND */
|
|
|
|
return (us << CLOCK_SCALE_BITS) / SECOND;
|
|
}
|
|
|
|
/* Fast version, for 32-bit precision */
|
|
static inline uint32_t scale_us2ticks_32(uint32_t us)
|
|
{
|
|
/*
|
|
* GCC optimizes this shift/divide into multiplication by a
|
|
* magic number
|
|
*/
|
|
return (us << CLOCK_SCALE_BITS) / SECOND;
|
|
}
|
|
|
|
static inline uint64_t scale_ticks2us(uint64_t ticks)
|
|
{
|
|
return (ticks * SECOND) >> CLOCK_SCALE_BITS;
|
|
}
|
|
|
|
/*
|
|
* HPET Control & Status register may indicate that a value which has
|
|
* been written still needs propogated by hardware. Before updating
|
|
* HPET_TIMER_CONF_CAP(N), be sure to wait on the value settling with
|
|
* the corresponding mask (see hpet.h).
|
|
*/
|
|
static inline void wait_while_settling(uint32_t mask)
|
|
{
|
|
/* Wait for timer settings to settle ~ 150us */
|
|
while (HPET_CTRL_STATUS & mask)
|
|
continue;
|
|
}
|
|
|
|
#else
|
|
#error "Must define CHIP_FAMILY_ISH(3|4|5)"
|
|
#endif
|
|
|
|
/*
|
|
* The 64-bit read on a 32-bit chip can tear during the read. Ensure that the
|
|
* value returned for 64-bit didn't rollover while we were reading it.
|
|
*/
|
|
static inline uint64_t read_main_timer(void)
|
|
{
|
|
timestamp_t t;
|
|
uint32_t hi;
|
|
|
|
/* need check main counter if valid when exit low power TCG mode */
|
|
wait_while_settling(HPET_MAIN_COUNTER_VALID);
|
|
|
|
do {
|
|
t.le.hi = HPET_MAIN_COUNTER_64_HI;
|
|
t.le.lo = HPET_MAIN_COUNTER_64_LO;
|
|
hi = HPET_MAIN_COUNTER_64_HI;
|
|
} while (t.le.hi != hi);
|
|
|
|
return t.val;
|
|
}
|
|
|
|
void __hw_clock_event_set(uint32_t deadline)
|
|
{
|
|
uint32_t remaining_us;
|
|
uint32_t current_us;
|
|
uint64_t current_ticks;
|
|
|
|
/* 'current_ticks' is the current absolute 64bit HW timer counter */
|
|
current_ticks = read_main_timer();
|
|
|
|
/*
|
|
* 'current_us' is the low 32bit part of current time in 64bit micro
|
|
* seconds format, it's can express 2^32 micro seconds in maximum.
|
|
*/
|
|
current_us = scale_ticks2us(current_ticks);
|
|
|
|
/*
|
|
* To ensure HW has enough time to react to the new timer value,
|
|
* we make remaining time not less than 'MINIMUM_EVENT_DELAY_US'
|
|
*/
|
|
remaining_us = deadline - current_us;
|
|
remaining_us = MAX(remaining_us, MINIMUM_EVENT_DELAY_US);
|
|
|
|
/*
|
|
* Set new 64bit absolute timeout ticks to Timer 1 comparator
|
|
* register.
|
|
* For ISH3, this assumes that remaining_us is less than 360 seconds
|
|
* (2^32 us / 12Mhz), otherwise we would need to handle 32-bit rollover
|
|
* of 12Mhz timer comparator value. Watchdog refresh happens at least
|
|
* every 10 seconds.
|
|
*/
|
|
wait_while_settling(HPET_T1_CMP_SETTLING);
|
|
HPET_TIMER_COMP(1) = current_ticks + scale_us2ticks_32(remaining_us);
|
|
|
|
/*
|
|
* Update 'last_deadline' and add calibrate delta due to HPET timer
|
|
* interrupt latency.
|
|
*/
|
|
last_deadline = current_us + remaining_us;
|
|
last_deadline += scale_ticks2us(HPET_INT_LATENCY_TICKS);
|
|
|
|
/* Enable timer interrupt */
|
|
wait_while_settling(HPET_T1_SETTLING);
|
|
HPET_TIMER_CONF_CAP(1) |= HPET_Tn_INT_ENB_CNF;
|
|
}
|
|
|
|
uint32_t __hw_clock_event_get(void)
|
|
{
|
|
return last_deadline;
|
|
}
|
|
|
|
void __hw_clock_event_clear(void)
|
|
{
|
|
/*
|
|
* We need to make sure that process_timers is called when the
|
|
* event timer rolls over, set for deadline when
|
|
* process_timers clears the event timer.
|
|
*/
|
|
__hw_clock_event_set(0xFFFFFFFF);
|
|
}
|
|
|
|
uint64_t __hw_clock_source_read64(void)
|
|
{
|
|
return scale_ticks2us(read_main_timer());
|
|
}
|
|
|
|
void __hw_clock_source_set64(uint64_t timestamp)
|
|
{
|
|
/* Reset both clock and overflow comparators */
|
|
wait_while_settling(HPET_ANY_SETTLING);
|
|
HPET_GENERAL_CONFIG &= ~HPET_ENABLE_CNF;
|
|
|
|
HPET_MAIN_COUNTER_64 = scale_us2ticks(timestamp);
|
|
|
|
wait_while_settling(HPET_ANY_SETTLING);
|
|
HPET_GENERAL_CONFIG |= HPET_ENABLE_CNF;
|
|
}
|
|
|
|
static void hw_clock_event_isr(void)
|
|
{
|
|
/* Clear interrupt */
|
|
wait_while_settling(HPET_INT_STATUS_SETTLING);
|
|
HPET_INTR_CLEAR = BIT(1);
|
|
|
|
process_timers(0);
|
|
}
|
|
DECLARE_IRQ(ISH_HPET_TIMER1_IRQ, hw_clock_event_isr);
|
|
|
|
int __hw_clock_source_init64(uint64_t start_t)
|
|
{
|
|
/*
|
|
* Timer 1 is used as an event timer. Timer 0 is unused, as
|
|
* CONFIG_HWTIMER_64BIT is enabled.
|
|
*/
|
|
uint32_t timer1_config = 0x00000000;
|
|
|
|
/* Disable HPET */
|
|
wait_while_settling(HPET_ANY_SETTLING);
|
|
HPET_GENERAL_CONFIG &= ~HPET_ENABLE_CNF;
|
|
|
|
/* Disable T0 */
|
|
HPET_TIMER_CONF_CAP(0) &= ~HPET_Tn_INT_ENB_CNF;
|
|
|
|
/* Disable T1 until we get it set up (below) */
|
|
HPET_TIMER_CONF_CAP(1) &= ~HPET_Tn_INT_ENB_CNF;
|
|
|
|
/* Initialize main counter */
|
|
HPET_MAIN_COUNTER_64 = scale_us2ticks(start_t);
|
|
|
|
/* Clear any interrupts from previously running image */
|
|
HPET_INTR_CLEAR = BIT(0);
|
|
HPET_INTR_CLEAR = BIT(1);
|
|
|
|
/* Timer 1 - IRQ routing */
|
|
timer1_config &= ~HPET_Tn_INT_ROUTE_CNF_MASK;
|
|
timer1_config |= (ISH_HPET_TIMER1_IRQ <<
|
|
HPET_Tn_INT_ROUTE_CNF_SHIFT);
|
|
|
|
/* Level triggered interrupt */
|
|
timer1_config |= HPET_Tn_INT_TYPE_CNF;
|
|
|
|
/* Initialize last_deadline until an event is scheduled */
|
|
last_deadline = 0xFFFFFFFF;
|
|
|
|
/* Before enabling, previous values must have settled */
|
|
wait_while_settling(HPET_ANY_SETTLING);
|
|
|
|
/* Unmask HPET IRQ in IOAPIC */
|
|
task_enable_irq(ISH_HPET_TIMER1_IRQ);
|
|
|
|
/* Copy timer config to hardware register */
|
|
HPET_TIMER_CONF_CAP(1) |= timer1_config;
|
|
|
|
/* Enable HPET */
|
|
HPET_GENERAL_CONFIG |= HPET_ENABLE_CNF;
|
|
|
|
/* Return IRQ value for OS event timer */
|
|
return ISH_HPET_TIMER1_IRQ;
|
|
}
|