freebsd-dev/sys/arm/mv/mv_ap806_sei.c
Kornel Duleba 986bbba9a6 arm/mv: Don't rely on firmware MSI mapping in ICU
On Armada8k boards various peripherals (e.g. USB) have interrupt lines
connected to on of the ICU interrupt controllers.
After an interrupt is detected it triggers MSI to a given address,
with a programmed value. This in turn triggers an SPI interrupt.
Normally MSI vector should be allocated by ICUs parent and set
during interrupt allocation.
Instead of doing that we relied on the ICU being pre-configured in firmware.
This worked with EDK2 and older versions of U-Boot, but in the newer
ones that is no longer the case.
Extend ICU msi-parents - GICP and SEI to support MSI interface
and use it during interrupt allocation.
This allows us to boot on Armada 7k/8k SoCs independent from the
firmware configuration and successfully use modern U-Boot + device tree.

For SATA interrupts we need to apply a WA previously done in firmware.
We have two SATA ports connected to one controller.
Each ports gets its own interrupt, but only one of them is
described in dts, also ahci_generic driver expects only one irq too.
Fix it by mapping both interrupts to the same MSI when one of them
is allocated, which allows us to use both SATA ports.

Reviewed by: mmel, mw
Obtained from: Semihalf
Sponsored by: Marvell
Differential Revision: https://reviews.freebsd.org/D28803
2021-07-20 23:24:42 +02:00

514 lines
13 KiB
C

