diff options
Diffstat (limited to 'drivers/irqchip/irq-mips-gic.c')
-rw-r--r-- | drivers/irqchip/irq-mips-gic.c | 855 |
1 files changed, 855 insertions, 0 deletions
diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c new file mode 100644 index 000000000..3a33aefec --- /dev/null +++ b/drivers/irqchip/irq-mips-gic.c @@ -0,0 +1,855 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2008 Ralf Baechle (ralf@linux-mips.org) + * Copyright (C) 2012 MIPS Technologies, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) "irq-mips-gic: " fmt + +#include <linux/bitfield.h> +#include <linux/bitmap.h> +#include <linux/clocksource.h> +#include <linux/cpuhotplug.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqchip.h> +#include <linux/irqdomain.h> +#include <linux/of_address.h> +#include <linux/percpu.h> +#include <linux/sched.h> +#include <linux/smp.h> + +#include <asm/mips-cps.h> +#include <asm/setup.h> +#include <asm/traps.h> + +#include <dt-bindings/interrupt-controller/mips-gic.h> + +#define GIC_MAX_INTRS 256 +#define GIC_MAX_LONGS BITS_TO_LONGS(GIC_MAX_INTRS) + +/* Add 2 to convert GIC CPU pin to core interrupt */ +#define GIC_CPU_PIN_OFFSET 2 + +/* Mapped interrupt to pin X, then GIC will generate the vector (X+1). */ +#define GIC_PIN_TO_VEC_OFFSET 1 + +/* Convert between local/shared IRQ number and GIC HW IRQ number. */ +#define GIC_LOCAL_HWIRQ_BASE 0 +#define GIC_LOCAL_TO_HWIRQ(x) (GIC_LOCAL_HWIRQ_BASE + (x)) +#define GIC_HWIRQ_TO_LOCAL(x) ((x) - GIC_LOCAL_HWIRQ_BASE) +#define GIC_SHARED_HWIRQ_BASE GIC_NUM_LOCAL_INTRS +#define GIC_SHARED_TO_HWIRQ(x) (GIC_SHARED_HWIRQ_BASE + (x)) +#define GIC_HWIRQ_TO_SHARED(x) ((x) - GIC_SHARED_HWIRQ_BASE) + +void __iomem *mips_gic_base; + +static DEFINE_PER_CPU_READ_MOSTLY(unsigned long[GIC_MAX_LONGS], pcpu_masks); + +static DEFINE_RAW_SPINLOCK(gic_lock); +static struct irq_domain *gic_irq_domain; +static int gic_shared_intrs; +static unsigned int gic_cpu_pin; +static unsigned int timer_cpu_pin; +static struct irq_chip gic_level_irq_controller, gic_edge_irq_controller; + +#ifdef CONFIG_GENERIC_IRQ_IPI +static DECLARE_BITMAP(ipi_resrv, GIC_MAX_INTRS); +static DECLARE_BITMAP(ipi_available, GIC_MAX_INTRS); +#endif /* CONFIG_GENERIC_IRQ_IPI */ + +static struct gic_all_vpes_chip_data { + u32 map; + bool mask; +} gic_all_vpes_chip_data[GIC_NUM_LOCAL_INTRS]; + +static void gic_clear_pcpu_masks(unsigned int intr) +{ + unsigned int i; + + /* Clear the interrupt's bit in all pcpu_masks */ + for_each_possible_cpu(i) + clear_bit(intr, per_cpu_ptr(pcpu_masks, i)); +} + +static bool gic_local_irq_is_routable(int intr) +{ + u32 vpe_ctl; + + /* All local interrupts are routable in EIC mode. */ + if (cpu_has_veic) + return true; + + vpe_ctl = read_gic_vl_ctl(); + switch (intr) { + case GIC_LOCAL_INT_TIMER: + return vpe_ctl & GIC_VX_CTL_TIMER_ROUTABLE; + case GIC_LOCAL_INT_PERFCTR: + return vpe_ctl & GIC_VX_CTL_PERFCNT_ROUTABLE; + case GIC_LOCAL_INT_FDC: + return vpe_ctl & GIC_VX_CTL_FDC_ROUTABLE; + case GIC_LOCAL_INT_SWINT0: + case GIC_LOCAL_INT_SWINT1: + return vpe_ctl & GIC_VX_CTL_SWINT_ROUTABLE; + default: + return true; + } +} + +static void gic_bind_eic_interrupt(int irq, int set) +{ + /* Convert irq vector # to hw int # */ + irq -= GIC_PIN_TO_VEC_OFFSET; + + /* Set irq to use shadow set */ + write_gic_vl_eic_shadow_set(irq, set); +} + +static void gic_send_ipi(struct irq_data *d, unsigned int cpu) +{ + irq_hw_number_t hwirq = GIC_HWIRQ_TO_SHARED(irqd_to_hwirq(d)); + + write_gic_wedge(GIC_WEDGE_RW | hwirq); +} + +int gic_get_c0_compare_int(void) +{ + if (!gic_local_irq_is_routable(GIC_LOCAL_INT_TIMER)) + return MIPS_CPU_IRQ_BASE + cp0_compare_irq; + return irq_create_mapping(gic_irq_domain, + GIC_LOCAL_TO_HWIRQ(GIC_LOCAL_INT_TIMER)); +} + +int gic_get_c0_perfcount_int(void) +{ + if (!gic_local_irq_is_routable(GIC_LOCAL_INT_PERFCTR)) { + /* Is the performance counter shared with the timer? */ + if (cp0_perfcount_irq < 0) + return -1; + return MIPS_CPU_IRQ_BASE + cp0_perfcount_irq; + } + return irq_create_mapping(gic_irq_domain, + GIC_LOCAL_TO_HWIRQ(GIC_LOCAL_INT_PERFCTR)); +} + +int gic_get_c0_fdc_int(void) +{ + if (!gic_local_irq_is_routable(GIC_LOCAL_INT_FDC)) { + /* Is the FDC IRQ even present? */ + if (cp0_fdc_irq < 0) + return -1; + return MIPS_CPU_IRQ_BASE + cp0_fdc_irq; + } + + return irq_create_mapping(gic_irq_domain, + GIC_LOCAL_TO_HWIRQ(GIC_LOCAL_INT_FDC)); +} + +static void gic_handle_shared_int(bool chained) +{ + unsigned int intr; + unsigned long *pcpu_mask; + DECLARE_BITMAP(pending, GIC_MAX_INTRS); + + /* Get per-cpu bitmaps */ + pcpu_mask = this_cpu_ptr(pcpu_masks); + + if (mips_cm_is64) + __ioread64_copy(pending, addr_gic_pend(), + DIV_ROUND_UP(gic_shared_intrs, 64)); + else + __ioread32_copy(pending, addr_gic_pend(), + DIV_ROUND_UP(gic_shared_intrs, 32)); + + bitmap_and(pending, pending, pcpu_mask, gic_shared_intrs); + + for_each_set_bit(intr, pending, gic_shared_intrs) { + if (chained) + generic_handle_domain_irq(gic_irq_domain, + GIC_SHARED_TO_HWIRQ(intr)); + else + do_domain_IRQ(gic_irq_domain, + GIC_SHARED_TO_HWIRQ(intr)); + } +} + +static void gic_mask_irq(struct irq_data *d) +{ + unsigned int intr = GIC_HWIRQ_TO_SHARED(d->hwirq); + + write_gic_rmask(intr); + gic_clear_pcpu_masks(intr); +} + +static void gic_unmask_irq(struct irq_data *d) +{ + unsigned int intr = GIC_HWIRQ_TO_SHARED(d->hwirq); + unsigned int cpu; + + write_gic_smask(intr); + + gic_clear_pcpu_masks(intr); + cpu = cpumask_first(irq_data_get_effective_affinity_mask(d)); + set_bit(intr, per_cpu_ptr(pcpu_masks, cpu)); +} + +static void gic_ack_irq(struct irq_data *d) +{ + unsigned int irq = GIC_HWIRQ_TO_SHARED(d->hwirq); + + write_gic_wedge(irq); +} + +static int gic_set_type(struct irq_data *d, unsigned int type) +{ + unsigned int irq, pol, trig, dual; + unsigned long flags; + + irq = GIC_HWIRQ_TO_SHARED(d->hwirq); + + raw_spin_lock_irqsave(&gic_lock, flags); + switch (type & IRQ_TYPE_SENSE_MASK) { + case IRQ_TYPE_EDGE_FALLING: + pol = GIC_POL_FALLING_EDGE; + trig = GIC_TRIG_EDGE; + dual = GIC_DUAL_SINGLE; + break; + case IRQ_TYPE_EDGE_RISING: + pol = GIC_POL_RISING_EDGE; + trig = GIC_TRIG_EDGE; + dual = GIC_DUAL_SINGLE; + break; + case IRQ_TYPE_EDGE_BOTH: + pol = 0; /* Doesn't matter */ + trig = GIC_TRIG_EDGE; + dual = GIC_DUAL_DUAL; + break; + case IRQ_TYPE_LEVEL_LOW: + pol = GIC_POL_ACTIVE_LOW; + trig = GIC_TRIG_LEVEL; + dual = GIC_DUAL_SINGLE; + break; + case IRQ_TYPE_LEVEL_HIGH: + default: + pol = GIC_POL_ACTIVE_HIGH; + trig = GIC_TRIG_LEVEL; + dual = GIC_DUAL_SINGLE; + break; + } + + change_gic_pol(irq, pol); + change_gic_trig(irq, trig); + change_gic_dual(irq, dual); + + if (trig == GIC_TRIG_EDGE) + irq_set_chip_handler_name_locked(d, &gic_edge_irq_controller, + handle_edge_irq, NULL); + else + irq_set_chip_handler_name_locked(d, &gic_level_irq_controller, + handle_level_irq, NULL); + raw_spin_unlock_irqrestore(&gic_lock, flags); + + return 0; +} + +#ifdef CONFIG_SMP +static int gic_set_affinity(struct irq_data *d, const struct cpumask *cpumask, + bool force) +{ + unsigned int irq = GIC_HWIRQ_TO_SHARED(d->hwirq); + unsigned long flags; + unsigned int cpu; + + cpu = cpumask_first_and(cpumask, cpu_online_mask); + if (cpu >= NR_CPUS) + return -EINVAL; + + /* Assumption : cpumask refers to a single CPU */ + raw_spin_lock_irqsave(&gic_lock, flags); + + /* Re-route this IRQ */ + write_gic_map_vp(irq, BIT(mips_cm_vp_id(cpu))); + + /* Update the pcpu_masks */ + gic_clear_pcpu_masks(irq); + if (read_gic_mask(irq)) + set_bit(irq, per_cpu_ptr(pcpu_masks, cpu)); + + irq_data_update_effective_affinity(d, cpumask_of(cpu)); + raw_spin_unlock_irqrestore(&gic_lock, flags); + + return IRQ_SET_MASK_OK; +} +#endif + +static struct irq_chip gic_level_irq_controller = { + .name = "MIPS GIC", + .irq_mask = gic_mask_irq, + .irq_unmask = gic_unmask_irq, + .irq_set_type = gic_set_type, +#ifdef CONFIG_SMP + .irq_set_affinity = gic_set_affinity, +#endif +}; + +static struct irq_chip gic_edge_irq_controller = { + .name = "MIPS GIC", + .irq_ack = gic_ack_irq, + .irq_mask = gic_mask_irq, + .irq_unmask = gic_unmask_irq, + .irq_set_type = gic_set_type, +#ifdef CONFIG_SMP + .irq_set_affinity = gic_set_affinity, +#endif + .ipi_send_single = gic_send_ipi, +}; + +static void gic_handle_local_int(bool chained) +{ + unsigned long pending, masked; + unsigned int intr; + + pending = read_gic_vl_pend(); + masked = read_gic_vl_mask(); + + bitmap_and(&pending, &pending, &masked, GIC_NUM_LOCAL_INTRS); + + for_each_set_bit(intr, &pending, GIC_NUM_LOCAL_INTRS) { + if (chained) + generic_handle_domain_irq(gic_irq_domain, + GIC_LOCAL_TO_HWIRQ(intr)); + else + do_domain_IRQ(gic_irq_domain, + GIC_LOCAL_TO_HWIRQ(intr)); + } +} + +static void gic_mask_local_irq(struct irq_data *d) +{ + int intr = GIC_HWIRQ_TO_LOCAL(d->hwirq); + + write_gic_vl_rmask(BIT(intr)); +} + +static void gic_unmask_local_irq(struct irq_data *d) +{ + int intr = GIC_HWIRQ_TO_LOCAL(d->hwirq); + + write_gic_vl_smask(BIT(intr)); +} + +static struct irq_chip gic_local_irq_controller = { + .name = "MIPS GIC Local", + .irq_mask = gic_mask_local_irq, + .irq_unmask = gic_unmask_local_irq, +}; + +static void gic_mask_local_irq_all_vpes(struct irq_data *d) +{ + struct gic_all_vpes_chip_data *cd; + unsigned long flags; + int intr, cpu; + + intr = GIC_HWIRQ_TO_LOCAL(d->hwirq); + cd = irq_data_get_irq_chip_data(d); + cd->mask = false; + + raw_spin_lock_irqsave(&gic_lock, flags); + for_each_online_cpu(cpu) { + write_gic_vl_other(mips_cm_vp_id(cpu)); + write_gic_vo_rmask(BIT(intr)); + } + raw_spin_unlock_irqrestore(&gic_lock, flags); +} + +static void gic_unmask_local_irq_all_vpes(struct irq_data *d) +{ + struct gic_all_vpes_chip_data *cd; + unsigned long flags; + int intr, cpu; + + intr = GIC_HWIRQ_TO_LOCAL(d->hwirq); + cd = irq_data_get_irq_chip_data(d); + cd->mask = true; + + raw_spin_lock_irqsave(&gic_lock, flags); + for_each_online_cpu(cpu) { + write_gic_vl_other(mips_cm_vp_id(cpu)); + write_gic_vo_smask(BIT(intr)); + } + raw_spin_unlock_irqrestore(&gic_lock, flags); +} + +static void gic_all_vpes_irq_cpu_online(void) +{ + static const unsigned int local_intrs[] = { + GIC_LOCAL_INT_TIMER, + GIC_LOCAL_INT_PERFCTR, + GIC_LOCAL_INT_FDC, + }; + unsigned long flags; + int i; + + raw_spin_lock_irqsave(&gic_lock, flags); + + for (i = 0; i < ARRAY_SIZE(local_intrs); i++) { + unsigned int intr = local_intrs[i]; + struct gic_all_vpes_chip_data *cd; + + if (!gic_local_irq_is_routable(intr)) + continue; + cd = &gic_all_vpes_chip_data[intr]; + write_gic_vl_map(mips_gic_vx_map_reg(intr), cd->map); + if (cd->mask) + write_gic_vl_smask(BIT(intr)); + } + + raw_spin_unlock_irqrestore(&gic_lock, flags); +} + +static struct irq_chip gic_all_vpes_local_irq_controller = { + .name = "MIPS GIC Local", + .irq_mask = gic_mask_local_irq_all_vpes, + .irq_unmask = gic_unmask_local_irq_all_vpes, +}; + +static void __gic_irq_dispatch(void) +{ + gic_handle_local_int(false); + gic_handle_shared_int(false); +} + +static void gic_irq_dispatch(struct irq_desc *desc) +{ + gic_handle_local_int(true); + gic_handle_shared_int(true); +} + +static int gic_shared_irq_domain_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hw, unsigned int cpu) +{ + int intr = GIC_HWIRQ_TO_SHARED(hw); + struct irq_data *data; + unsigned long flags; + + data = irq_get_irq_data(virq); + + raw_spin_lock_irqsave(&gic_lock, flags); + write_gic_map_pin(intr, GIC_MAP_PIN_MAP_TO_PIN | gic_cpu_pin); + write_gic_map_vp(intr, BIT(mips_cm_vp_id(cpu))); + irq_data_update_effective_affinity(data, cpumask_of(cpu)); + raw_spin_unlock_irqrestore(&gic_lock, flags); + + return 0; +} + +static int gic_irq_domain_xlate(struct irq_domain *d, struct device_node *ctrlr, + const u32 *intspec, unsigned int intsize, + irq_hw_number_t *out_hwirq, + unsigned int *out_type) +{ + if (intsize != 3) + return -EINVAL; + + if (intspec[0] == GIC_SHARED) + *out_hwirq = GIC_SHARED_TO_HWIRQ(intspec[1]); + else if (intspec[0] == GIC_LOCAL) + *out_hwirq = GIC_LOCAL_TO_HWIRQ(intspec[1]); + else + return -EINVAL; + *out_type = intspec[2] & IRQ_TYPE_SENSE_MASK; + + return 0; +} + +static int gic_irq_domain_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hwirq) +{ + struct gic_all_vpes_chip_data *cd; + unsigned long flags; + unsigned int intr; + int err, cpu; + u32 map; + + if (hwirq >= GIC_SHARED_HWIRQ_BASE) { +#ifdef CONFIG_GENERIC_IRQ_IPI + /* verify that shared irqs don't conflict with an IPI irq */ + if (test_bit(GIC_HWIRQ_TO_SHARED(hwirq), ipi_resrv)) + return -EBUSY; +#endif /* CONFIG_GENERIC_IRQ_IPI */ + + err = irq_domain_set_hwirq_and_chip(d, virq, hwirq, + &gic_level_irq_controller, + NULL); + if (err) + return err; + + irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(virq))); + return gic_shared_irq_domain_map(d, virq, hwirq, 0); + } + + intr = GIC_HWIRQ_TO_LOCAL(hwirq); + map = GIC_MAP_PIN_MAP_TO_PIN | gic_cpu_pin; + + /* + * If adding support for more per-cpu interrupts, keep the the + * array in gic_all_vpes_irq_cpu_online() in sync. + */ + switch (intr) { + case GIC_LOCAL_INT_TIMER: + /* CONFIG_MIPS_CMP workaround (see __gic_init) */ + map = GIC_MAP_PIN_MAP_TO_PIN | timer_cpu_pin; + fallthrough; + case GIC_LOCAL_INT_PERFCTR: + case GIC_LOCAL_INT_FDC: + /* + * HACK: These are all really percpu interrupts, but + * the rest of the MIPS kernel code does not use the + * percpu IRQ API for them. + */ + cd = &gic_all_vpes_chip_data[intr]; + cd->map = map; + err = irq_domain_set_hwirq_and_chip(d, virq, hwirq, + &gic_all_vpes_local_irq_controller, + cd); + if (err) + return err; + + irq_set_handler(virq, handle_percpu_irq); + break; + + default: + err = irq_domain_set_hwirq_and_chip(d, virq, hwirq, + &gic_local_irq_controller, + NULL); + if (err) + return err; + + irq_set_handler(virq, handle_percpu_devid_irq); + irq_set_percpu_devid(virq); + break; + } + + if (!gic_local_irq_is_routable(intr)) + return -EPERM; + + raw_spin_lock_irqsave(&gic_lock, flags); + for_each_online_cpu(cpu) { + write_gic_vl_other(mips_cm_vp_id(cpu)); + write_gic_vo_map(mips_gic_vx_map_reg(intr), map); + } + raw_spin_unlock_irqrestore(&gic_lock, flags); + + return 0; +} + +static int gic_irq_domain_alloc(struct irq_domain *d, unsigned int virq, + unsigned int nr_irqs, void *arg) +{ + struct irq_fwspec *fwspec = arg; + irq_hw_number_t hwirq; + + if (fwspec->param[0] == GIC_SHARED) + hwirq = GIC_SHARED_TO_HWIRQ(fwspec->param[1]); + else + hwirq = GIC_LOCAL_TO_HWIRQ(fwspec->param[1]); + + return gic_irq_domain_map(d, virq, hwirq); +} + +void gic_irq_domain_free(struct irq_domain *d, unsigned int virq, + unsigned int nr_irqs) +{ +} + +static const struct irq_domain_ops gic_irq_domain_ops = { + .xlate = gic_irq_domain_xlate, + .alloc = gic_irq_domain_alloc, + .free = gic_irq_domain_free, + .map = gic_irq_domain_map, +}; + +#ifdef CONFIG_GENERIC_IRQ_IPI + +static int gic_ipi_domain_xlate(struct irq_domain *d, struct device_node *ctrlr, + const u32 *intspec, unsigned int intsize, + irq_hw_number_t *out_hwirq, + unsigned int *out_type) +{ + /* + * There's nothing to translate here. hwirq is dynamically allocated and + * the irq type is always edge triggered. + * */ + *out_hwirq = 0; + *out_type = IRQ_TYPE_EDGE_RISING; + + return 0; +} + +static int gic_ipi_domain_alloc(struct irq_domain *d, unsigned int virq, + unsigned int nr_irqs, void *arg) +{ + struct cpumask *ipimask = arg; + irq_hw_number_t hwirq, base_hwirq; + int cpu, ret, i; + + base_hwirq = find_first_bit(ipi_available, gic_shared_intrs); + if (base_hwirq == gic_shared_intrs) + return -ENOMEM; + + /* check that we have enough space */ + for (i = base_hwirq; i < nr_irqs; i++) { + if (!test_bit(i, ipi_available)) + return -EBUSY; + } + bitmap_clear(ipi_available, base_hwirq, nr_irqs); + + /* map the hwirq for each cpu consecutively */ + i = 0; + for_each_cpu(cpu, ipimask) { + hwirq = GIC_SHARED_TO_HWIRQ(base_hwirq + i); + + ret = irq_domain_set_hwirq_and_chip(d, virq + i, hwirq, + &gic_edge_irq_controller, + NULL); + if (ret) + goto error; + + ret = irq_domain_set_hwirq_and_chip(d->parent, virq + i, hwirq, + &gic_edge_irq_controller, + NULL); + if (ret) + goto error; + + ret = irq_set_irq_type(virq + i, IRQ_TYPE_EDGE_RISING); + if (ret) + goto error; + + ret = gic_shared_irq_domain_map(d, virq + i, hwirq, cpu); + if (ret) + goto error; + + i++; + } + + return 0; +error: + bitmap_set(ipi_available, base_hwirq, nr_irqs); + return ret; +} + +static void gic_ipi_domain_free(struct irq_domain *d, unsigned int virq, + unsigned int nr_irqs) +{ + irq_hw_number_t base_hwirq; + struct irq_data *data; + + data = irq_get_irq_data(virq); + if (!data) + return; + + base_hwirq = GIC_HWIRQ_TO_SHARED(irqd_to_hwirq(data)); + bitmap_set(ipi_available, base_hwirq, nr_irqs); +} + +static int gic_ipi_domain_match(struct irq_domain *d, struct device_node *node, + enum irq_domain_bus_token bus_token) +{ + bool is_ipi; + + switch (bus_token) { + case DOMAIN_BUS_IPI: + is_ipi = d->bus_token == bus_token; + return (!node || to_of_node(d->fwnode) == node) && is_ipi; + break; + default: + return 0; + } +} + +static const struct irq_domain_ops gic_ipi_domain_ops = { + .xlate = gic_ipi_domain_xlate, + .alloc = gic_ipi_domain_alloc, + .free = gic_ipi_domain_free, + .match = gic_ipi_domain_match, +}; + +static int gic_register_ipi_domain(struct device_node *node) +{ + struct irq_domain *gic_ipi_domain; + unsigned int v[2], num_ipis; + + gic_ipi_domain = irq_domain_add_hierarchy(gic_irq_domain, + IRQ_DOMAIN_FLAG_IPI_PER_CPU, + GIC_NUM_LOCAL_INTRS + gic_shared_intrs, + node, &gic_ipi_domain_ops, NULL); + if (!gic_ipi_domain) { + pr_err("Failed to add IPI domain"); + return -ENXIO; + } + + irq_domain_update_bus_token(gic_ipi_domain, DOMAIN_BUS_IPI); + + if (node && + !of_property_read_u32_array(node, "mti,reserved-ipi-vectors", v, 2)) { + bitmap_set(ipi_resrv, v[0], v[1]); + } else { + /* + * Reserve 2 interrupts per possible CPU/VP for use as IPIs, + * meeting the requirements of arch/mips SMP. + */ + num_ipis = 2 * num_possible_cpus(); + bitmap_set(ipi_resrv, gic_shared_intrs - num_ipis, num_ipis); + } + + bitmap_copy(ipi_available, ipi_resrv, GIC_MAX_INTRS); + + return 0; +} + +#else /* !CONFIG_GENERIC_IRQ_IPI */ + +static inline int gic_register_ipi_domain(struct device_node *node) +{ + return 0; +} + +#endif /* !CONFIG_GENERIC_IRQ_IPI */ + +static int gic_cpu_startup(unsigned int cpu) +{ + /* Enable or disable EIC */ + change_gic_vl_ctl(GIC_VX_CTL_EIC, + cpu_has_veic ? GIC_VX_CTL_EIC : 0); + + /* Clear all local IRQ masks (ie. disable all local interrupts) */ + write_gic_vl_rmask(~0); + + /* Enable desired interrupts */ + gic_all_vpes_irq_cpu_online(); + + return 0; +} + +static int __init gic_of_init(struct device_node *node, + struct device_node *parent) +{ + unsigned int cpu_vec, i, gicconfig; + unsigned long reserved; + phys_addr_t gic_base; + struct resource res; + size_t gic_len; + int ret; + + /* Find the first available CPU vector. */ + i = 0; + reserved = (C_SW0 | C_SW1) >> __ffs(C_SW0); + while (!of_property_read_u32_index(node, "mti,reserved-cpu-vectors", + i++, &cpu_vec)) + reserved |= BIT(cpu_vec); + + cpu_vec = find_first_zero_bit(&reserved, hweight_long(ST0_IM)); + if (cpu_vec == hweight_long(ST0_IM)) { + pr_err("No CPU vectors available\n"); + return -ENODEV; + } + + if (of_address_to_resource(node, 0, &res)) { + /* + * Probe the CM for the GIC base address if not specified + * in the device-tree. + */ + if (mips_cm_present()) { + gic_base = read_gcr_gic_base() & + ~CM_GCR_GIC_BASE_GICEN; + gic_len = 0x20000; + pr_warn("Using inherited base address %pa\n", + &gic_base); + } else { + pr_err("Failed to get memory range\n"); + return -ENODEV; + } + } else { + gic_base = res.start; + gic_len = resource_size(&res); + } + + if (mips_cm_present()) { + write_gcr_gic_base(gic_base | CM_GCR_GIC_BASE_GICEN); + /* Ensure GIC region is enabled before trying to access it */ + __sync(); + } + + mips_gic_base = ioremap(gic_base, gic_len); + if (!mips_gic_base) { + pr_err("Failed to ioremap gic_base\n"); + return -ENOMEM; + } + + gicconfig = read_gic_config(); + gic_shared_intrs = FIELD_GET(GIC_CONFIG_NUMINTERRUPTS, gicconfig); + gic_shared_intrs = (gic_shared_intrs + 1) * 8; + + if (cpu_has_veic) { + /* Always use vector 1 in EIC mode */ + gic_cpu_pin = 0; + timer_cpu_pin = gic_cpu_pin; + set_vi_handler(gic_cpu_pin + GIC_PIN_TO_VEC_OFFSET, + __gic_irq_dispatch); + } else { + gic_cpu_pin = cpu_vec - GIC_CPU_PIN_OFFSET; + irq_set_chained_handler(MIPS_CPU_IRQ_BASE + cpu_vec, + gic_irq_dispatch); + /* + * With the CMP implementation of SMP (deprecated), other CPUs + * are started by the bootloader and put into a timer based + * waiting poll loop. We must not re-route those CPU's local + * timer interrupts as the wait instruction will never finish, + * so just handle whatever CPU interrupt it is routed to by + * default. + * + * This workaround should be removed when CMP support is + * dropped. + */ + if (IS_ENABLED(CONFIG_MIPS_CMP) && + gic_local_irq_is_routable(GIC_LOCAL_INT_TIMER)) { + timer_cpu_pin = read_gic_vl_timer_map() & GIC_MAP_PIN_MAP; + irq_set_chained_handler(MIPS_CPU_IRQ_BASE + + GIC_CPU_PIN_OFFSET + + timer_cpu_pin, + gic_irq_dispatch); + } else { + timer_cpu_pin = gic_cpu_pin; + } + } + + gic_irq_domain = irq_domain_add_simple(node, GIC_NUM_LOCAL_INTRS + + gic_shared_intrs, 0, + &gic_irq_domain_ops, NULL); + if (!gic_irq_domain) { + pr_err("Failed to add IRQ domain"); + return -ENXIO; + } + + ret = gic_register_ipi_domain(node); + if (ret) + return ret; + + board_bind_eic_interrupt = &gic_bind_eic_interrupt; + + /* Setup defaults */ + for (i = 0; i < gic_shared_intrs; i++) { + change_gic_pol(i, GIC_POL_ACTIVE_HIGH); + change_gic_trig(i, GIC_TRIG_LEVEL); + write_gic_rmask(i); + } + + return cpuhp_setup_state(CPUHP_AP_IRQ_MIPS_GIC_STARTING, + "irqchip/mips/gic:starting", + gic_cpu_startup, NULL); +} +IRQCHIP_DECLARE(mips_gic, "mti,gic", gic_of_init); |