[PATCH] ioc4: PCI bus speed detection

Several hardware features of SGI's IOC4 I/O controller chip require
timing-related driver calculations dependent upon the PCI bus speed.  This
patch enables the core IOC4 driver code to detect the actual bus speed and
store a value that can later be used by the IOC4 subdrivers as needed.

Signed-off-by: Brent Casavant <bcasavan@sgi.com>
Acked-by: Pat Gefre <pfg@sgi.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
diff --git a/drivers/sn/Kconfig b/drivers/sn/Kconfig
index 20f7515..13b8d24 100644
--- a/drivers/sn/Kconfig
+++ b/drivers/sn/Kconfig
@@ -6,7 +6,7 @@
 
 config SGI_IOC4
 	tristate "SGI IOC4 Base IO support"
-	depends on IA64_GENERIC || IA64_SGI_SN2
+	depends on (IA64_GENERIC || IA64_SGI_SN2) && MMTIMER
 	default m
 	---help---
 	This option enables basic support for the SGI IOC4-based Base IO
diff --git a/drivers/sn/ioc4.c b/drivers/sn/ioc4.c
index 70862d7..ea75b3d 100644
--- a/drivers/sn/ioc4.c
+++ b/drivers/sn/ioc4.c
@@ -29,7 +29,26 @@
 #include <linux/module.h>
 #include <linux/pci.h>
 #include <linux/ioc4.h>
+#include <linux/mmtimer.h>
+#include <linux/rtc.h>
 #include <linux/rwsem.h>
+#include <asm/sn/addrs.h>
+#include <asm/sn/clksupport.h>
+#include <asm/sn/shub_mmr.h>
+
+/***************
+ * Definitions *
+ ***************/
+
+/* Tweakable values */
+
+/* PCI bus speed detection/calibration */
+#define IOC4_CALIBRATE_COUNT 63	/* Calibration cycle period */
+#define IOC4_CALIBRATE_CYCLES 256	/* Average over this many cycles */
+#define IOC4_CALIBRATE_DISCARD 2	/* Discard first few cycles */
+#define IOC4_CALIBRATE_LOW_MHZ 25	/* Lower bound on bus speed sanity */
+#define IOC4_CALIBRATE_HIGH_MHZ 75	/* Upper bound on bus speed sanity */
+#define IOC4_CALIBRATE_DEFAULT_MHZ 66	/* Assumed if sanity check fails */
 
 /************************
  * Submodule management *
@@ -101,6 +120,112 @@
  * Device management *
  *********************/
 
