]> nv-tegra.nvidia Code Review - linux-2.6.git/commitdiff
sh: Hook up ERR/PERR/SERR detection for SH7780 PCI host controllers.
authorPaul Mundt <lethal@linux-sh.org>
Mon, 1 Feb 2010 07:39:46 +0000 (16:39 +0900)
committerPaul Mundt <lethal@linux-sh.org>
Mon, 1 Feb 2010 07:39:46 +0000 (16:39 +0900)
These were never handled before, so implement some common infrastructure
to support them, then make use of that in the SH7780-specific code. In
practice there is little here that can not be generalized for SH4 parts,
which will be an incremental change as the 7780/7751 code is gradually
unified.

Signed-off-by: Paul Mundt <lethal@linux-sh.org>
arch/sh/drivers/pci/common.c
arch/sh/drivers/pci/ops-sh4.c
arch/sh/drivers/pci/pci-sh7780.c
arch/sh/drivers/pci/pci.c
arch/sh/include/asm/pci.h

index f67c946a861292f72a06b89cbe1c3bb07dbbe646..25aec005da18caa2f54f5f06d08634782cb02f12 100644 (file)
@@ -1,4 +1,6 @@
 #include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/timer.h>
 #include <linux/kernel.h>
 
 static int __init
@@ -62,3 +64,80 @@ int __init pci_is_66mhz_capable(struct pci_channel *hose,
 
        return cap66 > 0;
 }
+
+static void pcibios_enable_err(unsigned long __data)
+{
+       struct pci_channel *hose = (struct pci_channel *)__data;
+
+       del_timer(&hose->err_timer);
+       printk(KERN_DEBUG "PCI: re-enabling error IRQ.\n");
+       enable_irq(hose->err_irq);
+}
+
+static void pcibios_enable_serr(unsigned long __data)
+{
+       struct pci_channel *hose = (struct pci_channel *)__data;
+
+       del_timer(&hose->serr_timer);
+       printk(KERN_DEBUG "PCI: re-enabling system error IRQ.\n");
+       enable_irq(hose->serr_irq);
+}
+
+void pcibios_enable_timers(struct pci_channel *hose)
+{
+       if (hose->err_irq) {
+               init_timer(&hose->err_timer);
+               hose->err_timer.data = (unsigned long)hose;
+               hose->err_timer.function = pcibios_enable_err;
+       }
+
+       if (hose->serr_irq) {
+               init_timer(&hose->serr_timer);
+               hose->serr_timer.data = (unsigned long)hose;
+               hose->serr_timer.function = pcibios_enable_serr;
+       }
+}
+
+/*
+ * A simple handler for the regular PCI status errors, called from IRQ
+ * context.
+ */
+unsigned int pcibios_handle_status_errors(unsigned long addr,
+                                         unsigned int status,
+                                         struct pci_channel *hose)
+{
+       unsigned int cmd = 0;
+
+       if (status & PCI_STATUS_REC_MASTER_ABORT) {
+               printk(KERN_DEBUG "PCI: master abort, pc=0x%08lx\n", addr);
+               cmd |= PCI_STATUS_REC_MASTER_ABORT;
+       }
+
+       if (status & PCI_STATUS_REC_TARGET_ABORT) {
+               printk(KERN_DEBUG "PCI: target abort: ");
+               pcibios_report_status(PCI_STATUS_REC_TARGET_ABORT |
+                                     PCI_STATUS_SIG_TARGET_ABORT |
+                                     PCI_STATUS_REC_MASTER_ABORT, 1);
+               printk("\n");
+
+               cmd |= PCI_STATUS_REC_TARGET_ABORT;
+       }
+
+       if (status & (PCI_STATUS_PARITY | PCI_STATUS_DETECTED_PARITY)) {
+               printk(KERN_DEBUG "PCI: parity error detected: ");
+               pcibios_report_status(PCI_STATUS_PARITY |
+                                     PCI_STATUS_DETECTED_PARITY, 1);
+               printk("\n");
+
+               cmd |= PCI_STATUS_PARITY | PCI_STATUS_DETECTED_PARITY;
+
+               /* Now back off of the IRQ for awhile */
+               if (hose->err_irq) {
+                       disable_irq(hose->err_irq);
+                       hose->err_timer.expires = jiffies + HZ;
+                       add_timer(&hose->err_timer);
+               }
+       }
+
+       return cmd;
+}
index e55e81a71727cb9cc33b61aa03b872db2a13251a..0b81999fb88ba96c15de7606fe80fa02956ae37e 100644 (file)
@@ -16,7 +16,7 @@
  * Direct access to PCI hardware...
  */
 #define CONFIG_CMD(bus, devfn, where) \
