1 /* SPDX-License-Identifier: GPL-2.0-only */
2
3 #include <arch/io.h>
4 #include <commonlib/helpers.h>
5 #include <cpu/x86/tsc.h>
6 #include <delay.h>
7 #include <pc80/i8254.h>
8
9 /* Initialize i8254 timers */
10
setup_i8254(void)11 void setup_i8254(void)
12 {
13 /* Timer 0 (taken from biosemu) */
14 outb(TIMER0_SEL | WORD_ACCESS | MODE3 | BINARY_COUNT, TIMER_MODE_PORT);
15 outb(0x00, TIMER0_PORT);
16 outb(0x00, TIMER0_PORT);
17
18 /* Timer 1 */
19 outb(TIMER1_SEL | LOBYTE_ACCESS | MODE3 | BINARY_COUNT,
20 TIMER_MODE_PORT);
21 outb(0x12, TIMER1_PORT);
22 }
23
24 #define CLOCK_TICK_RATE 1193180U /* Underlying HZ */
25
26 /* ------ Calibrate the TSC -------
27 * Too much 64-bit arithmetic here to do this cleanly in C, and for
28 * accuracy's sake we want to keep the overhead on the CTC speaker (channel 2)
29 * output busy loop as low as possible. We avoid reading the CTC registers
30 * directly because of the awkward 8-bit access mechanism of the 82C54
31 * device.
32 */
33
34 #define CALIBRATE_INTERVAL ((2*CLOCK_TICK_RATE)/1000) /* 2ms */
35 #define CALIBRATE_DIVISOR (2*1000) /* 2ms / 2000 == 1usec */
36
calibrate_tsc_with_pit(void)37 unsigned long calibrate_tsc_with_pit(void)
38 {
39 /* Set the Gate high, disable speaker */
40 outb((inb(0x61) & ~0x02) | 0x01, 0x61);
41
42 /*
43 * Now let's take care of CTC channel 2
44 *
45 * Set the Gate high, program CTC channel 2 for mode 0,
46 * (interrupt on terminal count mode), binary count,
47 * load 5 * LATCH count, (LSB and MSB) to begin countdown.
48 */
49 outb(0xb0, 0x43); /* binary, mode 0, LSB/MSB, Ch 2 */
50
51 outb(CALIBRATE_INTERVAL & 0xff, 0x42); /* LSB of count */
52 outb(CALIBRATE_INTERVAL >> 8, 0x42); /* MSB of count */
53
54 {
55 tsc_t start;
56 tsc_t end;
57 unsigned long count;
58
59 start = rdtsc();
60 count = 0;
61 do {
62 count++;
63 } while ((inb(0x61) & 0x20) == 0);
64 end = rdtsc();
65
66 /* Error: ECTCNEVERSET */
67 if (count <= 1)
68 goto bad_ctc;
69
70 /* 64-bit subtract - gcc just messes up with long longs */
71 __asm__("subl %2,%0\n\t"
72 "sbbl %3,%1"
73 : "=a" (end.lo), "=d" (end.hi)
74 : "g" (start.lo), "g" (start.hi),
75 "0" (end.lo), "1" (end.hi));
76
77 /* Error: ECPUTOOFAST */
78 if (end.hi)
79 goto bad_ctc;
80
81 /* Error: ECPUTOOSLOW */
82 if (end.lo <= CALIBRATE_DIVISOR)
83 goto bad_ctc;
84
85 return DIV_ROUND_UP(end.lo, CALIBRATE_DIVISOR);
86 }
87
88 /*
89 * The CTC wasn't reliable: we got a hit on the very first read,
90 * or the CPU was so fast/slow that the quotient wouldn't fit in
91 * 32 bits..
92 */
93 bad_ctc:
94 return 0;
95 }
96
97 #if CONFIG(UNKNOWN_TSC_RATE)
98 static u32 timer_tsc;
99
tsc_freq_mhz(void)100 unsigned long tsc_freq_mhz(void)
101 {
102 if (timer_tsc > 0)
103 return timer_tsc;
104
105 timer_tsc = calibrate_tsc_with_pit();
106
107 /* Set some semi-ridiculous rate if approximation fails. */
108 if (timer_tsc == 0)
109 timer_tsc = 5000;
110
111 return timer_tsc;
112 }
113 #endif
114
beep(unsigned int frequency_hz,unsigned int duration_msec)115 void beep(unsigned int frequency_hz, unsigned int duration_msec)
116 {
117 unsigned int count = CLOCK_TICK_RATE / frequency_hz;
118
119 /* Set command for counter 2, 2 byte write, mode 3 */
120 outb(TIMER2_SEL | WORD_ACCESS | MODE3, TIMER_MODE_PORT);
121
122 /* Select desired Hz */
123 outb(count & 0xff, TIMER2_PORT);
124 outb((count >> 8) & 0xff, TIMER2_PORT);
125
126 /* Switch on the speaker */
127 outb(inb(PPC_PORTB) | (PPCB_T2GATE | PPCB_SPKR), PPC_PORTB);
128
129 /* Block for specified milliseconds */
130 mdelay(duration_msec);
131
132 /* Switch off the speaker */
133 outb(inb(PPC_PORTB) & ~(PPCB_T2GATE | PPCB_SPKR), PPC_PORTB);
134 }
135