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). Each interrupt domain is a tree domain because of maximum 207 entries. Reallocating an ICU slot is prevented by the use of an ICU-wide bitmap. Signed-off-by: Miquel Raynal <miquel.raynal@xxxxxxxxxxx> --- drivers/irqchip/irq-mvebu-icu.c | 148 +++++++++++++++++++++++++++++++++------- 1 file changed, 123 insertions(+), 25 deletions(-) diff --git a/drivers/irqchip/irq-mvebu-icu.c b/drivers/irqchip/irq-mvebu-icu.c index bb4f06833d17..0cf741dd0f51 100644 --- a/drivers/irqchip/irq-mvebu-icu.c +++ b/drivers/irqchip/irq-mvebu-icu.c @@ -26,6 +26,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 +40,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; void __iomem *base; struct device *dev; bool is_legacy; + /* Lock on interrupt allocations/releases */ + struct mutex 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 { @@ -50,28 +70,38 @@ struct mvebu_icu_irq_data { unsigned int type; }; -static void mvebu_icu_init(struct mvebu_icu *icu, struct msi_msg *msg) +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 '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/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 '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) @@ -105,7 +135,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->is_legacy ? 3 : 2; /* Check the count of the parameters in dt */ @@ -117,7 +148,7 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, if (icu->is_legacy) { *hwirq = fwspec->param[1]; - *type = fwspec->param[2]; + *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]); @@ -125,7 +156,7 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, } } else { *hwirq = fwspec->param[0]; - *type = fwspec->param[1]; + *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK; } if (*hwirq >= ICU_MAX_IRQS) { @@ -133,12 +164,36 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, return -EINVAL; } - /* Mask the type to prevent wrong DT configuration */ - *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; + /* + * The ICU receives level-interrupts. MSI SEI are + * edge-interrupts while MSI NSR are level-interrupts. Update the type + * accordingly for the parent irqchip. + */ + if (msi_data->subset_data->icu_group == ICU_GRP_SEI) + *type = IRQ_TYPE_EDGE_RISING; return 0; } +static int mvebu_icu_msi_bitmap_region_alloc(struct mvebu_icu *icu, int hwirq) +{ + int ret; + + mutex_lock(&icu->msi_lock); + ret = bitmap_allocate_region(icu->msi_bitmap, hwirq, 0); + mutex_unlock(&icu->msi_lock); + + return ret; +} + +static void mvebu_icu_msi_bitmap_region_release(struct mvebu_icu *icu, + int hwirq) +{ + mutex_lock(&icu->msi_lock); + bitmap_release_region(icu->msi_bitmap, hwirq, 0); + mutex_unlock(&icu->msi_lock); +} + static int mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs, void *args) @@ -146,7 +201,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); @@ -160,16 +217,20 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, goto free_irqd; } + err = mvebu_icu_msi_bitmap_region_alloc(icu, hwirq); + if (err) + goto free_irqd; + if (icu->is_legacy) 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 */ @@ -188,6 +249,8 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, free_msi: platform_msi_domain_free(domain, virq, nr_irqs); +free_bitmap: + mvebu_icu_msi_bitmap_region_release(icu, hwirq); free_irqd: kfree(icu_irqd); return err; @@ -197,12 +260,17 @@ 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); + + mvebu_icu_msi_bitmap_region_release(icu, d->hwirq); } static const struct irq_domain_ops mvebu_icu_domain_ops = { @@ -211,28 +279,54 @@ 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; - struct mvebu_icu *icu; + + msi_data = devm_kzalloc(dev, sizeof(*msi_data), GFP_KERNEL); + if (!msi_data) + return -ENOMEM; /* * Device data being populated means we are using the legacy bindings. * Using the parent device data means we are using the new bindings. */ - if (dev_get_drvdata(dev)) - icu = dev_get_drvdata(dev); - else - icu = dev_get_drvdata(dev->parent); + if (dev_get_drvdata(dev)) { + 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); @@ -246,7 +340,7 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev) irq_domain = platform_msi_create_device_tree_domain(dev, ICU_MAX_IRQS, mvebu_icu_write_msg, &mvebu_icu_domain_ops, - icu); + msi_data); if (!irq_domain) { dev_err(dev, "Failed to create ICU MSI domain\n"); return -ENOMEM; @@ -284,6 +378,8 @@ static int mvebu_icu_probe(struct platform_device *pdev) return PTR_ERR(icu->base); } + mutex_init(&icu->msi_lock); + icu->irq_chip.name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "ICU.%x", (unsigned int)res->start); @@ -308,7 +404,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++) { @@ -331,7 +427,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 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html