The ICU is an IRQ chip found in Armada CP110. It currently has 207 wired
inputs. Its purpose is to aggregate all CP interrupts and report them to
the AP through MSIs. The ICU writes into GIC registers (AP side) by way
of the interconnect. These interrupts can be of several groups:
- SecuRe (SR);
- Non-SecuRe (NSR);
- System Error Interrupts (SEI);
- RAM Error Interrupts (REI);
- ...
Each ICU wired interrupt can be of any of these groups. The group is
encoded in the MSI payload.
Until now, only the non-secure interrupts (NSR) were handled by the ICU
driver. Interrupts of another group could work by chance because the
ICU driver does not erase all ATF configuration; it only erases the
configuration for NSR interrupts.
This series aims at adding support for the System Error Interrupts
(SEI). For this purpose, the ICU driver is a bit reworked to separate
the ICU 'generic' configuration from the NSR-related handling. Then,
the SEI driver (part of the GIC) is introduced and finally, support for
SEI interrupts are also added to the ICU driver.
The SEI driver is a bit different than its cousin the GICP because it
must handle MSIs from the CPs, as well as wired interrupts from the AP
itself. MSIs and wired interrupts will automatically update two
registers (GICP_SECR0/GICP_SECR1) that will trigger a single top-level
interrupt (SPI #32).
As this is my first contribution in the IRQ subsystem I might have
missed some specificities or misunderstood the API, please do not
hesitate to correct me if I'm wrong.
Also, for the sake of understandability (and because I love ASCII art),
this is a try to explain the ICU/SEI architecture:
+----------------------------------------------------------------------+
| |
| |
| SPIa SPIb SPIz SPI 32 |
| ^ ^ ^ ^ |
| | | . . . | | |
| | | | | |
| | | . . . | | |
| +------------------------+ +---------------------------------+ |
| | | | | | | | | |
| | | | | | | SEI | | |
| | | | . . . | | | ________|_______ | |
| | | | | | | /___SEI_SECR_____\ | |
| | |____|___________| | | / | \\ | |
| | \_GICP_SETSPI _/ | | / | \\ | |
| | || | | / ... | \\ | |
| | GICP || | | | | \\ | |
| +----------------||------+ +--|----------|------------||-----+ |
| || | | || |
| || | ... | || |
| || | | || |
| || | | || |
| \\_______ int 0 ... int 20 // |
| \_NSR__ \ // |
| \\ ____________________// |
| \\ /________SEI_________/ |
| AP 806 \\// |
| || |
+---------------------------------||-----------------------------------+
||
|| Interconnect
||\
||\\______
|| \______ <---> Others CP 110
||
+---------------------------------||-----------------------------------+
| || |
| CP 110 || |
| || |
| +-------------------------||------------------------+ |
| | || MSI | |
| | ICU || | |
| | /--------------/ \------\ | |
| | / /-------/ \ | |
| | / / / \ | |
| | / / / . . . \ | |
| | / / / \ | |
| | NSR NSR SEI NSR | |
| | | | | | | |
| +----^-------^-------^-----------------^------------+ |
| | | | | |
| | | | . . . | |
| | | | | |
| int 0 int 1 int 2 int 206 |
| |
| |
+----------------------------------------------------------------------+
Thank you,
Miquèl
Changes since v1:
=================
General
-------
* Spelling/function names/comments.
* Added Reviewed-by tags.
* Rebased on top of Marc Zyngier level-MSI series (tip:irq/core).
SEI
---
* Change the license for GPL-2.0 only in irq-mvebu-sei.c C file.
* Used alphabetic ordering when adding SEI driver in Makefile.
* Re-ordered register definitions by increasing offset.
* s/NB/COUNT/ in register definitions.
* avoid enabling all interrupt by default.
* fixed mask/unmask functions using the wrong hwirq number.
* removed hackish doorbell mechanism.
* Removed the ->xlate hook assigned for CP MSIs.
* Used devm_*() helpers.
* s/top_level_spi/parent_irq/ in probe.
* Added forgotten of_node_put(child).
* Reset the SEI registers before registering the IRQ domains.
* Introduced new DT property "marvell,sei-ranges" instead of using
"reg" to declare the range of MSI interrupts vs. wired interrupts in
the SEI subnodes.
* Finally did not change the ->alloc() about the fwspec->param[1]
line (to be checked by Marc).
ICU
---
* Updated the ICU documentation so the legacy bindings are still
documented somewhere.
* Added stable tags on the commit fixing the CP110 ICU node size.
* Removed the "syscon" compatible from the ICU node, instead the
syscon is created at probe time.
* s/user data/private data/ in the title of commit
"irqchip/irq-mvebu-icu: fix wrong user data retrieval"
Miquel Raynal (16):
dt-bindings/interrupt-controller: fix Marvell ICU length in the
example
arm64: dts: marvell: fix CP110 ICU node size
irqchip/irq-mvebu-icu: fix wrong private data retrieval
irqchip/irq-mvebu-icu: clarify the reset operation of configured
interrupts
irqchip/irq-mvebu-icu: switch to regmap
irqchip/irq-mvebu-icu: make irq_domain local
irqchip/irq-mvebu-icu: disociate ICU and NSR
irqchip/irq-mvebu-icu: support ICU subnodes
irqchip/irq-mvebu-sei: add new driver for Marvell SEI
arm64: marvell: enable SEI driver
irqchip/irq-mvebu-icu: add support for System Error Interrupts (SEI)
dt-bindings/interrupt-controller: update Marvell ICU bindings
dt-bindings/interrupt-controller: add documentation for Marvell SEI
controller
arm64: dts: marvell: add AP806 SEI subnode
arm64: dts: marvell: use new bindings for CP110 interrupts
arm64: dts: marvell: add CP110 ICU SEI subnode
.../bindings/interrupt-controller/marvell,icu.txt | 83 +++-
.../bindings/interrupt-controller/marvell,sei.txt | 50 +++
arch/arm64/Kconfig.platforms | 1 +
arch/arm64/boot/dts/marvell/armada-ap806.dtsi | 19 +
arch/arm64/boot/dts/marvell/armada-cp110.dtsi | 123 +++---
drivers/irqchip/Kconfig | 3 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-mvebu-icu.c | 301 ++++++++++++---
drivers/irqchip/irq-mvebu-sei.c | 422 +++++++++++++++++++++
9 files changed, 872 insertions(+), 131 deletions(-)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/marvell,sei.txt
create mode 100644 drivers/irqchip/irq-mvebu-sei.c
--
2.14.1
The irq_domain structure has an host_data pointer that just stores
private data. It is meant to not be touched by the IRQ core. However,
when it comes to MSI, the MSI layer adds its own private data there
with a structure that also has a host_data pointer.
Because this IRQ domain is an MSI domain, to access private data we
should do a d->host_data->host_data, also wrapped as
'platform_msi_get_host_data()'.
This bug was lying there silently because the 'icu' structure retrieved
this way was just called by dev_err(), only producing a
'(NULL device *):' output on the console.
Signed-off-by: Miquel Raynal <[email protected]>
Reviewed-by: Thomas Petazzoni <[email protected]>
---
drivers/irqchip/irq-mvebu-icu.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/irqchip/irq-mvebu-icu.c b/drivers/irqchip/irq-mvebu-icu.c
index 13063339b416..a2a3acd74491 100644
--- a/drivers/irqchip/irq-mvebu-icu.c
+++ b/drivers/irqchip/irq-mvebu-icu.c
@@ -105,7 +105,7 @@ static int
mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,
unsigned long *hwirq, unsigned int *type)
{
- struct mvebu_icu *icu = d->host_data;
+ struct mvebu_icu *icu = platform_msi_get_host_data(d);
unsigned int icu_group;
/* Check the count of the parameters in dt */
--
2.14.1
Rewrite a small section to clarify the reset operation of interrupts
already configured by ATF that we want to handle in the driver. This
will simplify the introduction of System Error Interrupts support.
Signed-off-by: Miquel Raynal <[email protected]>
Reviewed-by: Thomas Petazzoni <[email protected]>
---
drivers/irqchip/irq-mvebu-icu.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/drivers/irqchip/irq-mvebu-icu.c b/drivers/irqchip/irq-mvebu-icu.c
index a2a3acd74491..0f2655d7f19e 100644
--- a/drivers/irqchip/irq-mvebu-icu.c
+++ b/drivers/irqchip/irq-mvebu-icu.c
@@ -258,8 +258,12 @@ static int mvebu_icu_probe(struct platform_device *pdev)
* avoid unpredictable SPI assignments done by firmware.
*/
for (i = 0 ; i < ICU_MAX_IRQS ; i++) {
- u32 icu_int = readl_relaxed(icu->base + ICU_INT_CFG(i));
- if ((icu_int >> ICU_GROUP_SHIFT) == ICU_GRP_NSR)
+ u32 icu_int, icu_grp;
+
+ icu_int = readl_relaxed(icu->base + ICU_INT_CFG(i));
+ icu_grp = icu_int >> ICU_GROUP_SHIFT;
+
+ if (icu_grp == ICU_GRP_NSR)
writel_relaxed(0x0, icu->base + ICU_INT_CFG(i));
}
--
2.14.1
This is a cascaded interrupt controller in the AP806 GIC that collapses
SEIs (System Error Interrupt) coming from the AP and the CPs (through
the ICU).
The SEI handles up to 64 interrupts. The first 21 interrupts are wired
and come from the AP. The next 43 interrupts are from the CPs and are
triggered through MSI messages. To handle this complexity, the driver
has to declare to the upper layer: one IRQ domain for the wired
interrupts, one IRQ domain for the MSIs; and acts as a MSI server
('parent') by declaring an MSI domain.
Suggested-by: Haim Boot <[email protected]>
Signed-off-by: Miquel Raynal <[email protected]>
---
drivers/irqchip/Kconfig | 3 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-mvebu-sei.c | 422 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 426 insertions(+)
create mode 100644 drivers/irqchip/irq-mvebu-sei.c
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index e9233db16e03..922e2a919cf3 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -310,6 +310,9 @@ config MVEBU_ODMI
config MVEBU_PIC
bool
+config MVEBU_SEI
+ bool
+
config LS_SCFG_MSI
def_bool y if SOC_LS1021A || ARCH_LAYERSCAPE
depends on PCI && PCI_MSI
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 15f268f646bf..69d2ccb454ef 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -76,6 +76,7 @@ obj-$(CONFIG_MVEBU_GICP) += irq-mvebu-gicp.o
obj-$(CONFIG_MVEBU_ICU) += irq-mvebu-icu.o
obj-$(CONFIG_MVEBU_ODMI) += irq-mvebu-odmi.o
obj-$(CONFIG_MVEBU_PIC) += irq-mvebu-pic.o
+obj-$(CONFIG_MVEBU_SEI) += irq-mvebu-sei.o
obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o
obj-$(CONFIG_EZNPS_GIC) += irq-eznps.o
obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o irq-aspeed-i2c-ic.o
diff --git a/drivers/irqchip/irq-mvebu-sei.c b/drivers/irqchip/irq-mvebu-sei.c
new file mode 100644
index 000000000000..d9abd5e10741
--- /dev/null
+++ b/drivers/irqchip/irq-mvebu-sei.c
@@ -0,0 +1,422 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define pr_fmt(fmt) "mvebu-sei: " fmt
+
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/msi.h>
+#include <linux/platform_device.h>
+#include <linux/irqchip.h>
+
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+
+/* Cause register */
+#define GICP_SECR(idx) (0x0 + (idx * 0x4))
+/* Mask register */
+#define GICP_SEMR(idx) (0x20 + (idx * 0x4))
+#define GICP_SET_SEI_OFFSET 0x30
+
+#define SEI_IRQ_COUNT_PER_REG 32
+#define SEI_IRQ_REG_COUNT 2
+#define SEI_IRQ_COUNT (SEI_IRQ_COUNT_PER_REG * SEI_IRQ_REG_COUNT)
+#define SEI_IRQ_REG_IDX(irq_id) (irq_id / SEI_IRQ_COUNT_PER_REG)
+#define SEI_IRQ_REG_BIT(irq_id) (irq_id % SEI_IRQ_COUNT_PER_REG)
+
+struct mvebu_sei_interrupt_range {
+ u32 first;
+ u32 number;
+};
+
+struct mvebu_sei {
+ struct device *dev;
+ void __iomem *base;
+ struct resource *res;
+ struct irq_domain *ap_domain;
+ struct irq_domain *cp_domain;
+ struct mvebu_sei_interrupt_range ap_interrupts;
+ struct mvebu_sei_interrupt_range cp_interrupts;
+ /* Lock on MSI allocations/releases */
+ spinlock_t cp_msi_lock;
+ DECLARE_BITMAP(cp_msi_bitmap, SEI_IRQ_COUNT);
+};
+
+static int mvebu_sei_domain_to_sei_irq(struct mvebu_sei *sei,
+ struct irq_domain *domain,
+ irq_hw_number_t hwirq)
+{
+ if (domain == sei->ap_domain)
+ return sei->ap_interrupts.first + hwirq;
+ else
+ return sei->cp_interrupts.first + hwirq;
+}
+
+static void mvebu_sei_reset(struct mvebu_sei *sei)
+{
+ u32 reg_idx;
+
+ /* Clear IRQ cause registers */
+ for (reg_idx = 0; reg_idx < SEI_IRQ_REG_COUNT; reg_idx++)
+ writel(0xFFFFFFFF, sei->base + GICP_SECR(reg_idx));
+}
+
+static void mvebu_sei_mask_irq(struct irq_data *d)
+{
+ struct mvebu_sei *sei = irq_data_get_irq_chip_data(d);
+ u32 sei_irq = mvebu_sei_domain_to_sei_irq(sei, d->domain, d->hwirq);
+ u32 reg_idx = SEI_IRQ_REG_IDX(sei_irq);
+ u32 reg;
+
+ /* 1 disables the interrupt */
+ reg = readl(sei->base + GICP_SEMR(reg_idx));
+ reg |= BIT(SEI_IRQ_REG_BIT(sei_irq));
+ writel(reg, sei->base + GICP_SEMR(reg_idx));
+}
+
+static void mvebu_sei_unmask_irq(struct irq_data *d)
+{
+ struct mvebu_sei *sei = irq_data_get_irq_chip_data(d);
+ u32 sei_irq = mvebu_sei_domain_to_sei_irq(sei, d->domain, d->hwirq);
+ u32 reg_idx = SEI_IRQ_REG_IDX(sei_irq);
+ u32 reg;
+
+ /* 0 enables the interrupt */
+ reg = readl(sei->base + GICP_SEMR(reg_idx));
+ reg &= ~BIT(SEI_IRQ_REG_BIT(sei_irq));
+ writel(reg, sei->base + GICP_SEMR(reg_idx));
+}
+
+static void mvebu_sei_compose_msi_msg(struct irq_data *data,
+ struct msi_msg *msg)
+{
+ struct mvebu_sei *sei = data->chip_data;
+ phys_addr_t set = sei->res->start + GICP_SET_SEI_OFFSET;
+
+ msg->data = mvebu_sei_domain_to_sei_irq(sei, data->domain, data->hwirq);
+ msg->address_lo = lower_32_bits(set);
+ msg->address_hi = upper_32_bits(set);
+}
+
+static struct irq_chip mvebu_sei_ap_wired_irq_chip = {
+ .name = "AP wired SEI",
+ .irq_mask = mvebu_sei_mask_irq,
+ .irq_unmask = mvebu_sei_unmask_irq,
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_set_type = irq_chip_set_type_parent,
+};
+
+static struct irq_chip mvebu_sei_cp_msi_irq_chip = {
+ .name = "CP MSI SEI",
+ .irq_mask = mvebu_sei_mask_irq,
+ .irq_unmask = mvebu_sei_unmask_irq,
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_set_type = irq_chip_set_type_parent,
+ .irq_compose_msi_msg = mvebu_sei_compose_msi_msg,
+};
+
+static int mvebu_sei_irq_domain_alloc(struct irq_domain *domain,
+ unsigned int virq, unsigned int nr_irqs,
+ void *args)
+{
+ struct mvebu_sei *sei = domain->host_data;
+ struct irq_fwspec *fwspec = args;
+ struct irq_chip *irq_chip;
+ int sei_hwirq, hwirq;
+ int ret;
+
+ /* Software only supports single allocations for now */
+ if (nr_irqs != 1)
+ return -ENOTSUPP;
+
+ if (domain == sei->ap_domain) {
+ irq_chip = &mvebu_sei_ap_wired_irq_chip;
+ hwirq = fwspec->param[0];
+ } else {
+ irq_chip = &mvebu_sei_cp_msi_irq_chip;
+ spin_lock(&sei->cp_msi_lock);
+ hwirq = bitmap_find_free_region(sei->cp_msi_bitmap,
+ SEI_IRQ_COUNT, 0);
+ spin_unlock(&sei->cp_msi_lock);
+ if (hwirq < 0)
+ return -ENOSPC;
+ }
+
+ sei_hwirq = mvebu_sei_domain_to_sei_irq(sei, domain, hwirq);
+
+ fwspec->fwnode = domain->parent->fwnode;
+ fwspec->param_count = 3;
+ fwspec->param[0] = GIC_SPI;
+ fwspec->param[1] = sei_hwirq;
+ fwspec->param[2] = IRQ_TYPE_EDGE_RISING;
+
+ ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, fwspec);
+ if (ret)
+ goto release_region;
+
+ ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, irq_chip, sei);
+ if (ret)
+ goto free_irq_parents;
+
+ return 0;
+
+free_irq_parents:
+ irq_domain_free_irqs_parent(domain, virq, nr_irqs);
+release_region:
+ if (domain == sei->cp_domain) {
+ spin_lock(&sei->cp_msi_lock);
+ bitmap_release_region(sei->cp_msi_bitmap, hwirq, 0);
+ spin_unlock(&sei->cp_msi_lock);
+ }
+
+ return ret;
+}
+
+static void mvebu_sei_irq_domain_free(struct irq_domain *domain,
+ unsigned int virq, unsigned int nr_irqs)
+{
+ struct mvebu_sei *sei = domain->host_data;
+ struct irq_data *d = irq_domain_get_irq_data(domain, virq);
+ u32 irq_nb = sei->ap_interrupts.number + sei->cp_interrupts.number;
+
+ if (nr_irqs != 1 || d->hwirq >= irq_nb) {
+ dev_err(sei->dev, "Invalid hwirq %lu\n", d->hwirq);
+ return;
+ }
+
+ irq_domain_free_irqs_parent(domain, virq, nr_irqs);
+
+ spin_lock(&sei->cp_msi_lock);
+ bitmap_release_region(sei->cp_msi_bitmap, d->hwirq, 0);
+ spin_unlock(&sei->cp_msi_lock);
+}
+
+static const struct irq_domain_ops mvebu_sei_ap_domain_ops = {
+ .xlate = irq_domain_xlate_onecell,
+ .alloc = mvebu_sei_irq_domain_alloc,
+ .free = mvebu_sei_irq_domain_free,
+};
+
+static const struct irq_domain_ops mvebu_sei_cp_domain_ops = {
+ .alloc = mvebu_sei_irq_domain_alloc,
+ .free = mvebu_sei_irq_domain_free,
+};
+
+static struct irq_chip mvebu_sei_msi_irq_chip = {
+ .name = "SEI",
+ .irq_set_type = irq_chip_set_type_parent,
+};
+
+static struct msi_domain_ops mvebu_sei_msi_ops = {
+};
+
+static struct msi_domain_info mvebu_sei_msi_domain_info = {
+ .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS,
+ .ops = &mvebu_sei_msi_ops,
+ .chip = &mvebu_sei_msi_irq_chip,
+};
+
+static void mvebu_sei_handle_cascade_irq(struct irq_desc *desc)
+{
+ struct mvebu_sei *sei = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ unsigned long irqmap, irq_bit;
+ u32 reg_idx, virq, irqn;
+
+ chained_irq_enter(chip, desc);
+
+ /* Read both SEI cause registers (64 bits) */
+ for (reg_idx = 0; reg_idx < SEI_IRQ_REG_COUNT; reg_idx++) {
+ irqmap = readl_relaxed(sei->base + GICP_SECR(reg_idx));
+
+ /* Call handler for each set bit */
+ for_each_set_bit(irq_bit, &irqmap, SEI_IRQ_COUNT_PER_REG) {
+ /* Cause Register gives the SEI number */
+ irqn = irq_bit + reg_idx * SEI_IRQ_COUNT_PER_REG;
+ /*
+ * Finding Linux mapping (virq) needs the right domain
+ * and the relative hwirq (which start at 0 in both
+ * cases, while irqn is relative to all SEI interrupts).
+ */
+ if (irqn < sei->ap_interrupts.number) {
+ virq = irq_find_mapping(sei->ap_domain, irqn);
+ } else {
+ irqn -= sei->ap_interrupts.number;
+ virq = irq_find_mapping(sei->cp_domain, irqn);
+ }
+
+ /* Call IRQ handler */
+ generic_handle_irq(virq);
+ }
+
+ /* Clear interrupt indication by writing 1 to it */
+ writel(irqmap, sei->base + GICP_SECR(reg_idx));
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static int mvebu_sei_probe(struct platform_device *pdev)
+{
+ struct device_node *node = pdev->dev.of_node, *parent, *child;
+ struct irq_domain *parent_domain, *plat_domain;
+ struct mvebu_sei *sei;
+ const __be32 *property;
+ u32 parent_irq, size;
+ int ret;
+
+ sei = devm_kzalloc(&pdev->dev, sizeof(*sei), GFP_KERNEL);
+ if (!sei)
+ return -ENOMEM;
+
+ sei->dev = &pdev->dev;
+
+ spin_lock_init(&sei->cp_msi_lock);
+
+ sei->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ sei->base = devm_ioremap_resource(sei->dev, sei->res);
+ if (!sei->base) {
+ dev_err(sei->dev, "Failed to remap SEI resource\n");
+ return -ENODEV;
+ }
+
+ mvebu_sei_reset(sei);
+
+ /*
+ * Reserve the single (top-level) parent SPI IRQ from which all the
+ * interrupts handled by this driver will be signaled.
+ */
+ parent_irq = irq_of_parse_and_map(node, 0);
+ if (parent_irq <= 0) {
+ dev_err(sei->dev, "Failed to retrieve top-level SPI IRQ\n");
+ return -ENODEV;
+ }
+
+ irq_set_chained_handler(parent_irq, mvebu_sei_handle_cascade_irq);
+ irq_set_handler_data(parent_irq, sei);
+
+ /*
+ * SEIs in the range [ 0; 20] are wired and come from the AP.
+ * SEIs in the range [21; 63] are CP SEI and are triggered through MSIs.
+ *
+ * Each SEI 'domain' is represented as a subnode.
+ */
+
+ /* Get a reference to the parent domain to create a hierarchy */
+ parent = of_irq_find_parent(node);
+ if (!parent) {
+ dev_err(sei->dev, "Failed to find parent IRQ node\n");
+ ret = -ENODEV;
+ goto dispose_irq;
+ }
+
+ parent_domain = irq_find_host(parent);
+ if (!parent_domain) {
+ dev_err(sei->dev, "Failed to find parent IRQ domain\n");
+ ret = -ENODEV;
+ goto dispose_irq;
+ }
+
+ /* Create the 'wired' hierarchy */
+ child = of_find_node_by_name(node, "sei-wired-controller");
+ if (!child) {
+ dev_err(sei->dev, "Missing 'sei-wired-controller' subnode\n");
+ ret = -ENODEV;
+ goto dispose_irq;
+ }
+
+ property = of_get_property(child, "marvell,sei-ranges", &size);
+ if (!property || size != (2 * sizeof(u32))) {
+ dev_err(sei->dev, "Missing 'marvell,sei-ranges' property\n");
+ of_node_put(child);
+ ret = -ENODEV;
+ goto dispose_irq;
+ }
+
+ sei->ap_interrupts.first = be32_to_cpu(property[0]);
+ sei->ap_interrupts.number = be32_to_cpu(property[1]);
+ sei->ap_domain = irq_domain_create_hierarchy(parent_domain, 0,
+ sei->ap_interrupts.number,
+ of_node_to_fwnode(child),
+ &mvebu_sei_ap_domain_ops,
+ sei);
+ of_node_put(child);
+ if (!sei->ap_domain) {
+ dev_err(sei->dev, "Failed to create AP IRQ domain\n");
+ ret = -ENOMEM;
+ goto dispose_irq;
+ }
+
+ /* Create the 'MSI' hierarchy */
+ child = of_find_node_by_name(node, "sei-msi-controller");
+ if (!child) {
+ dev_err(sei->dev, "Missing 'sei-msi-controller' subnode\n");
+ ret = -ENODEV;
+ goto remove_ap_domain;
+ }
+
+ property = of_get_property(child, "marvell,sei-ranges", &size);
+ if (!property || size != (2 * sizeof(u32))) {
+ dev_err(sei->dev, "Missing 'marvell,sei-ranges' property\n");
+ of_node_put(child);
+ ret = -ENODEV;
+ goto remove_ap_domain;
+ }
+
+ sei->cp_interrupts.first = be32_to_cpu(property[0]);
+ sei->cp_interrupts.number = be32_to_cpu(property[1]);
+ sei->cp_domain = irq_domain_create_hierarchy(parent_domain, 0,
+ sei->cp_interrupts.number,
+ of_node_to_fwnode(child),
+ &mvebu_sei_cp_domain_ops,
+ sei);
+ if (!sei->cp_domain) {
+ pr_err("Failed to create CPs IRQ domain\n");
+ of_node_put(child);
+ ret = -ENOMEM;
+ goto remove_ap_domain;
+ }
+
+ plat_domain = platform_msi_create_irq_domain(of_node_to_fwnode(child),
+ &mvebu_sei_msi_domain_info,
+ sei->cp_domain);
+ of_node_put(child);
+ if (!plat_domain) {
+ pr_err("Failed to create CPs MSI domain\n");
+ ret = -ENOMEM;
+ goto remove_cp_domain;
+ }
+
+ platform_set_drvdata(pdev, sei);
+
+ return 0;
+
+remove_cp_domain:
+ irq_domain_remove(sei->cp_domain);
+remove_ap_domain:
+ irq_domain_remove(sei->ap_domain);
+dispose_irq:
+ irq_dispose_mapping(parent_irq);
+
+ return ret;
+}
+
+static const struct of_device_id mvebu_sei_of_match[] = {
+ { .compatible = "marvell,armada-8k-sei", },
+ {},
+};
+
+static struct platform_driver mvebu_sei_driver = {
+ .probe = mvebu_sei_probe,
+ .driver = {
+ .name = "mvebu-sei",
+ .of_match_table = mvebu_sei_of_match,
+ },
+};
+builtin_platform_driver(mvebu_sei_driver);
--
2.14.1
ICU size in CP110 is not 0x10 but at least 0x440 bytes long (from the
specification).
Fixes: 6ef84a827c37 ("arm64: dts: marvell: enable GICP and ICU on Armada 7K/8K")
Cc: [email protected]
Signed-off-by: Miquel Raynal <[email protected]>
Reviewed-by: Thomas Petazzoni <[email protected]>
---
arch/arm64/boot/dts/marvell/armada-cp110.dtsi | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/marvell/armada-cp110.dtsi b/arch/arm64/boot/dts/marvell/armada-cp110.dtsi
index 48cad7919efa..9fa41c54f69c 100644
--- a/arch/arm64/boot/dts/marvell/armada-cp110.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-cp110.dtsi
@@ -146,7 +146,7 @@
CP110_LABEL(icu): interrupt-controller@1e0000 {
compatible = "marvell,cp110-icu";
- reg = <0x1e0000 0x10>;
+ reg = <0x1e0000 0x440>;
#interrupt-cells = <3>;
interrupt-controller;
msi-parent = <&gicp>;
--
2.14.1
Describe the System Error Interrupt (SEI) controller. It aggregates two
types of interrupts, wired and MSIs from respectively the AP and the
CPs, into a single SPI interrupt.
Suggested-by: Haim Boot <[email protected]>
Signed-off-by: Miquel Raynal <[email protected]>
---
.../bindings/interrupt-controller/marvell,sei.txt | 50 ++++++++++++++++++++++
1 file changed, 50 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/marvell,sei.txt
diff --git a/Documentation/devicetree/bindings/interrupt-controller/marvell,sei.txt b/Documentation/devicetree/bindings/interrupt-controller/marvell,sei.txt
new file mode 100644
index 000000000000..689981036c30
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/marvell,sei.txt
@@ -0,0 +1,50 @@
+Marvell SEI (System Error Interrupt) Controller
+-----------------------------------------------
+
+Marvell SEI (System Error Interrupt) controller is an interrupt
+aggregator. It receives interrupts from several sources and aggregates
+them to a single interrupt line (an SPI) on the parent interrupt
+controller.
+
+This interrupt controller can handle up to 64 SEIs, a set comes from the
+AP and is wired while a second set comes from the CPs by the mean of
+MSIs. Each 'domain' is represented as a subnode.
+
+Required properties:
+
+- compatible: should be "marvell,armada-8k-sei".
+- reg: SEI registers location and length.
+- interrupts: identifies the parent IRQ that will be triggered.
+
+Child node 'sei-wired-controller' required properties:
+
+- marvell,sei-ranges: ranges of wired interrupts.
+- #interrupt-cells: number of cells to define an SEI wired interrupt
+ coming from the AP, should be 1. The cell is the IRQ
+ number.
+- interrupt-controller: identifies the node as an interrupt controller.
+
+Child node 'sei-msi-controller' required properties:
+
+- marvell,sei-ranges: ranges of non-wired interrupts triggered by way of
+ MSIs.
+- msi-controller: identifies the node as an MSI controller.
+
+Example:
+
+ sei: sei@3f0200 {
+ compatible = "marvell,armada-8k-sei";
+ reg = <0x3f0200 0x40>;
+ interrupts = <GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>;
+
+ sei_wired_controller: sei-wired-controller@0 {
+ marvell,sei-ranges = <0 21>;
+ #interrupt-cells = <1>;
+ interrupt-controller;
+ };
+
+ sei_msi_controller: sei-msi-controller@21 {
+ marvell,sei-ranges = <21 43>;
+ msi-controller;
+ };
+ };
--
2.14.1
Add the System Error Interrupt node, representing an IRQ chip which is
part of the GIC. The SEI node has two subnodes, one for each interrupt
domain: wired (from the AP) and not-wired (MSIs from the CPs).
Signed-off-by: Miquel Raynal <[email protected]>
---
arch/arm64/boot/dts/marvell/armada-ap806.dtsi | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/arch/arm64/boot/dts/marvell/armada-ap806.dtsi b/arch/arm64/boot/dts/marvell/armada-ap806.dtsi
index 176e38d54872..40204a3b893a 100644
--- a/arch/arm64/boot/dts/marvell/armada-ap806.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-ap806.dtsi
@@ -124,6 +124,25 @@
interrupts = <GIC_PPI 15 IRQ_TYPE_LEVEL_HIGH>;
};
+ sei: sei@3f0200 {
+ compatible = "marvell,armada-8k-sei";
+ reg = <0x3f0200 0x40>;
+ interrupts = <GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ sei_wired_controller: sei-wired-controller {
+ marvell,sei-ranges = <0 21>;
+ #interrupt-cells = <1>;
+ interrupt-controller;
+ };
+
+ sei_msi_controller: sei-msi-controller {
+ marvell,sei-ranges = <21 43>;
+ msi-controller;
+ };
+ };
+
xor@400000 {
compatible = "marvell,armada-7k-xor", "marvell,xor-v2";
reg = <0x400000 0x1000>,
--
2.14.1
The ICU handles several interrupt groups, each of them being a subpart
of the ICU node.
Signed-off-by: Miquel Raynal <[email protected]>
---
arch/arm64/boot/dts/marvell/armada-cp110.dtsi | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/arch/arm64/boot/dts/marvell/armada-cp110.dtsi b/arch/arm64/boot/dts/marvell/armada-cp110.dtsi
index 5637ff2601c9..0038a922e7db 100644
--- a/arch/arm64/boot/dts/marvell/armada-cp110.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-cp110.dtsi
@@ -154,6 +154,13 @@
interrupt-controller;
msi-parent = <&gicp>;
};
+
+ CP110_LABEL(icu_sei): icu-sei {
+ compatible = "marvell,cp110-icu-sei";
+ #interrupt-cells = <2>;
+ interrupt-controller;
+ msi-parent = <&sei>;
+ };
};
CP110_LABEL(rtc): rtc@284000 {
--
2.14.1
Create an ICU subnode for the NSR interrupts. This subnode becomes the
CP110 interrupt parent, removing the need for the ICU_GRP_NSR parameter.
Move all DT110 nodes to use these new bindings.
Signed-off-by: Miquel Raynal <[email protected]>
---
arch/arm64/boot/dts/marvell/armada-cp110.dtsi | 114 +++++++++++++-------------
1 file changed, 59 insertions(+), 55 deletions(-)
diff --git a/arch/arm64/boot/dts/marvell/armada-cp110.dtsi b/arch/arm64/boot/dts/marvell/armada-cp110.dtsi
index 9fa41c54f69c..5637ff2601c9 100644
--- a/arch/arm64/boot/dts/marvell/armada-cp110.dtsi
+++ b/arch/arm64/boot/dts/marvell/armada-cp110.dtsi
@@ -25,7 +25,7 @@
#address-cells = <2>;
#size-cells = <2>;
compatible = "simple-bus";
- interrupt-parent = <&CP110_LABEL(icu)>;
+ interrupt-parent = <&CP110_LABEL(icu_nsr)>;
ranges;
config-space@CP110_BASE {
@@ -46,12 +46,12 @@
dma-coherent;
CP110_LABEL(eth0): eth0 {
- interrupts = <ICU_GRP_NSR 39 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 43 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 47 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 51 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 55 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 129 IRQ_TYPE_LEVEL_HIGH>;
+ interrupts = <39 IRQ_TYPE_LEVEL_HIGH>,
+ <43 IRQ_TYPE_LEVEL_HIGH>,
+ <47 IRQ_TYPE_LEVEL_HIGH>,
+ <51 IRQ_TYPE_LEVEL_HIGH>,
+ <55 IRQ_TYPE_LEVEL_HIGH>,
+ <129 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "tx-cpu0", "tx-cpu1", "tx-cpu2",
"tx-cpu3", "rx-shared", "link";
port-id = <0>;
@@ -60,12 +60,12 @@
};
CP110_LABEL(eth1): eth1 {
- interrupts = <ICU_GRP_NSR 40 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 44 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 48 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 52 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 56 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 128 IRQ_TYPE_LEVEL_HIGH>;
+ interrupts = <40 IRQ_TYPE_LEVEL_HIGH>,
+ <44 IRQ_TYPE_LEVEL_HIGH>,
+ <48 IRQ_TYPE_LEVEL_HIGH>,
+ <52 IRQ_TYPE_LEVEL_HIGH>,
+ <56 IRQ_TYPE_LEVEL_HIGH>,
+ <128 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "tx-cpu0", "tx-cpu1", "tx-cpu2",
"tx-cpu3", "rx-shared", "link";
port-id = <1>;
@@ -74,12 +74,12 @@
};
CP110_LABEL(eth2): eth2 {
- interrupts = <ICU_GRP_NSR 41 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 45 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 49 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 53 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 57 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 127 IRQ_TYPE_LEVEL_HIGH>;
+ interrupts = <41 IRQ_TYPE_LEVEL_HIGH>,
+ <45 IRQ_TYPE_LEVEL_HIGH>,
+ <49 IRQ_TYPE_LEVEL_HIGH>,
+ <53 IRQ_TYPE_LEVEL_HIGH>,
+ <57 IRQ_TYPE_LEVEL_HIGH>,
+ <127 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "tx-cpu0", "tx-cpu1", "tx-cpu2",
"tx-cpu3", "rx-shared", "link";
port-id = <2>;
@@ -147,16 +147,20 @@
CP110_LABEL(icu): interrupt-controller@1e0000 {
compatible = "marvell,cp110-icu";
reg = <0x1e0000 0x440>;
- #interrupt-cells = <3>;
- interrupt-controller;
- msi-parent = <&gicp>;
+
+ CP110_LABEL(icu_nsr): icu-nsr {
+ compatible = "marvell,cp110-icu-nsr";
+ #interrupt-cells = <2>;
+ interrupt-controller;
+ msi-parent = <&gicp>;
+ };
};
CP110_LABEL(rtc): rtc@284000 {
compatible = "marvell,armada-8k-rtc";
reg = <0x284000 0x20>, <0x284080 0x24>;
reg-names = "rtc", "rtc-soc";
- interrupts = <ICU_GRP_NSR 77 IRQ_TYPE_LEVEL_HIGH>;
+ interrupts = <77 IRQ_TYPE_LEVEL_HIGH>;
};
CP110_LABEL(thermal): thermal@400078 {
@@ -182,10 +186,10 @@
#gpio-cells = <2>;
gpio-ranges = <&CP110_LABEL(pinctrl) 0 0 32>;
interrupt-controller;
- interrupts = <ICU_GRP_NSR 86 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 85 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 84 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 83 IRQ_TYPE_LEVEL_HIGH>;
+ interrupts = <86 IRQ_TYPE_LEVEL_HIGH>,
+ <85 IRQ_TYPE_LEVEL_HIGH>,
+ <84 IRQ_TYPE_LEVEL_HIGH>,
+ <83 IRQ_TYPE_LEVEL_HIGH>;
status = "disabled";
};
@@ -197,10 +201,10 @@
#gpio-cells = <2>;
gpio-ranges = <&CP110_LABEL(pinctrl) 0 32 31>;
interrupt-controller;
- interrupts = <ICU_GRP_NSR 82 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 81 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 80 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 79 IRQ_TYPE_LEVEL_HIGH>;
+ interrupts = <82 IRQ_TYPE_LEVEL_HIGH>,
+ <81 IRQ_TYPE_LEVEL_HIGH>,
+ <80 IRQ_TYPE_LEVEL_HIGH>,
+ <79 IRQ_TYPE_LEVEL_HIGH>;
status = "disabled";
};
};
@@ -210,7 +214,7 @@
"generic-xhci";
reg = <0x500000 0x4000>;
dma-coherent;
- interrupts = <ICU_GRP_NSR 106 IRQ_TYPE_LEVEL_HIGH>;
+ interrupts = <106 IRQ_TYPE_LEVEL_HIGH>;
clock-names = "core", "reg";
clocks = <&CP110_LABEL(clk) 1 22>,
<&CP110_LABEL(clk) 1 16>;
@@ -222,7 +226,7 @@
"generic-xhci";
reg = <0x510000 0x4000>;
dma-coherent;
- interrupts = <ICU_GRP_NSR 105 IRQ_TYPE_LEVEL_HIGH>;
+ interrupts = <105 IRQ_TYPE_LEVEL_HIGH>;
clock-names = "core", "reg";
clocks = <&CP110_LABEL(clk) 1 23>,
<&CP110_LABEL(clk) 1 16>;
@@ -233,7 +237,7 @@
compatible = "marvell,armada-8k-ahci",
"generic-ahci";
reg = <0x540000 0x30000>;
- interrupts = <ICU_GRP_NSR 107 IRQ_TYPE_LEVEL_HIGH>;
+ interrupts = <107 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&CP110_LABEL(clk) 1 15>,
<&CP110_LABEL(clk) 1 16>;
status = "disabled";
@@ -286,7 +290,7 @@
reg = <0x701000 0x20>;
#address-cells = <1>;
#size-cells = <0>;
- interrupts = <ICU_GRP_NSR 120 IRQ_TYPE_LEVEL_HIGH>;
+ interrupts = <120 IRQ_TYPE_LEVEL_HIGH>;
clock-names = "core", "reg";
clocks = <&CP110_LABEL(clk) 1 21>,
<&CP110_LABEL(clk) 1 17>;
@@ -298,7 +302,7 @@
reg = <0x701100 0x20>;
#address-cells = <1>;
#size-cells = <0>;
- interrupts = <ICU_GRP_NSR 121 IRQ_TYPE_LEVEL_HIGH>;
+ interrupts = <121 IRQ_TYPE_LEVEL_HIGH>;
clock-names = "core", "reg";
clocks = <&CP110_LABEL(clk) 1 21>,
<&CP110_LABEL(clk) 1 17>;
@@ -309,7 +313,7 @@
compatible = "snps,dw-apb-uart";
reg = <0x702000 0x100>;
reg-shift = <2>;
- interrupts = <ICU_GRP_NSR 122 IRQ_TYPE_LEVEL_HIGH>;
+ interrupts = <122 IRQ_TYPE_LEVEL_HIGH>;
reg-io-width = <1>;
clock-names = "baudclk", "apb_pclk";
clocks = <&CP110_LABEL(clk) 1 21>,
@@ -321,7 +325,7 @@
compatible = "snps,dw-apb-uart";
reg = <0x702100 0x100>;
reg-shift = <2>;
- interrupts = <ICU_GRP_NSR 123 IRQ_TYPE_LEVEL_HIGH>;
+ interrupts = <123 IRQ_TYPE_LEVEL_HIGH>;
reg-io-width = <1>;
clock-names = "baudclk", "apb_pclk";
clocks = <&CP110_LABEL(clk) 1 21>,
@@ -333,7 +337,7 @@
compatible = "snps,dw-apb-uart";
reg = <0x702200 0x100>;
reg-shift = <2>;
- interrupts = <ICU_GRP_NSR 124 IRQ_TYPE_LEVEL_HIGH>;
+ interrupts = <124 IRQ_TYPE_LEVEL_HIGH>;
reg-io-width = <1>;
clock-names = "baudclk", "apb_pclk";
clocks = <&CP110_LABEL(clk) 1 21>,
@@ -345,7 +349,7 @@
compatible = "snps,dw-apb-uart";
reg = <0x702300 0x100>;
reg-shift = <2>;
- interrupts = <ICU_GRP_NSR 125 IRQ_TYPE_LEVEL_HIGH>;
+ interrupts = <125 IRQ_TYPE_LEVEL_HIGH>;
reg-io-width = <1>;
clock-names = "baudclk", "apb_pclk";
clocks = <&CP110_LABEL(clk) 1 21>,
@@ -364,7 +368,7 @@
reg = <0x720000 0x54>;
#address-cells = <1>;
#size-cells = <0>;
- interrupts = <ICU_GRP_NSR 115 IRQ_TYPE_LEVEL_HIGH>;
+ interrupts = <115 IRQ_TYPE_LEVEL_HIGH>;
clock-names = "core", "reg";
clocks = <&CP110_LABEL(clk) 1 2>,
<&CP110_LABEL(clk) 1 17>;
@@ -376,7 +380,7 @@
compatible = "marvell,armada-8k-rng",
"inside-secure,safexcel-eip76";
reg = <0x760000 0x7d>;
- interrupts = <ICU_GRP_NSR 95 IRQ_TYPE_LEVEL_HIGH>;
+ interrupts = <95 IRQ_TYPE_LEVEL_HIGH>;
clock-names = "core", "reg";
clocks = <&CP110_LABEL(clk) 1 25>,
<&CP110_LABEL(clk) 1 17>;
@@ -386,7 +390,7 @@
CP110_LABEL(sdhci0): sdhci@780000 {
compatible = "marvell,armada-cp110-sdhci";
reg = <0x780000 0x300>;
- interrupts = <ICU_GRP_NSR 27 IRQ_TYPE_LEVEL_HIGH>;
+ interrupts = <27 IRQ_TYPE_LEVEL_HIGH>;
clock-names = "core", "axi";
clocks = <&CP110_LABEL(clk) 1 4>, <&CP110_LABEL(clk) 1 18>;
dma-coherent;
@@ -396,12 +400,12 @@
CP110_LABEL(crypto): crypto@800000 {
compatible = "inside-secure,safexcel-eip197";
reg = <0x800000 0x200000>;
- interrupts = <ICU_GRP_NSR 87 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 88 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 89 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 90 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 91 IRQ_TYPE_LEVEL_HIGH>,
- <ICU_GRP_NSR 92 IRQ_TYPE_LEVEL_HIGH>;
+ interrupts = <87 IRQ_TYPE_LEVEL_HIGH>,
+ <88 IRQ_TYPE_LEVEL_HIGH>,
+ <89 IRQ_TYPE_LEVEL_HIGH>,
+ <90 IRQ_TYPE_LEVEL_HIGH>,
+ <91 IRQ_TYPE_LEVEL_HIGH>,
+ <92 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "mem", "ring0", "ring1",
"ring2", "ring3", "eip";
clock-names = "core", "reg";
@@ -430,8 +434,8 @@
/* non-prefetchable memory */
0x82000000 0 CP110_PCIEx_MEM_BASE(0) 0 CP110_PCIEx_MEM_BASE(0) 0 0xf00000>;
interrupt-map-mask = <0 0 0 0>;
- interrupt-map = <0 0 0 0 &CP110_LABEL(icu) ICU_GRP_NSR 22 IRQ_TYPE_LEVEL_HIGH>;
- interrupts = <ICU_GRP_NSR 22 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-map = <0 0 0 0 &CP110_LABEL(icu_nsr) 22 IRQ_TYPE_LEVEL_HIGH>;
+ interrupts = <22 IRQ_TYPE_LEVEL_HIGH>;
num-lanes = <1>;
clock-names = "core", "reg";
clocks = <&CP110_LABEL(clk) 1 13>, <&CP110_LABEL(clk) 1 14>;
@@ -457,8 +461,8 @@
/* non-prefetchable memory */
0x82000000 0 CP110_PCIEx_MEM_BASE(1) 0 CP110_PCIEx_MEM_BASE(1) 0 0xf00000>;
interrupt-map-mask = <0 0 0 0>;
- interrupt-map = <0 0 0 0 &CP110_LABEL(icu) ICU_GRP_NSR 24 IRQ_TYPE_LEVEL_HIGH>;
- interrupts = <ICU_GRP_NSR 24 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-map = <0 0 0 0 &CP110_LABEL(icu_nsr) 24 IRQ_TYPE_LEVEL_HIGH>;
+ interrupts = <24 IRQ_TYPE_LEVEL_HIGH>;
num-lanes = <1>;
clock-names = "core", "reg";
@@ -485,8 +489,8 @@
/* non-prefetchable memory */
0x82000000 0 CP110_PCIEx_MEM_BASE(2) 0 CP110_PCIEx_MEM_BASE(2) 0 0xf00000>;
interrupt-map-mask = <0 0 0 0>;
- interrupt-map = <0 0 0 0 &CP110_LABEL(icu) ICU_GRP_NSR 23 IRQ_TYPE_LEVEL_HIGH>;
- interrupts = <ICU_GRP_NSR 23 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-map = <0 0 0 0 &CP110_LABEL(icu_nsr) 23 IRQ_TYPE_LEVEL_HIGH>;
+ interrupts = <23 IRQ_TYPE_LEVEL_HIGH>;
num-lanes = <1>;
clock-names = "core", "reg";
--
2.14.1
ICU size in CP110 is not 0x10 but at least 0x440 bytes long (from the
specification).
Signed-off-by: Miquel Raynal <[email protected]>
Reviewed-by: Rob Herring <[email protected]>
Reviewed-by: Thomas Petazzoni <[email protected]>
---
Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt b/Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt
index aa8bf2ec8905..649b7ec9d9b1 100644
--- a/Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt
+++ b/Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt
@@ -39,7 +39,7 @@ Example:
icu: interrupt-controller@1e0000 {
compatible = "marvell,cp110-icu";
- reg = <0x1e0000 0x10>;
+ reg = <0x1e0000 0x440>;
#interrupt-cells = <3>;
interrupt-controller;
msi-parent = <&gicp>;
--
2.14.1
Enable the newly introduced Marvell SEI driver for the 64-bit Marvell
EBU platforms.
Suggested-by: Haim Boot <[email protected]>
Signed-off-by: Miquel Raynal <[email protected]>
Reviewed-by: Gregory CLEMENT <[email protected]>
---
arch/arm64/Kconfig.platforms | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms
index 2b1535cdeb7c..dc3c42938051 100644
--- a/arch/arm64/Kconfig.platforms
+++ b/arch/arm64/Kconfig.platforms
@@ -121,6 +121,7 @@ config ARCH_MVEBU
select MVEBU_ICU
select MVEBU_ODMI
select MVEBU_PIC
+ select MVEBU_SEI
select OF_GPIO
select PINCTRL
select PINCTRL_ARMADA_37XX
--
2.14.1
The ICU can handle several type of interrupt, each of them being handled
differently on AP side. On CP side, the ICU should be able to make the
distinction between each interrupt group by pointing to the right parent.
This is done through the introduction of new bindings, presenting the ICU
node as the parent of multiple ICU sub-nodes, each of them being an
interrupt type with a different interrupt parent. ICU interrupt 'clients'
now directly point to the right sub-node, avoiding the need for the extra
ICU_GRP_* parameter.
ICU subnodes are probed automatically with devm_platform_populate(). If
the node as no child, the probe function for NSRs will still be called
'manually' in order to preserve backward compatibility with DT using the
old binding.
Signed-off-by: Miquel Raynal <[email protected]>
---
drivers/irqchip/irq-mvebu-icu.c | 90 ++++++++++++++++++++++++++++++++++-------
1 file changed, 75 insertions(+), 15 deletions(-)
diff --git a/drivers/irqchip/irq-mvebu-icu.c b/drivers/irqchip/irq-mvebu-icu.c
index 24d45186eb6b..977e47b2716f 100644
--- a/drivers/irqchip/irq-mvebu-icu.c
+++ b/drivers/irqchip/irq-mvebu-icu.c
@@ -43,6 +43,7 @@ struct mvebu_icu {
struct regmap *regmap;
struct device *dev;
atomic_t initialized;
+ bool legacy_bindings;
};
struct mvebu_icu_irq_data {
@@ -51,6 +52,30 @@ struct mvebu_icu_irq_data {
unsigned int type;
};
+static struct mvebu_icu *mvebu_icu_dev_get_drvdata(struct platform_device *pdev)
+{
+ struct mvebu_icu *icu;
+
+ /*
+ * Device data being populated means we should be using legacy bindings.
+ * Using the _parent_ device data means we should be using new bindings.
+ */
+ icu = dev_get_drvdata(&pdev->dev);
+ if (icu) {
+ if (!icu->legacy_bindings)
+ return ERR_PTR(-EINVAL);
+ } else {
+ icu = dev_get_drvdata(pdev->dev.parent);
+ if (!icu)
+ return ERR_PTR(-ENODEV);
+
+ if (icu->legacy_bindings)
+ return ERR_PTR(-EINVAL);
+ }
+
+ return icu;
+}
+
static void mvebu_icu_init(struct mvebu_icu *icu, struct msi_msg *msg)
{
if (atomic_cmpxchg(&icu->initialized, false, true))
@@ -107,31 +132,35 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,
unsigned long *hwirq, unsigned int *type)
{
struct mvebu_icu *icu = platform_msi_get_host_data(d);
- unsigned int icu_group;
+ unsigned int param_count = icu->legacy_bindings ? 3 : 2;
/* Check the count of the parameters in dt */
- if (WARN_ON(fwspec->param_count < 3)) {
+ if (WARN_ON(fwspec->param_count != param_count)) {
dev_err(icu->dev, "wrong ICU parameter count %d\n",
fwspec->param_count);
return -EINVAL;
}
- /* Only ICU group type is handled */
- icu_group = fwspec->param[0];
- if (icu_group != ICU_GRP_NSR && icu_group != ICU_GRP_SR &&
- icu_group != ICU_GRP_SEI && icu_group != ICU_GRP_REI) {
- dev_err(icu->dev, "wrong ICU group type %x\n", icu_group);
- return -EINVAL;
+ if (icu->legacy_bindings) {
+ *hwirq = fwspec->param[1];
+ *type = fwspec->param[2];
+ if (fwspec->param[0] != ICU_GRP_NSR) {
+ dev_err(icu->dev, "wrong ICU group type %x\n",
+ fwspec->param[0]);
+ return -EINVAL;
+ }
+ } else {
+ *hwirq = fwspec->param[0];
+ *type = fwspec->param[1];
}
- *hwirq = fwspec->param[1];
if (*hwirq >= ICU_MAX_IRQS) {
dev_err(icu->dev, "invalid interrupt number %ld\n", *hwirq);
return -EINVAL;
}
/* Mask the type to prevent wrong DT configuration */
- *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
+ *type &= IRQ_TYPE_SENSE_MASK;
return 0;
}
@@ -157,7 +186,10 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
goto free_irqd;
}
- icu_irqd->icu_group = fwspec->param[0];
+ if (icu->legacy_bindings)
+ icu_irqd->icu_group = fwspec->param[0];
+ else
+ icu_irqd->icu_group = ICU_GRP_NSR;
icu_irqd->icu = icu;
err = platform_msi_domain_alloc(domain, virq, nr_irqs);
@@ -211,9 +243,9 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)
struct irq_domain *irq_domain;
struct mvebu_icu *icu;
- icu = dev_get_drvdata(&pdev->dev);
- if (!icu)
- return -ENODEV;
+ icu = mvebu_icu_dev_get_drvdata(pdev);
+ if (IS_ERR(icu))
+ return PTR_ERR(icu);
pdev->dev.msi_domain = of_msi_get_domain(&pdev->dev, pdev->dev.of_node,
DOMAIN_BUS_PLATFORM_MSI);
@@ -236,6 +268,22 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)
return 0;
}
+static const struct of_device_id mvebu_icu_subset_of_match[] = {
+ {
+ .compatible = "marvell,cp110-icu-nsr",
+ },
+ {},
+};
+
+static struct platform_driver mvebu_icu_subset_driver = {
+ .probe = mvebu_icu_subset_probe,
+ .driver = {
+ .name = "mvebu-icu-subset",
+ .of_match_table = mvebu_icu_subset_of_match,
+ },
+};
+builtin_platform_driver(mvebu_icu_subset_driver);
+
static struct regmap_config mvebu_icu_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
@@ -275,6 +323,15 @@ static int mvebu_icu_probe(struct platform_device *pdev)
if (!icu->irq_chip.name)
return -ENOMEM;
+ /*
+ * Legacy bindings: ICU is one node with one MSI parent: force manually
+ * the probe of the NSR interrupts side.
+ * New bindings: ICU node has children, one per interrupt controller
+ * having its own MSI parent: call platform_populate().
+ */
+ if (!of_get_child_count(pdev->dev.of_node))
+ icu->legacy_bindings = true;
+
icu->irq_chip.irq_mask = irq_chip_mask_parent;
icu->irq_chip.irq_unmask = irq_chip_unmask_parent;
icu->irq_chip.irq_eoi = irq_chip_eoi_parent;
@@ -299,7 +356,10 @@ static int mvebu_icu_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, icu);
- return mvebu_icu_subset_probe(pdev);
+ if (icu->legacy_bindings)
+ return mvebu_icu_subset_probe(pdev);
+ else
+ return devm_of_platform_populate(&pdev->dev);
}
static const struct of_device_id mvebu_icu_of_match[] = {
--
2.14.1
NSR (non-secure interrupts) are handled in the ICU driver like if there
was only this type of interrupt in the ICU. Change this behavior to
prepare the introduction of SEI (System Error Interrupts) support by
moving the NSR code in a separate function. This is done under the form
of a 'probe' function to ease future migration to NSR/SEI being platform
devices part of the ICU.
Signed-off-by: Miquel Raynal <[email protected]>
Reviewed-by: Thomas Petazzoni <[email protected]>
---
drivers/irqchip/irq-mvebu-icu.c | 58 +++++++++++++++++++++++------------------
1 file changed, 33 insertions(+), 25 deletions(-)
diff --git a/drivers/irqchip/irq-mvebu-icu.c b/drivers/irqchip/irq-mvebu-icu.c
index 607948870a14..24d45186eb6b 100644
--- a/drivers/irqchip/irq-mvebu-icu.c
+++ b/drivers/irqchip/irq-mvebu-icu.c
@@ -205,6 +205,37 @@ static const struct irq_domain_ops mvebu_icu_domain_ops = {
.free = mvebu_icu_irq_domain_free,
};
+static int mvebu_icu_subset_probe(struct platform_device *pdev)
+{
+ struct device_node *msi_parent_dn;
+ struct irq_domain *irq_domain;
+ struct mvebu_icu *icu;
+
+ icu = dev_get_drvdata(&pdev->dev);
+ if (!icu)
+ return -ENODEV;
+
+ pdev->dev.msi_domain = of_msi_get_domain(&pdev->dev, pdev->dev.of_node,
+ DOMAIN_BUS_PLATFORM_MSI);
+ if (!pdev->dev.msi_domain)
+ return -EPROBE_DEFER;
+
+ msi_parent_dn = irq_domain_get_of_node(pdev->dev.msi_domain);
+ if (!msi_parent_dn)
+ return -ENODEV;
+
+ irq_domain = platform_msi_create_device_domain(&pdev->dev, ICU_MAX_IRQS,
+ mvebu_icu_write_msg,
+ &mvebu_icu_domain_ops,
+ icu);
+ if (!irq_domain) {
+ dev_err(&pdev->dev, "Failed to create ICU MSI domain\n");
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
static struct regmap_config mvebu_icu_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
@@ -215,9 +246,6 @@ static struct regmap_config mvebu_icu_regmap_config = {
static int mvebu_icu_probe(struct platform_device *pdev)
{
struct mvebu_icu *icu;
- struct device_node *node = pdev->dev.of_node;
- struct device_node *gicp_dn;
- struct irq_domain *irq_domain;
struct resource *res;
void __iomem *regs;
int i;
@@ -255,19 +283,6 @@ static int mvebu_icu_probe(struct platform_device *pdev)
icu->irq_chip.irq_set_affinity = irq_chip_set_affinity_parent;
#endif
- /*
- * We're probed after MSI domains have been resolved, so force
- * resolution here.
- */
- pdev->dev.msi_domain = of_msi_get_domain(&pdev->dev, node,
- DOMAIN_BUS_PLATFORM_MSI);
- if (!pdev->dev.msi_domain)
- return -EPROBE_DEFER;
-
- gicp_dn = irq_domain_get_of_node(pdev->dev.msi_domain);
- if (!gicp_dn)
- return -ENODEV;
-
/*
* Clean all ICU interrupts with type SPI_NSR, required to
* avoid unpredictable SPI assignments done by firmware.
@@ -282,16 +297,9 @@ static int mvebu_icu_probe(struct platform_device *pdev)
regmap_write(icu->regmap, ICU_INT_CFG(i), 0);
}
- irq_domain =
- platform_msi_create_device_domain(&pdev->dev, ICU_MAX_IRQS,
- mvebu_icu_write_msg,
- &mvebu_icu_domain_ops, icu);
- if (!irq_domain) {
- dev_err(&pdev->dev, "Failed to create ICU domain\n");
- return -ENOMEM;
- }
+ platform_set_drvdata(pdev, icu);
- return 0;
+ return mvebu_icu_subset_probe(pdev);
}
static const struct of_device_id mvebu_icu_of_match[] = {
--
2.14.1
An SEI driver provides an MSI domain through which it is possible to
raise SEIs.
Handle the NSR probe function in a more generic way to support other
type of interrupts (ie. the SEIs).
For clarity we do not use tree IRQ domains for now but linear ones
instead, allocating the 207 ICU lines for each interrupt group.
Reallocating an ICU slot is prevented by the use of an ICU-wide bitmap.
Signed-off-by: Miquel Raynal <[email protected]>
---
drivers/irqchip/irq-mvebu-icu.c | 126 ++++++++++++++++++++++++++++++++++------
1 file changed, 108 insertions(+), 18 deletions(-)
diff --git a/drivers/irqchip/irq-mvebu-icu.c b/drivers/irqchip/irq-mvebu-icu.c
index 977e47b2716f..6ad6236d6ff1 100644
--- a/drivers/irqchip/irq-mvebu-icu.c
+++ b/drivers/irqchip/irq-mvebu-icu.c
@@ -28,6 +28,10 @@
#define ICU_SETSPI_NSR_AH 0x14
#define ICU_CLRSPI_NSR_AL 0x18
#define ICU_CLRSPI_NSR_AH 0x1c
+#define ICU_SET_SEI_AL 0x50
+#define ICU_SET_SEI_AH 0x54
+#define ICU_CLR_SEI_AL 0x58
+#define ICU_CLR_SEI_AH 0x5C
#define ICU_INT_CFG(x) (0x100 + 4 * (x))
#define ICU_INT_ENABLE BIT(24)
#define ICU_IS_EDGE BIT(28)
@@ -38,12 +42,28 @@
#define ICU_SATA0_ICU_ID 109
#define ICU_SATA1_ICU_ID 107
+struct mvebu_icu_subset_data {
+ unsigned int icu_group;
+ unsigned int offset_set_ah;
+ unsigned int offset_set_al;
+ unsigned int offset_clr_ah;
+ unsigned int offset_clr_al;
+};
+
struct mvebu_icu {
struct irq_chip irq_chip;
struct regmap *regmap;
struct device *dev;
- atomic_t initialized;
bool legacy_bindings;
+ /* Lock on interrupt allocations/releases */
+ spinlock_t msi_lock;
+ DECLARE_BITMAP(msi_bitmap, ICU_MAX_IRQS);
+};
+
+struct mvebu_icu_msi_data {
+ struct mvebu_icu *icu;
+ atomic_t initialized;
+ const struct mvebu_icu_subset_data *subset_data;
};
struct mvebu_icu_irq_data {
@@ -76,16 +96,25 @@ static struct mvebu_icu *mvebu_icu_dev_get_drvdata(struct platform_device *pdev)
return icu;
}
-static void mvebu_icu_init(struct mvebu_icu *icu, struct msi_msg *msg)
+static void mvebu_icu_init(struct mvebu_icu *icu, struct irq_domain *d,
+ struct msi_msg *msg)
{
- if (atomic_cmpxchg(&icu->initialized, false, true))
+ struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(d);
+ const struct mvebu_icu_subset_data *subset = msi_data->subset_data;
+
+ if (atomic_cmpxchg(&msi_data->initialized, false, true))
+ return;
+
+ /* Set 'SET' ICU SPI message address in AP */
+ regmap_write(icu->regmap, subset->offset_set_ah, msg[0].address_hi);
+ regmap_write(icu->regmap, subset->offset_set_al, msg[0].address_lo);
+
+ if (subset->icu_group != ICU_GRP_NSR)
return;
- /* Set Clear/Set ICU SPI message address in AP */
- regmap_write(icu->regmap, ICU_SETSPI_NSR_AH, msg[0].address_hi);
- regmap_write(icu->regmap, ICU_SETSPI_NSR_AL, msg[0].address_lo);
- regmap_write(icu->regmap, ICU_CLRSPI_NSR_AH, msg[1].address_hi);
- regmap_write(icu->regmap, ICU_CLRSPI_NSR_AL, msg[1].address_lo);
+ /* Set 'CLEAR' ICU SPI message address in AP (level-MSI only) */
+ regmap_write(icu->regmap, subset->offset_clr_ah, msg[1].address_hi);
+ regmap_write(icu->regmap, subset->offset_clr_al, msg[1].address_lo);
}
static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg)
@@ -96,8 +125,8 @@ static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg)
unsigned int icu_int;
if (msg->address_lo || msg->address_hi) {
- /* One off initialization */
- mvebu_icu_init(icu, msg);
+ /* One off initialization per domain */
+ mvebu_icu_init(icu, d->domain, msg);
/* Configure the ICU with irq number & type */
icu_int = msg->data | ICU_INT_ENABLE;
if (icu_irqd->type & IRQ_TYPE_EDGE_RISING)
@@ -131,7 +160,8 @@ static int
mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,
unsigned long *hwirq, unsigned int *type)
{
- struct mvebu_icu *icu = platform_msi_get_host_data(d);
+ struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(d);
+ struct mvebu_icu *icu = msi_data->icu;
unsigned int param_count = icu->legacy_bindings ? 3 : 2;
/* Check the count of the parameters in dt */
@@ -172,7 +202,9 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
int err;
unsigned long hwirq;
struct irq_fwspec *fwspec = args;
- struct mvebu_icu *icu = platform_msi_get_host_data(domain);
+ struct mvebu_icu_msi_data *msi_data =
+ platform_msi_get_host_data(domain);
+ struct mvebu_icu *icu = msi_data->icu;
struct mvebu_icu_irq_data *icu_irqd;
icu_irqd = kmalloc(sizeof(*icu_irqd), GFP_KERNEL);
@@ -186,16 +218,22 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
goto free_irqd;
}
+ spin_lock(&icu->msi_lock);
+ err = bitmap_allocate_region(icu->msi_bitmap, hwirq, 0);
+ spin_unlock(&icu->msi_lock);
+ if (err < 0)
+ goto free_irqd;
+
if (icu->legacy_bindings)
icu_irqd->icu_group = fwspec->param[0];
else
- icu_irqd->icu_group = ICU_GRP_NSR;
+ icu_irqd->icu_group = msi_data->subset_data->icu_group;
icu_irqd->icu = icu;
err = platform_msi_domain_alloc(domain, virq, nr_irqs);
if (err) {
dev_err(icu->dev, "failed to allocate ICU interrupt in parent domain\n");
- goto free_irqd;
+ goto free_bitmap;
}
/* Make sure there is no interrupt left pending by the firmware */
@@ -214,6 +252,10 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
free_msi:
platform_msi_domain_free(domain, virq, nr_irqs);
+free_bitmap:
+ spin_lock(&icu->msi_lock);
+ bitmap_release_region(icu->msi_bitmap, hwirq, 0);
+ spin_unlock(&icu->msi_lock);
free_irqd:
kfree(icu_irqd);
return err;
@@ -223,12 +265,19 @@ static void
mvebu_icu_irq_domain_free(struct irq_domain *domain, unsigned int virq,
unsigned int nr_irqs)
{
+ struct mvebu_icu_msi_data *msi_data =
+ platform_msi_get_host_data(domain);
+ struct mvebu_icu *icu = msi_data->icu;
struct irq_data *d = irq_get_irq_data(virq);
struct mvebu_icu_irq_data *icu_irqd = d->chip_data;
kfree(icu_irqd);
platform_msi_domain_free(domain, virq, nr_irqs);
+
+ spin_lock(&icu->msi_lock);
+ bitmap_release_region(icu->msi_bitmap, d->hwirq, 0);
+ spin_unlock(&icu->msi_lock);
}
static const struct irq_domain_ops mvebu_icu_domain_ops = {
@@ -239,14 +288,29 @@ static const struct irq_domain_ops mvebu_icu_domain_ops = {
static int mvebu_icu_subset_probe(struct platform_device *pdev)
{
+ const struct mvebu_icu_subset_data *subset;
+ struct mvebu_icu_msi_data *msi_data;
struct device_node *msi_parent_dn;
struct irq_domain *irq_domain;
struct mvebu_icu *icu;
+ msi_data = devm_kzalloc(&pdev->dev, sizeof(*msi_data), GFP_KERNEL);
+ if (!msi_data)
+ return -ENOMEM;
+
icu = mvebu_icu_dev_get_drvdata(pdev);
if (IS_ERR(icu))
return PTR_ERR(icu);
+ subset = of_device_get_match_data(&pdev->dev);
+ if (!subset) {
+ dev_err(&pdev->dev, "Could not retrieve subset data\n");
+ return -EINVAL;
+ }
+
+ msi_data->icu = icu;
+ msi_data->subset_data = subset;
+
pdev->dev.msi_domain = of_msi_get_domain(&pdev->dev, pdev->dev.of_node,
DOMAIN_BUS_PLATFORM_MSI);
if (!pdev->dev.msi_domain)
@@ -259,7 +323,7 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)
irq_domain = platform_msi_create_device_domain(&pdev->dev, ICU_MAX_IRQS,
mvebu_icu_write_msg,
&mvebu_icu_domain_ops,
- icu);
+ msi_data);
if (!irq_domain) {
dev_err(&pdev->dev, "Failed to create ICU MSI domain\n");
return -ENOMEM;
@@ -268,9 +332,30 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)
return 0;
}
+static const struct mvebu_icu_subset_data mvebu_icu_nsr_subset_data = {
+ .icu_group = ICU_GRP_NSR,
+ .offset_set_ah = ICU_SETSPI_NSR_AH,
+ .offset_set_al = ICU_SETSPI_NSR_AL,
+ .offset_clr_ah = ICU_CLRSPI_NSR_AH,
+ .offset_clr_al = ICU_CLRSPI_NSR_AL,
+};
+
+static const struct mvebu_icu_subset_data mvebu_icu_sei_subset_data = {
+ .icu_group = ICU_GRP_SEI,
+ .offset_set_ah = ICU_SET_SEI_AH,
+ .offset_set_al = ICU_SET_SEI_AL,
+ .offset_clr_ah = ICU_CLR_SEI_AH,
+ .offset_clr_al = ICU_CLR_SEI_AL,
+};
+
static const struct of_device_id mvebu_icu_subset_of_match[] = {
{
.compatible = "marvell,cp110-icu-nsr",
+ .data = &mvebu_icu_nsr_subset_data,
+ },
+ {
+ .compatible = "marvell,cp110-icu-sei",
+ .data = &mvebu_icu_sei_subset_data,
},
{},
};
@@ -317,6 +402,8 @@ static int mvebu_icu_probe(struct platform_device *pdev)
if (IS_ERR(icu->regmap))
return PTR_ERR(icu->regmap);
+ spin_lock_init(&icu->msi_lock);
+
icu->irq_chip.name = devm_kasprintf(&pdev->dev, GFP_KERNEL,
"ICU.%x",
(unsigned int)res->start);
@@ -341,7 +428,7 @@ static int mvebu_icu_probe(struct platform_device *pdev)
#endif
/*
- * Clean all ICU interrupts with type SPI_NSR, required to
+ * Clean all ICU interrupts of type NSR and SEI, required to
* avoid unpredictable SPI assignments done by firmware.
*/
for (i = 0 ; i < ICU_MAX_IRQS ; i++) {
@@ -350,7 +437,8 @@ static int mvebu_icu_probe(struct platform_device *pdev)
regmap_read(icu->regmap, ICU_INT_CFG(i), &icu_int);
icu_grp = icu_int >> ICU_GROUP_SHIFT;
- if (icu_grp == ICU_GRP_NSR)
+ if (icu_grp == ICU_GRP_NSR ||
+ (icu_grp == ICU_GRP_SEI && !icu->legacy_bindings))
regmap_write(icu->regmap, ICU_INT_CFG(i), 0);
}
@@ -363,7 +451,9 @@ static int mvebu_icu_probe(struct platform_device *pdev)
}
static const struct of_device_id mvebu_icu_of_match[] = {
- { .compatible = "marvell,cp110-icu", },
+ {
+ .compatible = "marvell,cp110-icu",
+ },
{},
};
--
2.14.1
Change the documentation to reflect the new bindings used for Marvell
ICU. This involves describing each interrupt group as a subnode of the
ICU node. Each of them having their own compatible.
Signed-off-by: Miquel Raynal <[email protected]>
---
.../bindings/interrupt-controller/marvell,icu.txt | 81 ++++++++++++++++++----
1 file changed, 69 insertions(+), 12 deletions(-)
diff --git a/Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt b/Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt
index 649b7ec9d9b1..6f7e4355b3d8 100644
--- a/Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt
+++ b/Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt
@@ -5,6 +5,8 @@ The Marvell ICU (Interrupt Consolidation Unit) controller is
responsible for collecting all wired-interrupt sources in the CP and
communicating them to the GIC in the AP, the unit translates interrupt
requests on input wires to MSG memory mapped transactions to the GIC.
+These messages will access a different GIC memory area depending on
+their type (NSR, SR, SEI, REI, etc).
Required properties:
@@ -12,20 +14,19 @@ Required properties:
- reg: Should contain ICU registers location and length.
+Subnodes: Each group of interrupt is declared as a subnode of the ICU,
+with their own compatible.
+
+Required properties for the icu_nsr/icu_sei subnodes:
+
+- compatible: Should be "marvell,cp110-icu-nsr" or "marvell,cp110-icu-sei".
+
- #interrupt-cells: Specifies the number of cells needed to encode an
- interrupt source. The value shall be 3.
+ interrupt source. The value shall be 2.
- The 1st cell is the group type of the ICU interrupt. Possible group
- types are:
+ The 1st cell is the index of the interrupt in the ICU unit.
- ICU_GRP_NSR (0x0) : Shared peripheral interrupt, non-secure
- ICU_GRP_SR (0x1) : Shared peripheral interrupt, secure
- ICU_GRP_SEI (0x4) : System error interrupt
- ICU_GRP_REI (0x5) : RAM error interrupt
-
- The 2nd cell is the index of the interrupt in the ICU unit.
-
- The 3rd cell is the type of the interrupt. See arm,gic.txt for
+ The 2nd cell is the type of the interrupt. See arm,gic.txt for
details.
- interrupt-controller: Identifies the node as an interrupt
@@ -35,17 +36,73 @@ Required properties:
that allows to trigger interrupts using MSG memory mapped
transactions.
+Note: each 'interrupts' property referring to any 'icu_xxx' node shall
+ have a different number within [0:206].
+
Example:
icu: interrupt-controller@1e0000 {
compatible = "marvell,cp110-icu";
reg = <0x1e0000 0x440>;
+
+ CP110_LABEL(icu_nsr): icu-nsr {
+ compatible = "marvell,cp110-icu-nsr";
+ #interrupt-cells = <2>;
+ interrupt-controller;
+ msi-parent = <&gicp>;
+ };
+
+ CP110_LABEL(icu_sei): icu-sei {
+ compatible = "marvell,cp110-icu-sei";
+ #interrupt-cells = <2>;
+ interrupt-controller;
+ msi-parent = <&sei>;
+ };
+};
+
+node1 {
+ interrupt-parent = <&icu_nsr>;
+ interrupts = <106 IRQ_TYPE_LEVEL_HIGH>;
+};
+
+node2 {
+ interrupt-parent = <&icu_sei>;
+ interrupts = <107 IRQ_TYPE_LEVEL_HIGH>;
+};
+
+/* Would not work with the above nodes */
+node3 {
+ interrupt-parent = <&icu_nsr>;
+ interrupts = <107 IRQ_TYPE_LEVEL_HIGH>;
+};
+
+Note on legacy bindings:
+Before using a subnode for each domain, only NSR were
+supported. Bindings were different in this way:
+
+- #interrupt-cells: The value was 3.
+ The 1st cell was the group type of the ICU interrupt. Possible
+ group types were:
+ ICU_GRP_NSR (0x0) : Shared peripheral interrupt, non-secure
+ ICU_GRP_SR (0x1) : Shared peripheral interrupt, secure
+ ICU_GRP_SEI (0x4) : System error interrupt
+ ICU_GRP_REI (0x5) : RAM error interrupt
+ The 2nd cell was the index of the interrupt in the ICU unit.
+ The 3rd cell was the type of the interrupt. See arm,gic.txt for
+ details.
+
+Example:
+
+icu: interrupt-controller@1e0000 {
+ compatible = "marvell,cp110-icu";
+ reg = <0x1e0000 0x440>;
+
#interrupt-cells = <3>;
interrupt-controller;
msi-parent = <&gicp>;
};
-usb3h0: usb3@500000 {
+node1 {
interrupt-parent = <&icu>;
interrupts = <ICU_GRP_NSR 106 IRQ_TYPE_LEVEL_HIGH>;
};
--
2.14.1
Before splitting the code to support multiple platform devices to
be probed (one for the ICU, one per interrupt group), let's switch to
regmap first by creating one in the ->probe().
Signed-off-by: Miquel Raynal <[email protected]>
---
drivers/irqchip/irq-mvebu-icu.c | 45 +++++++++++++++++++++++++++--------------
1 file changed, 30 insertions(+), 15 deletions(-)
diff --git a/drivers/irqchip/irq-mvebu-icu.c b/drivers/irqchip/irq-mvebu-icu.c
index 0f2655d7f19e..3694c0d73c0d 100644
--- a/drivers/irqchip/irq-mvebu-icu.c
+++ b/drivers/irqchip/irq-mvebu-icu.c
@@ -18,6 +18,8 @@
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
#include <dt-bindings/interrupt-controller/mvebu-icu.h>
@@ -38,7 +40,7 @@
struct mvebu_icu {
struct irq_chip irq_chip;
- void __iomem *base;
+ struct regmap *regmap;
struct irq_domain *domain;
struct device *dev;
atomic_t initialized;
@@ -56,10 +58,10 @@ static void mvebu_icu_init(struct mvebu_icu *icu, struct msi_msg *msg)
return;
/* Set Clear/Set ICU SPI message address in AP */
- writel_relaxed(msg[0].address_hi, icu->base + ICU_SETSPI_NSR_AH);
- writel_relaxed(msg[0].address_lo, icu->base + ICU_SETSPI_NSR_AL);
- writel_relaxed(msg[1].address_hi, icu->base + ICU_CLRSPI_NSR_AH);
- writel_relaxed(msg[1].address_lo, icu->base + ICU_CLRSPI_NSR_AL);
+ regmap_write(icu->regmap, ICU_SETSPI_NSR_AH, msg[0].address_hi);
+ regmap_write(icu->regmap, ICU_SETSPI_NSR_AL, msg[0].address_lo);
+ regmap_write(icu->regmap, ICU_CLRSPI_NSR_AH, msg[1].address_hi);
+ regmap_write(icu->regmap, ICU_CLRSPI_NSR_AL, msg[1].address_lo);
}
static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg)
@@ -82,7 +84,7 @@ static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg)
icu_int = 0;
}
- writel_relaxed(icu_int, icu->base + ICU_INT_CFG(d->hwirq));
+ regmap_write(icu->regmap, ICU_INT_CFG(d->hwirq), icu_int);
/*
* The SATA unit has 2 ports, and a dedicated ICU entry per
@@ -94,10 +96,10 @@ static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg)
* configured (regardless of which port is actually in use).
*/
if (d->hwirq == ICU_SATA0_ICU_ID || d->hwirq == ICU_SATA1_ICU_ID) {
- writel_relaxed(icu_int,
- icu->base + ICU_INT_CFG(ICU_SATA0_ICU_ID));
- writel_relaxed(icu_int,
- icu->base + ICU_INT_CFG(ICU_SATA1_ICU_ID));
+ regmap_write(icu->regmap, ICU_INT_CFG(ICU_SATA0_ICU_ID),
+ icu_int);
+ regmap_write(icu->regmap, ICU_INT_CFG(ICU_SATA1_ICU_ID),
+ icu_int);
}
}
@@ -204,12 +206,20 @@ static const struct irq_domain_ops mvebu_icu_domain_ops = {
.free = mvebu_icu_irq_domain_free,
};
+static struct regmap_config mvebu_icu_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .name = "mvebu_icu",
+};
+
static int mvebu_icu_probe(struct platform_device *pdev)
{
struct mvebu_icu *icu;
struct device_node *node = pdev->dev.of_node;
struct device_node *gicp_dn;
struct resource *res;
+ void __iomem *regs;
int i;
icu = devm_kzalloc(&pdev->dev, sizeof(struct mvebu_icu),
@@ -220,12 +230,17 @@ static int mvebu_icu_probe(struct platform_device *pdev)
icu->dev = &pdev->dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- icu->base = devm_ioremap_resource(&pdev->dev, res);
- if (IS_ERR(icu->base)) {
+ regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(regs)) {
dev_err(&pdev->dev, "Failed to map icu base address.\n");
- return PTR_ERR(icu->base);
+ return PTR_ERR(regs);
}
+ icu->regmap = devm_regmap_init_mmio(icu->dev, regs,
+ &mvebu_icu_regmap_config);
+ if (IS_ERR(icu->regmap))
+ return PTR_ERR(icu->regmap);
+
icu->irq_chip.name = devm_kasprintf(&pdev->dev, GFP_KERNEL,
"ICU.%x",
(unsigned int)res->start);
@@ -260,11 +275,11 @@ static int mvebu_icu_probe(struct platform_device *pdev)
for (i = 0 ; i < ICU_MAX_IRQS ; i++) {
u32 icu_int, icu_grp;
- icu_int = readl_relaxed(icu->base + ICU_INT_CFG(i));
+ regmap_read(icu->regmap, ICU_INT_CFG(i), &icu_int);
icu_grp = icu_int >> ICU_GROUP_SHIFT;
if (icu_grp == ICU_GRP_NSR)
- writel_relaxed(0x0, icu->base + ICU_INT_CFG(i));
+ regmap_write(icu->regmap, ICU_INT_CFG(i), 0);
}
icu->domain =
--
2.14.1
Make the current MSI irq_domain local to ease the split between ICU
platform device code and NSR platform device code.
Signed-off-by: Miquel Raynal <[email protected]>
Reviewed-by: Thomas Petazzoni <[email protected]>
---
drivers/irqchip/irq-mvebu-icu.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/irqchip/irq-mvebu-icu.c b/drivers/irqchip/irq-mvebu-icu.c
index 3694c0d73c0d..607948870a14 100644
--- a/drivers/irqchip/irq-mvebu-icu.c
+++ b/drivers/irqchip/irq-mvebu-icu.c
@@ -41,7 +41,6 @@
struct mvebu_icu {
struct irq_chip irq_chip;
struct regmap *regmap;
- struct irq_domain *domain;
struct device *dev;
atomic_t initialized;
};
@@ -218,6 +217,7 @@ static int mvebu_icu_probe(struct platform_device *pdev)
struct mvebu_icu *icu;
struct device_node *node = pdev->dev.of_node;
struct device_node *gicp_dn;
+ struct irq_domain *irq_domain;
struct resource *res;
void __iomem *regs;
int i;
@@ -282,11 +282,11 @@ static int mvebu_icu_probe(struct platform_device *pdev)
regmap_write(icu->regmap, ICU_INT_CFG(i), 0);
}
- icu->domain =
+ irq_domain =
platform_msi_create_device_domain(&pdev->dev, ICU_MAX_IRQS,
mvebu_icu_write_msg,
&mvebu_icu_domain_ops, icu);
- if (!icu->domain) {
+ if (!irq_domain) {
dev_err(&pdev->dev, "Failed to create ICU domain\n");
return -ENOMEM;
}
--
2.14.1
Hi Miquel,
On mar., mai 22 2018, Miquel Raynal <[email protected]> wrote:
> ICU size in CP110 is not 0x10 but at least 0x440 bytes long (from the
> specification).
>
> Fixes: 6ef84a827c37 ("arm64: dts: marvell: enable GICP and ICU on Armada 7K/8K")
> Cc: [email protected]
> Signed-off-by: Miquel Raynal <[email protected]>
> Reviewed-by: Thomas Petazzoni <[email protected]>
Applied on mvebu/fixes
Thanks,
Gregory
> ---
> arch/arm64/boot/dts/marvell/armada-cp110.dtsi | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/arch/arm64/boot/dts/marvell/armada-cp110.dtsi b/arch/arm64/boot/dts/marvell/armada-cp110.dtsi
> index 48cad7919efa..9fa41c54f69c 100644
> --- a/arch/arm64/boot/dts/marvell/armada-cp110.dtsi
> +++ b/arch/arm64/boot/dts/marvell/armada-cp110.dtsi
> @@ -146,7 +146,7 @@
>
> CP110_LABEL(icu): interrupt-controller@1e0000 {
> compatible = "marvell,cp110-icu";
> - reg = <0x1e0000 0x10>;
> + reg = <0x1e0000 0x440>;
> #interrupt-cells = <3>;
> interrupt-controller;
> msi-parent = <&gicp>;
> --
> 2.14.1
>
--
Gregory Clement, Bootlin (formerly Free Electrons)
Embedded Linux and Kernel engineering
http://bootlin.com
On 22/05/18 10:40, Miquel Raynal wrote:
> This is a cascaded interrupt controller in the AP806 GIC that collapses
> SEIs (System Error Interrupt) coming from the AP and the CPs (through
> the ICU).
>
> The SEI handles up to 64 interrupts. The first 21 interrupts are wired
> and come from the AP. The next 43 interrupts are from the CPs and are
wired to the AP (no interrupts come from it)
> triggered through MSI messages. To handle this complexity, the driver
> has to declare to the upper layer: one IRQ domain for the wired
> interrupts, one IRQ domain for the MSIs; and acts as a MSI server
s/server/controller/
> ('parent') by declaring an MSI domain.
>
> Suggested-by: Haim Boot <[email protected]>
> Signed-off-by: Miquel Raynal <[email protected]>
> ---
> drivers/irqchip/Kconfig | 3 +
> drivers/irqchip/Makefile | 1 +
> drivers/irqchip/irq-mvebu-sei.c | 422 ++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 426 insertions(+)
> create mode 100644 drivers/irqchip/irq-mvebu-sei.c
>
> diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
> index e9233db16e03..922e2a919cf3 100644
> --- a/drivers/irqchip/Kconfig
> +++ b/drivers/irqchip/Kconfig
> @@ -310,6 +310,9 @@ config MVEBU_ODMI
> config MVEBU_PIC
> bool
>
> +config MVEBU_SEI
> + bool
> +
> config LS_SCFG_MSI
> def_bool y if SOC_LS1021A || ARCH_LAYERSCAPE
> depends on PCI && PCI_MSI
> diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> index 15f268f646bf..69d2ccb454ef 100644
> --- a/drivers/irqchip/Makefile
> +++ b/drivers/irqchip/Makefile
> @@ -76,6 +76,7 @@ obj-$(CONFIG_MVEBU_GICP) += irq-mvebu-gicp.o
> obj-$(CONFIG_MVEBU_ICU) += irq-mvebu-icu.o
> obj-$(CONFIG_MVEBU_ODMI) += irq-mvebu-odmi.o
> obj-$(CONFIG_MVEBU_PIC) += irq-mvebu-pic.o
> +obj-$(CONFIG_MVEBU_SEI) += irq-mvebu-sei.o
> obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o
> obj-$(CONFIG_EZNPS_GIC) += irq-eznps.o
> obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o irq-aspeed-i2c-ic.o
> diff --git a/drivers/irqchip/irq-mvebu-sei.c b/drivers/irqchip/irq-mvebu-sei.c
> new file mode 100644
> index 000000000000..d9abd5e10741
> --- /dev/null
> +++ b/drivers/irqchip/irq-mvebu-sei.c
> @@ -0,0 +1,422 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#define pr_fmt(fmt) "mvebu-sei: " fmt
> +
> +#include <linux/irq.h>
> +#include <linux/interrupt.h>
> +#include <linux/irqchip/chained_irq.h>
> +#include <linux/irqdomain.h>
> +#include <linux/kernel.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_platform.h>
> +#include <linux/msi.h>
> +#include <linux/platform_device.h>
> +#include <linux/irqchip.h>
> +
> +#include <dt-bindings/interrupt-controller/arm-gic.h>
> +
> +/* Cause register */
> +#define GICP_SECR(idx) (0x0 + (idx * 0x4))
> +/* Mask register */
> +#define GICP_SEMR(idx) (0x20 + (idx * 0x4))
> +#define GICP_SET_SEI_OFFSET 0x30
> +
> +#define SEI_IRQ_COUNT_PER_REG 32
> +#define SEI_IRQ_REG_COUNT 2
> +#define SEI_IRQ_COUNT (SEI_IRQ_COUNT_PER_REG * SEI_IRQ_REG_COUNT)
> +#define SEI_IRQ_REG_IDX(irq_id) (irq_id / SEI_IRQ_COUNT_PER_REG)
> +#define SEI_IRQ_REG_BIT(irq_id) (irq_id % SEI_IRQ_COUNT_PER_REG)
> +
> +struct mvebu_sei_interrupt_range {
> + u32 first;
> + u32 number;
> +};
> +
> +struct mvebu_sei {
> + struct device *dev;
> + void __iomem *base;
> + struct resource *res;
> + struct irq_domain *ap_domain;
> + struct irq_domain *cp_domain;
> + struct mvebu_sei_interrupt_range ap_interrupts;
> + struct mvebu_sei_interrupt_range cp_interrupts;
> + /* Lock on MSI allocations/releases */
> + spinlock_t cp_msi_lock;
> + DECLARE_BITMAP(cp_msi_bitmap, SEI_IRQ_COUNT);
> +};
> +
> +static int mvebu_sei_domain_to_sei_irq(struct mvebu_sei *sei,
> + struct irq_domain *domain,
> + irq_hw_number_t hwirq)
> +{
> + if (domain == sei->ap_domain)
> + return sei->ap_interrupts.first + hwirq;
> + else
> + return sei->cp_interrupts.first + hwirq;
> +}
> +
> +static void mvebu_sei_reset(struct mvebu_sei *sei)
> +{
> + u32 reg_idx;
> +
> + /* Clear IRQ cause registers */
> + for (reg_idx = 0; reg_idx < SEI_IRQ_REG_COUNT; reg_idx++)
> + writel(0xFFFFFFFF, sei->base + GICP_SECR(reg_idx));
> +}
> +
> +static void mvebu_sei_mask_irq(struct irq_data *d)
> +{
> + struct mvebu_sei *sei = irq_data_get_irq_chip_data(d);
> + u32 sei_irq = mvebu_sei_domain_to_sei_irq(sei, d->domain, d->hwirq);
> + u32 reg_idx = SEI_IRQ_REG_IDX(sei_irq);
> + u32 reg;
> +
> + /* 1 disables the interrupt */
> + reg = readl(sei->base + GICP_SEMR(reg_idx));
> + reg |= BIT(SEI_IRQ_REG_BIT(sei_irq));
> + writel(reg, sei->base + GICP_SEMR(reg_idx));
> +}
> +
> +static void mvebu_sei_unmask_irq(struct irq_data *d)
> +{
> + struct mvebu_sei *sei = irq_data_get_irq_chip_data(d);
> + u32 sei_irq = mvebu_sei_domain_to_sei_irq(sei, d->domain, d->hwirq);
> + u32 reg_idx = SEI_IRQ_REG_IDX(sei_irq);
> + u32 reg;
> +
> + /* 0 enables the interrupt */
> + reg = readl(sei->base + GICP_SEMR(reg_idx));
> + reg &= ~BIT(SEI_IRQ_REG_BIT(sei_irq));
> + writel(reg, sei->base + GICP_SEMR(reg_idx));
> +}
> +
> +static void mvebu_sei_compose_msi_msg(struct irq_data *data,
> + struct msi_msg *msg)
> +{
> + struct mvebu_sei *sei = data->chip_data;
> + phys_addr_t set = sei->res->start + GICP_SET_SEI_OFFSET;
> +
> + msg->data = mvebu_sei_domain_to_sei_irq(sei, data->domain, data->hwirq);
> + msg->address_lo = lower_32_bits(set);
> + msg->address_hi = upper_32_bits(set);
> +}
> +
> +static struct irq_chip mvebu_sei_ap_wired_irq_chip = {
> + .name = "AP wired SEI",
> + .irq_mask = mvebu_sei_mask_irq,
> + .irq_unmask = mvebu_sei_unmask_irq,
> + .irq_eoi = irq_chip_eoi_parent,
> + .irq_set_affinity = irq_chip_set_affinity_parent,
> + .irq_set_type = irq_chip_set_type_parent,
You seem to assume that this driver is purely dealing with edge
interrupts. And yet you pass the request directly to the parrent. What
does it mean? Shouldn't you at least check that this is an edge request
and fail otherwise?
> +};
> +
> +static struct irq_chip mvebu_sei_cp_msi_irq_chip = {
> + .name = "CP MSI SEI",
> + .irq_mask = mvebu_sei_mask_irq,
> + .irq_unmask = mvebu_sei_unmask_irq,
> + .irq_eoi = irq_chip_eoi_parent,
> + .irq_set_affinity = irq_chip_set_affinity_parent,
> + .irq_set_type = irq_chip_set_type_parent,
Same here.
> + .irq_compose_msi_msg = mvebu_sei_compose_msi_msg,
> +};
> +
> +static int mvebu_sei_irq_domain_alloc(struct irq_domain *domain,
> + unsigned int virq, unsigned int nr_irqs,
> + void *args)
> +{
> + struct mvebu_sei *sei = domain->host_data;
> + struct irq_fwspec *fwspec = args;
> + struct irq_chip *irq_chip;
> + int sei_hwirq, hwirq;
> + int ret;
> +
> + /* Software only supports single allocations for now */
> + if (nr_irqs != 1)
> + return -ENOTSUPP;
> +
> + if (domain == sei->ap_domain) {
That's not great, really. Pushing the management of the two domains into
the same callbacks is pretty ugly, and makes the code rather confusing.
Is there anything really preventing the two domains from being managed
independently?
> + irq_chip = &mvebu_sei_ap_wired_irq_chip;
> + hwirq = fwspec->param[0];
> + } else {
> + irq_chip = &mvebu_sei_cp_msi_irq_chip;
> + spin_lock(&sei->cp_msi_lock);
This could as well be a mutex.
> + hwirq = bitmap_find_free_region(sei->cp_msi_bitmap,
> + SEI_IRQ_COUNT, 0);
It is a bit weird that you're allocating from a 64bit bitmap while you
only have 43 interrupts available... At the 44th interrupt, something
bad is going to happen.
> + spin_unlock(&sei->cp_msi_lock);
> + if (hwirq < 0)
> + return -ENOSPC;
> + }
> +
> + sei_hwirq = mvebu_sei_domain_to_sei_irq(sei, domain, hwirq);
Splitting the callbacks would definitely avoid that kind of horror.
> +
> + fwspec->fwnode = domain->parent->fwnode;
> + fwspec->param_count = 3;
> + fwspec->param[0] = GIC_SPI;
> + fwspec->param[1] = sei_hwirq;
> + fwspec->param[2] = IRQ_TYPE_EDGE_RISING;
> +
> + ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, fwspec);
> + if (ret)
> + goto release_region;
> +
> + ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, irq_chip, sei);
> + if (ret)
> + goto free_irq_parents;
> +
> + return 0;
> +
> +free_irq_parents:
> + irq_domain_free_irqs_parent(domain, virq, nr_irqs);
> +release_region:
> + if (domain == sei->cp_domain) {
> + spin_lock(&sei->cp_msi_lock);
> + bitmap_release_region(sei->cp_msi_bitmap, hwirq, 0);
> + spin_unlock(&sei->cp_msi_lock);
> + }
> +
> + return ret;
> +}
> +
> +static void mvebu_sei_irq_domain_free(struct irq_domain *domain,
> + unsigned int virq, unsigned int nr_irqs)
> +{
> + struct mvebu_sei *sei = domain->host_data;
> + struct irq_data *d = irq_domain_get_irq_data(domain, virq);
> + u32 irq_nb = sei->ap_interrupts.number + sei->cp_interrupts.number;
> +
> + if (nr_irqs != 1 || d->hwirq >= irq_nb) {
> + dev_err(sei->dev, "Invalid hwirq %lu\n", d->hwirq);
> + return;
> + }
> +
> + irq_domain_free_irqs_parent(domain, virq, nr_irqs);
> +
> + spin_lock(&sei->cp_msi_lock);
> + bitmap_release_region(sei->cp_msi_bitmap, d->hwirq, 0);
> + spin_unlock(&sei->cp_msi_lock);
> +}
> +
> +static const struct irq_domain_ops mvebu_sei_ap_domain_ops = {
> + .xlate = irq_domain_xlate_onecell,
> + .alloc = mvebu_sei_irq_domain_alloc,
> + .free = mvebu_sei_irq_domain_free,
> +};
> +
> +static const struct irq_domain_ops mvebu_sei_cp_domain_ops = {
> + .alloc = mvebu_sei_irq_domain_alloc,
> + .free = mvebu_sei_irq_domain_free,
> +};
> +
> +static struct irq_chip mvebu_sei_msi_irq_chip = {
> + .name = "SEI",
> + .irq_set_type = irq_chip_set_type_parent,
Same question here.
> +};
> +
> +static struct msi_domain_ops mvebu_sei_msi_ops = {
> +};
> +
> +static struct msi_domain_info mvebu_sei_msi_domain_info = {
> + .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS,
> + .ops = &mvebu_sei_msi_ops,
> + .chip = &mvebu_sei_msi_irq_chip,
> +};
> +
> +static void mvebu_sei_handle_cascade_irq(struct irq_desc *desc)
> +{
> + struct mvebu_sei *sei = irq_desc_get_handler_data(desc);
> + struct irq_chip *chip = irq_desc_get_chip(desc);
> + unsigned long irqmap, irq_bit;
> + u32 reg_idx, virq, irqn;
> +
> + chained_irq_enter(chip, desc);
> +
> + /* Read both SEI cause registers (64 bits) */
> + for (reg_idx = 0; reg_idx < SEI_IRQ_REG_COUNT; reg_idx++) {
> + irqmap = readl_relaxed(sei->base + GICP_SECR(reg_idx));
> +
> + /* Call handler for each set bit */
> + for_each_set_bit(irq_bit, &irqmap, SEI_IRQ_COUNT_PER_REG) {
> + /* Cause Register gives the SEI number */
> + irqn = irq_bit + reg_idx * SEI_IRQ_COUNT_PER_REG;
> + /*
> + * Finding Linux mapping (virq) needs the right domain
> + * and the relative hwirq (which start at 0 in both
> + * cases, while irqn is relative to all SEI interrupts).
> + */
It is a bit odd that you're virtualizing the hwirq number. The whole
point of splitting hwirq from virq is that you don't have to do that and
can use the the raw HW number. You're saving a tiny bit of memory in the
irq_domain, at the expense of more complexity. I don't know if that's
worth it...
> + if (irqn < sei->ap_interrupts.number) {
> + virq = irq_find_mapping(sei->ap_domain, irqn);
> + } else {
> + irqn -= sei->ap_interrupts.number;
> + virq = irq_find_mapping(sei->cp_domain, irqn);
> + }
> +
> + /* Call IRQ handler */
> + generic_handle_irq(virq);
> + }
> +
> + /* Clear interrupt indication by writing 1 to it */
> + writel(irqmap, sei->base + GICP_SECR(reg_idx));
> + }
> +
> + chained_irq_exit(chip, desc);
> +}
> +
> +static int mvebu_sei_probe(struct platform_device *pdev)
> +{
> + struct device_node *node = pdev->dev.of_node, *parent, *child;
> + struct irq_domain *parent_domain, *plat_domain;
> + struct mvebu_sei *sei;
> + const __be32 *property;
> + u32 parent_irq, size;
> + int ret;
> +
> + sei = devm_kzalloc(&pdev->dev, sizeof(*sei), GFP_KERNEL);
> + if (!sei)
> + return -ENOMEM;
> +
> + sei->dev = &pdev->dev;
> +
> + spin_lock_init(&sei->cp_msi_lock);
> +
> + sei->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + sei->base = devm_ioremap_resource(sei->dev, sei->res);
> + if (!sei->base) {
> + dev_err(sei->dev, "Failed to remap SEI resource\n");
> + return -ENODEV;
> + }
> +
> + mvebu_sei_reset(sei);
> +
> + /*
> + * Reserve the single (top-level) parent SPI IRQ from which all the
> + * interrupts handled by this driver will be signaled.
> + */
> + parent_irq = irq_of_parse_and_map(node, 0);
> + if (parent_irq <= 0) {
> + dev_err(sei->dev, "Failed to retrieve top-level SPI IRQ\n");
> + return -ENODEV;
> + }
> +
> + irq_set_chained_handler(parent_irq, mvebu_sei_handle_cascade_irq);
> + irq_set_handler_data(parent_irq, sei);
> +
> + /*
> + * SEIs in the range [ 0; 20] are wired and come from the AP.
> + * SEIs in the range [21; 63] are CP SEI and are triggered through MSIs.
> + *
> + * Each SEI 'domain' is represented as a subnode.
> + */
> +
> + /* Get a reference to the parent domain to create a hierarchy */
> + parent = of_irq_find_parent(node);
> + if (!parent) {
> + dev_err(sei->dev, "Failed to find parent IRQ node\n");
> + ret = -ENODEV;
> + goto dispose_irq;
> + }
> +
> + parent_domain = irq_find_host(parent);
> + if (!parent_domain) {
> + dev_err(sei->dev, "Failed to find parent IRQ domain\n");
> + ret = -ENODEV;
> + goto dispose_irq;
> + }
> +
> + /* Create the 'wired' hierarchy */
> + child = of_find_node_by_name(node, "sei-wired-controller");
> + if (!child) {
> + dev_err(sei->dev, "Missing 'sei-wired-controller' subnode\n");
> + ret = -ENODEV;
> + goto dispose_irq;
> + }
> +
> + property = of_get_property(child, "marvell,sei-ranges", &size);
> + if (!property || size != (2 * sizeof(u32))) {
> + dev_err(sei->dev, "Missing 'marvell,sei-ranges' property\n");
> + of_node_put(child);
> + ret = -ENODEV;
> + goto dispose_irq;
> + }
> +
> + sei->ap_interrupts.first = be32_to_cpu(property[0]);
> + sei->ap_interrupts.number = be32_to_cpu(property[1]);
> + sei->ap_domain = irq_domain_create_hierarchy(parent_domain, 0,
> + sei->ap_interrupts.number,
> + of_node_to_fwnode(child),
> + &mvebu_sei_ap_domain_ops,
> + sei);
> + of_node_put(child);
> + if (!sei->ap_domain) {
> + dev_err(sei->dev, "Failed to create AP IRQ domain\n");
> + ret = -ENOMEM;
> + goto dispose_irq;
> + }
> +
> + /* Create the 'MSI' hierarchy */
> + child = of_find_node_by_name(node, "sei-msi-controller");
> + if (!child) {
> + dev_err(sei->dev, "Missing 'sei-msi-controller' subnode\n");
> + ret = -ENODEV;
> + goto remove_ap_domain;
> + }
> +
> + property = of_get_property(child, "marvell,sei-ranges", &size);
> + if (!property || size != (2 * sizeof(u32))) {
> + dev_err(sei->dev, "Missing 'marvell,sei-ranges' property\n");
> + of_node_put(child);
> + ret = -ENODEV;
> + goto remove_ap_domain;
> + }
> +
> + sei->cp_interrupts.first = be32_to_cpu(property[0]);
> + sei->cp_interrupts.number = be32_to_cpu(property[1]);
> + sei->cp_domain = irq_domain_create_hierarchy(parent_domain, 0,
> + sei->cp_interrupts.number,
> + of_node_to_fwnode(child),
> + &mvebu_sei_cp_domain_ops,
> + sei);
> + if (!sei->cp_domain) {
> + pr_err("Failed to create CPs IRQ domain\n");
> + of_node_put(child);
> + ret = -ENOMEM;
> + goto remove_ap_domain;
> + }
> +
> + plat_domain = platform_msi_create_irq_domain(of_node_to_fwnode(child),
> + &mvebu_sei_msi_domain_info,
> + sei->cp_domain);
> + of_node_put(child);
> + if (!plat_domain) {
> + pr_err("Failed to create CPs MSI domain\n");
> + ret = -ENOMEM;
> + goto remove_cp_domain;
> + }
> +
> + platform_set_drvdata(pdev, sei);
> +
> + return 0;
> +
> +remove_cp_domain:
> + irq_domain_remove(sei->cp_domain);
> +remove_ap_domain:
> + irq_domain_remove(sei->ap_domain);
> +dispose_irq:
> + irq_dispose_mapping(parent_irq);
> +
> + return ret;
> +}
> +
> +static const struct of_device_id mvebu_sei_of_match[] = {
> + { .compatible = "marvell,armada-8k-sei", },
> + {},
> +};
> +
> +static struct platform_driver mvebu_sei_driver = {
> + .probe = mvebu_sei_probe,
> + .driver = {
> + .name = "mvebu-sei",
> + .of_match_table = mvebu_sei_of_match,
> + },
> +};
> +builtin_platform_driver(mvebu_sei_driver);
>
It feels like this patch could do with a total split:
- Introduce the wired side of the driver
- then the MSI part
Drop the common domain callbacks, and treat the two domains separately.
I seriously doubt there will be much of an overlap anyway.
Thanks,
M.
--
Jazz is not dead. It just smells funny...
On 22/05/18 10:40, Miquel Raynal wrote:
> An SEI driver provides an MSI domain through which it is possible to
> raise SEIs.
>
> Handle the NSR probe function in a more generic way to support other
> type of interrupts (ie. the SEIs).
>
> For clarity we do not use tree IRQ domains for now but linear ones
> instead, allocating the 207 ICU lines for each interrupt group.
What's the rational for not using trees? Because that's effectively a
100% overhead...
> Reallocating an ICU slot is prevented by the use of an ICU-wide bitmap.
>
> Signed-off-by: Miquel Raynal <[email protected]>
> ---
> drivers/irqchip/irq-mvebu-icu.c | 126 ++++++++++++++++++++++++++++++++++------
> 1 file changed, 108 insertions(+), 18 deletions(-)
>
> diff --git a/drivers/irqchip/irq-mvebu-icu.c b/drivers/irqchip/irq-mvebu-icu.c
> index 977e47b2716f..6ad6236d6ff1 100644
> --- a/drivers/irqchip/irq-mvebu-icu.c
> +++ b/drivers/irqchip/irq-mvebu-icu.c
> @@ -28,6 +28,10 @@
> #define ICU_SETSPI_NSR_AH 0x14
> #define ICU_CLRSPI_NSR_AL 0x18
> #define ICU_CLRSPI_NSR_AH 0x1c
> +#define ICU_SET_SEI_AL 0x50
> +#define ICU_SET_SEI_AH 0x54
> +#define ICU_CLR_SEI_AL 0x58
> +#define ICU_CLR_SEI_AH 0x5C
> #define ICU_INT_CFG(x) (0x100 + 4 * (x))
> #define ICU_INT_ENABLE BIT(24)
> #define ICU_IS_EDGE BIT(28)
> @@ -38,12 +42,28 @@
> #define ICU_SATA0_ICU_ID 109
> #define ICU_SATA1_ICU_ID 107
>
> +struct mvebu_icu_subset_data {
> + unsigned int icu_group;
> + unsigned int offset_set_ah;
> + unsigned int offset_set_al;
> + unsigned int offset_clr_ah;
> + unsigned int offset_clr_al;
> +};
> +
> struct mvebu_icu {
> struct irq_chip irq_chip;
> struct regmap *regmap;
> struct device *dev;
> - atomic_t initialized;
> bool legacy_bindings;
> + /* Lock on interrupt allocations/releases */
> + spinlock_t msi_lock;
> + DECLARE_BITMAP(msi_bitmap, ICU_MAX_IRQS);
> +};
> +
> +struct mvebu_icu_msi_data {
> + struct mvebu_icu *icu;
> + atomic_t initialized;
> + const struct mvebu_icu_subset_data *subset_data;
> };
>
> struct mvebu_icu_irq_data {
> @@ -76,16 +96,25 @@ static struct mvebu_icu *mvebu_icu_dev_get_drvdata(struct platform_device *pdev)
> return icu;
> }
>
> -static void mvebu_icu_init(struct mvebu_icu *icu, struct msi_msg *msg)
> +static void mvebu_icu_init(struct mvebu_icu *icu, struct irq_domain *d,
> + struct msi_msg *msg)
> {
> - if (atomic_cmpxchg(&icu->initialized, false, true))
> + struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(d);
> + const struct mvebu_icu_subset_data *subset = msi_data->subset_data;
> +
> + if (atomic_cmpxchg(&msi_data->initialized, false, true))
> + return;
> +
> + /* Set 'SET' ICU SPI message address in AP */
> + regmap_write(icu->regmap, subset->offset_set_ah, msg[0].address_hi);
> + regmap_write(icu->regmap, subset->offset_set_al, msg[0].address_lo);
> +
> + if (subset->icu_group != ICU_GRP_NSR)
> return;
>
> - /* Set Clear/Set ICU SPI message address in AP */
> - regmap_write(icu->regmap, ICU_SETSPI_NSR_AH, msg[0].address_hi);
> - regmap_write(icu->regmap, ICU_SETSPI_NSR_AL, msg[0].address_lo);
> - regmap_write(icu->regmap, ICU_CLRSPI_NSR_AH, msg[1].address_hi);
> - regmap_write(icu->regmap, ICU_CLRSPI_NSR_AL, msg[1].address_lo);
> + /* Set 'CLEAR' ICU SPI message address in AP (level-MSI only) */
> + regmap_write(icu->regmap, subset->offset_clr_ah, msg[1].address_hi);
> + regmap_write(icu->regmap, subset->offset_clr_al, msg[1].address_lo);
> }
>
> static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg)
> @@ -96,8 +125,8 @@ static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg)
> unsigned int icu_int;
>
> if (msg->address_lo || msg->address_hi) {
> - /* One off initialization */
> - mvebu_icu_init(icu, msg);
> + /* One off initialization per domain */
> + mvebu_icu_init(icu, d->domain, msg);
> /* Configure the ICU with irq number & type */
> icu_int = msg->data | ICU_INT_ENABLE;
> if (icu_irqd->type & IRQ_TYPE_EDGE_RISING)
> @@ -131,7 +160,8 @@ static int
> mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,
> unsigned long *hwirq, unsigned int *type)
> {
> - struct mvebu_icu *icu = platform_msi_get_host_data(d);
> + struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(d);
> + struct mvebu_icu *icu = msi_data->icu;
> unsigned int param_count = icu->legacy_bindings ? 3 : 2;
>
> /* Check the count of the parameters in dt */
> @@ -172,7 +202,9 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
> int err;
> unsigned long hwirq;
> struct irq_fwspec *fwspec = args;
> - struct mvebu_icu *icu = platform_msi_get_host_data(domain);
> + struct mvebu_icu_msi_data *msi_data =
> + platform_msi_get_host_data(domain);
> + struct mvebu_icu *icu = msi_data->icu;
> struct mvebu_icu_irq_data *icu_irqd;
>
> icu_irqd = kmalloc(sizeof(*icu_irqd), GFP_KERNEL);
> @@ -186,16 +218,22 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
> goto free_irqd;
> }
>
> + spin_lock(&icu->msi_lock);
> + err = bitmap_allocate_region(icu->msi_bitmap, hwirq, 0);
> + spin_unlock(&icu->msi_lock);
This (and the freeing counterpart) could deserve a couple of helpers.
> + if (err < 0)
> + goto free_irqd;
> +
> if (icu->legacy_bindings)
> icu_irqd->icu_group = fwspec->param[0];
> else
> - icu_irqd->icu_group = ICU_GRP_NSR;
> + icu_irqd->icu_group = msi_data->subset_data->icu_group;
> icu_irqd->icu = icu;
>
> err = platform_msi_domain_alloc(domain, virq, nr_irqs);
> if (err) {
> dev_err(icu->dev, "failed to allocate ICU interrupt in parent domain\n");
> - goto free_irqd;
> + goto free_bitmap;
> }
>
> /* Make sure there is no interrupt left pending by the firmware */
> @@ -214,6 +252,10 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
>
> free_msi:
> platform_msi_domain_free(domain, virq, nr_irqs);
> +free_bitmap:
> + spin_lock(&icu->msi_lock);
> + bitmap_release_region(icu->msi_bitmap, hwirq, 0);
> + spin_unlock(&icu->msi_lock);
> free_irqd:
> kfree(icu_irqd);
> return err;
> @@ -223,12 +265,19 @@ static void
> mvebu_icu_irq_domain_free(struct irq_domain *domain, unsigned int virq,
> unsigned int nr_irqs)
> {
> + struct mvebu_icu_msi_data *msi_data =
> + platform_msi_get_host_data(domain);
> + struct mvebu_icu *icu = msi_data->icu;
> struct irq_data *d = irq_get_irq_data(virq);
> struct mvebu_icu_irq_data *icu_irqd = d->chip_data;
>
> kfree(icu_irqd);
>
> platform_msi_domain_free(domain, virq, nr_irqs);
> +
> + spin_lock(&icu->msi_lock);
> + bitmap_release_region(icu->msi_bitmap, d->hwirq, 0);
> + spin_unlock(&icu->msi_lock);
> }
>
> static const struct irq_domain_ops mvebu_icu_domain_ops = {
> @@ -239,14 +288,29 @@ static const struct irq_domain_ops mvebu_icu_domain_ops = {
>
> static int mvebu_icu_subset_probe(struct platform_device *pdev)
> {
> + const struct mvebu_icu_subset_data *subset;
> + struct mvebu_icu_msi_data *msi_data;
> struct device_node *msi_parent_dn;
> struct irq_domain *irq_domain;
> struct mvebu_icu *icu;
>
> + msi_data = devm_kzalloc(&pdev->dev, sizeof(*msi_data), GFP_KERNEL);
> + if (!msi_data)
> + return -ENOMEM;
> +
> icu = mvebu_icu_dev_get_drvdata(pdev);
> if (IS_ERR(icu))
> return PTR_ERR(icu);
>
> + subset = of_device_get_match_data(&pdev->dev);
> + if (!subset) {
> + dev_err(&pdev->dev, "Could not retrieve subset data\n");
> + return -EINVAL;
> + }
> +
> + msi_data->icu = icu;
> + msi_data->subset_data = subset;
> +
> pdev->dev.msi_domain = of_msi_get_domain(&pdev->dev, pdev->dev.of_node,
> DOMAIN_BUS_PLATFORM_MSI);
> if (!pdev->dev.msi_domain)
> @@ -259,7 +323,7 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)
> irq_domain = platform_msi_create_device_domain(&pdev->dev, ICU_MAX_IRQS,
> mvebu_icu_write_msg,
> &mvebu_icu_domain_ops,
> - icu);
> + msi_data);
> if (!irq_domain) {
> dev_err(&pdev->dev, "Failed to create ICU MSI domain\n");
> return -ENOMEM;
> @@ -268,9 +332,30 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)
> return 0;
> }
>
> +static const struct mvebu_icu_subset_data mvebu_icu_nsr_subset_data = {
> + .icu_group = ICU_GRP_NSR,
> + .offset_set_ah = ICU_SETSPI_NSR_AH,
> + .offset_set_al = ICU_SETSPI_NSR_AL,
> + .offset_clr_ah = ICU_CLRSPI_NSR_AH,
> + .offset_clr_al = ICU_CLRSPI_NSR_AL,
> +};
> +
> +static const struct mvebu_icu_subset_data mvebu_icu_sei_subset_data = {
> + .icu_group = ICU_GRP_SEI,
> + .offset_set_ah = ICU_SET_SEI_AH,
> + .offset_set_al = ICU_SET_SEI_AL,
> + .offset_clr_ah = ICU_CLR_SEI_AH,
> + .offset_clr_al = ICU_CLR_SEI_AL,
I thought SEI was edge only, given what you do in mvebu_icu_init.
Confused...
> +};
> +
> static const struct of_device_id mvebu_icu_subset_of_match[] = {
> {
> .compatible = "marvell,cp110-icu-nsr",
> + .data = &mvebu_icu_nsr_subset_data,
> + },
> + {
> + .compatible = "marvell,cp110-icu-sei",
> + .data = &mvebu_icu_sei_subset_data,
> },
> {},
> };
> @@ -317,6 +402,8 @@ static int mvebu_icu_probe(struct platform_device *pdev)
> if (IS_ERR(icu->regmap))
> return PTR_ERR(icu->regmap);
>
> + spin_lock_init(&icu->msi_lock);
> +
> icu->irq_chip.name = devm_kasprintf(&pdev->dev, GFP_KERNEL,
> "ICU.%x",
> (unsigned int)res->start);
> @@ -341,7 +428,7 @@ static int mvebu_icu_probe(struct platform_device *pdev)
> #endif
>
> /*
> - * Clean all ICU interrupts with type SPI_NSR, required to
> + * Clean all ICU interrupts of type NSR and SEI, required to
> * avoid unpredictable SPI assignments done by firmware.
> */
> for (i = 0 ; i < ICU_MAX_IRQS ; i++) {
> @@ -350,7 +437,8 @@ static int mvebu_icu_probe(struct platform_device *pdev)
> regmap_read(icu->regmap, ICU_INT_CFG(i), &icu_int);
> icu_grp = icu_int >> ICU_GROUP_SHIFT;
>
> - if (icu_grp == ICU_GRP_NSR)
> + if (icu_grp == ICU_GRP_NSR ||
> + (icu_grp == ICU_GRP_SEI && !icu->legacy_bindings))
> regmap_write(icu->regmap, ICU_INT_CFG(i), 0);
> }
>
> @@ -363,7 +451,9 @@ static int mvebu_icu_probe(struct platform_device *pdev)
> }
>
> static const struct of_device_id mvebu_icu_of_match[] = {
> - { .compatible = "marvell,cp110-icu", },
> + {
> + .compatible = "marvell,cp110-icu",
> + },
> {},
> };
>
>
Thanks,
M.
--
Jazz is not dead. It just smells funny...
On Tue, May 22, 2018 at 11:40:38AM +0200, Miquel Raynal wrote:
> Change the documentation to reflect the new bindings used for Marvell
> ICU. This involves describing each interrupt group as a subnode of the
> ICU node. Each of them having their own compatible.
Need to explain why you need to do this and why breaking backwards
compatibility is okay.
>
> Signed-off-by: Miquel Raynal <[email protected]>
> ---
> .../bindings/interrupt-controller/marvell,icu.txt | 81 ++++++++++++++++++----
> 1 file changed, 69 insertions(+), 12 deletions(-)
>
> diff --git a/Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt b/Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt
> index 649b7ec9d9b1..6f7e4355b3d8 100644
> --- a/Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt
> +++ b/Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt
> @@ -5,6 +5,8 @@ The Marvell ICU (Interrupt Consolidation Unit) controller is
> responsible for collecting all wired-interrupt sources in the CP and
> communicating them to the GIC in the AP, the unit translates interrupt
> requests on input wires to MSG memory mapped transactions to the GIC.
> +These messages will access a different GIC memory area depending on
> +their type (NSR, SR, SEI, REI, etc).
>
> Required properties:
>
> @@ -12,20 +14,19 @@ Required properties:
>
> - reg: Should contain ICU registers location and length.
>
> +Subnodes: Each group of interrupt is declared as a subnode of the ICU,
> +with their own compatible.
> +
> +Required properties for the icu_nsr/icu_sei subnodes:
> +
> +- compatible: Should be "marvell,cp110-icu-nsr" or "marvell,cp110-icu-sei".
> +
> - #interrupt-cells: Specifies the number of cells needed to encode an
> - interrupt source. The value shall be 3.
> + interrupt source. The value shall be 2.
>
> - The 1st cell is the group type of the ICU interrupt. Possible group
> - types are:
> + The 1st cell is the index of the interrupt in the ICU unit.
>
> - ICU_GRP_NSR (0x0) : Shared peripheral interrupt, non-secure
> - ICU_GRP_SR (0x1) : Shared peripheral interrupt, secure
> - ICU_GRP_SEI (0x4) : System error interrupt
> - ICU_GRP_REI (0x5) : RAM error interrupt
What happens to SR and REI interrupts?
> -
> - The 2nd cell is the index of the interrupt in the ICU unit.
> -
> - The 3rd cell is the type of the interrupt. See arm,gic.txt for
> + The 2nd cell is the type of the interrupt. See arm,gic.txt for
> details.
>
> - interrupt-controller: Identifies the node as an interrupt
> @@ -35,17 +36,73 @@ Required properties:
> that allows to trigger interrupts using MSG memory mapped
> transactions.
>
> +Note: each 'interrupts' property referring to any 'icu_xxx' node shall
> + have a different number within [0:206].
> +
> Example:
>
> icu: interrupt-controller@1e0000 {
> compatible = "marvell,cp110-icu";
> reg = <0x1e0000 0x440>;
> +
> + CP110_LABEL(icu_nsr): icu-nsr {
'interrupt-controller' is the proper node name. Is there no register
range associated sub nodes?
> + compatible = "marvell,cp110-icu-nsr";
> + #interrupt-cells = <2>;
> + interrupt-controller;
> + msi-parent = <&gicp>;
> + };
> +
> + CP110_LABEL(icu_sei): icu-sei {
> + compatible = "marvell,cp110-icu-sei";
> + #interrupt-cells = <2>;
> + interrupt-controller;
> + msi-parent = <&sei>;
> + };
Mixture of tabs and spaces.
> +};
> +
> +node1 {
> + interrupt-parent = <&icu_nsr>;
> + interrupts = <106 IRQ_TYPE_LEVEL_HIGH>;
> +};
> +
> +node2 {
> + interrupt-parent = <&icu_sei>;
> + interrupts = <107 IRQ_TYPE_LEVEL_HIGH>;
> +};
> +
> +/* Would not work with the above nodes */
> +node3 {
> + interrupt-parent = <&icu_nsr>;
> + interrupts = <107 IRQ_TYPE_LEVEL_HIGH>;
> +};
> +
> +Note on legacy bindings:
> +Before using a subnode for each domain, only NSR were
> +supported. Bindings were different in this way:
> +
> +- #interrupt-cells: The value was 3.
> + The 1st cell was the group type of the ICU interrupt. Possible
> + group types were:
> + ICU_GRP_NSR (0x0) : Shared peripheral interrupt, non-secure
> + ICU_GRP_SR (0x1) : Shared peripheral interrupt, secure
> + ICU_GRP_SEI (0x4) : System error interrupt
> + ICU_GRP_REI (0x5) : RAM error interrupt
> + The 2nd cell was the index of the interrupt in the ICU unit.
> + The 3rd cell was the type of the interrupt. See arm,gic.txt for
> + details.
> +
> +Example:
> +
> +icu: interrupt-controller@1e0000 {
> + compatible = "marvell,cp110-icu";
> + reg = <0x1e0000 0x440>;
> +
> #interrupt-cells = <3>;
> interrupt-controller;
> msi-parent = <&gicp>;
> };
>
> -usb3h0: usb3@500000 {
> +node1 {
> interrupt-parent = <&icu>;
> interrupts = <ICU_GRP_NSR 106 IRQ_TYPE_LEVEL_HIGH>;
> };
> --
> 2.14.1
>
Hello,
On Tue, 5 Jun 2018 14:29:02 -0600, Rob Herring wrote:
> On Tue, May 22, 2018 at 11:40:38AM +0200, Miquel Raynal wrote:
> > Change the documentation to reflect the new bindings used for Marvell
> > ICU. This involves describing each interrupt group as a subnode of the
> > ICU node. Each of them having their own compatible.
>
> Need to explain why you need to do this and why breaking backwards
> compatibility is okay.
It does not break backward compatibility. The driver changes keep
support for the previous DT binding where there was a single node with
no subnodes.
The DT binding documentation still documents the legacy binding,
because it is still supported, and still works.
Best regards,
Thomas
--
Thomas Petazzoni, CTO, Bootlin (formerly Free Electrons)
Embedded Linux and Kernel engineering
https://bootlin.com
On Tue, May 22, 2018 at 11:40:39AM +0200, Miquel Raynal wrote:
> Describe the System Error Interrupt (SEI) controller. It aggregates two
> types of interrupts, wired and MSIs from respectively the AP and the
> CPs, into a single SPI interrupt.
>
> Suggested-by: Haim Boot <[email protected]>
> Signed-off-by: Miquel Raynal <[email protected]>
> ---
> .../bindings/interrupt-controller/marvell,sei.txt | 50 ++++++++++++++++++++++
> 1 file changed, 50 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/interrupt-controller/marvell,sei.txt
>
> diff --git a/Documentation/devicetree/bindings/interrupt-controller/marvell,sei.txt b/Documentation/devicetree/bindings/interrupt-controller/marvell,sei.txt
> new file mode 100644
> index 000000000000..689981036c30
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/interrupt-controller/marvell,sei.txt
> @@ -0,0 +1,50 @@
> +Marvell SEI (System Error Interrupt) Controller
> +-----------------------------------------------
> +
> +Marvell SEI (System Error Interrupt) controller is an interrupt
> +aggregator. It receives interrupts from several sources and aggregates
> +them to a single interrupt line (an SPI) on the parent interrupt
> +controller.
> +
> +This interrupt controller can handle up to 64 SEIs, a set comes from the
> +AP and is wired while a second set comes from the CPs by the mean of
> +MSIs. Each 'domain' is represented as a subnode.
> +
> +Required properties:
> +
> +- compatible: should be "marvell,armada-8k-sei".
> +- reg: SEI registers location and length.
> +- interrupts: identifies the parent IRQ that will be triggered.
> +
> +Child node 'sei-wired-controller' required properties:
> +
> +- marvell,sei-ranges: ranges of wired interrupts.
> +- #interrupt-cells: number of cells to define an SEI wired interrupt
> + coming from the AP, should be 1. The cell is the IRQ
> + number.
> +- interrupt-controller: identifies the node as an interrupt controller.
> +
> +Child node 'sei-msi-controller' required properties:
> +
> +- marvell,sei-ranges: ranges of non-wired interrupts triggered by way of
> + MSIs.
> +- msi-controller: identifies the node as an MSI controller.
> +
> +Example:
> +
> + sei: sei@3f0200 {
> + compatible = "marvell,armada-8k-sei";
> + reg = <0x3f0200 0x40>;
> + interrupts = <GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>;
> +
> + sei_wired_controller: sei-wired-controller@0 {
> + marvell,sei-ranges = <0 21>;
> + #interrupt-cells = <1>;
> + interrupt-controller;
> + };
> +
> + sei_msi_controller: sei-msi-controller@21 {
> + marvell,sei-ranges = <21 43>;
> + msi-controller;
> + };
I still think this should just be all one node. There's several examples
in the tree of nodes which are both interrupt-controller and
msi-controller. Marvell MPIC is one example.
Rob
Hi Marc,
> > +static struct irq_chip mvebu_sei_ap_wired_irq_chip = {
> > + .name = "AP wired SEI",
> > + .irq_mask = mvebu_sei_mask_irq,
> > + .irq_unmask = mvebu_sei_unmask_irq,
> > + .irq_eoi = irq_chip_eoi_parent,
> > + .irq_set_affinity = irq_chip_set_affinity_parent,
> > + .irq_set_type = irq_chip_set_type_parent,
>
> You seem to assume that this driver is purely dealing with edge
> interrupts. And yet you pass the request directly to the parrent. What
> does it mean? Shouldn't you at least check that this is an edge request
> and fail otherwise?
MSI are rising-edge interrupts while wired ones are level (high)
interrupts. I will correct this.
> > + irq_chip = &mvebu_sei_ap_wired_irq_chip;
> > + hwirq = fwspec->param[0];
> > + } else {
> > + irq_chip = &mvebu_sei_cp_msi_irq_chip;
> > + spin_lock(&sei->cp_msi_lock);
>
> This could as well be a mutex.
Ok.
>
> > + hwirq = bitmap_find_free_region(sei->cp_msi_bitmap,
> > + SEI_IRQ_COUNT, 0);
>
> It is a bit weird that you're allocating from a 64bit bitmap while you
> only have 43 interrupts available... At the 44th interrupt, something
> bad is going to happen.
Absolutely, to solve this issue, I just had to:
s/SEI_IRQ_COUNT/sei->cp_interrupts.number/
>
> > + spin_unlock(&sei->cp_msi_lock);
> > + if (hwirq < 0)
> > + return -ENOSPC;
> > + }
> > +
[...]
> > +static void mvebu_sei_handle_cascade_irq(struct irq_desc *desc)
> > +{
> > + struct mvebu_sei *sei = irq_desc_get_handler_data(desc);
> > + struct irq_chip *chip = irq_desc_get_chip(desc);
> > + unsigned long irqmap, irq_bit;
> > + u32 reg_idx, virq, irqn;
> > +
> > + chained_irq_enter(chip, desc);
> > +
> > + /* Read both SEI cause registers (64 bits) */
> > + for (reg_idx = 0; reg_idx < SEI_IRQ_REG_COUNT; reg_idx++) {
> > + irqmap = readl_relaxed(sei->base + GICP_SECR(reg_idx));
> > +
> > + /* Call handler for each set bit */
> > + for_each_set_bit(irq_bit, &irqmap, SEI_IRQ_COUNT_PER_REG) {
> > + /* Cause Register gives the SEI number */
> > + irqn = irq_bit + reg_idx * SEI_IRQ_COUNT_PER_REG;
> > + /*
> > + * Finding Linux mapping (virq) needs the right domain
> > + * and the relative hwirq (which start at 0 in both
> > + * cases, while irqn is relative to all SEI interrupts).
> > + */
>
> It is a bit odd that you're virtualizing the hwirq number. The whole
> point of splitting hwirq from virq is that you don't have to do that and
> can use the the raw HW number. You're saving a tiny bit of memory in the
> irq_domain, at the expense of more complexity. I don't know if that's
> worth it...
>
> > + if (irqn < sei->ap_interrupts.number) {
> > + virq = irq_find_mapping(sei->ap_domain, irqn);
> > + } else {
> > + irqn -= sei->ap_interrupts.number;
> > + virq = irq_find_mapping(sei->cp_domain, irqn);
> > + }
> > +
> > + /* Call IRQ handler */
> > + generic_handle_irq(virq);
> > + }
> > +
> > + /* Clear interrupt indication by writing 1 to it */
> > + writel(irqmap, sei->base + GICP_SECR(reg_idx));
> > + }
> > +
> > + chained_irq_exit(chip, desc);
> > +}
[...]
> It feels like this patch could do with a total split:
>
> - Introduce the wired side of the driver
> - then the MSI part
>
> Drop the common domain callbacks, and treat the two domains separately.
> I seriously doubt there will be much of an overlap anyway.
Maybe I don't get what "saving a tiny bit of memory" really means in
this situation. What I am doing right now is duplicating hundreds of
lines and changing things like:
sei_hwirq = mvebu_sei_domain_to_sei_irq(..., hwirq)
into
sei_hwirq = sei->ap_interrupts.first + d->hwirq;
and
sei_hwirq = sei->cp_interrupts.first + d->hwirq;
because I still need to translate this hwirq number into an offset
within 64 bits. In fact, for each configuration/management operation
like clearing, checking or masking an interrupt, a bit must be twisted
within a pair of registers. This offset cannot be just the hwirq
number, it must be shifted depending on the IRQ domain/type of
interrupt.
I'm sorry but I will need more guidance on this because I don't see the
point in duplicating so much code that was factorized.
Thanks,
Miquèl
Hi Marc,
Thank you for the review.
On Wed, 23 May 2018 15:23:48 +0100, Marc Zyngier <[email protected]>
wrote:
> On 22/05/18 10:40, Miquel Raynal wrote:
> > An SEI driver provides an MSI domain through which it is possible to
> > raise SEIs.
> >
> > Handle the NSR probe function in a more generic way to support other
> > type of interrupts (ie. the SEIs).
> >
> > For clarity we do not use tree IRQ domains for now but linear ones
> > instead, allocating the 207 ICU lines for each interrupt group.
>
> What's the rational for not using trees? Because that's effectively a
> 100% overhead...
There is none.
I had a look at how to do it.
In the ICU driver I would like to just drop the nvec parameter (number
of interrupts in the domain) when calling
platform_msi_create_device_domain().
The above function would call irq_domain_create_hierarchy() which would
create a tree domain instead of a linear one because of nvec being 0.
However, there is a check in platform_msi_alloc_priv_data() (also
called by platform_msi_create_device_domain()) that will error out if
nvec is null.
I'm not 100% sure this is safe but I don't see the point of
prohibiting nvec to be null here. So would you accept this
change?
--- a/drivers/base/platform-msi.c
+++ b/drivers/base/platform-msi.c
@@ -203,7 +203,7 @@ platform_msi_alloc_priv_data(struct device *dev,
unsigned int nvec,
* accordingly (which would impact the max number of MSI
* capable devices).
*/
- if (!dev->msi_domain || !write_msi_msg || !nvec || nvec > MAX_DEV_MSIS)
+ if (!dev->msi_domain || !write_msi_msg || nvec > MAX_DEV_MSIS)
return ERR_PTR(-EINVAL);
if (dev->msi_domain->bus_token != DOMAIN_BUS_PLATFORM_MSI) {
>
> > Reallocating an ICU slot is prevented by the use of an ICU-wide bitmap.
> >
> > Signed-off-by: Miquel Raynal <[email protected]>
> > ---
[...]
> > @@ -131,7 +160,8 @@ static int
> > mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,
> > unsigned long *hwirq, unsigned int *type)
> > {
> > - struct mvebu_icu *icu = platform_msi_get_host_data(d);
> > + struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(d);
> > + struct mvebu_icu *icu = msi_data->icu;
> > unsigned int param_count = icu->legacy_bindings ? 3 : 2;
> >
> > /* Check the count of the parameters in dt */
> > @@ -172,7 +202,9 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
> > int err;
> > unsigned long hwirq;
> > struct irq_fwspec *fwspec = args;
> > - struct mvebu_icu *icu = platform_msi_get_host_data(domain);
> > + struct mvebu_icu_msi_data *msi_data =
> > + platform_msi_get_host_data(domain);
> > + struct mvebu_icu *icu = msi_data->icu;
> > struct mvebu_icu_irq_data *icu_irqd;
> >
> > icu_irqd = kmalloc(sizeof(*icu_irqd), GFP_KERNEL);
> > @@ -186,16 +218,22 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
> > goto free_irqd;
> > }
> >
> > + spin_lock(&icu->msi_lock);
> > + err = bitmap_allocate_region(icu->msi_bitmap, hwirq, 0);
> > + spin_unlock(&icu->msi_lock);
>
> This (and the freeing counterpart) could deserve a couple of helpers.
Sure.
>
> > + if (err < 0)
> > + goto free_irqd;
> > +
> > if (icu->legacy_bindings)
> > icu_irqd->icu_group = fwspec->param[0];
> > else
> > - icu_irqd->icu_group = ICU_GRP_NSR;
> > + icu_irqd->icu_group = msi_data->subset_data->icu_group;
> > icu_irqd->icu = icu;
> >
> > err = platform_msi_domain_alloc(domain, virq, nr_irqs);
> > if (err) {
> > dev_err(icu->dev, "failed to allocate ICU interrupt in parent domain\n");
> > - goto free_irqd;
> > + goto free_bitmap;
> > }
> >
> > /* Make sure there is no interrupt left pending by the firmware */
[...]
> > @@ -268,9 +332,30 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)
> > return 0;
> > }
> >
> > +static const struct mvebu_icu_subset_data mvebu_icu_nsr_subset_data = {
> > + .icu_group = ICU_GRP_NSR,
> > + .offset_set_ah = ICU_SETSPI_NSR_AH,
> > + .offset_set_al = ICU_SETSPI_NSR_AL,
> > + .offset_clr_ah = ICU_CLRSPI_NSR_AH,
> > + .offset_clr_al = ICU_CLRSPI_NSR_AL,
> > +};
> > +
> > +static const struct mvebu_icu_subset_data mvebu_icu_sei_subset_data = {
> > + .icu_group = ICU_GRP_SEI,
> > + .offset_set_ah = ICU_SET_SEI_AH,
> > + .offset_set_al = ICU_SET_SEI_AL,
> > + .offset_clr_ah = ICU_CLR_SEI_AH,
> > + .offset_clr_al = ICU_CLR_SEI_AL,
>
> I thought SEI was edge only, given what you do in mvebu_icu_init.
> Confused...
AFAIK, the ICU can produce both level and edge MSI. Currently,
when it comes to SEI, we don't use the .offset_clr_a[hl] entries
because the SEI block expects edge-MSIs, but I thought useful to fill
them anyway. I will remove them both to avoid the confusion.
Thanks,
Miquèl
Hi Rob,
Thanks for reviewing.
On Tue, 5 Jun 2018 14:29:02 -0600, Rob Herring <[email protected]> wrote:
> On Tue, May 22, 2018 at 11:40:38AM +0200, Miquel Raynal wrote:
> > Change the documentation to reflect the new bindings used for Marvell
> > ICU. This involves describing each interrupt group as a subnode of the
> > ICU node. Each of them having their own compatible.
>
> Need to explain why you need to do this and why breaking backwards
> compatibility is okay.
As explained by Thomas, backward compatibility is not broken as old
bindings are still documented and supported.
I will update the commit message to reflect that point.
>
> >
> > Signed-off-by: Miquel Raynal <[email protected]>
> > ---
> > .../bindings/interrupt-controller/marvell,icu.txt | 81 ++++++++++++++++++----
> > 1 file changed, 69 insertions(+), 12 deletions(-)
> >
> > diff --git a/Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt b/Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt
> > index 649b7ec9d9b1..6f7e4355b3d8 100644
> > --- a/Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt
> > +++ b/Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt
> > @@ -5,6 +5,8 @@ The Marvell ICU (Interrupt Consolidation Unit) controller is
> > responsible for collecting all wired-interrupt sources in the CP and
> > communicating them to the GIC in the AP, the unit translates interrupt
> > requests on input wires to MSG memory mapped transactions to the GIC.
> > +These messages will access a different GIC memory area depending on
> > +their type (NSR, SR, SEI, REI, etc).
> >
> > Required properties:
> >
> > @@ -12,20 +14,19 @@ Required properties:
> >
> > - reg: Should contain ICU registers location and length.
> >
> > +Subnodes: Each group of interrupt is declared as a subnode of the ICU,
> > +with their own compatible.
> > +
> > +Required properties for the icu_nsr/icu_sei subnodes:
> > +
> > +- compatible: Should be "marvell,cp110-icu-nsr" or "marvell,cp110-icu-sei".
> > +
> > - #interrupt-cells: Specifies the number of cells needed to encode an
> > - interrupt source. The value shall be 3.
> > + interrupt source. The value shall be 2.
> >
> > - The 1st cell is the group type of the ICU interrupt. Possible group
> > - types are:
> > + The 1st cell is the index of the interrupt in the ICU unit.
> >
> > - ICU_GRP_NSR (0x0) : Shared peripheral interrupt, non-secure
> > - ICU_GRP_SR (0x1) : Shared peripheral interrupt, secure
> > - ICU_GRP_SEI (0x4) : System error interrupt
> > - ICU_GRP_REI (0x5) : RAM error interrupt
>
> What happens to SR and REI interrupts?
They were unused since the beginning.
These values are still detailed below (in the legacy section).
[...]
>
> > -
> > - The 2nd cell is the index of the interrupt in the ICU unit.
> > -
> > - The 3rd cell is the type of the interrupt. See arm,gic.txt for
> > + The 2nd cell is the type of the interrupt. See arm,gic.txt for
> > details.
> >
> > - interrupt-controller: Identifies the node as an interrupt
> > @@ -35,17 +36,73 @@ Required properties:
> > that allows to trigger interrupts using MSG memory mapped
> > transactions.
> >
> > +Note: each 'interrupts' property referring to any 'icu_xxx' node shall
> > + have a different number within [0:206].
> > +
> > Example:
> >
> > icu: interrupt-controller@1e0000 {
> > compatible = "marvell,cp110-icu";
> > reg = <0x1e0000 0x440>;
> > +
> > + CP110_LABEL(icu_nsr): icu-nsr {
>
> 'interrupt-controller' is the proper node name. Is there no register
> range associated sub nodes?
I will update the name.
A few are used only for NSR, a few only for SEI and others are used for
both. But ok, I will add register ranges.
>
> > + compatible = "marvell,cp110-icu-nsr";
> > + #interrupt-cells = <2>;
> > + interrupt-controller;
> > + msi-parent = <&gicp>;
> > + };
> > +
> > + CP110_LABEL(icu_sei): icu-sei {
> > + compatible = "marvell,cp110-icu-sei";
> > + #interrupt-cells = <2>;
> > + interrupt-controller;
> > + msi-parent = <&sei>;
> > + };
>
> Mixture of tabs and spaces.
Oops.
>
> > +};
> > +
Thanks,
Miquèl
Hi Rob, Marc,
On Tue, 5 Jun 2018 14:51:21 -0600, Rob Herring <[email protected]> wrote:
> On Tue, May 22, 2018 at 11:40:39AM +0200, Miquel Raynal wrote:
> > Describe the System Error Interrupt (SEI) controller. It aggregates two
> > types of interrupts, wired and MSIs from respectively the AP and the
> > CPs, into a single SPI interrupt.
> >
> > Suggested-by: Haim Boot <[email protected]>
> > Signed-off-by: Miquel Raynal <[email protected]>
> > ---
> > .../bindings/interrupt-controller/marvell,sei.txt | 50 ++++++++++++++++++++++
> > 1 file changed, 50 insertions(+)
> > create mode 100644 Documentation/devicetree/bindings/interrupt-controller/marvell,sei.txt
> >
> > diff --git a/Documentation/devicetree/bindings/interrupt-controller/marvell,sei.txt b/Documentation/devicetree/bindings/interrupt-controller/marvell,sei.txt
> > new file mode 100644
> > index 000000000000..689981036c30
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/interrupt-controller/marvell,sei.txt
> > @@ -0,0 +1,50 @@
> > +Marvell SEI (System Error Interrupt) Controller
> > +-----------------------------------------------
> > +
> > +Marvell SEI (System Error Interrupt) controller is an interrupt
> > +aggregator. It receives interrupts from several sources and aggregates
> > +them to a single interrupt line (an SPI) on the parent interrupt
> > +controller.
> > +
> > +This interrupt controller can handle up to 64 SEIs, a set comes from the
> > +AP and is wired while a second set comes from the CPs by the mean of
> > +MSIs. Each 'domain' is represented as a subnode.
> > +
> > +Required properties:
> > +
> > +- compatible: should be "marvell,armada-8k-sei".
> > +- reg: SEI registers location and length.
> > +- interrupts: identifies the parent IRQ that will be triggered.
> > +
> > +Child node 'sei-wired-controller' required properties:
> > +
> > +- marvell,sei-ranges: ranges of wired interrupts.
> > +- #interrupt-cells: number of cells to define an SEI wired interrupt
> > + coming from the AP, should be 1. The cell is the IRQ
> > + number.
> > +- interrupt-controller: identifies the node as an interrupt controller.
> > +
> > +Child node 'sei-msi-controller' required properties:
> > +
> > +- marvell,sei-ranges: ranges of non-wired interrupts triggered by way of
> > + MSIs.
> > +- msi-controller: identifies the node as an MSI controller.
> > +
> > +Example:
> > +
> > + sei: sei@3f0200 {
> > + compatible = "marvell,armada-8k-sei";
> > + reg = <0x3f0200 0x40>;
> > + interrupts = <GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>;
> > +
> > + sei_wired_controller: sei-wired-controller@0 {
> > + marvell,sei-ranges = <0 21>;
> > + #interrupt-cells = <1>;
> > + interrupt-controller;
> > + };
> > +
> > + sei_msi_controller: sei-msi-controller@21 {
> > + marvell,sei-ranges = <21 43>;
> > + msi-controller;
> > + };
>
> I still think this should just be all one node. There's several examples
> in the tree of nodes which are both interrupt-controller and
> msi-controller. Marvell MPIC is one example.
I checked Marvell MPIC example (armada 370 XP), it does not use
hierarchy domains, so I totally understand your point but I'm not sure
how I could get inspired by this driver (I'm looking for others).
Here I'm stuck. I know from a pure DT point of view the following is
not a valid argument. But from Linux, there is no easy way to handle
this situation without two different device nodes due to the internals
of the irqchip subsystem. There is simply no easy solution and having
only one node would require consequent changes in the core.
Maybe Marc will have an idea, but I think we already gave up on this
topic :/
Regards,
Miquèl
Hi Rob, Marc,
On Fri, 8 Jun 2018 16:46:23 +0200, Miquel Raynal
<[email protected]> wrote:
> Hi Rob, Marc,
>
> On Tue, 5 Jun 2018 14:51:21 -0600, Rob Herring <[email protected]> wrote:
>
> > On Tue, May 22, 2018 at 11:40:39AM +0200, Miquel Raynal wrote:
> > > Describe the System Error Interrupt (SEI) controller. It aggregates two
> > > types of interrupts, wired and MSIs from respectively the AP and the
> > > CPs, into a single SPI interrupt.
> > >
> > > Suggested-by: Haim Boot <[email protected]>
> > > Signed-off-by: Miquel Raynal <[email protected]>
> > > ---
> > > .../bindings/interrupt-controller/marvell,sei.txt | 50 ++++++++++++++++++++++
> > > 1 file changed, 50 insertions(+)
> > > create mode 100644 Documentation/devicetree/bindings/interrupt-controller/marvell,sei.txt
> > >
> > > diff --git a/Documentation/devicetree/bindings/interrupt-controller/marvell,sei.txt b/Documentation/devicetree/bindings/interrupt-controller/marvell,sei.txt
> > > new file mode 100644
> > > index 000000000000..689981036c30
> > > --- /dev/null
> > > +++ b/Documentation/devicetree/bindings/interrupt-controller/marvell,sei.txt
> > > @@ -0,0 +1,50 @@
> > > +Marvell SEI (System Error Interrupt) Controller
> > > +-----------------------------------------------
> > > +
> > > +Marvell SEI (System Error Interrupt) controller is an interrupt
> > > +aggregator. It receives interrupts from several sources and aggregates
> > > +them to a single interrupt line (an SPI) on the parent interrupt
> > > +controller.
> > > +
> > > +This interrupt controller can handle up to 64 SEIs, a set comes from the
> > > +AP and is wired while a second set comes from the CPs by the mean of
> > > +MSIs. Each 'domain' is represented as a subnode.
> > > +
> > > +Required properties:
> > > +
> > > +- compatible: should be "marvell,armada-8k-sei".
> > > +- reg: SEI registers location and length.
> > > +- interrupts: identifies the parent IRQ that will be triggered.
> > > +
> > > +Child node 'sei-wired-controller' required properties:
> > > +
> > > +- marvell,sei-ranges: ranges of wired interrupts.
> > > +- #interrupt-cells: number of cells to define an SEI wired interrupt
> > > + coming from the AP, should be 1. The cell is the IRQ
> > > + number.
> > > +- interrupt-controller: identifies the node as an interrupt controller.
> > > +
> > > +Child node 'sei-msi-controller' required properties:
> > > +
> > > +- marvell,sei-ranges: ranges of non-wired interrupts triggered by way of
> > > + MSIs.
> > > +- msi-controller: identifies the node as an MSI controller.
> > > +
> > > +Example:
> > > +
> > > + sei: sei@3f0200 {
> > > + compatible = "marvell,armada-8k-sei";
> > > + reg = <0x3f0200 0x40>;
> > > + interrupts = <GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>;
> > > +
> > > + sei_wired_controller: sei-wired-controller@0 {
> > > + marvell,sei-ranges = <0 21>;
> > > + #interrupt-cells = <1>;
> > > + interrupt-controller;
> > > + };
> > > +
> > > + sei_msi_controller: sei-msi-controller@21 {
> > > + marvell,sei-ranges = <21 43>;
> > > + msi-controller;
> > > + };
> >
> > I still think this should just be all one node. There's several examples
> > in the tree of nodes which are both interrupt-controller and
> > msi-controller. Marvell MPIC is one example.
>
> I checked Marvell MPIC example (armada 370 XP), it does not use
> hierarchy domains, so I totally understand your point but I'm not sure
> how I could get inspired by this driver (I'm looking for others).
>
> Here I'm stuck. I know from a pure DT point of view the following is
> not a valid argument. But from Linux, there is no easy way to handle
> this situation without two different device nodes due to the internals
> of the irqchip subsystem. There is simply no easy solution and having
> only one node would require consequent changes in the core.
>
> Maybe Marc will have an idea, but I think we already gave up on this
> topic :/
I double checked the MPIC driver and I still don't understand how we
can allocate both MSIs and wired interrupts with the current driver.
Anyway, I finally managed to merge 'sei_wired_controller' and
'sei_msi_controller' and have only one 'sei' node.
I had to do not use the fwnode of the 'sei' node in at least one IRQ
domain (I choose the wired one) and instead implement for that domain
the ->match() hook. I hope this is the right way to do it.
Please have a look at the v3 coming soon.
Thanks,
Miquèl