162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * i8259 interrupt controller driver. 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci#undef DEBUG 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/ioport.h> 862306a36Sopenharmony_ci#include <linux/interrupt.h> 962306a36Sopenharmony_ci#include <linux/irqdomain.h> 1062306a36Sopenharmony_ci#include <linux/kernel.h> 1162306a36Sopenharmony_ci#include <linux/delay.h> 1262306a36Sopenharmony_ci#include <asm/io.h> 1362306a36Sopenharmony_ci#include <asm/i8259.h> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_cistatic volatile void __iomem *pci_intack; /* RO, gives us the irq vector */ 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_cistatic unsigned char cached_8259[2] = { 0xff, 0xff }; 1862306a36Sopenharmony_ci#define cached_A1 (cached_8259[0]) 1962306a36Sopenharmony_ci#define cached_21 (cached_8259[1]) 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cistatic DEFINE_RAW_SPINLOCK(i8259_lock); 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_cistatic struct irq_domain *i8259_host; 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci/* 2662306a36Sopenharmony_ci * Acknowledge the IRQ using either the PCI host bridge's interrupt 2762306a36Sopenharmony_ci * acknowledge feature or poll. How i8259_init() is called determines 2862306a36Sopenharmony_ci * which is called. It should be noted that polling is broken on some 2962306a36Sopenharmony_ci * IBM and Motorola PReP boxes so we must use the int-ack feature on them. 3062306a36Sopenharmony_ci */ 3162306a36Sopenharmony_ciunsigned int i8259_irq(void) 3262306a36Sopenharmony_ci{ 3362306a36Sopenharmony_ci int irq; 3462306a36Sopenharmony_ci int lock = 0; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci /* Either int-ack or poll for the IRQ */ 3762306a36Sopenharmony_ci if (pci_intack) 3862306a36Sopenharmony_ci irq = readb(pci_intack); 3962306a36Sopenharmony_ci else { 4062306a36Sopenharmony_ci raw_spin_lock(&i8259_lock); 4162306a36Sopenharmony_ci lock = 1; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci /* Perform an interrupt acknowledge cycle on controller 1. */ 4462306a36Sopenharmony_ci outb(0x0C, 0x20); /* prepare for poll */ 4562306a36Sopenharmony_ci irq = inb(0x20) & 7; 4662306a36Sopenharmony_ci if (irq == 2 ) { 4762306a36Sopenharmony_ci /* 4862306a36Sopenharmony_ci * Interrupt is cascaded so perform interrupt 4962306a36Sopenharmony_ci * acknowledge on controller 2. 5062306a36Sopenharmony_ci */ 5162306a36Sopenharmony_ci outb(0x0C, 0xA0); /* prepare for poll */ 5262306a36Sopenharmony_ci irq = (inb(0xA0) & 7) + 8; 5362306a36Sopenharmony_ci } 5462306a36Sopenharmony_ci } 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci if (irq == 7) { 5762306a36Sopenharmony_ci /* 5862306a36Sopenharmony_ci * This may be a spurious interrupt. 5962306a36Sopenharmony_ci * 6062306a36Sopenharmony_ci * Read the interrupt status register (ISR). If the most 6162306a36Sopenharmony_ci * significant bit is not set then there is no valid 6262306a36Sopenharmony_ci * interrupt. 6362306a36Sopenharmony_ci */ 6462306a36Sopenharmony_ci if (!pci_intack) 6562306a36Sopenharmony_ci outb(0x0B, 0x20); /* ISR register */ 6662306a36Sopenharmony_ci if(~inb(0x20) & 0x80) 6762306a36Sopenharmony_ci irq = 0; 6862306a36Sopenharmony_ci } else if (irq == 0xff) 6962306a36Sopenharmony_ci irq = 0; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci if (lock) 7262306a36Sopenharmony_ci raw_spin_unlock(&i8259_lock); 7362306a36Sopenharmony_ci return irq; 7462306a36Sopenharmony_ci} 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_cistatic void i8259_mask_and_ack_irq(struct irq_data *d) 7762306a36Sopenharmony_ci{ 7862306a36Sopenharmony_ci unsigned long flags; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci raw_spin_lock_irqsave(&i8259_lock, flags); 8162306a36Sopenharmony_ci if (d->irq > 7) { 8262306a36Sopenharmony_ci cached_A1 |= 1 << (d->irq-8); 8362306a36Sopenharmony_ci inb(0xA1); /* DUMMY */ 8462306a36Sopenharmony_ci outb(cached_A1, 0xA1); 8562306a36Sopenharmony_ci outb(0x20, 0xA0); /* Non-specific EOI */ 8662306a36Sopenharmony_ci outb(0x20, 0x20); /* Non-specific EOI to cascade */ 8762306a36Sopenharmony_ci } else { 8862306a36Sopenharmony_ci cached_21 |= 1 << d->irq; 8962306a36Sopenharmony_ci inb(0x21); /* DUMMY */ 9062306a36Sopenharmony_ci outb(cached_21, 0x21); 9162306a36Sopenharmony_ci outb(0x20, 0x20); /* Non-specific EOI */ 9262306a36Sopenharmony_ci } 9362306a36Sopenharmony_ci raw_spin_unlock_irqrestore(&i8259_lock, flags); 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_cistatic void i8259_set_irq_mask(int irq_nr) 9762306a36Sopenharmony_ci{ 9862306a36Sopenharmony_ci outb(cached_A1,0xA1); 9962306a36Sopenharmony_ci outb(cached_21,0x21); 10062306a36Sopenharmony_ci} 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_cistatic void i8259_mask_irq(struct irq_data *d) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci unsigned long flags; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci pr_debug("i8259_mask_irq(%d)\n", d->irq); 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci raw_spin_lock_irqsave(&i8259_lock, flags); 10962306a36Sopenharmony_ci if (d->irq < 8) 11062306a36Sopenharmony_ci cached_21 |= 1 << d->irq; 11162306a36Sopenharmony_ci else 11262306a36Sopenharmony_ci cached_A1 |= 1 << (d->irq-8); 11362306a36Sopenharmony_ci i8259_set_irq_mask(d->irq); 11462306a36Sopenharmony_ci raw_spin_unlock_irqrestore(&i8259_lock, flags); 11562306a36Sopenharmony_ci} 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_cistatic void i8259_unmask_irq(struct irq_data *d) 11862306a36Sopenharmony_ci{ 11962306a36Sopenharmony_ci unsigned long flags; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci pr_debug("i8259_unmask_irq(%d)\n", d->irq); 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci raw_spin_lock_irqsave(&i8259_lock, flags); 12462306a36Sopenharmony_ci if (d->irq < 8) 12562306a36Sopenharmony_ci cached_21 &= ~(1 << d->irq); 12662306a36Sopenharmony_ci else 12762306a36Sopenharmony_ci cached_A1 &= ~(1 << (d->irq-8)); 12862306a36Sopenharmony_ci i8259_set_irq_mask(d->irq); 12962306a36Sopenharmony_ci raw_spin_unlock_irqrestore(&i8259_lock, flags); 13062306a36Sopenharmony_ci} 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_cistatic struct irq_chip i8259_pic = { 13362306a36Sopenharmony_ci .name = "i8259", 13462306a36Sopenharmony_ci .irq_mask = i8259_mask_irq, 13562306a36Sopenharmony_ci .irq_disable = i8259_mask_irq, 13662306a36Sopenharmony_ci .irq_unmask = i8259_unmask_irq, 13762306a36Sopenharmony_ci .irq_mask_ack = i8259_mask_and_ack_irq, 13862306a36Sopenharmony_ci}; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_cistatic struct resource pic1_iores = { 14162306a36Sopenharmony_ci .name = "8259 (master)", 14262306a36Sopenharmony_ci .start = 0x20, 14362306a36Sopenharmony_ci .end = 0x21, 14462306a36Sopenharmony_ci .flags = IORESOURCE_IO | IORESOURCE_BUSY, 14562306a36Sopenharmony_ci}; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_cistatic struct resource pic2_iores = { 14862306a36Sopenharmony_ci .name = "8259 (slave)", 14962306a36Sopenharmony_ci .start = 0xa0, 15062306a36Sopenharmony_ci .end = 0xa1, 15162306a36Sopenharmony_ci .flags = IORESOURCE_IO | IORESOURCE_BUSY, 15262306a36Sopenharmony_ci}; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_cistatic struct resource pic_edgectrl_iores = { 15562306a36Sopenharmony_ci .name = "8259 edge control", 15662306a36Sopenharmony_ci .start = 0x4d0, 15762306a36Sopenharmony_ci .end = 0x4d1, 15862306a36Sopenharmony_ci .flags = IORESOURCE_IO | IORESOURCE_BUSY, 15962306a36Sopenharmony_ci}; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_cistatic int i8259_host_match(struct irq_domain *h, struct device_node *node, 16262306a36Sopenharmony_ci enum irq_domain_bus_token bus_token) 16362306a36Sopenharmony_ci{ 16462306a36Sopenharmony_ci struct device_node *of_node = irq_domain_get_of_node(h); 16562306a36Sopenharmony_ci return of_node == NULL || of_node == node; 16662306a36Sopenharmony_ci} 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_cistatic int i8259_host_map(struct irq_domain *h, unsigned int virq, 16962306a36Sopenharmony_ci irq_hw_number_t hw) 17062306a36Sopenharmony_ci{ 17162306a36Sopenharmony_ci pr_debug("i8259_host_map(%d, 0x%lx)\n", virq, hw); 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci /* We block the internal cascade */ 17462306a36Sopenharmony_ci if (hw == 2) 17562306a36Sopenharmony_ci irq_set_status_flags(virq, IRQ_NOREQUEST); 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci /* We use the level handler only for now, we might want to 17862306a36Sopenharmony_ci * be more cautious here but that works for now 17962306a36Sopenharmony_ci */ 18062306a36Sopenharmony_ci irq_set_status_flags(virq, IRQ_LEVEL); 18162306a36Sopenharmony_ci irq_set_chip_and_handler(virq, &i8259_pic, handle_level_irq); 18262306a36Sopenharmony_ci return 0; 18362306a36Sopenharmony_ci} 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_cistatic int i8259_host_xlate(struct irq_domain *h, struct device_node *ct, 18662306a36Sopenharmony_ci const u32 *intspec, unsigned int intsize, 18762306a36Sopenharmony_ci irq_hw_number_t *out_hwirq, unsigned int *out_flags) 18862306a36Sopenharmony_ci{ 18962306a36Sopenharmony_ci static unsigned char map_isa_senses[4] = { 19062306a36Sopenharmony_ci IRQ_TYPE_LEVEL_LOW, 19162306a36Sopenharmony_ci IRQ_TYPE_LEVEL_HIGH, 19262306a36Sopenharmony_ci IRQ_TYPE_EDGE_FALLING, 19362306a36Sopenharmony_ci IRQ_TYPE_EDGE_RISING, 19462306a36Sopenharmony_ci }; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci *out_hwirq = intspec[0]; 19762306a36Sopenharmony_ci if (intsize > 1 && intspec[1] < 4) 19862306a36Sopenharmony_ci *out_flags = map_isa_senses[intspec[1]]; 19962306a36Sopenharmony_ci else 20062306a36Sopenharmony_ci *out_flags = IRQ_TYPE_NONE; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci return 0; 20362306a36Sopenharmony_ci} 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_cistatic const struct irq_domain_ops i8259_host_ops = { 20662306a36Sopenharmony_ci .match = i8259_host_match, 20762306a36Sopenharmony_ci .map = i8259_host_map, 20862306a36Sopenharmony_ci .xlate = i8259_host_xlate, 20962306a36Sopenharmony_ci}; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_cistruct irq_domain *__init i8259_get_host(void) 21262306a36Sopenharmony_ci{ 21362306a36Sopenharmony_ci return i8259_host; 21462306a36Sopenharmony_ci} 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci/** 21762306a36Sopenharmony_ci * i8259_init - Initialize the legacy controller 21862306a36Sopenharmony_ci * @node: device node of the legacy PIC (can be NULL, but then, it will match 21962306a36Sopenharmony_ci * all interrupts, so beware) 22062306a36Sopenharmony_ci * @intack_addr: PCI interrupt acknowledge (real) address which will return 22162306a36Sopenharmony_ci * the active irq from the 8259 22262306a36Sopenharmony_ci */ 22362306a36Sopenharmony_civoid i8259_init(struct device_node *node, unsigned long intack_addr) 22462306a36Sopenharmony_ci{ 22562306a36Sopenharmony_ci unsigned long flags; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci /* initialize the controller */ 22862306a36Sopenharmony_ci raw_spin_lock_irqsave(&i8259_lock, flags); 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci /* Mask all first */ 23162306a36Sopenharmony_ci outb(0xff, 0xA1); 23262306a36Sopenharmony_ci outb(0xff, 0x21); 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci /* init master interrupt controller */ 23562306a36Sopenharmony_ci outb(0x11, 0x20); /* Start init sequence */ 23662306a36Sopenharmony_ci outb(0x00, 0x21); /* Vector base */ 23762306a36Sopenharmony_ci outb(0x04, 0x21); /* edge triggered, Cascade (slave) on IRQ2 */ 23862306a36Sopenharmony_ci outb(0x01, 0x21); /* Select 8086 mode */ 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci /* init slave interrupt controller */ 24162306a36Sopenharmony_ci outb(0x11, 0xA0); /* Start init sequence */ 24262306a36Sopenharmony_ci outb(0x08, 0xA1); /* Vector base */ 24362306a36Sopenharmony_ci outb(0x02, 0xA1); /* edge triggered, Cascade (slave) on IRQ2 */ 24462306a36Sopenharmony_ci outb(0x01, 0xA1); /* Select 8086 mode */ 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci /* That thing is slow */ 24762306a36Sopenharmony_ci udelay(100); 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci /* always read ISR */ 25062306a36Sopenharmony_ci outb(0x0B, 0x20); 25162306a36Sopenharmony_ci outb(0x0B, 0xA0); 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci /* Unmask the internal cascade */ 25462306a36Sopenharmony_ci cached_21 &= ~(1 << 2); 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci /* Set interrupt masks */ 25762306a36Sopenharmony_ci outb(cached_A1, 0xA1); 25862306a36Sopenharmony_ci outb(cached_21, 0x21); 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci raw_spin_unlock_irqrestore(&i8259_lock, flags); 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci /* create a legacy host */ 26362306a36Sopenharmony_ci i8259_host = irq_domain_add_legacy(node, NR_IRQS_LEGACY, 0, 0, 26462306a36Sopenharmony_ci &i8259_host_ops, NULL); 26562306a36Sopenharmony_ci if (i8259_host == NULL) { 26662306a36Sopenharmony_ci printk(KERN_ERR "i8259: failed to allocate irq host !\n"); 26762306a36Sopenharmony_ci return; 26862306a36Sopenharmony_ci } 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci /* reserve our resources */ 27162306a36Sopenharmony_ci /* XXX should we continue doing that ? it seems to cause problems 27262306a36Sopenharmony_ci * with further requesting of PCI IO resources for that range... 27362306a36Sopenharmony_ci * need to look into it. 27462306a36Sopenharmony_ci */ 27562306a36Sopenharmony_ci request_resource(&ioport_resource, &pic1_iores); 27662306a36Sopenharmony_ci request_resource(&ioport_resource, &pic2_iores); 27762306a36Sopenharmony_ci request_resource(&ioport_resource, &pic_edgectrl_iores); 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci if (intack_addr != 0) 28062306a36Sopenharmony_ci pci_intack = ioremap(intack_addr, 1); 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci printk(KERN_INFO "i8259 legacy interrupt controller initialized\n"); 28362306a36Sopenharmony_ci} 284