Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/sys/arm/mv/mv_cp110_icu.c
39536 views
1
/*-
2
* SPDX-License-Identifier: BSD-2-Clause
3
*
4
* Copyright (c) 2018 Rubicon Communications, LLC (Netgate)
5
*
6
* Redistribution and use in source and binary forms, with or without
7
* modification, are permitted provided that the following conditions
8
* are met:
9
* 1. Redistributions of source code must retain the above copyright
10
* notice, this list of conditions and the following disclaimer.
11
* 2. Redistributions in binary form must reproduce the above copyright
12
* notice, this list of conditions and the following disclaimer in the
13
* documentation and/or other materials provided with the distribution.
14
*
15
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25
* SUCH DAMAGE.
26
*/
27
28
#include <sys/param.h>
29
#include <sys/systm.h>
30
#include <sys/bus.h>
31
32
#include <sys/kernel.h>
33
#include <sys/module.h>
34
#include <sys/rman.h>
35
#include <sys/lock.h>
36
#include <sys/mutex.h>
37
38
#include <machine/bus.h>
39
#include <machine/resource.h>
40
#include <machine/intr.h>
41
42
#include <dev/fdt/simplebus.h>
43
44
#include <dev/ofw/ofw_bus.h>
45
#include <dev/ofw/ofw_bus_subr.h>
46
47
#include <dt-bindings/interrupt-controller/irq.h>
48
49
#include "pic_if.h"
50
#include "msi_if.h"
51
52
#define ICU_TYPE_NSR 1
53
#define ICU_TYPE_SEI 2
54
55
#define ICU_GRP_NSR 0x0
56
#define ICU_GRP_SR 0x1
57
#define ICU_GRP_SEI 0x4
58
#define ICU_GRP_REI 0x5
59
60
#define ICU_SETSPI_NSR_AL 0x10
61
#define ICU_SETSPI_NSR_AH 0x14
62
#define ICU_CLRSPI_NSR_AL 0x18
63
#define ICU_CLRSPI_NSR_AH 0x1c
64
#define ICU_SETSPI_SEI_AL 0x50
65
#define ICU_SETSPI_SEI_AH 0x54
66
#define ICU_INT_CFG(x) (0x100 + (x) * 4)
67
#define ICU_INT_ENABLE (1 << 24)
68
#define ICU_INT_EDGE (1 << 28)
69
#define ICU_INT_GROUP_SHIFT 29
70
#define ICU_INT_MASK 0x3ff
71
72
#define ICU_INT_SATA0 109
73
#define ICU_INT_SATA1 107
74
75
#define MV_CP110_ICU_MAX_NIRQS 207
76
77
#define MV_CP110_ICU_CLRSPI_OFFSET 0x8
78
79
struct mv_cp110_icu_softc {
80
device_t dev;
81
device_t parent;
82
struct resource *res;
83
struct intr_map_data_fdt *parent_map_data;
84
bool initialized;
85
int type;
86
};
87
88
static struct resource_spec mv_cp110_icu_res_spec[] = {
89
{ SYS_RES_MEMORY, 0, RF_ACTIVE | RF_SHAREABLE },
90
{ -1, 0 }
91
};
92
93
static struct ofw_compat_data compat_data[] = {
94
{"marvell,cp110-icu-nsr", ICU_TYPE_NSR},
95
{"marvell,cp110-icu-sei", ICU_TYPE_SEI},
96
{NULL, 0}
97
};
98
99
#define RD4(sc, reg) bus_read_4((sc)->res, (reg))
100
#define WR4(sc, reg, val) bus_write_4((sc)->res, (reg), (val))
101
102
static int
103
mv_cp110_icu_probe(device_t dev)
104
{
105
106
if (!ofw_bus_status_okay(dev))
107
return (ENXIO);
108
109
if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
110
return (ENXIO);
111
112
device_set_desc(dev, "Marvell Interrupt Consolidation Unit");
113
return (BUS_PROBE_DEFAULT);
114
}
115
116
static int
117
mv_cp110_icu_attach(device_t dev)
118
{
119
struct mv_cp110_icu_softc *sc;
120
phandle_t node, msi_parent;
121
uint32_t reg, icu_grp;
122
int i;
123
124
sc = device_get_softc(dev);
125
sc->dev = dev;
126
node = ofw_bus_get_node(dev);
127
sc->type = (int)ofw_bus_search_compatible(dev, compat_data)->ocd_data;
128
sc->initialized = false;
129
130
if (OF_getencprop(node, "msi-parent", &msi_parent,
131
sizeof(phandle_t)) <= 0) {
132
device_printf(dev, "cannot find msi-parent property\n");
133
return (ENXIO);
134
}
135
136
if ((sc->parent = OF_device_from_xref(msi_parent)) == NULL) {
137
device_printf(dev, "cannot find msi-parent device\n");
138
return (ENXIO);
139
}
140
if (bus_alloc_resources(dev, mv_cp110_icu_res_spec, &sc->res) != 0) {
141
device_printf(dev, "cannot allocate resources for device\n");
142
return (ENXIO);
143
}
144
145
if (intr_pic_register(dev, OF_xref_from_node(node)) == NULL) {
146
device_printf(dev, "Cannot register ICU\n");
147
goto fail;
148
}
149
150
/* Allocate GICP/SEI compatible mapping entry (2 cells) */
151
sc->parent_map_data = (struct intr_map_data_fdt *)intr_alloc_map_data(
152
INTR_MAP_DATA_FDT, sizeof(struct intr_map_data_fdt) +
153
+ 3 * sizeof(phandle_t), M_WAITOK | M_ZERO);
154
155
/* Clear any previous mapping done by firmware. */
156
for (i = 0; i < MV_CP110_ICU_MAX_NIRQS; i++) {
157
reg = RD4(sc, ICU_INT_CFG(i));
158
icu_grp = reg >> ICU_INT_GROUP_SHIFT;
159
160
if (icu_grp == ICU_GRP_NSR || icu_grp == ICU_GRP_SEI)
161
WR4(sc, ICU_INT_CFG(i), 0);
162
}
163
164
return (0);
165
166
fail:
167
bus_release_resources(dev, mv_cp110_icu_res_spec, &sc->res);
168
return (ENXIO);
169
}
170
171
static struct intr_map_data *
172
mv_cp110_icu_convert_map_data(struct mv_cp110_icu_softc *sc, struct intr_map_data *data)
173
{
174
struct intr_map_data_fdt *daf;
175
uint32_t reg, irq_no, irq_type;
176
177
daf = (struct intr_map_data_fdt *)data;
178
if (daf->ncells != 2)
179
return (NULL);
180
181
irq_no = daf->cells[0];
182
if (irq_no >= MV_CP110_ICU_MAX_NIRQS)
183
return (NULL);
184
185
irq_type = daf->cells[1];
186
if (irq_type != IRQ_TYPE_LEVEL_HIGH &&
187
irq_type != IRQ_TYPE_EDGE_RISING)
188
return (NULL);
189
190
/* ICU -> GICP/SEI mapping is set in mv_cp110_icu_map_intr. */
191
reg = RD4(sc, ICU_INT_CFG(irq_no));
192
193
/* Construct GICP compatible mapping. */
194
sc->parent_map_data->ncells = 2;
195
sc->parent_map_data->cells[0] = reg & ICU_INT_MASK;
196
sc->parent_map_data->cells[1] = irq_type;
197
198
return ((struct intr_map_data *)sc->parent_map_data);
199
}
200
201
static int
202
mv_cp110_icu_detach(device_t dev)
203
{
204
205
return (EBUSY);
206
}
207
208
static int
209
mv_cp110_icu_activate_intr(device_t dev, struct intr_irqsrc *isrc,
210
struct resource *res, struct intr_map_data *data)
211
{
212
struct mv_cp110_icu_softc *sc;
213
214
sc = device_get_softc(dev);
215
data = mv_cp110_icu_convert_map_data(sc, data);
216
if (data == NULL)
217
return (EINVAL);
218
return (PIC_ACTIVATE_INTR(sc->parent, isrc, res, data));
219
}
220
221
static void
222
mv_cp110_icu_enable_intr(device_t dev, struct intr_irqsrc *isrc)
223
{
224
struct mv_cp110_icu_softc *sc;
225
sc = device_get_softc(dev);
226
227
PIC_ENABLE_INTR(sc->parent, isrc);
228
}
229
230
static void
231
mv_cp110_icu_disable_intr(device_t dev, struct intr_irqsrc *isrc)
232
{
233
struct mv_cp110_icu_softc *sc;
234
235
sc = device_get_softc(dev);
236
237
PIC_DISABLE_INTR(sc->parent, isrc);
238
}
239
240
static void
241
mv_cp110_icu_init(struct mv_cp110_icu_softc *sc, uint64_t addr)
242
{
243
244
if (sc->initialized)
245
return;
246
247
switch (sc->type) {
248
case ICU_TYPE_NSR:
249
WR4(sc, ICU_SETSPI_NSR_AL, addr & UINT32_MAX);
250
WR4(sc, ICU_SETSPI_NSR_AH, (addr >> 32) & UINT32_MAX);
251
addr += MV_CP110_ICU_CLRSPI_OFFSET;
252
WR4(sc, ICU_CLRSPI_NSR_AL, addr & UINT32_MAX);
253
WR4(sc, ICU_CLRSPI_NSR_AH, (addr >> 32) & UINT32_MAX);
254
break;
255
case ICU_TYPE_SEI:
256
WR4(sc, ICU_SETSPI_SEI_AL, addr & UINT32_MAX);
257
WR4(sc, ICU_SETSPI_SEI_AH, (addr >> 32) & UINT32_MAX);
258
break;
259
default:
260
panic("Unkown ICU type.");
261
}
262
263
sc->initialized = true;
264
}
265
266
static int
267
mv_cp110_icu_map_intr(device_t dev, struct intr_map_data *data,
268
struct intr_irqsrc **isrcp)
269
{
270
struct mv_cp110_icu_softc *sc;
271
struct intr_map_data_fdt *daf;
272
uint32_t vector, irq_no, irq_type;
273
uint64_t addr;
274
int ret;
275
276
sc = device_get_softc(dev);
277
278
if (data->type != INTR_MAP_DATA_FDT)
279
return (ENOTSUP);
280
281
/* Parse original */
282
daf = (struct intr_map_data_fdt *)data;
283
if (daf->ncells != 2)
284
return (EINVAL);
285
286
irq_no = daf->cells[0];
287
if (irq_no >= MV_CP110_ICU_MAX_NIRQS)
288
return (EINVAL);
289
290
irq_type = daf->cells[1];
291
if (irq_type != IRQ_TYPE_LEVEL_HIGH &&
292
irq_type != IRQ_TYPE_EDGE_RISING)
293
return (EINVAL);
294
295
/*
296
* Allocate MSI vector.
297
* We don't use intr_alloc_msi wrapper, since it registers a new irq
298
* in the kernel. In our case irq was already added by the ofw code.
299
*/
300
ret = MSI_ALLOC_MSI(sc->parent, dev, 1, 1, NULL, isrcp);
301
if (ret != 0)
302
return (ret);
303
304
ret = MSI_MAP_MSI(sc->parent, dev, *isrcp, &addr, &vector);
305
if (ret != 0)
306
goto fail;
307
308
mv_cp110_icu_init(sc, addr);
309
vector |= ICU_INT_ENABLE;
310
311
if (sc->type == ICU_TYPE_NSR)
312
vector |= ICU_GRP_NSR << ICU_INT_GROUP_SHIFT;
313
else
314
vector |= ICU_GRP_SEI << ICU_INT_GROUP_SHIFT;
315
316
if (irq_type & IRQ_TYPE_EDGE_BOTH)
317
vector |= ICU_INT_EDGE;
318
319
WR4(sc, ICU_INT_CFG(irq_no), vector);
320
321
/*
322
* SATA controller has two ports, each gets its own interrupt.
323
* The problem is that only one irq is described in dts.
324
* Also ahci_generic driver supports only one irq per controller.
325
* As a workaround map both interrupts when one of them is allocated.
326
* This allows us to use both SATA ports.
327
*/
328
if (irq_no == ICU_INT_SATA0)
329
WR4(sc, ICU_INT_CFG(ICU_INT_SATA1), vector);
330
if (irq_no == ICU_INT_SATA1)
331
WR4(sc, ICU_INT_CFG(ICU_INT_SATA0), vector);
332
333
(*isrcp)->isrc_dev = sc->dev;
334
return (ret);
335
336
fail:
337
if (*isrcp != NULL)
338
MSI_RELEASE_MSI(sc->parent, dev, 1, isrcp);
339
340
return (ret);
341
}
342
343
static int
344
mv_cp110_icu_deactivate_intr(device_t dev, struct intr_irqsrc *isrc,
345
struct resource *res, struct intr_map_data *data)
346
{
347
struct mv_cp110_icu_softc *sc;
348
struct intr_map_data_fdt *daf;
349
int irq_no, ret;
350
351
if (data->type != INTR_MAP_DATA_FDT)
352
return (ENOTSUP);
353
354
sc = device_get_softc(dev);
355
daf = (struct intr_map_data_fdt *)data;
356
if (daf->ncells != 2)
357
return (EINVAL);
358
359
irq_no = daf->cells[0];
360
data = mv_cp110_icu_convert_map_data(sc, data);
361
if (data == NULL)
362
return (EINVAL);
363
364
/* Clear the mapping. */
365
WR4(sc, ICU_INT_CFG(irq_no), 0);
366
367
ret = PIC_DEACTIVATE_INTR(sc->parent, isrc, res, data);
368
if (ret != 0)
369
return (ret);
370
371
return (MSI_RELEASE_MSI(sc->parent, dev, 1, &isrc));
372
}
373
374
static int
375
mv_cp110_icu_setup_intr(device_t dev, struct intr_irqsrc *isrc,
376
struct resource *res, struct intr_map_data *data)
377
{
378
struct mv_cp110_icu_softc *sc;
379
380
sc = device_get_softc(dev);
381
data = mv_cp110_icu_convert_map_data(sc, data);
382
if (data == NULL)
383
return (EINVAL);
384
385
return (PIC_SETUP_INTR(sc->parent, isrc, res, data));
386
}
387
388
static int
389
mv_cp110_icu_teardown_intr(device_t dev, struct intr_irqsrc *isrc,
390
struct resource *res, struct intr_map_data *data)
391
{
392
struct mv_cp110_icu_softc *sc;
393
394
sc = device_get_softc(dev);
395
data = mv_cp110_icu_convert_map_data(sc, data);
396
if (data == NULL)
397
return (EINVAL);
398
399
return (PIC_TEARDOWN_INTR(sc->parent, isrc, res, data));
400
}
401
402
static void
403
mv_cp110_icu_pre_ithread(device_t dev, struct intr_irqsrc *isrc)
404
{
405
struct mv_cp110_icu_softc *sc;
406
407
sc = device_get_softc(dev);
408
409
PIC_PRE_ITHREAD(sc->parent, isrc);
410
}
411
412
static void
413
mv_cp110_icu_post_ithread(device_t dev, struct intr_irqsrc *isrc)
414
{
415
struct mv_cp110_icu_softc *sc;
416
417
sc = device_get_softc(dev);
418
419
PIC_POST_ITHREAD(sc->parent, isrc);
420
}
421
422
static void
423
mv_cp110_icu_post_filter(device_t dev, struct intr_irqsrc *isrc)
424
{
425
struct mv_cp110_icu_softc *sc;
426
427
sc = device_get_softc(dev);
428
429
PIC_POST_FILTER(sc->parent, isrc);
430
}
431
432
static device_method_t mv_cp110_icu_methods[] = {
433
/* Device interface */
434
DEVMETHOD(device_probe, mv_cp110_icu_probe),
435
DEVMETHOD(device_attach, mv_cp110_icu_attach),
436
DEVMETHOD(device_detach, mv_cp110_icu_detach),
437
438
/* Interrupt controller interface */
439
DEVMETHOD(pic_activate_intr, mv_cp110_icu_activate_intr),
440
DEVMETHOD(pic_disable_intr, mv_cp110_icu_disable_intr),
441
DEVMETHOD(pic_enable_intr, mv_cp110_icu_enable_intr),
442
DEVMETHOD(pic_map_intr, mv_cp110_icu_map_intr),
443
DEVMETHOD(pic_deactivate_intr, mv_cp110_icu_deactivate_intr),
444
DEVMETHOD(pic_setup_intr, mv_cp110_icu_setup_intr),
445
DEVMETHOD(pic_teardown_intr, mv_cp110_icu_teardown_intr),
446
DEVMETHOD(pic_post_filter, mv_cp110_icu_post_filter),
447
DEVMETHOD(pic_post_ithread, mv_cp110_icu_post_ithread),
448
DEVMETHOD(pic_pre_ithread, mv_cp110_icu_pre_ithread),
449
450
DEVMETHOD_END
451
};
452
453
static driver_t mv_cp110_icu_driver = {
454
"mv_cp110_icu",
455
mv_cp110_icu_methods,
456
sizeof(struct mv_cp110_icu_softc),
457
};
458
459
EARLY_DRIVER_MODULE(mv_cp110_icu, mv_cp110_icu_bus, mv_cp110_icu_driver, 0, 0,
460
BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LAST);
461
462