The branch main has been updated by mw:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=986bbba9a6994155ee651ce2df2f6fd9a67e13ea

commit 986bbba9a6994155ee651ce2df2f6fd9a67e13ea
Author:     Kornel Duleba <min...@semihalf.com>
AuthorDate: 2021-02-17 15:28:46 +0000
Commit:     Marcin Wojtas <m...@freebsd.org>
CommitDate: 2021-07-20 21:24:42 +0000

    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
---
 sys/arm/mv/mv_ap806_gicp.c | 184 ++++++++++++++++++++++++++++++++++++++-------
 sys/arm/mv/mv_ap806_sei.c  | 104 ++++++++++++++++++++++++-
 sys/arm/mv/mv_cp110_icu.c  | 145 +++++++++++++++++++++++++++++++----
 3 files changed, 386 insertions(+), 47 deletions(-)

diff --git a/sys/arm/mv/mv_ap806_gicp.c b/sys/arm/mv/mv_ap806_gicp.c
index 0a1a69707956..ab0c540bf3f8 100644
--- a/sys/arm/mv/mv_ap806_gicp.c
+++ b/sys/arm/mv/mv_ap806_gicp.c
@@ -34,6 +34,7 @@ __FBSDID("$FreeBSD$");
 #include <sys/systm.h>
 #include <sys/bus.h>
 
+#include <sys/bitset.h>
 #include <sys/kernel.h>
 #include <sys/module.h>
 #include <sys/rman.h>
@@ -49,10 +50,18 @@ __FBSDID("$FreeBSD$");
 #include <dev/ofw/ofw_bus.h>
 #include <dev/ofw/ofw_bus_subr.h>
 
+#include <arm/arm/gic_common.h>
+
+#include <dt-bindings/interrupt-controller/irq.h>
+
+#include "msi_if.h"
 #include "pic_if.h"
 
 #define        MV_AP806_GICP_MAX_NIRQS 207
 
