extint.c 6.0 KB
Newer Older
H
Haavard Skinnemoen 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*
 * External interrupt handling for AT32AP CPUs
 *
 * Copyright (C) 2006 Atmel Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/errno.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/platform_device.h>
#include <linux/random.h>

#include <asm/io.h>

20 21 22 23 24 25 26 27 28 29 30 31
/* EIC register offsets */
#define EIC_IER					0x0000
#define EIC_IDR					0x0004
#define EIC_IMR					0x0008
#define EIC_ISR					0x000c
#define EIC_ICR					0x0010
#define EIC_MODE				0x0014
#define EIC_EDGE				0x0018
#define EIC_LEVEL				0x001c
#define EIC_NMIC				0x0024

/* Bitfields in NMIC */
H
Haavard Skinnemoen 已提交
32
#define EIC_NMIC_ENABLE				(1 << 0)
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

/* Bit manipulation macros */
#define EIC_BIT(name)					\
	(1 << EIC_##name##_OFFSET)
#define EIC_BF(name,value)				\
	(((value) & ((1 << EIC_##name##_SIZE) - 1))	\
	 << EIC_##name##_OFFSET)
#define EIC_BFEXT(name,value)				\
	(((value) >> EIC_##name##_OFFSET)		\
	 & ((1 << EIC_##name##_SIZE) - 1))
#define EIC_BFINS(name,value,old)			\
	(((old) & ~(((1 << EIC_##name##_SIZE) - 1)	\
		    << EIC_##name##_OFFSET))		\
	 | EIC_BF(name,value))

/* Register access macros */
#define eic_readl(port,reg)				\
	__raw_readl((port)->regs + EIC_##reg)
#define eic_writel(port,reg,value)			\
	__raw_writel((value), (port)->regs + EIC_##reg)

struct eic {
	void __iomem *regs;
	struct irq_chip *chip;
	unsigned int first_irq;
};
H
Haavard Skinnemoen 已提交
59

H
Haavard Skinnemoen 已提交
60 61 62
static struct eic *nmi_eic;
static bool nmi_enabled;

63
static void eic_ack_irq(unsigned int irq)
H
Haavard Skinnemoen 已提交
64
{
65 66
	struct eic *eic = get_irq_chip_data(irq);
	eic_writel(eic, ICR, 1 << (irq - eic->first_irq));
H
Haavard Skinnemoen 已提交
67 68
}

69
static void eic_mask_irq(unsigned int irq)
H
Haavard Skinnemoen 已提交
70
{
71 72
	struct eic *eic = get_irq_chip_data(irq);
	eic_writel(eic, IDR, 1 << (irq - eic->first_irq));
H
Haavard Skinnemoen 已提交
73 74
}

75
static void eic_mask_ack_irq(unsigned int irq)
H
Haavard Skinnemoen 已提交
76
{
77 78 79
	struct eic *eic = get_irq_chip_data(irq);
	eic_writel(eic, ICR, 1 << (irq - eic->first_irq));
	eic_writel(eic, IDR, 1 << (irq - eic->first_irq));
H
Haavard Skinnemoen 已提交
80 81
}

82
static void eic_unmask_irq(unsigned int irq)
H
Haavard Skinnemoen 已提交
83
{
84 85
	struct eic *eic = get_irq_chip_data(irq);
	eic_writel(eic, IER, 1 << (irq - eic->first_irq));
H
Haavard Skinnemoen 已提交
86 87
}

88
static int eic_set_irq_type(unsigned int irq, unsigned int flow_type)
H
Haavard Skinnemoen 已提交
89
{
90
	struct eic *eic = get_irq_chip_data(irq);
91
	struct irq_desc *desc;
92
	unsigned int i = irq - eic->first_irq;
H
Haavard Skinnemoen 已提交
93 94 95
	u32 mode, edge, level;
	int ret = 0;

D
David Brownell 已提交
96
	flow_type &= IRQ_TYPE_SENSE_MASK;
97 98 99 100
	if (flow_type == IRQ_TYPE_NONE)
		flow_type = IRQ_TYPE_LEVEL_LOW;

	desc = &irq_desc[irq];
H
Haavard Skinnemoen 已提交
101

102 103 104
	mode = eic_readl(eic, MODE);
	edge = eic_readl(eic, EDGE);
	level = eic_readl(eic, LEVEL);
H
Haavard Skinnemoen 已提交
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127

	switch (flow_type) {
	case IRQ_TYPE_LEVEL_LOW:
		mode |= 1 << i;
		level &= ~(1 << i);
		break;
	case IRQ_TYPE_LEVEL_HIGH:
		mode |= 1 << i;
		level |= 1 << i;
		break;
	case IRQ_TYPE_EDGE_RISING:
		mode &= ~(1 << i);
		edge |= 1 << i;
		break;
	case IRQ_TYPE_EDGE_FALLING:
		mode &= ~(1 << i);
		edge &= ~(1 << i);
		break;
	default:
		ret = -EINVAL;
		break;
	}

D
David Brownell 已提交
128
	if (ret == 0) {
129 130 131
		eic_writel(eic, MODE, mode);
		eic_writel(eic, EDGE, edge);
		eic_writel(eic, LEVEL, level);
D
David Brownell 已提交
132

133
		if (flow_type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)) {
D
David Brownell 已提交
134
			flow_type |= IRQ_LEVEL;
135 136 137
			__set_irq_handler_unlocked(irq, handle_level_irq);
		} else
			__set_irq_handler_unlocked(irq, handle_edge_irq);
D
David Brownell 已提交
138 139 140
		desc->status &= ~(IRQ_TYPE_SENSE_MASK | IRQ_LEVEL);
		desc->status |= flow_type;
	}
H
Haavard Skinnemoen 已提交
141 142 143 144

	return ret;
}

145
static struct irq_chip eic_chip = {
146 147 148 149 150 151
	.name		= "eic",
	.ack		= eic_ack_irq,
	.mask		= eic_mask_irq,
	.mask_ack	= eic_mask_ack_irq,
	.unmask		= eic_unmask_irq,
	.set_type	= eic_set_irq_type,
H
Haavard Skinnemoen 已提交
152 153
};

154
static void demux_eic_irq(unsigned int irq, struct irq_desc *desc)
H
Haavard Skinnemoen 已提交
155
{
156
	struct eic *eic = desc->handler_data;
H
Haavard Skinnemoen 已提交
157
	unsigned long status, pending;
158
	unsigned int i;
H
Haavard Skinnemoen 已提交
159

160 161
	status = eic_readl(eic, ISR);
	pending = status & eic_readl(eic, IMR);
H
Haavard Skinnemoen 已提交
162 163 164 165 166

	while (pending) {
		i = fls(pending) - 1;
		pending &= ~(1 << i);

167
		generic_handle_irq(i + eic->first_irq);
H
Haavard Skinnemoen 已提交
168 169 170
	}
}

H
Haavard Skinnemoen 已提交
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
int nmi_enable(void)
{
	nmi_enabled = true;

	if (nmi_eic)
		eic_writel(nmi_eic, NMIC, EIC_NMIC_ENABLE);

	return 0;
}

void nmi_disable(void)
{
	if (nmi_eic)
		eic_writel(nmi_eic, NMIC, 0);

	nmi_enabled = false;
}

189
static int __init eic_probe(struct platform_device *pdev)
H
Haavard Skinnemoen 已提交
190
{
191 192
	struct eic *eic;
	struct resource *regs;
H
Haavard Skinnemoen 已提交
193 194 195
	unsigned int i;
	unsigned int nr_irqs;
	unsigned int int_irq;
196
	int ret;
H
Haavard Skinnemoen 已提交
197 198
	u32 pattern;

199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
	regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	int_irq = platform_get_irq(pdev, 0);
	if (!regs || !int_irq) {
		dev_dbg(&pdev->dev, "missing regs and/or irq resource\n");
		return -ENXIO;
	}

	ret = -ENOMEM;
	eic = kzalloc(sizeof(struct eic), GFP_KERNEL);
	if (!eic) {
		dev_dbg(&pdev->dev, "no memory for eic structure\n");
		goto err_kzalloc;
	}

	eic->first_irq = EIM_IRQ_BASE + 32 * pdev->id;
	eic->regs = ioremap(regs->start, regs->end - regs->start + 1);
	if (!eic->regs) {
		dev_dbg(&pdev->dev, "failed to map regs\n");
		goto err_ioremap;
	}
H
Haavard Skinnemoen 已提交
219 220 221 222 223

	/*
	 * Find out how many interrupt lines that are actually
	 * implemented in hardware.
	 */
224 225 226
	eic_writel(eic, IDR, ~0UL);
	eic_writel(eic, MODE, ~0UL);
	pattern = eic_readl(eic, MODE);
H
Haavard Skinnemoen 已提交
227 228
	nr_irqs = fls(pattern);

229
	/* Trigger on falling edge unless overridden by driver */
230 231
	eic_writel(eic, MODE, 0UL);
	eic_writel(eic, EDGE, 0UL);
232

233
	eic->chip = &eic_chip;
H
Haavard Skinnemoen 已提交
234 235

	for (i = 0; i < nr_irqs; i++) {
236
		set_irq_chip_and_handler(eic->first_irq + i, &eic_chip,
237
					 handle_edge_irq);
238
		set_irq_chip_data(eic->first_irq + i, eic);
H
Haavard Skinnemoen 已提交
239 240
	}

241 242
	set_irq_chained_handler(int_irq, demux_eic_irq);
	set_irq_data(int_irq, eic);
H
Haavard Skinnemoen 已提交
243

H
Haavard Skinnemoen 已提交
244 245 246 247 248 249 250 251 252 253
	if (pdev->id == 0) {
		nmi_eic = eic;
		if (nmi_enabled)
			/*
			 * Someone tried to enable NMI before we were
			 * ready. Do it now.
			 */
			nmi_enable();
	}

254 255 256 257 258 259
	dev_info(&pdev->dev,
		 "External Interrupt Controller at 0x%p, IRQ %u\n",
		 eic->regs, int_irq);
	dev_info(&pdev->dev,
		 "Handling %u external IRQs, starting with IRQ %u\n",
		 nr_irqs, eic->first_irq);
H
Haavard Skinnemoen 已提交
260 261

	return 0;
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277

err_ioremap:
	kfree(eic);
err_kzalloc:
	return ret;
}

static struct platform_driver eic_driver = {
	.driver = {
		.name = "at32_eic",
	},
};

static int __init eic_init(void)
{
	return platform_driver_probe(&eic_driver, eic_probe);
H
Haavard Skinnemoen 已提交
278
}
279
arch_initcall(eic_init);