Merge "esoc: Add debug engine for external modems." into msm-4.8
diff --git a/drivers/esoc/Kconfig b/drivers/esoc/Kconfig
index 35b2082..0efca1e 100644
--- a/drivers/esoc/Kconfig
+++ b/drivers/esoc/Kconfig
@@ -52,4 +52,13 @@
 	  such as mdm9x25/mdm9x35/mdm9x45/mdm9x55/QSC. Allows the primary soc to put the
 	  external modem in a specific mode. Also listens for events on the
 	  external modem.
+
+config ESOC_MDM_DBG_ENG
+	tristate "debug engine for 4x series external modems"
+	depends on ESOC_MDM_DRV
+	help
+	  Provides a user interface to mask out certain commands sent
+	  by command engine to the external modem. Also allows masking
+	  of certain notifications being sent to the external modem.
+
 endif
diff --git a/drivers/esoc/Makefile b/drivers/esoc/Makefile
index 0987215..76137ea 100644
--- a/drivers/esoc/Makefile
+++ b/drivers/esoc/Makefile
@@ -6,3 +6,4 @@
 obj-$(CONFIG_ESOC_CLIENT)	+= esoc_client.o
 obj-$(CONFIG_ESOC_MDM_4x)	+= esoc-mdm-pon.o esoc-mdm-4x.o
 obj-$(CONFIG_ESOC_MDM_DRV)	+= esoc-mdm-drv.o