/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2019 Michal Meloun <mmel@FreeBSD.org>
*
* 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 <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/bitset.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/rman.h>
#include <sys/lock.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <machine/bus.h>
#include <machine/intr.h>
#include <machine/resource.h>
#include <dev/fdt/simplebus.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include "msi_if.h"
#include "pic_if.h"
#define MV_AP806_SEI_LOCK(_sc) mtx_lock(&(_sc)->mtx)
#define MV_AP806_SEI_UNLOCK(_sc) mtx_unlock(&(_sc)->mtx)
#define MV_AP806_SEI_LOCK_INIT(_sc) mtx_init(&_sc->mtx, \
device_get_nameunit(_sc->dev), "mv_ap806_sei", MTX_DEF)
#define MV_AP806_SEI_LOCK_DESTROY(_sc) mtx_destroy(&_sc->mtx);
#define MV_AP806_SEI_ASSERT_LOCKED(_sc) mtx_assert(&_sc->mtx, MA_OWNED);
#define MV_AP806_SEI_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->mtx, MA_NOTOWNED);
#define GICP_SECR0 0x00
#define GICP_SECR1 0x04
#define GICP_SECR(i) (0x00 + (((i)/32) * 0x4))
#define GICP_SECR_BIT(i) ((i) % 32)
#define GICP_SEMR0 0x20
#define GICP_SEMR1 0x24
#define GICP_SEMR(i) (0x20 + (((i)/32) * 0x4))
#define GICP_SEMR_BIT(i) ((i) % 32)
#define MV_AP806_SEI_AP_FIRST 0
#define MV_AP806_SEI_AP_SIZE 21
#define MV_AP806_SEI_CP_FIRST 21
#define MV_AP806_SEI_CP_SIZE 43
#define MV_AP806_SEI_MAX_NIRQS (MV_AP806_SEI_AP_SIZE + MV_AP806_SEI_CP_SIZE)
#define MV_AP806_SEI_SETSPI_OFFSET 0x30
BITSET_DEFINE(sei_msi_bitmap, MV_AP806_SEI_CP_SIZE);
struct mv_ap806_sei_irqsrc {
struct intr_irqsrc isrc;
u_int irq;
};
struct mv_ap806_sei_softc {
device_t dev;
struct resource *mem_res;
struct resource *irq_res;
void *irq_ih;
struct mtx mtx;
struct mv_ap806_sei_irqsrc *isrcs;
struct sei_msi_bitmap msi_bitmap;
};
static struct ofw_compat_data compat_data[] = {
{"marvell,ap806-sei", 1},
{NULL, 0}
};
#define RD4(sc, reg) bus_read_4((sc)->mem_res, (reg))
#define WR4(sc, reg, val) bus_write_4((sc)->mem_res, (reg), (val))
static msi_alloc_msi_t mv_ap806_sei_alloc_msi;
static msi_release_msi_t mv_ap806_sei_release_msi;
static msi_map_msi_t mv_ap806_sei_map_msi;
static inline void
mv_ap806_sei_isrc_mask(struct mv_ap806_sei_softc *sc,
struct mv_ap806_sei_irqsrc *sisrc, uint32_t val)
{
uint32_t tmp;
int bit;
bit = GICP_SEMR_BIT(sisrc->irq);
MV_AP806_SEI_LOCK(sc);
tmp = RD4(sc, GICP_SEMR(sisrc->irq));
if (val != 0)
tmp |= 1 << bit;
else
tmp &= ~(1 << bit);
WR4(sc, GICP_SEMR(sisrc->irq), tmp);
MV_AP806_SEI_UNLOCK(sc);
}
static inline void
mv_ap806_sei_isrc_eoi(struct mv_ap806_sei_softc *sc,
struct mv_ap806_sei_irqsrc *sisrc)
{
WR4(sc, GICP_SECR(sisrc->irq), GICP_SECR_BIT(sisrc->irq));
}
static void
mv_ap806_sei_enable_intr(device_t dev, struct intr_irqsrc *isrc)
{
struct mv_ap806_sei_softc *sc;
struct mv_ap806_sei_irqsrc *sisrc;
sc = device_get_softc(dev);
sisrc = (struct mv_ap806_sei_irqsrc *)isrc;
mv_ap806_sei_isrc_mask(sc, sisrc, 0);
}
static void
mv_ap806_sei_disable_intr(device_t dev, struct intr_irqsrc *isrc)
{
struct mv_ap806_sei_softc *sc;
struct mv_ap806_sei_irqsrc *sisrc;
sc = device_get_softc(dev);
sisrc = (struct mv_ap806_sei_irqsrc *)isrc;
mv_ap806_sei_isrc_mask(sc, sisrc, 1);
}
static int
mv_ap806_sei_map(device_t dev, struct intr_map_data *data, u_int *irqp)
{
struct mv_ap806_sei_softc *sc;
struct intr_map_data_fdt *daf;
u_int irq;
sc = device_get_softc(dev);
if (data->type != INTR_MAP_DATA_FDT)
return (ENOTSUP);
daf = (struct intr_map_data_fdt *)data;
if (daf->ncells != 1)
return (EINVAL);
if (daf->cells[0] < MV_AP806_SEI_AP_FIRST ||
daf->cells[0] >= MV_AP806_SEI_AP_FIRST + MV_AP806_SEI_AP_SIZE)
return (EINVAL);
irq = daf->cells[0];
if (irqp != NULL)
*irqp = irq;
return(0);
}
static int
mv_ap806_sei_map_intr(device_t dev, struct intr_map_data *data,
struct intr_irqsrc **isrcp)
{
struct mv_ap806_sei_softc *sc;
u_int irq;
int rv;
sc = device_get_softc(dev);
rv = mv_ap806_sei_map(dev, data, &irq);
if (rv == 0)
*isrcp = &sc->isrcs[irq].isrc;
return (rv);
}
static int
mv_ap806_sei_setup_intr(device_t dev, struct intr_irqsrc *isrc,
struct resource *res, struct intr_map_data *data)
{
struct mv_ap806_sei_softc *sc;
struct mv_ap806_sei_irqsrc *sisrc;
u_int irq;
int rv;
sc = device_get_softc(dev);
sisrc = (struct mv_ap806_sei_irqsrc *)isrc;
if (data == NULL)
return (ENOTSUP);
rv = mv_ap806_sei_map(dev, data, &irq);
if (rv != 0)
return (rv);
if (irq != sisrc->irq)
return (EINVAL);
mv_ap806_sei_isrc_mask(sc, sisrc, 0);
return (0);
}
static int
mv_ap806_sei_teardown_intr(device_t dev, struct intr_irqsrc *isrc,
struct resource *res, struct intr_map_data *data)
{
struct mv_ap806_sei_softc *sc;
struct mv_ap806_sei_irqsrc *sisrc;
sc = device_get_softc(dev);
sisrc = (struct mv_ap806_sei_irqsrc *)isrc;
mv_ap806_sei_isrc_mask(sc, sisrc, 1);
return (0);
}
static void
mv_ap806_sei_pre_ithread(device_t dev, struct intr_irqsrc *isrc)
{
struct mv_ap806_sei_softc *sc;
struct mv_ap806_sei_irqsrc *sisrc;
sc = device_get_softc(dev);
sisrc = (struct mv_ap806_sei_irqsrc *)isrc;
mv_ap806_sei_isrc_mask(sc, sisrc, 1);
mv_ap806_sei_isrc_eoi(sc, sisrc);
}
static void
mv_ap806_sei_post_ithread(device_t dev, struct intr_irqsrc *isrc)
{
struct mv_ap806_sei_softc *sc;
struct mv_ap806_sei_irqsrc *sisrc;
sc = device_get_softc(dev);
sisrc = (struct mv_ap806_sei_irqsrc *)isrc;
mv_ap806_sei_isrc_mask(sc, sisrc, 1);
}
static void
mv_ap806_sei_post_filter(device_t dev, struct intr_irqsrc *isrc)
{
struct mv_ap806_sei_softc *sc;
struct mv_ap806_sei_irqsrc *sisrc;
sc = device_get_softc(dev);
sisrc = (struct mv_ap806_sei_irqsrc *)isrc;
mv_ap806_sei_isrc_mask(sc, sisrc, 1);
mv_ap806_sei_isrc_eoi(sc, sisrc);
}
/* ----------------------------------------------------------------------------
*
* B u s i n t e r f a c e
*/
static int
mv_ap806_sei_intr(void *arg)
{
struct mv_ap806_sei_softc *sc;
struct mv_ap806_sei_irqsrc *sirq;
struct trapframe *tf;
uint64_t cause;
u_int irq;
sc = (struct mv_ap806_sei_softc *)arg;
tf = curthread->td_intr_frame;
while (1) {
cause = RD4(sc, GICP_SECR1);
cause <<= 32;
cause |= RD4(sc, GICP_SECR0);
irq = ffsll(cause);
if (irq == 0) break;
irq--;
sirq = &sc->isrcs[irq];
if (intr_isrc_dispatch(&sirq->isrc, tf) != 0) {
mv_ap806_sei_isrc_mask(sc, sirq, 0);
mv_ap806_sei_isrc_eoi(sc, sirq);
device_printf(sc->dev,
"Stray irq %u disabled\n", irq);
}
}
return (FILTER_HANDLED);
}
static int
mv_ap806_sei_probe(device_t dev)
{
if (!ofw_bus_status_okay(dev))
return (ENXIO);
if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
return (ENXIO);
device_set_desc(dev, "Marvell SEI");
return (BUS_PROBE_DEFAULT);
}
static int
mv_ap806_sei_attach(device_t dev)
{
struct mv_ap806_sei_softc *sc;
phandle_t xref, node;
uint32_t irq;
const char *name;
int rv, rid;
sc = device_get_softc(dev);
sc->dev = dev;
node = ofw_bus_get_node(dev);
MV_AP806_SEI_LOCK_INIT(sc);
/* Allocate resources. */
rid = 0;
sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
RF_ACTIVE);
if (sc->mem_res == NULL) {
device_printf(dev, "Cannot allocate memory resources\n");
rv = ENXIO;
goto fail;
}
rid = 0;
sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE);
if (sc->irq_res == NULL) {
device_printf(dev, "Cannot allocate IRQ resources\n");
rv = ENXIO;
goto fail;
}
/* Mask all interrupts) */
WR4(sc, GICP_SEMR0, 0xFFFFFFFF);
WR4(sc, GICP_SEMR1, 0xFFFFFFFF);
/* Create all interrupt sources */
sc->isrcs = malloc(sizeof(*sc->isrcs) * MV_AP806_SEI_MAX_NIRQS,
M_DEVBUF, M_WAITOK | M_ZERO);
name = device_get_nameunit(sc->dev);
for (irq = 0; irq < MV_AP806_SEI_MAX_NIRQS; irq++) {
sc->isrcs[irq].irq = irq;
rv = intr_isrc_register(&sc->isrcs[irq].isrc,
sc->dev, 0, "%s,%u", name, irq);
if (rv != 0)
goto fail; /* XXX deregister ISRCs */
}
xref = OF_xref_from_node(node);;
if (intr_pic_register(dev, xref) == NULL) {
device_printf(dev, "Cannot register SEI\n");
rv = ENXIO;
goto fail;
}
if (bus_setup_intr(dev, sc->irq_res,INTR_TYPE_MISC | INTR_MPSAFE,
mv_ap806_sei_intr, NULL, sc, &sc->irq_ih)) {
device_printf(dev,
"Unable to register interrupt handler\n");
rv = ENXIO;
goto fail;
}
/*
* Bitmap of all IRQs.
* 1 - available, 0 - used.
*/
BIT_FILL(MV_AP806_SEI_CP_SIZE, &sc->msi_bitmap);
OF_device_register_xref(xref, dev);
return (0);
fail:
if (sc->irq_ih != NULL)
bus_teardown_intr(dev, sc->irq_res, sc->irq_ih);
if (sc->irq_res != NULL)
bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq_res);
if (sc->mem_res != NULL)
bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->mem_res);
MV_AP806_SEI_LOCK_DESTROY(sc);
return (ENXIO);
}
static int
mv_ap806_sei_detach(device_t dev)
{
return (EBUSY);
}
static int
mv_ap806_sei_alloc_msi(device_t dev, device_t child, int count, int maxcount,
device_t *pic, struct intr_irqsrc **srcs)
{
struct mv_ap806_sei_softc *sc;
int i, ret = 0, vector;
sc = device_get_softc(dev);
for (i = 0; i < count; i++) {
/*
* Find first available MSI vector represented by first set bit
* in the bitmap. BIT_FFS starts the count from 1,
* 0 means that nothing was found.
*/
vector = BIT_FFS_AT(MV_AP806_SEI_CP_SIZE, &sc->msi_bitmap, 0);
if (vector == 0) {
ret = ENOMEM;
i--;
goto fail;
}
vector--;
BIT_CLR(MV_AP806_SEI_CP_SIZE, vector, &sc->msi_bitmap);
vector += MV_AP806_SEI_CP_FIRST;
srcs[i] = &sc->isrcs[vector].isrc;
}
return (ret);
fail:
mv_ap806_sei_release_msi(dev, child, i + 1, srcs);
return (ret);
}
static int
mv_ap806_sei_release_msi(device_t dev, device_t child, int count, struct intr_irqsrc **srcs)
{
struct mv_ap806_sei_softc *sc;
int i;
sc = device_get_softc(dev);
for (i = 0; i < count; i++) {
BIT_SET(MV_AP806_SEI_CP_SIZE,
srcs[i]->isrc_irq - MV_AP806_SEI_CP_FIRST,
&sc->msi_bitmap);
}
return (0);
}
static int
mv_ap806_sei_map_msi(device_t dev, device_t child, struct intr_irqsrc *isrc,
uint64_t *addr, uint32_t *data)
{
struct mv_ap806_sei_softc *sc;
sc = device_get_softc(dev);
*addr = rman_get_start(sc->mem_res) + MV_AP806_SEI_SETSPI_OFFSET;
*data = isrc->isrc_irq;
return (0);
}
static device_method_t mv_ap806_sei_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, mv_ap806_sei_probe),
DEVMETHOD(device_attach, mv_ap806_sei_attach),
DEVMETHOD(device_detach, mv_ap806_sei_detach),
/* Interrupt controller interface */
DEVMETHOD(pic_disable_intr, mv_ap806_sei_disable_intr),
DEVMETHOD(pic_enable_intr, mv_ap806_sei_enable_intr),
DEVMETHOD(pic_map_intr, mv_ap806_sei_map_intr),
DEVMETHOD(pic_setup_intr, mv_ap806_sei_setup_intr),
DEVMETHOD(pic_teardown_intr, mv_ap806_sei_teardown_intr),
DEVMETHOD(pic_post_filter, mv_ap806_sei_post_filter),
DEVMETHOD(pic_post_ithread, mv_ap806_sei_post_ithread),
DEVMETHOD(pic_pre_ithread, mv_ap806_sei_pre_ithread),
/* MSI interface */
DEVMETHOD(msi_alloc_msi, mv_ap806_sei_alloc_msi),
DEVMETHOD(msi_release_msi, mv_ap806_sei_release_msi),
DEVMETHOD(msi_map_msi, mv_ap806_sei_map_msi),
DEVMETHOD_END
};
static devclass_t mv_ap806_sei_devclass;
static driver_t mv_ap806_sei_driver = {
"mv_ap806_sei",
mv_ap806_sei_methods,
sizeof(struct mv_ap806_sei_softc),
};
EARLY_DRIVER_MODULE(mv_ap806_sei, simplebus, mv_ap806_sei_driver,
mv_ap806_sei_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE);