Merge ../linux-2.6-watchdog-mm
[linux-drm-fsl-dcu.git] / arch / x86_64 / kernel / pci-calgary.c
index f541fb564d92b048f99c2fe2ce733c86e2bcdf7f..37a770859e7180ae38e06c22d6064fbe1a4ebeab 100644 (file)
@@ -2,8 +2,9 @@
  * Derived from arch/powerpc/kernel/iommu.c
  *
  * Copyright (C) IBM Corporation, 2006
+ * Copyright (C) 2006  Jon Mason <jdmason@kudzu.us>
  *
- * Author: Jon Mason <jdmason@us.ibm.com>
+ * Author: Jon Mason <jdmason@kudzu.us>
  * Author: Muli Ben-Yehuda <muli@il.ibm.com>
 
  * This program is free software; you can redistribute it and/or modify
@@ -21,7 +22,6 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
  */
 
-#include <linux/config.h>
 #include <linux/kernel.h>
 #include <linux/init.h>
 #include <linux/types.h>
@@ -52,7 +52,8 @@
 #define ONE_BASED_CHASSIS_NUM   1
 
 /* register offsets inside the host bridge space */
-#define PHB_CSR_OFFSET         0x0110
+#define CALGARY_CONFIG_REG     0x0108
+#define PHB_CSR_OFFSET         0x0110 /* Channel Status */
 #define PHB_PLSSR_OFFSET       0x0120
 #define PHB_CONFIG_RW_OFFSET   0x0160
 #define PHB_IOBASE_BAR_LOW     0x0170
 #define TAR_VALID              0x0000000000000008UL
 /* CSR (Channel/DMA Status Register) */
 #define CSR_AGENT_MASK         0xffe0ffff
+/* CCR (Calgary Configuration Register) */
+#define CCR_2SEC_TIMEOUT        0x000000000000000EUL
 
 #define MAX_NUM_OF_PHBS                8 /* how many PHBs in total? */
 #define MAX_NUM_CHASSIS                8 /* max number of chassis */
-#define MAX_PHB_BUS_NUM                (MAX_NUM_OF_PHBS * MAX_NUM_CHASSIS * 2) /* max dev->bus->number */
+/* MAX_PHB_BUS_NUM is the maximal possible dev->bus->number */
+#define MAX_PHB_BUS_NUM                (MAX_NUM_OF_PHBS * MAX_NUM_CHASSIS * 2)
 #define PHBS_PER_CALGARY       4
 
 /* register offsets in Calgary's internal register space */
@@ -117,7 +121,7 @@ static int calgary_detected __read_mostly = 0;
 
 struct calgary_bus_info {
        void *tce_space;
-       int translation_disabled;
+       unsigned char translation_disabled;
        signed char phbid;
 };
 
@@ -127,15 +131,33 @@ static void tce_cache_blast(struct iommu_table *tbl);
 
 /* enable this to stress test the chip's TCE cache */
 #ifdef CONFIG_IOMMU_DEBUG
-static inline void tce_cache_blast_stress(struct iommu_table *tbl)
+int debugging __read_mostly = 1;
+
+static inline unsigned long verify_bit_range(unsigned long* bitmap,
+       int expected, unsigned long start, unsigned long end)
 {
-       tce_cache_blast(tbl);
+       unsigned long idx = start;
+
+       BUG_ON(start >= end);
+
+       while (idx < end) {
+               if (!!test_bit(idx, bitmap) != expected)
+                       return idx;
+               ++idx;
+       }
+
+       /* all bits have the expected value */
+       return ~0UL;
 }
-#else
-static inline void tce_cache_blast_stress(struct iommu_table *tbl)
+#else /* debugging is disabled */
+int debugging __read_mostly = 0;
+
+static inline unsigned long verify_bit_range(unsigned long* bitmap,
+       int expected, unsigned long start, unsigned long end)
 {
+       return ~0UL;
 }
