1 /************************************************************************/
2 /* This module supports the iSeries PCI bus interrupt handling          */
3 /* Copyright (C) 20yy  <Robert L Holtorf> <IBM Corp>                    */
4 /*                                                                      */
5 /* This program is free software; you can redistribute it and/or modify */
6 /* it under the terms of the GNU General Public License as published by */
7 /* the Free Software Foundation; either version 2 of the License, or    */
8 /* (at your option) any later version.                                  */
9 /*                                                                      */
10 /* This program is distributed in the hope that it will be useful,      */
11 /* but WITHOUT ANY WARRANTY; without even the implied warranty of       */
12 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        */
13 /* GNU General Public License for more details.                         */
14 /*                                                                      */
15 /* You should have received a copy of the GNU General Public License    */
16 /* along with this program; if not, write to the:                       */
17 /* Free Software Foundation, Inc.,                                      */
18 /* 59 Temple Place, Suite 330,                                          */
19 /* Boston, MA  02111-1307  USA                                          */
20 /************************************************************************/
21 /* Change Activity:                                                     */
22 /*   Created, December 13, 2000 by Wayne Holm                           */
23 /* End Change Activity                                                  */
24 /************************************************************************/
25 #include <linux/pci.h>
26 #include <linux/init.h>
27 #include <linux/threads.h>
28 #include <linux/smp.h>
29 #include <linux/param.h>
30 #include <linux/string.h>
31 #include <linux/bootmem.h>
32 #include <linux/blk.h>
33 #include <linux/ide.h>
34 
35 #include <linux/irq.h>
36 #include <linux/spinlock.h>
37 #include <asm/ppcdebug.h>
38 
39 #include <asm/iSeries/HvCallPci.h>
40 #include <asm/iSeries/HvCallXm.h>
41 #include <asm/iSeries/iSeries_irq.h>
42 #include <asm/iSeries/XmPciLpEvent.h>
43 
44 
45 hw_irq_controller iSeries_IRQ_handler = {
46 	"iSeries irq controller",
47 	iSeries_startup_IRQ,	/* startup */
48 	iSeries_shutdown_IRQ,	/* shutdown */
49 	iSeries_enable_IRQ,	/* enable */
50 	iSeries_disable_IRQ,	/* disable */
51 	NULL,			/* ack  */
52 	iSeries_end_IRQ,	/* end  */
53 	NULL			/* set_affinity */
54 };
55 
56 
57 struct iSeries_irqEntry {
58 	u32 dsa;
59 	struct iSeries_irqEntry* next;
60 };
61 
62 struct iSeries_irqAnchor {
63 	u8  valid : 1;
64 	u8  reserved : 7;
65 	u16  entryCount;
66 	struct iSeries_irqEntry* head;
67 };
68 
69 struct iSeries_irqAnchor iSeries_irqMap[NR_IRQS];
70 
71 void iSeries_init_irqMap(int irq);
72 
iSeries_init_irq_desc(irq_desc_t * desc)73 void iSeries_init_irq_desc(irq_desc_t *desc)
74 {
75 	if (!desc->handler)
76 		desc->handler = &iSeries_IRQ_handler;
77 }
78 
79 /*  This is called by init_IRQ.  set in ppc_md.init_IRQ by iSeries_setup.c */
iSeries_init_IRQ(void)80 void __init iSeries_init_IRQ(void)
81 {
82 	int i;
83 	irq_desc_t *desc;
84 
85 	for (i = 0; i < NR_IRQS; i++) {
86 		desc = real_irqdesc(i);
87 		desc->handler = &iSeries_IRQ_handler;
88 		desc->status = 0;
89 		desc->status |= IRQ_DISABLED;
90 		desc->depth = 1;
91 		iSeries_init_irqMap(i);
92 	}
93 	/* Register PCI event handler and open an event path */
94 	PPCDBG(PPCDBG_BUSWALK,"Register PCI event handler and open an event path\n");
95 	XmPciLpEvent_init();
96 	return;
97 }
98 
99 /**********************************************************************
100  *  Called by iSeries_init_IRQ
101  * Prevent IRQs 0 and 255 from being used.  IRQ 0 appears in
102  * uninitialized devices.  IRQ 255 appears in the PCI interrupt
103  * line register if a PCI error occurs,
104  *********************************************************************/
iSeries_init_irqMap(int irq)105 void __init iSeries_init_irqMap(int irq)
106 {
107 	iSeries_irqMap[irq].valid = (irq == 0 || irq == 255)? 0 : 1;
108 	iSeries_irqMap[irq].entryCount = 0;
109 	iSeries_irqMap[irq].head = NULL;
110 }
111 
112 /* This is called out of iSeries_scan_slot to allocate an IRQ for an EADS slot */
113 /* It calculates the irq value for the slot.                                   */
iSeries_allocate_IRQ(HvBusNumber busNumber,HvSubBusNumber subBusNumber,HvAgentId deviceId)114 int __init iSeries_allocate_IRQ(HvBusNumber busNumber, HvSubBusNumber subBusNumber, HvAgentId deviceId)
115 {
116 	u8 idsel = (deviceId >> 4);
117 	u8 function = deviceId & 0x0F;
118 	int irq = ((((busNumber-1)*16 + (idsel-1)*8 + function)*9/8) % 253) + 2;
119 	return irq;
120 }
121 
122 /* This is called out of iSeries_scan_slot to assign the EADS slot to its IRQ number */
iSeries_assign_IRQ(int irq,HvBusNumber busNumber,HvSubBusNumber subBusNumber,HvAgentId deviceId)123 int __init iSeries_assign_IRQ(int irq, HvBusNumber busNumber, HvSubBusNumber subBusNumber, HvAgentId deviceId)
124 {
125 	int rc;
126 	u32 dsa = (busNumber << 16) | (subBusNumber << 8) | deviceId;
127 	struct iSeries_irqEntry* newEntry;
128 	unsigned long flags;
129 	irq_desc_t *desc = irqdesc(irq);
130 
131 	if (irq < 0 || irq >= NR_IRQS) {
132 		return -1;
133 	}
134 	newEntry = kmalloc(sizeof(*newEntry), GFP_KERNEL);
135 	if (newEntry == NULL) {
136 		return -ENOMEM;
137 	}
138 	newEntry->dsa  = dsa;
139 	newEntry->next = NULL;
140 	/********************************************************************
141 	* Probably not necessary to lock the irq since allocation is only
142 	* done during buswalk, but it should not hurt anything except a
143 	* little performance to be smp safe.
144 	*******************************************************************/
145 	spin_lock_irqsave(&desc->lock, flags);
146 
147 	if (iSeries_irqMap[irq].valid) {
148 		/* Push the new element onto the irq stack */
149 		newEntry->next = iSeries_irqMap[irq].head;
150 		iSeries_irqMap[irq].head = newEntry;
151 		++iSeries_irqMap[irq].entryCount;
152 		rc = 0;
153 		PPCDBG(PPCDBG_BUSWALK,"iSeries_assign_IRQ   0x%04X.%02X.%02X = 0x%04X\n",busNumber, subBusNumber, deviceId, irq);
154 	}
155 	else {
156 		printk("PCI: Something is wrong with the iSeries_irqMap. \n");
157 		kfree(newEntry);
158 		rc = -1;
159     }
160 	spin_unlock_irqrestore(&desc->lock, flags);
161 	return rc;
162 }
163 
164 
165 /* This is called by iSeries_activate_IRQs */
iSeries_startup_IRQ(unsigned int irq)166 unsigned int iSeries_startup_IRQ(unsigned int irq)
167 {
168 	struct iSeries_irqEntry* entry;
169 	u32 bus, subBus, deviceId, function, mask;
170 	for(entry=iSeries_irqMap[irq].head; entry!=NULL; entry=entry->next) {
171 		bus      = (entry->dsa >> 16) & 0xFFFF;
172 		subBus   = (entry->dsa >> 8) & 0xFF;
173 		deviceId = entry->dsa & 0xFF;
174 		function = deviceId & 0x0F;
175 		/* Link the IRQ number to the bridge */
176 		HvCallXm_connectBusUnit(bus, subBus, deviceId, irq);
177         	/* Unmask bridge interrupts in the FISR */
178 		mask = 0x01010000 << function;
179 		HvCallPci_unmaskFisr(bus, subBus, deviceId, mask);
180 		PPCDBG(PPCDBG_BUSWALK,"iSeries_activate_IRQ 0x%02X.%02X.%02X  Irq:0x%02X\n",bus,subBus,deviceId,irq);
181 	}
182 	return 0;
183 }
184 
185 /* This is called out of iSeries_fixup to activate interrupt
186  * generation for usable slots                              */
iSeries_activate_IRQs()187 void __init iSeries_activate_IRQs()
188 {
189 	int irq;
190 	unsigned long flags;
191 	for (irq=0; irq < NR_IRQS; irq++) {
192 		irq_desc_t *desc = irqdesc(irq);
193 		spin_lock_irqsave(&desc->lock, flags);
194 		desc->handler->startup(irq);
195 		spin_unlock_irqrestore(&desc->lock, flags);
196 	}
197 }
198 
199 /*  this is not called anywhere currently */
iSeries_shutdown_IRQ(unsigned int irq)200 void iSeries_shutdown_IRQ(unsigned int irq) {
201 	struct iSeries_irqEntry* entry;
202 	u32 bus, subBus, deviceId, function, mask;
203 
204 	/* irq should be locked by the caller */
205 
206 	for (entry=iSeries_irqMap[irq].head; entry; entry=entry->next) {
207 		bus = (entry->dsa >> 16) & 0xFFFF;
208 		subBus = (entry->dsa >> 8) & 0xFF;
209 		deviceId = entry->dsa & 0xFF;
210 		function = deviceId & 0x0F;
211 		/* Invalidate the IRQ number in the bridge */
212 		HvCallXm_connectBusUnit(bus, subBus, deviceId, 0);
213 		/* Mask bridge interrupts in the FISR */
214 		mask = 0x01010000 << function;
215 		HvCallPci_maskFisr(bus, subBus, deviceId, mask);
216 	}
217 
218 }
219 
220 /***********************************************************
221  * This will be called by device drivers (via disable_IRQ)
222  * to disable INTA in the bridge interrupt status register.
223  ***********************************************************/
iSeries_disable_IRQ(unsigned int irq)224 void iSeries_disable_IRQ(unsigned int irq)
225 {
226 	struct iSeries_irqEntry* entry;
227 	u32 bus, subBus, deviceId, mask;
228 
229 	/* The IRQ has already been locked by the caller */
230 
231 	for (entry=iSeries_irqMap[irq].head; entry; entry=entry->next) {
232 		bus      = (entry->dsa >> 16) & 0xFFFF;
233 		subBus   = (entry->dsa >> 8) & 0xFF;
234 		deviceId = entry->dsa & 0xFF;
235 		/* Mask secondary INTA   */
236 		mask = 0x80000000;
237 		HvCallPci_maskInterrupts(bus, subBus, deviceId, mask);
238 		PPCDBG(PPCDBG_BUSWALK,"iSeries_disable_IRQ 0x%02X.%02X.%02X 0x%04X\n",bus,subBus,deviceId,irq);
239     	}
240 }
241 
242 /***********************************************************
243  * This will be called by device drivers (via enable_IRQ)
244  * to enable INTA in the bridge interrupt status register.
245  ***********************************************************/
iSeries_enable_IRQ(unsigned int irq)246 void iSeries_enable_IRQ(unsigned int irq)
247 {
248 	struct iSeries_irqEntry* entry;
249 	u32 bus, subBus, deviceId, mask;
250 
251 	/* The IRQ has already been locked by the caller */
252 	for (entry=iSeries_irqMap[irq].head; entry; entry=entry->next) {
253 		bus      = (entry->dsa >> 16) & 0xFFFF;
254 		subBus   = (entry->dsa >> 8) & 0xFF;
255 		deviceId = entry->dsa & 0xFF;
256 		/* Unmask secondary INTA */
257 		mask = 0x80000000;
258 		HvCallPci_unmaskInterrupts(bus, subBus, deviceId, mask);
259 		PPCDBG(PPCDBG_BUSWALK,"iSeries_enable_IRQ 0x%02X.%02X.%02X 0x%04X\n",bus,subBus,deviceId,irq);
260 	}
261 }
262 
263 /* Need to define this so ppc_irq_dispatch_handler will NOT call
264    enable_IRQ at the end of interrupt handling.  However, this
265    does nothing because there is not enough information provided
266    to do the EOI HvCall.  This is done by XmPciLpEvent.c */
iSeries_end_IRQ(unsigned int irq)267 void iSeries_end_IRQ(unsigned int irq)
268 {
269 }
270 
271