| Haavard Skinnemoen | 5f97f7f | 2006-09-25 23:32:13 -0700 | [diff] [blame] | 1 | /* | 
|  | 2 | * External interrupt handling for AT32AP CPUs | 
|  | 3 | * | 
|  | 4 | * Copyright (C) 2006 Atmel Corporation | 
|  | 5 | * | 
|  | 6 | * This program is free software; you can redistribute it and/or modify | 
|  | 7 | * it under the terms of the GNU General Public License version 2 as | 
|  | 8 | * published by the Free Software Foundation. | 
|  | 9 | */ | 
|  | 10 |  | 
|  | 11 | #include <linux/errno.h> | 
|  | 12 | #include <linux/init.h> | 
|  | 13 | #include <linux/interrupt.h> | 
|  | 14 | #include <linux/irq.h> | 
|  | 15 | #include <linux/platform_device.h> | 
|  | 16 | #include <linux/random.h> | 
|  | 17 |  | 
|  | 18 | #include <asm/io.h> | 
|  | 19 |  | 
|  | 20 | #include <asm/arch/sm.h> | 
|  | 21 |  | 
|  | 22 | #include "sm.h" | 
|  | 23 |  | 
|  | 24 | static void eim_ack_irq(unsigned int irq) | 
|  | 25 | { | 
|  | 26 | struct at32_sm *sm = get_irq_chip_data(irq); | 
|  | 27 | sm_writel(sm, EIM_ICR, 1 << (irq - sm->eim_first_irq)); | 
|  | 28 | } | 
|  | 29 |  | 
|  | 30 | static void eim_mask_irq(unsigned int irq) | 
|  | 31 | { | 
|  | 32 | struct at32_sm *sm = get_irq_chip_data(irq); | 
|  | 33 | sm_writel(sm, EIM_IDR, 1 << (irq - sm->eim_first_irq)); | 
|  | 34 | } | 
|  | 35 |  | 
|  | 36 | static void eim_mask_ack_irq(unsigned int irq) | 
|  | 37 | { | 
|  | 38 | struct at32_sm *sm = get_irq_chip_data(irq); | 
|  | 39 | sm_writel(sm, EIM_ICR, 1 << (irq - sm->eim_first_irq)); | 
|  | 40 | sm_writel(sm, EIM_IDR, 1 << (irq - sm->eim_first_irq)); | 
|  | 41 | } | 
|  | 42 |  | 
|  | 43 | static void eim_unmask_irq(unsigned int irq) | 
|  | 44 | { | 
|  | 45 | struct at32_sm *sm = get_irq_chip_data(irq); | 
|  | 46 | sm_writel(sm, EIM_IER, 1 << (irq - sm->eim_first_irq)); | 
|  | 47 | } | 
|  | 48 |  | 
|  | 49 | static int eim_set_irq_type(unsigned int irq, unsigned int flow_type) | 
|  | 50 | { | 
|  | 51 | struct at32_sm *sm = get_irq_chip_data(irq); | 
| Haavard Skinnemoen | 01cb087 | 2006-12-04 12:00:03 +0100 | [diff] [blame] | 52 | struct irq_desc *desc; | 
| Haavard Skinnemoen | 5f97f7f | 2006-09-25 23:32:13 -0700 | [diff] [blame] | 53 | unsigned int i = irq - sm->eim_first_irq; | 
|  | 54 | u32 mode, edge, level; | 
|  | 55 | unsigned long flags; | 
|  | 56 | int ret = 0; | 
|  | 57 |  | 
| David Brownell | 58febc0 | 2007-01-23 20:21:36 -0800 | [diff] [blame] | 58 | flow_type &= IRQ_TYPE_SENSE_MASK; | 
| Haavard Skinnemoen | 01cb087 | 2006-12-04 12:00:03 +0100 | [diff] [blame] | 59 | if (flow_type == IRQ_TYPE_NONE) | 
|  | 60 | flow_type = IRQ_TYPE_LEVEL_LOW; | 
|  | 61 |  | 
|  | 62 | desc = &irq_desc[irq]; | 
| Haavard Skinnemoen | 5f97f7f | 2006-09-25 23:32:13 -0700 | [diff] [blame] | 63 | spin_lock_irqsave(&sm->lock, flags); | 
|  | 64 |  | 
|  | 65 | mode = sm_readl(sm, EIM_MODE); | 
|  | 66 | edge = sm_readl(sm, EIM_EDGE); | 
|  | 67 | level = sm_readl(sm, EIM_LEVEL); | 
|  | 68 |  | 
|  | 69 | switch (flow_type) { | 
|  | 70 | case IRQ_TYPE_LEVEL_LOW: | 
|  | 71 | mode |= 1 << i; | 
|  | 72 | level &= ~(1 << i); | 
|  | 73 | break; | 
|  | 74 | case IRQ_TYPE_LEVEL_HIGH: | 
|  | 75 | mode |= 1 << i; | 
|  | 76 | level |= 1 << i; | 
|  | 77 | break; | 
|  | 78 | case IRQ_TYPE_EDGE_RISING: | 
|  | 79 | mode &= ~(1 << i); | 
|  | 80 | edge |= 1 << i; | 
|  | 81 | break; | 
|  | 82 | case IRQ_TYPE_EDGE_FALLING: | 
|  | 83 | mode &= ~(1 << i); | 
|  | 84 | edge &= ~(1 << i); | 
|  | 85 | break; | 
|  | 86 | default: | 
|  | 87 | ret = -EINVAL; | 
|  | 88 | break; | 
|  | 89 | } | 
|  | 90 |  | 
| David Brownell | 58febc0 | 2007-01-23 20:21:36 -0800 | [diff] [blame] | 91 | if (ret == 0) { | 
|  | 92 | sm_writel(sm, EIM_MODE, mode); | 
|  | 93 | sm_writel(sm, EIM_EDGE, edge); | 
|  | 94 | sm_writel(sm, EIM_LEVEL, level); | 
|  | 95 |  | 
|  | 96 | if (flow_type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)) | 
|  | 97 | flow_type |= IRQ_LEVEL; | 
|  | 98 | desc->status &= ~(IRQ_TYPE_SENSE_MASK | IRQ_LEVEL); | 
|  | 99 | desc->status |= flow_type; | 
|  | 100 | } | 
| Haavard Skinnemoen | 5f97f7f | 2006-09-25 23:32:13 -0700 | [diff] [blame] | 101 |  | 
|  | 102 | spin_unlock_irqrestore(&sm->lock, flags); | 
|  | 103 |  | 
|  | 104 | return ret; | 
|  | 105 | } | 
|  | 106 |  | 
|  | 107 | struct irq_chip eim_chip = { | 
|  | 108 | .name		= "eim", | 
|  | 109 | .ack		= eim_ack_irq, | 
|  | 110 | .mask		= eim_mask_irq, | 
|  | 111 | .mask_ack	= eim_mask_ack_irq, | 
|  | 112 | .unmask		= eim_unmask_irq, | 
|  | 113 | .set_type	= eim_set_irq_type, | 
|  | 114 | }; | 
|  | 115 |  | 
| Haavard Skinnemoen | 4e0fadf | 2006-10-11 01:20:37 -0700 | [diff] [blame] | 116 | static void demux_eim_irq(unsigned int irq, struct irq_desc *desc) | 
| Haavard Skinnemoen | 5f97f7f | 2006-09-25 23:32:13 -0700 | [diff] [blame] | 117 | { | 
|  | 118 | struct at32_sm *sm = desc->handler_data; | 
|  | 119 | struct irq_desc *ext_desc; | 
|  | 120 | unsigned long status, pending; | 
|  | 121 | unsigned int i, ext_irq; | 
|  | 122 |  | 
| Haavard Skinnemoen | 5f97f7f | 2006-09-25 23:32:13 -0700 | [diff] [blame] | 123 | status = sm_readl(sm, EIM_ISR); | 
|  | 124 | pending = status & sm_readl(sm, EIM_IMR); | 
|  | 125 |  | 
|  | 126 | while (pending) { | 
|  | 127 | i = fls(pending) - 1; | 
|  | 128 | pending &= ~(1 << i); | 
|  | 129 |  | 
|  | 130 | ext_irq = i + sm->eim_first_irq; | 
|  | 131 | ext_desc = irq_desc + ext_irq; | 
| David Brownell | 58febc0 | 2007-01-23 20:21:36 -0800 | [diff] [blame] | 132 | if (ext_desc->status & IRQ_LEVEL) | 
|  | 133 | handle_level_irq(ext_irq, ext_desc); | 
|  | 134 | else | 
|  | 135 | handle_edge_irq(ext_irq, ext_desc); | 
| Haavard Skinnemoen | 5f97f7f | 2006-09-25 23:32:13 -0700 | [diff] [blame] | 136 | } | 
| Haavard Skinnemoen | 5f97f7f | 2006-09-25 23:32:13 -0700 | [diff] [blame] | 137 | } | 
|  | 138 |  | 
|  | 139 | static int __init eim_init(void) | 
|  | 140 | { | 
|  | 141 | struct at32_sm *sm = &system_manager; | 
|  | 142 | unsigned int i; | 
|  | 143 | unsigned int nr_irqs; | 
|  | 144 | unsigned int int_irq; | 
|  | 145 | u32 pattern; | 
|  | 146 |  | 
|  | 147 | /* | 
|  | 148 | * The EIM is really the same module as SM, so register | 
|  | 149 | * mapping, etc. has been taken care of already. | 
|  | 150 | */ | 
|  | 151 |  | 
|  | 152 | /* | 
|  | 153 | * Find out how many interrupt lines that are actually | 
|  | 154 | * implemented in hardware. | 
|  | 155 | */ | 
|  | 156 | sm_writel(sm, EIM_IDR, ~0UL); | 
|  | 157 | sm_writel(sm, EIM_MODE, ~0UL); | 
|  | 158 | pattern = sm_readl(sm, EIM_MODE); | 
|  | 159 | nr_irqs = fls(pattern); | 
|  | 160 |  | 
| Haavard Skinnemoen | 01cb087 | 2006-12-04 12:00:03 +0100 | [diff] [blame] | 161 | /* Trigger on falling edge unless overridden by driver */ | 
|  | 162 | sm_writel(sm, EIM_MODE, 0UL); | 
|  | 163 | sm_writel(sm, EIM_EDGE, 0UL); | 
|  | 164 |  | 
| Haavard Skinnemoen | 5f97f7f | 2006-09-25 23:32:13 -0700 | [diff] [blame] | 165 | sm->eim_chip = &eim_chip; | 
|  | 166 |  | 
|  | 167 | for (i = 0; i < nr_irqs; i++) { | 
| David Brownell | 58febc0 | 2007-01-23 20:21:36 -0800 | [diff] [blame] | 168 | /* NOTE the handler we set here is ignored by the demux */ | 
| Haavard Skinnemoen | 01cb087 | 2006-12-04 12:00:03 +0100 | [diff] [blame] | 169 | set_irq_chip_and_handler(sm->eim_first_irq + i, &eim_chip, | 
| David Brownell | 58febc0 | 2007-01-23 20:21:36 -0800 | [diff] [blame] | 170 | handle_level_irq); | 
| Haavard Skinnemoen | 5f97f7f | 2006-09-25 23:32:13 -0700 | [diff] [blame] | 171 | set_irq_chip_data(sm->eim_first_irq + i, sm); | 
|  | 172 | } | 
|  | 173 |  | 
|  | 174 | int_irq = platform_get_irq_byname(sm->pdev, "eim"); | 
|  | 175 |  | 
|  | 176 | set_irq_chained_handler(int_irq, demux_eim_irq); | 
|  | 177 | set_irq_data(int_irq, sm); | 
|  | 178 |  | 
|  | 179 | printk("EIM: External Interrupt Module at 0x%p, IRQ %u\n", | 
|  | 180 | sm->regs, int_irq); | 
|  | 181 | printk("EIM: Handling %u external IRQs, starting with IRQ %u\n", | 
|  | 182 | nr_irqs, sm->eim_first_irq); | 
|  | 183 |  | 
|  | 184 | return 0; | 
|  | 185 | } | 
|  | 186 | arch_initcall(eim_init); |