| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* | 
 | 2 |  *	Common functions used across the timers go here | 
 | 3 |  */ | 
 | 4 |  | 
 | 5 | #include <linux/init.h> | 
 | 6 | #include <linux/timex.h> | 
 | 7 | #include <linux/errno.h> | 
 | 8 | #include <linux/jiffies.h> | 
| Dave Jones | c5d28fb | 2005-05-31 19:03:46 -0700 | [diff] [blame] | 9 | #include <linux/module.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 10 |  | 
 | 11 | #include <asm/io.h> | 
 | 12 | #include <asm/timer.h> | 
 | 13 | #include <asm/hpet.h> | 
 | 14 |  | 
 | 15 | #include "mach_timer.h" | 
 | 16 |  | 
 | 17 | /* ------ Calibrate the TSC ------- | 
 | 18 |  * Return 2^32 * (1 / (TSC clocks per usec)) for do_fast_gettimeoffset(). | 
 | 19 |  * Too much 64-bit arithmetic here to do this cleanly in C, and for | 
 | 20 |  * accuracy's sake we want to keep the overhead on the CTC speaker (channel 2) | 
 | 21 |  * output busy loop as low as possible. We avoid reading the CTC registers | 
 | 22 |  * directly because of the awkward 8-bit access mechanism of the 82C54 | 
 | 23 |  * device. | 
 | 24 |  */ | 
 | 25 |  | 
 | 26 | #define CALIBRATE_TIME	(5 * 1000020/HZ) | 
 | 27 |  | 
| Dave Jones | c5d28fb | 2005-05-31 19:03:46 -0700 | [diff] [blame] | 28 | unsigned long calibrate_tsc(void) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 29 | { | 
 | 30 | 	mach_prepare_counter(); | 
 | 31 |  | 
 | 32 | 	{ | 
 | 33 | 		unsigned long startlow, starthigh; | 
 | 34 | 		unsigned long endlow, endhigh; | 
 | 35 | 		unsigned long count; | 
 | 36 |  | 
 | 37 | 		rdtsc(startlow,starthigh); | 
 | 38 | 		mach_countup(&count); | 
 | 39 | 		rdtsc(endlow,endhigh); | 
 | 40 |  | 
 | 41 |  | 
 | 42 | 		/* Error: ECTCNEVERSET */ | 
 | 43 | 		if (count <= 1) | 
 | 44 | 			goto bad_ctc; | 
 | 45 |  | 
 | 46 | 		/* 64-bit subtract - gcc just messes up with long longs */ | 
 | 47 | 		__asm__("subl %2,%0\n\t" | 
 | 48 | 			"sbbl %3,%1" | 
 | 49 | 			:"=a" (endlow), "=d" (endhigh) | 
 | 50 | 			:"g" (startlow), "g" (starthigh), | 
 | 51 | 			 "0" (endlow), "1" (endhigh)); | 
 | 52 |  | 
 | 53 | 		/* Error: ECPUTOOFAST */ | 
 | 54 | 		if (endhigh) | 
 | 55 | 			goto bad_ctc; | 
 | 56 |  | 
 | 57 | 		/* Error: ECPUTOOSLOW */ | 
 | 58 | 		if (endlow <= CALIBRATE_TIME) | 
 | 59 | 			goto bad_ctc; | 
 | 60 |  | 
 | 61 | 		__asm__("divl %2" | 
 | 62 | 			:"=a" (endlow), "=d" (endhigh) | 
 | 63 | 			:"r" (endlow), "0" (0), "1" (CALIBRATE_TIME)); | 
 | 64 |  | 
 | 65 | 		return endlow; | 
 | 66 | 	} | 
 | 67 |  | 
 | 68 | 	/* | 
 | 69 | 	 * The CTC wasn't reliable: we got a hit on the very first read, | 
 | 70 | 	 * or the CPU was so fast/slow that the quotient wouldn't fit in | 
 | 71 | 	 * 32 bits.. | 
 | 72 | 	 */ | 
 | 73 | bad_ctc: | 
 | 74 | 	return 0; | 
 | 75 | } | 
 | 76 |  | 
 | 77 | #ifdef CONFIG_HPET_TIMER | 
 | 78 | /* ------ Calibrate the TSC using HPET ------- | 
 | 79 |  * Return 2^32 * (1 / (TSC clocks per usec)) for getting the CPU freq. | 
 | 80 |  * Second output is parameter 1 (when non NULL) | 
 | 81 |  * Set 2^32 * (1 / (tsc per HPET clk)) for delay_hpet(). | 
 | 82 |  * calibrate_tsc() calibrates the processor TSC by comparing | 
 | 83 |  * it to the HPET timer of known frequency. | 
 | 84 |  * Too much 64-bit arithmetic here to do this cleanly in C | 
 | 85 |  */ | 
 | 86 | #define CALIBRATE_CNT_HPET 	(5 * hpet_tick) | 
 | 87 | #define CALIBRATE_TIME_HPET 	(5 * KERNEL_TICK_USEC) | 
 | 88 |  | 
