Blame view
drivers/irqchip/exynos-combiner.c
6.88 KB
a900e5d99
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/* * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. * http://www.samsung.com * * Combiner irqchip for EXYNOS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include <linux/err.h> #include <linux/export.h> #include <linux/init.h> #include <linux/io.h> |
d34f03d4a
|
15 |
#include <linux/slab.h> |
6fd4899a5
|
16 |
#include <linux/syscore_ops.h> |
a900e5d99
|
17 |
#include <linux/irqdomain.h> |
41a83e06e
|
18 |
#include <linux/irqchip.h> |
de88cbb7b
|
19 |
#include <linux/irqchip/chained_irq.h> |
bc64690e3
|
20 |
#include <linux/interrupt.h> |
a900e5d99
|
21 22 |
#include <linux/of_address.h> #include <linux/of_irq.h> |
a900e5d99
|
23 |
|
a900e5d99
|
24 25 26 |
#define COMBINER_ENABLE_SET 0x0 #define COMBINER_ENABLE_CLEAR 0x4 #define COMBINER_INT_STATUS 0xC |
6761dcfe8
|
27 |
#define IRQ_IN_COMBINER 8 |
a900e5d99
|
28 29 30 |
static DEFINE_SPINLOCK(irq_controller_lock); struct combiner_chip_data { |
20adee8fa
|
31 |
unsigned int hwirq_offset; |
a900e5d99
|
32 33 |
unsigned int irq_mask; void __iomem *base; |
df7ef462a
|
34 |
unsigned int parent_irq; |
6fd4899a5
|
35 36 37 |
#ifdef CONFIG_PM u32 pm_save; #endif |
a900e5d99
|
38 |
}; |
6fd4899a5
|
39 |
static struct combiner_chip_data *combiner_data; |
a900e5d99
|
40 |
static struct irq_domain *combiner_irq_domain; |
6fd4899a5
|
41 |
static unsigned int max_nr = 20; |
a900e5d99
|
42 43 44 45 46 47 48 49 50 51 52 53 |
static inline void __iomem *combiner_base(struct irq_data *data) { struct combiner_chip_data *combiner_data = irq_data_get_irq_chip_data(data); return combiner_data->base; } static void combiner_mask_irq(struct irq_data *data) { u32 mask = 1 << (data->hwirq % 32); |
2a4fe14bc
|
54 |
writel_relaxed(mask, combiner_base(data) + COMBINER_ENABLE_CLEAR); |
a900e5d99
|
55 56 57 58 59 |
} static void combiner_unmask_irq(struct irq_data *data) { u32 mask = 1 << (data->hwirq % 32); |
2a4fe14bc
|
60 |
writel_relaxed(mask, combiner_base(data) + COMBINER_ENABLE_SET); |
a900e5d99
|
61 |
} |
bd0b9ac40
|
62 |
static void combiner_handle_cascade_irq(struct irq_desc *desc) |
a900e5d99
|
63 |
{ |
5b29264c6
|
64 65 |
struct combiner_chip_data *chip_data = irq_desc_get_handler_data(desc); struct irq_chip *chip = irq_desc_get_chip(desc); |
a900e5d99
|
66 67 68 69 70 71 |
unsigned int cascade_irq, combiner_irq; unsigned long status; chained_irq_enter(chip, desc); spin_lock(&irq_controller_lock); |
2a4fe14bc
|
72 |
status = readl_relaxed(chip_data->base + COMBINER_INT_STATUS); |
a900e5d99
|
73 74 75 76 77 |
spin_unlock(&irq_controller_lock); status &= chip_data->irq_mask; if (status == 0) goto out; |
20adee8fa
|
78 79 |
combiner_irq = chip_data->hwirq_offset + __ffs(status); cascade_irq = irq_find_mapping(combiner_irq_domain, combiner_irq); |
a900e5d99
|
80 |
|
20adee8fa
|
81 |
if (unlikely(!cascade_irq)) |
bd0b9ac40
|
82 |
handle_bad_irq(desc); |
a900e5d99
|
83 84 85 86 87 88 |
else generic_handle_irq(cascade_irq); out: chained_irq_exit(chip, desc); } |
df7ef462a
|
89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
#ifdef CONFIG_SMP static int combiner_set_affinity(struct irq_data *d, const struct cpumask *mask_val, bool force) { struct combiner_chip_data *chip_data = irq_data_get_irq_chip_data(d); struct irq_chip *chip = irq_get_chip(chip_data->parent_irq); struct irq_data *data = irq_get_irq_data(chip_data->parent_irq); if (chip && chip->irq_set_affinity) return chip->irq_set_affinity(data, mask_val, force); else return -EINVAL; } #endif |
a900e5d99
|
103 |
static struct irq_chip combiner_chip = { |
df7ef462a
|
104 105 106 107 108 109 |
.name = "COMBINER", .irq_mask = combiner_mask_irq, .irq_unmask = combiner_unmask_irq, #ifdef CONFIG_SMP .irq_set_affinity = combiner_set_affinity, #endif |
a900e5d99
|
110 |
}; |
d34f03d4a
|
111 |
static void __init combiner_cascade_irq(struct combiner_chip_data *combiner_data, |
4e164dc5f
|
112 113 |
unsigned int irq) { |
741ff9661
|
114 115 |
irq_set_chained_handler_and_data(irq, combiner_handle_cascade_irq, combiner_data); |
a900e5d99
|
116 |
} |
d34f03d4a
|
117 118 |
static void __init combiner_init_one(struct combiner_chip_data *combiner_data, unsigned int combiner_nr, |
df7ef462a
|
119 |
void __iomem *base, unsigned int irq) |
a900e5d99
|
120 |
{ |
d34f03d4a
|
121 |
combiner_data->base = base; |
20adee8fa
|
122 |
combiner_data->hwirq_offset = (combiner_nr & ~3) * IRQ_IN_COMBINER; |
d34f03d4a
|
123 124 |
combiner_data->irq_mask = 0xff << ((combiner_nr % 4) << 3); combiner_data->parent_irq = irq; |
a900e5d99
|
125 126 |
/* Disable all interrupts */ |
2a4fe14bc
|
127 |
writel_relaxed(combiner_data->irq_mask, base + COMBINER_ENABLE_CLEAR); |
a900e5d99
|
128 |
} |
a900e5d99
|
129 130 131 132 133 134 |
static int combiner_irq_domain_xlate(struct irq_domain *d, struct device_node *controller, const u32 *intspec, unsigned int intsize, unsigned long *out_hwirq, unsigned int *out_type) { |
5d4c9bc77
|
135 |
if (irq_domain_get_of_node(d) != controller) |
a900e5d99
|
136 137 138 139 |
return -EINVAL; if (intsize < 2) return -EINVAL; |
6761dcfe8
|
140 |
*out_hwirq = intspec[0] * IRQ_IN_COMBINER + intspec[1]; |
a900e5d99
|
141 142 143 144 |
*out_type = 0; return 0; } |
a900e5d99
|
145 146 147 148 |
static int combiner_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw) { |
d34f03d4a
|
149 |
struct combiner_chip_data *combiner_data = d->host_data; |
a900e5d99
|
150 151 |
irq_set_chip_and_handler(irq, &combiner_chip, handle_level_irq); irq_set_chip_data(irq, &combiner_data[hw >> 3]); |
d17cab445
|
152 |
irq_set_probe(irq); |
a900e5d99
|
153 154 155 |
return 0; } |
960097365
|
156 |
static const struct irq_domain_ops combiner_irq_domain_ops = { |
a900e5d99
|
157 158 159 |
.xlate = combiner_irq_domain_xlate, .map = combiner_irq_domain_map, }; |
b8394dee7
|
160 |
static void __init combiner_init(void __iomem *combiner_base, |
6fd4899a5
|
161 |
struct device_node *np) |
a900e5d99
|
162 |
{ |
863a08dc8
|
163 |
int i, irq; |
6761dcfe8
|
164 |
unsigned int nr_irq; |
a900e5d99
|
165 |
|
6761dcfe8
|
166 |
nr_irq = max_nr * IRQ_IN_COMBINER; |
4e164dc5f
|
167 |
|
d34f03d4a
|
168 169 |
combiner_data = kcalloc(max_nr, sizeof (*combiner_data), GFP_KERNEL); if (!combiner_data) { |
faca10b9e
|
170 171 |
pr_warn("%s: could not allocate combiner data ", __func__); |
d34f03d4a
|
172 |
return; |
a900e5d99
|
173 |
} |
9403ac882
|
174 |
combiner_irq_domain = irq_domain_add_linear(np, nr_irq, |
d34f03d4a
|
175 |
&combiner_irq_domain_ops, combiner_data); |
a900e5d99
|
176 |
if (WARN_ON(!combiner_irq_domain)) { |
faca10b9e
|
177 178 |
pr_warn("%s: irq domain init failed ", __func__); |
a900e5d99
|
179 180 181 182 |
return; } for (i = 0; i < max_nr; i++) { |
0f5615117
|
183 |
irq = irq_of_parse_and_map(np, i); |
92c8e4962
|
184 |
|
d34f03d4a
|
185 186 187 |
combiner_init_one(&combiner_data[i], i, combiner_base + (i >> 2) * 0x10, irq); combiner_cascade_irq(&combiner_data[i], irq); |
a900e5d99
|
188 189 |
} } |
6fd4899a5
|
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
#ifdef CONFIG_PM /** * combiner_suspend - save interrupt combiner state before suspend * * Save the interrupt enable set register for all combiner groups since * the state is lost when the system enters into a sleep state. * */ static int combiner_suspend(void) { int i; for (i = 0; i < max_nr; i++) combiner_data[i].pm_save = |
2a4fe14bc
|
205 |
readl_relaxed(combiner_data[i].base + COMBINER_ENABLE_SET); |
6fd4899a5
|
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 |
return 0; } /** * combiner_resume - restore interrupt combiner state after resume * * Restore the interrupt enable set register for all combiner groups since * the state is lost when the system enters into a sleep state on suspend. * */ static void combiner_resume(void) { int i; for (i = 0; i < max_nr; i++) { |
2a4fe14bc
|
222 |
writel_relaxed(combiner_data[i].irq_mask, |
6fd4899a5
|
223 |
combiner_data[i].base + COMBINER_ENABLE_CLEAR); |
2a4fe14bc
|
224 |
writel_relaxed(combiner_data[i].pm_save, |
6fd4899a5
|
225 226 227 228 229 230 231 232 233 234 235 236 237 |
combiner_data[i].base + COMBINER_ENABLE_SET); } } #else #define combiner_suspend NULL #define combiner_resume NULL #endif static struct syscore_ops combiner_syscore_ops = { .suspend = combiner_suspend, .resume = combiner_resume, }; |
a900e5d99
|
238 239 240 241 242 243 244 245 246 247 248 |
static int __init combiner_of_init(struct device_node *np, struct device_node *parent) { void __iomem *combiner_base; combiner_base = of_iomap(np, 0); if (!combiner_base) { pr_err("%s: failed to map combiner registers ", __func__); return -ENXIO; } |
6761dcfe8
|
249 250 251 252 253 254 |
if (of_property_read_u32(np, "samsung,combiner-nr", &max_nr)) { pr_info("%s: number of combiners not specified, " "setting default as %d. ", __func__, max_nr); } |
6fd4899a5
|
255 256 257 |
combiner_init(combiner_base, np); register_syscore_ops(&combiner_syscore_ops); |
a900e5d99
|
258 259 260 261 262 |
return 0; } IRQCHIP_DECLARE(exynos4210_combiner, "samsung,exynos4210-combiner", combiner_of_init); |