diff options
author | David S. Miller <davem@davemloft.net> | 2008-12-05 11:43:03 +0300 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2008-12-06 04:02:32 +0300 |
commit | 5fce09c6f636449d6df971971af1bd4328a21890 (patch) | |
tree | 0658a110c1c8824f59127884c7fbace87cf5e28f /arch/sparc | |
parent | efeac2f87609ab25dab329ce16876eb9110b2084 (diff) | |
download | linux-5fce09c6f636449d6df971971af1bd4328a21890.tar.xz |
sparc: Move irq_trans_init() and support code into seperate file.
All sparc64 specific, so only build this file on sparc64.
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'arch/sparc')
-rw-r--r-- | arch/sparc/kernel/Makefile | 1 | ||||
-rw-r--r-- | arch/sparc/kernel/prom.h | 4 | ||||
-rw-r--r-- | arch/sparc/kernel/prom_64.c | 830 | ||||
-rw-r--r-- | arch/sparc/kernel/prom_irqtrans.c | 842 |
4 files changed, 847 insertions, 830 deletions
diff --git a/arch/sparc/kernel/Makefile b/arch/sparc/kernel/Makefile index d0d63b1ab02e..04c74d00f832 100644 --- a/arch/sparc/kernel/Makefile +++ b/arch/sparc/kernel/Makefile @@ -38,6 +38,7 @@ obj-$(CONFIG_SPARC32) += muldiv.o obj-y += prom_common.o obj-y += prom_$(BITS).o obj-y += of_device_$(BITS).o +obj-$(CONFIG_SPARC64) += prom_irqtrans.o obj-$(CONFIG_SPARC64) += reboot.o obj-$(CONFIG_SPARC64) += sysfs.o diff --git a/arch/sparc/kernel/prom.h b/arch/sparc/kernel/prom.h index b7258ce87ccc..49925101f8f8 100644 --- a/arch/sparc/kernel/prom.h +++ b/arch/sparc/kernel/prom.h @@ -9,4 +9,8 @@ extern rwlock_t devtree_lock; /* temporary while merging */ extern void * prom_early_alloc(unsigned long size); +#ifdef CONFIG_SPARC64 +extern void irq_trans_init(struct device_node *dp); +#endif + #endif /* __PROM_H */ diff --git a/arch/sparc/kernel/prom_64.c b/arch/sparc/kernel/prom_64.c index 1c7b9d7b7889..d745b9f44d2d 100644 --- a/arch/sparc/kernel/prom_64.c +++ b/arch/sparc/kernel/prom_64.c @@ -51,836 +51,6 @@ void * __init prom_early_alloc(unsigned long size) return ret; } -#ifdef CONFIG_PCI -/* PSYCHO interrupt mapping support. */ -#define PSYCHO_IMAP_A_SLOT0 0x0c00UL -#define PSYCHO_IMAP_B_SLOT0 0x0c20UL -static unsigned long psycho_pcislot_imap_offset(unsigned long ino) -{ - unsigned int bus = (ino & 0x10) >> 4; - unsigned int slot = (ino & 0x0c) >> 2; - - if (bus == 0) - return PSYCHO_IMAP_A_SLOT0 + (slot * 8); - else - return PSYCHO_IMAP_B_SLOT0 + (slot * 8); -} - -#define PSYCHO_OBIO_IMAP_BASE 0x1000UL - -#define PSYCHO_ONBOARD_IRQ_BASE 0x20 -#define psycho_onboard_imap_offset(__ino) \ - (PSYCHO_OBIO_IMAP_BASE + (((__ino) & 0x1f) << 3)) - -#define PSYCHO_ICLR_A_SLOT0 0x1400UL -#define PSYCHO_ICLR_SCSI 0x1800UL - -#define psycho_iclr_offset(ino) \ - ((ino & 0x20) ? (PSYCHO_ICLR_SCSI + (((ino) & 0x1f) << 3)) : \ - (PSYCHO_ICLR_A_SLOT0 + (((ino) & 0x1f)<<3))) - -static unsigned int psycho_irq_build(struct device_node *dp, - unsigned int ino, - void *_data) -{ - unsigned long controller_regs = (unsigned long) _data; - unsigned long imap, iclr; - unsigned long imap_off, iclr_off; - int inofixup = 0; - - ino &= 0x3f; - if (ino < PSYCHO_ONBOARD_IRQ_BASE) { - /* PCI slot */ - imap_off = psycho_pcislot_imap_offset(ino); - } else { - /* Onboard device */ - imap_off = psycho_onboard_imap_offset(ino); - } - - /* Now build the IRQ bucket. */ - imap = controller_regs + imap_off; - - iclr_off = psycho_iclr_offset(ino); - iclr = controller_regs + iclr_off; - - if ((ino & 0x20) == 0) - inofixup = ino & 0x03; - - return build_irq(inofixup, iclr, imap); -} - -static void __init psycho_irq_trans_init(struct device_node *dp) -{ - const struct linux_prom64_registers *regs; - - dp->irq_trans = prom_early_alloc(sizeof(struct of_irq_controller)); - dp->irq_trans->irq_build = psycho_irq_build; - - regs = of_get_property(dp, "reg", NULL); - dp->irq_trans->data = (void *) regs[2].phys_addr; -} - -#define sabre_read(__reg) \ -({ u64 __ret; \ - __asm__ __volatile__("ldxa [%1] %2, %0" \ - : "=r" (__ret) \ - : "r" (__reg), "i" (ASI_PHYS_BYPASS_EC_E) \ - : "memory"); \ - __ret; \ -}) - -struct sabre_irq_data { - unsigned long controller_regs; - unsigned int pci_first_busno; -}; -#define SABRE_CONFIGSPACE 0x001000000UL -#define SABRE_WRSYNC 0x1c20UL - -#define SABRE_CONFIG_BASE(CONFIG_SPACE) \ - (CONFIG_SPACE | (1UL << 24)) -#define SABRE_CONFIG_ENCODE(BUS, DEVFN, REG) \ - (((unsigned long)(BUS) << 16) | \ - ((unsigned long)(DEVFN) << 8) | \ - ((unsigned long)(REG))) - -/* When a device lives behind a bridge deeper in the PCI bus topology - * than APB, a special sequence must run to make sure all pending DMA - * transfers at the time of IRQ delivery are visible in the coherency - * domain by the cpu. This sequence is to perform a read on the far - * side of the non-APB bridge, then perform a read of Sabre's DMA - * write-sync register. - */ -static void sabre_wsync_handler(unsigned int ino, void *_arg1, void *_arg2) -{ - unsigned int phys_hi = (unsigned int) (unsigned long) _arg1; - struct sabre_irq_data *irq_data = _arg2; - unsigned long controller_regs = irq_data->controller_regs; - unsigned long sync_reg = controller_regs + SABRE_WRSYNC; - unsigned long config_space = controller_regs + SABRE_CONFIGSPACE; - unsigned int bus, devfn; - u16 _unused; - - config_space = SABRE_CONFIG_BASE(config_space); - - bus = (phys_hi >> 16) & 0xff; - devfn = (phys_hi >> 8) & 0xff; - - config_space |= SABRE_CONFIG_ENCODE(bus, devfn, 0x00); - - __asm__ __volatile__("membar #Sync\n\t" - "lduha [%1] %2, %0\n\t" - "membar #Sync" - : "=r" (_unused) - : "r" ((u16 *) config_space), - "i" (ASI_PHYS_BYPASS_EC_E_L) - : "memory"); - - sabre_read(sync_reg); -} - -#define SABRE_IMAP_A_SLOT0 0x0c00UL -#define SABRE_IMAP_B_SLOT0 0x0c20UL -#define SABRE_ICLR_A_SLOT0 0x1400UL -#define SABRE_ICLR_B_SLOT0 0x1480UL -#define SABRE_ICLR_SCSI 0x1800UL -#define SABRE_ICLR_ETH 0x1808UL -#define SABRE_ICLR_BPP 0x1810UL -#define SABRE_ICLR_AU_REC 0x1818UL -#define SABRE_ICLR_AU_PLAY 0x1820UL -#define SABRE_ICLR_PFAIL 0x1828UL -#define SABRE_ICLR_KMS 0x1830UL -#define SABRE_ICLR_FLPY 0x1838UL -#define SABRE_ICLR_SHW 0x1840UL -#define SABRE_ICLR_KBD 0x1848UL -#define SABRE_ICLR_MS 0x1850UL -#define SABRE_ICLR_SER 0x1858UL -#define SABRE_ICLR_UE 0x1870UL -#define SABRE_ICLR_CE 0x1878UL -#define SABRE_ICLR_PCIERR 0x1880UL - -static unsigned long sabre_pcislot_imap_offset(unsigned long ino) -{ - unsigned int bus = (ino & 0x10) >> 4; - unsigned int slot = (ino & 0x0c) >> 2; - - if (bus == 0) - return SABRE_IMAP_A_SLOT0 + (slot * 8); - else - return SABRE_IMAP_B_SLOT0 + (slot * 8); -} - -#define SABRE_OBIO_IMAP_BASE 0x1000UL -#define SABRE_ONBOARD_IRQ_BASE 0x20 -#define sabre_onboard_imap_offset(__ino) \ - (SABRE_OBIO_IMAP_BASE + (((__ino) & 0x1f) << 3)) - -#define sabre_iclr_offset(ino) \ - ((ino & 0x20) ? (SABRE_ICLR_SCSI + (((ino) & 0x1f) << 3)) : \ - (SABRE_ICLR_A_SLOT0 + (((ino) & 0x1f)<<3))) - -static int sabre_device_needs_wsync(struct device_node *dp) -{ - struct device_node *parent = dp->parent; - const char *parent_model, *parent_compat; - - /* This traversal up towards the root is meant to - * handle two cases: - * - * 1) non-PCI bus sitting under PCI, such as 'ebus' - * 2) the PCI controller interrupts themselves, which - * will use the sabre_irq_build but do not need - * the DMA synchronization handling - */ - while (parent) { - if (!strcmp(parent->type, "pci")) - break; - parent = parent->parent; - } - - if (!parent) - return 0; - - parent_model = of_get_property(parent, - "model", NULL); - if (parent_model && - (!strcmp(parent_model, "SUNW,sabre") || - !strcmp(parent_model, "SUNW,simba"))) - return 0; - - parent_compat = of_get_property(parent, - "compatible", NULL); - if (parent_compat && - (!strcmp(parent_compat, "pci108e,a000") || - !strcmp(parent_compat, "pci108e,a001"))) - return 0; - - return 1; -} - -static unsigned int sabre_irq_build(struct device_node *dp, - unsigned int ino, - void *_data) -{ - struct sabre_irq_data *irq_data = _data; - unsigned long controller_regs = irq_data->controller_regs; - const struct linux_prom_pci_registers *regs; - unsigned long imap, iclr; - unsigned long imap_off, iclr_off; - int inofixup = 0; - int virt_irq; - - ino &= 0x3f; - if (ino < SABRE_ONBOARD_IRQ_BASE) { - /* PCI slot */ - imap_off = sabre_pcislot_imap_offset(ino); - } else { - /* onboard device */ - imap_off = sabre_onboard_imap_offset(ino); - } - - /* Now build the IRQ bucket. */ - imap = controller_regs + imap_off; - - iclr_off = sabre_iclr_offset(ino); - iclr = controller_regs + iclr_off; - - if ((ino & 0x20) == 0) - inofixup = ino & 0x03; - - virt_irq = build_irq(inofixup, iclr, imap); - - /* If the parent device is a PCI<->PCI bridge other than - * APB, we have to install a pre-handler to ensure that - * all pending DMA is drained before the interrupt handler - * is run. - */ - regs = of_get_property(dp, "reg", NULL); - if (regs && sabre_device_needs_wsync(dp)) { - irq_install_pre_handler(virt_irq, - sabre_wsync_handler, - (void *) (long) regs->phys_hi, - (void *) irq_data); - } - - return virt_irq; -} - -static void __init sabre_irq_trans_init(struct device_node *dp) -{ - const struct linux_prom64_registers *regs; - struct sabre_irq_data *irq_data; - const u32 *busrange; - - dp->irq_trans = prom_early_alloc(sizeof(struct of_irq_controller)); - dp->irq_trans->irq_build = sabre_irq_build; - - irq_data = prom_early_alloc(sizeof(struct sabre_irq_data)); - - regs = of_get_property(dp, "reg", NULL); - irq_data->controller_regs = regs[0].phys_addr; - - busrange = of_get_property(dp, "bus-range", NULL); - irq_data->pci_first_busno = busrange[0]; - - dp->irq_trans->data = irq_data; -} - -/* SCHIZO interrupt mapping support. Unlike Psycho, for this controller the - * imap/iclr registers are per-PBM. - */ -#define SCHIZO_IMAP_BASE 0x1000UL -#define SCHIZO_ICLR_BASE 0x1400UL - -static unsigned long schizo_imap_offset(unsigned long ino) -{ - return SCHIZO_IMAP_BASE + (ino * 8UL); -} - -static unsigned long schizo_iclr_offset(unsigned long ino) -{ - return SCHIZO_ICLR_BASE + (ino * 8UL); -} - -static unsigned long schizo_ino_to_iclr(unsigned long pbm_regs, - unsigned int ino) -{ - - return pbm_regs + schizo_iclr_offset(ino); -} - -static unsigned long schizo_ino_to_imap(unsigned long pbm_regs, - unsigned int ino) -{ - return pbm_regs + schizo_imap_offset(ino); -} - -#define schizo_read(__reg) \ -({ u64 __ret; \ - __asm__ __volatile__("ldxa [%1] %2, %0" \ - : "=r" (__ret) \ - : "r" (__reg), "i" (ASI_PHYS_BYPASS_EC_E) \ - : "memory"); \ - __ret; \ -}) -#define schizo_write(__reg, __val) \ - __asm__ __volatile__("stxa %0, [%1] %2" \ - : /* no outputs */ \ - : "r" (__val), "r" (__reg), \ - "i" (ASI_PHYS_BYPASS_EC_E) \ - : "memory") - -static void tomatillo_wsync_handler(unsigned int ino, void *_arg1, void *_arg2) -{ - unsigned long sync_reg = (unsigned long) _arg2; - u64 mask = 1UL << (ino & IMAP_INO); - u64 val; - int limit; - - schizo_write(sync_reg, mask); - - limit = 100000; - val = 0; - while (--limit) { - val = schizo_read(sync_reg); - if (!(val & mask)) - break; - } - if (limit <= 0) { - printk("tomatillo_wsync_handler: DMA won't sync [%lx:%lx]\n", - val, mask); - } - - if (_arg1) { - static unsigned char cacheline[64] - __attribute__ ((aligned (64))); - - __asm__ __volatile__("rd %%fprs, %0\n\t" - "or %0, %4, %1\n\t" - "wr %1, 0x0, %%fprs\n\t" - "stda %%f0, [%5] %6\n\t" - "wr %0, 0x0, %%fprs\n\t" - "membar #Sync" - : "=&r" (mask), "=&r" (val) - : "0" (mask), "1" (val), - "i" (FPRS_FEF), "r" (&cacheline[0]), - "i" (ASI_BLK_COMMIT_P)); - } -} - -struct schizo_irq_data { - unsigned long pbm_regs; - unsigned long sync_reg; - u32 portid; - int chip_version; -}; - -static unsigned int schizo_irq_build(struct device_node *dp, - unsigned int ino, - void *_data) -{ - struct schizo_irq_data *irq_data = _data; - unsigned long pbm_regs = irq_data->pbm_regs; - unsigned long imap, iclr; - int ign_fixup; - int virt_irq; - int is_tomatillo; - - ino &= 0x3f; - - /* Now build the IRQ bucket. */ - imap = schizo_ino_to_imap(pbm_regs, ino); - iclr = schizo_ino_to_iclr(pbm_regs, ino); - - /* On Schizo, no inofixup occurs. This is because each - * INO has it's own IMAP register. On Psycho and Sabre - * there is only one IMAP register for each PCI slot even - * though four different INOs can be generated by each - * PCI slot. - * - * But, for JBUS variants (essentially, Tomatillo), we have - * to fixup the lowest bit of the interrupt group number. - */ - ign_fixup = 0; - - is_tomatillo = (irq_data->sync_reg != 0UL); - - if (is_tomatillo) { - if (irq_data->portid & 1) - ign_fixup = (1 << 6); - } - - virt_irq = build_irq(ign_fixup, iclr, imap); - - if (is_tomatillo) { - irq_install_pre_handler(virt_irq, - tomatillo_wsync_handler, - ((irq_data->chip_version <= 4) ? - (void *) 1 : (void *) 0), - (void *) irq_data->sync_reg); - } - - return virt_irq; -} - -static void __init __schizo_irq_trans_init(struct device_node *dp, - int is_tomatillo) -{ - const struct linux_prom64_registers *regs; - struct schizo_irq_data *irq_data; - - dp->irq_trans = prom_early_alloc(sizeof(struct of_irq_controller)); - dp->irq_trans->irq_build = schizo_irq_build; - - irq_data = prom_early_alloc(sizeof(struct schizo_irq_data)); - - regs = of_get_property(dp, "reg", NULL); - dp->irq_trans->data = irq_data; - - irq_data->pbm_regs = regs[0].phys_addr; - if (is_tomatillo) - irq_data->sync_reg = regs[3].phys_addr + 0x1a18UL; - else - irq_data->sync_reg = 0UL; - irq_data->portid = of_getintprop_default(dp, "portid", 0); - irq_data->chip_version = of_getintprop_default(dp, "version#", 0); -} - -static void __init schizo_irq_trans_init(struct device_node *dp) -{ - __schizo_irq_trans_init(dp, 0); -} - -static void __init tomatillo_irq_trans_init(struct device_node *dp) -{ - __schizo_irq_trans_init(dp, 1); -} - -static unsigned int pci_sun4v_irq_build(struct device_node *dp, - unsigned int devino, - void *_data) -{ - u32 devhandle = (u32) (unsigned long) _data; - - return sun4v_build_irq(devhandle, devino); -} - -static void __init pci_sun4v_irq_trans_init(struct device_node *dp) -{ - const struct linux_prom64_registers *regs; - - dp->irq_trans = prom_early_alloc(sizeof(struct of_irq_controller)); - dp->irq_trans->irq_build = pci_sun4v_irq_build; - - regs = of_get_property(dp, "reg", NULL); - dp->irq_trans->data = (void *) (unsigned long) - ((regs->phys_addr >> 32UL) & 0x0fffffff); -} - -struct fire_irq_data { - unsigned long pbm_regs; - u32 portid; -}; - -#define FIRE_IMAP_BASE 0x001000 -#define FIRE_ICLR_BASE 0x001400 - -static unsigned long fire_imap_offset(unsigned long ino) -{ - return FIRE_IMAP_BASE + (ino * 8UL); -} - -static unsigned long fire_iclr_offset(unsigned long ino) -{ - return FIRE_ICLR_BASE + (ino * 8UL); -} - -static unsigned long fire_ino_to_iclr(unsigned long pbm_regs, - unsigned int ino) -{ - return pbm_regs + fire_iclr_offset(ino); -} - -static unsigned long fire_ino_to_imap(unsigned long pbm_regs, - unsigned int ino) -{ - return pbm_regs + fire_imap_offset(ino); -} - -static unsigned int fire_irq_build(struct device_node *dp, - unsigned int ino, - void *_data) -{ - struct fire_irq_data *irq_data = _data; - unsigned long pbm_regs = irq_data->pbm_regs; - unsigned long imap, iclr; - unsigned long int_ctrlr; - - ino &= 0x3f; - - /* Now build the IRQ bucket. */ - imap = fire_ino_to_imap(pbm_regs, ino); - iclr = fire_ino_to_iclr(pbm_regs, ino); - - /* Set the interrupt controller number. */ - int_ctrlr = 1 << 6; - upa_writeq(int_ctrlr, imap); - - /* The interrupt map registers do not have an INO field - * like other chips do. They return zero in the INO - * field, and the interrupt controller number is controlled - * in bits 6 to 9. So in order for build_irq() to get - * the INO right we pass it in as part of the fixup - * which will get added to the map register zero value - * read by build_irq(). - */ - ino |= (irq_data->portid << 6); - ino -= int_ctrlr; - return build_irq(ino, iclr, imap); -} - -static void __init fire_irq_trans_init(struct device_node *dp) -{ - const struct linux_prom64_registers *regs; - struct fire_irq_data *irq_data; - - dp->irq_trans = prom_early_alloc(sizeof(struct of_irq_controller)); - dp->irq_trans->irq_build = fire_irq_build; - - irq_data = prom_early_alloc(sizeof(struct fire_irq_data)); - - regs = of_get_property(dp, "reg", NULL); - dp->irq_trans->data = irq_data; - - irq_data->pbm_regs = regs[0].phys_addr; - irq_data->portid = of_getintprop_default(dp, "portid", 0); -} -#endif /* CONFIG_PCI */ - -#ifdef CONFIG_SBUS -/* INO number to IMAP register offset for SYSIO external IRQ's. - * This should conform to both Sunfire/Wildfire server and Fusion - * desktop designs. - */ -#define SYSIO_IMAP_SLOT0 0x2c00UL -#define SYSIO_IMAP_SLOT1 0x2c08UL -#define SYSIO_IMAP_SLOT2 0x2c10UL -#define SYSIO_IMAP_SLOT3 0x2c18UL -#define SYSIO_IMAP_SCSI 0x3000UL -#define SYSIO_IMAP_ETH 0x3008UL -#define SYSIO_IMAP_BPP 0x3010UL -#define SYSIO_IMAP_AUDIO 0x3018UL -#define SYSIO_IMAP_PFAIL 0x3020UL -#define SYSIO_IMAP_KMS 0x3028UL -#define SYSIO_IMAP_FLPY 0x3030UL -#define SYSIO_IMAP_SHW 0x3038UL -#define SYSIO_IMAP_KBD 0x3040UL -#define SYSIO_IMAP_MS 0x3048UL -#define SYSIO_IMAP_SER 0x3050UL -#define SYSIO_IMAP_TIM0 0x3060UL -#define SYSIO_IMAP_TIM1 0x3068UL -#define SYSIO_IMAP_UE 0x3070UL -#define SYSIO_IMAP_CE 0x3078UL -#define SYSIO_IMAP_SBERR 0x3080UL -#define SYSIO_IMAP_PMGMT 0x3088UL -#define SYSIO_IMAP_GFX 0x3090UL -#define SYSIO_IMAP_EUPA 0x3098UL - -#define bogon ((unsigned long) -1) -static unsigned long sysio_irq_offsets[] = { - /* SBUS Slot 0 --> 3, level 1 --> 7 */ - SYSIO_IMAP_SLOT0, SYSIO_IMAP_SLOT0, SYSIO_IMAP_SLOT0, SYSIO_IMAP_SLOT0, - SYSIO_IMAP_SLOT0, SYSIO_IMAP_SLOT0, SYSIO_IMAP_SLOT0, SYSIO_IMAP_SLOT0, - SYSIO_IMAP_SLOT1, SYSIO_IMAP_SLOT1, SYSIO_IMAP_SLOT1, SYSIO_IMAP_SLOT1, - SYSIO_IMAP_SLOT1, SYSIO_IMAP_SLOT1, SYSIO_IMAP_SLOT1, SYSIO_IMAP_SLOT1, - SYSIO_IMAP_SLOT2, SYSIO_IMAP_SLOT2, SYSIO_IMAP_SLOT2, SYSIO_IMAP_SLOT2, - SYSIO_IMAP_SLOT2, SYSIO_IMAP_SLOT2, SYSIO_IMAP_SLOT2, SYSIO_IMAP_SLOT2, - SYSIO_IMAP_SLOT3, SYSIO_IMAP_SLOT3, SYSIO_IMAP_SLOT3, SYSIO_IMAP_SLOT3, - SYSIO_IMAP_SLOT3, SYSIO_IMAP_SLOT3, SYSIO_IMAP_SLOT3, SYSIO_IMAP_SLOT3, - - /* Onboard devices (not relevant/used on SunFire). */ - SYSIO_IMAP_SCSI, - SYSIO_IMAP_ETH, - SYSIO_IMAP_BPP, - bogon, - SYSIO_IMAP_AUDIO, - SYSIO_IMAP_PFAIL, - bogon, - bogon, - SYSIO_IMAP_KMS, - SYSIO_IMAP_FLPY, - SYSIO_IMAP_SHW, - SYSIO_IMAP_KBD, - SYSIO_IMAP_MS, - SYSIO_IMAP_SER, - bogon, - bogon, - SYSIO_IMAP_TIM0, - SYSIO_IMAP_TIM1, - bogon, - bogon, - SYSIO_IMAP_UE, - SYSIO_IMAP_CE, - SYSIO_IMAP_SBERR, - SYSIO_IMAP_PMGMT, - SYSIO_IMAP_GFX, - SYSIO_IMAP_EUPA, -}; - -#undef bogon - -#define NUM_SYSIO_OFFSETS ARRAY_SIZE(sysio_irq_offsets) - -/* Convert Interrupt Mapping register pointer to associated - * Interrupt Clear register pointer, SYSIO specific version. - */ -#define SYSIO_ICLR_UNUSED0 0x3400UL -#define SYSIO_ICLR_SLOT0 0x3408UL -#define SYSIO_ICLR_SLOT1 0x3448UL -#define SYSIO_ICLR_SLOT2 0x3488UL -#define SYSIO_ICLR_SLOT3 0x34c8UL -static unsigned long sysio_imap_to_iclr(unsigned long imap) -{ - unsigned long diff = SYSIO_ICLR_UNUSED0 - SYSIO_IMAP_SLOT0; - return imap + diff; -} - -static unsigned int sbus_of_build_irq(struct device_node *dp, - unsigned int ino, - void *_data) -{ - unsigned long reg_base = (unsigned long) _data; - const struct linux_prom_registers *regs; - unsigned long imap, iclr; - int sbus_slot = 0; - int sbus_level = 0; - - ino &= 0x3f; - - regs = of_get_property(dp, "reg", NULL); - if (regs) - sbus_slot = regs->which_io; - - if (ino < 0x20) - ino += (sbus_slot * 8); - - imap = sysio_irq_offsets[ino]; - if (imap == ((unsigned long)-1)) { - prom_printf("get_irq_translations: Bad SYSIO INO[%x]\n", - ino); - prom_halt(); - } - imap += reg_base; - - /* SYSIO inconsistency. For external SLOTS, we have to select - * the right ICLR register based upon the lower SBUS irq level - * bits. - */ - if (ino >= 0x20) { - iclr = sysio_imap_to_iclr(imap); - } else { - sbus_level = ino & 0x7; - - switch(sbus_slot) { - case 0: - iclr = reg_base + SYSIO_ICLR_SLOT0; - break; - case 1: - iclr = reg_base + SYSIO_ICLR_SLOT1; - break; - case 2: - iclr = reg_base + SYSIO_ICLR_SLOT2; - break; - default: - case 3: - iclr = reg_base + SYSIO_ICLR_SLOT3; - break; - }; - - iclr += ((unsigned long)sbus_level - 1UL) * 8UL; - } - return build_irq(sbus_level, iclr, imap); -} - -static void __init sbus_irq_trans_init(struct device_node *dp) -{ - const struct linux_prom64_registers *regs; - - dp->irq_trans = prom_early_alloc(sizeof(struct of_irq_controller)); - dp->irq_trans->irq_build = sbus_of_build_irq; - - regs = of_get_property(dp, "reg", NULL); - dp->irq_trans->data = (void *) (unsigned long) regs->phys_addr; -} -#endif /* CONFIG_SBUS */ - - -static unsigned int central_build_irq(struct device_node *dp, - unsigned int ino, - void *_data) -{ - struct device_node *central_dp = _data; - struct of_device *central_op = of_find_device_by_node(central_dp); - struct resource *res; - unsigned long imap, iclr; - u32 tmp; - - if (!strcmp(dp->name, "eeprom")) { - res = ¢ral_op->resource[5]; - } else if (!strcmp(dp->name, "zs")) { - res = ¢ral_op->resource[4]; - } else if (!strcmp(dp->name, "clock-board")) { - res = ¢ral_op->resource[3]; - } else { - return ino; - } - - imap = res->start + 0x00UL; - iclr = res->start + 0x10UL; - - /* Set the INO state to idle, and disable. */ - upa_writel(0, iclr); - upa_readl(iclr); - - tmp = upa_readl(imap); - tmp &= ~0x80000000; - upa_writel(tmp, imap); - - return build_irq(0, iclr, imap); -} - -static void __init central_irq_trans_init(struct device_node *dp) -{ - dp->irq_trans = prom_early_alloc(sizeof(struct of_irq_controller)); - dp->irq_trans->irq_build = central_build_irq; - - dp->irq_trans->data = dp; -} - -struct irq_trans { - const char *name; - void (*init)(struct device_node *); -}; - -#ifdef CONFIG_PCI -static struct irq_trans __initdata pci_irq_trans_table[] = { - { "SUNW,sabre", sabre_irq_trans_init }, - { "pci108e,a000", sabre_irq_trans_init }, - { "pci108e,a001", sabre_irq_trans_init }, - { "SUNW,psycho", psycho_irq_trans_init }, - { "pci108e,8000", psycho_irq_trans_init }, - { "SUNW,schizo", schizo_irq_trans_init }, - { "pci108e,8001", schizo_irq_trans_init }, - { "SUNW,schizo+", schizo_irq_trans_init }, - { "pci108e,8002", schizo_irq_trans_init }, - { "SUNW,tomatillo", tomatillo_irq_trans_init }, - { "pci108e,a801", tomatillo_irq_trans_init }, - { "SUNW,sun4v-pci", pci_sun4v_irq_trans_init }, - { "pciex108e,80f0", fire_irq_trans_init }, -}; -#endif - -static unsigned int sun4v_vdev_irq_build(struct device_node *dp, - unsigned int devino, - void *_data) -{ - u32 devhandle = (u32) (unsigned long) _data; - - return sun4v_build_irq(devhandle, devino); -} - -static void __init sun4v_vdev_irq_trans_init(struct device_node *dp) -{ - const struct linux_prom64_registers *regs; - - dp->irq_trans = prom_early_alloc(sizeof(struct of_irq_controller)); - dp->irq_trans->irq_build = sun4v_vdev_irq_build; - - regs = of_get_property(dp, "reg", NULL); - dp->irq_trans->data = (void *) (unsigned long) - ((regs->phys_addr >> 32UL) & 0x0fffffff); -} - -static void __init irq_trans_init(struct device_node *dp) -{ -#ifdef CONFIG_PCI - const char *model; - int i; -#endif - -#ifdef CONFIG_PCI - model = of_get_property(dp, "model", NULL); - if (!model) - model = of_get_property(dp, "compatible", NULL); - if (model) { - for (i = 0; i < ARRAY_SIZE(pci_irq_trans_table); i++) { - struct irq_trans *t = &pci_irq_trans_table[i]; - - if (!strcmp(model, t->name)) { - t->init(dp); - return; - } - } - } -#endif -#ifdef CONFIG_SBUS - if (!strcmp(dp->name, "sbus") || - !strcmp(dp->name, "sbi")) { - sbus_irq_trans_init(dp); - return; - } -#endif - if (!strcmp(dp->name, "fhc") && - !strcmp(dp->parent->name, "central")) { - central_irq_trans_init(dp); - return; - } - if (!strcmp(dp->name, "virtual-devices") || - !strcmp(dp->name, "niu")) { - sun4v_vdev_irq_trans_init(dp); - return; - } -} - static int is_root_node(const struct device_node *dp) { if (!dp) diff --git a/arch/sparc/kernel/prom_irqtrans.c b/arch/sparc/kernel/prom_irqtrans.c new file mode 100644 index 000000000000..96958c4dce8e --- /dev/null +++ b/arch/sparc/kernel/prom_irqtrans.c @@ -0,0 +1,842 @@ +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/of_platform.h> + +#include <asm/oplib.h> +#include <asm/prom.h> +#include <asm/irq.h> +#include <asm/upa.h> + +#include "prom.h" + +#ifdef CONFIG_PCI +/* PSYCHO interrupt mapping support. */ +#define PSYCHO_IMAP_A_SLOT0 0x0c00UL +#define PSYCHO_IMAP_B_SLOT0 0x0c20UL +static unsigned long psycho_pcislot_imap_offset(unsigned long ino) +{ + unsigned int bus = (ino & 0x10) >> 4; + unsigned int slot = (ino & 0x0c) >> 2; + + if (bus == 0) + return PSYCHO_IMAP_A_SLOT0 + (slot * 8); + else + return PSYCHO_IMAP_B_SLOT0 + (slot * 8); +} + +#define PSYCHO_OBIO_IMAP_BASE 0x1000UL + +#define PSYCHO_ONBOARD_IRQ_BASE 0x20 +#define psycho_onboard_imap_offset(__ino) \ + (PSYCHO_OBIO_IMAP_BASE + (((__ino) & 0x1f) << 3)) + +#define PSYCHO_ICLR_A_SLOT0 0x1400UL +#define PSYCHO_ICLR_SCSI 0x1800UL + +#define psycho_iclr_offset(ino) \ + ((ino & 0x20) ? (PSYCHO_ICLR_SCSI + (((ino) & 0x1f) << 3)) : \ + (PSYCHO_ICLR_A_SLOT0 + (((ino) & 0x1f)<<3))) + +static unsigned int psycho_irq_build(struct device_node *dp, + unsigned int ino, + void *_data) +{ + unsigned long controller_regs = (unsigned long) _data; + unsigned long imap, iclr; + unsigned long imap_off, iclr_off; + int inofixup = 0; + + ino &= 0x3f; + if (ino < PSYCHO_ONBOARD_IRQ_BASE) { + /* PCI slot */ + imap_off = psycho_pcislot_imap_offset(ino); + } else { + /* Onboard device */ + imap_off = psycho_onboard_imap_offset(ino); + } + + /* Now build the IRQ bucket. */ + imap = controller_regs + imap_off; + + iclr_off = psycho_iclr_offset(ino); + iclr = controller_regs + iclr_off; + + if ((ino & 0x20) == 0) + inofixup = ino & 0x03; + + return build_irq(inofixup, iclr, imap); +} + +static void __init psycho_irq_trans_init(struct device_node *dp) +{ + const struct linux_prom64_registers *regs; + + dp->irq_trans = prom_early_alloc(sizeof(struct of_irq_controller)); + dp->irq_trans->irq_build = psycho_irq_build; + + regs = of_get_property(dp, "reg", NULL); + dp->irq_trans->data = (void *) regs[2].phys_addr; +} + +#define sabre_read(__reg) \ +({ u64 __ret; \ + __asm__ __volatile__("ldxa [%1] %2, %0" \ + : "=r" (__ret) \ + : "r" (__reg), "i" (ASI_PHYS_BYPASS_EC_E) \ + : "memory"); \ + __ret; \ +}) + +struct sabre_irq_data { + unsigned long controller_regs; + unsigned int pci_first_busno; +}; +#define SABRE_CONFIGSPACE 0x001000000UL +#define SABRE_WRSYNC 0x1c20UL + +#define SABRE_CONFIG_BASE(CONFIG_SPACE) \ + (CONFIG_SPACE | (1UL << 24)) +#define SABRE_CONFIG_ENCODE(BUS, DEVFN, REG) \ + (((unsigned long)(BUS) << 16) | \ + ((unsigned long)(DEVFN) << 8) | \ + ((unsigned long)(REG))) + +/* When a device lives behind a bridge deeper in the PCI bus topology + * than APB, a special sequence must run to make sure all pending DMA + * transfers at the time of IRQ delivery are visible in the coherency + * domain by the cpu. This sequence is to perform a read on the far + * side of the non-APB bridge, then perform a read of Sabre's DMA + * write-sync register. + */ +static void sabre_wsync_handler(unsigned int ino, void *_arg1, void *_arg2) +{ + unsigned int phys_hi = (unsigned int) (unsigned long) _arg1; + struct sabre_irq_data *irq_data = _arg2; + unsigned long controller_regs = irq_data->controller_regs; + unsigned long sync_reg = controller_regs + SABRE_WRSYNC; + unsigned long config_space = controller_regs + SABRE_CONFIGSPACE; + unsigned int bus, devfn; + u16 _unused; + + config_space = SABRE_CONFIG_BASE(config_space); + + bus = (phys_hi >> 16) & 0xff; + devfn = (phys_hi >> 8) & 0xff; + + config_space |= SABRE_CONFIG_ENCODE(bus, devfn, 0x00); + + __asm__ __volatile__("membar #Sync\n\t" + "lduha [%1] %2, %0\n\t" + "membar #Sync" + : "=r" (_unused) + : "r" ((u16 *) config_space), + "i" (ASI_PHYS_BYPASS_EC_E_L) + : "memory"); + + sabre_read(sync_reg); +} + +#define SABRE_IMAP_A_SLOT0 0x0c00UL +#define SABRE_IMAP_B_SLOT0 0x0c20UL +#define SABRE_ICLR_A_SLOT0 0x1400UL +#define SABRE_ICLR_B_SLOT0 0x1480UL +#define SABRE_ICLR_SCSI 0x1800UL +#define SABRE_ICLR_ETH 0x1808UL +#define SABRE_ICLR_BPP 0x1810UL +#define SABRE_ICLR_AU_REC 0x1818UL +#define SABRE_ICLR_AU_PLAY 0x1820UL +#define SABRE_ICLR_PFAIL 0x1828UL +#define SABRE_ICLR_KMS 0x1830UL +#define SABRE_ICLR_FLPY 0x1838UL +#define SABRE_ICLR_SHW 0x1840UL +#define SABRE_ICLR_KBD 0x1848UL +#define SABRE_ICLR_MS 0x1850UL +#define SABRE_ICLR_SER 0x1858UL +#define SABRE_ICLR_UE 0x1870UL +#define SABRE_ICLR_CE 0x1878UL +#define SABRE_ICLR_PCIERR 0x1880UL + +static unsigned long sabre_pcislot_imap_offset(unsigned long ino) +{ + unsigned int bus = (ino & 0x10) >> 4; + unsigned int slot = (ino & 0x0c) >> 2; + + if (bus == 0) + return SABRE_IMAP_A_SLOT0 + (slot * 8); + else + return SABRE_IMAP_B_SLOT0 + (slot * 8); +} + +#define SABRE_OBIO_IMAP_BASE 0x1000UL +#define SABRE_ONBOARD_IRQ_BASE 0x20 +#define sabre_onboard_imap_offset(__ino) \ + (SABRE_OBIO_IMAP_BASE + (((__ino) & 0x1f) << 3)) + +#define sabre_iclr_offset(ino) \ + ((ino & 0x20) ? (SABRE_ICLR_SCSI + (((ino) & 0x1f) << 3)) : \ + (SABRE_ICLR_A_SLOT0 + (((ino) & 0x1f)<<3))) + +static int sabre_device_needs_wsync(struct device_node *dp) +{ + struct device_node *parent = dp->parent; + const char *parent_model, *parent_compat; + + /* This traversal up towards the root is meant to + * handle two cases: + * + * 1) non-PCI bus sitting under PCI, such as 'ebus' + * 2) the PCI controller interrupts themselves, which + * will use the sabre_irq_build but do not need + * the DMA synchronization handling + */ + while (parent) { + if (!strcmp(parent->type, "pci")) + break; + parent = parent->parent; + } + + if (!parent) + return 0; + + parent_model = of_get_property(parent, + "model", NULL); + if (parent_model && + (!strcmp(parent_model, "SUNW,sabre") || + !strcmp(parent_model, "SUNW,simba"))) + return 0; + + parent_compat = of_get_property(parent, + "compatible", NULL); + if (parent_compat && + (!strcmp(parent_compat, "pci108e,a000") || + !strcmp(parent_compat, "pci108e,a001"))) + return 0; + + return 1; +} + +static unsigned int sabre_irq_build(struct device_node *dp, + unsigned int ino, + void *_data) +{ + struct sabre_irq_data *irq_data = _data; + unsigned long controller_regs = irq_data->controller_regs; + const struct linux_prom_pci_registers *regs; + unsigned long imap, iclr; + unsigned long imap_off, iclr_off; + int inofixup = 0; + int virt_irq; + + ino &= 0x3f; + if (ino < SABRE_ONBOARD_IRQ_BASE) { + /* PCI slot */ + imap_off = sabre_pcislot_imap_offset(ino); + } else { + /* onboard device */ + imap_off = sabre_onboard_imap_offset(ino); + } + + /* Now build the IRQ bucket. */ + imap = controller_regs + imap_off; + + iclr_off = sabre_iclr_offset(ino); + iclr = controller_regs + iclr_off; + + if ((ino & 0x20) == 0) + inofixup = ino & 0x03; + + virt_irq = build_irq(inofixup, iclr, imap); + + /* If the parent device is a PCI<->PCI bridge other than + * APB, we have to install a pre-handler to ensure that + * all pending DMA is drained before the interrupt handler + * is run. + */ + regs = of_get_property(dp, "reg", NULL); + if (regs && sabre_device_needs_wsync(dp)) { + irq_install_pre_handler(virt_irq, + sabre_wsync_handler, + (void *) (long) regs->phys_hi, + (void *) irq_data); + } + + return virt_irq; +} + +static void __init sabre_irq_trans_init(struct device_node *dp) +{ + const struct linux_prom64_registers *regs; + struct sabre_irq_data *irq_data; + const u32 *busrange; + + dp->irq_trans = prom_early_alloc(sizeof(struct of_irq_controller)); + dp->irq_trans->irq_build = sabre_irq_build; + + irq_data = prom_early_alloc(sizeof(struct sabre_irq_data)); + + regs = of_get_property(dp, "reg", NULL); + irq_data->controller_regs = regs[0].phys_addr; + + busrange = of_get_property(dp, "bus-range", NULL); + irq_data->pci_first_busno = busrange[0]; + + dp->irq_trans->data = irq_data; +} + +/* SCHIZO interrupt mapping support. Unlike Psycho, for this controller the + * imap/iclr registers are per-PBM. + */ +#define SCHIZO_IMAP_BASE 0x1000UL +#define SCHIZO_ICLR_BASE 0x1400UL + +static unsigned long schizo_imap_offset(unsigned long ino) +{ + return SCHIZO_IMAP_BASE + (ino * 8UL); +} + +static unsigned long schizo_iclr_offset(unsigned long ino) +{ + return SCHIZO_ICLR_BASE + (ino * 8UL); +} + +static unsigned long schizo_ino_to_iclr(unsigned long pbm_regs, + unsigned int ino) +{ + + return pbm_regs + schizo_iclr_offset(ino); +} + +static unsigned long schizo_ino_to_imap(unsigned long pbm_regs, + unsigned int ino) +{ + return pbm_regs + schizo_imap_offset(ino); +} + +#define schizo_read(__reg) \ +({ u64 __ret; \ + __asm__ __volatile__("ldxa [%1] %2, %0" \ + : "=r" (__ret) \ + : "r" (__reg), "i" (ASI_PHYS_BYPASS_EC_E) \ + : "memory"); \ + __ret; \ +}) +#define schizo_write(__reg, __val) \ + __asm__ __volatile__("stxa %0, [%1] %2" \ + : /* no outputs */ \ + : "r" (__val), "r" (__reg), \ + "i" (ASI_PHYS_BYPASS_EC_E) \ + : "memory") + +static void tomatillo_wsync_handler(unsigned int ino, void *_arg1, void *_arg2) +{ + unsigned long sync_reg = (unsigned long) _arg2; + u64 mask = 1UL << (ino & IMAP_INO); + u64 val; + int limit; + + schizo_write(sync_reg, mask); + + limit = 100000; + val = 0; + while (--limit) { + val = schizo_read(sync_reg); + if (!(val & mask)) + break; + } + if (limit <= 0) { + printk("tomatillo_wsync_handler: DMA won't sync [%lx:%lx]\n", + val, mask); + } + + if (_arg1) { + static unsigned char cacheline[64] + __attribute__ ((aligned (64))); + + __asm__ __volatile__("rd %%fprs, %0\n\t" + "or %0, %4, %1\n\t" + "wr %1, 0x0, %%fprs\n\t" + "stda %%f0, [%5] %6\n\t" + "wr %0, 0x0, %%fprs\n\t" + "membar #Sync" + : "=&r" (mask), "=&r" (val) + : "0" (mask), "1" (val), + "i" (FPRS_FEF), "r" (&cacheline[0]), + "i" (ASI_BLK_COMMIT_P)); + } +} + +struct schizo_irq_data { + unsigned long pbm_regs; + unsigned long sync_reg; + u32 portid; + int chip_version; +}; + +static unsigned int schizo_irq_build(struct device_node *dp, + unsigned int ino, + void *_data) +{ + struct schizo_irq_data *irq_data = _data; + unsigned long pbm_regs = irq_data->pbm_regs; + unsigned long imap, iclr; + int ign_fixup; + int virt_irq; + int is_tomatillo; + + ino &= 0x3f; + + /* Now build the IRQ bucket. */ + imap = schizo_ino_to_imap(pbm_regs, ino); + iclr = schizo_ino_to_iclr(pbm_regs, ino); + + /* On Schizo, no inofixup occurs. This is because each + * INO has it's own IMAP register. On Psycho and Sabre + * there is only one IMAP register for each PCI slot even + * though four different INOs can be generated by each + * PCI slot. + * + * But, for JBUS variants (essentially, Tomatillo), we have + * to fixup the lowest bit of the interrupt group number. + */ + ign_fixup = 0; + + is_tomatillo = (irq_data->sync_reg != 0UL); + + if (is_tomatillo) { + if (irq_data->portid & 1) + ign_fixup = (1 << 6); + } + + virt_irq = build_irq(ign_fixup, iclr, imap); + + if (is_tomatillo) { + irq_install_pre_handler(virt_irq, + tomatillo_wsync_handler, + ((irq_data->chip_version <= 4) ? + (void *) 1 : (void *) 0), + (void *) irq_data->sync_reg); + } + + return virt_irq; +} + +static void __init __schizo_irq_trans_init(struct device_node *dp, + int is_tomatillo) +{ + const struct linux_prom64_registers *regs; + struct schizo_irq_data *irq_data; + + dp->irq_trans = prom_early_alloc(sizeof(struct of_irq_controller)); + dp->irq_trans->irq_build = schizo_irq_build; + + irq_data = prom_early_alloc(sizeof(struct schizo_irq_data)); + + regs = of_get_property(dp, "reg", NULL); + dp->irq_trans->data = irq_data; + + irq_data->pbm_regs = regs[0].phys_addr; + if (is_tomatillo) + irq_data->sync_reg = regs[3].phys_addr + 0x1a18UL; + else + irq_data->sync_reg = 0UL; + irq_data->portid = of_getintprop_default(dp, "portid", 0); + irq_data->chip_version = of_getintprop_default(dp, "version#", 0); +} + +static void __init schizo_irq_trans_init(struct device_node *dp) +{ + __schizo_irq_trans_init(dp, 0); +} + +static void __init tomatillo_irq_trans_init(struct device_node *dp) +{ + __schizo_irq_trans_init(dp, 1); +} + +static unsigned int pci_sun4v_irq_build(struct device_node *dp, + unsigned int devino, + void *_data) +{ + u32 devhandle = (u32) (unsigned long) _data; + + return sun4v_build_irq(devhandle, devino); +} + +static void __init pci_sun4v_irq_trans_init(struct device_node *dp) +{ + const struct linux_prom64_registers *regs; + + dp->irq_trans = prom_early_alloc(sizeof(struct of_irq_controller)); + dp->irq_trans->irq_build = pci_sun4v_irq_build; + + regs = of_get_property(dp, "reg", NULL); + dp->irq_trans->data = (void *) (unsigned long) + ((regs->phys_addr >> 32UL) & 0x0fffffff); +} + +struct fire_irq_data { + unsigned long pbm_regs; + u32 portid; +}; + +#define FIRE_IMAP_BASE 0x001000 +#define FIRE_ICLR_BASE 0x001400 + +static unsigned long fire_imap_offset(unsigned long ino) +{ + return FIRE_IMAP_BASE + (ino * 8UL); +} + +static unsigned long fire_iclr_offset(unsigned long ino) +{ + return FIRE_ICLR_BASE + (ino * 8UL); +} + +static unsigned long fire_ino_to_iclr(unsigned long pbm_regs, + unsigned int ino) +{ + return pbm_regs + fire_iclr_offset(ino); +} + +static unsigned long fire_ino_to_imap(unsigned long pbm_regs, + unsigned int ino) +{ + return pbm_regs + fire_imap_offset(ino); +} + +static unsigned int fire_irq_build(struct device_node *dp, + unsigned int ino, + void *_data) +{ + struct fire_irq_data *irq_data = _data; + unsigned long pbm_regs = irq_data->pbm_regs; + unsigned long imap, iclr; + unsigned long int_ctrlr; + + ino &= 0x3f; + + /* Now build the IRQ bucket. */ + imap = fire_ino_to_imap(pbm_regs, ino); + iclr = fire_ino_to_iclr(pbm_regs, ino); + + /* Set the interrupt controller number. */ + int_ctrlr = 1 << 6; + upa_writeq(int_ctrlr, imap); + + /* The interrupt map registers do not have an INO field + * like other chips do. They return zero in the INO + * field, and the interrupt controller number is controlled + * in bits 6 to 9. So in order for build_irq() to get + * the INO right we pass it in as part of the fixup + * which will get added to the map register zero value + * read by build_irq(). + */ + ino |= (irq_data->portid << 6); + ino -= int_ctrlr; + return build_irq(ino, iclr, imap); +} + +static void __init fire_irq_trans_init(struct device_node *dp) +{ + const struct linux_prom64_registers *regs; + struct fire_irq_data *irq_data; + + dp->irq_trans = prom_early_alloc(sizeof(struct of_irq_controller)); + dp->irq_trans->irq_build = fire_irq_build; + + irq_data = prom_early_alloc(sizeof(struct fire_irq_data)); + + regs = of_get_property(dp, "reg", NULL); + dp->irq_trans->data = irq_data; + + irq_data->pbm_regs = regs[0].phys_addr; + irq_data->portid = of_getintprop_default(dp, "portid", 0); +} +#endif /* CONFIG_PCI */ + +#ifdef CONFIG_SBUS +/* INO number to IMAP register offset for SYSIO external IRQ's. + * This should conform to both Sunfire/Wildfire server and Fusion + * desktop designs. + */ +#define SYSIO_IMAP_SLOT0 0x2c00UL +#define SYSIO_IMAP_SLOT1 0x2c08UL +#define SYSIO_IMAP_SLOT2 0x2c10UL +#define SYSIO_IMAP_SLOT3 0x2c18UL +#define SYSIO_IMAP_SCSI 0x3000UL +#define SYSIO_IMAP_ETH 0x3008UL +#define SYSIO_IMAP_BPP 0x3010UL +#define SYSIO_IMAP_AUDIO 0x3018UL +#define SYSIO_IMAP_PFAIL 0x3020UL +#define SYSIO_IMAP_KMS 0x3028UL +#define SYSIO_IMAP_FLPY 0x3030UL +#define SYSIO_IMAP_SHW 0x3038UL +#define SYSIO_IMAP_KBD 0x3040UL +#define SYSIO_IMAP_MS 0x3048UL +#define SYSIO_IMAP_SER 0x3050UL +#define SYSIO_IMAP_TIM0 0x3060UL +#define SYSIO_IMAP_TIM1 0x3068UL +#define SYSIO_IMAP_UE 0x3070UL +#define SYSIO_IMAP_CE 0x3078UL +#define SYSIO_IMAP_SBERR 0x3080UL +#define SYSIO_IMAP_PMGMT 0x3088UL +#define SYSIO_IMAP_GFX 0x3090UL +#define SYSIO_IMAP_EUPA 0x3098UL + +#define bogon ((unsigned long) -1) +static unsigned long sysio_irq_offsets[] = { + /* SBUS Slot 0 --> 3, level 1 --> 7 */ + SYSIO_IMAP_SLOT0, SYSIO_IMAP_SLOT0, SYSIO_IMAP_SLOT0, SYSIO_IMAP_SLOT0, + SYSIO_IMAP_SLOT0, SYSIO_IMAP_SLOT0, SYSIO_IMAP_SLOT0, SYSIO_IMAP_SLOT0, + SYSIO_IMAP_SLOT1, SYSIO_IMAP_SLOT1, SYSIO_IMAP_SLOT1, SYSIO_IMAP_SLOT1, + SYSIO_IMAP_SLOT1, SYSIO_IMAP_SLOT1, SYSIO_IMAP_SLOT1, SYSIO_IMAP_SLOT1, + SYSIO_IMAP_SLOT2, SYSIO_IMAP_SLOT2, SYSIO_IMAP_SLOT2, SYSIO_IMAP_SLOT2, + SYSIO_IMAP_SLOT2, SYSIO_IMAP_SLOT2, SYSIO_IMAP_SLOT2, SYSIO_IMAP_SLOT2, + SYSIO_IMAP_SLOT3, SYSIO_IMAP_SLOT3, SYSIO_IMAP_SLOT3, SYSIO_IMAP_SLOT3, + SYSIO_IMAP_SLOT3, SYSIO_IMAP_SLOT3, SYSIO_IMAP_SLOT3, SYSIO_IMAP_SLOT3, + + /* Onboard devices (not relevant/used on SunFire). */ + SYSIO_IMAP_SCSI, + SYSIO_IMAP_ETH, + SYSIO_IMAP_BPP, + bogon, + SYSIO_IMAP_AUDIO, + SYSIO_IMAP_PFAIL, + bogon, + bogon, + SYSIO_IMAP_KMS, + SYSIO_IMAP_FLPY, + SYSIO_IMAP_SHW, + SYSIO_IMAP_KBD, + SYSIO_IMAP_MS, + SYSIO_IMAP_SER, + bogon, + bogon, + SYSIO_IMAP_TIM0, + SYSIO_IMAP_TIM1, + bogon, + bogon, + SYSIO_IMAP_UE, + SYSIO_IMAP_CE, + SYSIO_IMAP_SBERR, + SYSIO_IMAP_PMGMT, + SYSIO_IMAP_GFX, + SYSIO_IMAP_EUPA, +}; + +#undef bogon + +#define NUM_SYSIO_OFFSETS ARRAY_SIZE(sysio_irq_offsets) + +/* Convert Interrupt Mapping register pointer to associated + * Interrupt Clear register pointer, SYSIO specific version. + */ +#define SYSIO_ICLR_UNUSED0 0x3400UL +#define SYSIO_ICLR_SLOT0 0x3408UL +#define SYSIO_ICLR_SLOT1 0x3448UL +#define SYSIO_ICLR_SLOT2 0x3488UL +#define SYSIO_ICLR_SLOT3 0x34c8UL +static unsigned long sysio_imap_to_iclr(unsigned long imap) +{ + unsigned long diff = SYSIO_ICLR_UNUSED0 - SYSIO_IMAP_SLOT0; + return imap + diff; +} + +static unsigned int sbus_of_build_irq(struct device_node *dp, + unsigned int ino, + void *_data) +{ + unsigned long reg_base = (unsigned long) _data; + const struct linux_prom_registers *regs; + unsigned long imap, iclr; + int sbus_slot = 0; + int sbus_level = 0; + + ino &= 0x3f; + + regs = of_get_property(dp, "reg", NULL); + if (regs) + sbus_slot = regs->which_io; + + if (ino < 0x20) + ino += (sbus_slot * 8); + + imap = sysio_irq_offsets[ino]; + if (imap == ((unsigned long)-1)) { + prom_printf("get_irq_translations: Bad SYSIO INO[%x]\n", + ino); + prom_halt(); + } + imap += reg_base; + + /* SYSIO inconsistency. For external SLOTS, we have to select + * the right ICLR register based upon the lower SBUS irq level + * bits. + */ + if (ino >= 0x20) { + iclr = sysio_imap_to_iclr(imap); + } else { + sbus_level = ino & 0x7; + + switch(sbus_slot) { + case 0: + iclr = reg_base + SYSIO_ICLR_SLOT0; + break; + case 1: + iclr = reg_base + SYSIO_ICLR_SLOT1; + break; + case 2: + iclr = reg_base + SYSIO_ICLR_SLOT2; + break; + default: + case 3: + iclr = reg_base + SYSIO_ICLR_SLOT3; + break; + }; + + iclr += ((unsigned long)sbus_level - 1UL) * 8UL; + } + return build_irq(sbus_level, iclr, imap); +} + +static void __init sbus_irq_trans_init(struct device_node *dp) +{ + const struct linux_prom64_registers *regs; + + dp->irq_trans = prom_early_alloc(sizeof(struct of_irq_controller)); + dp->irq_trans->irq_build = sbus_of_build_irq; + + regs = of_get_property(dp, "reg", NULL); + dp->irq_trans->data = (void *) (unsigned long) regs->phys_addr; +} +#endif /* CONFIG_SBUS */ + + +static unsigned int central_build_irq(struct device_node *dp, + unsigned int ino, + void *_data) +{ + struct device_node *central_dp = _data; + struct of_device *central_op = of_find_device_by_node(central_dp); + struct resource *res; + unsigned long imap, iclr; + u32 tmp; + + if (!strcmp(dp->name, "eeprom")) { + res = ¢ral_op->resource[5]; + } else if (!strcmp(dp->name, "zs")) { + res = ¢ral_op->resource[4]; + } else if (!strcmp(dp->name, "clock-board")) { + res = ¢ral_op->resource[3]; + } else { + return ino; + } + + imap = res->start + 0x00UL; + iclr = res->start + 0x10UL; + + /* Set the INO state to idle, and disable. */ + upa_writel(0, iclr); + upa_readl(iclr); + + tmp = upa_readl(imap); + tmp &= ~0x80000000; + upa_writel(tmp, imap); + + return build_irq(0, iclr, imap); +} + +static void __init central_irq_trans_init(struct device_node *dp) +{ + dp->irq_trans = prom_early_alloc(sizeof(struct of_irq_controller)); + dp->irq_trans->irq_build = central_build_irq; + + dp->irq_trans->data = dp; +} + +struct irq_trans { + const char *name; + void (*init)(struct device_node *); +}; + +#ifdef CONFIG_PCI +static struct irq_trans __initdata pci_irq_trans_table[] = { + { "SUNW,sabre", sabre_irq_trans_init }, + { "pci108e,a000", sabre_irq_trans_init }, + { "pci108e,a001", sabre_irq_trans_init }, + { "SUNW,psycho", psycho_irq_trans_init }, + { "pci108e,8000", psycho_irq_trans_init }, + { "SUNW,schizo", schizo_irq_trans_init }, + { "pci108e,8001", schizo_irq_trans_init }, + { "SUNW,schizo+", schizo_irq_trans_init }, + { "pci108e,8002", schizo_irq_trans_init }, + { "SUNW,tomatillo", tomatillo_irq_trans_init }, + { "pci108e,a801", tomatillo_irq_trans_init }, + { "SUNW,sun4v-pci", pci_sun4v_irq_trans_init }, + { "pciex108e,80f0", fire_irq_trans_init }, +}; +#endif + +static unsigned int sun4v_vdev_irq_build(struct device_node *dp, + unsigned int devino, + void *_data) +{ + u32 devhandle = (u32) (unsigned long) _data; + + return sun4v_build_irq(devhandle, devino); +} + +static void __init sun4v_vdev_irq_trans_init(struct device_node *dp) +{ + const struct linux_prom64_registers *regs; + + dp->irq_trans = prom_early_alloc(sizeof(struct of_irq_controller)); + dp->irq_trans->irq_build = sun4v_vdev_irq_build; + + regs = of_get_property(dp, "reg", NULL); + dp->irq_trans->data = (void *) (unsigned long) + ((regs->phys_addr >> 32UL) & 0x0fffffff); +} + +void __init irq_trans_init(struct device_node *dp) +{ +#ifdef CONFIG_PCI + const char *model; + int i; +#endif + +#ifdef CONFIG_PCI + model = of_get_property(dp, "model", NULL); + if (!model) + model = of_get_property(dp, "compatible", NULL); + if (model) { + for (i = 0; i < ARRAY_SIZE(pci_irq_trans_table); i++) { + struct irq_trans *t = &pci_irq_trans_table[i]; + + if (!strcmp(model, t->name)) { + t->init(dp); + return; + } + } + } +#endif +#ifdef CONFIG_SBUS + if (!strcmp(dp->name, "sbus") || + !strcmp(dp->name, "sbi")) { + sbus_irq_trans_init(dp); + return; + } +#endif + if (!strcmp(dp->name, "fhc") && + !strcmp(dp->parent->name, "central")) { + central_irq_trans_init(dp); + return; + } + if (!strcmp(dp->name, "virtual-devices") || + !strcmp(dp->name, "niu")) { + sun4v_vdev_irq_trans_init(dp); + return; + } +} |