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[] = { |