1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. |
3 | */ |
4 | |
5 | /* |
6 | * Driver for interrupt combiners in the Top-level Control and Status |
7 | * Registers (TCSR) hardware block in Qualcomm Technologies chips. |
8 | * An interrupt combiner in this block combines a set of interrupts by |
9 | * OR'ing the individual interrupt signals into a summary interrupt |
10 | * signal routed to a parent interrupt controller, and provides read- |
11 | * only, 32-bit registers to query the status of individual interrupts. |
12 | * The status bit for IRQ n is bit (n % 32) within register (n / 32) |
13 | * of the given combiner. Thus, each combiner can be described as a set |
14 | * of register offsets and the number of IRQs managed. |
15 | */ |
16 | |
17 | #define pr_fmt(fmt) "QCOM80B1:" fmt |
18 | |
19 | #include <linux/acpi.h> |
20 | #include <linux/irqchip/chained_irq.h> |
21 | #include <linux/irqdomain.h> |
22 | #include <linux/platform_device.h> |
23 | |
24 | #define REG_SIZE 32 |
25 | |
26 | struct combiner_reg { |
27 | void __iomem *addr; |
28 | unsigned long enabled; |
29 | }; |
30 | |
31 | struct combiner { |
32 | struct irq_domain *domain; |
33 | int parent_irq; |
34 | u32 nirqs; |
35 | u32 nregs; |
36 | struct combiner_reg regs[]; |
37 | }; |
38 | |
39 | static inline int irq_nr(u32 reg, u32 bit) |
40 | { |
41 | return reg * REG_SIZE + bit; |
42 | } |
43 | |
44 | /* |
45 | * Handler for the cascaded IRQ. |
46 | */ |
47 | static void combiner_handle_irq(struct irq_desc *desc) |
48 | { |
49 | struct combiner *combiner = irq_desc_get_handler_data(desc); |
50 | struct irq_chip *chip = irq_desc_get_chip(desc); |
51 | u32 reg; |
52 | |
53 | chained_irq_enter(chip, desc); |
54 | |
55 | for (reg = 0; reg < combiner->nregs; reg++) { |
56 | int hwirq; |
57 | u32 bit; |
58 | u32 status; |
59 | |
60 | bit = readl_relaxed(combiner->regs[reg].addr); |
61 | status = bit & combiner->regs[reg].enabled; |
62 | if (bit && !status) |
63 | pr_warn_ratelimited("Unexpected IRQ on CPU%d: (%08x %08lx %p)\n" , |
64 | smp_processor_id(), bit, |
65 | combiner->regs[reg].enabled, |
66 | combiner->regs[reg].addr); |
67 | |
68 | while (status) { |
69 | bit = __ffs(status); |
70 | status &= ~(1 << bit); |
71 | hwirq = irq_nr(reg, bit); |
72 | generic_handle_domain_irq(domain: combiner->domain, hwirq); |
73 | } |
74 | } |
75 | |
76 | chained_irq_exit(chip, desc); |
77 | } |
78 | |
79 | static void combiner_irq_chip_mask_irq(struct irq_data *data) |
80 | { |
81 | struct combiner *combiner = irq_data_get_irq_chip_data(d: data); |
82 | struct combiner_reg *reg = combiner->regs + data->hwirq / REG_SIZE; |
83 | |
84 | clear_bit(nr: data->hwirq % REG_SIZE, addr: ®->enabled); |
85 | } |
86 | |
87 | static void combiner_irq_chip_unmask_irq(struct irq_data *data) |
88 | { |
89 | struct combiner *combiner = irq_data_get_irq_chip_data(d: data); |
90 | struct combiner_reg *reg = combiner->regs + data->hwirq / REG_SIZE; |
91 | |
92 | set_bit(nr: data->hwirq % REG_SIZE, addr: ®->enabled); |
93 | } |
94 | |
95 | static struct irq_chip irq_chip = { |
96 | .irq_mask = combiner_irq_chip_mask_irq, |
97 | .irq_unmask = combiner_irq_chip_unmask_irq, |
98 | .name = "qcom-irq-combiner" |
99 | }; |
100 | |
101 | static int combiner_irq_map(struct irq_domain *domain, unsigned int irq, |
102 | irq_hw_number_t hwirq) |
103 | { |
104 | irq_set_chip_and_handler(irq, chip: &irq_chip, handle: handle_level_irq); |
105 | irq_set_chip_data(irq, data: domain->host_data); |
106 | irq_set_noprobe(irq); |
107 | return 0; |
108 | } |
109 | |
110 | static void combiner_irq_unmap(struct irq_domain *domain, unsigned int irq) |
111 | { |
112 | irq_domain_reset_irq_data(irq_data: irq_get_irq_data(irq)); |
113 | } |
114 | |
115 | static int combiner_irq_translate(struct irq_domain *d, struct irq_fwspec *fws, |
116 | unsigned long *hwirq, unsigned int *type) |
117 | { |
118 | struct combiner *combiner = d->host_data; |
119 | |
120 | if (is_acpi_node(fwnode: fws->fwnode)) { |
121 | if (WARN_ON((fws->param_count != 2) || |
122 | (fws->param[0] >= combiner->nirqs) || |
123 | (fws->param[1] & IORESOURCE_IRQ_LOWEDGE) || |
124 | (fws->param[1] & IORESOURCE_IRQ_HIGHEDGE))) |
125 | return -EINVAL; |
126 | |
127 | *hwirq = fws->param[0]; |
128 | *type = fws->param[1]; |
129 | return 0; |
130 | } |
131 | |
132 | return -EINVAL; |
133 | } |
134 | |
135 | static const struct irq_domain_ops domain_ops = { |
136 | .map = combiner_irq_map, |
137 | .unmap = combiner_irq_unmap, |
138 | .translate = combiner_irq_translate |
139 | }; |
140 | |
141 | static acpi_status count_registers_cb(struct acpi_resource *ares, void *context) |
142 | { |
143 | int *count = context; |
144 | |
145 | if (ares->type == ACPI_RESOURCE_TYPE_GENERIC_REGISTER) |
146 | ++(*count); |
147 | return AE_OK; |
148 | } |
149 | |
150 | static int count_registers(struct platform_device *pdev) |
151 | { |
152 | acpi_handle ahandle = ACPI_HANDLE(&pdev->dev); |
153 | acpi_status status; |
154 | int count = 0; |
155 | |
156 | if (!acpi_has_method(handle: ahandle, METHOD_NAME__CRS)) |
157 | return -EINVAL; |
158 | |
159 | status = acpi_walk_resources(device: ahandle, METHOD_NAME__CRS, |
160 | user_function: count_registers_cb, context: &count); |
161 | if (ACPI_FAILURE(status)) |
162 | return -EINVAL; |
163 | return count; |
164 | } |
165 | |
166 | struct get_registers_context { |
167 | struct device *dev; |
168 | struct combiner *combiner; |
169 | int err; |
170 | }; |
171 | |
172 | static acpi_status get_registers_cb(struct acpi_resource *ares, void *context) |
173 | { |
174 | struct get_registers_context *ctx = context; |
175 | struct acpi_resource_generic_register *reg; |
176 | phys_addr_t paddr; |
177 | void __iomem *vaddr; |
178 | |
179 | if (ares->type != ACPI_RESOURCE_TYPE_GENERIC_REGISTER) |
180 | return AE_OK; |
181 | |
182 | reg = &ares->data.generic_reg; |
183 | paddr = reg->address; |
184 | if ((reg->space_id != ACPI_SPACE_MEM) || |
185 | (reg->bit_offset != 0) || |
186 | (reg->bit_width > REG_SIZE)) { |
187 | dev_err(ctx->dev, "Bad register resource @%pa\n" , &paddr); |
188 | ctx->err = -EINVAL; |
189 | return AE_ERROR; |
190 | } |
191 | |
192 | vaddr = devm_ioremap(dev: ctx->dev, offset: reg->address, REG_SIZE); |
193 | if (!vaddr) { |
194 | dev_err(ctx->dev, "Can't map register @%pa\n" , &paddr); |
195 | ctx->err = -ENOMEM; |
196 | return AE_ERROR; |
197 | } |
198 | |
199 | ctx->combiner->regs[ctx->combiner->nregs].addr = vaddr; |
200 | ctx->combiner->nirqs += reg->bit_width; |
201 | ctx->combiner->nregs++; |
202 | return AE_OK; |
203 | } |
204 | |
205 | static int get_registers(struct platform_device *pdev, struct combiner *comb) |
206 | { |
207 | acpi_handle ahandle = ACPI_HANDLE(&pdev->dev); |
208 | acpi_status status; |
209 | struct get_registers_context ctx; |
210 | |
211 | if (!acpi_has_method(handle: ahandle, METHOD_NAME__CRS)) |
212 | return -EINVAL; |
213 | |
214 | ctx.dev = &pdev->dev; |
215 | ctx.combiner = comb; |
216 | ctx.err = 0; |
217 | |
218 | status = acpi_walk_resources(device: ahandle, METHOD_NAME__CRS, |
219 | user_function: get_registers_cb, context: &ctx); |
220 | if (ACPI_FAILURE(status)) |
221 | return ctx.err; |
222 | return 0; |
223 | } |
224 | |
225 | static int __init combiner_probe(struct platform_device *pdev) |
226 | { |
227 | struct combiner *combiner; |
228 | int nregs; |
229 | int err; |
230 | |
231 | nregs = count_registers(pdev); |
232 | if (nregs <= 0) { |
233 | dev_err(&pdev->dev, "Error reading register resources\n" ); |
234 | return -EINVAL; |
235 | } |
236 | |
237 | combiner = devm_kzalloc(dev: &pdev->dev, struct_size(combiner, regs, nregs), |
238 | GFP_KERNEL); |
239 | if (!combiner) |
240 | return -ENOMEM; |
241 | |
242 | err = get_registers(pdev, comb: combiner); |
243 | if (err < 0) |
244 | return err; |
245 | |
246 | combiner->parent_irq = platform_get_irq(pdev, 0); |
247 | if (combiner->parent_irq <= 0) |
248 | return -EPROBE_DEFER; |
249 | |
250 | combiner->domain = irq_domain_create_linear(fwnode: pdev->dev.fwnode, size: combiner->nirqs, |
251 | ops: &domain_ops, host_data: combiner); |
252 | if (!combiner->domain) |
253 | /* Errors printed by irq_domain_create_linear */ |
254 | return -ENODEV; |
255 | |
256 | irq_set_chained_handler_and_data(irq: combiner->parent_irq, |
257 | handle: combiner_handle_irq, data: combiner); |
258 | |
259 | dev_info(&pdev->dev, "Initialized with [p=%d,n=%d,r=%p]\n" , |
260 | combiner->parent_irq, combiner->nirqs, combiner->regs[0].addr); |
261 | return 0; |
262 | } |
263 | |
264 | static const struct acpi_device_id qcom_irq_combiner_ids[] = { |
265 | { "QCOM80B1" , }, |
266 | { } |
267 | }; |
268 | |
269 | static struct platform_driver qcom_irq_combiner_probe = { |
270 | .driver = { |
271 | .name = "qcom-irq-combiner" , |
272 | .acpi_match_table = ACPI_PTR(qcom_irq_combiner_ids), |
273 | }, |
274 | .probe = combiner_probe, |
275 | }; |
276 | builtin_platform_driver(qcom_irq_combiner_probe); |
277 | |