| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* | 
|  | 2 | * Interrupt management for most GSC and related devices. | 
|  | 3 | * | 
|  | 4 | * (c) Copyright 1999 Alex deVries for The Puffin Group | 
|  | 5 | * (c) Copyright 1999 Grant Grundler for Hewlett-Packard | 
|  | 6 | * (c) Copyright 1999 Matthew Wilcox | 
|  | 7 | * (c) Copyright 2000 Helge Deller | 
|  | 8 | * (c) Copyright 2001 Matthew Wilcox for Hewlett-Packard | 
|  | 9 | * | 
|  | 10 | *	This program is free software; you can redistribute it and/or modify | 
|  | 11 | *	it under the terms of the GNU General Public License as published by | 
|  | 12 | *      the Free Software Foundation; either version 2 of the License, or | 
|  | 13 | *      (at your option) any later version. | 
|  | 14 | */ | 
|  | 15 |  | 
|  | 16 | #include <linux/bitops.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 17 | #include <linux/errno.h> | 
|  | 18 | #include <linux/init.h> | 
|  | 19 | #include <linux/interrupt.h> | 
|  | 20 | #include <linux/ioport.h> | 
|  | 21 | #include <linux/module.h> | 
|  | 22 | #include <linux/slab.h> | 
|  | 23 | #include <linux/types.h> | 
|  | 24 |  | 
|  | 25 | #include <asm/hardware.h> | 
|  | 26 | #include <asm/io.h> | 
|  | 27 |  | 
|  | 28 | #include "gsc.h" | 
|  | 29 |  | 
|  | 30 | #undef DEBUG | 
|  | 31 |  | 
|  | 32 | #ifdef DEBUG | 
|  | 33 | #define DEBPRINTK printk | 
|  | 34 | #else | 
|  | 35 | #define DEBPRINTK(x,...) | 
|  | 36 | #endif | 
|  | 37 |  | 
|  | 38 | int gsc_alloc_irq(struct gsc_irq *i) | 
|  | 39 | { | 
|  | 40 | int irq = txn_alloc_irq(GSC_EIM_WIDTH); | 
|  | 41 | if (irq < 0) { | 
|  | 42 | printk("cannot get irq\n"); | 
|  | 43 | return irq; | 
|  | 44 | } | 
|  | 45 |  | 
|  | 46 | i->txn_addr = txn_alloc_addr(irq); | 
|  | 47 | i->txn_data = txn_alloc_data(irq); | 
|  | 48 | i->irq = irq; | 
|  | 49 |  | 
|  | 50 | return irq; | 
|  | 51 | } | 
|  | 52 |  | 
|  | 53 | int gsc_claim_irq(struct gsc_irq *i, int irq) | 
|  | 54 | { | 
|  | 55 | int c = irq; | 
|  | 56 |  | 
|  | 57 | irq += CPU_IRQ_BASE; /* virtualize the IRQ first */ | 
|  | 58 |  | 
|  | 59 | irq = txn_claim_irq(irq); | 
|  | 60 | if (irq < 0) { | 
|  | 61 | printk("cannot claim irq %d\n", c); | 
|  | 62 | return irq; | 
|  | 63 | } | 
|  | 64 |  | 
|  | 65 | i->txn_addr = txn_alloc_addr(irq); | 
|  | 66 | i->txn_data = txn_alloc_data(irq); | 
|  | 67 | i->irq = irq; | 
|  | 68 |  | 
|  | 69 | return irq; | 
|  | 70 | } | 
|  | 71 |  | 
|  | 72 | EXPORT_SYMBOL(gsc_alloc_irq); | 
|  | 73 | EXPORT_SYMBOL(gsc_claim_irq); | 
|  | 74 |  | 
|  | 75 | /* Common interrupt demultiplexer used by Asp, Lasi & Wax.  */ | 
| David Howells | 7d12e78 | 2006-10-05 14:55:46 +0100 | [diff] [blame] | 76 | irqreturn_t gsc_asic_intr(int gsc_asic_irq, void *dev) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 77 | { | 
|  | 78 | unsigned long irr; | 
|  | 79 | struct gsc_asic *gsc_asic = dev; | 
|  | 80 |  | 
|  | 81 | irr = gsc_readl(gsc_asic->hpa + OFFSET_IRR); | 
|  | 82 | if (irr == 0) | 
|  | 83 | return IRQ_NONE; | 
|  | 84 |  | 
|  | 85 | DEBPRINTK("%s intr, mask=0x%x\n", gsc_asic->name, irr); | 
|  | 86 |  | 
|  | 87 | do { | 
|  | 88 | int local_irq = __ffs(irr); | 
|  | 89 | unsigned int irq = gsc_asic->global_irq[local_irq]; | 
| David Howells | 7d12e78 | 2006-10-05 14:55:46 +0100 | [diff] [blame] | 90 | __do_IRQ(irq); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 91 | irr &= ~(1 << local_irq); | 
|  | 92 | } while (irr); | 
|  | 93 |  | 
|  | 94 | return IRQ_HANDLED; | 
|  | 95 | } | 
|  | 96 |  | 
|  | 97 | int gsc_find_local_irq(unsigned int irq, int *global_irqs, int limit) | 
|  | 98 | { | 
|  | 99 | int local_irq; | 
|  | 100 |  | 
|  | 101 | for (local_irq = 0; local_irq < limit; local_irq++) { | 
|  | 102 | if (global_irqs[local_irq] == irq) | 
|  | 103 | return local_irq; | 
|  | 104 | } | 
|  | 105 |  | 
|  | 106 | return NO_IRQ; | 
|  | 107 | } | 
|  | 108 |  | 
|  | 109 | static void gsc_asic_disable_irq(unsigned int irq) | 
|  | 110 | { | 
| Ingo Molnar | d1bef4e | 2006-06-29 02:24:36 -0700 | [diff] [blame] | 111 | struct gsc_asic *irq_dev = irq_desc[irq].chip_data; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 112 | int local_irq = gsc_find_local_irq(irq, irq_dev->global_irq, 32); | 
|  | 113 | u32 imr; | 
|  | 114 |  | 
|  | 115 | DEBPRINTK(KERN_DEBUG "%s(%d) %s: IMR 0x%x\n", __FUNCTION__, irq, | 
|  | 116 | irq_dev->name, imr); | 
|  | 117 |  | 
|  | 118 | /* Disable the IRQ line by clearing the bit in the IMR */ | 
|  | 119 | imr = gsc_readl(irq_dev->hpa + OFFSET_IMR); | 
|  | 120 | imr &= ~(1 << local_irq); | 
|  | 121 | gsc_writel(imr, irq_dev->hpa + OFFSET_IMR); | 
|  | 122 | } | 
|  | 123 |  | 
|  | 124 | static void gsc_asic_enable_irq(unsigned int irq) | 
|  | 125 | { | 
| Ingo Molnar | d1bef4e | 2006-06-29 02:24:36 -0700 | [diff] [blame] | 126 | struct gsc_asic *irq_dev = irq_desc[irq].chip_data; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 127 | int local_irq = gsc_find_local_irq(irq, irq_dev->global_irq, 32); | 
|  | 128 | u32 imr; | 
|  | 129 |  | 
|  | 130 | DEBPRINTK(KERN_DEBUG "%s(%d) %s: IMR 0x%x\n", __FUNCTION__, irq, | 
|  | 131 | irq_dev->name, imr); | 
|  | 132 |  | 
|  | 133 | /* Enable the IRQ line by setting the bit in the IMR */ | 
|  | 134 | imr = gsc_readl(irq_dev->hpa + OFFSET_IMR); | 
|  | 135 | imr |= 1 << local_irq; | 
|  | 136 | gsc_writel(imr, irq_dev->hpa + OFFSET_IMR); | 
|  | 137 | /* | 
|  | 138 | * FIXME: read IPR to make sure the IRQ isn't already pending. | 
|  | 139 | *   If so, we need to read IRR and manually call do_irq(). | 
|  | 140 | */ | 
|  | 141 | } | 
|  | 142 |  | 
|  | 143 | static unsigned int gsc_asic_startup_irq(unsigned int irq) | 
|  | 144 | { | 
|  | 145 | gsc_asic_enable_irq(irq); | 
|  | 146 | return 0; | 
|  | 147 | } | 
|  | 148 |  | 
|  | 149 | static struct hw_interrupt_type gsc_asic_interrupt_type = { | 
|  | 150 | .typename =	"GSC-ASIC", | 
|  | 151 | .startup =	gsc_asic_startup_irq, | 
|  | 152 | .shutdown =	gsc_asic_disable_irq, | 
|  | 153 | .enable =	gsc_asic_enable_irq, | 
|  | 154 | .disable =	gsc_asic_disable_irq, | 
|  | 155 | .ack =		no_ack_irq, | 
|  | 156 | .end =		no_end_irq, | 
|  | 157 | }; | 
|  | 158 |  | 
|  | 159 | int gsc_assign_irq(struct hw_interrupt_type *type, void *data) | 
|  | 160 | { | 
|  | 161 | static int irq = GSC_IRQ_BASE; | 
|  | 162 |  | 
|  | 163 | if (irq > GSC_IRQ_MAX) | 
|  | 164 | return NO_IRQ; | 
|  | 165 |  | 
| Ingo Molnar | d1bef4e | 2006-06-29 02:24:36 -0700 | [diff] [blame] | 166 | irq_desc[irq].chip = type; | 
|  | 167 | irq_desc[irq].chip_data = data; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 168 | return irq++; | 
|  | 169 | } | 
|  | 170 |  | 
|  | 171 | void gsc_asic_assign_irq(struct gsc_asic *asic, int local_irq, int *irqp) | 
|  | 172 | { | 
|  | 173 | int irq = asic->global_irq[local_irq]; | 
|  | 174 |  | 
|  | 175 | if (irq <= 0) { | 
|  | 176 | irq = gsc_assign_irq(&gsc_asic_interrupt_type, asic); | 
|  | 177 | if (irq == NO_IRQ) | 
|  | 178 | return; | 
|  | 179 |  | 
|  | 180 | asic->global_irq[local_irq] = irq; | 
|  | 181 | } | 
|  | 182 | *irqp = irq; | 
|  | 183 | } | 
|  | 184 |  | 
| Matthew Wilcox | 5658374 | 2005-10-21 22:33:38 -0400 | [diff] [blame] | 185 | static struct device *next_device(struct klist_iter *i) | 
|  | 186 | { | 
|  | 187 | struct klist_node * n = klist_next(i); | 
|  | 188 | return n ? container_of(n, struct device, knode_parent) : NULL; | 
|  | 189 | } | 
|  | 190 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 191 | void gsc_fixup_irqs(struct parisc_device *parent, void *ctrl, | 
|  | 192 | void (*choose_irq)(struct parisc_device *, void *)) | 
|  | 193 | { | 
|  | 194 | struct device *dev; | 
| Matthew Wilcox | 5658374 | 2005-10-21 22:33:38 -0400 | [diff] [blame] | 195 | struct klist_iter i; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 196 |  | 
| Matthew Wilcox | 5658374 | 2005-10-21 22:33:38 -0400 | [diff] [blame] | 197 | klist_iter_init(&parent->dev.klist_children, &i); | 
|  | 198 | while ((dev = next_device(&i))) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 199 | struct parisc_device *padev = to_parisc_device(dev); | 
|  | 200 |  | 
|  | 201 | /* work-around for 715/64 and others which have parent | 
|  | 202 | at path [5] and children at path [5/0/x] */ | 
|  | 203 | if (padev->id.hw_type == HPHW_FAULTY) | 
|  | 204 | return gsc_fixup_irqs(padev, ctrl, choose_irq); | 
|  | 205 | choose_irq(padev, ctrl); | 
|  | 206 | } | 
| Matthew Wilcox | 5658374 | 2005-10-21 22:33:38 -0400 | [diff] [blame] | 207 | klist_iter_exit(&i); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 208 | } | 
|  | 209 |  | 
|  | 210 | int gsc_common_setup(struct parisc_device *parent, struct gsc_asic *gsc_asic) | 
|  | 211 | { | 
|  | 212 | struct resource *res; | 
|  | 213 | int i; | 
|  | 214 |  | 
|  | 215 | gsc_asic->gsc = parent; | 
|  | 216 |  | 
|  | 217 | /* Initialise local irq -> global irq mapping */ | 
|  | 218 | for (i = 0; i < 32; i++) { | 
|  | 219 | gsc_asic->global_irq[i] = NO_IRQ; | 
|  | 220 | } | 
|  | 221 |  | 
|  | 222 | /* allocate resource region */ | 
|  | 223 | res = request_mem_region(gsc_asic->hpa, 0x100000, gsc_asic->name); | 
|  | 224 | if (res) { | 
|  | 225 | res->flags = IORESOURCE_MEM; 	/* do not mark it busy ! */ | 
|  | 226 | } | 
|  | 227 |  | 
|  | 228 | #if 0 | 
|  | 229 | printk(KERN_WARNING "%s IRQ %d EIM 0x%x", gsc_asic->name, | 
|  | 230 | parent->irq, gsc_asic->eim); | 
|  | 231 | if (gsc_readl(gsc_asic->hpa + OFFSET_IMR)) | 
|  | 232 | printk("  IMR is non-zero! (0x%x)", | 
|  | 233 | gsc_readl(gsc_asic->hpa + OFFSET_IMR)); | 
|  | 234 | printk("\n"); | 
|  | 235 | #endif | 
|  | 236 |  | 
|  | 237 | return 0; | 
|  | 238 | } | 
|  | 239 |  | 
|  | 240 | extern struct parisc_driver lasi_driver; | 
|  | 241 | extern struct parisc_driver asp_driver; | 
|  | 242 | extern struct parisc_driver wax_driver; | 
|  | 243 |  | 
|  | 244 | void __init gsc_init(void) | 
|  | 245 | { | 
|  | 246 | #ifdef CONFIG_GSC_LASI | 
|  | 247 | register_parisc_driver(&lasi_driver); | 
|  | 248 | register_parisc_driver(&asp_driver); | 
|  | 249 | #endif | 
|  | 250 | #ifdef CONFIG_GSC_WAX | 
|  | 251 | register_parisc_driver(&wax_driver); | 
|  | 252 | #endif | 
|  | 253 | } |