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 @@ -51,7 +52,15 @@ #include "pic_if.h" -#define MV_AP806_GICP_MAX_NIRQS 207 +#include "mv_intr.h" + +#define MV_AP806_GICP_MAX_NIRQS 207 + +#define MV_AP806_GICP_SETSPI_OFFSET 0x0 +#define MV_AP806_GICP_CLRSPI_OFFSET 0x8 + +MALLOC_DECLARE(M_GICP); +MALLOC_DEFINE(M_GICP, "gicp", "Marvell gicp driver"); struct mv_ap806_gicp_softc { device_t dev; @@ -61,6 +70,9 @@ ssize_t spi_ranges_cnt; uint32_t *spi_ranges; struct intr_map_data_fdt *parent_map_data; + + ssize_t spi_bitmap_size; /* Nr of bits in the bitmap. */ + BITSET_DEFINE_VAR() *spi_bitmap; }; static struct ofw_compat_data compat_data[] = { @@ -90,6 +102,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 +120,30 @@ 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(node, "marvell,spi-ranges", (void **)&sc->spi_ranges) / sizeof(*sc->spi_ranges); + sc->spi_bitmap_size = 0; + for (i = 0; i < sc->spi_ranges_cnt; i += 2) + sc->spi_bitmap_size += sc->spi_ranges[i + 1]; + + /* + * Create a bitmap of all SPI IRQs that are available for us. + * It will be used to dynamically allocate IRQs when requested. + * Note that a simple counter won't do, since we also need to be able + * to free and reuse IRQs. + */ + sc->spi_bitmap = BITSET_ALLOC(sc->spi_bitmap_size, M_GICP, M_WAITOK); + /* 1 - available, 0 - used. */ + BIT_FILL(sc->spi_bitmap_size, sc->spi_bitmap); + xref = OF_xref_from_node(node); if (intr_pic_register(dev, xref) == NULL) { device_printf(dev, "Cannot register GICP\n"); @@ -131,6 +165,33 @@ return (EBUSY); } +static int +mv_ap806_gicp_allocate_irq(struct mv_ap806_gicp_softc *sc, int *irq_no) +{ + int irq; + + /* + * Find first available IRQ represented by first set bit in the bitmap. + * BIT_FFS starts the count from 1, 0 means that nothing was found. + */ + irq = BIT_FFS(sc->spi_bitmap_size, sc->spi_bitmap); + if (irq == 0) + return (ENOMEM); + + irq--; + BIT_CLR(sc->spi_bitmap_size, irq, sc->spi_bitmap); + *irq_no = irq; + + return (0); +} + +static void +mv_ap806_gicp_free_irq(struct mv_ap806_gicp_softc *sc, int irq_no) +{ + + BIT_SET(sc->spi_bitmap_size, irq_no, sc->spi_bitmap); +} + static struct intr_map_data * mv_ap806_gicp_convert_map_data(struct mv_ap806_gicp_softc *sc, struct intr_map_data *data) @@ -139,7 +200,7 @@ uint32_t i, irq_num, irq_type; daf = (struct intr_map_data_fdt *)data; - if (daf->ncells != 2) + if (daf->ncells != MV_INTRMAP_NCELLS_ICU) return (NULL); irq_num = daf->cells[0]; @@ -206,6 +267,9 @@ struct intr_irqsrc **isrcp) { struct mv_ap806_gicp_softc *sc; + struct intr_map_data_fdt *daf; + int irq_no, irq_type; + vm_paddr_t paddr; int ret; sc = device_get_softc(dev); @@ -213,12 +277,46 @@ if (data->type != INTR_MAP_DATA_FDT) return (ENOTSUP); - data = mv_ap806_gicp_convert_map_data(sc, data); - if (data == NULL) + daf = (struct intr_map_data_fdt*) data; + if (daf->ncells != MV_INTRMAP_NCELLS_ICU) return (EINVAL); + ret = mv_ap806_gicp_allocate_irq(sc, &irq_no); + if (ret != 0) + return (ret); + + daf->cells[0] = irq_no; + irq_type = daf->cells[1]; + + /* Convert map data to GIC format and call the parent. */ + data = mv_ap806_gicp_convert_map_data(sc, data); + if (data == NULL) { + ret = EINVAL; + goto fail; + } + ret = PIC_MAP_INTR(sc->parent, data, isrcp); + if (ret != 0) + goto fail; + + /* + * Fill the map structure again, it will be read in the ICU. + * It includes irq number allocated above and + * initialization data. + */ + daf->cells[0] = irq_no; + daf->cells[1] = irq_type; + paddr = rman_get_start(sc->res) + MV_AP806_GICP_SETSPI_OFFSET; + daf->cells[2] = (uint32_t)paddr; + daf->cells[3] = (uint32_t)((paddr >> 32) & UINT32_MAX); + paddr = rman_get_start(sc->res) + MV_AP806_GICP_CLRSPI_OFFSET; + daf->cells[4] = (uint32_t)paddr; + daf->cells[5] = (uint32_t)((paddr >> 32) & UINT32_MAX); + (*isrcp)->isrc_dev = sc->dev; + return (0); +fail: + mv_ap806_gicp_free_irq(sc, irq_no); return(ret); } @@ -227,9 +325,14 @@ struct resource *res, struct intr_map_data *data) { struct mv_ap806_gicp_softc *sc; + struct intr_map_data_fdt *daf; sc = device_get_softc(dev); + daf = (struct intr_map_data_fdt *)data; + if (daf->ncells != MV_INTRMAP_NCELLS_ICU) + return (EINVAL); + mv_ap806_gicp_free_irq(sc, daf->cells[0]); data = mv_ap806_gicp_convert_map_data(sc, data); if (data == NULL) return (EINVAL); 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 @@ -50,6 +52,8 @@ #include "pic_if.h" +#include "mv_intr.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, \ @@ -58,7 +62,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 +71,23 @@ #define GICP_SEMR(i) (0x20 + (((i)/32) * 0x4)) #define GICP_SEMR_BIT(i) ((i) % 32) +/* + * SEI controls two interrupt types - ones directly routed to the AP806 and + * other that are linked through the CP110 via message interrupt interface, + * in the same way as ICU<->GICP. They are only differentiated in DTS. + * AP consumers are using interrupt-parent node, whereas CP consumers + * use msi-parent. + */ +#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_spi_bitmap, MV_AP806_SEI_MAX_NIRQS); + struct mv_ap806_sei_irqsrc { struct intr_irqsrc isrc; u_int irq; @@ -81,6 +101,8 @@ struct mtx mtx; struct mv_ap806_sei_irqsrc *isrcs; + + struct sei_spi_bitmap spi_bitmap; }; static struct ofw_compat_data compat_data[] = { @@ -152,8 +174,12 @@ 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 && daf->ncells != MV_INTRMAP_NCELLS_ICU) + return (EINVAL); + + if (daf->cells[0] >= MV_AP806_SEI_MAX_NIRQS) return (EINVAL); + irq = daf->cells[0]; if (irqp != NULL) *irqp = irq; @@ -161,20 +187,97 @@ return(0); } +static int +mv_ap806_sei_allocate_cp_intr(struct mv_ap806_sei_softc *sc, int *irq_no) +{ + int irq; + + /* + * Find first available IRQ represented by first set bit in the bitmap. + * BIT_FFS starts the count from 1, 0 means that nothing was found. + * The bitmap is split into two, one for CP and another for AP interrupts. + */ + irq = BIT_FFS_AT(MV_AP806_SEI_MAX_NIRQS, &sc->spi_bitmap, + MV_AP806_SEI_CP_FIRST); + if (irq == 0) + return (ENOMEM); + + irq--; + BIT_CLR(MV_AP806_SEI_MAX_NIRQS, irq, &sc->spi_bitmap); + *irq_no = irq; + return (0); + +} + +static int +mv_ap806_sei_allocate_ap_intr(struct mv_ap806_sei_softc *sc, int *irq_no) +{ + + /* In the case of AP we have a 1:1 mapping. */ + if (*irq_no < MV_AP806_SEI_AP_FIRST || + *irq_no >= MV_AP806_SEI_AP_FIRST + MV_AP806_SEI_AP_SIZE) + return (EINVAL); + + if (!BIT_ISSET(MV_AP806_SEI_MAX_NIRQS, *irq_no, &sc->spi_bitmap)) + return (ENOMEM); + + BIT_CLR(MV_AP806_SEI_MAX_NIRQS, *irq_no, &sc->spi_bitmap); + return (0); +} + +static void +mv_ap806_sei_free_irq(struct mv_ap806_sei_softc *sc, int irq_no) +{ + + BIT_SET(MV_AP806_SEI_MAX_NIRQS, irq_no, &sc->spi_bitmap); +} + 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; + struct intr_map_data_fdt *daf; + vm_paddr_t paddr; 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); + if (data->type != INTR_MAP_DATA_FDT) + return (ENOTSUP); + + daf = (struct intr_map_data_fdt *)data; + irq = daf->cells[0]; + switch (daf->ncells) { + case 1: + rv = mv_ap806_sei_allocate_ap_intr(sc, &irq); + break; + case MV_INTRMAP_NCELLS_ICU: + rv = mv_ap806_sei_allocate_cp_intr(sc, &irq); + break; + default: + return (EINVAL); + } + + if (rv != 0) + return (rv); + + *isrcp = &sc->isrcs[irq].isrc; + + if (daf->ncells != MV_INTRMAP_NCELLS_ICU) + return (0); + + /* + * Fill the map structure again, it will be read in the ICU. + * It includes irq number allocated above and + * initialization data. + */ + daf->cells[0] = irq; + paddr = rman_get_start(sc->mem_res) + MV_AP806_SEI_SETSPI_OFFSET; + daf->cells[2] = (uint32_t)paddr; + daf->cells[3] = (uint32_t)((paddr >> 32) & UINT32_MAX); + return (0); } static int @@ -199,6 +302,24 @@ return (0); } +static int +mv_ap806_sei_deactivate_intr(device_t dev, struct intr_irqsrc *isrc, + struct resource *res, struct intr_map_data *data) +{ + 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) + return (rv); + + mv_ap806_sei_free_irq(sc, irq); + return (0); +} + static int mv_ap806_sei_teardown_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) @@ -361,6 +482,12 @@ goto fail; } + /* + * Bitmap of all IRQs. + * 1 - available, 0 - used. + */ + BIT_FILL(MV_AP806_SEI_MAX_NIRQS, &sc->spi_bitmap); + OF_device_register_xref(xref, dev); return (0); @@ -392,6 +519,7 @@ 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_deactivate_intr, mv_ap806_sei_deactivate_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), 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 @@ -52,6 +52,11 @@ #include #include "pic_if.h" +#include "mv_intr.h" + +#define ICU_TYPE_NSR 1 +#define ICU_TYPE_SEI 2 + #define ICU_GRP_NSR 0x0 #define ICU_GRP_SR 0x1 #define ICU_GRP_SEI 0x4 @@ -61,12 +66,17 @@ #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 struct mv_cp110_icu_softc { @@ -74,6 +84,8 @@ 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 +94,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 +121,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 +150,19 @@ goto fail; } - /* Allocate GICP compatible mapping entry (2 cells) */ + /* Allocate GICP/SEI compatible mapping entry (6 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); + + MV_INTRMAP_NCELLS_ICU * 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: @@ -162,11 +187,11 @@ irq_type != IRQ_TYPE_EDGE_RISING) return (NULL); - /* We rely on fact that ICU->GIC mapping is preset by bootstrap. */ + /* ICU -> GICP/SEI mapping in set in mv_cp110_icu_map_intr. */ reg = RD4(sc, ICU_INT_CFG(irq_no)); /* Construct GICP compatible mapping. */ - sc->parent_map_data->ncells = 2; + sc->parent_map_data->ncells = MV_INTRMAP_NCELLS_ICU; sc->parent_map_data->cells[0] = reg & ICU_INT_MASK; sc->parent_map_data->cells[1] = irq_type; @@ -212,6 +237,32 @@ PIC_DISABLE_INTR(sc->parent, isrc); } +static int +mv_cp110_icu_init(struct mv_cp110_icu_softc *sc, struct intr_map_data_fdt *daf) +{ + + if (sc->initialized) + return (0); + + switch (sc->type) { + case ICU_TYPE_NSR: + WR4(sc, ICU_SETSPI_NSR_AL, daf->cells[2]); + WR4(sc, ICU_SETSPI_NSR_AH, daf->cells[3]); + WR4(sc, ICU_CLRSPI_NSR_AL, daf->cells[4]); + WR4(sc, ICU_CLRSPI_NSR_AH, daf->cells[5]); + break; + case ICU_TYPE_SEI: + WR4(sc, ICU_SETSPI_SEI_AL, daf->cells[2]); + WR4(sc, ICU_SETSPI_SEI_AH, daf->cells[3]); + break; + default: + return (EINVAL); + } + + sc->initialized = true; + return (0); +} + static int mv_cp110_icu_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) @@ -232,19 +283,58 @@ return (EINVAL); irq_no = daf->cells[0]; irq_type = daf->cells[1]; - data = mv_cp110_icu_convert_map_data(sc, data); - if (data == NULL) + if (irq_no >= MV_CP110_ICU_MAX_NIRQS) + return (EINVAL); + if (irq_type != IRQ_TYPE_LEVEL_HIGH && + irq_type != IRQ_TYPE_EDGE_RISING) return (EINVAL); - reg = RD4(sc, ICU_INT_CFG(irq_no)); + sc->parent_map_data->ncells = MV_INTRMAP_NCELLS_ICU; + sc->parent_map_data->cells[0] = irq_no; + sc->parent_map_data->cells[1] = irq_type; + data = (struct intr_map_data *)sc->parent_map_data; + daf = (struct intr_map_data_fdt *)data; + + /* Let our parent allocate GICP/SEI irq_no for us. */ + ret = PIC_MAP_INTR(sc->parent, data, isrcp); + if (ret != 0) + return (ret); + + /* + * Initialization is done once based on data acquired from parent. + * It is deferred to this point since it is easier to acquire necessary + * data here than in attach. + */ + ret = mv_cp110_icu_init(sc, daf); + if (ret != 0) + return (ret); + + /* Use IRQ returned by our parent. */ + reg = daf->cells[0]; reg |= ICU_INT_ENABLE; - if (irq_type == IRQ_TYPE_LEVEL_HIGH) - reg &= ~ICU_INT_EDGE; + + if (sc->type == ICU_TYPE_NSR) + reg |= ICU_GRP_NSR << ICU_INT_GROUP_SHIFT; else + reg |= ICU_GRP_SEI << ICU_INT_GROUP_SHIFT; + + if (irq_type & IRQ_TYPE_EDGE_BOTH) reg |= ICU_INT_EDGE; + WR4(sc, ICU_INT_CFG(irq_no), reg); - ret = PIC_MAP_INTR(sc->parent, data, isrcp); + /* + * 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), reg); + if (irq_no == ICU_INT_SATA1) + WR4(sc, ICU_INT_CFG(ICU_INT_SATA0), reg); + (*isrcp)->isrc_dev = sc->dev; return (ret); } @@ -254,12 +344,25 @@ struct resource *res, struct intr_map_data *data) { struct mv_cp110_icu_softc *sc; + struct intr_map_data_fdt *daf; + int irq_no; + + 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); + /* Clear the mapping. */ + WR4(sc, ICU_INT_CFG(irq_no), 0); + return (PIC_DEACTIVATE_INTR(sc->parent, isrc, res, data)); } diff --git a/sys/arm/mv/mv_intr.h b/sys/arm/mv/mv_intr.h new file mode 100644 --- /dev/null +++ b/sys/arm/mv/mv_intr.h @@ -0,0 +1,33 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 Semihalf + * + * 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. + * + * $FreeBSD$ + */ + +#ifndef _MV_INTR_H_ +#define _MV_INTR_H_ +#define MV_INTRMAP_NCELLS_ICU 6 +#endif