Commit | Line | Data |
---|---|---|
5fd54ace | 1 | // SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) |
56f5b1cf PZ |
2 | /* |
3 | * core_intr.c - DesignWare HS OTG Controller common interrupt handling | |
4 | * | |
5 | * Copyright (C) 2004-2013 Synopsys, Inc. | |
6 | * | |
7 | * Redistribution and use in source and binary forms, with or without | |
8 | * modification, are permitted provided that the following conditions | |
9 | * are met: | |
10 | * 1. Redistributions of source code must retain the above copyright | |
11 | * notice, this list of conditions, and the following disclaimer, | |
12 | * without modification. | |
13 | * 2. Redistributions in binary form must reproduce the above copyright | |
14 | * notice, this list of conditions and the following disclaimer in the | |
15 | * documentation and/or other materials provided with the distribution. | |
16 | * 3. The names of the above-listed copyright holders may not be used | |
17 | * to endorse or promote products derived from this software without | |
18 | * specific prior written permission. | |
19 | * | |
20 | * ALTERNATIVELY, this software may be distributed under the terms of the | |
21 | * GNU General Public License ("GPL") as published by the Free Software | |
22 | * Foundation; either version 2 of the License, or (at your option) any | |
23 | * later version. | |
24 | * | |
25 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS | |
26 | * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, | |
27 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
28 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |
29 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
30 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
31 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
32 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |
33 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
34 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
35 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
36 | */ | |
37 | ||
38 | /* | |
39 | * This file contains the common interrupt handlers | |
40 | */ | |
41 | #include <linux/kernel.h> | |
42 | #include <linux/module.h> | |
43 | #include <linux/moduleparam.h> | |
44 | #include <linux/spinlock.h> | |
45 | #include <linux/interrupt.h> | |
46 | #include <linux/dma-mapping.h> | |
47 | #include <linux/io.h> | |
48 | #include <linux/slab.h> | |
49 | #include <linux/usb.h> | |
50 | ||
51 | #include <linux/usb/hcd.h> | |
52 | #include <linux/usb/ch11.h> | |
53 | ||
54 | #include "core.h" | |
55 | #include "hcd.h" | |
56 | ||
57 | static const char *dwc2_op_state_str(struct dwc2_hsotg *hsotg) | |
58 | { | |
56f5b1cf PZ |
59 | switch (hsotg->op_state) { |
60 | case OTG_STATE_A_HOST: | |
61 | return "a_host"; | |
62 | case OTG_STATE_A_SUSPEND: | |
63 | return "a_suspend"; | |
64 | case OTG_STATE_A_PERIPHERAL: | |
65 | return "a_peripheral"; | |
66 | case OTG_STATE_B_PERIPHERAL: | |
67 | return "b_peripheral"; | |
68 | case OTG_STATE_B_HOST: | |
69 | return "b_host"; | |
70 | default: | |
71 | return "unknown"; | |
72 | } | |
56f5b1cf PZ |
73 | } |
74 | ||
93571adb DN |
75 | /** |
76 | * dwc2_handle_usb_port_intr - handles OTG PRTINT interrupts. | |
77 | * When the PRTINT interrupt fires, there are certain status bits in the Host | |
78 | * Port that needs to get cleared. | |
79 | * | |
80 | * @hsotg: Programming view of DWC_otg controller | |
81 | */ | |
82 | static void dwc2_handle_usb_port_intr(struct dwc2_hsotg *hsotg) | |
83 | { | |
f25c42b8 | 84 | u32 hprt0 = dwc2_readl(hsotg, HPRT0); |
93571adb DN |
85 | |
86 | if (hprt0 & HPRT0_ENACHG) { | |
87 | hprt0 &= ~HPRT0_ENA; | |
f25c42b8 | 88 | dwc2_writel(hsotg, hprt0, HPRT0); |
93571adb | 89 | } |
93571adb DN |
90 | } |
91 | ||
56f5b1cf PZ |
92 | /** |
93 | * dwc2_handle_mode_mismatch_intr() - Logs a mode mismatch warning message | |
94 | * | |
95 | * @hsotg: Programming view of DWC_otg controller | |
96 | */ | |
97 | static void dwc2_handle_mode_mismatch_intr(struct dwc2_hsotg *hsotg) | |
98 | { | |
56f5b1cf | 99 | /* Clear interrupt */ |
f25c42b8 | 100 | dwc2_writel(hsotg, GINTSTS_MODEMIS, GINTSTS); |
29539019 DA |
101 | |
102 | dev_warn(hsotg->dev, "Mode Mismatch Interrupt: currently in %s mode\n", | |
103 | dwc2_is_host_mode(hsotg) ? "Host" : "Device"); | |
56f5b1cf PZ |
104 | } |
105 | ||
106 | /** | |
107 | * dwc2_handle_otg_intr() - Handles the OTG Interrupts. It reads the OTG | |
108 | * Interrupt Register (GOTGINT) to determine what interrupt has occurred. | |
109 | * | |
110 | * @hsotg: Programming view of DWC_otg controller | |
111 | */ | |
112 | static void dwc2_handle_otg_intr(struct dwc2_hsotg *hsotg) | |
113 | { | |
114 | u32 gotgint; | |
115 | u32 gotgctl; | |
116 | u32 gintmsk; | |
117 | ||
f25c42b8 GS |
118 | gotgint = dwc2_readl(hsotg, GOTGINT); |
119 | gotgctl = dwc2_readl(hsotg, GOTGCTL); | |
56f5b1cf PZ |
120 | dev_dbg(hsotg->dev, "++OTG Interrupt gotgint=%0x [%s]\n", gotgint, |
121 | dwc2_op_state_str(hsotg)); | |
122 | ||
123 | if (gotgint & GOTGINT_SES_END_DET) { | |
124 | dev_dbg(hsotg->dev, | |
125 | " ++OTG Interrupt: Session End Detected++ (%s)\n", | |
126 | dwc2_op_state_str(hsotg)); | |
f25c42b8 | 127 | gotgctl = dwc2_readl(hsotg, GOTGCTL); |
56f5b1cf | 128 | |
4ace06e8 | 129 | if (dwc2_is_device_mode(hsotg)) |
1f91b4cc | 130 | dwc2_hsotg_disconnect(hsotg); |
4ace06e8 | 131 | |
56f5b1cf PZ |
132 | if (hsotg->op_state == OTG_STATE_B_HOST) { |
133 | hsotg->op_state = OTG_STATE_B_PERIPHERAL; | |
134 | } else { | |
135 | /* | |
136 | * If not B_HOST and Device HNP still set, HNP did | |
137 | * not succeed! | |
138 | */ | |
139 | if (gotgctl & GOTGCTL_DEVHNPEN) { | |
140 | dev_dbg(hsotg->dev, "Session End Detected\n"); | |
141 | dev_err(hsotg->dev, | |
142 | "Device Not Connected/Responding!\n"); | |
143 | } | |
144 | ||
145 | /* | |
146 | * If Session End Detected the B-Cable has been | |
147 | * disconnected | |
148 | */ | |
149 | /* Reset to a clean state */ | |
150 | hsotg->lx_state = DWC2_L0; | |
151 | } | |
152 | ||
f25c42b8 | 153 | gotgctl = dwc2_readl(hsotg, GOTGCTL); |
56f5b1cf | 154 | gotgctl &= ~GOTGCTL_DEVHNPEN; |
f25c42b8 | 155 | dwc2_writel(hsotg, gotgctl, GOTGCTL); |
56f5b1cf PZ |
156 | } |
157 | ||
158 | if (gotgint & GOTGINT_SES_REQ_SUC_STS_CHNG) { | |
159 | dev_dbg(hsotg->dev, | |
160 | " ++OTG Interrupt: Session Request Success Status Change++\n"); | |
f25c42b8 | 161 | gotgctl = dwc2_readl(hsotg, GOTGCTL); |
56f5b1cf | 162 | if (gotgctl & GOTGCTL_SESREQSCS) { |
ab283202 | 163 | if (hsotg->params.phy_type == DWC2_PHY_TYPE_PARAM_FS && |
95832c00 | 164 | hsotg->params.i2c_enable) { |
56f5b1cf PZ |
165 | hsotg->srp_success = 1; |
166 | } else { | |
167 | /* Clear Session Request */ | |
f25c42b8 | 168 | gotgctl = dwc2_readl(hsotg, GOTGCTL); |
56f5b1cf | 169 | gotgctl &= ~GOTGCTL_SESREQ; |
f25c42b8 | 170 | dwc2_writel(hsotg, gotgctl, GOTGCTL); |
56f5b1cf PZ |
171 | } |
172 | } | |
173 | } | |
174 | ||
175 | if (gotgint & GOTGINT_HST_NEG_SUC_STS_CHNG) { | |
176 | /* | |
177 | * Print statements during the HNP interrupt handling | |
178 | * can cause it to fail | |
179 | */ | |
f25c42b8 | 180 | gotgctl = dwc2_readl(hsotg, GOTGCTL); |
56f5b1cf PZ |
181 | /* |
182 | * WA for 3.00a- HW is not setting cur_mode, even sometimes | |
183 | * this does not help | |
184 | */ | |
9badec2f | 185 | if (hsotg->hw_params.snpsid >= DWC2_CORE_REV_3_00a) |
56f5b1cf PZ |
186 | udelay(100); |
187 | if (gotgctl & GOTGCTL_HSTNEGSCS) { | |
188 | if (dwc2_is_host_mode(hsotg)) { | |
189 | hsotg->op_state = OTG_STATE_B_HOST; | |
190 | /* | |
191 | * Need to disable SOF interrupt immediately. | |
192 | * When switching from device to host, the PCD | |
193 | * interrupt handler won't handle the interrupt | |
194 | * if host mode is already set. The HCD | |
195 | * interrupt handler won't get called if the | |
196 | * HCD state is HALT. This means that the | |
197 | * interrupt does not get handled and Linux | |
198 | * complains loudly. | |
199 | */ | |
f25c42b8 | 200 | gintmsk = dwc2_readl(hsotg, GINTMSK); |
56f5b1cf | 201 | gintmsk &= ~GINTSTS_SOF; |
f25c42b8 | 202 | dwc2_writel(hsotg, gintmsk, GINTMSK); |
56f5b1cf PZ |
203 | |
204 | /* | |
205 | * Call callback function with spin lock | |
206 | * released | |
207 | */ | |
208 | spin_unlock(&hsotg->lock); | |
209 | ||
210 | /* Initialize the Core for Host mode */ | |
211 | dwc2_hcd_start(hsotg); | |
212 | spin_lock(&hsotg->lock); | |
213 | hsotg->op_state = OTG_STATE_B_HOST; | |
214 | } | |
215 | } else { | |
f25c42b8 | 216 | gotgctl = dwc2_readl(hsotg, GOTGCTL); |
56f5b1cf | 217 | gotgctl &= ~(GOTGCTL_HNPREQ | GOTGCTL_DEVHNPEN); |
f25c42b8 | 218 | dwc2_writel(hsotg, gotgctl, GOTGCTL); |
56f5b1cf PZ |
219 | dev_dbg(hsotg->dev, "HNP Failed\n"); |
220 | dev_err(hsotg->dev, | |
221 | "Device Not Connected/Responding\n"); | |
222 | } | |
223 | } | |
224 | ||
225 | if (gotgint & GOTGINT_HST_NEG_DET) { | |
226 | /* | |
227 | * The disconnect interrupt is set at the same time as | |
228 | * Host Negotiation Detected. During the mode switch all | |
229 | * interrupts are cleared so the disconnect interrupt | |
230 | * handler will not get executed. | |
231 | */ | |
232 | dev_dbg(hsotg->dev, | |
233 | " ++OTG Interrupt: Host Negotiation Detected++ (%s)\n", | |
234 | (dwc2_is_host_mode(hsotg) ? "Host" : "Device")); | |
235 | if (dwc2_is_device_mode(hsotg)) { | |
236 | dev_dbg(hsotg->dev, "a_suspend->a_peripheral (%d)\n", | |
237 | hsotg->op_state); | |
238 | spin_unlock(&hsotg->lock); | |
6a659531 | 239 | dwc2_hcd_disconnect(hsotg, false); |
56f5b1cf PZ |
240 | spin_lock(&hsotg->lock); |
241 | hsotg->op_state = OTG_STATE_A_PERIPHERAL; | |
242 | } else { | |
243 | /* Need to disable SOF interrupt immediately */ | |
f25c42b8 | 244 | gintmsk = dwc2_readl(hsotg, GINTMSK); |
56f5b1cf | 245 | gintmsk &= ~GINTSTS_SOF; |
f25c42b8 | 246 | dwc2_writel(hsotg, gintmsk, GINTMSK); |
56f5b1cf PZ |
247 | spin_unlock(&hsotg->lock); |
248 | dwc2_hcd_start(hsotg); | |
249 | spin_lock(&hsotg->lock); | |
250 | hsotg->op_state = OTG_STATE_A_HOST; | |
251 | } | |
252 | } | |
253 | ||
254 | if (gotgint & GOTGINT_A_DEV_TOUT_CHG) | |
255 | dev_dbg(hsotg->dev, | |
256 | " ++OTG Interrupt: A-Device Timeout Change++\n"); | |
257 | if (gotgint & GOTGINT_DBNCE_DONE) | |
258 | dev_dbg(hsotg->dev, " ++OTG Interrupt: Debounce Done++\n"); | |
259 | ||
260 | /* Clear GOTGINT */ | |
f25c42b8 | 261 | dwc2_writel(hsotg, gotgint, GOTGINT); |
56f5b1cf PZ |
262 | } |
263 | ||
264 | /** | |
265 | * dwc2_handle_conn_id_status_change_intr() - Handles the Connector ID Status | |
266 | * Change Interrupt | |
267 | * | |
268 | * @hsotg: Programming view of DWC_otg controller | |
269 | * | |
270 | * Reads the OTG Interrupt Register (GOTCTL) to determine whether this is a | |
271 | * Device to Host Mode transition or a Host to Device Mode transition. This only | |
272 | * occurs when the cable is connected/removed from the PHY connector. | |
273 | */ | |
274 | static void dwc2_handle_conn_id_status_change_intr(struct dwc2_hsotg *hsotg) | |
275 | { | |
29539019 DA |
276 | u32 gintmsk; |
277 | ||
278 | /* Clear interrupt */ | |
f25c42b8 | 279 | dwc2_writel(hsotg, GINTSTS_CONIDSTSCHNG, GINTSTS); |
56f5b1cf PZ |
280 | |
281 | /* Need to disable SOF interrupt immediately */ | |
f25c42b8 | 282 | gintmsk = dwc2_readl(hsotg, GINTMSK); |
56f5b1cf | 283 | gintmsk &= ~GINTSTS_SOF; |
f25c42b8 | 284 | dwc2_writel(hsotg, gintmsk, GINTMSK); |
56f5b1cf PZ |
285 | |
286 | dev_dbg(hsotg->dev, " ++Connector ID Status Change Interrupt++ (%s)\n", | |
287 | dwc2_is_host_mode(hsotg) ? "Host" : "Device"); | |
288 | ||
289 | /* | |
290 | * Need to schedule a work, as there are possible DELAY function calls. | |
56f5b1cf | 291 | */ |
d8bc3bf8 | 292 | if (hsotg->wq_otg) |
117777b2 | 293 | queue_work(hsotg->wq_otg, &hsotg->wf_otg); |
56f5b1cf PZ |
294 | } |
295 | ||
296 | /** | |
297 | * dwc2_handle_session_req_intr() - This interrupt indicates that a device is | |
298 | * initiating the Session Request Protocol to request the host to turn on bus | |
299 | * power so a new session can begin | |
300 | * | |
301 | * @hsotg: Programming view of DWC_otg controller | |
302 | * | |
303 | * This handler responds by turning on bus power. If the DWC_otg controller is | |
304 | * in low power mode, this handler brings the controller out of low power mode | |
305 | * before turning on bus power. | |
306 | */ | |
307 | static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg) | |
308 | { | |
21795c82 MYK |
309 | int ret; |
310 | ||
56f5b1cf | 311 | /* Clear interrupt */ |
f25c42b8 | 312 | dwc2_writel(hsotg, GINTSTS_SESSREQINT, GINTSTS); |
4ace06e8 | 313 | |
29539019 | 314 | dev_dbg(hsotg->dev, "Session request interrupt - lx_state=%d\n", |
9da51974 | 315 | hsotg->lx_state); |
29539019 | 316 | |
21795c82 | 317 | if (dwc2_is_device_mode(hsotg)) { |
c9c394ab AP |
318 | if (hsotg->lx_state == DWC2_L2 && hsotg->in_ppd) { |
319 | ret = dwc2_exit_partial_power_down(hsotg, 0, | |
320 | true); | |
321 | if (ret) | |
21795c82 | 322 | dev_err(hsotg->dev, |
41ba9b9b | 323 | "exit power_down failed\n"); |
21795c82 MYK |
324 | } |
325 | ||
326 | /* | |
327 | * Report disconnect if there is any previous session | |
328 | * established | |
329 | */ | |
1f91b4cc | 330 | dwc2_hsotg_disconnect(hsotg); |
21795c82 | 331 | } |
56f5b1cf PZ |
332 | } |
333 | ||
273d576c SA |
334 | /** |
335 | * dwc2_wakeup_from_lpm_l1 - Exit the device from LPM L1 state | |
336 | * | |
337 | * @hsotg: Programming view of DWC_otg controller | |
338 | * | |
339 | */ | |
340 | static void dwc2_wakeup_from_lpm_l1(struct dwc2_hsotg *hsotg) | |
341 | { | |
342 | u32 glpmcfg; | |
343 | u32 i = 0; | |
344 | ||
345 | if (hsotg->lx_state != DWC2_L1) { | |
346 | dev_err(hsotg->dev, "Core isn't in DWC2_L1 state\n"); | |
347 | return; | |
348 | } | |
349 | ||
f25c42b8 | 350 | glpmcfg = dwc2_readl(hsotg, GLPMCFG); |
273d576c SA |
351 | if (dwc2_is_device_mode(hsotg)) { |
352 | dev_dbg(hsotg->dev, "Exit from L1 state\n"); | |
353 | glpmcfg &= ~GLPMCFG_ENBLSLPM; | |
354 | glpmcfg &= ~GLPMCFG_HIRD_THRES_EN; | |
f25c42b8 | 355 | dwc2_writel(hsotg, glpmcfg, GLPMCFG); |
273d576c SA |
356 | |
357 | do { | |
f25c42b8 | 358 | glpmcfg = dwc2_readl(hsotg, GLPMCFG); |
273d576c SA |
359 | |
360 | if (!(glpmcfg & (GLPMCFG_COREL1RES_MASK | | |
361 | GLPMCFG_L1RESUMEOK | GLPMCFG_SLPSTS))) | |
362 | break; | |
363 | ||
364 | udelay(1); | |
365 | } while (++i < 200); | |
366 | ||
367 | if (i == 200) { | |
368 | dev_err(hsotg->dev, "Failed to exit L1 sleep state in 200us.\n"); | |
369 | return; | |
370 | } | |
21b03405 | 371 | dwc2_gadget_init_lpm(hsotg); |
273d576c SA |
372 | } else { |
373 | /* TODO */ | |
374 | dev_err(hsotg->dev, "Host side LPM is not supported.\n"); | |
375 | return; | |
376 | } | |
377 | ||
378 | /* Change to L0 state */ | |
379 | hsotg->lx_state = DWC2_L0; | |
c655557c GT |
380 | |
381 | /* Inform gadget to exit from L1 */ | |
382 | call_gadget(hsotg, resume); | |
273d576c SA |
383 | } |
384 | ||
56f5b1cf PZ |
385 | /* |
386 | * This interrupt indicates that the DWC_otg controller has detected a | |
387 | * resume or remote wakeup sequence. If the DWC_otg controller is in | |
388 | * low power mode, the handler must brings the controller out of low | |
389 | * power mode. The controller automatically begins resume signaling. | |
390 | * The handler schedules a time to stop resume signaling. | |
391 | */ | |
392 | static void dwc2_handle_wakeup_detected_intr(struct dwc2_hsotg *hsotg) | |
393 | { | |
f81f46e1 | 394 | int ret; |
29539019 DA |
395 | |
396 | /* Clear interrupt */ | |
f25c42b8 | 397 | dwc2_writel(hsotg, GINTSTS_WKUPINT, GINTSTS); |
29539019 | 398 | |
56f5b1cf PZ |
399 | dev_dbg(hsotg->dev, "++Resume or Remote Wakeup Detected Interrupt++\n"); |
400 | dev_dbg(hsotg->dev, "%s lxstate = %d\n", __func__, hsotg->lx_state); | |
401 | ||
273d576c SA |
402 | if (hsotg->lx_state == DWC2_L1) { |
403 | dwc2_wakeup_from_lpm_l1(hsotg); | |
404 | return; | |
405 | } | |
406 | ||
56f5b1cf | 407 | if (dwc2_is_device_mode(hsotg)) { |
95c8bc36 | 408 | dev_dbg(hsotg->dev, "DSTS=0x%0x\n", |
f25c42b8 | 409 | dwc2_readl(hsotg, DSTS)); |
c9c394ab | 410 | if (hsotg->lx_state == DWC2_L2 && hsotg->in_ppd) { |
f25c42b8 | 411 | u32 dctl = dwc2_readl(hsotg, DCTL); |
56f5b1cf PZ |
412 | /* Clear Remote Wakeup Signaling */ |
413 | dctl &= ~DCTL_RMTWKUPSIG; | |
f25c42b8 | 414 | dwc2_writel(hsotg, dctl, DCTL); |
c9c394ab AP |
415 | ret = dwc2_exit_partial_power_down(hsotg, 1, |
416 | true); | |
417 | if (ret) | |
418 | dev_err(hsotg->dev, | |
419 | "exit partial_power_down failed\n"); | |
f81f46e1 | 420 | call_gadget(hsotg, resume); |
8c935dea FG |
421 | } else { |
422 | /* Change to L0 state */ | |
423 | hsotg->lx_state = DWC2_L0; | |
56f5b1cf | 424 | } |
56f5b1cf | 425 | } else { |
41ba9b9b | 426 | if (hsotg->params.power_down) |
6e74162f | 427 | return; |
29539019 | 428 | |
56f5b1cf | 429 | if (hsotg->lx_state != DWC2_L1) { |
f25c42b8 | 430 | u32 pcgcctl = dwc2_readl(hsotg, PCGCTL); |
56f5b1cf PZ |
431 | |
432 | /* Restart the Phy Clock */ | |
433 | pcgcctl &= ~PCGCTL_STOPPCLK; | |
f25c42b8 | 434 | dwc2_writel(hsotg, pcgcctl, PCGCTL); |
c40cf770 DA |
435 | |
436 | /* | |
437 | * If we've got this quirk then the PHY is stuck upon | |
438 | * wakeup. Assert reset. This will propagate out and | |
439 | * eventually we'll re-enumerate the device. Not great | |
440 | * but the best we can do. We can't call phy_reset() | |
441 | * at interrupt time but there's no hurry, so we'll | |
442 | * schedule it for later. | |
443 | */ | |
444 | if (hsotg->reset_phy_on_wake) | |
445 | dwc2_host_schedule_phy_reset(hsotg); | |
446 | ||
56f5b1cf PZ |
447 | mod_timer(&hsotg->wkp_timer, |
448 | jiffies + msecs_to_jiffies(71)); | |
449 | } else { | |
450 | /* Change to L0 state */ | |
451 | hsotg->lx_state = DWC2_L0; | |
452 | } | |
453 | } | |
56f5b1cf PZ |
454 | } |
455 | ||
456 | /* | |
457 | * This interrupt indicates that a device has been disconnected from the | |
458 | * root port | |
459 | */ | |
460 | static void dwc2_handle_disconnect_intr(struct dwc2_hsotg *hsotg) | |
461 | { | |
f25c42b8 | 462 | dwc2_writel(hsotg, GINTSTS_DISCONNINT, GINTSTS); |
29539019 | 463 | |
56f5b1cf PZ |
464 | dev_dbg(hsotg->dev, "++Disconnect Detected Interrupt++ (%s) %s\n", |
465 | dwc2_is_host_mode(hsotg) ? "Host" : "Device", | |
466 | dwc2_op_state_str(hsotg)); | |
467 | ||
509d612b | 468 | if (hsotg->op_state == OTG_STATE_A_HOST) |
6a659531 | 469 | dwc2_hcd_disconnect(hsotg, false); |
56f5b1cf PZ |
470 | } |
471 | ||
472 | /* | |
473 | * This interrupt indicates that SUSPEND state has been detected on the USB. | |
474 | * | |
475 | * For HNP the USB Suspend interrupt signals the change from "a_peripheral" | |
476 | * to "a_host". | |
477 | * | |
478 | * When power management is enabled the core will be put in low power mode. | |
479 | */ | |
480 | static void dwc2_handle_usb_suspend_intr(struct dwc2_hsotg *hsotg) | |
481 | { | |
482 | u32 dsts; | |
f81f46e1 | 483 | int ret; |
56f5b1cf | 484 | |
29539019 | 485 | /* Clear interrupt */ |
f25c42b8 | 486 | dwc2_writel(hsotg, GINTSTS_USBSUSP, GINTSTS); |
29539019 | 487 | |
56f5b1cf PZ |
488 | dev_dbg(hsotg->dev, "USB SUSPEND\n"); |
489 | ||
490 | if (dwc2_is_device_mode(hsotg)) { | |
491 | /* | |
492 | * Check the Device status register to determine if the Suspend | |
493 | * state is active | |
494 | */ | |
f25c42b8 | 495 | dsts = dwc2_readl(hsotg, DSTS); |
97861781 | 496 | dev_dbg(hsotg->dev, "%s: DSTS=0x%0x\n", __func__, dsts); |
56f5b1cf | 497 | dev_dbg(hsotg->dev, |
97861781 | 498 | "DSTS.Suspend Status=%d HWCFG4.Power Optimize=%d HWCFG4.Hibernation=%d\n", |
56f5b1cf | 499 | !!(dsts & DSTS_SUSPSTS), |
97861781 VM |
500 | hsotg->hw_params.power_optimized, |
501 | hsotg->hw_params.hibernation); | |
502 | ||
503 | /* Ignore suspend request before enumeration */ | |
504 | if (!dwc2_is_device_connected(hsotg)) { | |
505 | dev_dbg(hsotg->dev, | |
506 | "ignore suspend request before enumeration\n"); | |
507 | return; | |
508 | } | |
509 | if (dsts & DSTS_SUSPSTS) { | |
510 | if (hsotg->hw_params.power_optimized) { | |
511 | ret = dwc2_enter_partial_power_down(hsotg); | |
512 | if (ret) { | |
513 | if (ret != -ENOTSUPP) | |
514 | dev_err(hsotg->dev, | |
515 | "%s: enter partial_power_down failed\n", | |
516 | __func__); | |
517 | goto skip_power_saving; | |
518 | } | |
519 | ||
520 | udelay(100); | |
521 | ||
522 | /* Ask phy to be suspended */ | |
523 | if (!IS_ERR_OR_NULL(hsotg->uphy)) | |
524 | usb_phy_set_suspend(hsotg->uphy, true); | |
f81f46e1 GH |
525 | } |
526 | ||
97861781 VM |
527 | if (hsotg->hw_params.hibernation) { |
528 | ret = dwc2_enter_hibernation(hsotg, 0); | |
529 | if (ret && ret != -ENOTSUPP) | |
285046aa | 530 | dev_err(hsotg->dev, |
97861781 VM |
531 | "%s: enter hibernation failed\n", |
532 | __func__); | |
f81f46e1 | 533 | } |
f81f46e1 | 534 | skip_power_saving: |
3eb42df3 GH |
535 | /* |
536 | * Change to L2 (suspend) state before releasing | |
537 | * spinlock | |
538 | */ | |
539 | hsotg->lx_state = DWC2_L2; | |
540 | ||
f81f46e1 GH |
541 | /* Call gadget suspend callback */ |
542 | call_gadget(hsotg, suspend); | |
543 | } | |
56f5b1cf PZ |
544 | } else { |
545 | if (hsotg->op_state == OTG_STATE_A_PERIPHERAL) { | |
546 | dev_dbg(hsotg->dev, "a_peripheral->a_host\n"); | |
547 | ||
3eb42df3 GH |
548 | /* Change to L2 (suspend) state */ |
549 | hsotg->lx_state = DWC2_L2; | |
56f5b1cf PZ |
550 | /* Clear the a_peripheral flag, back to a_host */ |
551 | spin_unlock(&hsotg->lock); | |
552 | dwc2_hcd_start(hsotg); | |
553 | spin_lock(&hsotg->lock); | |
554 | hsotg->op_state = OTG_STATE_A_HOST; | |
555 | } | |
556 | } | |
56f5b1cf PZ |
557 | } |
558 | ||
d2521849 SA |
559 | /** |
560 | * dwc2_handle_lpm_intr - GINTSTS_LPMTRANRCVD Interrupt handler | |
561 | * | |
562 | * @hsotg: Programming view of DWC_otg controller | |
563 | * | |
564 | */ | |
565 | static void dwc2_handle_lpm_intr(struct dwc2_hsotg *hsotg) | |
566 | { | |
567 | u32 glpmcfg; | |
568 | u32 pcgcctl; | |
569 | u32 hird; | |
570 | u32 hird_thres; | |
571 | u32 hird_thres_en; | |
572 | u32 enslpm; | |
573 | ||
574 | /* Clear interrupt */ | |
f25c42b8 | 575 | dwc2_writel(hsotg, GINTSTS_LPMTRANRCVD, GINTSTS); |
d2521849 | 576 | |
f25c42b8 | 577 | glpmcfg = dwc2_readl(hsotg, GLPMCFG); |
d2521849 SA |
578 | |
579 | if (!(glpmcfg & GLPMCFG_LPMCAP)) { | |
580 | dev_err(hsotg->dev, "Unexpected LPM interrupt\n"); | |
581 | return; | |
582 | } | |
583 | ||
584 | hird = (glpmcfg & GLPMCFG_HIRD_MASK) >> GLPMCFG_HIRD_SHIFT; | |
585 | hird_thres = (glpmcfg & GLPMCFG_HIRD_THRES_MASK & | |
586 | ~GLPMCFG_HIRD_THRES_EN) >> GLPMCFG_HIRD_THRES_SHIFT; | |
587 | hird_thres_en = glpmcfg & GLPMCFG_HIRD_THRES_EN; | |
376f0401 | 588 | enslpm = glpmcfg & GLPMCFG_ENBLSLPM; |
d2521849 SA |
589 | |
590 | if (dwc2_is_device_mode(hsotg)) { | |
591 | dev_dbg(hsotg->dev, "HIRD_THRES_EN = %d\n", hird_thres_en); | |
592 | ||
593 | if (hird_thres_en && hird >= hird_thres) { | |
594 | dev_dbg(hsotg->dev, "L1 with utmi_l1_suspend_n\n"); | |
595 | } else if (enslpm) { | |
596 | dev_dbg(hsotg->dev, "L1 with utmi_sleep_n\n"); | |
597 | } else { | |
598 | dev_dbg(hsotg->dev, "Entering Sleep with L1 Gating\n"); | |
599 | ||
f25c42b8 | 600 | pcgcctl = dwc2_readl(hsotg, PCGCTL); |
d2521849 | 601 | pcgcctl |= PCGCTL_ENBL_SLEEP_GATING; |
f25c42b8 | 602 | dwc2_writel(hsotg, pcgcctl, PCGCTL); |
d2521849 SA |
603 | } |
604 | /** | |
605 | * Examine prt_sleep_sts after TL1TokenTetry period max (10 us) | |
606 | */ | |
607 | udelay(10); | |
608 | ||
f25c42b8 | 609 | glpmcfg = dwc2_readl(hsotg, GLPMCFG); |
d2521849 SA |
610 | |
611 | if (glpmcfg & GLPMCFG_SLPSTS) { | |
612 | /* Save the current state */ | |
613 | hsotg->lx_state = DWC2_L1; | |
614 | dev_dbg(hsotg->dev, | |
615 | "Core is in L1 sleep glpmcfg=%08x\n", glpmcfg); | |
c655557c GT |
616 | |
617 | /* Inform gadget that we are in L1 state */ | |
618 | call_gadget(hsotg, suspend); | |
d2521849 SA |
619 | } |
620 | } | |
621 | } | |
622 | ||
56f5b1cf PZ |
623 | #define GINTMSK_COMMON (GINTSTS_WKUPINT | GINTSTS_SESSREQINT | \ |
624 | GINTSTS_CONIDSTSCHNG | GINTSTS_OTGINT | \ | |
625 | GINTSTS_MODEMIS | GINTSTS_DISCONNINT | \ | |
376f0401 SA |
626 | GINTSTS_USBSUSP | GINTSTS_PRTINT | \ |
627 | GINTSTS_LPMTRANRCVD) | |
56f5b1cf PZ |
628 | |
629 | /* | |
630 | * This function returns the Core Interrupt register | |
631 | */ | |
632 | static u32 dwc2_read_common_intr(struct dwc2_hsotg *hsotg) | |
633 | { | |
634 | u32 gintsts; | |
635 | u32 gintmsk; | |
636 | u32 gahbcfg; | |
637 | u32 gintmsk_common = GINTMSK_COMMON; | |
638 | ||
f25c42b8 GS |
639 | gintsts = dwc2_readl(hsotg, GINTSTS); |
640 | gintmsk = dwc2_readl(hsotg, GINTMSK); | |
641 | gahbcfg = dwc2_readl(hsotg, GAHBCFG); | |
56f5b1cf | 642 | |
56f5b1cf PZ |
643 | /* If any common interrupts set */ |
644 | if (gintsts & gintmsk_common) | |
645 | dev_dbg(hsotg->dev, "gintsts=%08x gintmsk=%08x\n", | |
646 | gintsts, gintmsk); | |
56f5b1cf PZ |
647 | |
648 | if (gahbcfg & GAHBCFG_GLBL_INTR_EN) | |
649 | return gintsts & gintmsk & gintmsk_common; | |
650 | else | |
651 | return 0; | |
652 | } | |
653 | ||
65c9c4c6 VM |
654 | /* |
655 | * GPWRDN interrupt handler. | |
656 | * | |
657 | * The GPWRDN interrupts are those that occur in both Host and | |
658 | * Device mode while core is in hibernated state. | |
659 | */ | |
660 | static void dwc2_handle_gpwrdn_intr(struct dwc2_hsotg *hsotg) | |
661 | { | |
662 | u32 gpwrdn; | |
663 | int linestate; | |
664 | ||
f25c42b8 | 665 | gpwrdn = dwc2_readl(hsotg, GPWRDN); |
65c9c4c6 | 666 | /* clear all interrupt */ |
f25c42b8 | 667 | dwc2_writel(hsotg, gpwrdn, GPWRDN); |
65c9c4c6 VM |
668 | linestate = (gpwrdn & GPWRDN_LINESTATE_MASK) >> GPWRDN_LINESTATE_SHIFT; |
669 | dev_dbg(hsotg->dev, | |
670 | "%s: dwc2_handle_gpwrdwn_intr called gpwrdn= %08x\n", __func__, | |
671 | gpwrdn); | |
672 | ||
673 | if ((gpwrdn & GPWRDN_DISCONN_DET) && | |
674 | (gpwrdn & GPWRDN_DISCONN_DET_MSK) && !linestate) { | |
675 | u32 gpwrdn_tmp; | |
676 | ||
677 | dev_dbg(hsotg->dev, "%s: GPWRDN_DISCONN_DET\n", __func__); | |
678 | ||
679 | /* Switch-on voltage to the core */ | |
f25c42b8 | 680 | gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); |
65c9c4c6 | 681 | gpwrdn_tmp &= ~GPWRDN_PWRDNSWTCH; |
f25c42b8 | 682 | dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); |
65c9c4c6 VM |
683 | udelay(10); |
684 | ||
685 | /* Reset core */ | |
f25c42b8 | 686 | gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); |
65c9c4c6 | 687 | gpwrdn_tmp &= ~GPWRDN_PWRDNRSTN; |
f25c42b8 | 688 | dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); |
65c9c4c6 VM |
689 | udelay(10); |
690 | ||
691 | /* Disable Power Down Clamp */ | |
f25c42b8 | 692 | gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); |
65c9c4c6 | 693 | gpwrdn_tmp &= ~GPWRDN_PWRDNCLMP; |
f25c42b8 | 694 | dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); |
65c9c4c6 VM |
695 | udelay(10); |
696 | ||
697 | /* Deassert reset core */ | |
f25c42b8 | 698 | gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); |
65c9c4c6 | 699 | gpwrdn_tmp |= GPWRDN_PWRDNRSTN; |
f25c42b8 | 700 | dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); |
65c9c4c6 VM |
701 | udelay(10); |
702 | ||
703 | /* Disable PMU interrupt */ | |
f25c42b8 | 704 | gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); |
65c9c4c6 | 705 | gpwrdn_tmp &= ~GPWRDN_PMUINTSEL; |
f25c42b8 | 706 | dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); |
65c9c4c6 VM |
707 | |
708 | /* De-assert Wakeup Logic */ | |
f25c42b8 | 709 | gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); |
65c9c4c6 | 710 | gpwrdn_tmp &= ~GPWRDN_PMUACTV; |
f25c42b8 | 711 | dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); |
65c9c4c6 VM |
712 | |
713 | hsotg->hibernated = 0; | |
714 | ||
715 | if (gpwrdn & GPWRDN_IDSTS) { | |
716 | hsotg->op_state = OTG_STATE_B_PERIPHERAL; | |
717 | dwc2_core_init(hsotg, false); | |
718 | dwc2_enable_global_interrupts(hsotg); | |
719 | dwc2_hsotg_core_init_disconnected(hsotg, false); | |
720 | dwc2_hsotg_core_connect(hsotg); | |
721 | } else { | |
722 | hsotg->op_state = OTG_STATE_A_HOST; | |
723 | ||
724 | /* Initialize the Core for Host mode */ | |
725 | dwc2_core_init(hsotg, false); | |
726 | dwc2_enable_global_interrupts(hsotg); | |
727 | dwc2_hcd_start(hsotg); | |
728 | } | |
729 | } | |
730 | ||
731 | if ((gpwrdn & GPWRDN_LNSTSCHG) && | |
732 | (gpwrdn & GPWRDN_LNSTSCHG_MSK) && linestate) { | |
733 | dev_dbg(hsotg->dev, "%s: GPWRDN_LNSTSCHG\n", __func__); | |
734 | if (hsotg->hw_params.hibernation && | |
735 | hsotg->hibernated) { | |
736 | if (gpwrdn & GPWRDN_IDSTS) { | |
737 | dwc2_exit_hibernation(hsotg, 0, 0, 0); | |
738 | call_gadget(hsotg, resume); | |
739 | } else { | |
740 | dwc2_exit_hibernation(hsotg, 1, 0, 1); | |
741 | } | |
742 | } | |
743 | } | |
744 | if ((gpwrdn & GPWRDN_RST_DET) && (gpwrdn & GPWRDN_RST_DET_MSK)) { | |
745 | dev_dbg(hsotg->dev, "%s: GPWRDN_RST_DET\n", __func__); | |
746 | if (!linestate && (gpwrdn & GPWRDN_BSESSVLD)) | |
747 | dwc2_exit_hibernation(hsotg, 0, 1, 0); | |
748 | } | |
749 | if ((gpwrdn & GPWRDN_STS_CHGINT) && | |
750 | (gpwrdn & GPWRDN_STS_CHGINT_MSK) && linestate) { | |
751 | dev_dbg(hsotg->dev, "%s: GPWRDN_STS_CHGINT\n", __func__); | |
752 | if (hsotg->hw_params.hibernation && | |
753 | hsotg->hibernated) { | |
754 | if (gpwrdn & GPWRDN_IDSTS) { | |
755 | dwc2_exit_hibernation(hsotg, 0, 0, 0); | |
756 | call_gadget(hsotg, resume); | |
757 | } else { | |
758 | dwc2_exit_hibernation(hsotg, 1, 0, 1); | |
759 | } | |
760 | } | |
761 | } | |
762 | } | |
763 | ||
56f5b1cf PZ |
764 | /* |
765 | * Common interrupt handler | |
766 | * | |
767 | * The common interrupts are those that occur in both Host and Device mode. | |
768 | * This handler handles the following interrupts: | |
769 | * - Mode Mismatch Interrupt | |
770 | * - OTG Interrupt | |
771 | * - Connector ID Status Change Interrupt | |
772 | * - Disconnect Interrupt | |
773 | * - Session Request Interrupt | |
774 | * - Resume / Remote Wakeup Detected Interrupt | |
775 | * - Suspend Interrupt | |
776 | */ | |
777 | irqreturn_t dwc2_handle_common_intr(int irq, void *dev) | |
778 | { | |
779 | struct dwc2_hsotg *hsotg = dev; | |
780 | u32 gintsts; | |
6aafb003 | 781 | irqreturn_t retval = IRQ_NONE; |
56f5b1cf | 782 | |
cf54772b RB |
783 | spin_lock(&hsotg->lock); |
784 | ||
057715f2 PZ |
785 | if (!dwc2_is_controller_alive(hsotg)) { |
786 | dev_warn(hsotg->dev, "Controller is dead\n"); | |
56f5b1cf PZ |
787 | goto out; |
788 | } | |
789 | ||
c7c24e7a AP |
790 | /* Reading current frame number value in device or host modes. */ |
791 | if (dwc2_is_device_mode(hsotg)) | |
f25c42b8 | 792 | hsotg->frame_number = (dwc2_readl(hsotg, DSTS) |
c7c24e7a AP |
793 | & DSTS_SOFFN_MASK) >> DSTS_SOFFN_SHIFT; |
794 | else | |
f25c42b8 | 795 | hsotg->frame_number = (dwc2_readl(hsotg, HFNUM) |
c7c24e7a AP |
796 | & HFNUM_FRNUM_MASK) >> HFNUM_FRNUM_SHIFT; |
797 | ||
56f5b1cf PZ |
798 | gintsts = dwc2_read_common_intr(hsotg); |
799 | if (gintsts & ~GINTSTS_PRTINT) | |
6aafb003 | 800 | retval = IRQ_HANDLED; |
56f5b1cf | 801 | |
65c9c4c6 VM |
802 | /* In case of hibernated state gintsts must not work */ |
803 | if (hsotg->hibernated) { | |
804 | dwc2_handle_gpwrdn_intr(hsotg); | |
805 | retval = IRQ_HANDLED; | |
806 | goto out; | |
807 | } | |
808 | ||
56f5b1cf PZ |
809 | if (gintsts & GINTSTS_MODEMIS) |
810 | dwc2_handle_mode_mismatch_intr(hsotg); | |
811 | if (gintsts & GINTSTS_OTGINT) | |
812 | dwc2_handle_otg_intr(hsotg); | |
813 | if (gintsts & GINTSTS_CONIDSTSCHNG) | |
814 | dwc2_handle_conn_id_status_change_intr(hsotg); | |
815 | if (gintsts & GINTSTS_DISCONNINT) | |
816 | dwc2_handle_disconnect_intr(hsotg); | |
817 | if (gintsts & GINTSTS_SESSREQINT) | |
818 | dwc2_handle_session_req_intr(hsotg); | |
819 | if (gintsts & GINTSTS_WKUPINT) | |
820 | dwc2_handle_wakeup_detected_intr(hsotg); | |
821 | if (gintsts & GINTSTS_USBSUSP) | |
822 | dwc2_handle_usb_suspend_intr(hsotg); | |
d2521849 SA |
823 | if (gintsts & GINTSTS_LPMTRANRCVD) |
824 | dwc2_handle_lpm_intr(hsotg); | |
56f5b1cf | 825 | |
56f5b1cf PZ |
826 | if (gintsts & GINTSTS_PRTINT) { |
827 | /* | |
828 | * The port interrupt occurs while in device mode with HPRT0 | |
829 | * Port Enable/Disable | |
830 | */ | |
831 | if (dwc2_is_device_mode(hsotg)) { | |
832 | dev_dbg(hsotg->dev, | |
833 | " --Port interrupt received in Device mode--\n"); | |
93571adb DN |
834 | dwc2_handle_usb_port_intr(hsotg); |
835 | retval = IRQ_HANDLED; | |
56f5b1cf PZ |
836 | } |
837 | } | |
838 | ||
56f5b1cf | 839 | out: |
cf54772b | 840 | spin_unlock(&hsotg->lock); |
6aafb003 | 841 | return retval; |
56f5b1cf | 842 | } |