diff options
Diffstat (limited to 'drivers/irqchip/irq-gic-v2m.c')
-rw-r--r-- | drivers/irqchip/irq-gic-v2m.c | 167 |
1 files changed, 113 insertions, 54 deletions
diff --git a/drivers/irqchip/irq-gic-v2m.c b/drivers/irqchip/irq-gic-v2m.c index db04fc1f56b2..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) @@ -95,9 +107,12 @@ static void gicv2m_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) struct v2m_data *v2m = irq_data_get_irq_chip_data(data); phys_addr_t addr = v2m->res.start + V2M_MSI_SETSPI_NS; - msg->address_hi = (u32) (addr >> 32); - msg->address_lo = (u32) (addr); + 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; } |