summaryrefslogtreecommitdiff
path: root/drivers/irqchip/irq-mscc-ocelot.c
blob: 8235d98650c1e0a54d6b7de2987dbd244473ca7a (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
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/*
 * Microsemi Ocelot IRQ controller driver
 *
 * Copyright (c) 2017 Microsemi Corporation
 */
#include <linux/bitops.h>
#include <linux/irq.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/irqchip.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/interrupt.h>

#define ICPU_CFG_INTR_DST_INTR_IDENT(_p, x) ((_p)->reg_off_ident + 0x4 * (x))
#define ICPU_CFG_INTR_INTR_TRIGGER(_p, x)   ((_p)->reg_off_trigger + 0x4 * (x))

#define FLAGS_HAS_TRIGGER	BIT(0)
#define FLAGS_NEED_INIT_ENABLE	BIT(1)

struct chip_props {
	u8 flags;
	u8 reg_off_sticky;
	u8 reg_off_ena;
	u8 reg_off_ena_clr;
	u8 reg_off_ena_set;
	u8 reg_off_ident;
	u8 reg_off_trigger;
	u8 reg_off_ena_irq0;
	u8 n_irq;
};

static struct chip_props ocelot_props = {
	.flags			= FLAGS_HAS_TRIGGER,
	.reg_off_sticky		= 0x10,
	.reg_off_ena		= 0x18,
	.reg_off_ena_clr	= 0x1c,
	.reg_off_ena_set	= 0x20,
	.reg_off_ident		= 0x38,
	.reg_off_trigger	= 0x5c,
	.n_irq			= 24,
};

static struct chip_props serval_props = {
	.flags			= FLAGS_HAS_TRIGGER,
	.reg_off_sticky		= 0xc,
	.reg_off_ena		= 0x14,
	.reg_off_ena_clr	= 0x18,
	.reg_off_ena_set	= 0x1c,
	.reg_off_ident		= 0x20,
	.reg_off_trigger	= 0x4,
	.n_irq			= 24,
};

static struct chip_props luton_props = {
	.flags			= FLAGS_NEED_INIT_ENABLE,
	.reg_off_sticky		= 0,
	.reg_off_ena		= 0x4,
	.reg_off_ena_clr	= 0x8,
	.reg_off_ena_set	= 0xc,
	.reg_off_ident		= 0x18,
	.reg_off_ena_irq0	= 0x14,
	.n_irq			= 28,
};

static struct chip_props jaguar2_props = {
	.flags			= FLAGS_HAS_TRIGGER,
	.reg_off_sticky		= 0x10,
	.reg_off_ena		= 0x18,
	.reg_off_ena_clr	= 0x1c,
	.reg_off_ena_set	= 0x20,
	.reg_off_ident		= 0x38,
	.reg_off_trigger	= 0x5c,
	.n_irq			= 29,
};

static void ocelot_irq_unmask(struct irq_data *data)
{
	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data);
	struct irq_domain *d = data->domain;
	struct chip_props *p = d->host_data;
	struct irq_chip_type *ct = irq_data_get_chip_type(data);
	unsigned int mask = data->mask;
	u32 val;

	irq_gc_lock(gc);
	val = irq_reg_readl(gc, ICPU_CFG_INTR_INTR_TRIGGER(p, 0)) |
		irq_reg_readl(gc, ICPU_CFG_INTR_INTR_TRIGGER(p, 1));
	if (!(val & mask))
		irq_reg_writel(gc, mask, p->reg_off_sticky);

	*ct->mask_cache &= ~mask;
	irq_reg_writel(gc, mask, p->reg_off_ena_set);
	irq_gc_unlock(gc);
}

static void ocelot_irq_handler(struct irq_desc *desc)
{
	struct irq_chip *chip = irq_desc_get_chip(desc);
	struct irq_domain *d = irq_desc_get_handler_data(desc);
	struct chip_props *p = d->host_data;
	struct irq_chip_generic *gc = irq_get_domain_generic_chip(d, 0);
	u32 reg = irq_reg_readl(gc, ICPU_CFG_INTR_DST_INTR_IDENT(p, 0));

	chained_irq_enter(chip, desc);

	while (reg) {
		u32 hwirq = __fls(reg);

		generic_handle_irq(irq_find_mapping(d, hwirq));
		reg &= ~(BIT(hwirq));
	}

	chained_irq_exit(chip, desc);
}

