freebsd-skq/sys/arm/broadcom/bcm2835/bcm2835_intr.c
gonzo 6caff90996 Make sure intc is attached before interrupt consumers
If pass order is not specified devices are attached in the order they are
defined in dts. Some interrupt consumers may be defined before intc. Also
make sure intc interrupt-parent local_intc is attached before intc itself.
2016-10-12 02:58:27 +00:00

575 lines
15 KiB
C

/*-
* Copyright (c) 2012 Damjan Marion <dmarion@Freebsd.org>
* All rights reserved.
*
* Based on OMAP3 INTC code by Ben Gray
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include "opt_platform.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/ktr.h>
#include <sys/module.h>
#include <sys/proc.h>
#include <sys/rman.h>
#include <machine/bus.h>
#include <machine/intr.h>
#include <dev/fdt/fdt_common.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#ifdef SOC_BCM2836
#include <arm/broadcom/bcm2835/bcm2836.h>
#endif
#ifdef INTRNG
#include "pic_if.h"
#endif
#define INTC_PENDING_BASIC 0x00
#define INTC_PENDING_BANK1 0x04
#define INTC_PENDING_BANK2 0x08
#define INTC_FIQ_CONTROL 0x0C
#define INTC_ENABLE_BANK1 0x10
#define INTC_ENABLE_BANK2 0x14
#define INTC_ENABLE_BASIC 0x18
#define INTC_DISABLE_BANK1 0x1C
#define INTC_DISABLE_BANK2 0x20
#define INTC_DISABLE_BASIC 0x24
#define INTC_PENDING_BASIC_ARM 0x0000FF
#define INTC_PENDING_BASIC_GPU1_PEND 0x000100
#define INTC_PENDING_BASIC_GPU2_PEND 0x000200
#define INTC_PENDING_BASIC_GPU1_7 0x000400
#define INTC_PENDING_BASIC_GPU1_9 0x000800
#define INTC_PENDING_BASIC_GPU1_10 0x001000
#define INTC_PENDING_BASIC_GPU1_18 0x002000
#define INTC_PENDING_BASIC_GPU1_19 0x004000
#define INTC_PENDING_BASIC_GPU2_21 0x008000
#define INTC_PENDING_BASIC_GPU2_22 0x010000
#define INTC_PENDING_BASIC_GPU2_23 0x020000
#define INTC_PENDING_BASIC_GPU2_24 0x040000
#define INTC_PENDING_BASIC_GPU2_25 0x080000
#define INTC_PENDING_BASIC_GPU2_30 0x100000
#define INTC_PENDING_BASIC_MASK 0x1FFFFF
#define INTC_PENDING_BASIC_GPU1_MASK (INTC_PENDING_BASIC_GPU1_7 | \
INTC_PENDING_BASIC_GPU1_9 | \
INTC_PENDING_BASIC_GPU1_10 | \
INTC_PENDING_BASIC_GPU1_18 | \
INTC_PENDING_BASIC_GPU1_19)
#define INTC_PENDING_BASIC_GPU2_MASK (INTC_PENDING_BASIC_GPU2_21 | \
INTC_PENDING_BASIC_GPU2_22 | \
INTC_PENDING_BASIC_GPU2_23 | \
INTC_PENDING_BASIC_GPU2_24 | \
INTC_PENDING_BASIC_GPU2_25 | \
INTC_PENDING_BASIC_GPU2_30)
#define INTC_PENDING_BANK1_MASK (~((1 << 7) | (1 << 9) | (1 << 10) | \
(1 << 18) | (1 << 19)))
#define INTC_PENDING_BANK2_MASK (~((1 << 21) | (1 << 22) | (1 << 23) | \
(1 << 24) | (1 << 25) | (1 << 30)))
#define BANK1_START 8
#define BANK1_END (BANK1_START + 32 - 1)
#define BANK2_START (BANK1_START + 32)
#define BANK2_END (BANK2_START + 32 - 1)
#ifndef INTRNG
#define BANK3_START (BANK2_START + 32)
#define BANK3_END (BANK3_START + 32 - 1)
#endif
#define IS_IRQ_BASIC(n) (((n) >= 0) && ((n) < BANK1_START))
#define IS_IRQ_BANK1(n) (((n) >= BANK1_START) && ((n) <= BANK1_END))
#define IS_IRQ_BANK2(n) (((n) >= BANK2_START) && ((n) <= BANK2_END))
#ifndef INTRNG
#define ID_IRQ_BCM2836(n) (((n) >= BANK3_START) && ((n) <= BANK3_END))
#endif
#define IRQ_BANK1(n) ((n) - BANK1_START)
#define IRQ_BANK2(n) ((n) - BANK2_START)
#ifdef DEBUG
#define dprintf(fmt, args...) printf(fmt, ##args)
#else
#define dprintf(fmt, args...)
#endif
#ifdef INTRNG
#define BCM_INTC_NIRQS 72 /* 8 + 32 + 32 */
struct bcm_intc_irqsrc {
struct intr_irqsrc bii_isrc;
u_int bii_irq;
uint16_t bii_disable_reg;
uint16_t bii_enable_reg;
uint32_t bii_mask;
};
#endif
struct bcm_intc_softc {
device_t sc_dev;
struct resource * intc_res;
bus_space_tag_t intc_bst;
bus_space_handle_t intc_bsh;
#ifdef INTRNG
struct resource * intc_irq_res;
void * intc_irq_hdl;
struct bcm_intc_irqsrc intc_isrcs[BCM_INTC_NIRQS];
#endif
};
static struct bcm_intc_softc *bcm_intc_sc = NULL;
#define intc_read_4(_sc, reg) \
bus_space_read_4((_sc)->intc_bst, (_sc)->intc_bsh, (reg))
#define intc_write_4(_sc, reg, val) \
bus_space_write_4((_sc)->intc_bst, (_sc)->intc_bsh, (reg), (val))
#ifdef INTRNG
static inline void
bcm_intc_isrc_mask(struct bcm_intc_softc *sc, struct bcm_intc_irqsrc *bii)
{
intc_write_4(sc, bii->bii_disable_reg, bii->bii_mask);
}
static inline void
bcm_intc_isrc_unmask(struct bcm_intc_softc *sc, struct bcm_intc_irqsrc *bii)
{
intc_write_4(sc, bii->bii_enable_reg, bii->bii_mask);
}
static inline int
bcm2835_intc_active_intr(struct bcm_intc_softc *sc)
{
uint32_t pending, pending_gpu;
pending = intc_read_4(sc, INTC_PENDING_BASIC) & INTC_PENDING_BASIC_MASK;
if (pending == 0)
return (-1);
if (pending & INTC_PENDING_BASIC_ARM)
return (ffs(pending) - 1);
if (pending & INTC_PENDING_BASIC_GPU1_MASK) {
if (pending & INTC_PENDING_BASIC_GPU1_7)
return (BANK1_START + 7);
if (pending & INTC_PENDING_BASIC_GPU1_9)
return (BANK1_START + 9);
if (pending & INTC_PENDING_BASIC_GPU1_10)
return (BANK1_START + 10);
if (pending & INTC_PENDING_BASIC_GPU1_18)
return (BANK1_START + 18);
if (pending & INTC_PENDING_BASIC_GPU1_19)
return (BANK1_START + 19);
}
if (pending & INTC_PENDING_BASIC_GPU2_MASK) {
if (pending & INTC_PENDING_BASIC_GPU2_21)
return (BANK2_START + 21);
if (pending & INTC_PENDING_BASIC_GPU2_22)
return (BANK2_START + 22);
if (pending & INTC_PENDING_BASIC_GPU2_23)
return (BANK2_START + 23);
if (pending & INTC_PENDING_BASIC_GPU2_24)
return (BANK2_START + 24);
if (pending & INTC_PENDING_BASIC_GPU2_25)
return (BANK2_START + 25);
if (pending & INTC_PENDING_BASIC_GPU2_30)
return (BANK2_START + 30);
}
if (pending & INTC_PENDING_BASIC_GPU1_PEND) {
pending_gpu = intc_read_4(sc, INTC_PENDING_BANK1);
pending_gpu &= INTC_PENDING_BANK1_MASK;
if (pending_gpu != 0)
return (BANK1_START + ffs(pending_gpu) - 1);
}
if (pending & INTC_PENDING_BASIC_GPU2_PEND) {
pending_gpu = intc_read_4(sc, INTC_PENDING_BANK2);
pending_gpu &= INTC_PENDING_BANK2_MASK;
if (pending_gpu != 0)
return (BANK2_START + ffs(pending_gpu) - 1);
}
return (-1); /* It shouldn't end here, but it's hardware. */
}
static int
bcm2835_intc_intr(void *arg)
{
int irq, num;
struct bcm_intc_softc *sc = arg;
for (num = 0; ; num++) {
irq = bcm2835_intc_active_intr(sc);
if (irq == -1)
break;
if (intr_isrc_dispatch(&sc->intc_isrcs[irq].bii_isrc,
curthread->td_intr_frame) != 0) {
bcm_intc_isrc_mask(sc, &sc->intc_isrcs[irq]);
device_printf(sc->sc_dev, "Stray irq %u disabled\n",
irq);
}
arm_irq_memory_barrier(0); /* XXX */
}
if (num == 0)
device_printf(sc->sc_dev, "Spurious interrupt detected\n");
return (FILTER_HANDLED);
}
static void
bcm_intc_enable_intr(device_t dev, struct intr_irqsrc *isrc)
{
struct bcm_intc_irqsrc *bii = (struct bcm_intc_irqsrc *)isrc;
arm_irq_memory_barrier(bii->bii_irq);
bcm_intc_isrc_unmask(device_get_softc(dev), bii);
}
static void
bcm_intc_disable_intr(device_t dev, struct intr_irqsrc *isrc)
{
bcm_intc_isrc_mask(device_get_softc(dev),
(struct bcm_intc_irqsrc *)isrc);
}
static int
bcm_intc_map_intr(device_t dev, struct intr_map_data *data,
struct intr_irqsrc **isrcp)
{
u_int irq;
struct intr_map_data_fdt *daf;
struct bcm_intc_softc *sc;
bool valid;
if (data->type != INTR_MAP_DATA_FDT)
return (ENOTSUP);
daf = (struct intr_map_data_fdt *)data;
if (daf->ncells == 1)
irq = daf->cells[0];
else if (daf->ncells == 2) {
valid = true;
switch (daf->cells[0]) {
case 0:
irq = daf->cells[1];
if (irq >= BANK1_START)
valid = false;
break;
case 1:
irq = daf->cells[1] + BANK1_START;
if (irq > BANK1_END)
valid = false;
break;
case 2:
irq = daf->cells[1] + BANK2_START;
if (irq > BANK2_END)
valid = false;
break;
default:
valid = false;
break;
}
if (!valid) {
device_printf(dev,
"invalid IRQ config: bank=%d, irq=%d\n",
daf->cells[0], daf->cells[1]);
return (EINVAL);
}
}
else
return (EINVAL);
if (irq >= BCM_INTC_NIRQS)
return (EINVAL);
sc = device_get_softc(dev);
*isrcp = &sc->intc_isrcs[irq].bii_isrc;
return (0);
}
static void
bcm_intc_pre_ithread(device_t dev, struct intr_irqsrc *isrc)
{
bcm_intc_disable_intr(dev, isrc);
}
static void
bcm_intc_post_ithread(device_t dev, struct intr_irqsrc *isrc)
{
bcm_intc_enable_intr(dev, isrc);
}
static void
bcm_intc_post_filter(device_t dev, struct intr_irqsrc *isrc)
{
}
static int
bcm_intc_pic_register(struct bcm_intc_softc *sc, intptr_t xref)
{
struct bcm_intc_irqsrc *bii;
int error;
uint32_t irq;
const char *name;
name = device_get_nameunit(sc->sc_dev);
for (irq = 0; irq < BCM_INTC_NIRQS; irq++) {
bii = &sc->intc_isrcs[irq];
bii->bii_irq = irq;
if (IS_IRQ_BASIC(irq)) {
bii->bii_disable_reg = INTC_DISABLE_BASIC;
bii->bii_enable_reg = INTC_ENABLE_BASIC;
bii->bii_mask = 1 << irq;
} else if (IS_IRQ_BANK1(irq)) {
bii->bii_disable_reg = INTC_DISABLE_BANK1;
bii->bii_enable_reg = INTC_ENABLE_BANK1;
bii->bii_mask = 1 << IRQ_BANK1(irq);
} else if (IS_IRQ_BANK2(irq)) {
bii->bii_disable_reg = INTC_DISABLE_BANK2;
bii->bii_enable_reg = INTC_ENABLE_BANK2;
bii->bii_mask = 1 << IRQ_BANK2(irq);
} else
return (ENXIO);
error = intr_isrc_register(&bii->bii_isrc, sc->sc_dev, 0,
"%s,%u", name, irq);
if (error != 0)
return (error);
}
if (intr_pic_register(sc->sc_dev, xref) == NULL)
return (ENXIO);
return (0);
}
#endif
static int
bcm_intc_probe(device_t dev)
{
if (!ofw_bus_status_okay(dev))
return (ENXIO);
if (!ofw_bus_is_compatible(dev, "broadcom,bcm2835-armctrl-ic") &&
!ofw_bus_is_compatible(dev, "brcm,bcm2836-armctrl-ic"))
return (ENXIO);
device_set_desc(dev, "BCM2835 Interrupt Controller");
return (BUS_PROBE_DEFAULT);
}
static int
bcm_intc_attach(device_t dev)
{
struct bcm_intc_softc *sc = device_get_softc(dev);
int rid = 0;
#ifdef INTRNG
intptr_t xref;
#endif
sc->sc_dev = dev;
if (bcm_intc_sc)
return (ENXIO);
sc->intc_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE);
if (sc->intc_res == NULL) {
device_printf(dev, "could not allocate memory resource\n");
return (ENXIO);
}
#ifdef INTRNG
xref = OF_xref_from_node(ofw_bus_get_node(dev));
if (bcm_intc_pic_register(sc, xref) != 0) {
bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->intc_res);
device_printf(dev, "could not register PIC\n");
return (ENXIO);
}
rid = 0;
sc->intc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
RF_ACTIVE);
if (sc->intc_irq_res == NULL) {
if (intr_pic_claim_root(dev, xref, bcm2835_intc_intr, sc, 0) != 0) {
/* XXX clean up */
device_printf(dev, "could not set PIC as a root\n");
return (ENXIO);
}
} else {
if (bus_setup_intr(dev, sc->intc_irq_res, INTR_TYPE_CLK,
bcm2835_intc_intr, NULL, sc, &sc->intc_irq_hdl)) {
/* XXX clean up */
device_printf(dev, "could not setup irq handler\n");
return (ENXIO);
}
}
#endif
sc->intc_bst = rman_get_bustag(sc->intc_res);
sc->intc_bsh = rman_get_bushandle(sc->intc_res);
bcm_intc_sc = sc;
return (0);
}
static device_method_t bcm_intc_methods[] = {
DEVMETHOD(device_probe, bcm_intc_probe),
DEVMETHOD(device_attach, bcm_intc_attach),
#ifdef INTRNG
DEVMETHOD(pic_disable_intr, bcm_intc_disable_intr),
DEVMETHOD(pic_enable_intr, bcm_intc_enable_intr),
DEVMETHOD(pic_map_intr, bcm_intc_map_intr),
DEVMETHOD(pic_post_filter, bcm_intc_post_filter),
DEVMETHOD(pic_post_ithread, bcm_intc_post_ithread),
DEVMETHOD(pic_pre_ithread, bcm_intc_pre_ithread),
#endif
{ 0, 0 }
};
static driver_t bcm_intc_driver = {
"intc",
bcm_intc_methods,
sizeof(struct bcm_intc_softc),
};
static devclass_t bcm_intc_devclass;
EARLY_DRIVER_MODULE(intc, simplebus, bcm_intc_driver, bcm_intc_devclass,
0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE);
#ifndef INTRNG
int
arm_get_next_irq(int last_irq)
{
struct bcm_intc_softc *sc = bcm_intc_sc;
uint32_t pending;
int32_t irq = last_irq + 1;
#ifdef SOC_BCM2836
int ret;
#endif
/* Sanity check */
if (irq < 0)
irq = 0;
#ifdef SOC_BCM2836
if ((ret = bcm2836_get_next_irq(irq)) < 0)
return (-1);
if (ret != BCM2836_GPU_IRQ)
return (ret + BANK3_START);
#endif
/* TODO: should we mask last_irq? */
if (irq < BANK1_START) {
pending = intc_read_4(sc, INTC_PENDING_BASIC);
if ((pending & 0xFF) == 0) {
irq = BANK1_START; /* skip to next bank */
} else do {
if (pending & (1 << irq))
return irq;
irq++;
} while (irq < BANK1_START);
}
if (irq < BANK2_START) {
pending = intc_read_4(sc, INTC_PENDING_BANK1);
if (pending == 0) {
irq = BANK2_START; /* skip to next bank */
} else do {
if (pending & (1 << IRQ_BANK1(irq)))
return irq;
irq++;
} while (irq < BANK2_START);
}
if (irq < BANK3_START) {
pending = intc_read_4(sc, INTC_PENDING_BANK2);
if (pending != 0) do {
if (pending & (1 << IRQ_BANK2(irq)))
return irq;
irq++;
} while (irq < BANK3_START);
}
return (-1);
}
void
arm_mask_irq(uintptr_t nb)
{
struct bcm_intc_softc *sc = bcm_intc_sc;
dprintf("%s: %d\n", __func__, nb);
if (IS_IRQ_BASIC(nb))
intc_write_4(sc, INTC_DISABLE_BASIC, (1 << nb));
else if (IS_IRQ_BANK1(nb))
intc_write_4(sc, INTC_DISABLE_BANK1, (1 << IRQ_BANK1(nb)));
else if (IS_IRQ_BANK2(nb))
intc_write_4(sc, INTC_DISABLE_BANK2, (1 << IRQ_BANK2(nb)));
#ifdef SOC_BCM2836
else if (ID_IRQ_BCM2836(nb))
bcm2836_mask_irq(nb - BANK3_START);
#endif
else
printf("arm_mask_irq: Invalid IRQ number: %d\n", nb);
}
void
arm_unmask_irq(uintptr_t nb)
{
struct bcm_intc_softc *sc = bcm_intc_sc;
dprintf("%s: %d\n", __func__, nb);
if (IS_IRQ_BASIC(nb))
intc_write_4(sc, INTC_ENABLE_BASIC, (1 << nb));
else if (IS_IRQ_BANK1(nb))
intc_write_4(sc, INTC_ENABLE_BANK1, (1 << IRQ_BANK1(nb)));
else if (IS_IRQ_BANK2(nb))
intc_write_4(sc, INTC_ENABLE_BANK2, (1 << IRQ_BANK2(nb)));
#ifdef SOC_BCM2836
else if (ID_IRQ_BCM2836(nb))
bcm2836_unmask_irq(nb - BANK3_START);
#endif
else
printf("arm_mask_irq: Invalid IRQ number: %d\n", nb);
}
#ifdef SMP
void
intr_pic_init_secondary(void)
{
}
#endif
#endif