]>
Commit | Line | Data |
---|---|---|
ba764c4d AE |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | ||
3 | /* Copyright (c) 2014-2018, The Linux Foundation. All rights reserved. | |
4 | * Copyright (C) 2018-2020 Linaro Ltd. | |
5 | */ | |
6 | ||
7 | /* DOC: IPA Interrupts | |
8 | * | |
9 | * The IPA has an interrupt line distinct from the interrupt used by the GSI | |
10 | * code. Whereas GSI interrupts are generally related to channel events (like | |
11 | * transfer completions), IPA interrupts are related to other events related | |
12 | * to the IPA. Some of the IPA interrupts come from a microcontroller | |
13 | * embedded in the IPA. Each IPA interrupt type can be both masked and | |
14 | * acknowledged independent of the others. | |
15 | * | |
16 | * Two of the IPA interrupts are initiated by the microcontroller. A third | |
17 | * can be generated to signal the need for a wakeup/resume when an IPA | |
18 | * endpoint has been suspended. There are other IPA events, but at this | |
19 | * time only these three are supported. | |
20 | */ | |
21 | ||
22 | #include <linux/types.h> | |
23 | #include <linux/interrupt.h> | |
24 | ||
25 | #include "ipa.h" | |
26 | #include "ipa_clock.h" | |
27 | #include "ipa_reg.h" | |
28 | #include "ipa_endpoint.h" | |
29 | #include "ipa_interrupt.h" | |
30 | ||
31 | /** | |
32 | * struct ipa_interrupt - IPA interrupt information | |
33 | * @ipa: IPA pointer | |
34 | * @irq: Linux IRQ number used for IPA interrupts | |
35 | * @enabled: Mask indicating which interrupts are enabled | |
36 | * @handler: Array of handlers indexed by IPA interrupt ID | |
37 | */ | |
38 | struct ipa_interrupt { | |
39 | struct ipa *ipa; | |
40 | u32 irq; | |
41 | u32 enabled; | |
42 | ipa_irq_handler_t handler[IPA_IRQ_COUNT]; | |
43 | }; | |
44 | ||
45 | /* Returns true if the interrupt type is associated with the microcontroller */ | |
46 | static bool ipa_interrupt_uc(struct ipa_interrupt *interrupt, u32 irq_id) | |
47 | { | |
48 | return irq_id == IPA_IRQ_UC_0 || irq_id == IPA_IRQ_UC_1; | |
49 | } | |
50 | ||
51 | /* Process a particular interrupt type that has been received */ | |
52 | static void ipa_interrupt_process(struct ipa_interrupt *interrupt, u32 irq_id) | |
53 | { | |
54 | bool uc_irq = ipa_interrupt_uc(interrupt, irq_id); | |
55 | struct ipa *ipa = interrupt->ipa; | |
56 | u32 mask = BIT(irq_id); | |
57 | ||
58 | /* For microcontroller interrupts, clear the interrupt right away, | |
59 | * "to avoid clearing unhandled interrupts." | |
60 | */ | |
61 | if (uc_irq) | |
62 | iowrite32(mask, ipa->reg_virt + IPA_REG_IRQ_CLR_OFFSET); | |
63 | ||
64 | if (irq_id < IPA_IRQ_COUNT && interrupt->handler[irq_id]) | |
65 | interrupt->handler[irq_id](interrupt->ipa, irq_id); | |
66 | ||
67 | /* Clearing the SUSPEND_TX interrupt also clears the register | |
68 | * that tells us which suspended endpoint(s) caused the interrupt, | |
69 | * so defer clearing until after the handler has been called. | |
70 | */ | |
71 | if (!uc_irq) | |
72 | iowrite32(mask, ipa->reg_virt + IPA_REG_IRQ_CLR_OFFSET); | |
73 | } | |
74 | ||
75 | /* Process all IPA interrupt types that have been signaled */ | |
76 | static void ipa_interrupt_process_all(struct ipa_interrupt *interrupt) | |
77 | { | |
78 | struct ipa *ipa = interrupt->ipa; | |
79 | u32 enabled = interrupt->enabled; | |
80 | u32 mask; | |
81 | ||
82 | /* The status register indicates which conditions are present, | |
83 | * including conditions whose interrupt is not enabled. Handle | |
84 | * only the enabled ones. | |
85 | */ | |
86 | mask = ioread32(ipa->reg_virt + IPA_REG_IRQ_STTS_OFFSET); | |
87 | while ((mask &= enabled)) { | |
88 | do { | |
89 | u32 irq_id = __ffs(mask); | |
90 | ||
91 | mask ^= BIT(irq_id); | |
92 | ||
93 | ipa_interrupt_process(interrupt, irq_id); | |
94 | } while (mask); | |
95 | mask = ioread32(ipa->reg_virt + IPA_REG_IRQ_STTS_OFFSET); | |
96 | } | |
97 | } | |
98 | ||
99 | /* Threaded part of the IPA IRQ handler */ | |
100 | static irqreturn_t ipa_isr_thread(int irq, void *dev_id) | |
101 | { | |
102 | struct ipa_interrupt *interrupt = dev_id; | |
103 | ||
104 | ipa_clock_get(interrupt->ipa); | |
105 | ||
106 | ipa_interrupt_process_all(interrupt); | |
107 | ||
108 | ipa_clock_put(interrupt->ipa); | |
109 | ||
110 | return IRQ_HANDLED; | |
111 | } | |
112 | ||
113 | /* Hard part (i.e., "real" IRQ handler) of the IRQ handler */ | |
114 | static irqreturn_t ipa_isr(int irq, void *dev_id) | |
115 | { | |
116 | struct ipa_interrupt *interrupt = dev_id; | |
117 | struct ipa *ipa = interrupt->ipa; | |
118 | u32 mask; | |
119 | ||
120 | mask = ioread32(ipa->reg_virt + IPA_REG_IRQ_STTS_OFFSET); | |
121 | if (mask & interrupt->enabled) | |
122 | return IRQ_WAKE_THREAD; | |
123 | ||
124 | /* Nothing in the mask was supposed to cause an interrupt */ | |
125 | iowrite32(mask, ipa->reg_virt + IPA_REG_IRQ_CLR_OFFSET); | |
126 | ||
127 | dev_err(&ipa->pdev->dev, "%s: unexpected interrupt, mask 0x%08x\n", | |
128 | __func__, mask); | |
129 | ||
130 | return IRQ_HANDLED; | |
131 | } | |
132 | ||
133 | /* Common function used to enable/disable TX_SUSPEND for an endpoint */ | |
134 | static void ipa_interrupt_suspend_control(struct ipa_interrupt *interrupt, | |
135 | u32 endpoint_id, bool enable) | |
136 | { | |
137 | struct ipa *ipa = interrupt->ipa; | |
138 | u32 mask = BIT(endpoint_id); | |
139 | u32 val; | |
140 | ||
141 | /* assert(mask & ipa->available); */ | |
142 | val = ioread32(ipa->reg_virt + IPA_REG_SUSPEND_IRQ_EN_OFFSET); | |
143 | if (enable) | |
144 | val |= mask; | |
145 | else | |
146 | val &= ~mask; | |
147 | iowrite32(val, ipa->reg_virt + IPA_REG_SUSPEND_IRQ_EN_OFFSET); | |
148 | } | |
149 | ||
150 | /* Enable TX_SUSPEND for an endpoint */ | |
151 | void | |
152 | ipa_interrupt_suspend_enable(struct ipa_interrupt *interrupt, u32 endpoint_id) | |
153 | { | |
154 | ipa_interrupt_suspend_control(interrupt, endpoint_id, true); | |
155 | } | |
156 | ||
157 | /* Disable TX_SUSPEND for an endpoint */ | |
158 | void | |
159 | ipa_interrupt_suspend_disable(struct ipa_interrupt *interrupt, u32 endpoint_id) | |
160 | { | |
161 | ipa_interrupt_suspend_control(interrupt, endpoint_id, false); | |
162 | } | |
163 | ||
164 | /* Clear the suspend interrupt for all endpoints that signaled it */ | |
165 | void ipa_interrupt_suspend_clear_all(struct ipa_interrupt *interrupt) | |
166 | { | |
167 | struct ipa *ipa = interrupt->ipa; | |
168 | u32 val; | |
169 | ||
170 | val = ioread32(ipa->reg_virt + IPA_REG_IRQ_SUSPEND_INFO_OFFSET); | |
171 | iowrite32(val, ipa->reg_virt + IPA_REG_SUSPEND_IRQ_CLR_OFFSET); | |
172 | } | |
173 | ||
174 | /* Simulate arrival of an IPA TX_SUSPEND interrupt */ | |
175 | void ipa_interrupt_simulate_suspend(struct ipa_interrupt *interrupt) | |
176 | { | |
177 | ipa_interrupt_process(interrupt, IPA_IRQ_TX_SUSPEND); | |
178 | } | |
179 | ||
180 | /* Add a handler for an IPA interrupt */ | |
181 | void ipa_interrupt_add(struct ipa_interrupt *interrupt, | |
182 | enum ipa_irq_id ipa_irq, ipa_irq_handler_t handler) | |
183 | { | |
184 | struct ipa *ipa = interrupt->ipa; | |
185 | ||
186 | /* assert(ipa_irq < IPA_IRQ_COUNT); */ | |
187 | interrupt->handler[ipa_irq] = handler; | |
188 | ||
189 | /* Update the IPA interrupt mask to enable it */ | |
190 | interrupt->enabled |= BIT(ipa_irq); | |
191 | iowrite32(interrupt->enabled, ipa->reg_virt + IPA_REG_IRQ_EN_OFFSET); | |
192 | } | |
193 | ||
194 | /* Remove the handler for an IPA interrupt type */ | |
195 | void | |
196 | ipa_interrupt_remove(struct ipa_interrupt *interrupt, enum ipa_irq_id ipa_irq) | |
197 | { | |
198 | struct ipa *ipa = interrupt->ipa; | |
199 | ||
200 | /* assert(ipa_irq < IPA_IRQ_COUNT); */ | |
201 | /* Update the IPA interrupt mask to disable it */ | |
202 | interrupt->enabled &= ~BIT(ipa_irq); | |
203 | iowrite32(interrupt->enabled, ipa->reg_virt + IPA_REG_IRQ_EN_OFFSET); | |
204 | ||
205 | interrupt->handler[ipa_irq] = NULL; | |
206 | } | |
207 | ||
208 | /* Set up the IPA interrupt framework */ | |
209 | struct ipa_interrupt *ipa_interrupt_setup(struct ipa *ipa) | |
210 | { | |
211 | struct device *dev = &ipa->pdev->dev; | |
212 | struct ipa_interrupt *interrupt; | |
213 | unsigned int irq; | |
214 | int ret; | |
215 | ||
216 | ret = platform_get_irq_byname(ipa->pdev, "ipa"); | |
217 | if (ret <= 0) { | |
218 | dev_err(dev, "DT error %d getting \"ipa\" IRQ property\n", | |
219 | ret); | |
220 | return ERR_PTR(ret ? : -EINVAL); | |
221 | } | |
222 | irq = ret; | |
223 | ||
224 | interrupt = kzalloc(sizeof(*interrupt), GFP_KERNEL); | |
225 | if (!interrupt) | |
226 | return ERR_PTR(-ENOMEM); | |
227 | interrupt->ipa = ipa; | |
228 | interrupt->irq = irq; | |
229 | ||
230 | /* Start with all IPA interrupts disabled */ | |
231 | iowrite32(0, ipa->reg_virt + IPA_REG_IRQ_EN_OFFSET); | |
232 | ||
233 | ret = request_threaded_irq(irq, ipa_isr, ipa_isr_thread, IRQF_ONESHOT, | |
234 | "ipa", interrupt); | |
235 | if (ret) { | |
236 | dev_err(dev, "error %d requesting \"ipa\" IRQ\n", ret); | |
237 | goto err_kfree; | |
238 | } | |
239 | ||
d1b5126a AE |
240 | ret = enable_irq_wake(irq); |
241 | if (ret) { | |
242 | dev_err(dev, "error %d enabling wakeup for \"ipa\" IRQ\n", ret); | |
243 | goto err_free_irq; | |
244 | } | |
245 | ||
ba764c4d AE |
246 | return interrupt; |
247 | ||
d1b5126a AE |
248 | err_free_irq: |
249 | free_irq(interrupt->irq, interrupt); | |
ba764c4d AE |
250 | err_kfree: |
251 | kfree(interrupt); | |
252 | ||
253 | return ERR_PTR(ret); | |
254 | } | |
255 | ||
256 | /* Tear down the IPA interrupt framework */ | |
257 | void ipa_interrupt_teardown(struct ipa_interrupt *interrupt) | |
258 | { | |
d1b5126a AE |
259 | struct device *dev = &interrupt->ipa->pdev->dev; |
260 | int ret; | |
261 | ||
262 | ret = disable_irq_wake(interrupt->irq); | |
263 | if (ret) | |
264 | dev_err(dev, "error %d disabling \"ipa\" IRQ wakeup\n", ret); | |
ba764c4d AE |
265 | free_irq(interrupt->irq, interrupt); |
266 | kfree(interrupt); | |
267 | } |