msm: mdm: Add shutdown ioctl and send poweroff request to the mdm

When the whole phone is being powered off, a poweroff request needs
to be sent to the external modem so that it can shutdown gracefully.
This request needs to be triggered from userspace before kernel drivers
start unloading so that other drivers needed to send the request are
still available. The shutdown ioctl is provided for this purpose.
The request is sent over system monitor.

Crs-Fixed: 401598
Signed-off-by: Joel King <joelking@codeaurora.org>
diff --git a/arch/arm/mach-msm/mdm2.c b/arch/arm/mach-msm/mdm2.c
index 43c85bb..3364232 100644
--- a/arch/arm/mach-msm/mdm2.c
+++ b/arch/arm/mach-msm/mdm2.c
@@ -112,8 +112,12 @@
 
 	/* Wait for the modem to complete its power down actions. */
 	for (i = 20; i > 0; i--) {
-		if (gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 0)
+		if (gpio_get_value(mdm_drv->mdm2ap_status_gpio) == 0) {
+			if (mdm_debug_mask & MDM_DEBUG_MASK_SHDN_LOG)
+				pr_info("%s: mdm2ap_status went low, i = %d\n",
+					__func__, i);
 			break;
+		}
 		msleep(100);
 	}
 	if (i == 0) {
diff --git a/arch/arm/mach-msm/mdm_common.c b/arch/arm/mach-msm/mdm_common.c
index 2ee6938..0597c62 100644
--- a/arch/arm/mach-msm/mdm_common.c
+++ b/arch/arm/mach-msm/mdm_common.c
@@ -290,6 +290,15 @@
 		} else
 			pr_debug("%s Image upgrade not supported\n", __func__);
 		break;
+	case SHUTDOWN_CHARM:
+		mdm_drv->mdm_ready = 0;
+		if (mdm_debug_mask & MDM_DEBUG_MASK_SHDN_LOG)
+			pr_info("Sending shutdown request to mdm\n");
+		ret = sysmon_send_shutdown(SYSMON_SS_EXT_MODEM);
+		if (ret)
+			pr_err("%s: Graceful shutdown of the external modem failed, ret = %d\n",
+			   __func__, ret);
+		break;
 	default:
 		pr_err("%s: invalid ioctl cmd = %d\n", __func__, _IOC_NR(cmd));
 		ret = -EINVAL;
@@ -382,6 +391,9 @@
 {
 	int value = gpio_get_value(mdm_drv->mdm2ap_status_gpio);
 
+	if ((mdm_debug_mask & MDM_DEBUG_MASK_SHDN_LOG) && (value == 0))
+		pr_info("%s: mdm2ap_status went low\n", __func__);
+
 	pr_debug("%s: mdm sent status change interrupt\n", __func__);
 	if (value == 0 && mdm_drv->mdm_ready == 1) {
 		pr_info("%s: unexpected reset external modem\n", __func__);
diff --git a/arch/arm/mach-msm/mdm_private.h b/arch/arm/mach-msm/mdm_private.h
index c406b89..9e865c5 100644
--- a/arch/arm/mach-msm/mdm_private.h
+++ b/arch/arm/mach-msm/mdm_private.h
@@ -14,6 +14,7 @@
 #define _ARCH_ARM_MACH_MSM_MDM_PRIVATE_H
 
 #define MDM_DEBUG_MASK_VDDMIN_SETUP (0x00000002)
+#define MDM_DEBUG_MASK_SHDN_LOG     (0x00000004)
 #define GPIO_IS_VALID(gpio) \
 	(gpio != -1)
 struct mdm_modem_drv;
diff --git a/arch/arm/mach-msm/sysmon.c b/arch/arm/mach-msm/sysmon.c
index 78290a2..0d62e6a 100644
--- a/arch/arm/mach-msm/sysmon.c
+++ b/arch/arm/mach-msm/sysmon.c
@@ -159,6 +159,33 @@
 }
 
 /**
+ * sysmon_send_shutdown() - send shutdown command to a
+ * subsystem.
+ * @dest_ss:	ID of subsystem to send to.
+ *
+ * Returns 0 for success, -EINVAL for an invalid destination, -ENODEV if
+ * the SMD transport channel is not open, -ETIMEDOUT if the destination
+ * subsystem does not respond, and -ENOSYS if the destination subsystem
+ * responds with something unexpected.
+ *
+ * If CONFIG_MSM_SYSMON_COMM is not defined, always return success (0).
+ */
+int sysmon_send_shutdown(enum subsys_id dest_ss)
+{
+	struct sysmon_subsys *ss = &subsys[dest_ss];
+	const char tx_buf[] = "ssr:poweroff";
+	int ret;
+
+	if (dest_ss < 0 || dest_ss >= SYSMON_NUM_SS)
+		return -EINVAL;
+
+	mutex_lock(&ss->lock);
+	ret = sysmon_send_msg(ss, tx_buf, ARRAY_SIZE(tx_buf));
+	mutex_unlock(&ss->lock);
+	return ret;
+}
+
+/**
  * sysmon_get_reason() - Retrieve failure reason from a subsystem.
  * @dest_ss:	ID of subsystem to query
  * @buf:	Caller-allocated buffer for the returned NUL-terminated reason
diff --git a/arch/arm/mach-msm/sysmon.h b/arch/arm/mach-msm/sysmon.h
index 77c3329..439c617 100644
--- a/arch/arm/mach-msm/sysmon.h
+++ b/arch/arm/mach-msm/sysmon.h
@@ -38,6 +38,7 @@
 int sysmon_send_event(enum subsys_id dest_ss, const char *event_ss,
 		      enum subsys_notif_type notif);
 int sysmon_get_reason(enum subsys_id dest_ss, char *buf, size_t len);
+int sysmon_send_shutdown(enum subsys_id dest_ss);
 #else
 static inline int sysmon_send_event(enum subsys_id dest_ss,
 				    const char *event_ss,