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
24static 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
30static 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
36static 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
43static 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
49static int eim_set_irq_type(unsigned int irq, unsigned int flow_type)
50{
51	struct at32_sm *sm = get_irq_chip_data(irq);
52	struct irq_desc *desc;
53	unsigned int i = irq - sm->eim_first_irq;
54	u32 mode, edge, level;
55	unsigned long flags;
56	int ret = 0;
57
58	flow_type &= IRQ_TYPE_SENSE_MASK;
59	if (flow_type == IRQ_TYPE_NONE)
60		flow_type = IRQ_TYPE_LEVEL_LOW;
61
62	desc = &irq_desc[irq];
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
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	}
101
102	spin_unlock_irqrestore(&sm->lock, flags);
103
104	return ret;
105}
106
107struct 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
116static void demux_eim_irq(unsigned int irq, struct irq_desc *desc)
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
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;
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);
136	}
137}
138
139static 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
161	/* Trigger on falling edge unless overridden by driver */
162	sm_writel(sm, EIM_MODE, 0UL);
163	sm_writel(sm, EIM_EDGE, 0UL);
164
165	sm->eim_chip = &eim_chip;
166
167	for (i = 0; i < nr_irqs; i++) {
168		/* NOTE the handler we set here is ignored by the demux */
169		set_irq_chip_and_handler(sm->eim_first_irq + i, &eim_chip,
170					 handle_level_irq);
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}
186arch_initcall(eim_init);
187