summaryrefslogtreecommitdiff
path: root/src/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'src/drivers')
-rw-r--r--src/drivers/pc80/pc/Makefile.inc8
-rw-r--r--src/drivers/pc80/pc/i8254.c76
2 files changed, 83 insertions, 1 deletions
diff --git a/src/drivers/pc80/pc/Makefile.inc b/src/drivers/pc80/pc/Makefile.inc
index 8c348e323e..bd56cd43a1 100644
--- a/src/drivers/pc80/pc/Makefile.inc
+++ b/src/drivers/pc80/pc/Makefile.inc
@@ -1,7 +1,6 @@
ifeq ($(CONFIG_ARCH_X86),y)
ramstage-y += isa-dma.c
-ramstage-y += i8254.c
ramstage-y += i8259.c
ramstage-$(CONFIG_UDELAY_IO) += udelay_io.c
romstage-$(CONFIG_UDELAY_IO) += udelay_io.c
@@ -9,4 +8,11 @@ ramstage-y += keyboard.c
ramstage-$(CONFIG_SPKMODEM) += spkmodem.c
romstage-$(CONFIG_SPKMODEM) += spkmodem.c
+bootblock-y += i8254.c
+verstage-y += i8254.c
+romstage-y += i8254.c
+ramstage-y += i8254.c
+postcar-y += i8254.c
+smm-y += i8254.c
+
endif
diff --git a/src/drivers/pc80/pc/i8254.c b/src/drivers/pc80/pc/i8254.c
index 53d880cc79..8e15d4ded1 100644
--- a/src/drivers/pc80/pc/i8254.c
+++ b/src/drivers/pc80/pc/i8254.c
@@ -12,6 +12,8 @@
*/
#include <arch/io.h>
+#include <commonlib/helpers.h>
+#include <cpu/x86/tsc.h>
#include <pc80/i8254.h>
/* Initialize i8254 timers */
@@ -46,3 +48,77 @@ void udelay(int usecs)
;
}
#endif
+
+#define CLOCK_TICK_RATE 1193180U /* Underlying HZ */
+
+/* ------ Calibrate the TSC -------
+ * Too much 64-bit arithmetic here to do this cleanly in C, and for
+ * accuracy's sake we want to keep the overhead on the CTC speaker (channel 2)
+ * output busy loop as low as possible. We avoid reading the CTC registers
+ * directly because of the awkward 8-bit access mechanism of the 82C54
+ * device.
+ */
+
+#define CALIBRATE_INTERVAL ((2*CLOCK_TICK_RATE)/1000) /* 2ms */
+#define CALIBRATE_DIVISOR (2*1000) /* 2ms / 2000 == 1usec */
+
+unsigned long calibrate_tsc_with_pit(void)
+{
+ /* Set the Gate high, disable speaker */
+ outb((inb(0x61) & ~0x02) | 0x01, 0x61);
+
+ /*
+ * Now let's take care of CTC channel 2
+ *
+ * Set the Gate high, program CTC channel 2 for mode 0,
+ * (interrupt on terminal count mode), binary count,
+ * load 5 * LATCH count, (LSB and MSB) to begin countdown.
+ */
+ outb(0xb0, 0x43); /* binary, mode 0, LSB/MSB, Ch 2 */
+
+ outb(CALIBRATE_INTERVAL & 0xff, 0x42); /* LSB of count */
+ outb(CALIBRATE_INTERVAL >> 8, 0x42); /* MSB of count */
+
+ {
+ tsc_t start;
+ tsc_t end;
+ unsigned long count;
+
+ start = rdtsc();
+ count = 0;
+ do {
+ count++;
+ } while ((inb(0x61) & 0x20) == 0);
+ end = rdtsc();
+
+ /* Error: ECTCNEVERSET */
+ if (count <= 1)
+ goto bad_ctc;
+
+ /* 64-bit subtract - gcc just messes up with long longs */
+ __asm__("subl %2,%0\n\t"
+ "sbbl %3,%1"
+ : "=a" (end.lo), "=d" (end.hi)
+ : "g" (start.lo), "g" (start.hi),
+ "0" (end.lo), "1" (end.hi));
+
+ /* Error: ECPUTOOFAST */
+ if (end.hi)
+ goto bad_ctc;
+
+
+ /* Error: ECPUTOOSLOW */
+ if (end.lo <= CALIBRATE_DIVISOR)
+ goto bad_ctc;
+
+ return DIV_ROUND_UP(end.lo, CALIBRATE_DIVISOR);
+ }
+
+ /*
+ * The CTC wasn't reliable: we got a hit on the very first read,
+ * or the CPU was so fast/slow that the quotient wouldn't fit in
+ * 32 bits..
+ */
+bad_ctc:
+ return 0;
+}