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