/* * Copyright (C) 2011 Google, Inc. * * Author: * Colin Cross * * Copyright (c) 2010-2015, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Required by asm/hardware/gic.h */ #include #include /* HACK: will be removed once cpuidle is moved to drivers */ #include "../../arch/arm/mach-tegra/pm.h" #include "irqchip.h" #define ICTLR_CPU_IEP_VFIQ 0x08 #define ICTLR_CPU_IEP_FIR 0x14 #define ICTLR_CPU_IEP_FIR_SET 0x18 #define ICTLR_CPU_IEP_FIR_CLR 0x1c #define ICTLR_CPU_IER 0x20 #define ICTLR_CPU_IER_SET 0x24 #define ICTLR_CPU_IER_CLR 0x28 #define ICTLR_CPU_IEP_CLASS 0x2C #define ICTLR_COP_IER 0x30 #define ICTLR_COP_IER_SET 0x34 #define ICTLR_COP_IER_CLR 0x38 #define ICTLR_COP_IEP_CLASS 0x3c #ifdef CONFIG_ARCH_TEGRA_2x_SOC #define NUM_ICTLRS 4 #else #define NUM_ICTLRS 5 #endif #define FIRST_LEGACY_IRQ 32 #define ARM_VERSION_CORTEX_A15 0xC0F static u32 gic_version; static void __iomem *gic_dist_base; static void __iomem *gic_cpu_base; static u32 gic_affinity[INT_GIC_NR/4]; static int num_ictlrs; static void __iomem **ictlr_reg_base; #ifdef CONFIG_PM_SLEEP static u32 cop_ier[NUM_ICTLRS]; static u32 cop_iep[NUM_ICTLRS]; static u32 cpu_ier[NUM_ICTLRS]; static u32 cpu_iep[NUM_ICTLRS]; static u32 ictlr_wake_mask[NUM_ICTLRS]; #endif #ifdef CONFIG_FIQ #if !defined(CONFIG_TRUSTED_FOUNDATIONS) && \ defined(CONFIG_ARCH_TEGRA_12x_SOC) && defined(CONFIG_FIQ_DEBUGGER) void tegra_fiq_ack(unsigned int fiq) { void __iomem *base = IO_ADDRESS(TEGRA_ARM_PERIF_BASE + 0x2000); readl_relaxed(base + GIC_CPU_INTACK); } static void tegra_legacy_avp_irq_mask(unsigned int irq) { void __iomem *base; pr_debug("%s: %d\n", __func__, irq); irq -= FIRST_LEGACY_IRQ; base = ictlr_reg_base[irq>>5]; writel(1 << (irq & 31), base + ICTLR_COP_IER_CLR); } static void tegra_legacy_avp_irq_unmask(unsigned int irq) { void __iomem *base; pr_debug("%s: %d\n", __func__, irq); irq -= FIRST_LEGACY_IRQ; base = ictlr_reg_base[irq>>5]; writel(1 << (irq & 31), base + ICTLR_COP_IER_SET); } #else static void tegra_legacy_select_fiq(unsigned int irq, bool fiq) { void __iomem *base; pr_debug("%s: %d\n", __func__, irq); irq -= FIRST_LEGACY_IRQ; base = ictlr_reg_base[irq>>5]; writel(fiq << (irq & 31), base + ICTLR_CPU_IEP_CLASS); } static void tegra_fiq_mask(struct irq_data *d) { void __iomem *base; int leg_irq; if (d->irq < FIRST_LEGACY_IRQ) return; leg_irq = d->irq - FIRST_LEGACY_IRQ; base = ictlr_reg_base[leg_irq >> 5]; writel(1 << (leg_irq & 31), base + ICTLR_CPU_IER_CLR); } static void tegra_fiq_unmask(struct irq_data *d) { void __iomem *base; int leg_irq; if (d->irq < FIRST_LEGACY_IRQ) return; leg_irq = d->irq - FIRST_LEGACY_IRQ; base = ictlr_reg_base[leg_irq >> 5]; writel(1 << (leg_irq & 31), base + ICTLR_CPU_IER_SET); } #endif void tegra_fiq_enable(int irq) { #if !defined(CONFIG_TRUSTED_FOUNDATIONS) && \ defined(CONFIG_ARCH_TEGRA_12x_SOC) && defined(CONFIG_FIQ_DEBUGGER) tegra_legacy_avp_irq_mask(irq); #else void __iomem *base = IO_ADDRESS(TEGRA_ARM_PERIF_BASE + 0x100); /* enable FIQ */ u32 val = readl(base + GIC_CPU_CTRL); val &= ~8; /* pass FIQs through */ val |= 2; /* enableNS */ writel(val, base + GIC_CPU_CTRL); tegra_legacy_select_fiq(irq, true); tegra_fiq_unmask(irq_get_irq_data(irq)); #endif } void tegra_fiq_disable(int irq) { #if !defined(CONFIG_TRUSTED_FOUNDATIONS) && \ defined(CONFIG_ARCH_TEGRA_12x_SOC) && defined(CONFIG_FIQ_DEBUGGER) tegra_legacy_avp_irq_unmask(irq); #else tegra_fiq_mask(irq_get_irq_data(irq)); tegra_legacy_select_fiq(irq, false); #endif } #endif /* CONFIG_FIQ */ #if defined(CONFIG_HOTPLUG_CPU) || defined(CONFIG_PM_SLEEP) void tegra_gic_cpu_disable(bool disable_pass_through) { u32 gic_cpu_ctrl = 0; #ifndef CONFIG_ARCH_TEGRA_2x_SOC if (disable_pass_through) { if (gic_version == GIC_V2) gic_cpu_ctrl = 0x1E0; else gic_cpu_ctrl = 2; } #endif writel(gic_cpu_ctrl, gic_cpu_base + GIC_CPU_CTRL); } #endif void tegra_gic_cpu_enable(void) { #if !defined(CONFIG_TRUSTED_FOUNDATIONS) && \ defined(CONFIG_ARCH_TEGRA_12x_SOC) && defined(CONFIG_FIQ_DEBUGGER) writel(0x1EF, gic_cpu_base + GIC_CPU_CTRL); #else writel(0x1, gic_cpu_base + GIC_CPU_CTRL); #endif } #ifdef CONFIG_PM_SLEEP int tegra_gic_pending_interrupt(void) { u32 irq = readl(gic_cpu_base + GIC_CPU_HIGHPRI); irq &= 0x3FF; return irq; } #ifndef CONFIG_ARCH_TEGRA_2x_SOC void tegra_gic_dist_disable(void) { writel(0, gic_dist_base + GIC_DIST_CTRL); } void tegra_gic_dist_enable(void) { #if !defined(CONFIG_TRUSTED_FOUNDATIONS) && \ defined(CONFIG_ARCH_TEGRA_12x_SOC) && defined(CONFIG_FIQ_DEBUGGER) writel(0x3, gic_dist_base + GIC_DIST_CTRL); #else writel(0x1, gic_dist_base + GIC_DIST_CTRL); #endif } void tegra_gic_disable_affinity(void) { unsigned int i; BUG_ON(is_lp_cluster()); /* The GIC distributor TARGET register is one byte per IRQ. */ for (i = 32; i < INT_GIC_NR; i += 4) { /* Save the affinity. */ gic_affinity[i/4] = __raw_readl(gic_dist_base + GIC_DIST_TARGET + i); /* Force this interrupt to CPU0. */ __raw_writel(0x01010101, gic_dist_base + GIC_DIST_TARGET + i); } wmb(); } void tegra_gic_restore_affinity(void) { unsigned int i; BUG_ON(is_lp_cluster()); /* The GIC distributor TARGET register is one byte per IRQ. */ for (i = 32; i < INT_GIC_NR; i += 4) { #ifdef CONFIG_BUG u32 reg = __raw_readl(gic_dist_base + GIC_DIST_TARGET + i); if (reg & 0xFEFEFEFE) panic("GIC affinity changed!"); #endif /* Restore this interrupt's affinity. */ __raw_writel(gic_affinity[i/4], gic_dist_base + GIC_DIST_TARGET + i); } wmb(); } void tegra_gic_affinity_to_cpu0(void) { unsigned int i; BUG_ON(is_lp_cluster()); for (i = 32; i < INT_GIC_NR; i += 4) __raw_writel(0x01010101, gic_dist_base + GIC_DIST_TARGET + i); wmb(); } #endif /* CONFIG_ARCH_TEGRA_2x_SOC */ #if !defined(CONFIG_TRUSTED_FOUNDATIONS) && \ defined(CONFIG_ARCH_TEGRA_12x_SOC) && defined(CONFIG_FIQ_DEBUGGER) static void tegra_gic_cpu_restore(void) { u32 val = 0; val = readl_relaxed(gic_dist_base + GIC_DIST_IGROUP + (INT_WDT_AVP/32)*4); val &= ~(1 << (INT_WDT_AVP%32)); writel_relaxed(val, gic_dist_base + GIC_DIST_IGROUP + (INT_WDT_AVP/32)*4); val = readl_relaxed(gic_dist_base + GIC_DIST_PRI + (INT_WDT_AVP/4)*4); val &= ~(0xff << ((INT_WDT_AVP%4)*8)); writel_relaxed(val, gic_dist_base + GIC_DIST_PRI + (INT_WDT_AVP/4)*4); } static void tegra_gic_dist_restore(void) { u32 val = 0; val = readl_relaxed(gic_dist_base + GIC_DIST_IGROUP + (INT_WDT_AVP/32)*4); val &= ~(1 << (INT_WDT_AVP%32)); writel_relaxed(val, gic_dist_base + GIC_DIST_IGROUP + (INT_WDT_AVP/32)*4); val = readl_relaxed(gic_dist_base + GIC_DIST_PRI + (INT_WDT_AVP/4)*4); val &= ~(0xff << ((INT_WDT_AVP%4)*8)); writel_relaxed(val, gic_dist_base + GIC_DIST_PRI + (INT_WDT_AVP/4)*4); } #endif static int tegra_gic_notifier(struct notifier_block *self, unsigned long cmd, void *v) { switch (cmd) { case CPU_PM_ENTER: writel(0x1E0, gic_cpu_base + GIC_CPU_CTRL); break; #if !defined(CONFIG_TRUSTED_FOUNDATIONS) && \ defined(CONFIG_ARCH_TEGRA_12x_SOC) && defined(CONFIG_FIQ_DEBUGGER) case CPU_PM_ENTER_FAILED: case CPU_PM_EXIT: tegra_gic_cpu_restore(); break; case CPU_CLUSTER_PM_ENTER_FAILED: case CPU_CLUSTER_PM_EXIT: tegra_gic_dist_restore(); break; #endif } return NOTIFY_OK; } static struct notifier_block tegra_gic_notifier_block = { .notifier_call = tegra_gic_notifier, }; #endif /* CONFIG_PM_SLEEP */ #if !defined(CONFIG_TRUSTED_FOUNDATIONS) && \ defined(CONFIG_ARCH_TEGRA_12x_SOC) && defined(CONFIG_FIQ_DEBUGGER) static void __init tegra_gic_dist_init(void) { u32 val = 0; val = readl_relaxed(gic_dist_base + GIC_DIST_IGROUP + (INT_WDT_AVP/32)*4); val &= ~(1 << (INT_WDT_AVP%32)); writel_relaxed(val, gic_dist_base + GIC_DIST_IGROUP + (INT_WDT_AVP/32)*4); val = readl_relaxed(gic_dist_base + GIC_DIST_PRI + (INT_WDT_AVP/4)*4); val &= ~(0xff << ((INT_WDT_AVP%4)*8)); writel_relaxed(val, gic_dist_base + GIC_DIST_PRI + (INT_WDT_AVP/4)*4); } void __cpuinit tegra_gic_secondary_init(void) { u32 val = 0; val = readl_relaxed(gic_dist_base + GIC_DIST_IGROUP + (INT_WDT_AVP/32)*4); val &= ~(1 << (INT_WDT_AVP%32)); writel_relaxed(val, gic_dist_base + GIC_DIST_IGROUP + (INT_WDT_AVP/32)*4); val = readl_relaxed(gic_dist_base + GIC_DIST_PRI + (INT_WDT_AVP/4)*4); val &= ~(0xff << ((INT_WDT_AVP%4)*8)); writel_relaxed(val, gic_dist_base + GIC_DIST_PRI + (INT_WDT_AVP/4)*4); } #endif int tegra_update_lp1_irq_wake(unsigned int irq, bool enable) { #ifdef CONFIG_PM_SLEEP u8 index; u32 mask; BUG_ON(irq < FIRST_LEGACY_IRQ || irq >= FIRST_LEGACY_IRQ + num_ictlrs * 32); index = ((irq - FIRST_LEGACY_IRQ) / 32); mask = BIT((irq - FIRST_LEGACY_IRQ) % 32); if (enable) ictlr_wake_mask[index] |= mask; else ictlr_wake_mask[index] &= ~mask; #endif return 0; } static inline void tegra_irq_write_mask(unsigned int irq, unsigned long reg) { void __iomem *base; u32 mask; BUG_ON(irq < FIRST_LEGACY_IRQ || irq >= FIRST_LEGACY_IRQ + num_ictlrs * 32); base = ictlr_reg_base[(irq - FIRST_LEGACY_IRQ) / 32]; mask = BIT((irq - FIRST_LEGACY_IRQ) % 32); __raw_writel(mask, base + reg); } static void tegra_mask(struct irq_data *d) { if (d->irq < FIRST_LEGACY_IRQ) return; tegra_irq_write_mask(d->irq, ICTLR_CPU_IER_CLR); } static void tegra_unmask(struct irq_data *d) { if (d->irq < FIRST_LEGACY_IRQ) return; tegra_irq_write_mask(d->irq, ICTLR_CPU_IER_SET); } static void tegra_ack(struct irq_data *d) { if (d->irq < FIRST_LEGACY_IRQ) return; tegra_irq_write_mask(d->irq, ICTLR_CPU_IEP_FIR_CLR); } static void tegra_eoi(struct irq_data *d) { if (d->irq < FIRST_LEGACY_IRQ) return; tegra_irq_write_mask(d->irq, ICTLR_CPU_IEP_FIR_CLR); } static int tegra_retrigger(struct irq_data *d) { if (d->irq < FIRST_LEGACY_IRQ) return 0; tegra_irq_write_mask(d->irq, ICTLR_CPU_IEP_FIR_SET); return 1; } static int tegra_set_type(struct irq_data *d, unsigned int flow_type) { int wake_size; int wake_list[PMC_MAX_WAKE_COUNT]; int i; int err = 0; int ret; tegra_irq_to_wake(d->irq, wake_list, &wake_size); for (i = 0; i < wake_size; i++) { ret = tegra_pm_irq_set_wake_type(wake_list[i], flow_type); if (ret < 0) { pr_err("Set lp0 wake type=%d fail for irq=%d, wake%d ret=%d\n", flow_type, d->irq, wake_list[i], ret); if (!err) err = ret; } } return err; } #ifdef CONFIG_PM_SLEEP /* * Caller ensures that tegra_set_wake (irq_set_wake callback) * is called for non-gpio wake sources only */ static int tegra_set_wake(struct irq_data *d, unsigned int enable) { int ret; int wake_size; int wake_list[PMC_MAX_WAKE_COUNT]; int i; int err = 0; tegra_irq_to_wake(d->irq, wake_list, &wake_size); for (i = 0; i < wake_size; i++) { /* pmc lp0 wake enable for non-gpio wake sources */ ret = tegra_pm_irq_set_wake(wake_list[i], enable); if (ret < 0) { pr_err("Failed lp0 wake %s for irq=%d, wake%d ret=%d\n", (enable ? "enable" : "disable"), d->irq, wake_list[i], ret); if (!err) err = ret; } } /* lp1 wake enable for wake sources */ ret = tegra_update_lp1_irq_wake(d->irq, enable); if (ret) pr_err("Failed lp1 wake %s for irq=%d\n", (enable ? "enable" : "disable"), d->irq); return ret; } static int tegra_legacy_irq_suspend(void) { unsigned long flags; int i; local_irq_save(flags); for (i = 0; i < NUM_ICTLRS; i++) { void __iomem *ictlr = ictlr_reg_base[i]; /* save interrupt state */ cpu_ier[i] = readl(ictlr + ICTLR_CPU_IER); cpu_iep[i] = readl(ictlr + ICTLR_CPU_IEP_CLASS); cop_ier[i] = readl(ictlr + ICTLR_COP_IER); cop_iep[i] = readl(ictlr + ICTLR_COP_IEP_CLASS); /* disable COP interrupts */ writel(~0, ictlr + ICTLR_COP_IER_CLR); /* disable CPU interrupts */ writel(~0, ictlr + ICTLR_CPU_IER_CLR); /* enable lp1 wake sources */ writel(ictlr_wake_mask[i], ictlr + ICTLR_CPU_IER_SET); } local_irq_restore(flags); return 0; } static void tegra_legacy_irq_resume(void) { unsigned long flags; int i; local_irq_save(flags); for (i = 0; i < NUM_ICTLRS; i++) { void __iomem *ictlr = ictlr_reg_base[i]; writel(cpu_iep[i], ictlr + ICTLR_CPU_IEP_CLASS); writel(~0ul, ictlr + ICTLR_CPU_IER_CLR); writel(cpu_ier[i], ictlr + ICTLR_CPU_IER_SET); writel(cop_iep[i], ictlr + ICTLR_COP_IEP_CLASS); writel(~0ul, ictlr + ICTLR_COP_IER_CLR); writel(cop_ier[i], ictlr + ICTLR_COP_IER_SET); } local_irq_restore(flags); } static struct syscore_ops tegra_legacy_irq_syscore_ops = { .suspend = tegra_legacy_irq_suspend, .resume = tegra_legacy_irq_resume, .save = tegra_legacy_irq_suspend, .restore = tegra_legacy_irq_resume, }; static int __init tegra_legacy_irq_syscore_init(void) { register_syscore_ops(&tegra_legacy_irq_syscore_ops); return 0; } #else #define tegra_set_wake NULL #endif void tegra_init_legacy_irq_cop(void) { int i; for (i = 0; i < NUM_ICTLRS; i++) { void __iomem *ictlr = ictlr_reg_base[i]; writel(~0, ictlr + ICTLR_COP_IER_CLR); writel(0, ictlr + ICTLR_COP_IEP_CLASS); } } static int __init tegra_gic_of_init(struct device_node *node, struct device_node *parent) { int i; struct device_node *arm_gic_np = of_find_compatible_node(NULL, NULL, "arm,cortex-a15-gic"); struct device_node *tegra_gic_np = of_find_compatible_node(NULL, NULL, "nvidia,tegra-gic"); tegra_wakeup_table_init(); gic_dist_base = of_iomap(arm_gic_np, 0); gic_cpu_base = of_iomap(arm_gic_np, 1); gic_version = (readl(gic_dist_base + 0xFE8) & 0xF0) >> 4; /* Retrieve # of ictrls from DT and fallback to gic dist */ if (of_property_read_u32(tegra_gic_np, "num-ictrls", &num_ictlrs)) num_ictlrs = readl_relaxed(gic_dist_base + GIC_DIST_CTR) & 0x1f; pr_info("the number of interrupt controllers found is %d", num_ictlrs); ictlr_reg_base = kzalloc(sizeof(void *) * num_ictlrs, GFP_KERNEL); tegra_clocks_init(); for (i = 0; i < num_ictlrs; i++) { ictlr_reg_base[i] = of_iomap(node, i); if (!ictlr_reg_base[i]) { pr_info("failed to get the right register\n"); return -EINVAL; } writel(~0, ictlr_reg_base[i] + ICTLR_CPU_IER_CLR); writel(0, ictlr_reg_base[i] + ICTLR_CPU_IEP_CLASS); writel(~0, ictlr_reg_base[i] + ICTLR_CPU_IEP_FIR_CLR); } gic_arch_extn.irq_ack = tegra_ack; gic_arch_extn.irq_eoi = tegra_eoi; gic_arch_extn.irq_mask = tegra_mask; gic_arch_extn.irq_unmask = tegra_unmask; gic_arch_extn.irq_retrigger = tegra_retrigger; gic_arch_extn.irq_set_type = tegra_set_type; gic_arch_extn.irq_set_wake = tegra_set_wake; gic_arch_extn.flags = IRQCHIP_MASK_ON_SUSPEND; #ifdef CONFIG_PM_SLEEP tegra_legacy_irq_syscore_init(); if (gic_version == GIC_V2) cpu_pm_register_notifier(&tegra_gic_notifier_block); #endif #if !defined(CONFIG_TRUSTED_FOUNDATIONS) && \ defined(CONFIG_ARCH_TEGRA_12x_SOC) && defined(CONFIG_FIQ_DEBUGGER) tegra_gic_dist_init(); #endif return 0; } IRQCHIP_DECLARE(tegra_gic, "nvidia,tegra-gic", tegra_gic_of_init);