118 lines
3.1 KiB
C
118 lines
3.1 KiB
C
/*
|
|
* Copyright (c) 2019 Intel Corporation
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/init.h>
|
|
#include <zephyr/drivers/timer/system_timer.h>
|
|
#include <zephyr/sys_clock.h>
|
|
#include <zephyr/spinlock.h>
|
|
#include <zephyr/drivers/interrupt_controller/loapic.h>
|
|
#include <zephyr/irq.h>
|
|
|
|
BUILD_ASSERT(!IS_ENABLED(CONFIG_TICKLESS_KERNEL), "this is a tickfull driver");
|
|
|
|
/*
|
|
* Overview:
|
|
*
|
|
* This driver enables the local APIC as the Zephyr system timer. It supports
|
|
* legacy ("tickful") mode only. The driver will work with any APIC that has
|
|
* the ARAT "always running APIC timer" feature (CPUID 0x06, EAX bit 2).
|
|
*
|
|
* Configuration:
|
|
*
|
|
* CONFIG_APIC_TIMER=y enables this timer driver.
|
|
* CONFIG_APIC_TIMER_IRQ=<irq> which IRQ to configure for the timer.
|
|
* CONFIG_APIC_TIMER_IRQ_PRIORITY=<p> priority for IRQ_CONNECT()
|
|
*
|
|
* CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=<hz> must contain the frequency seen
|
|
* by the local APIC timer block (before it gets to the timer divider).
|
|
*/
|
|
|
|
/* These should be merged into include/drivers/interrupt_controller/loapic.h. */
|
|
|
|
#define DCR_DIVIDER_MASK 0x0000000F /* divider bits */
|
|
#define DCR_DIVIDER 0x0000000B /* divide by 1 */
|
|
#define LVT_MODE_MASK 0x00060000 /* timer mode bits */
|
|
#define LVT_MODE 0x00020000 /* periodic mode */
|
|
|
|
#if defined(CONFIG_TEST)
|
|
const int32_t z_sys_timer_irq_for_test = CONFIG_APIC_TIMER_IRQ;
|
|
#endif
|
|
|
|
#define CYCLES_PER_TICK \
|
|
(CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC / CONFIG_SYS_CLOCK_TICKS_PER_SEC)
|
|
|
|
BUILD_ASSERT(CYCLES_PER_TICK >= 1, "APIC timer: bad CYCLES_PER_TICK");
|
|
|
|
static volatile uint64_t total_cycles;
|
|
|
|
static void isr(const void *arg)
|
|
{
|
|
ARG_UNUSED(arg);
|
|
|
|
total_cycles += CYCLES_PER_TICK;
|
|
sys_clock_announce(1);
|
|
}
|
|
|
|
uint32_t sys_clock_elapsed(void)
|
|
{
|
|
return 0U;
|
|
}
|
|
|
|
uint64_t sys_clock_cycle_get_64(void)
|
|
{
|
|
uint32_t ccr_1st, ccr_2nd;
|
|
uint64_t cycles;
|
|
|
|
/*
|
|
* We may race with CCR reaching 0 and reloading, and the interrupt
|
|
* handler updating total_cycles. Let's make sure we sample everything
|
|
* away from this roll-over transition by ensuring consecutive CCR
|
|
* values are descending so we're sure the enclosed (volatile)
|
|
* total_cycles sample and CCR value are coherent with each other.
|
|
*/
|
|
do {
|
|
ccr_1st = x86_read_loapic(LOAPIC_TIMER_CCR);
|
|
cycles = total_cycles;
|
|
ccr_2nd = x86_read_loapic(LOAPIC_TIMER_CCR);
|
|
} while (ccr_2nd > ccr_1st);
|
|
|
|
return cycles + (CYCLES_PER_TICK - ccr_2nd);
|
|
}
|
|
|
|
uint32_t sys_clock_cycle_get_32(void)
|
|
{
|
|
return (uint32_t)sys_clock_cycle_get_64();
|
|
}
|
|
|
|
static int sys_clock_driver_init(void)
|
|
{
|
|
uint32_t val;
|
|
|
|
|
|
val = x86_read_loapic(LOAPIC_TIMER_CONFIG); /* set divider */
|
|
val &= ~DCR_DIVIDER_MASK;
|
|
val |= DCR_DIVIDER;
|
|
x86_write_loapic(LOAPIC_TIMER_CONFIG, val);
|
|
|
|
val = x86_read_loapic(LOAPIC_TIMER); /* set timer mode */
|
|
val &= ~LVT_MODE_MASK;
|
|
val |= LVT_MODE;
|
|
x86_write_loapic(LOAPIC_TIMER, val);
|
|
|
|
/* remember, wiring up the interrupt will mess with the LVT, too */
|
|
|
|
IRQ_CONNECT(CONFIG_APIC_TIMER_IRQ,
|
|
CONFIG_APIC_TIMER_IRQ_PRIORITY,
|
|
isr, 0, 0);
|
|
|
|
x86_write_loapic(LOAPIC_TIMER_ICR, CYCLES_PER_TICK);
|
|
irq_enable(CONFIG_APIC_TIMER_IRQ);
|
|
|
|
return 0;
|
|
}
|
|
|
|
SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2,
|
|
CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);
|