summaryrefslogtreecommitdiffstats
path: root/drivers/irqchip/irq-davinci-aintc.c
blob: 123eb7bfc11781dccf5de94c826a378568a17e0f (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
// SPDX-License-Identifier: GPL-2.0-or-later
//
// Copyright (C) 2006, 2019 Texas Instruments.
//
// Interrupt handler for DaVinci boards.

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqchip/irq-davinci-aintc.h>
#include <linux/io.h>
#include <linux/irqdomain.h>

#include <asm/exception.h>

#define DAVINCI_AINTC_FIQ_REG0		0x00
#define DAVINCI_AINTC_FIQ_REG1		0x04
#define DAVINCI_AINTC_IRQ_REG0		0x08
#define DAVINCI_AINTC_IRQ_REG1		0x0c
#define DAVINCI_AINTC_IRQ_IRQENTRY	0x14
#define DAVINCI_AINTC_IRQ_ENT_REG0	0x18
#define DAVINCI_AINTC_IRQ_ENT_REG1	0x1c
#define DAVINCI_AINTC_IRQ_INCTL_REG	0x20
#define DAVINCI_AINTC_IRQ_EABASE_REG	0x24
#define DAVINCI_AINTC_IRQ_INTPRI0_REG	0x30
#define DAVINCI_AINTC_IRQ_INTPRI7_REG	0x4c

static void __iomem *davinci_aintc_base;
static struct irq_domain *davinci_aintc_irq_domain;

static inline void davinci_aintc_writel(unsigned long value, int offset)
{
	writel_relaxed(value, davinci_aintc_base + offset);
}

static inline unsigned long davinci_aintc_readl(int offset)
{
	return readl_relaxed(davinci_aintc_base + offset);
}

static __init void
davinci_aintc_setup_gc(void __iomem *base,
		       unsigned int irq_start, unsigned int num)
{
	struct irq_chip_generic *gc;
	struct irq_chip_type *ct;

	gc = irq_get_domain_generic_chip(davinci_aintc_irq_domain, irq_start);
	gc->reg_base = base;
	gc->irq_base = irq_start;

	ct = gc->chip_types;
	ct->chip.irq_ack = irq_gc_ack_set_bit;
	ct->chip.irq_mask = irq_gc_mask_clr_bit;
	ct->chip.irq_unmask = irq_gc_mask_set_bit;

	ct->regs.ack = DAVINCI_AINTC_IRQ_REG0;
	ct->regs.mask = DAVINCI_AINTC_IRQ_ENT_REG0;
	irq_setup_generic_chip(gc, IRQ_MSK(num), IRQ_GC_INIT_MASK_CACHE,
			       IRQ_NOREQUEST | IRQ_NOPROBE, 0);
}

static asmlinkage void __exception_irq_entry
davinci_aintc_handle_irq(struct pt_regs *regs)
{
	int irqnr = davinci_aintc_readl(DAVINCI_AINTC_IRQ_IRQENTRY);

	/*
	 * Use the formula for entry vector index generation from section
	 * 8.3.3 of the manual.
	 */
	irqnr >>= 2;
	irqnr -= 1;

	generic_handle_domain_irq(davinci_aintc_irq_domain, irqnr);
}

/* ARM Interrupt Controller Initialization */
void __init davinci_aintc_init(const struct davinci_aintc_config *config)
{
	unsigned int irq_off, reg_off, prio, shift;
	void __iomem *req;
	int ret, irq_base;
	const u8 *prios;

	req = request_mem_region(config->reg.start,
				 resource_size(&config->reg),
				 "davinci-cp-intc");
	if (!req) {
		pr_err("%s: register range busy\n", __func__);
		return;
	}

	davinci_aintc_base = ioremap(config->reg.start,
				     resource_size(&config->reg));
	if (!davinci_aintc_base) {
		pr_err("%s: unable to ioremap register range\n", __func__);
		return;
	}

	/* Clear all interrupt requests */
	davinci_aintc_writel(~0x0, DAVINCI_AINTC_FIQ_REG0);
	davinci_aintc_writel(~0x0, DAVINCI_AINTC_FIQ_REG1);
	davinci_aintc_writel(~0x0, DAVINCI_AINTC_IRQ_REG0);
	davinci_aintc_writel(~0x0, DAVINCI_AINTC_IRQ_REG1);

	/* Disable all interrupts */
	davinci_aintc_writel(0x0, DAVINCI_AINTC_IRQ_ENT_REG0);
	davinci_aintc_writel(0x0, DAVINCI_AINTC_IRQ_ENT_REG1);

	/* Interrupts disabled immediately, IRQ entry reflects all */
	davinci_aintc_writel(0x0, DAVINCI_AINTC_IRQ_INCTL_REG);

	/* we don't use the hardware vector table, just its entry addresses */
	davinci_aintc_writel(0, DAVINCI_AINTC_IRQ_EABASE_REG);

	/* Clear all interrupt requests */
	davinci_aintc_writel(~0x0, DAVINCI_AINTC_FIQ_REG0);
	davinci_aintc_writel(~0x0, DAVINCI_AINTC_FIQ_REG1);
	davinci_aintc_writel(~0x0, DAVINCI_AINTC_IRQ_REG0);
	davinci_aintc_writel(~0x0, DAVINCI_AINTC_IRQ_REG1);

	prios = config->prios;
	for (reg_off = DAVINCI_AINTC_IRQ_INTPRI0_REG;
	     reg_off <= DAVINCI_AINTC_IRQ_INTPRI7_REG; reg_off += 4) {
		for (shift = 0, prio = 0; shift < 32; shift += 4, prios++)
			prio |= (*prios & 0x07) << shift;
		davinci_aintc_writel(prio, reg_off);
	}

	irq_base = irq_alloc_descs(-1, 0, config->num_irqs, 0);
	if (irq_base < 0) {
		pr_err("%s: unable to allocate interrupt descriptors: %d\n",
		       __func__, irq_base);
		return;
	}

	davinci_aintc_irq_domain = irq_domain_add_legacy(NULL,
						config->num_irqs, irq_base, 0,
						&irq_domain_simple_ops, NULL);
	if (!davinci_aintc_irq_domain) {
		pr_err("%s: unable to create interrupt domain\n", __func__);
		return;
	}

	ret = irq_alloc_domain_generic_chips(davinci_aintc_irq_domain, 32, 1,
					     "AINTC", handle_edge_irq,
					     IRQ_NOREQUEST | IRQ_NOPROBE, 0, 0);
	if (ret) {
		pr_err("%s: unable to allocate generic irq chips for domain\n",
		       __func__);
		return;
	}

	for (irq_off = 0, reg_off = 0;
	     irq_off < config->num_irqs;
	     irq_off += 32, reg_off += 0x04)
		davinci_aintc_setup_gc(davinci_aintc_base + reg_off,
				       irq_base + irq_off, 32);

	set_handle_irq(davinci_aintc_handle_irq);
}