Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
1da177e4 LT |
2 | /* |
3 | * linux/arch/alpha/kernel/irq_i8259.c | |
4 | * | |
5 | * This is the 'legacy' 8259A Programmable Interrupt Controller, | |
6 | * present in the majority of PC/AT boxes. | |
7 | * | |
8 | * Started hacking from linux-2.3.30pre6/arch/i386/kernel/i8259.c. | |
9 | */ | |
10 | ||
1da177e4 LT |
11 | #include <linux/init.h> |
12 | #include <linux/cache.h> | |
13 | #include <linux/sched.h> | |
14 | #include <linux/irq.h> | |
15 | #include <linux/interrupt.h> | |
16 | ||
17 | #include <asm/io.h> | |
18 | ||
19 | #include "proto.h" | |
20 | #include "irq_impl.h" | |
21 | ||
22 | ||
23 | /* Note mask bit is true for DISABLED irqs. */ | |
24 | static unsigned int cached_irq_mask = 0xffff; | |
25 | static DEFINE_SPINLOCK(i8259_irq_lock); | |
26 | ||
27 | static inline void | |
28 | i8259_update_irq_hw(unsigned int irq, unsigned long mask) | |
29 | { | |
30 | int port = 0x21; | |
31 | if (irq & 8) mask >>= 8; | |
32 | if (irq & 8) port = 0xA1; | |
33 | outb(mask, port); | |
34 | } | |
35 | ||
36 | inline void | |
ff53afe6 | 37 | i8259a_enable_irq(struct irq_data *d) |
1da177e4 LT |
38 | { |
39 | spin_lock(&i8259_irq_lock); | |
ff53afe6 | 40 | i8259_update_irq_hw(d->irq, cached_irq_mask &= ~(1 << d->irq)); |
1da177e4 LT |
41 | spin_unlock(&i8259_irq_lock); |
42 | } | |
43 | ||
44 | static inline void | |
45 | __i8259a_disable_irq(unsigned int irq) | |
46 | { | |
47 | i8259_update_irq_hw(irq, cached_irq_mask |= 1 << irq); | |
48 | } | |
49 | ||
50 | void | |
ff53afe6 | 51 | i8259a_disable_irq(struct irq_data *d) |
1da177e4 LT |
52 | { |
53 | spin_lock(&i8259_irq_lock); | |
ff53afe6 | 54 | __i8259a_disable_irq(d->irq); |
1da177e4 LT |
55 | spin_unlock(&i8259_irq_lock); |
56 | } | |
57 | ||
58 | void | |
ff53afe6 | 59 | i8259a_mask_and_ack_irq(struct irq_data *d) |
1da177e4 | 60 | { |
ff53afe6 TG |
61 | unsigned int irq = d->irq; |
62 | ||
1da177e4 LT |
63 | spin_lock(&i8259_irq_lock); |
64 | __i8259a_disable_irq(irq); | |
65 | ||
66 | /* Ack the interrupt making it the lowest priority. */ | |
67 | if (irq >= 8) { | |
68 | outb(0xE0 | (irq - 8), 0xa0); /* ack the slave */ | |
69 | irq = 2; | |
70 | } | |
71 | outb(0xE0 | irq, 0x20); /* ack the master */ | |
72 | spin_unlock(&i8259_irq_lock); | |
73 | } | |
74 | ||
44377f62 | 75 | struct irq_chip i8259a_irq_type = { |
8ab1221c | 76 | .name = "XT-PIC", |
ff53afe6 TG |
77 | .irq_unmask = i8259a_enable_irq, |
78 | .irq_mask = i8259a_disable_irq, | |
79 | .irq_mask_ack = i8259a_mask_and_ack_irq, | |
1da177e4 LT |
80 | }; |
81 | ||
82 | void __init | |
83 | init_i8259a_irqs(void) | |
84 | { | |
1da177e4 LT |
85 | long i; |
86 | ||
87 | outb(0xff, 0x21); /* mask all of 8259A-1 */ | |
88 | outb(0xff, 0xA1); /* mask all of 8259A-2 */ | |
89 | ||
90 | for (i = 0; i < 16; i++) { | |
a9eb076b | 91 | irq_set_chip_and_handler(i, &i8259a_irq_type, handle_level_irq); |
1da177e4 LT |
92 | } |
93 | ||
82c849eb | 94 | if (request_irq(2, no_action, 0, "cascade", NULL)) |
95 | pr_err("Failed to request irq 2 (cascade)\n"); | |
1da177e4 LT |
96 | } |
97 | ||
98 | ||
99 | #if defined(CONFIG_ALPHA_GENERIC) | |
100 | # define IACK_SC alpha_mv.iack_sc | |
1da177e4 LT |
101 | #elif defined(CONFIG_ALPHA_CIA) |
102 | # define IACK_SC CIA_IACK_SC | |
103 | #elif defined(CONFIG_ALPHA_PYXIS) | |
104 | # define IACK_SC PYXIS_IACK_SC | |
105 | #elif defined(CONFIG_ALPHA_TITAN) | |
106 | # define IACK_SC TITAN_IACK_SC | |
107 | #elif defined(CONFIG_ALPHA_TSUNAMI) | |
108 | # define IACK_SC TSUNAMI_IACK_SC | |
109 | #elif defined(CONFIG_ALPHA_IRONGATE) | |
110 | # define IACK_SC IRONGATE_IACK_SC | |
111 | #endif | |
112 | /* Note that CONFIG_ALPHA_POLARIS is intentionally left out here, since | |
113 | sys_rx164 wants to use isa_no_iack_sc_device_interrupt for some reason. */ | |
114 | ||
115 | #if defined(IACK_SC) | |
116 | void | |
7ca56053 | 117 | isa_device_interrupt(unsigned long vector) |
1da177e4 LT |
118 | { |
119 | /* | |
120 | * Generate a PCI interrupt acknowledge cycle. The PIC will | |
121 | * respond with the interrupt vector of the highest priority | |
122 | * interrupt that is pending. The PALcode sets up the | |
123 | * interrupts vectors such that irq level L generates vector L. | |
124 | */ | |
125 | int j = *(vuip) IACK_SC; | |
126 | j &= 0xff; | |
3dbb8c62 | 127 | handle_irq(j); |
1da177e4 LT |
128 | } |
129 | #endif | |
130 | ||
131 | #if defined(CONFIG_ALPHA_GENERIC) || !defined(IACK_SC) | |
132 | void | |
3dbb8c62 | 133 | isa_no_iack_sc_device_interrupt(unsigned long vector) |
1da177e4 LT |
134 | { |
135 | unsigned long pic; | |
136 | ||
137 | /* | |
138 | * It seems to me that the probability of two or more *device* | |
139 | * interrupts occurring at almost exactly the same time is | |
140 | * pretty low. So why pay the price of checking for | |
141 | * additional interrupts here if the common case can be | |
142 | * handled so much easier? | |
143 | */ | |
144 | /* | |
145 | * The first read of gives you *all* interrupting lines. | |
146 | * Therefore, read the mask register and and out those lines | |
147 | * not enabled. Note that some documentation has 21 and a1 | |
148 | * write only. This is not true. | |
149 | */ | |
150 | pic = inb(0x20) | (inb(0xA0) << 8); /* read isr */ | |
151 | pic &= 0xFFFB; /* mask out cascade & hibits */ | |
152 | ||
153 | while (pic) { | |
154 | int j = ffz(~pic); | |
155 | pic &= pic - 1; | |
3dbb8c62 | 156 | handle_irq(j); |
1da177e4 LT |
157 | } |
158 | } | |
159 | #endif |