-#endif /* BLAST_TCE_CACHE_ON_UNMAP */
+#endif /* CONFIG_IOMMU_DEBUG */
 
 static inline unsigned int num_dma_pages(unsigned long dma, unsigned int dmalen)
 {
@@ -158,6 +180,7 @@ static void iommu_range_reserve(struct iommu_table *tbl,
 {
        unsigned long index;
        unsigned long end;
+       unsigned long badbit;
 
        index = start_addr >> PAGE_SHIFT;
 
@@ -169,14 +192,15 @@ static void iommu_range_reserve(struct iommu_table *tbl,
        if (end > tbl->it_size) /* don't go off the table */
                end = tbl->it_size;
 
-       while (index < end) {
-               if (test_bit(index, tbl->it_map))
+       badbit = verify_bit_range(tbl->it_map, 0, index, end);
+       if (badbit != ~0UL) {
+               if (printk_ratelimit())
                        printk(KERN_ERR "Calgary: entry already allocated at "
                               "0x%lx tbl %p dma 0x%lx npages %u\n",
-                              index, tbl, start_addr, npages);
-               ++index;
+                              badbit, tbl, start_addr, npages);
        }
-       set_bit_string(tbl->it_map, start_addr >> PAGE_SHIFT, npages);
+
+       set_bit_string(tbl->it_map, index, npages);
 }
 
 static unsigned long iommu_range_alloc(struct iommu_table *tbl,
@@ -243,7 +267,7 @@ static void __iommu_free(struct iommu_table *tbl, dma_addr_t dma_addr,
        unsigned int npages)
 {
        unsigned long entry;
-       unsigned long i;
+       unsigned long badbit;
 
        entry = dma_addr >> PAGE_SHIFT;
 
@@ -251,16 +275,15 @@ static void __iommu_free(struct iommu_table *tbl, dma_addr_t dma_addr,
 
        tce_free(tbl, entry, npages);
 
-       for (i = 0; i < npages; ++i) {
-               if (!test_bit(entry + i, tbl->it_map))
+       badbit = verify_bit_range(tbl->it_map, 1, entry, entry + npages);
+       if (badbit != ~0UL) {
+               if (printk_ratelimit())
                        printk(KERN_ERR "Calgary: bit is off at 0x%lx "
                               "tbl %p dma 0x%Lx entry 0x%lx npages %u\n",
-                              entry + i, tbl, dma_addr, entry, npages);
+                              badbit, tbl, dma_addr, entry, npages);
        }
 
        __clear_bit_string(tbl->it_map, entry, npages);
-
-       tce_cache_blast_stress(tbl);
 }
 
 static void iommu_free(struct iommu_table *tbl, dma_addr_t dma_addr,
@@ -695,7 +718,7 @@ static void calgary_watchdog(unsigned long data)
 
        /* If no error, the agent ID in the CSR is not valid */
        if (val32 & CSR_AGENT_MASK) {
-               printk(KERN_EMERG "calgary_watchdog: DMA error on bus %d, "
+               printk(KERN_EMERG "calgary_watchdog: DMA error on PHB %#x, "
                                  "CSR = %#x\n", dev->bus->number, val32);
                writel(0, target);
 
@@ -712,6 +735,38 @@ static void calgary_watchdog(unsigned long data)
        }
 }
 
+static void __init calgary_increase_split_completion_timeout(void __iomem *bbar,
+       unsigned char busnum)
+{
+       u64 val64;
+       void __iomem *target;
+       unsigned long phb_shift = -1;
+       u64 mask;
+
+       switch (busno_to_phbid(busnum)) {
+       case 0: phb_shift = (63 - 19);
+               break;
+       case 1: phb_shift = (63 - 23);
+               break;
+       case 2: phb_shift = (63 - 27);
+               break;
+       case 3: phb_shift = (63 - 35);
+               break;
+       default:
+               BUG_ON(busno_to_phbid(busnum));
+       }
+
+       target = calgary_reg(bbar, CALGARY_CONFIG_REG);
+       val64 = be64_to_cpu(readq(target));
+
+       /* zero out this PHB's timer bits */
+       mask = ~(0xFUL << phb_shift);
+       val64 &= mask;
+       val64 |= (CCR_2SEC_TIMEOUT << phb_shift);
+       writeq(cpu_to_be64(val64), target);
+       readq(target); /* flush */
+}
+
 static void __init calgary_enable_translation(struct pci_dev *dev)
 {
        u32 val32;
@@ -729,13 +784,20 @@ static void __init calgary_enable_translation(struct pci_dev *dev)
        val32 = be32_to_cpu(readl(target));
        val32 |= PHB_TCE_ENABLE | PHB_DAC_DISABLE | PHB_MCSR_ENABLE;
 
-       printk(KERN_INFO "Calgary: enabling translation on PHB %d\n", busnum);
+       printk(KERN_INFO "Calgary: enabling translation on PHB %#x\n", busnum);
        printk(KERN_INFO "Calgary: errant DMAs will now be prevented on this "
               "bus.\n");
 
        writel(cpu_to_be32(val32), target);
        readl(target); /* flush */
 
+       /*
+        * Give split completion a longer timeout on bus 1 for aic94xx
+        * http://bugzilla.kernel.org/show_bug.cgi?id=7180
+        */
+       if (busnum == 1)
+               calgary_increase_split_completion_timeout(bbar, busnum);
+
        init_timer(&tbl->watchdog_timer);
        tbl->watchdog_timer.function = &calgary_watchdog;
        tbl->watchdog_timer.data = (unsigned long)dev;
@@ -759,7 +821,7 @@ static void __init calgary_disable_translation(struct pci_dev *dev)
        val32 = be32_to_cpu(readl(target));
        val32 &= ~(PHB_TCE_ENABLE | PHB_DAC_DISABLE | PHB_MCSR_ENABLE);
 
-       printk(KERN_INFO "Calgary: disabling translation on PHB %d!\n", busnum);
+       printk(KERN_INFO "Calgary: disabling translation on PHB %#x!\n", busnum);
        writel(cpu_to_be32(val32), target);
        readl(target); /* flush */
 
@@ -771,7 +833,16 @@ static inline unsigned int __init locate_register_space(struct pci_dev *dev)
        int rionodeid;
        u32 address;
 
-       rionodeid = (dev->bus->number % 15 > 4) ? 3 : 2;
+       /*
+        * Each Calgary has four busses. The first four busses (first Calgary)
+        * have RIO node ID 2, then the next four (second Calgary) have RIO
+        * node ID 3, the next four (third Calgary) have node ID 2 again, etc.
+        * We use a gross hack - relying on the dev->bus->number ordering,
+        * modulo 14 - to decide which Calgary a given bus is on. Busses 0, 1,
+        * 2 and 4 are on the first Calgary (id 2), 6, 8, a and c are on the
+        * second (id 3), and then it repeats modulo 14.
+        */
+       rionodeid = (dev->bus->number % 14 > 4) ? 3 : 2;
        /*
         * register space address calculation as follows:
         * FE0MB-8MB*OneBasedChassisNumber+1MB*(RioNodeId-ChassisBase)
@@ -779,7 +850,7 @@ static inline unsigned int __init locate_register_space(struct pci_dev *dev)
         * RioNodeId is 2 for first Calgary, 3 for second Calgary
         */
        address = START_ADDRESS -
-               (0x800000 * (ONE_BASED_CHASSIS_NUM + dev->bus->number / 15)) +
+               (0x800000 * (ONE_BASED_CHASSIS_NUM + dev->bus->number / 14)) +
                (0x100000) * (rionodeid - CHASSIS_BASE);
        return address;
 }
@@ -797,6 +868,8 @@ static int __init calgary_init_one(struct pci_dev *dev)
        void __iomem *bbar;
        int ret;
 
+       BUG_ON(dev->bus->number >= MAX_PHB_BUS_NUM);
+
        address = locate_register_space(dev);
        /* map entire 1MB of Calgary config space */
        bbar = ioremap_nocache(address, 1024 * 1024);
@@ -823,10 +896,10 @@ done:
 
 static int __init calgary_init(void)
 {
-       int i, ret = -ENODEV;
+       int ret = -ENODEV;
        struct pci_dev *dev = NULL;
 
-       for (i = 0; i < MAX_PHB_BUS_NUM; i++) {
+       do {
                dev = pci_get_device(PCI_VENDOR_ID_IBM,
                                     PCI_DEVICE_ID_IBM_CALGARY,
                                     dev);
@@ -842,12 +915,12 @@ static int __init calgary_init(void)
                ret = calgary_init_one(dev);
                if (ret)
                        goto error;
-       }
+       } while (1);
 
        return ret;
 
 error:
-       for (i--; i >= 0; i--) {
+       do {
                dev = pci_find_device_reverse(PCI_VENDOR_ID_IBM,
                                              PCI_DEVICE_ID_IBM_CALGARY,
                                              dev);
@@ -863,7 +936,7 @@ error:
                calgary_disable_translation(dev);
                calgary_free_bus(dev);
                pci_dev_put(dev); /* Undo calgary_init_one()'s pci_dev_get() */
-       }
+       } while (1);
 
        return ret;
 }
@@ -904,6 +977,9 @@ void __init detect_calgary(void)
        if (swiotlb || no_iommu || iommu_detected)
                return;
 
+       if (!early_pci_allowed())
+               return;
+
        specified_table_size = determine_tce_table_size(end_pfn * PAGE_SIZE);
 
        for (bus = 0; bus < MAX_PHB_BUS_NUM; bus++) {
@@ -944,8 +1020,10 @@ void __init detect_calgary(void)
        if (calgary_found) {
                iommu_detected = 1;
                calgary_detected = 1;
-               printk(KERN_INFO "PCI-DMA: Calgary IOMMU detected. "
-                      "TCE table spec is %d.\n", specified_table_size);
+               printk(KERN_INFO "PCI-DMA: Calgary IOMMU detected.\n");
+               printk(KERN_INFO "PCI-DMA: Calgary TCE table spec is %d, "
+                      "CONFIG_IOMMU_DEBUG is %s.\n", specified_table_size,
+                      debugging ? "enabled" : "disabled");
        }
        return;
 
@@ -1028,7 +1106,7 @@ static int __init calgary_parse_options(char *p)
 
                        if (bridge < MAX_PHB_BUS_NUM) {
                                printk(KERN_INFO "Calgary: disabling "
-                                      "translation for PHB 0x%x\n", bridge);
+                                      "translation for PHB %#x\n", bridge);
                                bus_info[bridge].translation_disabled = 1;
                        }
                }