/* Copyright 2018 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. */ /* * High-res hardware timer * * SCP hardware 32bit count down timer can be configured to source clock from * 32KHz, 26MHz, BCLK or PCLK. This implementation selects BCLK (ULPOSC1/8) as a * source, countdown mode and converts to micro second value matching common * timer. */ #include "clock.h" #include "clock_chip.h" #include "common.h" #include "console.h" #include "hooks.h" #include "hwtimer.h" #include "panic.h" #include "registers.h" #include "task.h" #include "timer.h" #include "watchdog.h" #define IRQ_TIMER(n) CONCAT2(SCP_IRQ_TIMER, n) #define TIMER_SYSTEM 5 #define TIMER_EVENT 3 /* ULPOSC1 should be a multiple of 8. */ BUILD_ASSERT((ULPOSC1_CLOCK_MHZ % 8) == 0); #define TIMER_CLOCK_MHZ (ULPOSC1_CLOCK_MHZ / 8) /* Common timer overflows at 0x100000000 micro seconds */ #define OVERFLOW_TICKS (TIMER_CLOCK_MHZ * 0x100000000 - 1) static uint8_t sys_high; static uint8_t event_high; /* Convert hardware countdown timer to 64bit countup ticks */ static inline uint64_t timer_read_raw_system(void) { uint32_t timer_ctrl = SCP_TIMER_IRQ_CTRL(TIMER_SYSTEM); uint32_t sys_high_adj = sys_high; /* * If an IRQ is pending, but has not been serviced yet, adjust the * sys_high value. */ if (timer_ctrl & TIMER_IRQ_STATUS) sys_high_adj = sys_high ? (sys_high - 1) : (TIMER_CLOCK_MHZ-1); return OVERFLOW_TICKS - (((uint64_t)sys_high_adj << 32) | SCP_TIMER_VAL(TIMER_SYSTEM)); } static inline uint64_t timer_read_raw_event(void) { return OVERFLOW_TICKS - (((uint64_t)event_high << 32) | SCP_TIMER_VAL(TIMER_EVENT)); } static inline void timer_set_clock(int n, uint32_t clock_source) { SCP_TIMER_EN(n) = (SCP_TIMER_EN(n) & ~TIMER_CLK_MASK) | clock_source; } static inline void timer_ack_irq(int n) { SCP_TIMER_IRQ_CTRL(n) |= TIMER_IRQ_CLEAR; } /* Set hardware countdown value */ static inline void timer_set_reset_value(int n, uint32_t reset_value) { SCP_TIMER_RESET_VAL(n) = reset_value; } static void timer_reset(int n) { __hw_timer_enable_clock(n, 0); timer_ack_irq(n); timer_set_reset_value(n, 0xffffffff); timer_set_clock(n, TIMER_CLK_32K); } /* Reload a new 32bit countdown value */ static void timer_reload(int n, uint32_t value) { __hw_timer_enable_clock(n, 0); timer_set_reset_value(n, value); __hw_timer_enable_clock(n, 1); } static int timer_reload_event_high(void) { if (event_high) { if (SCP_TIMER_RESET_VAL(TIMER_EVENT) == 0xffffffff) __hw_timer_enable_clock(TIMER_EVENT, 1); else timer_reload(TIMER_EVENT, 0xffffffff); event_high--; return 1; } /* Disable event timer clock when done. */ __hw_timer_enable_clock(TIMER_EVENT, 0); return 0; } void __hw_clock_event_clear(void) { __hw_timer_enable_clock(TIMER_EVENT, 0); timer_set_reset_value(TIMER_EVENT, 0x0000c1ea4); event_high = 0; } void __hw_clock_event_set(uint32_t deadline) { uint64_t deadline_raw = (uint64_t)deadline * TIMER_CLOCK_MHZ; uint64_t now_raw = timer_read_raw_system(); uint32_t event_deadline; if (deadline_raw > now_raw) { deadline_raw -= now_raw; event_deadline = (uint32_t)deadline_raw; event_high = deadline_raw >> 32; } else { event_deadline = 1; event_high = 0; } if (event_deadline) timer_reload(TIMER_EVENT, event_deadline); else timer_reload_event_high(); } void __hw_timer_enable_clock(int n, int enable) { if (enable) { SCP_TIMER_IRQ_CTRL(n) |= 1; SCP_TIMER_EN(n) |= 1; } else { SCP_TIMER_EN(n) &= ~1; SCP_TIMER_IRQ_CTRL(n) &= ~1; } } int __hw_clock_source_init(uint32_t start_t) { int t; /* * TODO(b/120169529): check clock tree to see if we need to turn on * MCLK and BCLK gate. */ SCP_CLK_GATE |= (CG_TIMER_M | CG_TIMER_B); /* Reset all timer, select 32768Hz clock source */ for (t = 0; t < NUM_TIMERS; t++) timer_reset(t); /* Enable timer IRQ wake source */ SCP_INTC_IRQ_WAKEUP |= (1 << IRQ_TIMER(0)) | (1 << IRQ_TIMER(1)) | (1 << IRQ_TIMER(2)) | (1 << IRQ_TIMER(3)) | (1 << IRQ_TIMER(4)) | (1 << IRQ_TIMER(5)); /* * Timer configuration: * OS TIMER - count up @ 13MHz, 64bit value with latch. * SYS TICK - count down @ 26MHz * EVENT TICK - count down @ 26MHz */ /* Turn on OS TIMER, tick at 13MHz */ SCP_OSTIMER_CON |= 1; /* System timestamp timer from BCLK (sourced from ULPOSC) */ SCP_CLK_BCLK = CLK_BCLK_SEL_ULPOSC1_DIV8; timer_set_clock(TIMER_SYSTEM, TIMER_CLK_BCLK); sys_high = TIMER_CLOCK_MHZ-1; timer_set_reset_value(TIMER_SYSTEM, 0xffffffff); __hw_timer_enable_clock(TIMER_SYSTEM, 1); task_enable_irq(IRQ_TIMER(TIMER_SYSTEM)); /* Event tick timer */ timer_set_clock(TIMER_EVENT, TIMER_CLK_BCLK); task_enable_irq(IRQ_TIMER(TIMER_EVENT)); return IRQ_TIMER(TIMER_SYSTEM); } uint32_t __hw_clock_source_read(void) { return timer_read_raw_system() / TIMER_CLOCK_MHZ; } uint32_t __hw_clock_event_get(void) { return (timer_read_raw_event() + timer_read_raw_system()) / TIMER_CLOCK_MHZ; } static void __hw_clock_source_irq(int n) { uint32_t timer_ctrl = SCP_TIMER_IRQ_CTRL(n); /* Ack if we're hardware interrupt */ if (timer_ctrl & TIMER_IRQ_STATUS) timer_ack_irq(n); switch (n) { case TIMER_EVENT: if (timer_ctrl & TIMER_IRQ_STATUS) { if (timer_reload_event_high()) return; } process_timers(0); break; case TIMER_SYSTEM: /* If this is a hardware irq, check overflow */ if (timer_ctrl & TIMER_IRQ_STATUS) { if (sys_high) { sys_high--; process_timers(0); } else { /* Overflow, reload system timer */ sys_high = TIMER_CLOCK_MHZ-1; process_timers(1); } } else { process_timers(0); } break; default: return; } } #define DECLARE_TIMER_IRQ(n) \ DECLARE_IRQ(IRQ_TIMER(n), __hw_clock_source_irq_##n, 2); \ void __hw_clock_source_irq_##n(void) { __hw_clock_source_irq(n); } DECLARE_TIMER_IRQ(0); DECLARE_TIMER_IRQ(1); DECLARE_TIMER_IRQ(2); DECLARE_TIMER_IRQ(3); DECLARE_TIMER_IRQ(4); DECLARE_TIMER_IRQ(5);