soc: qcom: msm_smd: Add snapshot of Shared memory driver

This is a snapshot of the SMD driver as of msm-4.4 commit
<bb3bb010ec74> (Merge "ASoC: wcd9xxx: restrict debugfs permission")

Shared memory data[SMD] drivers provides data communication to
remote peripherals such as Modem and ADSP over shared memory.

CRs-Fixed: 2144497
Change-Id: I50ded677a10dae3910b8369e6c17f175dda3edf5
Signed-off-by: Arun Kumar Neelakantam <aneela@codeaurora.org>
diff --git a/Documentation/devicetree/bindings/arm/msm/smdpkt.txt b/Documentation/devicetree/bindings/arm/msm/smdpkt.txt
new file mode 100644
index 0000000..be9084b
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/msm/smdpkt.txt
@@ -0,0 +1,43 @@
+Qualcomm Technologies, Inc Shared Memory Packet Driver (smdpkt)
+
+[Root level node]
+Required properties:
+-compatible : should be "qcom,smdpkt"
+
+[Second level nodes]
+qcom,smdpkt-port-names
+Required properties:
+-qcom,smdpkt-remote : the remote subsystem name
+-qcom,smdpkt-port-name : the smd channel name
+-qcom,smdpkt-dev-name : the smdpkt device name
+
+Example:
+
+	qcom,smdpkt {
+		compatible = "qcom,smdpkt";
+
+		qcom,smdpkt-data5-cntl {
+			qcom,smdpkt-remote = "modem";
+			qcom,smdpkt-port-name = "DATA5_CNTL";
+			qcom,smdpkt-dev-name = "smdcntl0";
+		};
+
+		qcom,smdpkt-data6-cntl {
+			qcom,smdpkt-remote = "modem";
+			qcom,smdpkt-port-name = "DATA6_CNTL";
+			qcom,smdpkt-dev-name = "smdcntl1";
+		};
+
+		qcom,smdpkt-cxm-qmi-port-8064 {
+			qcom,smdpkt-remote = "wcnss";
+			qcom,smdpkt-port-name = "CXM_QMI_PORT_8064";
+			qcom,smdpkt-dev-name = "smd_cxm_qmi";
+		};
+
+		qcom,smdpkt-loopback {
+			qcom,smdpkt-remote = "modem";
+			qcom,smdpkt-port-name = "LOOPBACK";
+			qcom,smdpkt-dev-name = "smd_pkt_loopback";
+		};
+	};
+
diff --git a/Documentation/devicetree/bindings/arm/msm/smdtty.txt b/Documentation/devicetree/bindings/arm/msm/smdtty.txt
new file mode 100644
index 0000000..a445c60
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/msm/smdtty.txt
@@ -0,0 +1,40 @@
+Qualcomm Technologies, Inc Shared Memory TTY Driver (smdtty)
+
+[Root level node]
+Required properties:
+-compatible : should be "qcom,smdtty"
+
+[Second level nodes]
+qcom,smdtty-port-names
+Required properties:
+-qcom,smdtty-remote: the remote subsystem name
+-qcom,smdtty-port-name : the smd channel name
+
+Optional properties:
+-qcom,smdtty-dev-name : the smdtty device name
+
+Required alias:
+- The index into TTY subsystem is specified via an alias with the following format
+         'smd{n}' where n is the tty device index.
+
+Example:
+	aliases {
+		smd1 = &smdtty_apps_fm;
+		smd36 = &smdtty_loopback;
+	};
+
+	qcom,smdtty {
+		compatible = "qcom,smdtty";
+
+		smdtty_apps_fm: qcom,smdtty-apps-fm {
+			qcom,smdtty-remote = "wcnss";
+			qcom,smdtty-port-name = "APPS_FM";
+		};
+
+		smdtty_loopback: smdtty-loopback {
+			qcom,smdtty-remote = "modem";
+			qcom,smdtty-port-name = "LOOPBACK";
+			qcom,smdtty-dev-name = "LOOPBACK_TTY";
+		};
+	};
+
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index 49fb8e5..1ea2053 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -582,6 +582,16 @@
 
 source "drivers/s390/char/Kconfig"
 
+config MSM_SMD_PKT
+	bool "Enable device interface for some SMD packet ports"
+	default n
+	depends on MSM_SMD
+	help
+	  smd_pkt driver provides the interface for the userspace clients
+	  to communicate over smd via device nodes. This enable the
+	  usersapce clients to read and write to some smd packets channel
+	  for MSM chipset.
+
 config TILE_SROM
 	bool "Character-device access via hypervisor to the Tilera SPI ROM"
 	depends on TILE
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index 19c3c98..81283c4 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -9,6 +9,7 @@
 obj-$(CONFIG_VIRTIO_CONSOLE)	+= virtio_console.o
 obj-$(CONFIG_RAW_DRIVER)	+= raw.o
 obj-$(CONFIG_SGI_SNSC)		+= snsc.o snsc_event.o
+obj-$(CONFIG_MSM_SMD_PKT)	+= msm_smd_pkt.o
 obj-$(CONFIG_MSPEC)		+= mspec.o
 obj-$(CONFIG_MMTIMER)		+= mmtimer.o
 obj-$(CONFIG_UV_MMTIMER)	+= uv_mmtimer.o