+obj-$(CONFIG_ESOC_MDM_DBG_ENG)	+= esoc-mdm-dbg-eng.o
diff --git a/drivers/esoc/esoc-mdm-dbg-eng.c b/drivers/esoc/esoc-mdm-dbg-eng.c
new file mode 100644
index 0000000..a186ea8
--- /dev/null
+++ b/drivers/esoc/esoc-mdm-dbg-eng.c
@@ -0,0 +1,204 @@
+/* Copyright (c) 2015-2016, 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 <linux/atomic.h>
+#include <linux/device.h>
+#include "esoc.h"
+
+/*
+ * cmd_mask : Specifies if a command/notifier is masked, and
+ * whats the trigger value for mask to take effect.
+ * @mask_trigger: trigger value for mask.
+ * @mask: boolean to determine if command should be masked.
+ */
+struct esoc_mask {
+	atomic_t mask_trigger;
+	bool mask;
+};
+
+/*
+ * manual_to_esoc_cmd: Converts a user provided command
+ * to a corresponding esoc command.
+ * @cmd: ESOC command
+ * @manual_cmd: user specified command string.
+ */
+struct manual_to_esoc_cmd {
+	unsigned int cmd;
+	char manual_cmd[20];
+};
+
+/*
+ * manual_to_esoc_notify: Converts a user provided notification
+ * to corresponding esoc notification for Primary SOC.
+ * @notfication: ESOC notification.
+ * @manual_notifier: user specified notification string.
+ */
+struct manual_to_esoc_notify {
+	unsigned int notify;
+	char manual_notify[20];
+};
+
+static const struct manual_to_esoc_cmd cmd_map[] = {
+	{
+		.cmd = ESOC_PWR_ON,
+		.manual_cmd = "PON",
+	},
+	{
+		.cmd = ESOC_PREPARE_DEBUG,
+		.manual_cmd = "ENTER_DLOAD",
+	},
+	{	.cmd = ESOC_PWR_OFF,
+		.manual_cmd = "POFF",
+	},
+	{
+		.cmd = ESOC_FORCE_PWR_OFF,
+		.manual_cmd = "FORCE_POFF",
+	},
+};
+
+static struct esoc_mask cmd_mask[] = {
+	[ESOC_PWR_ON] = {
+		.mask = false,
+		.mask_trigger = ATOMIC_INIT(1),
+	},
+	[ESOC_PREPARE_DEBUG] = {
+		.mask = false,
+		.mask_trigger = ATOMIC_INIT(0),
+	},
+	[ESOC_PWR_OFF] = {
+		.mask = false,
+		.mask_trigger = ATOMIC_INIT(0),
+	},
+	[ESOC_FORCE_PWR_OFF] = {
+		.mask = false,
+		.mask_trigger = ATOMIC_INIT(0),
+	},
+};
+
+static const struct manual_to_esoc_notify notify_map[] = {
+	{
+		.notify = ESOC_PRIMARY_REBOOT,
+		.manual_notify = "REBOOT",
+	},
+	{
+		.notify = ESOC_PRIMARY_CRASH,
+		.manual_notify = "PANIC",
+	},
+};
+
+static struct esoc_mask notify_mask[] = {
+	[ESOC_PRIMARY_REBOOT] = {
+		.mask = false,
+		.mask_trigger = ATOMIC_INIT(0),
+	},
+	[ESOC_PRIMARY_CRASH] = {
+		.mask = false,
+		.mask_trigger = ATOMIC_INIT(0),
+	},
+};
+
+bool dbg_check_cmd_mask(unsigned int cmd)
+{
+	pr_debug("command to mask %d\n", cmd);
+	if (cmd_mask[cmd].mask)
+		return atomic_add_negative(-1, &cmd_mask[cmd].mask_trigger);
+	else
+		return false;
+}
+EXPORT_SYMBOL(dbg_check_cmd_mask);
+
+bool dbg_check_notify_mask(unsigned int notify)
+{
+	pr_debug("notifier to mask %d\n", notify);
+	if (notify_mask[notify].mask)
+		return atomic_add_negative(-1,
+					&notify_mask[notify].mask_trigger);
+	else
+		return false;
+}
+EXPORT_SYMBOL(dbg_check_notify_mask);
+/*
+ * Create driver attributes that let you mask
+ * specific commands.
+ */
+static ssize_t cmd_mask_store(struct device_driver *drv, const char *buf,
+							size_t count)
+{
+	unsigned int cmd, i;
+
+	pr_debug("user input command %s", buf);
+	for (i = 0; i < ARRAY_SIZE(cmd_map); i++) {
+		if (!strcmp(cmd_map[i].manual_cmd, buf)) {
+			/*
+			 * Map manual command string to ESOC command
+			 * set mask for ESOC command
+			 */
+			cmd = cmd_map[i].cmd;
+			cmd_mask[cmd].mask = true;
+			pr_debug("Setting mask for manual command %s\n",
+								buf);
+			break;
+		}
+	}
+	if (i >= ARRAY_SIZE(cmd_map))
+		pr_err("invalid command specified\n");
+	return count;
+}
+static DRIVER_ATTR(command_mask, 00200, NULL, cmd_mask_store);
+
+static ssize_t notifier_mask_store(struct device_driver *drv, const char *buf,
+							size_t count)
+{
+	unsigned int notify, i;
+
+	pr_debug("user input notifier %s", buf);
+	for (i = 0; i < ARRAY_SIZE(notify_map); i++) {
+		if (!strcmp(buf, notify_map[i].manual_notify)) {
+			/*
+			 * Map manual notifier string to primary soc
+			 * notifier. Also set mask for the notifier.
+			 */
+			notify = notify_map[i].notify;
+			notify_mask[notify].mask = true;
+			pr_debug("Setting mask for manual notification %s\n",
+									buf);
+			break;
+		}
+	}
+	if (i >= ARRAY_SIZE(notify_map))
+		pr_err("invalid notifier specified\n");
+	return count;
+}
+static DRIVER_ATTR(notifier_mask, 00200, NULL, notifier_mask_store);
+
+int mdm_dbg_eng_init(struct esoc_drv *esoc_drv)
+{
+	int ret;
+	struct device_driver *drv = &esoc_drv->driver;
+
+	ret = driver_create_file(drv, &driver_attr_command_mask);
+	if (ret) {
+		pr_err("Unable to create command mask file\n");
+		goto cmd_mask_err;
+	}
+	ret = driver_create_file(drv, &driver_attr_notifier_mask);
+	if (ret) {
+		pr_err("Unable to create notify mask file\n");
+		goto notify_mask_err;
+	}
+	return 0;
+notify_mask_err:
+	driver_remove_file(drv, &driver_attr_command_mask);
+cmd_mask_err:
+	return ret;
+}
+EXPORT_SYMBOL(mdm_dbg_eng_init);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/esoc/esoc-mdm-drv.c b/drivers/esoc/esoc-mdm-drv.c
index c62efe4..473a9c7 100644
--- a/drivers/esoc/esoc-mdm-drv.c
+++ b/drivers/esoc/esoc-mdm-drv.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2013-2016, 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
@@ -14,6 +14,7 @@
 #include <linux/workqueue.h>
 #include <linux/reboot.h>
 #include "esoc.h"
+#include "mdm-dbg.h"
 
 enum {
 	 PWR_OFF = 0x1,
@@ -49,6 +50,8 @@ static int esoc_msm_restart_handler(struct notifier_block *nb,
 	const struct esoc_clink_ops * const clink_ops = esoc_clink->clink_ops;
 
 	if (action == SYS_RESTART) {
+		if (mdm_dbg_stall_notify(ESOC_PRIMARY_REBOOT))
+			return NOTIFY_OK;
 		dev_dbg(&esoc_clink->dev, "Notifying esoc of cold reboot\n");
 		clink_ops->notify(ESOC_PRIMARY_REBOOT, esoc_clink);
 	}
@@ -102,6 +105,9 @@ static void mdm_crash_shutdown(const struct subsys_desc *mdm_subsys)
 								subsys);
 	const struct esoc_clink_ops * const clink_ops = esoc_clink->clink_ops;
 
+	if (mdm_dbg_stall_notify(ESOC_PRIMARY_CRASH))
+		return;
+
 	clink_ops->notify(ESOC_PRIMARY_CRASH, esoc_clink);
 }
 
