1#include <acpi/ioapic.h>
2#include <asm.h>
3#include <console/printf.h>
4#include <irq/irq.h>
5#include <log.h>
6#include <pit.h>
7#include <stdint.h>
8#include <time/time.h>
9#include <types/types.h>
10
11LOG_HANDLE_DECLARE_PRINT(pit);
12
13static inline void pit_write_command(uint8_t cmd) {
14 outb(PIT_PORT_COMMAND, value: cmd);
15}
16
17static inline void pit_write_count16(uint8_t channel, uint16_t count) {
18 uint16_t port = PIT_PORT_CHANNEL(channel);
19 outb(port, value: (uint8_t) (count & 0xFF));
20 outb(port, value: (uint8_t) ((count >> 8) & 0xFF));
21}
22
23static uint16_t pit_read_counter(uint8_t channel) {
24 pit_write_command(PIT_CMD_CHANNEL(channel) | PIT_CMD_ACCESS_LATCH);
25
26 uint16_t port = PIT_PORT_CHANNEL(channel);
27 uint8_t low = inb(port);
28 uint8_t high = inb(port);
29
30 return ((uint16_t) high << 8) | low;
31}
32
33static uint16_t pit_read_count(void) {
34 return pit_read_counter(channel: 0);
35}
36
37static void pit_wait_until_zero(void) {
38 while (true) {
39 uint16_t count = pit_read_count();
40 if (count <= 1)
41 break;
42 cpu_relax();
43 }
44}
45
46void pit_set_divisor(uint16_t divisor) {
47 pit_write_command(PIT_CMD_CHANNEL0 | PIT_CMD_ACCESS_LOHI |
48 PIT_CMD_MODE_RATE_GEN | PIT_CMD_BINARY);
49 pit_write_count16(channel: 0, count: divisor);
50}
51
52void pit_set_periodic_mode(uint16_t divisor) {
53 pit_set_divisor(divisor);
54}
55
56void pit_set_oneshot_mode(uint16_t divisor) {
57 pit_write_command(PIT_CMD_CHANNEL0 | PIT_CMD_ACCESS_LOHI |
58 PIT_CMD_MODE_ONESHOT | PIT_CMD_BINARY);
59 pit_write_count16(channel: 0, count: divisor);
60}
61
62void pit_set_frequency(freq_hz_t freq_hz) {
63 if (freq_hz == 0)
64 freq_hz = 1;
65
66 uint32_t divisor;
67 if (freq_hz >= PIT_BASE_FREQUENCY_HZ) {
68 divisor = PIT_DIVISOR_MIN;
69 } else {
70 divisor = (uint32_t) (PIT_BASE_FREQUENCY_HZ / freq_hz);
71 if (divisor > PIT_DIVISOR_MAX_COUNT)
72 divisor = PIT_DIVISOR_ROLLOVER_VAL; /* 0 is 65536 in bin mode */
73 else if (divisor < PIT_DIVISOR_MIN)
74 divisor = PIT_DIVISOR_MIN;
75 }
76
77 pit_set_divisor(divisor: (uint16_t) divisor);
78}
79
80void pit_set_interval_ns(time_ns_t interval_ns) {
81 if (interval_ns == 0) {
82 pit_set_divisor(PIT_DIVISOR_MIN);
83 return;
84 }
85
86 uint64_t divisor = (interval_ns * PIT_BASE_FREQUENCY_HZ) / NS_PER_S;
87
88 if (divisor > PIT_DIVISOR_MAX_COUNT)
89 divisor = PIT_DIVISOR_ROLLOVER_VAL;
90
91 else if (divisor < PIT_DIVISOR_MIN)
92 divisor = PIT_DIVISOR_MIN;
93
94 pit_set_divisor(divisor: (uint16_t) divisor);
95}
96
97void pit_wire_periodic_nmi(time_ns_t interval_ns) {
98 pit_set_interval_ns(interval_ns);
99 ioapic_route_isa_nmi(isa_irq: 0, dest_apic_id: 0);
100}
101
102void pit_init(void) {
103 pit_write_command(PIT_DEFAULT_MODE);
104 pit_write_count16(channel: 0, PIT_DEFAULT_COUNT);
105}
106
107freq_hz_t pit_measure_tsc_freq(void) {
108 pit_write_command(PIT_CALIBRATION_MODE);
109 pit_write_count16(channel: 0, PIT_CALIBRATION_COUNT);
110
111 uint64_t start_tsc = rdtsc();
112
113 pit_wait_until_zero();
114
115 uint64_t end_tsc = rdtsc();
116
117 pit_write_command(PIT_DEFAULT_MODE);
118 pit_write_count16(channel: 0, PIT_DEFAULT_COUNT);
119
120 uint64_t tsc_frequency =
121 (end_tsc - start_tsc) * PIT_FREQUENCY / PIT_CALIBRATION_COUNT;
122
123 return tsc_frequency;
124}
125