| /* | 
 |  * This module supports the iSeries PCI bus interrupt handling | 
 |  * Copyright (C) 20yy  <Robert L Holtorf> <IBM Corp> | 
 |  * Copyright (C) 2004-2005 IBM Corporation | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or modify | 
 |  * it under the terms of the GNU General Public License as published by | 
 |  * the Free Software Foundation; either version 2 of the License, or | 
 |  * (at your option) any later version. | 
 |  * | 
 |  * This program is distributed in the hope that it will be useful, | 
 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 |  * GNU General Public License for more details. | 
 |  * | 
 |  * You should have received a copy of the GNU General Public License | 
 |  * along with this program; if not, write to the: | 
 |  * Free Software Foundation, Inc., | 
 |  * 59 Temple Place, Suite 330, | 
 |  * Boston, MA  02111-1307  USA | 
 |  * | 
 |  * Change Activity: | 
 |  *   Created, December 13, 2000 by Wayne Holm | 
 |  * End Change Activity | 
 |  */ | 
 | #include <linux/pci.h> | 
 | #include <linux/init.h> | 
 | #include <linux/threads.h> | 
 | #include <linux/smp.h> | 
 | #include <linux/param.h> | 
 | #include <linux/string.h> | 
 | #include <linux/bootmem.h> | 
 | #include <linux/irq.h> | 
 | #include <linux/spinlock.h> | 
 |  | 
 | #include <asm/paca.h> | 
 | #include <asm/iseries/hv_types.h> | 
 | #include <asm/iseries/hv_lp_event.h> | 
 | #include <asm/iseries/hv_call_xm.h> | 
 | #include <asm/iseries/it_lp_queue.h> | 
 |  | 
 | #include "irq.h" | 
 | #include "pci.h" | 
 | #include "call_pci.h" | 
 | #include "smp.h" | 
 |  | 
 | #ifdef CONFIG_PCI | 
 |  | 
 | enum pci_event_type { | 
 | 	pe_bus_created		= 0,	/* PHB has been created */ | 
 | 	pe_bus_error		= 1,	/* PHB has failed */ | 
 | 	pe_bus_failed		= 2,	/* Msg to Secondary, Primary failed bus */ | 
 | 	pe_node_failed		= 4,	/* Multi-adapter bridge has failed */ | 
 | 	pe_node_recovered	= 5,	/* Multi-adapter bridge has recovered */ | 
 | 	pe_bus_recovered	= 12,	/* PHB has been recovered */ | 
 | 	pe_unquiese_bus		= 18,	/* Secondary bus unqiescing */ | 
 | 	pe_bridge_error		= 21,	/* Bridge Error */ | 
 | 	pe_slot_interrupt	= 22	/* Slot interrupt */ | 
 | }; | 
 |  | 
 | struct pci_event { | 
 | 	struct HvLpEvent event; | 
 | 	union { | 
 | 		u64 __align;		/* Align on an 8-byte boundary */ | 
 | 		struct { | 
 | 			u32		fisr; | 
 | 			HvBusNumber	bus_number; | 
 | 			HvSubBusNumber	sub_bus_number; | 
 | 			HvAgentId	dev_id; | 
 | 		} slot; | 
 | 		struct { | 
 | 			HvBusNumber	bus_number; | 
 | 			HvSubBusNumber	sub_bus_number; | 
 | 		} bus; | 
 | 		struct { | 
 | 			HvBusNumber	bus_number; | 
 | 			HvSubBusNumber	sub_bus_number; | 
 | 			HvAgentId	dev_id; | 
 | 		} node; | 
 | 	} data; | 
 | }; | 
 |  | 
 | static DEFINE_SPINLOCK(pending_irqs_lock); | 
 | static int num_pending_irqs; | 
 | static int pending_irqs[NR_IRQS]; | 
 |  | 
 | static void int_received(struct pci_event *event) | 
 | { | 
 | 	int irq; | 
 |  | 
 | 	switch (event->event.xSubtype) { | 
 | 	case pe_slot_interrupt: | 
 | 		irq = event->event.xCorrelationToken; | 
 | 		if (irq < NR_IRQS) { | 
 | 			spin_lock(&pending_irqs_lock); | 
 | 			pending_irqs[irq]++; | 
 | 			num_pending_irqs++; | 
 | 			spin_unlock(&pending_irqs_lock); | 
 | 		} else { | 
 | 			printk(KERN_WARNING "int_received: bad irq number %d\n", | 
 | 					irq); | 
 | 			HvCallPci_eoi(event->data.slot.bus_number, | 
 | 					event->data.slot.sub_bus_number, | 
 | 					event->data.slot.dev_id); | 
 | 		} | 
 | 		break; | 
 | 		/* Ignore error recovery events for now */ | 
 | 	case pe_bus_created: | 
 | 		printk(KERN_INFO "int_received: system bus %d created\n", | 
 | 			event->data.bus.bus_number); | 
 | 		break; | 
 | 	case pe_bus_error: | 
 | 	case pe_bus_failed: | 
 | 		printk(KERN_INFO "int_received: system bus %d failed\n", | 
 | 			event->data.bus.bus_number); | 
 | 		break; | 
 | 	case pe_bus_recovered: | 
 | 	case pe_unquiese_bus: | 
 | 		printk(KERN_INFO "int_received: system bus %d recovered\n", | 
 | 			event->data.bus.bus_number); | 
 | 		break; | 
 | 	case pe_node_failed: | 
 | 	case pe_bridge_error: | 
 | 		printk(KERN_INFO | 
 | 			"int_received: multi-adapter bridge %d/%d/%d failed\n", | 
 | 			event->data.node.bus_number, | 
 | 			event->data.node.sub_bus_number, | 
 | 			event->data.node.dev_id); | 
 | 		break; | 
 | 	case pe_node_recovered: | 
 | 		printk(KERN_INFO | 
 | 			"int_received: multi-adapter bridge %d/%d/%d recovered\n", | 
 | 			event->data.node.bus_number, | 
 | 			event->data.node.sub_bus_number, | 
 | 			event->data.node.dev_id); | 
 | 		break; | 
 | 	default: | 
 | 		printk(KERN_ERR | 
 | 			"int_received: unrecognized event subtype 0x%x\n", | 
 | 			event->event.xSubtype); | 
 | 		break; | 
 | 	} | 
 | } | 
 |  | 
 | static void pci_event_handler(struct HvLpEvent *event) | 
 | { | 
 | 	if (event && (event->xType == HvLpEvent_Type_PciIo)) { | 
 | 		if (hvlpevent_is_int(event)) | 
 | 			int_received((struct pci_event *)event); | 
 | 		else | 
 | 			printk(KERN_ERR | 
 | 				"pci_event_handler: unexpected ack received\n"); | 
 | 	} else if (event) | 
 | 		printk(KERN_ERR | 
 | 			"pci_event_handler: Unrecognized PCI event type 0x%x\n", | 
 | 			(int)event->xType); | 
 | 	else | 
 | 		printk(KERN_ERR "pci_event_handler: NULL event received\n"); | 
 | } | 
 |  | 
 | #define REAL_IRQ_TO_SUBBUS(irq)	(((irq) >> 14) & 0xff) | 
 | #define REAL_IRQ_TO_BUS(irq)	((((irq) >> 6) & 0xff) + 1) | 
 | #define REAL_IRQ_TO_IDSEL(irq)	((((irq) >> 3) & 7) + 1) | 
 | #define REAL_IRQ_TO_FUNC(irq)	((irq) & 7) | 
 |  | 
 | /* | 
 |  * This will be called by device drivers (via enable_IRQ) | 
 |  * to enable INTA in the bridge interrupt status register. | 
 |  */ | 
 | static void iseries_enable_IRQ(struct irq_data *d) | 
 | { | 
 | 	u32 bus, dev_id, function, mask; | 
 | 	const u32 sub_bus = 0; | 
 | 	unsigned int rirq = (unsigned int)irq_map[d->irq].hwirq; | 
 |  | 
 | 	/* The IRQ has already been locked by the caller */ | 
 | 	bus = REAL_IRQ_TO_BUS(rirq); | 
 | 	function = REAL_IRQ_TO_FUNC(rirq); | 
 | 	dev_id = (REAL_IRQ_TO_IDSEL(rirq) << 4) + function; | 
 |  | 
 | 	/* Unmask secondary INTA */ | 
 | 	mask = 0x80000000; | 
 | 	HvCallPci_unmaskInterrupts(bus, sub_bus, dev_id, mask); | 
 | } | 
 |  | 
 | /* This is called by iseries_activate_IRQs */ | 
 | static unsigned int iseries_startup_IRQ(struct irq_data *d) | 
 | { | 
 | 	u32 bus, dev_id, function, mask; | 
 | 	const u32 sub_bus = 0; | 
 | 	unsigned int rirq = (unsigned int)irq_map[d->irq].hwirq; | 
 |  | 
 | 	bus = REAL_IRQ_TO_BUS(rirq); | 
 | 	function = REAL_IRQ_TO_FUNC(rirq); | 
 | 	dev_id = (REAL_IRQ_TO_IDSEL(rirq) << 4) + function; | 
 |  | 
 | 	/* Link the IRQ number to the bridge */ | 
 | 	HvCallXm_connectBusUnit(bus, sub_bus, dev_id, d->irq); | 
 |  | 
 | 	/* Unmask bridge interrupts in the FISR */ | 
 | 	mask = 0x01010000 << function; | 
 | 	HvCallPci_unmaskFisr(bus, sub_bus, dev_id, mask); | 
 | 	iseries_enable_IRQ(d); | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* | 
 |  * This is called out of iSeries_fixup to activate interrupt | 
 |  * generation for usable slots | 
 |  */ | 
 | void __init iSeries_activate_IRQs() | 
 | { | 
 | 	int irq; | 
 | 	unsigned long flags; | 
 |  | 
 | 	for_each_irq (irq) { | 
 | 		struct irq_desc *desc = irq_to_desc(irq); | 
 | 		struct irq_chip *chip; | 
 |  | 
 | 		if (!desc) | 
 | 			continue; | 
 |  | 
 | 		chip = irq_desc_get_chip(desc); | 
 | 		if (chip && chip->irq_startup) { | 
 | 			raw_spin_lock_irqsave(&desc->lock, flags); | 
 | 			chip->irq_startup(&desc->irq_data); | 
 | 			raw_spin_unlock_irqrestore(&desc->lock, flags); | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | /*  this is not called anywhere currently */ | 
 | static void iseries_shutdown_IRQ(struct irq_data *d) | 
 | { | 
 | 	u32 bus, dev_id, function, mask; | 
 | 	const u32 sub_bus = 0; | 
 | 	unsigned int rirq = (unsigned int)irq_map[d->irq].hwirq; | 
 |  | 
 | 	/* irq should be locked by the caller */ | 
 | 	bus = REAL_IRQ_TO_BUS(rirq); | 
 | 	function = REAL_IRQ_TO_FUNC(rirq); | 
 | 	dev_id = (REAL_IRQ_TO_IDSEL(rirq) << 4) + function; | 
 |  | 
 | 	/* Invalidate the IRQ number in the bridge */ | 
 | 	HvCallXm_connectBusUnit(bus, sub_bus, dev_id, 0); | 
 |  | 
 | 	/* Mask bridge interrupts in the FISR */ | 
 | 	mask = 0x01010000 << function; | 
 | 	HvCallPci_maskFisr(bus, sub_bus, dev_id, mask); | 
 | } | 
 |  | 
 | /* | 
 |  * This will be called by device drivers (via disable_IRQ) | 
 |  * to disable INTA in the bridge interrupt status register. | 
 |  */ | 
 | static void iseries_disable_IRQ(struct irq_data *d) | 
 | { | 
 | 	u32 bus, dev_id, function, mask; | 
 | 	const u32 sub_bus = 0; | 
 | 	unsigned int rirq = (unsigned int)irq_map[d->irq].hwirq; | 
 |  | 
 | 	/* The IRQ has already been locked by the caller */ | 
 | 	bus = REAL_IRQ_TO_BUS(rirq); | 
 | 	function = REAL_IRQ_TO_FUNC(rirq); | 
 | 	dev_id = (REAL_IRQ_TO_IDSEL(rirq) << 4) + function; | 
 |  | 
 | 	/* Mask secondary INTA   */ | 
 | 	mask = 0x80000000; | 
 | 	HvCallPci_maskInterrupts(bus, sub_bus, dev_id, mask); | 
 | } | 
 |  | 
 | static void iseries_end_IRQ(struct irq_data *d) | 
 | { | 
 | 	unsigned int rirq = (unsigned int)irq_map[d->irq].hwirq; | 
 |  | 
 | 	HvCallPci_eoi(REAL_IRQ_TO_BUS(rirq), REAL_IRQ_TO_SUBBUS(rirq), | 
 | 		(REAL_IRQ_TO_IDSEL(rirq) << 4) + REAL_IRQ_TO_FUNC(rirq)); | 
 | } | 
 |  | 
 | static struct irq_chip iseries_pic = { | 
 | 	.name		= "iSeries", | 
 | 	.irq_startup	= iseries_startup_IRQ, | 
 | 	.irq_shutdown	= iseries_shutdown_IRQ, | 
 | 	.irq_unmask	= iseries_enable_IRQ, | 
 | 	.irq_mask	= iseries_disable_IRQ, | 
 | 	.irq_eoi	= iseries_end_IRQ | 
 | }; | 
 |  | 
 | /* | 
 |  * This is called out of iSeries_scan_slot to allocate an IRQ for an EADS slot | 
 |  * It calculates the irq value for the slot. | 
 |  * Note that sub_bus is always 0 (at the moment at least). | 
 |  */ | 
 | int __init iSeries_allocate_IRQ(HvBusNumber bus, | 
 | 		HvSubBusNumber sub_bus, u32 bsubbus) | 
 | { | 
 | 	unsigned int realirq; | 
 | 	u8 idsel = ISERIES_GET_DEVICE_FROM_SUBBUS(bsubbus); | 
 | 	u8 function = ISERIES_GET_FUNCTION_FROM_SUBBUS(bsubbus); | 
 |  | 
 | 	realirq = (((((sub_bus << 8) + (bus - 1)) << 3) + (idsel - 1)) << 3) | 
 | 		+ function; | 
 |  | 
 | 	return irq_create_mapping(NULL, realirq); | 
 | } | 
 |  | 
 | #endif /* CONFIG_PCI */ | 
 |  | 
 | /* | 
 |  * Get the next pending IRQ. | 
 |  */ | 
 | unsigned int iSeries_get_irq(void) | 
 | { | 
 | 	int irq = NO_IRQ_IGNORE; | 
 |  | 
 | #ifdef CONFIG_SMP | 
 | 	if (get_lppaca()->int_dword.fields.ipi_cnt) { | 
 | 		get_lppaca()->int_dword.fields.ipi_cnt = 0; | 
 | 		iSeries_smp_message_recv(); | 
 | 	} | 
 | #endif /* CONFIG_SMP */ | 
 | 	if (hvlpevent_is_pending()) | 
 | 		process_hvlpevents(); | 
 |  | 
 | #ifdef CONFIG_PCI | 
 | 	if (num_pending_irqs) { | 
 | 		spin_lock(&pending_irqs_lock); | 
 | 		for (irq = 0; irq < NR_IRQS; irq++) { | 
 | 			if (pending_irqs[irq]) { | 
 | 				pending_irqs[irq]--; | 
 | 				num_pending_irqs--; | 
 | 				break; | 
 | 			} | 
 | 		} | 
 | 		spin_unlock(&pending_irqs_lock); | 
 | 		if (irq >= NR_IRQS) | 
 | 			irq = NO_IRQ_IGNORE; | 
 | 	} | 
 | #endif | 
 |  | 
 | 	return irq; | 
 | } | 
 |  | 
 | #ifdef CONFIG_PCI | 
 |  | 
 | static int iseries_irq_host_map(struct irq_host *h, unsigned int virq, | 
 | 				irq_hw_number_t hw) | 
 | { | 
 | 	irq_set_chip_and_handler(virq, &iseries_pic, handle_fasteoi_irq); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int iseries_irq_host_match(struct irq_host *h, struct device_node *np) | 
 | { | 
 | 	/* Match all */ | 
 | 	return 1; | 
 | } | 
 |  | 
 | static struct irq_host_ops iseries_irq_host_ops = { | 
 | 	.map = iseries_irq_host_map, | 
 | 	.match = iseries_irq_host_match, | 
 | }; | 
 |  | 
 | /* | 
 |  * This is called by init_IRQ.  set in ppc_md.init_IRQ by iSeries_setup.c | 
 |  * It must be called before the bus walk. | 
 |  */ | 
 | void __init iSeries_init_IRQ(void) | 
 | { | 
 | 	/* Register PCI event handler and open an event path */ | 
 | 	struct irq_host *host; | 
 | 	int ret; | 
 |  | 
 | 	/* | 
 | 	 * The Hypervisor only allows us up to 256 interrupt | 
 | 	 * sources (the irq number is passed in a u8). | 
 | 	 */ | 
 | 	irq_set_virq_count(256); | 
 |  | 
 | 	/* Create irq host. No need for a revmap since HV will give us | 
 | 	 * back our virtual irq number | 
 | 	 */ | 
 | 	host = irq_alloc_host(NULL, IRQ_HOST_MAP_NOMAP, 0, | 
 | 			      &iseries_irq_host_ops, 0); | 
 | 	BUG_ON(host == NULL); | 
 | 	irq_set_default_host(host); | 
 |  | 
 | 	ret = HvLpEvent_registerHandler(HvLpEvent_Type_PciIo, | 
 | 			&pci_event_handler); | 
 | 	if (ret == 0) { | 
 | 		ret = HvLpEvent_openPath(HvLpEvent_Type_PciIo, 0); | 
 | 		if (ret != 0) | 
 | 			printk(KERN_ERR "iseries_init_IRQ: open event path " | 
 | 					"failed with rc 0x%x\n", ret); | 
 | 	} else | 
 | 		printk(KERN_ERR "iseries_init_IRQ: register handler " | 
 | 				"failed with rc 0x%x\n", ret); | 
 | } | 
 |  | 
 | #endif	/* CONFIG_PCI */ |