@@ -115,6 +121,12 @@ static int mdm_subsys_shutdown(const struct subsys_desc *crashed_subsys,
 	const struct esoc_clink_ops * const clink_ops = esoc_clink->clink_ops;
 
 	if (mdm_drv->mode == CRASH || mdm_drv->mode == PEER_CRASH) {
+		if (mdm_dbg_stall_cmd(ESOC_PREPARE_DEBUG))
+			/* We want to mask debug command.
+			 * In this case return success
+			 * to move to next stage
+			 */
+			return 0;
 		ret = clink_ops->cmd_exe(ESOC_PREPARE_DEBUG,
 							esoc_clink);
 		if (ret) {
@@ -126,8 +138,15 @@ static int mdm_subsys_shutdown(const struct subsys_desc *crashed_subsys,
 		if (esoc_clink->subsys.sysmon_shutdown_ret)
 			ret = clink_ops->cmd_exe(ESOC_FORCE_PWR_OFF,
 							esoc_clink);
-		else
+		else {
+			if (mdm_dbg_stall_cmd(ESOC_PWR_OFF))
+				/* Since power off command is masked
+				 * we return success, and leave the state
+				 * of the command engine as is.
+				 */
+				return 0;
 			ret = clink_ops->cmd_exe(ESOC_PWR_OFF, esoc_clink);
+		}
 		if (ret) {
 			dev_err(&esoc_clink->dev, "failed to exe power off\n");
 			return ret;
@@ -151,6 +170,8 @@ static int mdm_subsys_powerup(const struct subsys_desc *crashed_subsys)
 		wait_for_completion(&mdm_drv->req_eng_wait);
 	}
 	if (mdm_drv->mode == PWR_OFF) {
+		if (mdm_dbg_stall_cmd(ESOC_PWR_ON))
+			return -EBUSY;
 		ret = clink_ops->cmd_exe(ESOC_PWR_ON, esoc_clink);
 		if (ret) {
 			dev_err(&esoc_clink->dev, "pwr on fail\n");
@@ -240,6 +261,14 @@ int esoc_ssr_probe(struct esoc_clink *esoc_clink, struct esoc_drv *drv)
 	ret = register_reboot_notifier(&mdm_drv->esoc_restart);
 	if (ret)
 		dev_err(&esoc_clink->dev, "register for reboot failed\n");
+	ret = mdm_dbg_eng_init(drv);
+	if (ret) {
+		debug_init_done = false;
+		dev_err(&esoc_clink->dev, "dbg engine failure\n");
+	} else {
+		dev_dbg(&esoc_clink->dev, "dbg engine initialized\n");
+		debug_init_done = true;
+	}
 	return 0;
 queue_err:
 	esoc_clink_unregister_ssr(esoc_clink);
diff --git a/drivers/esoc/mdm-dbg.h b/drivers/esoc/mdm-dbg.h
new file mode 100644
index 0000000..ae31339
--- /dev/null
+++ b/drivers/esoc/mdm-dbg.h
@@ -0,0 +1,54 @@
+/* Copyright (c) 2015, 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.
+ */
+
+static bool debug_init_done;
+
+#ifndef CONFIG_ESOC_MDM_DBG_ENG
+
+static inline bool dbg_check_cmd_mask(unsigned int cmd)
+{
+	return false;
+}
+
+static inline bool dbg_check_notify_mask(unsigned int notify)
+{
+	return false;
+}
+
+static inline int mdm_dbg_eng_init(struct esoc_drv *drv)
+{
+	return 0;
+}
+
+#else
+extern bool dbg_check_cmd_mask(unsigned int cmd);
+extern bool dbg_check_notify_mask(unsigned int notify);
+extern int mdm_dbg_eng_init(struct esoc_drv *drv);
+#endif
+
+static inline bool mdm_dbg_stall_cmd(unsigned int cmd)
+{
+	if (debug_init_done)
+		return dbg_check_cmd_mask(cmd);
+	else
+		return false;
+}
+
+static inline bool mdm_dbg_stall_notify(unsigned int notify)
+{
+	if (debug_init_done)
+		return dbg_check_notify_mask(notify);
+	else
+		return false;
+}
+
+