esoc: Add support for autoboot

Some of the external SoC are flash based and can boot independently.
Extend esoc driver to support such auto boot esocs.

This patch also adds support for primary esoc. Primary esoc are
esoc that control secondary esoc such as modems. Primary esoc have
control over reset/poweroff of secondary esoc. Secondary esoc don't
have control over reset/poweroff of primary esoc. In general modems
are considered as secondary esoc while apps processor is considered
as primary esoc.

Change-Id: Id02417fcd122ac108cf75d3381ee7955f0f8f783
Signed-off-by: Arun KS <arunks@codeaurora.org>
Signed-off-by: Srivatsa Vaddagiri <vatsa@codeaurora.org>
[rananta@codeaurora.org: resolved trivial conflicts]
Signed-off-by: Raghavendra Rao Ananta <rananta@codeaurora.org>
diff --git a/drivers/esoc/esoc-mdm-4x.c b/drivers/esoc/esoc-mdm-4x.c
index 7eb0458..87f1f62 100644
--- a/drivers/esoc/esoc-mdm-4x.c
+++ b/drivers/esoc/esoc-mdm-4x.c
@@ -179,19 +179,37 @@
 	struct device *dev = mdm->dev;
 	int ret;
 	bool graceful_shutdown = false;
+	u32 status, err_fatal;
 
 	switch (cmd) {
 	case ESOC_PWR_ON:
+		if (esoc->auto_boot) {
+			/*
+			 * If esoc has already booted, we would have missed
+			 * status change interrupt. Read status and err_fatal
+			 * signals to arrive at the state of esoc.
+			 */
+			esoc->clink_ops->get_status(&status, esoc);
+			esoc->clink_ops->get_err_fatal(&err_fatal, esoc);
+			if (err_fatal)
+				return -EIO;
+			if (status && !mdm->ready) {
+				mdm->ready = true;
+				esoc->clink_ops->notify(ESOC_BOOT_DONE, esoc);
+			}
+		}
 		gpio_set_value(MDM_GPIO(mdm, AP2MDM_ERRFATAL), 0);
-		mdm_enable_irqs(mdm);
 		mdm->init = 1;
 		mdm_do_first_power_on(mdm);
+		mdm_enable_irqs(mdm);
 		break;
 	case ESOC_PWR_OFF:
 		mdm_disable_irqs(mdm);
 		mdm->debug = 0;
 		mdm->ready = false;
 		mdm->trig_cnt = 0;
+		if (esoc->primary)
+			break;
 		graceful_shutdown = true;
 		ret = sysmon_send_shutdown(&esoc->subsys);
 		if (ret) {
@@ -229,6 +247,8 @@
 				esoc->subsys.sysmon_shutdown_ret);
 		}
 
+		if (esoc->primary)
+			break;
 		/*
 		 * Force a shutdown of the mdm. This is required in order
 		 * to prevent the mdm from immediately powering back on
@@ -250,9 +270,12 @@
 		 */
 		mdm->ready = false;
 		cancel_delayed_work(&mdm->mdm2ap_status_check_work);
-		gpio_set_value(MDM_GPIO(mdm, AP2MDM_ERRFATAL), 1);
-		dev_dbg(mdm->dev, "set ap2mdm errfatal to force reset\n");
-		msleep(mdm->ramdump_delay_ms);
+		if (!mdm->esoc->auto_boot) {
+			gpio_set_value(MDM_GPIO(mdm, AP2MDM_ERRFATAL), 1);
+			dev_dbg(mdm->dev,
+				"set ap2mdm errfatal to force reset\n");
+			msleep(mdm->ramdump_delay_ms);
+		}
 		break;
 	case ESOC_EXE_DEBUG:
 		mdm->debug = 1;
@@ -380,6 +403,8 @@
 		status_down = false;
 		dev_dbg(dev, "signal apq err fatal for graceful restart\n");
 		gpio_set_value(MDM_GPIO(mdm, AP2MDM_ERRFATAL), 1);
+		if (esoc->primary)
+			break;
 		timeout = local_clock();
 		do_div(timeout, NSEC_PER_MSEC);
 		timeout += MDM_MODEM_TIMEOUT;
@@ -421,7 +446,7 @@
 		goto mdm_pwroff_irq;
 	esoc = mdm->esoc;
 	dev_err(dev, "%s: mdm sent errfatal interrupt\n",
-					 __func__);
+					__func__);
 	/* disable irq ?*/
 	esoc_clink_evt_notify(ESOC_ERR_FATAL, esoc);
 	return IRQ_HANDLED;
