| /* |
| * Copyright (C) 2016, Red Hat Inc, Andrew Jones <drjones@redhat.com> |
| * |
| * This work is licensed under the terms of the GNU LGPL, version 2. |
| */ |
| #include <acpi.h> |
| #include <devicetree.h> |
| #include <asm/gic.h> |
| #include <asm/io.h> |
| |
| struct gicv2_data gicv2_data; |
| struct gicv3_data gicv3_data; |
| struct its_data its_data; |
| |
| struct gic_common_ops { |
| void (*enable_defaults)(void); |
| u32 (*read_iar)(void); |
| u32 (*iar_irqnr)(u32 iar); |
| void (*write_eoir)(u32 irqstat); |
| void (*ipi_send_single)(int irq, int cpu); |
| void (*ipi_send_mask)(int irq, const cpumask_t *dest); |
| }; |
| |
| static const struct gic_common_ops *gic_common_ops; |
| |
| static const struct gic_common_ops gicv2_common_ops = { |
| .enable_defaults = gicv2_enable_defaults, |
| .read_iar = gicv2_read_iar, |
| .iar_irqnr = gicv2_iar_irqnr, |
| .write_eoir = gicv2_write_eoir, |
| .ipi_send_single = gicv2_ipi_send_single, |
| .ipi_send_mask = gicv2_ipi_send_mask, |
| }; |
| |
| static const struct gic_common_ops gicv3_common_ops = { |
| .enable_defaults = gicv3_enable_defaults, |
| .read_iar = gicv3_read_iar, |
| .iar_irqnr = gicv3_iar_irqnr, |
| .write_eoir = gicv3_write_eoir, |
| .ipi_send_single = gicv3_ipi_send_single, |
| .ipi_send_mask = gicv3_ipi_send_mask, |
| }; |
| |
| /* |
| * Documentation/devicetree/bindings/interrupt-controller/arm,gic.txt |
| * Documentation/devicetree/bindings/interrupt-controller/arm,gic-v3.txt |
| */ |
| static bool |
| gic_get_dt_bases(const char *compatible, void **base1, void **base2, void **base3) |
| { |
| struct dt_pbus_reg reg; |
| struct dt_device gic, its; |
| struct dt_bus bus; |
| int node, subnode, ret, i, len; |
| const void *fdt = dt_fdt(); |
| |
| dt_bus_init_defaults(&bus); |
| dt_device_init(&gic, &bus, NULL); |
| |
| node = dt_device_find_compatible(&gic, compatible); |
| assert(node >= 0 || node == -FDT_ERR_NOTFOUND); |
| |
| if (node == -FDT_ERR_NOTFOUND) |
| return false; |
| |
| dt_device_bind_node(&gic, node); |
| |
| ret = dt_pbus_translate(&gic, 0, ®); |
| assert(ret == 0); |
| *base1 = ioremap(reg.addr, reg.size); |
| |
| for (i = 0; i < GICV3_NR_REDISTS; ++i) { |
| ret = dt_pbus_translate(&gic, i + 1, ®); |
| if (ret == -FDT_ERR_NOTFOUND) |
| break; |
| assert(ret == 0); |
| base2[i] = ioremap(reg.addr, reg.size); |
| } |
| |
| if (!base3) { |
| assert(!strcmp(compatible, "arm,cortex-a15-gic")); |
| return true; |
| } |
| |
| assert(!strcmp(compatible, "arm,gic-v3")); |
| |
| dt_for_each_subnode(node, subnode) { |
| const struct fdt_property *prop; |
| |
| prop = fdt_get_property(fdt, subnode, "compatible", &len); |
| if (!strcmp((char *)prop->data, "arm,gic-v3-its")) { |
| dt_device_bind_node(&its, subnode); |
| ret = dt_pbus_translate(&its, 0, ®); |
| assert(ret == 0); |
| *base3 = ioremap(reg.addr, reg.size); |
| break; |
| } |
| } |
| |
| return true; |
| } |
| |
| int gicv2_init(void) |
| { |
| return gic_get_dt_bases("arm,cortex-a15-gic", |
| &gicv2_data.dist_base, &gicv2_data.cpu_base, NULL); |
| } |
| |
| int gicv3_init(void) |
| { |
| return gic_get_dt_bases("arm,gic-v3", &gicv3_data.dist_base, |
| &gicv3_data.redist_bases[0], &its_data.base); |
| } |
| |
| int gic_version(void) |
| { |
| if (gic_common_ops == &gicv2_common_ops) |
| return 2; |
| else if (gic_common_ops == &gicv3_common_ops) |
| return 3; |
| return 0; |
| } |
| |
| static int gic_init_fdt(void) |
| { |
| if (gicv2_init()) { |
| gic_common_ops = &gicv2_common_ops; |
| } else if (gicv3_init()) { |
| gic_common_ops = &gicv3_common_ops; |
| #ifdef __aarch64__ |
| its_init(); |
| #endif |
| } |
| return gic_version(); |
| } |
| |
| #ifdef CONFIG_EFI |
| |
| #define ACPI_GICV2_DIST_MEM_SIZE (SZ_4K) |
| #define ACPI_GIC_CPU_IF_MEM_SIZE (SZ_8K) |
| #define ACPI_GICV3_DIST_MEM_SIZE (SZ_64K) |
| #define ACPI_GICV3_ITS_MEM_SIZE (SZ_128K) |
| |
| static int gic_acpi_version(struct acpi_subtable_header *header) |
| { |
| struct acpi_madt_generic_distributor *dist = (void *)header; |
| int version = dist->version; |
| |
| if (version == 2) |
| gic_common_ops = &gicv2_common_ops; |
| else if (version == 3) |
| gic_common_ops = &gicv3_common_ops; |
| |
| return version; |
| } |
| |
| static int gicv2_acpi_parse_madt_cpu(struct acpi_subtable_header *header) |
| { |
| struct acpi_madt_generic_interrupt *gicc = (void *)header; |
| static phys_addr_t gicc_base_address; |
| |
| if (!(gicc->flags & ACPI_MADT_ENABLED)) |
| return 0; |
| |
| if (!gicc_base_address) { |
| gicc_base_address = gicc->base_address; |
| gicv2_data.cpu_base = ioremap(gicc_base_address, ACPI_GIC_CPU_IF_MEM_SIZE); |
| } |
| assert(gicc_base_address == gicc->base_address); |
| |
| return 0; |
| } |
| |
| static int gicv2_acpi_parse_madt_dist(struct acpi_subtable_header *header) |
| { |
| struct acpi_madt_generic_distributor *dist = (void *)header; |
| |
| gicv2_data.dist_base = ioremap(dist->base_address, ACPI_GICV2_DIST_MEM_SIZE); |
| |
| return 0; |
| } |
| |
| static int gicv3_acpi_parse_madt_gicc(struct acpi_subtable_header *header) |
| { |
| struct acpi_madt_generic_interrupt *gicc = (void *)header; |
| static phys_addr_t gicr_base_address; |
| |
| if (!(gicc->flags & ACPI_MADT_ENABLED)) |
| return 0; |
| |
| if (!gicr_base_address) { |
| gicr_base_address = gicc->gicr_base_address; |
| gicv3_data.redist_bases[0] = ioremap(gicr_base_address, SZ_64K * 2); |
| } |
| assert(gicr_base_address == gicc->gicr_base_address); |
| |
| return 0; |
| } |
| |
| static int gicv3_acpi_parse_madt_dist(struct acpi_subtable_header *header) |
| { |
| struct acpi_madt_generic_distributor *dist = (void *)header; |
| |
| gicv3_data.dist_base = ioremap(dist->base_address, ACPI_GICV3_DIST_MEM_SIZE); |
| |
| return 0; |
| } |
| |
| static int gicv3_acpi_parse_madt_redist(struct acpi_subtable_header *header) |
| { |
| static int i; |
| struct acpi_madt_generic_redistributor *redist = (void *)header; |
| |
| gicv3_data.redist_bases[i++] = ioremap(redist->base_address, redist->length); |
| |
| return 0; |
| } |
| |
| static int gicv3_acpi_parse_madt_its(struct acpi_subtable_header *header) |
| { |
| struct acpi_madt_generic_translator *its_entry = (void *)header; |
| |
| its_data.base = ioremap(its_entry->base_address, ACPI_GICV3_ITS_MEM_SIZE - 1); |
| |
| return 0; |
| } |
| |
| static int gic_init_acpi(void) |
| { |
| int count; |
| |
| acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_DISTRIBUTOR, gic_acpi_version); |
| if (gic_version() == 2) { |
| acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_INTERRUPT, |
| gicv2_acpi_parse_madt_cpu); |
| acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_DISTRIBUTOR, |
| gicv2_acpi_parse_madt_dist); |
| } else if (gic_version() == 3) { |
| acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_DISTRIBUTOR, |
| gicv3_acpi_parse_madt_dist); |
| count = acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_REDISTRIBUTOR, |
| gicv3_acpi_parse_madt_redist); |
| if (!count) |
| acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_INTERRUPT, |
| gicv3_acpi_parse_madt_gicc); |
| acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_TRANSLATOR, |
| gicv3_acpi_parse_madt_its); |
| #ifdef __aarch64__ |
| its_init(); |
| #endif |
| } |
| |
| return gic_version(); |
| } |
| |
| #else |
| |
| static int gic_init_acpi(void) |
| { |
| assert_msg(false, "ACPI not available"); |
| } |
| |
| #endif /* CONFIG_EFI */ |
| |
| int gic_init(void) |
| { |
| if (dt_available()) |
| return gic_init_fdt(); |
| else |
| return gic_init_acpi(); |
| } |
| |
| void gic_enable_defaults(void) |
| { |
| if (!gic_common_ops) { |
| int ret = gic_init(); |
| assert(ret != 0); |
| } else |
| assert(gic_common_ops->enable_defaults); |
| gic_common_ops->enable_defaults(); |
| } |
| |
| u32 gic_read_iar(void) |
| { |
| assert(gic_common_ops && gic_common_ops->read_iar); |
| return gic_common_ops->read_iar(); |
| } |
| |
| u32 gic_iar_irqnr(u32 iar) |
| { |
| assert(gic_common_ops && gic_common_ops->iar_irqnr); |
| return gic_common_ops->iar_irqnr(iar); |
| } |
| |
| void gic_write_eoir(u32 irqstat) |
| { |
| assert(gic_common_ops && gic_common_ops->write_eoir); |
| gic_common_ops->write_eoir(irqstat); |
| } |
| |
| void gic_ipi_send_single(int irq, int cpu) |
| { |
| assert(gic_common_ops && gic_common_ops->ipi_send_single); |
| gic_common_ops->ipi_send_single(irq, cpu); |
| } |
| |
| void gic_ipi_send_mask(int irq, const cpumask_t *dest) |
| { |
| assert(gic_common_ops && gic_common_ops->ipi_send_mask); |
| gic_common_ops->ipi_send_mask(irq, dest); |
| } |
| |
| void gic_irq_set_clr_enable(int irq, bool enable) |
| { |
| u32 offset, split = 32, shift = (irq % 32); |
| void *base; |
| |
| assert(irq < 1020); |
| |
| switch (gic_version()) { |
| case 2: |
| offset = enable ? GICD_ISENABLER : GICD_ICENABLER; |
| base = gicv2_dist_base(); |
| break; |
| case 3: |
| if (irq < 32) { |
| offset = enable ? GICR_ISENABLER0 : GICR_ICENABLER0; |
| base = gicv3_sgi_base(); |
| } else { |
| offset = enable ? GICD_ISENABLER : GICD_ICENABLER; |
| base = gicv3_dist_base(); |
| } |
| break; |
| default: |
| assert(0); |
| } |
| base += offset + (irq / split) * 4; |
| writel(BIT(shift), base); |
| } |
| |
| enum gic_irq_state gic_irq_state(int irq) |
| { |
| enum gic_irq_state state; |
| void *ispendr, *isactiver; |
| bool pending, active; |
| int offset, mask; |
| |
| assert(gic_common_ops); |
| assert(irq < 1020); |
| |
| switch (gic_version()) { |
| case 2: |
| ispendr = gicv2_dist_base() + GICD_ISPENDR; |
| isactiver = gicv2_dist_base() + GICD_ISACTIVER; |
| break; |
| case 3: |
| if (irq < GIC_NR_PRIVATE_IRQS) { |
| ispendr = gicv3_sgi_base() + GICR_ISPENDR0; |
| isactiver = gicv3_sgi_base() + GICR_ISACTIVER0; |
| } else { |
| ispendr = gicv3_dist_base() + GICD_ISPENDR; |
| isactiver = gicv3_dist_base() + GICD_ISACTIVER; |
| } |
| break; |
| default: |
| assert(0); |
| } |
| |
| offset = irq / 32 * 4; |
| mask = 1 << (irq % 32); |
| pending = readl(ispendr + offset) & mask; |
| active = readl(isactiver + offset) & mask; |
| |
| if (!active && !pending) |
| state = GIC_IRQ_STATE_INACTIVE; |
| if (pending) |
| state = GIC_IRQ_STATE_PENDING; |
| if (active) |
| state = GIC_IRQ_STATE_ACTIVE; |
| if (active && pending) |
| state = GIC_IRQ_STATE_ACTIVE_PENDING; |
| |
| return state; |
| } |
| |