1 /*
2 * Copyright (c) 2019 Intel Corporation
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6 #include <zephyr/init.h>
7 #include <zephyr/drivers/timer/system_timer.h>
8 #include <zephyr/sys_clock.h>
9 #include <zephyr/spinlock.h>
10 #include <zephyr/drivers/interrupt_controller/loapic.h>
11 #include <zephyr/irq.h>
12
13 BUILD_ASSERT(!IS_ENABLED(CONFIG_TICKLESS_KERNEL), "this is a tickfull driver");
14
15 /*
16 * Overview:
17 *
18 * This driver enables the local APIC as the Zephyr system timer. It supports
19 * legacy ("tickful") mode only. The driver will work with any APIC that has
20 * the ARAT "always running APIC timer" feature (CPUID 0x06, EAX bit 2).
21 *
22 * Configuration:
23 *
24 * CONFIG_APIC_TIMER=y enables this timer driver.
25 * CONFIG_APIC_TIMER_IRQ=<irq> which IRQ to configure for the timer.
26 * CONFIG_APIC_TIMER_IRQ_PRIORITY=<p> priority for IRQ_CONNECT()
27 *
28 * CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=<hz> must contain the frequency seen
29 * by the local APIC timer block (before it gets to the timer divider).
30 */
31
32 /* These should be merged into include/drivers/interrupt_controller/loapic.h. */
33
34 #define DCR_DIVIDER_MASK 0x0000000F /* divider bits */
35 #define DCR_DIVIDER 0x0000000B /* divide by 1 */
36 #define LVT_MODE_MASK 0x00060000 /* timer mode bits */
37 #define LVT_MODE 0x00020000 /* periodic mode */
38
39 #if defined(CONFIG_TEST)
40 const int32_t z_sys_timer_irq_for_test = CONFIG_APIC_TIMER_IRQ;
41 #endif
42
43 #define CYCLES_PER_TICK \
44 (CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC / CONFIG_SYS_CLOCK_TICKS_PER_SEC)
45
46 BUILD_ASSERT(CYCLES_PER_TICK >= 1, "APIC timer: bad CYCLES_PER_TICK");
47
48 static volatile uint64_t total_cycles;
49
isr(const void * arg)50 static void isr(const void *arg)
51 {
52 ARG_UNUSED(arg);
53
54 total_cycles += CYCLES_PER_TICK;
55 sys_clock_announce(1);
56 }
57
sys_clock_elapsed(void)58 uint32_t sys_clock_elapsed(void)
59 {
60 return 0U;
61 }
62
sys_clock_cycle_get_64(void)63 uint64_t sys_clock_cycle_get_64(void)
64 {
65 uint32_t ccr_1st, ccr_2nd;
66 uint64_t cycles;
67
68 /*
69 * We may race with CCR reaching 0 and reloading, and the interrupt
70 * handler updating total_cycles. Let's make sure we sample everything
71 * away from this roll-over transition by ensuring consecutive CCR
72 * values are descending so we're sure the enclosed (volatile)
73 * total_cycles sample and CCR value are coherent with each other.
74 */
75 do {
76 ccr_1st = x86_read_loapic(LOAPIC_TIMER_CCR);
77 cycles = total_cycles;
78 ccr_2nd = x86_read_loapic(LOAPIC_TIMER_CCR);
79 } while (ccr_2nd > ccr_1st);
80
81 return cycles + (CYCLES_PER_TICK - ccr_2nd);
82 }
83
sys_clock_cycle_get_32(void)84 uint32_t sys_clock_cycle_get_32(void)
85 {
86 return (uint32_t)sys_clock_cycle_get_64();
87 }
88
sys_clock_driver_init(void)89 static int sys_clock_driver_init(void)
90 {
91 uint32_t val;
92
93
94 val = x86_read_loapic(LOAPIC_TIMER_CONFIG); /* set divider */
95 val &= ~DCR_DIVIDER_MASK;
96 val |= DCR_DIVIDER;
97 x86_write_loapic(LOAPIC_TIMER_CONFIG, val);
98
99 val = x86_read_loapic(LOAPIC_TIMER); /* set timer mode */
100 val &= ~LVT_MODE_MASK;
101 val |= LVT_MODE;
102 x86_write_loapic(LOAPIC_TIMER, val);
103
104 /* remember, wiring up the interrupt will mess with the LVT, too */
105
106 IRQ_CONNECT(CONFIG_APIC_TIMER_IRQ,
107 CONFIG_APIC_TIMER_IRQ_PRIORITY,
108 isr, 0, 0);
109
110 x86_write_loapic(LOAPIC_TIMER_ICR, CYCLES_PER_TICK);
111 irq_enable(CONFIG_APIC_TIMER_IRQ);
112
113 return 0;
114 }
115
116 SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2,
117 CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);
118