[BLOCK] kill generic max_back_kb handling
[powerpc.git] / drivers / sn / ioc4.c
index 70862d7..ea75b3d 100644 (file)
 #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 @@ ioc4_unregister_submodule(struct ioc4_submodule *is)
  * 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 @@ ioc4_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id)
        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.