| #include <linux/kernel.h> |
| #include <linux/init.h> |
| #include <linux/pci.h> |
| #include <linux/slab.h> |
| #include <asm/oplib.h> |
| #include <asm/isa.h> |
| |
| struct sparc_isa_bridge *isa_chain; |
| |
| static void __init fatal_err(const char *reason) |
| { |
| prom_printf("ISA: fatal error, %s.\n", reason); |
| } |
| |
| static void __init report_dev(struct sparc_isa_device *isa_dev, int child) |
| { |
| if (child) |
| printk(" (%s)", isa_dev->prom_name); |
| else |
| printk(" [%s", isa_dev->prom_name); |
| } |
| |
| static void __init isa_dev_get_resource(struct sparc_isa_device *isa_dev, |
| struct linux_prom_registers *pregs, |
| int pregs_size) |
| { |
| unsigned long base, len; |
| int prop_len; |
| |
| prop_len = prom_getproperty(isa_dev->prom_node, "reg", |
| (char *) pregs, pregs_size); |
| |
| if (prop_len <= 0) |
| return; |
| |
| /* Only the first one is interesting. */ |
| len = pregs[0].reg_size; |
| base = (((unsigned long)pregs[0].which_io << 32) | |
| (unsigned long)pregs[0].phys_addr); |
| base += isa_dev->bus->parent->io_space.start; |
| |
| isa_dev->resource.start = base; |
| isa_dev->resource.end = (base + len - 1UL); |
| isa_dev->resource.flags = IORESOURCE_IO; |
| isa_dev->resource.name = isa_dev->prom_name; |
| |
| request_resource(&isa_dev->bus->parent->io_space, |
| &isa_dev->resource); |
| } |
| |
| /* I can't believe they didn't put a real INO in the isa device |
| * interrupts property. The whole point of the OBP properties |
| * is to shield the kernel from IRQ routing details. |
| * |
| * The P1275 standard for ISA devices seems to also have been |
| * totally ignored. |
| * |
| * On later systems, an interrupt-map and interrupt-map-mask scheme |
| * akin to EBUS is used. |
| */ |
| static struct { |
| int obp_irq; |
| int pci_ino; |
| } grover_irq_table[] = { |
| { 1, 0x00 }, /* dma, unknown ino at this point */ |
| { 2, 0x27 }, /* floppy */ |
| { 3, 0x22 }, /* parallel */ |
| { 4, 0x2b }, /* serial */ |
| { 5, 0x25 }, /* acpi power management */ |
| |
| { 0, 0x00 } /* end of table */ |
| }; |
| |
| static int __init isa_dev_get_irq_using_imap(struct sparc_isa_device *isa_dev, |
| struct sparc_isa_bridge *isa_br, |
| int *interrupt, |
| struct linux_prom_registers *pregs) |
| { |
| unsigned int hi, lo, irq; |
| int i; |
| |
| hi = pregs->which_io & isa_br->isa_intmask.phys_hi; |
| lo = pregs->phys_addr & isa_br->isa_intmask.phys_lo; |
| irq = *interrupt & isa_br->isa_intmask.interrupt; |
| for (i = 0; i < isa_br->num_isa_intmap; i++) { |
| if ((isa_br->isa_intmap[i].phys_hi == hi) && |
| (isa_br->isa_intmap[i].phys_lo == lo) && |
| (isa_br->isa_intmap[i].interrupt == irq)) { |
| *interrupt = isa_br->isa_intmap[i].cinterrupt; |
| return 0; |
| } |
| } |
| return -1; |
| } |
| |
| static void __init isa_dev_get_irq(struct sparc_isa_device *isa_dev, |
| struct linux_prom_registers *pregs) |
| { |
| int irq_prop; |
| |
| irq_prop = prom_getintdefault(isa_dev->prom_node, |
| "interrupts", -1); |
| if (irq_prop <= 0) { |
| goto no_irq; |
| } else { |
| struct pci_controller_info *pcic; |
| struct pci_pbm_info *pbm; |
| int i; |
| |
| if (isa_dev->bus->num_isa_intmap) { |
| if (!isa_dev_get_irq_using_imap(isa_dev, |
| isa_dev->bus, |
| &irq_prop, |
| pregs)) |
| goto route_irq; |
| } |
| |
| for (i = 0; grover_irq_table[i].obp_irq != 0; i++) { |
| if (grover_irq_table[i].obp_irq == irq_prop) { |
| int ino = grover_irq_table[i].pci_ino; |
| |
| if (ino == 0) |
| goto no_irq; |
| |
| irq_prop = ino; |
| goto route_irq; |
| } |
| } |
| goto no_irq; |
| |
| route_irq: |
| pbm = isa_dev->bus->parent; |
| pcic = pbm->parent; |
| isa_dev->irq = pcic->irq_build(pbm, NULL, irq_prop); |
| return; |
| } |
| |
| no_irq: |
| isa_dev->irq = PCI_IRQ_NONE; |
| } |
| |
| static void __init isa_fill_children(struct sparc_isa_device *parent_isa_dev) |
| { |
| int node = prom_getchild(parent_isa_dev->prom_node); |
| |
| if (node == 0) |
| return; |
| |
| printk(" ->"); |
| while (node != 0) { |
| struct linux_prom_registers regs[PROMREG_MAX]; |
| struct sparc_isa_device *isa_dev; |
| int prop_len; |
| |
| isa_dev = kmalloc(sizeof(*isa_dev), GFP_KERNEL); |
| if (!isa_dev) { |
| fatal_err("cannot allocate child isa_dev"); |
| prom_halt(); |
| } |
| |
| memset(isa_dev, 0, sizeof(*isa_dev)); |
| |
| /* Link it in to parent. */ |
| isa_dev->next = parent_isa_dev->child; |
| parent_isa_dev->child = isa_dev; |
| |
| isa_dev->bus = parent_isa_dev->bus; |
| isa_dev->prom_node = node; |
| prop_len = prom_getproperty(node, "name", |
| (char *) isa_dev->prom_name, |
| sizeof(isa_dev->prom_name)); |
| if (prop_len <= 0) { |
| fatal_err("cannot get child isa_dev OBP node name"); |
| prom_halt(); |
| } |
| |
| prop_len = prom_getproperty(node, "compatible", |
| (char *) isa_dev->compatible, |
| sizeof(isa_dev->compatible)); |
| |
| /* Not having this is OK. */ |
| if (prop_len <= 0) |
| isa_dev->compatible[0] = '\0'; |
| |
| isa_dev_get_resource(isa_dev, regs, sizeof(regs)); |
| isa_dev_get_irq(isa_dev, regs); |
| |
| report_dev(isa_dev, 1); |
| |
| node = prom_getsibling(node); |
| } |
| } |
| |
| static void __init isa_fill_devices(struct sparc_isa_bridge *isa_br) |
| { |
| int node = prom_getchild(isa_br->prom_node); |
| |
| while (node != 0) { |
| struct linux_prom_registers regs[PROMREG_MAX]; |
| struct sparc_isa_device *isa_dev; |
| int prop_len; |
| |
| isa_dev = kmalloc(sizeof(*isa_dev), GFP_KERNEL); |
| if (!isa_dev) { |
| fatal_err("cannot allocate isa_dev"); |
| prom_halt(); |
| } |
| |
| memset(isa_dev, 0, sizeof(*isa_dev)); |
| |
| /* Link it in. */ |
| isa_dev->next = NULL; |
| if (isa_br->devices == NULL) { |
| isa_br->devices = isa_dev; |
| } else { |
| struct sparc_isa_device *tmp = isa_br->devices; |
| |
| while (tmp->next) |
| tmp = tmp->next; |
| |
| tmp->next = isa_dev; |
| } |
| |
| isa_dev->bus = isa_br; |
| isa_dev->prom_node = node; |
| prop_len = prom_getproperty(node, "name", |
| (char *) isa_dev->prom_name, |
| sizeof(isa_dev->prom_name)); |
| if (prop_len <= 0) { |
| fatal_err("cannot get isa_dev OBP node name"); |
| prom_halt(); |
| } |
| |
| prop_len = prom_getproperty(node, "compatible", |
| (char *) isa_dev->compatible, |
| sizeof(isa_dev->compatible)); |
| |
| /* Not having this is OK. */ |
| if (prop_len <= 0) |
| isa_dev->compatible[0] = '\0'; |
| |
| isa_dev_get_resource(isa_dev, regs, sizeof(regs)); |
| isa_dev_get_irq(isa_dev, regs); |
| |
| report_dev(isa_dev, 0); |
| |
| isa_fill_children(isa_dev); |
| |
| printk("]"); |
| |
| node = prom_getsibling(node); |
| } |
| } |
| |
| void __init isa_init(void) |
| { |
| struct pci_dev *pdev; |
| unsigned short vendor, device; |
| int index = 0; |
| |
| vendor = PCI_VENDOR_ID_AL; |
| device = PCI_DEVICE_ID_AL_M1533; |
| |
| pdev = NULL; |
| while ((pdev = pci_get_device(vendor, device, pdev)) != NULL) { |
| struct pcidev_cookie *pdev_cookie; |
| struct pci_pbm_info *pbm; |
| struct sparc_isa_bridge *isa_br; |
| int prop_len; |
| |
| pdev_cookie = pdev->sysdata; |
| if (!pdev_cookie) { |
| printk("ISA: Warning, ISA bridge ignored due to " |
| "lack of OBP data.\n"); |
| continue; |
| } |
| pbm = pdev_cookie->pbm; |
| |
| isa_br = kmalloc(sizeof(*isa_br), GFP_KERNEL); |
| if (!isa_br) { |
| fatal_err("cannot allocate sparc_isa_bridge"); |
| prom_halt(); |
| } |
| |
| memset(isa_br, 0, sizeof(*isa_br)); |
| |
| /* Link it in. */ |
| isa_br->next = isa_chain; |
| isa_chain = isa_br; |
| |
| isa_br->parent = pbm; |
| isa_br->self = pdev; |
| isa_br->index = index++; |
| isa_br->prom_node = pdev_cookie->prom_node; |
| strncpy(isa_br->prom_name, pdev_cookie->prom_name, |
| sizeof(isa_br->prom_name)); |
| |
| prop_len = prom_getproperty(isa_br->prom_node, |
| "ranges", |
| (char *) isa_br->isa_ranges, |
| sizeof(isa_br->isa_ranges)); |
| if (prop_len <= 0) |
| isa_br->num_isa_ranges = 0; |
| else |
| isa_br->num_isa_ranges = |
| (prop_len / sizeof(struct linux_prom_isa_ranges)); |
| |
| prop_len = prom_getproperty(isa_br->prom_node, |
| "interrupt-map", |
| (char *) isa_br->isa_intmap, |
| sizeof(isa_br->isa_intmap)); |
| if (prop_len <= 0) |
| isa_br->num_isa_intmap = 0; |
| else |
| isa_br->num_isa_intmap = |
| (prop_len / sizeof(struct linux_prom_isa_intmap)); |
| |
| prop_len = prom_getproperty(isa_br->prom_node, |
| "interrupt-map-mask", |
| (char *) &(isa_br->isa_intmask), |
| sizeof(isa_br->isa_intmask)); |
| |
| printk("isa%d:", isa_br->index); |
| |
| isa_fill_devices(isa_br); |
| |
| printk("\n"); |
| } |
| } |