Received: by 2002:ac0:a594:0:0:0:0:0 with SMTP id m20-v6csp1353730imm; Tue, 22 May 2018 02:42:20 -0700 (PDT) X-Google-Smtp-Source: AB8JxZqN8h2R8hXO9duzSraQZghq5C9mn7llcpLWIwD7SBGJu2wOwmcF/5EGKb7jMlamYOMBEHT5 X-Received: by 2002:aa7:828c:: with SMTP id s12-v6mr23479574pfm.136.1526982140617; Tue, 22 May 2018 02:42:20 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1526982140; cv=none; d=google.com; s=arc-20160816; b=sniDfckGVQJc0hEoObymQefJn6/IN1m8ABde//die3thATs8AunhPaZxag1wirPlNn mLBru3E7mQKzmJI9L3mHWUrgglI66VmcJhPKyE1MZQKq1Wop3MNCck1BVDbAlF9KNOTi QTvEzzI58bVOxzL1d059V5iL9UyWRHDycB00eGG/QfYjfHPfrErgvUxs+B4ZXXnDSmAs AWudG3uLGYWi7mW4l+6ygIjPGolgoVmUZRhWZtFvqjrAsJOS0M/5qYNe23hqHqk39wMn cnjbyKUiX7HnZ7ieqnleGjMx/sAfQcTjjtyMaLcQXnJRDxMnikcNvsOtSos8SLSfF6Ts OPhQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:references:in-reply-to:message-id:date :subject:cc:to:from:arc-authentication-results; bh=9DMNJpIlfj4ZikE5pM3RZ5OKjapZVeL/mxR4mMWZ0zc=; b=VD7/ghBKTlLRN71czZ0X+zxyfwGjj7hwZSQOodPsXxmdH3ymoIQo9gdszckR21AaD5 GVW6lywsbKHyl+sTw3tfcTCBZT8LneTvRHpFtVJtWmE03PNHJO87CmD6NUq/eBjo1p+k 5u7Ed4c5yEDnjX3CquSWwIzkM7nqGeDF/PyWNz6J+4kpb5PZ/jL73kqMxD27CtY9nQZX mdhxIQECQElnvcO2NP2pI9VzYuV3LKfT0vOCj7BHUS6KuPWpY/CW/s9LMOyg7s/0L802 a867FasrU1+nRHarKuVFFs/gsGd8Ykm8jxFwmelHgfM+99DkB+ekT077MKHbSii+sB3c x2bQ== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id t10-v6si16031394plh.378.2018.05.22.02.42.06; Tue, 22 May 2018 02:42:20 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751646AbeEVJlO (ORCPT + 99 others); Tue, 22 May 2018 05:41:14 -0400 Received: from mail.bootlin.com ([62.4.15.54]:49705 "EHLO mail.bootlin.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751421AbeEVJk7 (ORCPT ); Tue, 22 May 2018 05:40:59 -0400 Received: by mail.bootlin.com (Postfix, from userid 110) id 75AFD207C2; Tue, 22 May 2018 11:40:57 +0200 (CEST) X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on mail.bootlin.com X-Spam-Level: X-Spam-Status: No, score=-1.0 required=5.0 tests=ALL_TRUSTED,SHORTCIRCUIT, URIBL_BLOCKED shortcircuit=ham autolearn=disabled version=3.4.0 Received: from localhost.localdomain (LStLambert-657-1-97-87.w90-63.abo.wanadoo.fr [90.63.216.87]) by mail.bootlin.com (Postfix) with ESMTPSA id D3CE9207DB; Tue, 22 May 2018 11:40:46 +0200 (CEST) From: Miquel Raynal To: Thomas Gleixner , Jason Cooper , Marc Zyngier , Catalin Marinas , Will Deacon , Andrew Lunn , Gregory Clement , Sebastian Hesselbarth Cc: Rob Herring , Mark Rutland , devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org, Thomas Petazzoni , Antoine Tenart , Maxime Chevallier , Nadav Haklai , Haim Boot , Hanna Hawa , linux-kernel@vger.kernel.org, Miquel Raynal Subject: [PATCH v2 09/16] irqchip/irq-mvebu-sei: add new driver for Marvell SEI Date: Tue, 22 May 2018 11:40:35 +0200 Message-Id: <20180522094042.24770-10-miquel.raynal@bootlin.com> X-Mailer: git-send-email 2.14.1 In-Reply-To: <20180522094042.24770-1-miquel.raynal@bootlin.com> References: <20180522094042.24770-1-miquel.raynal@bootlin.com> Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org 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 Signed-off-by: Miquel Raynal --- 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* 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