blob: bdb5455b1f4ac35d7e863d51a1c4de05c9485631 [file] [log] [blame]
Taniya Das2b7646b2013-01-15 11:28:14 +05301/* 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 Thakur552e58a2013-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 Thakur552e58a2013-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 Thakur552e58a2013-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 Thakur552e58a2013-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
141struct mdm_device *mdm_get_device_by_device_id(int device_id)
142{
143 unsigned long flags;
144 struct mdm_device *mdev = NULL;
145
146 spin_lock_irqsave(&mdm_devices_lock, flags);
147 list_for_each_entry(mdev, &mdm_devices, link) {
148 if (mdev && mdev->mdm_data.device_id == device_id) {
149 spin_unlock_irqrestore(&mdm_devices_lock, flags);
150 return mdev;
151 }
152 }
153 spin_unlock_irqrestore(&mdm_devices_lock, flags);
154 return NULL;
155}
156
157struct mdm_device *mdm_get_device_by_name(const char *name)
158{
159 unsigned long flags;
160 struct mdm_device *mdev;
161
162 if (!name)
163 return NULL;
164 spin_lock_irqsave(&mdm_devices_lock, flags);
165 list_for_each_entry(mdev, &mdm_devices, link) {
166 if (mdev && !strncmp(mdev->device_name, name,
167 sizeof(mdev->device_name))) {
168 spin_unlock_irqrestore(&mdm_devices_lock, flags);
169 return mdev;
170 }
171 }
172 spin_unlock_irqrestore(&mdm_devices_lock, flags);
173 return NULL;
174}
175
176/* If the platform's cascading_ssr flag is set, the subsystem
177 * restart module will restart the other modems so stop
178 * monitoring them as well.
179 * This function can be called from interrupt context.
180 */
181static void mdm_start_ssr(struct mdm_device *mdev)
182{
183 unsigned long flags;
184 int start_ssr = 1;
185
186 spin_lock_irqsave(&ssr_lock, flags);
187 if (mdev->mdm_data.pdata->cascading_ssr &&
188 ssr_count > 0) {
189 start_ssr = 0;
190 } else {
191 ssr_count++;
192 mdev->ssr_started_internally = 1;
193 }
194 spin_unlock_irqrestore(&ssr_lock, flags);
195
196 if (start_ssr) {
197 atomic_set(&mdev->mdm_data.mdm_ready, 0);
198 pr_debug("%s: Resetting mdm id %d due to mdm error\n",
199 __func__, mdev->mdm_data.device_id);
200 subsystem_restart_dev(mdev->mdm_subsys_dev);
201 } else {
202 pr_debug("%s: Another modem is already in SSR\n",
203 __func__);
204 }
205}
206
207/* Increment the reference count to handle the case where
208 * subsystem restart is initiated by the SSR service.
209 */
210static void mdm_ssr_started(struct mdm_device *mdev)
211{
212 unsigned long flags;
213
214 spin_lock_irqsave(&ssr_lock, flags);
215 ssr_count++;
216 atomic_set(&mdev->mdm_data.mdm_ready, 0);
217 spin_unlock_irqrestore(&ssr_lock, flags);
218}
219
220/* mdm_ssr_completed assumes that mdm_ssr_started has previously
221 * been called.
222 */
223static void mdm_ssr_completed(struct mdm_device *mdev)
224{
225 unsigned long flags;
226
227 spin_lock_irqsave(&ssr_lock, flags);
228 ssr_count--;
229 if (mdev->ssr_started_internally) {
230 mdev->ssr_started_internally = 0;
231 ssr_count--;
232 }
233
234 if (ssr_count < 0) {
235 pr_err("%s: ssr_count = %d\n",
236 __func__, ssr_count);
237 panic("%s: ssr_count = %d < 0\n",
238 __func__, ssr_count);
239 }
240 spin_unlock_irqrestore(&ssr_lock, flags);
241}
Joel King269aa602012-07-23 08:07:35 -0700242
Joel Kingef390842012-05-23 16:42:48 -0700243static irqreturn_t mdm_vddmin_change(int irq, void *dev_id)
244{
Ameya Thakur552e58a2013-01-30 11:33:22 -0800245 struct mdm_device *mdev = (struct mdm_device *)dev_id;
246 struct mdm_vddmin_resource *vddmin_res;
247 int value;
Joel Kingef390842012-05-23 16:42:48 -0700248
Ameya Thakur552e58a2013-01-30 11:33:22 -0800249 if (!mdev)
250 goto handled;
251
252 vddmin_res = mdev->mdm_data.pdata->vddmin_resource;
253 if (!vddmin_res)
254 goto handled;
255
256 value = gpio_get_value(
257 vddmin_res->mdm2ap_vddmin_gpio);
Joel Kingef390842012-05-23 16:42:48 -0700258 if (value == 0)
Ameya Thakur552e58a2013-01-30 11:33:22 -0800259 pr_debug("External Modem id %d entered Vddmin\n",
260 mdev->mdm_data.device_id);
Joel Kingef390842012-05-23 16:42:48 -0700261 else
Ameya Thakur552e58a2013-01-30 11:33:22 -0800262 pr_debug("External Modem id %d exited Vddmin\n",
263 mdev->mdm_data.device_id);
264handled:
Joel Kingef390842012-05-23 16:42:48 -0700265 return IRQ_HANDLED;
266}
267
Ameya Thakur552e58a2013-01-30 11:33:22 -0800268/* The vddmin_res resource may not be supported by some platforms. */
Joel Kingef390842012-05-23 16:42:48 -0700269static void mdm_setup_vddmin_gpios(void)
270{
Ameya Thakur552e58a2013-01-30 11:33:22 -0800271 unsigned long flags;
Joel Kingef390842012-05-23 16:42:48 -0700272 struct msm_rpm_iv_pair req;
Ameya Thakur552e58a2013-01-30 11:33:22 -0800273 struct mdm_device *mdev;
Joel Kingef390842012-05-23 16:42:48 -0700274 struct mdm_vddmin_resource *vddmin_res;
275 int irq, ret;
276
Ameya Thakur552e58a2013-01-30 11:33:22 -0800277 spin_lock_irqsave(&mdm_devices_lock, flags);
278 list_for_each_entry(mdev, &mdm_devices, link) {
279 vddmin_res = mdev->mdm_data.pdata->vddmin_resource;
280 if (!vddmin_res)
281 continue;
Joel Kingef390842012-05-23 16:42:48 -0700282
Ameya Thakur552e58a2013-01-30 11:33:22 -0800283 pr_debug("Enabling vddmin logging on modem id %d\n",
284 mdev->mdm_data.device_id);
285 req.id = vddmin_res->rpm_id;
286 req.value =
287 ((uint32_t)vddmin_res->ap2mdm_vddmin_gpio & 0x0000FFFF)
288 << 16;
289 req.value |= ((uint32_t)vddmin_res->modes & 0x000000FF) << 8;
290 req.value |= (uint32_t)vddmin_res->drive_strength & 0x000000FF;
Joel Kingef390842012-05-23 16:42:48 -0700291
Ameya Thakur552e58a2013-01-30 11:33:22 -0800292 msm_rpm_set(MSM_RPM_CTX_SET_0, &req, 1);
Joel Kingef390842012-05-23 16:42:48 -0700293
Ameya Thakur552e58a2013-01-30 11:33:22 -0800294 /* Start monitoring low power gpio from mdm */
295 irq = gpio_to_irq(vddmin_res->mdm2ap_vddmin_gpio);
296 if (irq < 0)
297 pr_err("%s: could not get LPM POWER IRQ resource mdm id %d.\n",
298 __func__, mdev->mdm_data.device_id);
299 else {
300 ret = request_threaded_irq(irq, NULL, mdm_vddmin_change,
301 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
302 "mdm lpm", mdev);
303
304 if (ret < 0)
305 pr_err("%s: MDM LPM IRQ#%d request failed with error=%d",
306 __func__, irq, ret);
307 }
Joel Kingef390842012-05-23 16:42:48 -0700308 }
Ameya Thakur552e58a2013-01-30 11:33:22 -0800309 spin_unlock_irqrestore(&mdm_devices_lock, flags);
Joel Kingef390842012-05-23 16:42:48 -0700310 return;
311}
312
Joel King30fdd662012-05-07 19:39:29 -0700313static void mdm_restart_reason_fn(struct work_struct *work)
314{
315 int ret, ntries = 0;
316 char sfr_buf[RD_BUF_SIZE];
Ameya Thakur552e58a2013-01-30 11:33:22 -0800317 struct mdm_platform_data *pdata;
318 struct mdm_device *mdev = container_of(work,
319 struct mdm_device, sfr_reason_work);
Joel King30fdd662012-05-07 19:39:29 -0700320
Ameya Thakur552e58a2013-01-30 11:33:22 -0800321 pdata = mdev->mdm_data.pdata;
322 if (pdata->sysmon_subsys_id_valid) {
323 do {
324 ret = sysmon_get_reason(pdata->sysmon_subsys_id,
Joel King30fdd662012-05-07 19:39:29 -0700325 sfr_buf, sizeof(sfr_buf));
Ameya Thakur552e58a2013-01-30 11:33:22 -0800326 if (!ret) {
327 pr_err("mdm restart reason: %s\n", sfr_buf);
328 return;
329 }
330 /* Wait for the modem to be fully booted after a
331 * subsystem restart. This may take several seconds.
Joel King30fdd662012-05-07 19:39:29 -0700332 */
Ameya Thakur552e58a2013-01-30 11:33:22 -0800333 msleep(SFR_RETRY_INTERVAL);
334 } while (++ntries < SFR_MAX_RETRIES);
335 pr_debug("%s: Error retrieving restart reason: %d\n",
336 __func__, ret);
337 }
Joel King30fdd662012-05-07 19:39:29 -0700338}
339
Joel King493b3ac2012-05-14 11:39:45 -0700340static void mdm2ap_status_check(struct work_struct *work)
341{
Ameya Thakur552e58a2013-01-30 11:33:22 -0800342 struct mdm_device *mdev =
343 container_of(work, struct mdm_device,
344 mdm2ap_status_check_work.work);
345 struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
Joel King493b3ac2012-05-14 11:39:45 -0700346 /*
347 * If the mdm modem did not pull the MDM2AP_STATUS gpio
348 * high then call subsystem_restart.
349 */
Ameya Thakur43248fd2012-07-10 18:50:52 -0700350 if (!mdm_drv->disable_status_check) {
351 if (gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 0) {
Ameya Thakur552e58a2013-01-30 11:33:22 -0800352 pr_debug("%s: MDM2AP_STATUS did not go high on mdm id %d\n",
353 __func__, mdev->mdm_data.device_id);
354 mdm_start_ssr(mdev);
Ameya Thakur43248fd2012-07-10 18:50:52 -0700355 }
Joel King493b3ac2012-05-14 11:39:45 -0700356 }
357}
358
Ameya Thakur552e58a2013-01-30 11:33:22 -0800359static void mdm_update_gpio_configs(struct mdm_device *mdev,
360 enum gpio_update_config gpio_config)
Joel King269aa602012-07-23 08:07:35 -0700361{
Ameya Thakur552e58a2013-01-30 11:33:22 -0800362 struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
363
Joel King269aa602012-07-23 08:07:35 -0700364 /* Some gpio configuration may need updating after modem bootup.*/
365 switch (gpio_config) {
366 case GPIO_UPDATE_RUNNING_CONFIG:
367 if (mdm_drv->pdata->mdm2ap_status_gpio_run_cfg) {
368 if (msm_gpiomux_write(mdm_drv->mdm2ap_status_gpio,
369 GPIOMUX_ACTIVE,
370 mdm_drv->pdata->mdm2ap_status_gpio_run_cfg,
Ameya Thakur552e58a2013-01-30 11:33:22 -0800371 &mdev->mdm2ap_status_old_config))
372 pr_err("%s: failed updating running gpio config mdm id %d\n",
373 __func__, mdev->mdm_data.device_id);
Joel King269aa602012-07-23 08:07:35 -0700374 else
Ameya Thakur552e58a2013-01-30 11:33:22 -0800375 mdev->mdm2ap_status_valid_old_config = 1;
Joel King269aa602012-07-23 08:07:35 -0700376 }
377 break;
378 case GPIO_UPDATE_BOOTING_CONFIG:
Ameya Thakur552e58a2013-01-30 11:33:22 -0800379 if (mdev->mdm2ap_status_valid_old_config) {
Joel King269aa602012-07-23 08:07:35 -0700380 msm_gpiomux_write(mdm_drv->mdm2ap_status_gpio,
381 GPIOMUX_ACTIVE,
Ameya Thakur552e58a2013-01-30 11:33:22 -0800382 &mdev->mdm2ap_status_old_config,
Joel King269aa602012-07-23 08:07:35 -0700383 NULL);
Ameya Thakur552e58a2013-01-30 11:33:22 -0800384 mdev->mdm2ap_status_valid_old_config = 0;
Joel King269aa602012-07-23 08:07:35 -0700385 }
386 break;
387 default:
388 pr_err("%s: called with no config\n", __func__);
389 break;
390 }
391}
392
Joel Kingb6f0f612011-11-01 16:59:14 -0700393long mdm_modem_ioctl(struct file *filp, unsigned int cmd,
394 unsigned long arg)
395{
396 int status, ret = 0;
Ameya Thakur552e58a2013-01-30 11:33:22 -0800397 struct mdm_device *mdev;
398 struct mdm_modem_drv *mdm_drv;
399
400 mdev = mdm_get_device_by_name(filp->f_path.dentry->d_iname);
401 if (!mdev) {
402 pr_err("%s: mdm_device not found\n", __func__);
403 return -ENODEV;
404 }
Joel Kingb6f0f612011-11-01 16:59:14 -0700405
406 if (_IOC_TYPE(cmd) != CHARM_CODE) {
Ameya Thakur552e58a2013-01-30 11:33:22 -0800407 pr_err("%s: invalid ioctl code to mdm id %d\n",
408 __func__, mdev->mdm_data.device_id);
Joel Kingb6f0f612011-11-01 16:59:14 -0700409 return -EINVAL;
410 }
411
Ameya Thakur552e58a2013-01-30 11:33:22 -0800412 mdm_drv = &mdev->mdm_data;
413 pr_debug("%s: Entering ioctl cmd = %d, mdm id = %d\n",
414 __func__, _IOC_NR(cmd), mdev->mdm_data.device_id);
Joel Kingb6f0f612011-11-01 16:59:14 -0700415 switch (cmd) {
416 case WAKE_CHARM:
Ameya Thakur552e58a2013-01-30 11:33:22 -0800417 pr_debug("%s: Powering on mdm id %d\n",
418 __func__, mdev->mdm_data.device_id);
419 mdm_ops->power_on_mdm_cb(mdm_drv);
Joel Kingb6f0f612011-11-01 16:59:14 -0700420 break;
421 case CHECK_FOR_BOOT:
422 if (gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 0)
423 put_user(1, (unsigned long __user *) arg);
424 else
425 put_user(0, (unsigned long __user *) arg);
426 break;
427 case NORMAL_BOOT_DONE:
Ameya Thakur552e58a2013-01-30 11:33:22 -0800428 pr_debug("%s: check if mdm id %d is booted up\n",
429 __func__, mdev->mdm_data.device_id);
Joel Kingb6f0f612011-11-01 16:59:14 -0700430 get_user(status, (unsigned long __user *) arg);
Joel King2a42f502012-02-03 11:36:25 -0800431 if (status) {
Ameya Thakur552e58a2013-01-30 11:33:22 -0800432 pr_debug("%s: normal boot of mdm id %d failed\n",
433 __func__, mdev->mdm_data.device_id);
Joel Kingb6f0f612011-11-01 16:59:14 -0700434 mdm_drv->mdm_boot_status = -EIO;
Joel King2a42f502012-02-03 11:36:25 -0800435 } else {
Ameya Thakur552e58a2013-01-30 11:33:22 -0800436 pr_debug("%s: normal boot of mdm id %d done\n",
437 __func__, mdev->mdm_data.device_id);
Joel Kingb6f0f612011-11-01 16:59:14 -0700438 mdm_drv->mdm_boot_status = 0;
Joel King2a42f502012-02-03 11:36:25 -0800439 }
Ameya Thakur552e58a2013-01-30 11:33:22 -0800440 atomic_set(&mdm_drv->mdm_ready, 1);
Joel Kingb6f0f612011-11-01 16:59:14 -0700441
Ameya Thakur552e58a2013-01-30 11:33:22 -0800442 if (mdm_ops->normal_boot_done_cb != NULL)
443 mdm_ops->normal_boot_done_cb(mdm_drv);
Joel Kingb6f0f612011-11-01 16:59:14 -0700444
Ameya Thakur552e58a2013-01-30 11:33:22 -0800445 if (!mdev->first_boot)
446 complete(&mdev->mdm_boot);
Joel Kingb6f0f612011-11-01 16:59:14 -0700447 else
Ameya Thakur552e58a2013-01-30 11:33:22 -0800448 mdev->first_boot = 0;
Joel King493b3ac2012-05-14 11:39:45 -0700449
Joel King269aa602012-07-23 08:07:35 -0700450 /* If successful, start a timer to check that the mdm2ap_status
451 * gpio goes high.
Joel King493b3ac2012-05-14 11:39:45 -0700452 */
Joel King269aa602012-07-23 08:07:35 -0700453 if (!status && gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 0)
Ameya Thakur552e58a2013-01-30 11:33:22 -0800454 schedule_delayed_work(&mdev->mdm2ap_status_check_work,
Joel King493b3ac2012-05-14 11:39:45 -0700455 msecs_to_jiffies(MDM2AP_STATUS_TIMEOUT_MS));
Joel Kingb6f0f612011-11-01 16:59:14 -0700456 break;
457 case RAM_DUMP_DONE:
Joel King2a42f502012-02-03 11:36:25 -0800458 pr_debug("%s: mdm done collecting RAM dumps\n", __func__);
Joel Kingb6f0f612011-11-01 16:59:14 -0700459 get_user(status, (unsigned long __user *) arg);
460 if (status)
461 mdm_drv->mdm_ram_dump_status = -EIO;
Joel King2a42f502012-02-03 11:36:25 -0800462 else {
Ameya Thakur552e58a2013-01-30 11:33:22 -0800463 pr_debug("%s: ramdump collection completed\n",
464 __func__);
Joel Kingb6f0f612011-11-01 16:59:14 -0700465 mdm_drv->mdm_ram_dump_status = 0;
Joel King2a42f502012-02-03 11:36:25 -0800466 }
Ameya Thakur552e58a2013-01-30 11:33:22 -0800467 complete(&mdev->mdm_ram_dumps);
Joel Kingb6f0f612011-11-01 16:59:14 -0700468 break;
469 case WAIT_FOR_RESTART:
Joel King2a42f502012-02-03 11:36:25 -0800470 pr_debug("%s: wait for mdm to need images reloaded\n",
Joel Kingb6f0f612011-11-01 16:59:14 -0700471 __func__);
Ameya Thakur552e58a2013-01-30 11:33:22 -0800472 ret = wait_for_completion_interruptible(
473 &mdev->mdm_needs_reload);
Joel Kingb6f0f612011-11-01 16:59:14 -0700474 if (!ret)
475 put_user(mdm_drv->boot_type,
476 (unsigned long __user *) arg);
Ameya Thakur552e58a2013-01-30 11:33:22 -0800477 init_completion(&mdev->mdm_needs_reload);
Joel Kingb6f0f612011-11-01 16:59:14 -0700478 break;
Joel Kinge92eb872012-05-06 09:30:24 -0700479 case GET_DLOAD_STATUS:
480 pr_debug("getting status of mdm2ap_errfatal_gpio\n");
481 if (gpio_get_value(mdm_drv->mdm2ap_errfatal_gpio) == 1 &&
Ameya Thakur552e58a2013-01-30 11:33:22 -0800482 !atomic_read(&mdm_drv->mdm_ready))
Joel Kinge92eb872012-05-06 09:30:24 -0700483 put_user(1, (unsigned long __user *) arg);
484 else
485 put_user(0, (unsigned long __user *) arg);
486 break;
Ameya Thakur43248fd2012-07-10 18:50:52 -0700487 case IMAGE_UPGRADE:
488 pr_debug("%s Image upgrade ioctl recieved\n", __func__);
489 if (mdm_drv->pdata->image_upgrade_supported &&
Ameya Thakur552e58a2013-01-30 11:33:22 -0800490 mdm_ops->image_upgrade_cb) {
Ameya Thakur43248fd2012-07-10 18:50:52 -0700491 get_user(status, (unsigned long __user *) arg);
Ameya Thakur552e58a2013-01-30 11:33:22 -0800492 mdm_ops->image_upgrade_cb(mdm_drv, status);
Ameya Thakur43248fd2012-07-10 18:50:52 -0700493 } else
494 pr_debug("%s Image upgrade not supported\n", __func__);
495 break;
Joel Kingbf3e4b52012-09-26 09:10:34 -0700496 case SHUTDOWN_CHARM:
497 if (!mdm_drv->pdata->send_shdn)
498 break;
Ameya Thakur552e58a2013-01-30 11:33:22 -0800499 atomic_set(&mdm_drv->mdm_ready, 0);
Joel Kingbf3e4b52012-09-26 09:10:34 -0700500 if (mdm_debug_mask & MDM_DEBUG_MASK_SHDN_LOG)
Ameya Thakur552e58a2013-01-30 11:33:22 -0800501 pr_debug("Sending shutdown request to mdm\n");
Joel Kingbf3e4b52012-09-26 09:10:34 -0700502 ret = sysmon_send_shutdown(SYSMON_SS_EXT_MODEM);
503 if (ret)
504 pr_err("%s: Graceful shutdown of the external modem failed, ret = %d\n",
505 __func__, ret);
Taniya Dasd1ed37b2012-11-29 12:56:38 +0530506 put_user(ret, (unsigned long __user *) arg);
Joel Kingbf3e4b52012-09-26 09:10:34 -0700507 break;
Joel Kingb6f0f612011-11-01 16:59:14 -0700508 default:
509 pr_err("%s: invalid ioctl cmd = %d\n", __func__, _IOC_NR(cmd));
510 ret = -EINVAL;
511 break;
512 }
Joel Kingb6f0f612011-11-01 16:59:14 -0700513 return ret;
514}
515
Joel Kingb6f0f612011-11-01 16:59:14 -0700516static void mdm_status_fn(struct work_struct *work)
517{
Ameya Thakur552e58a2013-01-30 11:33:22 -0800518 struct mdm_device *mdev =
519 container_of(work, struct mdm_device, mdm_status_work);
520 struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
Vamsi Krishna33925632011-12-13 15:43:09 -0800521 int value = gpio_get_value(mdm_drv->mdm2ap_status_gpio);
522
Joel King2a42f502012-02-03 11:36:25 -0800523 pr_debug("%s: status:%d\n", __func__, value);
Ameya Thakur552e58a2013-01-30 11:33:22 -0800524 if (atomic_read(&mdm_drv->mdm_ready) && mdm_ops->status_cb)
525 mdm_ops->status_cb(mdm_drv, value);
Joel King269aa602012-07-23 08:07:35 -0700526
527 /* Update gpio configuration to "running" config. */
Ameya Thakur552e58a2013-01-30 11:33:22 -0800528 mdm_update_gpio_configs(mdev, GPIO_UPDATE_RUNNING_CONFIG);
Joel Kingb6f0f612011-11-01 16:59:14 -0700529}
530
Ameya Thakur552e58a2013-01-30 11:33:22 -0800531static void mdm_disable_irqs(struct mdm_device *mdev)
Joel Kingb6f0f612011-11-01 16:59:14 -0700532{
Ameya Thakur552e58a2013-01-30 11:33:22 -0800533 if (!mdev)
534 return;
535 disable_irq_nosync(mdev->mdm_errfatal_irq);
536 disable_irq_nosync(mdev->mdm_status_irq);
537 disable_irq_nosync(mdev->mdm_pblrdy_irq);
Joel Kingb6f0f612011-11-01 16:59:14 -0700538}
539
540static irqreturn_t mdm_errfatal(int irq, void *dev_id)
541{
Ameya Thakur552e58a2013-01-30 11:33:22 -0800542 struct mdm_modem_drv *mdm_drv;
543 struct mdm_device *mdev = (struct mdm_device *)dev_id;
544 if (!mdev)
545 return IRQ_HANDLED;
546
547 pr_debug("%s: mdm id %d sent errfatal interrupt\n",
548 __func__, mdev->mdm_data.device_id);
549 mdm_drv = &mdev->mdm_data;
550 if (atomic_read(&mdm_drv->mdm_ready) &&
Joel Kingb6f0f612011-11-01 16:59:14 -0700551 (gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 1)) {
Ameya Thakur552e58a2013-01-30 11:33:22 -0800552 pr_debug("%s: Received err fatal from mdm id %d\n",
553 __func__, mdev->mdm_data.device_id);
554 mdm_start_ssr(mdev);
Joel Kingb6f0f612011-11-01 16:59:14 -0700555 }
556 return IRQ_HANDLED;
557}
558
Ameya Thakur552e58a2013-01-30 11:33:22 -0800559/* set the mdm_device as the file's private data */
Joel Kingb6f0f612011-11-01 16:59:14 -0700560static int mdm_modem_open(struct inode *inode, struct file *file)
561{
562 return 0;
563}
564
Joel Kingb6f0f612011-11-01 16:59:14 -0700565static int mdm_panic_prep(struct notifier_block *this,
566 unsigned long event, void *ptr)
567{
568 int i;
Ameya Thakur552e58a2013-01-30 11:33:22 -0800569 struct mdm_modem_drv *mdm_drv;
570 struct mdm_device *mdev =
571 container_of(this, struct mdm_device, mdm_panic_blk);
572
573 mdm_drv = &mdev->mdm_data;
Joel Kingb6f0f612011-11-01 16:59:14 -0700574
Joel King2a42f502012-02-03 11:36:25 -0800575 pr_debug("%s: setting AP2MDM_ERRFATAL high for a non graceful reset\n",
Joel Kingb6f0f612011-11-01 16:59:14 -0700576 __func__);
Ameya Thakur552e58a2013-01-30 11:33:22 -0800577 mdm_disable_irqs(mdev);
Joel Kingb6f0f612011-11-01 16:59:14 -0700578 gpio_set_value(mdm_drv->ap2mdm_errfatal_gpio, 1);
579
Joel Kingb6f0f612011-11-01 16:59:14 -0700580 for (i = MDM_MODEM_TIMEOUT; i > 0; i -= MDM_MODEM_DELTA) {
581 pet_watchdog();
582 mdelay(MDM_MODEM_DELTA);
583 if (gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 0)
584 break;
585 }
Joel Kinge92eb872012-05-06 09:30:24 -0700586 if (i <= 0) {
Joel Kingb6f0f612011-11-01 16:59:14 -0700587 pr_err("%s: MDM2AP_STATUS never went low\n", __func__);
Joel Kinge92eb872012-05-06 09:30:24 -0700588 /* Reset the modem so that it will go into download mode. */
Ameya Thakur552e58a2013-01-30 11:33:22 -0800589 if (mdm_drv && mdm_ops->atomic_reset_mdm_cb)
590 mdm_ops->atomic_reset_mdm_cb(mdm_drv);
Joel Kinge92eb872012-05-06 09:30:24 -0700591 }
Joel Kingb6f0f612011-11-01 16:59:14 -0700592 return NOTIFY_DONE;
593}
594
Joel Kingb6f0f612011-11-01 16:59:14 -0700595static irqreturn_t mdm_status_change(int irq, void *dev_id)
596{
Ameya Thakur552e58a2013-01-30 11:33:22 -0800597 struct mdm_modem_drv *mdm_drv;
598 struct mdm_device *mdev = (struct mdm_device *)dev_id;
599 int value;
600 if (!mdev)
601 return IRQ_HANDLED;
602
603 mdm_drv = &mdev->mdm_data;
604 value = gpio_get_value(mdm_drv->mdm2ap_status_gpio);
Joel Kinge92eb872012-05-06 09:30:24 -0700605
Joel Kingbf3e4b52012-09-26 09:10:34 -0700606 if ((mdm_debug_mask & MDM_DEBUG_MASK_SHDN_LOG) && (value == 0))
Ameya Thakur552e58a2013-01-30 11:33:22 -0800607 pr_debug("%s: mdm2ap_status went low\n", __func__);
Joel Kingbf3e4b52012-09-26 09:10:34 -0700608
Ameya Thakur552e58a2013-01-30 11:33:22 -0800609 pr_debug("%s: mdm id %d sent status change interrupt\n",
610 __func__, mdev->mdm_data.device_id);
611 if (value == 0 && atomic_read(&mdm_drv->mdm_ready)) {
612 pr_debug("%s: unexpected reset external modem id %d\n",
613 __func__, mdev->mdm_data.device_id);
Joel Kinge92eb872012-05-06 09:30:24 -0700614 mdm_drv->mdm_unexpected_reset_occurred = 1;
Ameya Thakur552e58a2013-01-30 11:33:22 -0800615 mdm_start_ssr(mdev);
Joel Kinge92eb872012-05-06 09:30:24 -0700616 } else if (value == 1) {
Ameya Thakur552e58a2013-01-30 11:33:22 -0800617 cancel_delayed_work(&mdev->mdm2ap_status_check_work);
618 pr_debug("%s: status = 1: mdm id %d is now ready\n",
619 __func__, mdev->mdm_data.device_id);
620 queue_work(mdev->mdm_queue, &mdev->mdm_status_work);
Joel Kinge92eb872012-05-06 09:30:24 -0700621 }
Joel Kingb6f0f612011-11-01 16:59:14 -0700622 return IRQ_HANDLED;
623}
624
Vamsi Krishnac6dcd5e2012-05-09 15:38:01 -0700625static irqreturn_t mdm_pblrdy_change(int irq, void *dev_id)
626{
Ameya Thakur552e58a2013-01-30 11:33:22 -0800627 struct mdm_modem_drv *mdm_drv;
628 struct mdm_device *mdev = (struct mdm_device *)dev_id;
629 if (!mdev)
630 return IRQ_HANDLED;
Vamsi Krishnac6dcd5e2012-05-09 15:38:01 -0700631
Ameya Thakur552e58a2013-01-30 11:33:22 -0800632 mdm_drv = &mdev->mdm_data;
633 pr_debug("%s: mdm id %d: pbl ready:%d\n",
634 __func__, mdev->mdm_data.device_id,
635 gpio_get_value(mdm_drv->mdm2ap_pblrdy));
Vamsi Krishnac6dcd5e2012-05-09 15:38:01 -0700636 return IRQ_HANDLED;
637}
638
Stephen Boyd0ebf7212012-04-30 20:42:35 -0700639static int mdm_subsys_shutdown(const struct subsys_desc *crashed_subsys)
Joel Kingb6f0f612011-11-01 16:59:14 -0700640{
Ameya Thakur552e58a2013-01-30 11:33:22 -0800641 struct mdm_device *mdev =
642 container_of(crashed_subsys, struct mdm_device, mdm_subsys);
643 struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
644
645 pr_debug("%s: ssr on modem id %d\n", __func__,
646 mdev->mdm_data.device_id);
647
648 mdm_ssr_started(mdev);
649 cancel_delayed_work(&mdev->mdm2ap_status_check_work);
Joel Kingb6f0f612011-11-01 16:59:14 -0700650 gpio_direction_output(mdm_drv->ap2mdm_errfatal_gpio, 1);
Joel King415af512012-02-03 10:22:43 -0800651 if (mdm_drv->pdata->ramdump_delay_ms > 0) {
652 /* Wait for the external modem to complete
653 * its preparation for ramdumps.
654 */
Joel King6e7a3e82012-04-13 17:37:33 -0700655 msleep(mdm_drv->pdata->ramdump_delay_ms);
Joel King415af512012-02-03 10:22:43 -0800656 }
Joel King269aa602012-07-23 08:07:35 -0700657 if (!mdm_drv->mdm_unexpected_reset_occurred) {
Ameya Thakur552e58a2013-01-30 11:33:22 -0800658 mdm_ops->reset_mdm_cb(mdm_drv);
Joel King269aa602012-07-23 08:07:35 -0700659 /* Update gpio configuration to "booting" config. */
Ameya Thakur552e58a2013-01-30 11:33:22 -0800660 mdm_update_gpio_configs(mdev, GPIO_UPDATE_BOOTING_CONFIG);
Joel King269aa602012-07-23 08:07:35 -0700661 } else {
Joel Kinge92eb872012-05-06 09:30:24 -0700662 mdm_drv->mdm_unexpected_reset_occurred = 0;
Joel King269aa602012-07-23 08:07:35 -0700663 }
Joel Kingb6f0f612011-11-01 16:59:14 -0700664 return 0;
665}
666
Stephen Boyd0ebf7212012-04-30 20:42:35 -0700667static int mdm_subsys_powerup(const struct subsys_desc *crashed_subsys)
Joel Kingb6f0f612011-11-01 16:59:14 -0700668{
Ameya Thakur552e58a2013-01-30 11:33:22 -0800669 struct mdm_device *mdev =
670 container_of(crashed_subsys, struct mdm_device,
671 mdm_subsys);
672 struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
673
674 pr_debug("%s: ssr on modem id %d\n",
675 __func__, mdev->mdm_data.device_id);
676
Joel Kingbc48e4c2012-02-27 13:18:52 -0800677 gpio_direction_output(mdm_drv->ap2mdm_errfatal_gpio, 0);
678 gpio_direction_output(mdm_drv->ap2mdm_status_gpio, 1);
Taniya Das2b7646b2013-01-15 11:28:14 +0530679
680 if (mdm_drv->pdata->ps_hold_delay_ms > 0)
681 msleep(mdm_drv->pdata->ps_hold_delay_ms);
682
Ameya Thakur552e58a2013-01-30 11:33:22 -0800683 mdm_ops->power_on_mdm_cb(mdm_drv);
Joel Kingb6f0f612011-11-01 16:59:14 -0700684 mdm_drv->boot_type = CHARM_NORMAL_BOOT;
Ameya Thakur552e58a2013-01-30 11:33:22 -0800685 mdm_ssr_completed(mdev);
686 complete(&mdev->mdm_needs_reload);
687 if (!wait_for_completion_timeout(&mdev->mdm_boot,
Joel King52d7fa62012-03-21 08:40:52 -0700688 msecs_to_jiffies(MDM_BOOT_TIMEOUT))) {
689 mdm_drv->mdm_boot_status = -ETIMEDOUT;
Ameya Thakur552e58a2013-01-30 11:33:22 -0800690 pr_debug("%s: mdm modem restart timed out.\n", __func__);
Joel King30fdd662012-05-07 19:39:29 -0700691 } else {
Ameya Thakur552e58a2013-01-30 11:33:22 -0800692 pr_debug("%s: id %d: mdm modem has been restarted\n",
693 __func__, mdm_drv->device_id);
Joel Kinge92eb872012-05-06 09:30:24 -0700694
Joel King30fdd662012-05-07 19:39:29 -0700695 /* Log the reason for the restart */
Joel Kinge92eb872012-05-06 09:30:24 -0700696 if (mdm_drv->pdata->sfr_query)
Ameya Thakur552e58a2013-01-30 11:33:22 -0800697 queue_work(mdev->mdm_sfr_queue, &mdev->sfr_reason_work);
Joel King30fdd662012-05-07 19:39:29 -0700698 }
Ameya Thakur552e58a2013-01-30 11:33:22 -0800699 init_completion(&mdev->mdm_boot);
Joel Kingb6f0f612011-11-01 16:59:14 -0700700 return mdm_drv->mdm_boot_status;
701}
702
703static int mdm_subsys_ramdumps(int want_dumps,
Stephen Boyd0ebf7212012-04-30 20:42:35 -0700704 const struct subsys_desc *crashed_subsys)
Joel Kingb6f0f612011-11-01 16:59:14 -0700705{
Ameya Thakur552e58a2013-01-30 11:33:22 -0800706 struct mdm_device *mdev =
707 container_of(crashed_subsys, struct mdm_device,
708 mdm_subsys);
709 struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
710
711 pr_debug("%s: ssr on modem id %d\n", __func__,
712 mdev->mdm_data.device_id);
713
Joel Kingb6f0f612011-11-01 16:59:14 -0700714 mdm_drv->mdm_ram_dump_status = 0;
Ameya Thakur552e58a2013-01-30 11:33:22 -0800715 cancel_delayed_work(&mdev->mdm2ap_status_check_work);
Joel Kingb6f0f612011-11-01 16:59:14 -0700716 if (want_dumps) {
717 mdm_drv->boot_type = CHARM_RAM_DUMPS;
Ameya Thakur552e58a2013-01-30 11:33:22 -0800718 complete(&mdev->mdm_needs_reload);
719 if (!wait_for_completion_timeout(&mdev->mdm_ram_dumps,
720 msecs_to_jiffies(mdev->dump_timeout_ms))) {
Joel King52d7fa62012-03-21 08:40:52 -0700721 mdm_drv->mdm_ram_dump_status = -ETIMEDOUT;
Ameya Thakur552e58a2013-01-30 11:33:22 -0800722 mdm_ssr_completed(mdev);
723 pr_err("%s: mdm modem ramdumps timed out.\n",
Joel King52d7fa62012-03-21 08:40:52 -0700724 __func__);
725 } else
Ameya Thakur552e58a2013-01-30 11:33:22 -0800726 pr_debug("%s: mdm modem ramdumps completed.\n",
Joel King52d7fa62012-03-21 08:40:52 -0700727 __func__);
Ameya Thakur552e58a2013-01-30 11:33:22 -0800728 init_completion(&mdev->mdm_ram_dumps);
Joel King269aa602012-07-23 08:07:35 -0700729 if (!mdm_drv->pdata->no_powerdown_after_ramdumps) {
Ameya Thakur552e58a2013-01-30 11:33:22 -0800730 mdm_ops->power_down_mdm_cb(mdm_drv);
Joel King269aa602012-07-23 08:07:35 -0700731 /* Update gpio configuration to "booting" config. */
Ameya Thakur552e58a2013-01-30 11:33:22 -0800732 mdm_update_gpio_configs(mdev,
733 GPIO_UPDATE_BOOTING_CONFIG);
Joel King269aa602012-07-23 08:07:35 -0700734 }
Joel Kingb6f0f612011-11-01 16:59:14 -0700735 }
736 return mdm_drv->mdm_ram_dump_status;
737}
738
Joel King96c96dc2012-07-30 09:06:15 -0700739/* Once the gpios are sent to RPM and debugging
740 * starts, there is no way to stop it without
741 * rebooting the device.
742 */
743static int mdm_debug_mask_set(void *data, u64 val)
Joel Kingb6f0f612011-11-01 16:59:14 -0700744{
Joel King96c96dc2012-07-30 09:06:15 -0700745 if (!vddmin_gpios_sent &&
746 (val & MDM_DEBUG_MASK_VDDMIN_SETUP)) {
747 mdm_setup_vddmin_gpios();
748 vddmin_gpios_sent = 1;
749 }
750
751 mdm_debug_mask = val;
Ameya Thakur552e58a2013-01-30 11:33:22 -0800752 if (mdm_ops->debug_state_changed_cb)
753 mdm_ops->debug_state_changed_cb(mdm_debug_mask);
Joel Kingb6f0f612011-11-01 16:59:14 -0700754 return 0;
755}
756
Joel King96c96dc2012-07-30 09:06:15 -0700757static int mdm_debug_mask_get(void *data, u64 *val)
Joel Kingb6f0f612011-11-01 16:59:14 -0700758{
Joel King96c96dc2012-07-30 09:06:15 -0700759 *val = mdm_debug_mask;
Joel Kingb6f0f612011-11-01 16:59:14 -0700760 return 0;
761}
762
Joel King96c96dc2012-07-30 09:06:15 -0700763DEFINE_SIMPLE_ATTRIBUTE(mdm_debug_mask_fops,
764 mdm_debug_mask_get,
765 mdm_debug_mask_set, "%llu\n");
Joel Kingb6f0f612011-11-01 16:59:14 -0700766
767static int mdm_debugfs_init(void)
768{
769 struct dentry *dent;
770
771 dent = debugfs_create_dir("mdm_dbg", 0);
772 if (IS_ERR(dent))
773 return PTR_ERR(dent);
774
Joel King96c96dc2012-07-30 09:06:15 -0700775 debugfs_create_file("debug_mask", 0644, dent, NULL,
776 &mdm_debug_mask_fops);
Joel Kingb6f0f612011-11-01 16:59:14 -0700777 return 0;
778}
779
Ameya Thakur552e58a2013-01-30 11:33:22 -0800780static const struct file_operations mdm_modem_fops = {
781 .owner = THIS_MODULE,
782 .open = mdm_modem_open,
783 .unlocked_ioctl = mdm_modem_ioctl,
784};
785
786static void mdm_modem_initialize_data(struct platform_device *pdev,
787 struct mdm_device *mdev)
Joel Kingb6f0f612011-11-01 16:59:14 -0700788{
Ameya Thakur552e58a2013-01-30 11:33:22 -0800789 struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
Joel Kingb6f0f612011-11-01 16:59:14 -0700790 struct resource *pres;
791
Ameya Thakur552e58a2013-01-30 11:33:22 -0800792 mdm_drv->pdata = pdev->dev.platform_data;
793 if (pdev->id < 0)
794 mdm_drv->device_id = 0;
795 else
796 mdm_drv->device_id = pdev->id;
797
798 memset((void *)&mdev->mdm_subsys, 0,
799 sizeof(struct subsys_desc));
800 if (mdev->mdm_data.device_id <= 0)
801 snprintf(mdev->subsys_name, sizeof(mdev->subsys_name),
802 "%s", EXTERNAL_MODEM);
803 else
804 snprintf(mdev->subsys_name, sizeof(mdev->subsys_name),
805 "%s.%d", EXTERNAL_MODEM, mdev->mdm_data.device_id);
806 mdev->mdm_subsys.shutdown = mdm_subsys_shutdown;
807 mdev->mdm_subsys.ramdump = mdm_subsys_ramdumps;
808 mdev->mdm_subsys.powerup = mdm_subsys_powerup;
809 mdev->mdm_subsys.name = mdev->subsys_name;
810
811 memset((void *)&mdev->misc_device, 0,
812 sizeof(struct miscdevice));
813 if (mdev->mdm_data.device_id <= 0)
814 snprintf(mdev->device_name, sizeof(mdev->device_name),
815 "%s", DEVICE_BASE_NAME);
816 else
817 snprintf(mdev->device_name, sizeof(mdev->device_name),
818 "%s%d", DEVICE_BASE_NAME, mdev->mdm_data.device_id);
819 mdev->misc_device.minor = MISC_DYNAMIC_MINOR;
820 mdev->misc_device.name = mdev->device_name;
821 mdev->misc_device.fops = &mdm_modem_fops;
822
823 memset((void *)&mdev->mdm_panic_blk, 0,
824 sizeof(struct notifier_block));
825 mdev->mdm_panic_blk.notifier_call = mdm_panic_prep;
826 atomic_notifier_chain_register(&panic_notifier_list,
827 &mdev->mdm_panic_blk);
828
Joel Kingb6f0f612011-11-01 16:59:14 -0700829 /* MDM2AP_ERRFATAL */
830 pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
831 "MDM2AP_ERRFATAL");
Ameya Thakure155ece2012-07-09 12:08:37 -0700832 mdm_drv->mdm2ap_errfatal_gpio = pres ? pres->start : -1;
Joel Kingb6f0f612011-11-01 16:59:14 -0700833
834 /* AP2MDM_ERRFATAL */
835 pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
836 "AP2MDM_ERRFATAL");
Ameya Thakure155ece2012-07-09 12:08:37 -0700837 mdm_drv->ap2mdm_errfatal_gpio = pres ? pres->start : -1;
Joel Kingb6f0f612011-11-01 16:59:14 -0700838
839 /* MDM2AP_STATUS */
840 pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
841 "MDM2AP_STATUS");
Ameya Thakure155ece2012-07-09 12:08:37 -0700842 mdm_drv->mdm2ap_status_gpio = pres ? pres->start : -1;
Joel Kingb6f0f612011-11-01 16:59:14 -0700843
844 /* AP2MDM_STATUS */
845 pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
846 "AP2MDM_STATUS");
Ameya Thakure155ece2012-07-09 12:08:37 -0700847 mdm_drv->ap2mdm_status_gpio = pres ? pres->start : -1;
Joel Kingb6f0f612011-11-01 16:59:14 -0700848
849 /* MDM2AP_WAKEUP */
850 pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
851 "MDM2AP_WAKEUP");
Ameya Thakure155ece2012-07-09 12:08:37 -0700852 mdm_drv->mdm2ap_wakeup_gpio = pres ? pres->start : -1;
Joel Kingb6f0f612011-11-01 16:59:14 -0700853
854 /* AP2MDM_WAKEUP */
855 pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
856 "AP2MDM_WAKEUP");
Ameya Thakure155ece2012-07-09 12:08:37 -0700857 mdm_drv->ap2mdm_wakeup_gpio = pres ? pres->start : -1;
Joel Kingb6f0f612011-11-01 16:59:14 -0700858
Joel Kinge92eb872012-05-06 09:30:24 -0700859 /* AP2MDM_SOFT_RESET */
Joel Kingb6f0f612011-11-01 16:59:14 -0700860 pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
Joel Kinge92eb872012-05-06 09:30:24 -0700861 "AP2MDM_SOFT_RESET");
Ameya Thakure155ece2012-07-09 12:08:37 -0700862 mdm_drv->ap2mdm_soft_reset_gpio = pres ? pres->start : -1;
Joel Kingb6f0f612011-11-01 16:59:14 -0700863
864 /* AP2MDM_KPDPWR_N */
865 pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
866 "AP2MDM_KPDPWR_N");
Ameya Thakure155ece2012-07-09 12:08:37 -0700867 mdm_drv->ap2mdm_kpdpwr_n_gpio = pres ? pres->start : -1;
Joel Kingb6f0f612011-11-01 16:59:14 -0700868
Joel Kinge92eb872012-05-06 09:30:24 -0700869 /* AP2MDM_PMIC_PWR_EN */
870 pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
871 "AP2MDM_PMIC_PWR_EN");
Ameya Thakure155ece2012-07-09 12:08:37 -0700872 mdm_drv->ap2mdm_pmic_pwr_en_gpio = pres ? pres->start : -1;
Joel Kinge92eb872012-05-06 09:30:24 -0700873
Vamsi Krishnac6dcd5e2012-05-09 15:38:01 -0700874 /* MDM2AP_PBLRDY */
875 pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
876 "MDM2AP_PBLRDY");
Ameya Thakure155ece2012-07-09 12:08:37 -0700877 mdm_drv->mdm2ap_pblrdy = pres ? pres->start : -1;
Vamsi Krishnac6dcd5e2012-05-09 15:38:01 -0700878
Ameya Thakur43248fd2012-07-10 18:50:52 -0700879 /*USB_SW*/
880 pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
881 "USB_SW");
Ameya Thakure155ece2012-07-09 12:08:37 -0700882 mdm_drv->usb_switch_gpio = pres ? pres->start : -1;
Ameya Thakur43248fd2012-07-10 18:50:52 -0700883
Joel Kingb6f0f612011-11-01 16:59:14 -0700884 mdm_drv->boot_type = CHARM_NORMAL_BOOT;
885
Ameya Thakur552e58a2013-01-30 11:33:22 -0800886 mdm_drv->dump_timeout_ms = mdm_drv->pdata->ramdump_timeout_ms > 0 ?
Ameya Thakurc9a7a842012-06-24 22:47:52 -0700887 mdm_drv->pdata->ramdump_timeout_ms : MDM_RDUMP_TIMEOUT;
Ameya Thakur552e58a2013-01-30 11:33:22 -0800888
889 init_completion(&mdev->mdm_needs_reload);
890 init_completion(&mdev->mdm_boot);
891 init_completion(&mdev->mdm_ram_dumps);
892
893 mdev->first_boot = 1;
894 mutex_init(&mdm_drv->peripheral_status_lock);
Joel Kingb6f0f612011-11-01 16:59:14 -0700895}
896
Ameya Thakur552e58a2013-01-30 11:33:22 -0800897static void mdm_deconfigure_ipc(struct mdm_device *mdev)
Joel Kingb6f0f612011-11-01 16:59:14 -0700898{
Ameya Thakur552e58a2013-01-30 11:33:22 -0800899 struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
Joel Kingb6f0f612011-11-01 16:59:14 -0700900
Ameya Thakur552e58a2013-01-30 11:33:22 -0800901 gpio_free(mdm_drv->ap2mdm_status_gpio);
902 gpio_free(mdm_drv->ap2mdm_errfatal_gpio);
903 if (GPIO_IS_VALID(mdm_drv->ap2mdm_kpdpwr_n_gpio))
904 gpio_free(mdm_drv->ap2mdm_kpdpwr_n_gpio);
905 if (GPIO_IS_VALID(mdm_drv->ap2mdm_pmic_pwr_en_gpio))
906 gpio_free(mdm_drv->ap2mdm_pmic_pwr_en_gpio);
907 gpio_free(mdm_drv->mdm2ap_status_gpio);
908 gpio_free(mdm_drv->mdm2ap_errfatal_gpio);
909 if (GPIO_IS_VALID(mdm_drv->ap2mdm_soft_reset_gpio))
910 gpio_free(mdm_drv->ap2mdm_soft_reset_gpio);
911
912 if (GPIO_IS_VALID(mdm_drv->ap2mdm_wakeup_gpio))
913 gpio_free(mdm_drv->ap2mdm_wakeup_gpio);
914
915 if (mdev->mdm_queue) {
916 destroy_workqueue(mdev->mdm_queue);
917 mdev->mdm_queue = NULL;
Joel Kingb6f0f612011-11-01 16:59:14 -0700918 }
Ameya Thakur552e58a2013-01-30 11:33:22 -0800919 if (mdev->mdm_sfr_queue) {
920 destroy_workqueue(mdev->mdm_sfr_queue);
921 mdev->mdm_sfr_queue = NULL;
922 }
923}
Joel Kingb6f0f612011-11-01 16:59:14 -0700924
Ameya Thakur552e58a2013-01-30 11:33:22 -0800925static int mdm_configure_ipc(struct mdm_device *mdev)
926{
927 struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
928 int ret = -1, irq;
Joel Kingb6f0f612011-11-01 16:59:14 -0700929
930 gpio_request(mdm_drv->ap2mdm_status_gpio, "AP2MDM_STATUS");
931 gpio_request(mdm_drv->ap2mdm_errfatal_gpio, "AP2MDM_ERRFATAL");
Ameya Thakure155ece2012-07-09 12:08:37 -0700932 if (GPIO_IS_VALID(mdm_drv->ap2mdm_kpdpwr_n_gpio))
Joel Kinge92eb872012-05-06 09:30:24 -0700933 gpio_request(mdm_drv->ap2mdm_kpdpwr_n_gpio, "AP2MDM_KPDPWR_N");
Joel Kingb6f0f612011-11-01 16:59:14 -0700934 gpio_request(mdm_drv->mdm2ap_status_gpio, "MDM2AP_STATUS");
935 gpio_request(mdm_drv->mdm2ap_errfatal_gpio, "MDM2AP_ERRFATAL");
Ameya Thakure155ece2012-07-09 12:08:37 -0700936 if (GPIO_IS_VALID(mdm_drv->mdm2ap_pblrdy))
Vamsi Krishnac6dcd5e2012-05-09 15:38:01 -0700937 gpio_request(mdm_drv->mdm2ap_pblrdy, "MDM2AP_PBLRDY");
Joel Kingb6f0f612011-11-01 16:59:14 -0700938
Ameya Thakure155ece2012-07-09 12:08:37 -0700939 if (GPIO_IS_VALID(mdm_drv->ap2mdm_pmic_pwr_en_gpio))
Joel Kinge92eb872012-05-06 09:30:24 -0700940 gpio_request(mdm_drv->ap2mdm_pmic_pwr_en_gpio,
941 "AP2MDM_PMIC_PWR_EN");
Ameya Thakure155ece2012-07-09 12:08:37 -0700942 if (GPIO_IS_VALID(mdm_drv->ap2mdm_soft_reset_gpio))
Joel Kinge92eb872012-05-06 09:30:24 -0700943 gpio_request(mdm_drv->ap2mdm_soft_reset_gpio,
944 "AP2MDM_SOFT_RESET");
945
Ameya Thakure155ece2012-07-09 12:08:37 -0700946 if (GPIO_IS_VALID(mdm_drv->ap2mdm_wakeup_gpio))
Joel Kingb6f0f612011-11-01 16:59:14 -0700947 gpio_request(mdm_drv->ap2mdm_wakeup_gpio, "AP2MDM_WAKEUP");
948
Ameya Thakure155ece2012-07-09 12:08:37 -0700949 if (GPIO_IS_VALID(mdm_drv->usb_switch_gpio)) {
Ameya Thakur43248fd2012-07-10 18:50:52 -0700950 if (gpio_request(mdm_drv->usb_switch_gpio, "USB_SW")) {
951 pr_err("%s Failed to get usb switch gpio\n", __func__);
952 mdm_drv->usb_switch_gpio = -1;
953 }
954 }
Taniya Dasa1a14a92013-01-21 15:14:15 +0530955 gpio_direction_output(mdm_drv->ap2mdm_status_gpio, 0);
Joel Kingb6f0f612011-11-01 16:59:14 -0700956 gpio_direction_output(mdm_drv->ap2mdm_errfatal_gpio, 0);
957
Ameya Thakure155ece2012-07-09 12:08:37 -0700958 if (GPIO_IS_VALID(mdm_drv->ap2mdm_wakeup_gpio))
Joel Kingb6f0f612011-11-01 16:59:14 -0700959 gpio_direction_output(mdm_drv->ap2mdm_wakeup_gpio, 0);
960
961 gpio_direction_input(mdm_drv->mdm2ap_status_gpio);
962 gpio_direction_input(mdm_drv->mdm2ap_errfatal_gpio);
963
Ameya Thakur552e58a2013-01-30 11:33:22 -0800964 mdev->mdm_queue = alloc_workqueue("mdm_queue", 0, 0);
965 if (!mdev->mdm_queue) {
966 pr_err("%s: could not create mdm_queue for mdm id %d\n",
967 __func__, mdev->mdm_data.device_id);
Joel Kingb6f0f612011-11-01 16:59:14 -0700968 ret = -ENOMEM;
969 goto fatal_err;
970 }
971
Ameya Thakur552e58a2013-01-30 11:33:22 -0800972 mdev->mdm_sfr_queue = alloc_workqueue("mdm_sfr_queue", 0, 0);
973 if (!mdev->mdm_sfr_queue) {
974 pr_err("%s: could not create mdm_sfr_queue for mdm id %d\n",
975 __func__, mdev->mdm_data.device_id);
Joel King30fdd662012-05-07 19:39:29 -0700976 ret = -ENOMEM;
Joel King30fdd662012-05-07 19:39:29 -0700977 goto fatal_err;
978 }
979
Joel Kingb6f0f612011-11-01 16:59:14 -0700980 /* Register subsystem handlers */
Ameya Thakur552e58a2013-01-30 11:33:22 -0800981 mdev->mdm_subsys_dev = subsys_register(&mdev->mdm_subsys);
982 if (IS_ERR(mdev->mdm_subsys_dev)) {
983 ret = PTR_ERR(mdev->mdm_subsys_dev);
Stephen Boyd0ebf7212012-04-30 20:42:35 -0700984 goto fatal_err;
985 }
Ameya Thakur552e58a2013-01-30 11:33:22 -0800986 subsys_default_online(mdev->mdm_subsys_dev);
Joel Kingb6f0f612011-11-01 16:59:14 -0700987
988 /* ERR_FATAL irq. */
Ameya Thakur552e58a2013-01-30 11:33:22 -0800989 irq = gpio_to_irq(mdm_drv->mdm2ap_errfatal_gpio);
Joel Kingb6f0f612011-11-01 16:59:14 -0700990 if (irq < 0) {
Ameya Thakur552e58a2013-01-30 11:33:22 -0800991 pr_err("%s: bad MDM2AP_ERRFATAL IRQ resource, err = %d\n",
992 __func__, irq);
Joel Kingb6f0f612011-11-01 16:59:14 -0700993 goto errfatal_err;
994 }
995 ret = request_irq(irq, mdm_errfatal,
Ameya Thakur552e58a2013-01-30 11:33:22 -0800996 IRQF_TRIGGER_RISING , "mdm errfatal", mdev);
Joel Kingb6f0f612011-11-01 16:59:14 -0700997
998 if (ret < 0) {
Ameya Thakur552e58a2013-01-30 11:33:22 -0800999 pr_err("%s: MDM2AP_ERRFATAL IRQ#%d request failed, err=%d\n",
1000 __func__, irq, ret);
Joel Kingb6f0f612011-11-01 16:59:14 -07001001 goto errfatal_err;
1002 }
Ameya Thakur552e58a2013-01-30 11:33:22 -08001003 mdev->mdm_errfatal_irq = irq;
Joel Kingb6f0f612011-11-01 16:59:14 -07001004
1005errfatal_err:
1006
Ameya Thakur552e58a2013-01-30 11:33:22 -08001007 /* status irq */
1008 irq = gpio_to_irq(mdm_drv->mdm2ap_status_gpio);
Joel Kingb6f0f612011-11-01 16:59:14 -07001009 if (irq < 0) {
Ameya Thakur552e58a2013-01-30 11:33:22 -08001010 pr_err("%s: bad MDM2AP_STATUS IRQ resource, err = %d\n",
1011 __func__, irq);
Joel Kingb6f0f612011-11-01 16:59:14 -07001012 goto status_err;
1013 }
1014
1015 ret = request_threaded_irq(irq, NULL, mdm_status_change,
Vamsi Krishna33925632011-12-13 15:43:09 -08001016 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED,
Ameya Thakur552e58a2013-01-30 11:33:22 -08001017 "mdm status", mdev);
Joel Kingb6f0f612011-11-01 16:59:14 -07001018
1019 if (ret < 0) {
Ameya Thakur552e58a2013-01-30 11:33:22 -08001020 pr_err("%s: MDM2AP_STATUS IRQ#%d request failed, err=%d",
1021 __func__, irq, ret);
Joel Kingb6f0f612011-11-01 16:59:14 -07001022 goto status_err;
1023 }
Ameya Thakur552e58a2013-01-30 11:33:22 -08001024 mdev->mdm_status_irq = irq;
Joel Kingb6f0f612011-11-01 16:59:14 -07001025
1026status_err:
Ameya Thakure155ece2012-07-09 12:08:37 -07001027 if (GPIO_IS_VALID(mdm_drv->mdm2ap_pblrdy)) {
Ameya Thakur552e58a2013-01-30 11:33:22 -08001028 irq = gpio_to_irq(mdm_drv->mdm2ap_pblrdy);
Vamsi Krishnac6dcd5e2012-05-09 15:38:01 -07001029 if (irq < 0) {
Ameya Thakur552e58a2013-01-30 11:33:22 -08001030 pr_err("%s: could not get MDM2AP_PBLRDY IRQ resource\n",
1031 __func__);
Vamsi Krishnac6dcd5e2012-05-09 15:38:01 -07001032 goto pblrdy_err;
1033 }
1034
1035 ret = request_threaded_irq(irq, NULL, mdm_pblrdy_change,
Ameya Thakur552e58a2013-01-30 11:33:22 -08001036 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
1037 IRQF_SHARED,
1038 "mdm pbl ready", mdev);
Vamsi Krishnac6dcd5e2012-05-09 15:38:01 -07001039
1040 if (ret < 0) {
Ameya Thakur552e58a2013-01-30 11:33:22 -08001041 pr_err("%s: MDM2AP_PBL IRQ#%d request failed error=%d\n",
Vamsi Krishnac6dcd5e2012-05-09 15:38:01 -07001042 __func__, irq, ret);
1043 goto pblrdy_err;
1044 }
Ameya Thakur552e58a2013-01-30 11:33:22 -08001045 mdev->mdm_pblrdy_irq = irq;
Vamsi Krishnac6dcd5e2012-05-09 15:38:01 -07001046 }
1047
1048pblrdy_err:
Joel Kinge92eb872012-05-06 09:30:24 -07001049 /*
1050 * If AP2MDM_PMIC_PWR_EN gpio is used, pull it high. It remains
1051 * high until the whole phone is shut down.
1052 */
Ameya Thakure155ece2012-07-09 12:08:37 -07001053 if (GPIO_IS_VALID(mdm_drv->ap2mdm_pmic_pwr_en_gpio))
Joel Kinge92eb872012-05-06 09:30:24 -07001054 gpio_direction_output(mdm_drv->ap2mdm_pmic_pwr_en_gpio, 1);
1055
Ameya Thakur552e58a2013-01-30 11:33:22 -08001056 return 0;
Joel Kingb6f0f612011-11-01 16:59:14 -07001057
1058fatal_err:
Ameya Thakur552e58a2013-01-30 11:33:22 -08001059 mdm_deconfigure_ipc(mdev);
Joel Kingb6f0f612011-11-01 16:59:14 -07001060 return ret;
1061}
1062
Ameya Thakur552e58a2013-01-30 11:33:22 -08001063static int __devinit mdm_modem_probe(struct platform_device *pdev)
1064{
1065 struct mdm_device *mdev = NULL;
1066 int ret = -1;
1067
1068 mdev = kzalloc(sizeof(struct mdm_device), GFP_KERNEL);
1069 if (!mdev) {
1070 pr_err("%s: kzalloc fail.\n", __func__);
1071 ret = -ENOMEM;
1072 goto init_err;
1073 }
1074
1075 mdm_modem_initialize_data(pdev, mdev);
1076
1077 if (mdm_ops->debug_state_changed_cb)
1078 mdm_ops->debug_state_changed_cb(mdm_debug_mask);
1079
1080 if (mdm_configure_ipc(mdev)) {
1081 pr_err("%s: mdm_configure_ipc failed, id = %d\n",
1082 __func__, mdev->mdm_data.device_id);
1083 goto init_err;
1084 }
1085
1086 pr_debug("%s: Registering mdm id %d\n", __func__,
1087 mdev->mdm_data.device_id);
1088 ret = misc_register(&mdev->misc_device);
1089 if (ret) {
1090 pr_err("%s: failed registering mdm id %d, ret = %d\n",
1091 __func__, mdev->mdm_data.device_id, ret);
1092 mdm_deconfigure_ipc(mdev);
1093 goto init_err;
1094 } else {
1095 pr_err("%s: registered mdm id %d\n",
1096 __func__, mdev->mdm_data.device_id);
1097
1098 mdm_device_list_add(mdev);
1099 INIT_DELAYED_WORK(&mdev->mdm2ap_status_check_work,
1100 mdm2ap_status_check);
1101 INIT_WORK(&mdev->mdm_status_work, mdm_status_fn);
1102 INIT_WORK(&mdev->sfr_reason_work, mdm_restart_reason_fn);
1103
1104 /* Perform early powerup of the external modem in order to
1105 * allow tabla devices to be found.
1106 */
1107 if (mdev->mdm_data.pdata->early_power_on)
1108 mdm_ops->power_on_mdm_cb(&mdev->mdm_data);
1109 }
1110
1111 return ret;
1112
1113init_err:
1114 kfree(mdev);
1115 return ret;
1116}
1117
1118static int __devexit mdm_modem_remove(struct platform_device *pdev)
Joel Kingb6f0f612011-11-01 16:59:14 -07001119{
1120 int ret;
Ameya Thakur552e58a2013-01-30 11:33:22 -08001121 struct mdm_device *mdev = mdm_get_device_by_device_id(pdev->id);
Joel Kingb6f0f612011-11-01 16:59:14 -07001122
Ameya Thakur552e58a2013-01-30 11:33:22 -08001123 if (!mdev)
1124 return -ENODEV;
Joel Kingb6f0f612011-11-01 16:59:14 -07001125
Ameya Thakur552e58a2013-01-30 11:33:22 -08001126 pr_debug("%s: removing device id %d\n",
1127 __func__, mdev->mdm_data.device_id);
1128 mdm_deconfigure_ipc(mdev);
1129 ret = misc_deregister(&mdev->misc_device);
1130 mdm_device_list_remove(mdev);
1131 kfree(mdev);
Joel Kingb6f0f612011-11-01 16:59:14 -07001132 return ret;
1133}
1134
Ameya Thakur552e58a2013-01-30 11:33:22 -08001135static void mdm_modem_shutdown(struct platform_device *pdev)
Joel Kingb6f0f612011-11-01 16:59:14 -07001136{
Ameya Thakur552e58a2013-01-30 11:33:22 -08001137 struct mdm_modem_drv *mdm_drv;
1138 struct mdm_device *mdev = mdm_get_device_by_device_id(pdev->id);
1139 if (!mdev)
1140 return;
Joel Kingb6f0f612011-11-01 16:59:14 -07001141
Ameya Thakur552e58a2013-01-30 11:33:22 -08001142 pr_debug("%s: shutting down device id %d\n",
1143 __func__, mdev->mdm_data.device_id);
1144
1145 mdm_disable_irqs(mdev);
1146 mdm_drv = &mdev->mdm_data;
1147 mdm_ops->power_down_mdm_cb(mdm_drv);
Ameya Thakure155ece2012-07-09 12:08:37 -07001148 if (GPIO_IS_VALID(mdm_drv->ap2mdm_pmic_pwr_en_gpio))
Joel Kinge92eb872012-05-06 09:30:24 -07001149 gpio_direction_output(mdm_drv->ap2mdm_pmic_pwr_en_gpio, 0);
Joel Kingb6f0f612011-11-01 16:59:14 -07001150}
1151
Ameya Thakur552e58a2013-01-30 11:33:22 -08001152static struct of_device_id mdm_match_table[] = {
1153 {.compatible = "qcom,mdm2_modem,mdm2_modem.1"},
1154 {},
1155};
1156
1157static struct platform_driver mdm_modem_driver = {
1158 .probe = mdm_modem_probe,
1159 .remove = __devexit_p(mdm_modem_remove),
1160 .shutdown = mdm_modem_shutdown,
1161 .driver = {
1162 .name = "mdm2_modem",
1163 .owner = THIS_MODULE,
1164 .of_match_table = mdm_match_table,
1165 },
1166};
1167
1168static int __init mdm_modem_init(void)
1169{
1170 int ret;
1171
1172 ret = mdm_get_ops(&mdm_ops);
1173 if (ret)
1174 return ret;
1175
1176 INIT_LIST_HEAD(&mdm_devices);
1177 mdm_debugfs_init();
1178 return platform_driver_register(&mdm_modem_driver);
1179}
1180
1181static void __exit mdm_modem_exit(void)
1182{
1183 platform_driver_unregister(&mdm_modem_driver);
1184}
1185
1186module_init(mdm_modem_init);
1187module_exit(mdm_modem_exit);
1188
1189MODULE_LICENSE("GPL v2");
1190MODULE_DESCRIPTION("mdm modem driver");
1191MODULE_VERSION("2.0");
1192MODULE_ALIAS("mdm_modem");