// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2019 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* #include */ #include #include #include #include #ifdef CONFIG_CPU_PM #include #endif #ifdef CONFIG_PM_SLEEP #include #endif #define IOMEM(x) ((void __force __iomem *)(x)) #define GICD_IROUTER 0x6000 #define GICD_IROUTER_SPI_MODE_ANY (1U << 31) /* for cirq use */ void __iomem *GIC_DIST_BASE; #ifdef CONFIG_MTK_SYSIRQ void __iomem *INT_POL_CTL0; void __iomem *INT_POL_CTL1; static u32 reg_len_pol0; #endif void __iomem *MCUSYS_BASE_SWMODE; static void __iomem *GIC_REDIST_BASE; unsigned int __attribute__((weak)) irq_sw_mode_support(void) { return 0; } #ifndef readq /* for some kernel config, readq might not be defined, ex aarch32 */ static inline u64 readq(const void __iomem *addr) { u64 ret = readl(addr + 4); ret <<= 32; ret |= readl(addr); return ret; } #endif #ifdef CONFIG_FAST_CIRQ_CLONE_FLUSH void __iomem *get_dist_base(void) { return GIC_DIST_BASE; } #endif static int gic_populate_rdist(void __iomem **rdist_base) { int cpu = smp_processor_id(); *rdist_base = GIC_REDIST_BASE + cpu*SZ_64K*2; return 0; } bool mt_get_irq_gic_targets(struct irq_data *d, cpumask_t *mask) { void __iomem *dist_base; void __iomem *routing_reg; u32 cpu; u32 cluster; u64 routing_val; u64 target_mask; /* for SPI/PPI, target to current cpu */ if (gic_irq(d) < 32) { target_mask = 1<>8; cpu = routing_val&0xff; /* assume 1 cluster contain 4 cpu in little, * and only the last cluster can contain less than 4 cpu */ target_mask = 1<<(cluster*4 + cpu); pr_debug("%s:%d: irq(%d) target_mask(0x%llx)\n", __func__, __LINE__, gic_irq(d), target_mask); } build_mask: cpumask_clear(mask); for_each_cpu(cpu, cpu_possible_mask) { if (target_mask & (1<= reg_len_pol0)) { if (!INT_POL_CTL1) { pr_notice("MUST have 2nd INT_POL_CTRL\n"); /* is a bug */ WARN_ON(1); return 0; } reg -= reg_len_pol0; base = INT_POL_CTL1; } return readl_relaxed(IOMEM(base + reg*4)); } u32 mt_irq_get_pol(u32 irq) { u32 hwirq = virq_to_hwirq(irq); return mt_irq_get_pol_hw(hwirq); } #endif /* * mt_irq_mask_all: disable all interrupts * @mask: pointer to struct mtk_irq_mask for storing the original mask value. * Return 0 for success; return negative values for failure. * (This is ONLY used for the idle current measurement by the factory mode.) */ int mt_irq_mask_all(struct mtk_irq_mask *mask) { void __iomem *dist_base; dist_base = GIC_DIST_BASE; if (mask) { /* for SPI */ mask->mask1 = readl((dist_base + GIC_DIST_ENABLE_SET + 0x4)); mask->mask2 = readl((dist_base + GIC_DIST_ENABLE_SET + 0x8)); mask->mask3 = readl((dist_base + GIC_DIST_ENABLE_SET + 0xc)); mask->mask4 = readl((dist_base + GIC_DIST_ENABLE_SET + 0x10)); mask->mask5 = readl((dist_base + GIC_DIST_ENABLE_SET + 0x14)); mask->mask6 = readl((dist_base + GIC_DIST_ENABLE_SET + 0x18)); mask->mask7 = readl((dist_base + GIC_DIST_ENABLE_SET + 0x1c)); mask->mask8 = readl((dist_base + GIC_DIST_ENABLE_SET + 0x20)); mask->mask9 = readl((dist_base + GIC_DIST_ENABLE_SET + 0x24)); mask->mask10 = readl((dist_base + GIC_DIST_ENABLE_SET + 0x28)); mask->mask11 = readl((dist_base + GIC_DIST_ENABLE_SET + 0x2c)); mask->mask12 = readl((dist_base + GIC_DIST_ENABLE_SET + 0x30)); /* for SPI */ writel(0xFFFFFFFF, (dist_base + GIC_DIST_ENABLE_CLEAR + 0x4)); writel(0xFFFFFFFF, (dist_base + GIC_DIST_ENABLE_CLEAR + 0x8)); writel(0xFFFFFFFF, (dist_base + GIC_DIST_ENABLE_CLEAR + 0xC)); writel(0xFFFFFFFF, (dist_base + GIC_DIST_ENABLE_CLEAR + 0x10)); writel(0xFFFFFFFF, (dist_base + GIC_DIST_ENABLE_CLEAR + 0x14)); writel(0xFFFFFFFF, (dist_base + GIC_DIST_ENABLE_CLEAR + 0x18)); writel(0xFFFFFFFF, (dist_base + GIC_DIST_ENABLE_CLEAR + 0x1C)); writel(0xFFFFFFFF, (dist_base + GIC_DIST_ENABLE_CLEAR + 0x20)); writel(0xFFFFFFFF, (dist_base + GIC_DIST_ENABLE_CLEAR + 0x24)); writel(0xFFFFFFFF, (dist_base + GIC_DIST_ENABLE_CLEAR + 0x28)); writel(0xFFFFFFFF, (dist_base + GIC_DIST_ENABLE_CLEAR + 0x2c)); writel(0xFFFFFFFF, (dist_base + GIC_DIST_ENABLE_CLEAR + 0x30)); /* make sure distributor changes happen */ mb(); mask->header = IRQ_MASK_HEADER; mask->footer = IRQ_MASK_FOOTER; return 0; } else { return -1; } } /* * mt_irq_mask_restore: restore all interrupts * @mask: pointer to struct mtk_irq_mask for storing the original mask value. * Return 0 for success; return negative values for failure. * (This is ONLY used for the idle current measurement by the factory mode.) */ int mt_irq_mask_restore(struct mtk_irq_mask *mask) { void __iomem *dist_base; dist_base = GIC_DIST_BASE; if (!mask) return -1; if (mask->header != IRQ_MASK_HEADER) return -1; if (mask->footer != IRQ_MASK_FOOTER) return -1; writel(mask->mask1, (dist_base + GIC_DIST_ENABLE_SET + 0x4)); writel(mask->mask2, (dist_base + GIC_DIST_ENABLE_SET + 0x8)); writel(mask->mask3, (dist_base + GIC_DIST_ENABLE_SET + 0xc)); writel(mask->mask4, (dist_base + GIC_DIST_ENABLE_SET + 0x10)); writel(mask->mask5, (dist_base + GIC_DIST_ENABLE_SET + 0x14)); writel(mask->mask6, (dist_base + GIC_DIST_ENABLE_SET + 0x18)); writel(mask->mask7, (dist_base + GIC_DIST_ENABLE_SET + 0x1c)); writel(mask->mask8, (dist_base + GIC_DIST_ENABLE_SET + 0x20)); writel(mask->mask9, (dist_base + GIC_DIST_ENABLE_SET + 0x24)); writel(mask->mask10, (dist_base + GIC_DIST_ENABLE_SET + 0x28)); writel(mask->mask11, (dist_base + GIC_DIST_ENABLE_SET + 0x2c)); writel(mask->mask12, (dist_base + GIC_DIST_ENABLE_SET + 0x30)); /* make sure dist changes happen */ mb(); return 0; } u32 mt_irq_get_pending_hw(unsigned int hwirq) { void __iomem *base; u32 bit = 1 << (hwirq % 32); if (hwirq >= 32) { base = GIC_DIST_BASE; } else { gic_populate_rdist(&base); base += SZ_64K; } return (readl_relaxed(base + GIC_DIST_PENDING_SET + (hwirq/32)*4)&bit) ? 1 : 0; } u32 mt_irq_get_pending(unsigned int irq) { unsigned int hwirq = virq_to_hwirq(irq); return mt_irq_get_pending_hw(hwirq); } u32 mt_irq_get_pending_vec(u32 start_irq) { void __iomem *base = 0; u32 pending_vec = 0; u32 reg = start_irq/32; u32 LSB_num, MSB_num; u32 LSB_vec, MSB_vec; if (start_irq >= 32) { base = GIC_DIST_BASE; } else { gic_populate_rdist(&base); base += SZ_64K; } /* if start_irq is not aligned 32, do some assembling */ MSB_num = start_irq%32; if (MSB_num != 0) { LSB_num = 32 - MSB_num; LSB_vec = readl_relaxed(base + GIC_DIST_PENDING_SET + reg*4) >>MSB_num; MSB_vec = readl_relaxed(base + GIC_DIST_PENDING_SET + (reg+1)*4) <= 32) { base = GIC_DIST_BASE + GIC_DIST_ENABLE_SET; } else { gic_populate_rdist(&base); base += SZ_64K; base = base + GIC_DIST_ENABLE_SET; } return (readl_relaxed(base + (hwirq/32)*4) & bit) ? 1 : 0; } unsigned int mt_irq_get_sens(unsigned int irq) { unsigned int config; /* * 2'b10 edge * 2'b00 level */ config = readl_relaxed(GIC_DIST_BASE + GIC_DIST_CONFIG + (irq / 16) * 4); config = (config >> (irq % 16) * 2) & 0x3; return config; } #endif void mt_irq_set_pending_hw(unsigned int hwirq) { void __iomem *base; u32 bit = 1 << (hwirq % 32); if (hwirq >= 32) { base = GIC_DIST_BASE; } else { gic_populate_rdist(&base); base += SZ_64K; } writel(bit, base + GIC_DIST_PENDING_SET + (hwirq/32)*4); } void mt_irq_set_pending(unsigned int irq) { unsigned int hwirq = virq_to_hwirq(irq); mt_irq_set_pending_hw(hwirq); } void mt_irq_unmask_for_sleep_ex(unsigned int virq) { void __iomem *dist_base; u32 mask; unsigned int hwirq; hwirq = virq_to_hwirq(virq); dist_base = GIC_DIST_BASE; mask = 1 << (hwirq % 32); if (hwirq < 16) { pr_notice("Fail to enable interrupt %d\n", hwirq); return; } writel(mask, dist_base + GIC_DIST_ENABLE_SET + hwirq / 32 * 4); /* make sure enable happen */ mb(); } /* * mt_irq_unmask_for_sleep: enable an interrupt for the sleep manager's use * @irq: interrupt id * (THIS IS ONLY FOR SLEEP FUNCTION USE. DO NOT USE IT YOURSELF!) */ void mt_irq_unmask_for_sleep(unsigned int hwirq) { void __iomem *dist_base; u32 mask; mask = 1 << (hwirq % 32); dist_base = GIC_DIST_BASE; if (hwirq < 16) { pr_notice("Fail to enable interrupt %d\n", hwirq); return; } writel(mask, dist_base + GIC_DIST_ENABLE_SET + hwirq / 32 * 4); /* make sure enable happen */ mb(); } /* * mt_irq_mask_for_sleep: disable an interrupt for the sleep manager's use * @irq: interrupt id * (THIS IS ONLY FOR SLEEP FUNCTION USE. DO NOT USE IT YOURSELF!) */ void mt_irq_mask_for_sleep(unsigned int irq) { void __iomem *dist_base; u32 mask; irq = virq_to_hwirq(irq); mask = 1 << (irq % 32); dist_base = GIC_DIST_BASE; if (irq < 16) { pr_notice("Fail to enable interrupt %d\n", irq); return; } writel(mask, dist_base + GIC_DIST_ENABLE_CLEAR + irq / 32 * 4); /* make sure clr happen */ mb(); } char *mt_irq_dump_status_buf(int irq, char *buf) { int rc, is_gic600 = 0; struct arm_smccc_res res = {0}; unsigned int result; char *ptr = buf; irq = virq_to_hwirq(irq); if (!ptr) return NULL; result = readl(GIC_DIST_BASE + GIC_IIDR); is_gic600 = ((result >> GICD_V3_IIDR_PROD_ID_SHIFT) == GICD_V3_IIDR_GIC600) ? 1 : 0; ptr += sprintf(ptr, "[mt gic dump] irq = %d\n", irq); #if defined(CONFIG_ARM64) || defined(CONFIG_ARM_PSCI) arm_smccc_smc(MTK_SIP_KERNEL_GIC_DUMP, irq, 0, 0, 0, 0, 0, 0, &res); #endif rc = res.a0; if (rc < 0) { ptr += sprintf(ptr, "[mt gic dump] not allowed to dump!\n"); return ptr; } /* get mask */ result = rc & 0x1; ptr += sprintf(ptr, "[mt gic dump] enable = %d\n", result); /* get group */ result = (rc >> 1) & 0x1; ptr += sprintf(ptr, "[mt gic dump] group = %x (0x1:irq,0x0:fiq)\n", result); /* get priority */ result = (rc >> 2) & 0xff; ptr += sprintf(ptr, "[mt gic dump] priority = %x\n", result); /* get sensitivity */ result = (rc >> 10) & 0x1; ptr += sprintf(ptr, "[mt gic dump] sensitivity = %x ", result); ptr += sprintf(ptr, "(edge:0x1, level:0x0)\n"); /* get pending status */ result = (rc >> 11) & 0x1; ptr += sprintf(ptr, "[mt gic dump] pending = %x\n", result); /* get active status */ result = (rc >> 12) & 0x1; ptr += sprintf(ptr, "[mt gic dump] active status = %x\n", result); #ifdef CONFIG_MTK_SYSIRQ /* get polarity */ result = (rc >> 13) & 0x1; ptr += sprintf(ptr, "[mt gic dump] polarity = %x (0x0: high, 0x1:low)\n", result); #endif /* get target cpu mask */ if (is_gic600) result = (rc >> 15) & 0xffff; else result = (rc >> 14) & 0xffff; ptr += sprintf(ptr, "[mt gic dump] tartget cpu mask = 0x%x\n", result); return ptr; } void mt_irq_dump_status(int irq) { char *buf = kmalloc(2048, GFP_ATOMIC); if (!buf) return; if (mt_irq_dump_status_buf(irq, buf)) pr_notice("%s", buf); kfree(buf); } EXPORT_SYMBOL(mt_irq_dump_status); #ifdef CONFIG_MTK_SYSIRQ static void _mt_set_pol_reg(void __iomem *add, u32 val) { writel_relaxed(val, add); } void _mt_irq_set_polarity(unsigned int hwirq, unsigned int polarity) { u32 offset, reg, value; void __iomem *base = INT_POL_CTL0; if (hwirq < 32) { pr_notice("Fail to set polarity of interrupt %d\n", hwirq); return; } offset = hwirq%32; reg = ((hwirq - 32)/32); /* if reg_len_pol0 != 0, means there is 2nd POL reg base, * compute the correct offset for polarity reg in 2nd POL reg */ if ((reg_len_pol0 != 0) && (reg >= reg_len_pol0)) { if (!INT_POL_CTL1) { pr_notice("MUST have 2nd INT_POL_CTRL\n"); /* is a bug */ WARN_ON(1); return; } reg -= reg_len_pol0; base = INT_POL_CTL1; } value = readl_relaxed(IOMEM(base + reg*4)); if (polarity == 0) { /* active low */ value |= (1 << offset); } else { /* active high */ value &= ~(0x1 << offset); } /* some platforms has to write POL register in secure world */ _mt_set_pol_reg(base + reg*4, value); } #endif #ifdef CONFIG_MACH_MT6779 #define GIC_INT_MASK (MCUSYS_BASE_SWMODE + 0xa6f0) #define GIC500_ACTIVE_SEL_SHIFT 16 #define GIC500_ACTIVE_SEL_MASK (0x7 << GIC500_ACTIVE_SEL_SHIFT) #define GIC500_ACTIVE_CPU_SHIFT 0 #define GIC500_ACTIVE_CPU_MASK (0xff << GIC500_ACTIVE_CPU_SHIFT) #elif defined(CONFIG_MACH_MT6885) || defined(CONFIG_MACH_MT6873) || \ defined(CONFIG_MACH_MT6853) || defined(CONFIG_MACH_MT6893) ||\ defined(CONFIG_MACH_MT6877) || defined(CONFIG_MACH_MT6781) #define GIC_INT_MASK (MCUSYS_BASE_SWMODE + 0xaa88) #define GIC500_ACTIVE_CPU_SHIFT 0 #define GIC500_ACTIVE_CPU_MASK (0xff << GIC500_ACTIVE_CPU_SHIFT) #else #define GIC_INT_MASK (MCUSYS_BASE_SWMODE + 0x5e8) #define GIC500_ACTIVE_SEL_SHIFT 3 #define GIC500_ACTIVE_SEL_MASK (0x7 << GIC500_ACTIVE_SEL_SHIFT) #define GIC500_ACTIVE_CPU_SHIFT 16 #define GIC500_ACTIVE_CPU_MASK (0xff << GIC500_ACTIVE_CPU_SHIFT) #endif static spinlock_t domain_lock; int print_en; int add_cpu_to_prefer_schedule_domain(unsigned int cpu) { unsigned long domain; if (irq_sw_mode_support() != 1) return 0; if (!MCUSYS_BASE_SWMODE) return 0; spin_lock(&domain_lock); domain = ioread32(GIC_INT_MASK); domain = domain | (1 << (cpu + GIC500_ACTIVE_CPU_SHIFT)); iowrite32(domain, GIC_INT_MASK); spin_unlock(&domain_lock); return 0; } int remove_cpu_from_prefer_schedule_domain(unsigned int cpu) { unsigned long domain; if (irq_sw_mode_support() != 1) return 0; if (!MCUSYS_BASE_SWMODE) return 0; spin_lock(&domain_lock); domain = ioread32(GIC_INT_MASK); domain = domain & ~(1 << (cpu + GIC500_ACTIVE_CPU_SHIFT)); iowrite32(domain, GIC_INT_MASK); spin_unlock(&domain_lock); return 0; } #ifdef CONFIG_CPU_PM static int gic_sched_pm_notifier(struct notifier_block *self, unsigned long cmd, void *v) { unsigned int cur_cpu = smp_processor_id(); if (cmd == CPU_PM_EXIT) add_cpu_to_prefer_schedule_domain(cur_cpu); else if (cmd == CPU_PM_ENTER) remove_cpu_from_prefer_schedule_domain(cur_cpu); return NOTIFY_OK; } static struct notifier_block gic_sched_pm_notifier_block = { .notifier_call = gic_sched_pm_notifier, }; static void gic_sched_pm_init(void) { cpu_pm_register_notifier(&gic_sched_pm_notifier_block); } #else static inline void gic_cpu_pm_init(void) { } #endif /* CONFIG_CPU_PM */ void irq_sw_mode_init(void) { struct device_node *node; int ret; if (irq_sw_mode_support() != 1) { pr_notice("### IRQ SW mode not support ###\n"); return; } node = of_find_compatible_node(NULL, NULL, "mediatek,mcucfg"); if (node) MCUSYS_BASE_SWMODE = of_iomap(node, 0); else pr_info("[gic_ext] fail to find mcucfg node\n"); spin_lock_init(&domain_lock); gic_sched_pm_init(); ret = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN, "irq_sw_mode:online", add_cpu_to_prefer_schedule_domain, remove_cpu_from_prefer_schedule_domain); WARN_ON(ret < 0); } int __init mt_gic_ext_init(void) { struct device_node *node; node = of_find_compatible_node(NULL, NULL, "arm,gic-v3"); if (!node) { pr_notice("[gic_ext] find arm,gic-v3 node failed\n"); return -EINVAL; } GIC_DIST_BASE = of_iomap(node, 0); if (IS_ERR(GIC_DIST_BASE)) return -EINVAL; GIC_REDIST_BASE = of_iomap(node, 1); if (IS_ERR(GIC_REDIST_BASE)) return -EINVAL; #ifdef CONFIG_MTK_SYSIRQ INT_POL_CTL0 = of_iomap(node, 2); if (IS_ERR(INT_POL_CTL0)) return -EINVAL; /* if INT_POL_CTL1 get NULL, * only means no extra polarity register, * INT_POL_CTL0 is enough */ INT_POL_CTL1 = of_iomap(node, 3); if (of_property_read_u32(node, "mediatek,reg_len_pol0", ®_len_pol0)) reg_len_pol0 = 0; #endif irq_sw_mode_init(); pr_notice("### gic-v3 init done. ###\n"); return 0; } MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("MediaTek gicv3 extend Driver");