aboutsummaryrefslogtreecommitdiff
path: root/drivers/arm/gic/gic_v2.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/arm/gic/gic_v2.c')
-rw-r--r--drivers/arm/gic/gic_v2.c353
1 files changed, 353 insertions, 0 deletions
diff --git a/drivers/arm/gic/gic_v2.c b/drivers/arm/gic/gic_v2.c
new file mode 100644
index 000000000..48ee29e2a
--- /dev/null
+++ b/drivers/arm/gic/gic_v2.c
@@ -0,0 +1,353 @@
+/*
+ * Copyright (c) 2018, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <arch.h>
+#include <arch_helpers.h>
+#include <arm_gic.h>
+#include <assert.h>
+#include <gic_common.h>
+#include <gic_v2.h>
+#include <mmio.h>
+#include <platform.h>
+
+/*
+ * Data structure to store the GIC per CPU context before entering
+ * system suspend. Only the GIC context of first 32 interrupts (SGIs and PPIs)
+ * will be saved. The GIC SPI context needs to be restored by the respective
+ * drivers. The GICC_PMR is not saved here as it will be reinitialized during
+ * GIC restore.
+ */
+struct gicv2_pcpu_ctx {
+ unsigned int gicc_ctlr;
+ unsigned int gicd_isenabler0;
+ unsigned int gicd_ipriorityr[NUM_PCPU_INTR >> IPRIORITYR_SHIFT];
+ unsigned int gicd_icfgr;
+};
+
+static struct gicv2_pcpu_ctx pcpu_gic_ctx[PLATFORM_CORE_COUNT];
+
+static uintptr_t gicc_base_addr;
+static uintptr_t gicd_base_addr;
+
+static unsigned int gic_cpu_id[PLATFORM_CORE_COUNT] = {UINT32_MAX};
+
+/* Helper function to convert core pos to gic id */
+static unsigned int core_pos_to_gic_id(unsigned int core_pos)
+{
+ assert(gic_cpu_id[core_pos] != UINT32_MAX);
+ return gic_cpu_id[core_pos];
+}
+
+/*******************************************************************************
+ * GIC Distributor interface accessors for reading entire registers
+ ******************************************************************************/
+unsigned int gicd_read_itargetsr(unsigned int base, unsigned int interrupt_id)
+{
+ unsigned n = interrupt_id >> ITARGETSR_SHIFT;
+ return mmio_read_32(base + GICD_ITARGETSR + (n << 2));
+}
+
+unsigned int gicd_read_cpendsgir(unsigned int base, unsigned int interrupt_id)
+{
+ unsigned n = interrupt_id >> CPENDSGIR_SHIFT;
+ return mmio_read_32(base + GICD_CPENDSGIR + (n << 2));
+}
+
+unsigned int gicd_read_spendsgir(unsigned int base, unsigned int interrupt_id)
+{
+ unsigned n = interrupt_id >> SPENDSGIR_SHIFT;
+ return mmio_read_32(base + GICD_SPENDSGIR + (n << 2));
+}
+
+/*******************************************************************************
+ * GIC Distributor interface accessors for writing entire registers
+ ******************************************************************************/
+void gicd_write_itargetsr(unsigned int base,
+ unsigned int interrupt_id, unsigned int val)
+{
+ unsigned n = interrupt_id >> ITARGETSR_SHIFT;
+ mmio_write_32(base + GICD_ITARGETSR + (n << 2), val);
+}
+
+void gicd_write_itargetsr_byte(unsigned int base,
+ unsigned int interrupt_id, unsigned int val)
+{
+ mmio_write_8(base + GICD_ITARGETSR + interrupt_id, val);
+}
+
+void gicd_write_cpendsgir(unsigned int base,
+ unsigned int interrupt_id, unsigned int val)
+{
+ unsigned n = interrupt_id >> CPENDSGIR_SHIFT;
+ mmio_write_32(base + GICD_CPENDSGIR + (n << 2), val);
+}
+
+void gicd_write_spendsgir(unsigned int base,
+ unsigned int interrupt_id, unsigned int val)
+{
+ unsigned n = interrupt_id >> SPENDSGIR_SHIFT;
+ mmio_write_32(base + GICD_SPENDSGIR + (n << 2), val);
+}
+
+/*******************************************************************************
+ * GIC Distributor interface accessors for individual interrupt manipulation
+ ******************************************************************************/
+void gicd_set_itargetsr(unsigned int base,
+ unsigned int interrupt_id, unsigned int iface)
+{
+ mmio_write_8(base + GICD_ITARGETSR + interrupt_id, (1 << iface));
+}
+
+/******************************************************************************
+ * GICv2 public driver API
+ *****************************************************************************/
+
+void gicv2_enable_cpuif(void)
+{
+ unsigned int gicc_ctlr;
+
+ assert(gicc_base_addr);
+
+ /* Enable the GICC and disable bypass */
+ gicc_ctlr = GICC_CTLR_ENABLE | FIQ_BYP_DIS_GRP1
+ | IRQ_BYP_DIS_GRP1;
+ gicc_write_ctlr(gicc_base_addr, gicc_ctlr);
+}
+
+void gicv2_probe_gic_cpu_id(void)
+{
+ unsigned int gicd_itargets_val, core_pos;
+
+ assert(gicd_base_addr);
+ core_pos = platform_get_core_pos(read_mpidr_el1());
+ gicd_itargets_val = gicd_read_itargetsr(gicd_base_addr, 0);
+
+ assert(gicd_itargets_val);
+
+ /* Convert the bit pos returned by read of ITARGETSR0 to GIC CPU ID */
+ gic_cpu_id[core_pos] = __builtin_ctz(gicd_itargets_val);
+}
+
+void gicv2_setup_cpuif(void)
+{
+ assert(gicc_base_addr);
+
+ /* Set the priority mask register to allow all interrupts to trickle in */
+ gicc_write_pmr(gicc_base_addr, GIC_PRI_MASK);
+ gicv2_enable_cpuif();
+}
+
+void gicv2_disable_cpuif(void)
+{
+ unsigned int gicc_ctlr;
+
+ assert(gicc_base_addr);
+
+ /* Disable non-secure interrupts and disable their bypass */
+ gicc_ctlr = gicc_read_ctlr(gicc_base_addr);
+ gicc_ctlr &= ~GICC_CTLR_ENABLE;
+ gicc_ctlr |= FIQ_BYP_DIS_GRP1 | IRQ_BYP_DIS_GRP1;
+ gicc_write_ctlr(gicc_base_addr, gicc_ctlr);
+}
+
+void gicv2_save_cpuif_context(void)
+{
+ unsigned int core_pos = platform_get_core_pos(read_mpidr_el1());
+
+ assert(gicc_base_addr);
+ pcpu_gic_ctx[core_pos].gicc_ctlr =
+ gicc_read_ctlr(gicc_base_addr);
+}
+
+void gicv2_restore_cpuif_context(void)
+{
+ unsigned int core_pos = platform_get_core_pos(read_mpidr_el1());
+
+ assert(gicc_base_addr);
+
+ /* The GICC_PMR is never modified, hence we initialize this register */
+ gicc_write_pmr(gicc_base_addr, GIC_PRI_MASK);
+
+ gicc_write_ctlr(gicc_base_addr,
+ pcpu_gic_ctx[core_pos].gicc_ctlr);
+}
+
+void gicv2_setup_distif(void)
+{
+ unsigned int gicd_ctlr;
+
+ assert(gicd_base_addr);
+
+ /* Enable the forwarding of interrupts to CPU interface */
+ gicd_ctlr = gicd_read_ctlr(gicd_base_addr);
+ gicd_ctlr |= GICD_CTLR_ENABLE;
+ gicd_write_ctlr(gicd_base_addr, gicd_ctlr);
+}
+
+/* Save the per-cpu GICD ISENABLER, IPRIORITYR and ICFGR registers */
+void gicv2_save_sgi_ppi_context(void)
+{
+ unsigned int i;
+ unsigned int core_pos = platform_get_core_pos(read_mpidr_el1());
+
+ assert(gicd_base_addr);
+ pcpu_gic_ctx[core_pos].gicd_isenabler0 =
+ gicd_read_isenabler(gicd_base_addr, 0);
+
+ /* Read the ipriority registers, 4 at a time */
+ for (i = 0; i < (NUM_PCPU_INTR >> IPRIORITYR_SHIFT); i++)
+ pcpu_gic_ctx[core_pos].gicd_ipriorityr[i] =
+ gicd_read_ipriorityr(gicd_base_addr, i << IPRIORITYR_SHIFT);
+
+ pcpu_gic_ctx[core_pos].gicd_icfgr =
+ gicd_read_icfgr(gicd_base_addr, MIN_PPI_ID);
+}
+
+/* Restore the per-cpu GICD ISENABLER, IPRIORITYR and ICFGR registers */
+void gicv2_restore_sgi_ppi_context(void)
+{
+ unsigned int i;
+ unsigned int core_pos = platform_get_core_pos(read_mpidr_el1());
+
+ assert(gicd_base_addr);
+
+ /* Write the ipriority registers, 4 at a time */
+ for (i = 0; i < (NUM_PCPU_INTR >> IPRIORITYR_SHIFT); i++)
+ gicd_write_ipriorityr(gicd_base_addr, i << IPRIORITYR_SHIFT,
+ pcpu_gic_ctx[core_pos].gicd_ipriorityr[i]);
+
+ gicd_write_icfgr(gicd_base_addr, MIN_PPI_ID,
+ pcpu_gic_ctx[core_pos].gicd_icfgr);
+
+ gicd_write_isenabler(gicd_base_addr, 0,
+ pcpu_gic_ctx[core_pos].gicd_isenabler0);
+}
+
+unsigned int gicv2_gicd_get_ipriorityr(unsigned int interrupt_id)
+{
+ assert(gicd_base_addr);
+ assert(IS_VALID_INTR_ID(interrupt_id));
+
+ return gicd_get_ipriorityr(gicd_base_addr, interrupt_id);
+}
+
+void gicv2_gicd_set_ipriorityr(unsigned int interrupt_id,
+ unsigned int priority)
+{
+ assert(gicd_base_addr);
+ assert(IS_VALID_INTR_ID(interrupt_id));
+
+ gicd_set_ipriorityr(gicd_base_addr, interrupt_id, priority);
+}
+
+void gicv2_send_sgi(unsigned int sgi_id, unsigned int core_pos)
+{
+ unsigned int sgir_val;
+
+ assert(gicd_base_addr);
+ assert(IS_SGI(sgi_id));
+
+ sgir_val = sgi_id << GICD_SGIR_INTID_SHIFT;
+ sgir_val |= (1 << core_pos_to_gic_id(core_pos)) << GICD_SGIR_CPUTL_SHIFT;
+
+ gicd_write_sgir(gicd_base_addr, sgir_val);
+}
+
+void gicv2_set_itargetsr(unsigned int num, unsigned int core_pos)
+{
+ unsigned int gic_cpu_id;
+ assert(gicd_base_addr);
+ assert(IS_SPI(num));
+
+ gic_cpu_id = core_pos_to_gic_id(core_pos);
+ gicd_set_itargetsr(gicd_base_addr, num, gic_cpu_id);
+}
+
+void gicv2_set_itargetsr_value(unsigned int num, unsigned int val)
+{
+ assert(gicd_base_addr);
+ assert(IS_SPI(num));
+
+ gicd_write_itargetsr_byte(gicd_base_addr, num, val);
+}
+
+unsigned int gicv2_gicd_get_isenabler(unsigned int num)
+{
+ assert(gicd_base_addr);
+ assert(IS_VALID_INTR_ID(num));
+
+ return gicd_get_isenabler(gicd_base_addr, num);
+}
+
+void gicv2_gicd_set_isenabler(unsigned int num)
+{
+ assert(gicd_base_addr);
+ assert(IS_VALID_INTR_ID(num));
+
+ gicd_set_isenabler(gicd_base_addr, num);
+}
+
+void gicv2_gicd_set_icenabler(unsigned int num)
+{
+ assert(gicd_base_addr);
+ assert(IS_VALID_INTR_ID(num));
+
+ gicd_set_icenabler(gicd_base_addr, num);
+}
+
+unsigned int gicv2_gicc_read_iar(void)
+{
+ assert(gicc_base_addr);
+ return gicc_read_iar(gicc_base_addr);
+}
+
+unsigned int gicv2_gicd_get_ispendr(unsigned int interrupt_id)
+{
+ unsigned int ispendr;
+ unsigned int bit_pos;
+
+ assert(gicd_base_addr);
+ assert(IS_VALID_INTR_ID(interrupt_id));
+
+ ispendr = gicd_read_ispendr(gicd_base_addr, interrupt_id);
+ bit_pos = interrupt_id % (1 << ISPENDR_SHIFT);
+
+ return !!(ispendr & (1 << bit_pos));
+}
+
+void gicv2_gicd_set_ispendr(unsigned int interrupt_id)
+{
+ assert(gicd_base_addr);
+ assert(IS_PPI(interrupt_id) || IS_SPI(interrupt_id));
+ gicd_set_ispendr(gicd_base_addr, interrupt_id);
+}
+
+void gicv2_gicd_set_icpendr(unsigned int interrupt_id)
+{
+ assert(gicd_base_addr);
+ assert(IS_PPI(interrupt_id) || IS_SPI(interrupt_id));
+
+ gicd_set_icpendr(gicd_base_addr, interrupt_id);
+}
+
+void gicv2_gicc_write_eoir(unsigned int val)
+{
+ assert(gicc_base_addr);
+
+ gicc_write_eoir(gicc_base_addr, val);
+}
+
+void gicv2_init(uintptr_t gicc_base,
+ uintptr_t gicd_base)
+{
+ assert(gicc_base);
+ assert(gicd_base);
+
+ /* Assert that this is a GICv2 system */
+ assert(!is_gicv3_mode());
+ gicc_base_addr = gicc_base;
+ gicd_base_addr = gicd_base;
+}