Merge branch irq/aic-v2 into irq/irqchip-next
authorMarc Zyngier <maz@kernel.org>
Fri, 11 Mar 2022 09:10:12 +0000 (09:10 +0000)
committerMarc Zyngier <maz@kernel.org>
Fri, 11 Mar 2022 09:10:12 +0000 (09:10 +0000)
* irq/aic-v2:
  : .
  : Add support for the interrupt controller found is the latest
  : incarnation of Apple M1 systems, courtesy of Hector Martin.
  : .
  irqchip/apple-aic: Add support for AICv2
  irqchip/apple-aic: Support multiple dies
  irqchip/apple-aic: Dynamically compute register offsets
  irqchip/apple-aic: Switch to irq_domain_create_tree and sparse hwirqs
  irqchip/apple-aic: Add Fast IPI support
  dt-bindings: interrupt-controller: apple,aic2: New binding for AICv2
  PCI: apple: Change MSI handling to handle 4-cell AIC fwspec form

Signed-off-by: Marc Zyngier <maz@kernel.org>
1  2 
drivers/irqchip/irq-apple-aic.c

@@@ -55,7 -56,7 +56,8 @@@
  #include <linux/limits.h>
  #include <linux/of_address.h>
  #include <linux/slab.h>
 +#include <asm/apple_m1_pmu.h>
+ #include <asm/cputype.h>
  #include <asm/exception.h>
  #include <asm/sysreg.h>
  #include <asm/virt.h>
  
  /*
   * IMP-DEF sysregs that control FIQ sources
-  * Note: sysreg-based IPIs are not supported yet.
   */
  
 -/* Core PMC control register */
 -#define SYS_IMP_APL_PMCR0_EL1         sys_reg(3, 1, 15, 0, 0)
 -#define PMCR0_IMODE                   GENMASK(10, 8)
 -#define PMCR0_IMODE_OFF                       0
 -#define PMCR0_IMODE_PMI                       1
 -#define PMCR0_IMODE_AIC                       2
 -#define PMCR0_IMODE_HALT              3
 -#define PMCR0_IMODE_FIQ                       4
 -#define PMCR0_IACT                    BIT(11)
 -
  /* IPI request registers */
  #define SYS_IMP_APL_IPI_RR_LOCAL_EL1  sys_reg(3, 5, 15, 0, 0)
  #define SYS_IMP_APL_IPI_RR_GLOBAL_EL1 sys_reg(3, 5, 15, 0, 1)
  #define SYS_IMP_APL_UPMSR_EL1         sys_reg(3, 7, 15, 6, 4)
  #define UPMSR_IACT                    BIT(0)
  
 -#define AIC_NR_FIQ            4
+ /* MPIDR fields */
+ #define MPIDR_CPU(x)                  MPIDR_AFFINITY_LEVEL(x, 0)
+ #define MPIDR_CLUSTER(x)              MPIDR_AFFINITY_LEVEL(x, 1)
+ #define AIC_IRQ_HWIRQ(die, irq)       (FIELD_PREP(AIC_EVENT_DIE, die) | \
+                                FIELD_PREP(AIC_EVENT_TYPE, AIC_EVENT_TYPE_IRQ) | \
+                                FIELD_PREP(AIC_EVENT_NUM, irq))
+ #define AIC_FIQ_HWIRQ(x)      (FIELD_PREP(AIC_EVENT_TYPE, AIC_EVENT_TYPE_FIQ) | \
+                                FIELD_PREP(AIC_EVENT_NUM, x))
+ #define AIC_HWIRQ_IRQ(x)      FIELD_GET(AIC_EVENT_NUM, x)
+ #define AIC_HWIRQ_DIE(x)      FIELD_GET(AIC_EVENT_DIE, x)
 +#define AIC_NR_FIQ            6
  #define AIC_NR_SWIPI          32
  
  /*
  #define AIC_TMR_EL02_PHYS     AIC_TMR_GUEST_PHYS
  #define AIC_TMR_EL02_VIRT     AIC_TMR_GUEST_VIRT
  
+ DEFINE_STATIC_KEY_TRUE(use_fast_ipi);
+ struct aic_info {
+       int version;
+       /* Register offsets */
+       u32 event;
+       u32 target_cpu;
+       u32 irq_cfg;
+       u32 sw_set;
+       u32 sw_clr;
+       u32 mask_set;
+       u32 mask_clr;
+       u32 die_stride;
+       /* Features */
+       bool fast_ipi;
+ };
+ static const struct aic_info aic1_info = {
+       .version        = 1,
+       .event          = AIC_EVENT,
+       .target_cpu     = AIC_TARGET_CPU,
+ };
+ static const struct aic_info aic1_fipi_info = {
+       .version        = 1,
+       .event          = AIC_EVENT,
+       .target_cpu     = AIC_TARGET_CPU,
+       .fast_ipi       = true,
+ };
+ static const struct aic_info aic2_info = {
+       .version        = 2,
+       .irq_cfg        = AIC2_IRQ_CFG,
+       .fast_ipi       = true,
+ };
+ static const struct of_device_id aic_info_match[] = {
+       {
+               .compatible = "apple,t8103-aic",
+               .data = &aic1_fipi_info,
+       },
+       {
+               .compatible = "apple,aic",
+               .data = &aic1_info,
+       },
+       {
+               .compatible = "apple,aic2",
+               .data = &aic2_info,
+       },
+       {}
+ };
  struct aic_irq_chip {
        void __iomem *base;
+       void __iomem *event;
        struct irq_domain *hw_domain;
        struct irq_domain *ipi_domain;
-       int nr_hw;
 +      struct {
 +              cpumask_t aff;
 +      } *fiq_aff[AIC_NR_FIQ];
+       int nr_irq;
+       int max_irq;
+       int nr_die;
+       int max_die;
+       struct aic_info info;
  };
  
  static DEFINE_PER_CPU(uint32_t, aic_fiq_unmasked);
