diff options
Diffstat (limited to 'drivers/irqchip/irq-gic-v2m.c')
| -rw-r--r-- | drivers/irqchip/irq-gic-v2m.c | 163 | 
1 files changed, 111 insertions, 52 deletions
| diff --git a/drivers/irqchip/irq-gic-v2m.c b/drivers/irqchip/irq-gic-v2m.c index 12985daa66ab..87f8d104acab 100644 --- a/drivers/irqchip/irq-gic-v2m.c +++ b/drivers/irqchip/irq-gic-v2m.c @@ -37,19 +37,31 @@  #define V2M_MSI_SETSPI_NS	       0x040  #define V2M_MIN_SPI		       32  #define V2M_MAX_SPI		       1019 +#define V2M_MSI_IIDR		       0xFCC  #define V2M_MSI_TYPER_BASE_SPI(x)      \  	       (((x) >> V2M_MSI_TYPER_BASE_SHIFT) & V2M_MSI_TYPER_BASE_MASK)  #define V2M_MSI_TYPER_NUM_SPI(x)       ((x) & V2M_MSI_TYPER_NUM_MASK) +/* APM X-Gene with GICv2m MSI_IIDR register value */ +#define XGENE_GICV2M_MSI_IIDR		0x06000170 + +/* List of flags for specific v2m implementation */ +#define GICV2M_NEEDS_SPI_OFFSET		0x00000001 + +static LIST_HEAD(v2m_nodes); +static DEFINE_SPINLOCK(v2m_lock); +  struct v2m_data { -	spinlock_t msi_cnt_lock; +	struct list_head entry; +	struct device_node *node;  	struct resource res;	/* GICv2m resource */  	void __iomem *base;	/* GICv2m virt address */  	u32 spi_start;		/* The SPI number that MSIs start */  	u32 nr_spis;		/* The number of SPIs for MSIs */  	unsigned long *bm;	/* MSI vector bitmap */ +	u32 flags;		/* v2m flags for specific implementation */  };  static void gicv2m_mask_msi_irq(struct irq_data *d) @@ -98,6 +110,9 @@ static void gicv2m_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)  	msg->address_hi = upper_32_bits(addr);  	msg->address_lo = lower_32_bits(addr);  	msg->data = data->hwirq; + +	if (v2m->flags & GICV2M_NEEDS_SPI_OFFSET) +		msg->data -= v2m->spi_start;  }  static struct irq_chip gicv2m_irq_chip = { @@ -113,17 +128,21 @@ static int gicv2m_irq_gic_domain_alloc(struct irq_domain *domain,  				       unsigned int virq,  				       irq_hw_number_t hwirq)  { -	struct of_phandle_args args; +	struct irq_fwspec fwspec;  	struct irq_data *d;  	int err; -	args.np = domain->parent->of_node; -	args.args_count = 3; -	args.args[0] = 0; -	args.args[1] = hwirq - 32; -	args.args[2] = IRQ_TYPE_EDGE_RISING; +	if (is_of_node(domain->parent->fwnode)) { +		fwspec.fwnode = domain->parent->fwnode; +		fwspec.param_count = 3; +		fwspec.param[0] = 0; +		fwspec.param[1] = hwirq - 32; +		fwspec.param[2] = IRQ_TYPE_EDGE_RISING; +	} else { +		return -EINVAL; +	} -	err = irq_domain_alloc_irqs_parent(domain, virq, 1, &args); +	err = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);  	if (err)  		return err; @@ -143,27 +162,30 @@ static void gicv2m_unalloc_msi(struct v2m_data *v2m, unsigned int hwirq)  		return;  	} -	spin_lock(&v2m->msi_cnt_lock); +	spin_lock(&v2m_lock);  	__clear_bit(pos, v2m->bm); -	spin_unlock(&v2m->msi_cnt_lock); +	spin_unlock(&v2m_lock);  }  static int gicv2m_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,  				   unsigned int nr_irqs, void *args)  { -	struct v2m_data *v2m = domain->host_data; +	struct v2m_data *v2m = NULL, *tmp;  	int hwirq, offset, err = 0; -	spin_lock(&v2m->msi_cnt_lock); -	offset = find_first_zero_bit(v2m->bm, v2m->nr_spis); -	if (offset < v2m->nr_spis) -		__set_bit(offset, v2m->bm); -	else -		err = -ENOSPC; -	spin_unlock(&v2m->msi_cnt_lock); +	spin_lock(&v2m_lock); +	list_for_each_entry(tmp, &v2m_nodes, entry) { +		offset = find_first_zero_bit(tmp->bm, tmp->nr_spis); +		if (offset < tmp->nr_spis) { +			__set_bit(offset, tmp->bm); +			v2m = tmp; +			break; +		} +	} +	spin_unlock(&v2m_lock); -	if (err) -		return err; +	if (!v2m) +		return -ENOSPC;  	hwirq = v2m->spi_start + offset; @@ -224,12 +246,61 @@ static struct msi_domain_info gicv2m_pmsi_domain_info = {  	.chip	= &gicv2m_pmsi_irq_chip,  }; +static void gicv2m_teardown(void) +{ +	struct v2m_data *v2m, *tmp; + +	list_for_each_entry_safe(v2m, tmp, &v2m_nodes, entry) { +		list_del(&v2m->entry); +		kfree(v2m->bm); +		iounmap(v2m->base); +		of_node_put(v2m->node); +		kfree(v2m); +	} +} + +static int gicv2m_allocate_domains(struct irq_domain *parent) +{ +	struct irq_domain *inner_domain, *pci_domain, *plat_domain; +	struct v2m_data *v2m; + +	v2m = list_first_entry_or_null(&v2m_nodes, struct v2m_data, entry); +	if (!v2m) +		return 0; + +	inner_domain = irq_domain_create_tree(of_node_to_fwnode(v2m->node), +					      &gicv2m_domain_ops, v2m); +	if (!inner_domain) { +		pr_err("Failed to create GICv2m domain\n"); +		return -ENOMEM; +	} + +	inner_domain->bus_token = DOMAIN_BUS_NEXUS; +	inner_domain->parent = parent; +	pci_domain = pci_msi_create_irq_domain(of_node_to_fwnode(v2m->node), +					       &gicv2m_msi_domain_info, +					       inner_domain); +	plat_domain = platform_msi_create_irq_domain(of_node_to_fwnode(v2m->node), +						     &gicv2m_pmsi_domain_info, +						     inner_domain); +	if (!pci_domain || !plat_domain) { +		pr_err("Failed to create MSI domains\n"); +		if (plat_domain) +			irq_domain_remove(plat_domain); +		if (pci_domain) +			irq_domain_remove(pci_domain); +		irq_domain_remove(inner_domain); +		return -ENOMEM; +	} + +	return 0; +} +  static int __init gicv2m_init_one(struct device_node *node,  				  struct irq_domain *parent)  {  	int ret;  	struct v2m_data *v2m; -	struct irq_domain *inner_domain, *pci_domain, *plat_domain;  	v2m = kzalloc(sizeof(struct v2m_data), GFP_KERNEL);  	if (!v2m) { @@ -237,6 +308,9 @@ static int __init gicv2m_init_one(struct device_node *node,  		return -ENOMEM;  	} +	INIT_LIST_HEAD(&v2m->entry); +	v2m->node = node; +  	ret = of_address_to_resource(node, 0, &v2m->res);  	if (ret) {  		pr_err("Failed to allocate v2m resource.\n"); @@ -266,6 +340,17 @@ static int __init gicv2m_init_one(struct device_node *node,  		goto err_iounmap;  	} +	/* +	 * APM X-Gene GICv2m implementation has an erratum where +	 * the MSI data needs to be the offset from the spi_start +	 * in order to trigger the correct MSI interrupt. This is +	 * different from the standard GICv2m implementation where +	 * the MSI data is the absolute value within the range from +	 * spi_start to (spi_start + num_spis). +	 */ +	if (readl_relaxed(v2m->base + V2M_MSI_IIDR) == XGENE_GICV2M_MSI_IIDR) +		v2m->flags |= GICV2M_NEEDS_SPI_OFFSET; +  	v2m->bm = kzalloc(sizeof(long) * BITS_TO_LONGS(v2m->nr_spis),  			  GFP_KERNEL);  	if (!v2m->bm) { @@ -273,43 +358,13 @@ static int __init gicv2m_init_one(struct device_node *node,  		goto err_iounmap;  	} -	inner_domain = irq_domain_add_tree(node, &gicv2m_domain_ops, v2m); -	if (!inner_domain) { -		pr_err("Failed to create GICv2m domain\n"); -		ret = -ENOMEM; -		goto err_free_bm; -	} - -	inner_domain->bus_token = DOMAIN_BUS_NEXUS; -	inner_domain->parent = parent; -	pci_domain = pci_msi_create_irq_domain(node, &gicv2m_msi_domain_info, -					       inner_domain); -	plat_domain = platform_msi_create_irq_domain(node, -						     &gicv2m_pmsi_domain_info, -						     inner_domain); -	if (!pci_domain || !plat_domain) { -		pr_err("Failed to create MSI domains\n"); -		ret = -ENOMEM; -		goto err_free_domains; -	} - -	spin_lock_init(&v2m->msi_cnt_lock); - +	list_add_tail(&v2m->entry, &v2m_nodes);  	pr_info("Node %s: range[%#lx:%#lx], SPI[%d:%d]\n", node->name,  		(unsigned long)v2m->res.start, (unsigned long)v2m->res.end,  		v2m->spi_start, (v2m->spi_start + v2m->nr_spis));  	return 0; -err_free_domains: -	if (plat_domain) -		irq_domain_remove(plat_domain); -	if (pci_domain) -		irq_domain_remove(pci_domain); -	if (inner_domain) -		irq_domain_remove(inner_domain); -err_free_bm: -	kfree(v2m->bm);  err_iounmap:  	iounmap(v2m->base);  err_free_v2m: @@ -339,5 +394,9 @@ int __init gicv2m_of_init(struct device_node *node, struct irq_domain *parent)  		}  	} +	if (!ret) +		ret = gicv2m_allocate_domains(parent); +	if (ret) +		gicv2m_teardown();  	return ret;  } |