summaryrefslogtreecommitdiffstats
path: root/drivers/irqchip/irq-vt8500.c
blob: b7af816f276933c914c89e91e8f3305d47bb0e48 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
/*
 *  arch/arm/mach-vt8500/irq.c
 *
 *  Copyright (C) 2012 Tony Prisk <linux@prisktech.co.nz>
 *  Copyright (C) 2010 Alexey Charkov <alchark@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * This file is copied and modified from the original irq.c provided by
 * Alexey Charkov. Minor changes have been made for Device Tree Support.
 */

#include <linux/slab.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/interrupt.h>
#include <linux/bitops.h>

#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>

#include <asm/irq.h>
#include <asm/exception.h>
#include <asm/mach/irq.h>

#include "irqchip.h"

#define VT8500_ICPC_IRQ		0x20
#define VT8500_ICPC_FIQ		0x24
#define VT8500_ICDC		0x40		/* Destination Control 64*u32 */
#define VT8500_ICIS		0x80		/* Interrupt status, 16*u32 */

/* ICPC */
#define ICPC_MASK		0x3F
#define ICPC_ROTATE		BIT(6)

/* IC_DCTR */
#define ICDC_IRQ		0x00
#define ICDC_FIQ		0x01
#define ICDC_DSS0		0x02
#define ICDC_DSS1		0x03
#define ICDC_DSS2		0x04
#define ICDC_DSS3		0x05
#define ICDC_DSS4		0x06
#define ICDC_DSS5		0x07

#define VT8500_INT_DISABLE	0
#define VT8500_INT_ENABLE	BIT(3)

#define VT8500_TRIGGER_HIGH	0
#define VT8500_TRIGGER_RISING	BIT(5)
#define VT8500_TRIGGER_FALLING	BIT(6)
#define VT8500_EDGE		( VT8500_TRIGGER_RISING \
				| VT8500_TRIGGER_FALLING)

/* vt8500 has 1 intc, wm8505 and wm8650 have 2 */
#define VT8500_INTC_MAX		2

struct vt8500_irq_data {
	void __iomem 		*base;		/* IO Memory base address */
	struct irq_domain	*domain;	/* Domain for this controller */
};

/* Global variable for accessing io-mem addresses */
static struct vt8500_irq_data intc[VT8500_INTC_MAX];
static u32 active_cnt = 0;

static void vt8500_irq_mask(struct irq_data *d)
{
	struct vt8500_irq_data *priv = d->domain->host_data;
	void __iomem *base = priv->base;
	void __iomem *stat_reg = base + VT8500_ICIS + (d->hwirq < 32 ? 0 : 4);
	u8 edge, dctr;
	u32 status;

	edge = readb(base + VT8500_ICDC + d->hwirq) & VT8500_EDGE;
	if (edge) {
		status = readl(stat_reg);

		status |= (1 << (d->hwirq & 0x1f));
		writel(status, stat_reg);
	} else {
		dctr = readb(base + VT8500_ICDC + d->hwirq);
		dctr &= ~VT8500_INT_ENABLE;
		writeb(dctr, base + VT8500_ICDC + d->hwirq);
	}
}

static void vt8500_irq_unmask(struct irq_data *d)
{
	struct vt8500_irq_data *priv = d->domain->host_data;
	void __iomem *base = priv->base;
	u8 dctr;

	dctr = readb(base + VT8500_ICDC + d->hwirq);
	dctr |= VT8500_INT_ENABLE;
	writeb(dctr, base + VT8500_ICDC + d->hwirq);
}

static int vt8500_irq_set_type(struct irq_data *d, unsigned int flow_type)
{
	struct vt8500_irq_data *priv = d->domain->host_data;
	void __iomem *base = priv->base;
	u8 dctr;

	dctr = readb(base + VT8500_ICDC + d->hwirq);
	dctr &= ~VT8500_EDGE;

	switch (flow_type) {
	case IRQF_TRIGGER_LOW:
		return -EINVAL;
	case IRQF_TRIGGER_HIGH:
		dctr |= VT8500_TRIGGER_HIGH;
		__irq_set_handler_locked(d->irq, handle_level_irq);
		break;
	case IRQF_TRIGGER_FALLING:
		dctr |= VT8500_TRIGGER_FALLING;
		__irq_set_handler_locked(d->irq, handle_edge_irq);
		break;
	case IRQF_TRIGGER_RISING:
		dctr |= VT8500_TRIGGER_RISING;
		__irq_set_handler_locked(d->irq, handle_edge_irq);
		break;
	}
	writeb(dctr, base + VT8500_ICDC + d->hwirq);

	return 0;
}

