blob: ae5519d0fb8136453664b5c4093e4d0e369acadf [file] [log] [blame]
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001/* Copyright (c) 2011, Code Aurora Forum. 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#include <linux/kernel.h>
14#include <linux/interrupt.h>
15#include <linux/reboot.h>
16#include <linux/workqueue.h>
17#include <linux/io.h>
18#include <linux/jiffies.h>
19#include <linux/stringify.h>
20#include <linux/delay.h>
21#include <linux/module.h>
22
23#include <mach/irqs.h>
24#include <mach/scm.h>
25#include <mach/peripheral-loader.h>
26#include <mach/subsystem_restart.h>
27#include <mach/subsystem_notif.h>
28
29#include "smd_private.h"
30#include "modem_notifier.h"
31#include "ramdump.h"
32
33#define MODEM_HWIO_MSS_RESET_ADDR 0x00902C48
34#define SCM_Q6_NMI_CMD 0x1
35#define MODULE_NAME "subsystem_fatal_8x60"
36#define Q6SS_SOFT_INTR_WAKEUP 0x288A001C
37#define MODEM_WDOG_ENABLE 0x10020008
38#define Q6SS_WDOG_ENABLE 0x28882024
Vikram Mulukutla89549562011-07-28 16:10:41 -070039#define MODEM_CLEANUP_DELAY_MS 20
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070040
41#define SUBSYS_FATAL_DEBUG
42
43#if defined(SUBSYS_FATAL_DEBUG)
44static void debug_crash_modem_fn(struct work_struct *);
45static int reset_modem;
46
47static DECLARE_DELAYED_WORK(debug_crash_modem_work,
48 debug_crash_modem_fn);
49
50module_param(reset_modem, int, 0644);
51#endif
52
53static void do_soc_restart(void);
54
55/* Subsystem restart: QDSP6 data, functions */
56static void q6_fatal_fn(struct work_struct *);
57static DECLARE_WORK(q6_fatal_work, q6_fatal_fn);
58static void *q6_ramdump_dev, *modem_ramdump_dev;
59
60static void q6_fatal_fn(struct work_struct *work)
61{
62 pr_err("%s: Watchdog bite received from Q6!\n", MODULE_NAME);
63 subsystem_restart("lpass");
Vikram Mulukutla93510c42011-08-19 19:04:40 -070064 enable_irq(LPASS_Q6SS_WDOG_EXPIRED);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070065}
66
67static void send_q6_nmi(void)
68{
69 /* Send NMI to QDSP6 via an SCM call. */
70 uint32_t cmd = 0x1;
71 void __iomem *q6_wakeup_intr;
72
73 scm_call(SCM_SVC_UTIL, SCM_Q6_NMI_CMD,
74 &cmd, sizeof(cmd), NULL, 0);
75
76 /* Wakeup the Q6 */
77 q6_wakeup_intr = ioremap_nocache(Q6SS_SOFT_INTR_WAKEUP, 8);
78 writel_relaxed(0x2000, q6_wakeup_intr);
79 iounmap(q6_wakeup_intr);
80 mb();
81
Vikram Mulukutla189724d2011-07-22 13:24:50 -070082 /* Q6 requires atleast 100ms to dump caches etc.*/
83 msleep(100);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070084
85 pr_info("subsystem-fatal-8x60: Q6 NMI was sent.\n");
86}
87
88int subsys_q6_shutdown(const struct subsys_data *crashed_subsys)
89{
90 void __iomem *q6_wdog_addr =
91 ioremap_nocache(Q6SS_WDOG_ENABLE, 8);
92
93 send_q6_nmi();
94 writel_relaxed(0x0, q6_wdog_addr);
95 /* The write needs to go through before the q6 is shutdown. */
96 mb();
97 iounmap(q6_wdog_addr);
98
99 pil_force_shutdown("q6");
100 disable_irq_nosync(LPASS_Q6SS_WDOG_EXPIRED);
101
102 if (get_restart_level() == RESET_SUBSYS_MIXED)
103 smsm_reset_modem(SMSM_RESET);
104
105 return 0;
106}
107
108int subsys_q6_powerup(const struct subsys_data *crashed_subsys)
109{
110 int ret = pil_force_boot("q6");
111 enable_irq(LPASS_Q6SS_WDOG_EXPIRED);
112 return ret;
113}
114
115/* FIXME: Get address, size from PIL */
116static struct ramdump_segment q6_segments[] = { {0x46700000, 0x47F00000 -
117 0x46700000}, {0x28400000, 0x12800} };
118static int subsys_q6_ramdump(int enable,
119 const struct subsys_data *crashed_subsys)
120{
121 if (enable)
122 return do_ramdump(q6_ramdump_dev, q6_segments,
123 ARRAY_SIZE(q6_segments));
124 else
125 return 0;
126}
127
128void subsys_q6_crash_shutdown(const struct subsys_data *crashed_subsys)
129{
130 send_q6_nmi();
131}
132
133/* Subsystem restart: Modem data, functions */
134static void modem_fatal_fn(struct work_struct *);
135static void modem_unlock_timeout(struct work_struct *work);
136static int modem_notif_handler(struct notifier_block *this,
137 unsigned long code,
138 void *_cmd);
139static DECLARE_WORK(modem_fatal_work, modem_fatal_fn);
140static DECLARE_DELAYED_WORK(modem_unlock_timeout_work,
141 modem_unlock_timeout);
142
143static struct notifier_block modem_notif_nb = {
144 .notifier_call = modem_notif_handler,
145};
146
147static void modem_unlock_timeout(struct work_struct *work)
148{
149 void __iomem *hwio_modem_reset_addr =
150 ioremap_nocache(MODEM_HWIO_MSS_RESET_ADDR, 8);
151 pr_crit("%s: Timeout waiting for modem to unlock.\n", MODULE_NAME);
152
153 /* Set MSS_MODEM_RESET to 0x0 since the unlock didn't work */
154 writel_relaxed(0x0, hwio_modem_reset_addr);
155 /* Write needs to go through before the modem is restarted. */
156 mb();
157 iounmap(hwio_modem_reset_addr);
158
159 subsystem_restart("modem");
Vikram Mulukutla93510c42011-08-19 19:04:40 -0700160 enable_irq(MARM_WDOG_EXPIRED);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700161}
162
163static void modem_fatal_fn(struct work_struct *work)
164{
165 uint32_t modem_state;
166 uint32_t panic_smsm_states = SMSM_RESET | SMSM_SYSTEM_DOWNLOAD;
167 uint32_t reset_smsm_states = SMSM_SYSTEM_REBOOT_USR |
168 SMSM_SYSTEM_PWRDWN_USR;
169
170 pr_err("%s: Watchdog bite received from modem!\n", MODULE_NAME);
171
172 modem_state = smsm_get_state(SMSM_MODEM_STATE);
173 pr_err("%s: Modem SMSM state = 0x%x!", MODULE_NAME, modem_state);
174
175 if (modem_state == 0 || modem_state & panic_smsm_states) {
176
177 subsystem_restart("modem");
Vikram Mulukutla93510c42011-08-19 19:04:40 -0700178 enable_irq(MARM_WDOG_EXPIRED);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700179
180 } else if (modem_state & reset_smsm_states) {
181
182 pr_err("%s: User-invoked system reset/powerdown.",
183 MODULE_NAME);
184 do_soc_restart();
185
186 } else {
187
188 int ret;
189 void *hwio_modem_reset_addr =
190 ioremap_nocache(MODEM_HWIO_MSS_RESET_ADDR, 8);
191
192 pr_err("%s: Modem AHB locked up.\n", MODULE_NAME);
193 pr_err("%s: Trying to free up modem!\n", MODULE_NAME);
194
195 writel(0x3, hwio_modem_reset_addr);
196
197 /* If we are still alive after 6 seconds (allowing for
198 * the 5-second-delayed-panic-reboot), modem is either
199 * still wedged or SMSM didn't come through. Force panic
200 * in that case.
201 */
202 ret = schedule_delayed_work(&modem_unlock_timeout_work,
203 msecs_to_jiffies(6000));
204
205 iounmap(hwio_modem_reset_addr);
206 }
207}
208
209static int modem_notif_handler(struct notifier_block *this,
210 unsigned long code,
211 void *_cmd)
212{
213 if (code == MODEM_NOTIFIER_START_RESET) {
214
215 pr_err("%s: Modem error fatal'ed.", MODULE_NAME);
216 subsystem_restart("modem");
217 }
218 return NOTIFY_DONE;
219}
220
221static int subsys_modem_shutdown(const struct subsys_data *crashed_subsys)
222{
223 void __iomem *modem_wdog_addr;
224 int smsm_notif_unregistered = 0;
225
226 /* If the modem didn't already crash, setting SMSM_RESET
227 * here will help flush caches etc. Unregister for SMSM
228 * notifications to prevent unnecessary secondary calls to
229 * subsystem_restart.
230 */
231 if (!(smsm_get_state(SMSM_MODEM_STATE) & SMSM_RESET)) {
232 modem_unregister_notifier(&modem_notif_nb);
233 smsm_notif_unregistered = 1;
234 smsm_reset_modem(SMSM_RESET);
235 }
236
237 /* Disable the modem watchdog to allow clean modem bootup */
238 modem_wdog_addr = ioremap_nocache(MODEM_WDOG_ENABLE, 8);
239 writel_relaxed(0x0, modem_wdog_addr);
240
241 /*
242 * The write above needs to go through before the modem is
243 * powered up again (subsystem restart).
244 */
245 mb();
246 iounmap(modem_wdog_addr);
247
Vikram Mulukutla89549562011-07-28 16:10:41 -0700248 /* Wait here to allow the modem to clean up caches etc. */
249 msleep(MODEM_CLEANUP_DELAY_MS);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700250 pil_force_shutdown("modem");
251 disable_irq_nosync(MARM_WDOG_EXPIRED);
252
253 /* Re-register for SMSM notifications if necessary */
254 if (smsm_notif_unregistered)
255 modem_register_notifier(&modem_notif_nb);
256
257
258 return 0;
259}
260
261static int subsys_modem_powerup(const struct subsys_data *crashed_subsys)
262{
263 int ret;
264
265 ret = pil_force_boot("modem");
266 enable_irq(MARM_WDOG_EXPIRED);
267
268 return ret;
269}
270
271/* FIXME: Get address, size from PIL */
272static struct ramdump_segment modem_segments[] = {
273 {0x42F00000, 0x46000000 - 0x42F00000} };
274
275static int subsys_modem_ramdump(int enable,
276 const struct subsys_data *crashed_subsys)
277{
278 if (enable)
279 return do_ramdump(modem_ramdump_dev, modem_segments,
280 ARRAY_SIZE(modem_segments));
281 else
282 return 0;
283}
284
285static void subsys_modem_crash_shutdown(
286 const struct subsys_data *crashed_subsys)
287{
288 /* If modem hasn't already crashed, send SMSM_RESET. */
289 if (!(smsm_get_state(SMSM_MODEM_STATE) & SMSM_RESET)) {
290 modem_unregister_notifier(&modem_notif_nb);
291 smsm_reset_modem(SMSM_RESET);
292 }
293
294 /* Wait for 5ms to allow the modem to clean up caches etc. */
295 usleep(5000);
296}
297
298/* Non-subsystem-specific functions */
299static void do_soc_restart(void)
300{
301 pr_err("%s: Rebooting SoC..\n", MODULE_NAME);
302 kernel_restart(NULL);
303}
304
305static irqreturn_t subsys_wdog_bite_irq(int irq, void *dev_id)
306{
307 int ret;
308
309 switch (irq) {
310
311 case MARM_WDOG_EXPIRED:
312 ret = schedule_work(&modem_fatal_work);
313 disable_irq_nosync(MARM_WDOG_EXPIRED);
314 break;
315
316 case LPASS_Q6SS_WDOG_EXPIRED:
317 ret = schedule_work(&q6_fatal_work);
318 disable_irq_nosync(LPASS_Q6SS_WDOG_EXPIRED);
319 break;
320
321 default:
322 pr_err("%s: %s: Unknown IRQ!\n", MODULE_NAME, __func__);
323 }
324
325 return IRQ_HANDLED;
326}
327
328static struct subsys_data subsys_8x60_q6 = {
329 .name = "lpass",
330 .shutdown = subsys_q6_shutdown,
331 .powerup = subsys_q6_powerup,
332 .ramdump = subsys_q6_ramdump,
333 .crash_shutdown = subsys_q6_crash_shutdown
334};
335
336static struct subsys_data subsys_8x60_modem = {
337 .name = "modem",
338 .shutdown = subsys_modem_shutdown,
339 .powerup = subsys_modem_powerup,
340 .ramdump = subsys_modem_ramdump,
341 .crash_shutdown = subsys_modem_crash_shutdown
342};
343
344static int __init subsystem_restart_8x60_init(void)
345{
346 ssr_register_subsystem(&subsys_8x60_modem);
347 ssr_register_subsystem(&subsys_8x60_q6);
348
349 return 0;
350}
351
352static int __init subsystem_fatal_init(void)
353{
354 int ret;
355
356 /* Need to listen for SMSM_RESET always */
357 modem_register_notifier(&modem_notif_nb);
358
359#if defined(SUBSYS_FATAL_DEBUG)
360 schedule_delayed_work(&debug_crash_modem_work, msecs_to_jiffies(5000));
361#endif
362
363 ret = request_irq(MARM_WDOG_EXPIRED, subsys_wdog_bite_irq,
364 IRQF_TRIGGER_RISING, "modem_wdog", NULL);
365
366 if (ret < 0) {
367 pr_err("%s: Unable to request MARM_WDOG_EXPIRED irq.",
368 __func__);
369 goto out;
370 }
371
372 ret = request_irq(LPASS_Q6SS_WDOG_EXPIRED, subsys_wdog_bite_irq,
373 IRQF_TRIGGER_RISING, "q6_wdog", NULL);
374
375 if (ret < 0) {
376 pr_err("%s: Unable to request LPASS_Q6SS_WDOG_EXPIRED irq.",
377 __func__);
378 goto out;
379 }
380
381 q6_ramdump_dev = create_ramdump_device("lpass");
382
383 if (!q6_ramdump_dev) {
384 ret = -ENOMEM;
385 goto out;
386 }
387
388 modem_ramdump_dev = create_ramdump_device("modem");
389
390 if (!modem_ramdump_dev) {
391 ret = -ENOMEM;
392 goto out;
393 }
394
395 ret = subsystem_restart_8x60_init();
396out:
397 return ret;
398}
399
400static void __exit subsystem_fatal_exit(void)
401{
402 free_irq(MARM_WDOG_EXPIRED, NULL);
403 free_irq(LPASS_Q6SS_WDOG_EXPIRED, NULL);
404}
405
406#ifdef SUBSYS_FATAL_DEBUG
407static void debug_crash_modem_fn(struct work_struct *work)
408{
409 if (reset_modem == 1)
410 smsm_reset_modem(SMSM_RESET);
411 else if (reset_modem == 2)
412 subsystem_restart("lpass");
413
414 reset_modem = 0;
415 schedule_delayed_work(&debug_crash_modem_work, msecs_to_jiffies(1000));
416}
417#endif
418
419module_init(subsystem_fatal_init);
420module_exit(subsystem_fatal_exit);