diff --git a/drivers/char/msm_smd_pkt.c b/drivers/char/msm_smd_pkt.c
new file mode 100644
index 0000000..ff77cb2
--- /dev/null
+++ b/drivers/char/msm_smd_pkt.c
@@ -0,0 +1,1397 @@
+/* Copyright (c) 2008-2017, 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
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+/*
+ * SMD Packet Driver -- Provides a binary SMD non-muxed packet port
+ *                       interface.
+ */
+
+#include <linux/slab.h>
+#include <linux/cdev.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/device.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/uaccess.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+#include <linux/completion.h>
+#include <linux/msm_smd_pkt.h>
+#include <linux/poll.h>
+#include <soc/qcom/smd.h>
+#include <soc/qcom/smsm.h>
+#include <soc/qcom/subsystem_restart.h>
+#include <asm/ioctls.h>
+#include <linux/pm.h>
+#include <linux/of.h>
+#include <linux/ipc_logging.h>
+
+#define MODULE_NAME "msm_smdpkt"
+#define DEVICE_NAME "smdpkt"
+#define WAKEUPSOURCE_TIMEOUT (2000) /* two seconds */
+
+struct smd_pkt_dev {
+	struct list_head dev_list;
+	char dev_name[SMD_MAX_CH_NAME_LEN];
+	char ch_name[SMD_MAX_CH_NAME_LEN];
+	uint32_t edge;
+
+	struct cdev cdev;
+	struct device *devicep;
+	void *pil;
+
+	struct smd_channel *ch;
+	struct mutex ch_lock;
+	struct mutex rx_lock;
+	struct mutex tx_lock;
+	wait_queue_head_t ch_read_wait_queue;
+	wait_queue_head_t ch_write_wait_queue;
+	wait_queue_head_t ch_opened_wait_queue;
+
+	int i;
+	int ref_cnt;
+
+	int blocking_write;
+	int is_open;
+	int poll_mode;
+	unsigned int ch_size;
+	uint open_modem_wait;
+
+	int has_reset;
+	int do_reset_notification;
+	struct completion ch_allocated;
+	struct wakeup_source pa_ws;	/* Packet Arrival Wakeup Source */
+	struct work_struct packet_arrival_work;
+	spinlock_t pa_spinlock;
+	int ws_locked;
+};
+
+
+struct smd_pkt_driver {
+	struct list_head list;
+	int ref_cnt;
+	char pdriver_name[SMD_MAX_CH_NAME_LEN];
+	struct platform_driver driver;
+};
+
+static DEFINE_MUTEX(smd_pkt_driver_lock_lha1);
+static LIST_HEAD(smd_pkt_driver_list);
+
+struct class *smd_pkt_classp;
+static dev_t smd_pkt_number;
+static struct delayed_work loopback_work;
+static void check_and_wakeup_reader(struct smd_pkt_dev *smd_pkt_devp);
+static void check_and_wakeup_writer(struct smd_pkt_dev *smd_pkt_devp);
+static uint32_t is_modem_smsm_inited(void);
+
+static DEFINE_MUTEX(smd_pkt_dev_lock_lha1);
+static LIST_HEAD(smd_pkt_dev_list);
+static int num_smd_pkt_ports;
+
+#define SMD_PKT_IPC_LOG_PAGE_CNT 2
+static void *smd_pkt_ilctxt;
+
+static int msm_smd_pkt_debug_mask;
+module_param_named(debug_mask, msm_smd_pkt_debug_mask, int, 0664);
+
+enum {
+	SMD_PKT_STATUS = 1U << 0,
+	SMD_PKT_READ = 1U << 1,
+	SMD_PKT_WRITE = 1U << 2,
+	SMD_PKT_POLL = 1U << 5,
+};
+
+#define DEBUG
+
+#ifdef DEBUG
+
+#define SMD_PKT_LOG_STRING(x...) \
+do { \
+	if (smd_pkt_ilctxt) \
+		ipc_log_string(smd_pkt_ilctxt, "<SMD_PKT>: "x); \
+} while (0)
+
+#define D_STATUS(x...) \
+do { \
+	if (msm_smd_pkt_debug_mask & SMD_PKT_STATUS) \
+		pr_info("Status: "x); \
+	SMD_PKT_LOG_STRING(x); \
+} while (0)
+
+#define D_READ(x...) \
+do { \
+	if (msm_smd_pkt_debug_mask & SMD_PKT_READ) \
+		pr_info("Read: "x); \
+	SMD_PKT_LOG_STRING(x); \
+} while (0)
+
+#define D_WRITE(x...) \
+do { \
+	if (msm_smd_pkt_debug_mask & SMD_PKT_WRITE) \
+		pr_info("Write: "x); \
+	SMD_PKT_LOG_STRING(x); \
+} while (0)
+
+#define D_POLL(x...) \
+do { \
+	if (msm_smd_pkt_debug_mask & SMD_PKT_POLL) \
+		pr_info("Poll: "x); \
+	SMD_PKT_LOG_STRING(x); \
+} while (0)
+
+#define E_SMD_PKT_SSR(x) \
+do { \
+	if (x->do_reset_notification) \
+		pr_err("%s notifying reset for smd_pkt_dev id:%d\n", \
+			__func__, x->i); \
+} while (0)
+#else
+#define D_STATUS(x...) do {} while (0)
+#define D_READ(x...) do {} while (0)
+#define D_WRITE(x...) do {} while (0)
+#define D_POLL(x...) do {} while (0)
+#define E_SMD_PKT_SSR(x) do {} while (0)
+#endif
+
+static ssize_t open_timeout_store(struct device *d,
+				  struct device_attribute *attr,
+				  const char *buf,
+				  size_t n)
+{
+	struct smd_pkt_dev *smd_pkt_devp;
+	unsigned long tmp;
+
+	mutex_lock(&smd_pkt_dev_lock_lha1);
+	list_for_each_entry(smd_pkt_devp, &smd_pkt_dev_list, dev_list) {
+		if (smd_pkt_devp->devicep == d) {
+			if (!kstrtoul(buf, 10, &tmp)) {
+				smd_pkt_devp->open_modem_wait = tmp;
+				mutex_unlock(&smd_pkt_dev_lock_lha1);
+				return n;
+			}
+			mutex_unlock(&smd_pkt_dev_lock_lha1);
+			pr_err("%s: unable to convert: %s to an int\n",
+						__func__, buf);
+			return -EINVAL;
+		}
+	}
+	mutex_unlock(&smd_pkt_dev_lock_lha1);
+
+	pr_err("%s: unable to match device to valid smd_pkt port\n", __func__);
+	return -EINVAL;
+}
+
+static ssize_t open_timeout_show(struct device *d,
+				 struct device_attribute *attr,
+				 char *buf)
+{
+	struct smd_pkt_dev *smd_pkt_devp;
+
+	mutex_lock(&smd_pkt_dev_lock_lha1);
+	list_for_each_entry(smd_pkt_devp, &smd_pkt_dev_list, dev_list) {
+		if (smd_pkt_devp->devicep == d) {
+			mutex_unlock(&smd_pkt_dev_lock_lha1);
+			return snprintf(buf, PAGE_SIZE, "%d\n",
+					smd_pkt_devp->open_modem_wait);
+		}
+	}
+	mutex_unlock(&smd_pkt_dev_lock_lha1);
+	pr_err("%s: unable to match device to valid smd_pkt port\n", __func__);
+	return -EINVAL;
+
+}
+
+static DEVICE_ATTR(open_timeout, 0664, open_timeout_show, open_timeout_store);
+
+/**
+ * loopback_edge_store() - Set the edge type for loopback device
+ * @d:		Linux device structure
+ * @attr:	Device attribute structure
+ * @buf:	Input string
+ * @n:		Length of the input string
+ *
+ * This function is used to set the loopback device edge runtime
+ * by writing to the loopback_edge node.
+ */
+static ssize_t loopback_edge_store(struct device *d,
+				  struct device_attribute *attr,
+				  const char *buf,
+				  size_t n)
+{
+	struct smd_pkt_dev *smd_pkt_devp;
+	unsigned long tmp;
+
+	mutex_lock(&smd_pkt_dev_lock_lha1);
+	list_for_each_entry(smd_pkt_devp, &smd_pkt_dev_list, dev_list) {
+		if (smd_pkt_devp->devicep == d) {
+			if (!kstrtoul(buf, 10, &tmp)) {
+				smd_pkt_devp->edge = tmp;
+				mutex_unlock(&smd_pkt_dev_lock_lha1);
+				return n;
+			}
+			mutex_unlock(&smd_pkt_dev_lock_lha1);
+			pr_err("%s: unable to convert: %s to an int\n",
+						__func__, buf);
+			return -EINVAL;
+		}
+	}
+	mutex_unlock(&smd_pkt_dev_lock_lha1);
+	pr_err("%s: unable to match device to valid smd_pkt port\n", __func__);
+	return -EINVAL;
+}
+
+/**
+ * loopback_edge_show() - Get the edge type for loopback device
+ * @d:		Linux device structure
+ * @attr:	Device attribute structure
+ * @buf:	Output buffer
+ *
+ * This function is used to get the loopback device edge runtime
+ * by reading the loopback_edge node.
+ */
+static ssize_t loopback_edge_show(struct device *d,
+				 struct device_attribute *attr,
+				 char *buf)
+{
+	struct smd_pkt_dev *smd_pkt_devp;
+
+	mutex_lock(&smd_pkt_dev_lock_lha1);
+	list_for_each_entry(smd_pkt_devp, &smd_pkt_dev_list, dev_list) {
+		if (smd_pkt_devp->devicep == d) {
+			mutex_unlock(&smd_pkt_dev_lock_lha1);
+			return snprintf(buf, PAGE_SIZE, "%d\n",
+					smd_pkt_devp->edge);
+		}
+	}
+	mutex_unlock(&smd_pkt_dev_lock_lha1);
+	pr_err("%s: unable to match device to valid smd_pkt port\n", __func__);
+	return -EINVAL;
+
+}
+
+static DEVICE_ATTR(loopback_edge, 0664, loopback_edge_show,
+						loopback_edge_store);
+
+static int notify_reset(struct smd_pkt_dev *smd_pkt_devp)
+{
+	smd_pkt_devp->do_reset_notification = 0;
+
+	return -ENETRESET;
+}
+
+static void clean_and_signal(struct smd_pkt_dev *smd_pkt_devp)
+{
+	smd_pkt_devp->do_reset_notification = 1;
+	smd_pkt_devp->has_reset = 1;
+
+	smd_pkt_devp->is_open = 0;
+
+	wake_up(&smd_pkt_devp->ch_read_wait_queue);
+	wake_up(&smd_pkt_devp->ch_write_wait_queue);
+	wake_up_interruptible(&smd_pkt_devp->ch_opened_wait_queue);
+	D_STATUS("%s smd_pkt_dev id:%d\n", __func__, smd_pkt_devp->i);
+}
+
+static void loopback_probe_worker(struct work_struct *work)
+{
+
+	/* Wait for the modem SMSM to be inited for the SMD
+	 ** Loopback channel to be allocated at the modem. Since
+	 ** the wait need to be done atmost once, using msleep
+	 ** doesn't degrade the performance.
+	 */
+	if (!is_modem_smsm_inited())
+		schedule_delayed_work(&loopback_work, msecs_to_jiffies(1000));
+	else
+		smsm_change_state(SMSM_APPS_STATE,
+			  0, SMSM_SMD_LOOPBACK);
+
+}
+
+static void packet_arrival_worker(struct work_struct *work)
+{
+	struct smd_pkt_dev *smd_pkt_devp;
+	unsigned long flags;
+
+	smd_pkt_devp = container_of(work, struct smd_pkt_dev,
+				    packet_arrival_work);
+	mutex_lock(&smd_pkt_devp->ch_lock);
+	spin_lock_irqsave(&smd_pkt_devp->pa_spinlock, flags);
+	if (smd_pkt_devp->ch && smd_pkt_devp->ws_locked) {
+		D_READ("%s locking smd_pkt_dev id:%d wakeup source\n",
+			__func__, smd_pkt_devp->i);
+		/*
+		 * Keep system awake long enough to allow userspace client
+		 * to process the packet.
+		 */
+		__pm_wakeup_event(&smd_pkt_devp->pa_ws, WAKEUPSOURCE_TIMEOUT);
+	}
+	spin_unlock_irqrestore(&smd_pkt_devp->pa_spinlock, flags);
+	mutex_unlock(&smd_pkt_devp->ch_lock);
+}
+
+static long smd_pkt_ioctl(struct file *file, unsigned int cmd,
+					     unsigned long arg)
+{
+	int ret;
+	struct smd_pkt_dev *smd_pkt_devp;
+	uint32_t val;
+
+	smd_pkt_devp = file->private_data;
+	if (!smd_pkt_devp)
+		return -EINVAL;
+
+	mutex_lock(&smd_pkt_devp->ch_lock);
+	switch (cmd) {
+	case TIOCMGET:
+		D_STATUS("%s TIOCMGET command on smd_pkt_dev id:%d\n",
+			 __func__, smd_pkt_devp->i);
+		ret = smd_tiocmget(smd_pkt_devp->ch);
+		break;
+	case TIOCMSET:
+		ret = get_user(val, (uint32_t *)arg);
+		if (ret) {
+			pr_err("Error getting TIOCMSET value\n");
+			mutex_unlock(&smd_pkt_devp->ch_lock);
+			return ret;
+		}
+		D_STATUS("%s TIOCSET command on smd_pkt_dev id:%d arg[0x%x]\n",
+			 __func__, smd_pkt_devp->i, val);
+		ret = smd_tiocmset(smd_pkt_devp->ch, val, ~val);
+		break;
+	case SMD_PKT_IOCTL_BLOCKING_WRITE:
+		ret = get_user(smd_pkt_devp->blocking_write, (int *)arg);
+		break;
+	default:
+		pr_err_ratelimited("%s: Unrecognized ioctl command %d\n",
+			__func__, cmd);
+		ret = -ENOIOCTLCMD;
+	}
+	mutex_unlock(&smd_pkt_devp->ch_lock);
+
+	return ret;
+}
+
+ssize_t smd_pkt_read(struct file *file,
+		       char __user *_buf,
+		       size_t count,
+		       loff_t *ppos)
+{
+	int r;
+	int bytes_read;
+	int pkt_size;
+	struct smd_pkt_dev *smd_pkt_devp;
+	unsigned long flags;
+	void *buf;
+
+	smd_pkt_devp = file->private_data;
+
+	if (!smd_pkt_devp) {
+		pr_err_ratelimited("%s on NULL smd_pkt_dev\n", __func__);
+		return -EINVAL;
+	}
+
+	if (!smd_pkt_devp->ch) {
+		pr_err_ratelimited("%s on a closed smd_pkt_dev id:%d\n",
+			__func__, smd_pkt_devp->i);
+		return -EINVAL;
+	}
+
+	if (smd_pkt_devp->do_reset_notification) {
+		/* notify client that a reset occurred */
+		E_SMD_PKT_SSR(smd_pkt_devp);
+		return notify_reset(smd_pkt_devp);
+	}
+	D_READ("Begin %s on smd_pkt_dev id:%d buffer_size %zu\n",
+		__func__, smd_pkt_devp->i, count);
+
+	buf = kmalloc(count, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+wait_for_packet:
+	r = wait_event_interruptible(smd_pkt_devp->ch_read_wait_queue,
+				     !smd_pkt_devp->ch ||
+				     (smd_cur_packet_size(smd_pkt_devp->ch) > 0
+				      && smd_read_avail(smd_pkt_devp->ch)) ||
+				     smd_pkt_devp->has_reset);
+
+	mutex_lock(&smd_pkt_devp->rx_lock);
+	if (smd_pkt_devp->has_reset) {
+		mutex_unlock(&smd_pkt_devp->rx_lock);
+		E_SMD_PKT_SSR(smd_pkt_devp);
+		kfree(buf);
+		return notify_reset(smd_pkt_devp);
+	}
+
+	if (!smd_pkt_devp->ch) {
+		mutex_unlock(&smd_pkt_devp->rx_lock);
+		pr_err_ratelimited("%s on a closed smd_pkt_dev id:%d\n",
+			__func__, smd_pkt_devp->i);
+		kfree(buf);
+		return -EINVAL;
+	}
+
+	if (r < 0) {
+		mutex_unlock(&smd_pkt_devp->rx_lock);
+		/* qualify error message */
+		if (r != -ERESTARTSYS) {
+			/* we get this anytime a signal comes in */
+			pr_err_ratelimited("%s: wait_event_interruptible on smd_pkt_dev id:%d ret %i\n",
+				__func__, smd_pkt_devp->i, r);
+		}
+		kfree(buf);
+		return r;
+	}
+
+	/* Here we have a whole packet waiting for us */
+	pkt_size = smd_cur_packet_size(smd_pkt_devp->ch);
+
+	if (!pkt_size) {
+		pr_err_ratelimited("%s: No data on smd_pkt_dev id:%d, False wakeup\n",
+			__func__, smd_pkt_devp->i);
+		mutex_unlock(&smd_pkt_devp->rx_lock);
+		goto wait_for_packet;
+	}
+
+	if (pkt_size < 0) {
+		pr_err_ratelimited("%s: Error %d obtaining packet size for Channel %s",
+				__func__, pkt_size, smd_pkt_devp->ch_name);
+		kfree(buf);
+		return pkt_size;
+	}
+
+	if ((uint32_t)pkt_size > count) {
+		pr_err_ratelimited("%s: failure on smd_pkt_dev id: %d - packet size %d > buffer size %zu,",
+			__func__, smd_pkt_devp->i,
+			pkt_size, count);
+		mutex_unlock(&smd_pkt_devp->rx_lock);
+		kfree(buf);
+		return -ETOOSMALL;
+	}
+
+	bytes_read = 0;
+	do {
+		r = smd_read(smd_pkt_devp->ch,
+					 (buf + bytes_read),
+					 (pkt_size - bytes_read));
+		if (r < 0) {
+			mutex_unlock(&smd_pkt_devp->rx_lock);
+			if (smd_pkt_devp->has_reset) {
+				E_SMD_PKT_SSR(smd_pkt_devp);
+				return notify_reset(smd_pkt_devp);
+			}
+			pr_err_ratelimited("%s Error while reading %d\n",
+				__func__, r);
+			kfree(buf);
+			return r;
+		}
+		bytes_read += r;
+		if (pkt_size != bytes_read)
+			wait_event(smd_pkt_devp->ch_read_wait_queue,
+				   smd_read_avail(smd_pkt_devp->ch) ||
+				   smd_pkt_devp->has_reset);
+		if (smd_pkt_devp->has_reset) {
+			mutex_unlock(&smd_pkt_devp->rx_lock);
+			E_SMD_PKT_SSR(smd_pkt_devp);
+			kfree(buf);
+			return notify_reset(smd_pkt_devp);
+		}
+	} while (pkt_size != bytes_read);
+	mutex_unlock(&smd_pkt_devp->rx_lock);
+
+	mutex_lock(&smd_pkt_devp->ch_lock);
+	spin_lock_irqsave(&smd_pkt_devp->pa_spinlock, flags);
+	if (smd_pkt_devp->poll_mode &&
+	    !smd_cur_packet_size(smd_pkt_devp->ch)) {
+		__pm_relax(&smd_pkt_devp->pa_ws);
+		smd_pkt_devp->ws_locked = 0;
+		smd_pkt_devp->poll_mode = 0;
+		D_READ("%s unlocked smd_pkt_dev id:%d wakeup_source\n",
+			__func__, smd_pkt_devp->i);
+	}
+	spin_unlock_irqrestore(&smd_pkt_devp->pa_spinlock, flags);
+	mutex_unlock(&smd_pkt_devp->ch_lock);
+
+	r = copy_to_user(_buf, buf, bytes_read);
+	if (r) {
+		kfree(buf);
+		return -EFAULT;
+	}
+	D_READ("Finished %s on smd_pkt_dev id:%d  %d bytes\n",
+		__func__, smd_pkt_devp->i, bytes_read);
+	kfree(buf);
+
+	/* check and wakeup read threads waiting on this device */
+	check_and_wakeup_reader(smd_pkt_devp);
+
+	return bytes_read;
+}
+
+ssize_t smd_pkt_write(struct file *file,
+		       const char __user *_buf,
+		       size_t count,
+		       loff_t *ppos)
+{
+	int r = 0, bytes_written;
+	struct smd_pkt_dev *smd_pkt_devp;
+	DEFINE_WAIT(write_wait);
+	void *buf;
+
+	smd_pkt_devp = file->private_data;
+
+	if (!smd_pkt_devp) {
+		pr_err_ratelimited("%s on NULL smd_pkt_dev\n", __func__);
+		return -EINVAL;
+	}
+
+	if (!smd_pkt_devp->ch) {
+		pr_err_ratelimited("%s on a closed smd_pkt_dev id:%d\n",
+			__func__, smd_pkt_devp->i);
+		return -EINVAL;
+	}
+
+	if (smd_pkt_devp->do_reset_notification || smd_pkt_devp->has_reset) {
+		E_SMD_PKT_SSR(smd_pkt_devp);
+		/* notify client that a reset occurred */
+		return notify_reset(smd_pkt_devp);
+	}
+	D_WRITE("Begin %s on smd_pkt_dev id:%d data_size %zu\n",
+		__func__, smd_pkt_devp->i, count);
+
+	buf = kmalloc(count, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	r = copy_from_user(buf, _buf, count);
+	if (r) {
+		kfree(buf);
+		return -EFAULT;
+	}
+
+	mutex_lock(&smd_pkt_devp->tx_lock);
+	if (!smd_pkt_devp->blocking_write) {
+		if (smd_write_avail(smd_pkt_devp->ch) < count) {
+			pr_err_ratelimited("%s: Not enough space in smd_pkt_dev id:%d\n",
+				   __func__, smd_pkt_devp->i);
+			mutex_unlock(&smd_pkt_devp->tx_lock);
+			kfree(buf);
+			return -ENOMEM;
+		}
+	}
+
+	r = smd_write_start(smd_pkt_devp->ch, count);
+	if (r < 0) {
+		mutex_unlock(&smd_pkt_devp->tx_lock);
+		pr_err_ratelimited("%s: Error:%d in smd_pkt_dev id:%d @ smd_write_start\n",
+			__func__, r, smd_pkt_devp->i);
+		kfree(buf);
+		return r;
+	}
+
+	bytes_written = 0;
+	do {
+		prepare_to_wait(&smd_pkt_devp->ch_write_wait_queue,
+				&write_wait, TASK_UNINTERRUPTIBLE);
+		if (!smd_write_segment_avail(smd_pkt_devp->ch) &&
+		    !smd_pkt_devp->has_reset) {
+			smd_enable_read_intr(smd_pkt_devp->ch);
+			schedule();
+		}
+		finish_wait(&smd_pkt_devp->ch_write_wait_queue, &write_wait);
+		smd_disable_read_intr(smd_pkt_devp->ch);
+
+		if (smd_pkt_devp->has_reset) {
+			mutex_unlock(&smd_pkt_devp->tx_lock);
+			E_SMD_PKT_SSR(smd_pkt_devp);
+			kfree(buf);
+			return notify_reset(smd_pkt_devp);
+		}
+		r = smd_write_segment(smd_pkt_devp->ch,
+				      (void *)(buf + bytes_written),
+				      (count - bytes_written));
+		if (r < 0) {
+			mutex_unlock(&smd_pkt_devp->tx_lock);
+			if (smd_pkt_devp->has_reset) {
+				E_SMD_PKT_SSR(smd_pkt_devp);
+				return notify_reset(smd_pkt_devp);
+			}
+			pr_err_ratelimited("%s on smd_pkt_dev id:%d failed r:%d\n",
+				__func__, smd_pkt_devp->i, r);
+			kfree(buf);
+			return r;
+		}
+		bytes_written += r;
+	} while (bytes_written != count);
+	smd_write_end(smd_pkt_devp->ch);
+	mutex_unlock(&smd_pkt_devp->tx_lock);
+	D_WRITE("Finished %s on smd_pkt_dev id:%d %zu bytes\n",
+		__func__, smd_pkt_devp->i, count);
+
+	kfree(buf);
+	return count;
+}
+
+static unsigned int smd_pkt_poll(struct file *file, poll_table *wait)
+{
+	struct smd_pkt_dev *smd_pkt_devp;
+	unsigned int mask = 0;
+
+	smd_pkt_devp = file->private_data;
+	if (!smd_pkt_devp) {
+		pr_err_ratelimited("%s on a NULL device\n", __func__);
+		return POLLERR;
+	}
+
+	smd_pkt_devp->poll_mode = 1;
+	poll_wait(file, &smd_pkt_devp->ch_read_wait_queue, wait);
+	mutex_lock(&smd_pkt_devp->ch_lock);
+	if (smd_pkt_devp->has_reset || !smd_pkt_devp->ch) {
+		mutex_unlock(&smd_pkt_devp->ch_lock);
+		return POLLERR;
+	}
+
+	if (smd_read_avail(smd_pkt_devp->ch)) {
+		mask |= POLLIN | POLLRDNORM;
+		D_POLL("%s sets POLLIN for smd_pkt_dev id: %d\n",
+			__func__, smd_pkt_devp->i);
+	}
+	mutex_unlock(&smd_pkt_devp->ch_lock);
+
+	return mask;
+}
+
+static void check_and_wakeup_reader(struct smd_pkt_dev *smd_pkt_devp)
+{
+	int sz;
+	unsigned long flags;
+
+	if (!smd_pkt_devp) {
+		pr_err("%s on a NULL device\n", __func__);
+		return;
+	}
+
+	if (!smd_pkt_devp->ch) {
+		pr_err("%s on a closed smd_pkt_dev id:%d\n",
+			__func__, smd_pkt_devp->i);
+		return;
+	}
+
+	sz = smd_cur_packet_size(smd_pkt_devp->ch);
+	if (sz == 0) {
+		D_READ("%s: No packet in smd_pkt_dev id:%d\n",
+			__func__, smd_pkt_devp->i);
+		return;
+	}
+	if (!smd_read_avail(smd_pkt_devp->ch)) {
+		D_READ(
+			"%s: packet size is %d in smd_pkt_dev id:%d - but the data isn't here\n",
+			__func__, sz, smd_pkt_devp->i);
+		return;
+	}
+
+	/* here we have a packet of size sz ready */
+	spin_lock_irqsave(&smd_pkt_devp->pa_spinlock, flags);
+	__pm_stay_awake(&smd_pkt_devp->pa_ws);
+	smd_pkt_devp->ws_locked = 1;
+	spin_unlock_irqrestore(&smd_pkt_devp->pa_spinlock, flags);
+	wake_up(&smd_pkt_devp->ch_read_wait_queue);
+	schedule_work(&smd_pkt_devp->packet_arrival_work);
+	D_READ("%s: wake_up smd_pkt_dev id:%d\n", __func__, smd_pkt_devp->i);
+}
+
+static void check_and_wakeup_writer(struct smd_pkt_dev *smd_pkt_devp)
+{
+	int sz;
+
+	if (!smd_pkt_devp) {
+		pr_err("%s on a NULL device\n", __func__);
+		return;
+	}
+
+	if (!smd_pkt_devp->ch) {
+		pr_err("%s on a closed smd_pkt_dev id:%d\n",
+			__func__, smd_pkt_devp->i);
+		return;
+	}
+
+	sz = smd_write_segment_avail(smd_pkt_devp->ch);
+	if (sz) {
+		D_WRITE("%s: %d bytes write space in smd_pkt_dev id:%d\n",
+			__func__, sz, smd_pkt_devp->i);
+		smd_disable_read_intr(smd_pkt_devp->ch);
+		wake_up(&smd_pkt_devp->ch_write_wait_queue);
+	}
+}
+
+static void ch_notify(void *priv, unsigned int event)
+{
+	struct smd_pkt_dev *smd_pkt_devp = priv;
+
+	if (smd_pkt_devp->ch == 0) {
+		if (event != SMD_EVENT_CLOSE)
+			pr_err("%s on a closed smd_pkt_dev id:%d\n",
+					__func__, smd_pkt_devp->i);
+		return;
+	}
+
+	switch (event) {
+	case SMD_EVENT_DATA: {
+		D_STATUS("%s: DATA event in smd_pkt_dev id:%d\n",
+			 __func__, smd_pkt_devp->i);
+		check_and_wakeup_reader(smd_pkt_devp);
+		if (smd_pkt_devp->blocking_write)
+			check_and_wakeup_writer(smd_pkt_devp);
+		break;
+	}
+	case SMD_EVENT_OPEN:
+		D_STATUS("%s: OPEN event in smd_pkt_dev id:%d\n",
+			  __func__, smd_pkt_devp->i);
+		smd_pkt_devp->has_reset = 0;
+		smd_pkt_devp->is_open = 1;
+		wake_up_interruptible(&smd_pkt_devp->ch_opened_wait_queue);
+		break;
+	case SMD_EVENT_CLOSE:
+		D_STATUS("%s: CLOSE event in smd_pkt_dev id:%d\n",
+			  __func__, smd_pkt_devp->i);
+		smd_pkt_devp->is_open = 0;
+		/* put port into reset state */
+		clean_and_signal(smd_pkt_devp);
+		if (!strcmp(smd_pkt_devp->ch_name, "LOOPBACK"))
+			schedule_delayed_work(&loopback_work,
+					msecs_to_jiffies(1000));
+		break;
+	}
+}
+
+static int smd_pkt_dummy_probe(struct platform_device *pdev)
+{
+	struct smd_pkt_dev *smd_pkt_devp;
+
+	mutex_lock(&smd_pkt_dev_lock_lha1);
+	list_for_each_entry(smd_pkt_devp, &smd_pkt_dev_list, dev_list) {
+		if (smd_pkt_devp->edge == pdev->id
+		    && !strcmp(pdev->name, smd_pkt_devp->ch_name)) {
+			complete_all(&smd_pkt_devp->ch_allocated);
+			D_STATUS("%s allocated SMD ch for smd_pkt_dev id:%d\n",
+				 __func__, smd_pkt_devp->i);
+			break;
+		}
+	}
+	mutex_unlock(&smd_pkt_dev_lock_lha1);
+	return 0;
+}
+
+static uint32_t is_modem_smsm_inited(void)
+{
+	uint32_t modem_state;
+	uint32_t ready_state = (SMSM_INIT | SMSM_SMDINIT);
+
+	modem_state = smsm_get_state(SMSM_MODEM_STATE);
+	return (modem_state & ready_state) == ready_state;
+}
+
+/**
+ * smd_pkt_add_driver() - Add platform drivers for smd pkt device
+ *
+ * @smd_pkt_devp: pointer to the smd pkt device structure
+ *
+ * @returns:	0 for success, standard Linux error code otherwise
+ *
+ * This function is used to register platform driver once for all
+ * smd pkt devices which have same names and increment the reference
+ * count for 2nd to nth devices.
+ */
+static int smd_pkt_add_driver(struct smd_pkt_dev *smd_pkt_devp)
+{
+	int r = 0;
+	struct smd_pkt_driver *smd_pkt_driverp;
+	struct smd_pkt_driver *item;
+
+	if (!smd_pkt_devp) {
+		pr_err("%s on a NULL device\n", __func__);
+		return -EINVAL;
+	}
+	D_STATUS("Begin %s on smd_pkt_ch[%s]\n", __func__,
+					smd_pkt_devp->ch_name);
+
+	mutex_lock(&smd_pkt_driver_lock_lha1);
+	list_for_each_entry(item, &smd_pkt_driver_list, list) {
+		if (!strcmp(item->pdriver_name, smd_pkt_devp->ch_name)) {
+			D_STATUS("%s:%s Already Platform driver reg. cnt:%d\n",
+				__func__, smd_pkt_devp->ch_name, item->ref_cnt);
+			++item->ref_cnt;
+			goto exit;
+		}
+	}
+
+	smd_pkt_driverp = kzalloc(sizeof(*smd_pkt_driverp), GFP_KERNEL);
+	if (IS_ERR_OR_NULL(smd_pkt_driverp)) {
+		pr_err("%s: kzalloc() failed for smd_pkt_driver[%s]\n",
+			__func__, smd_pkt_devp->ch_name);
+		r = -ENOMEM;
+		goto exit;
+	}
+
+	smd_pkt_driverp->driver.probe = smd_pkt_dummy_probe;
+	scnprintf(smd_pkt_driverp->pdriver_name, SMD_MAX_CH_NAME_LEN,
+		  "%s", smd_pkt_devp->ch_name);
+	smd_pkt_driverp->driver.driver.name = smd_pkt_driverp->pdriver_name;
+	smd_pkt_driverp->driver.driver.owner = THIS_MODULE;
+	r = platform_driver_register(&smd_pkt_driverp->driver);
+	if (r) {
+		pr_err("%s: %s Platform driver reg. failed\n",
+			__func__, smd_pkt_devp->ch_name);
+		kfree(smd_pkt_driverp);
+		goto exit;
+	}
+	++smd_pkt_driverp->ref_cnt;
+	list_add(&smd_pkt_driverp->list, &smd_pkt_driver_list);
+
+exit:
+	D_STATUS("End %s on smd_pkt_ch[%s]\n", __func__, smd_pkt_devp->ch_name);
+	mutex_unlock(&smd_pkt_driver_lock_lha1);
+	return r;
+}
+
+/**
+ * smd_pkt_remove_driver() - Remove the platform drivers for smd pkt device
+ *
+ * @smd_pkt_devp: pointer to the smd pkt device structure
+ *
+ * This function is used to decrement the reference count on
+ * platform drivers for smd pkt devices and removes the drivers
+ * when the reference count becomes zero.
+ */
+static void smd_pkt_remove_driver(struct smd_pkt_dev *smd_pkt_devp)
+{
+	struct smd_pkt_driver *smd_pkt_driverp;
+	bool found_item = false;
+
+	if (!smd_pkt_devp) {
+		pr_err("%s on a NULL device\n", __func__);
+		return;
+	}
+
+	D_STATUS("Begin %s on smd_pkt_ch[%s]\n", __func__,
+					smd_pkt_devp->ch_name);
+	mutex_lock(&smd_pkt_driver_lock_lha1);
+	list_for_each_entry(smd_pkt_driverp, &smd_pkt_driver_list, list) {
+		if (!strcmp(smd_pkt_driverp->pdriver_name,
+					smd_pkt_devp->ch_name)) {
+			found_item = true;
+			D_STATUS("%s:%s Platform driver cnt:%d\n",
+				__func__, smd_pkt_devp->ch_name,
+				smd_pkt_driverp->ref_cnt);
+			if (smd_pkt_driverp->ref_cnt > 0)
+				--smd_pkt_driverp->ref_cnt;
+			else
+				pr_warn("%s reference count <= 0\n", __func__);
+			break;
+		}
+	}
+	if (!found_item)
+		pr_err("%s:%s No item found in list.\n",
+				__func__, smd_pkt_devp->ch_name);
+
+	if (found_item && smd_pkt_driverp->ref_cnt == 0) {
+		platform_driver_unregister(&smd_pkt_driverp->driver);
+		smd_pkt_driverp->driver.probe = NULL;
+		list_del(&smd_pkt_driverp->list);
+		kfree(smd_pkt_driverp);
+	}
+	mutex_unlock(&smd_pkt_driver_lock_lha1);
+	D_STATUS("End %s on smd_pkt_ch[%s]\n", __func__, smd_pkt_devp->ch_name);
+}
+
+int smd_pkt_open(struct inode *inode, struct file *file)
+{
+	int r = 0;
+	struct smd_pkt_dev *smd_pkt_devp;
+	const char *peripheral = NULL;
+
+	smd_pkt_devp = container_of(inode->i_cdev, struct smd_pkt_dev, cdev);
+
+	if (!smd_pkt_devp) {
+		pr_err_ratelimited("%s on a NULL device\n", __func__);
+		return -EINVAL;
+	}
+	D_STATUS("Begin %s on smd_pkt_dev id:%d\n", __func__, smd_pkt_devp->i);
+
+	file->private_data = smd_pkt_devp;
+
+	mutex_lock(&smd_pkt_devp->ch_lock);
+	if (smd_pkt_devp->ch == 0) {
+		unsigned int open_wait_rem;
+
+		open_wait_rem = smd_pkt_devp->open_modem_wait * 1000;
+		reinit_completion(&smd_pkt_devp->ch_allocated);
+
+		r = smd_pkt_add_driver(smd_pkt_devp);
+		if (r) {
+			pr_err_ratelimited("%s: %s Platform driver reg. failed\n",
+				__func__, smd_pkt_devp->ch_name);
+			goto out;
+		}
+
+		peripheral = smd_edge_to_pil_str(smd_pkt_devp->edge);
+		if (!IS_ERR_OR_NULL(peripheral)) {
+			smd_pkt_devp->pil = subsystem_get(peripheral);
+			if (IS_ERR(smd_pkt_devp->pil)) {
+				r = PTR_ERR(smd_pkt_devp->pil);
+				pr_err_ratelimited("%s failed on smd_pkt_dev id:%d - subsystem_get failed for %s\n",
+					__func__, smd_pkt_devp->i, peripheral);
+				/*
+				 * Sleep inorder to reduce the frequency of
+				 * retry by user-space modules and to avoid
+				 * possible watchdog bite.
+				 */
+				msleep(open_wait_rem);
+				goto release_pd;
+			}
+		}
+
+		/* Wait for the modem SMSM to be inited for the SMD
+		 ** Loopback channel to be allocated at the modem. Since
+		 ** the wait need to be done atmost once, using msleep
+		 ** doesn't degrade the performance.
+		 */
+		if (!strcmp(smd_pkt_devp->ch_name, "LOOPBACK")) {
+			if (!is_modem_smsm_inited())
+				msleep(5000);
+			smsm_change_state(SMSM_APPS_STATE,
+					  0, SMSM_SMD_LOOPBACK);
+			msleep(100);
+		}
+
+		/*
+		 * Wait for a packet channel to be allocated so we know
+		 * the modem is ready enough.
+		 */
+		if (open_wait_rem) {
+			r = wait_for_completion_interruptible_timeout(
+				&smd_pkt_devp->ch_allocated,
+				msecs_to_jiffies(open_wait_rem));
+			if (r >= 0)
+				open_wait_rem = jiffies_to_msecs(r);
+			if (r == 0)
+				r = -ETIMEDOUT;
+			if (r == -ERESTARTSYS) {
+				pr_info_ratelimited("%s: wait on smd_pkt_dev id:%d allocation interrupted\n",
+					__func__, smd_pkt_devp->i);
+				goto release_pil;
+			}
+			if (r < 0) {
+				pr_err_ratelimited("%s: wait on smd_pkt_dev id:%d allocation failed rc:%d\n",
+					__func__, smd_pkt_devp->i, r);
+				goto release_pil;
+			}
+		}
+
+		r = smd_named_open_on_edge(smd_pkt_devp->ch_name,
+					   smd_pkt_devp->edge,
+					   &smd_pkt_devp->ch,
+					   smd_pkt_devp,
+					   ch_notify);
+		if (r < 0) {
+			pr_err_ratelimited("%s: %s open failed %d\n", __func__,
+			       smd_pkt_devp->ch_name, r);
+			goto release_pil;
+		}
+
+		open_wait_rem = max_t(unsigned int, 2000, open_wait_rem);
+		r = wait_event_interruptible_timeout(
+				smd_pkt_devp->ch_opened_wait_queue,
+				smd_pkt_devp->is_open,
+				msecs_to_jiffies(open_wait_rem));
+		if (r == 0)
+			r = -ETIMEDOUT;
+
+		if (r < 0) {
+			/* close the ch to sync smd's state with smd_pkt */
+			smd_close(smd_pkt_devp->ch);
+			smd_pkt_devp->ch = NULL;
+		}
+
+		if (r == -ERESTARTSYS) {
+			pr_info_ratelimited("%s: wait on smd_pkt_dev id:%d OPEN interrupted\n",
+				__func__, smd_pkt_devp->i);
+		} else if (r < 0) {
+			pr_err_ratelimited("%s: wait on smd_pkt_dev id:%d OPEN event failed rc:%d\n",
+				__func__, smd_pkt_devp->i, r);
+		} else if (!smd_pkt_devp->is_open) {
+			pr_err_ratelimited("%s: Invalid OPEN event on smd_pkt_dev id:%d\n",
+				__func__, smd_pkt_devp->i);
+			r = -ENODEV;
+		} else {
+			smd_disable_read_intr(smd_pkt_devp->ch);
+			smd_pkt_devp->ch_size =
+				smd_write_avail(smd_pkt_devp->ch);
+			r = 0;
+			smd_pkt_devp->ref_cnt++;
+			D_STATUS("Finished %s on smd_pkt_dev id:%d\n",
+				 __func__, smd_pkt_devp->i);
+		}
+	} else {
+		smd_pkt_devp->ref_cnt++;
+	}
+release_pil:
+	if (peripheral && (r < 0)) {
+		subsystem_put(smd_pkt_devp->pil);
+		smd_pkt_devp->pil = NULL;
+	}
+
+release_pd:
+	if (r < 0)
+		smd_pkt_remove_driver(smd_pkt_devp);
+out:
+	mutex_unlock(&smd_pkt_devp->ch_lock);
+
+
+	return r;
+}
+
+int smd_pkt_release(struct inode *inode, struct file *file)
+{
+	int r = 0;
+	struct smd_pkt_dev *smd_pkt_devp = file->private_data;
+	unsigned long flags;
+
+	if (!smd_pkt_devp) {
+		pr_err_ratelimited("%s on a NULL device\n", __func__);
+		return -EINVAL;
+	}
+	D_STATUS("Begin %s on smd_pkt_dev id:%d\n",
+		 __func__, smd_pkt_devp->i);
+
+	mutex_lock(&smd_pkt_devp->ch_lock);
+	mutex_lock(&smd_pkt_devp->rx_lock);
+	mutex_lock(&smd_pkt_devp->tx_lock);
+	if (smd_pkt_devp->ref_cnt > 0)
+		smd_pkt_devp->ref_cnt--;
+
+	if (smd_pkt_devp->ch != 0 && smd_pkt_devp->ref_cnt == 0) {
+		clean_and_signal(smd_pkt_devp);
+		r = smd_close(smd_pkt_devp->ch);
+		smd_pkt_devp->ch = 0;
+		smd_pkt_devp->blocking_write = 0;
+		smd_pkt_devp->poll_mode = 0;
+		smd_pkt_remove_driver(smd_pkt_devp);
+		if (smd_pkt_devp->pil)
+			subsystem_put(smd_pkt_devp->pil);
+		smd_pkt_devp->has_reset = 0;
+		smd_pkt_devp->do_reset_notification = 0;
+		spin_lock_irqsave(&smd_pkt_devp->pa_spinlock, flags);
+		if (smd_pkt_devp->ws_locked) {
+			__pm_relax(&smd_pkt_devp->pa_ws);
+			smd_pkt_devp->ws_locked = 0;
+		}
+		spin_unlock_irqrestore(&smd_pkt_devp->pa_spinlock, flags);
+	}
+	mutex_unlock(&smd_pkt_devp->tx_lock);
+	mutex_unlock(&smd_pkt_devp->rx_lock);
+	mutex_unlock(&smd_pkt_devp->ch_lock);
+
+	if (flush_work(&smd_pkt_devp->packet_arrival_work))
+		D_STATUS("%s: Flushed work for smd_pkt_dev id:%d\n", __func__,
+				smd_pkt_devp->i);
+
+	D_STATUS("Finished %s on smd_pkt_dev id:%d\n",
+		 __func__, smd_pkt_devp->i);
+
+	return r;
+}
+
+static const struct file_operations smd_pkt_fops = {
+	.owner = THIS_MODULE,
+	.open = smd_pkt_open,
+	.release = smd_pkt_release,
+	.read = smd_pkt_read,
+	.write = smd_pkt_write,
+	.poll = smd_pkt_poll,
+	.unlocked_ioctl = smd_pkt_ioctl,
+	.compat_ioctl = smd_pkt_ioctl,
+};
+
+static int smd_pkt_init_add_device(struct smd_pkt_dev *smd_pkt_devp, int i)
+{
+	int r = 0;
+
+	smd_pkt_devp->i = i;
+
+	init_waitqueue_head(&smd_pkt_devp->ch_read_wait_queue);
+	init_waitqueue_head(&smd_pkt_devp->ch_write_wait_queue);
+	smd_pkt_devp->is_open = 0;
+	smd_pkt_devp->poll_mode = 0;
+	smd_pkt_devp->ws_locked = 0;
+	init_waitqueue_head(&smd_pkt_devp->ch_opened_wait_queue);
+
+	spin_lock_init(&smd_pkt_devp->pa_spinlock);
+	mutex_init(&smd_pkt_devp->ch_lock);
+	mutex_init(&smd_pkt_devp->rx_lock);
+	mutex_init(&smd_pkt_devp->tx_lock);
+	wakeup_source_init(&smd_pkt_devp->pa_ws, smd_pkt_devp->dev_name);
+	INIT_WORK(&smd_pkt_devp->packet_arrival_work, packet_arrival_worker);
+	init_completion(&smd_pkt_devp->ch_allocated);
+
+	cdev_init(&smd_pkt_devp->cdev, &smd_pkt_fops);
+	smd_pkt_devp->cdev.owner = THIS_MODULE;
+
+	r = cdev_add(&smd_pkt_devp->cdev, (smd_pkt_number + i), 1);
+	if (r) {
+		pr_err("%s: cdev_add() failed for smd_pkt_dev id:%d ret:%i\n",
+			__func__, i, r);
+		return r;
+	}
+
+	smd_pkt_devp->devicep =
+		device_create(smd_pkt_classp,
+			      NULL,
+			      (smd_pkt_number + i),
+			      NULL,
+			      smd_pkt_devp->dev_name);
+
+	if (IS_ERR_OR_NULL(smd_pkt_devp->devicep)) {
+		pr_err("%s: device_create() failed for smd_pkt_dev id:%d\n",
+			__func__, i);
+		r = -ENOMEM;
+		cdev_del(&smd_pkt_devp->cdev);
+		wakeup_source_trash(&smd_pkt_devp->pa_ws);
+		return r;
+	}
+	if (device_create_file(smd_pkt_devp->devicep,
+				&dev_attr_open_timeout))
+		pr_err("%s: unable to create device attr for smd_pkt_dev id:%d\n",
+			__func__, i);
+
+	if (!strcmp(smd_pkt_devp->ch_name, "LOOPBACK")) {
+		if (device_create_file(smd_pkt_devp->devicep,
+					&dev_attr_loopback_edge))
+			pr_err("%s: unable to create device attr for smd_pkt_dev id:%d\n",
+				__func__, i);
+	}
+	mutex_lock(&smd_pkt_dev_lock_lha1);
+	list_add(&smd_pkt_devp->dev_list, &smd_pkt_dev_list);
+	mutex_unlock(&smd_pkt_dev_lock_lha1);
+
+	return r;
+}
+
+static void smd_pkt_core_deinit(void)
+{
+	struct smd_pkt_dev *smd_pkt_devp;
+	struct smd_pkt_dev *index;
+
+	mutex_lock(&smd_pkt_dev_lock_lha1);
+	list_for_each_entry_safe(smd_pkt_devp, index, &smd_pkt_dev_list,
+							dev_list) {
+		cdev_del(&smd_pkt_devp->cdev);
+		list_del(&smd_pkt_devp->dev_list);
+		device_destroy(smd_pkt_classp,
+			       MKDEV(MAJOR(smd_pkt_number), smd_pkt_devp->i));
+		kfree(smd_pkt_devp);
+	}
+	mutex_unlock(&smd_pkt_dev_lock_lha1);
+
+	if (!IS_ERR_OR_NULL(smd_pkt_classp))
+		class_destroy(smd_pkt_classp);
+
+	unregister_chrdev_region(MAJOR(smd_pkt_number), num_smd_pkt_ports);
+}
+
+static int smd_pkt_alloc_chrdev_region(void)
+{
+	int r = alloc_chrdev_region(&smd_pkt_number,
+			       0,
+			       num_smd_pkt_ports,
+			       DEVICE_NAME);
+
+	if (r) {
+		pr_err("%s: alloc_chrdev_region() failed ret:%i\n",
+			__func__, r);
+		return r;
+	}
+
+	smd_pkt_classp = class_create(THIS_MODULE, DEVICE_NAME);
+	if (IS_ERR(smd_pkt_classp)) {
+		pr_err("%s: class_create() failed ENOMEM\n", __func__);
+		r = -ENOMEM;
+		unregister_chrdev_region(MAJOR(smd_pkt_number),
+						num_smd_pkt_ports);
+		return r;
+	}
+
+	return 0;
+}
+
+static int parse_smdpkt_devicetree(struct device_node *node,
+					struct smd_pkt_dev *smd_pkt_devp)
+{
+	int edge;
+	char *key;
+	const char *ch_name;
+	const char *dev_name;
+	const char *remote_ss;
+
+	key = "qcom,smdpkt-remote";
+	remote_ss = of_get_property(node, key, NULL);
+	if (!remote_ss)
+		goto error;
+
+	edge = smd_remote_ss_to_edge(remote_ss);
+	if (edge < 0)
+		goto error;
+
+	smd_pkt_devp->edge = edge;
+	D_STATUS("%s: %s = %d", __func__, key, edge);
+
+	key = "qcom,smdpkt-port-name";
+	ch_name = of_get_property(node, key, NULL);
+	if (!ch_name)
+		goto error;
+
+	strlcpy(smd_pkt_devp->ch_name, ch_name, SMD_MAX_CH_NAME_LEN);
+	D_STATUS("%s ch_name = %s\n", __func__, ch_name);
+
+	key = "qcom,smdpkt-dev-name";
+	dev_name = of_get_property(node, key, NULL);
+	if (!dev_name)
+		goto error;
+
+	strlcpy(smd_pkt_devp->dev_name, dev_name, SMD_MAX_CH_NAME_LEN);
+	D_STATUS("%s dev_name = %s\n", __func__, dev_name);
+
+	return 0;
+
+error:
+	pr_err("%s: missing key: %s\n", __func__, key);
+	return -ENODEV;
+
+}
+
+static int smd_pkt_devicetree_init(struct platform_device *pdev)
+{
+	int ret;
+	int i = 0;
+	struct device_node *node;
+	struct smd_pkt_dev *smd_pkt_devp;
+	int subnode_num = 0;
+
+	for_each_child_of_node(pdev->dev.of_node, node)
+		++subnode_num;
+
+	num_smd_pkt_ports = subnode_num;
+
+	ret = smd_pkt_alloc_chrdev_region();
+	if (ret) {
+		pr_err("%s: smd_pkt_alloc_chrdev_region() failed ret:%i\n",
+			__func__, ret);
+		return ret;
+	}
+
+	for_each_child_of_node(pdev->dev.of_node, node) {
+		smd_pkt_devp = kzalloc(sizeof(struct smd_pkt_dev), GFP_KERNEL);
+		if (IS_ERR_OR_NULL(smd_pkt_devp)) {
+			pr_err("%s: kzalloc() failed for smd_pkt_dev id:%d\n",
+				__func__, i);
+			ret = -ENOMEM;
+			goto error_destroy;
+		}
+
+		ret = parse_smdpkt_devicetree(node, smd_pkt_devp);
+		if (ret) {
+			pr_err(" failed to parse_smdpkt_devicetree %d\n", i);
+			kfree(smd_pkt_devp);
+			goto error_destroy;
+		}
+
+		ret = smd_pkt_init_add_device(smd_pkt_devp, i);
+		if (ret < 0) {
+			pr_err("add device failed for idx:%d ret=%d\n", i, ret);
+			kfree(smd_pkt_devp);
+			goto error_destroy;
+		}
+		i++;
+	}
+
+	INIT_DELAYED_WORK(&loopback_work, loopback_probe_worker);
+
+	D_STATUS("SMD Packet Port Driver Initialized.\n");
+	return 0;
+
+error_destroy:
+	smd_pkt_core_deinit();
+	return ret;
+}
+
+static int msm_smd_pkt_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	if (pdev) {
+		if (pdev->dev.of_node) {
+			D_STATUS("%s device tree implementation\n", __func__);
+			ret = smd_pkt_devicetree_init(pdev);
+			if (ret)
+				pr_err("%s: device tree init failed\n",
+					__func__);
+		}
+	}
+
+	return 0;
+}
+
+static const struct of_device_id msm_smd_pkt_match_table[] = {
+	{ .compatible = "qcom,smdpkt" },
+	{},
+};
+
+static struct platform_driver msm_smd_pkt_driver = {
+	.probe = msm_smd_pkt_probe,
+	.driver = {
+		.name = MODULE_NAME,
+		.owner = THIS_MODULE,
+		.of_match_table = msm_smd_pkt_match_table,
+	 },
+};
+
+static int __init smd_pkt_init(void)
+{
+	int rc;
+
+	INIT_LIST_HEAD(&smd_pkt_dev_list);
+	INIT_LIST_HEAD(&smd_pkt_driver_list);
+	rc = platform_driver_register(&msm_smd_pkt_driver);
+	if (rc) {
+		pr_err("%s: msm_smd_driver register failed %d\n",
+			 __func__, rc);
+		return rc;
+	}
+
+	smd_pkt_ilctxt = ipc_log_context_create(SMD_PKT_IPC_LOG_PAGE_CNT,
+						"smd_pkt", 0);
+	return 0;
+}
+
+static void __exit smd_pkt_cleanup(void)
+{
+	smd_pkt_core_deinit();
+}
+
+module_init(smd_pkt_init);
+module_exit(smd_pkt_cleanup);
+
+MODULE_DESCRIPTION("MSM Shared Memory Packet Port");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 9750969..bd90802 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -310,6 +310,27 @@
 	  processors in the System on a Chip (SoC) which allows basic
 	  inter-processor communication.
 
+config MSM_SMD
+	depends on MSM_SMEM
+	bool "MSM Shared Memory Driver (SMD)"
+	help
+	  Support for the shared memory interprocessor communication protocol
+	  which provides virual point to point serial channels between processes
+	  on the apps processor and processes on other processors in the SoC.
+	  Also includes support for the Shared Memory State Machine (SMSM)
+	  protocol which provides a mechanism to publish single bit state
+	  information to one or more processors in the SoC.
+
+config MSM_SMD_DEBUG
+	depends on MSM_SMD
+	bool "MSM SMD debug support"
+	help
+	  Support for debugging SMD and SMSM communication between apps and
+	  other processors in the SoC. Debug support primarily consists of
+	  logs consisting of information such as what interrupts were processed,
+	  what channels caused interrupt activity, and when internal state
+	  change events occur.
+
 config MSM_GLINK
 	bool "Generic Link (G-Link)"
 	help
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 768d4d9..4454512 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -32,6 +32,7 @@
 obj-$(CONFIG_QCOM_RUN_QUEUE_STATS) += rq_stats.o
 obj-$(CONFIG_QCOM_SECURE_BUFFER) += secure_buffer.o
 obj-$(CONFIG_MSM_SMEM) += msm_smem.o smem_debug.o
+obj-$(CONFIG_MSM_SMD)	+= msm_smd.o smd_debug.o smd_private.o smd_init_dt.o smsm_debug.o
 obj-$(CONFIG_MSM_GLINK) += glink.o glink_debugfs.o glink_ssr.o
 obj-$(CONFIG_MSM_GLINK_LOOPBACK_SERVER) += glink_loopback_server.o
 obj-$(CONFIG_MSM_GLINK_SMEM_NATIVE_XPRT) += glink_smem_native_xprt.o
diff --git a/drivers/soc/qcom/msm_smd.c b/drivers/soc/qcom/msm_smd.c
new file mode 100644
index 0000000..1631984
--- /dev/null
+++ b/drivers/soc/qcom/msm_smd.c
@@ -0,0 +1,3254 @@
+/* drivers/soc/qcom/msm_smd.c
+ *
+ * Copyright (C) 2007 Google, Inc.
+ * Copyright (c) 2008-2017, The Linux Foundation. All rights reserved.
+ * Author: Brian Swetland <swetland@google.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/wait.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/termios.h>
+#include <linux/ctype.h>
+#include <linux/remote_spinlock.h>
+#include <linux/uaccess.h>
+#include <linux/kfifo.h>
+#include <linux/pm.h>
+#include <linux/notifier.h>
+#include <linux/suspend.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/ipc_logging.h>
+
+#include <soc/qcom/ramdump.h>
+#include <soc/qcom/smd.h>
+#include <soc/qcom/smem.h>
+#include <soc/qcom/subsystem_notif.h>
+#include <soc/qcom/subsystem_restart.h>
+
+#include "smd_private.h"
+#include "smem_private.h"
+
+#define SMSM_SNAPSHOT_CNT 64
+#define SMSM_SNAPSHOT_SIZE ((SMSM_NUM_ENTRIES + 1) * 4 + sizeof(uint64_t))
+#define RSPIN_INIT_WAIT_MS 1000
+#define SMD_FIFO_FULL_RESERVE 4
+#define SMD_FIFO_ADDR_ALIGN_BYTES 3
+
+uint32_t SMSM_NUM_ENTRIES = 8;
+uint32_t SMSM_NUM_HOSTS = 3;
+
+/* Legacy SMSM interrupt notifications */
+#define LEGACY_MODEM_SMSM_MASK (SMSM_RESET | SMSM_INIT | SMSM_SMDINIT)
+
+struct smsm_shared_info {
+	uint32_t *state;
+	uint32_t *intr_mask;
+	uint32_t *intr_mux;
+};
+
+static struct smsm_shared_info smsm_info;
+static struct kfifo smsm_snapshot_fifo;
+static struct wakeup_source smsm_snapshot_ws;
+static int smsm_snapshot_count;
+static DEFINE_SPINLOCK(smsm_snapshot_count_lock);
+
+struct smsm_size_info_type {
+	uint32_t num_hosts;
+	uint32_t num_entries;
+	uint32_t reserved0;
+	uint32_t reserved1;
+};
+
+struct smsm_state_cb_info {
+	struct list_head cb_list;
+	uint32_t mask;
+	void *data;
+	void (*notify)(void *data, uint32_t old_state, uint32_t new_state);
+};
+
+struct smsm_state_info {
+	struct list_head callbacks;
+	uint32_t last_value;
+	uint32_t intr_mask_set;
+	uint32_t intr_mask_clear;
+};
+
+static irqreturn_t smsm_irq_handler(int irq, void *data);
+
+/*
+ * Interrupt configuration consists of static configuration for the supported
+ * processors that is done here along with interrupt configuration that is
+ * added by the separate initialization modules (device tree, platform data, or
+ * hard coded).
+ */
+static struct interrupt_config private_intr_config[NUM_SMD_SUBSYSTEMS] = {
+	[SMD_MODEM] = {
+		.smd.irq_handler = smd_modem_irq_handler,
+		.smsm.irq_handler = smsm_modem_irq_handler,
+	},
+	[SMD_Q6] = {
+		.smd.irq_handler = smd_dsp_irq_handler,
+		.smsm.irq_handler = smsm_dsp_irq_handler,
+	},
+	[SMD_DSPS] = {
+		.smd.irq_handler = smd_dsps_irq_handler,
+		.smsm.irq_handler = smsm_dsps_irq_handler,
+	},
+	[SMD_WCNSS] = {
+		.smd.irq_handler = smd_wcnss_irq_handler,
+		.smsm.irq_handler = smsm_wcnss_irq_handler,
+	},
+	[SMD_MODEM_Q6_FW] = {
+		.smd.irq_handler = smd_modemfw_irq_handler,
+		.smsm.irq_handler = NULL, /* does not support smsm */
+	},
+	[SMD_RPM] = {
+		.smd.irq_handler = smd_rpm_irq_handler,
+		.smsm.irq_handler = NULL, /* does not support smsm */
+	},
+};
+
+struct interrupt_stat interrupt_stats[NUM_SMD_SUBSYSTEMS];
+
+#define SMSM_STATE_ADDR(entry)           (smsm_info.state + entry)
+#define SMSM_INTR_MASK_ADDR(entry, host) (smsm_info.intr_mask + \
+					  entry * SMSM_NUM_HOSTS + host)
+#define SMSM_INTR_MUX_ADDR(entry)        (smsm_info.intr_mux + entry)
+
+int msm_smd_debug_mask = MSM_SMD_POWER_INFO | MSM_SMD_INFO |
+							MSM_SMSM_POWER_INFO;
+module_param_named(debug_mask, msm_smd_debug_mask, int, 0664);
+void *smd_log_ctx;
+void *smsm_log_ctx;
+#define NUM_LOG_PAGES 4
+
+#define IPC_LOG_SMD(level, x...) do { \
+	if (smd_log_ctx) \
+		ipc_log_string(smd_log_ctx, x); \
+	else \
+		printk(level x); \
+	} while (0)
+
+#define IPC_LOG_SMSM(level, x...) do { \
+	if (smsm_log_ctx) \
+		ipc_log_string(smsm_log_ctx, x); \
+	else \
+		printk(level x); \
+	} while (0)
+
+#if defined(CONFIG_MSM_SMD_DEBUG)
+#define SMD_DBG(x...) do {				\
+		if (msm_smd_debug_mask & MSM_SMD_DEBUG) \
+			IPC_LOG_SMD(KERN_DEBUG, x);	\
+	} while (0)
+
+#define SMSM_DBG(x...) do {					\
+		if (msm_smd_debug_mask & MSM_SMSM_DEBUG)	\
+			IPC_LOG_SMSM(KERN_DEBUG, x);		\
+	} while (0)
+
+#define SMD_INFO(x...) do {				\
+		if (msm_smd_debug_mask & MSM_SMD_INFO)	\
+			IPC_LOG_SMD(KERN_INFO, x);	\
+	} while (0)
+
+#define SMSM_INFO(x...) do {				\
+		if (msm_smd_debug_mask & MSM_SMSM_INFO) \
+			IPC_LOG_SMSM(KERN_INFO, x);	\
+	} while (0)
+
+#define SMD_POWER_INFO(x...) do {				\
+		if (msm_smd_debug_mask & MSM_SMD_POWER_INFO)	\
+			IPC_LOG_SMD(KERN_INFO, x);		\
+	} while (0)
+
+#define SMSM_POWER_INFO(x...) do {				\
+		if (msm_smd_debug_mask & MSM_SMSM_POWER_INFO)	\
+			IPC_LOG_SMSM(KERN_INFO, x);		\
+	} while (0)
+#else
+#define SMD_DBG(x...) do { } while (0)
+#define SMSM_DBG(x...) do { } while (0)
+#define SMD_INFO(x...) do { } while (0)
+#define SMSM_INFO(x...) do { } while (0)
+#define SMD_POWER_INFO(x...) do { } while (0)
+#define SMSM_POWER_INFO(x...) do { } while (0)
+#endif
+
+static void smd_fake_irq_handler(unsigned long arg);
+static void smsm_cb_snapshot(uint32_t use_wakeup_source);
+
+static struct workqueue_struct *smsm_cb_wq;
+static void notify_smsm_cb_clients_worker(struct work_struct *work);
+static DECLARE_WORK(smsm_cb_work, notify_smsm_cb_clients_worker);
+static DEFINE_MUTEX(smsm_lock);
+static struct smsm_state_info *smsm_states;
+
+static int smd_stream_write_avail(struct smd_channel *ch);
+static int smd_stream_read_avail(struct smd_channel *ch);
+
+static bool pid_is_on_edge(uint32_t edge_num, unsigned int pid);
+
+static inline void smd_write_intr(unsigned int val, void __iomem *addr)
+{
+	wmb(); /* Make sure memory is visible before dorebell */
+	__raw_writel(val, addr);
+}
+
+/**
+ * smd_memcpy_to_fifo() - copy to SMD channel FIFO
+ * @dest: Destination address
+ * @src: Source address
+ * @num_bytes: Number of bytes to copy
+ *
+ * @return: Address of destination
+ *
+ * This function copies num_bytes from src to dest. This is used as the memcpy
+ * function to copy data to SMD FIFO in case the SMD FIFO is naturally aligned.
+ */
+static void *smd_memcpy_to_fifo(void *dest, const void *src, size_t num_bytes)
+{
+
+	memcpy_toio(dest, src, num_bytes);
+	return dest;
+}
+
+/**
+ * smd_memcpy_from_fifo() - copy from SMD channel FIFO
+ * @dest: Destination address
+ * @src: Source address
+ * @num_bytes: Number of bytes to copy
+ *
+ * @return: Address of destination
+ *
+ * This function copies num_bytes from src to dest. This is used as the memcpy
+ * function to copy data from SMD FIFO in case the SMD FIFO is naturally
+ * aligned.
+ */
+static void *smd_memcpy_from_fifo(void *dest, const void *src, size_t num_bytes)
+{
+	memcpy_fromio(dest, src, num_bytes);
+	return dest;
+}
+
+/**
+ * smd_memcpy32_to_fifo() - Copy to SMD channel FIFO
+ *
+ * @dest: Destination address
+ * @src: Source address
+ * @num_bytes: Number of bytes to copy
+ *
+ * @return: On Success, address of destination
+ *
+ * This function copies num_bytes data from src to dest. This is used as the
+ * memcpy function to copy data to SMD FIFO in case the SMD FIFO is 4 byte
+ * aligned.
+ */
+static void *smd_memcpy32_to_fifo(void *dest, const void *src, size_t num_bytes)
+{
+	uint32_t *dest_local = (uint32_t *)dest;
+	uint32_t *src_local = (uint32_t *)src;
+
+	WARN_ON(num_bytes & SMD_FIFO_ADDR_ALIGN_BYTES);
+	WARN_ON(!dest_local ||
+			((uintptr_t)dest_local & SMD_FIFO_ADDR_ALIGN_BYTES));
+	WARN_ON(!src_local ||
+			((uintptr_t)src_local & SMD_FIFO_ADDR_ALIGN_BYTES));
+	num_bytes /= sizeof(uint32_t);
+
+	while (num_bytes--)
+		__raw_writel_no_log(*src_local++, dest_local++);
+
+	return dest;
+}
+
+/**
+ * smd_memcpy32_from_fifo() - Copy from SMD channel FIFO
+ * @dest: Destination address
+ * @src: Source address
+ * @num_bytes: Number of bytes to copy
+ *
+ * @return: On Success, destination address
+ *
+ * This function copies num_bytes data from SMD FIFO to dest. This is used as
+ * the memcpy function to copy data from SMD FIFO in case the SMD FIFO is 4 byte
+ * aligned.
+ */
+static void *smd_memcpy32_from_fifo(void *dest, const void *src,
+						size_t num_bytes)
+{
+
+	uint32_t *dest_local = (uint32_t *)dest;
+	uint32_t *src_local = (uint32_t *)src;
+
+	WARN_ON(num_bytes & SMD_FIFO_ADDR_ALIGN_BYTES);
+	WARN_ON(!dest_local ||
+			((uintptr_t)dest_local & SMD_FIFO_ADDR_ALIGN_BYTES));
+	WARN_ON(!src_local ||
+			((uintptr_t)src_local & SMD_FIFO_ADDR_ALIGN_BYTES));
+	num_bytes /= sizeof(uint32_t);
+
+	while (num_bytes--)
+		*dest_local++ = __raw_readl_no_log(src_local++);
+
+	return dest;
+}
+
+static inline void log_notify(uint32_t subsystem, smd_channel_t *ch)
+{
+	const char *subsys = smd_edge_to_subsystem(subsystem);
+
+	(void) subsys;
+
+	if (!ch)
+		SMD_POWER_INFO("Apps->%s\n", subsys);
+	else
+		SMD_POWER_INFO(
+			"Apps->%s ch%d '%s': tx%d/rx%d %dr/%dw : %dr/%dw\n",
+			subsys, ch->n, ch->name,
+			ch->fifo_size -
+				(smd_stream_write_avail(ch) + 1),
+			smd_stream_read_avail(ch),
+			ch->half_ch->get_tail(ch->send),
+			ch->half_ch->get_head(ch->send),
+			ch->half_ch->get_tail(ch->recv),
+			ch->half_ch->get_head(ch->recv)
+			);
+}
+
+static inline void notify_modem_smd(smd_channel_t *ch)
+{
+	static const struct interrupt_config_item *intr
+	   = &private_intr_config[SMD_MODEM].smd;
+
+	log_notify(SMD_APPS_MODEM, ch);
+	if (intr->out_base) {
+		++interrupt_stats[SMD_MODEM].smd_out_count;
+		smd_write_intr(intr->out_bit_pos,
+		intr->out_base + intr->out_offset);
+	}
+}
+
+static inline void notify_dsp_smd(smd_channel_t *ch)
+{
+	static const struct interrupt_config_item *intr
+		= &private_intr_config[SMD_Q6].smd;
+
+	log_notify(SMD_APPS_QDSP, ch);
+	if (intr->out_base) {
+		++interrupt_stats[SMD_Q6].smd_out_count;
+		smd_write_intr(intr->out_bit_pos,
+		intr->out_base + intr->out_offset);
+	}
+}
+
+static inline void notify_dsps_smd(smd_channel_t *ch)
+{
+	static const struct interrupt_config_item *intr
+		= &private_intr_config[SMD_DSPS].smd;
+
+	log_notify(SMD_APPS_DSPS, ch);
+	if (intr->out_base) {
+		++interrupt_stats[SMD_DSPS].smd_out_count;
+		smd_write_intr(intr->out_bit_pos,
+		intr->out_base + intr->out_offset);
+	}
+}
+
+static inline void notify_wcnss_smd(struct smd_channel *ch)
+{
+	static const struct interrupt_config_item *intr
+		= &private_intr_config[SMD_WCNSS].smd;
+
+	log_notify(SMD_APPS_WCNSS, ch);
+	if (intr->out_base) {
+		++interrupt_stats[SMD_WCNSS].smd_out_count;
+		smd_write_intr(intr->out_bit_pos,
+		intr->out_base + intr->out_offset);
+	}
+}
+
+static inline void notify_modemfw_smd(smd_channel_t *ch)
+{
+	static const struct interrupt_config_item *intr
+		= &private_intr_config[SMD_MODEM_Q6_FW].smd;
+
+	log_notify(SMD_APPS_Q6FW, ch);
+	if (intr->out_base) {
+		++interrupt_stats[SMD_MODEM_Q6_FW].smd_out_count;
+		smd_write_intr(intr->out_bit_pos,
+		intr->out_base + intr->out_offset);
+	}
+}
+
+static inline void notify_rpm_smd(smd_channel_t *ch)
+{
+	static const struct interrupt_config_item *intr
+		= &private_intr_config[SMD_RPM].smd;
+
+	if (intr->out_base) {
+		log_notify(SMD_APPS_RPM, ch);
+		++interrupt_stats[SMD_RPM].smd_out_count;
+		smd_write_intr(intr->out_bit_pos,
+		intr->out_base + intr->out_offset);
+	}
+}
+
+static inline void notify_modem_smsm(void)
+{
+	static const struct interrupt_config_item *intr
+		= &private_intr_config[SMD_MODEM].smsm;
+
+	SMSM_POWER_INFO("SMSM Apps->%s", "MODEM");
+
+	if (intr->out_base) {
+		++interrupt_stats[SMD_MODEM].smsm_out_count;
+		smd_write_intr(intr->out_bit_pos,
+		intr->out_base + intr->out_offset);
+	}
+}
+
+static inline void notify_dsp_smsm(void)
+{
+	static const struct interrupt_config_item *intr
+		= &private_intr_config[SMD_Q6].smsm;
+
+	SMSM_POWER_INFO("SMSM Apps->%s", "ADSP");
+
+	if (intr->out_base) {
+		++interrupt_stats[SMD_Q6].smsm_out_count;
+		smd_write_intr(intr->out_bit_pos,
+		intr->out_base + intr->out_offset);
+	}
+}
+
+static inline void notify_dsps_smsm(void)
+{
+	static const struct interrupt_config_item *intr
+		= &private_intr_config[SMD_DSPS].smsm;
+
+	SMSM_POWER_INFO("SMSM Apps->%s", "DSPS");
+
+	if (intr->out_base) {
+		++interrupt_stats[SMD_DSPS].smsm_out_count;
+		smd_write_intr(intr->out_bit_pos,
+		intr->out_base + intr->out_offset);
+	}
+}
+
+static inline void notify_wcnss_smsm(void)
+{
+	static const struct interrupt_config_item *intr
+		= &private_intr_config[SMD_WCNSS].smsm;
+
+	SMSM_POWER_INFO("SMSM Apps->%s", "WCNSS");
+
+	if (intr->out_base) {
+		++interrupt_stats[SMD_WCNSS].smsm_out_count;
+		smd_write_intr(intr->out_bit_pos,
+		intr->out_base + intr->out_offset);
+	}
+}
+
+static void notify_other_smsm(uint32_t smsm_entry, uint32_t notify_mask)
+{
+	if (smsm_info.intr_mask &&
+	    (__raw_readl(SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_MODEM))
+				& notify_mask))
+		notify_modem_smsm();
+
+	if (smsm_info.intr_mask &&
+	    (__raw_readl(SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_Q6))
+				& notify_mask))
+		notify_dsp_smsm();
+
+	if (smsm_info.intr_mask &&
+	    (__raw_readl(SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_WCNSS))
+				& notify_mask)) {
+		notify_wcnss_smsm();
+	}
+
+	if (smsm_info.intr_mask &&
+	    (__raw_readl(SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_DSPS))
+				& notify_mask)) {
+		notify_dsps_smsm();
+	}
+
+	if (smsm_info.intr_mask &&
+	    (__raw_readl(SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_APPS))
+				& notify_mask)) {
+		smsm_cb_snapshot(1);
+	}
+}
+
+static int smsm_pm_notifier(struct notifier_block *nb,
+				unsigned long event, void *unused)
+{
+	switch (event) {
+	case PM_SUSPEND_PREPARE:
+		smsm_change_state(SMSM_APPS_STATE, SMSM_PROC_AWAKE, 0);
+		break;
+
+	case PM_POST_SUSPEND:
+		smsm_change_state(SMSM_APPS_STATE, 0, SMSM_PROC_AWAKE);
+		break;
+	}
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block smsm_pm_nb = {
+	.notifier_call = smsm_pm_notifier,
+	.priority = 0,
+};
+
+/* the spinlock is used to synchronize between the
+ * irq handler and code that mutates the channel
+ * list or fiddles with channel state
+ */
+static DEFINE_SPINLOCK(smd_lock);
+DEFINE_SPINLOCK(smem_lock);
+
+/* the mutex is used during open() and close()
+ * operations to avoid races while creating or
+ * destroying smd_channel structures
+ */
+static DEFINE_MUTEX(smd_creation_mutex);
+
+struct smd_shared {
+	struct smd_half_channel ch0;
+	struct smd_half_channel ch1;
+};
+
+struct smd_shared_word_access {
+	struct smd_half_channel_word_access ch0;
+	struct smd_half_channel_word_access ch1;
+};
+
+/**
+ * Maps edge type to local and remote processor ID's.
+ */
+static struct edge_to_pid edge_to_pids[] = {
+	[SMD_APPS_MODEM] = {SMD_APPS, SMD_MODEM, "modem"},
+	[SMD_APPS_QDSP] = {SMD_APPS, SMD_Q6, "adsp"},
+	[SMD_MODEM_QDSP] = {SMD_MODEM, SMD_Q6},
+	[SMD_APPS_DSPS] = {SMD_APPS, SMD_DSPS, "dsps"},
+	[SMD_MODEM_DSPS] = {SMD_MODEM, SMD_DSPS},
+	[SMD_QDSP_DSPS] = {SMD_Q6, SMD_DSPS},
+	[SMD_APPS_WCNSS] = {SMD_APPS, SMD_WCNSS, "wcnss"},
+	[SMD_MODEM_WCNSS] = {SMD_MODEM, SMD_WCNSS},
+	[SMD_QDSP_WCNSS] = {SMD_Q6, SMD_WCNSS},
+	[SMD_DSPS_WCNSS] = {SMD_DSPS, SMD_WCNSS},
+	[SMD_APPS_Q6FW] = {SMD_APPS, SMD_MODEM_Q6_FW},
+	[SMD_MODEM_Q6FW] = {SMD_MODEM, SMD_MODEM_Q6_FW},
+	[SMD_QDSP_Q6FW] = {SMD_Q6, SMD_MODEM_Q6_FW},
+	[SMD_DSPS_Q6FW] = {SMD_DSPS, SMD_MODEM_Q6_FW},
+	[SMD_WCNSS_Q6FW] = {SMD_WCNSS, SMD_MODEM_Q6_FW},
+	[SMD_APPS_RPM] = {SMD_APPS, SMD_RPM},
+	[SMD_MODEM_RPM] = {SMD_MODEM, SMD_RPM},
+	[SMD_QDSP_RPM] = {SMD_Q6, SMD_RPM},
+	[SMD_WCNSS_RPM] = {SMD_WCNSS, SMD_RPM},
+	[SMD_TZ_RPM] = {SMD_TZ, SMD_RPM},
+};
+
+struct restart_notifier_block {
+	unsigned int processor;
+	char *name;
+	struct notifier_block nb;
+};
+
+static struct platform_device loopback_tty_pdev = {.name = "LOOPBACK_TTY"};
+
+static LIST_HEAD(smd_ch_closed_list);
+static LIST_HEAD(smd_ch_closing_list);
+static LIST_HEAD(smd_ch_to_close_list);
+
+struct remote_proc_info {
+	unsigned int remote_pid;
+	unsigned int free_space;
+	struct work_struct probe_work;
+	struct list_head ch_list;
+	/* 2 total supported tables of channels */
+	unsigned char ch_allocated[SMEM_NUM_SMD_STREAM_CHANNELS * 2];
+	bool skip_pil;
+};
+
+static struct remote_proc_info remote_info[NUM_SMD_SUBSYSTEMS];
+
+static void finalize_channel_close_fn(struct work_struct *work);
+static DECLARE_WORK(finalize_channel_close_work, finalize_channel_close_fn);
+static struct workqueue_struct *channel_close_wq;
+
+#define PRI_ALLOC_TBL 1
+#define SEC_ALLOC_TBL 2
+static int smd_alloc_channel(struct smd_alloc_elm *alloc_elm, int table_id,
+				struct remote_proc_info *r_info);
+
+static bool smd_edge_inited(int edge)
+{
+	return edge_to_pids[edge].initialized;
+}
+
+/* on smp systems, the probe might get called from multiple cores,
+ * hence use a lock
+ */
+static DEFINE_MUTEX(smd_probe_lock);
+
+/**
+ * scan_alloc_table - Scans a specified SMD channel allocation table in SMEM for
+ *			newly created channels that need to be made locally
+ *			visable
+ *
+ * @shared: pointer to the table array in SMEM
+ * @smd_ch_allocated: pointer to an array indicating already allocated channels
+ * @table_id: identifier for this channel allocation table
+ * @num_entries: number of entries in this allocation table
+ * @r_info: pointer to the info structure of the remote proc we care about
+ *
+ * The smd_probe_lock must be locked by the calling function.  Shared and
+ * smd_ch_allocated are assumed to be valid pointers.
+ */
+static void scan_alloc_table(struct smd_alloc_elm *shared,
+				char *smd_ch_allocated,
+				int table_id,
+				unsigned int num_entries,
+				struct remote_proc_info *r_info)
+{
+	unsigned int n;
+	uint32_t type;
+
+	for (n = 0; n < num_entries; n++) {
+		if (smd_ch_allocated[n])
+			continue;
+
+		/*
+		 * channel should be allocated only if APPS processor is
+		 * involved
+		 */
+		type = SMD_CHANNEL_TYPE(shared[n].type);
+		if (!pid_is_on_edge(type, SMD_APPS) ||
+				!pid_is_on_edge(type, r_info->remote_pid))
+			continue;
+		if (!shared[n].ref_count)
+			continue;
+		if (!shared[n].name[0])
+			continue;
+
+		if (!smd_edge_inited(type)) {
+			SMD_INFO(
+				"Probe skipping proc %d, tbl %d, ch %d, edge not inited\n",
+				r_info->remote_pid, table_id, n);
+			continue;
+		}
+
+		if (!smd_alloc_channel(&shared[n], table_id, r_info))
+			smd_ch_allocated[n] = 1;
+		else
+			SMD_INFO(
+				"Probe skipping proc %d, tbl %d, ch %d, not allocated\n",
+				r_info->remote_pid, table_id, n);
+	}
+}
+
+static void smd_channel_probe_now(struct remote_proc_info *r_info)
+{
+	struct smd_alloc_elm *shared;
+	unsigned int tbl_size;
+
+	shared = smem_get_entry(ID_CH_ALLOC_TBL, &tbl_size,
+							r_info->remote_pid, 0);
+
+	if (!shared) {
+		pr_err("%s: allocation table not initialized\n", __func__);
+		return;
+	}
+
+	mutex_lock(&smd_probe_lock);
+
+	scan_alloc_table(shared, r_info->ch_allocated, PRI_ALLOC_TBL,
+						tbl_size / sizeof(*shared),
+						r_info);
+
+	shared = smem_get_entry(SMEM_CHANNEL_ALLOC_TBL_2, &tbl_size,
+							r_info->remote_pid, 0);
+	if (shared)
+		scan_alloc_table(shared,
+			&(r_info->ch_allocated[SMEM_NUM_SMD_STREAM_CHANNELS]),
+			SEC_ALLOC_TBL,
+			tbl_size / sizeof(*shared),
+			r_info);
+
+	mutex_unlock(&smd_probe_lock);
+}
+
+/**
+ * smd_channel_probe_worker() - Scan for newly created SMD channels and init
+ *				local structures so the channels are visable to
+ *				local clients
+ *
+ * @work: work_struct corresponding to an instance of this function running on
+ *		a workqueue.
+ */
+static void smd_channel_probe_worker(struct work_struct *work)
+{
+	struct remote_proc_info *r_info;
+
+	r_info = container_of(work, struct remote_proc_info, probe_work);
+
+	smd_channel_probe_now(r_info);
+}
+
+/**
+ * get_remote_ch() - gathers remote channel info
+ *
+ * @shared2:   Pointer to v2 shared channel structure
+ * @type:      Edge type
+ * @pid:       Processor ID of processor on edge
+ * @remote_ch:  Channel that belongs to processor @pid
+ * @is_word_access_ch: Bool, is this a word aligned access channel
+ *
+ * @returns:		0 on success, error code on failure
+ */
+static int get_remote_ch(void *shared2,
+		uint32_t type, uint32_t pid,
+		void **remote_ch,
+		int is_word_access_ch
+		)
+{
+	if (!remote_ch || !shared2 || !pid_is_on_edge(type, pid) ||
+				!pid_is_on_edge(type, SMD_APPS))
+		return -EINVAL;
+
+	if (is_word_access_ch)
+		*remote_ch =
+			&((struct smd_shared_word_access *)(shared2))->ch1;
+	else
+		*remote_ch = &((struct smd_shared *)(shared2))->ch1;
+
+	return 0;
+}
+
+/**
+ * smd_remote_ss_to_edge() - return edge type from remote ss type
+ * @name:	remote subsystem name
+ *
+ * Returns the edge type connected between the local subsystem(APPS)
+ * and remote subsystem @name.
+ */
+int smd_remote_ss_to_edge(const char *name)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(edge_to_pids); ++i) {
+		if (edge_to_pids[i].subsys_name[0] != 0x0) {
+			if (!strcmp(edge_to_pids[i].subsys_name, name))
+				return i;
+		}
+	}
+
+	return -EINVAL;
+}
+EXPORT_SYMBOL(smd_remote_ss_to_edge);
+
+/**
+ * smd_edge_to_pil_str - Returns the PIL string used to load the remote side of
+ *			 the indicated edge.
+ *
+ * @type -	Edge definition
+ * @returns -	The PIL string to load the remove side of @type or NULL if the
+ *		PIL string does not exist.
+ */
+const char *smd_edge_to_pil_str(uint32_t type)
+{
+	const char *pil_str = NULL;
+
+	if (type < ARRAY_SIZE(edge_to_pids)) {
+		if (!edge_to_pids[type].initialized)
+			return ERR_PTR(-EPROBE_DEFER);
+		if (!remote_info[smd_edge_to_remote_pid(type)].skip_pil) {
+			pil_str = edge_to_pids[type].subsys_name;
+			if (pil_str[0] == 0x0)
+				pil_str = NULL;
+		}
+	}
+	return pil_str;
+}
+EXPORT_SYMBOL(smd_edge_to_pil_str);
+
+/*
+ * Returns a pointer to the subsystem name or NULL if no
+ * subsystem name is available.
+ *
+ * @type - Edge definition
+ */
+const char *smd_edge_to_subsystem(uint32_t type)
+{
+	const char *subsys = NULL;
+
+	if (type < ARRAY_SIZE(edge_to_pids)) {
+		subsys = edge_to_pids[type].subsys_name;
+		if (subsys[0] == 0x0)
+			subsys = NULL;
+		if (!edge_to_pids[type].initialized)
+			subsys = ERR_PTR(-EPROBE_DEFER);
+	}
+	return subsys;
+}
+EXPORT_SYMBOL(smd_edge_to_subsystem);
+
+/*
+ * Returns a pointer to the subsystem name given the
+ * remote processor ID.
+ * subsystem is not necessarily PIL-loadable
+ *
+ * @pid     Remote processor ID
+ * @returns Pointer to subsystem name or NULL if not found
+ */
+const char *smd_pid_to_subsystem(uint32_t pid)
+{
+	const char *subsys = NULL;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(edge_to_pids); ++i) {
+		if (pid == edge_to_pids[i].remote_pid) {
+			if (!edge_to_pids[i].initialized) {
+				subsys = ERR_PTR(-EPROBE_DEFER);
+				break;
+			}
+			if (edge_to_pids[i].subsys_name[0] != 0x0) {
+				subsys = edge_to_pids[i].subsys_name;
+				break;
+			} else if (pid == SMD_RPM) {
+				subsys = "rpm";
+				break;
+			}
+		}
+	}
+
+	return subsys;
+}
+EXPORT_SYMBOL(smd_pid_to_subsystem);
+
+static void smd_reset_edge(void *void_ch, unsigned int new_state,
+				int is_word_access_ch)
+{
+	if (is_word_access_ch) {
+		struct smd_half_channel_word_access *ch =
+			(struct smd_half_channel_word_access *)(void_ch);
+		if (ch->state != SMD_SS_CLOSED) {
+			ch->state = new_state;
+			ch->fDSR = 0;
+			ch->fCTS = 0;
+			ch->fCD = 0;
+			ch->fSTATE = 1;
+		}
+	} else {
+		struct smd_half_channel *ch =
+			(struct smd_half_channel *)(void_ch);
+		if (ch->state != SMD_SS_CLOSED) {
+			ch->state = new_state;
+			ch->fDSR = 0;
+			ch->fCTS = 0;
+			ch->fCD = 0;
+			ch->fSTATE = 1;
+		}
+	}
+}
+
+/**
+ * smd_channel_reset_state() - find channels in an allocation table and set them
+ *				to the specified state
+ *
+ * @shared:	Pointer to the allocation table to scan
+ * @table_id:	ID of the table
+ * @new_state:	New state that channels should be set to
+ * @pid:	Processor ID of the remote processor for the channels
+ * @num_entries: Number of entries in the table
+ *
+ * Scan the indicated table for channels between Apps and @pid.  If a valid
+ * channel is found, set the remote side of the channel to @new_state.
+ */
+static void smd_channel_reset_state(struct smd_alloc_elm *shared, int table_id,
+		unsigned int new_state, unsigned int pid,
+		unsigned int num_entries)
+{
+	unsigned int n;
+	void *shared2;
+	uint32_t type;
+	void *remote_ch;
+	int is_word_access;
+	unsigned int base_id;
+
+	switch (table_id) {
+	case PRI_ALLOC_TBL:
+		base_id = SMEM_SMD_BASE_ID;
+		break;
+	case SEC_ALLOC_TBL:
+		base_id = SMEM_SMD_BASE_ID_2;
+		break;
+	default:
+		SMD_INFO("%s: invalid table_id:%d\n", __func__, table_id);
+		return;
+	}
+
+	for (n = 0; n < num_entries; n++) {
+		if (!shared[n].ref_count)
+			continue;
+		if (!shared[n].name[0])
+			continue;
+
+		type = SMD_CHANNEL_TYPE(shared[n].type);
+		is_word_access = is_word_access_ch(type);
+		if (is_word_access)
+			shared2 = smem_find(base_id + n,
+				sizeof(struct smd_shared_word_access), pid,
+				0);
+		else
+			shared2 = smem_find(base_id + n,
+				sizeof(struct smd_shared), pid, 0);
+		if (!shared2)
+			continue;
+
+		if (!get_remote_ch(shared2, type, pid,
+					&remote_ch, is_word_access))
+			smd_reset_edge(remote_ch, new_state, is_word_access);
+	}
+}
+
+/**
+ * pid_is_on_edge() - checks to see if the processor with id pid is on the
+ * edge specified by edge_num
+ *
+ * @edge_num:		the number of the edge which is being tested
+ * @pid:		the id of the processor being tested
+ *
+ * @returns:		true if on edge, false otherwise
+ */
+static bool pid_is_on_edge(uint32_t edge_num, unsigned int pid)
+{
+	struct edge_to_pid edge;
+
+	if (edge_num >= ARRAY_SIZE(edge_to_pids))
+		return 0;
+
+	edge = edge_to_pids[edge_num];
+	return (edge.local_pid == pid || edge.remote_pid == pid);
+}
+
+void smd_channel_reset(uint32_t restart_pid)
+{
+	struct smd_alloc_elm *shared_pri;
+	struct smd_alloc_elm *shared_sec;
+	unsigned long flags;
+	unsigned int pri_size;
+	unsigned int sec_size;
+
+	SMD_POWER_INFO("%s: starting reset\n", __func__);
+
+	shared_pri = smem_get_entry(ID_CH_ALLOC_TBL, &pri_size,	restart_pid, 0);
+	if (!shared_pri) {
+		pr_err("%s: allocation table not initialized\n", __func__);
+		return;
+	}
+	shared_sec = smem_get_entry(SMEM_CHANNEL_ALLOC_TBL_2, &sec_size,
+								restart_pid, 0);
+
+	/* reset SMSM entry */
+	if (smsm_info.state) {
+		writel_relaxed(0, SMSM_STATE_ADDR(restart_pid));
+
+		/* restart SMSM init handshake */
+		if (restart_pid == SMSM_MODEM) {
+			smsm_change_state(SMSM_APPS_STATE,
+				SMSM_INIT | SMSM_SMD_LOOPBACK | SMSM_RESET,
+				0);
+		}
+
+		/* notify SMSM processors */
+		smsm_irq_handler(0, 0);
+		notify_modem_smsm();
+		notify_dsp_smsm();
+		notify_dsps_smsm();
+		notify_wcnss_smsm();
+	}
+
+	/* change all remote states to CLOSING */
+	mutex_lock(&smd_probe_lock);
+	spin_lock_irqsave(&smd_lock, flags);
+	smd_channel_reset_state(shared_pri, PRI_ALLOC_TBL, SMD_SS_CLOSING,
+				restart_pid, pri_size / sizeof(*shared_pri));
+	if (shared_sec)
+		smd_channel_reset_state(shared_sec, SEC_ALLOC_TBL,
+						SMD_SS_CLOSING, restart_pid,
+						sec_size / sizeof(*shared_sec));
+	spin_unlock_irqrestore(&smd_lock, flags);
+	mutex_unlock(&smd_probe_lock);
+
+	mb(); /* Make sure memory is visible before proceeding */
+	smd_fake_irq_handler(0);
+
+	/* change all remote states to CLOSED */
+	mutex_lock(&smd_probe_lock);
+	spin_lock_irqsave(&smd_lock, flags);
+	smd_channel_reset_state(shared_pri, PRI_ALLOC_TBL, SMD_SS_CLOSED,
+				restart_pid, pri_size / sizeof(*shared_pri));
+	if (shared_sec)
+		smd_channel_reset_state(shared_sec, SEC_ALLOC_TBL,
+						SMD_SS_CLOSED, restart_pid,
+						sec_size / sizeof(*shared_sec));
+	spin_unlock_irqrestore(&smd_lock, flags);
+	mutex_unlock(&smd_probe_lock);
+
+	mb(); /* Make sure memory is visible before proceeding */
+	smd_fake_irq_handler(0);
+
+	SMD_POWER_INFO("%s: finished reset\n", __func__);
+}
+
+/* how many bytes are available for reading */
+static int smd_stream_read_avail(struct smd_channel *ch)
+{
+	unsigned int head = ch->half_ch->get_head(ch->recv);
+	unsigned int tail = ch->half_ch->get_tail(ch->recv);
+	unsigned int fifo_size = ch->fifo_size;
+	unsigned int bytes_avail = head - tail;
+
+	if (head < tail)
+		bytes_avail += fifo_size;
+
+	WARN_ON(bytes_avail >= fifo_size);
+	return bytes_avail;
+}
+
+/* how many bytes we are free to write */
+static int smd_stream_write_avail(struct smd_channel *ch)
+{
+	unsigned int head = ch->half_ch->get_head(ch->send);
+	unsigned int tail = ch->half_ch->get_tail(ch->send);
+	unsigned int fifo_size = ch->fifo_size;
+	unsigned int bytes_avail = tail - head;
+
+	if (tail <= head)
+		bytes_avail += fifo_size;
+	if (bytes_avail < SMD_FIFO_FULL_RESERVE)
+		bytes_avail = 0;
+	else
+		bytes_avail -= SMD_FIFO_FULL_RESERVE;
+
+	WARN_ON(bytes_avail >= fifo_size);
+	return bytes_avail;
+}
+
+static int smd_packet_read_avail(struct smd_channel *ch)
+{
+	if (ch->current_packet) {
+		int n = smd_stream_read_avail(ch);
+
+		if (n > ch->current_packet)
+			n = ch->current_packet;
+		return n;
+	} else {
+		return 0;
+	}
+}
+
+static int smd_packet_write_avail(struct smd_channel *ch)
+{
+	int n = smd_stream_write_avail(ch);
+
+	return n > SMD_HEADER_SIZE ? n - SMD_HEADER_SIZE : 0;
+}
+
+static int ch_is_open(struct smd_channel *ch)
+{
+	return (ch->half_ch->get_state(ch->recv) == SMD_SS_OPENED ||
+		ch->half_ch->get_state(ch->recv) == SMD_SS_FLUSHING)
+		&& (ch->half_ch->get_state(ch->send) == SMD_SS_OPENED);
+}
+
+/* provide a pointer and length to readable data in the fifo */
+static unsigned int ch_read_buffer(struct smd_channel *ch, void **ptr)
+{
+	unsigned int head = ch->half_ch->get_head(ch->recv);
+	unsigned int tail = ch->half_ch->get_tail(ch->recv);
+	unsigned int fifo_size = ch->fifo_size;
+
+	WARN_ON(fifo_size >= SZ_1M);
+	WARN_ON(head >= fifo_size);
+	WARN_ON(tail >= fifo_size);
+	WARN_ON(OVERFLOW_ADD_UNSIGNED(uintptr_t, (uintptr_t)ch->recv_data,
+								tail));
+	*ptr = (void *) (ch->recv_data + tail);
+	if (tail <= head)
+		return head - tail;
+	else
+		return fifo_size - tail;
+}
+
+static int read_intr_blocked(struct smd_channel *ch)
+{
+	return ch->half_ch->get_fBLOCKREADINTR(ch->recv);
+}
+
+/* advance the fifo read pointer after data from ch_read_buffer is consumed */
+static void ch_read_done(struct smd_channel *ch, unsigned int count)
+{
+	unsigned int tail = ch->half_ch->get_tail(ch->recv);
+	unsigned int fifo_size = ch->fifo_size;
+
+	WARN_ON(count > smd_stream_read_avail(ch));
+
+	tail += count;
+	if (tail >= fifo_size)
+		tail -= fifo_size;
+	ch->half_ch->set_tail(ch->recv, tail);
+	wmb(); /* Make sure memory is visible before setting signal */
+	ch->half_ch->set_fTAIL(ch->send,  1);
+}
+
+/* basic read interface to ch_read_{buffer,done} used
+ * by smd_*_read() and update_packet_state()
+ * will read-and-discard if the _data pointer is null
+ */
+static int ch_read(struct smd_channel *ch, void *_data, int len)
+{
+	void *ptr;
+	unsigned int n;
+	unsigned char *data = _data;
+	int orig_len = len;
+
+	while (len > 0) {
+		n = ch_read_buffer(ch, &ptr);
+		if (n == 0)
+			break;
+
+		if (n > len)
+			n = len;
+		if (_data)
+			ch->read_from_fifo(data, ptr, n);
+
+		data += n;
+		len -= n;
+		ch_read_done(ch, n);
+	}
+
+	return orig_len - len;
+}
+
+static void update_stream_state(struct smd_channel *ch)
+{
+	/* streams have no special state requiring updating */
+}
+
+static void update_packet_state(struct smd_channel *ch)
+{
+	unsigned int hdr[5];
+	int r;
+	const char *peripheral = NULL;
+
+	/* can't do anything if we're in the middle of a packet */
+	while (ch->current_packet == 0) {
+		/* discard 0 length packets if any */
+
+		/* don't bother unless we can get the full header */
+		if (smd_stream_read_avail(ch) < SMD_HEADER_SIZE)
+			return;
+
+		r = ch_read(ch, hdr, SMD_HEADER_SIZE);
+		WARN_ON(r != SMD_HEADER_SIZE);
+
+		ch->current_packet = hdr[0];
+		if (ch->current_packet > (uint32_t)INT_MAX) {
+			pr_err("%s: Invalid packet size of %d bytes detected. Edge: %d, Channel : %s, RPTR: %d, WPTR: %d",
+				__func__, ch->current_packet, ch->type,
+				ch->name, ch->half_ch->get_tail(ch->recv),
+				ch->half_ch->get_head(ch->recv));
+			peripheral = smd_edge_to_pil_str(ch->type);
+			if (peripheral) {
+				if (subsystem_restart(peripheral) < 0)
+					WARN_ON(1);
+			} else {
+				WARN_ON(1);
+			}
+		}
+	}
+}
+
+/**
+ * ch_write_buffer() - Provide a pointer and length for the next segment of
+ * free space in the FIFO.
+ * @ch: channel
+ * @ptr: Address to pointer for the next segment write
+ * @returns: Maximum size that can be written until the FIFO is either full
+ *           or the end of the FIFO has been reached.
+ *
+ * The returned pointer and length are passed to memcpy, so the next segment is
+ * defined as either the space available between the read index (tail) and the
+ * write index (head) or the space available to the end of the FIFO.
+ */
+static unsigned int ch_write_buffer(struct smd_channel *ch, void **ptr)
+{
+	unsigned int head = ch->half_ch->get_head(ch->send);
+	unsigned int tail = ch->half_ch->get_tail(ch->send);
+	unsigned int fifo_size = ch->fifo_size;
+
+	WARN_ON(fifo_size >= SZ_1M);
+	WARN_ON(head >= fifo_size);
+	WARN_ON(tail >= fifo_size);
+	WARN_ON(OVERFLOW_ADD_UNSIGNED(uintptr_t, (uintptr_t)ch->send_data,
+								head));
+
+	*ptr = (void *) (ch->send_data + head);
+	if (head < tail)
+		return tail - head - SMD_FIFO_FULL_RESERVE;
+
+	if (tail < SMD_FIFO_FULL_RESERVE)
+		return fifo_size + tail - head
+				- SMD_FIFO_FULL_RESERVE;
+
+	return fifo_size - head;
+
+}
+
+/* advace the fifo write pointer after freespace
+ * from ch_write_buffer is filled
+ */
+static void ch_write_done(struct smd_channel *ch, unsigned int count)
+{
+	unsigned int head = ch->half_ch->get_head(ch->send);
+	unsigned int fifo_size = ch->fifo_size;
+
+	WARN_ON(count > smd_stream_write_avail(ch));
+	head += count;
+	if (head >= fifo_size)
+		head -= fifo_size;
+	ch->half_ch->set_head(ch->send, head);
+	wmb();  /* Make sure memory is visible before setting signal */
+	ch->half_ch->set_fHEAD(ch->send, 1);
+}
+
+static void ch_set_state(struct smd_channel *ch, unsigned int n)
+{
+	if (n == SMD_SS_OPENED) {
+		ch->half_ch->set_fDSR(ch->send, 1);
+		ch->half_ch->set_fCTS(ch->send, 1);
+		ch->half_ch->set_fCD(ch->send, 1);
+	} else {
+		ch->half_ch->set_fDSR(ch->send, 0);
+		ch->half_ch->set_fCTS(ch->send, 0);
+		ch->half_ch->set_fCD(ch->send, 0);
+	}
+	ch->half_ch->set_state(ch->send, n);
+	ch->half_ch->set_fSTATE(ch->send, 1);
+	ch->notify_other_cpu(ch);
+}
+
+/**
+ * do_smd_probe() - Look for newly created SMD channels a specific processor
+ *
+ * @remote_pid: remote processor id of the proc that may have created channels
+ */
+static void do_smd_probe(unsigned int remote_pid)
+{
+	unsigned int free_space;
+
+	free_space = smem_get_free_space(remote_pid);
+	if (free_space != remote_info[remote_pid].free_space) {
+		remote_info[remote_pid].free_space = free_space;
+		schedule_work(&remote_info[remote_pid].probe_work);
+	}
+}
+
+static void remote_processed_close(struct smd_channel *ch)
+{
+	/* The remote side has observed our close, we can allow a reopen */
+	list_move(&ch->ch_list, &smd_ch_to_close_list);
+	queue_work(channel_close_wq, &finalize_channel_close_work);
+}
+
+static void smd_state_change(struct smd_channel *ch,
+			     unsigned int last, unsigned int next)
+{
+	ch->last_state = next;
+
+	SMD_INFO("SMD: ch %d %d -> %d\n", ch->n, last, next);
+
+	switch (next) {
+	case SMD_SS_OPENING:
+		if (last == SMD_SS_OPENED &&
+		    ch->half_ch->get_state(ch->send) == SMD_SS_CLOSED) {
+			/* We missed the CLOSING and CLOSED states */
+			remote_processed_close(ch);
+		} else if (ch->half_ch->get_state(ch->send) == SMD_SS_CLOSING ||
+		    ch->half_ch->get_state(ch->send) == SMD_SS_CLOSED) {
+			ch->half_ch->set_tail(ch->recv, 0);
+			ch->half_ch->set_head(ch->send, 0);
+			ch->half_ch->set_fBLOCKREADINTR(ch->send, 0);
+			ch->current_packet = 0;
+			ch_set_state(ch, SMD_SS_OPENING);
+		}
+		break;
+	case SMD_SS_OPENED:
+		if (ch->half_ch->get_state(ch->send) == SMD_SS_OPENING) {
+			ch_set_state(ch, SMD_SS_OPENED);
+			ch->notify(ch->priv, SMD_EVENT_OPEN);
+		}
+		break;
+	case SMD_SS_FLUSHING:
+	case SMD_SS_RESET:
+		/* we should force them to close? */
+		break;
+	case SMD_SS_CLOSED:
+		if (ch->half_ch->get_state(ch->send) == SMD_SS_OPENED) {
+			ch_set_state(ch, SMD_SS_CLOSING);
+			ch->pending_pkt_sz = 0;
+			ch->notify(ch->priv, SMD_EVENT_CLOSE);
+		}
+		/* We missed the CLOSING state */
+		if (ch->half_ch->get_state(ch->send) == SMD_SS_CLOSED)
+			remote_processed_close(ch);
+		break;
+	case SMD_SS_CLOSING:
+		if (ch->half_ch->get_state(ch->send) == SMD_SS_CLOSED)
+			remote_processed_close(ch);
+		break;
+	}
+}
+
+static void handle_smd_irq_closing_list(void)
+{
+	unsigned long flags;
+	struct smd_channel *ch;
+	struct smd_channel *index;
+	unsigned int tmp;
+
+	spin_lock_irqsave(&smd_lock, flags);
+	list_for_each_entry_safe(ch, index, &smd_ch_closing_list, ch_list) {
+		if (ch->half_ch->get_fSTATE(ch->recv))
+			ch->half_ch->set_fSTATE(ch->recv, 0);
+		tmp = ch->half_ch->get_state(ch->recv);
+		if (tmp != ch->last_state)
+			smd_state_change(ch, ch->last_state, tmp);
+	}
+	spin_unlock_irqrestore(&smd_lock, flags);
+}
+
+static void handle_smd_irq(struct remote_proc_info *r_info,
+		void (*notify)(smd_channel_t *ch))
+{
+	unsigned long flags;
+	struct smd_channel *ch;
+	unsigned int ch_flags;
+	unsigned int tmp;
+	unsigned char state_change;
+	struct list_head *list;
+
+	list = &r_info->ch_list;
+
+	spin_lock_irqsave(&smd_lock, flags);
+	list_for_each_entry(ch, list, ch_list) {
+		state_change = 0;
+		ch_flags = 0;
+		if (ch_is_open(ch)) {
+			if (ch->half_ch->get_fHEAD(ch->recv)) {
+				ch->half_ch->set_fHEAD(ch->recv, 0);
+				ch_flags |= 1;
+			}
+			if (ch->half_ch->get_fTAIL(ch->recv)) {
+				ch->half_ch->set_fTAIL(ch->recv, 0);
+				ch_flags |= 2;
+			}
+			if (ch->half_ch->get_fSTATE(ch->recv)) {
+				ch->half_ch->set_fSTATE(ch->recv, 0);
+				ch_flags |= 4;
+			}
+		}
+		tmp = ch->half_ch->get_state(ch->recv);
+		if (tmp != ch->last_state) {
+			SMD_POWER_INFO("SMD ch%d '%s' State change %d->%d\n",
+					ch->n, ch->name, ch->last_state, tmp);
+			smd_state_change(ch, ch->last_state, tmp);
+			state_change = 1;
+		}
+		if (ch_flags & 0x3) {
+			ch->update_state(ch);
+			SMD_POWER_INFO(
+				"SMD ch%d '%s' Data event 0x%x tx%d/rx%d %dr/%dw : %dr/%dw\n",
+				ch->n, ch->name,
+				ch_flags,
+				ch->fifo_size -
+					(smd_stream_write_avail(ch) + 1),
+				smd_stream_read_avail(ch),
+				ch->half_ch->get_tail(ch->send),
+				ch->half_ch->get_head(ch->send),
+				ch->half_ch->get_tail(ch->recv),
+				ch->half_ch->get_head(ch->recv)
+				);
+			ch->notify(ch->priv, SMD_EVENT_DATA);
+		}
+		if (ch_flags & 0x4 && !state_change) {
+			SMD_POWER_INFO("SMD ch%d '%s' State update\n",
+					ch->n, ch->name);
+			ch->notify(ch->priv, SMD_EVENT_STATUS);
+		}
+	}
+	spin_unlock_irqrestore(&smd_lock, flags);
+	do_smd_probe(r_info->remote_pid);
+}
+
+static inline void log_irq(uint32_t subsystem)
+{
+	const char *subsys = smd_edge_to_subsystem(subsystem);
+
+	(void) subsys;
+
+	SMD_POWER_INFO("SMD Int %s->Apps\n", subsys);
+}
+
+irqreturn_t smd_modem_irq_handler(int irq, void *data)
+{
+	if (unlikely(!edge_to_pids[SMD_APPS_MODEM].initialized))
+		return IRQ_HANDLED;
+	log_irq(SMD_APPS_MODEM);
+	++interrupt_stats[SMD_MODEM].smd_in_count;
+	handle_smd_irq(&remote_info[SMD_MODEM], notify_modem_smd);
+	handle_smd_irq_closing_list();
+	return IRQ_HANDLED;
+}
+
+irqreturn_t smd_dsp_irq_handler(int irq, void *data)
+{
+	if (unlikely(!edge_to_pids[SMD_APPS_QDSP].initialized))
+		return IRQ_HANDLED;
+	log_irq(SMD_APPS_QDSP);
+	++interrupt_stats[SMD_Q6].smd_in_count;
+	handle_smd_irq(&remote_info[SMD_Q6], notify_dsp_smd);
+	handle_smd_irq_closing_list();
+	return IRQ_HANDLED;
+}
+
+irqreturn_t smd_dsps_irq_handler(int irq, void *data)
+{
+	if (unlikely(!edge_to_pids[SMD_APPS_DSPS].initialized))
+		return IRQ_HANDLED;
+	log_irq(SMD_APPS_DSPS);
+	++interrupt_stats[SMD_DSPS].smd_in_count;
+	handle_smd_irq(&remote_info[SMD_DSPS], notify_dsps_smd);
+	handle_smd_irq_closing_list();
+	return IRQ_HANDLED;
+}
+
+irqreturn_t smd_wcnss_irq_handler(int irq, void *data)
+{
+	if (unlikely(!edge_to_pids[SMD_APPS_WCNSS].initialized))
+		return IRQ_HANDLED;
+	log_irq(SMD_APPS_WCNSS);
+	++interrupt_stats[SMD_WCNSS].smd_in_count;
+	handle_smd_irq(&remote_info[SMD_WCNSS], notify_wcnss_smd);
+	handle_smd_irq_closing_list();
+	return IRQ_HANDLED;
+}
+
+irqreturn_t smd_modemfw_irq_handler(int irq, void *data)
+{
+	if (unlikely(!edge_to_pids[SMD_APPS_Q6FW].initialized))
+		return IRQ_HANDLED;
+	log_irq(SMD_APPS_Q6FW);
+	++interrupt_stats[SMD_MODEM_Q6_FW].smd_in_count;
+	handle_smd_irq(&remote_info[SMD_MODEM_Q6_FW], notify_modemfw_smd);
+	handle_smd_irq_closing_list();
+	return IRQ_HANDLED;
+}
+
+irqreturn_t smd_rpm_irq_handler(int irq, void *data)
+{
+	if (unlikely(!edge_to_pids[SMD_APPS_RPM].initialized))
+		return IRQ_HANDLED;
+	log_irq(SMD_APPS_RPM);
+	++interrupt_stats[SMD_RPM].smd_in_count;
+	handle_smd_irq(&remote_info[SMD_RPM], notify_rpm_smd);
+	handle_smd_irq_closing_list();
+	return IRQ_HANDLED;
+}
+
+static void smd_fake_irq_handler(unsigned long arg)
+{
+	handle_smd_irq(&remote_info[SMD_MODEM], notify_modem_smd);
+	handle_smd_irq(&remote_info[SMD_Q6], notify_dsp_smd);
+	handle_smd_irq(&remote_info[SMD_DSPS], notify_dsps_smd);
+	handle_smd_irq(&remote_info[SMD_WCNSS], notify_wcnss_smd);
+	handle_smd_irq(&remote_info[SMD_MODEM_Q6_FW], notify_modemfw_smd);
+	handle_smd_irq(&remote_info[SMD_RPM], notify_rpm_smd);
+	handle_smd_irq_closing_list();
+}
+
+static int smd_is_packet(struct smd_alloc_elm *alloc_elm)
+{
+	if (SMD_XFER_TYPE(alloc_elm->type) == 1)
+		return 0;
+	else if (SMD_XFER_TYPE(alloc_elm->type) == 2)
+		return 1;
+
+	panic("Unsupported SMD xfer type: %d name:%s edge:%d\n",
+					SMD_XFER_TYPE(alloc_elm->type),
+					alloc_elm->name,
+					SMD_CHANNEL_TYPE(alloc_elm->type));
+}
+
+static int smd_stream_write(smd_channel_t *ch, const void *_data, int len,
+				bool intr_ntfy)
+{
+	void *ptr;
+	const unsigned char *buf = _data;
+	unsigned int xfer;
+	int orig_len = len;
+
+	SMD_DBG("smd_stream_write() %d -> ch%d\n", len, ch->n);
+	if (len < 0)
+		return -EINVAL;
+	else if (len == 0)
+		return 0;
+
+	while ((xfer = ch_write_buffer(ch, &ptr)) != 0) {
+		if (!ch_is_open(ch)) {
+			len = orig_len;
+			break;
+		}
+		if (xfer > len)
+			xfer = len;
+
+		ch->write_to_fifo(ptr, buf, xfer);
+		ch_write_done(ch, xfer);
+		len -= xfer;
+		buf += xfer;
+		if (len == 0)
+			break;
+	}
+
+	if (orig_len - len && intr_ntfy)
+		ch->notify_other_cpu(ch);
+
+	return orig_len - len;
+}
+
+static int smd_packet_write(smd_channel_t *ch, const void *_data, int len,
+				bool intr_ntfy)
+{
+	int ret;
+	unsigned int hdr[5];
+
+	SMD_DBG("smd_packet_write() %d -> ch%d\n", len, ch->n);
+	if (len < 0)
+		return -EINVAL;
+	else if (len == 0)
+		return 0;
+
+	if (smd_stream_write_avail(ch) < (len + SMD_HEADER_SIZE))
+		return -ENOMEM;
+
+	hdr[0] = len;
+	hdr[1] = hdr[2] = hdr[3] = hdr[4] = 0;
+
+
+	ret = smd_stream_write(ch, hdr, sizeof(hdr), false);
+	if (ret < 0 || ret != sizeof(hdr)) {
+		SMD_DBG("%s failed to write pkt header: %d returned\n",
+								__func__, ret);
+		return -EFAULT;
+	}
+
+
+	ret = smd_stream_write(ch, _data, len, true);
+	if (ret < 0 || ret != len) {
+		SMD_DBG("%s failed to write pkt data: %d returned\n",
+								__func__, ret);
+		return ret;
+	}
+
+	return len;
+}
+
+static int smd_stream_read(smd_channel_t *ch, void *data, int len)
+{
+	int r;
+
+	if (len < 0)
+		return -EINVAL;
+
+	r = ch_read(ch, data, len);
+	if (r > 0)
+		if (!read_intr_blocked(ch))
+			ch->notify_other_cpu(ch);
+
+	return r;
+}
+
+static int smd_packet_read(smd_channel_t *ch, void *data, int len)
+{
+	unsigned long flags;
+	int r;
+
+	if (len < 0)
+		return -EINVAL;
+
+	if (ch->current_packet > (uint32_t)INT_MAX) {
+		pr_err("%s: Invalid packet size for Edge %d and Channel %s",
+			__func__, ch->type, ch->name);
+		return -EFAULT;
+	}
+
+	if (len > ch->current_packet)
+		len = ch->current_packet;
+
+	r = ch_read(ch, data, len);
+	if (r > 0)
+		if (!read_intr_blocked(ch))
+			ch->notify_other_cpu(ch);
+
+	spin_lock_irqsave(&smd_lock, flags);
+	ch->current_packet -= r;
+	update_packet_state(ch);
+	spin_unlock_irqrestore(&smd_lock, flags);
+
+	return r;
+}
+
+static int smd_packet_read_from_cb(smd_channel_t *ch, void *data, int len)
+{
+	int r;
+
+	if (len < 0)
+		return -EINVAL;
+
+	if (ch->current_packet > (uint32_t)INT_MAX) {
+		pr_err("%s: Invalid packet size for Edge %d and Channel %s",
+			__func__, ch->type, ch->name);
+		return -EFAULT;
+	}
+
+	if (len > ch->current_packet)
+		len = ch->current_packet;
+
+	r = ch_read(ch, data, len);
+	if (r > 0)
+		if (!read_intr_blocked(ch))
+			ch->notify_other_cpu(ch);
+
+	ch->current_packet -= r;
+	update_packet_state(ch);
+
+	return r;
+}
+
+/**
+ * smd_alloc_v2() - Init local channel structure with information stored in SMEM
+ *
+ * @ch: pointer to the local structure for this channel
+ * @table_id: the id of the table this channel resides in. 1 = first table, 2 =
+ *		second table, etc
+ * @r_info: pointer to the info structure of the remote proc for this channel
+ * @returns: -EINVAL for failure; 0 for success
+ *
+ * ch must point to an allocated instance of struct smd_channel that is zeroed
+ * out, and has the n and type members already initialized to the correct values
+ */
+static int smd_alloc(struct smd_channel *ch, int table_id,
+						struct remote_proc_info *r_info)
+{
+	void *buffer;
+	unsigned int buffer_sz;
+	unsigned int base_id;
+	unsigned int fifo_id;
+
+	switch (table_id) {
+	case PRI_ALLOC_TBL:
+		base_id = SMEM_SMD_BASE_ID;
+		fifo_id = SMEM_SMD_FIFO_BASE_ID;
+		break;
+	case SEC_ALLOC_TBL:
+		base_id = SMEM_SMD_BASE_ID_2;
+		fifo_id = SMEM_SMD_FIFO_BASE_ID_2;
+		break;
+	default:
+		SMD_INFO("Invalid table_id:%d passed to smd_alloc\n", table_id);
+		return -EINVAL;
+	}
+
+	if (is_word_access_ch(ch->type)) {
+		struct smd_shared_word_access *shared2;
+
+		shared2 = smem_find(base_id + ch->n, sizeof(*shared2),
+							r_info->remote_pid, 0);
+		if (!shared2) {
+			SMD_INFO("smem_find failed ch=%d\n", ch->n);
+			return -EINVAL;
+		}
+		ch->send = &shared2->ch0;
+		ch->recv = &shared2->ch1;
+	} else {
+		struct smd_shared *shared2;
+
+		shared2 = smem_find(base_id + ch->n, sizeof(*shared2),
+							r_info->remote_pid, 0);
+		if (!shared2) {
+			SMD_INFO("smem_find failed ch=%d\n", ch->n);
+			return -EINVAL;
+		}
+		ch->send = &shared2->ch0;
+		ch->recv = &shared2->ch1;
+	}
+	ch->half_ch = get_half_ch_funcs(ch->type);
+
+	buffer = smem_get_entry(fifo_id + ch->n, &buffer_sz,
+							r_info->remote_pid, 0);
+	if (!buffer) {
+		SMD_INFO("smem_get_entry failed\n");
+		return -EINVAL;
+	}
+
+	/* buffer must be a multiple of 32 size */
+	if ((buffer_sz & (SZ_32 - 1)) != 0) {
+		SMD_INFO("Buffer size: %u not multiple of 32\n", buffer_sz);
+		return -EINVAL;
+	}
+	buffer_sz /= 2;
+	ch->send_data = buffer;
+	ch->recv_data = buffer + buffer_sz;
+	ch->fifo_size = buffer_sz;
+
+	return 0;
+}
+
+/**
+ * smd_alloc_channel() - Create and init local structures for a newly allocated
+ *			SMD channel
+ *
+ * @alloc_elm: the allocation element stored in SMEM for this channel
+ * @table_id: the id of the table this channel resides in. 1 = first table, 2 =
+ *		seconds table, etc
+ * @r_info: pointer to the info structure of the remote proc for this channel
+ * @returns: error code for failure; 0 for success
+ */
+static int smd_alloc_channel(struct smd_alloc_elm *alloc_elm, int table_id,
+				struct remote_proc_info *r_info)
+{
+	struct smd_channel *ch;
+
+	ch = kzalloc(sizeof(struct smd_channel), GFP_KERNEL);
+	if (ch == 0) {
+		pr_err("smd_alloc_channel() out of memory\n");
+		return -ENOMEM;
+	}
+	ch->n = alloc_elm->cid;
+	ch->type = SMD_CHANNEL_TYPE(alloc_elm->type);
+
+	if (smd_alloc(ch, table_id, r_info)) {
+		kfree(ch);
+		return -ENODEV;
+	}
+
+	/* probe_worker guarentees ch->type will be a valid type */
+	if (ch->type == SMD_APPS_MODEM)
+		ch->notify_other_cpu = notify_modem_smd;
+	else if (ch->type == SMD_APPS_QDSP)
+		ch->notify_other_cpu = notify_dsp_smd;
+	else if (ch->type == SMD_APPS_DSPS)
+		ch->notify_other_cpu = notify_dsps_smd;
+	else if (ch->type == SMD_APPS_WCNSS)
+		ch->notify_other_cpu = notify_wcnss_smd;
+	else if (ch->type == SMD_APPS_Q6FW)
+		ch->notify_other_cpu = notify_modemfw_smd;
+	else if (ch->type == SMD_APPS_RPM)
+		ch->notify_other_cpu = notify_rpm_smd;
+
+	if (smd_is_packet(alloc_elm)) {
+		ch->read = smd_packet_read;
+		ch->write = smd_packet_write;
+		ch->read_avail = smd_packet_read_avail;
+		ch->write_avail = smd_packet_write_avail;
+		ch->update_state = update_packet_state;
+		ch->read_from_cb = smd_packet_read_from_cb;
+		ch->is_pkt_ch = 1;
+	} else {
+		ch->read = smd_stream_read;
+		ch->write = smd_stream_write;
+		ch->read_avail = smd_stream_read_avail;
+		ch->write_avail = smd_stream_write_avail;
+		ch->update_state = update_stream_state;
+		ch->read_from_cb = smd_stream_read;
+	}
+
+	if (is_word_access_ch(ch->type)) {
+		ch->read_from_fifo = smd_memcpy32_from_fifo;
+		ch->write_to_fifo = smd_memcpy32_to_fifo;
+	} else {
+		ch->read_from_fifo = smd_memcpy_from_fifo;
+		ch->write_to_fifo = smd_memcpy_to_fifo;
+	}
+
+	smd_memcpy_from_fifo(ch->name, alloc_elm->name, SMD_MAX_CH_NAME_LEN);
+	ch->name[SMD_MAX_CH_NAME_LEN-1] = 0;
+
+	ch->pdev.name = ch->name;
+	ch->pdev.id = ch->type;
+
+	SMD_INFO("smd_alloc_channel() '%s' cid=%d\n",
+		 ch->name, ch->n);
+
+	mutex_lock(&smd_creation_mutex);
+	list_add(&ch->ch_list, &smd_ch_closed_list);
+	mutex_unlock(&smd_creation_mutex);
+
+	platform_device_register(&ch->pdev);
+	if (!strcmp(ch->name, "LOOPBACK") && ch->type == SMD_APPS_MODEM) {
+		/* create a platform driver to be used by smd_tty driver
+		 * so that it can access the loopback port
+		 */
+		loopback_tty_pdev.id = ch->type;
+		platform_device_register(&loopback_tty_pdev);
+	}
+	return 0;
+}
+
+static void do_nothing_notify(void *priv, unsigned int flags)
+{
+}
+
+static void finalize_channel_close_fn(struct work_struct *work)
+{
+	unsigned long flags;
+	struct smd_channel *ch;
+	struct smd_channel *index;
+
+	mutex_lock(&smd_creation_mutex);
+	spin_lock_irqsave(&smd_lock, flags);
+	list_for_each_entry_safe(ch, index,  &smd_ch_to_close_list, ch_list) {
+		list_del(&ch->ch_list);
+		list_add(&ch->ch_list, &smd_ch_closed_list);
+		ch->notify(ch->priv, SMD_EVENT_REOPEN_READY);
+		ch->notify = do_nothing_notify;
+	}
+	spin_unlock_irqrestore(&smd_lock, flags);
+	mutex_unlock(&smd_creation_mutex);
+}
+
+struct smd_channel *smd_get_channel(const char *name, uint32_t type)
+{
+	struct smd_channel *ch;
+
+	mutex_lock(&smd_creation_mutex);
+	list_for_each_entry(ch, &smd_ch_closed_list, ch_list) {
+		if (!strcmp(name, ch->name) &&
+			(type == ch->type)) {
+			list_del(&ch->ch_list);
+			mutex_unlock(&smd_creation_mutex);
+			return ch;
+		}
+	}
+	mutex_unlock(&smd_creation_mutex);
+
+	return NULL;
+}
+
+int smd_named_open_on_edge(const char *name, uint32_t edge,
+			   smd_channel_t **_ch,
+			   void *priv, void (*notify)(void *, unsigned int))
+{
+	struct smd_channel *ch;
+	unsigned long flags;
+
+	if (edge >= SMD_NUM_TYPE) {
+		pr_err("%s: edge:%d is invalid\n", __func__, edge);
+		return -EINVAL;
+	}
+
+	if (!smd_edge_inited(edge)) {
+		SMD_INFO("smd_open() before smd_init()\n");
+		return -EPROBE_DEFER;
+	}
+
+	SMD_DBG("smd_open('%s', %p, %p)\n", name, priv, notify);
+
+	ch = smd_get_channel(name, edge);
+	if (!ch) {
+		spin_lock_irqsave(&smd_lock, flags);
+		/* check opened list for port */
+		list_for_each_entry(ch,
+			&remote_info[edge_to_pids[edge].remote_pid].ch_list,
+			ch_list) {
+			if (!strcmp(name, ch->name)) {
+				/* channel is already open */
+				spin_unlock_irqrestore(&smd_lock, flags);
+				SMD_DBG("smd_open: channel '%s' already open\n",
+					ch->name);
+				return -EBUSY;
+			}
+		}
+
+		/* check closing list for port */
+		list_for_each_entry(ch, &smd_ch_closing_list, ch_list) {
+			if (!strcmp(name, ch->name) && (edge == ch->type)) {
+				/* channel exists, but is being closed */
+				spin_unlock_irqrestore(&smd_lock, flags);
+				return -EAGAIN;
+			}
+		}
+
+		/* check closing workqueue list for port */
+		list_for_each_entry(ch, &smd_ch_to_close_list, ch_list) {
+			if (!strcmp(name, ch->name) && (edge == ch->type)) {
+				/* channel exists, but is being closed */
+				spin_unlock_irqrestore(&smd_lock, flags);
+				return -EAGAIN;
+			}
+		}
+		spin_unlock_irqrestore(&smd_lock, flags);
+
+		/* one final check to handle closing->closed race condition */
+		ch = smd_get_channel(name, edge);
+		if (!ch)
+			return -ENODEV;
+	}
+
+	if (ch->half_ch->get_fSTATE(ch->send)) {
+		/* remote side hasn't acknowledged our last state transition */
+		SMD_INFO("%s: ch %d valid, waiting for remote to ack state\n",
+				__func__, ch->n);
+		msleep(250);
+		if (ch->half_ch->get_fSTATE(ch->send))
+			SMD_INFO("%s: ch %d - no remote ack, continuing\n",
+					__func__, ch->n);
+	}
+
+	if (notify == 0)
+		notify = do_nothing_notify;
+
+	ch->notify = notify;
+	ch->current_packet = 0;
+	ch->last_state = SMD_SS_CLOSED;
+	ch->priv = priv;
+
+	*_ch = ch;
+
+	SMD_DBG("smd_open: opening '%s'\n", ch->name);
+
+	spin_lock_irqsave(&smd_lock, flags);
+	list_add(&ch->ch_list,
+		       &remote_info[edge_to_pids[ch->type].remote_pid].ch_list);
+
+	SMD_DBG("%s: opening ch %d\n", __func__, ch->n);
+
+	smd_state_change(ch, ch->last_state, SMD_SS_OPENING);
+
+	spin_unlock_irqrestore(&smd_lock, flags);
+
+	return 0;
+}
+EXPORT_SYMBOL(smd_named_open_on_edge);
+
+int smd_close(smd_channel_t *ch)
+{
+	unsigned long flags;
+	bool was_opened;
+
+	if (ch == 0)
+		return -EINVAL;
+
+	SMD_INFO("smd_close(%s)\n", ch->name);
+
+	spin_lock_irqsave(&smd_lock, flags);
+	list_del(&ch->ch_list);
+
+	was_opened = ch->half_ch->get_state(ch->recv) == SMD_SS_OPENED;
+	ch_set_state(ch, SMD_SS_CLOSED);
+
+	if (was_opened) {
+		list_add(&ch->ch_list, &smd_ch_closing_list);
+		spin_unlock_irqrestore(&smd_lock, flags);
+	} else {
+		spin_unlock_irqrestore(&smd_lock, flags);
+		ch->notify = do_nothing_notify;
+		mutex_lock(&smd_creation_mutex);
+		list_add(&ch->ch_list, &smd_ch_closed_list);
+		mutex_unlock(&smd_creation_mutex);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(smd_close);
+
+int smd_write_start(smd_channel_t *ch, int len)
+{
+	int ret;
+	unsigned int hdr[5];
+
+	if (!ch) {
+		pr_err("%s: Invalid channel specified\n", __func__);
+		return -ENODEV;
+	}
+	if (!ch->is_pkt_ch) {
+		pr_err("%s: non-packet channel specified\n", __func__);
+		return -EACCES;
+	}
+	if (len < 1) {
+		pr_err("%s: invalid length: %d\n", __func__, len);
+		return -EINVAL;
+	}
+
+	if (ch->pending_pkt_sz) {
+		pr_err("%s: packet of size: %d in progress\n", __func__,
+			ch->pending_pkt_sz);
+		return -EBUSY;
+	}
+	ch->pending_pkt_sz = len;
+
+	if (smd_stream_write_avail(ch) < (SMD_HEADER_SIZE)) {
+		ch->pending_pkt_sz = 0;
+		SMD_DBG("%s: no space to write packet header\n", __func__);
+		return -EAGAIN;
+	}
+
+	hdr[0] = len;
+	hdr[1] = hdr[2] = hdr[3] = hdr[4] = 0;
+
+
+	ret = smd_stream_write(ch, hdr, sizeof(hdr), true);
+	if (ret < 0 || ret != sizeof(hdr)) {
+		ch->pending_pkt_sz = 0;
+		pr_err("%s: packet header failed to write\n", __func__);
+		return -EPERM;
+	}
+	return 0;
+}
+EXPORT_SYMBOL(smd_write_start);
+
+int smd_write_segment(smd_channel_t *ch, const void *data, int len)
+{
+	int bytes_written;
+
+	if (!ch) {
+		pr_err("%s: Invalid channel specified\n", __func__);
+		return -ENODEV;
+	}
+	if (len < 1) {
+		pr_err("%s: invalid length: %d\n", __func__, len);
+		return -EINVAL;
+	}
+
+	if (!ch->pending_pkt_sz) {
+		pr_err("%s: no transaction in progress\n", __func__);
+		return -ENOEXEC;
+	}
+	if (ch->pending_pkt_sz - len < 0) {
+		pr_err("%s: segment of size: %d will make packet go over length\n",
+								__func__, len);
+		return -EINVAL;
+	}
+
+	bytes_written = smd_stream_write(ch, data, len, true);
+
+	ch->pending_pkt_sz -= bytes_written;
+
+	return bytes_written;
+}
+EXPORT_SYMBOL(smd_write_segment);
+
+int smd_write_end(smd_channel_t *ch)
+{
+
+	if (!ch) {
+		pr_err("%s: Invalid channel specified\n", __func__);
+		return -ENODEV;
+	}
+	if (ch->pending_pkt_sz) {
+		pr_err("%s: current packet not completely written\n", __func__);
+		return -E2BIG;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(smd_write_end);
+
+int smd_write_segment_avail(smd_channel_t *ch)
+{
+	int n;
+
+	if (!ch) {
+		pr_err("%s: Invalid channel specified\n", __func__);
+		return -ENODEV;
+	}
+	if (!ch->is_pkt_ch) {
+		pr_err("%s: non-packet channel specified\n", __func__);
+		return -ENODEV;
+	}
+
+	n = smd_stream_write_avail(ch);
+
+	/* pkt hdr already written, no need to reserve space for it */
+	if (ch->pending_pkt_sz)
+		return n;
+
+	return n > SMD_HEADER_SIZE ? n - SMD_HEADER_SIZE : 0;
+}
+EXPORT_SYMBOL(smd_write_segment_avail);
+
+int smd_read(smd_channel_t *ch, void *data, int len)
+{
+	if (!ch) {
+		pr_err("%s: Invalid channel specified\n", __func__);
+		return -ENODEV;
+	}
+
+	return ch->read(ch, data, len);
+}
+EXPORT_SYMBOL(smd_read);
+
+int smd_read_from_cb(smd_channel_t *ch, void *data, int len)
+{
+	if (!ch) {
+		pr_err("%s: Invalid channel specified\n", __func__);
+		return -ENODEV;
+	}
+
+	return ch->read_from_cb(ch, data, len);
+}
+EXPORT_SYMBOL(smd_read_from_cb);
+
+int smd_write(smd_channel_t *ch, const void *data, int len)
+{
+	if (!ch) {
+		pr_err("%s: Invalid channel specified\n", __func__);
+		return -ENODEV;
+	}
+
+	return ch->pending_pkt_sz ? -EBUSY : ch->write(ch, data, len, true);
+}
+EXPORT_SYMBOL(smd_write);
+
+int smd_read_avail(smd_channel_t *ch)
+{
+	if (!ch) {
+		pr_err("%s: Invalid channel specified\n", __func__);
+		return -ENODEV;
+	}
+
+	if (ch->current_packet > (uint32_t)INT_MAX) {
+		pr_err("%s: Invalid packet size for Edge %d and Channel %s",
+			__func__, ch->type, ch->name);
+		return -EFAULT;
+	}
+	return ch->read_avail(ch);
+}
+EXPORT_SYMBOL(smd_read_avail);
+
+int smd_write_avail(smd_channel_t *ch)
+{
+	if (!ch) {
+		pr_err("%s: Invalid channel specified\n", __func__);
+		return -ENODEV;
+	}
+
+	return ch->write_avail(ch);
+}
+EXPORT_SYMBOL(smd_write_avail);
+
+void smd_enable_read_intr(smd_channel_t *ch)
+{
+	if (ch)
+		ch->half_ch->set_fBLOCKREADINTR(ch->send, 0);
+}
+EXPORT_SYMBOL(smd_enable_read_intr);
+
+void smd_disable_read_intr(smd_channel_t *ch)
+{
+	if (ch)
+		ch->half_ch->set_fBLOCKREADINTR(ch->send, 1);
+}
+EXPORT_SYMBOL(smd_disable_read_intr);
+
+/**
+ * Enable/disable receive interrupts for the remote processor used by a
+ * particular channel.
+ * @ch:      open channel handle to use for the edge
+ * @mask:    1 = mask interrupts; 0 = unmask interrupts
+ * @cpumask  cpumask for the next cpu scheduled to be woken up
+ * @returns: 0 for success; < 0 for failure
+ *
+ * Note that this enables/disables all interrupts from the remote subsystem for
+ * all channels.  As such, it should be used with care and only for specific
+ * use cases such as power-collapse sequencing.
+ */
+int smd_mask_receive_interrupt(smd_channel_t *ch, bool mask,
+		const struct cpumask *cpumask)
+{
+	struct irq_chip *irq_chip;
+	struct irq_data *irq_data;
+	struct interrupt_config_item *int_cfg;
+
+	if (!ch)
+		return -EINVAL;
+
+	if (ch->type >= ARRAY_SIZE(edge_to_pids))
+		return -ENODEV;
+
+	int_cfg = &private_intr_config[edge_to_pids[ch->type].remote_pid].smd;
+
+	if (int_cfg->irq_id < 0)
+		return -ENODEV;
+
+	irq_chip = irq_get_chip(int_cfg->irq_id);
+	if (!irq_chip)
+		return -ENODEV;
+
+	irq_data = irq_get_irq_data(int_cfg->irq_id);
+	if (!irq_data)
+		return -ENODEV;
+
+	if (mask) {
+		SMD_POWER_INFO("SMD Masking interrupts from %s\n",
+				edge_to_pids[ch->type].subsys_name);
+		irq_chip->irq_mask(irq_data);
+		if (cpumask)
+			irq_set_affinity(int_cfg->irq_id, cpumask);
+	} else {
+		SMD_POWER_INFO("SMD Unmasking interrupts from %s\n",
+				edge_to_pids[ch->type].subsys_name);
+		irq_chip->irq_unmask(irq_data);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(smd_mask_receive_interrupt);
+
+int smd_cur_packet_size(smd_channel_t *ch)
+{
+	if (!ch) {
+		pr_err("%s: Invalid channel specified\n", __func__);
+		return -ENODEV;
+	}
+
+	if (ch->current_packet > (uint32_t)INT_MAX) {
+		pr_err("%s: Invalid packet size for Edge %d and Channel %s",
+			__func__, ch->type, ch->name);
+		return -EFAULT;
+	}
+	return ch->current_packet;
+}
+EXPORT_SYMBOL(smd_cur_packet_size);
+
+int smd_tiocmget(smd_channel_t *ch)
+{
+	if (!ch) {
+		pr_err("%s: Invalid channel specified\n", __func__);
+		return -ENODEV;
+	}
+
+	return  (ch->half_ch->get_fDSR(ch->recv) ? TIOCM_DSR : 0) |
+		(ch->half_ch->get_fCTS(ch->recv) ? TIOCM_CTS : 0) |
+		(ch->half_ch->get_fCD(ch->recv) ? TIOCM_CD : 0) |
+		(ch->half_ch->get_fRI(ch->recv) ? TIOCM_RI : 0) |
+		(ch->half_ch->get_fCTS(ch->send) ? TIOCM_RTS : 0) |
+		(ch->half_ch->get_fDSR(ch->send) ? TIOCM_DTR : 0);
+}
+EXPORT_SYMBOL(smd_tiocmget);
+
+/* this api will be called while holding smd_lock */
+int
+smd_tiocmset_from_cb(smd_channel_t *ch, unsigned int set, unsigned int clear)
+{
+	if (!ch) {
+		pr_err("%s: Invalid channel specified\n", __func__);
+		return -ENODEV;
+	}
+
+	if (set & TIOCM_DTR)
+		ch->half_ch->set_fDSR(ch->send, 1);
+
+	if (set & TIOCM_RTS)
+		ch->half_ch->set_fCTS(ch->send, 1);
+
+	if (clear & TIOCM_DTR)
+		ch->half_ch->set_fDSR(ch->send, 0);
+
+	if (clear & TIOCM_RTS)
+		ch->half_ch->set_fCTS(ch->send, 0);
+
+	ch->half_ch->set_fSTATE(ch->send, 1);
+	barrier();
+	ch->notify_other_cpu(ch);
+
+	return 0;
+}
+EXPORT_SYMBOL(smd_tiocmset_from_cb);
+
+int smd_tiocmset(smd_channel_t *ch, unsigned int set, unsigned int clear)
+{
+	unsigned long flags;
+
+	if (!ch) {
+		pr_err("%s: Invalid channel specified\n", __func__);
+		return -ENODEV;
+	}
+
+	spin_lock_irqsave(&smd_lock, flags);
+	smd_tiocmset_from_cb(ch, set, clear);
+	spin_unlock_irqrestore(&smd_lock, flags);
+
+	return 0;
+}
+EXPORT_SYMBOL(smd_tiocmset);
+
+int smd_is_pkt_avail(smd_channel_t *ch)
+{
+	unsigned long flags;
+
+	if (!ch || !ch->is_pkt_ch)
+		return -EINVAL;
+
+	if (ch->current_packet)
+		return 1;
+
+	spin_lock_irqsave(&smd_lock, flags);
+	update_packet_state(ch);
+	spin_unlock_irqrestore(&smd_lock, flags);
+
+	return ch->current_packet ? 1 : 0;
+}
+EXPORT_SYMBOL(smd_is_pkt_avail);
+
+static int smsm_cb_init(void)
+{
+	struct smsm_state_info *state_info;
+	int n;
+	int ret = 0;
+
+	smsm_states = kmalloc(sizeof(struct smsm_state_info)*SMSM_NUM_ENTRIES,
+		   GFP_KERNEL);
+
+	if (!smsm_states) {
+		pr_err("%s: SMSM init failed\n", __func__);
+		return -ENOMEM;
+	}
+
+	smsm_cb_wq = create_singlethread_workqueue("smsm_cb_wq");
+	if (!smsm_cb_wq) {
+		pr_err("%s: smsm_cb_wq creation failed\n", __func__);
+		kfree(smsm_states);
+		return -EFAULT;
+	}
+
+	mutex_lock(&smsm_lock);
+	for (n = 0; n < SMSM_NUM_ENTRIES; n++) {
+		state_info = &smsm_states[n];
+		state_info->last_value = __raw_readl(SMSM_STATE_ADDR(n));
+		state_info->intr_mask_set = 0x0;
+		state_info->intr_mask_clear = 0x0;
+		INIT_LIST_HEAD(&state_info->callbacks);
+	}
+	mutex_unlock(&smsm_lock);
+
+	return ret;
+}
+
+static int smsm_init(void)
+{
+	int i;
+	struct smsm_size_info_type *smsm_size_info;
+	unsigned long flags;
+	unsigned long j_start;
+	static int first = 1;
+	remote_spinlock_t *remote_spinlock;
+
+	if (!first)
+		return 0;
+	first = 0;
+
+	/* Verify that remote spinlock is not deadlocked */
+	remote_spinlock = smem_get_remote_spinlock();
+	j_start = jiffies;
+	while (!remote_spin_trylock_irqsave(remote_spinlock, flags)) {
+		if (jiffies_to_msecs(jiffies - j_start) > RSPIN_INIT_WAIT_MS) {
+			panic("%s: Remote processor %d will not release spinlock\n",
+				__func__, remote_spin_owner(remote_spinlock));
+		}
+	}
+	remote_spin_unlock_irqrestore(remote_spinlock, flags);
+
+	smsm_size_info = smem_find(SMEM_SMSM_SIZE_INFO,
+				sizeof(struct smsm_size_info_type), 0,
+				SMEM_ANY_HOST_FLAG);
+	if (smsm_size_info) {
+		SMSM_NUM_ENTRIES = smsm_size_info->num_entries;
+		SMSM_NUM_HOSTS = smsm_size_info->num_hosts;
+	}
+
+	i = kfifo_alloc(&smsm_snapshot_fifo,
+			sizeof(uint32_t) * SMSM_NUM_ENTRIES * SMSM_SNAPSHOT_CNT,
+			GFP_KERNEL);
+	if (i) {
+		pr_err("%s: SMSM state fifo alloc failed %d\n", __func__, i);
+		return i;
+	}
+	wakeup_source_init(&smsm_snapshot_ws, "smsm_snapshot");
+
+	if (!smsm_info.state) {
+		smsm_info.state = smem_alloc(ID_SHARED_STATE,
+						SMSM_NUM_ENTRIES *
+						sizeof(uint32_t), 0,
+						SMEM_ANY_HOST_FLAG);
+
+		if (smsm_info.state)
+			__raw_writel(0, SMSM_STATE_ADDR(SMSM_APPS_STATE));
+	}
+
+	if (!smsm_info.intr_mask) {
+		smsm_info.intr_mask = smem_alloc(SMEM_SMSM_CPU_INTR_MASK,
+						SMSM_NUM_ENTRIES *
+						SMSM_NUM_HOSTS *
+						sizeof(uint32_t), 0,
+						SMEM_ANY_HOST_FLAG);
+
+		if (smsm_info.intr_mask) {
+			for (i = 0; i < SMSM_NUM_ENTRIES; i++)
+				__raw_writel(0x0,
+					SMSM_INTR_MASK_ADDR(i, SMSM_APPS));
+
+			/* Configure legacy modem bits */
+			__raw_writel(LEGACY_MODEM_SMSM_MASK,
+				SMSM_INTR_MASK_ADDR(SMSM_MODEM_STATE,
+					SMSM_APPS));
+		}
+	}
+
+	i = smsm_cb_init();
+	if (i)
+		return i;
+
+	wmb(); /* Make sure memory is visible before proceeding */
+
+	smsm_pm_notifier(&smsm_pm_nb, PM_POST_SUSPEND, NULL);
+	i = register_pm_notifier(&smsm_pm_nb);
+	if (i)
+		pr_err("%s: power state notif error %d\n", __func__, i);
+
+	return 0;
+}
+
+static void smsm_cb_snapshot(uint32_t use_wakeup_source)
+{
+	int n;
+	uint32_t new_state;
+	unsigned long flags;
+	int ret;
+	uint64_t timestamp;
+
+	timestamp = sched_clock();
+	ret = kfifo_avail(&smsm_snapshot_fifo);
+	if (ret < SMSM_SNAPSHOT_SIZE) {
+		pr_err("%s: SMSM snapshot full %d\n", __func__, ret);
+		return;
+	}
+
+	/*
+	 * To avoid a race condition with notify_smsm_cb_clients_worker, the
+	 * following sequence must be followed:
+	 *   1) increment snapshot count
+	 *   2) insert data into FIFO
+	 *
+	 *   Potentially in parallel, the worker:
+	 *   a) verifies >= 1 snapshots are in FIFO
+	 *   b) processes snapshot
+	 *   c) decrements reference count
+	 *
+	 *   This order ensures that 1 will always occur before abc.
+	 */
+	if (use_wakeup_source) {
+		spin_lock_irqsave(&smsm_snapshot_count_lock, flags);
+		if (smsm_snapshot_count == 0) {
+			SMSM_POWER_INFO("SMSM snapshot wake lock\n");
+			__pm_stay_awake(&smsm_snapshot_ws);
+		}
+		++smsm_snapshot_count;
+		spin_unlock_irqrestore(&smsm_snapshot_count_lock, flags);
+	}
+
+	/* queue state entries */
+	for (n = 0; n < SMSM_NUM_ENTRIES; n++) {
+		new_state = __raw_readl(SMSM_STATE_ADDR(n));
+
+		ret = kfifo_in(&smsm_snapshot_fifo,
+				&new_state, sizeof(new_state));
+		if (ret != sizeof(new_state)) {
+			pr_err("%s: SMSM snapshot failure %d\n", __func__, ret);
+			goto restore_snapshot_count;
+		}
+	}
+
+	ret = kfifo_in(&smsm_snapshot_fifo, &timestamp, sizeof(timestamp));
+	if (ret != sizeof(timestamp)) {
+		pr_err("%s: SMSM snapshot failure %d\n", __func__, ret);
+		goto restore_snapshot_count;
+	}
+
+	/* queue wakelock usage flag */
+	ret = kfifo_in(&smsm_snapshot_fifo,
+			&use_wakeup_source, sizeof(use_wakeup_source));
+	if (ret != sizeof(use_wakeup_source)) {
+		pr_err("%s: SMSM snapshot failure %d\n", __func__, ret);
+		goto restore_snapshot_count;
+	}
+
+	queue_work(smsm_cb_wq, &smsm_cb_work);
+	return;
+
+restore_snapshot_count:
+	if (use_wakeup_source) {
+		spin_lock_irqsave(&smsm_snapshot_count_lock, flags);
+		if (smsm_snapshot_count) {
+			--smsm_snapshot_count;
+			if (smsm_snapshot_count == 0) {
+				SMSM_POWER_INFO("SMSM snapshot wake unlock\n");
+				__pm_relax(&smsm_snapshot_ws);
+			}
+		} else {
+			pr_err("%s: invalid snapshot count\n", __func__);
+		}
+		spin_unlock_irqrestore(&smsm_snapshot_count_lock, flags);
+	}
+}
+
+static irqreturn_t smsm_irq_handler(int irq, void *data)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&smem_lock, flags);
+	if (!smsm_info.state) {
+		SMSM_INFO("<SM NO STATE>\n");
+	} else {
+		unsigned int old_apps, apps;
+		unsigned int modm;
+
+		modm = __raw_readl(SMSM_STATE_ADDR(SMSM_MODEM_STATE));
+		old_apps = apps = __raw_readl(SMSM_STATE_ADDR(SMSM_APPS_STATE));
+
+		SMSM_DBG("<SM %08x %08x>\n", apps, modm);
+		if (modm & SMSM_RESET) {
+			pr_err("SMSM: Modem SMSM state changed to SMSM_RESET.\n");
+		} else if (modm & SMSM_INIT) {
+			if (!(apps & SMSM_INIT))
+				apps |= SMSM_INIT;
+			if (modm & SMSM_SMDINIT)
+				apps |= SMSM_SMDINIT;
+		}
+
+		if (old_apps != apps) {
+			SMSM_DBG("<SM %08x NOTIFY>\n", apps);
+			__raw_writel(apps, SMSM_STATE_ADDR(SMSM_APPS_STATE));
+			notify_other_smsm(SMSM_APPS_STATE, (old_apps ^ apps));
+		}
+
+		smsm_cb_snapshot(1);
+	}
+	spin_unlock_irqrestore(&smem_lock, flags);
+	return IRQ_HANDLED;
+}
+
+irqreturn_t smsm_modem_irq_handler(int irq, void *data)
+{
+	SMSM_POWER_INFO("SMSM Int Modem->Apps\n");
+	++interrupt_stats[SMD_MODEM].smsm_in_count;
+	return smsm_irq_handler(irq, data);
+}
+
+irqreturn_t smsm_dsp_irq_handler(int irq, void *data)
+{
+	SMSM_POWER_INFO("SMSM Int LPASS->Apps\n");
+	++interrupt_stats[SMD_Q6].smsm_in_count;
+	return smsm_irq_handler(irq, data);
+}
+
+irqreturn_t smsm_dsps_irq_handler(int irq, void *data)
+{
+	SMSM_POWER_INFO("SMSM Int DSPS->Apps\n");
+	++interrupt_stats[SMD_DSPS].smsm_in_count;
+	return smsm_irq_handler(irq, data);
+}
+
+irqreturn_t smsm_wcnss_irq_handler(int irq, void *data)
+{
+	SMSM_POWER_INFO("SMSM Int WCNSS->Apps\n");
+	++interrupt_stats[SMD_WCNSS].smsm_in_count;
+	return smsm_irq_handler(irq, data);
+}
+
+/*
+ * Changes the global interrupt mask.  The set and clear masks are re-applied
+ * every time the global interrupt mask is updated for callback registration
+ * and de-registration.
+ *
+ * The clear mask is applied first, so if a bit is set to 1 in both the clear
+ * mask and the set mask, the result will be that the interrupt is set.
+ *
+ * @smsm_entry  SMSM entry to change
+ * @clear_mask  1 = clear bit, 0 = no-op
+ * @set_mask    1 = set bit, 0 = no-op
+ *
+ * @returns 0 for success, < 0 for error
+ */
+int smsm_change_intr_mask(uint32_t smsm_entry,
+			  uint32_t clear_mask, uint32_t set_mask)
+{
+	uint32_t  old_mask, new_mask;
+	unsigned long flags;
+
+	if (smsm_entry >= SMSM_NUM_ENTRIES) {
+		pr_err("smsm_change_state: Invalid entry %d\n",
+		       smsm_entry);
+		return -EINVAL;
+	}
+
+	if (!smsm_info.intr_mask) {
+		pr_err("smsm_change_intr_mask <SM NO STATE>\n");
+		return -EIO;
+	}
+
+	spin_lock_irqsave(&smem_lock, flags);
+	smsm_states[smsm_entry].intr_mask_clear = clear_mask;
+	smsm_states[smsm_entry].intr_mask_set = set_mask;
+
+	old_mask = __raw_readl(SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_APPS));
+	new_mask = (old_mask & ~clear_mask) | set_mask;
+	__raw_writel(new_mask, SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_APPS));
+
+	wmb(); /* Make sure memory is visible before proceeding */
+	spin_unlock_irqrestore(&smem_lock, flags);
+
+	return 0;
+}
+EXPORT_SYMBOL(smsm_change_intr_mask);
+
+int smsm_get_intr_mask(uint32_t smsm_entry, uint32_t *intr_mask)
+{
+	if (smsm_entry >= SMSM_NUM_ENTRIES) {
+		pr_err("smsm_change_state: Invalid entry %d\n",
+		       smsm_entry);
+		return -EINVAL;
+	}
+
+	if (!smsm_info.intr_mask) {
+		pr_err("smsm_change_intr_mask <SM NO STATE>\n");
+		return -EIO;
+	}
+
+	*intr_mask = __raw_readl(SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_APPS));
+	return 0;
+}
+EXPORT_SYMBOL(smsm_get_intr_mask);
+
+int smsm_change_state(uint32_t smsm_entry,
+		      uint32_t clear_mask, uint32_t set_mask)
+{
+	unsigned long flags;
+	uint32_t  old_state, new_state;
+
+	if (smsm_entry >= SMSM_NUM_ENTRIES) {
+		pr_err("smsm_change_state: Invalid entry %d",
+		       smsm_entry);
+		return -EINVAL;
+	}
+
+	if (!smsm_info.state) {
+		pr_err("smsm_change_state <SM NO STATE>\n");
+		return -EIO;
+	}
+	spin_lock_irqsave(&smem_lock, flags);
+
+	old_state = __raw_readl(SMSM_STATE_ADDR(smsm_entry));
+	new_state = (old_state & ~clear_mask) | set_mask;
+	__raw_writel(new_state, SMSM_STATE_ADDR(smsm_entry));
+	SMSM_POWER_INFO("%s %d:%08x->%08x", __func__, smsm_entry,
+			old_state, new_state);
+	notify_other_smsm(SMSM_APPS_STATE, (old_state ^ new_state));
+
+	spin_unlock_irqrestore(&smem_lock, flags);
+
+	return 0;
+}
+EXPORT_SYMBOL(smsm_change_state);
+
+uint32_t smsm_get_state(uint32_t smsm_entry)
+{
+	uint32_t rv = 0;
+
+	/* needs interface change to return error code */
+	if (smsm_entry >= SMSM_NUM_ENTRIES) {
+		pr_err("smsm_change_state: Invalid entry %d",
+		       smsm_entry);
+		return 0;
+	}
+
+	if (!smsm_info.state)
+		pr_err("smsm_get_state <SM NO STATE>\n");
+	else
+		rv = __raw_readl(SMSM_STATE_ADDR(smsm_entry));
+
+	return rv;
+}
+EXPORT_SYMBOL(smsm_get_state);
+
+/**
+ * Performs SMSM callback client notifiction.
+ */
+void notify_smsm_cb_clients_worker(struct work_struct *work)
+{
+	struct smsm_state_cb_info *cb_info;
+	struct smsm_state_info *state_info;
+	int n;
+	uint32_t new_state;
+	uint32_t state_changes;
+	uint32_t use_wakeup_source;
+	int ret;
+	unsigned long flags;
+	uint64_t t_snapshot;
+	uint64_t t_start;
+	unsigned long nanosec_rem;
+
+	while (kfifo_len(&smsm_snapshot_fifo) >= SMSM_SNAPSHOT_SIZE) {
+		t_start = sched_clock();
+		mutex_lock(&smsm_lock);
+		for (n = 0; n < SMSM_NUM_ENTRIES; n++) {
+			state_info = &smsm_states[n];
+
+			ret = kfifo_out(&smsm_snapshot_fifo, &new_state,
+					sizeof(new_state));
+			if (ret != sizeof(new_state)) {
+				pr_err("%s: snapshot underflow %d\n",
+					__func__, ret);
+				mutex_unlock(&smsm_lock);
+				return;
+			}
+
+			state_changes = state_info->last_value ^ new_state;
+			if (state_changes) {
+				SMSM_POWER_INFO("SMSM Change %d: %08x->%08x\n",
+						n, state_info->last_value,
+						new_state);
+				list_for_each_entry(cb_info,
+					&state_info->callbacks, cb_list) {
+
+					if (cb_info->mask & state_changes)
+						cb_info->notify(cb_info->data,
+							state_info->last_value,
+							new_state);
+				}
+				state_info->last_value = new_state;
+			}
+		}
+
+		ret = kfifo_out(&smsm_snapshot_fifo, &t_snapshot,
+				sizeof(t_snapshot));
+		if (ret != sizeof(t_snapshot)) {
+			pr_err("%s: snapshot underflow %d\n",
+				__func__, ret);
+			mutex_unlock(&smsm_lock);
+			return;
+		}
+
+		/* read wakelock flag */
+		ret = kfifo_out(&smsm_snapshot_fifo, &use_wakeup_source,
+				sizeof(use_wakeup_source));
+		if (ret != sizeof(use_wakeup_source)) {
+			pr_err("%s: snapshot underflow %d\n",
+				__func__, ret);
+			mutex_unlock(&smsm_lock);
+			return;
+		}
+		mutex_unlock(&smsm_lock);
+
+		if (use_wakeup_source) {
+			spin_lock_irqsave(&smsm_snapshot_count_lock, flags);
+			if (smsm_snapshot_count) {
+				--smsm_snapshot_count;
+				if (smsm_snapshot_count == 0) {
+					SMSM_POWER_INFO(
+						"SMSM snapshot wake unlock\n");
+					__pm_relax(&smsm_snapshot_ws);
+				}
+			} else {
+				pr_err("%s: invalid snapshot count\n",
+						__func__);
+			}
+			spin_unlock_irqrestore(&smsm_snapshot_count_lock,
+					flags);
+		}
+
+		t_start = t_start - t_snapshot;
+		nanosec_rem = do_div(t_start, 1000000000U);
+		SMSM_POWER_INFO(
+			"SMSM snapshot queue response time %6u.%09lu s\n",
+			(unsigned int)t_start, nanosec_rem);
+	}
+}
+
+
+/**
+ * Registers callback for SMSM state notifications when the specified
+ * bits change.
+ *
+ * @smsm_entry  Processor entry to deregister
+ * @mask        Bits to deregister (if result is 0, callback is removed)
+ * @notify      Notification function to deregister
+ * @data        Opaque data passed in to callback
+ *
+ * @returns Status code
+ *  <0 error code
+ *  0  inserted new entry
+ *  1  updated mask of existing entry
+ */
+int smsm_state_cb_register(uint32_t smsm_entry, uint32_t mask,
+		void (*notify)(void *, uint32_t, uint32_t), void *data)
+{
+	struct smsm_state_info *state;
+	struct smsm_state_cb_info *cb_info;
+	struct smsm_state_cb_info *cb_found = 0;
+	uint32_t new_mask = 0;
+	int ret = 0;
+
+	if (smsm_entry >= SMSM_NUM_ENTRIES)
+		return -EINVAL;
+
+	mutex_lock(&smsm_lock);
+
+	if (!smsm_states) {
+		/* smsm not yet initialized */
+		ret = -ENODEV;
+		goto cleanup;
+	}
+
+	state = &smsm_states[smsm_entry];
+	list_for_each_entry(cb_info,
+			&state->callbacks, cb_list) {
+		if (!ret && (cb_info->notify == notify) &&
+				(cb_info->data == data)) {
+			cb_info->mask |= mask;
+			cb_found = cb_info;
+			ret = 1;
+		}
+		new_mask |= cb_info->mask;
+	}
+
+	if (!cb_found) {
+		cb_info = kmalloc(sizeof(struct smsm_state_cb_info),
+			GFP_ATOMIC);
+		if (!cb_info) {
+			ret = -ENOMEM;
+			goto cleanup;
+		}
+
+		cb_info->mask = mask;
+		cb_info->notify = notify;
+		cb_info->data = data;
+		INIT_LIST_HEAD(&cb_info->cb_list);
+		list_add_tail(&cb_info->cb_list,
+			&state->callbacks);
+		new_mask |= mask;
+	}
+
+	/* update interrupt notification mask */
+	if (smsm_entry == SMSM_MODEM_STATE)
+		new_mask |= LEGACY_MODEM_SMSM_MASK;
+
+	if (smsm_info.intr_mask) {
+		unsigned long flags;
+
+		spin_lock_irqsave(&smem_lock, flags);
+		new_mask = (new_mask & ~state->intr_mask_clear)
+				| state->intr_mask_set;
+		__raw_writel(new_mask,
+				SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_APPS));
+		wmb(); /* Make sure memory is visible before proceeding */
+		spin_unlock_irqrestore(&smem_lock, flags);
+	}
+
+cleanup:
+	mutex_unlock(&smsm_lock);
+	return ret;
+}
+EXPORT_SYMBOL(smsm_state_cb_register);
+
+
+/**
+ * Deregisters for SMSM state notifications for the specified bits.
+ *
+ * @smsm_entry  Processor entry to deregister
+ * @mask        Bits to deregister (if result is 0, callback is removed)
+ * @notify      Notification function to deregister
+ * @data        Opaque data passed in to callback
+ *
+ * @returns Status code
+ *  <0 error code
+ *  0  not found
+ *  1  updated mask
+ *  2  removed callback
+ */
+int smsm_state_cb_deregister(uint32_t smsm_entry, uint32_t mask,
+		void (*notify)(void *, uint32_t, uint32_t), void *data)
+{
+	struct smsm_state_cb_info *cb_info;
+	struct smsm_state_cb_info *cb_tmp;
+	struct smsm_state_info *state;
+	uint32_t new_mask = 0;
+	int ret = 0;
+
+	if (smsm_entry >= SMSM_NUM_ENTRIES)
+		return -EINVAL;
+
+	mutex_lock(&smsm_lock);
+
+	if (!smsm_states) {
+		/* smsm not yet initialized */
+		mutex_unlock(&smsm_lock);
+		return -ENODEV;
+	}
+
+	state = &smsm_states[smsm_entry];
+	list_for_each_entry_safe(cb_info, cb_tmp,
+		&state->callbacks, cb_list) {
+		if (!ret && (cb_info->notify == notify) &&
+			(cb_info->data == data)) {
+			cb_info->mask &= ~mask;
+			ret = 1;
+			if (!cb_info->mask) {
+				/* no mask bits set, remove callback */
+				list_del(&cb_info->cb_list);
+				kfree(cb_info);
+				ret = 2;
+				continue;
+			}
+		}
+		new_mask |= cb_info->mask;
+	}
+
+	/* update interrupt notification mask */
+	if (smsm_entry == SMSM_MODEM_STATE)
+		new_mask |= LEGACY_MODEM_SMSM_MASK;
+
+	if (smsm_info.intr_mask) {
+		unsigned long flags;
+
+		spin_lock_irqsave(&smem_lock, flags);
+		new_mask = (new_mask & ~state->intr_mask_clear)
+				| state->intr_mask_set;
+		__raw_writel(new_mask,
+				SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_APPS));
+		wmb(); /* Make sure memory is visible before proceeding */
+		spin_unlock_irqrestore(&smem_lock, flags);
+	}
+
+	mutex_unlock(&smsm_lock);
+	return ret;
+}
+EXPORT_SYMBOL(smsm_state_cb_deregister);
+
+static int restart_notifier_cb(struct notifier_block *this,
+				  unsigned long code,
+				  void *data);
+
+static struct restart_notifier_block restart_notifiers[] = {
+	{SMD_MODEM, "modem", .nb.notifier_call = restart_notifier_cb},
+	{SMD_Q6, "lpass", .nb.notifier_call = restart_notifier_cb},
+	{SMD_WCNSS, "wcnss", .nb.notifier_call = restart_notifier_cb},
+	{SMD_DSPS, "dsps", .nb.notifier_call = restart_notifier_cb},
+	{SMD_MODEM, "gss", .nb.notifier_call = restart_notifier_cb},
+	{SMD_Q6, "adsp", .nb.notifier_call = restart_notifier_cb},
+	{SMD_DSPS, "slpi", .nb.notifier_call = restart_notifier_cb},
+};
+
+static int restart_notifier_cb(struct notifier_block *this,
+				  unsigned long code,
+				  void *data)
+{
+	remote_spinlock_t *remote_spinlock;
+
+	/*
+	 * Some SMD or SMSM clients assume SMD/SMSM SSR handling will be
+	 * done in the AFTER_SHUTDOWN level.  If this ever changes, extra
+	 * care should be taken to verify no clients are broken.
+	 */
+	if (code == SUBSYS_AFTER_SHUTDOWN) {
+		struct restart_notifier_block *notifier;
+
+		notifier = container_of(this,
+				struct restart_notifier_block, nb);
+		SMD_INFO("%s: ssrestart for processor %d ('%s')\n",
+				__func__, notifier->processor,
+				notifier->name);
+
+		remote_spinlock = smem_get_remote_spinlock();
+		remote_spin_release(remote_spinlock, notifier->processor);
+		remote_spin_release_all(notifier->processor);
+
+		smd_channel_reset(notifier->processor);
+	}
+
+	return NOTIFY_DONE;
+}
+
+/**
+ * smd_post_init() - SMD post initialization
+ * @remote_pid: remote pid that has been initialized.  Ignored when is_legacy=1
+ *
+ * This function is used by the device tree initialization to complete the SMD
+ * init sequence.
+ */
+void smd_post_init(unsigned int remote_pid)
+{
+	smd_channel_probe_now(&remote_info[remote_pid]);
+}
+
+/**
+ * smsm_post_init() - SMSM post initialization
+ * @returns:	0 for success, standard Linux error code otherwise
+ *
+ * This function is used by the legacy and device tree initialization
+ * to complete the SMSM init sequence.
+ */
+int smsm_post_init(void)
+{
+	int ret;
+
+	ret = smsm_init();
+	if (ret) {
+		pr_err("smsm_init() failed ret = %d\n", ret);
+		return ret;
+	}
+	smsm_irq_handler(0, 0);
+
+	return ret;
+}
+
+/**
+ * smd_get_intr_config() - Get interrupt configuration structure
+ * @edge:	edge type identifes local and remote processor
+ * @returns:	pointer to interrupt configuration
+ *
+ * This function returns the interrupt configuration of remote processor
+ * based on the edge type.
+ */
+struct interrupt_config *smd_get_intr_config(uint32_t edge)
+{
+	if (edge >= ARRAY_SIZE(edge_to_pids))
+		return NULL;
+	return &private_intr_config[edge_to_pids[edge].remote_pid];
+}
+
+/**
+ * smd_get_edge_remote_pid() - Get the remote processor ID
+ * @edge:	edge type identifes local and remote processor
+ * @returns:	remote processor ID
+ *
+ * This function returns remote processor ID based on edge type.
+ */
+int smd_edge_to_remote_pid(uint32_t edge)
+{
+	if (edge >= ARRAY_SIZE(edge_to_pids))
+		return -EINVAL;
+	return edge_to_pids[edge].remote_pid;
+}
+
+/**
+ * smd_get_edge_local_pid() - Get the local processor ID
+ * @edge:	edge type identifies local and remote processor
+ * @returns:	local processor ID
+ *
+ * This function returns local processor ID based on edge type.
+ */
+int smd_edge_to_local_pid(uint32_t edge)
+{
+	if (edge >= ARRAY_SIZE(edge_to_pids))
+		return -EINVAL;
+	return edge_to_pids[edge].local_pid;
+}
+
+/**
+ * smd_proc_set_skip_pil() - Mark if the indicated processor is be loaded by PIL
+ * @pid:		the processor id to mark
+ * @skip_pil:		true if @pid cannot by loaded by PIL
+ */
+void smd_proc_set_skip_pil(unsigned int pid, bool skip_pil)
+{
+	if (pid >= NUM_SMD_SUBSYSTEMS) {
+		pr_err("%s: invalid pid:%d\n", __func__, pid);
+		return;
+	}
+	remote_info[pid].skip_pil = skip_pil;
+}
+
+/**
+ * smd_set_edge_subsys_name() - Set the subsystem name
+ * @edge:		edge type identifies local and remote processor
+ * @subsys_name:	pointer to subsystem name
+ *
+ * This function is used to set the subsystem name for given edge type.
+ */
+void smd_set_edge_subsys_name(uint32_t edge, const char *subsys_name)
+{
+	if (edge < ARRAY_SIZE(edge_to_pids))
+		if (subsys_name)
+			strlcpy(edge_to_pids[edge].subsys_name,
+				subsys_name, SMD_MAX_CH_NAME_LEN);
+		else
+			strlcpy(edge_to_pids[edge].subsys_name,
+				"", SMD_MAX_CH_NAME_LEN);
+	else
+		pr_err("%s: Invalid edge type[%d]\n", __func__, edge);
+}
+
+/**
+ * smd_reset_all_edge_subsys_name() - Reset the subsystem name
+ *
+ * This function is used to reset the subsystem name of all edges in
+ * targets where configuration information is available through
+ * device tree.
+ */
+void smd_reset_all_edge_subsys_name(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(edge_to_pids); i++)
+		strlcpy(edge_to_pids[i].subsys_name,
+			"", sizeof(""));
+}
+
+/**
+ * smd_set_edge_initialized() - Set the edge initialized status
+ * @edge:	edge type identifies local and remote processor
+ *
+ * This function set the initialized varibale based on edge type.
+ */
+void smd_set_edge_initialized(uint32_t edge)
+{
+	if (edge < ARRAY_SIZE(edge_to_pids))
+		edge_to_pids[edge].initialized = true;
+	else
+		pr_err("%s: Invalid edge type[%d]\n", __func__, edge);
+}
+
+/**
+ * smd_cfg_smd_intr() - Set the SMD interrupt configuration
+ * @proc:	remote processor ID
+ * @mask:	bit position in IRQ register
+ * @ptr:	IRQ register
+ *
+ * This function is called in Legacy init sequence and used to set
+ * the SMD interrupt configurations for particular processor.
+ */
+void smd_cfg_smd_intr(uint32_t proc, uint32_t mask, void *ptr)
+{
+	private_intr_config[proc].smd.out_bit_pos = mask;
+	private_intr_config[proc].smd.out_base = ptr;
+	private_intr_config[proc].smd.out_offset = 0;
+}
+
+/*
+ * smd_cfg_smsm_intr() -  Set the SMSM interrupt configuration
+ * @proc:	remote processor ID
+ * @mask:	bit position in IRQ register
+ * @ptr:	IRQ register
+ *
+ * This function is called in Legacy init sequence and used to set
+ * the SMSM interrupt configurations for particular processor.
+ */
+void smd_cfg_smsm_intr(uint32_t proc, uint32_t mask, void *ptr)
+{
+	private_intr_config[proc].smsm.out_bit_pos = mask;
+	private_intr_config[proc].smsm.out_base = ptr;
+	private_intr_config[proc].smsm.out_offset = 0;
+}
+
+static __init int modem_restart_late_init(void)
+{
+	int i;
+	void *handle;
+	struct restart_notifier_block *nb;
+
+	for (i = 0; i < ARRAY_SIZE(restart_notifiers); i++) {
+		nb = &restart_notifiers[i];
+		handle = subsys_notif_register_notifier(nb->name, &nb->nb);
+		SMD_DBG("%s: registering notif for '%s', handle=%p\n",
+				__func__, nb->name, handle);
+	}
+
+	return 0;
+}
+late_initcall(modem_restart_late_init);
+
+int __init msm_smd_init(void)
+{
+	static bool registered;
+	int rc;
+	int i;
+
+	if (registered)
+		return 0;
+
+	smd_log_ctx = ipc_log_context_create(NUM_LOG_PAGES, "smd", 0);
+	if (!smd_log_ctx) {
+		pr_err("%s: unable to create SMD logging context\n", __func__);
+		msm_smd_debug_mask = 0;
+	}
+
+	smsm_log_ctx = ipc_log_context_create(NUM_LOG_PAGES, "smsm", 0);
+	if (!smsm_log_ctx) {
+		pr_err("%s: unable to create SMSM logging context\n", __func__);
+		msm_smd_debug_mask = 0;
+	}
+
+	registered = true;
+
+	for (i = 0; i < NUM_SMD_SUBSYSTEMS; ++i) {
+		remote_info[i].remote_pid = i;
+		remote_info[i].free_space = UINT_MAX;
+		INIT_WORK(&remote_info[i].probe_work, smd_channel_probe_worker);
+		INIT_LIST_HEAD(&remote_info[i].ch_list);
+	}
+
+	channel_close_wq = create_singlethread_workqueue("smd_channel_close");
+	if (IS_ERR(channel_close_wq)) {
+		pr_err("%s: create_singlethread_workqueue ENOMEM\n", __func__);
+		return -ENOMEM;
+	}
+
+	rc = msm_smd_driver_register();
+	if (rc) {
+		pr_err("%s: msm_smd_driver register failed %d\n",
+			__func__, rc);
+		return rc;
+	}
+	return 0;
+}
+
+arch_initcall(msm_smd_init);
+
+MODULE_DESCRIPTION("MSM Shared Memory Core");
+MODULE_AUTHOR("Brian Swetland <swetland@google.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/soc/qcom/smd_debug.c b/drivers/soc/qcom/smd_debug.c
new file mode 100644
index 0000000..07c5aeb
--- /dev/null
+++ b/drivers/soc/qcom/smd_debug.c
@@ -0,0 +1,429 @@
+/* drivers/soc/qcom/smd_debug.c
+ *
+ * Copyright (C) 2007 Google, Inc.
+ * Copyright (c) 2009-2014, 2017, The Linux Foundation. All rights reserved.
+ * Author: Brian Swetland <swetland@google.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/debugfs.h>
+#include <linux/list.h>
+#include <linux/ctype.h>
+#include <linux/jiffies.h>
+#include <linux/err.h>
+
+#include <soc/qcom/smem.h>
+
+#include "smd_private.h"
+
+#if defined(CONFIG_DEBUG_FS)
+
+static char *chstate(unsigned int n)
+{
+	switch (n) {
+	case SMD_SS_CLOSED:
+		return "CLOSED";
+	case SMD_SS_OPENING:
+		return "OPENING";
+	case SMD_SS_OPENED:
+		return "OPENED";
+	case SMD_SS_FLUSHING:
+		return "FLUSHING";
+	case SMD_SS_CLOSING:
+		return "CLOSING";
+	case SMD_SS_RESET:
+		return "RESET";
+	case SMD_SS_RESET_OPENING:
+		return "ROPENING";
+	default:
+		return "UNKNOWN";
+	}
+}
+
+static void debug_int_stats(struct seq_file *s)
+{
+	int subsys;
+	struct interrupt_stat *stats = interrupt_stats;
+	const char *subsys_name;
+
+	seq_puts(s,
+		"   Subsystem    | Interrupt ID |    In     |    Out    |\n");
+
+	for (subsys = 0; subsys < NUM_SMD_SUBSYSTEMS; ++subsys) {
+		subsys_name = smd_pid_to_subsystem(subsys);
+		if (!IS_ERR_OR_NULL(subsys_name)) {
+			seq_printf(s, "%-10s %4s |    %9d | %9u | %9u |\n",
+				smd_pid_to_subsystem(subsys), "smd",
+				stats->smd_interrupt_id,
+				stats->smd_in_count,
+				stats->smd_out_count);
+
+			seq_printf(s, "%-10s %4s |    %9d | %9u | %9u |\n",
+				smd_pid_to_subsystem(subsys), "smsm",
+				stats->smsm_interrupt_id,
+				stats->smsm_in_count,
+				stats->smsm_out_count);
+		}
+		++stats;
+	}
+}
+
+static void debug_int_stats_reset(struct seq_file *s)
+{
+	int subsys;
+	struct interrupt_stat *stats = interrupt_stats;
+
+	seq_puts(s, "Resetting interrupt stats.\n");
+
+	for (subsys = 0; subsys < NUM_SMD_SUBSYSTEMS; ++subsys) {
+		stats->smd_in_count = 0;
+		stats->smd_out_count = 0;
+		stats->smsm_in_count = 0;
+		stats->smsm_out_count = 0;
+		++stats;
+	}
+}
+
+/* NNV: revist, it may not be smd version */
+static void debug_read_smd_version(struct seq_file *s)
+{
+	uint32_t *smd_ver;
+	uint32_t n, version;
+
+	smd_ver = smem_find(SMEM_VERSION_SMD, 32 * sizeof(uint32_t),
+							0, SMEM_ANY_HOST_FLAG);
+
+	if (smd_ver)
+		for (n = 0; n < 32; n++) {
+			version = smd_ver[n];
+			seq_printf(s, "entry %d: %d.%d\n", n,
+				       version >> 16,
+				       version & 0xffff);
+		}
+}
+
+/**
+ * pid_to_str - Convert a numeric processor id value into a human readable
+ *		string value.
+ *
+ * @pid: the processor id to convert
+ * @returns: a string representation of @pid
+ */
+static char *pid_to_str(int pid)
+{
+	switch (pid) {
+	case SMD_APPS:
+		return "APPS";
+	case SMD_MODEM:
+		return "MDMSW";
+	case SMD_Q6:
+		return "ADSP";
+	case SMD_TZ:
+		return "TZ";
+	case SMD_WCNSS:
+		return "WCNSS";
+	case SMD_MODEM_Q6_FW:
+		return "MDMFW";
+	case SMD_RPM:
+		return "RPM";
+	default:
+		return "???";
+	}
+}
+
+/**
+ * print_half_ch_state - Print the state of half of a SMD channel in a human
+ *			readable format.
+ *
+ * @s: the sequential file to print to
+ * @half_ch: half of a SMD channel that should have its state printed
+ * @half_ch_funcs: the relevant channel access functions for @half_ch
+ * @size: size of the fifo in bytes associated with @half_ch
+ * @proc: the processor id that owns the part of the SMD channel associated with
+ *		@half_ch
+ * @is_restricted: true if memory access is restricted
+ */
+static void print_half_ch_state(struct seq_file *s,
+				void *half_ch,
+				struct smd_half_channel_access *half_ch_funcs,
+				unsigned int size,
+				int proc,
+				bool is_restricted)
+{
+	seq_printf(s, "%-5s|", pid_to_str(proc));
+
+	if (!is_restricted) {
+		seq_printf(s, "%-7s|0x%05X|0x%05X|0x%05X",
+			chstate(half_ch_funcs->get_state(half_ch)),
+			size,
+			half_ch_funcs->get_tail(half_ch),
+			half_ch_funcs->get_head(half_ch));
+		seq_printf(s, "|%c%c%c%c%c%c%c%c|0x%05X",
+			half_ch_funcs->get_fDSR(half_ch) ? 'D' : 'd',
+			half_ch_funcs->get_fCTS(half_ch) ? 'C' : 'c',
+			half_ch_funcs->get_fCD(half_ch) ? 'C' : 'c',
+			half_ch_funcs->get_fRI(half_ch) ? 'I' : 'i',
+			half_ch_funcs->get_fHEAD(half_ch) ? 'W' : 'w',
+			half_ch_funcs->get_fTAIL(half_ch) ? 'R' : 'r',
+			half_ch_funcs->get_fSTATE(half_ch) ? 'S' : 's',
+			half_ch_funcs->get_fBLOCKREADINTR(half_ch) ? 'B' : 'b',
+			(half_ch_funcs->get_head(half_ch) -
+				half_ch_funcs->get_tail(half_ch)) & (size - 1));
+	} else {
+		seq_puts(s, " Access Restricted");
+	}
+}
+
+/**
+ * smd_xfer_type_to_str - Convert a numeric transfer type value into a human
+ *		readable string value.
+ *
+ * @xfer_type: the processor id to convert
+ * @returns: a string representation of @xfer_type
+ */
+static char *smd_xfer_type_to_str(uint32_t xfer_type)
+{
+	if (xfer_type == 1)
+		return "S"; /* streaming type */
+	else if (xfer_type == 2)
+		return "P"; /* packet type */
+	else
+		return "L"; /* legacy type */
+}
+
+/**
+ * print_smd_ch_table - Print the current state of every valid SMD channel in a
+ *			specific SMD channel allocation table to a human
+ *			readable formatted output.
+ *
+ * @s: the sequential file to print to
+ * @tbl: a valid pointer to the channel allocation table to print from
+ * @num_tbl_entries: total number of entries in the table referenced by @tbl
+ * @ch_base_id: the SMEM item id corresponding to the array of channel
+ *		structures for the channels found in @tbl
+ * @fifo_base_id: the SMEM item id corresponding to the array of channel fifos
+ *		for the channels found in @tbl
+ * @pid: processor id to use for any SMEM operations
+ * @flags: flags to use for any SMEM operations
+ */
+static void print_smd_ch_table(struct seq_file *s,
+				struct smd_alloc_elm *tbl,
+				unsigned int num_tbl_entries,
+				unsigned int ch_base_id,
+				unsigned int fifo_base_id,
+				unsigned int pid,
+				unsigned int flags)
+{
+	void *half_ch;
+	unsigned int half_ch_size;
+	uint32_t ch_type;
+	void *buffer;
+	unsigned int buffer_size;
+	int n;
+	bool is_restricted;
+
+/*
+ * formatted, human readable channel state output, ie:
+ID|CHANNEL NAME       |T|PROC |STATE  |FIFO SZ|RDPTR  |WRPTR  |FLAGS   |DATAPEN
+-------------------------------------------------------------------------------
+00|DS                 |S|APPS |CLOSED |0x02000|0x00000|0x00000|dcCiwrsb|0x00000
+  |                   | |MDMSW|OPENING|0x02000|0x00000|0x00000|dcCiwrsb|0x00000
+-------------------------------------------------------------------------------
+ */
+
+	seq_printf(s, "%2s|%-19s|%1s|%-5s|%-7s|%-7s|%-7s|%-7s|%-8s|%-7s\n",
+								"ID",
+								"CHANNEL NAME",
+								"T",
+								"PROC",
+								"STATE",
+								"FIFO SZ",
+								"RDPTR",
+								"WRPTR",
+								"FLAGS",
+								"DATAPEN");
+	seq_puts(s,
+		"-------------------------------------------------------------------------------\n");
+	for (n = 0; n < num_tbl_entries; ++n) {
+		if (strlen(tbl[n].name) == 0)
+			continue;
+
+		seq_printf(s, "%2u|%-19s|%s|", tbl[n].cid, tbl[n].name,
+			smd_xfer_type_to_str(SMD_XFER_TYPE(tbl[n].type)));
+		ch_type = SMD_CHANNEL_TYPE(tbl[n].type);
+
+
+		if (smd_edge_to_remote_pid(ch_type) == SMD_RPM &&
+		   smd_edge_to_local_pid(ch_type) != SMD_APPS)
+			is_restricted = true;
+		else
+			is_restricted = false;
+
+		if (is_word_access_ch(ch_type))
+			half_ch_size =
+				sizeof(struct smd_half_channel_word_access);
+		else
+			half_ch_size = sizeof(struct smd_half_channel);
+
+		half_ch = smem_find(ch_base_id + n, 2 * half_ch_size,
+								pid, flags);
+		buffer = smem_get_entry(fifo_base_id + n, &buffer_size,
+								pid, flags);
+		if (half_ch && buffer)
+			print_half_ch_state(s,
+					half_ch,
+					get_half_ch_funcs(ch_type),
+					buffer_size / 2,
+					smd_edge_to_local_pid(ch_type),
+					is_restricted);
+
+		seq_puts(s, "\n");
+		seq_printf(s, "%2s|%-19s|%1s|", "", "", "");
+
+		if (half_ch && buffer)
+			print_half_ch_state(s,
+					half_ch + half_ch_size,
+					get_half_ch_funcs(ch_type),
+					buffer_size / 2,
+					smd_edge_to_remote_pid(ch_type),
+					is_restricted);
+
+		seq_puts(s, "\n");
+		seq_puts(s,
+			"-------------------------------------------------------------------------------\n");
+	}
+}
+
+/**
+ * debug_ch - Print the current state of every valid SMD channel in a human
+ *		readable formatted table.
+ *
+ * @s: the sequential file to print to
+ */
+static void debug_ch(struct seq_file *s)
+{
+	struct smd_alloc_elm *tbl;
+	struct smd_alloc_elm *default_pri_tbl;
+	struct smd_alloc_elm *default_sec_tbl;
+	unsigned int tbl_size;
+	int i;
+
+	tbl = smem_get_entry(ID_CH_ALLOC_TBL, &tbl_size, 0, SMEM_ANY_HOST_FLAG);
+	default_pri_tbl = tbl;
+
+	if (!tbl) {
+		seq_puts(s, "Channel allocation table not found\n");
+		return;
+	}
+
+	if (IS_ERR(tbl) && PTR_ERR(tbl) == -EPROBE_DEFER) {
+		seq_puts(s, "SMEM is not initialized\n");
+		return;
+	}
+
+	seq_puts(s, "Primary allocation table:\n");
+	print_smd_ch_table(s, tbl, tbl_size / sizeof(*tbl), ID_SMD_CHANNELS,
+							SMEM_SMD_FIFO_BASE_ID,
+							0,
+							SMEM_ANY_HOST_FLAG);
+
+	tbl = smem_get_entry(SMEM_CHANNEL_ALLOC_TBL_2, &tbl_size, 0,
+							SMEM_ANY_HOST_FLAG);
+	default_sec_tbl = tbl;
+	if (tbl) {
+		seq_puts(s, "\n\nSecondary allocation table:\n");
+		print_smd_ch_table(s, tbl, tbl_size / sizeof(*tbl),
+						SMEM_SMD_BASE_ID_2,
+						SMEM_SMD_FIFO_BASE_ID_2,
+						0,
+						SMEM_ANY_HOST_FLAG);
+	}
+
+	for (i = 1; i < NUM_SMD_SUBSYSTEMS; ++i) {
+		tbl = smem_get_entry(ID_CH_ALLOC_TBL, &tbl_size, i, 0);
+		if (tbl && tbl != default_pri_tbl) {
+			seq_puts(s, "\n\n");
+			seq_printf(s, "%s <-> %s Primary allocation table:\n",
+							pid_to_str(SMD_APPS),
+							pid_to_str(i));
+			print_smd_ch_table(s, tbl, tbl_size / sizeof(*tbl),
+							ID_SMD_CHANNELS,
+							SMEM_SMD_FIFO_BASE_ID,
+							i,
+							0);
+		}
+
+		tbl = smem_get_entry(SMEM_CHANNEL_ALLOC_TBL_2, &tbl_size, i, 0);
+		if (tbl && tbl != default_sec_tbl) {
+			seq_puts(s, "\n\n");
+			seq_printf(s, "%s <-> %s Secondary allocation table:\n",
+							pid_to_str(SMD_APPS),
+							pid_to_str(i));
+			print_smd_ch_table(s, tbl, tbl_size / sizeof(*tbl),
+						SMEM_SMD_BASE_ID_2,
+						SMEM_SMD_FIFO_BASE_ID_2,
+						i,
+						0);
+		}
+	}
+}
+
+static int debugfs_show(struct seq_file *s, void *data)
+{
+	void (*show)(struct seq_file *) = s->private;
+
+	show(s);
+
+	return 0;
+}
+
+static int debug_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, debugfs_show, inode->i_private);
+}
+
+static const struct file_operations debug_ops = {
+	.open = debug_open,
+	.release = single_release,
+	.read = seq_read,
+	.llseek = seq_lseek,
+};
+
+static void debug_create(const char *name, umode_t mode,
+			 struct dentry *dent,
+			 void (*show)(struct seq_file *))
+{
+	struct dentry *file;
+
+	file = debugfs_create_file(name, mode, dent, show, &debug_ops);
+	if (!file)
+		pr_err("%s: unable to create file '%s'\n", __func__, name);
+}
+
+static int __init smd_debugfs_init(void)
+{
+	struct dentry *dent;
+
+	dent = debugfs_create_dir("smd", 0);
+	if (IS_ERR(dent))
+		return PTR_ERR(dent);
+
+	debug_create("ch", 0444, dent, debug_ch);
+	debug_create("version", 0444, dent, debug_read_smd_version);
+	debug_create("int_stats", 0444, dent, debug_int_stats);
+	debug_create("int_stats_reset", 0444, dent, debug_int_stats_reset);
+
+	return 0;
+}
+
+late_initcall(smd_debugfs_init);
+#endif
diff --git a/drivers/soc/qcom/smd_init_dt.c b/drivers/soc/qcom/smd_init_dt.c
new file mode 100644
index 0000000..f14461f
--- /dev/null
+++ b/drivers/soc/qcom/smd_init_dt.c
@@ -0,0 +1,343 @@
+/* drivers/soc/qcom/smd_init_dt.c
+ *
+ * Copyright (c) 2013-2015, 2017, The Linux Foundation. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ipc_logging.h>
+
+#include "smd_private.h"
+
+#define MODULE_NAME "msm_smd"
+#define IPC_LOG(level, x...) do { \
+	if (smd_log_ctx) \
+		ipc_log_string(smd_log_ctx, x); \
+	else \
+		printk(level x); \
+	} while (0)
+
+#if defined(CONFIG_MSM_SMD_DEBUG)
+#define SMD_DBG(x...) do {				\
+		if (msm_smd_debug_mask & MSM_SMD_DEBUG) \
+			IPC_LOG(KERN_DEBUG, x);		\
+	} while (0)
+
+#define SMSM_DBG(x...) do {					\
+		if (msm_smd_debug_mask & MSM_SMSM_DEBUG)	\
+			IPC_LOG(KERN_DEBUG, x);		\
+	} while (0)
+#else
+#define SMD_DBG(x...) do { } while (0)
+#define SMSM_DBG(x...) do { } while (0)
+#endif
+
+static DEFINE_MUTEX(smd_probe_lock);
+static int first_probe_done;
+
+static int msm_smsm_probe(struct platform_device *pdev)
+{
+	uint32_t edge;
+	char *key;
+	int ret;
+	uint32_t irq_offset;
+	uint32_t irq_bitmask;
+	uint32_t irq_line;
+	struct interrupt_config_item *private_irq;
+	struct device_node *node;
+	void *irq_out_base;
+	resource_size_t irq_out_size;
+	struct platform_device *parent_pdev;
+	struct resource *r;
+	struct interrupt_config *private_intr_config;
+	uint32_t remote_pid;
+
+	node = pdev->dev.of_node;
+
+	if (!pdev->dev.parent) {
+		pr_err("%s: missing link to parent device\n", __func__);
+		return -ENODEV;
+	}
+
+	parent_pdev = to_platform_device(pdev->dev.parent);
+
+	key = "irq-reg-base";
+	r = platform_get_resource_byname(parent_pdev, IORESOURCE_MEM, key);
+	if (!r)
+		goto missing_key;
+	irq_out_size = resource_size(r);
+	irq_out_base = ioremap_nocache(r->start, irq_out_size);
+	if (!irq_out_base) {
+		pr_err("%s: ioremap_nocache() of irq_out_base addr:%pr size:%pr\n",
+				__func__, &r->start, &irq_out_size);
+		return -ENOMEM;
+	}
+	SMSM_DBG("%s: %s = %p", __func__, key, irq_out_base);
+
+	key = "qcom,smsm-edge";
+	ret = of_property_read_u32(node, key, &edge);
+	if (ret)
+		goto missing_key;
+	SMSM_DBG("%s: %s = %d", __func__, key, edge);
+
+	key = "qcom,smsm-irq-offset";
+	ret = of_property_read_u32(node, key, &irq_offset);
+	if (ret)
+		goto missing_key;
+	SMSM_DBG("%s: %s = %x", __func__, key, irq_offset);
+
+	key = "qcom,smsm-irq-bitmask";
+	ret = of_property_read_u32(node, key, &irq_bitmask);
+	if (ret)
+		goto missing_key;
+	SMSM_DBG("%s: %s = %x", __func__, key, irq_bitmask);
+
+	key = "interrupts";
+	irq_line = irq_of_parse_and_map(node, 0);
+	if (!irq_line)
+		goto missing_key;
+	SMSM_DBG("%s: %s = %d", __func__, key, irq_line);
+
+	private_intr_config = smd_get_intr_config(edge);
+	if (!private_intr_config) {
+		pr_err("%s: invalid edge\n", __func__);
+		iounmap(irq_out_base);
+		return -ENODEV;
+	}
+	private_irq = &private_intr_config->smsm;
+	private_irq->out_bit_pos = irq_bitmask;
+	private_irq->out_offset = irq_offset;
+	private_irq->out_base = irq_out_base;
+	private_irq->irq_id = irq_line;
+	remote_pid = smd_edge_to_remote_pid(edge);
+	interrupt_stats[remote_pid].smsm_interrupt_id = irq_line;
+
+	ret = request_irq(irq_line,
+				private_irq->irq_handler,
+				IRQF_TRIGGER_RISING | IRQF_NO_SUSPEND,
+				node->name,
+				NULL);
+	if (ret < 0) {
+		pr_err("%s: request_irq() failed on %d\n", __func__, irq_line);
+		iounmap(irq_out_base);
+		return ret;
+	}
+	ret = enable_irq_wake(irq_line);
+	if (ret < 0)
+		pr_err("%s: enable_irq_wake() failed on %d\n", __func__,
+			irq_line);
+
+	ret = smsm_post_init();
+	if (ret) {
+		pr_err("smd_post_init() failed ret=%d\n", ret);
+		iounmap(irq_out_base);
+		free_irq(irq_line, NULL);
+		return ret;
+	}
+
+	return 0;
+
+missing_key:
+	pr_err("%s: missing key: %s", __func__, key);
+	return -ENODEV;
+}
+
+static int msm_smd_probe(struct platform_device *pdev)
+{
+	uint32_t edge;
+	char *key;
+	int ret;
+	uint32_t irq_offset;
+	uint32_t irq_bitmask;
+	uint32_t irq_line;
+	const char *subsys_name;
+	struct interrupt_config_item *private_irq;
+	struct device_node *node;
+	void *irq_out_base;
+	resource_size_t irq_out_size;
+	struct platform_device *parent_pdev;
+	struct resource *r;
+	struct interrupt_config *private_intr_config;
+	uint32_t remote_pid;
+	bool skip_pil;
+
+	node = pdev->dev.of_node;
+
+	if (!pdev->dev.parent) {
+		pr_err("%s: missing link to parent device\n", __func__);
+		return -ENODEV;
+	}
+
+	mutex_lock(&smd_probe_lock);
+	if (!first_probe_done) {
+		smd_reset_all_edge_subsys_name();
+		first_probe_done = 1;
+	}
+	mutex_unlock(&smd_probe_lock);
+
+	parent_pdev = to_platform_device(pdev->dev.parent);
+
+	key = "irq-reg-base";
+	r = platform_get_resource_byname(parent_pdev, IORESOURCE_MEM, key);
+	if (!r)
+		goto missing_key;
+	irq_out_size = resource_size(r);
+	irq_out_base = ioremap_nocache(r->start, irq_out_size);
+	if (!irq_out_base) {
+		pr_err("%s: ioremap_nocache() of irq_out_base addr:%pr size:%pr\n",
+				__func__, &r->start, &irq_out_size);
+		return -ENOMEM;
+	}
+	SMD_DBG("%s: %s = %p", __func__, key, irq_out_base);
+
+	key = "qcom,smd-edge";
+	ret = of_property_read_u32(node, key, &edge);
+	if (ret)
+		goto missing_key;
+	SMD_DBG("%s: %s = %d", __func__, key, edge);
+
+	key = "qcom,smd-irq-offset";
+	ret = of_property_read_u32(node, key, &irq_offset);
+	if (ret)
+		goto missing_key;
+	SMD_DBG("%s: %s = %x", __func__, key, irq_offset);
+
+	key = "qcom,smd-irq-bitmask";
+	ret = of_property_read_u32(node, key, &irq_bitmask);
+	if (ret)
+		goto missing_key;
+	SMD_DBG("%s: %s = %x", __func__, key, irq_bitmask);
+
+	key = "interrupts";
+	irq_line = irq_of_parse_and_map(node, 0);
+	if (!irq_line)
+		goto missing_key;
+	SMD_DBG("%s: %s = %d", __func__, key, irq_line);
+
+	key = "label";
+	subsys_name = of_get_property(node, key, NULL);
+	SMD_DBG("%s: %s = %s", __func__, key, subsys_name);
+	/*
+	 * Backwards compatibility.  Although label is required, some DTs may
+	 * still list the legacy pil-string.  Sanely handle pil-string.
+	 */
+	if (!subsys_name) {
+		pr_warn("msm_smd: Missing required property - label. Using legacy parsing\n");
+		key = "qcom,pil-string";
+		subsys_name = of_get_property(node, key, NULL);
+		SMD_DBG("%s: %s = %s", __func__, key, subsys_name);
+		if (subsys_name)
+			skip_pil = false;
+		else
+			skip_pil = true;
+	} else {
+		key = "qcom,not-loadable";
+		skip_pil = of_property_read_bool(node, key);
+		SMD_DBG("%s: %s = %d\n", __func__, key, skip_pil);
+	}
+
+	private_intr_config = smd_get_intr_config(edge);
+	if (!private_intr_config) {
+		pr_err("%s: invalid edge\n", __func__);
+		return -ENODEV;
+	}
+	private_irq = &private_intr_config->smd;
+	private_irq->out_bit_pos = irq_bitmask;
+	private_irq->out_offset = irq_offset;
+	private_irq->out_base = irq_out_base;
+	private_irq->irq_id = irq_line;
+	remote_pid = smd_edge_to_remote_pid(edge);
+	interrupt_stats[remote_pid].smd_interrupt_id = irq_line;
+
+	ret = request_irq(irq_line,
+			private_irq->irq_handler,
+			IRQF_TRIGGER_RISING | IRQF_NO_SUSPEND | IRQF_SHARED,
+			node->name,
+			&pdev->dev);
+	if (ret < 0) {
+		pr_err("%s: request_irq() failed on %d\n", __func__, irq_line);
+		return ret;
+	}
+
+	ret = enable_irq_wake(irq_line);
+	if (ret < 0)
+		pr_err("%s: enable_irq_wake() failed on %d\n", __func__,
+				irq_line);
+
+	smd_set_edge_subsys_name(edge, subsys_name);
+	smd_proc_set_skip_pil(smd_edge_to_remote_pid(edge), skip_pil);
+
+	smd_set_edge_initialized(edge);
+	smd_post_init(remote_pid);
+	return 0;
+
+missing_key:
+	pr_err("%s: missing key: %s", __func__, key);
+	return -ENODEV;
+}
+
+static const struct of_device_id msm_smd_match_table[] = {
+	{ .compatible = "qcom,smd" },
+	{},
+};
+
+static struct platform_driver msm_smd_driver = {
+	.probe = msm_smd_probe,
+	.driver = {
+		.name = MODULE_NAME,
+		.owner = THIS_MODULE,
+		.of_match_table = msm_smd_match_table,
+	},
+};
+
+static const struct of_device_id msm_smsm_match_table[] = {
+	{ .compatible = "qcom,smsm" },
+	{},
+};
+
+static struct platform_driver msm_smsm_driver = {
+	.probe = msm_smsm_probe,
+	.driver = {
+		.name = "msm_smsm",
+		.owner = THIS_MODULE,
+		.of_match_table = msm_smsm_match_table,
+	},
+};
+
+int msm_smd_driver_register(void)
+{
+	int rc;
+
+	rc = platform_driver_register(&msm_smd_driver);
+	if (rc) {
+		pr_err("%s: smd_driver register failed %d\n",
+			__func__, rc);
+		return rc;
+	}
+
+	rc = platform_driver_register(&msm_smsm_driver);
+	if (rc) {
+		pr_err("%s: msm_smsm_driver register failed %d\n",
+			__func__, rc);
+		return rc;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(msm_smd_driver_register);
+
+MODULE_DESCRIPTION("MSM SMD Device Tree Init");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/soc/qcom/smd_private.c b/drivers/soc/qcom/smd_private.c
new file mode 100644
index 0000000..a554696
--- /dev/null
+++ b/drivers/soc/qcom/smd_private.c
@@ -0,0 +1,336 @@
+/* Copyright (c) 2012, 2017, 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
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "smd_private.h"
+
+void set_state(volatile void __iomem *half_channel, unsigned int data)
+{
+	((struct smd_half_channel __force *)(half_channel))->state = data;
+}
+
+unsigned int get_state(volatile void __iomem *half_channel)
+{
+	return ((struct smd_half_channel __force *)(half_channel))->state;
+}
+
+void set_fDSR(volatile void __iomem *half_channel, unsigned char data)
+{
+	((struct smd_half_channel __force *)(half_channel))->fDSR = data;
+}
+
+unsigned int get_fDSR(volatile void __iomem *half_channel)
+{
+	return ((struct smd_half_channel __force *)(half_channel))->fDSR;
+}
+
+void set_fCTS(volatile void __iomem *half_channel, unsigned char data)
+{
+	((struct smd_half_channel __force *)(half_channel))->fCTS = data;
+}
+
+unsigned int get_fCTS(volatile void __iomem *half_channel)
+{
+	return ((struct smd_half_channel __force *)(half_channel))->fCTS;
+}
+
+void set_fCD(volatile void __iomem *half_channel, unsigned char data)
+{
+	((struct smd_half_channel __force *)(half_channel))->fCD = data;
+}
+
+unsigned int get_fCD(volatile void __iomem *half_channel)
+{
+	return ((struct smd_half_channel __force *)(half_channel))->fCD;
+}
+
+void set_fRI(volatile void __iomem *half_channel, unsigned char data)
+{
+	((struct smd_half_channel __force *)(half_channel))->fRI = data;
+}
+
+unsigned int get_fRI(volatile void __iomem *half_channel)
+{
+	return ((struct smd_half_channel __force *)(half_channel))->fRI;
+}
+
+void set_fHEAD(volatile void __iomem *half_channel, unsigned char data)
+{
+	((struct smd_half_channel __force *)(half_channel))->fHEAD = data;
+}
+
+unsigned int get_fHEAD(volatile void __iomem *half_channel)
+{
+	return ((struct smd_half_channel __force *)(half_channel))->fHEAD;
+}
+
+void set_fTAIL(volatile void __iomem *half_channel, unsigned char data)
+{
+	((struct smd_half_channel __force *)(half_channel))->fTAIL = data;
+}
+
+unsigned int get_fTAIL(volatile void __iomem *half_channel)
+{
+	return ((struct smd_half_channel __force *)(half_channel))->fTAIL;
+}
+
+void set_fSTATE(volatile void __iomem *half_channel, unsigned char data)
+{
+	((struct smd_half_channel __force *)(half_channel))->fSTATE = data;
+}
+
+unsigned int get_fSTATE(volatile void __iomem *half_channel)
+{
+	return ((struct smd_half_channel __force *)(half_channel))->fSTATE;
+}
+
+void set_fBLOCKREADINTR(volatile void __iomem *half_channel, unsigned char data)
+{
+	((struct smd_half_channel __force *)
+				(half_channel))->fBLOCKREADINTR = data;
+}
+
+unsigned int get_fBLOCKREADINTR(volatile void __iomem *half_channel)
+{
+	return ((struct smd_half_channel __force *)
+				(half_channel))->fBLOCKREADINTR;
+}
+
+void set_tail(volatile void __iomem *half_channel, unsigned int data)
+{
+	((struct smd_half_channel __force *)(half_channel))->tail = data;
+}
+
+unsigned int get_tail(volatile void __iomem *half_channel)
+{
+	return ((struct smd_half_channel __force *)(half_channel))->tail;
+}
+
+void set_head(volatile void __iomem *half_channel, unsigned int data)
+{
+	((struct smd_half_channel __force *)(half_channel))->head = data;
+}
+
+unsigned int get_head(volatile void __iomem *half_channel)
+{
+	return ((struct smd_half_channel __force *)(half_channel))->head;
+}
+
+void set_state_word_access(volatile void __iomem *half_channel,
+				unsigned int data)
+{
+	((struct smd_half_channel_word_access __force *)
+					(half_channel))->state = data;
+}
+
+unsigned int get_state_word_access(volatile void __iomem *half_channel)
+{
+	return ((struct smd_half_channel_word_access __force *)
+					(half_channel))->state;
+}
+
+void set_fDSR_word_access(volatile void __iomem *half_channel,
+						unsigned char data)
+{
+	((struct smd_half_channel_word_access __force *)
+					(half_channel))->fDSR = data;
+}
+
+unsigned int get_fDSR_word_access(volatile void __iomem *half_channel)
+{
+	return ((struct smd_half_channel_word_access __force *)
+					(half_channel))->fDSR;
+}
+
+void set_fCTS_word_access(volatile void __iomem *half_channel,
+						unsigned char data)
+{
+	((struct smd_half_channel_word_access __force *)
+					(half_channel))->fCTS = data;
+}
+
+unsigned int get_fCTS_word_access(volatile void __iomem *half_channel)
+{
+	return ((struct smd_half_channel_word_access __force *)
+					(half_channel))->fCTS;
+}
+
+void set_fCD_word_access(volatile void __iomem *half_channel,
+						unsigned char data)
+{
+	((struct smd_half_channel_word_access __force *)
+					(half_channel))->fCD = data;
+}
+
+unsigned int get_fCD_word_access(volatile void __iomem *half_channel)
+{
+	return ((struct smd_half_channel_word_access __force *)
+					(half_channel))->fCD;
+}
+
+void set_fRI_word_access(volatile void __iomem *half_channel,
+						unsigned char data)
+{
+	((struct smd_half_channel_word_access __force *)
+					(half_channel))->fRI = data;
+}
+
+unsigned int get_fRI_word_access(volatile void __iomem *half_channel)
+{
+	return ((struct smd_half_channel_word_access __force *)
+					(half_channel))->fRI;
+}
+
+void set_fHEAD_word_access(volatile void __iomem *half_channel,
+						unsigned char data)
+{
+	((struct smd_half_channel_word_access __force *)
+					(half_channel))->fHEAD = data;
+}
+
+unsigned int get_fHEAD_word_access(volatile void __iomem *half_channel)
+{
+	return ((struct smd_half_channel_word_access __force *)
+					(half_channel))->fHEAD;
+}
+
+void set_fTAIL_word_access(volatile void __iomem *half_channel,
+						unsigned char data)
+{
+	((struct smd_half_channel_word_access __force *)
+					(half_channel))->fTAIL = data;
+}
+
+unsigned int get_fTAIL_word_access(volatile void __iomem *half_channel)
+{
+	return ((struct smd_half_channel_word_access __force *)
+					(half_channel))->fTAIL;
+}
+
+void set_fSTATE_word_access(volatile void __iomem *half_channel,
+						unsigned char data)
+{
+	((struct smd_half_channel_word_access __force *)
+					(half_channel))->fSTATE = data;
+}
+
+unsigned int get_fSTATE_word_access(volatile void __iomem *half_channel)
+{
+	return ((struct smd_half_channel_word_access __force *)
+					(half_channel))->fSTATE;
+}
+
+void set_fBLOCKREADINTR_word_access(volatile void __iomem *half_channel,
+							unsigned char data)
+{
+	((struct smd_half_channel_word_access __force *)
+					(half_channel))->fBLOCKREADINTR = data;
+}
+
+unsigned int get_fBLOCKREADINTR_word_access(volatile void __iomem *half_channel)
+{
+	return ((struct smd_half_channel_word_access __force *)
+					(half_channel))->fBLOCKREADINTR;
+}
+
+void set_tail_word_access(volatile void __iomem *half_channel,
+				unsigned int data)
+{
+	((struct smd_half_channel_word_access __force *)
+					(half_channel))->tail = data;
+}
+
+unsigned int get_tail_word_access(volatile void __iomem *half_channel)
+{
+	return ((struct smd_half_channel_word_access __force *)
+					(half_channel))->tail;
+}
+
+void set_head_word_access(volatile void __iomem *half_channel,
+				unsigned int data)
+{
+	((struct smd_half_channel_word_access __force *)
+					(half_channel))->head = data;
+}
+
+unsigned int get_head_word_access(volatile void __iomem *half_channel)
+{
+	return ((struct smd_half_channel_word_access __force *)
+					(half_channel))->head;
+}
+
+int is_word_access_ch(unsigned int ch_type)
+{
+	if (ch_type == SMD_APPS_RPM || ch_type == SMD_MODEM_RPM ||
+		ch_type == SMD_QDSP_RPM || ch_type == SMD_WCNSS_RPM ||
+		ch_type == SMD_TZ_RPM)
+		return 1;
+	else
+		return 0;
+}
+
+struct smd_half_channel_access *get_half_ch_funcs(unsigned int ch_type)
+{
+	static struct smd_half_channel_access byte_access = {
+		.set_state = set_state,
+		.get_state = get_state,
+		.set_fDSR = set_fDSR,
+		.get_fDSR = get_fDSR,
+		.set_fCTS = set_fCTS,
+		.get_fCTS = get_fCTS,
+		.set_fCD = set_fCD,
+		.get_fCD = get_fCD,
+		.set_fRI = set_fRI,
+		.get_fRI = get_fRI,
+		.set_fHEAD = set_fHEAD,
+		.get_fHEAD = get_fHEAD,
+		.set_fTAIL = set_fTAIL,
+		.get_fTAIL = get_fTAIL,
+		.set_fSTATE = set_fSTATE,
+		.get_fSTATE = get_fSTATE,
+		.set_fBLOCKREADINTR = set_fBLOCKREADINTR,
+		.get_fBLOCKREADINTR = get_fBLOCKREADINTR,
+		.set_tail = set_tail,
+		.get_tail = get_tail,
+		.set_head = set_head,
+		.get_head = get_head,
+	};
+	static struct smd_half_channel_access word_access = {
+		.set_state = set_state_word_access,
+		.get_state = get_state_word_access,
+		.set_fDSR = set_fDSR_word_access,
+		.get_fDSR = get_fDSR_word_access,
+		.set_fCTS = set_fCTS_word_access,
+		.get_fCTS = get_fCTS_word_access,
+		.set_fCD = set_fCD_word_access,
+		.get_fCD = get_fCD_word_access,
+		.set_fRI = set_fRI_word_access,
+		.get_fRI = get_fRI_word_access,
+		.set_fHEAD = set_fHEAD_word_access,
+		.get_fHEAD = get_fHEAD_word_access,
+		.set_fTAIL = set_fTAIL_word_access,
+		.get_fTAIL = get_fTAIL_word_access,
+		.set_fSTATE = set_fSTATE_word_access,
+		.get_fSTATE = get_fSTATE_word_access,
+		.set_fBLOCKREADINTR = set_fBLOCKREADINTR_word_access,
+		.get_fBLOCKREADINTR = get_fBLOCKREADINTR_word_access,
+		.set_tail = set_tail_word_access,
+		.get_tail = get_tail_word_access,
+		.set_head = set_head_word_access,
+		.get_head = get_head_word_access,
+	};
+
+	if (is_word_access_ch(ch_type))
+		return &word_access;
+	else
+		return &byte_access;
+}
+
diff --git a/drivers/soc/qcom/smd_private.h b/drivers/soc/qcom/smd_private.h
new file mode 100644
index 0000000..98d0bde
--- /dev/null
+++ b/drivers/soc/qcom/smd_private.h
@@ -0,0 +1,246 @@
+/* drivers/soc/qcom/smd_private.h
+ *
+ * Copyright (C) 2007 Google, Inc.
+ * Copyright (c) 2007-2014, 2017, The Linux Foundation. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+#ifndef _ARCH_ARM_MACH_MSM_MSM_SMD_PRIVATE_H_
+#define _ARCH_ARM_MACH_MSM_MSM_SMD_PRIVATE_H_
+
+#include <linux/types.h>
+#include <linux/spinlock.h>
+#include <linux/errno.h>
+#include <linux/remote_spinlock.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+
+#include <soc/qcom/smd.h>
+#include <soc/qcom/smsm.h>
+
+#define VERSION_QDSP6     4
+#define VERSION_APPS_SBL  6
+#define VERSION_MODEM_SBL 7
+#define VERSION_APPS      8
+#define VERSION_MODEM     9
+#define VERSION_DSPS      10
+
+#define ID_SMD_CHANNELS SMEM_SMD_BASE_ID
+#define ID_SHARED_STATE SMEM_SMSM_SHARED_STATE
+#define ID_CH_ALLOC_TBL SMEM_CHANNEL_ALLOC_TBL
+
+#define SMD_SS_CLOSED            0x00000000
+#define SMD_SS_OPENING           0x00000001
+#define SMD_SS_OPENED            0x00000002
+#define SMD_SS_FLUSHING          0x00000003
+#define SMD_SS_CLOSING           0x00000004
+#define SMD_SS_RESET             0x00000005
+#define SMD_SS_RESET_OPENING     0x00000006
+
+#define SMD_HEADER_SIZE          20
+
+/* 'type' field of smd_alloc_elm structure
+ * has the following breakup
+ * bits 0-7   -> channel type
+ * bits 8-11  -> xfer type
+ * bits 12-31 -> reserved
+ */
+struct smd_alloc_elm {
+	char name[20];
+	uint32_t cid;
+	uint32_t type;
+	uint32_t ref_count;
+};
+
+#define SMD_CHANNEL_TYPE(x) ((x) & 0x000000FF)
+#define SMD_XFER_TYPE(x)    (((x) & 0x00000F00) >> 8)
+
+struct smd_half_channel {
+	unsigned int state;
+	unsigned char fDSR;
+	unsigned char fCTS;
+	unsigned char fCD;
+	unsigned char fRI;
+	unsigned char fHEAD;
+	unsigned char fTAIL;
+	unsigned char fSTATE;
+	unsigned char fBLOCKREADINTR;
+	unsigned int tail;
+	unsigned int head;
+};
+
+struct smd_half_channel_word_access {
+	unsigned int state;
+	unsigned int fDSR;
+	unsigned int fCTS;
+	unsigned int fCD;
+	unsigned int fRI;
+	unsigned int fHEAD;
+	unsigned int fTAIL;
+	unsigned int fSTATE;
+	unsigned int fBLOCKREADINTR;
+	unsigned int tail;
+	unsigned int head;
+};
+
+struct smd_half_channel_access {
+	void (*set_state)(volatile void __iomem *half_channel,
+					unsigned int data);
+	unsigned int (*get_state)(volatile void __iomem *half_channel);
+	void (*set_fDSR)(volatile void __iomem *half_channel,
+					unsigned char data);
+	unsigned int (*get_fDSR)(volatile void __iomem *half_channel);
+	void (*set_fCTS)(volatile void __iomem *half_channel,
+					unsigned char data);
+	unsigned int (*get_fCTS)(volatile void __iomem *half_channel);
+	void (*set_fCD)(volatile void __iomem *half_channel,
+					unsigned char data);
+	unsigned int (*get_fCD)(volatile void __iomem *half_channel);
+	void (*set_fRI)(volatile void __iomem *half_channel,
+					unsigned char data);
+	unsigned int (*get_fRI)(volatile void __iomem *half_channel);
+	void (*set_fHEAD)(volatile void __iomem *half_channel,
+					unsigned char data);
+	unsigned int (*get_fHEAD)(volatile void __iomem *half_channel);
+	void (*set_fTAIL)(volatile void __iomem *half_channel,
+					unsigned char data);
+	unsigned int (*get_fTAIL)(volatile void __iomem *half_channel);
+	void (*set_fSTATE)(volatile void __iomem *half_channel,
+					unsigned char data);
+	unsigned int (*get_fSTATE)(volatile void __iomem *half_channel);
+	void (*set_fBLOCKREADINTR)(volatile void __iomem *half_channel,
+					unsigned char data);
+	unsigned int (*get_fBLOCKREADINTR)(volatile void __iomem *half_channel);
+	void (*set_tail)(volatile void __iomem *half_channel,
+					unsigned int data);
+	unsigned int (*get_tail)(volatile void __iomem *half_channel);
+	void (*set_head)(volatile void __iomem *half_channel,
+					unsigned int data);
+	unsigned int (*get_head)(volatile void __iomem *half_channel);
+};
+
+int is_word_access_ch(unsigned int ch_type);
+
+struct smd_half_channel_access *get_half_ch_funcs(unsigned int ch_type);
+
+struct smd_channel {
+	volatile void __iomem *send; /* some variant of smd_half_channel */
+	volatile void __iomem *recv; /* some variant of smd_half_channel */
+	unsigned char *send_data;
+	unsigned char *recv_data;
+	unsigned int fifo_size;
+	struct list_head ch_list;
+
+	unsigned int current_packet;
+	unsigned int n;
+	void *priv;
+	void (*notify)(void *priv, unsigned int flags);
+
+	int (*read)(smd_channel_t *ch, void *data, int len);
+	int (*write)(smd_channel_t *ch, const void *data, int len,
+			bool int_ntfy);
+	int (*read_avail)(smd_channel_t *ch);
+	int (*write_avail)(smd_channel_t *ch);
+	int (*read_from_cb)(smd_channel_t *ch, void *data, int len);
+
+	void (*update_state)(smd_channel_t *ch);
+	unsigned int last_state;
+	void (*notify_other_cpu)(smd_channel_t *ch);
+	void * (*read_from_fifo)(void *dest, const void *src, size_t num_bytes);
+	void * (*write_to_fifo)(void *dest, const void *src, size_t num_bytes);
+
+	char name[20];
+	struct platform_device pdev;
+	unsigned int type;
+
+	int pending_pkt_sz;
+
+	char is_pkt_ch;
+
+	/*
+	 * private internal functions to access *send and *recv.
+	 * never to be exported outside of smd
+	 */
+	struct smd_half_channel_access *half_ch;
+};
+
+extern spinlock_t smem_lock;
+
+struct interrupt_stat {
+	uint32_t smd_in_count;
+	uint32_t smd_out_count;
+	uint32_t smd_interrupt_id;
+
+	uint32_t smsm_in_count;
+	uint32_t smsm_out_count;
+	uint32_t smsm_interrupt_id;
+};
+extern struct interrupt_stat interrupt_stats[NUM_SMD_SUBSYSTEMS];
+
+struct interrupt_config_item {
+	/* must be initialized */
+	irqreturn_t (*irq_handler)(int req, void *data);
+	/* outgoing interrupt config (set from platform data) */
+	uint32_t out_bit_pos;
+	void __iomem *out_base;
+	uint32_t out_offset;
+	int irq_id;
+};
+
+enum {
+	MSM_SMD_DEBUG = 1U << 0,
+	MSM_SMSM_DEBUG = 1U << 1,
+	MSM_SMD_INFO = 1U << 2,
+	MSM_SMSM_INFO = 1U << 3,
+	MSM_SMD_POWER_INFO = 1U << 4,
+	MSM_SMSM_POWER_INFO = 1U << 5,
+};
+
+struct interrupt_config {
+	struct interrupt_config_item smd;
+	struct interrupt_config_item smsm;
+};
+
+struct edge_to_pid {
+	uint32_t	local_pid;
+	uint32_t	remote_pid;
+	char		subsys_name[SMD_MAX_CH_NAME_LEN];
+	bool		initialized;
+};
+
+extern void *smd_log_ctx;
+extern int msm_smd_debug_mask;
+
+extern irqreturn_t smd_modem_irq_handler(int irq, void *data);
+extern irqreturn_t smsm_modem_irq_handler(int irq, void *data);
+extern irqreturn_t smd_dsp_irq_handler(int irq, void *data);
+extern irqreturn_t smsm_dsp_irq_handler(int irq, void *data);
+extern irqreturn_t smd_dsps_irq_handler(int irq, void *data);
+extern irqreturn_t smsm_dsps_irq_handler(int irq, void *data);
+extern irqreturn_t smd_wcnss_irq_handler(int irq, void *data);
+extern irqreturn_t smsm_wcnss_irq_handler(int irq, void *data);
+extern irqreturn_t smd_rpm_irq_handler(int irq, void *data);
+extern irqreturn_t smd_modemfw_irq_handler(int irq, void *data);
+
+extern int msm_smd_driver_register(void);
+extern void smd_post_init(unsigned int remote_pid);
+extern int smsm_post_init(void);
+
+extern struct interrupt_config *smd_get_intr_config(uint32_t edge);
+extern int smd_edge_to_remote_pid(uint32_t edge);
+extern int smd_edge_to_local_pid(uint32_t edge);
+extern void smd_set_edge_subsys_name(uint32_t edge, const char *subsys_name);
+extern void smd_reset_all_edge_subsys_name(void);
+extern void smd_proc_set_skip_pil(unsigned int pid, bool skip_pil);
+extern void smd_set_edge_initialized(uint32_t edge);
+extern void smd_cfg_smd_intr(uint32_t proc, uint32_t mask, void *ptr);
+extern void smd_cfg_smsm_intr(uint32_t proc, uint32_t mask, void *ptr);
+#endif
diff --git a/drivers/soc/qcom/smsm_debug.c b/drivers/soc/qcom/smsm_debug.c
new file mode 100644
index 0000000..b9b97ef
--- /dev/null
+++ b/drivers/soc/qcom/smsm_debug.c
@@ -0,0 +1,330 @@
+/* drivers/soc/qcom/smsm_debug.c
+ *
+ * Copyright (C) 2007 Google, Inc.
+ * Copyright (c) 2009-2014, 2017, The Linux Foundation. All rights reserved.
+ * Author: Brian Swetland <swetland@google.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/debugfs.h>
+#include <linux/list.h>
+#include <linux/ctype.h>
+#include <linux/jiffies.h>
+
+#include <soc/qcom/smem.h>
+#include <soc/qcom/smsm.h>
+
+#if defined(CONFIG_DEBUG_FS)
+
+
+static void debug_read_smsm_state(struct seq_file *s)
+{
+	uint32_t *smsm;
+	int n;
+
+	smsm = smem_find(SMEM_SMSM_SHARED_STATE,
+			 SMSM_NUM_ENTRIES * sizeof(uint32_t),
+			 0,
+			 SMEM_ANY_HOST_FLAG);
+
+	if (smsm)
+		for (n = 0; n < SMSM_NUM_ENTRIES; n++)
+			seq_printf(s, "entry %d: 0x%08x\n", n, smsm[n]);
+}
+
+struct SMSM_CB_DATA {
+	int cb_count;
+	void *data;
+	uint32_t old_state;
+	uint32_t new_state;
+};
+static struct SMSM_CB_DATA smsm_cb_data;
+static struct completion smsm_cb_completion;
+
+static void smsm_state_cb(void *data, uint32_t old_state, uint32_t new_state)
+{
+	smsm_cb_data.cb_count++;
+	smsm_cb_data.old_state = old_state;
+	smsm_cb_data.new_state = new_state;
+	smsm_cb_data.data = data;
+	complete_all(&smsm_cb_completion);
+}
+
+#define UT_EQ_INT(a, b) \
+	{ \
+		if ((a) != (b)) { \
+			seq_printf(s, "%s:%d " #a "(%d) != " #b "(%d)\n", \
+					__func__, __LINE__, \
+					a, b); \
+			break; \
+		} \
+	}
+
+#define UT_GT_INT(a, b) \
+	{ \
+		if ((a) <= (b)) { \
+			seq_printf(s, "%s:%d " #a "(%d) > " #b "(%d)\n", \
+					__func__, __LINE__, \
+					a, b); \
+			break; \
+		} \
+	}
+
+#define SMSM_CB_TEST_INIT() \
+	do { \
+		smsm_cb_data.cb_count = 0; \
+		smsm_cb_data.old_state = 0; \
+		smsm_cb_data.new_state = 0; \
+		smsm_cb_data.data = 0; \
+	} while (0)
+
+
+static void debug_test_smsm(struct seq_file *s)
+{
+	int test_num = 0;
+	int ret;
+
+	/* Test case 1 - Register new callback for notification */
+	do {
+		test_num++;
+		SMSM_CB_TEST_INIT();
+		ret = smsm_state_cb_register(SMSM_APPS_STATE, SMSM_SMDINIT,
+				smsm_state_cb, (void *)0x1234);
+		UT_EQ_INT(ret, 0);
+
+		/* de-assert SMSM_SMD_INIT to trigger state update */
+		UT_EQ_INT(smsm_cb_data.cb_count, 0);
+		reinit_completion(&smsm_cb_completion);
+		smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0);
+		UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
+
+		UT_EQ_INT(smsm_cb_data.cb_count, 1);
+		UT_EQ_INT(smsm_cb_data.old_state & SMSM_SMDINIT, SMSM_SMDINIT);
+		UT_EQ_INT(smsm_cb_data.new_state & SMSM_SMDINIT, 0x0);
+		UT_EQ_INT((int)(uintptr_t)smsm_cb_data.data, 0x1234);
+
+		/* re-assert SMSM_SMD_INIT to trigger state update */
+		reinit_completion(&smsm_cb_completion);
+		smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT);
+		UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
+		UT_EQ_INT(smsm_cb_data.cb_count, 2);
+		UT_EQ_INT(smsm_cb_data.old_state & SMSM_SMDINIT, 0x0);
+		UT_EQ_INT(smsm_cb_data.new_state & SMSM_SMDINIT, SMSM_SMDINIT);
+
+		/* deregister callback */
+		ret = smsm_state_cb_deregister(SMSM_APPS_STATE, SMSM_SMDINIT,
+				smsm_state_cb, (void *)0x1234);
+		UT_EQ_INT(ret, 2);
+
+		/* make sure state change doesn't cause any more callbacks */
+		reinit_completion(&smsm_cb_completion);
+		smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0);
+		smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT);
+		UT_EQ_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
+		UT_EQ_INT(smsm_cb_data.cb_count, 2);
+
+		seq_printf(s, "Test %d - PASS\n", test_num);
+	} while (0);
+
+	/* Test case 2 - Update already registered callback */
+	do {
+		test_num++;
+		SMSM_CB_TEST_INIT();
+		ret = smsm_state_cb_register(SMSM_APPS_STATE, SMSM_SMDINIT,
+				smsm_state_cb, (void *)0x1234);
+		UT_EQ_INT(ret, 0);
+		ret = smsm_state_cb_register(SMSM_APPS_STATE, SMSM_INIT,
+				smsm_state_cb, (void *)0x1234);
+		UT_EQ_INT(ret, 1);
+
+		/* verify both callback bits work */
+		reinit_completion(&smsm_cb_completion);
+		UT_EQ_INT(smsm_cb_data.cb_count, 0);
+		smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0);
+		UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
+		UT_EQ_INT(smsm_cb_data.cb_count, 1);
+		reinit_completion(&smsm_cb_completion);
+		smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT);
+		UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
+		UT_EQ_INT(smsm_cb_data.cb_count, 2);
+
+		reinit_completion(&smsm_cb_completion);
+		smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0);
+		UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
+		UT_EQ_INT(smsm_cb_data.cb_count, 3);
+		reinit_completion(&smsm_cb_completion);
+		smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_INIT);
+		UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
+		UT_EQ_INT(smsm_cb_data.cb_count, 4);
+
+		/* deregister 1st callback */
+		ret = smsm_state_cb_deregister(SMSM_APPS_STATE, SMSM_SMDINIT,
+				smsm_state_cb, (void *)0x1234);
+		UT_EQ_INT(ret, 1);
+		reinit_completion(&smsm_cb_completion);
+		smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0);
+		smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT);
+		UT_EQ_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
+		UT_EQ_INT(smsm_cb_data.cb_count, 4);
+
+		reinit_completion(&smsm_cb_completion);
+		smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0);
+		UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
+		UT_EQ_INT(smsm_cb_data.cb_count, 5);
+		reinit_completion(&smsm_cb_completion);
+		smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_INIT);
+		UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
+		UT_EQ_INT(smsm_cb_data.cb_count, 6);
+
+		/* deregister 2nd callback */
+		ret = smsm_state_cb_deregister(SMSM_APPS_STATE, SMSM_INIT,
+				smsm_state_cb, (void *)0x1234);
+		UT_EQ_INT(ret, 2);
+
+		/* make sure state change doesn't cause any more callbacks */
+		reinit_completion(&smsm_cb_completion);
+		smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0);
+		smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_INIT);
+		UT_EQ_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
+		UT_EQ_INT(smsm_cb_data.cb_count, 6);
+
+		seq_printf(s, "Test %d - PASS\n", test_num);
+	} while (0);
+
+	/* Test case 3 - Two callback registrations with different data */
+	do {
+		test_num++;
+		SMSM_CB_TEST_INIT();
+		ret = smsm_state_cb_register(SMSM_APPS_STATE, SMSM_SMDINIT,
+				smsm_state_cb, (void *)0x1234);
+		UT_EQ_INT(ret, 0);
+		ret = smsm_state_cb_register(SMSM_APPS_STATE, SMSM_INIT,
+				smsm_state_cb, (void *)0x3456);
+		UT_EQ_INT(ret, 0);
+
+		/* verify both callbacks work */
+		reinit_completion(&smsm_cb_completion);
+		UT_EQ_INT(smsm_cb_data.cb_count, 0);
+		smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0);
+		UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
+		UT_EQ_INT(smsm_cb_data.cb_count, 1);
+		UT_EQ_INT((int)(uintptr_t)smsm_cb_data.data, 0x1234);
+
+		reinit_completion(&smsm_cb_completion);
+		smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0);
+		UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion,
+					msecs_to_jiffies(20)), 0);
+		UT_EQ_INT(smsm_cb_data.cb_count, 2);
+		UT_EQ_INT((int)(uintptr_t)smsm_cb_data.data, 0x3456);
+
+		/* cleanup and unregister
+		 * degregister in reverse to verify data field is
+		 * being used
+		 */
+		smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT);
+		smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_INIT);
+		ret = smsm_state_cb_deregister(SMSM_APPS_STATE,
+				SMSM_INIT,
+				smsm_state_cb, (void *)0x3456);
+		UT_EQ_INT(ret, 2);
+		ret = smsm_state_cb_deregister(SMSM_APPS_STATE,
+				SMSM_SMDINIT,
+				smsm_state_cb, (void *)0x1234);
+		UT_EQ_INT(ret, 2);
+
+		seq_printf(s, "Test %d - PASS\n", test_num);
+	} while (0);
+}
+
+static void debug_read_intr_mask(struct seq_file *s)
+{
+	uint32_t *smsm;
+	int m, n;
+
+	smsm = smem_find(SMEM_SMSM_CPU_INTR_MASK,
+			  SMSM_NUM_ENTRIES * SMSM_NUM_HOSTS * sizeof(uint32_t),
+			  0,
+			  SMEM_ANY_HOST_FLAG);
+
+	if (smsm)
+		for (m = 0; m < SMSM_NUM_ENTRIES; m++) {
+			seq_printf(s, "entry %d:", m);
+			for (n = 0; n < SMSM_NUM_HOSTS; n++)
+				seq_printf(s, "   host %d: 0x%08x",
+					       n, smsm[m * SMSM_NUM_HOSTS + n]);
+			seq_puts(s, "\n");
+		}
+}
+
+static int debugfs_show(struct seq_file *s, void *data)
+{
+	void (*show)(struct seq_file *) = s->private;
+
+	show(s);
+
+	return 0;
+}
+
+static int debug_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, debugfs_show, inode->i_private);
+}
+
+static const struct file_operations debug_ops = {
+	.open = debug_open,
+	.release = single_release,
+	.read = seq_read,
+	.llseek = seq_lseek,
+};
+
+static void debug_create(const char *name, umode_t mode,
+			 struct dentry *dent,
+			 void (*show)(struct seq_file *))
+{
+	struct dentry *file;
+
+	file = debugfs_create_file(name, mode, dent, show, &debug_ops);
+	if (!file)
+		pr_err("%s: unable to create file '%s'\n", __func__, name);
+}
+
+static int __init smsm_debugfs_init(void)
+{
+	struct dentry *dent;
+
+	dent = debugfs_create_dir("smsm", 0);
+	if (IS_ERR(dent))
+		return PTR_ERR(dent);
+
+	debug_create("state", 0444, dent, debug_read_smsm_state);
+	debug_create("intr_mask", 0444, dent, debug_read_intr_mask);
+	debug_create("smsm_test", 0444, dent, debug_test_smsm);
+
+	init_completion(&smsm_cb_completion);
+
+	return 0;
+}
+
+late_initcall(smsm_debugfs_init);
+#endif
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 626cfdc..0c5e9ca 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1690,6 +1690,16 @@
 	  and warnings and which allows logins in single user mode)
 	  Otherwise, say 'N'.
 
