diff options
Diffstat (limited to 'arch/powerpc/sysdev/xilinx_intc.c')
| -rw-r--r-- | arch/powerpc/sysdev/xilinx_intc.c | 117 |
1 files changed, 107 insertions, 10 deletions
diff --git a/arch/powerpc/sysdev/xilinx_intc.c b/arch/powerpc/sysdev/xilinx_intc.c index a22e1a2df1af..c658b413c9b4 100644 --- a/arch/powerpc/sysdev/xilinx_intc.c +++ b/arch/powerpc/sysdev/xilinx_intc.c | |||
| @@ -41,8 +41,32 @@ | |||
| 41 | 41 | ||
| 42 | static struct irq_host *master_irqhost; | 42 | static struct irq_host *master_irqhost; |
| 43 | 43 | ||
| 44 | #define XILINX_INTC_MAXIRQS (32) | ||
| 45 | |||
| 46 | /* The following table allows the interrupt type, edge or level, | ||
| 47 | * to be cached after being read from the device tree until the interrupt | ||
| 48 | * is mapped | ||
| 49 | */ | ||
| 50 | static int xilinx_intc_typetable[XILINX_INTC_MAXIRQS]; | ||
| 51 | |||
| 52 | /* Map the interrupt type from the device tree to the interrupt types | ||
| 53 | * used by the interrupt subsystem | ||
| 54 | */ | ||
| 55 | static unsigned char xilinx_intc_map_senses[] = { | ||
| 56 | IRQ_TYPE_EDGE_RISING, | ||
| 57 | IRQ_TYPE_EDGE_FALLING, | ||
| 58 | IRQ_TYPE_LEVEL_HIGH, | ||
| 59 | IRQ_TYPE_LEVEL_LOW, | ||
| 60 | }; | ||
| 61 | |||
| 44 | /* | 62 | /* |
| 45 | * IRQ Chip operations | 63 | * The interrupt controller is setup such that it doesn't work well with |
| 64 | * the level interrupt handler in the kernel because the handler acks the | ||
| 65 | * interrupt before calling the application interrupt handler. To deal with | ||
| 66 | * that, we use 2 different irq chips so that different functions can be | ||
| 67 | * used for level and edge type interrupts. | ||
| 68 | * | ||
| 69 | * IRQ Chip common (across level and edge) operations | ||
| 46 | */ | 70 | */ |
| 47 | static void xilinx_intc_mask(unsigned int virq) | 71 | static void xilinx_intc_mask(unsigned int virq) |
| 48 | { | 72 | { |
| @@ -52,15 +76,54 @@ static void xilinx_intc_mask(unsigned int virq) | |||
| 52 | out_be32(regs + XINTC_CIE, 1 << irq); | 76 | out_be32(regs + XINTC_CIE, 1 << irq); |
| 53 | } | 77 | } |
| 54 | 78 | ||
| 55 | static void xilinx_intc_unmask(unsigned int virq) | 79 | static int xilinx_intc_set_type(unsigned int virq, unsigned int flow_type) |
| 80 | { | ||
| 81 | struct irq_desc *desc = get_irq_desc(virq); | ||
| 82 | |||
| 83 | desc->status &= ~(IRQ_TYPE_SENSE_MASK | IRQ_LEVEL); | ||
| 84 | desc->status |= flow_type & IRQ_TYPE_SENSE_MASK; | ||
| 85 | if (flow_type & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) | ||
| 86 | desc->status |= IRQ_LEVEL; | ||
| 87 | return 0; | ||
| 88 | } | ||
| 89 | |||
| 90 | /* | ||
| 91 | * IRQ Chip level operations | ||
| 92 | */ | ||
| 93 | static void xilinx_intc_level_unmask(unsigned int virq) | ||
| 56 | { | 94 | { |
| 57 | int irq = virq_to_hw(virq); | 95 | int irq = virq_to_hw(virq); |
| 58 | void * regs = get_irq_chip_data(virq); | 96 | void * regs = get_irq_chip_data(virq); |
| 59 | pr_debug("unmask: %d\n", irq); | 97 | pr_debug("unmask: %d\n", irq); |
| 60 | out_be32(regs + XINTC_SIE, 1 << irq); | 98 | out_be32(regs + XINTC_SIE, 1 << irq); |
| 99 | |||
| 100 | /* ack level irqs because they can't be acked during | ||
| 101 | * ack function since the handle_level_irq function | ||
| 102 | * acks the irq before calling the inerrupt handler | ||
| 103 | */ | ||
| 104 | out_be32(regs + XINTC_IAR, 1 << irq); | ||
| 61 | } | 105 | } |
| 62 | 106 | ||
| 63 | static void xilinx_intc_ack(unsigned int virq) | 107 | static struct irq_chip xilinx_intc_level_irqchip = { |
| 108 | .typename = "Xilinx Level INTC", | ||
| 109 | .mask = xilinx_intc_mask, | ||
| 110 | .mask_ack = xilinx_intc_mask, | ||
| 111 | .unmask = xilinx_intc_level_unmask, | ||
| 112 | .set_type = xilinx_intc_set_type, | ||
| 113 | }; | ||
| 114 | |||
| 115 | /* | ||
| 116 | * IRQ Chip edge operations | ||
| 117 | */ | ||
| 118 | static void xilinx_intc_edge_unmask(unsigned int virq) | ||
| 119 | { | ||
| 120 | int irq = virq_to_hw(virq); | ||
| 121 | void *regs = get_irq_chip_data(virq); | ||
| 122 | pr_debug("unmask: %d\n", irq); | ||
| 123 | out_be32(regs + XINTC_SIE, 1 << irq); | ||
| 124 | } | ||
| 125 | |||
| 126 | static void xilinx_intc_edge_ack(unsigned int virq) | ||
| 64 | { | 127 | { |
| 65 | int irq = virq_to_hw(virq); | 128 | int irq = virq_to_hw(virq); |
| 66 | void * regs = get_irq_chip_data(virq); | 129 | void * regs = get_irq_chip_data(virq); |
| @@ -68,27 +131,60 @@ static void xilinx_intc_ack(unsigned int virq) | |||
| 68 | out_be32(regs + XINTC_IAR, 1 << irq); | 131 | out_be32(regs + XINTC_IAR, 1 << irq); |
| 69 | } | 132 | } |
| 70 | 133 | ||
| 71 | static struct irq_chip xilinx_intc_irqchip = { | 134 | static struct irq_chip xilinx_intc_edge_irqchip = { |
| 72 | .typename = "Xilinx INTC", | 135 | .typename = "Xilinx Edge INTC", |
| 73 | .mask = xilinx_intc_mask, | 136 | .mask = xilinx_intc_mask, |
| 74 | .unmask = xilinx_intc_unmask, | 137 | .unmask = xilinx_intc_edge_unmask, |
| 75 | .ack = xilinx_intc_ack, | 138 | .ack = xilinx_intc_edge_ack, |
| 139 | .set_type = xilinx_intc_set_type, | ||
| 76 | }; | 140 | }; |
| 77 | 141 | ||
| 78 | /* | 142 | /* |
| 79 | * IRQ Host operations | 143 | * IRQ Host operations |
| 80 | */ | 144 | */ |
| 145 | |||
| 146 | /** | ||
| 147 | * xilinx_intc_xlate - translate virq# from device tree interrupts property | ||
| 148 | */ | ||
| 149 | static int xilinx_intc_xlate(struct irq_host *h, struct device_node *ct, | ||
| 150 | u32 *intspec, unsigned int intsize, | ||
| 151 | irq_hw_number_t *out_hwirq, | ||
| 152 | unsigned int *out_flags) | ||
| 153 | { | ||
| 154 | if ((intsize < 2) || (intspec[0] >= XILINX_INTC_MAXIRQS)) | ||
| 155 | return -EINVAL; | ||
| 156 | |||
| 157 | /* keep a copy of the interrupt type til the interrupt is mapped | ||
| 158 | */ | ||
| 159 | xilinx_intc_typetable[intspec[0]] = xilinx_intc_map_senses[intspec[1]]; | ||
| 160 | |||
| 161 | /* Xilinx uses 2 interrupt entries, the 1st being the h/w | ||
| 162 | * interrupt number, the 2nd being the interrupt type, edge or level | ||
| 163 | */ | ||
| 164 | *out_hwirq = intspec[0]; | ||
| 165 | *out_flags = xilinx_intc_map_senses[intspec[1]]; | ||
| 166 | |||
| 167 | return 0; | ||
| 168 | } | ||
| 81 | static int xilinx_intc_map(struct irq_host *h, unsigned int virq, | 169 | static int xilinx_intc_map(struct irq_host *h, unsigned int virq, |
| 82 | irq_hw_number_t irq) | 170 | irq_hw_number_t irq) |
| 83 | { | 171 | { |
| 84 | set_irq_chip_data(virq, h->host_data); | 172 | set_irq_chip_data(virq, h->host_data); |
| 85 | set_irq_chip_and_handler(virq, &xilinx_intc_irqchip, handle_level_irq); | 173 | |
| 86 | set_irq_type(virq, IRQ_TYPE_NONE); | 174 | if (xilinx_intc_typetable[irq] == IRQ_TYPE_LEVEL_HIGH || |
| 175 | xilinx_intc_typetable[irq] == IRQ_TYPE_LEVEL_LOW) { | ||
| 176 | set_irq_chip_and_handler(virq, &xilinx_intc_level_irqchip, | ||
| 177 | handle_level_irq); | ||
| 178 | } else { | ||
| 179 | set_irq_chip_and_handler(virq, &xilinx_intc_edge_irqchip, | ||
| 180 | handle_edge_irq); | ||
| 181 | } | ||
| 87 | return 0; | 182 | return 0; |
| 88 | } | 183 | } |
| 89 | 184 | ||
| 90 | static struct irq_host_ops xilinx_intc_ops = { | 185 | static struct irq_host_ops xilinx_intc_ops = { |
| 91 | .map = xilinx_intc_map, | 186 | .map = xilinx_intc_map, |
| 187 | .xlate = xilinx_intc_xlate, | ||
| 92 | }; | 188 | }; |
| 93 | 189 | ||
| 94 | struct irq_host * __init | 190 | struct irq_host * __init |
| @@ -116,7 +212,8 @@ xilinx_intc_init(struct device_node *np) | |||
| 116 | out_be32(regs + XINTC_MER, 0x3UL); /* Turn on the Master Enable. */ | 212 | out_be32(regs + XINTC_MER, 0x3UL); /* Turn on the Master Enable. */ |
| 117 | 213 | ||
| 118 | /* Allocate and initialize an irq_host structure. */ | 214 | /* Allocate and initialize an irq_host structure. */ |
| 119 | irq = irq_alloc_host(np, IRQ_HOST_MAP_LINEAR, 32, &xilinx_intc_ops, -1); | 215 | irq = irq_alloc_host(np, IRQ_HOST_MAP_LINEAR, XILINX_INTC_MAXIRQS, |
| 216 | &xilinx_intc_ops, -1); | ||
| 120 | if (!irq) | 217 | if (!irq) |
| 121 | panic(__FILE__ ": Cannot allocate IRQ host\n"); | 218 | panic(__FILE__ ": Cannot allocate IRQ host\n"); |
| 122 | irq->host_data = regs; | 219 | irq->host_data = regs; |
