diff options
-rw-r--r-- | drivers/irqchip/Kconfig | 9 | ||||
-rw-r--r-- | drivers/irqchip/Makefile | 1 | ||||
-rw-r--r-- | drivers/irqchip/qcom-irq-combiner.c | 296 |
3 files changed, 306 insertions, 0 deletions
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index ae96731cd2fb..125528f39e92 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig | |||
@@ -283,3 +283,12 @@ config EZNPS_GIC | |||
283 | config STM32_EXTI | 283 | config STM32_EXTI |
284 | bool | 284 | bool |
285 | select IRQ_DOMAIN | 285 | select IRQ_DOMAIN |
286 | |||
287 | config QCOM_IRQ_COMBINER | ||
288 | bool "QCOM IRQ combiner support" | ||
289 | depends on ARCH_QCOM && ACPI | ||
290 | select IRQ_DOMAIN | ||
291 | select IRQ_DOMAIN_HIERARCHY | ||
292 | help | ||
293 | Say yes here to add support for the IRQ combiner devices embedded | ||
294 | in Qualcomm Technologies chips. | ||
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 0e55d94065bf..5a53186351ef 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile | |||
@@ -75,3 +75,4 @@ obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o | |||
75 | obj-$(CONFIG_EZNPS_GIC) += irq-eznps.o | 75 | obj-$(CONFIG_EZNPS_GIC) += irq-eznps.o |
76 | obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o | 76 | obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o |
77 | obj-$(CONFIG_STM32_EXTI) += irq-stm32-exti.o | 77 | obj-$(CONFIG_STM32_EXTI) += irq-stm32-exti.o |
78 | obj-$(CONFIG_QCOM_IRQ_COMBINER) += qcom-irq-combiner.o | ||
diff --git a/drivers/irqchip/qcom-irq-combiner.c b/drivers/irqchip/qcom-irq-combiner.c new file mode 100644 index 000000000000..03251da95397 --- /dev/null +++ b/drivers/irqchip/qcom-irq-combiner.c | |||
@@ -0,0 +1,296 @@ | |||
1 | /* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. | ||
2 | * | ||
3 | * This program is free software; you can redistribute it and/or modify | ||
4 | * it under the terms of the GNU General Public License version 2 and | ||
5 | * only version 2 as published by the Free Software Foundation. | ||
6 | * | ||
7 | * This program is distributed in the hope that it will be useful, | ||
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
10 | * GNU General Public License for more details. | ||
11 | */ | ||
12 | |||
13 | /* | ||
14 | * Driver for interrupt combiners in the Top-level Control and Status | ||
15 | * Registers (TCSR) hardware block in Qualcomm Technologies chips. | ||
16 | * An interrupt combiner in this block combines a set of interrupts by | ||
17 | * OR'ing the individual interrupt signals into a summary interrupt | ||
18 | * signal routed to a parent interrupt controller, and provides read- | ||
19 | * only, 32-bit registers to query the status of individual interrupts. | ||
20 | * The status bit for IRQ n is bit (n % 32) within register (n / 32) | ||
21 | * of the given combiner. Thus, each combiner can be described as a set | ||
22 | * of register offsets and the number of IRQs managed. | ||
23 | */ | ||
24 | |||
25 | #define pr_fmt(fmt) "QCOM80B1:" fmt | ||
26 | |||
27 | #include <linux/acpi.h> | ||
28 | #include <linux/irqchip/chained_irq.h> | ||
29 | #include <linux/irqdomain.h> | ||
30 | #include <linux/platform_device.h> | ||
31 | |||
32 | #define REG_SIZE 32 | ||
33 | |||
34 | struct combiner_reg { | ||
35 | void __iomem *addr; | ||
36 | unsigned long enabled; | ||
37 | }; | ||
38 | |||
39 | struct combiner { | ||
40 | struct irq_domain *domain; | ||
41 | int parent_irq; | ||
42 | u32 nirqs; | ||
43 | u32 nregs; | ||
44 | struct combiner_reg regs[0]; | ||
45 | }; | ||
46 | |||
47 | static inline int irq_nr(u32 reg, u32 bit) | ||
48 | { | ||
49 | return reg * REG_SIZE + bit; | ||
50 | } | ||
51 | |||
52 | /* | ||
53 | * Handler for the cascaded IRQ. | ||
54 | */ | ||
55 | static void combiner_handle_irq(struct irq_desc *desc) | ||
56 | { | ||
57 | struct combiner *combiner = irq_desc_get_handler_data(desc); | ||
58 | struct irq_chip *chip = irq_desc_get_chip(desc); | ||
59 | u32 reg; | ||
60 | |||
61 | chained_irq_enter(chip, desc); | ||
62 | |||
63 | for (reg = 0; reg < combiner->nregs; reg++) { | ||
64 | int virq; | ||
65 | int hwirq; | ||
66 | u32 bit; | ||
67 | u32 status; | ||
68 | |||
69 | bit = readl_relaxed(combiner->regs[reg].addr); | ||
70 | status = bit & combiner->regs[reg].enabled; | ||
71 | if (!status) | ||
72 | pr_warn_ratelimited("Unexpected IRQ on CPU%d: (%08x %08lx %p)\n", | ||
73 | smp_processor_id(), bit, | ||
74 | combiner->regs[reg].enabled, | ||
75 | combiner->regs[reg].addr); | ||
76 | |||
77 | while (status) { | ||
78 | bit = __ffs(status); | ||
79 | status &= ~(1 << bit); | ||
80 | hwirq = irq_nr(reg, bit); | ||
81 | virq = irq_find_mapping(combiner->domain, hwirq); | ||
82 | if (virq > 0) | ||
83 | generic_handle_irq(virq); | ||
84 | |||
85 | } | ||
86 | } | ||
87 | |||
88 | chained_irq_exit(chip, desc); | ||
89 | } | ||
90 | |||
91 | static void combiner_irq_chip_mask_irq(struct irq_data *data) | ||
92 | { | ||
93 | struct combiner *combiner = irq_data_get_irq_chip_data(data); | ||
94 | struct combiner_reg *reg = combiner->regs + data->hwirq / REG_SIZE; | ||
95 | |||
96 | clear_bit(data->hwirq % REG_SIZE, ®->enabled); | ||
97 | } | ||
98 | |||
99 | static void combiner_irq_chip_unmask_irq(struct irq_data *data) | ||
100 | { | ||
101 | struct combiner *combiner = irq_data_get_irq_chip_data(data); | ||
102 | struct combiner_reg *reg = combiner->regs + data->hwirq / REG_SIZE; | ||
103 | |||
104 | set_bit(data->hwirq % REG_SIZE, ®->enabled); | ||
105 | } | ||
106 | |||
107 | static struct irq_chip irq_chip = { | ||
108 | .irq_mask = combiner_irq_chip_mask_irq, | ||
109 | .irq_unmask = combiner_irq_chip_unmask_irq, | ||
110 | .name = "qcom-irq-combiner" | ||
111 | }; | ||
112 | |||
113 | static int combiner_irq_map(struct irq_domain *domain, unsigned int irq, | ||
114 | irq_hw_number_t hwirq) | ||
115 | { | ||
116 | irq_set_chip_and_handler(irq, &irq_chip, handle_level_irq); | ||
117 | irq_set_chip_data(irq, domain->host_data); | ||
118 | irq_set_noprobe(irq); | ||
119 | return 0; | ||
120 | } | ||
121 | |||
122 | static void combiner_irq_unmap(struct irq_domain *domain, unsigned int irq) | ||
123 | { | ||
124 | irq_domain_reset_irq_data(irq_get_irq_data(irq)); | ||
125 | } | ||
126 | |||
127 | static int combiner_irq_translate(struct irq_domain *d, struct irq_fwspec *fws, | ||
128 | unsigned long *hwirq, unsigned int *type) | ||
129 | { | ||
130 | struct combiner *combiner = d->host_data; | ||
131 | |||
132 | if (is_acpi_node(fws->fwnode)) { | ||
133 | if (WARN_ON((fws->param_count != 2) || | ||
134 | (fws->param[0] >= combiner->nirqs) || | ||
135 | (fws->param[1] & IORESOURCE_IRQ_LOWEDGE) || | ||
136 | (fws->param[1] & IORESOURCE_IRQ_HIGHEDGE))) | ||
137 | return -EINVAL; | ||
138 | |||
139 | *hwirq = fws->param[0]; | ||
140 | *type = fws->param[1]; | ||
141 | return 0; | ||
142 | } | ||
143 | |||
144 | return -EINVAL; | ||
145 | } | ||
146 | |||
147 | static const struct irq_domain_ops domain_ops = { | ||
148 | .map = combiner_irq_map, | ||
149 | .unmap = combiner_irq_unmap, | ||
150 | .translate = combiner_irq_translate | ||
151 | }; | ||
152 | |||
153 | static acpi_status count_registers_cb(struct acpi_resource *ares, void *context) | ||
154 | { | ||
155 | int *count = context; | ||
156 | |||
157 | if (ares->type == ACPI_RESOURCE_TYPE_GENERIC_REGISTER) | ||
158 | ++(*count); | ||
159 | return AE_OK; | ||
160 | } | ||
161 | |||
162 | static int count_registers(struct platform_device *pdev) | ||
163 | { | ||
164 | acpi_handle ahandle = ACPI_HANDLE(&pdev->dev); | ||
165 | acpi_status status; | ||
166 | int count = 0; | ||
167 | |||
168 | if (!acpi_has_method(ahandle, METHOD_NAME__CRS)) | ||
169 | return -EINVAL; | ||
170 | |||
171 | status = acpi_walk_resources(ahandle, METHOD_NAME__CRS, | ||
172 | count_registers_cb, &count); | ||
173 | if (ACPI_FAILURE(status)) | ||
174 | return -EINVAL; | ||
175 | return count; | ||
176 | } | ||
177 | |||
178 | struct get_registers_context { | ||
179 | struct device *dev; | ||
180 | struct combiner *combiner; | ||
181 | int err; | ||
182 | }; | ||
183 | |||
184 | static acpi_status get_registers_cb(struct acpi_resource *ares, void *context) | ||
185 | { | ||
186 | struct get_registers_context *ctx = context; | ||
187 | struct acpi_resource_generic_register *reg; | ||
188 | phys_addr_t paddr; | ||
189 | void __iomem *vaddr; | ||
190 | |||
191 | if (ares->type != ACPI_RESOURCE_TYPE_GENERIC_REGISTER) | ||
192 | return AE_OK; | ||
193 | |||
194 | reg = &ares->data.generic_reg; | ||
195 | paddr = reg->address; | ||
196 | if ((reg->space_id != ACPI_SPACE_MEM) || | ||
197 | (reg->bit_offset != 0) || | ||
198 | (reg->bit_width > REG_SIZE)) { | ||
199 | dev_err(ctx->dev, "Bad register resource @%pa\n", &paddr); | ||
200 | ctx->err = -EINVAL; | ||
201 | return AE_ERROR; | ||
202 | } | ||
203 | |||
204 | vaddr = devm_ioremap(ctx->dev, reg->address, REG_SIZE); | ||
205 | if (IS_ERR(vaddr)) { | ||
206 | dev_err(ctx->dev, "Can't map register @%pa\n", &paddr); | ||
207 | ctx->err = PTR_ERR(vaddr); | ||
208 | return AE_ERROR; | ||
209 | } | ||
210 | |||
211 | ctx->combiner->regs[ctx->combiner->nregs].addr = vaddr; | ||
212 | ctx->combiner->nirqs += reg->bit_width; | ||
213 | ctx->combiner->nregs++; | ||
214 | return AE_OK; | ||
215 | } | ||
216 | |||
217 | static int get_registers(struct platform_device *pdev, struct combiner *comb) | ||
218 | { | ||
219 | acpi_handle ahandle = ACPI_HANDLE(&pdev->dev); | ||
220 | acpi_status status; | ||
221 | struct get_registers_context ctx; | ||
222 | |||
223 | if (!acpi_has_method(ahandle, METHOD_NAME__CRS)) | ||
224 | return -EINVAL; | ||
225 | |||
226 | ctx.dev = &pdev->dev; | ||
227 | ctx.combiner = comb; | ||
228 | ctx.err = 0; | ||
229 | |||
230 | status = acpi_walk_resources(ahandle, METHOD_NAME__CRS, | ||
231 | get_registers_cb, &ctx); | ||
232 | if (ACPI_FAILURE(status)) | ||
233 | return ctx.err; | ||
234 | return 0; | ||
235 | } | ||
236 | |||
237 | static int __init combiner_probe(struct platform_device *pdev) | ||
238 | { | ||
239 | struct combiner *combiner; | ||
240 | size_t alloc_sz; | ||
241 | u32 nregs; | ||
242 | int err; | ||
243 | |||
244 | nregs = count_registers(pdev); | ||
245 | if (nregs <= 0) { | ||
246 | dev_err(&pdev->dev, "Error reading register resources\n"); | ||
247 | return -EINVAL; | ||
248 | } | ||
249 | |||
250 | alloc_sz = sizeof(*combiner) + sizeof(struct combiner_reg) * nregs; | ||
251 | combiner = devm_kzalloc(&pdev->dev, alloc_sz, GFP_KERNEL); | ||
252 | if (!combiner) | ||
253 | return -ENOMEM; | ||
254 | |||
255 | err = get_registers(pdev, combiner); | ||
256 | if (err < 0) | ||
257 | return err; | ||
258 | |||
259 | combiner->parent_irq = platform_get_irq(pdev, 0); | ||
260 | if (combiner->parent_irq <= 0) { | ||
261 | dev_err(&pdev->dev, "Error getting IRQ resource\n"); | ||
262 | return -EPROBE_DEFER; | ||
263 | } | ||
264 | |||
265 | combiner->domain = irq_domain_create_linear(pdev->dev.fwnode, combiner->nirqs, | ||
266 | &domain_ops, combiner); | ||
267 | if (!combiner->domain) | ||
268 | /* Errors printed by irq_domain_create_linear */ | ||
269 | return -ENODEV; | ||
270 | |||
271 | irq_set_chained_handler_and_data(combiner->parent_irq, | ||
272 | combiner_handle_irq, combiner); | ||
273 | |||
274 | dev_info(&pdev->dev, "Initialized with [p=%d,n=%d,r=%p]\n", | ||
275 | combiner->parent_irq, combiner->nirqs, combiner->regs[0].addr); | ||
276 | return 0; | ||
277 | } | ||
278 | |||
279 | static const struct acpi_device_id qcom_irq_combiner_ids[] = { | ||
280 | { "QCOM80B1", }, | ||
281 | { } | ||
282 | }; | ||
283 | |||
284 | static struct platform_driver qcom_irq_combiner_probe = { | ||
285 | .driver = { | ||
286 | .name = "qcom-irq-combiner", | ||
287 | .acpi_match_table = ACPI_PTR(qcom_irq_combiner_ids), | ||
288 | }, | ||
289 | .probe = combiner_probe, | ||
290 | }; | ||
291 | |||
292 | static int __init register_qcom_irq_combiner(void) | ||
293 | { | ||
294 | return platform_driver_register(&qcom_irq_combiner_probe); | ||
295 | } | ||
296 | device_initcall(register_qcom_irq_combiner); | ||