static int __init vcoreiii_irq_init(struct device_node *node,
				    struct device_node *parent,
				    struct chip_props *p)
{
	struct irq_domain *domain;
	struct irq_chip_generic *gc;
	int parent_irq, ret;

	parent_irq = irq_of_parse_and_map(node, 0);
	if (!parent_irq)
		return -EINVAL;

	domain = irq_domain_add_linear(node, p->n_irq,
				       &irq_generic_chip_ops, NULL);
	if (!domain) {
		pr_err("%pOFn: unable to add irq domain\n", node);
		return -ENOMEM;
	}

	ret = irq_alloc_domain_generic_chips(domain, p->n_irq, 1,
					     "icpu", handle_level_irq,
					     0, 0, 0);
	if (ret) {
		pr_err("%pOFn: unable to alloc irq domain gc\n", node);
		goto err_domain_remove;
	}

	gc = irq_get_domain_generic_chip(domain, 0);
	gc->reg_base = of_iomap(node, 0);
	if (!gc->reg_base) {
		pr_err("%pOFn: unable to map resource\n", node);
		ret = -ENOMEM;
		goto err_gc_free;
	}

	gc->chip_types[0].chip.irq_ack = irq_gc_ack_set_bit;
	gc->chip_types[0].regs.ack = p->reg_off_sticky;
	if (p->flags & FLAGS_HAS_TRIGGER) {
		gc->chip_types[0].regs.mask = p->reg_off_ena_clr;
		gc->chip_types[0].chip.irq_unmask = ocelot_irq_unmask;
		gc->chip_types[0].chip.irq_mask = irq_gc_mask_set_bit;
	} else {
		gc->chip_types[0].regs.enable = p->reg_off_ena_set;
		gc->chip_types[0].regs.disable = p->reg_off_ena_clr;
		gc->chip_types[0].chip.irq_mask = irq_gc_mask_disable_reg;
		gc->chip_types[0].chip.irq_unmask = irq_gc_unmask_enable_reg;
	}

	/* Mask and ack all interrupts */
	irq_reg_writel(gc, 0, p->reg_off_ena);
	irq_reg_writel(gc, 0xffffffff, p->reg_off_sticky);

	/* Overall init */
	if (p->flags & FLAGS_NEED_INIT_ENABLE)
		irq_reg_writel(gc, BIT(0), p->reg_off_ena_irq0);

	domain->host_data = p;
	irq_set_chained_handler_and_data(parent_irq, ocelot_irq_handler,
					 domain);

	return 0;

err_gc_free:
	irq_free_generic_chip(gc);

err_domain_remove:
	irq_domain_remove(domain);

	return ret;
}

static int __init ocelot_irq_init(struct device_node *node,
				  struct device_node *parent)
{
	return vcoreiii_irq_init(node, parent, &ocelot_props);
}

IRQCHIP_DECLARE(ocelot_icpu, "mscc,ocelot-icpu-intr", ocelot_irq_init);

static int __init serval_irq_init(struct device_node *node,
				  struct device_node *parent)
{
	return vcoreiii_irq_init(node, parent, &serval_props);
}

IRQCHIP_DECLARE(serval_icpu, "mscc,serval-icpu-intr", serval_irq_init);

static int __init luton_irq_init(struct device_node *node,
				 struct device_node *parent)
{
	return vcoreiii_irq_init(node, parent, &luton_props);
}

IRQCHIP_DECLARE(luton_icpu, "mscc,luton-icpu-intr", luton_irq_init);

static int __init jaguar2_irq_init(struct device_node *node,
				   struct device_node *parent)
{
	return vcoreiii_irq_init(node, parent, &jaguar2_props);
}

IRQCHIP_DECLARE(jaguar2_icpu, "mscc,jaguar2-icpu-intr", jaguar2_irq_init);