diff options
Diffstat (limited to 'drivers/irqchip/irq-mvebu-icu.c')
-rw-r--r-- | drivers/irqchip/irq-mvebu-icu.c | 253 |
1 files changed, 188 insertions, 65 deletions
diff --git a/drivers/irqchip/irq-mvebu-icu.c b/drivers/irqchip/irq-mvebu-icu.c index 13063339b416..547045d89c4b 100644 --- a/drivers/irqchip/irq-mvebu-icu.c +++ b/drivers/irqchip/irq-mvebu-icu.c @@ -13,6 +13,7 @@ #include <linux/irq.h> #include <linux/irqchip.h> #include <linux/irqdomain.h> +#include <linux/jump_label.h> #include <linux/kernel.h> #include <linux/msi.h> #include <linux/of_irq.h> @@ -26,6 +27,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) @@ -36,12 +41,23 @@ #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; void __iomem *base; - struct irq_domain *domain; struct device *dev; +}; + +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 { @@ -50,28 +66,40 @@ struct mvebu_icu_irq_data { unsigned int type; }; -static void mvebu_icu_init(struct mvebu_icu *icu, struct msi_msg *msg) +DEFINE_STATIC_KEY_FALSE(legacy_bindings); + +static void mvebu_icu_init(struct mvebu_icu *icu, + struct mvebu_icu_msi_data *msi_data, + struct msi_msg *msg) { - if (atomic_cmpxchg(&icu->initialized, false, true)) + const struct mvebu_icu_subset_data *subset = msi_data->subset_data; + + if (atomic_cmpxchg(&msi_data->initialized, false, true)) 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); + /* Set 'SET' ICU SPI message address in AP */ + writel_relaxed(msg[0].address_hi, icu->base + subset->offset_set_ah); + writel_relaxed(msg[0].address_lo, icu->base + subset->offset_set_al); + + if (subset->icu_group != ICU_GRP_NSR) + return; + + /* Set 'CLEAR' ICU SPI message address in AP (level-MSI only) */ + writel_relaxed(msg[1].address_hi, icu->base + subset->offset_clr_ah); + writel_relaxed(msg[1].address_lo, icu->base + subset->offset_clr_al); } static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg) { struct irq_data *d = irq_get_irq_data(desc->irq); + struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(d->domain); struct mvebu_icu_irq_data *icu_irqd = d->chip_data; struct mvebu_icu *icu = icu_irqd->icu; 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, msi_data, msg); /* Configure the ICU with irq number & type */ icu_int = msg->data | ICU_INT_ENABLE; if (icu_irqd->type & IRQ_TYPE_EDGE_RISING) @@ -101,37 +129,66 @@ static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg) } } +static struct irq_chip mvebu_icu_nsr_chip = { + .name = "ICU-NSR", + .irq_mask = irq_chip_mask_parent, + .irq_unmask = irq_chip_unmask_parent, + .irq_eoi = irq_chip_eoi_parent, + .irq_set_type = irq_chip_set_type_parent, + .irq_set_affinity = irq_chip_set_affinity_parent, +}; + +static struct irq_chip mvebu_icu_sei_chip = { + .name = "ICU-SEI", + .irq_ack = irq_chip_ack_parent, + .irq_mask = irq_chip_mask_parent, + .irq_unmask = irq_chip_unmask_parent, + .irq_set_type = irq_chip_set_type_parent, + .irq_set_affinity = irq_chip_set_affinity_parent, +}; + 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; - unsigned int icu_group; + struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(d); + struct mvebu_icu *icu = platform_msi_get_host_data(d); + unsigned int param_count = static_branch_unlikely(&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 (static_branch_unlikely(&legacy_bindings)) { + *hwirq = fwspec->param[1]; + *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; + 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] & IRQ_TYPE_SENSE_MASK; + + /* + * The ICU receives level interrupts. While the NSR are also + * level interrupts, SEI are edge interrupts. Force the type + * here in this case. Please note that this makes the interrupt + * handling unreliable. + */ + if (msi_data->subset_data->icu_group == ICU_GRP_SEI) + *type = IRQ_TYPE_EDGE_RISING; } - *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; - return 0; } @@ -142,8 +199,10 @@ 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; + struct irq_chip *chip = &mvebu_icu_nsr_chip; icu_irqd = kmalloc(sizeof(*icu_irqd), GFP_KERNEL); if (!icu_irqd) @@ -156,7 +215,10 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, goto free_irqd; } - icu_irqd->icu_group = fwspec->param[0]; + if (static_branch_unlikely(&legacy_bindings)) + icu_irqd->icu_group = fwspec->param[0]; + else + icu_irqd->icu_group = msi_data->subset_data->icu_group; icu_irqd->icu = icu; err = platform_msi_domain_alloc(domain, virq, nr_irqs); @@ -170,8 +232,11 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, if (err) goto free_msi; + if (icu_irqd->icu_group == ICU_GRP_SEI) + chip = &mvebu_icu_sei_chip; + err = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, - &icu->irq_chip, icu_irqd); + chip, icu_irqd); if (err) { dev_err(icu->dev, "failed to set the data to IRQ domain\n"); goto free_msi; @@ -204,11 +269,84 @@ static const struct irq_domain_ops mvebu_icu_domain_ops = { .free = mvebu_icu_irq_domain_free, }; +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, +}; + +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, + }, + {}, +}; + +static int mvebu_icu_subset_probe(struct platform_device *pdev) +{ + struct mvebu_icu_msi_data *msi_data; + struct device_node *msi_parent_dn; + struct device *dev = &pdev->dev; + struct irq_domain *irq_domain; + + msi_data = devm_kzalloc(dev, sizeof(*msi_data), GFP_KERNEL); + if (!msi_data) + return -ENOMEM; + + if (static_branch_unlikely(&legacy_bindings)) { + msi_data->icu = dev_get_drvdata(dev); + msi_data->subset_data = &mvebu_icu_nsr_subset_data; + } else { + msi_data->icu = dev_get_drvdata(dev->parent); + msi_data->subset_data = of_device_get_match_data(dev); + } + + dev->msi_domain = of_msi_get_domain(dev, dev->of_node, + DOMAIN_BUS_PLATFORM_MSI); + if (!dev->msi_domain) + return -EPROBE_DEFER; + + msi_parent_dn = irq_domain_get_of_node(dev->msi_domain); + if (!msi_parent_dn) + return -ENODEV; + + irq_domain = platform_msi_create_device_tree_domain(dev, ICU_MAX_IRQS, + mvebu_icu_write_msg, + &mvebu_icu_domain_ops, + msi_data); + if (!irq_domain) { + dev_err(dev, "Failed to create ICU MSI domain\n"); + return -ENOMEM; + } + + return 0; +} + +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 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; int i; @@ -226,53 +364,38 @@ static int mvebu_icu_probe(struct platform_device *pdev) return PTR_ERR(icu->base); } - icu->irq_chip.name = devm_kasprintf(&pdev->dev, GFP_KERNEL, - "ICU.%x", - (unsigned int)res->start); - if (!icu->irq_chip.name) - return -ENOMEM; - - 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; - icu->irq_chip.irq_set_type = irq_chip_set_type_parent; -#ifdef CONFIG_SMP - 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. + * 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(). + * All ICU instances should use the same bindings. */ - 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; + if (!of_get_child_count(pdev->dev.of_node)) + static_branch_enable(&legacy_bindings); /* - * 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++) { - 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 || + (icu_grp == ICU_GRP_SEI && + !static_branch_unlikely(&legacy_bindings))) writel_relaxed(0x0, icu->base + ICU_INT_CFG(i)); } - icu->domain = - platform_msi_create_device_domain(&pdev->dev, ICU_MAX_IRQS, - mvebu_icu_write_msg, - &mvebu_icu_domain_ops, icu); - if (!icu->domain) { - dev_err(&pdev->dev, "Failed to create ICU domain\n"); - return -ENOMEM; - } + platform_set_drvdata(pdev, icu); - return 0; + if (static_branch_unlikely(&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[] = { |