]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - drivers/irqchip/irq-gic-v2m.c
Merge remote-tracking branch 'sound-current/for-linus'
[karo-tx-linux.git] / drivers / irqchip / irq-gic-v2m.c
index 12985daa66ab31c9c7981e65ea0926938e5662fa..87f8d104acab37970b834ded3b74a93ba8b692f6 100644 (file)
 #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;
 }