+MALLOC_DECLARE(M_GICP);
+MALLOC_DEFINE(M_GICP, "gicp", "Marvell gicp driver");
+
 struct mv_ap806_gicp_softc {
        device_t                dev;
        device_t                parent;
@@ -61,6 +70,9 @@ struct mv_ap806_gicp_softc {
        ssize_t                 spi_ranges_cnt;
        uint32_t                *spi_ranges;
        struct intr_map_data_fdt *parent_map_data;
+
+       ssize_t                 msi_bitmap_size; /* Nr of bits in the bitmap. */
+       BITSET_DEFINE_VAR()     *msi_bitmap;
 };
 
 static struct ofw_compat_data compat_data[] = {
@@ -71,6 +83,10 @@ static struct ofw_compat_data compat_data[] = {
 #define        RD4(sc, reg)            bus_read_4((sc)->res, (reg))
 #define        WR4(sc, reg, val)       bus_write_4((sc)->res, (reg), (val))
 
+static msi_alloc_msi_t mv_ap806_gicp_alloc_msi;
+static msi_release_msi_t mv_ap806_gicp_release_msi;
+static msi_map_msi_t mv_ap806_gicp_map_msi;
+
 static int
 mv_ap806_gicp_probe(device_t dev)
 {
@@ -90,6 +106,7 @@ mv_ap806_gicp_attach(device_t dev)
 {
        struct mv_ap806_gicp_softc *sc;
        phandle_t node, xref, intr_parent;
+       int i, rid;
 
        sc = device_get_softc(dev);
        sc->dev = dev;
@@ -107,9 +124,28 @@ mv_ap806_gicp_attach(device_t dev)
                return (ENXIO);
        }
 
+       rid = 0;
+       sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE);
+       if (sc->res == NULL) {
+               device_printf(dev, "cannot allocate resources for device\n");
+               return (ENXIO);
+        }
+
        sc->spi_ranges_cnt = OF_getencprop_alloc_multi(node, 
"marvell,spi-ranges",
            sizeof(*sc->spi_ranges), (void **)&sc->spi_ranges);
 
+       sc->msi_bitmap_size = 0;
+       for (i = 0; i < sc->spi_ranges_cnt; i += 2)
+               sc->msi_bitmap_size += sc->spi_ranges[i + 1];
+
+       /*
+        * Create a bitmap of all MSIs that we have.
+        * Each has a correspoding SPI in the GIC.
+        * It will be used to dynamically allocate IRQs when requested.
+        */
+       sc->msi_bitmap = BITSET_ALLOC(sc->msi_bitmap_size, M_GICP, M_WAITOK);
+       BIT_FILL(sc->msi_bitmap_size, sc->msi_bitmap);  /* 1 - available, 0 - 
used. */
+
        xref = OF_xref_from_node(node);
        if (intr_pic_register(dev, xref) == NULL) {
                device_printf(dev, "Cannot register GICP\n");
@@ -131,38 +167,58 @@ mv_ap806_gicp_detach(device_t dev)
        return (EBUSY);
 }
 
+static uint32_t
+mv_ap806_gicp_msi_to_spi(struct mv_ap806_gicp_softc *sc, int irq)
+{
+       int i;
+
+       for (i = 0; i < sc->spi_ranges_cnt; i += 2) {
+               if (irq < sc->spi_ranges[i + 1]) {
+                       irq += sc->spi_ranges[i];
+                       break;
+               }
+               irq -= sc->spi_ranges[i + 1];
+       }
+
+       return (irq - GIC_FIRST_SPI);
+}
+
+static uint32_t
+mv_ap806_gicp_irq_to_msi(struct mv_ap806_gicp_softc *sc, int irq)
+{
+       int i;
+
+       for (i = 0; i < sc->spi_ranges_cnt; i += 2) {
+               if (irq >= sc->spi_ranges[i] &&
+                   irq - sc->spi_ranges[i] < sc->spi_ranges[i + 1]) {
+                       irq -= sc->spi_ranges[i];
+                       break;
+               }
+       }
+
+       return (irq);
+}
+
 static struct intr_map_data *
 mv_ap806_gicp_convert_map_data(struct mv_ap806_gicp_softc *sc,
     struct intr_map_data *data)
 {
        struct intr_map_data_fdt *daf;
-       uint32_t i, irq_num, irq_type;
+       uint32_t irq_num;
 
        daf = (struct intr_map_data_fdt *)data;
        if (daf->ncells != 2)
                return (NULL);
 
        irq_num = daf->cells[0];
-       irq_type = daf->cells[1];
        if (irq_num >= MV_AP806_GICP_MAX_NIRQS)
                return (NULL);
 
        /* Construct GIC compatible mapping. */
        sc->parent_map_data->ncells = 3;
        sc->parent_map_data->cells[0] = 0; /* SPI */
-       sc->parent_map_data->cells[2] = irq_type;
-
-       /* Map the interrupt number to SPI number */
-       for (i = 0; i < sc->spi_ranges_cnt; i += 2) {
-               if (irq_num < sc->spi_ranges[i + 1]) {
-                       irq_num += sc->spi_ranges[i];
-                       break;
-               }
-
-               irq_num -= sc->spi_ranges[i];
-       }
-
-       sc->parent_map_data->cells[1] = irq_num - 32;
+       sc->parent_map_data->cells[1] = mv_ap806_gicp_msi_to_spi(sc, irq_num);
+       sc->parent_map_data->cells[2] = IRQ_TYPE_LEVEL_HIGH;
 
        return ((struct intr_map_data *)sc->parent_map_data);
 }
@@ -205,21 +261,9 @@ static int
 mv_ap806_gicp_map_intr(device_t dev, struct intr_map_data *data,
     struct intr_irqsrc **isrcp)
 {
-       struct mv_ap806_gicp_softc *sc;
-       int ret;
-
-       sc = device_get_softc(dev);
-
-       if (data->type != INTR_MAP_DATA_FDT)
-               return (ENOTSUP);
 
-       data = mv_ap806_gicp_convert_map_data(sc, data);
-       if (data == NULL)
-               return (EINVAL);
-
-       ret = PIC_MAP_INTR(sc->parent, data, isrcp);
-       (*isrcp)->isrc_dev = sc->dev;
-       return(ret);
+       panic("%s: MSI interface has to be used to map an interrupt.\n",
+           __func__);
 }
 
 static int
@@ -295,6 +339,83 @@ mv_ap806_gicp_post_filter(device_t dev, struct intr_irqsrc 
*isrc)
        PIC_POST_FILTER(sc->parent, isrc);
 }
 
+static int
+mv_ap806_gicp_alloc_msi(device_t dev, device_t child, int count, int maxcount,
+    device_t *pic, struct intr_irqsrc **srcs)
+{
+       struct mv_ap806_gicp_softc *sc;
+       int i, ret, vector;
+
+       sc = device_get_softc(dev);
+
+       for (i = 0; i < count; i++) {
+               /*
+                * Find first available 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(sc->msi_bitmap_size, sc->msi_bitmap);
+               if (vector == 0) {
+                       ret = ENOMEM;
+                       i--;
+                       goto fail;
+               }
+               vector--;
+               BIT_CLR(sc->msi_bitmap_size, vector, sc->msi_bitmap);
+
+               /* Create GIC compatible SPI interrupt description. */
+               sc->parent_map_data->ncells = 3;
+               sc->parent_map_data->cells[0] = 0;      /* SPI */
+               sc->parent_map_data->cells[1] = mv_ap806_gicp_msi_to_spi(sc, 
vector);
+               sc->parent_map_data->cells[2] = IRQ_TYPE_LEVEL_HIGH;
+
+               ret = PIC_MAP_INTR(sc->parent,
+                   (struct intr_map_data *)sc->parent_map_data,
+                   &srcs[i]);
+               if (ret != 0)
+                       goto fail;
+
+               srcs[i]->isrc_dev = dev;
+       }
+
+       return (0);
+fail:
+       mv_ap806_gicp_release_msi(dev, child, i + 1, srcs);
+       return (ret);
+}
+
+static int
+mv_ap806_gicp_release_msi(device_t dev, device_t child, int count,
+    struct intr_irqsrc **srcs)
+{
+       struct mv_ap806_gicp_softc *sc;
+       int i;
+
+       sc = device_get_softc(dev);
+
+       for (i = 0; i < count; i++) {
+               BIT_SET(sc->msi_bitmap_size,
+                   mv_ap806_gicp_irq_to_msi(sc, srcs[i]->isrc_irq),
+                   sc->msi_bitmap);
+       }
+
+       return (0);
+}
+
+static int
+mv_ap806_gicp_map_msi(device_t dev, device_t child, struct intr_irqsrc *isrc,
+    uint64_t *addr, uint32_t *data)
+{
+       struct mv_ap806_gicp_softc *sc;
+
+       sc = device_get_softc(dev);
+
+       *addr = rman_get_start(sc->res);
+       *data = mv_ap806_gicp_irq_to_msi(sc, isrc->isrc_irq);
+
+       return (0);
+}
+
 static device_method_t mv_ap806_gicp_methods[] = {
        /* Device interface */
        DEVMETHOD(device_probe,         mv_ap806_gicp_probe),
@@ -313,6 +434,11 @@ static device_method_t mv_ap806_gicp_methods[] = {
        DEVMETHOD(pic_post_ithread,     mv_ap806_gicp_post_ithread),
        DEVMETHOD(pic_pre_ithread,      mv_ap806_gicp_pre_ithread),
 
+       /* MSI interface */
+       DEVMETHOD(msi_alloc_msi,        mv_ap806_gicp_alloc_msi),
+       DEVMETHOD(msi_release_msi,      mv_ap806_gicp_release_msi),
+       DEVMETHOD(msi_map_msi,          mv_ap806_gicp_map_msi),
+
        DEVMETHOD_END
 };
 
diff --git a/sys/arm/mv/mv_ap806_sei.c b/sys/arm/mv/mv_ap806_sei.c
index 5022e6765d0a..fad57dcad17f 100644
--- a/sys/arm/mv/mv_ap806_sei.c
+++ b/sys/arm/mv/mv_ap806_sei.c
@@ -32,6 +32,8 @@ __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>
@@ -48,6 +50,7 @@ __FBSDID("$FreeBSD$");
 #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)
@@ -58,7 +61,6 @@ __FBSDID("$FreeBSD$");
 #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        MV_AP806_SEI_MAX_NIRQS  64
 #define GICP_SECR0             0x00
 #define GICP_SECR1             0x04
 #define GICP_SECR(i)           (0x00  + (((i)/32) * 0x4))
@@ -68,6 +70,16 @@ __FBSDID("$FreeBSD$");
 #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;
@@ -81,6 +93,8 @@ struct mv_ap806_sei_softc {
        struct mtx              mtx;
 
        struct mv_ap806_sei_irqsrc *isrcs;
+
+       struct sei_msi_bitmap   msi_bitmap;
 };
 
 static struct ofw_compat_data compat_data[] = {
@@ -91,6 +105,10 @@ static struct ofw_compat_data compat_data[] = {
 #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)
@@ -152,8 +170,13 @@ mv_ap806_sei_map(device_t dev, struct intr_map_data *data, 
u_int *irqp)
                return (ENOTSUP);
 
        daf = (struct intr_map_data_fdt *)data;
-       if (daf->ncells != 1 || daf->cells[0] >= MV_AP806_SEI_MAX_NIRQS)
+       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;
@@ -361,6 +384,12 @@ mv_ap806_sei_attach(device_t dev)
                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);
 
@@ -382,6 +411,72 @@ 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),
@@ -398,6 +493,11 @@ static device_method_t mv_ap806_sei_methods[] = {
        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
 };
 
diff --git a/sys/arm/mv/mv_cp110_icu.c b/sys/arm/mv/mv_cp110_icu.c
index 12dd6989e339..c783043ab1df 100644
--- a/sys/arm/mv/mv_cp110_icu.c
+++ b/sys/arm/mv/mv_cp110_icu.c
@@ -50,7 +50,12 @@ __FBSDID("$FreeBSD$");
 #include <dev/ofw/ofw_bus_subr.h>
 
 #include <dt-bindings/interrupt-controller/irq.h>
+
 #include "pic_if.h"
+#include "msi_if.h"
+
+#define        ICU_TYPE_NSR            1
+#define        ICU_TYPE_SEI            2
 
 #define        ICU_GRP_NSR             0x0
 #define        ICU_GRP_SR              0x1
@@ -61,19 +66,28 @@ __FBSDID("$FreeBSD$");
 #define        ICU_SETSPI_NSR_AH       0x14
 #define        ICU_CLRSPI_NSR_AL       0x18
 #define        ICU_CLRSPI_NSR_AH       0x1c
+#define        ICU_SETSPI_SEI_AL       0x50
+#define        ICU_SETSPI_SEI_AH       0x54
 #define        ICU_INT_CFG(x)  (0x100 + (x) * 4)
 #define         ICU_INT_ENABLE         (1 << 24)
 #define         ICU_INT_EDGE           (1 << 28)
 #define         ICU_INT_GROUP_SHIFT    29
 #define         ICU_INT_MASK           0x3ff
 
+#define        ICU_INT_SATA0           109
+#define        ICU_INT_SATA1           107
+
 #define        MV_CP110_ICU_MAX_NIRQS  207
 
+#define        MV_CP110_ICU_CLRSPI_OFFSET      0x8
+
 struct mv_cp110_icu_softc {
        device_t                dev;
        device_t                parent;
        struct resource         *res;
        struct intr_map_data_fdt *parent_map_data;
+       bool                    initialized;
+       int                     type;
 };
 
 static struct resource_spec mv_cp110_icu_res_spec[] = {
@@ -82,8 +96,8 @@ static struct resource_spec mv_cp110_icu_res_spec[] = {
 };
 
 static struct ofw_compat_data compat_data[] = {
-       {"marvell,cp110-icu-nsr",       1},
-       {"marvell,cp110-icu-sei",       2},
+       {"marvell,cp110-icu-nsr",       ICU_TYPE_NSR},
+       {"marvell,cp110-icu-sei",       ICU_TYPE_SEI},
        {NULL,                          0}
 };
 
@@ -109,10 +123,14 @@ mv_cp110_icu_attach(device_t dev)
 {
        struct mv_cp110_icu_softc *sc;
        phandle_t node, msi_parent;
+       uint32_t reg, icu_grp;
+       int i;
 
        sc = device_get_softc(dev);
        sc->dev = dev;
        node = ofw_bus_get_node(dev);
+       sc->type = (int)ofw_bus_search_compatible(dev, compat_data)->ocd_data;
+       sc->initialized = false;
 
        if (OF_getencprop(node, "msi-parent", &msi_parent,
            sizeof(phandle_t)) <= 0) {
@@ -134,10 +152,20 @@ mv_cp110_icu_attach(device_t dev)
                goto fail;
        }
 
-       /* Allocate GICP compatible mapping entry (2 cells) */
+       /* Allocate GICP/SEI compatible mapping entry (2 cells) */
        sc->parent_map_data = (struct intr_map_data_fdt *)intr_alloc_map_data(
            INTR_MAP_DATA_FDT, sizeof(struct intr_map_data_fdt) +
            + 3 * sizeof(phandle_t), M_WAITOK | M_ZERO);
+
+       /* Clear any previous mapping done by firmware. */
+       for (i = 0; i < MV_CP110_ICU_MAX_NIRQS; i++) {
+               reg = RD4(sc, ICU_INT_CFG(i));
+               icu_grp = reg >> ICU_INT_GROUP_SHIFT;
+
+               if (icu_grp == ICU_GRP_NSR || icu_grp == ICU_GRP_SEI)
+                       WR4(sc, ICU_INT_CFG(i), 0);
+       }
+
        return (0);
 
 fail:
@@ -154,15 +182,17 @@ mv_cp110_icu_convert_map_data(struct mv_cp110_icu_softc 
*sc, struct intr_map_dat
        daf = (struct intr_map_data_fdt *)data;
        if (daf->ncells != 2)
                return (NULL);
+
        irq_no = daf->cells[0];
-       irq_type = daf->cells[1];
        if (irq_no >= MV_CP110_ICU_MAX_NIRQS)
                return (NULL);
+
+       irq_type = daf->cells[1];
        if (irq_type != IRQ_TYPE_LEVEL_HIGH &&
            irq_type != IRQ_TYPE_EDGE_RISING)
                return (NULL);
 
-       /* We rely on fact that ICU->GIC mapping is preset by bootstrap. */
+       /* ICU -> GICP/SEI mapping is set in mv_cp110_icu_map_intr. */
        reg = RD4(sc, ICU_INT_CFG(irq_no));
 
        /* Construct GICP compatible mapping. */
@@ -212,13 +242,40 @@ mv_cp110_icu_disable_intr(device_t dev, struct 
intr_irqsrc *isrc)
        PIC_DISABLE_INTR(sc->parent, isrc);
 }
 
+static void
+mv_cp110_icu_init(struct mv_cp110_icu_softc *sc, uint64_t addr)
+{
+
+       if (sc->initialized)
+               return;
+
+       switch (sc->type) {
+       case ICU_TYPE_NSR:
+               WR4(sc, ICU_SETSPI_NSR_AL, addr & UINT32_MAX);
+               WR4(sc, ICU_SETSPI_NSR_AH, (addr >> 32) & UINT32_MAX);
+               addr += MV_CP110_ICU_CLRSPI_OFFSET;
+               WR4(sc, ICU_CLRSPI_NSR_AL, addr & UINT32_MAX);
+               WR4(sc, ICU_CLRSPI_NSR_AH, (addr >> 32) & UINT32_MAX);
+               break;
+       case ICU_TYPE_SEI:
+               WR4(sc, ICU_SETSPI_SEI_AL, addr & UINT32_MAX);
+               WR4(sc, ICU_SETSPI_SEI_AH, (addr >> 32) & UINT32_MAX);
+               break;
+       default:
+               panic("Unkown ICU type.");
+       }
+
+       sc->initialized = true;
+}
+
 static int
 mv_cp110_icu_map_intr(device_t dev, struct intr_map_data *data,
     struct intr_irqsrc **isrcp)
 {
        struct mv_cp110_icu_softc *sc;
        struct intr_map_data_fdt *daf;
-       uint32_t reg, irq_no, irq_type;
+       uint32_t vector, irq_no, irq_type;
+       uint64_t addr;
        int ret;
 
        sc = device_get_softc(dev);
@@ -230,23 +287,62 @@ mv_cp110_icu_map_intr(device_t dev, struct intr_map_data 
*data,
        daf = (struct intr_map_data_fdt *)data;
        if (daf->ncells != 2)
                return (EINVAL);
+
        irq_no = daf->cells[0];
+       if (irq_no >= MV_CP110_ICU_MAX_NIRQS)
+               return (EINVAL);
+
        irq_type = daf->cells[1];
-       data = mv_cp110_icu_convert_map_data(sc, data);
-       if (data == NULL)
+       if (irq_type != IRQ_TYPE_LEVEL_HIGH &&
+           irq_type != IRQ_TYPE_EDGE_RISING)
                return (EINVAL);
 
-       reg = RD4(sc, ICU_INT_CFG(irq_no));
-       reg |= ICU_INT_ENABLE;
-       if (irq_type == IRQ_TYPE_LEVEL_HIGH)
-               reg &= ~ICU_INT_EDGE;
+       /*
+        * Allocate MSI vector.
+        * We don't use intr_alloc_msi wrapper, since it registers a new irq
+        * in the kernel. In our case irq was already added by the ofw code.
+        */
+       ret = MSI_ALLOC_MSI(sc->parent, dev, 1, 1, NULL, isrcp);
+       if (ret != 0)
+               return (ret);
+
+       ret = MSI_MAP_MSI(sc->parent, dev, *isrcp, &addr, &vector);
+       if (ret != 0)
+               goto fail;
+
+       mv_cp110_icu_init(sc, addr);
+       vector |= ICU_INT_ENABLE;
+
+       if (sc->type == ICU_TYPE_NSR)
+               vector |= ICU_GRP_NSR << ICU_INT_GROUP_SHIFT;
        else
-               reg |= ICU_INT_EDGE;
-       WR4(sc, ICU_INT_CFG(irq_no), reg);
+               vector |= ICU_GRP_SEI << ICU_INT_GROUP_SHIFT;
+
+       if (irq_type & IRQ_TYPE_EDGE_BOTH)
+               vector |= ICU_INT_EDGE;
+
+       WR4(sc, ICU_INT_CFG(irq_no), vector);
+
+       /*
+        * SATA controller has two ports, each gets its own interrupt.
+        * The problem is that only one irq is described in dts.
+        * Also ahci_generic driver supports only one irq per controller.
+        * As a workaround map both interrupts when one of them is allocated.
+        * This allows us to use both SATA ports.
+        */
+       if (irq_no == ICU_INT_SATA0)
+               WR4(sc, ICU_INT_CFG(ICU_INT_SATA1), vector);
+       if (irq_no == ICU_INT_SATA1)
+               WR4(sc, ICU_INT_CFG(ICU_INT_SATA0), vector);
 
-       ret = PIC_MAP_INTR(sc->parent, data, isrcp);
        (*isrcp)->isrc_dev = sc->dev;
        return (ret);
+
+fail:
+       if (*isrcp != NULL)
+               MSI_RELEASE_MSI(sc->parent, dev, 1, isrcp);
+
+       return (ret);
 }
 
 static int
@@ -254,13 +350,30 @@ mv_cp110_icu_deactivate_intr(device_t dev, struct 
intr_irqsrc *isrc,
     struct resource *res, struct intr_map_data *data)
 {
        struct mv_cp110_icu_softc *sc;
+       struct intr_map_data_fdt *daf;
+       int irq_no, ret;
+
+       if (data->type != INTR_MAP_DATA_FDT)
+               return (ENOTSUP);
 
        sc = device_get_softc(dev);
+       daf = (struct intr_map_data_fdt *)data;
+       if (daf->ncells != 2)
+               return (EINVAL);
+
+       irq_no = daf->cells[0];
        data = mv_cp110_icu_convert_map_data(sc, data);
        if (data == NULL)
                return (EINVAL);
 
-       return (PIC_DEACTIVATE_INTR(sc->parent, isrc, res, data));
+       /* Clear the mapping. */
+       WR4(sc, ICU_INT_CFG(irq_no), 0);
+
+       ret = PIC_DEACTIVATE_INTR(sc->parent, isrc, res, data);
+       if (ret != 0)
+               return (ret);
+
+       return (MSI_RELEASE_MSI(sc->parent, dev, 1, &isrc));
 }
 
 static int
_______________________________________________
dev-commits-src-main@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/dev-commits-src-main
To unsubscribe, send any mail to "dev-commits-src-main-unsubscr...@freebsd.org"

Reply via email to