@@@ -403,18 -558,19 +552,18 @@@ static void __exception_irq_entry aic_h
                if ((enabled & VM_TMR_FIQ_ENABLE_V) &&
                    TIMER_FIRING(read_sysreg_s(SYS_CNTV_CTL_EL02)))
                        generic_handle_domain_irq(aic_irqc->hw_domain,
-                                                 aic_irqc->nr_hw + AIC_TMR_EL02_VIRT);
+                                                 AIC_FIQ_HWIRQ(AIC_TMR_EL02_VIRT));
        }
  
 -      if ((read_sysreg_s(SYS_IMP_APL_PMCR0_EL1) & (PMCR0_IMODE | PMCR0_IACT)) ==
 -                      (FIELD_PREP(PMCR0_IMODE, PMCR0_IMODE_FIQ) | PMCR0_IACT)) {
 -              /*
 -               * Not supported yet, let's figure out how to handle this when
 -               * we implement these proprietary performance counters. For now,
 -               * just mask it and move on.
 -               */
 -              pr_err_ratelimited("PMC FIQ fired. Masking.\n");
 -              sysreg_clear_set_s(SYS_IMP_APL_PMCR0_EL1, PMCR0_IMODE | PMCR0_IACT,
 -                                 FIELD_PREP(PMCR0_IMODE, PMCR0_IMODE_OFF));
 +      if (read_sysreg_s(SYS_IMP_APL_PMCR0_EL1) & PMCR0_IACT) {
 +              int irq;
 +              if (cpumask_test_cpu(smp_processor_id(),
 +                                   &aic_irqc->fiq_aff[AIC_CPU_PMU_P]->aff))
 +                      irq = AIC_CPU_PMU_P;
 +              else
 +                      irq = AIC_CPU_PMU_E;
 +              generic_handle_domain_irq(aic_irqc->hw_domain,
-                                         aic_irqc->nr_hw + irq);
++                                        AIC_FIQ_HWIRQ(irq));
        }
  
        if (FIELD_GET(UPMCR0_IMODE, read_sysreg_s(SYS_IMP_APL_UPMCR0_EL1)) == UPMCR0_IMODE_FIQ &&
@@@ -454,18 -615,7 +608,18 @@@ static int aic_irq_domain_map(struct ir
                                    handle_fasteoi_irq, NULL, NULL);
                irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(irq)));
        } else {
-               int fiq = hw - ic->nr_hw;
 -              irq_set_percpu_devid(irq);
++              int fiq = FIELD_GET(AIC_EVENT_NUM, hw);
 +
 +              switch (fiq) {
 +              case AIC_CPU_PMU_P:
 +              case AIC_CPU_PMU_E:
 +                      irq_set_percpu_devid_partition(irq, &ic->fiq_aff[fiq]->aff);
 +                      break;
 +              default:
 +                      irq_set_percpu_devid(irq);
 +                      break;
 +              }
 +
                irq_domain_set_info(id, irq, hw, &fiq_chip, id->host_data,
                                    handle_percpu_devid_irq, NULL, NULL);
        }
