msm: mdm: add support for multiple external modems

The mdm driver previously assumed that it would only need to support
one external modem at a time. This is no longer true. The changes now
support a arbitrary number of modems. Each modem must have its own
platform data.

Change-Id: I43918f92935b4b447a46d02721239fed0b5e877e
Signed-off-by: Ameya Thakur <ameyat@codeaurora.org>
diff --git a/arch/arm/mach-msm/mdm_common.c b/arch/arm/mach-msm/mdm_common.c
index 0597c62..536e859 100644
--- a/arch/arm/mach-msm/mdm_common.c
+++ b/arch/arm/mach-msm/mdm_common.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved.
+/* Copyright (c) 2011-2013, The Linux Foundation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 and
@@ -49,22 +49,15 @@
 #define MDM_RDUMP_TIMEOUT	120000L
 #define MDM2AP_STATUS_TIMEOUT_MS 60000L
 
-static unsigned int mdm_debug_mask;
-static struct workqueue_struct *mdm_queue;
-static struct workqueue_struct *mdm_sfr_queue;
-static unsigned int dump_timeout_ms;
-static int vddmin_gpios_sent;
-
+/* Allow a maximum device id of this many digits */
+#define MAX_DEVICE_DIGITS  10
 #define EXTERNAL_MODEM "external_modem"
+#define SUBSYS_NAME_LENGTH \
+	(sizeof(EXTERNAL_MODEM) + MAX_DEVICE_DIGITS)
 
-static struct mdm_modem_drv *mdm_drv;
-static struct subsys_device *mdm_subsys_dev;
-
-DECLARE_COMPLETION(mdm_needs_reload);
-DECLARE_COMPLETION(mdm_boot);
-DECLARE_COMPLETION(mdm_ram_dumps);
-
-static int first_boot = 1;
+#define DEVICE_BASE_NAME "mdm"
+#define DEVICE_NAME_LENGTH \
+	(sizeof(DEVICE_BASE_NAME) + MAX_DEVICE_DIGITS)
 
 #define RD_BUF_SIZE			100
 #define SFR_MAX_RETRIES		10
@@ -74,58 +67,246 @@
 	GPIO_UPDATE_BOOTING_CONFIG = 1,
 	GPIO_UPDATE_RUNNING_CONFIG,
 };
-static int mdm2ap_status_valid_old_config;
-static struct gpiomux_setting mdm2ap_status_old_config;
+
+struct mdm_device {
+	struct list_head		link;
+	struct mdm_modem_drv	mdm_data;
+
+	int mdm2ap_status_valid_old_config;
+	struct gpiomux_setting mdm2ap_status_old_config;
+	int first_boot;
+	struct workqueue_struct *mdm_queue;
+	struct workqueue_struct *mdm_sfr_queue;
+	unsigned int dump_timeout_ms;
+
+	char subsys_name[SUBSYS_NAME_LENGTH];
+	struct subsys_desc mdm_subsys;
+	struct subsys_device *mdm_subsys_dev;
+
+	char device_name[DEVICE_NAME_LENGTH];
+	struct miscdevice misc_device;
+
+	struct completion mdm_needs_reload;
+	struct completion mdm_boot;
+	struct completion mdm_ram_dumps;
+	int mdm_errfatal_irq;
+	int mdm_status_irq;
+	int mdm_pblrdy_irq;
+
+	struct delayed_work mdm2ap_status_check_work;
+	struct work_struct mdm_status_work;
+	struct work_struct sfr_reason_work;
+
+	struct notifier_block mdm_panic_blk;
+
+	int ssr_started_internally;
+};
+
+static struct list_head	mdm_devices;
+static DEFINE_SPINLOCK(mdm_devices_lock);
+
+static int ssr_count;
+static DEFINE_SPINLOCK(ssr_lock);
+
+static unsigned int mdm_debug_mask;
+int vddmin_gpios_sent;
+static struct mdm_ops *mdm_ops;
+
+static void mdm_device_list_add(struct mdm_device *mdev)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&mdm_devices_lock, flags);
+	list_add_tail(&mdev->link, &mdm_devices);
+	spin_unlock_irqrestore(&mdm_devices_lock, flags);
+}
+
+static void mdm_device_list_remove(struct mdm_device *mdev)
+{
+	unsigned long flags;
+	struct mdm_device *lmdev, *tmp;
+
+	spin_lock_irqsave(&mdm_devices_lock, flags);
+	list_for_each_entry_safe(lmdev, tmp, &mdm_devices, link) {
+		if (mdev && mdev == lmdev) {
+			pr_debug("%s: removing device id %d\n",
+			  __func__, mdev->mdm_data.device_id);
+			list_del(&mdev->link);
+			break;
+		}
+	}
+	spin_unlock_irqrestore(&mdm_devices_lock, flags);
+}
+
+struct mdm_device *mdm_get_device_by_device_id(int device_id)
+{
+	unsigned long flags;
+	struct mdm_device *mdev = NULL;
+
+	spin_lock_irqsave(&mdm_devices_lock, flags);
+	list_for_each_entry(mdev, &mdm_devices, link) {
+		if (mdev && mdev->mdm_data.device_id == device_id) {
+			spin_unlock_irqrestore(&mdm_devices_lock, flags);
+			return mdev;
+		}
+	}
+	spin_unlock_irqrestore(&mdm_devices_lock, flags);
+	return NULL;
+}
+
+struct mdm_device *mdm_get_device_by_name(const char *name)
+{
+	unsigned long flags;
+	struct mdm_device *mdev;
+
+	if (!name)
+		return NULL;
+	spin_lock_irqsave(&mdm_devices_lock, flags);
+	list_for_each_entry(mdev, &mdm_devices, link) {
+		if (mdev && !strncmp(mdev->device_name, name,
+				sizeof(mdev->device_name))) {
+			spin_unlock_irqrestore(&mdm_devices_lock, flags);
+			return mdev;
+		}
+	}
+	spin_unlock_irqrestore(&mdm_devices_lock, flags);
+	return NULL;
+}
+
+/* If the platform's cascading_ssr flag is set, the subsystem
+ * restart module will restart the other modems so stop
+ * monitoring them as well.
+ * This function can be called from interrupt context.
+ */
+static void mdm_start_ssr(struct mdm_device *mdev)
+{
+	unsigned long flags;
+	int start_ssr = 1;
+
+	spin_lock_irqsave(&ssr_lock, flags);
+	if (mdev->mdm_data.pdata->cascading_ssr &&
+			ssr_count > 0) {
+		start_ssr = 0;
+	} else {
+		ssr_count++;
+		mdev->ssr_started_internally = 1;
+	}
+	spin_unlock_irqrestore(&ssr_lock, flags);
+
+	if (start_ssr) {
+		atomic_set(&mdev->mdm_data.mdm_ready, 0);
+		pr_info("%s: Resetting mdm id %d due to mdm error\n",
+				__func__, mdev->mdm_data.device_id);
+		subsystem_restart_dev(mdev->mdm_subsys_dev);
+	} else {
+		pr_info("%s: Another modem is already in SSR\n",
+				__func__);
+	}
+}
+
+/* Increment the reference count to handle the case where
+ * subsystem restart is initiated by the SSR service.
+ */
+static void mdm_ssr_started(struct mdm_device *mdev)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&ssr_lock, flags);
+	ssr_count++;
+	atomic_set(&mdev->mdm_data.mdm_ready, 0);
+	spin_unlock_irqrestore(&ssr_lock, flags);
+}
+
+/* mdm_ssr_completed assumes that mdm_ssr_started has previously
+ * been called.
+ */
+static void mdm_ssr_completed(struct mdm_device *mdev)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&ssr_lock, flags);
+	ssr_count--;
+	if (mdev->ssr_started_internally) {
+		mdev->ssr_started_internally = 0;
+		ssr_count--;
+	}
+
+	if (ssr_count < 0) {
+		pr_err("%s: ssr_count = %d\n",
+			    __func__, ssr_count);
+		panic("%s: ssr_count = %d < 0\n",
+			  __func__, ssr_count);
+	}
+	spin_unlock_irqrestore(&ssr_lock, flags);
+}
 
 static irqreturn_t mdm_vddmin_change(int irq, void *dev_id)
 {
-	int value = gpio_get_value(
-		mdm_drv->pdata->vddmin_resource->mdm2ap_vddmin_gpio);
+	struct mdm_device *mdev = (struct mdm_device *)dev_id;
+	struct mdm_vddmin_resource *vddmin_res;
+	int value;
 
+	if (!mdev)
+		goto handled;
+
+	vddmin_res = mdev->mdm_data.pdata->vddmin_resource;
+	if (!vddmin_res)
+		goto handled;
+
+	value = gpio_get_value(
+	   vddmin_res->mdm2ap_vddmin_gpio);
 	if (value == 0)
-		pr_debug("External Modem entered Vddmin\n");
+		pr_info("External Modem id %d entered Vddmin\n",
+				mdev->mdm_data.device_id);
 	else
-		pr_debug("External Modem exited Vddmin\n");
-
+		pr_info("External Modem id %d exited Vddmin\n",
+				mdev->mdm_data.device_id);
+handled:
 	return IRQ_HANDLED;
 }
 
