1a900e5d9SRob Herring /* 2a900e5d9SRob Herring * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. 3a900e5d9SRob Herring * http://www.samsung.com 4a900e5d9SRob Herring * 5a900e5d9SRob Herring * Combiner irqchip for EXYNOS 6a900e5d9SRob Herring * 7a900e5d9SRob Herring * This program is free software; you can redistribute it and/or modify 8a900e5d9SRob Herring * it under the terms of the GNU General Public License version 2 as 9a900e5d9SRob Herring * published by the Free Software Foundation. 10a900e5d9SRob Herring */ 11a900e5d9SRob Herring #include <linux/err.h> 12a900e5d9SRob Herring #include <linux/export.h> 13a900e5d9SRob Herring #include <linux/init.h> 14a900e5d9SRob Herring #include <linux/io.h> 15d34f03d4SArnd Bergmann #include <linux/slab.h> 166fd4899aSJavier Martinez Canillas #include <linux/syscore_ops.h> 17a900e5d9SRob Herring #include <linux/irqdomain.h> 1841a83e06SJoel Porquet #include <linux/irqchip.h> 19de88cbb7SCatalin Marinas #include <linux/irqchip/chained_irq.h> 20bc64690eSNaveen Krishna Chatradhi #include <linux/interrupt.h> 21a900e5d9SRob Herring #include <linux/of_address.h> 22a900e5d9SRob Herring #include <linux/of_irq.h> 23a900e5d9SRob Herring 24a900e5d9SRob Herring #define COMBINER_ENABLE_SET 0x0 25a900e5d9SRob Herring #define COMBINER_ENABLE_CLEAR 0x4 26a900e5d9SRob Herring #define COMBINER_INT_STATUS 0xC 27a900e5d9SRob Herring 286761dcfeSArnd Bergmann #define IRQ_IN_COMBINER 8 296761dcfeSArnd Bergmann 30a900e5d9SRob Herring static DEFINE_SPINLOCK(irq_controller_lock); 31a900e5d9SRob Herring 32a900e5d9SRob Herring struct combiner_chip_data { 3320adee8fSArnd Bergmann unsigned int hwirq_offset; 34a900e5d9SRob Herring unsigned int irq_mask; 35a900e5d9SRob Herring void __iomem *base; 36df7ef462SChanho Park unsigned int parent_irq; 376fd4899aSJavier Martinez Canillas #ifdef CONFIG_PM 386fd4899aSJavier Martinez Canillas u32 pm_save; 396fd4899aSJavier Martinez Canillas #endif 40a900e5d9SRob Herring }; 41a900e5d9SRob Herring 426fd4899aSJavier Martinez Canillas static struct combiner_chip_data *combiner_data; 43a900e5d9SRob Herring static struct irq_domain *combiner_irq_domain; 446fd4899aSJavier Martinez Canillas static unsigned int max_nr = 20; 45a900e5d9SRob Herring 46a900e5d9SRob Herring static inline void __iomem *combiner_base(struct irq_data *data) 47a900e5d9SRob Herring { 48a900e5d9SRob Herring struct combiner_chip_data *combiner_data = 49a900e5d9SRob Herring irq_data_get_irq_chip_data(data); 50a900e5d9SRob Herring 51a900e5d9SRob Herring return combiner_data->base; 52a900e5d9SRob Herring } 53a900e5d9SRob Herring 54a900e5d9SRob Herring static void combiner_mask_irq(struct irq_data *data) 55a900e5d9SRob Herring { 56a900e5d9SRob Herring u32 mask = 1 << (data->hwirq % 32); 57a900e5d9SRob Herring 58a900e5d9SRob Herring __raw_writel(mask, combiner_base(data) + COMBINER_ENABLE_CLEAR); 59a900e5d9SRob Herring } 60a900e5d9SRob Herring 61a900e5d9SRob Herring static void combiner_unmask_irq(struct irq_data *data) 62a900e5d9SRob Herring { 63a900e5d9SRob Herring u32 mask = 1 << (data->hwirq % 32); 64a900e5d9SRob Herring 65a900e5d9SRob Herring __raw_writel(mask, combiner_base(data) + COMBINER_ENABLE_SET); 66a900e5d9SRob Herring } 67a900e5d9SRob Herring 68a900e5d9SRob Herring static void combiner_handle_cascade_irq(unsigned int irq, struct irq_desc *desc) 69a900e5d9SRob Herring { 705b29264cSJiang Liu struct combiner_chip_data *chip_data = irq_desc_get_handler_data(desc); 715b29264cSJiang Liu struct irq_chip *chip = irq_desc_get_chip(desc); 72a900e5d9SRob Herring unsigned int cascade_irq, combiner_irq; 73a900e5d9SRob Herring unsigned long status; 74a900e5d9SRob Herring 75a900e5d9SRob Herring chained_irq_enter(chip, desc); 76a900e5d9SRob Herring 77a900e5d9SRob Herring spin_lock(&irq_controller_lock); 78a900e5d9SRob Herring status = __raw_readl(chip_data->base + COMBINER_INT_STATUS); 79a900e5d9SRob Herring spin_unlock(&irq_controller_lock); 80a900e5d9SRob Herring status &= chip_data->irq_mask; 81a900e5d9SRob Herring 82a900e5d9SRob Herring if (status == 0) 83a900e5d9SRob Herring goto out; 84a900e5d9SRob Herring 8520adee8fSArnd Bergmann combiner_irq = chip_data->hwirq_offset + __ffs(status); 8620adee8fSArnd Bergmann cascade_irq = irq_find_mapping(combiner_irq_domain, combiner_irq); 87a900e5d9SRob Herring 8820adee8fSArnd Bergmann if (unlikely(!cascade_irq)) 89a8378485SPankaj Dubey handle_bad_irq(irq, desc); 90a900e5d9SRob Herring else 91a900e5d9SRob Herring generic_handle_irq(cascade_irq); 92a900e5d9SRob Herring 93a900e5d9SRob Herring out: 94a900e5d9SRob Herring chained_irq_exit(chip, desc); 95a900e5d9SRob Herring } 96a900e5d9SRob Herring 97df7ef462SChanho Park #ifdef CONFIG_SMP 98df7ef462SChanho Park static int combiner_set_affinity(struct irq_data *d, 99df7ef462SChanho Park const struct cpumask *mask_val, bool force) 100df7ef462SChanho Park { 101df7ef462SChanho Park struct combiner_chip_data *chip_data = irq_data_get_irq_chip_data(d); 102df7ef462SChanho Park struct irq_chip *chip = irq_get_chip(chip_data->parent_irq); 103df7ef462SChanho Park struct irq_data *data = irq_get_irq_data(chip_data->parent_irq); 104df7ef462SChanho Park 105df7ef462SChanho Park if (chip && chip->irq_set_affinity) 106df7ef462SChanho Park return chip->irq_set_affinity(data, mask_val, force); 107df7ef462SChanho Park else 108df7ef462SChanho Park return -EINVAL; 109df7ef462SChanho Park } 110df7ef462SChanho Park #endif 111df7ef462SChanho Park 112a900e5d9SRob Herring static struct irq_chip combiner_chip = { 113a900e5d9SRob Herring .name = "COMBINER", 114a900e5d9SRob Herring .irq_mask = combiner_mask_irq, 115a900e5d9SRob Herring .irq_unmask = combiner_unmask_irq, 116df7ef462SChanho Park #ifdef CONFIG_SMP 117df7ef462SChanho Park .irq_set_affinity = combiner_set_affinity, 118df7ef462SChanho Park #endif 119a900e5d9SRob Herring }; 120a900e5d9SRob Herring 121d34f03d4SArnd Bergmann static void __init combiner_cascade_irq(struct combiner_chip_data *combiner_data, 1224e164dc5SChanho Park unsigned int irq) 1234e164dc5SChanho Park { 124741ff966SThomas Gleixner irq_set_chained_handler_and_data(irq, combiner_handle_cascade_irq, 125741ff966SThomas Gleixner combiner_data); 126a900e5d9SRob Herring } 127a900e5d9SRob Herring 128d34f03d4SArnd Bergmann static void __init combiner_init_one(struct combiner_chip_data *combiner_data, 129d34f03d4SArnd Bergmann unsigned int combiner_nr, 130df7ef462SChanho Park void __iomem *base, unsigned int irq) 131a900e5d9SRob Herring { 132d34f03d4SArnd Bergmann combiner_data->base = base; 13320adee8fSArnd Bergmann combiner_data->hwirq_offset = (combiner_nr & ~3) * IRQ_IN_COMBINER; 134d34f03d4SArnd Bergmann combiner_data->irq_mask = 0xff << ((combiner_nr % 4) << 3); 135d34f03d4SArnd Bergmann combiner_data->parent_irq = irq; 136a900e5d9SRob Herring 137a900e5d9SRob Herring /* Disable all interrupts */ 138d34f03d4SArnd Bergmann __raw_writel(combiner_data->irq_mask, base + COMBINER_ENABLE_CLEAR); 139a900e5d9SRob Herring } 140a900e5d9SRob Herring 141a900e5d9SRob Herring static int combiner_irq_domain_xlate(struct irq_domain *d, 142a900e5d9SRob Herring struct device_node *controller, 143a900e5d9SRob Herring const u32 *intspec, unsigned int intsize, 144a900e5d9SRob Herring unsigned long *out_hwirq, 145a900e5d9SRob Herring unsigned int *out_type) 146a900e5d9SRob Herring { 147a900e5d9SRob Herring if (d->of_node != controller) 148a900e5d9SRob Herring return -EINVAL; 149a900e5d9SRob Herring 150a900e5d9SRob Herring if (intsize < 2) 151a900e5d9SRob Herring return -EINVAL; 152a900e5d9SRob Herring 1536761dcfeSArnd Bergmann *out_hwirq = intspec[0] * IRQ_IN_COMBINER + intspec[1]; 154a900e5d9SRob Herring *out_type = 0; 155a900e5d9SRob Herring 156a900e5d9SRob Herring return 0; 157a900e5d9SRob Herring } 158a900e5d9SRob Herring 159a900e5d9SRob Herring static int combiner_irq_domain_map(struct irq_domain *d, unsigned int irq, 160a900e5d9SRob Herring irq_hw_number_t hw) 161a900e5d9SRob Herring { 162d34f03d4SArnd Bergmann struct combiner_chip_data *combiner_data = d->host_data; 163d34f03d4SArnd Bergmann 164a900e5d9SRob Herring irq_set_chip_and_handler(irq, &combiner_chip, handle_level_irq); 165a900e5d9SRob Herring irq_set_chip_data(irq, &combiner_data[hw >> 3]); 166a900e5d9SRob Herring set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); 167a900e5d9SRob Herring 168a900e5d9SRob Herring return 0; 169a900e5d9SRob Herring } 170a900e5d9SRob Herring 17196009736SKrzysztof Kozlowski static const struct irq_domain_ops combiner_irq_domain_ops = { 172a900e5d9SRob Herring .xlate = combiner_irq_domain_xlate, 173a900e5d9SRob Herring .map = combiner_irq_domain_map, 174a900e5d9SRob Herring }; 175a900e5d9SRob Herring 176b8394deeSSachin Kamat static void __init combiner_init(void __iomem *combiner_base, 1776fd4899aSJavier Martinez Canillas struct device_node *np) 178a900e5d9SRob Herring { 179863a08dcSArnd Bergmann int i, irq; 1806761dcfeSArnd Bergmann unsigned int nr_irq; 181a900e5d9SRob Herring 1826761dcfeSArnd Bergmann nr_irq = max_nr * IRQ_IN_COMBINER; 1834e164dc5SChanho Park 184d34f03d4SArnd Bergmann combiner_data = kcalloc(max_nr, sizeof (*combiner_data), GFP_KERNEL); 185d34f03d4SArnd Bergmann if (!combiner_data) { 186d34f03d4SArnd Bergmann pr_warning("%s: could not allocate combiner data\n", __func__); 187d34f03d4SArnd Bergmann return; 188a900e5d9SRob Herring } 1894e164dc5SChanho Park 1909403ac88SChander Kashyap combiner_irq_domain = irq_domain_add_linear(np, nr_irq, 191d34f03d4SArnd Bergmann &combiner_irq_domain_ops, combiner_data); 192a900e5d9SRob Herring if (WARN_ON(!combiner_irq_domain)) { 193a900e5d9SRob Herring pr_warning("%s: irq domain init failed\n", __func__); 194a900e5d9SRob Herring return; 195a900e5d9SRob Herring } 196a900e5d9SRob Herring 197a900e5d9SRob Herring for (i = 0; i < max_nr; i++) { 198a900e5d9SRob Herring irq = irq_of_parse_and_map(np, i); 19992c8e496SArnd Bergmann 200d34f03d4SArnd Bergmann combiner_init_one(&combiner_data[i], i, 201d34f03d4SArnd Bergmann combiner_base + (i >> 2) * 0x10, irq); 202d34f03d4SArnd Bergmann combiner_cascade_irq(&combiner_data[i], irq); 203a900e5d9SRob Herring } 204a900e5d9SRob Herring } 205a900e5d9SRob Herring 2066fd4899aSJavier Martinez Canillas #ifdef CONFIG_PM 2076fd4899aSJavier Martinez Canillas 2086fd4899aSJavier Martinez Canillas /** 2096fd4899aSJavier Martinez Canillas * combiner_suspend - save interrupt combiner state before suspend 2106fd4899aSJavier Martinez Canillas * 2116fd4899aSJavier Martinez Canillas * Save the interrupt enable set register for all combiner groups since 2126fd4899aSJavier Martinez Canillas * the state is lost when the system enters into a sleep state. 2136fd4899aSJavier Martinez Canillas * 2146fd4899aSJavier Martinez Canillas */ 2156fd4899aSJavier Martinez Canillas static int combiner_suspend(void) 2166fd4899aSJavier Martinez Canillas { 2176fd4899aSJavier Martinez Canillas int i; 2186fd4899aSJavier Martinez Canillas 2196fd4899aSJavier Martinez Canillas for (i = 0; i < max_nr; i++) 2206fd4899aSJavier Martinez Canillas combiner_data[i].pm_save = 2216fd4899aSJavier Martinez Canillas __raw_readl(combiner_data[i].base + COMBINER_ENABLE_SET); 2226fd4899aSJavier Martinez Canillas 2236fd4899aSJavier Martinez Canillas return 0; 2246fd4899aSJavier Martinez Canillas } 2256fd4899aSJavier Martinez Canillas 2266fd4899aSJavier Martinez Canillas /** 2276fd4899aSJavier Martinez Canillas * combiner_resume - restore interrupt combiner state after resume 2286fd4899aSJavier Martinez Canillas * 2296fd4899aSJavier Martinez Canillas * Restore the interrupt enable set register for all combiner groups since 2306fd4899aSJavier Martinez Canillas * the state is lost when the system enters into a sleep state on suspend. 2316fd4899aSJavier Martinez Canillas * 2326fd4899aSJavier Martinez Canillas */ 2336fd4899aSJavier Martinez Canillas static void combiner_resume(void) 2346fd4899aSJavier Martinez Canillas { 2356fd4899aSJavier Martinez Canillas int i; 2366fd4899aSJavier Martinez Canillas 2376fd4899aSJavier Martinez Canillas for (i = 0; i < max_nr; i++) { 2386fd4899aSJavier Martinez Canillas __raw_writel(combiner_data[i].irq_mask, 2396fd4899aSJavier Martinez Canillas combiner_data[i].base + COMBINER_ENABLE_CLEAR); 2406fd4899aSJavier Martinez Canillas __raw_writel(combiner_data[i].pm_save, 2416fd4899aSJavier Martinez Canillas combiner_data[i].base + COMBINER_ENABLE_SET); 2426fd4899aSJavier Martinez Canillas } 2436fd4899aSJavier Martinez Canillas } 2446fd4899aSJavier Martinez Canillas 2456fd4899aSJavier Martinez Canillas #else 2466fd4899aSJavier Martinez Canillas #define combiner_suspend NULL 2476fd4899aSJavier Martinez Canillas #define combiner_resume NULL 2486fd4899aSJavier Martinez Canillas #endif 2496fd4899aSJavier Martinez Canillas 2506fd4899aSJavier Martinez Canillas static struct syscore_ops combiner_syscore_ops = { 2516fd4899aSJavier Martinez Canillas .suspend = combiner_suspend, 2526fd4899aSJavier Martinez Canillas .resume = combiner_resume, 2536fd4899aSJavier Martinez Canillas }; 2546fd4899aSJavier Martinez Canillas 255a900e5d9SRob Herring static int __init combiner_of_init(struct device_node *np, 256a900e5d9SRob Herring struct device_node *parent) 257a900e5d9SRob Herring { 258a900e5d9SRob Herring void __iomem *combiner_base; 259a900e5d9SRob Herring 260a900e5d9SRob Herring combiner_base = of_iomap(np, 0); 261a900e5d9SRob Herring if (!combiner_base) { 262a900e5d9SRob Herring pr_err("%s: failed to map combiner registers\n", __func__); 263a900e5d9SRob Herring return -ENXIO; 264a900e5d9SRob Herring } 265a900e5d9SRob Herring 2666761dcfeSArnd Bergmann if (of_property_read_u32(np, "samsung,combiner-nr", &max_nr)) { 2676761dcfeSArnd Bergmann pr_info("%s: number of combiners not specified, " 2686761dcfeSArnd Bergmann "setting default as %d.\n", 2696761dcfeSArnd Bergmann __func__, max_nr); 2706761dcfeSArnd Bergmann } 2716761dcfeSArnd Bergmann 2726fd4899aSJavier Martinez Canillas combiner_init(combiner_base, np); 2736fd4899aSJavier Martinez Canillas 2746fd4899aSJavier Martinez Canillas register_syscore_ops(&combiner_syscore_ops); 275a900e5d9SRob Herring 276a900e5d9SRob Herring return 0; 277a900e5d9SRob Herring } 278a900e5d9SRob Herring IRQCHIP_DECLARE(exynos4210_combiner, "samsung,exynos4210-combiner", 279a900e5d9SRob Herring combiner_of_init); 280