@@@ -797,91 -1001,99 +1005,145 @@@ static struct gic_kvm_info vgic_info __
        .no_hw_deactivation     = true,
  };
  
 +static void build_fiq_affinity(struct aic_irq_chip *ic, struct device_node *aff)
 +{
 +      int i, n;
 +      u32 fiq;
 +
 +      if (of_property_read_u32(aff, "apple,fiq-index", &fiq) ||
 +          WARN_ON(fiq >= AIC_NR_FIQ) || ic->fiq_aff[fiq])
 +              return;
 +
 +      n = of_property_count_elems_of_size(aff, "cpus", sizeof(u32));
 +      if (WARN_ON(n < 0))
 +              return;
 +
 +      ic->fiq_aff[fiq] = kzalloc(sizeof(*ic->fiq_aff[fiq]), GFP_KERNEL);
 +      if (!ic->fiq_aff[fiq])
 +              return;
 +
 +      for (i = 0; i < n; i++) {
 +              struct device_node *cpu_node;
 +              u32 cpu_phandle;
 +              int cpu;
 +
 +              if (of_property_read_u32_index(aff, "cpus", i, &cpu_phandle))
 +                      continue;
 +
 +              cpu_node = of_find_node_by_phandle(cpu_phandle);
 +              if (WARN_ON(!cpu_node))
 +                      continue;
 +
 +              cpu = of_cpu_node_to_id(cpu_node);
 +              if (WARN_ON(cpu < 0))
 +                      continue;
 +
 +              cpumask_set_cpu(cpu, &ic->fiq_aff[fiq]->aff);
 +      }
 +}
 +
  static int __init aic_of_ic_init(struct device_node *node, struct device_node *parent)
  {
-       int i;
+       int i, die;
+       u32 off, start_off;
        void __iomem *regs;
-       u32 info;
        struct aic_irq_chip *irqc;
 +      struct device_node *affs;
+       const struct of_device_id *match;
  
        regs = of_iomap(node, 0);
        if (WARN_ON(!regs))
                return -EIO;
  
        irqc = kzalloc(sizeof(*irqc), GFP_KERNEL);
-       if (!irqc)
+       if (!irqc) {
+               iounmap(regs);
                return -ENOMEM;
+       }
  
-       aic_irqc = irqc;
        irqc->base = regs;
  
-       info = aic_ic_read(irqc, AIC_INFO);
-       irqc->nr_hw = FIELD_GET(AIC_INFO_NR_HW, info);
+       match = of_match_node(aic_info_match, node);
+       if (!match)
+               goto err_unmap;
  
-       irqc->hw_domain = irq_domain_create_linear(of_node_to_fwnode(node),
-                                                  irqc->nr_hw + AIC_NR_FIQ,
-                                                  &aic_irq_domain_ops, irqc);
-       if (WARN_ON(!irqc->hw_domain)) {
-               iounmap(irqc->base);
-               kfree(irqc);
-               return -ENODEV;
+       irqc->info = *(struct aic_info *)match->data;
+       aic_irqc = irqc;
+       switch (irqc->info.version) {
+       case 1: {
+               u32 info;
+               info = aic_ic_read(irqc, AIC_INFO);
+               irqc->nr_irq = FIELD_GET(AIC_INFO_NR_IRQ, info);
+               irqc->max_irq = AIC_MAX_IRQ;
+               irqc->nr_die = irqc->max_die = 1;
+               off = start_off = irqc->info.target_cpu;
+               off += sizeof(u32) * irqc->max_irq; /* TARGET_CPU */
+               irqc->event = irqc->base;
+               break;
        }
+       case 2: {
+               u32 info1, info3;
  
-       irq_domain_update_bus_token(irqc->hw_domain, DOMAIN_BUS_WIRED);
+               info1 = aic_ic_read(irqc, AIC2_INFO1);
+               info3 = aic_ic_read(irqc, AIC2_INFO3);
  
-       if (aic_init_smp(irqc, node)) {
-               irq_domain_remove(irqc->hw_domain);
-               iounmap(irqc->base);
-               kfree(irqc);
-               return -ENODEV;
+               irqc->nr_irq = FIELD_GET(AIC2_INFO1_NR_IRQ, info1);
+               irqc->max_irq = FIELD_GET(AIC2_INFO3_MAX_IRQ, info3);
+               irqc->nr_die = FIELD_GET(AIC2_INFO1_LAST_DIE, info1) + 1;
+               irqc->max_die = FIELD_GET(AIC2_INFO3_MAX_DIE, info3);
+               off = start_off = irqc->info.irq_cfg;
+               off += sizeof(u32) * irqc->max_irq; /* IRQ_CFG */
+               irqc->event = of_iomap(node, 1);
+               if (WARN_ON(!irqc->event))
+                       goto err_unmap;
+               break;
        }
+       }
+       irqc->info.sw_set = off;
+       off += sizeof(u32) * (irqc->max_irq >> 5); /* SW_SET */
+       irqc->info.sw_clr = off;
+       off += sizeof(u32) * (irqc->max_irq >> 5); /* SW_CLR */
+       irqc->info.mask_set = off;
+       off += sizeof(u32) * (irqc->max_irq >> 5); /* MASK_SET */
+       irqc->info.mask_clr = off;
+       off += sizeof(u32) * (irqc->max_irq >> 5); /* MASK_CLR */
+       off += sizeof(u32) * (irqc->max_irq >> 5); /* HW_STATE */
+       if (irqc->info.fast_ipi)
+               static_branch_enable(&use_fast_ipi);
+       else
+               static_branch_disable(&use_fast_ipi);
+       irqc->info.die_stride = off - start_off;
+       irqc->hw_domain = irq_domain_create_tree(of_node_to_fwnode(node),
+                                                &aic_irq_domain_ops, irqc);
+       if (WARN_ON(!irqc->hw_domain))
+               goto err_unmap;
+       irq_domain_update_bus_token(irqc->hw_domain, DOMAIN_BUS_WIRED);
+       if (aic_init_smp(irqc, node))
+               goto err_remove_domain;
  
 +      affs = of_get_child_by_name(node, "affinities");
 +      if (affs) {
 +              struct device_node *chld;
 +
 +              for_each_child_of_node(affs, chld)
 +                      build_fiq_affinity(irqc, chld);
 +      }
 +
        set_handle_irq(aic_handle_irq);
        set_handle_fiq(aic_handle_fiq);