summaryrefslogtreecommitdiff
path: root/src/cpu/x86/tsc
diff options
context:
space:
mode:
Diffstat (limited to 'src/cpu/x86/tsc')
-rw-r--r--src/cpu/x86/tsc/Config.lb5
-rw-r--r--src/cpu/x86/tsc/delay_tsc.c165
2 files changed, 170 insertions, 0 deletions
diff --git a/src/cpu/x86/tsc/Config.lb b/src/cpu/x86/tsc/Config.lb
new file mode 100644
index 0000000000..07272ad9d7
--- /dev/null
+++ b/src/cpu/x86/tsc/Config.lb
@@ -0,0 +1,5 @@
+uses CONFIG_UDELAY_TSC
+uses CONFIG_TSC_X86RDTSC_CALIBRATE_WITH_TIMER2
+
+default CONFIG_TSC_X86RDTSC_CALIBRATE_WITH_TIMER2=0
+if CONFIG_UDELAY_TSC object delay_tsc.o end
diff --git a/src/cpu/x86/tsc/delay_tsc.c b/src/cpu/x86/tsc/delay_tsc.c
new file mode 100644
index 0000000000..c7c431baac
--- /dev/null
+++ b/src/cpu/x86/tsc/delay_tsc.c
@@ -0,0 +1,165 @@
+#include <console/console.h>
+#include <arch/io.h>
+#include <cpu/x86/msr.h>
+#include <cpu/x86/tsc.h>
+#include <smp/spinlock.h>
+#include <delay.h>
+
+static unsigned long clocks_per_usec;
+
+#if (CONFIG_TSC_X86RDTSC_CALIBRATE_WITH_TIMER2 == 1)
+#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 ((20*CLOCK_TICK_RATE)/1000) /* 20ms */
+#define CALIBRATE_DIVISOR (20*1000) /* 20ms / 20000 == 1usec */
+
+static unsigned long long calibrate_tsc(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 (end.lo + CALIBRATE_DIVISOR -1)/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:
+ printk_err("bad_ctc\n");
+ return 0;
+}
+
+#else /* CONFIG_TSC_X86RDTSC_CALIBRATE_WITH_TIMER2 */
+
+/*
+ * this is the "no timer2" version.
+ * to calibrate tsc, we get a TSC reading, then do 1,000,000 outbs to port 0x80
+ * then we read TSC again, and divide the difference by 1,000,000
+ * we have found on a wide range of machines that this gives us a a
+ * good microsecond value
+ * to +- 10%. On a dual AMD 1.6 Ghz box, it gives us .97 microseconds, and on a
+ * 267 Mhz. p5, it gives us 1.1 microseconds.
+ * also, since gcc now supports long long, we use that.
+ * also no unsigned long long / operator, so we play games.
+ * about the only thing you can do with long longs, it seems,
+ *is return them and assign them.
+ * (and do asm on them, yuck)
+ * so avoid all ops on long longs.
+ */
+static unsigned long long calibrate_tsc(void)
+{
+ unsigned long long start, end, delta;
+ unsigned long allones = (unsigned long) -1, result;
+ unsigned long count;
+
+ start = rdtscll();
+ // no udivdi3, dammit.
+ // so we count to 1<< 20 and then right shift 20
+ for(count = 0; count < (1<<20); count ++)
+ outb(0x80, 0x80);
+ end = rdtscll();
+
+#if 0
+ // make delta be (endhigh - starthigh) + (endlow - startlow)
+ // but >> 20
+ // do it this way to avoid gcc warnings.
+ start = tsc_start.hi;
+ start <<= 32;
+ start |= start.lo;
+ end = tsc_end.hi;
+ end <<= 32;
+ end |= tsc_end.lo;
+#endif
+ delta = end - start;
+ // at this point we have a delta for 1,000,000 outbs. Now rescale for one microsecond.
+ delta >>= 20;
+ // save this for microsecond timing.
+ result = delta;
+ printk_spew("end %x:%x, start %x:%x\n",
+ endhigh, endlow, starthigh, startlow);
+ printk_spew("32-bit delta %d\n", (unsigned long) delta);
+
+ printk_spew(__FUNCTION__ " 32-bit result is %d\n", result);
+ return delta;
+}
+
+
+#endif /* CONFIG_TSC_X86RDTSC_CALIBRATE_WITH_TIMER2*/
+
+void init_timer(void)
+{
+ if (!clocks_per_usec) {
+ clocks_per_usec = calibrate_tsc();
+ printk_info("clocks_per_usec: %u\n", clocks_per_usec);
+ }
+}
+
+void udelay(unsigned us)
+{
+ unsigned long long count;
+ unsigned long long stop;
+ unsigned long long clocks;
+
+ init_timer();
+ clocks = us;
+ clocks *= clocks_per_usec;
+ count = rdtscll();
+ stop = clocks + count;
+ while(stop > count) {
+ cpu_relax();
+ count = rdtscll();
+ }
+}