1 | /* |
2 | * linux/arch/m68k/amiga/cia.c - CIA support |
3 | * |
4 | * Copyright (C) 1996 Roman Zippel |
5 | * |
6 | * The concept of some functions bases on the original Amiga OS function |
7 | * |
8 | * This file is subject to the terms and conditions of the GNU General Public |
9 | * License. See the file COPYING in the main directory of this archive |
10 | * for more details. |
11 | */ |
12 | |
13 | #include <linux/types.h> |
14 | #include <linux/kernel.h> |
15 | #include <linux/sched.h> |
16 | #include <linux/errno.h> |
17 | #include <linux/kernel_stat.h> |
18 | #include <linux/init.h> |
19 | #include <linux/seq_file.h> |
20 | #include <linux/interrupt.h> |
21 | #include <linux/irq.h> |
22 | |
23 | #include <asm/irq.h> |
24 | #include <asm/amigahw.h> |
25 | #include <asm/amigaints.h> |
26 | |
27 | struct ciabase { |
28 | volatile struct CIA *cia; |
29 | unsigned char icr_mask, icr_data; |
30 | unsigned short int_mask; |
31 | int handler_irq, cia_irq, server_irq; |
32 | char *name; |
33 | } ciaa_base = { |
34 | .cia = &ciaa, |
35 | .int_mask = IF_PORTS, |
36 | .handler_irq = IRQ_AMIGA_PORTS, |
37 | .cia_irq = IRQ_AMIGA_CIAA, |
38 | .name = "CIAA" |
39 | }, ciab_base = { |
40 | .cia = &ciab, |
41 | .int_mask = IF_EXTER, |
42 | .handler_irq = IRQ_AMIGA_EXTER, |
43 | .cia_irq = IRQ_AMIGA_CIAB, |
44 | .name = "CIAB" |
45 | }; |
46 | |
47 | /* |
48 | * Cause or clear CIA interrupts, return old interrupt status. |
49 | */ |
50 | |
51 | unsigned char cia_set_irq(struct ciabase *base, unsigned char mask) |
52 | { |
53 | unsigned char old; |
54 | |
55 | old = (base->icr_data |= base->cia->icr); |
56 | if (mask & CIA_ICR_SETCLR) |
57 | base->icr_data |= mask; |
58 | else |
59 | base->icr_data &= ~mask; |
60 | if (base->icr_data & base->icr_mask) |
61 | amiga_custom.intreq = IF_SETCLR | base->int_mask; |
62 | return old & base->icr_mask; |
63 | } |
64 | |
65 | /* |
66 | * Enable or disable CIA interrupts, return old interrupt mask, |
67 | */ |
68 | |
69 | unsigned char cia_able_irq(struct ciabase *base, unsigned char mask) |
70 | { |
71 | unsigned char old; |
72 | |
73 | old = base->icr_mask; |
74 | base->icr_data |= base->cia->icr; |
75 | base->cia->icr = mask; |
76 | if (mask & CIA_ICR_SETCLR) |
77 | base->icr_mask |= mask; |
78 | else |
79 | base->icr_mask &= ~mask; |
80 | base->icr_mask &= CIA_ICR_ALL; |
81 | if (base->icr_data & base->icr_mask) |
82 | amiga_custom.intreq = IF_SETCLR | base->int_mask; |
83 | return old; |
84 | } |
85 | |
86 | static irqreturn_t cia_handler(int irq, void *dev_id) |
87 | { |
88 | struct ciabase *base = dev_id; |
89 | int mach_irq; |
90 | unsigned char ints; |
91 | unsigned long flags; |
92 | |
93 | /* Interrupts get disabled while the timer irq flag is cleared and |
94 | * the timer interrupt serviced. |
95 | */ |
96 | mach_irq = base->cia_irq; |
97 | local_irq_save(flags); |
98 | ints = cia_set_irq(base, mask: CIA_ICR_ALL); |
99 | amiga_custom.intreq = base->int_mask; |
100 | if (ints & 1) |
101 | generic_handle_irq(irq: mach_irq); |
102 | local_irq_restore(flags); |
103 | mach_irq++, ints >>= 1; |
104 | for (; ints; mach_irq++, ints >>= 1) { |
105 | if (ints & 1) |
106 | generic_handle_irq(irq: mach_irq); |
107 | } |
108 | return IRQ_HANDLED; |
109 | } |
110 | |
111 | static void cia_irq_enable(struct irq_data *data) |
112 | { |
113 | unsigned int irq = data->irq; |
114 | unsigned char mask; |
115 | |
116 | if (irq >= IRQ_AMIGA_CIAB) { |
117 | mask = 1 << (irq - IRQ_AMIGA_CIAB); |
118 | cia_set_irq(base: &ciab_base, mask); |
119 | cia_able_irq(base: &ciab_base, mask: CIA_ICR_SETCLR | mask); |
120 | } else { |
121 | mask = 1 << (irq - IRQ_AMIGA_CIAA); |
122 | cia_set_irq(base: &ciaa_base, mask); |
123 | cia_able_irq(base: &ciaa_base, mask: CIA_ICR_SETCLR | mask); |
124 | } |
125 | } |
126 | |
127 | static void cia_irq_disable(struct irq_data *data) |
128 | { |
129 | unsigned int irq = data->irq; |
130 | |
131 | if (irq >= IRQ_AMIGA_CIAB) |
132 | cia_able_irq(&ciab_base, 1 << (irq - IRQ_AMIGA_CIAB)); |
133 | else |
134 | cia_able_irq(&ciaa_base, 1 << (irq - IRQ_AMIGA_CIAA)); |
135 | } |
136 | |
137 | static struct irq_chip cia_irq_chip = { |
138 | .name = "cia" , |
139 | .irq_enable = cia_irq_enable, |
140 | .irq_disable = cia_irq_disable, |
141 | }; |
142 | |
143 | /* |
144 | * Override auto irq 2 & 6 and use them as general chain |
145 | * for external interrupts, we link the CIA interrupt sources |
146 | * into this chain. |
147 | */ |
148 | |
149 | static void auto_irq_enable(struct irq_data *data) |
150 | { |
151 | switch (data->irq) { |
152 | case IRQ_AUTO_2: |
153 | amiga_custom.intena = IF_SETCLR | IF_PORTS; |
154 | break; |
155 | case IRQ_AUTO_6: |
156 | amiga_custom.intena = IF_SETCLR | IF_EXTER; |
157 | break; |
158 | } |
159 | } |
160 | |
161 | static void auto_irq_disable(struct irq_data *data) |
162 | { |
163 | switch (data->irq) { |
164 | case IRQ_AUTO_2: |
165 | amiga_custom.intena = IF_PORTS; |
166 | break; |
167 | case IRQ_AUTO_6: |
168 | amiga_custom.intena = IF_EXTER; |
169 | break; |
170 | } |
171 | } |
172 | |
173 | static struct irq_chip auto_irq_chip = { |
174 | .name = "auto" , |
175 | .irq_enable = auto_irq_enable, |
176 | .irq_disable = auto_irq_disable, |
177 | }; |
178 | |
179 | void __init cia_init_IRQ(struct ciabase *base) |
180 | { |
181 | m68k_setup_irq_controller(&cia_irq_chip, handle_simple_irq, |
182 | base->cia_irq, CIA_IRQS); |
183 | |
184 | /* clear any pending interrupt and turn off all interrupts */ |
185 | cia_set_irq(base, CIA_ICR_ALL); |
186 | cia_able_irq(base, CIA_ICR_ALL); |
187 | |
188 | /* override auto int and install CIA handler */ |
189 | m68k_setup_irq_controller(&auto_irq_chip, handle_simple_irq, |
190 | base->handler_irq, 1); |
191 | m68k_irq_startup_irq(base->handler_irq); |
192 | if (request_irq(irq: base->handler_irq, handler: cia_handler, IRQF_SHARED, |
193 | name: base->name, dev: base)) |
194 | pr_err("Couldn't register %s interrupt\n" , base->name); |
195 | } |
196 | |