blob: 8dd4bacc6e5ddd1c2125b827b617bf83edc5a445 [file] [log] [blame]
Stephen Boyd0ebf7212012-04-30 20:42:35 -07001/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved.
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002 *
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#include <linux/module.h>
15#include <linux/platform_device.h>
16#include <linux/err.h>
17#include <linux/slab.h>
18#include <linux/io.h>
19#include <linux/mutex.h>
20#include <linux/miscdevice.h>
21#include <linux/fs.h>
22#include <linux/gpio.h>
23#include <linux/kernel.h>
24#include <linux/irq.h>
25#include <linux/ioctl.h>
26#include <linux/delay.h>
27#include <linux/reboot.h>
28#include <linux/debugfs.h>
29#include <linux/completion.h>
30#include <linux/workqueue.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070031#include <asm/mach-types.h>
32#include <asm/uaccess.h>
Anirudh Ghayal33d93bf2011-12-16 10:22:26 +053033#include <linux/mfd/pm8xxx/misc.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070034#include <mach/mdm.h>
35#include <mach/restart.h>
36#include <mach/subsystem_notif.h>
37#include <mach/subsystem_restart.h>
38#include <linux/msm_charm.h>
39#include "msm_watchdog.h"
40#include "devices.h"
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070041
42#define CHARM_MODEM_TIMEOUT 6000
43#define CHARM_HOLD_TIME 4000
44#define CHARM_MODEM_DELTA 100
45
46static void (*power_on_charm)(void);
47static void (*power_down_charm)(void);
48
49static int charm_debug_on;
50static int charm_status_irq;
51static int charm_errfatal_irq;
52static int charm_ready;
53static enum charm_boot_type boot_type = CHARM_NORMAL_BOOT;
54static int charm_boot_status;
55static int charm_ram_dump_status;
56static struct workqueue_struct *charm_queue;
57
58#define CHARM_DBG(...) do { if (charm_debug_on) \
59 pr_info(__VA_ARGS__); \
60 } while (0);
61
62
63DECLARE_COMPLETION(charm_needs_reload);
64DECLARE_COMPLETION(charm_boot);
65DECLARE_COMPLETION(charm_ram_dumps);
66
67static void charm_disable_irqs(void)
68{
69 disable_irq_nosync(charm_errfatal_irq);
70 disable_irq_nosync(charm_status_irq);
71
72}
73
Stephen Boyd0ebf7212012-04-30 20:42:35 -070074static int charm_subsys_shutdown(const struct subsys_desc *crashed_subsys)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070075{
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070076 charm_ready = 0;
Laura Abbottd161daf2011-08-10 17:24:48 -070077 power_down_charm();
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070078 return 0;
79}
80
Stephen Boyd0ebf7212012-04-30 20:42:35 -070081static int charm_subsys_powerup(const struct subsys_desc *crashed_subsys)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070082{
83 power_on_charm();
84 boot_type = CHARM_NORMAL_BOOT;
85 complete(&charm_needs_reload);
86 wait_for_completion(&charm_boot);
87 pr_info("%s: charm modem has been restarted\n", __func__);
88 INIT_COMPLETION(charm_boot);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070089 return charm_boot_status;
90}
91
92static int charm_subsys_ramdumps(int want_dumps,
Stephen Boyd0ebf7212012-04-30 20:42:35 -070093 const struct subsys_desc *crashed_subsys)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070094{
95 charm_ram_dump_status = 0;
96 if (want_dumps) {
97 boot_type = CHARM_RAM_DUMPS;
98 complete(&charm_needs_reload);
99 wait_for_completion(&charm_ram_dumps);
100 INIT_COMPLETION(charm_ram_dumps);
101 power_down_charm();
102 }
103 return charm_ram_dump_status;
104}
105
Stephen Boyd0ebf7212012-04-30 20:42:35 -0700106static struct subsys_device *charm_subsys;
107
108static struct subsys_desc charm_subsystem = {
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700109 .shutdown = charm_subsys_shutdown,
110 .ramdump = charm_subsys_ramdumps,
111 .powerup = charm_subsys_powerup,
112 .name = "external_modem",
113};
114
115static int charm_panic_prep(struct notifier_block *this,
116 unsigned long event, void *ptr)
117{
Laura Abbott63215122011-08-25 14:43:01 -0700118 int i;
119
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700120 CHARM_DBG("%s: setting AP2MDM_ERRFATAL high for a non graceful reset\n",
121 __func__);
Laura Abbottd424dc02011-11-04 17:56:34 -0700122 if (get_restart_level() == RESET_SOC)
Anirudh Ghayal33d93bf2011-12-16 10:22:26 +0530123 pm8xxx_stay_on();
Laura Abbottd424dc02011-11-04 17:56:34 -0700124
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700125 charm_disable_irqs();
126 gpio_set_value(AP2MDM_ERRFATAL, 1);
Laura Abbotte31d1332011-08-24 17:12:07 -0700127 gpio_set_value(AP2MDM_WAKEUP, 1);
Laura Abbott63215122011-08-25 14:43:01 -0700128 for (i = CHARM_MODEM_TIMEOUT; i > 0; i -= CHARM_MODEM_DELTA) {
129 pet_watchdog();
130 mdelay(CHARM_MODEM_DELTA);
131 if (gpio_get_value(MDM2AP_STATUS) == 0)
132 break;
133 }
134 if (i <= 0)
135 pr_err("%s: MDM2AP_STATUS never went low\n", __func__);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700136 return NOTIFY_DONE;
137}
138
139static struct notifier_block charm_panic_blk = {
140 .notifier_call = charm_panic_prep,
141};
142
Laura Abbottd42d1202011-07-20 14:49:57 -0700143static int first_boot = 1;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700144
145static long charm_modem_ioctl(struct file *filp, unsigned int cmd,
146 unsigned long arg)
147{
148
149 int status, ret = 0;
150
151 if (_IOC_TYPE(cmd) != CHARM_CODE) {
152 pr_err("%s: invalid ioctl code\n", __func__);
153 return -EINVAL;
154 }
155
156 CHARM_DBG("%s: Entering ioctl cmd = %d\n", __func__, _IOC_NR(cmd));
157 switch (cmd) {
158 case WAKE_CHARM:
159 CHARM_DBG("%s: Powering on\n", __func__);
160 power_on_charm();
161 break;
162 case CHECK_FOR_BOOT:
163 if (gpio_get_value(MDM2AP_STATUS) == 0)
164 put_user(1, (unsigned long __user *) arg);
165 else
166 put_user(0, (unsigned long __user *) arg);
167 break;
168 case NORMAL_BOOT_DONE:
169 CHARM_DBG("%s: check if charm is booted up\n", __func__);
170 get_user(status, (unsigned long __user *) arg);
171 if (status)
172 charm_boot_status = -EIO;
173 else
174 charm_boot_status = 0;
Laura Abbotta267ea92011-07-15 19:53:05 -0700175 charm_ready = 1;
Laura Abbottd42d1202011-07-20 14:49:57 -0700176
Laura Abbott943af572011-08-24 17:28:45 -0700177 gpio_set_value(AP2MDM_KPDPWR_N, 0);
Laura Abbottd42d1202011-07-20 14:49:57 -0700178 if (!first_boot)
179 complete(&charm_boot);
180 else
181 first_boot = 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700182 break;
183 case RAM_DUMP_DONE:
184 CHARM_DBG("%s: charm done collecting RAM dumps\n", __func__);
185 get_user(status, (unsigned long __user *) arg);
186 if (status)
187 charm_ram_dump_status = -EIO;
188 else
189 charm_ram_dump_status = 0;
190 complete(&charm_ram_dumps);
191 break;
192 case WAIT_FOR_RESTART:
193 CHARM_DBG("%s: wait for charm to need images reloaded\n",
194 __func__);
195 ret = wait_for_completion_interruptible(&charm_needs_reload);
196 if (!ret)
197 put_user(boot_type, (unsigned long __user *) arg);
198 INIT_COMPLETION(charm_needs_reload);
199 break;
200 default:
201 pr_err("%s: invalid ioctl cmd = %d\n", __func__, _IOC_NR(cmd));
202 ret = -EINVAL;
203 break;
204 }
205
206 return ret;
207}
208
209static int charm_modem_open(struct inode *inode, struct file *file)
210{
211 return 0;
212}
213
214static const struct file_operations charm_modem_fops = {
215 .owner = THIS_MODULE,
216 .open = charm_modem_open,
217 .unlocked_ioctl = charm_modem_ioctl,
218};
219
220
221struct miscdevice charm_modem_misc = {
222 .minor = MISC_DYNAMIC_MINOR,
223 .name = "mdm",
224 .fops = &charm_modem_fops
225};
226
227
228
229static void charm_status_fn(struct work_struct *work)
230{
231 pr_info("Reseting the charm because status changed\n");
Stephen Boyd0ebf7212012-04-30 20:42:35 -0700232 subsystem_restart_dev(charm_subsys);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700233}
234
235static DECLARE_WORK(charm_status_work, charm_status_fn);
236
237static void charm_fatal_fn(struct work_struct *work)
238{
239 pr_info("Reseting the charm due to an errfatal\n");
Willie Ruan20667832011-08-03 13:36:31 -0700240 if (get_restart_level() == RESET_SOC)
Anirudh Ghayal33d93bf2011-12-16 10:22:26 +0530241 pm8xxx_stay_on();
Stephen Boyd0ebf7212012-04-30 20:42:35 -0700242 subsystem_restart_dev(charm_subsys);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700243}
244
245static DECLARE_WORK(charm_fatal_work, charm_fatal_fn);
246
247static irqreturn_t charm_errfatal(int irq, void *dev_id)
248{
249 CHARM_DBG("%s: charm got errfatal interrupt\n", __func__);
Laura Abbottd161daf2011-08-10 17:24:48 -0700250 if (charm_ready && (gpio_get_value(MDM2AP_STATUS) == 1)) {
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700251 CHARM_DBG("%s: scheduling work now\n", __func__);
252 queue_work(charm_queue, &charm_fatal_work);
253 }
254 return IRQ_HANDLED;
255}
256
257static irqreturn_t charm_status_change(int irq, void *dev_id)
258{
259 CHARM_DBG("%s: charm sent status change interrupt\n", __func__);
260 if ((gpio_get_value(MDM2AP_STATUS) == 0) && charm_ready) {
261 CHARM_DBG("%s: scheduling work now\n", __func__);
262 queue_work(charm_queue, &charm_status_work);
263 } else if (gpio_get_value(MDM2AP_STATUS) == 1) {
264 CHARM_DBG("%s: charm is now ready\n", __func__);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700265 }
266 return IRQ_HANDLED;
267}
268
269static int charm_debug_on_set(void *data, u64 val)
270{
271 charm_debug_on = val;
272 return 0;
273}
274
275static int charm_debug_on_get(void *data, u64 *val)
276{
277 *val = charm_debug_on;
278 return 0;
279}
280
281DEFINE_SIMPLE_ATTRIBUTE(charm_debug_on_fops,
282 charm_debug_on_get,
283 charm_debug_on_set, "%llu\n");
284
285static int charm_debugfs_init(void)
286{
287 struct dentry *dent;
288
289 dent = debugfs_create_dir("charm_dbg", 0);
290 if (IS_ERR(dent))
291 return PTR_ERR(dent);
292
293 debugfs_create_file("debug_on", 0644, dent, NULL,
294 &charm_debug_on_fops);
295 return 0;
296}
297
298static int gsbi9_uart_notifier_cb(struct notifier_block *this,
299 unsigned long code, void *_cmd)
300{
301 switch (code) {
302 case SUBSYS_AFTER_SHUTDOWN:
303 platform_device_unregister(msm_device_uart_gsbi9);
304 msm_device_uart_gsbi9 = msm_add_gsbi9_uart();
305 if (IS_ERR(msm_device_uart_gsbi9))
306 pr_err("%s(): Failed to create uart gsbi9 device\n",
307 __func__);
308 default:
309 break;
310 }
311 return NOTIFY_DONE;
312}
313
314static struct notifier_block gsbi9_nb = {
315 .notifier_call = gsbi9_uart_notifier_cb,
316};
317
318static int __init charm_modem_probe(struct platform_device *pdev)
319{
320 int ret, irq;
321 struct charm_platform_data *d = pdev->dev.platform_data;
322
323 gpio_request(AP2MDM_STATUS, "AP2MDM_STATUS");
324 gpio_request(AP2MDM_ERRFATAL, "AP2MDM_ERRFATAL");
325 gpio_request(AP2MDM_KPDPWR_N, "AP2MDM_KPDPWR_N");
326 gpio_request(AP2MDM_PMIC_RESET_N, "AP2MDM_PMIC_RESET_N");
327 gpio_request(MDM2AP_STATUS, "MDM2AP_STATUS");
328 gpio_request(MDM2AP_ERRFATAL, "MDM2AP_ERRFATAL");
Laura Abbottd2a3bb52011-07-21 12:39:22 -0700329 gpio_request(AP2MDM_WAKEUP, "AP2MDM_WAKEUP");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700330
331 gpio_direction_output(AP2MDM_STATUS, 1);
332 gpio_direction_output(AP2MDM_ERRFATAL, 0);
Laura Abbottd2a3bb52011-07-21 12:39:22 -0700333 gpio_direction_output(AP2MDM_WAKEUP, 0);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700334 gpio_direction_input(MDM2AP_STATUS);
335 gpio_direction_input(MDM2AP_ERRFATAL);
336
337 power_on_charm = d->charm_modem_on;
338 power_down_charm = d->charm_modem_off;
339
340 charm_queue = create_singlethread_workqueue("charm_queue");
341 if (!charm_queue) {
342 pr_err("%s: could not create workqueue. All charm \
343 functionality will be disabled\n",
344 __func__);
345 ret = -ENOMEM;
346 goto fatal_err;
347 }
348
349 atomic_notifier_chain_register(&panic_notifier_list, &charm_panic_blk);
350 charm_debugfs_init();
351
Stephen Boyd0ebf7212012-04-30 20:42:35 -0700352 charm_subsys = subsys_register(&charm_subsystem);
353 if (IS_ERR(charm_subsys)) {
354 ret = PTR_ERR(charm_subsys);
355 goto fatal_err;
356 }
Stephen Boyd43b380a2012-09-21 17:34:24 -0700357 subsys_default_online(charm_subsys);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700358
359 irq = platform_get_irq(pdev, 0);
360 if (irq < 0) {
361 pr_err("%s: could not get MDM2AP_ERRFATAL IRQ resource. \
362 error=%d No IRQ will be generated on errfatal.",
363 __func__, irq);
364 goto errfatal_err;
365 }
366
367 ret = request_irq(irq, charm_errfatal,
368 IRQF_TRIGGER_RISING , "charm errfatal", NULL);
369
370 if (ret < 0) {
371 pr_err("%s: MDM2AP_ERRFATAL IRQ#%d request failed with error=%d\
372 . No IRQ will be generated on errfatal.",
373 __func__, irq, ret);
374 goto errfatal_err;
375 }
376 charm_errfatal_irq = irq;
377
378errfatal_err:
379
380 irq = platform_get_irq(pdev, 1);
381 if (irq < 0) {
382 pr_err("%s: could not get MDM2AP_STATUS IRQ resource. \
383 error=%d No IRQ will be generated on status change.",
384 __func__, irq);
385 goto status_err;
386 }
387
388 ret = request_threaded_irq(irq, NULL, charm_status_change,
389 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
390 "charm status", NULL);
391
392 if (ret < 0) {
393 pr_err("%s: MDM2AP_STATUS IRQ#%d request failed with error=%d\
394 . No IRQ will be generated on status change.",
395 __func__, irq, ret);
396 goto status_err;
397 }
398 charm_status_irq = irq;
399
400status_err:
401 subsys_notif_register_notifier("external_modem", &gsbi9_nb);
402
403 pr_info("%s: Registering charm modem\n", __func__);
404
405 return misc_register(&charm_modem_misc);
406
407fatal_err:
408 gpio_free(AP2MDM_STATUS);
409 gpio_free(AP2MDM_ERRFATAL);
410 gpio_free(AP2MDM_KPDPWR_N);
411 gpio_free(AP2MDM_PMIC_RESET_N);
412 gpio_free(MDM2AP_STATUS);
413 gpio_free(MDM2AP_ERRFATAL);
414 return ret;
415
416}
417
418
419static int __devexit charm_modem_remove(struct platform_device *pdev)
420{
421 gpio_free(AP2MDM_STATUS);
422 gpio_free(AP2MDM_ERRFATAL);
423 gpio_free(AP2MDM_KPDPWR_N);
424 gpio_free(AP2MDM_PMIC_RESET_N);
425 gpio_free(MDM2AP_STATUS);
426 gpio_free(MDM2AP_ERRFATAL);
427
428 return misc_deregister(&charm_modem_misc);
429}
430
431static void charm_modem_shutdown(struct platform_device *pdev)
432{
433 int i;
434
435 CHARM_DBG("%s: setting AP2MDM_STATUS low for a graceful restart\n",
436 __func__);
437
438 charm_disable_irqs();
439
440 gpio_set_value(AP2MDM_STATUS, 0);
Laura Abbottd2a3bb52011-07-21 12:39:22 -0700441 gpio_set_value(AP2MDM_WAKEUP, 1);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700442
443 for (i = CHARM_MODEM_TIMEOUT; i > 0; i -= CHARM_MODEM_DELTA) {
444 pet_watchdog();
445 msleep(CHARM_MODEM_DELTA);
446 if (gpio_get_value(MDM2AP_STATUS) == 0)
447 break;
448 }
449
450 if (i <= 0) {
451 pr_err("%s: MDM2AP_STATUS never went low.\n",
452 __func__);
453 gpio_direction_output(AP2MDM_PMIC_RESET_N, 1);
454 for (i = CHARM_HOLD_TIME; i > 0; i -= CHARM_MODEM_DELTA) {
455 pet_watchdog();
456 msleep(CHARM_MODEM_DELTA);
457 }
458 gpio_direction_output(AP2MDM_PMIC_RESET_N, 0);
459 }
Laura Abbottd2a3bb52011-07-21 12:39:22 -0700460 gpio_set_value(AP2MDM_WAKEUP, 0);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700461}
462
463static struct platform_driver charm_modem_driver = {
464 .remove = charm_modem_remove,
465 .shutdown = charm_modem_shutdown,
466 .driver = {
467 .name = "charm_modem",
468 .owner = THIS_MODULE
469 },
470};
471
472static int __init charm_modem_init(void)
473{
474 return platform_driver_probe(&charm_modem_driver, charm_modem_probe);
475}
476
477static void __exit charm_modem_exit(void)
478{
479 platform_driver_unregister(&charm_modem_driver);
480}
481
482module_init(charm_modem_init);
483module_exit(charm_modem_exit);
484
485MODULE_LICENSE("GPL v2");
486MODULE_DESCRIPTION("msm8660 charm modem driver");
487MODULE_VERSION("1.0");
488MODULE_ALIAS("charm_modem");