-       (P1SEG | (bus->number << 16) | (devfn << 8) | (where & ~3))
+       (0x80000000 | (bus->number << 16) | (devfn << 8) | (where & ~3))
 
 static DEFINE_SPINLOCK(sh4_pci_lock);
 
index 0e0ddd67e6e13944b9536818becffeba7cca5aeb..86373314f45829a1e9d12560fd45660ffbe3e31b 100644 (file)
@@ -11,6 +11,9 @@
 #include <linux/kernel.h>
 #include <linux/init.h>
 #include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/timer.h>
+#include <linux/irq.h>
 #include <linux/errno.h>
 #include <linux/delay.h>
 #include <linux/log2.h>
@@ -39,8 +42,165 @@ static struct pci_channel sh7780_pci_controller = {
        .io_resource    = &sh7785_io_resource,
        .io_offset      = 0x00000000,
        .io_map_base    = SH7780_PCI_IO_BASE,
+       .serr_irq       = evt2irq(0xa00),
+       .err_irq        = evt2irq(0xaa0),
 };
 
+struct pci_errors {
+       unsigned int    mask;
+       const char      *str;
+} pci_arbiter_errors[] = {
+       { SH4_PCIAINT_MBKN,     "master broken" },
+       { SH4_PCIAINT_TBTO,     "target bus time out" },
+       { SH4_PCIAINT_MBTO,     "master bus time out" },
+       { SH4_PCIAINT_TABT,     "target abort" },
+       { SH4_PCIAINT_MABT,     "master abort" },
+       { SH4_PCIAINT_RDPE,     "read data parity error" },
+       { SH4_PCIAINT_WDPE,     "write data parity error" },
+}, pci_interrupt_errors[] = {
+       { SH4_PCIINT_MLCK,      "master lock error" },
+       { SH4_PCIINT_TABT,      "target-target abort" },
+       { SH4_PCIINT_TRET,      "target retry time out" },
+       { SH4_PCIINT_MFDE,      "master function disable erorr" },
+       { SH4_PCIINT_PRTY,      "address parity error" },
+       { SH4_PCIINT_SERR,      "SERR" },
+       { SH4_PCIINT_TWDP,      "data parity error for target write" },
+       { SH4_PCIINT_TRDP,      "PERR detected for target read" },
+       { SH4_PCIINT_MTABT,     "target abort for master" },
+       { SH4_PCIINT_MMABT,     "master abort for master" },
+       { SH4_PCIINT_MWPD,      "master write data parity error" },
+       { SH4_PCIINT_MRPD,      "master read data parity error" },
+};
+
+static irqreturn_t sh7780_pci_err_irq(int irq, void *dev_id)
+{
+       struct pci_channel *hose = dev_id;
+       unsigned long addr;
+       unsigned int status;
+       unsigned int cmd;
+       int i;
+
+       addr = __raw_readl(hose->reg_base + SH4_PCIALR);
+
+       /*
+        * Handle status errors.
+        */
+       status = __raw_readw(hose->reg_base + PCI_STATUS);
+       if (status & (PCI_STATUS_PARITY |
+                     PCI_STATUS_DETECTED_PARITY |
+                     PCI_STATUS_SIG_TARGET_ABORT |
+                     PCI_STATUS_REC_TARGET_ABORT |
+                     PCI_STATUS_REC_MASTER_ABORT)) {
+               cmd = pcibios_handle_status_errors(addr, status, hose);
+               if (likely(cmd))
+                       __raw_writew(cmd, hose->reg_base + PCI_STATUS);
+       }
+
+       /*
+        * Handle arbiter errors.
+        */
+       status = __raw_readl(hose->reg_base + SH4_PCIAINT);
+       for (i = cmd = 0; i < ARRAY_SIZE(pci_arbiter_errors); i++) {
+               if (status & pci_arbiter_errors[i].mask) {
+                       printk(KERN_DEBUG "PCI: %s, addr=%08lx\n",
+                              pci_arbiter_errors[i].str, addr);
+                       cmd |= pci_arbiter_errors[i].mask;
+               }
+       }
+       __raw_writel(cmd, hose->reg_base + SH4_PCIAINT);
+
+       /*
+        * Handle the remaining PCI errors.
+        */
+       status = __raw_readl(hose->reg_base + SH4_PCIINT);
+       for (i = cmd = 0; i < ARRAY_SIZE(pci_interrupt_errors); i++) {
+               if (status & pci_interrupt_errors[i].mask) {
+                       printk(KERN_DEBUG "PCI: %s, addr=%08lx\n",
+                              pci_interrupt_errors[i].str, addr);
+                       cmd |= pci_interrupt_errors[i].mask;
+               }
+       }
+       __raw_writel(cmd, hose->reg_base + SH4_PCIINT);
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t sh7780_pci_serr_irq(int irq, void *dev_id)
+{
+       struct pci_channel *hose = dev_id;
+
+       printk(KERN_DEBUG "PCI: system error received: ");
+       pcibios_report_status(PCI_STATUS_SIG_SYSTEM_ERROR, 1);
+       printk("\n");
+
+       /* Deassert SERR */
+       __raw_writel(SH4_PCIINTM_SDIM, hose->reg_base + SH4_PCIINTM);
+
+       /* Back off the IRQ for awhile */
+       disable_irq(irq);
+       hose->serr_timer.expires = jiffies + HZ;
+       add_timer(&hose->serr_timer);
+
+       return IRQ_HANDLED;
+}
+
+static int __init sh7780_pci_setup_irqs(struct pci_channel *hose)
+{
+       int ret;
+
+       /* Clear out PCI arbiter IRQs */
+       __raw_writel(0, hose->reg_base + SH4_PCIAINT);
+
+       /* Clear all error conditions */
+       __raw_writew(PCI_STATUS_DETECTED_PARITY  | \
+                    PCI_STATUS_SIG_SYSTEM_ERROR | \
+                    PCI_STATUS_REC_MASTER_ABORT | \
+                    PCI_STATUS_REC_TARGET_ABORT | \
+                    PCI_STATUS_SIG_TARGET_ABORT | \
+                    PCI_STATUS_PARITY, hose->reg_base + PCI_STATUS);
+
+       ret = request_irq(hose->serr_irq, sh7780_pci_serr_irq, IRQF_DISABLED,
+                         "PCI SERR interrupt", hose);
+       if (unlikely(ret)) {
+               printk(KERN_ERR "PCI: Failed hooking SERR IRQ\n");
+               return ret;
+       }
+
+       /*
+        * The PCI ERR IRQ needs to be IRQF_SHARED since all of the power
+        * down IRQ vectors are routed through the ERR IRQ vector. We
+        * only request_irq() once as there is only a single masking
+        * source for multiple events.
+        */
+       ret = request_irq(hose->err_irq, sh7780_pci_err_irq, IRQF_SHARED,
+                         "PCI ERR interrupt", hose);
+       if (unlikely(ret)) {
+               free_irq(hose->serr_irq, hose);
+               return ret;
+       }
+
+       /* Unmask all of the arbiter IRQs. */
+       __raw_writel(SH4_PCIAINT_MBKN | SH4_PCIAINT_TBTO | SH4_PCIAINT_MBTO | \
+                    SH4_PCIAINT_TABT | SH4_PCIAINT_MABT | SH4_PCIAINT_RDPE | \
+                    SH4_PCIAINT_WDPE, hose->reg_base + SH4_PCIAINTM);
+
+       /* Unmask all of the PCI IRQs */
+       __raw_writel(SH4_PCIINTM_TTADIM  | SH4_PCIINTM_TMTOIM  | \
+                    SH4_PCIINTM_MDEIM   | SH4_PCIINTM_APEDIM  | \
+                    SH4_PCIINTM_SDIM    | SH4_PCIINTM_DPEITWM | \
+                    SH4_PCIINTM_PEDITRM | SH4_PCIINTM_TADIMM  | \
+                    SH4_PCIINTM_MADIMM  | SH4_PCIINTM_MWPDIM  | \
+                    SH4_PCIINTM_MRDPEIM, hose->reg_base + SH4_PCIINTM);
+
+       return ret;
+}
+
+static inline void __init sh7780_pci_teardown_irqs(struct pci_channel *hose)
+{
+       free_irq(hose->err_irq, hose);
+       free_irq(hose->serr_irq, hose);
+}
+
 static void __init sh7780_pci66_init(struct pci_channel *hose)
 {
        unsigned int tmp;
@@ -149,33 +309,12 @@ static int __init sh7780_pci_init(void)
        __raw_writel(((memsize - SZ_1M) & 0x1ff00000) | 1,
                     chan->reg_base + SH4_PCILSR0);
 
-       /* Clear out PCI arbiter IRQs */
-       __raw_writel(0, chan->reg_base + SH4_PCIAINT);
-
-       /* Unmask all of the arbiter IRQs. */
-       __raw_writel(SH4_PCIAINT_MBKN | SH4_PCIAINT_TBTO | SH4_PCIAINT_MBTO | \
-                    SH4_PCIAINT_TABT | SH4_PCIAINT_MABT | SH4_PCIAINT_RDPE | \
-                    SH4_PCIAINT_WDPE, chan->reg_base + SH4_PCIAINTM);
-
-       /* Clear all error conditions */
-       __raw_writew(PCI_STATUS_DETECTED_PARITY  | \
-                    PCI_STATUS_SIG_SYSTEM_ERROR | \
-                    PCI_STATUS_REC_MASTER_ABORT | \
-                    PCI_STATUS_REC_TARGET_ABORT | \
-                    PCI_STATUS_SIG_TARGET_ABORT | \
-                    PCI_STATUS_PARITY, chan->reg_base + PCI_STATUS);
-
-       __raw_writew(PCI_COMMAND_SERR | PCI_COMMAND_WAIT | \
-                    PCI_COMMAND_PARITY | PCI_COMMAND_MASTER | \
-                    PCI_COMMAND_MEMORY, chan->reg_base + PCI_COMMAND);
-
-       /* Unmask all of the PCI IRQs */
-       __raw_writel(SH4_PCIINTM_TTADIM  | SH4_PCIINTM_TMTOIM  | \
-                    SH4_PCIINTM_MDEIM   | SH4_PCIINTM_APEDIM  | \
-                    SH4_PCIINTM_SDIM    | SH4_PCIINTM_DPEITWM | \
-                    SH4_PCIINTM_PEDITRM | SH4_PCIINTM_TADIMM  | \
-                    SH4_PCIINTM_MADIMM  | SH4_PCIINTM_MWPDIM  | \
-                    SH4_PCIINTM_MRDPEIM, chan->reg_base + SH4_PCIINTM);
+       /*
+        * Hook up the ERR and SERR IRQs.
+        */
+       ret = sh7780_pci_setup_irqs(chan);
+       if (unlikely(ret))
+               return ret;
 
        /*
         * Disable the cache snoop controller for non-coherent DMA.
@@ -191,6 +330,10 @@ static int __init sh7780_pci_init(void)
        __raw_writel(0, chan->reg_base + SH7780_PCIIOBR);
        __raw_writel(0, chan->reg_base + SH7780_PCIIOBMR);
 
+       __raw_writew(PCI_COMMAND_SERR   | PCI_COMMAND_WAIT   | \
+                    PCI_COMMAND_PARITY | PCI_COMMAND_MASTER | \
+                    PCI_COMMAND_MEMORY, chan->reg_base + PCI_COMMAND);
+
        /*
         * Initialization mode complete, release the control register and
         * enable round robin mode to stop device overruns/starvation.
@@ -200,7 +343,7 @@ static int __init sh7780_pci_init(void)
 
        ret = register_pci_controller(chan);
        if (unlikely(ret))
-               return ret;
+               goto err;
 
        sh7780_pci66_init(chan);
 
@@ -209,5 +352,9 @@ static int __init sh7780_pci_init(void)
               66 : 33);
 
        return 0;
+
+err:
+       sh7780_pci_teardown_irqs(chan);
+       return ret;
 }
 arch_initcall(sh7780_pci_init);
index 488331c45033d9159c7d7a2b81861bbb2f7fe1b3..8e42dfbbe76a27f88f0c7d326187a74b47e64f1a 100644 (file)
@@ -78,6 +78,11 @@ int __devinit register_pci_controller(struct pci_channel *hose)
                       "registering PCI controller with io_map_base unset\n");
        }
 
+       /*
+        * Setup the ERR/PERR and SERR timers, if available.
+        */
+       pcibios_enable_timers(hose);
+
        /*
         * Scan the bus if it is register after the PCI subsystem
         * initialization.
@@ -289,6 +294,52 @@ char * __devinit pcibios_setup(char *str)
        return str;
 }
 
+/*
+ * We can't use pci_find_device() here since we are
+ * called from interrupt context.
+ */
+static void pcibios_bus_report_status(struct pci_bus *bus,
+               unsigned int status_mask, int warn)
+{
+       struct pci_dev *dev;
+
+       list_for_each_entry(dev, &bus->devices, bus_list) {
+               u16 status;
+
+               /*
+                * ignore host bridge - we handle
+                * that separately
+                */
+               if (dev->bus->number == 0 && dev->devfn == 0)
+                       continue;
+
+               pci_read_config_word(dev, PCI_STATUS, &status);
+               if (status == 0xffff)
+                       continue;
+
+               if ((status & status_mask) == 0)
+                       continue;
+
+               /* clear the status errors */
+               pci_write_config_word(dev, PCI_STATUS, status & status_mask);
+
+               if (warn)
+                       printk("(%s: %04X) ", pci_name(dev), status);
+       }
+
+       list_for_each_entry(dev, &bus->devices, bus_list)
+               if (dev->subordinate)
+                       pcibios_bus_report_status(dev->subordinate, status_mask, warn);
+}
+
+void pcibios_report_status(unsigned int status_mask, int warn)
+{
+       struct pci_channel *hose;
+
+       for (hose = hose_head; hose; hose = hose->next)
+               pcibios_bus_report_status(hose->bus, status_mask, warn);
+}
+
 int pci_mmap_page_range(struct pci_dev *dev, struct vm_area_struct *vma,
                        enum pci_mmap_state mmap_state, int write_combine)
 {
index bbd10cf7982586fcdc1d72bd5620b81ac95cb649..1de83f2161f7b126a9e18157b9e65f520cbb0f11 100644 (file)
@@ -29,9 +29,20 @@ struct pci_channel {
 
        unsigned int            index;
        unsigned int            need_domain_info;
+
+       /* Optional error handling */
+       struct timer_list       err_timer, serr_timer;
+       unsigned int            err_irq, serr_irq;
 };
 
+/* arch/sh/drivers/pci/pci.c */
 extern int register_pci_controller(struct pci_channel *hose);
+extern void pcibios_report_status(unsigned int status_mask, int warn);
+
+/* arch/sh/drivers/pci/common.c */
+extern void pcibios_enable_timers(struct pci_channel *hose);
+extern unsigned int pcibios_handle_status_errors(unsigned long addr,
+                                unsigned int status, struct pci_channel *hose);
 extern int pci_is_66mhz_capable(struct pci_channel *hose,
                                int top_bus, int current_bus);