diff --git a/sys/arm/mv/mv_ap806_gicp.c b/sys/arm/mv/mv_ap806_gicp.c --- a/sys/arm/mv/mv_ap806_gicp.c +++ b/sys/arm/mv/mv_ap806_gicp.c @@ -34,6 +34,7 @@ #include #include +#include #include #include #include @@ -49,10 +50,18 @@ #include #include +#include + +#include + +#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 @@ 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 @@ #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 @@ { 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 --- a/sys/arm/mv/mv_ap806_sei.c +++ b/sys/arm/mv/mv_ap806_sei.c @@ -32,6 +32,8 @@ #include #include #include + +#include #include #include #include @@ -48,6 +50,7 @@ #include #include +#include "msi_if.h" #include "pic_if.h" #define MV_AP806_SEI_LOCK(_sc) mtx_lock(&(_sc)->mtx) @@ -58,7 +61,6 @@ #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 @@ #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 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 @@ #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 @@ 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 @@ 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 @@ 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 @@ 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 --- a/sys/arm/mv/mv_cp110_icu.c +++ b/sys/arm/mv/mv_cp110_icu.c @@ -50,7 +50,12 @@ #include #include + #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 @@ #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 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 @@ { 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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