blob: a2f169974986c04f09b84b7ec6bdf36e28b44caa [file] [log] [blame]
Ameya Thakurffd21b02013-01-30 11:33:22 -08001/* Copyright (c) 2011-2013, The Linux Foundation. All rights reserved.
Joel Kingb6f0f612011-11-01 16:59:14 -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>
31#include <linux/clk.h>
32#include <linux/mfd/pmic8058.h>
Joel King269aa602012-07-23 08:07:35 -070033#include <linux/msm_charm.h>
Joel Kingb6f0f612011-11-01 16:59:14 -070034#include <asm/mach-types.h>
35#include <asm/uaccess.h>
36#include <mach/mdm2.h>
37#include <mach/restart.h>
38#include <mach/subsystem_notif.h>
39#include <mach/subsystem_restart.h>
Joel Kingef390842012-05-23 16:42:48 -070040#include <mach/rpm.h>
Joel King269aa602012-07-23 08:07:35 -070041#include <mach/gpiomux.h>
Joel Kingb6f0f612011-11-01 16:59:14 -070042#include "msm_watchdog.h"
43#include "mdm_private.h"
Joel King30fdd662012-05-07 19:39:29 -070044#include "sysmon.h"
Joel Kingb6f0f612011-11-01 16:59:14 -070045
46#define MDM_MODEM_TIMEOUT 6000
47#define MDM_MODEM_DELTA 100
Joel King52d7fa62012-03-21 08:40:52 -070048#define MDM_BOOT_TIMEOUT 60000L
Joel King733377c2012-06-20 13:07:38 -070049#define MDM_RDUMP_TIMEOUT 120000L
Joel King493b3ac2012-05-14 11:39:45 -070050#define MDM2AP_STATUS_TIMEOUT_MS 60000L
Joel Kingb6f0f612011-11-01 16:59:14 -070051
Ameya Thakurffd21b02013-01-30 11:33:22 -080052/* Allow a maximum device id of this many digits */
53#define MAX_DEVICE_DIGITS 10
Joel Kingb6f0f612011-11-01 16:59:14 -070054#define EXTERNAL_MODEM "external_modem"
Ameya Thakurffd21b02013-01-30 11:33:22 -080055#define SUBSYS_NAME_LENGTH \
56 (sizeof(EXTERNAL_MODEM) + MAX_DEVICE_DIGITS)
Joel Kingb6f0f612011-11-01 16:59:14 -070057
Ameya Thakurffd21b02013-01-30 11:33:22 -080058#define DEVICE_BASE_NAME "mdm"
59#define DEVICE_NAME_LENGTH \
60 (sizeof(DEVICE_BASE_NAME) + MAX_DEVICE_DIGITS)
Joel Kingb6f0f612011-11-01 16:59:14 -070061
Joel King30fdd662012-05-07 19:39:29 -070062#define RD_BUF_SIZE 100
63#define SFR_MAX_RETRIES 10
64#define SFR_RETRY_INTERVAL 1000
65
Joel King269aa602012-07-23 08:07:35 -070066enum gpio_update_config {
67 GPIO_UPDATE_BOOTING_CONFIG = 1,
68 GPIO_UPDATE_RUNNING_CONFIG,
69};
Ameya Thakurffd21b02013-01-30 11:33:22 -080070
71struct mdm_device {
72 struct list_head link;
73 struct mdm_modem_drv mdm_data;
74
75 int mdm2ap_status_valid_old_config;
76 struct gpiomux_setting mdm2ap_status_old_config;
77 int first_boot;
78 struct workqueue_struct *mdm_queue;
79 struct workqueue_struct *mdm_sfr_queue;
80 unsigned int dump_timeout_ms;
81
82 char subsys_name[SUBSYS_NAME_LENGTH];
83 struct subsys_desc mdm_subsys;
84 struct subsys_device *mdm_subsys_dev;
85
86 char device_name[DEVICE_NAME_LENGTH];
87 struct miscdevice misc_device;
88
89 struct completion mdm_needs_reload;
90 struct completion mdm_boot;
91 struct completion mdm_ram_dumps;
92 int mdm_errfatal_irq;
93 int mdm_status_irq;
94 int mdm_pblrdy_irq;
95
96 struct delayed_work mdm2ap_status_check_work;
97 struct work_struct mdm_status_work;
98 struct work_struct sfr_reason_work;
99
100 struct notifier_block mdm_panic_blk;
101
102 int ssr_started_internally;
103};
104
105static struct list_head mdm_devices;
106static DEFINE_SPINLOCK(mdm_devices_lock);
107
108static int ssr_count;
109static DEFINE_SPINLOCK(ssr_lock);
110
111static unsigned int mdm_debug_mask;
112int vddmin_gpios_sent;
113static struct mdm_ops *mdm_ops;
114
115static void mdm_device_list_add(struct mdm_device *mdev)
116{
117 unsigned long flags;
118
119 spin_lock_irqsave(&mdm_devices_lock, flags);
120 list_add_tail(&mdev->link, &mdm_devices);
121 spin_unlock_irqrestore(&mdm_devices_lock, flags);
122}
123
124static void mdm_device_list_remove(struct mdm_device *mdev)
125{
126 unsigned long flags;
127 struct mdm_device *lmdev, *tmp;
128
129 spin_lock_irqsave(&mdm_devices_lock, flags);
130 list_for_each_entry_safe(lmdev, tmp, &mdm_devices, link) {
131 if (mdev && mdev == lmdev) {
132 pr_debug("%s: removing device id %d\n",
133 __func__, mdev->mdm_data.device_id);
134 list_del(&mdev->link);
135 break;
136 }
137 }
138 spin_unlock_irqrestore(&mdm_devices_lock, flags);
139}
140
Ameya Thakurffd21b02013-01-30 11:33:22 -0800141/* If the platform's cascading_ssr flag is set, the subsystem
142 * restart module will restart the other modems so stop
143 * monitoring them as well.
144 * This function can be called from interrupt context.
145 */
146static void mdm_start_ssr(struct mdm_device *mdev)
147{
148 unsigned long flags;
149 int start_ssr = 1;
150
151 spin_lock_irqsave(&ssr_lock, flags);
152 if (mdev->mdm_data.pdata->cascading_ssr &&
153 ssr_count > 0) {
154 start_ssr = 0;
155 } else {
156 ssr_count++;
157 mdev->ssr_started_internally = 1;
158 }
159 spin_unlock_irqrestore(&ssr_lock, flags);
160
161 if (start_ssr) {
162 atomic_set(&mdev->mdm_data.mdm_ready, 0);
163 pr_info("%s: Resetting mdm id %d due to mdm error\n",
164 __func__, mdev->mdm_data.device_id);
165 subsystem_restart_dev(mdev->mdm_subsys_dev);
166 } else {
167 pr_info("%s: Another modem is already in SSR\n",
168 __func__);
169 }
170}
171
172/* Increment the reference count to handle the case where
173 * subsystem restart is initiated by the SSR service.
174 */
175static void mdm_ssr_started(struct mdm_device *mdev)
176{
177 unsigned long flags;
178
179 spin_lock_irqsave(&ssr_lock, flags);
180 ssr_count++;
181 atomic_set(&mdev->mdm_data.mdm_ready, 0);
182 spin_unlock_irqrestore(&ssr_lock, flags);
183}
184
185/* mdm_ssr_completed assumes that mdm_ssr_started has previously
186 * been called.
187 */
188static void mdm_ssr_completed(struct mdm_device *mdev)
189{
190 unsigned long flags;
191
192 spin_lock_irqsave(&ssr_lock, flags);
193 ssr_count--;
194 if (mdev->ssr_started_internally) {
195 mdev->ssr_started_internally = 0;
196 ssr_count--;
197 }
198
199 if (ssr_count < 0) {
200 pr_err("%s: ssr_count = %d\n",
201 __func__, ssr_count);
202 panic("%s: ssr_count = %d < 0\n",
203 __func__, ssr_count);
204 }
205 spin_unlock_irqrestore(&ssr_lock, flags);
206}
Joel King269aa602012-07-23 08:07:35 -0700207
Joel Kingef390842012-05-23 16:42:48 -0700208static irqreturn_t mdm_vddmin_change(int irq, void *dev_id)
209{
Ameya Thakurffd21b02013-01-30 11:33:22 -0800210 struct mdm_device *mdev = (struct mdm_device *)dev_id;
211 struct mdm_vddmin_resource *vddmin_res;
212 int value;
Joel Kingef390842012-05-23 16:42:48 -0700213
Ameya Thakurffd21b02013-01-30 11:33:22 -0800214 if (!mdev)
215 goto handled;
216
217 vddmin_res = mdev->mdm_data.pdata->vddmin_resource;
218 if (!vddmin_res)
219 goto handled;
220
221 value = gpio_get_value(
222 vddmin_res->mdm2ap_vddmin_gpio);
Joel Kingef390842012-05-23 16:42:48 -0700223 if (value == 0)
Ameya Thakurffd21b02013-01-30 11:33:22 -0800224 pr_info("External Modem id %d entered Vddmin\n",
225 mdev->mdm_data.device_id);
Joel Kingef390842012-05-23 16:42:48 -0700226 else
Ameya Thakurffd21b02013-01-30 11:33:22 -0800227 pr_info("External Modem id %d exited Vddmin\n",
228 mdev->mdm_data.device_id);
229handled:
Joel Kingef390842012-05-23 16:42:48 -0700230 return IRQ_HANDLED;
231}
232
Ameya Thakurffd21b02013-01-30 11:33:22 -0800233/* The vddmin_res resource may not be supported by some platforms. */
Joel Kingef390842012-05-23 16:42:48 -0700234static void mdm_setup_vddmin_gpios(void)
235{
Ameya Thakurffd21b02013-01-30 11:33:22 -0800236 unsigned long flags;
Joel Kingef390842012-05-23 16:42:48 -0700237 struct msm_rpm_iv_pair req;
Ameya Thakurffd21b02013-01-30 11:33:22 -0800238 struct mdm_device *mdev;
Joel Kingef390842012-05-23 16:42:48 -0700239 struct mdm_vddmin_resource *vddmin_res;
240 int irq, ret;
241
Ameya Thakurffd21b02013-01-30 11:33:22 -0800242 spin_lock_irqsave(&mdm_devices_lock, flags);
243 list_for_each_entry(mdev, &mdm_devices, link) {
244 vddmin_res = mdev->mdm_data.pdata->vddmin_resource;
245 if (!vddmin_res)
246 continue;
Joel Kingef390842012-05-23 16:42:48 -0700247
Ameya Thakurffd21b02013-01-30 11:33:22 -0800248 pr_info("Enabling vddmin logging on modem id %d\n",
249 mdev->mdm_data.device_id);
250 req.id = vddmin_res->rpm_id;
251 req.value =
252 ((uint32_t)vddmin_res->ap2mdm_vddmin_gpio & 0x0000FFFF)
253 << 16;
254 req.value |= ((uint32_t)vddmin_res->modes & 0x000000FF) << 8;
255 req.value |= (uint32_t)vddmin_res->drive_strength & 0x000000FF;
Joel Kingef390842012-05-23 16:42:48 -0700256
Ameya Thakurffd21b02013-01-30 11:33:22 -0800257 msm_rpm_set(MSM_RPM_CTX_SET_0, &req, 1);
Joel Kingef390842012-05-23 16:42:48 -0700258
Ameya Thakurffd21b02013-01-30 11:33:22 -0800259 /* Start monitoring low power gpio from mdm */
260 irq = MSM_GPIO_TO_INT(vddmin_res->mdm2ap_vddmin_gpio);
261 if (irq < 0)
262 pr_err("%s: could not get LPM POWER IRQ resource mdm id %d.\n",
263 __func__, mdev->mdm_data.device_id);
264 else {
265 ret = request_threaded_irq(irq, NULL, mdm_vddmin_change,
266 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
267 "mdm lpm", mdev);
268
269 if (ret < 0)
270 pr_err("%s: MDM LPM IRQ#%d request failed with error=%d",
271 __func__, irq, ret);
272 }
Joel Kingef390842012-05-23 16:42:48 -0700273 }
Ameya Thakurffd21b02013-01-30 11:33:22 -0800274 spin_unlock_irqrestore(&mdm_devices_lock, flags);
Joel Kingef390842012-05-23 16:42:48 -0700275 return;
276}
277
Joel King30fdd662012-05-07 19:39:29 -0700278static void mdm_restart_reason_fn(struct work_struct *work)
279{
280 int ret, ntries = 0;
281 char sfr_buf[RD_BUF_SIZE];
Ameya Thakurffd21b02013-01-30 11:33:22 -0800282 struct mdm_platform_data *pdata;
283 struct mdm_device *mdev = container_of(work,
284 struct mdm_device, sfr_reason_work);
Joel King30fdd662012-05-07 19:39:29 -0700285
Ameya Thakurffd21b02013-01-30 11:33:22 -0800286 pdata = mdev->mdm_data.pdata;
Joel King30fdd662012-05-07 19:39:29 -0700287 do {
Ameya Thakurffd21b02013-01-30 11:33:22 -0800288 if (pdata->sysmon_subsys_id_valid)
289 {
290 msleep(SFR_RETRY_INTERVAL);
291 ret = sysmon_get_reason(pdata->sysmon_subsys_id,
Joel King30fdd662012-05-07 19:39:29 -0700292 sfr_buf, sizeof(sfr_buf));
Ameya Thakurffd21b02013-01-30 11:33:22 -0800293 if (ret) {
294 /*
295 * The sysmon device may not have been probed as
296 * yet after the restart.
297 */
298 pr_err("%s: Error retrieving restart reason,"
299 "ret = %d %d/%d tries\n",
300 __func__, ret,
301 ntries + 1,
302 SFR_MAX_RETRIES);
303 } else {
304 pr_err("mdm restart reason: %s\n", sfr_buf);
305 break;
306 }
Joel King30fdd662012-05-07 19:39:29 -0700307 }
308 } while (++ntries < SFR_MAX_RETRIES);
309}
310
Joel King493b3ac2012-05-14 11:39:45 -0700311static void mdm2ap_status_check(struct work_struct *work)
312{
Ameya Thakurffd21b02013-01-30 11:33:22 -0800313 struct mdm_device *mdev =
314 container_of(work, struct mdm_device,
315 mdm2ap_status_check_work.work);
316 struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
Joel King493b3ac2012-05-14 11:39:45 -0700317 /*
318 * If the mdm modem did not pull the MDM2AP_STATUS gpio
319 * high then call subsystem_restart.
320 */
Ameya Thakur43248fd2012-07-10 18:50:52 -0700321 if (!mdm_drv->disable_status_check) {
322 if (gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 0) {
Ameya Thakurffd21b02013-01-30 11:33:22 -0800323 pr_err("%s: MDM2AP_STATUS did not go high on mdm id %d\n",
324 __func__, mdev->mdm_data.device_id);
325 mdm_start_ssr(mdev);
Ameya Thakur43248fd2012-07-10 18:50:52 -0700326 }
Joel King493b3ac2012-05-14 11:39:45 -0700327 }
328}
329
Ameya Thakurffd21b02013-01-30 11:33:22 -0800330static void mdm_update_gpio_configs(struct mdm_device *mdev,
331 enum gpio_update_config gpio_config)
Joel King269aa602012-07-23 08:07:35 -0700332{
Ameya Thakurffd21b02013-01-30 11:33:22 -0800333 struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
334
Joel King269aa602012-07-23 08:07:35 -0700335 /* Some gpio configuration may need updating after modem bootup.*/
336 switch (gpio_config) {
337 case GPIO_UPDATE_RUNNING_CONFIG:
338 if (mdm_drv->pdata->mdm2ap_status_gpio_run_cfg) {
339 if (msm_gpiomux_write(mdm_drv->mdm2ap_status_gpio,
340 GPIOMUX_ACTIVE,
341 mdm_drv->pdata->mdm2ap_status_gpio_run_cfg,
Ameya Thakurffd21b02013-01-30 11:33:22 -0800342 &mdev->mdm2ap_status_old_config))
343 pr_err("%s: failed updating running gpio config mdm id %d\n",
344 __func__, mdev->mdm_data.device_id);
Joel King269aa602012-07-23 08:07:35 -0700345 else
Ameya Thakurffd21b02013-01-30 11:33:22 -0800346 mdev->mdm2ap_status_valid_old_config = 1;
Joel King269aa602012-07-23 08:07:35 -0700347 }
348 break;
349 case GPIO_UPDATE_BOOTING_CONFIG:
Ameya Thakurffd21b02013-01-30 11:33:22 -0800350 if (mdev->mdm2ap_status_valid_old_config) {
Joel King269aa602012-07-23 08:07:35 -0700351 msm_gpiomux_write(mdm_drv->mdm2ap_status_gpio,
352 GPIOMUX_ACTIVE,
Ameya Thakurffd21b02013-01-30 11:33:22 -0800353 &mdev->mdm2ap_status_old_config,
Joel King269aa602012-07-23 08:07:35 -0700354 NULL);
Ameya Thakurffd21b02013-01-30 11:33:22 -0800355 mdev->mdm2ap_status_valid_old_config = 0;
Joel King269aa602012-07-23 08:07:35 -0700356 }
357 break;
358 default:
359 pr_err("%s: called with no config\n", __func__);
360 break;
361 }
362}
363
Pavankumar Kondeti06c6c232013-02-12 18:10:21 +0530364static long mdm_modem_ioctl(struct file *filp, unsigned int cmd,
Joel Kingb6f0f612011-11-01 16:59:14 -0700365 unsigned long arg)
366{
367 int status, ret = 0;
Pavankumar Kondeti06c6c232013-02-12 18:10:21 +0530368 struct mdm_device *mdev = filp->private_data;
Ameya Thakurffd21b02013-01-30 11:33:22 -0800369 struct mdm_modem_drv *mdm_drv;
Ameya Thakurda626422013-04-29 12:18:28 -0700370 struct mdm_device *l_mdev;
Ameya Thakurffd21b02013-01-30 11:33:22 -0800371
Joel Kingb6f0f612011-11-01 16:59:14 -0700372 if (_IOC_TYPE(cmd) != CHARM_CODE) {
Ameya Thakurffd21b02013-01-30 11:33:22 -0800373 pr_err("%s: invalid ioctl code to mdm id %d\n",
374 __func__, mdev->mdm_data.device_id);
Joel Kingb6f0f612011-11-01 16:59:14 -0700375 return -EINVAL;
376 }
377
Ameya Thakurffd21b02013-01-30 11:33:22 -0800378 mdm_drv = &mdev->mdm_data;
379 pr_debug("%s: Entering ioctl cmd = %d, mdm id = %d\n",
380 __func__, _IOC_NR(cmd), mdev->mdm_data.device_id);
Joel Kingb6f0f612011-11-01 16:59:14 -0700381 switch (cmd) {
382 case WAKE_CHARM:
Ameya Thakurffd21b02013-01-30 11:33:22 -0800383 pr_info("%s: Powering on mdm id %d\n",
384 __func__, mdev->mdm_data.device_id);
385 mdm_ops->power_on_mdm_cb(mdm_drv);
Joel Kingb6f0f612011-11-01 16:59:14 -0700386 break;
387 case CHECK_FOR_BOOT:
388 if (gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 0)
389 put_user(1, (unsigned long __user *) arg);
390 else
391 put_user(0, (unsigned long __user *) arg);
392 break;
393 case NORMAL_BOOT_DONE:
Ameya Thakurffd21b02013-01-30 11:33:22 -0800394 pr_debug("%s: check if mdm id %d is booted up\n",
395 __func__, mdev->mdm_data.device_id);
Joel Kingb6f0f612011-11-01 16:59:14 -0700396 get_user(status, (unsigned long __user *) arg);
Joel King2a42f502012-02-03 11:36:25 -0800397 if (status) {
Ameya Thakurffd21b02013-01-30 11:33:22 -0800398 pr_debug("%s: normal boot of mdm id %d failed\n",
399 __func__, mdev->mdm_data.device_id);
Joel Kingb6f0f612011-11-01 16:59:14 -0700400 mdm_drv->mdm_boot_status = -EIO;
Joel King2a42f502012-02-03 11:36:25 -0800401 } else {
Ameya Thakurffd21b02013-01-30 11:33:22 -0800402 pr_info("%s: normal boot of mdm id %d done\n",
403 __func__, mdev->mdm_data.device_id);
Joel Kingb6f0f612011-11-01 16:59:14 -0700404 mdm_drv->mdm_boot_status = 0;
Joel King2a42f502012-02-03 11:36:25 -0800405 }
Ameya Thakurffd21b02013-01-30 11:33:22 -0800406 atomic_set(&mdm_drv->mdm_ready, 1);
Joel Kingb6f0f612011-11-01 16:59:14 -0700407
Ameya Thakurffd21b02013-01-30 11:33:22 -0800408 if (mdm_ops->normal_boot_done_cb != NULL)
409 mdm_ops->normal_boot_done_cb(mdm_drv);
Joel Kingb6f0f612011-11-01 16:59:14 -0700410
Ameya Thakurffd21b02013-01-30 11:33:22 -0800411 if (!mdev->first_boot)
412 complete(&mdev->mdm_boot);
Joel Kingb6f0f612011-11-01 16:59:14 -0700413 else
Ameya Thakurffd21b02013-01-30 11:33:22 -0800414 mdev->first_boot = 0;
Joel King493b3ac2012-05-14 11:39:45 -0700415
Joel King269aa602012-07-23 08:07:35 -0700416 /* If successful, start a timer to check that the mdm2ap_status
417 * gpio goes high.
Joel King493b3ac2012-05-14 11:39:45 -0700418 */
Joel King269aa602012-07-23 08:07:35 -0700419 if (!status && gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 0)
Ameya Thakurffd21b02013-01-30 11:33:22 -0800420 schedule_delayed_work(&mdev->mdm2ap_status_check_work,
Joel King493b3ac2012-05-14 11:39:45 -0700421 msecs_to_jiffies(MDM2AP_STATUS_TIMEOUT_MS));
Joel Kingb6f0f612011-11-01 16:59:14 -0700422 break;
423 case RAM_DUMP_DONE:
Joel King2a42f502012-02-03 11:36:25 -0800424 pr_debug("%s: mdm done collecting RAM dumps\n", __func__);
Joel Kingb6f0f612011-11-01 16:59:14 -0700425 get_user(status, (unsigned long __user *) arg);
426 if (status)
427 mdm_drv->mdm_ram_dump_status = -EIO;
Joel King2a42f502012-02-03 11:36:25 -0800428 else {
429 pr_info("%s: ramdump collection completed\n", __func__);
Joel Kingb6f0f612011-11-01 16:59:14 -0700430 mdm_drv->mdm_ram_dump_status = 0;
Joel King2a42f502012-02-03 11:36:25 -0800431 }
Ameya Thakurffd21b02013-01-30 11:33:22 -0800432 complete(&mdev->mdm_ram_dumps);
Joel Kingb6f0f612011-11-01 16:59:14 -0700433 break;
434 case WAIT_FOR_RESTART:
Joel King2a42f502012-02-03 11:36:25 -0800435 pr_debug("%s: wait for mdm to need images reloaded\n",
Joel Kingb6f0f612011-11-01 16:59:14 -0700436 __func__);
Ameya Thakurffd21b02013-01-30 11:33:22 -0800437 ret = wait_for_completion_interruptible(
438 &mdev->mdm_needs_reload);
Joel Kingb6f0f612011-11-01 16:59:14 -0700439 if (!ret)
440 put_user(mdm_drv->boot_type,
441 (unsigned long __user *) arg);
Ameya Thakurffd21b02013-01-30 11:33:22 -0800442 init_completion(&mdev->mdm_needs_reload);
Joel Kingb6f0f612011-11-01 16:59:14 -0700443 break;
Joel Kinge92eb872012-05-06 09:30:24 -0700444 case GET_DLOAD_STATUS:
445 pr_debug("getting status of mdm2ap_errfatal_gpio\n");
446 if (gpio_get_value(mdm_drv->mdm2ap_errfatal_gpio) == 1 &&
Ameya Thakurffd21b02013-01-30 11:33:22 -0800447 !atomic_read(&mdm_drv->mdm_ready))
Joel Kinge92eb872012-05-06 09:30:24 -0700448 put_user(1, (unsigned long __user *) arg);
449 else
450 put_user(0, (unsigned long __user *) arg);
451 break;
Ameya Thakur43248fd2012-07-10 18:50:52 -0700452 case IMAGE_UPGRADE:
453 pr_debug("%s Image upgrade ioctl recieved\n", __func__);
454 if (mdm_drv->pdata->image_upgrade_supported &&
Ameya Thakurffd21b02013-01-30 11:33:22 -0800455 mdm_ops->image_upgrade_cb) {
Ameya Thakurda626422013-04-29 12:18:28 -0700456 list_for_each_entry(l_mdev, &mdm_devices, link) {
457 if (l_mdev != mdev) {
458 pr_debug("%s:setting mdm_rdy to false",
459 __func__);
460 atomic_set(&l_mdev->mdm_data.mdm_ready,
461 0);
462 }
463 }
Ameya Thakur43248fd2012-07-10 18:50:52 -0700464 get_user(status, (unsigned long __user *) arg);
Ameya Thakurffd21b02013-01-30 11:33:22 -0800465 mdm_ops->image_upgrade_cb(mdm_drv, status);
Ameya Thakur43248fd2012-07-10 18:50:52 -0700466 } else
467 pr_debug("%s Image upgrade not supported\n", __func__);
468 break;
agathon.jungb0c0c6d2012-09-26 11:36:51 -0700469 case SHUTDOWN_CHARM:
Ameya Thakur2bf90852013-02-08 14:19:15 -0800470 if (!mdm_drv->pdata->send_shdn ||
471 !mdm_drv->pdata->sysmon_subsys_id_valid) {
472 pr_debug("%s shutdown not supported for this mdm\n",
473 __func__);
Ameya Thakurffd21b02013-01-30 11:33:22 -0800474 break;
Ameya Thakur2bf90852013-02-08 14:19:15 -0800475 }
Ameya Thakurffd21b02013-01-30 11:33:22 -0800476 atomic_set(&mdm_drv->mdm_ready, 0);
agathon.jungb0c0c6d2012-09-26 11:36:51 -0700477 if (mdm_debug_mask & MDM_DEBUG_MASK_SHDN_LOG)
478 pr_info("Sending shutdown request to mdm\n");
Ameya Thakur2bf90852013-02-08 14:19:15 -0800479 ret = sysmon_send_shutdown(mdm_drv->pdata->sysmon_subsys_id);
agathon.jungb0c0c6d2012-09-26 11:36:51 -0700480 if (ret)
Ameya Thakur2bf90852013-02-08 14:19:15 -0800481 pr_err("%s:Graceful shutdown of mdm failed, ret = %d\n",
agathon.jungb0c0c6d2012-09-26 11:36:51 -0700482 __func__, ret);
Taniya Das0fcd0e32013-03-19 11:12:35 +0530483 put_user(ret, (unsigned long __user *) arg);
agathon.jungb0c0c6d2012-09-26 11:36:51 -0700484 break;
Joel Kingb6f0f612011-11-01 16:59:14 -0700485 default:
486 pr_err("%s: invalid ioctl cmd = %d\n", __func__, _IOC_NR(cmd));
487 ret = -EINVAL;
488 break;
489 }
Joel Kingb6f0f612011-11-01 16:59:14 -0700490 return ret;
491}
492
Joel Kingb6f0f612011-11-01 16:59:14 -0700493static void mdm_status_fn(struct work_struct *work)
494{
Ameya Thakurffd21b02013-01-30 11:33:22 -0800495 struct mdm_device *mdev =
496 container_of(work, struct mdm_device, mdm_status_work);
497 struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
Vamsi Krishna33925632011-12-13 15:43:09 -0800498 int value = gpio_get_value(mdm_drv->mdm2ap_status_gpio);
499
Joel King2a42f502012-02-03 11:36:25 -0800500 pr_debug("%s: status:%d\n", __func__, value);
Ameya Thakurffd21b02013-01-30 11:33:22 -0800501 if (atomic_read(&mdm_drv->mdm_ready) && mdm_ops->status_cb)
502 mdm_ops->status_cb(mdm_drv, value);
Joel King269aa602012-07-23 08:07:35 -0700503
504 /* Update gpio configuration to "running" config. */
Ameya Thakurffd21b02013-01-30 11:33:22 -0800505 mdm_update_gpio_configs(mdev, GPIO_UPDATE_RUNNING_CONFIG);
Joel Kingb6f0f612011-11-01 16:59:14 -0700506}
507
Ameya Thakurffd21b02013-01-30 11:33:22 -0800508static void mdm_disable_irqs(struct mdm_device *mdev)
Joel Kingb6f0f612011-11-01 16:59:14 -0700509{
Ameya Thakurffd21b02013-01-30 11:33:22 -0800510 if (!mdev)
511 return;
512 disable_irq_nosync(mdev->mdm_errfatal_irq);
513 disable_irq_nosync(mdev->mdm_status_irq);
514 disable_irq_nosync(mdev->mdm_pblrdy_irq);
Joel Kingb6f0f612011-11-01 16:59:14 -0700515}
516
517static irqreturn_t mdm_errfatal(int irq, void *dev_id)
518{
Ameya Thakurffd21b02013-01-30 11:33:22 -0800519 struct mdm_modem_drv *mdm_drv;
520 struct mdm_device *mdev = (struct mdm_device *)dev_id;
521 if (!mdev)
522 return IRQ_HANDLED;
523
524 pr_debug("%s: mdm id %d sent errfatal interrupt\n",
525 __func__, mdev->mdm_data.device_id);
526 mdm_drv = &mdev->mdm_data;
527 if (atomic_read(&mdm_drv->mdm_ready) &&
Joel Kingb6f0f612011-11-01 16:59:14 -0700528 (gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 1)) {
Ameya Thakurffd21b02013-01-30 11:33:22 -0800529 pr_info("%s: Received err fatal from mdm id %d\n",
530 __func__, mdev->mdm_data.device_id);
531 mdm_start_ssr(mdev);
Joel Kingb6f0f612011-11-01 16:59:14 -0700532 }
533 return IRQ_HANDLED;
534}
535
Ameya Thakurffd21b02013-01-30 11:33:22 -0800536/* set the mdm_device as the file's private data */
Joel Kingb6f0f612011-11-01 16:59:14 -0700537static int mdm_modem_open(struct inode *inode, struct file *file)
538{
Pavankumar Kondeti06c6c232013-02-12 18:10:21 +0530539 struct miscdevice *misc = file->private_data;
540 struct mdm_device *mdev = container_of(misc,
541 struct mdm_device, misc_device);
542
543 file->private_data = mdev;
Joel Kingb6f0f612011-11-01 16:59:14 -0700544 return 0;
545}
546
Joel Kingb6f0f612011-11-01 16:59:14 -0700547static int mdm_panic_prep(struct notifier_block *this,
548 unsigned long event, void *ptr)
549{
550 int i;
Ameya Thakurffd21b02013-01-30 11:33:22 -0800551 struct mdm_modem_drv *mdm_drv;
552 struct mdm_device *mdev =
553 container_of(this, struct mdm_device, mdm_panic_blk);
554
555 mdm_drv = &mdev->mdm_data;
Joel Kingb6f0f612011-11-01 16:59:14 -0700556
Joel King2a42f502012-02-03 11:36:25 -0800557 pr_debug("%s: setting AP2MDM_ERRFATAL high for a non graceful reset\n",
Joel Kingb6f0f612011-11-01 16:59:14 -0700558 __func__);
Ameya Thakurffd21b02013-01-30 11:33:22 -0800559 mdm_disable_irqs(mdev);
Joel Kingb6f0f612011-11-01 16:59:14 -0700560 gpio_set_value(mdm_drv->ap2mdm_errfatal_gpio, 1);
561
Joel Kingb6f0f612011-11-01 16:59:14 -0700562 for (i = MDM_MODEM_TIMEOUT; i > 0; i -= MDM_MODEM_DELTA) {
563 pet_watchdog();
564 mdelay(MDM_MODEM_DELTA);
565 if (gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 0)
566 break;
567 }
Joel Kinge92eb872012-05-06 09:30:24 -0700568 if (i <= 0) {
Joel Kingb6f0f612011-11-01 16:59:14 -0700569 pr_err("%s: MDM2AP_STATUS never went low\n", __func__);
Joel Kinge92eb872012-05-06 09:30:24 -0700570 /* Reset the modem so that it will go into download mode. */
Ameya Thakurffd21b02013-01-30 11:33:22 -0800571 if (mdm_drv && mdm_ops->atomic_reset_mdm_cb)
572 mdm_ops->atomic_reset_mdm_cb(mdm_drv);
Joel Kinge92eb872012-05-06 09:30:24 -0700573 }
Joel Kingb6f0f612011-11-01 16:59:14 -0700574 return NOTIFY_DONE;
575}
576
Joel Kingb6f0f612011-11-01 16:59:14 -0700577static irqreturn_t mdm_status_change(int irq, void *dev_id)
578{
Ameya Thakurffd21b02013-01-30 11:33:22 -0800579 struct mdm_modem_drv *mdm_drv;
580 struct mdm_device *mdev = (struct mdm_device *)dev_id;
581 int value;
582 if (!mdev)
583 return IRQ_HANDLED;
584
585 mdm_drv = &mdev->mdm_data;
586 value = gpio_get_value(mdm_drv->mdm2ap_status_gpio);
Joel Kinge92eb872012-05-06 09:30:24 -0700587
agathon.jungb0c0c6d2012-09-26 11:36:51 -0700588 if ((mdm_debug_mask & MDM_DEBUG_MASK_SHDN_LOG) && (value == 0))
589 pr_info("%s: mdm2ap_status went low\n", __func__);
590
Ameya Thakurffd21b02013-01-30 11:33:22 -0800591 pr_debug("%s: mdm id %d sent status change interrupt\n",
592 __func__, mdev->mdm_data.device_id);
593 if (value == 0 && atomic_read(&mdm_drv->mdm_ready)) {
594 pr_info("%s: unexpected reset external modem id %d\n",
595 __func__, mdev->mdm_data.device_id);
Joel Kinge92eb872012-05-06 09:30:24 -0700596 mdm_drv->mdm_unexpected_reset_occurred = 1;
Ameya Thakurffd21b02013-01-30 11:33:22 -0800597 mdm_start_ssr(mdev);
Joel Kinge92eb872012-05-06 09:30:24 -0700598 } else if (value == 1) {
Ameya Thakurffd21b02013-01-30 11:33:22 -0800599 cancel_delayed_work(&mdev->mdm2ap_status_check_work);
600 pr_info("%s: status = 1: mdm id %d is now ready\n",
601 __func__, mdev->mdm_data.device_id);
602 queue_work(mdev->mdm_queue, &mdev->mdm_status_work);
Joel Kinge92eb872012-05-06 09:30:24 -0700603 }
Joel Kingb6f0f612011-11-01 16:59:14 -0700604 return IRQ_HANDLED;
605}
606
Vamsi Krishnac6dcd5e2012-05-09 15:38:01 -0700607static irqreturn_t mdm_pblrdy_change(int irq, void *dev_id)
608{
Ameya Thakurffd21b02013-01-30 11:33:22 -0800609 struct mdm_modem_drv *mdm_drv;
610 struct mdm_device *mdev = (struct mdm_device *)dev_id;
611 if (!mdev)
612 return IRQ_HANDLED;
Vamsi Krishnac6dcd5e2012-05-09 15:38:01 -0700613
Ameya Thakurffd21b02013-01-30 11:33:22 -0800614 mdm_drv = &mdev->mdm_data;
615 pr_info("%s: mdm id %d: pbl ready:%d\n",
616 __func__, mdev->mdm_data.device_id,
617 gpio_get_value(mdm_drv->mdm2ap_pblrdy));
Vamsi Krishnac6dcd5e2012-05-09 15:38:01 -0700618 return IRQ_HANDLED;
619}
620
Stephen Boyd0ebf7212012-04-30 20:42:35 -0700621static int mdm_subsys_shutdown(const struct subsys_desc *crashed_subsys)
Joel Kingb6f0f612011-11-01 16:59:14 -0700622{
Ameya Thakurffd21b02013-01-30 11:33:22 -0800623 struct mdm_device *mdev =
624 container_of(crashed_subsys, struct mdm_device, mdm_subsys);
625 struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
626
627 pr_debug("%s: ssr on modem id %d\n", __func__,
628 mdev->mdm_data.device_id);
629
630 mdm_ssr_started(mdev);
631 cancel_delayed_work(&mdev->mdm2ap_status_check_work);
Joel Kingf3cb4872013-02-06 17:20:05 -0800632
633 if (!mdm_drv->pdata->no_a2m_errfatal_on_ssr)
634 gpio_direction_output(mdm_drv->ap2mdm_errfatal_gpio, 1);
635
Joel King415af512012-02-03 10:22:43 -0800636 if (mdm_drv->pdata->ramdump_delay_ms > 0) {
637 /* Wait for the external modem to complete
638 * its preparation for ramdumps.
639 */
Joel King6e7a3e82012-04-13 17:37:33 -0700640 msleep(mdm_drv->pdata->ramdump_delay_ms);
Joel King415af512012-02-03 10:22:43 -0800641 }
Joel King269aa602012-07-23 08:07:35 -0700642 if (!mdm_drv->mdm_unexpected_reset_occurred) {
Ameya Thakurffd21b02013-01-30 11:33:22 -0800643 mdm_ops->reset_mdm_cb(mdm_drv);
Joel King269aa602012-07-23 08:07:35 -0700644 /* Update gpio configuration to "booting" config. */
Ameya Thakurffd21b02013-01-30 11:33:22 -0800645 mdm_update_gpio_configs(mdev, GPIO_UPDATE_BOOTING_CONFIG);
Joel King269aa602012-07-23 08:07:35 -0700646 } else {
Joel Kinge92eb872012-05-06 09:30:24 -0700647 mdm_drv->mdm_unexpected_reset_occurred = 0;
Joel King269aa602012-07-23 08:07:35 -0700648 }
Joel Kingb6f0f612011-11-01 16:59:14 -0700649 return 0;
650}
651
Stephen Boyd0ebf7212012-04-30 20:42:35 -0700652static int mdm_subsys_powerup(const struct subsys_desc *crashed_subsys)
Joel Kingb6f0f612011-11-01 16:59:14 -0700653{
Ameya Thakurffd21b02013-01-30 11:33:22 -0800654 struct mdm_device *mdev =
655 container_of(crashed_subsys, struct mdm_device,
656 mdm_subsys);
657 struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
658
659 pr_debug("%s: ssr on modem id %d\n",
660 __func__, mdev->mdm_data.device_id);
661
Joel Kingbc48e4c2012-02-27 13:18:52 -0800662 gpio_direction_output(mdm_drv->ap2mdm_errfatal_gpio, 0);
663 gpio_direction_output(mdm_drv->ap2mdm_status_gpio, 1);
Ameya Thakurffd21b02013-01-30 11:33:22 -0800664
665 if (mdm_drv->pdata->ps_hold_delay_ms > 0)
666 msleep(mdm_drv->pdata->ps_hold_delay_ms);
667
668 mdm_ops->power_on_mdm_cb(mdm_drv);
Joel Kingb6f0f612011-11-01 16:59:14 -0700669 mdm_drv->boot_type = CHARM_NORMAL_BOOT;
Ameya Thakurffd21b02013-01-30 11:33:22 -0800670 mdm_ssr_completed(mdev);
671 complete(&mdev->mdm_needs_reload);
672 if (!wait_for_completion_timeout(&mdev->mdm_boot,
Joel King52d7fa62012-03-21 08:40:52 -0700673 msecs_to_jiffies(MDM_BOOT_TIMEOUT))) {
674 mdm_drv->mdm_boot_status = -ETIMEDOUT;
675 pr_info("%s: mdm modem restart timed out.\n", __func__);
Joel King30fdd662012-05-07 19:39:29 -0700676 } else {
Ameya Thakurffd21b02013-01-30 11:33:22 -0800677 pr_info("%s: id %d: mdm modem has been restarted\n",
678 __func__, mdm_drv->device_id);
Joel Kinge92eb872012-05-06 09:30:24 -0700679
Joel King30fdd662012-05-07 19:39:29 -0700680 /* Log the reason for the restart */
Joel Kinge92eb872012-05-06 09:30:24 -0700681 if (mdm_drv->pdata->sfr_query)
Ameya Thakurffd21b02013-01-30 11:33:22 -0800682 queue_work(mdev->mdm_sfr_queue, &mdev->sfr_reason_work);
Joel King30fdd662012-05-07 19:39:29 -0700683 }
Ameya Thakurffd21b02013-01-30 11:33:22 -0800684 init_completion(&mdev->mdm_boot);
Joel Kingb6f0f612011-11-01 16:59:14 -0700685 return mdm_drv->mdm_boot_status;
686}
687
688static int mdm_subsys_ramdumps(int want_dumps,
Stephen Boyd0ebf7212012-04-30 20:42:35 -0700689 const struct subsys_desc *crashed_subsys)
Joel Kingb6f0f612011-11-01 16:59:14 -0700690{
Ameya Thakurffd21b02013-01-30 11:33:22 -0800691 struct mdm_device *mdev =
692 container_of(crashed_subsys, struct mdm_device,
693 mdm_subsys);
694 struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
695
696 pr_debug("%s: ssr on modem id %d\n", __func__,
697 mdev->mdm_data.device_id);
698
Joel Kingb6f0f612011-11-01 16:59:14 -0700699 mdm_drv->mdm_ram_dump_status = 0;
Ameya Thakurffd21b02013-01-30 11:33:22 -0800700 cancel_delayed_work(&mdev->mdm2ap_status_check_work);
Joel Kingb6f0f612011-11-01 16:59:14 -0700701 if (want_dumps) {
702 mdm_drv->boot_type = CHARM_RAM_DUMPS;
Ameya Thakurffd21b02013-01-30 11:33:22 -0800703 complete(&mdev->mdm_needs_reload);
704 if (!wait_for_completion_timeout(&mdev->mdm_ram_dumps,
705 msecs_to_jiffies(mdev->dump_timeout_ms))) {
Joel King52d7fa62012-03-21 08:40:52 -0700706 mdm_drv->mdm_ram_dump_status = -ETIMEDOUT;
Ameya Thakurffd21b02013-01-30 11:33:22 -0800707 pr_err("%s: mdm modem ramdumps timed out.\n",
Joel King52d7fa62012-03-21 08:40:52 -0700708 __func__);
709 } else
710 pr_info("%s: mdm modem ramdumps completed.\n",
711 __func__);
Ameya Thakurffd21b02013-01-30 11:33:22 -0800712 init_completion(&mdev->mdm_ram_dumps);
Joel King269aa602012-07-23 08:07:35 -0700713 if (!mdm_drv->pdata->no_powerdown_after_ramdumps) {
Ameya Thakurffd21b02013-01-30 11:33:22 -0800714 mdm_ops->power_down_mdm_cb(mdm_drv);
Joel King269aa602012-07-23 08:07:35 -0700715 /* Update gpio configuration to "booting" config. */
Ameya Thakurffd21b02013-01-30 11:33:22 -0800716 mdm_update_gpio_configs(mdev,
717 GPIO_UPDATE_BOOTING_CONFIG);
Joel King269aa602012-07-23 08:07:35 -0700718 }
Joel Kingb6f0f612011-11-01 16:59:14 -0700719 }
720 return mdm_drv->mdm_ram_dump_status;
721}
722
Joel King96c96dc2012-07-30 09:06:15 -0700723/* Once the gpios are sent to RPM and debugging
724 * starts, there is no way to stop it without
725 * rebooting the device.
726 */
727static int mdm_debug_mask_set(void *data, u64 val)
Joel Kingb6f0f612011-11-01 16:59:14 -0700728{
Joel King96c96dc2012-07-30 09:06:15 -0700729 if (!vddmin_gpios_sent &&
730 (val & MDM_DEBUG_MASK_VDDMIN_SETUP)) {
731 mdm_setup_vddmin_gpios();
732 vddmin_gpios_sent = 1;
733 }
734
735 mdm_debug_mask = val;
Ameya Thakurffd21b02013-01-30 11:33:22 -0800736 if (mdm_ops->debug_state_changed_cb)
737 mdm_ops->debug_state_changed_cb(mdm_debug_mask);
Joel Kingb6f0f612011-11-01 16:59:14 -0700738 return 0;
739}
740
Joel King96c96dc2012-07-30 09:06:15 -0700741static int mdm_debug_mask_get(void *data, u64 *val)
Joel Kingb6f0f612011-11-01 16:59:14 -0700742{
Joel King96c96dc2012-07-30 09:06:15 -0700743 *val = mdm_debug_mask;
Joel Kingb6f0f612011-11-01 16:59:14 -0700744 return 0;
745}
746
Joel King96c96dc2012-07-30 09:06:15 -0700747DEFINE_SIMPLE_ATTRIBUTE(mdm_debug_mask_fops,
748 mdm_debug_mask_get,
749 mdm_debug_mask_set, "%llu\n");
Joel Kingb6f0f612011-11-01 16:59:14 -0700750
751static int mdm_debugfs_init(void)
752{
753 struct dentry *dent;
754
755 dent = debugfs_create_dir("mdm_dbg", 0);
756 if (IS_ERR(dent))
757 return PTR_ERR(dent);
758
Joel King96c96dc2012-07-30 09:06:15 -0700759 debugfs_create_file("debug_mask", 0644, dent, NULL,
760 &mdm_debug_mask_fops);
Joel Kingb6f0f612011-11-01 16:59:14 -0700761 return 0;
762}
763
Ameya Thakurffd21b02013-01-30 11:33:22 -0800764static const struct file_operations mdm_modem_fops = {
765 .owner = THIS_MODULE,
766 .open = mdm_modem_open,
767 .unlocked_ioctl = mdm_modem_ioctl,
768};
769
770static void mdm_modem_initialize_data(struct platform_device *pdev,
771 struct mdm_device *mdev)
Joel Kingb6f0f612011-11-01 16:59:14 -0700772{
Ameya Thakurffd21b02013-01-30 11:33:22 -0800773 struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
Joel Kingb6f0f612011-11-01 16:59:14 -0700774 struct resource *pres;
775
Ameya Thakurffd21b02013-01-30 11:33:22 -0800776 mdm_drv->pdata = pdev->dev.platform_data;
777 if (pdev->id < 0)
778 mdm_drv->device_id = 0;
779 else
780 mdm_drv->device_id = pdev->id;
781
782 memset((void *)&mdev->mdm_subsys, 0,
783 sizeof(struct subsys_desc));
Joel Kinga0e9ce92013-03-25 10:42:35 -0700784 if (mdm_drv->pdata->subsys_name) {
785 strlcpy(mdev->subsys_name, mdm_drv->pdata->subsys_name,
786 sizeof(mdev->subsys_name));
787 } else {
788 if (mdev->mdm_data.device_id <= 0)
789 snprintf(mdev->subsys_name, sizeof(mdev->subsys_name),
790 "%s", EXTERNAL_MODEM);
791 else
792 snprintf(mdev->subsys_name, sizeof(mdev->subsys_name),
793 "%s.%d", EXTERNAL_MODEM,
794 mdev->mdm_data.device_id);
795 }
Ameya Thakurffd21b02013-01-30 11:33:22 -0800796 mdev->mdm_subsys.shutdown = mdm_subsys_shutdown;
797 mdev->mdm_subsys.ramdump = mdm_subsys_ramdumps;
798 mdev->mdm_subsys.powerup = mdm_subsys_powerup;
799 mdev->mdm_subsys.name = mdev->subsys_name;
800
801 memset((void *)&mdev->misc_device, 0,
802 sizeof(struct miscdevice));
803 if (mdev->mdm_data.device_id <= 0)
804 snprintf(mdev->device_name, sizeof(mdev->device_name),
805 "%s", DEVICE_BASE_NAME);
806 else
807 snprintf(mdev->device_name, sizeof(mdev->device_name),
808 "%s%d", DEVICE_BASE_NAME, mdev->mdm_data.device_id);
809 mdev->misc_device.minor = MISC_DYNAMIC_MINOR;
810 mdev->misc_device.name = mdev->device_name;
811 mdev->misc_device.fops = &mdm_modem_fops;
812
813 memset((void *)&mdev->mdm_panic_blk, 0,
814 sizeof(struct notifier_block));
815 mdev->mdm_panic_blk.notifier_call = mdm_panic_prep;
816 atomic_notifier_chain_register(&panic_notifier_list,
817 &mdev->mdm_panic_blk);
818
Joel Kingb6f0f612011-11-01 16:59:14 -0700819 /* MDM2AP_ERRFATAL */
820 pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
821 "MDM2AP_ERRFATAL");
Ameya Thakure155ece2012-07-09 12:08:37 -0700822 mdm_drv->mdm2ap_errfatal_gpio = pres ? pres->start : -1;
Joel Kingb6f0f612011-11-01 16:59:14 -0700823
824 /* AP2MDM_ERRFATAL */
825 pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
826 "AP2MDM_ERRFATAL");
Ameya Thakure155ece2012-07-09 12:08:37 -0700827 mdm_drv->ap2mdm_errfatal_gpio = pres ? pres->start : -1;
Joel Kingb6f0f612011-11-01 16:59:14 -0700828
829 /* MDM2AP_STATUS */
830 pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
831 "MDM2AP_STATUS");
Ameya Thakure155ece2012-07-09 12:08:37 -0700832 mdm_drv->mdm2ap_status_gpio = pres ? pres->start : -1;
Joel Kingb6f0f612011-11-01 16:59:14 -0700833
834 /* AP2MDM_STATUS */
835 pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
836 "AP2MDM_STATUS");
Ameya Thakure155ece2012-07-09 12:08:37 -0700837 mdm_drv->ap2mdm_status_gpio = pres ? pres->start : -1;
Joel Kingb6f0f612011-11-01 16:59:14 -0700838
839 /* MDM2AP_WAKEUP */
840 pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
841 "MDM2AP_WAKEUP");
Ameya Thakure155ece2012-07-09 12:08:37 -0700842 mdm_drv->mdm2ap_wakeup_gpio = pres ? pres->start : -1;
Joel Kingb6f0f612011-11-01 16:59:14 -0700843
844 /* AP2MDM_WAKEUP */
845 pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
846 "AP2MDM_WAKEUP");
Ameya Thakure155ece2012-07-09 12:08:37 -0700847 mdm_drv->ap2mdm_wakeup_gpio = pres ? pres->start : -1;
Joel Kingb6f0f612011-11-01 16:59:14 -0700848
Joel Kinge92eb872012-05-06 09:30:24 -0700849 /* AP2MDM_SOFT_RESET */
Joel Kingb6f0f612011-11-01 16:59:14 -0700850 pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
Joel Kinge92eb872012-05-06 09:30:24 -0700851 "AP2MDM_SOFT_RESET");
Ameya Thakure155ece2012-07-09 12:08:37 -0700852 mdm_drv->ap2mdm_soft_reset_gpio = pres ? pres->start : -1;
Joel Kingb6f0f612011-11-01 16:59:14 -0700853
854 /* AP2MDM_KPDPWR_N */
855 pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
856 "AP2MDM_KPDPWR_N");
Ameya Thakure155ece2012-07-09 12:08:37 -0700857 mdm_drv->ap2mdm_kpdpwr_n_gpio = pres ? pres->start : -1;
Joel Kingb6f0f612011-11-01 16:59:14 -0700858
Joel Kinge92eb872012-05-06 09:30:24 -0700859 /* AP2MDM_PMIC_PWR_EN */
860 pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
861 "AP2MDM_PMIC_PWR_EN");
Ameya Thakure155ece2012-07-09 12:08:37 -0700862 mdm_drv->ap2mdm_pmic_pwr_en_gpio = pres ? pres->start : -1;
Joel Kinge92eb872012-05-06 09:30:24 -0700863
Vamsi Krishnac6dcd5e2012-05-09 15:38:01 -0700864 /* MDM2AP_PBLRDY */
865 pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
866 "MDM2AP_PBLRDY");
Ameya Thakure155ece2012-07-09 12:08:37 -0700867 mdm_drv->mdm2ap_pblrdy = pres ? pres->start : -1;
Vamsi Krishnac6dcd5e2012-05-09 15:38:01 -0700868
Ameya Thakur43248fd2012-07-10 18:50:52 -0700869 /*USB_SW*/
870 pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
871 "USB_SW");
Ameya Thakure155ece2012-07-09 12:08:37 -0700872 mdm_drv->usb_switch_gpio = pres ? pres->start : -1;
Ameya Thakur43248fd2012-07-10 18:50:52 -0700873
Joel Kingb6f0f612011-11-01 16:59:14 -0700874 mdm_drv->boot_type = CHARM_NORMAL_BOOT;
875
Ameya Thakur3e69bfc2013-02-13 11:48:11 -0800876 mdev->dump_timeout_ms = mdm_drv->pdata->ramdump_timeout_ms > 0 ?
Ameya Thakurc9a7a842012-06-24 22:47:52 -0700877 mdm_drv->pdata->ramdump_timeout_ms : MDM_RDUMP_TIMEOUT;
Ameya Thakurffd21b02013-01-30 11:33:22 -0800878
879 init_completion(&mdev->mdm_needs_reload);
880 init_completion(&mdev->mdm_boot);
881 init_completion(&mdev->mdm_ram_dumps);
882
883 mdev->first_boot = 1;
884 mutex_init(&mdm_drv->peripheral_status_lock);
Joel Kingb6f0f612011-11-01 16:59:14 -0700885}
886
Ameya Thakurffd21b02013-01-30 11:33:22 -0800887static void mdm_deconfigure_ipc(struct mdm_device *mdev)
Joel Kingb6f0f612011-11-01 16:59:14 -0700888{
Ameya Thakurffd21b02013-01-30 11:33:22 -0800889 struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
Joel Kingb6f0f612011-11-01 16:59:14 -0700890
Ameya Thakurffd21b02013-01-30 11:33:22 -0800891 gpio_free(mdm_drv->ap2mdm_status_gpio);
892 gpio_free(mdm_drv->ap2mdm_errfatal_gpio);
893 if (GPIO_IS_VALID(mdm_drv->ap2mdm_kpdpwr_n_gpio))
894 gpio_free(mdm_drv->ap2mdm_kpdpwr_n_gpio);
895 if (GPIO_IS_VALID(mdm_drv->ap2mdm_pmic_pwr_en_gpio))
896 gpio_free(mdm_drv->ap2mdm_pmic_pwr_en_gpio);
897 gpio_free(mdm_drv->mdm2ap_status_gpio);
898 gpio_free(mdm_drv->mdm2ap_errfatal_gpio);
899 if (GPIO_IS_VALID(mdm_drv->ap2mdm_soft_reset_gpio))
900 gpio_free(mdm_drv->ap2mdm_soft_reset_gpio);
901
902 if (GPIO_IS_VALID(mdm_drv->ap2mdm_wakeup_gpio))
903 gpio_free(mdm_drv->ap2mdm_wakeup_gpio);
904
905 if (mdev->mdm_queue) {
906 destroy_workqueue(mdev->mdm_queue);
907 mdev->mdm_queue = NULL;
Joel Kingb6f0f612011-11-01 16:59:14 -0700908 }
Ameya Thakurffd21b02013-01-30 11:33:22 -0800909 if (mdev->mdm_sfr_queue) {
910 destroy_workqueue(mdev->mdm_sfr_queue);
911 mdev->mdm_sfr_queue = NULL;
912 }
913}
Joel Kingb6f0f612011-11-01 16:59:14 -0700914
Ameya Thakurffd21b02013-01-30 11:33:22 -0800915static int mdm_configure_ipc(struct mdm_device *mdev)
916{
917 struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
918 int ret = -1, irq;
Joel Kingb6f0f612011-11-01 16:59:14 -0700919
920 gpio_request(mdm_drv->ap2mdm_status_gpio, "AP2MDM_STATUS");
921 gpio_request(mdm_drv->ap2mdm_errfatal_gpio, "AP2MDM_ERRFATAL");
Ameya Thakure155ece2012-07-09 12:08:37 -0700922 if (GPIO_IS_VALID(mdm_drv->ap2mdm_kpdpwr_n_gpio))
Joel Kinge92eb872012-05-06 09:30:24 -0700923 gpio_request(mdm_drv->ap2mdm_kpdpwr_n_gpio, "AP2MDM_KPDPWR_N");
Joel Kingb6f0f612011-11-01 16:59:14 -0700924 gpio_request(mdm_drv->mdm2ap_status_gpio, "MDM2AP_STATUS");
925 gpio_request(mdm_drv->mdm2ap_errfatal_gpio, "MDM2AP_ERRFATAL");
Ameya Thakure155ece2012-07-09 12:08:37 -0700926 if (GPIO_IS_VALID(mdm_drv->mdm2ap_pblrdy))
Vamsi Krishnac6dcd5e2012-05-09 15:38:01 -0700927 gpio_request(mdm_drv->mdm2ap_pblrdy, "MDM2AP_PBLRDY");
Joel Kingb6f0f612011-11-01 16:59:14 -0700928
Ameya Thakure155ece2012-07-09 12:08:37 -0700929 if (GPIO_IS_VALID(mdm_drv->ap2mdm_pmic_pwr_en_gpio))
Joel Kinge92eb872012-05-06 09:30:24 -0700930 gpio_request(mdm_drv->ap2mdm_pmic_pwr_en_gpio,
931 "AP2MDM_PMIC_PWR_EN");
Ameya Thakure155ece2012-07-09 12:08:37 -0700932 if (GPIO_IS_VALID(mdm_drv->ap2mdm_soft_reset_gpio))
Joel Kinge92eb872012-05-06 09:30:24 -0700933 gpio_request(mdm_drv->ap2mdm_soft_reset_gpio,
934 "AP2MDM_SOFT_RESET");
935
Ameya Thakure155ece2012-07-09 12:08:37 -0700936 if (GPIO_IS_VALID(mdm_drv->ap2mdm_wakeup_gpio))
Joel Kingb6f0f612011-11-01 16:59:14 -0700937 gpio_request(mdm_drv->ap2mdm_wakeup_gpio, "AP2MDM_WAKEUP");
938
Ameya Thakure155ece2012-07-09 12:08:37 -0700939 if (GPIO_IS_VALID(mdm_drv->usb_switch_gpio)) {
Ameya Thakur43248fd2012-07-10 18:50:52 -0700940 if (gpio_request(mdm_drv->usb_switch_gpio, "USB_SW")) {
941 pr_err("%s Failed to get usb switch gpio\n", __func__);
942 mdm_drv->usb_switch_gpio = -1;
943 }
944 }
Joel Kingb6f0f612011-11-01 16:59:14 -0700945 gpio_direction_output(mdm_drv->ap2mdm_status_gpio, 1);
946 gpio_direction_output(mdm_drv->ap2mdm_errfatal_gpio, 0);
947
Ameya Thakure155ece2012-07-09 12:08:37 -0700948 if (GPIO_IS_VALID(mdm_drv->ap2mdm_wakeup_gpio))
Joel Kingb6f0f612011-11-01 16:59:14 -0700949 gpio_direction_output(mdm_drv->ap2mdm_wakeup_gpio, 0);
950
951 gpio_direction_input(mdm_drv->mdm2ap_status_gpio);
952 gpio_direction_input(mdm_drv->mdm2ap_errfatal_gpio);
953
Ameya Thakurffd21b02013-01-30 11:33:22 -0800954 mdev->mdm_queue = alloc_workqueue("mdm_queue", 0, 0);
955 if (!mdev->mdm_queue) {
956 pr_err("%s: could not create mdm_queue for mdm id %d\n",
957 __func__, mdev->mdm_data.device_id);
Joel Kingb6f0f612011-11-01 16:59:14 -0700958 ret = -ENOMEM;
959 goto fatal_err;
960 }
961
Ameya Thakurffd21b02013-01-30 11:33:22 -0800962 mdev->mdm_sfr_queue = alloc_workqueue("mdm_sfr_queue", 0, 0);
963 if (!mdev->mdm_sfr_queue) {
964 pr_err("%s: could not create mdm_sfr_queue for mdm id %d\n",
965 __func__, mdev->mdm_data.device_id);
Joel King30fdd662012-05-07 19:39:29 -0700966 ret = -ENOMEM;
Joel King30fdd662012-05-07 19:39:29 -0700967 goto fatal_err;
968 }
969
Joel Kingb6f0f612011-11-01 16:59:14 -0700970 /* Register subsystem handlers */
Ameya Thakurffd21b02013-01-30 11:33:22 -0800971 mdev->mdm_subsys_dev = subsys_register(&mdev->mdm_subsys);
972 if (IS_ERR(mdev->mdm_subsys_dev)) {
973 ret = PTR_ERR(mdev->mdm_subsys_dev);
Stephen Boyd0ebf7212012-04-30 20:42:35 -0700974 goto fatal_err;
975 }
Joel Kingb6f0f612011-11-01 16:59:14 -0700976
977 /* ERR_FATAL irq. */
978 irq = MSM_GPIO_TO_INT(mdm_drv->mdm2ap_errfatal_gpio);
979 if (irq < 0) {
Ameya Thakurffd21b02013-01-30 11:33:22 -0800980 pr_err("%s: bad MDM2AP_ERRFATAL IRQ resource, err = %d\n",
981 __func__, irq);
Joel Kingb6f0f612011-11-01 16:59:14 -0700982 goto errfatal_err;
983 }
984 ret = request_irq(irq, mdm_errfatal,
Ameya Thakurffd21b02013-01-30 11:33:22 -0800985 IRQF_TRIGGER_RISING , "mdm errfatal", mdev);
Joel Kingb6f0f612011-11-01 16:59:14 -0700986
987 if (ret < 0) {
Ameya Thakurffd21b02013-01-30 11:33:22 -0800988 pr_err("%s: MDM2AP_ERRFATAL IRQ#%d request failed, err=%d\n",
989 __func__, irq, ret);
Joel Kingb6f0f612011-11-01 16:59:14 -0700990 goto errfatal_err;
991 }
Ameya Thakurffd21b02013-01-30 11:33:22 -0800992 mdev->mdm_errfatal_irq = irq;
Joel Kingb6f0f612011-11-01 16:59:14 -0700993
994errfatal_err:
995
Ameya Thakurffd21b02013-01-30 11:33:22 -0800996 /* status irq */
Joel Kingb6f0f612011-11-01 16:59:14 -0700997 irq = MSM_GPIO_TO_INT(mdm_drv->mdm2ap_status_gpio);
998 if (irq < 0) {
Ameya Thakurffd21b02013-01-30 11:33:22 -0800999 pr_err("%s: bad MDM2AP_STATUS IRQ resource, err = %d\n",
1000 __func__, irq);
Joel Kingb6f0f612011-11-01 16:59:14 -07001001 goto status_err;
1002 }
1003
1004 ret = request_threaded_irq(irq, NULL, mdm_status_change,
Vamsi Krishna33925632011-12-13 15:43:09 -08001005 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED,
Ameya Thakurffd21b02013-01-30 11:33:22 -08001006 "mdm status", mdev);
Joel Kingb6f0f612011-11-01 16:59:14 -07001007
1008 if (ret < 0) {
Ameya Thakurffd21b02013-01-30 11:33:22 -08001009 pr_err("%s: MDM2AP_STATUS IRQ#%d request failed, err=%d",
1010 __func__, irq, ret);
Joel Kingb6f0f612011-11-01 16:59:14 -07001011 goto status_err;
1012 }
Ameya Thakurffd21b02013-01-30 11:33:22 -08001013 mdev->mdm_status_irq = irq;
Joel Kingb6f0f612011-11-01 16:59:14 -07001014
1015status_err:
Ameya Thakure155ece2012-07-09 12:08:37 -07001016 if (GPIO_IS_VALID(mdm_drv->mdm2ap_pblrdy)) {
Vamsi Krishnac6dcd5e2012-05-09 15:38:01 -07001017 irq = MSM_GPIO_TO_INT(mdm_drv->mdm2ap_pblrdy);
1018 if (irq < 0) {
Ameya Thakurffd21b02013-01-30 11:33:22 -08001019 pr_err("%s: could not get MDM2AP_PBLRDY IRQ resource\n",
1020 __func__);
Vamsi Krishnac6dcd5e2012-05-09 15:38:01 -07001021 goto pblrdy_err;
1022 }
1023
1024 ret = request_threaded_irq(irq, NULL, mdm_pblrdy_change,
Ameya Thakurffd21b02013-01-30 11:33:22 -08001025 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
1026 IRQF_SHARED,
1027 "mdm pbl ready", mdev);
Vamsi Krishnac6dcd5e2012-05-09 15:38:01 -07001028
1029 if (ret < 0) {
Ameya Thakurffd21b02013-01-30 11:33:22 -08001030 pr_err("%s: MDM2AP_PBL IRQ#%d request failed error=%d\n",
Vamsi Krishnac6dcd5e2012-05-09 15:38:01 -07001031 __func__, irq, ret);
1032 goto pblrdy_err;
1033 }
Ameya Thakurffd21b02013-01-30 11:33:22 -08001034 mdev->mdm_pblrdy_irq = irq;
Vamsi Krishnac6dcd5e2012-05-09 15:38:01 -07001035 }
1036
1037pblrdy_err:
Joel Kinge92eb872012-05-06 09:30:24 -07001038 /*
1039 * If AP2MDM_PMIC_PWR_EN gpio is used, pull it high. It remains
1040 * high until the whole phone is shut down.
1041 */
Ameya Thakure155ece2012-07-09 12:08:37 -07001042 if (GPIO_IS_VALID(mdm_drv->ap2mdm_pmic_pwr_en_gpio))
Joel Kinge92eb872012-05-06 09:30:24 -07001043 gpio_direction_output(mdm_drv->ap2mdm_pmic_pwr_en_gpio, 1);
1044
Ameya Thakurffd21b02013-01-30 11:33:22 -08001045 return 0;
Joel Kingb6f0f612011-11-01 16:59:14 -07001046
1047fatal_err:
Ameya Thakurffd21b02013-01-30 11:33:22 -08001048 mdm_deconfigure_ipc(mdev);
Joel Kingb6f0f612011-11-01 16:59:14 -07001049 return ret;
1050}
1051
Ameya Thakurffd21b02013-01-30 11:33:22 -08001052static int __devinit mdm_modem_probe(struct platform_device *pdev)
1053{
1054 struct mdm_device *mdev = NULL;
1055 int ret = -1;
1056
1057 mdev = kzalloc(sizeof(struct mdm_device), GFP_KERNEL);
1058 if (!mdev) {
1059 pr_err("%s: kzalloc fail.\n", __func__);
1060 ret = -ENOMEM;
1061 goto init_err;
1062 }
1063
Pavankumar Kondeti06c6c232013-02-12 18:10:21 +05301064 platform_set_drvdata(pdev, mdev);
Ameya Thakurffd21b02013-01-30 11:33:22 -08001065 mdm_modem_initialize_data(pdev, mdev);
1066
1067 if (mdm_ops->debug_state_changed_cb)
1068 mdm_ops->debug_state_changed_cb(mdm_debug_mask);
1069
1070 if (mdm_configure_ipc(mdev)) {
1071 pr_err("%s: mdm_configure_ipc failed, id = %d\n",
1072 __func__, mdev->mdm_data.device_id);
1073 goto init_err;
1074 }
1075
1076 pr_debug("%s: Registering mdm id %d\n", __func__,
1077 mdev->mdm_data.device_id);
1078 ret = misc_register(&mdev->misc_device);
1079 if (ret) {
1080 pr_err("%s: failed registering mdm id %d, ret = %d\n",
1081 __func__, mdev->mdm_data.device_id, ret);
1082 mdm_deconfigure_ipc(mdev);
1083 goto init_err;
1084 } else {
1085 pr_err("%s: registered mdm id %d\n",
1086 __func__, mdev->mdm_data.device_id);
1087
1088 mdm_device_list_add(mdev);
1089 INIT_DELAYED_WORK(&mdev->mdm2ap_status_check_work,
1090 mdm2ap_status_check);
1091 INIT_WORK(&mdev->mdm_status_work, mdm_status_fn);
1092 INIT_WORK(&mdev->sfr_reason_work, mdm_restart_reason_fn);
1093
1094 /* Perform early powerup of the external modem in order to
1095 * allow tabla devices to be found.
1096 */
1097 if (mdev->mdm_data.pdata->early_power_on)
1098 mdm_ops->power_on_mdm_cb(&mdev->mdm_data);
1099 }
1100
1101 return ret;
1102
1103init_err:
1104 kfree(mdev);
1105 return ret;
1106}
1107
1108static int __devexit mdm_modem_remove(struct platform_device *pdev)
Joel Kingb6f0f612011-11-01 16:59:14 -07001109{
1110 int ret;
Pavankumar Kondeti06c6c232013-02-12 18:10:21 +05301111 struct mdm_device *mdev = platform_get_drvdata(pdev);
Joel Kingb6f0f612011-11-01 16:59:14 -07001112
Ameya Thakurffd21b02013-01-30 11:33:22 -08001113 pr_debug("%s: removing device id %d\n",
1114 __func__, mdev->mdm_data.device_id);
1115 mdm_deconfigure_ipc(mdev);
1116 ret = misc_deregister(&mdev->misc_device);
1117 mdm_device_list_remove(mdev);
1118 kfree(mdev);
Joel Kingb6f0f612011-11-01 16:59:14 -07001119 return ret;
1120}
1121
Ameya Thakurffd21b02013-01-30 11:33:22 -08001122static void mdm_modem_shutdown(struct platform_device *pdev)
Joel Kingb6f0f612011-11-01 16:59:14 -07001123{
Ameya Thakurffd21b02013-01-30 11:33:22 -08001124 struct mdm_modem_drv *mdm_drv;
Pavankumar Kondeti06c6c232013-02-12 18:10:21 +05301125 struct mdm_device *mdev = platform_get_drvdata(pdev);
Joel Kingb6f0f612011-11-01 16:59:14 -07001126
Ameya Thakurffd21b02013-01-30 11:33:22 -08001127 pr_debug("%s: shutting down device id %d\n",
1128 __func__, mdev->mdm_data.device_id);
1129
1130 mdm_disable_irqs(mdev);
1131 mdm_drv = &mdev->mdm_data;
1132 mdm_ops->power_down_mdm_cb(mdm_drv);
Ameya Thakure155ece2012-07-09 12:08:37 -07001133 if (GPIO_IS_VALID(mdm_drv->ap2mdm_pmic_pwr_en_gpio))
Joel Kinge92eb872012-05-06 09:30:24 -07001134 gpio_direction_output(mdm_drv->ap2mdm_pmic_pwr_en_gpio, 0);
Joel Kingb6f0f612011-11-01 16:59:14 -07001135}
1136
Ameya Thakurffd21b02013-01-30 11:33:22 -08001137static struct of_device_id mdm_match_table[] = {
1138 {.compatible = "qcom,mdm2_modem,mdm2_modem.1"},
1139 {},
1140};
1141
1142static struct platform_driver mdm_modem_driver = {
1143 .probe = mdm_modem_probe,
1144 .remove = __devexit_p(mdm_modem_remove),
1145 .shutdown = mdm_modem_shutdown,
1146 .driver = {
1147 .name = "mdm2_modem",
1148 .owner = THIS_MODULE,
1149 .of_match_table = mdm_match_table,
1150 },
1151};
1152
1153static int __init mdm_modem_init(void)
1154{
1155 int ret;
1156
1157 ret = mdm_get_ops(&mdm_ops);
1158 if (ret)
1159 return ret;
1160
1161 INIT_LIST_HEAD(&mdm_devices);
1162 mdm_debugfs_init();
1163 return platform_driver_register(&mdm_modem_driver);
1164}
1165
1166static void __exit mdm_modem_exit(void)
1167{
1168 platform_driver_unregister(&mdm_modem_driver);
1169}
1170
1171module_init(mdm_modem_init);
1172module_exit(mdm_modem_exit);
1173
1174MODULE_LICENSE("GPL v2");
1175MODULE_DESCRIPTION("mdm modem driver");
1176MODULE_VERSION("2.0");
1177MODULE_ALIAS("mdm_modem");