@@ -442,11 +467,25 @@
 		return IRQ_HANDLED;
 	dev = mdm->dev;
 	esoc = mdm->esoc;
+	/*
+	 * On auto boot devices, there is a possibility of receiving
+	 * status change interrupt before esoc_clink structure is
+	 * initialized. Ignore them.
+	 */
+	if (!esoc)
+		return IRQ_HANDLED;
 	value = gpio_get_value(MDM_GPIO(mdm, MDM2AP_STATUS));
 	if (value == 0 && mdm->ready) {
 		dev_err(dev, "unexpected reset external modem\n");
 		esoc_clink_evt_notify(ESOC_UNEXPECTED_RESET, esoc);
 	} else if (value == 1) {
+		/*
+		 * In auto_boot cases, bailout early if mdm
+		 * is up already.
+		 */
+		if (esoc->auto_boot && mdm->ready)
+			return IRQ_HANDLED;
+
 		cancel_delayed_work(&mdm->mdm2ap_status_check_work);
 		dev_dbg(dev, "status = 1: mdm is now ready\n");
 		mdm->ready = true;
@@ -454,6 +493,8 @@
 		queue_work(mdm->mdm_queue, &mdm->mdm_status_work);
 		if (mdm->get_restart_reason)
 			queue_work(mdm->mdm_queue, &mdm->restart_reason_work);
+		if (esoc->auto_boot)
+			esoc->clink_ops->notify(ESOC_BOOT_DONE, esoc);
 	}
 	return IRQ_HANDLED;
 }
@@ -582,13 +623,21 @@
 						&mdm->ramdump_delay_ms);
 	if (ret)
 		mdm->ramdump_delay_ms = DEF_RAMDUMP_DELAY;
-	/* Multilple gpio_request calls are allowed */
+	/*
+	 * In certain scenarios, multiple esoc devices are monitoring
+	 * same AP2MDM_STATUS line. But only one of them will have a
+	 * successful gpio_request call. Initialize gpio only if request
+	 * succeeds.
+	 */
 	if (gpio_request(MDM_GPIO(mdm, AP2MDM_STATUS), "AP2MDM_STATUS"))
 		dev_err(dev, "Failed to configure AP2MDM_STATUS gpio\n");
-	/* Multilple gpio_request calls are allowed */
+	else
+		gpio_direction_output(MDM_GPIO(mdm, AP2MDM_STATUS), 0);
 	if (gpio_request(MDM_GPIO(mdm, AP2MDM_ERRFATAL), "AP2MDM_ERRFATAL"))
 		dev_err(dev, "%s Failed to configure AP2MDM_ERRFATAL gpio\n",
 			   __func__);
+	else
+		gpio_direction_output(MDM_GPIO(mdm, AP2MDM_ERRFATAL), 0);
 	if (gpio_request(MDM_GPIO(mdm, MDM2AP_STATUS), "MDM2AP_STATUS")) {
 		dev_err(dev, "%s Failed to configure MDM2AP_STATUS gpio\n",
 			   __func__);
@@ -621,9 +670,6 @@
 		}
 	}
 
-	gpio_direction_output(MDM_GPIO(mdm, AP2MDM_STATUS), 0);
-	gpio_direction_output(MDM_GPIO(mdm, AP2MDM_ERRFATAL), 0);
-
 	if (gpio_is_valid(MDM_GPIO(mdm, AP2MDM_CHNLRDY)))
 		gpio_direction_output(MDM_GPIO(mdm, AP2MDM_CHNLRDY), 0);
 
diff --git a/drivers/esoc/esoc-mdm-drv.c b/drivers/esoc/esoc-mdm-drv.c
index 31cd8c4..77ae84b 100644
--- a/drivers/esoc/esoc-mdm-drv.c
+++ b/drivers/esoc/esoc-mdm-drv.c
@@ -13,6 +13,7 @@
 #include <linux/delay.h>
 #include <linux/workqueue.h>
 #include <linux/reboot.h>
+#include <linux/of.h>
 #include "esoc.h"
 #include "mdm-dbg.h"
 
@@ -74,7 +75,14 @@
 		break;
 	case ESOC_UNEXPECTED_RESET:
 	case ESOC_ERR_FATAL:
-		if (mdm_drv->mode == CRASH)
+		/*
+		 * Modem can crash while we are waiting for boot_done during
+		 * a subsystem_get(). Setting mode to CRASH will prevent a
+		 * subsequent subsystem_get() from entering poweron ops. Avoid
+		 * this by seting mode to CRASH only if device was up and
+		 * running.
+		 */
+		if (mdm_drv->mode == CRASH || mdm_drv->mode != RUN)
 			return;
 		mdm_drv->mode = CRASH;
 		queue_work(mdm_drv->mdm_queue, &mdm_drv->ssr_work);
