Merge "slim_msm: support non-blocking writes from slimbus MSM controller"
diff --git a/drivers/slimbus/slim-msm-ctrl.c b/drivers/slimbus/slim-msm-ctrl.c
index c662a2b..924a028 100644
--- a/drivers/slimbus/slim-msm-ctrl.c
+++ b/drivers/slimbus/slim-msm-ctrl.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2011-2013, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2011-2014, 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
@@ -207,8 +207,7 @@
 		 * signalling completion/exiting ISR
 		 */
 		mb();
-		if (dev->wr_comp)
-			complete(dev->wr_comp);
+		msm_slim_manage_tx_msgq(dev, false, NULL);
 	}
 	if (stat & MGR_INT_RX_MSG_RCVD) {
 		u32 rx_buf[10];
@@ -372,8 +371,7 @@
 		}
 	}
 	txn->rl--;
-	pbuf = msm_get_msg_buf(dev, txn->rl);
-	dev->wr_comp = NULL;
+	pbuf = msm_get_msg_buf(dev, txn->rl, &done);
 	dev->err = 0;
 
 	if (txn->dt == SLIM_MSG_DEST_ENUMADDR) {
@@ -438,11 +436,8 @@
 	if (txn->mt == SLIM_MSG_MT_CORE &&
 		mc == SLIM_MSG_MC_BEGIN_RECONFIGURATION)
 		dev->reconf_busy = true;
-	dev->wr_comp = &done;
 	msm_send_msg_buf(dev, pbuf, txn->rl, MGR_TX_MSG);
 	timeout = wait_for_completion_timeout(&done, HZ);
-	if (!timeout)
-		dev->wr_comp = NULL;
 	if (mc == SLIM_MSG_MC_RECONFIGURE_NOW) {
 		if ((txn->mc == (SLIM_MSG_MC_RECONFIGURE_NOW |
 					SLIM_MSG_CLK_PAUSE_SEQ_FLG)) &&
@@ -505,7 +500,9 @@
 retry_laddr:
 	init_completion(&done);
 	mutex_lock(&dev->tx_lock);
-	buf = msm_get_msg_buf(dev, 9);
+	buf = msm_get_msg_buf(dev, 9, &done);
+	if (buf == NULL)
+		return -ENOMEM;
 	buf[0] = SLIM_MSG_ASM_FIRST_WORD(9, SLIM_MSG_MT_CORE,
 					SLIM_MSG_MC_ASSIGN_LOGICAL_ADDRESS,
 					SLIM_MSG_DEST_LOGICALADDR,
@@ -513,7 +510,6 @@
 	buf[1] = ea[3] | (ea[2] << 8) | (ea[1] << 16) | (ea[0] << 24);
 	buf[2] = laddr;
 
-	dev->wr_comp = &done;
 	ret = msm_send_msg_buf(dev, buf, 9, MGR_TX_MSG);
 	timeout = wait_for_completion_timeout(&done, HZ);
 	if (!timeout)
@@ -521,7 +517,6 @@
 	if (dev->err) {
 		ret = dev->err;
 		dev->err = 0;
-		dev->wr_comp = NULL;
 	}
 	mutex_unlock(&dev->tx_lock);
 	if (ret) {
@@ -1183,6 +1178,10 @@
 		ret = -ENOMEM;
 		goto err_get_res_failed;
 	}
+	dev->wr_comp = kzalloc(sizeof(struct completion *) * MSM_TX_BUFS,
+				GFP_KERNEL);
+	if (!dev->wr_comp)
+		return -ENOMEM;
 	dev->dev = &pdev->dev;
 	platform_set_drvdata(pdev, dev);
 	slim_set_ctrldata(&dev->ctrl, dev);
@@ -1271,7 +1270,8 @@
 	dev->ctrl.dev.parent = &pdev->dev;
 	dev->ctrl.dev.of_node = pdev->dev.of_node;
 
-	ret = request_irq(dev->irq, msm_slim_interrupt, IRQF_TRIGGER_HIGH,
+	ret = request_threaded_irq(dev->irq, NULL, msm_slim_interrupt,
+				IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
 				"msm_slim_irq", dev);
 	if (ret) {
 		dev_err(&pdev->dev, "request IRQ failed\n");
@@ -1400,6 +1400,7 @@
 err_ioremap_bam_failed:
 	iounmap(dev->base);
 err_ioremap_failed:
+	kfree(dev->wr_comp);
 	kfree(dev);
 err_get_res_failed:
 	release_mem_region(bam_mem->start, resource_size(bam_mem));
@@ -1437,6 +1438,7 @@
 	kthread_stop(dev->rx_msgq_thread);
 	iounmap(dev->bam.base);
 	iounmap(dev->base);
+	kfree(dev->wr_comp);
 	kfree(dev);
 	bam_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM,
 						"slimbus_bam_physical");
diff --git a/drivers/slimbus/slim-msm-ngd.c b/drivers/slimbus/slim-msm-ngd.c
index c29b71e..2498298 100644
--- a/drivers/slimbus/slim-msm-ngd.c
+++ b/drivers/slimbus/slim-msm-ngd.c
@@ -101,15 +101,13 @@
 								dev->err);
 		/* Guarantee that error interrupts are cleared */
 		mb();
-		if (dev->wr_comp)
-			complete(dev->wr_comp);
+		msm_slim_manage_tx_msgq(dev, false, NULL);
 
 	} else if (stat & NGD_INT_TX_MSG_SENT) {
 		writel_relaxed(NGD_INT_TX_MSG_SENT, ngd + NGD_INT_CLR);
 		/* Make sure interrupt is cleared */
 		mb();
-		if (dev->wr_comp)
-			complete(dev->wr_comp);
+		msm_slim_manage_tx_msgq(dev, false, NULL);
 	}
 	if (stat & NGD_INT_RX_MSG_RCVD) {
 		u32 rx_buf[10];
@@ -286,6 +284,7 @@
 	u16 txn_mc = txn->mc;
 	u8 wbuf[SLIM_MSGQ_BUF_LEN];
 	bool report_sat = false;
+	bool sync_wr = true;
 
 	if (txn->mc == SLIM_USR_MC_REPORT_SATELLITE &&
 		txn->mt == SLIM_MSG_MT_SRC_REFERRED_USER)
@@ -439,7 +438,25 @@
 		txn->rl = txn->len + 4;
 	}
 	txn->rl--;
-	pbuf = msm_get_msg_buf(dev, txn->rl);
+
+	if (txn->mt == SLIM_MSG_MT_CORE && txn->comp &&
+		dev->use_tx_msgqs == MSM_MSGQ_ENABLED &&
+		(txn_mc != SLIM_MSG_MC_REQUEST_INFORMATION &&
+		 txn_mc != SLIM_MSG_MC_REQUEST_VALUE &&
+		 txn_mc != SLIM_MSG_MC_REQUEST_CHANGE_VALUE &&
+		 txn_mc != SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION)) {
+		sync_wr = false;
+		pbuf = msm_get_msg_buf(dev, txn->rl, txn->comp);
+	} else if (txn->mt == SLIM_MSG_MT_DEST_REFERRED_USER &&
+			dev->use_tx_msgqs == MSM_MSGQ_ENABLED &&
+			txn->mc == SLIM_USR_MC_REPEAT_CHANGE_VALUE &&
+			txn->comp) {
+		sync_wr = false;
+		pbuf = msm_get_msg_buf(dev, txn->rl, txn->comp);
+	} else {
+		pbuf = msm_get_msg_buf(dev, txn->rl, &tx_sent);
+	}
+
 	if (!pbuf) {
 		SLIM_ERR(dev, "Message buffer unavailable\n");
 		ret = -ENOMEM;
@@ -510,10 +527,9 @@
 	 */
 	txn_mc = txn->mc;
 	txn_mt = txn->mt;
-	dev->wr_comp = &tx_sent;
 	ret = msm_send_msg_buf(dev, pbuf, txn->rl,
 			NGD_BASE(dev->ctrl.nr, dev->ver) + NGD_TX_MSG);
-	if (!ret) {
+	if (!ret && sync_wr) {
 		int timeout = wait_for_completion_timeout(&tx_sent, HZ);
 		if (!timeout) {
 			ret = -ETIMEDOUT;
@@ -529,7 +545,6 @@
 			ret = dev->err;
 		}
 	}
-	dev->wr_comp = NULL;
 	if (ret) {
 		u32 conf, stat, rx_msgq, int_stat, int_en, int_clr;
 		void __iomem *ngd = dev->base + NGD_BASE(dev->ctrl.nr,
@@ -1296,6 +1311,10 @@
 		dev_err(&pdev->dev, "no memory for MSM slimbus controller\n");
 		return PTR_ERR(dev);
 	}
+	dev->wr_comp = kzalloc(sizeof(struct completion *) * MSM_TX_BUFS,
+				GFP_KERNEL);
+	if (!dev->wr_comp)
+		return -ENOMEM;
 	dev->dev = &pdev->dev;
 	platform_set_drvdata(pdev, dev);
 	slim_set_ctrldata(&dev->ctrl, dev);
@@ -1379,6 +1398,7 @@
 	init_completion(&dev->reconf);
 	init_completion(&dev->ctrl_up);
 	mutex_init(&dev->tx_lock);
+	mutex_init(&dev->tx_buf_lock);
 	spin_lock_init(&dev->rx_lock);
 	dev->ee = 1;
 	dev->irq = irq->start;
@@ -1406,8 +1426,9 @@
 	dev->ctrl.dev.of_node = pdev->dev.of_node;
 	dev->state = MSM_CTRL_DOWN;
 
-	ret = request_irq(dev->irq, ngd_slim_interrupt,
-			IRQF_TRIGGER_HIGH, "ngd_slim_irq", dev);
+	ret = request_threaded_irq(dev->irq, NULL,
+			ngd_slim_interrupt,
+			IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "ngd_slim_irq", dev);
 
 	if (ret) {
 		dev_err(&pdev->dev, "request IRQ failed\n");
@@ -1470,6 +1491,7 @@
 	if (dev->sysfs_created)
 		sysfs_remove_file(&dev->dev->kobj,
 				&dev_attr_debug_mask.attr);
+	kfree(dev->wr_comp);
 	kfree(dev);
 	return ret;
 }
@@ -1492,6 +1514,7 @@
 	kthread_stop(dev->rx_msgq_thread);
 	iounmap(dev->bam.base);
 	iounmap(dev->base);
+	kfree(dev->wr_comp);
 	kfree(dev);
 	return 0;
 }
diff --git a/drivers/slimbus/slim-msm.c b/drivers/slimbus/slim-msm.c
index 915bf88..11741b8 100644
--- a/drivers/slimbus/slim-msm.c
+++ b/drivers/slimbus/slim-msm.c
@@ -11,6 +11,7 @@
  */
 #include <linux/pm_runtime.h>
 #include <linux/dma-mapping.h>
+#include <linux/delay.h>
 #include <linux/slimbus/slimbus.h>
 #include <mach/sps.h>
 #include "slim-msm.h"
@@ -390,9 +391,9 @@
 	struct msm_slim_endp *endpoint = &dev->tx_msgq;
 	struct sps_mem_buffer *mem = &endpoint->buf;
 	struct sps_pipe *pipe = endpoint->sps;
-	int ix = (buf - (u8 *)mem->base) / SLIM_MSGQ_BUF_LEN;
+	int ix = (buf - (u8 *)mem->base);
 
-	phys_addr_t phys_addr = mem->phys_base + (SLIM_MSGQ_BUF_LEN * ix);
+	phys_addr_t phys_addr = mem->phys_base + ix;
 
 	for (ret = 0; ret < ((len + 3) >> 2); ret++)
 		pr_debug("BAM TX buf[%d]:0x%x", ret, ((u32 *)buf)[ret]);
@@ -405,29 +406,110 @@
 	return ret;
 }
 
-static u32 *msm_slim_tx_msgq_return(struct msm_slim_ctrl *dev)
+void msm_slim_tx_msg_return(struct msm_slim_ctrl *dev)
 {
 	struct msm_slim_endp *endpoint = &dev->tx_msgq;
 	struct sps_mem_buffer *mem = &endpoint->buf;
 	struct sps_pipe *pipe = endpoint->sps;
 	struct sps_iovec iovec;
-	int ret;
-
-	/* first transaction after establishing connection */
-	if (dev->tx_idx == -1) {
-		dev->tx_idx = 0;
-		return mem->base;
+	int idx, ret = 0;
+	if (dev->use_tx_msgqs != MSM_MSGQ_ENABLED) {
+		/* use 1 buffer, non-blocking writes are not possible */
+		if (dev->wr_comp[0]) {
+			struct completion *comp = dev->wr_comp[0];
+			dev->wr_comp[0] = NULL;
+			complete(comp);
+		}
+		return;
 	}
-	ret = sps_get_iovec(pipe, &iovec);
-	if (ret || iovec.addr == 0) {
-		dev_err(dev->dev, "sps_get_iovec() failed 0x%x\n", ret);
+	while (!ret) {
+		ret = sps_get_iovec(pipe, &iovec);
+		if (ret || iovec.addr == 0) {
+			if (ret)
+				pr_err("SLIM TX get IOVEC failed:%d", ret);
+			return;
+		}
+		idx = (int) ((iovec.addr - mem->phys_base) / SLIM_MSGQ_BUF_LEN);
+		if (idx < MSM_TX_BUFS && dev->wr_comp[idx]) {
+			struct completion *comp = dev->wr_comp[idx];
+			dev->wr_comp[idx] = NULL;
+			complete(comp);
+		}
+		/* reclaim all packets that were delivered out of order */
+		if (idx != dev->tx_head)
+			pr_err("SLIM OUT OF ORDER TX:idx:%d, head:%d", idx,
+								dev->tx_head);
+		while (idx == dev->tx_head) {
+			dev->tx_head = (dev->tx_head + 1) % MSM_TX_BUFS;
+			idx++;
+			if (dev->tx_head == dev->tx_tail ||
+					dev->wr_comp[idx] != NULL)
+				break;
+		}
+	}
+}
+
+static u32 *msm_slim_modify_tx_buf(struct msm_slim_ctrl *dev,
+					struct completion *comp)
+{
+	struct msm_slim_endp *endpoint = &dev->tx_msgq;
+	struct sps_mem_buffer *mem = &endpoint->buf;
+	u32 *retbuf = NULL;
+	if ((dev->tx_tail + 1) % MSM_TX_BUFS == dev->tx_head)
+		return NULL;
+
+	retbuf = (u32 *)((u8 *)mem->base +
+				(dev->tx_tail * SLIM_MSGQ_BUF_LEN));
+	dev->wr_comp[dev->tx_tail] = comp;
+	dev->tx_tail = (dev->tx_tail + 1) % MSM_TX_BUFS;
+	return retbuf;
+}
+u32 *msm_slim_manage_tx_msgq(struct msm_slim_ctrl *dev, bool getbuf,
+					struct completion *comp)
+{
+	int ret = 0;
+	int retries = 0;
+	u32 *retbuf = NULL;
+
+	mutex_lock(&dev->tx_buf_lock);
+	if (!getbuf) {
+		msm_slim_tx_msg_return(dev);
+		mutex_unlock(&dev->tx_buf_lock);
 		return NULL;
 	}
 
-	/* Calculate buffer index */
-	dev->tx_idx = ((int)(iovec.addr - mem->phys_base)) / SLIM_MSGQ_BUF_LEN;
+	retbuf = msm_slim_modify_tx_buf(dev, comp);
+	if (retbuf) {
+		mutex_unlock(&dev->tx_buf_lock);
+		return retbuf;
+	}
 
-	return (u32 *)((u8 *)mem->base + (dev->tx_idx * SLIM_MSGQ_BUF_LEN));
+	do {
+		msm_slim_tx_msg_return(dev);
+		retbuf = msm_slim_modify_tx_buf(dev, comp);
+		if (!retbuf)
+			ret = -EAGAIN;
+		else {
+			if (retries > 0)
+				SLIM_INFO(dev, "SLIM TX retrieved:%d retries",
+							retries);
+			mutex_unlock(&dev->tx_buf_lock);
+			return retbuf;
+		}
+
+		/*
+		 * superframe size will vary based on clock gear
+		 * 1 superframe will consume at least 1 message
+		 * if HW is in good condition. With MX_RETRIES,
+		 * make sure we wait for a [3, 10] superframes
+		 * before deciding HW couldn't process descriptors
+		 */
+		usleep_range(100, 250);
+		retries++;
+	} while (ret && (retries < INIT_MX_RETRIES));
+
+	mutex_unlock(&dev->tx_buf_lock);
+	return NULL;
 }
 
 int msm_send_msg_buf(struct msm_slim_ctrl *dev, u32 *buf, u8 len, u32 tx_reg)
@@ -445,16 +527,19 @@
 	return msm_slim_post_tx_msgq(dev, (u8 *)buf, len);
 }
 
-u32 *msm_get_msg_buf(struct msm_slim_ctrl *dev, int len)
+u32 *msm_get_msg_buf(struct msm_slim_ctrl *dev, int len,
+			struct completion *comp)
 {
 	/*
 	 * Currently we block a transaction until the current one completes.
 	 * In case we need multiple transactions, use message Q
 	 */
-	if (dev->use_tx_msgqs != MSM_MSGQ_ENABLED)
+	if (dev->use_tx_msgqs != MSM_MSGQ_ENABLED) {
+		dev->wr_comp[0] = comp;
 		return dev->tx_buf;
+	}
 
-	return msm_slim_tx_msgq_return(dev);
+	return msm_slim_manage_tx_msgq(dev, true, comp);
 }
 
 static void
@@ -604,7 +689,8 @@
 		}
 		dev->use_rx_msgqs = MSM_MSGQ_ENABLED;
 	} else {
-		dev->tx_idx = -1;
+		dev->tx_tail = 0;
+		dev->tx_head = 0;
 		dev->use_tx_msgqs = MSM_MSGQ_ENABLED;
 	}
 
@@ -711,16 +797,18 @@
 	config->options = SPS_O_ERROR | SPS_O_NO_Q |
 				SPS_O_ACK_TRANSFERS | SPS_O_AUTO_ENABLE;
 
+	/* Desc and TX buf are circular queues */
 	/* Allocate memory for the FIFO descriptors */
 	ret = msm_slim_sps_mem_alloc(dev, descr,
-				MSM_TX_BUFS * sizeof(struct sps_iovec));
+				(MSM_TX_BUFS + 1) * sizeof(struct sps_iovec));
 	if (ret) {
 		dev_err(dev->dev, "unable to allocate SPS descriptors\n");
 		goto alloc_descr_failed;
 	}
 
-	/* Allocate memory for the message buffer(s), N descrs, 40-byte mesg */
-	ret = msm_slim_sps_mem_alloc(dev, mem, MSM_TX_BUFS * SLIM_MSGQ_BUF_LEN);
+	/* Allocate TX buffer from which descriptors are created */
+	ret = msm_slim_sps_mem_alloc(dev, mem, ((MSM_TX_BUFS + 1) *
+					SLIM_MSGQ_BUF_LEN));
 	if (ret) {
 		dev_err(dev->dev, "dma_alloc_coherent failed\n");
 		goto alloc_buffer_failed;
diff --git a/drivers/slimbus/slim-msm.h b/drivers/slimbus/slim-msm.h
index 9673208..5f12f24 100644
--- a/drivers/slimbus/slim-msm.h
+++ b/drivers/slimbus/slim-msm.h
@@ -22,7 +22,7 @@
 /* Per spec.max 40 bytes per received message */
 #define SLIM_MSGQ_BUF_LEN	40
 
-#define MSM_TX_BUFS	2
+#define MSM_TX_BUFS		32
 
 #define SLIM_USR_MC_GENERIC_ACK		0x25
 #define SLIM_USR_MC_MASTER_CAPABILITY	0x0
@@ -236,14 +236,15 @@
 	u8			msg_cnt;
 	u32			tx_buf[10];
 	u8			rx_msgs[MSM_CONCUR_MSG][SLIM_MSGQ_BUF_LEN];
-	int			tx_idx;
+	int			tx_tail;
+	int			tx_head;
 	spinlock_t		rx_lock;
 	int			head;
 	int			tail;
 	int			irq;
 	int			err;
 	int			ee;
-	struct completion	*wr_comp;
+	struct completion	**wr_comp;
 	struct msm_slim_sat	*satd[MSM_MAX_NSATS];
 	struct msm_slim_endp	pipes[7];
 	struct msm_slim_sps_bam	bam;
@@ -254,6 +255,7 @@
 	struct clk		*rclk;
 	struct clk		*hclk;
 	struct mutex		tx_lock;
+	struct mutex		tx_buf_lock;
 	u8			pgdla;
 	enum msm_slim_msgq	use_rx_msgqs;
 	enum msm_slim_msgq	use_tx_msgqs;
@@ -372,7 +374,10 @@
 int msm_slim_port_xfer(struct slim_controller *ctrl, u8 pn, phys_addr_t iobuf,
 			u32 len, struct completion *comp);
 int msm_send_msg_buf(struct msm_slim_ctrl *dev, u32 *buf, u8 len, u32 tx_reg);
-u32 *msm_get_msg_buf(struct msm_slim_ctrl *dev, int len);
+u32 *msm_get_msg_buf(struct msm_slim_ctrl *dev, int len,
+			struct completion *comp);
+u32 *msm_slim_manage_tx_msgq(struct msm_slim_ctrl *dev, bool getbuf,
+			struct completion *comp);
 int msm_slim_rx_msgq_get(struct msm_slim_ctrl *dev, u32 *data, int offset);
 int msm_slim_sps_init(struct msm_slim_ctrl *dev, struct resource *bam_mem,
 			u32 pipe_reg, bool remote);
diff --git a/drivers/slimbus/slimbus.c b/drivers/slimbus/slimbus.c
index fecf5ec..27b92ba 100644
--- a/drivers/slimbus/slimbus.c
+++ b/drivers/slimbus/slimbus.c
@@ -1100,7 +1100,7 @@
 	} else
 		ret = slim_processtxn(ctrl, SLIM_MSG_DEST_LOGICALADDR, mc, ec,
 				SLIM_MSG_MT_CORE, rbuf, wbuf, len, mlen,
-				NULL, sbdev->laddr, NULL);
+				msg->comp, sbdev->laddr, NULL);
 xfer_err:
 	return ret;
 }