xref: /aosp_15_r20/external/coreboot/src/drivers/pc80/pc/i8254.c (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
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