@@ -164,8 +172,9 @@
 								subsys);
 	struct mdm_drv *mdm_drv = esoc_get_drv_data(esoc_clink);
 	const struct esoc_clink_ops * const clink_ops = esoc_clink->clink_ops;
+	int timeout = INT_MAX;
 
-	if (!esoc_req_eng_enabled(esoc_clink)) {
+	if (!esoc_clink->auto_boot && !esoc_req_eng_enabled(esoc_clink)) {
 		dev_dbg(&esoc_clink->dev, "Wait for req eng registration\n");
 		wait_for_completion(&mdm_drv->req_eng_wait);
 	}
@@ -190,8 +199,17 @@
 			return ret;
 		}
 	}
-	wait_for_completion(&mdm_drv->boot_done);
-	if (mdm_drv->boot_fail) {
+
+	/*
+	 * In autoboot case, it is possible that we can forever wait for
+	 * boot completion, when esoc fails to boot. This is because there
+	 * is no helper application which can alert esoc driver about boot
+	 * failure. Prevent going to wait forever in such case.
+	 */
+	if (esoc_clink->auto_boot)
+		timeout = 10 * HZ;
+	ret = wait_for_completion_timeout(&mdm_drv->boot_done, timeout);
+	if (mdm_drv->boot_fail || ret <= 0) {
 		dev_err(&esoc_clink->dev, "booting failed\n");
 		return -EIO;
 	}
@@ -219,10 +237,12 @@
 
 static int mdm_register_ssr(struct esoc_clink *esoc_clink)
 {
-	esoc_clink->subsys.shutdown = mdm_subsys_shutdown;
-	esoc_clink->subsys.ramdump = mdm_subsys_ramdumps;
-	esoc_clink->subsys.powerup = mdm_subsys_powerup;
-	esoc_clink->subsys.crash_shutdown = mdm_crash_shutdown;
+	struct subsys_desc *subsys = &esoc_clink->subsys;
+
+	subsys->shutdown = mdm_subsys_shutdown;
+	subsys->ramdump = mdm_subsys_ramdumps;
+	subsys->powerup = mdm_subsys_powerup;
+	subsys->crash_shutdown = mdm_crash_shutdown;
 	return esoc_clink_register_ssr(esoc_clink);
 }
 
diff --git a/drivers/esoc/esoc-mdm-pon.c b/drivers/esoc/esoc-mdm-pon.c
index 47d54db..293182e 100644
--- a/drivers/esoc/esoc-mdm-pon.c
+++ b/drivers/esoc/esoc-mdm-pon.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2014-2015, 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
@@ -68,6 +68,9 @@
 	struct device *dev = mdm->dev;
 
 	dev_dbg(dev, "Powering on modem for the first time\n");
+	if (mdm->esoc->auto_boot)
+		return 0;
+
 	mdm_toggle_soft_reset(mdm, false);
 	/* Add a delay to allow PON sequence to complete*/
 	mdelay(50);
@@ -134,6 +137,9 @@
 
 static void mdm4x_cold_reset(struct mdm_ctrl *mdm)
 {
+	if (!gpio_is_valid(MDM_GPIO(mdm, AP2MDM_SOFT_RESET)))
+		return;
+
 	dev_dbg(mdm->dev, "Triggering mdm cold reset");
 	gpio_direction_output(MDM_GPIO(mdm, AP2MDM_SOFT_RESET),
 			!!mdm->soft_reset_inverted);
diff --git a/drivers/esoc/esoc.h b/drivers/esoc/esoc.h
index e6794c2..b5ef764 100644
--- a/drivers/esoc/esoc.h
+++ b/drivers/esoc/esoc.h
@@ -60,6 +60,9 @@
  * @subsys_desc: descriptor for subsystem restart
  * @subsys_dev: ssr device handle.
  * @np: device tree node for esoc_clink.
+ * @auto_boot: boots independently.
+ * @primary: primary esoc controls(reset/poweroff) all secondary
+ *	 esocs, but not	otherway around.
  */
 struct esoc_clink {
 	const char *name;
@@ -79,6 +82,8 @@
 	struct subsys_desc subsys;
 	struct subsys_device *subsys_dev;
 	struct device_node *np;
+	bool auto_boot;
+	bool primary;
 };
 
 /**