+/* The vddmin_res resource may not be supported by some platforms. */
 static void mdm_setup_vddmin_gpios(void)
 {
+	unsigned long flags;
 	struct msm_rpm_iv_pair req;
+	struct mdm_device *mdev;
 	struct mdm_vddmin_resource *vddmin_res;
 	int irq, ret;
 
-	/* This resource may not be supported by some platforms. */
-	vddmin_res = mdm_drv->pdata->vddmin_resource;
-	if (!vddmin_res)
-		return;
+	spin_lock_irqsave(&mdm_devices_lock, flags);
+	list_for_each_entry(mdev, &mdm_devices, link) {
+		vddmin_res = mdev->mdm_data.pdata->vddmin_resource;
+		if (!vddmin_res)
+			continue;
 
-	pr_info("Enabling vddmin logging\n");
-	req.id = vddmin_res->rpm_id;
-	req.value = ((uint32_t)vddmin_res->ap2mdm_vddmin_gpio & 0x0000FFFF)
-							<< 16;
-	req.value |= ((uint32_t)vddmin_res->modes & 0x000000FF) << 8;
-	req.value |= (uint32_t)vddmin_res->drive_strength & 0x000000FF;
+		pr_info("Enabling vddmin logging on modem id %d\n",
+				mdev->mdm_data.device_id);
+		req.id = vddmin_res->rpm_id;
+		req.value =
+			((uint32_t)vddmin_res->ap2mdm_vddmin_gpio & 0x0000FFFF)
+						<< 16;
+		req.value |= ((uint32_t)vddmin_res->modes & 0x000000FF) << 8;
+		req.value |= (uint32_t)vddmin_res->drive_strength & 0x000000FF;
 
-	msm_rpm_set(MSM_RPM_CTX_SET_0, &req, 1);
+		msm_rpm_set(MSM_RPM_CTX_SET_0, &req, 1);
 
-	/* Start monitoring low power gpio from mdm */
-	irq = MSM_GPIO_TO_INT(vddmin_res->mdm2ap_vddmin_gpio);
-	if (irq < 0) {
-		pr_err("%s: could not get LPM POWER IRQ resource.\n",
-			__func__);
-		goto error_end;
+		/* Start monitoring low power gpio from mdm */
+		irq = MSM_GPIO_TO_INT(vddmin_res->mdm2ap_vddmin_gpio);
+		if (irq < 0)
+			pr_err("%s: could not get LPM POWER IRQ resource mdm id %d.\n",
+				   __func__, mdev->mdm_data.device_id);
+		else {
+			ret = request_threaded_irq(irq, NULL, mdm_vddmin_change,
+				IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+				"mdm lpm", mdev);
+
+			if (ret < 0)
+				pr_err("%s: MDM LPM IRQ#%d request failed with error=%d",
+					   __func__, irq, ret);
+		}
 	}
-
-	ret = request_threaded_irq(irq, NULL, mdm_vddmin_change,
-		IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
-		"mdm lpm", NULL);
-
-	if (ret < 0)
-		pr_err("%s: MDM LPM IRQ#%d request failed with error=%d",
-			__func__, irq, ret);
-error_end:
+	spin_unlock_irqrestore(&mdm_devices_lock, flags);
 	return;
 }
 
@@ -133,48 +314,59 @@
 {
 	int ret, ntries = 0;
 	char sfr_buf[RD_BUF_SIZE];
+	struct mdm_platform_data *pdata;
+	struct mdm_device *mdev = container_of(work,
+			struct mdm_device, sfr_reason_work);
 
+	pdata = mdev->mdm_data.pdata;
 	do {
-		msleep(SFR_RETRY_INTERVAL);
-		ret = sysmon_get_reason(SYSMON_SS_EXT_MODEM,
+		if (pdata->sysmon_subsys_id_valid)
+		{
+			msleep(SFR_RETRY_INTERVAL);
+			ret = sysmon_get_reason(pdata->sysmon_subsys_id,
 					sfr_buf, sizeof(sfr_buf));
-		if (ret) {
-			/*
-			 * The sysmon device may not have been probed as yet
-			 * after the restart.
-			 */
-			pr_err("%s: Error retrieving mdm restart reason, ret = %d, "
-					"%d/%d tries\n", __func__, ret,
-					ntries + 1,	SFR_MAX_RETRIES);
-		} else {
-			pr_err("mdm restart reason: %s\n", sfr_buf);
-			break;
+			if (ret) {
+				/*
+				 * The sysmon device may not have been probed as
+				 * yet after the restart.
+				 */
+				pr_err("%s: Error retrieving restart reason,"
+						"ret = %d %d/%d tries\n",
+						__func__, ret,
+						ntries + 1,
+						SFR_MAX_RETRIES);
+			} else {
+				pr_err("mdm restart reason: %s\n", sfr_buf);
+				break;
+			}
 		}
 	} while (++ntries < SFR_MAX_RETRIES);
 }
 
-static DECLARE_WORK(sfr_reason_work, mdm_restart_reason_fn);
-
 static void mdm2ap_status_check(struct work_struct *work)
 {
+	struct mdm_device *mdev =
+		container_of(work, struct mdm_device,
+					 mdm2ap_status_check_work.work);
+	struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
 	/*
 	 * If the mdm modem did not pull the MDM2AP_STATUS gpio
 	 * high then call subsystem_restart.
 	 */
 	if (!mdm_drv->disable_status_check) {
 		if (gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 0) {
-			pr_err("%s: MDM2AP_STATUS gpio did not go high\n",
-					__func__);
-			mdm_drv->mdm_ready = 0;
-			subsystem_restart_dev(mdm_subsys_dev);
+			pr_err("%s: MDM2AP_STATUS did not go high on mdm id %d\n",
+				   __func__, mdev->mdm_data.device_id);
+			mdm_start_ssr(mdev);
 		}
 	}
 }
 
-static DECLARE_DELAYED_WORK(mdm2ap_status_check_work, mdm2ap_status_check);
-
-static void mdm_update_gpio_configs(enum gpio_update_config gpio_config)
+static void mdm_update_gpio_configs(struct mdm_device *mdev,
+				enum gpio_update_config gpio_config)
 {
+	struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
+
 	/* Some gpio configuration may need updating after modem bootup.*/
 	switch (gpio_config) {
 	case GPIO_UPDATE_RUNNING_CONFIG:
@@ -182,20 +374,20 @@
 			if (msm_gpiomux_write(mdm_drv->mdm2ap_status_gpio,
 				GPIOMUX_ACTIVE,
 				mdm_drv->pdata->mdm2ap_status_gpio_run_cfg,
-				&mdm2ap_status_old_config))
-				pr_err("%s: failed updating running gpio config\n",
-					   __func__);
+				&mdev->mdm2ap_status_old_config))
+				pr_err("%s: failed updating running gpio config mdm id %d\n",
+					   __func__, mdev->mdm_data.device_id);
 			else
-				mdm2ap_status_valid_old_config = 1;
+				mdev->mdm2ap_status_valid_old_config = 1;
 		}
 		break;
 	case GPIO_UPDATE_BOOTING_CONFIG:
-		if (mdm2ap_status_valid_old_config) {
+		if (mdev->mdm2ap_status_valid_old_config) {
 			msm_gpiomux_write(mdm_drv->mdm2ap_status_gpio,
 					GPIOMUX_ACTIVE,
-					&mdm2ap_status_old_config,
+					&mdev->mdm2ap_status_old_config,
 					NULL);
-			mdm2ap_status_valid_old_config = 0;
+			mdev->mdm2ap_status_valid_old_config = 0;
 		}
 		break;
 	default:
@@ -208,17 +400,29 @@
 				unsigned long arg)
 {
 	int status, ret = 0;
+	struct mdm_device *mdev;
+	struct mdm_modem_drv *mdm_drv;
+
+	mdev = mdm_get_device_by_name(filp->f_path.dentry->d_iname);
+	if (!mdev) {
+		pr_err("%s: mdm_device not found\n", __func__);
+		return -ENODEV;
+	}
 
 	if (_IOC_TYPE(cmd) != CHARM_CODE) {
-		pr_err("%s: invalid ioctl code\n", __func__);
+		pr_err("%s: invalid ioctl code to mdm id %d\n",
+			   __func__, mdev->mdm_data.device_id);
 		return -EINVAL;
 	}
 
-	pr_debug("%s: Entering ioctl cmd = %d\n", __func__, _IOC_NR(cmd));
+	mdm_drv = &mdev->mdm_data;
+	pr_debug("%s: Entering ioctl cmd = %d, mdm id = %d\n",
+			 __func__, _IOC_NR(cmd), mdev->mdm_data.device_id);
 	switch (cmd) {
 	case WAKE_CHARM:
-		pr_info("%s: Powering on mdm\n", __func__);
-		mdm_drv->ops->power_on_mdm_cb(mdm_drv);
+		pr_info("%s: Powering on mdm id %d\n",
+				__func__, mdev->mdm_data.device_id);
+		mdm_ops->power_on_mdm_cb(mdm_drv);
 		break;
 	case CHECK_FOR_BOOT:
 		if (gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 0)
@@ -227,30 +431,33 @@
 			put_user(0, (unsigned long __user *) arg);
 		break;
 	case NORMAL_BOOT_DONE:
-		pr_debug("%s: check if mdm is booted up\n", __func__);
+		pr_debug("%s: check if mdm id %d is booted up\n",
+				 __func__, mdev->mdm_data.device_id);
 		get_user(status, (unsigned long __user *) arg);
 		if (status) {
-			pr_debug("%s: normal boot failed\n", __func__);
+			pr_debug("%s: normal boot of mdm id %d failed\n",
+					 __func__, mdev->mdm_data.device_id);
 			mdm_drv->mdm_boot_status = -EIO;
 		} else {
-			pr_info("%s: normal boot done\n", __func__);
+			pr_info("%s: normal boot of mdm id %d done\n",
+					__func__, mdev->mdm_data.device_id);
 			mdm_drv->mdm_boot_status = 0;
 		}
-		mdm_drv->mdm_ready = 1;
+		atomic_set(&mdm_drv->mdm_ready, 1);
 
-		if (mdm_drv->ops->normal_boot_done_cb != NULL)
-			mdm_drv->ops->normal_boot_done_cb(mdm_drv);
+		if (mdm_ops->normal_boot_done_cb != NULL)
+			mdm_ops->normal_boot_done_cb(mdm_drv);
 
-		if (!first_boot)
-			complete(&mdm_boot);
+		if (!mdev->first_boot)
+			complete(&mdev->mdm_boot);
 		else
-			first_boot = 0;
+			mdev->first_boot = 0;
 
 		/* If successful, start a timer to check that the mdm2ap_status
 		 * gpio goes high.
 		 */
 		if (!status && gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 0)
-			schedule_delayed_work(&mdm2ap_status_check_work,
+			schedule_delayed_work(&mdev->mdm2ap_status_check_work,
 				msecs_to_jiffies(MDM2AP_STATUS_TIMEOUT_MS));
 		break;
 	case RAM_DUMP_DONE:
@@ -262,21 +469,22 @@
 			pr_info("%s: ramdump collection completed\n", __func__);
 			mdm_drv->mdm_ram_dump_status = 0;
 		}
-		complete(&mdm_ram_dumps);
+		complete(&mdev->mdm_ram_dumps);
 		break;
 	case WAIT_FOR_RESTART:
 		pr_debug("%s: wait for mdm to need images reloaded\n",
 				__func__);
-		ret = wait_for_completion_interruptible(&mdm_needs_reload);
+		ret = wait_for_completion_interruptible(
+				&mdev->mdm_needs_reload);
 		if (!ret)
 			put_user(mdm_drv->boot_type,
 					 (unsigned long __user *) arg);
-		INIT_COMPLETION(mdm_needs_reload);
+		init_completion(&mdev->mdm_needs_reload);
 		break;
 	case GET_DLOAD_STATUS:
 		pr_debug("getting status of mdm2ap_errfatal_gpio\n");
 		if (gpio_get_value(mdm_drv->mdm2ap_errfatal_gpio) == 1 &&
-			!mdm_drv->mdm_ready)
+			!atomic_read(&mdm_drv->mdm_ready))
 			put_user(1, (unsigned long __user *) arg);
 		else
 			put_user(0, (unsigned long __user *) arg);
@@ -284,14 +492,16 @@
 	case IMAGE_UPGRADE:
 		pr_debug("%s Image upgrade ioctl recieved\n", __func__);
 		if (mdm_drv->pdata->image_upgrade_supported &&
-				mdm_drv->ops->image_upgrade_cb) {
+				mdm_ops->image_upgrade_cb) {
 			get_user(status, (unsigned long __user *) arg);
-			mdm_drv->ops->image_upgrade_cb(mdm_drv, status);
+			mdm_ops->image_upgrade_cb(mdm_drv, status);
 		} else
 			pr_debug("%s Image upgrade not supported\n", __func__);
 		break;
 	case SHUTDOWN_CHARM:
-		mdm_drv->mdm_ready = 0;
+		if (!mdm_drv->pdata->send_shdn)
+			break;
+		atomic_set(&mdm_drv->mdm_ready, 0);
 		if (mdm_debug_mask & MDM_DEBUG_MASK_SHDN_LOG)
 			pr_info("Sending shutdown request to mdm\n");
 		ret = sysmon_send_shutdown(SYSMON_SS_EXT_MODEM);
@@ -304,68 +514,71 @@
 		ret = -EINVAL;
 		break;
 	}
-
 	return ret;
 }
 
 static void mdm_status_fn(struct work_struct *work)
 {
+	struct mdm_device *mdev =
+		container_of(work, struct mdm_device, mdm_status_work);
+	struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
 	int value = gpio_get_value(mdm_drv->mdm2ap_status_gpio);
 
 	pr_debug("%s: status:%d\n", __func__, value);
-	if (mdm_drv->mdm_ready && mdm_drv->ops->status_cb)
-		mdm_drv->ops->status_cb(mdm_drv, value);
+	if (atomic_read(&mdm_drv->mdm_ready) && mdm_ops->status_cb)
+		mdm_ops->status_cb(mdm_drv, value);
 
 	/* Update gpio configuration to "running" config. */
-	mdm_update_gpio_configs(GPIO_UPDATE_RUNNING_CONFIG);
+	mdm_update_gpio_configs(mdev, GPIO_UPDATE_RUNNING_CONFIG);
 }
 
-static DECLARE_WORK(mdm_status_work, mdm_status_fn);
-
-static void mdm_disable_irqs(void)
+static void mdm_disable_irqs(struct mdm_device *mdev)
 {
-	disable_irq_nosync(mdm_drv->mdm_errfatal_irq);
-	disable_irq_nosync(mdm_drv->mdm_status_irq);
+	if (!mdev)
+		return;
+	disable_irq_nosync(mdev->mdm_errfatal_irq);
+	disable_irq_nosync(mdev->mdm_status_irq);
+	disable_irq_nosync(mdev->mdm_pblrdy_irq);
 }
 
 static irqreturn_t mdm_errfatal(int irq, void *dev_id)
 {
-	pr_debug("%s: mdm got errfatal interrupt\n", __func__);
-	if (mdm_drv->mdm_ready &&
+	struct mdm_modem_drv *mdm_drv;
+	struct mdm_device *mdev = (struct mdm_device *)dev_id;
+	if (!mdev)
+		return IRQ_HANDLED;
+
+	pr_debug("%s: mdm id %d sent errfatal interrupt\n",
+			 __func__, mdev->mdm_data.device_id);
+	mdm_drv = &mdev->mdm_data;
+	if (atomic_read(&mdm_drv->mdm_ready) &&
 		(gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 1)) {
-		pr_info("%s: Reseting the mdm due to an errfatal\n", __func__);
-		mdm_drv->mdm_ready = 0;
-		subsystem_restart_dev(mdm_subsys_dev);
+		pr_info("%s: Received err fatal from mdm id %d\n",
+				__func__, mdev->mdm_data.device_id);
+		mdm_start_ssr(mdev);
 	}
 	return IRQ_HANDLED;
 }
 
+/* set the mdm_device as the file's private data */
 static int mdm_modem_open(struct inode *inode, struct file *file)
 {
 	return 0;
 }
 
-static const struct file_operations mdm_modem_fops = {
-	.owner		= THIS_MODULE,
-	.open		= mdm_modem_open,
-	.unlocked_ioctl	= mdm_modem_ioctl,
-};
-
-
-static struct miscdevice mdm_modem_misc = {
-	.minor	= MISC_DYNAMIC_MINOR,
-	.name	= "mdm",
-	.fops	= &mdm_modem_fops
-};
-
 static int mdm_panic_prep(struct notifier_block *this,
 				unsigned long event, void *ptr)
 {
 	int i;
+	struct mdm_modem_drv *mdm_drv;
+	struct mdm_device *mdev =
+		container_of(this, struct mdm_device, mdm_panic_blk);
+
+	mdm_drv = &mdev->mdm_data;
 
 	pr_debug("%s: setting AP2MDM_ERRFATAL high for a non graceful reset\n",
 			 __func__);
-	mdm_disable_irqs();
+	mdm_disable_irqs(mdev);
 	gpio_set_value(mdm_drv->ap2mdm_errfatal_gpio, 1);
 
 	for (i = MDM_MODEM_TIMEOUT; i > 0; i -= MDM_MODEM_DELTA) {
@@ -377,49 +590,67 @@
 	if (i <= 0) {
 		pr_err("%s: MDM2AP_STATUS never went low\n", __func__);
 		/* Reset the modem so that it will go into download mode. */
-		if (mdm_drv && mdm_drv->ops->atomic_reset_mdm_cb)
-			mdm_drv->ops->atomic_reset_mdm_cb(mdm_drv);
+		if (mdm_drv && mdm_ops->atomic_reset_mdm_cb)
+			mdm_ops->atomic_reset_mdm_cb(mdm_drv);
 	}
 	return NOTIFY_DONE;
 }
 
-static struct notifier_block mdm_panic_blk = {
-	.notifier_call  = mdm_panic_prep,
-};
-
 static irqreturn_t mdm_status_change(int irq, void *dev_id)
 {
-	int value = gpio_get_value(mdm_drv->mdm2ap_status_gpio);
+	struct mdm_modem_drv *mdm_drv;
+	struct mdm_device *mdev = (struct mdm_device *)dev_id;
+	int value;
+	if (!mdev)
+		return IRQ_HANDLED;
+
+	mdm_drv = &mdev->mdm_data;
+	value = gpio_get_value(mdm_drv->mdm2ap_status_gpio);
 
 	if ((mdm_debug_mask & MDM_DEBUG_MASK_SHDN_LOG) && (value == 0))
 		pr_info("%s: mdm2ap_status went low\n", __func__);
 
-	pr_debug("%s: mdm sent status change interrupt\n", __func__);
-	if (value == 0 && mdm_drv->mdm_ready == 1) {
-		pr_info("%s: unexpected reset external modem\n", __func__);
+	pr_debug("%s: mdm id %d sent status change interrupt\n",
+			 __func__, mdev->mdm_data.device_id);
+	if (value == 0 && atomic_read(&mdm_drv->mdm_ready)) {
+		pr_info("%s: unexpected reset external modem id %d\n",
+				__func__, mdev->mdm_data.device_id);
 		mdm_drv->mdm_unexpected_reset_occurred = 1;
-		mdm_drv->mdm_ready = 0;
-		subsystem_restart_dev(mdm_subsys_dev);
+		mdm_start_ssr(mdev);
 	} else if (value == 1) {
-		cancel_delayed_work(&mdm2ap_status_check_work);
-		pr_info("%s: status = 1: mdm is now ready\n", __func__);
-		queue_work(mdm_queue, &mdm_status_work);
+		cancel_delayed_work(&mdev->mdm2ap_status_check_work);
+		pr_info("%s: status = 1: mdm id %d is now ready\n",
+				__func__, mdev->mdm_data.device_id);
+		queue_work(mdev->mdm_queue, &mdev->mdm_status_work);
 	}
 	return IRQ_HANDLED;
 }
 
 static irqreturn_t mdm_pblrdy_change(int irq, void *dev_id)
 {
-	pr_info("%s: pbl ready:%d\n", __func__,
-			gpio_get_value(mdm_drv->mdm2ap_pblrdy));
+	struct mdm_modem_drv *mdm_drv;
+	struct mdm_device *mdev = (struct mdm_device *)dev_id;
+	if (!mdev)
+		return IRQ_HANDLED;
 
+	mdm_drv = &mdev->mdm_data;
+	pr_info("%s: mdm id %d: pbl ready:%d\n",
+			__func__, mdev->mdm_data.device_id,
+			gpio_get_value(mdm_drv->mdm2ap_pblrdy));
 	return IRQ_HANDLED;
 }
 
 static int mdm_subsys_shutdown(const struct subsys_desc *crashed_subsys)
 {
-	mdm_drv->mdm_ready = 0;
-	cancel_delayed_work(&mdm2ap_status_check_work);
+	struct mdm_device *mdev =
+	 container_of(crashed_subsys, struct mdm_device, mdm_subsys);
+	struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
+
+	pr_debug("%s: ssr on modem id %d\n", __func__,
+			 mdev->mdm_data.device_id);
+
+	mdm_ssr_started(mdev);
+	cancel_delayed_work(&mdev->mdm2ap_status_check_work);
 	gpio_direction_output(mdm_drv->ap2mdm_errfatal_gpio, 1);
 	if (mdm_drv->pdata->ramdump_delay_ms > 0) {
 		/* Wait for the external modem to complete
@@ -428,9 +659,9 @@
 		msleep(mdm_drv->pdata->ramdump_delay_ms);
 	}
 	if (!mdm_drv->mdm_unexpected_reset_occurred) {
-		mdm_drv->ops->reset_mdm_cb(mdm_drv);
+		mdm_ops->reset_mdm_cb(mdm_drv);
 		/* Update gpio configuration to "booting" config. */
-		mdm_update_gpio_configs(GPIO_UPDATE_BOOTING_CONFIG);
+		mdm_update_gpio_configs(mdev, GPIO_UPDATE_BOOTING_CONFIG);
 	} else {
 		mdm_drv->mdm_unexpected_reset_occurred = 0;
 	}
@@ -439,59 +670,76 @@
 
 static int mdm_subsys_powerup(const struct subsys_desc *crashed_subsys)
 {
+	struct mdm_device *mdev =
+		container_of(crashed_subsys, struct mdm_device,
+					 mdm_subsys);
+	struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
+
+	pr_debug("%s: ssr on modem id %d\n",
+			 __func__, mdev->mdm_data.device_id);
+
 	gpio_direction_output(mdm_drv->ap2mdm_errfatal_gpio, 0);
 	gpio_direction_output(mdm_drv->ap2mdm_status_gpio, 1);
-	mdm_drv->ops->power_on_mdm_cb(mdm_drv);
+
+	if (mdm_drv->pdata->ps_hold_delay_ms > 0)
+		msleep(mdm_drv->pdata->ps_hold_delay_ms);
+
+	mdm_ops->power_on_mdm_cb(mdm_drv);
 	mdm_drv->boot_type = CHARM_NORMAL_BOOT;
-	complete(&mdm_needs_reload);
-	if (!wait_for_completion_timeout(&mdm_boot,
+	mdm_ssr_completed(mdev);
+	complete(&mdev->mdm_needs_reload);
+	if (!wait_for_completion_timeout(&mdev->mdm_boot,
 			msecs_to_jiffies(MDM_BOOT_TIMEOUT))) {
 		mdm_drv->mdm_boot_status = -ETIMEDOUT;
 		pr_info("%s: mdm modem restart timed out.\n", __func__);
 	} else {
-		pr_info("%s: mdm modem has been restarted\n", __func__);
+		pr_info("%s: id %d: mdm modem has been restarted\n",
+				__func__, mdm_drv->device_id);
 
 		/* Log the reason for the restart */
 		if (mdm_drv->pdata->sfr_query)
-			queue_work(mdm_sfr_queue, &sfr_reason_work);
+			queue_work(mdev->mdm_sfr_queue, &mdev->sfr_reason_work);
 	}
-	INIT_COMPLETION(mdm_boot);
+	init_completion(&mdev->mdm_boot);
 	return mdm_drv->mdm_boot_status;
 }
 
 static int mdm_subsys_ramdumps(int want_dumps,
 				const struct subsys_desc *crashed_subsys)
 {
+	struct mdm_device *mdev =
+		container_of(crashed_subsys, struct mdm_device,
+					 mdm_subsys);
+	struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
+
+	pr_debug("%s: ssr on modem id %d\n", __func__,
+			 mdev->mdm_data.device_id);
+
 	mdm_drv->mdm_ram_dump_status = 0;
-	cancel_delayed_work(&mdm2ap_status_check_work);
+	cancel_delayed_work(&mdev->mdm2ap_status_check_work);
 	if (want_dumps) {
 		mdm_drv->boot_type = CHARM_RAM_DUMPS;
-		complete(&mdm_needs_reload);
-		if (!wait_for_completion_timeout(&mdm_ram_dumps,
-				msecs_to_jiffies(dump_timeout_ms))) {
+		complete(&mdev->mdm_needs_reload);
+		if (!wait_for_completion_timeout(&mdev->mdm_ram_dumps,
+				msecs_to_jiffies(mdev->dump_timeout_ms))) {
 			mdm_drv->mdm_ram_dump_status = -ETIMEDOUT;
-			pr_info("%s: mdm modem ramdumps timed out.\n",
+			mdm_ssr_completed(mdev);
+			pr_err("%s: mdm modem ramdumps timed out.\n",
 					__func__);
 		} else
 			pr_info("%s: mdm modem ramdumps completed.\n",
 					__func__);
-		INIT_COMPLETION(mdm_ram_dumps);
+		init_completion(&mdev->mdm_ram_dumps);
 		if (!mdm_drv->pdata->no_powerdown_after_ramdumps) {
-			mdm_drv->ops->power_down_mdm_cb(mdm_drv);
+			mdm_ops->power_down_mdm_cb(mdm_drv);
 			/* Update gpio configuration to "booting" config. */
-			mdm_update_gpio_configs(GPIO_UPDATE_BOOTING_CONFIG);
+			mdm_update_gpio_configs(mdev,
+						GPIO_UPDATE_BOOTING_CONFIG);
 		}
 	}
 	return mdm_drv->mdm_ram_dump_status;
 }
 
-static struct subsys_desc mdm_subsystem = {
-	.shutdown = mdm_subsys_shutdown,
-	.ramdump = mdm_subsys_ramdumps,
-	.powerup = mdm_subsys_powerup,
-	.name = EXTERNAL_MODEM,
-};
-
 /* Once the gpios are sent to RPM and debugging
  * starts, there is no way to stop it without
  * rebooting the device.
@@ -505,8 +753,8 @@
 	}
 
 	mdm_debug_mask = val;
-	if (mdm_drv->ops->debug_state_changed_cb)
-		mdm_drv->ops->debug_state_changed_cb(mdm_debug_mask);
+	if (mdm_ops->debug_state_changed_cb)
+		mdm_ops->debug_state_changed_cb(mdm_debug_mask);
 	return 0;
 }
 
@@ -533,11 +781,55 @@
 	return 0;
 }
 
-static void mdm_modem_initialize_data(struct platform_device  *pdev,
-				struct mdm_ops *mdm_ops)
+static const struct file_operations mdm_modem_fops = {
+	.owner		= THIS_MODULE,
+	.open		= mdm_modem_open,
+	.unlocked_ioctl	= mdm_modem_ioctl,
+};
+
+static void mdm_modem_initialize_data(struct platform_device *pdev,
+						struct mdm_device *mdev)
 {
+	struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
 	struct resource *pres;
 
+	mdm_drv->pdata    = pdev->dev.platform_data;
+	if (pdev->id < 0)
+		mdm_drv->device_id   = 0;
+	else
+		mdm_drv->device_id   = pdev->id;
+
+	memset((void *)&mdev->mdm_subsys, 0,
+		   sizeof(struct subsys_desc));
+	if (mdev->mdm_data.device_id <= 0)
+		snprintf(mdev->subsys_name, sizeof(mdev->subsys_name),
+			 "%s",  EXTERNAL_MODEM);
+	else
+		snprintf(mdev->subsys_name, sizeof(mdev->subsys_name),
+			 "%s.%d",  EXTERNAL_MODEM, mdev->mdm_data.device_id);
+	mdev->mdm_subsys.shutdown = mdm_subsys_shutdown;
+	mdev->mdm_subsys.ramdump = mdm_subsys_ramdumps;
+	mdev->mdm_subsys.powerup = mdm_subsys_powerup;
+	mdev->mdm_subsys.name = mdev->subsys_name;
+
+	memset((void *)&mdev->misc_device, 0,
+		   sizeof(struct miscdevice));
+	if (mdev->mdm_data.device_id <= 0)
+		snprintf(mdev->device_name, sizeof(mdev->device_name),
+			 "%s",  DEVICE_BASE_NAME);
+	else
+		snprintf(mdev->device_name, sizeof(mdev->device_name),
+			 "%s%d",  DEVICE_BASE_NAME, mdev->mdm_data.device_id);
+	mdev->misc_device.minor	= MISC_DYNAMIC_MINOR;
+	mdev->misc_device.name	= mdev->device_name;
+	mdev->misc_device.fops	= &mdm_modem_fops;
+
+	memset((void *)&mdev->mdm_panic_blk, 0,
+		   sizeof(struct notifier_block));
+	mdev->mdm_panic_blk.notifier_call  = mdm_panic_prep;
+	atomic_notifier_chain_register(&panic_notifier_list,
+				   &mdev->mdm_panic_blk);
+
 	/* MDM2AP_ERRFATAL */
 	pres = platform_get_resource_byname(pdev, IORESOURCE_IO,
 							"MDM2AP_ERRFATAL");
@@ -595,26 +887,49 @@
 
 	mdm_drv->boot_type                  = CHARM_NORMAL_BOOT;
 
-	mdm_drv->ops      = mdm_ops;
-	mdm_drv->pdata    = pdev->dev.platform_data;
-	dump_timeout_ms = mdm_drv->pdata->ramdump_timeout_ms > 0 ?
+	mdm_drv->dump_timeout_ms = mdm_drv->pdata->ramdump_timeout_ms > 0 ?
 		mdm_drv->pdata->ramdump_timeout_ms : MDM_RDUMP_TIMEOUT;
+
+	init_completion(&mdev->mdm_needs_reload);
+	init_completion(&mdev->mdm_boot);
+	init_completion(&mdev->mdm_ram_dumps);
+
+	mdev->first_boot = 1;
+	mutex_init(&mdm_drv->peripheral_status_lock);
 }
 
-int mdm_common_create(struct platform_device  *pdev,
-					  struct mdm_ops *p_mdm_cb)
+static void mdm_deconfigure_ipc(struct mdm_device *mdev)
 {
-	int ret = -1, irq;
+	struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
 
-	mdm_drv = kzalloc(sizeof(struct mdm_modem_drv), GFP_KERNEL);
-	if (mdm_drv == NULL) {
-		pr_err("%s: kzalloc fail.\n", __func__);
-		goto alloc_err;
+	gpio_free(mdm_drv->ap2mdm_status_gpio);
+	gpio_free(mdm_drv->ap2mdm_errfatal_gpio);
+	if (GPIO_IS_VALID(mdm_drv->ap2mdm_kpdpwr_n_gpio))
+		gpio_free(mdm_drv->ap2mdm_kpdpwr_n_gpio);
+	if (GPIO_IS_VALID(mdm_drv->ap2mdm_pmic_pwr_en_gpio))
+		gpio_free(mdm_drv->ap2mdm_pmic_pwr_en_gpio);
+	gpio_free(mdm_drv->mdm2ap_status_gpio);
+	gpio_free(mdm_drv->mdm2ap_errfatal_gpio);
+	if (GPIO_IS_VALID(mdm_drv->ap2mdm_soft_reset_gpio))
+		gpio_free(mdm_drv->ap2mdm_soft_reset_gpio);
+
+	if (GPIO_IS_VALID(mdm_drv->ap2mdm_wakeup_gpio))
+		gpio_free(mdm_drv->ap2mdm_wakeup_gpio);
+
+	if (mdev->mdm_queue) {
+		destroy_workqueue(mdev->mdm_queue);
+		mdev->mdm_queue = NULL;
 	}
+	if (mdev->mdm_sfr_queue) {
+		destroy_workqueue(mdev->mdm_sfr_queue);
+		mdev->mdm_sfr_queue = NULL;
+	}
+}
 
-	mdm_modem_initialize_data(pdev, p_mdm_cb);
-	if (mdm_drv->ops->debug_state_changed_cb)
-		mdm_drv->ops->debug_state_changed_cb(mdm_debug_mask);
+static int mdm_configure_ipc(struct mdm_device *mdev)
+{
+	struct mdm_modem_drv *mdm_drv = &mdev->mdm_data;
+	int ret = -1, irq;
 
 	gpio_request(mdm_drv->ap2mdm_status_gpio, "AP2MDM_STATUS");
 	gpio_request(mdm_drv->ap2mdm_errfatal_gpio, "AP2MDM_ERRFATAL");
@@ -641,7 +956,6 @@
 			mdm_drv->usb_switch_gpio = -1;
 		}
 	}
-
 	gpio_direction_output(mdm_drv->ap2mdm_status_gpio, 1);
 	gpio_direction_output(mdm_drv->ap2mdm_errfatal_gpio, 0);
 
@@ -651,96 +965,87 @@
 	gpio_direction_input(mdm_drv->mdm2ap_status_gpio);
 	gpio_direction_input(mdm_drv->mdm2ap_errfatal_gpio);
 
-	mdm_queue = create_singlethread_workqueue("mdm_queue");
-	if (!mdm_queue) {
-		pr_err("%s: could not create workqueue. All mdm "
-				"functionality will be disabled\n",
-			__func__);
+	mdev->mdm_queue = alloc_workqueue("mdm_queue", 0, 0);
+	if (!mdev->mdm_queue) {
+		pr_err("%s: could not create mdm_queue for mdm id %d\n",
+			   __func__, mdev->mdm_data.device_id);
 		ret = -ENOMEM;
 		goto fatal_err;
 	}
 
-	mdm_sfr_queue = alloc_workqueue("mdm_sfr_queue", 0, 0);
-	if (!mdm_sfr_queue) {
-		pr_err("%s: could not create workqueue mdm_sfr_queue."
-			" All mdm functionality will be disabled\n",
-			__func__);
+	mdev->mdm_sfr_queue = alloc_workqueue("mdm_sfr_queue", 0, 0);
+	if (!mdev->mdm_sfr_queue) {
+		pr_err("%s: could not create mdm_sfr_queue for mdm id %d\n",
+			   __func__, mdev->mdm_data.device_id);
 		ret = -ENOMEM;
-		destroy_workqueue(mdm_queue);
 		goto fatal_err;
 	}
 
-	atomic_notifier_chain_register(&panic_notifier_list, &mdm_panic_blk);
-	mdm_debugfs_init();
-
 	/* Register subsystem handlers */
-	mdm_subsys_dev = subsys_register(&mdm_subsystem);
-	if (IS_ERR(mdm_subsys_dev)) {
-		ret = PTR_ERR(mdm_subsys_dev);
+	mdev->mdm_subsys_dev = subsys_register(&mdev->mdm_subsys);
+	if (IS_ERR(mdev->mdm_subsys_dev)) {
+		ret = PTR_ERR(mdev->mdm_subsys_dev);
 		goto fatal_err;
 	}
 
 	/* ERR_FATAL irq. */
 	irq = MSM_GPIO_TO_INT(mdm_drv->mdm2ap_errfatal_gpio);
 	if (irq < 0) {
-		pr_err("%s: could not get MDM2AP_ERRFATAL IRQ resource. "
-			"error=%d No IRQ will be generated on errfatal.",
-			__func__, irq);
+		pr_err("%s: bad MDM2AP_ERRFATAL IRQ resource, err = %d\n",
+			   __func__, irq);
 		goto errfatal_err;
 	}
 	ret = request_irq(irq, mdm_errfatal,
-		IRQF_TRIGGER_RISING , "mdm errfatal", NULL);
+			IRQF_TRIGGER_RISING , "mdm errfatal", mdev);
 
 	if (ret < 0) {
-		pr_err("%s: MDM2AP_ERRFATAL IRQ#%d request failed with error=%d"
-			". No IRQ will be generated on errfatal.",
-			__func__, irq, ret);
+		pr_err("%s: MDM2AP_ERRFATAL IRQ#%d request failed, err=%d\n",
+					__func__, irq, ret);
 		goto errfatal_err;
 	}
-	mdm_drv->mdm_errfatal_irq = irq;
+	mdev->mdm_errfatal_irq = irq;
 
 errfatal_err:
 
-	/* status irq */
+	 /* status irq */
 	irq = MSM_GPIO_TO_INT(mdm_drv->mdm2ap_status_gpio);
 	if (irq < 0) {
-		pr_err("%s: could not get MDM2AP_STATUS IRQ resource. "
-			"error=%d No IRQ will be generated on status change.",
-			__func__, irq);
+		pr_err("%s: bad MDM2AP_STATUS IRQ resource, err = %d\n",
+				__func__, irq);
 		goto status_err;
 	}
 
 	ret = request_threaded_irq(irq, NULL, mdm_status_change,
 		IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED,
-		"mdm status", mdm_drv);
+		"mdm status", mdev);
 
 	if (ret < 0) {
-		pr_err("%s: MDM2AP_STATUS IRQ#%d request failed with error=%d"
-			". No IRQ will be generated on status change.",
-			__func__, irq, ret);
+		pr_err("%s: MDM2AP_STATUS IRQ#%d request failed, err=%d",
+			 __func__, irq, ret);
 		goto status_err;
 	}
-	mdm_drv->mdm_status_irq = irq;
+	mdev->mdm_status_irq = irq;
 
 status_err:
 	if (GPIO_IS_VALID(mdm_drv->mdm2ap_pblrdy)) {
 		irq = MSM_GPIO_TO_INT(mdm_drv->mdm2ap_pblrdy);
 		if (irq < 0) {
-			pr_err("%s: could not get MDM2AP_PBLRDY IRQ resource",
-				__func__);
+			pr_err("%s: could not get MDM2AP_PBLRDY IRQ resource\n",
+				 __func__);
 			goto pblrdy_err;
 		}
 
 		ret = request_threaded_irq(irq, NULL, mdm_pblrdy_change,
-			IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
-			IRQF_SHARED,
-			"mdm pbl ready", mdm_drv);
+				IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
+				IRQF_SHARED,
+				"mdm pbl ready", mdev);
 
 		if (ret < 0) {
-			pr_err("%s: MDM2AP_PBL IRQ#%d request failed error=%d",
+			pr_err("%s: MDM2AP_PBL IRQ#%d request failed error=%d\n",
 				__func__, irq, ret);
 			goto pblrdy_err;
 		}
+		mdev->mdm_pblrdy_irq = irq;
 	}
 
 pblrdy_err:
@@ -751,67 +1056,140 @@
 	if (GPIO_IS_VALID(mdm_drv->ap2mdm_pmic_pwr_en_gpio))
 		gpio_direction_output(mdm_drv->ap2mdm_pmic_pwr_en_gpio, 1);
 
-	/* Perform early powerup of the external modem in order to
-	 * allow tabla devices to be found.
-	 */
-	if (mdm_drv->pdata->early_power_on)
-		mdm_drv->ops->power_on_mdm_cb(mdm_drv);
-
-	pr_info("%s: Registering mdm modem\n", __func__);
-	return misc_register(&mdm_modem_misc);
+	return 0;
 
 fatal_err:
-	gpio_free(mdm_drv->ap2mdm_status_gpio);
-	gpio_free(mdm_drv->ap2mdm_errfatal_gpio);
-	if (GPIO_IS_VALID(mdm_drv->ap2mdm_kpdpwr_n_gpio))
-		gpio_free(mdm_drv->ap2mdm_kpdpwr_n_gpio);
-	if (GPIO_IS_VALID(mdm_drv->ap2mdm_pmic_pwr_en_gpio))
-		gpio_free(mdm_drv->ap2mdm_pmic_pwr_en_gpio);
-	gpio_free(mdm_drv->mdm2ap_status_gpio);
-	gpio_free(mdm_drv->mdm2ap_errfatal_gpio);
-	if (GPIO_IS_VALID(mdm_drv->ap2mdm_soft_reset_gpio))
-		gpio_free(mdm_drv->ap2mdm_soft_reset_gpio);
-
-	if (GPIO_IS_VALID(mdm_drv->ap2mdm_wakeup_gpio))
-		gpio_free(mdm_drv->ap2mdm_wakeup_gpio);
-
-	kfree(mdm_drv);
-	ret = -ENODEV;
-
-alloc_err:
+	mdm_deconfigure_ipc(mdev);
 	return ret;
 }
 
-int mdm_common_modem_remove(struct platform_device *pdev)
+static int __devinit mdm_modem_probe(struct platform_device *pdev)
+{
+	struct mdm_device *mdev = NULL;
+	int ret = -1;
+
+	mdev = kzalloc(sizeof(struct mdm_device), GFP_KERNEL);
+	if (!mdev) {
+		pr_err("%s: kzalloc fail.\n", __func__);
+		ret = -ENOMEM;
+		goto init_err;
+	}
+
+	mdm_modem_initialize_data(pdev, mdev);
+
+	if (mdm_ops->debug_state_changed_cb)
+		mdm_ops->debug_state_changed_cb(mdm_debug_mask);
+
+	if (mdm_configure_ipc(mdev)) {
+		pr_err("%s: mdm_configure_ipc failed, id = %d\n",
+			   __func__, mdev->mdm_data.device_id);
+		goto init_err;
+	}
+
+	pr_debug("%s: Registering mdm id %d\n", __func__,
+			mdev->mdm_data.device_id);
+	ret = misc_register(&mdev->misc_device);
+	if (ret) {
+		pr_err("%s: failed registering mdm id %d, ret = %d\n",
+			   __func__, mdev->mdm_data.device_id, ret);
+		mdm_deconfigure_ipc(mdev);
+		goto init_err;
+	} else {
+		pr_err("%s: registered mdm id %d\n",
+			   __func__, mdev->mdm_data.device_id);
+
+		mdm_device_list_add(mdev);
+		INIT_DELAYED_WORK(&mdev->mdm2ap_status_check_work,
+					mdm2ap_status_check);
+		INIT_WORK(&mdev->mdm_status_work, mdm_status_fn);
+		INIT_WORK(&mdev->sfr_reason_work, mdm_restart_reason_fn);
+
+		/* Perform early powerup of the external modem in order to
+		 * allow tabla devices to be found.
+		 */
+		if (mdev->mdm_data.pdata->early_power_on)
+			mdm_ops->power_on_mdm_cb(&mdev->mdm_data);
+	}
+
+	return ret;
+
+init_err:
+	kfree(mdev);
+	return ret;
+}
+
+static int __devexit mdm_modem_remove(struct platform_device *pdev)
 {
 	int ret;
+	struct mdm_device *mdev = mdm_get_device_by_device_id(pdev->id);
 
-	gpio_free(mdm_drv->ap2mdm_status_gpio);
-	gpio_free(mdm_drv->ap2mdm_errfatal_gpio);
-	if (GPIO_IS_VALID(mdm_drv->ap2mdm_kpdpwr_n_gpio))
-		gpio_free(mdm_drv->ap2mdm_kpdpwr_n_gpio);
-	if (GPIO_IS_VALID(mdm_drv->ap2mdm_pmic_pwr_en_gpio))
-		gpio_free(mdm_drv->ap2mdm_pmic_pwr_en_gpio);
-	gpio_free(mdm_drv->mdm2ap_status_gpio);
-	gpio_free(mdm_drv->mdm2ap_errfatal_gpio);
-	if (GPIO_IS_VALID(mdm_drv->ap2mdm_soft_reset_gpio))
-		gpio_free(mdm_drv->ap2mdm_soft_reset_gpio);
+	if (!mdev)
+		return -ENODEV;
 
-	if (GPIO_IS_VALID(mdm_drv->ap2mdm_wakeup_gpio))
-		gpio_free(mdm_drv->ap2mdm_wakeup_gpio);
-
-	kfree(mdm_drv);
-
-	ret = misc_deregister(&mdm_modem_misc);
+	pr_debug("%s: removing device id %d\n",
+			__func__, mdev->mdm_data.device_id);
+	mdm_deconfigure_ipc(mdev);
+	ret = misc_deregister(&mdev->misc_device);
+	mdm_device_list_remove(mdev);
+	kfree(mdev);
 	return ret;
 }
 
-void mdm_common_modem_shutdown(struct platform_device *pdev)
+static void mdm_modem_shutdown(struct platform_device *pdev)
 {
-	mdm_disable_irqs();
+	struct mdm_modem_drv *mdm_drv;
+	struct mdm_device *mdev = mdm_get_device_by_device_id(pdev->id);
+	if (!mdev)
+		return;
 
-	mdm_drv->ops->power_down_mdm_cb(mdm_drv);
+	pr_debug("%s: shutting down device id %d\n",
+		 __func__, mdev->mdm_data.device_id);
+
+	mdm_disable_irqs(mdev);
+	mdm_drv = &mdev->mdm_data;
+	mdm_ops->power_down_mdm_cb(mdm_drv);
 	if (GPIO_IS_VALID(mdm_drv->ap2mdm_pmic_pwr_en_gpio))
 		gpio_direction_output(mdm_drv->ap2mdm_pmic_pwr_en_gpio, 0);
 }
 
+static struct of_device_id mdm_match_table[] = {
+	{.compatible = "qcom,mdm2_modem,mdm2_modem.1"},
+	{},
+};
+
+static struct platform_driver mdm_modem_driver = {
+	.probe    = mdm_modem_probe,
+	.remove   = __devexit_p(mdm_modem_remove),
+	.shutdown = mdm_modem_shutdown,
+	.driver         = {
+		.name = "mdm2_modem",
+		.owner = THIS_MODULE,
+		.of_match_table = mdm_match_table,
+	},
+};
+
+static int __init mdm_modem_init(void)
+{
+	int ret;
+
+	ret = mdm_get_ops(&mdm_ops);
+	if (ret)
+		return ret;
+
+	INIT_LIST_HEAD(&mdm_devices);
+	mdm_debugfs_init();
+	return platform_driver_register(&mdm_modem_driver);
+}
+
+static void __exit mdm_modem_exit(void)
+{
+	platform_driver_unregister(&mdm_modem_driver);
+}
+
+module_init(mdm_modem_init);
+module_exit(mdm_modem_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("mdm modem driver");
+MODULE_VERSION("2.0");
+MODULE_ALIAS("mdm_modem");