make irq specifier to be provided when assigining irq
[lunaix-os.git] / lunaix-os / arch / x86 / hal / apic_timer.c
1 #include "apic_timer.h"
2 #include <hal/hwtimer.h>
3 #include <hal/irq.h>
4
5 #include <lunaix/clock.h>
6 #include <lunaix/compiler.h>
7 #include <lunaix/spike.h>
8 #include <lunaix/syslog.h>
9
10 #include "asm/soc/apic.h"
11
12 LOG_MODULE("timer(apic)")
13
14 #define LVT_ENTRY_TIMER(vector, mode) (LVT_DELIVERY_FIXED | mode | vector)
15 #define APIC_BASETICKS 0x100000
16
17 static void
18 apic_timer_count_stop(irq_t irq, const struct hart_state* state)
19 {
20     struct hwtimer_pot* pot;
21
22     pot = irq_payload(irq, struct hwtimer_pot);
23     pot->systick_raw = (ticks_t)-1;
24 }
25
26 static void
27 apic_timer_tick_isr(irq_t irq, const struct hart_state* state)
28 {
29     struct hwtimer_pot* pot;
30
31     pot = irq_payload(irq, struct hwtimer_pot);
32     pot->systick_raw++;
33
34     if (likely(__ptr(pot->callback))) {
35         pot->callback();
36     }
37 }
38
39 static void
40 __apic_timer_calibrate(struct hwtimer_pot* pot, u32_t hertz)
41 {
42     irq_t irq;
43     ticks_t base_freq = 0;
44
45     cpu_disable_interrupt();
46
47     irq = irq_declare_direct(apic_timer_count_stop);
48     irq_set_payload(irq, pot);
49     irq_assign(irq_get_default_domain(), irq, NULL);
50
51     // Setup a one-shot timer, we will use this to measure the bus speed. So we
52     // can then calibrate apic timer to work at *nearly* accurate hz
53     apic_write_reg(APIC_TIMER_LVT,
54                    LVT_ENTRY_TIMER(irq->vector, LVT_TIMER_ONESHOT));
55
56     // Set divider to 64
57     apic_write_reg(APIC_TIMER_DCR, APIC_TIMER_DIV64);
58
59     /*
60         Timer calibration process - measure the APIC timer base frequency
61
62          step 1: setup a temporary isr for RTC timer which trigger at each tick
63                  (1024Hz)
64          step 2: setup a temporary isr for #APIC_TIMER_IV
65          step 3: setup the divider, APIC_TIMER_DCR
66          step 4: Startup RTC timer
67          step 5: Write a large value, v, to APIC_TIMER_ICR to start APIC timer
68        (this must be followed immediately after step 4) step 6: issue a write to
69        EOI and clean up.
70
71         When the APIC ICR counting down to 0 #APIC_TIMER_IV triggered, save the
72        rtc timer's counter, k, and disable RTC timer immediately (although the
73        RTC interrupts should be blocked by local APIC as we are currently busy
74        on handling #APIC_TIMER_IV)
75
76         So the apic timer frequency F_apic in Hz can be calculate as
77             v / F_apic = k / 1024
78             =>  F_apic = v / k * 1024
79
80     */
81
82     sysrtc->ops->set_proactive(sysrtc, true);
83     apic_write_reg(APIC_TIMER_ICR, APIC_BASETICKS); // start APIC timer
84
85     pot->systick_raw = 0;
86     cpu_enable_interrupt();
87
88     wait_until(!(pot->systick_raw + 1));
89
90     cpu_disable_interrupt();
91
92     sysrtc->ops->set_proactive(sysrtc, false);
93     
94     base_freq = sysrtc->live;
95     base_freq = APIC_BASETICKS / base_freq * sysrtc->base_freq;
96     pot->base_freq = base_freq;
97     pot->systick_raw = 0;
98
99     assert_msg(base_freq, "Fail to initialize timer (NOFREQ)");
100     INFO("hw: %u Hz; os: %u Hz", base_freq, hertz);
101
102     irq_set_servant(irq, apic_timer_tick_isr);
103
104     apic_write_reg(APIC_TIMER_ICR, base_freq / hertz);
105     apic_write_reg(
106         APIC_TIMER_LVT,
107         LVT_ENTRY_TIMER(irq->vector, LVT_TIMER_PERIODIC));
108 }
109
110 static struct hwtimer_pot_ops potops = {
111     .calibrate = __apic_timer_calibrate,
112 };
113
114 static int
115 __apic_timer_load(struct device_def* def)
116 {
117     struct device* timer;
118
119     timer = device_allocsys(NULL, NULL);
120
121     hwtimer_attach_potens(timer, HWTIMER_MAX_PRECEDENCE, &potops);
122     register_device_var(timer, &def->class, "apic-timer");
123     return 0;
124 }
125
126
127 static struct device_def x86_apic_timer = 
128 {
129     def_device_class(INTEL, TIME, TIMER_APIC),
130     def_device_name("Intel APIC Timer"),
131     
132     def_on_load(__apic_timer_load)
133 };
134 EXPORT_DEVICE(apic_timer, &x86_apic_timer, load_sysconf);