18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * i8259 interrupt controller driver. 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci#undef DEBUG 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <linux/ioport.h> 88c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 98c2ecf20Sopenharmony_ci#include <linux/kernel.h> 108c2ecf20Sopenharmony_ci#include <linux/delay.h> 118c2ecf20Sopenharmony_ci#include <asm/io.h> 128c2ecf20Sopenharmony_ci#include <asm/i8259.h> 138c2ecf20Sopenharmony_ci#include <asm/prom.h> 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_cistatic volatile void __iomem *pci_intack; /* RO, gives us the irq vector */ 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_cistatic unsigned char cached_8259[2] = { 0xff, 0xff }; 188c2ecf20Sopenharmony_ci#define cached_A1 (cached_8259[0]) 198c2ecf20Sopenharmony_ci#define cached_21 (cached_8259[1]) 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_cistatic DEFINE_RAW_SPINLOCK(i8259_lock); 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_cistatic struct irq_domain *i8259_host; 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci/* 268c2ecf20Sopenharmony_ci * Acknowledge the IRQ using either the PCI host bridge's interrupt 278c2ecf20Sopenharmony_ci * acknowledge feature or poll. How i8259_init() is called determines 288c2ecf20Sopenharmony_ci * which is called. It should be noted that polling is broken on some 298c2ecf20Sopenharmony_ci * IBM and Motorola PReP boxes so we must use the int-ack feature on them. 308c2ecf20Sopenharmony_ci */ 318c2ecf20Sopenharmony_ciunsigned int i8259_irq(void) 328c2ecf20Sopenharmony_ci{ 338c2ecf20Sopenharmony_ci int irq; 348c2ecf20Sopenharmony_ci int lock = 0; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci /* Either int-ack or poll for the IRQ */ 378c2ecf20Sopenharmony_ci if (pci_intack) 388c2ecf20Sopenharmony_ci irq = readb(pci_intack); 398c2ecf20Sopenharmony_ci else { 408c2ecf20Sopenharmony_ci raw_spin_lock(&i8259_lock); 418c2ecf20Sopenharmony_ci lock = 1; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci /* Perform an interrupt acknowledge cycle on controller 1. */ 448c2ecf20Sopenharmony_ci outb(0x0C, 0x20); /* prepare for poll */ 458c2ecf20Sopenharmony_ci irq = inb(0x20) & 7; 468c2ecf20Sopenharmony_ci if (irq == 2 ) { 478c2ecf20Sopenharmony_ci /* 488c2ecf20Sopenharmony_ci * Interrupt is cascaded so perform interrupt 498c2ecf20Sopenharmony_ci * acknowledge on controller 2. 508c2ecf20Sopenharmony_ci */ 518c2ecf20Sopenharmony_ci outb(0x0C, 0xA0); /* prepare for poll */ 528c2ecf20Sopenharmony_ci irq = (inb(0xA0) & 7) + 8; 538c2ecf20Sopenharmony_ci } 548c2ecf20Sopenharmony_ci } 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci if (irq == 7) { 578c2ecf20Sopenharmony_ci /* 588c2ecf20Sopenharmony_ci * This may be a spurious interrupt. 598c2ecf20Sopenharmony_ci * 608c2ecf20Sopenharmony_ci * Read the interrupt status register (ISR). If the most 618c2ecf20Sopenharmony_ci * significant bit is not set then there is no valid 628c2ecf20Sopenharmony_ci * interrupt. 638c2ecf20Sopenharmony_ci */ 648c2ecf20Sopenharmony_ci if (!pci_intack) 658c2ecf20Sopenharmony_ci outb(0x0B, 0x20); /* ISR register */ 668c2ecf20Sopenharmony_ci if(~inb(0x20) & 0x80) 678c2ecf20Sopenharmony_ci irq = 0; 688c2ecf20Sopenharmony_ci } else if (irq == 0xff) 698c2ecf20Sopenharmony_ci irq = 0; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci if (lock) 728c2ecf20Sopenharmony_ci raw_spin_unlock(&i8259_lock); 738c2ecf20Sopenharmony_ci return irq; 748c2ecf20Sopenharmony_ci} 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_cistatic void i8259_mask_and_ack_irq(struct irq_data *d) 778c2ecf20Sopenharmony_ci{ 788c2ecf20Sopenharmony_ci unsigned long flags; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci raw_spin_lock_irqsave(&i8259_lock, flags); 818c2ecf20Sopenharmony_ci if (d->irq > 7) { 828c2ecf20Sopenharmony_ci cached_A1 |= 1 << (d->irq-8); 838c2ecf20Sopenharmony_ci inb(0xA1); /* DUMMY */ 848c2ecf20Sopenharmony_ci outb(cached_A1, 0xA1); 858c2ecf20Sopenharmony_ci outb(0x20, 0xA0); /* Non-specific EOI */ 868c2ecf20Sopenharmony_ci outb(0x20, 0x20); /* Non-specific EOI to cascade */ 878c2ecf20Sopenharmony_ci } else { 888c2ecf20Sopenharmony_ci cached_21 |= 1 << d->irq; 898c2ecf20Sopenharmony_ci inb(0x21); /* DUMMY */ 908c2ecf20Sopenharmony_ci outb(cached_21, 0x21); 918c2ecf20Sopenharmony_ci outb(0x20, 0x20); /* Non-specific EOI */ 928c2ecf20Sopenharmony_ci } 938c2ecf20Sopenharmony_ci raw_spin_unlock_irqrestore(&i8259_lock, flags); 948c2ecf20Sopenharmony_ci} 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_cistatic void i8259_set_irq_mask(int irq_nr) 978c2ecf20Sopenharmony_ci{ 988c2ecf20Sopenharmony_ci outb(cached_A1,0xA1); 998c2ecf20Sopenharmony_ci outb(cached_21,0x21); 1008c2ecf20Sopenharmony_ci} 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_cistatic void i8259_mask_irq(struct irq_data *d) 1038c2ecf20Sopenharmony_ci{ 1048c2ecf20Sopenharmony_ci unsigned long flags; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci pr_debug("i8259_mask_irq(%d)\n", d->irq); 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci raw_spin_lock_irqsave(&i8259_lock, flags); 1098c2ecf20Sopenharmony_ci if (d->irq < 8) 1108c2ecf20Sopenharmony_ci cached_21 |= 1 << d->irq; 1118c2ecf20Sopenharmony_ci else 1128c2ecf20Sopenharmony_ci cached_A1 |= 1 << (d->irq-8); 1138c2ecf20Sopenharmony_ci i8259_set_irq_mask(d->irq); 1148c2ecf20Sopenharmony_ci raw_spin_unlock_irqrestore(&i8259_lock, flags); 1158c2ecf20Sopenharmony_ci} 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_cistatic void i8259_unmask_irq(struct irq_data *d) 1188c2ecf20Sopenharmony_ci{ 1198c2ecf20Sopenharmony_ci unsigned long flags; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci pr_debug("i8259_unmask_irq(%d)\n", d->irq); 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci raw_spin_lock_irqsave(&i8259_lock, flags); 1248c2ecf20Sopenharmony_ci if (d->irq < 8) 1258c2ecf20Sopenharmony_ci cached_21 &= ~(1 << d->irq); 1268c2ecf20Sopenharmony_ci else 1278c2ecf20Sopenharmony_ci cached_A1 &= ~(1 << (d->irq-8)); 1288c2ecf20Sopenharmony_ci i8259_set_irq_mask(d->irq); 1298c2ecf20Sopenharmony_ci raw_spin_unlock_irqrestore(&i8259_lock, flags); 1308c2ecf20Sopenharmony_ci} 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_cistatic struct irq_chip i8259_pic = { 1338c2ecf20Sopenharmony_ci .name = "i8259", 1348c2ecf20Sopenharmony_ci .irq_mask = i8259_mask_irq, 1358c2ecf20Sopenharmony_ci .irq_disable = i8259_mask_irq, 1368c2ecf20Sopenharmony_ci .irq_unmask = i8259_unmask_irq, 1378c2ecf20Sopenharmony_ci .irq_mask_ack = i8259_mask_and_ack_irq, 1388c2ecf20Sopenharmony_ci}; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_cistatic struct resource pic1_iores = { 1418c2ecf20Sopenharmony_ci .name = "8259 (master)", 1428c2ecf20Sopenharmony_ci .start = 0x20, 1438c2ecf20Sopenharmony_ci .end = 0x21, 1448c2ecf20Sopenharmony_ci .flags = IORESOURCE_IO | IORESOURCE_BUSY, 1458c2ecf20Sopenharmony_ci}; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_cistatic struct resource pic2_iores = { 1488c2ecf20Sopenharmony_ci .name = "8259 (slave)", 1498c2ecf20Sopenharmony_ci .start = 0xa0, 1508c2ecf20Sopenharmony_ci .end = 0xa1, 1518c2ecf20Sopenharmony_ci .flags = IORESOURCE_IO | IORESOURCE_BUSY, 1528c2ecf20Sopenharmony_ci}; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_cistatic struct resource pic_edgectrl_iores = { 1558c2ecf20Sopenharmony_ci .name = "8259 edge control", 1568c2ecf20Sopenharmony_ci .start = 0x4d0, 1578c2ecf20Sopenharmony_ci .end = 0x4d1, 1588c2ecf20Sopenharmony_ci .flags = IORESOURCE_IO | IORESOURCE_BUSY, 1598c2ecf20Sopenharmony_ci}; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_cistatic int i8259_host_match(struct irq_domain *h, struct device_node *node, 1628c2ecf20Sopenharmony_ci enum irq_domain_bus_token bus_token) 1638c2ecf20Sopenharmony_ci{ 1648c2ecf20Sopenharmony_ci struct device_node *of_node = irq_domain_get_of_node(h); 1658c2ecf20Sopenharmony_ci return of_node == NULL || of_node == node; 1668c2ecf20Sopenharmony_ci} 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_cistatic int i8259_host_map(struct irq_domain *h, unsigned int virq, 1698c2ecf20Sopenharmony_ci irq_hw_number_t hw) 1708c2ecf20Sopenharmony_ci{ 1718c2ecf20Sopenharmony_ci pr_debug("i8259_host_map(%d, 0x%lx)\n", virq, hw); 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci /* We block the internal cascade */ 1748c2ecf20Sopenharmony_ci if (hw == 2) 1758c2ecf20Sopenharmony_ci irq_set_status_flags(virq, IRQ_NOREQUEST); 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci /* We use the level handler only for now, we might want to 1788c2ecf20Sopenharmony_ci * be more cautious here but that works for now 1798c2ecf20Sopenharmony_ci */ 1808c2ecf20Sopenharmony_ci irq_set_status_flags(virq, IRQ_LEVEL); 1818c2ecf20Sopenharmony_ci irq_set_chip_and_handler(virq, &i8259_pic, handle_level_irq); 1828c2ecf20Sopenharmony_ci return 0; 1838c2ecf20Sopenharmony_ci} 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_cistatic int i8259_host_xlate(struct irq_domain *h, struct device_node *ct, 1868c2ecf20Sopenharmony_ci const u32 *intspec, unsigned int intsize, 1878c2ecf20Sopenharmony_ci irq_hw_number_t *out_hwirq, unsigned int *out_flags) 1888c2ecf20Sopenharmony_ci{ 1898c2ecf20Sopenharmony_ci static unsigned char map_isa_senses[4] = { 1908c2ecf20Sopenharmony_ci IRQ_TYPE_LEVEL_LOW, 1918c2ecf20Sopenharmony_ci IRQ_TYPE_LEVEL_HIGH, 1928c2ecf20Sopenharmony_ci IRQ_TYPE_EDGE_FALLING, 1938c2ecf20Sopenharmony_ci IRQ_TYPE_EDGE_RISING, 1948c2ecf20Sopenharmony_ci }; 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci *out_hwirq = intspec[0]; 1978c2ecf20Sopenharmony_ci if (intsize > 1 && intspec[1] < 4) 1988c2ecf20Sopenharmony_ci *out_flags = map_isa_senses[intspec[1]]; 1998c2ecf20Sopenharmony_ci else 2008c2ecf20Sopenharmony_ci *out_flags = IRQ_TYPE_NONE; 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci return 0; 2038c2ecf20Sopenharmony_ci} 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_cistatic const struct irq_domain_ops i8259_host_ops = { 2068c2ecf20Sopenharmony_ci .match = i8259_host_match, 2078c2ecf20Sopenharmony_ci .map = i8259_host_map, 2088c2ecf20Sopenharmony_ci .xlate = i8259_host_xlate, 2098c2ecf20Sopenharmony_ci}; 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_cistruct irq_domain *i8259_get_host(void) 2128c2ecf20Sopenharmony_ci{ 2138c2ecf20Sopenharmony_ci return i8259_host; 2148c2ecf20Sopenharmony_ci} 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci/** 2178c2ecf20Sopenharmony_ci * i8259_init - Initialize the legacy controller 2188c2ecf20Sopenharmony_ci * @node: device node of the legacy PIC (can be NULL, but then, it will match 2198c2ecf20Sopenharmony_ci * all interrupts, so beware) 2208c2ecf20Sopenharmony_ci * @intack_addr: PCI interrupt acknowledge (real) address which will return 2218c2ecf20Sopenharmony_ci * the active irq from the 8259 2228c2ecf20Sopenharmony_ci */ 2238c2ecf20Sopenharmony_civoid i8259_init(struct device_node *node, unsigned long intack_addr) 2248c2ecf20Sopenharmony_ci{ 2258c2ecf20Sopenharmony_ci unsigned long flags; 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci /* initialize the controller */ 2288c2ecf20Sopenharmony_ci raw_spin_lock_irqsave(&i8259_lock, flags); 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci /* Mask all first */ 2318c2ecf20Sopenharmony_ci outb(0xff, 0xA1); 2328c2ecf20Sopenharmony_ci outb(0xff, 0x21); 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci /* init master interrupt controller */ 2358c2ecf20Sopenharmony_ci outb(0x11, 0x20); /* Start init sequence */ 2368c2ecf20Sopenharmony_ci outb(0x00, 0x21); /* Vector base */ 2378c2ecf20Sopenharmony_ci outb(0x04, 0x21); /* edge triggered, Cascade (slave) on IRQ2 */ 2388c2ecf20Sopenharmony_ci outb(0x01, 0x21); /* Select 8086 mode */ 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci /* init slave interrupt controller */ 2418c2ecf20Sopenharmony_ci outb(0x11, 0xA0); /* Start init sequence */ 2428c2ecf20Sopenharmony_ci outb(0x08, 0xA1); /* Vector base */ 2438c2ecf20Sopenharmony_ci outb(0x02, 0xA1); /* edge triggered, Cascade (slave) on IRQ2 */ 2448c2ecf20Sopenharmony_ci outb(0x01, 0xA1); /* Select 8086 mode */ 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci /* That thing is slow */ 2478c2ecf20Sopenharmony_ci udelay(100); 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci /* always read ISR */ 2508c2ecf20Sopenharmony_ci outb(0x0B, 0x20); 2518c2ecf20Sopenharmony_ci outb(0x0B, 0xA0); 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci /* Unmask the internal cascade */ 2548c2ecf20Sopenharmony_ci cached_21 &= ~(1 << 2); 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci /* Set interrupt masks */ 2578c2ecf20Sopenharmony_ci outb(cached_A1, 0xA1); 2588c2ecf20Sopenharmony_ci outb(cached_21, 0x21); 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci raw_spin_unlock_irqrestore(&i8259_lock, flags); 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci /* create a legacy host */ 2638c2ecf20Sopenharmony_ci i8259_host = irq_domain_add_legacy_isa(node, &i8259_host_ops, NULL); 2648c2ecf20Sopenharmony_ci if (i8259_host == NULL) { 2658c2ecf20Sopenharmony_ci printk(KERN_ERR "i8259: failed to allocate irq host !\n"); 2668c2ecf20Sopenharmony_ci return; 2678c2ecf20Sopenharmony_ci } 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci /* reserve our resources */ 2708c2ecf20Sopenharmony_ci /* XXX should we continue doing that ? it seems to cause problems 2718c2ecf20Sopenharmony_ci * with further requesting of PCI IO resources for that range... 2728c2ecf20Sopenharmony_ci * need to look into it. 2738c2ecf20Sopenharmony_ci */ 2748c2ecf20Sopenharmony_ci request_resource(&ioport_resource, &pic1_iores); 2758c2ecf20Sopenharmony_ci request_resource(&ioport_resource, &pic2_iores); 2768c2ecf20Sopenharmony_ci request_resource(&ioport_resource, &pic_edgectrl_iores); 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci if (intack_addr != 0) 2798c2ecf20Sopenharmony_ci pci_intack = ioremap(intack_addr, 1); 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci printk(KERN_INFO "i8259 legacy interrupt controller initialized\n"); 2828c2ecf20Sopenharmony_ci} 283