blob: af43a96d880a5a2b99b6fda393d45d089c3b8cf2 [file] [log] [blame]
/*
* 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, &reg);
assert(ret == 0);
*base1 = ioremap(reg.addr, reg.size);
for (i = 0; i < GICV3_NR_REDISTS; ++i) {
ret = dt_pbus_translate(&gic, i + 1, &reg);
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, &reg);
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;
}