+#define IOC4_CALIBRATE_LOW_LIMIT \
+	(1000*IOC4_EXTINT_COUNT_DIVISOR/IOC4_CALIBRATE_LOW_MHZ)
+#define IOC4_CALIBRATE_HIGH_LIMIT \
+	(1000*IOC4_EXTINT_COUNT_DIVISOR/IOC4_CALIBRATE_HIGH_MHZ)
+#define IOC4_CALIBRATE_DEFAULT \
+	(1000*IOC4_EXTINT_COUNT_DIVISOR/IOC4_CALIBRATE_DEFAULT_MHZ)
+
+#define IOC4_CALIBRATE_END \
+	(IOC4_CALIBRATE_CYCLES + IOC4_CALIBRATE_DISCARD)
+
+#define IOC4_INT_OUT_MODE_TOGGLE 0x7	/* Toggle INT_OUT every COUNT+1 ticks */
+
+/* Determines external interrupt output clock period of the PCI bus an
+ * IOC4 is attached to.  This value can be used to determine the PCI
+ * bus speed.
+ *
+ * IOC4 has a design feature that various internal timers are derived from
+ * the PCI bus clock.  This causes IOC4 device drivers to need to take the
+ * bus speed into account when setting various register values (e.g. INT_OUT
+ * register COUNT field, UART divisors, etc).  Since this information is
+ * needed by several subdrivers, it is determined by the main IOC4 driver,
+ * even though the following code utilizes external interrupt registers
+ * to perform the speed calculation.
+ */
+static void
+ioc4_clock_calibrate(struct ioc4_driver_data *idd)
+{
+	extern unsigned long sn_rtc_cycles_per_second;
+	union ioc4_int_out int_out;
+	union ioc4_gpcr gpcr;
+	unsigned int state, last_state = 1;
+	uint64_t start = 0, end, period;
+	unsigned int count = 0;
+
+	/* Enable output */
+	gpcr.raw = 0;
+	gpcr.fields.dir = IOC4_GPCR_DIR_0;
+	gpcr.fields.int_out_en = 1;
+	writel(gpcr.raw, &idd->idd_misc_regs->gpcr_s.raw);
+
+	/* Reset to power-on state */
+	writel(0, &idd->idd_misc_regs->int_out.raw);
+	mmiowb();
+
+	printk(KERN_INFO
+	       "%s: Calibrating PCI bus speed "
+	       "for pci_dev %s ... ", __FUNCTION__, pci_name(idd->idd_pdev));
+	/* Set up square wave */
+	int_out.raw = 0;
+	int_out.fields.count = IOC4_CALIBRATE_COUNT;
+	int_out.fields.mode = IOC4_INT_OUT_MODE_TOGGLE;
+	int_out.fields.diag = 0;
+	writel(int_out.raw, &idd->idd_misc_regs->int_out.raw);
+	mmiowb();
+
+	/* Check square wave period averaged over some number of cycles */
+	do {
+		int_out.raw = readl(&idd->idd_misc_regs->int_out.raw);
+		state = int_out.fields.int_out;
+		if (!last_state && state) {
+			count++;
+			if (count == IOC4_CALIBRATE_END) {
+				end = rtc_time();
+				break;
+			} else if (count == IOC4_CALIBRATE_DISCARD)
+				start = rtc_time();
+		}
+		last_state = state;
+	} while (1);
+
+	/* Calculation rearranged to preserve intermediate precision.
+	 * Logically:
+	 * 1. "end - start" gives us number of RTC cycles over all the
+	 *    square wave cycles measured.
+	 * 2. Divide by number of square wave cycles to get number of
+	 *    RTC cycles per square wave cycle.
+	 * 3. Divide by 2*(int_out.fields.count+1), which is the formula
+	 *    by which the IOC4 generates the square wave, to get the
+	 *    number of RTC cycles per IOC4 INT_OUT count.
+	 * 4. Divide by sn_rtc_cycles_per_second to get seconds per
+	 *    count.
+	 * 5. Multiply by 1E9 to get nanoseconds per count.
+	 */
+	period = ((end - start) * 1000000000) /
+	    (IOC4_CALIBRATE_CYCLES * 2 * (IOC4_CALIBRATE_COUNT + 1)
+	     * sn_rtc_cycles_per_second);
+
+	/* Bounds check the result. */
+	if (period > IOC4_CALIBRATE_LOW_LIMIT ||
+	    period < IOC4_CALIBRATE_HIGH_LIMIT) {
+		printk("failed. Assuming PCI clock ticks are %d ns.\n",
+		       IOC4_CALIBRATE_DEFAULT / IOC4_EXTINT_COUNT_DIVISOR);
+		period = IOC4_CALIBRATE_DEFAULT;
+	} else {
+		printk("succeeded. PCI clock ticks are %ld ns.\n",
+		       period / IOC4_EXTINT_COUNT_DIVISOR);
+	}
+
+	/* Remember results.  We store the extint clock period rather
+	 * than the PCI clock period so that greater precision is
+	 * retained.  Divide by IOC4_EXTINT_COUNT_DIVISOR to get
+	 * PCI clock period.
+	 */
+	idd->count_period = period;
+}
+
 /* Adds a new instance of an IOC4 card */
 static int
 ioc4_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id)
@@ -170,6 +295,9 @@
 	pci_write_config_dword(idd->idd_pdev, PCI_COMMAND,
 			       pcmd | PCI_COMMAND_PARITY | PCI_COMMAND_SERR);
 
+	/* Determine PCI clock */
+	ioc4_clock_calibrate(idd);
+
 	/* Disable/clear all interrupts.  Need to do this here lest
 	 * one submodule request the shared IOC4 IRQ, but interrupt
 	 * is generated by a different subdevice.