msm_slim: Pause clock before disabling the reference clock.

Pause clock is used to conserve power and to make sure that reference
clock can be disabled.

Signed-off-by: Sagar Dharia <sdharia@codeaurora.org>
diff --git a/drivers/slimbus/slim-msm-ctrl.c b/drivers/slimbus/slim-msm-ctrl.c
index decc77f..cbee19c 100644
--- a/drivers/slimbus/slim-msm-ctrl.c
+++ b/drivers/slimbus/slim-msm-ctrl.c
@@ -411,6 +411,23 @@
 			 */
 			mb();
 			complete(&dev->rx_msgq_notify);
+		} else if (mc == SLIM_MSG_MC_REPORT_INFORMATION) {
+			u8 *buf = (u8 *)rx_buf;
+			u8 l_addr = buf[2];
+			u16 ele = (u16)buf[4] << 4;
+			ele |= ((buf[3] & 0xf0) >> 4);
+			dev_err(dev->dev, "Slim-dev:%d report inf element:0x%x",
+					l_addr, ele);
+			for (i = 0; i < len - 5; i++)
+				dev_err(dev->dev, "offset:0x%x:bit mask:%x",
+						i, buf[i+5]);
+			writel_relaxed(MGR_INT_RX_MSG_RCVD, dev->base +
+						MGR_INT_CLR);
+			/*
+			 * Guarantee that CLR bit write goes through
+			 * before exiting
+			 */
+			mb();
 		} else {
 			dev_err(dev->dev, "Unexpected MC,%x MT:%x, len:%d",
 						mc, mt, len);
@@ -742,6 +759,26 @@
 	return timeout ? dev->err : -ETIMEDOUT;
 }
 
+static int msm_clk_pause_wakeup(struct slim_controller *ctrl)
+{
+	struct msm_slim_ctrl *dev = slim_get_ctrldata(ctrl);
+	clk_enable(dev->rclk);
+	writel_relaxed(1, dev->base + FRM_WAKEUP);
+	/* Make sure framer wakeup write goes through before exiting function */
+	mb();
+	/*
+	 * Workaround: Currently, slave is reporting lost-sync messages
+	 * after slimbus comes out of clock pause.
+	 * Transaction with slave fail before slave reports that message
+	 * Give some time for that report to come
+	 * Slimbus wakes up in clock gear 10 at 24.576MHz. With each superframe
+	 * being 250 usecs, we wait for 20 superframes here to ensure
+	 * we get the message
+	 */
+	usleep_range(5000, 5000);
+	return 0;
+}
+
 static int msm_config_port(struct slim_controller *ctrl, u8 pn)
 {
 	struct msm_slim_ctrl *dev = slim_get_ctrldata(ctrl);
@@ -890,6 +927,15 @@
 			dev_dbg(dev->dev, "tid:%d, len:%d\n", tid, len - 4);
 			slim_msg_response(&dev->ctrl, &buf[4], tid,
 						len - 4);
+		} else if (mc == SLIM_MSG_MC_REPORT_INFORMATION) {
+			u8 l_addr = buf[2];
+			u16 ele = (u16)buf[4] << 4;
+			ele |= ((buf[3] & 0xf0) >> 4);
+			dev_err(dev->dev, "Slim-dev:%d report inf element:0x%x",
+					l_addr, ele);
+			for (i = 0; i < len - 5; i++)
+				dev_err(dev->dev, "offset:0x%x:bit mask:%x",
+						i, buf[i+5]);
 		} else {
 			dev_err(dev->dev, "unexpected message:mc:%x, mt:%x",
 					mc, mt);
@@ -1493,6 +1539,7 @@
 	dev->ctrl.nports = MSM_SLIM_NPORTS;
 	dev->ctrl.set_laddr = msm_set_laddr;
 	dev->ctrl.xfer_msg = msm_xfer_msg;
+	dev->ctrl.wakeup =  msm_clk_pause_wakeup;
 	dev->ctrl.config_port = msm_config_port;
 	dev->ctrl.port_xfer = msm_slim_port_xfer;
 	dev->ctrl.port_xfer_status = msm_slim_port_xfer_status;
@@ -1576,7 +1623,6 @@
 	wmb();
 
 	/* Framer register initialization */
-	writel_relaxed(1, dev->base + FRM_WAKEUP);
 	writel_relaxed((0xA << REF_CLK_GEAR) | (0xA << CLK_GEAR) |
 		(1 << ROOT_FREQ) | (1 << FRM_ACTIVE) | 1,
 		dev->base + FRM_CFG);
@@ -1677,27 +1723,44 @@
 {
 	struct platform_device *pdev = to_platform_device(device);
 	struct msm_slim_ctrl *dev = platform_get_drvdata(pdev);
-
-	/* Make sure we are not transmitting anything */
+	int ret = slim_ctrl_clk_pause(&dev->ctrl, false, SLIM_CLK_UNSPECIFIED);
+	/* Make sure clock pause goes through */
 	mutex_lock(&dev->tx_lock);
-	if (dev->reconf_busy) {
+	if (!ret && dev->reconf_busy) {
 		wait_for_completion(&dev->reconf);
 		dev->reconf_busy = false;
 	}
-	dev->suspended = 1;
 	mutex_unlock(&dev->tx_lock);
-	clk_disable(dev->rclk);
-	disable_irq(dev->irq);
-	return 0;
+	if (!ret) {
+		clk_disable(dev->rclk);
+		disable_irq(dev->irq);
+		dev->suspended = 1;
+	} else if (ret == -EBUSY) {
+		/*
+		 * If the clock pause failed due to active channels, there is
+		 * a possibility that some audio stream is active during suspend
+		 * We dont want to return suspend failure in that case so that
+		 * display and relevant components can still go to suspend.
+		 * If there is some other error, then it should be passed-on
+		 * to system level suspend
+		 */
+		ret = 0;
+	}
+	return ret;
 }
 
 static int msm_slim_resume(struct device *device)
 {
 	struct platform_device *pdev = to_platform_device(device);
 	struct msm_slim_ctrl *dev = platform_get_drvdata(pdev);
-	enable_irq(dev->irq);
-	clk_enable(dev->rclk);
-	dev->suspended = 0;
+	mutex_lock(&dev->tx_lock);
+	if (dev->suspended) {
+		dev->suspended = 0;
+		mutex_unlock(&dev->tx_lock);
+		enable_irq(dev->irq);
+		return slim_ctrl_clk_pause(&dev->ctrl, true, 0);
+	}
+	mutex_unlock(&dev->tx_lock);
 	return 0;
 }
 #else