+config SERIAL_MSM_SMD
+	bool "Enable tty device interface for some SMD ports"
+	default n
+	depends on MSM_SMD
+	help
+	  This driver provides the interface for the userspace clients
+	  to communicate over smd via device nodes. This enable the
+	  usersapce clients to read and write to some streaming SMD ports
+	  via tty device interface for MSM chipset.
+
 endmenu
 
 config SERIAL_MCTRL_GPIO
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index 1bdc7f8..882fff9 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -98,3 +98,4 @@
 
 # GPIOLIB helpers for modem control lines
 obj-$(CONFIG_SERIAL_MCTRL_GPIO)	+= serial_mctrl_gpio.o
+obj-$(CONFIG_SERIAL_MSM_SMD)	+= msm_smd_tty.o
diff --git a/drivers/tty/serial/msm_smd_tty.c b/drivers/tty/serial/msm_smd_tty.c
new file mode 100644
index 0000000..84ee1dd
--- /dev/null
+++ b/drivers/tty/serial/msm_smd_tty.c
@@ -0,0 +1,1049 @@
+/* Copyright (C) 2007 Google, Inc.
+ * Copyright (c) 2009-2015, 2017, The Linux Foundation. All rights reserved.
+ * Author: Brian Swetland <swetland@google.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/ipc_logging.h>
+#include <linux/of.h>
+#include <linux/suspend.h>
+
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+
+#include <soc/qcom/smd.h>
+#include <soc/qcom/smsm.h>
+#include <soc/qcom/subsystem_restart.h>
+
+#define MODULE_NAME "msm_smdtty"
+#define MAX_SMD_TTYS 37
+#define MAX_TTY_BUF_SIZE 2048
+#define TTY_PUSH_WS_DELAY 500
+#define TTY_PUSH_WS_POST_SUSPEND_DELAY 100
+#define MAX_RA_WAKE_LOCK_NAME_LEN 32
+#define SMD_TTY_LOG_PAGES 2
+
+#define SMD_TTY_INFO(buf...) \
+do { \
+	if (smd_tty_log_ctx) { \
+		ipc_log_string(smd_tty_log_ctx, buf); \
+	} \
+} while (0)
+
+#define SMD_TTY_ERR(buf...) \
+do { \
+	if (smd_tty_log_ctx) \
+		ipc_log_string(smd_tty_log_ctx, buf); \
+	pr_err(buf); \
+} while (0)
+
+static void *smd_tty_log_ctx;
+static bool smd_tty_in_suspend;
+static bool smd_tty_read_in_suspend;
+static struct wakeup_source read_in_suspend_ws;
+
+/**
+ * struct smd_tty_info - context for an individual SMD TTY device
+ *
+ * @ch:  SMD channel handle
+ * @port:  TTY port context structure
+ * @device_ptr:  TTY device pointer
+ * @pending_ws:  pending-data wakeup source
+ * @tty_tsklt:  read tasklet
+ * @buf_req_timer:  RX buffer retry timer
+ * @ch_allocated:  completion set when SMD channel is allocated
+ * @pil:  Peripheral Image Loader handle
+ * @edge:  SMD edge associated with port
+ * @ch_name:  SMD channel name associated with port
+ * @dev_name:  SMD platform device name associated with port
+ *
+ * @open_lock_lha1: open/close lock - used to serialize open/close operations
+ * @open_wait:  Timeout in seconds to wait for SMD port to be created / opened
+ *
+ * @reset_lock_lha2: lock for reset and open state
+ * @in_reset:  True if SMD channel is closed / in SSR
+ * @in_reset_updated:  reset state changed
+ * @is_open:  True if SMD port is open
+ * @ch_opened_wait_queue:  SMD port open/close wait queue
+ *
+ * @ra_lock_lha3:  Read-available lock - used to synchronize reads from SMD
+ * @ra_wakeup_source_name: Name of the read-available wakeup source
+ * @ra_wakeup_source:  Read-available wakeup source
+ */
+struct smd_tty_info {
+	smd_channel_t *ch;
+	struct tty_port port;
+	struct device *device_ptr;
+	struct wakeup_source pending_ws;
+	struct tasklet_struct tty_tsklt;
+	struct timer_list buf_req_timer;
+	struct completion ch_allocated;
+	void *pil;
+	uint32_t edge;
+	char ch_name[SMD_MAX_CH_NAME_LEN];
+	char dev_name[SMD_MAX_CH_NAME_LEN];
+
+	struct mutex open_lock_lha1;
+	unsigned int open_wait;
+
+	spinlock_t reset_lock_lha2;
+	int in_reset;
+	int in_reset_updated;
+	int is_open;
+	wait_queue_head_t ch_opened_wait_queue;
+
+	spinlock_t ra_lock_lha3;
+	char ra_wakeup_source_name[MAX_RA_WAKE_LOCK_NAME_LEN];
+	struct wakeup_source ra_wakeup_source;
+};
+
+/**
+ * struct smd_tty_pfdriver - SMD tty channel platform driver structure
+ *
+ * @list:  Adds this structure into smd_tty_platform_driver_list::list.
+ * @ref_cnt:  reference count for this structure.
+ * @driver:  SMD channel platform driver context structure
+ */
+struct smd_tty_pfdriver {
+	struct list_head list;
+	int ref_cnt;
+	struct platform_driver driver;
+};
+
+#define LOOPBACK_IDX 36
+
+static struct delayed_work loopback_work;
+static struct smd_tty_info smd_tty[MAX_SMD_TTYS];
+
+static DEFINE_MUTEX(smd_tty_pfdriver_lock_lha1);
+static LIST_HEAD(smd_tty_pfdriver_list);
+
+static int is_in_reset(struct smd_tty_info *info)
+{
+	return info->in_reset;
+}
+
+static void buf_req_retry(unsigned long param)
+{
+	struct smd_tty_info *info = (struct smd_tty_info *)param;
+	unsigned long flags;
+
+	spin_lock_irqsave(&info->reset_lock_lha2, flags);
+	if (info->is_open) {
+		spin_unlock_irqrestore(&info->reset_lock_lha2, flags);
+		tasklet_hi_schedule(&info->tty_tsklt);
+		return;
+	}
+	spin_unlock_irqrestore(&info->reset_lock_lha2, flags);
+}
+
+static ssize_t open_timeout_store(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t n)
+{
+	unsigned int num_dev;
+	unsigned long wait;
+
+	if (dev == NULL) {
+		SMD_TTY_INFO("%s: Invalid Device passed", __func__);
+		return -EINVAL;
+	}
+	for (num_dev = 0; num_dev < MAX_SMD_TTYS; num_dev++) {
+		if (dev == smd_tty[num_dev].device_ptr)
+			break;
+	}
+	if (num_dev >= MAX_SMD_TTYS) {
+		SMD_TTY_ERR("[%s]: Device Not found", __func__);
+		return -EINVAL;
+	}
+	if (!kstrtoul(buf, 10, &wait)) {
+		mutex_lock(&smd_tty[num_dev].open_lock_lha1);
+		smd_tty[num_dev].open_wait = wait;
+		mutex_unlock(&smd_tty[num_dev].open_lock_lha1);
+		return n;
+	}
+
+	SMD_TTY_INFO("[%s]: Unable to convert %s to an int",
+			__func__, buf);
+	return -EINVAL;
+
+}
+
+static ssize_t open_timeout_show(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	unsigned int num_dev;
+	unsigned int open_wait;
+
+	if (dev == NULL) {
+		SMD_TTY_INFO("%s: Invalid Device passed", __func__);
+		return -EINVAL;
+	}
+	for (num_dev = 0; num_dev < MAX_SMD_TTYS; num_dev++) {
+		if (dev == smd_tty[num_dev].device_ptr)
+			break;
+	}
+	if (num_dev >= MAX_SMD_TTYS) {
+		SMD_TTY_ERR("[%s]: Device Not Found", __func__);
+		return -EINVAL;
+	}
+
+	mutex_lock(&smd_tty[num_dev].open_lock_lha1);
+	open_wait = smd_tty[num_dev].open_wait;
+	mutex_unlock(&smd_tty[num_dev].open_lock_lha1);
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", open_wait);
+}
+
+static DEVICE_ATTR
+	(open_timeout, 0664, open_timeout_show, open_timeout_store);
+
+static void smd_tty_read(unsigned long param)
+{
+	unsigned char *ptr;
+	int avail;
+	struct smd_tty_info *info = (struct smd_tty_info *)param;
+	struct tty_struct *tty = tty_port_tty_get(&info->port);
+	unsigned long flags;
+
+	if (!tty)
+		return;
+
+	for (;;) {
+		if (is_in_reset(info)) {
+			/* signal TTY clients using TTY_BREAK */
+			tty_insert_flip_char(tty->port, 0x00, TTY_BREAK);
+			tty_flip_buffer_push(tty->port);
+			break;
+		}
+
+		if (test_bit(TTY_THROTTLED, &tty->flags))
+			break;
+		spin_lock_irqsave(&info->ra_lock_lha3, flags);
+		avail = smd_read_avail(info->ch);
+		if (avail == 0) {
+			__pm_relax(&info->ra_wakeup_source);
+			spin_unlock_irqrestore(&info->ra_lock_lha3, flags);
+			break;
+		}
+		spin_unlock_irqrestore(&info->ra_lock_lha3, flags);
+
+		if (avail > MAX_TTY_BUF_SIZE)
+			avail = MAX_TTY_BUF_SIZE;
+
+		avail = tty_prepare_flip_string(tty->port, &ptr, avail);
+		if (avail <= 0) {
+			mod_timer(&info->buf_req_timer,
+					jiffies + msecs_to_jiffies(30));
+			tty_kref_put(tty);
+			return;
+		}
+
+		if (smd_read(info->ch, ptr, avail) != avail) {
+			/* shouldn't be possible since we're in interrupt
+			 * context here and nobody else could 'steal' our
+			 * characters.
+			 */
+			SMD_TTY_ERR(
+				"%s - Possible smd_tty_buffer mismatch for %s",
+				__func__, info->ch_name);
+		}
+
+		/*
+		 * Keep system awake long enough to allow the TTY
+		 * framework to pass the flip buffer to any waiting
+		 * userspace clients.
+		 */
+		__pm_wakeup_event(&info->pending_ws, TTY_PUSH_WS_DELAY);
+
+		if (smd_tty_in_suspend)
+			smd_tty_read_in_suspend = true;
+
+		tty_flip_buffer_push(tty->port);
+	}
+
+	/* XXX only when writable and necessary */
+	tty_wakeup(tty);
+	tty_kref_put(tty);
+}
+
+static void smd_tty_notify(void *priv, unsigned int event)
+{
+	struct smd_tty_info *info = priv;
+	struct tty_struct *tty;
+	unsigned long flags;
+
+	switch (event) {
+	case SMD_EVENT_DATA:
+		spin_lock_irqsave(&info->reset_lock_lha2, flags);
+		if (!info->is_open) {
+			spin_unlock_irqrestore(&info->reset_lock_lha2, flags);
+			break;
+		}
+		spin_unlock_irqrestore(&info->reset_lock_lha2, flags);
+		/* There may be clients (tty framework) that are blocked
+		 * waiting for space to write data, so if a possible read
+		 * interrupt came in wake anyone waiting and disable the
+		 * interrupts
+		 */
+		if (smd_write_avail(info->ch)) {
+			smd_disable_read_intr(info->ch);
+			tty = tty_port_tty_get(&info->port);
+			if (tty)
+				wake_up_interruptible(&tty->write_wait);
+			tty_kref_put(tty);
+		}
+		spin_lock_irqsave(&info->ra_lock_lha3, flags);
+		if (smd_read_avail(info->ch)) {
+			__pm_stay_awake(&info->ra_wakeup_source);
+			tasklet_hi_schedule(&info->tty_tsklt);
+		}
+		spin_unlock_irqrestore(&info->ra_lock_lha3, flags);
+		break;
+
+	case SMD_EVENT_OPEN:
+		tty = tty_port_tty_get(&info->port);
+		spin_lock_irqsave(&info->reset_lock_lha2, flags);
+		if (tty)
+			clear_bit(TTY_OTHER_CLOSED, &tty->flags);
+		info->in_reset = 0;
+		info->in_reset_updated = 1;
+		info->is_open = 1;
+		wake_up_interruptible(&info->ch_opened_wait_queue);
+		spin_unlock_irqrestore(&info->reset_lock_lha2, flags);
+		tty_kref_put(tty);
+		break;
+
+	case SMD_EVENT_CLOSE:
+		spin_lock_irqsave(&info->reset_lock_lha2, flags);
+		info->in_reset = 1;
+		info->in_reset_updated = 1;
+		info->is_open = 0;
+		wake_up_interruptible(&info->ch_opened_wait_queue);
+		spin_unlock_irqrestore(&info->reset_lock_lha2, flags);
+
+		tty = tty_port_tty_get(&info->port);
+		if (tty) {
+			/* send TTY_BREAK through read tasklet */
+			set_bit(TTY_OTHER_CLOSED, &tty->flags);
+			tasklet_hi_schedule(&info->tty_tsklt);
+
+			if (tty->index == LOOPBACK_IDX)
+				schedule_delayed_work(&loopback_work,
+						msecs_to_jiffies(1000));
+		}
+		tty_kref_put(tty);
+		break;
+	}
+}
+
+static uint32_t is_modem_smsm_inited(void)
+{
+	uint32_t modem_state;
+	uint32_t ready_state = (SMSM_INIT | SMSM_SMDINIT);
+
+	modem_state = smsm_get_state(SMSM_MODEM_STATE);
+	return (modem_state & ready_state) == ready_state;
+}
+
+static int smd_tty_dummy_probe(struct platform_device *pdev)
+{
+	int n;
+
+	for (n = 0; n < MAX_SMD_TTYS; ++n) {
+		if (!smd_tty[n].dev_name)
+			continue;
+
+		if (pdev->id == smd_tty[n].edge &&
+			!strcmp(pdev->name, smd_tty[n].dev_name)) {
+			complete_all(&smd_tty[n].ch_allocated);
+			return 0;
+		}
+	}
+	SMD_TTY_ERR("[ERR]%s: unknown device '%s'\n", __func__, pdev->name);
+
+	return -ENODEV;
+}
+
+/**
+ * smd_tty_add_driver() - Add platform drivers for smd tty device
+ *
+ * @info: context for an individual SMD TTY device
+ *
+ * @returns: 0 for success, standard Linux error code otherwise
+ *
+ * This function is used to register platform driver once for all
+ * smd tty devices which have same names and increment the reference
+ * count for 2nd to nth devices.
+ */
+static int smd_tty_add_driver(struct smd_tty_info *info)
+{
+	int r = 0;
+	struct smd_tty_pfdriver *smd_tty_pfdriverp;
+	struct smd_tty_pfdriver *item;
+
+	if (!info) {
+		pr_err("%s on a NULL device structure\n", __func__);
+		return -EINVAL;
+	}
+
+	SMD_TTY_INFO("Begin %s on smd_tty[%s]\n", __func__,
+					info->ch_name);
+
+	mutex_lock(&smd_tty_pfdriver_lock_lha1);
+	list_for_each_entry(item, &smd_tty_pfdriver_list, list) {
+		if (!strcmp(item->driver.driver.name, info->dev_name)) {
+			SMD_TTY_INFO("%s:%s Driver Already reg. cnt:%d\n",
+				__func__, info->ch_name, item->ref_cnt);
+			++item->ref_cnt;
+			goto exit;
+		}
+	}
+
+	smd_tty_pfdriverp = kzalloc(sizeof(*smd_tty_pfdriverp), GFP_KERNEL);
+	if (IS_ERR_OR_NULL(smd_tty_pfdriverp)) {
+		pr_err("%s: kzalloc() failed for smd_tty_pfdriver[%s]\n",
+			__func__, info->ch_name);
+		r = -ENOMEM;
+		goto exit;
+	}
+
+	smd_tty_pfdriverp->driver.probe = smd_tty_dummy_probe;
+	smd_tty_pfdriverp->driver.driver.name = info->dev_name;
+	smd_tty_pfdriverp->driver.driver.owner = THIS_MODULE;
+	r = platform_driver_register(&smd_tty_pfdriverp->driver);
+	if (r) {
+		pr_err("%s: %s Platform driver reg. failed\n",
+			__func__, info->ch_name);
+		kfree(smd_tty_pfdriverp);
+		goto exit;
+	}
+	++smd_tty_pfdriverp->ref_cnt;
+	list_add(&smd_tty_pfdriverp->list, &smd_tty_pfdriver_list);
+
+exit:
+	SMD_TTY_INFO("End %s on smd_tty_ch[%s]\n", __func__, info->ch_name);
+	mutex_unlock(&smd_tty_pfdriver_lock_lha1);
+	return r;
+}
+
+/**
+ * smd_tty_remove_driver() - Remove the platform drivers for smd tty device
+ *
+ * @info: context for an individual SMD TTY device
+ *
+ * This function is used to decrement the reference count on
+ * platform drivers for smd pkt devices and removes the drivers
+ * when the reference count becomes zero.
+ */
+static void smd_tty_remove_driver(struct smd_tty_info *info)
+{
+	struct smd_tty_pfdriver *smd_tty_pfdriverp;
+	bool found_item = false;
+
+	if (!info) {
+		pr_err("%s on a NULL device\n", __func__);
+		return;
+	}
+
+	SMD_TTY_INFO("Begin %s on smd_tty_ch[%s]\n", __func__,
+					info->ch_name);
+	mutex_lock(&smd_tty_pfdriver_lock_lha1);
+	list_for_each_entry(smd_tty_pfdriverp, &smd_tty_pfdriver_list, list) {
+		if (!strcmp(smd_tty_pfdriverp->driver.driver.name,
+					info->dev_name)) {
+			found_item = true;
+			SMD_TTY_INFO("%s:%s Platform driver cnt:%d\n",
+				__func__, info->ch_name,
+				smd_tty_pfdriverp->ref_cnt);
+			if (smd_tty_pfdriverp->ref_cnt > 0)
+				--smd_tty_pfdriverp->ref_cnt;
+			else
+				pr_warn("%s reference count <= 0\n", __func__);
+			break;
+		}
+	}
+	if (!found_item)
+		SMD_TTY_ERR("%s:%s No item found in list.\n",
+			__func__, info->ch_name);
+
+	if (found_item && smd_tty_pfdriverp->ref_cnt == 0) {
+		platform_driver_unregister(&smd_tty_pfdriverp->driver);
+		smd_tty_pfdriverp->driver.probe = NULL;
+		list_del(&smd_tty_pfdriverp->list);
+		kfree(smd_tty_pfdriverp);
+	}
+	mutex_unlock(&smd_tty_pfdriver_lock_lha1);
+	SMD_TTY_INFO("End %s on smd_tty_ch[%s]\n", __func__, info->ch_name);
+}
+
+static int smd_tty_port_activate(struct tty_port *tport,
+				 struct tty_struct *tty)
+{
+	int res = 0;
+	unsigned int n = tty->index;
+	struct smd_tty_info *info;
+	const char *peripheral = NULL;
+
+	if (n >= MAX_SMD_TTYS || !smd_tty[n].ch_name)
+		return -ENODEV;
+
+	info = smd_tty + n;
+
+	mutex_lock(&info->open_lock_lha1);
+	tty->driver_data = info;
+
+	res = smd_tty_add_driver(info);
+	if (res) {
+		SMD_TTY_ERR("%s:%d Idx smd_tty_driver register failed %d\n",
+							__func__, n, res);
+		goto out;
+	}
+
+	peripheral = smd_edge_to_pil_str(smd_tty[n].edge);
+	if (!IS_ERR_OR_NULL(peripheral)) {
+		info->pil = subsystem_get(peripheral);
+		if (IS_ERR(info->pil)) {
+			SMD_TTY_INFO(
+				"%s failed on smd_tty device :%s subsystem_get failed for %s",
+				__func__, info->ch_name,
+				peripheral);
+
+			/*
+			 * Sleep, inorder to reduce the frequency of
+			 * retry by user-space modules and to avoid
+			 * possible watchdog bite.
+			 */
+			msleep((smd_tty[n].open_wait * 1000));
+			res = PTR_ERR(info->pil);
+			goto platform_unregister;
+		}
+	}
+
+	/* Wait for the modem SMSM to be inited for the SMD
+	 * Loopback channel to be allocated at the modem. Since
+	 * the wait need to be done atmost once, using msleep
+	 * doesn't degrade the performance.
+	 */
+	if (n == LOOPBACK_IDX) {
+		if (!is_modem_smsm_inited())
+			msleep(5000);
+		smsm_change_state(SMSM_APPS_STATE,
+				  0, SMSM_SMD_LOOPBACK);
+		msleep(100);
+	}
+
+	/*
+	 * Wait for a channel to be allocated so we know
+	 * the modem is ready enough.
+	 */
+	if (smd_tty[n].open_wait) {
+		res = wait_for_completion_interruptible_timeout(
+				&info->ch_allocated,
+				msecs_to_jiffies(smd_tty[n].open_wait *
+								1000));
+
+		if (res == 0) {
+			SMD_TTY_INFO(
+				"Timed out waiting for SMD channel %s",
+				info->ch_name);
+			res = -ETIMEDOUT;
+			goto release_pil;
+		} else if (res < 0) {
+			SMD_TTY_INFO(
+				"Error waiting for SMD channel %s : %d\n",
+				info->ch_name, res);
+			goto release_pil;
+		}
+	}
+
+	tasklet_init(&info->tty_tsklt, smd_tty_read, (unsigned long)info);
+	wakeup_source_init(&info->pending_ws, info->ch_name);
+	scnprintf(info->ra_wakeup_source_name, MAX_RA_WAKE_LOCK_NAME_LEN,
+		  "SMD_TTY_%s_RA", info->ch_name);
+	wakeup_source_init(&info->ra_wakeup_source,
+			info->ra_wakeup_source_name);
+
+	res = smd_named_open_on_edge(info->ch_name,
+				     smd_tty[n].edge, &info->ch, info,
+				     smd_tty_notify);
+	if (res < 0) {
+		SMD_TTY_INFO("%s: %s open failed %d\n",
+			      __func__, info->ch_name, res);
+		goto release_wl_tl;
+	}
+
+	res = wait_event_interruptible_timeout(info->ch_opened_wait_queue,
+					       info->is_open, (2 * HZ));
+	if (res == 0)
+		res = -ETIMEDOUT;
+	if (res < 0) {
+		SMD_TTY_INFO("%s: wait for %s smd_open failed %d\n",
+			      __func__, info->ch_name, res);
+		goto close_ch;
+	}
+	SMD_TTY_INFO("%s with PID %u opened port %s",
+		      current->comm, current->pid, info->ch_name);
+	smd_disable_read_intr(info->ch);
+	mutex_unlock(&info->open_lock_lha1);
+	return 0;
+
+close_ch:
+	smd_close(info->ch);
+	info->ch = NULL;
+
+release_wl_tl:
+	tasklet_kill(&info->tty_tsklt);
+	wakeup_source_trash(&info->pending_ws);
+	wakeup_source_trash(&info->ra_wakeup_source);
+
+release_pil:
+	subsystem_put(info->pil);
+
+platform_unregister:
+	smd_tty_remove_driver(info);
+
+out:
+	mutex_unlock(&info->open_lock_lha1);
+
+	return res;
+}
+
+static void smd_tty_port_shutdown(struct tty_port *tport)
+{
+	struct smd_tty_info *info;
+	struct tty_struct *tty = tty_port_tty_get(tport);
+	unsigned long flags;
+
+	info = tty->driver_data;
+	if (info == 0) {
+		tty_kref_put(tty);
+		return;
+	}
+
+	mutex_lock(&info->open_lock_lha1);
+
+	spin_lock_irqsave(&info->reset_lock_lha2, flags);
+	info->is_open = 0;
+	spin_unlock_irqrestore(&info->reset_lock_lha2, flags);
+
+	tasklet_kill(&info->tty_tsklt);
+	wakeup_source_trash(&info->pending_ws);
+	wakeup_source_trash(&info->ra_wakeup_source);
+
+	SMD_TTY_INFO("%s with PID %u closed port %s",
+			current->comm, current->pid,
+			info->ch_name);
+	tty->driver_data = NULL;
+	del_timer(&info->buf_req_timer);
+
+	smd_close(info->ch);
+	info->ch = NULL;
+	subsystem_put(info->pil);
+	smd_tty_remove_driver(info);
+
+	mutex_unlock(&info->open_lock_lha1);
+	tty_kref_put(tty);
+}
+
+static int smd_tty_open(struct tty_struct *tty, struct file *f)
+{
+	struct smd_tty_info *info = smd_tty + tty->index;
+
+	return tty_port_open(&info->port, tty, f);
+}
+
+static void smd_tty_close(struct tty_struct *tty, struct file *f)
+{
+	struct smd_tty_info *info = smd_tty + tty->index;
+
+	tty_port_close(&info->port, tty, f);
+}
+
+static int smd_tty_write(struct tty_struct *tty, const unsigned char *buf,
+									int len)
+{
+	struct smd_tty_info *info = tty->driver_data;
+	int avail;
+
+	/* if we're writing to a packet channel we will
+	 * never be able to write more data than there
+	 * is currently space for
+	 */
+	if (is_in_reset(info))
+		return -ENETRESET;
+
+	avail = smd_write_avail(info->ch);
+	/* if no space, we'll have to setup a notification later to wake up the
+	 * tty framework when space becomes available
+	 */
+	if (!avail) {
+		smd_enable_read_intr(info->ch);
+		return 0;
+	}
+	if (len > avail)
+		len = avail;
+	SMD_TTY_INFO("[WRITE]: PID %u -> port %s %x bytes",
+			current->pid, info->ch_name, len);
+
+	return smd_write(info->ch, buf, len);
+}
+
+static int smd_tty_write_room(struct tty_struct *tty)
+{
+	struct smd_tty_info *info = tty->driver_data;
+
+	return smd_write_avail(info->ch);
+}
+
+static int smd_tty_chars_in_buffer(struct tty_struct *tty)
+{
+	struct smd_tty_info *info = tty->driver_data;
+
+	return smd_read_avail(info->ch);
+}
+
+static void smd_tty_unthrottle(struct tty_struct *tty)
+{
+	struct smd_tty_info *info = tty->driver_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&info->reset_lock_lha2, flags);
+	if (info->is_open) {
+		spin_unlock_irqrestore(&info->reset_lock_lha2, flags);
+		tasklet_hi_schedule(&info->tty_tsklt);
+		return;
+	}
+	spin_unlock_irqrestore(&info->reset_lock_lha2, flags);
+}
+
+/*
+ * Returns the current TIOCM status bits including:
+ *      SMD Signals (DTR/DSR, CTS/RTS, CD, RI)
+ *      TIOCM_OUT1 - reset state (1=in reset)
+ *      TIOCM_OUT2 - reset state updated (1=updated)
+ */
+static int smd_tty_tiocmget(struct tty_struct *tty)
+{
+	struct smd_tty_info *info = tty->driver_data;
+	unsigned long flags;
+	int tiocm;
+
+	tiocm = smd_tiocmget(info->ch);
+
+	spin_lock_irqsave(&info->reset_lock_lha2, flags);
+	tiocm |= (info->in_reset ? TIOCM_OUT1 : 0);
+	if (info->in_reset_updated) {
+		tiocm |= TIOCM_OUT2;
+		info->in_reset_updated = 0;
+	}
+	SMD_TTY_INFO("PID %u --> %s TIOCM is %x ",
+			current->pid, __func__, tiocm);
+	spin_unlock_irqrestore(&info->reset_lock_lha2, flags);
+
+	return tiocm;
+}
+
+static int smd_tty_tiocmset(struct tty_struct *tty,
+				unsigned int set, unsigned int clear)
+{
+	struct smd_tty_info *info = tty->driver_data;
+
+	if (info->in_reset)
+		return -ENETRESET;
+
+	SMD_TTY_INFO("PID %u --> %s Set: %x Clear: %x",
+			current->pid, __func__, set, clear);
+	return smd_tiocmset(info->ch, set, clear);
+}
+
+static void loopback_probe_worker(struct work_struct *work)
+{
+	/* wait for modem to restart before requesting loopback server */
+	if (!is_modem_smsm_inited())
+		schedule_delayed_work(&loopback_work, msecs_to_jiffies(1000));
+	else
+		smsm_change_state(SMSM_APPS_STATE,
+			  0, SMSM_SMD_LOOPBACK);
+}
+
+static const struct tty_port_operations smd_tty_port_ops = {
+	.shutdown = smd_tty_port_shutdown,
+	.activate = smd_tty_port_activate,
+};
+
+static const struct tty_operations smd_tty_ops = {
+	.open = smd_tty_open,
+	.close = smd_tty_close,
+	.write = smd_tty_write,
+	.write_room = smd_tty_write_room,
+	.chars_in_buffer = smd_tty_chars_in_buffer,
+	.unthrottle = smd_tty_unthrottle,
+	.tiocmget = smd_tty_tiocmget,
+	.tiocmset = smd_tty_tiocmset,
+};
+
+static int smd_tty_pm_notifier(struct notifier_block *nb,
+				unsigned long event, void *unused)
+{
+	switch (event) {
+	case PM_SUSPEND_PREPARE:
+		smd_tty_read_in_suspend = false;
+		smd_tty_in_suspend = true;
+		break;
+
+	case PM_POST_SUSPEND:
+		smd_tty_in_suspend = false;
+		if (smd_tty_read_in_suspend) {
+			smd_tty_read_in_suspend = false;
+			__pm_wakeup_event(&read_in_suspend_ws,
+					TTY_PUSH_WS_POST_SUSPEND_DELAY);
+		}
+		break;
+	}
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block smd_tty_pm_nb = {
+	.notifier_call = smd_tty_pm_notifier,
+	.priority = 0,
+};
+
+/**
+ * smd_tty_log_init()- Init function for IPC logging
+ *
+ * Initialize the buffer that is used to provide the log information
+ * pertaining to the smd_tty module.
+ */
+static void smd_tty_log_init(void)
+{
+	smd_tty_log_ctx = ipc_log_context_create(SMD_TTY_LOG_PAGES,
+						"smd_tty", 0);
+	if (!smd_tty_log_ctx)
+		pr_err("%s: Unable to create IPC log", __func__);
+}
+
+static struct tty_driver *smd_tty_driver;
+
+static int smd_tty_register_driver(void)
+{
+	int ret;
+
+	smd_tty_driver = alloc_tty_driver(MAX_SMD_TTYS);
+	if (smd_tty_driver == 0) {
+		SMD_TTY_ERR("%s - Driver allocation failed", __func__);
+		return -ENOMEM;
+	}
+
+	smd_tty_driver->owner = THIS_MODULE;
+	smd_tty_driver->driver_name = "smd_tty_driver";
+	smd_tty_driver->name = "smd";
+	smd_tty_driver->major = 0;
+	smd_tty_driver->minor_start = 0;
+	smd_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
+	smd_tty_driver->subtype = SERIAL_TYPE_NORMAL;
+	smd_tty_driver->init_termios = tty_std_termios;
+	smd_tty_driver->init_termios.c_iflag = 0;
+	smd_tty_driver->init_termios.c_oflag = 0;
+	smd_tty_driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
+	smd_tty_driver->init_termios.c_lflag = 0;
+	smd_tty_driver->flags = TTY_DRIVER_RESET_TERMIOS |
+		TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
+	tty_set_operations(smd_tty_driver, &smd_tty_ops);
+
+	ret = tty_register_driver(smd_tty_driver);
+	if (ret) {
+		put_tty_driver(smd_tty_driver);
+		SMD_TTY_ERR("%s: driver registration failed %d", __func__, ret);
+	}
+
+	return ret;
+}
+
+static void smd_tty_device_init(int idx)
+{
+	struct tty_port *port;
+
+	port = &smd_tty[idx].port;
+	tty_port_init(port);
+	port->ops = &smd_tty_port_ops;
+	smd_tty[idx].device_ptr = tty_port_register_device(port, smd_tty_driver,
+							   idx, NULL);
+	if (IS_ERR_OR_NULL(smd_tty[idx].device_ptr)) {
+		SMD_TTY_ERR("%s: Unable to register tty port %s reason %d\n",
+				__func__,
+				smd_tty[idx].ch_name,
+				PTR_ERR_OR_ZERO(smd_tty[idx].device_ptr));
+		return;
+	}
+	init_completion(&smd_tty[idx].ch_allocated);
+	mutex_init(&smd_tty[idx].open_lock_lha1);
+	spin_lock_init(&smd_tty[idx].reset_lock_lha2);
+	spin_lock_init(&smd_tty[idx].ra_lock_lha3);
+	smd_tty[idx].is_open = 0;
+	setup_timer(&smd_tty[idx].buf_req_timer, buf_req_retry,
+			(unsigned long)&smd_tty[idx]);
+	init_waitqueue_head(&smd_tty[idx].ch_opened_wait_queue);
+
+	if (device_create_file(smd_tty[idx].device_ptr, &dev_attr_open_timeout))
+		SMD_TTY_ERR("%s: Unable to create device attributes for %s",
+			__func__, smd_tty[idx].ch_name);
+}
+
+static int smd_tty_devicetree_init(struct platform_device *pdev)
+{
+	int ret;
+	int idx;
+	int edge;
+	char *key = NULL;
+	const char *ch_name;
+	const char *dev_name;
+	const char *remote_ss;
+	struct device_node *node;
+
+	ret = smd_tty_register_driver();
+	if (ret) {
+		SMD_TTY_ERR("%s: driver registration failed %d\n",
+						__func__, ret);
+		return ret;
+	}
+
+	for_each_child_of_node(pdev->dev.of_node, node) {
+
+		ret = of_alias_get_id(node, "smd");
+		SMD_TTY_INFO("%s:adding smd%d\n", __func__, ret);
+
+		if (ret < 0 || ret >= MAX_SMD_TTYS)
+			goto error;
+		idx = ret;
+
+		key = "qcom,smdtty-remote";
+		remote_ss = of_get_property(node, key, NULL);
+		if (!remote_ss)
+			goto error;
+
+		edge = smd_remote_ss_to_edge(remote_ss);
+		if (edge < 0)
+			goto error;
+		smd_tty[idx].edge = edge;
+
+		key = "qcom,smdtty-port-name";
+		ch_name = of_get_property(node, key, NULL);
+		if (!ch_name)
+			goto error;
+		strlcpy(smd_tty[idx].ch_name, ch_name,
+					SMD_MAX_CH_NAME_LEN);
+
+		key = "qcom,smdtty-dev-name";
+		dev_name = of_get_property(node, key, NULL);
+		if (!dev_name) {
+			strlcpy(smd_tty[idx].dev_name, smd_tty[idx].ch_name,
+							SMD_MAX_CH_NAME_LEN);
+		} else {
+			strlcpy(smd_tty[idx].dev_name, dev_name,
+						SMD_MAX_CH_NAME_LEN);
+		}
+
+		smd_tty_device_init(idx);
+	}
+	INIT_DELAYED_WORK(&loopback_work, loopback_probe_worker);
+
+	ret = register_pm_notifier(&smd_tty_pm_nb);
+	if (ret)
+		pr_err("%s: power state notif error %d\n", __func__, ret);
+
+	return 0;
+
+error:
+	SMD_TTY_ERR("%s:Initialization error, key[%s]\n", __func__, key);
+	/* Unregister tty platform devices */
+	for_each_child_of_node(pdev->dev.of_node, node) {
+
+		ret = of_alias_get_id(node, "smd");
+		SMD_TTY_INFO("%s:Removing smd%d\n", __func__, ret);
+
+		if (ret < 0 || ret >= MAX_SMD_TTYS)
+			goto out;
+		idx = ret;
+
+		if (smd_tty[idx].device_ptr) {
+			device_remove_file(smd_tty[idx].device_ptr,
+						&dev_attr_open_timeout);
+			tty_unregister_device(smd_tty_driver, idx);
+		}
+	}
+out:
+	tty_unregister_driver(smd_tty_driver);
+	put_tty_driver(smd_tty_driver);
+	return ret;
+}
+
+static int msm_smd_tty_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	if (pdev) {
+		if (pdev->dev.of_node) {
+			ret = smd_tty_devicetree_init(pdev);
+			if (ret) {
+				SMD_TTY_ERR("%s: device tree init failed\n",
+								__func__);
+				return ret;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static const struct of_device_id msm_smd_tty_match_table[] = {
+	{ .compatible = "qcom,smdtty" },
+	{},
+};
+
+static struct platform_driver msm_smd_tty_driver = {
+	.probe = msm_smd_tty_probe,
+	.driver = {
+		.name = MODULE_NAME,
+		.owner = THIS_MODULE,
+		.of_match_table = msm_smd_tty_match_table,
+	 },
+};
+
+
+static int __init smd_tty_init(void)
+{
+	int rc;
+
+	smd_tty_log_init();
+	rc = platform_driver_register(&msm_smd_tty_driver);
+	if (rc) {
+		SMD_TTY_ERR("%s: msm_smd_tty_driver register failed %d\n",
+								__func__, rc);
+		return rc;
+	}
+
+	wakeup_source_init(&read_in_suspend_ws, "SMDTTY_READ_IN_SUSPEND");
+	return 0;
+}
+
+module_init(smd_tty_init);
diff --git a/include/linux/msm_smd_pkt.h b/include/linux/msm_smd_pkt.h
new file mode 100644
index 0000000..c79933d
--- /dev/null
+++ b/include/linux/msm_smd_pkt.h
@@ -0,0 +1,23 @@
+/* Copyright (c) 2010,2017 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
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+#ifndef __LINUX_MSM_SMD_PKT_H
+#define __LINUX_MSM_SMD_PKT_H
+
+#include <linux/ioctl.h>
+
+#define SMD_PKT_IOCTL_MAGIC (0xC2)
+
+#define SMD_PKT_IOCTL_BLOCKING_WRITE \
+	_IOR(SMD_PKT_IOCTL_MAGIC, 0, unsigned int)
+
+#endif /* __LINUX_MSM_SMD_PKT_H */
diff --git a/include/soc/qcom/smd.h b/include/soc/qcom/smd.h
new file mode 100644
index 0000000..9853a93
--- /dev/null
+++ b/include/soc/qcom/smd.h
@@ -0,0 +1,381 @@
+/* include/soc/qcom/smd.h
+ *
+ * Copyright (C) 2007 Google, Inc.
+ * Copyright (c) 2009-2014, 2017, The Linux Foundation. All rights reserved.
+ * Author: Brian Swetland <swetland@google.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef __ASM_ARCH_MSM_SMD_H
+#define __ASM_ARCH_MSM_SMD_H
+
+#include <linux/io.h>
+
+#include <soc/qcom/smem.h>
+
+typedef struct smd_channel smd_channel_t;
+struct cpumask;
+
+#define SMD_MAX_CH_NAME_LEN 20 /* includes null char at end */
+
+#define SMD_EVENT_DATA 1
+#define SMD_EVENT_OPEN 2
+#define SMD_EVENT_CLOSE 3
+#define SMD_EVENT_STATUS 4
+#define SMD_EVENT_REOPEN_READY 5
+
+/*
+ * SMD Processor ID's.
+ *
+ * For all processors that have both SMSM and SMD clients,
+ * the SMSM Processor ID and the SMD Processor ID will
+ * be the same.  In cases where a processor only supports
+ * SMD, the entry will only exist in this enum.
+ */
+enum {
+	SMD_APPS = SMEM_APPS,
+	SMD_MODEM = SMEM_MODEM,
+	SMD_Q6 = SMEM_Q6,
+	SMD_DSPS = SMEM_DSPS,
+	SMD_TZ = SMEM_DSPS,
+	SMD_WCNSS = SMEM_WCNSS,
+	SMD_MODEM_Q6_FW = SMEM_MODEM_Q6_FW,
+	SMD_RPM = SMEM_RPM,
+	NUM_SMD_SUBSYSTEMS,
+};
+
+enum {
+	SMD_APPS_MODEM = 0,
+	SMD_APPS_QDSP,
+	SMD_MODEM_QDSP,
+	SMD_APPS_DSPS,
+	SMD_MODEM_DSPS,
+	SMD_QDSP_DSPS,
+	SMD_APPS_WCNSS,
+	SMD_MODEM_WCNSS,
+	SMD_QDSP_WCNSS,
+	SMD_DSPS_WCNSS,
+	SMD_APPS_Q6FW,
+	SMD_MODEM_Q6FW,
+	SMD_QDSP_Q6FW,
+	SMD_DSPS_Q6FW,
+	SMD_WCNSS_Q6FW,
+	SMD_APPS_RPM,
+	SMD_MODEM_RPM,
+	SMD_QDSP_RPM,
+	SMD_WCNSS_RPM,
+	SMD_TZ_RPM,
+	SMD_NUM_TYPE,
+
+};
+
+#ifdef CONFIG_MSM_SMD
+int smd_close(smd_channel_t *ch);
+
+/* passing a null pointer for data reads and discards */
+int smd_read(smd_channel_t *ch, void *data, int len);
+int smd_read_from_cb(smd_channel_t *ch, void *data, int len);
+
+/* Write to stream channels may do a partial write and return
+ * the length actually written.
+ * Write to packet channels will never do a partial write --
+ * it will return the requested length written or an error.
+ */
+int smd_write(smd_channel_t *ch, const void *data, int len);
+
+int smd_write_avail(smd_channel_t *ch);
+int smd_read_avail(smd_channel_t *ch);
+
+/* Returns the total size of the current packet being read.
+ * Returns 0 if no packets available or a stream channel.
+ */
+int smd_cur_packet_size(smd_channel_t *ch);
+
+/* these are used to get and set the IF sigs of a channel.
+ * DTR and RTS can be set; DSR, CTS, CD and RI can be read.
+ */
+int smd_tiocmget(smd_channel_t *ch);
+int smd_tiocmset(smd_channel_t *ch, unsigned int set, unsigned int clear);
+int
+smd_tiocmset_from_cb(smd_channel_t *ch, unsigned int set, unsigned int clear);
+int smd_named_open_on_edge(const char *name, uint32_t edge, smd_channel_t **_ch,
+			   void *priv, void (*notify)(void *, unsigned int));
+
+/* Tells the other end of the smd channel that this end wants to receive
+ * interrupts when the written data is read.  Read interrupts should only
+ * enabled when there is no space left in the buffer to write to, thus the
+ * interrupt acts as notification that space may be available.  If the
+ * other side does not support enabling/disabling interrupts on demand,
+ * then this function has no effect if called.
+ */
+void smd_enable_read_intr(smd_channel_t *ch);
+
+/* Tells the other end of the smd channel that this end does not want
+ * interrupts when written data is read.  The interrupts should be
+ * disabled by default.  If the other side does not support enabling/
+ * disabling interrupts on demand, then this function has no effect if
+ * called.
+ */
+void smd_disable_read_intr(smd_channel_t *ch);
+
+/**
+ * Enable/disable receive interrupts for the remote processor used by a
+ * particular channel.
+ * @ch:      open channel handle to use for the edge
+ * @mask:    1 = mask interrupts; 0 = unmask interrupts
+ * @cpumask  cpumask for the next cpu scheduled to be woken up
+ * @returns: 0 for success; < 0 for failure
+ *
+ * Note that this enables/disables all interrupts from the remote subsystem for
+ * all channels.  As such, it should be used with care and only for specific
+ * use cases such as power-collapse sequencing.
+ */
+int smd_mask_receive_interrupt(smd_channel_t *ch, bool mask,
+		const struct cpumask *cpumask);
+
+/* Starts a packet transaction.  The size of the packet may exceed the total
+ * size of the smd ring buffer.
+ *
+ * @ch: channel to write the packet to
+ * @len: total length of the packet
+ *
+ * Returns:
+ *      0 - success
+ *      -ENODEV - invalid smd channel
+ *      -EACCES - non-packet channel specified
+ *      -EINVAL - invalid length
+ *      -EBUSY - transaction already in progress
+ *      -EAGAIN - no enough memory in ring buffer to start transaction
+ *      -EPERM - unable to successfully start transaction due to write error
+ */
+int smd_write_start(smd_channel_t *ch, int len);
+
+/* Writes a segment of the packet for a packet transaction.
+ *
+ * @ch: channel to write packet to
+ * @data: buffer of data to write
+ * @len: length of data buffer
+ *
+ * Returns:
+ *      number of bytes written
+ *      -ENODEV - invalid smd channel
+ *      -EINVAL - invalid length
+ *      -ENOEXEC - transaction not started
+ */
+int smd_write_segment(smd_channel_t *ch, const void *data, int len);
+
+/* Completes a packet transaction.  Do not call from interrupt context.
+ *
+ * @ch: channel to complete transaction on
+ *
+ * Returns:
+ *      0 - success
+ *      -ENODEV - invalid smd channel
+ *      -E2BIG - some ammount of packet is not yet written
+ */
+int smd_write_end(smd_channel_t *ch);
+
+/**
+ * smd_write_segment_avail() - available write space for packet transactions
+ * @ch: channel to write packet to
+ * @returns: number of bytes available to write to, or -ENODEV for invalid ch
+ *
+ * This is a version of smd_write_avail() intended for use with packet
+ * transactions.  This version correctly accounts for any internal reserved
+ * space at all stages of the transaction.
+ */
+int smd_write_segment_avail(smd_channel_t *ch);
+
+/*
+ * Returns a pointer to the subsystem name or NULL if no
+ * subsystem name is available.
+ *
+ * @type - Edge definition
+ */
+const char *smd_edge_to_subsystem(uint32_t type);
+
+/*
+ * Returns a pointer to the subsystem name given the
+ * remote processor ID.
+ *
+ * @pid     Remote processor ID
+ * @returns Pointer to subsystem name or NULL if not found
+ */
+const char *smd_pid_to_subsystem(uint32_t pid);
+
+/*
+ * Checks to see if a new packet has arrived on the channel.  Only to be
+ * called with interrupts disabled.
+ *
+ * @ch: channel to check if a packet has arrived
+ *
+ * Returns:
+ *      0 - packet not available
+ *      1 - packet available
+ *      -EINVAL - NULL parameter or non-packet based channel provided
+ */
+int smd_is_pkt_avail(smd_channel_t *ch);
+
+/*
+ * SMD initialization function that registers for a SMD platform driver.
+ *
+ * returns success on successful driver registration.
+ */
+int __init msm_smd_init(void);
+
+/**
+ * smd_remote_ss_to_edge() - return edge type from remote ss type
+ * @name:	remote subsystem name
+ *
+ * Returns the edge type connected between the local subsystem(APPS)
+ * and remote subsystem @name.
+ */
+int smd_remote_ss_to_edge(const char *name);
+
+/**
+ * smd_edge_to_pil_str - Returns the PIL string used to load the remote side of
+ *			the indicated edge.
+ *
+ * @type - Edge definition
+ * @returns - The PIL string to load the remove side of @type or NULL if the
+ *		PIL string does not exist.
+ */
+const char *smd_edge_to_pil_str(uint32_t type);
+
+#else
+
+static inline int smd_close(smd_channel_t *ch)
+{
+	return -ENODEV;
+}
+
+static inline int smd_read(smd_channel_t *ch, void *data, int len)
+{
+	return -ENODEV;
+}
+
+static inline int smd_read_from_cb(smd_channel_t *ch, void *data, int len)
+{
+	return -ENODEV;
+}
+
+static inline int smd_write(smd_channel_t *ch, const void *data, int len)
+{
+	return -ENODEV;
+}
+
+static inline int smd_write_avail(smd_channel_t *ch)
+{
+	return -ENODEV;
+}
+
+static inline int smd_read_avail(smd_channel_t *ch)
+{
+	return -ENODEV;
+}
+
+static inline int smd_cur_packet_size(smd_channel_t *ch)
+{
+	return -ENODEV;
+}
+
+static inline int smd_tiocmget(smd_channel_t *ch)
+{
+	return -ENODEV;
+}
+
+static inline int
+smd_tiocmset(smd_channel_t *ch, unsigned int set, unsigned int clear)
+{
+	return -ENODEV;
+}
+
+static inline int
+smd_tiocmset_from_cb(smd_channel_t *ch, unsigned int set, unsigned int clear)
+{
+	return -ENODEV;
+}
+
+static inline int
+smd_named_open_on_edge(const char *name, uint32_t edge, smd_channel_t **_ch,
+			   void *priv, void (*notify)(void *, unsigned int))
+{
+	return -ENODEV;
+}
+
+static inline void smd_enable_read_intr(smd_channel_t *ch)
+{
+}
+
+static inline void smd_disable_read_intr(smd_channel_t *ch)
+{
+}
+
+static inline int smd_mask_receive_interrupt(smd_channel_t *ch, bool mask,
+		const struct cpumask *cpumask)
+{
+	return -ENODEV;
+}
+
+static inline int smd_write_start(smd_channel_t *ch, int len)
+{
+	return -ENODEV;
+}
+
+static inline int
+smd_write_segment(smd_channel_t *ch, const void *data, int len)
+{
+	return -ENODEV;
+}
+
+static inline int smd_write_end(smd_channel_t *ch)
+{
+	return -ENODEV;
+}
+
+static inline int smd_write_segment_avail(smd_channel_t *ch)
+{
+	return -ENODEV;
+}
+
+static inline const char *smd_edge_to_subsystem(uint32_t type)
+{
+	return NULL;
+}
+
+static inline const char *smd_pid_to_subsystem(uint32_t pid)
+{
+	return NULL;
+}
+
+static inline int smd_is_pkt_avail(smd_channel_t *ch)
+{
+	return -ENODEV;
+}
+
+static inline int __init msm_smd_init(void)
+{
+	return 0;
+}
+
+static inline int smd_remote_ss_to_edge(const char *name)
+{
+	return -EINVAL;
+}
+
+static inline const char *smd_edge_to_pil_str(uint32_t type)
+{
+	return NULL;
+}
+#endif
+
+#endif
diff --git a/include/soc/qcom/smem.h b/include/soc/qcom/smem.h
index bef98d6..6bb76f7 100644
--- a/include/soc/qcom/smem.h
+++ b/include/soc/qcom/smem.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2013-2016, 2017, 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
@@ -21,7 +21,8 @@
 	SMEM_Q6,
 	SMEM_DSPS,
 	SMEM_WCNSS,
-	SMEM_CDSP,
+	SMEM_MODEM_Q6_FW,
+	SMEM_CDSP = SMEM_MODEM_Q6_FW,
 	SMEM_RPM,
 	SMEM_TZ,
 	SMEM_SPSS,
diff --git a/include/soc/qcom/smsm.h b/include/soc/qcom/smsm.h
new file mode 100644
index 0000000..00d31e8
--- /dev/null
+++ b/include/soc/qcom/smsm.h
@@ -0,0 +1,147 @@
+/* Copyright (c) 2011-2013, 2017, 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
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _ARCH_ARM_MACH_MSM_SMSM_H_
+#define _ARCH_ARM_MACH_MSM_SMSM_H_
+
+#include <soc/qcom/smem.h>
+
+enum {
+	SMSM_APPS_STATE,
+	SMSM_MODEM_STATE,
+	SMSM_Q6_STATE,
+	SMSM_APPS_DEM,
+	SMSM_WCNSS_STATE = SMSM_APPS_DEM,
+	SMSM_MODEM_DEM,
+	SMSM_DSPS_STATE = SMSM_MODEM_DEM,
+	SMSM_Q6_DEM,
+	SMSM_POWER_MASTER_DEM,
+	SMSM_TIME_MASTER_DEM,
+};
+extern uint32_t SMSM_NUM_ENTRIES;
+
+/*
+ * Ordered by when processors adopted the SMSM protocol.  May not be 1-to-1
+ * with SMEM PIDs, despite initial expectations.
+ */
+enum {
+	SMSM_APPS = SMEM_APPS,
+	SMSM_MODEM = SMEM_MODEM,
+	SMSM_Q6 = SMEM_Q6,
+	SMSM_WCNSS,
+	SMSM_DSPS,
+};
+extern uint32_t SMSM_NUM_HOSTS;
+
+#define SMSM_INIT              0x00000001
+#define SMSM_SMDINIT           0x00000008
+#define SMSM_RPCINIT           0x00000020
+#define SMSM_RESET             0x00000040
+#define SMSM_TIMEWAIT          0x00000400
+#define SMSM_TIMEINIT          0x00000800
+#define SMSM_PROC_AWAKE        0x00001000
+#define SMSM_SMD_LOOPBACK      0x00800000
+
+#define SMSM_USB_PLUG_UNPLUG    0x00002000
+
+#define SMSM_A2_POWER_CONTROL  0x00000002
+#define SMSM_A2_POWER_CONTROL_ACK  0x00000800
+
+#ifdef CONFIG_MSM_SMD
+int smsm_change_state(uint32_t smsm_entry,
+		      uint32_t clear_mask, uint32_t set_mask);
+
+/*
+ * Changes the global interrupt mask.  The set and clear masks are re-applied
+ * every time the global interrupt mask is updated for callback registration
+ * and de-registration.
+ *
+ * The clear mask is applied first, so if a bit is set to 1 in both the clear
+ * mask and the set mask, the result will be that the interrupt is set.
+ *
+ * @smsm_entry  SMSM entry to change
+ * @clear_mask  1 = clear bit, 0 = no-op
+ * @set_mask    1 = set bit, 0 = no-op
+ *
+ * @returns 0 for success, < 0 for error
+ */
+int smsm_change_intr_mask(uint32_t smsm_entry,
+			  uint32_t clear_mask, uint32_t set_mask);
+int smsm_get_intr_mask(uint32_t smsm_entry, uint32_t *intr_mask);
+uint32_t smsm_get_state(uint32_t smsm_entry);
+int smsm_state_cb_register(uint32_t smsm_entry, uint32_t mask,
+	void (*notify)(void *, uint32_t old_state, uint32_t new_state),
+	void *data);
+int smsm_state_cb_deregister(uint32_t smsm_entry, uint32_t mask,
+	void (*notify)(void *, uint32_t, uint32_t), void *data);
+
+#else
+static inline int smsm_change_state(uint32_t smsm_entry,
+		      uint32_t clear_mask, uint32_t set_mask)
+{
+	return -ENODEV;
+}
+
+/*
+ * Changes the global interrupt mask.  The set and clear masks are re-applied
+ * every time the global interrupt mask is updated for callback registration
+ * and de-registration.
+ *
+ * The clear mask is applied first, so if a bit is set to 1 in both the clear
+ * mask and the set mask, the result will be that the interrupt is set.
+ *
+ * @smsm_entry  SMSM entry to change
+ * @clear_mask  1 = clear bit, 0 = no-op
+ * @set_mask    1 = set bit, 0 = no-op
+ *
+ * @returns 0 for success, < 0 for error
+ */
+static inline int smsm_change_intr_mask(uint32_t smsm_entry,
+			  uint32_t clear_mask, uint32_t set_mask)
+{
+	return -ENODEV;
+}
+
+static inline int smsm_get_intr_mask(uint32_t smsm_entry, uint32_t *intr_mask)
+{
+	return -ENODEV;
+}
+static inline uint32_t smsm_get_state(uint32_t smsm_entry)
+{
+	return 0;
+}
+static inline int smsm_state_cb_register(uint32_t smsm_entry, uint32_t mask,
+	void (*notify)(void *, uint32_t old_state, uint32_t new_state),
+	void *data)
+{
+	return -ENODEV;
+}
+static inline int smsm_state_cb_deregister(uint32_t smsm_entry, uint32_t mask,
+	void (*notify)(void *, uint32_t, uint32_t), void *data)
+{
+	return -ENODEV;
+}
+static inline void smsm_reset_modem(unsigned int mode)
+{
+}
+static inline void smsm_reset_modem_cont(void)
+{
+}
+static inline void smd_sleep_exit(void)
+{
+}
+static inline int smsm_check_for_modem_crash(void)
+{
+	return -ENODEV;
+}
+#endif
+#endif