1/*
2 * This file is subject to the terms and conditions of the GNU General Public
3 * License.  See the file "COPYING" in the main directory of this archive
4 * for more details.
5 *
6 * arch/sh64/kernel/irq_cayman.c
7 *
8 * SH-5 Cayman Interrupt Support
9 *
10 * This file handles the board specific parts of the Cayman interrupt system
11 *
12 * Copyright (C) 2002 Stuart Menefy
13 */
14
15#include <asm/irq.h>
16#include <asm/page.h>
17#include <asm/io.h>
18#include <linux/irq.h>
19#include <linux/interrupt.h>
20#include <linux/signal.h>
21#include <asm/cayman.h>
22
23unsigned long epld_virt;
24
25#define EPLD_BASE        0x04002000
26#define EPLD_STATUS_BASE (epld_virt + 0x10)
27#define EPLD_MASK_BASE   (epld_virt + 0x20)
28
29/* Note the SMSC SuperIO chip and SMSC LAN chip interrupts are all muxed onto
30   the same SH-5 interrupt */
31
32static irqreturn_t cayman_interrupt_smsc(int irq, void *dev_id)
33{
34        printk(KERN_INFO "CAYMAN: spurious SMSC interrupt\n");
35	return IRQ_NONE;
36}
37
38static irqreturn_t cayman_interrupt_pci2(int irq, void *dev_id)
39{
40        printk(KERN_INFO "CAYMAN: spurious PCI interrupt, IRQ %d\n", irq);
41	return IRQ_NONE;
42}
43
44static struct irqaction cayman_action_smsc = {
45	.name		= "Cayman SMSC Mux",
46	.handler	= cayman_interrupt_smsc,
47	.flags		= IRQF_DISABLED,
48};
49
50static struct irqaction cayman_action_pci2 = {
51	.name		= "Cayman PCI2 Mux",
52	.handler	= cayman_interrupt_pci2,
53	.flags		= IRQF_DISABLED,
54};
55
56static void enable_cayman_irq(unsigned int irq)
57{
58	unsigned long flags;
59	unsigned long mask;
60	unsigned int reg;
61	unsigned char bit;
62
63	irq -= START_EXT_IRQS;
64	reg = EPLD_MASK_BASE + ((irq / 8) << 2);
65	bit = 1<<(irq % 8);
66	local_irq_save(flags);
67	mask = ctrl_inl(reg);
68	mask |= bit;
69	ctrl_outl(mask, reg);
70	local_irq_restore(flags);
71}
72
73void disable_cayman_irq(unsigned int irq)
74{
75	unsigned long flags;
76	unsigned long mask;
77	unsigned int reg;
78	unsigned char bit;
79
80	irq -= START_EXT_IRQS;
81	reg = EPLD_MASK_BASE + ((irq / 8) << 2);
82	bit = 1<<(irq % 8);
83	local_irq_save(flags);
84	mask = ctrl_inl(reg);
85	mask &= ~bit;
86	ctrl_outl(mask, reg);
87	local_irq_restore(flags);
88}
89
90static void ack_cayman_irq(unsigned int irq)
91{
92	disable_cayman_irq(irq);
93}
94
95static void end_cayman_irq(unsigned int irq)
96{
97	if (!(irq_desc[irq].status & (IRQ_DISABLED|IRQ_INPROGRESS)))
98		enable_cayman_irq(irq);
99}
100
101static unsigned int startup_cayman_irq(unsigned int irq)
102{
103	enable_cayman_irq(irq);
104	return 0; /* never anything pending */
105}
106
107static void shutdown_cayman_irq(unsigned int irq)
108{
109	disable_cayman_irq(irq);
110}
111
112struct hw_interrupt_type cayman_irq_type = {
113	.typename	= "Cayman-IRQ",
114	.startup	= startup_cayman_irq,
115	.shutdown	= shutdown_cayman_irq,
116	.enable		= enable_cayman_irq,
117	.disable	= disable_cayman_irq,
118	.ack		= ack_cayman_irq,
119	.end		= end_cayman_irq,
120};
121
122int cayman_irq_demux(int evt)
123{
124	int irq = intc_evt_to_irq[evt];
125
126	if (irq == SMSC_IRQ) {
127		unsigned long status;
128		int i;
129
130		status = ctrl_inl(EPLD_STATUS_BASE) &
131			 ctrl_inl(EPLD_MASK_BASE) & 0xff;
132		if (status == 0) {
133			irq = -1;
134		} else {
135			for (i=0; i<8; i++) {
136				if (status & (1<<i))
137					break;
138			}
139			irq = START_EXT_IRQS + i;
140		}
141	}
142
143	if (irq == PCI2_IRQ) {
144		unsigned long status;
145		int i;
146
147		status = ctrl_inl(EPLD_STATUS_BASE + 3 * sizeof(u32)) &
148			 ctrl_inl(EPLD_MASK_BASE + 3 * sizeof(u32)) & 0xff;
149		if (status == 0) {
150			irq = -1;
151		} else {
152			for (i=0; i<8; i++) {
153				if (status & (1<<i))
154					break;
155			}
156			irq = START_EXT_IRQS + (3 * 8) + i;
157		}
158	}
159
160	return irq;
161}
162
163#if defined(CONFIG_PROC_FS) && defined(CONFIG_SYSCTL)
164int cayman_irq_describe(char* p, int irq)
165{
166	if (irq < NR_INTC_IRQS) {
167		return intc_irq_describe(p, irq);
168	} else if (irq < NR_INTC_IRQS + 8) {
169		return sprintf(p, "(SMSC %d)", irq - NR_INTC_IRQS);
170	} else if ((irq >= NR_INTC_IRQS + 24) && (irq < NR_INTC_IRQS + 32)) {
171		return sprintf(p, "(PCI2 %d)", irq - (NR_INTC_IRQS + 24));
172	}
173
174	return 0;
175}
176#endif
177
178void init_cayman_irq(void)
179{
180	int i;
181
182	epld_virt = onchip_remap(EPLD_BASE, 1024, "EPLD");
183	if (!epld_virt) {
184		printk(KERN_ERR "Cayman IRQ: Unable to remap EPLD\n");
185		return;
186	}
187
188	for (i=0; i<NR_EXT_IRQS; i++) {
189		irq_desc[START_EXT_IRQS + i].chip = &cayman_irq_type;
190	}
191
192	/* Setup the SMSC interrupt */
193	setup_irq(SMSC_IRQ, &cayman_action_smsc);
194	setup_irq(PCI2_IRQ, &cayman_action_pci2);
195}
196