static struct irq_chip vt8500_irq_chip = {
	.name = "vt8500",
	.irq_ack = vt8500_irq_mask,
	.irq_mask = vt8500_irq_mask,
	.irq_unmask = vt8500_irq_unmask,
	.irq_set_type = vt8500_irq_set_type,
};

static void __init vt8500_init_irq_hw(void __iomem *base)
{
	u32 i;

	/* Enable rotating priority for IRQ */
	writel(ICPC_ROTATE, base + VT8500_ICPC_IRQ);
	writel(0x00, base + VT8500_ICPC_FIQ);

	/* Disable all interrupts and route them to IRQ */
	for (i = 0; i < 64; i++)
		writeb(VT8500_INT_DISABLE | ICDC_IRQ, base + VT8500_ICDC + i);
}

static int vt8500_irq_map(struct irq_domain *h, unsigned int virq,
							irq_hw_number_t hw)
{
	irq_set_chip_and_handler(virq, &vt8500_irq_chip, handle_level_irq);
	set_irq_flags(virq, IRQF_VALID);

	return 0;
}

static struct irq_domain_ops vt8500_irq_domain_ops = {
	.map = vt8500_irq_map,
	.xlate = irq_domain_xlate_onecell,
};

static void __exception_irq_entry vt8500_handle_irq(struct pt_regs *regs)
{
	u32 stat, i;
	int irqnr;
	void __iomem *base;

	/* Loop through each active controller */
	for (i=0; i<active_cnt; i++) {
		base = intc[i].base;
		irqnr = readl_relaxed(base) & 0x3F;
		/*
		  Highest Priority register default = 63, so check that this
		  is a real interrupt by checking the status register
		*/
		if (irqnr == 63) {
			stat = readl_relaxed(base + VT8500_ICIS + 4);
			if (!(stat & BIT(31)))
				continue;
		}

		handle_domain_irq(intc[i].domain, irqnr, regs);
	}
}

static int __init vt8500_irq_init(struct device_node *node,
				  struct device_node *parent)
{
	int irq, i;
	struct device_node *np = node;

	if (active_cnt == VT8500_INTC_MAX) {
		pr_err("%s: Interrupt controllers > VT8500_INTC_MAX\n",
								__func__);
		goto out;
	}

	intc[active_cnt].base = of_iomap(np, 0);
	intc[active_cnt].domain = irq_domain_add_linear(node, 64,
			&vt8500_irq_domain_ops,	&intc[active_cnt]);

	if (!intc[active_cnt].base) {
		pr_err("%s: Unable to map IO memory\n", __func__);
		goto out;
	}

	if (!intc[active_cnt].domain) {
		pr_err("%s: Unable to add irq domain!\n", __func__);
		goto out;
	}

	set_handle_irq(vt8500_handle_irq);

	vt8500_init_irq_hw(intc[active_cnt].base);

	pr_info("vt8500-irq: Added interrupt controller\n");

	active_cnt++;

	/* check if this is a slaved controller */
	if (of_irq_count(np) != 0) {
		/* check that we have the correct number of interrupts */
		if (of_irq_count(np) != 8) {
			pr_err("%s: Incorrect IRQ map for slaved controller\n",
					__func__);
			return -EINVAL;
		}

		for (i = 0; i < 8; i++) {
			irq = irq_of_parse_and_map(np, i);
			enable_irq(irq);
		}

		pr_info("vt8500-irq: Enabled slave->parent interrupts\n");
	}
out:
	return 0;
}

IRQCHIP_DECLARE(vt8500_irq, "via,vt8500-intc", vt8500_irq_init);