| Shaohua Li | a13db56 | 2005-06-25 14:54:48 -0700 | [diff] [blame] | 89 | unsigned long __devinit calibrate_tsc_hpet(unsigned long *tsc_hpet_quotient_ptr) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 90 | { | 
 | 91 | 	unsigned long tsc_startlow, tsc_starthigh; | 
 | 92 | 	unsigned long tsc_endlow, tsc_endhigh; | 
 | 93 | 	unsigned long hpet_start, hpet_end; | 
 | 94 | 	unsigned long result, remain; | 
 | 95 |  | 
 | 96 | 	hpet_start = hpet_readl(HPET_COUNTER); | 
 | 97 | 	rdtsc(tsc_startlow, tsc_starthigh); | 
 | 98 | 	do { | 
 | 99 | 		hpet_end = hpet_readl(HPET_COUNTER); | 
 | 100 | 	} while ((hpet_end - hpet_start) < CALIBRATE_CNT_HPET); | 
 | 101 | 	rdtsc(tsc_endlow, tsc_endhigh); | 
 | 102 |  | 
 | 103 | 	/* 64-bit subtract - gcc just messes up with long longs */ | 
 | 104 | 	__asm__("subl %2,%0\n\t" | 
 | 105 | 		"sbbl %3,%1" | 
 | 106 | 		:"=a" (tsc_endlow), "=d" (tsc_endhigh) | 
 | 107 | 		:"g" (tsc_startlow), "g" (tsc_starthigh), | 
 | 108 | 		 "0" (tsc_endlow), "1" (tsc_endhigh)); | 
 | 109 |  | 
 | 110 | 	/* Error: ECPUTOOFAST */ | 
 | 111 | 	if (tsc_endhigh) | 
 | 112 | 		goto bad_calibration; | 
 | 113 |  | 
 | 114 | 	/* Error: ECPUTOOSLOW */ | 
 | 115 | 	if (tsc_endlow <= CALIBRATE_TIME_HPET) | 
 | 116 | 		goto bad_calibration; | 
 | 117 |  | 
 | 118 | 	ASM_DIV64_REG(result, remain, tsc_endlow, 0, CALIBRATE_TIME_HPET); | 
 | 119 | 	if (remain > (tsc_endlow >> 1)) | 
 | 120 | 		result++; /* rounding the result */ | 
 | 121 |  | 
 | 122 | 	if (tsc_hpet_quotient_ptr) { | 
 | 123 | 		unsigned long tsc_hpet_quotient; | 
 | 124 |  | 
 | 125 | 		ASM_DIV64_REG(tsc_hpet_quotient, remain, tsc_endlow, 0, | 
 | 126 | 			CALIBRATE_CNT_HPET); | 
 | 127 | 		if (remain > (tsc_endlow >> 1)) | 
 | 128 | 			tsc_hpet_quotient++; /* rounding the result */ | 
 | 129 | 		*tsc_hpet_quotient_ptr = tsc_hpet_quotient; | 
 | 130 | 	} | 
 | 131 |  | 
 | 132 | 	return result; | 
 | 133 | bad_calibration: | 
 | 134 | 	/* | 
 | 135 | 	 * the CPU was so fast/slow that the quotient wouldn't fit in | 
 | 136 | 	 * 32 bits.. | 
 | 137 | 	 */ | 
 | 138 | 	return 0; | 
 | 139 | } | 
 | 140 | #endif | 
 | 141 |  | 
| Venkatesh Pallipadi | 8a9e1b0 | 2005-06-23 00:08:13 -0700 | [diff] [blame] | 142 |  | 
 | 143 | unsigned long read_timer_tsc(void) | 
 | 144 | { | 
 | 145 | 	unsigned long retval; | 
 | 146 | 	rdtscl(retval); | 
 | 147 | 	return retval; | 
 | 148 | } | 
 | 149 |  | 
 | 150 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 151 | /* calculate cpu_khz */ | 
| Dave Jones | c5d28fb | 2005-05-31 19:03:46 -0700 | [diff] [blame] | 152 | void init_cpu_khz(void) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 153 | { | 
 | 154 | 	if (cpu_has_tsc) { | 
 | 155 | 		unsigned long tsc_quotient = calibrate_tsc(); | 
 | 156 | 		if (tsc_quotient) { | 
 | 157 | 			/* report CPU clock rate in Hz. | 
 | 158 | 			 * The formula is (10^6 * 2^32) / (2^32 * 1 / (clocks/us)) = | 
 | 159 | 			 * clock/second. Our precision is about 100 ppm. | 
 | 160 | 			 */ | 
 | 161 | 			{	unsigned long eax=0, edx=1000; | 
 | 162 | 				__asm__("divl %2" | 
 | 163 | 		       		:"=a" (cpu_khz), "=d" (edx) | 
 | 164 |         	       		:"r" (tsc_quotient), | 
 | 165 | 	                	"0" (eax), "1" (edx)); | 
| Andrew Morton | a3a255e | 2005-06-23 00:08:34 -0700 | [diff] [blame] | 166 | 				printk("Detected %u.%03u MHz processor.\n", | 
 | 167 | 					cpu_khz / 1000, cpu_khz % 1000); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 168 | 			} | 
 | 169 | 		} | 
 | 170 | 	} | 
 | 171 | } | 
| Dave Jones | c5d28fb | 2005-05-31 19:03:46 -0700 | [diff] [blame] | 172 |  |