Merge "slim_msm: Support TX message queues"
diff --git a/drivers/slimbus/slim-msm-ngd.c b/drivers/slimbus/slim-msm-ngd.c
index 86ae8db..6962d53 100644
--- a/drivers/slimbus/slim-msm-ngd.c
+++ b/drivers/slimbus/slim-msm-ngd.c
@@ -9,7 +9,6 @@
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  */
-
 #include <linux/irq.h>
 #include <linux/kernel.h>
 #include <linux/init.h>
@@ -222,7 +221,7 @@
 	u8 *puc;
 	int ret = 0;
 	u8 la = txn->la;
-	u8 wbuf[SLIM_RX_MSGQ_BUF_LEN];
+	u8 wbuf[SLIM_MSGQ_BUF_LEN];
 
 	if (!pm_runtime_enabled(dev->dev) && dev->state == MSM_CTRL_ASLEEP &&
 			txn->mc != SLIM_USR_MC_REPORT_SATELLITE) {
@@ -235,17 +234,14 @@
 		 */
 		ngd_slim_runtime_resume(dev->dev);
 	}
-	if (txn->mc == (SLIM_MSG_CLK_PAUSE_SEQ_FLG |
-			SLIM_MSG_MC_RECONFIGURE_NOW)) {
-		if (dev->use_rx_msgqs == MSM_MSGQ_ENABLED) {
-			ret = sps_disconnect(dev->rx_msgq.sps);
-			dev->use_rx_msgqs = MSM_MSGQ_RESET;
-		}
-		if (!ret)
-			ret = msm_slim_qmi_power_request(dev, false);
-		else
-			pr_err("SPS pipe disconnect error:%d", ret);
-		return ret;
+	if ((txn->mc == (SLIM_MSG_CLK_PAUSE_SEQ_FLG |
+			SLIM_MSG_MC_RECONFIGURE_NOW)) &&
+			dev->state <= MSM_CTRL_SLEEPING) {
+		msm_slim_disconnect_endp(dev, &dev->rx_msgq,
+					&dev->use_rx_msgqs);
+		msm_slim_disconnect_endp(dev, &dev->tx_msgq,
+					&dev->use_tx_msgqs);
+		return msm_slim_qmi_power_request(dev, false);
 	}
 	else if (txn->mc & SLIM_MSG_CLK_PAUSE_SEQ_FLG)
 		return 0;
@@ -389,10 +385,19 @@
 			NGD_BASE(dev->ctrl.nr, dev->ver) + NGD_TX_MSG);
 	if (!ret) {
 		int timeout = wait_for_completion_timeout(&tx_sent, HZ);
-		if (!timeout)
+		if (!timeout) {
 			ret = -ETIMEDOUT;
-		else
+			/*
+			 * disconnect/recoonect pipe so that subsequent
+			 * transactions don't timeout due to unavailable
+			 * descriptors
+			 */
+			msm_slim_disconnect_endp(dev, &dev->tx_msgq,
+						&dev->use_tx_msgqs);
+			msm_slim_connect_endp(dev, &dev->tx_msgq, NULL);
+		} else {
 			ret = dev->err;
+		}
 	}
 	dev->wr_comp = NULL;
 	if (ret) {
@@ -469,7 +474,7 @@
 	struct slim_msg_txn txn;
 	struct slim_controller *ctrl = sb->ctrl;
 	DECLARE_COMPLETION_ONSTACK(done);
-	u8 wbuf[SLIM_RX_MSGQ_BUF_LEN];
+	u8 wbuf[SLIM_MSGQ_BUF_LEN];
 
 	*clkgear = ctrl->clkgear;
 	*subfrmc = 0;
@@ -631,24 +636,26 @@
 	return ret;
 }
 
-static void ngd_slim_setup_rx_path(struct msm_slim_ctrl *dev)
+static void ngd_slim_setup_msg_path(struct msm_slim_ctrl *dev)
 {
-	int ret;
 	if (dev->state == MSM_CTRL_DOWN) {
 		msm_slim_sps_init(dev, dev->bam_mem,
 			NGD_BASE(dev->ctrl.nr,
 			dev->ver) + NGD_STATUS, true);
 	} else {
 		if (dev->use_rx_msgqs == MSM_MSGQ_DISABLED)
-			return;
-		ret = msm_slim_connect_endp(dev, &dev->rx_msgq,
+			goto setup_tx_msg_path;
+		msm_slim_connect_endp(dev, &dev->rx_msgq,
 				&dev->rx_msgq_notify);
-		if (!ret)
-			dev->use_rx_msgqs = MSM_MSGQ_ENABLED;
-		else
-			pr_err("RX msgq not being used:%d", ret);
+
+setup_tx_msg_path:
+		if (dev->use_tx_msgqs == MSM_MSGQ_DISABLED)
+			return;
+		msm_slim_connect_endp(dev, &dev->tx_msgq,
+				NULL);
 	}
 }
+
 static void ngd_slim_rx(struct msm_slim_ctrl *dev, u8 *buf)
 {
 	u8 mc, mt, len;
@@ -677,9 +684,11 @@
 		txn.len = 4;
 		pr_info("SLIM SAT: Received master capability");
 		if (dev->state >= MSM_CTRL_ASLEEP) {
-			ngd_slim_setup_rx_path(dev);
+			ngd_slim_setup_msg_path(dev);
 			if (dev->use_rx_msgqs == MSM_MSGQ_ENABLED)
 				msgq_en |= NGD_CFG_RX_MSGQ_EN;
+			if (dev->use_tx_msgqs == MSM_MSGQ_ENABLED)
+				msgq_en |= NGD_CFG_TX_MSGQ_EN;
 			writel_relaxed(msgq_en, dev->base +
 					NGD_BASE(dev->ctrl.nr, dev->ver));
 			/* make sure NGD MSG-Q config goes through */
@@ -793,7 +802,7 @@
 		 * ADSP power collapse case, where HW wasn't reset.
 		 * Reconnect BAM pipes if disconnected
 		 */
-		ngd_slim_setup_rx_path(dev);
+		ngd_slim_setup_msg_path(dev);
 		return 0;
 	} else if (cur_state != MSM_CTRL_DOWN) {
 		pr_info("ADSP P.C. CTRL state:%d NGD not enumerated:0x%x",
@@ -806,6 +815,12 @@
 		sps_free_endpoint(endpoint->sps);
 		dev->use_rx_msgqs = MSM_MSGQ_RESET;
 	}
+	if (dev->use_tx_msgqs == MSM_MSGQ_DOWN) {
+		struct msm_slim_endp *endpoint = &dev->tx_msgq;
+		sps_disconnect(endpoint->sps);
+		sps_free_endpoint(endpoint->sps);
+		dev->use_tx_msgqs = MSM_MSGQ_RESET;
+	}
 	/*
 	 * ADSP power collapse case (OR SSR), where HW was reset
 	 * BAM programming will happen when capability message is received
@@ -957,6 +972,8 @@
 	/* disconnect BAM pipes */
 	if (dev->use_rx_msgqs == MSM_MSGQ_ENABLED)
 		dev->use_rx_msgqs = MSM_MSGQ_DOWN;
+	if (dev->use_tx_msgqs == MSM_MSGQ_ENABLED)
+		dev->use_tx_msgqs = MSM_MSGQ_DOWN;
 	msm_slim_sps_exit(dev, false);
 	mutex_lock(&ctrl->m_ctrl);
 	/* device up should be called again after SSR */
@@ -1083,6 +1100,10 @@
 		dev->use_rx_msgqs = MSM_MSGQ_DISABLED;
 	else
 		dev->use_rx_msgqs = MSM_MSGQ_RESET;
+
+	/* Enable TX message queues by default as recommended by HW */
+	dev->use_tx_msgqs = MSM_MSGQ_RESET;
+
 	init_completion(&dev->rx_msgq_notify);
 
 	/* Register with framework */
diff --git a/drivers/slimbus/slim-msm.c b/drivers/slimbus/slim-msm.c
index 30341e2..0166196 100644
--- a/drivers/slimbus/slim-msm.c
+++ b/drivers/slimbus/slim-msm.c
@@ -269,16 +269,66 @@
 	return ret;
 }
 
+/* Queue up Tx message buffer */
+static int msm_slim_post_tx_msgq(struct msm_slim_ctrl *dev, u8 *buf, int len)
+{
+	int ret;
+	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;
+
+	u32 phys_addr = mem->phys_base + (SLIM_MSGQ_BUF_LEN * ix);
+
+	for (ret = 0; ret < ((len + 3) >> 2); ret++)
+		pr_debug("BAM TX buf[%d]:0x%x", ret, ((u32 *)buf)[ret]);
+
+	ret = sps_transfer_one(pipe, phys_addr, ((len + 3) & 0xFC), NULL,
+				SPS_IOVEC_FLAG_EOT);
+	if (ret)
+		dev_err(dev->dev, "transfer_one() failed 0x%x, %d\n", ret, ix);
+
+	return ret;
+}
+
+static u32 *msm_slim_tx_msgq_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;
+	}
+	ret = sps_get_iovec(pipe, &iovec);
+	if (ret || iovec.addr == 0) {
+		dev_err(dev->dev, "sps_get_iovec() failed 0x%x\n", ret);
+		return NULL;
+	}
+
+	/* Calculate buffer index */
+	dev->tx_idx = (iovec.addr - mem->phys_base) / SLIM_MSGQ_BUF_LEN;
+
+	return (u32 *)((u8 *)mem->base + (dev->tx_idx * SLIM_MSGQ_BUF_LEN));
+}
+
 int msm_send_msg_buf(struct msm_slim_ctrl *dev, u32 *buf, u8 len, u32 tx_reg)
 {
-	int i;
-	for (i = 0; i < (len + 3) >> 2; i++) {
-		dev_dbg(dev->dev, "TX data:0x%x\n", buf[i]);
-		writel_relaxed(buf[i], dev->base + tx_reg + (i * 4));
+	if (dev->use_tx_msgqs != MSM_MSGQ_ENABLED) {
+		int i;
+		for (i = 0; i < (len + 3) >> 2; i++) {
+			dev_dbg(dev->dev, "AHB TX data:0x%x\n", buf[i]);
+			writel_relaxed(buf[i], dev->base + tx_reg + (i * 4));
+		}
+		/* Guarantee that message is sent before returning */
+		mb();
+		return 0;
 	}
-	/* Guarantee that message is sent before returning */
-	mb();
-	return 0;
+	return msm_slim_post_tx_msgq(dev, (u8 *)buf, len);
 }
 
 u32 *msm_get_msg_buf(struct msm_slim_ctrl *dev, int len)
@@ -287,7 +337,10 @@
 	 * Currently we block a transaction until the current one completes.
 	 * In case we need multiple transactions, use message Q
 	 */
-	return dev->tx_buf;
+	if (dev->use_tx_msgqs != MSM_MSGQ_ENABLED)
+		return dev->tx_buf;
+
+	return msm_slim_tx_msgq_return(dev);
 }
 
 static void
@@ -394,15 +447,17 @@
 
 	memset(&sps_descr_event, 0x00, sizeof(sps_descr_event));
 
-	sps_descr_event.mode = SPS_TRIGGER_CALLBACK;
-	sps_descr_event.options = SPS_O_DESC_DONE;
-	sps_descr_event.user = (void *)dev;
-	sps_descr_event.xfer_done = notify;
+	if (notify) {
+		sps_descr_event.mode = SPS_TRIGGER_CALLBACK;
+		sps_descr_event.options = SPS_O_DESC_DONE;
+		sps_descr_event.user = (void *)dev;
+		sps_descr_event.xfer_done = notify;
 
-	ret = sps_register_event(endpoint->sps, &sps_descr_event);
-	if (ret) {
-		dev_err(dev->dev, "sps_connect() failed 0x%x\n", ret);
-		goto sps_reg_event_failed;
+		ret = sps_register_event(endpoint->sps, &sps_descr_event);
+		if (ret) {
+			dev_err(dev->dev, "sps_connect() failed 0x%x\n", ret);
+			goto sps_reg_event_failed;
+		}
 	}
 
 	/* Register callback for errors */
@@ -423,13 +478,20 @@
 	 * Use (buf->size/4) - 1 for the number of buffer to post
 	 */
 
-	/* Setup the transfer */
-	for (i = 0; i < (MSM_SLIM_DESC_NUM - 1); i++) {
-		ret = msm_slim_post_rx_msgq(dev, i);
-		if (ret) {
-			dev_err(dev->dev, "post_rx_msgq() failed 0x%x\n", ret);
-			goto sps_transfer_failed;
+	if (endpoint == &dev->rx_msgq) {
+		/* Setup the transfer */
+		for (i = 0; i < (MSM_SLIM_DESC_NUM - 1); i++) {
+			ret = msm_slim_post_rx_msgq(dev, i);
+			if (ret) {
+				dev_err(dev->dev,
+					"post_rx_msgq() failed 0x%x\n", ret);
+				goto sps_transfer_failed;
+			}
 		}
+		dev->use_rx_msgqs = MSM_MSGQ_ENABLED;
+	} else {
+		dev->tx_idx = -1;
+		dev->use_tx_msgqs = MSM_MSGQ_ENABLED;
 	}
 
 	return 0;
@@ -440,6 +502,7 @@
 	sps_disconnect(endpoint->sps);
 	return ret;
 }
+
 static int msm_slim_init_rx_msgq(struct msm_slim_ctrl *dev, u32 pipe_reg)
 {
 	int ret;
@@ -489,10 +552,8 @@
 
 	ret = msm_slim_connect_endp(dev, endpoint, notify);
 
-	if (!ret) {
-		dev->use_rx_msgqs = MSM_MSGQ_ENABLED;
+	if (!ret)
 		return 0;
-	}
 
 	msm_slim_sps_mem_free(dev, mem);
 alloc_buffer_failed:
@@ -504,6 +565,67 @@
 	return ret;
 }
 
+static int msm_slim_init_tx_msgq(struct msm_slim_ctrl *dev, u32 pipe_reg)
+{
+	int ret;
+	u32 pipe_offset;
+	struct msm_slim_endp *endpoint = &dev->tx_msgq;
+	struct sps_connect *config = &endpoint->config;
+	struct sps_mem_buffer *descr = &config->desc;
+	struct sps_mem_buffer *mem = &endpoint->buf;
+
+	if (dev->use_tx_msgqs == MSM_MSGQ_DISABLED)
+		return 0;
+
+	/* Allocate the endpoint */
+	ret = msm_slim_init_endpoint(dev, endpoint);
+	if (ret) {
+		dev_err(dev->dev, "init_endpoint failed 0x%x\n", ret);
+		goto sps_init_endpoint_failed;
+	}
+
+	/* Get the pipe indices for the message queues */
+	pipe_offset = (readl_relaxed(dev->base + pipe_reg) & 0xfc) >> 2;
+	pipe_offset += 1;
+	dev_dbg(dev->dev, "TX Message queue pipe offset %d\n", pipe_offset);
+
+	config->mode = SPS_MODE_DEST;
+	config->source = SPS_DEV_HANDLE_MEM;
+	config->destination = dev->bam.hdl;
+	config->dest_pipe_index = pipe_offset;
+	config->src_pipe_index = 0;
+	config->options = SPS_O_ERROR | SPS_O_NO_Q |
+				SPS_O_ACK_TRANSFERS | SPS_O_AUTO_ENABLE;
+
+	/* Allocate memory for the FIFO descriptors */
+	ret = msm_slim_sps_mem_alloc(dev, descr,
+				MSM_TX_BUFS * 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);
+	if (ret) {
+		dev_err(dev->dev, "dma_alloc_coherent failed\n");
+		goto alloc_buffer_failed;
+	}
+	ret = msm_slim_connect_endp(dev, endpoint, NULL);
+
+	if (!ret)
+		return 0;
+
+	msm_slim_sps_mem_free(dev, mem);
+alloc_buffer_failed:
+	msm_slim_sps_mem_free(dev, descr);
+alloc_descr_failed:
+	msm_slim_free_endpoint(endpoint);
+sps_init_endpoint_failed:
+	dev->use_tx_msgqs = MSM_MSGQ_DISABLED;
+	return ret;
+}
+
 /* Registers BAM h/w resource with SPS driver and initializes msgq endpoints */
 int msm_slim_sps_init(struct msm_slim_ctrl *dev, struct resource *bam_mem,
 			u32 pipe_reg, bool remote)
@@ -529,8 +651,10 @@
 		},
 	};
 
-	if (dev->bam.hdl)
-		goto init_rx_msgq;
+	if (dev->bam.hdl) {
+		bam_handle = dev->bam.hdl;
+		goto init_msgq;
+	}
 	bam_props.ee = dev->ee;
 	bam_props.virt_addr = dev->bam.base;
 	bam_props.phys_addr = bam_mem->start;
@@ -563,40 +687,68 @@
 	if (ret) {
 		dev_err(dev->dev, "disabling BAM: reg-bam failed 0x%x\n", ret);
 		dev->use_rx_msgqs = MSM_MSGQ_DISABLED;
-		goto init_rx_msgq;
+		dev->use_tx_msgqs = MSM_MSGQ_DISABLED;
+		return ret;
 	}
 	dev->bam.hdl = bam_handle;
 	dev_dbg(dev->dev, "SLIM BAM registered, handle = 0x%x\n", bam_handle);
 
-init_rx_msgq:
+init_msgq:
 	ret = msm_slim_init_rx_msgq(dev, pipe_reg);
 	if (ret)
 		dev_err(dev->dev, "msm_slim_init_rx_msgq failed 0x%x\n", ret);
-	if (ret && bam_handle) {
+	if (ret && bam_handle)
+		dev->use_rx_msgqs = MSM_MSGQ_DISABLED;
+
+	ret = msm_slim_init_tx_msgq(dev, pipe_reg);
+	if (ret)
+		dev_err(dev->dev, "msm_slim_init_tx_msgq failed 0x%x\n", ret);
+	if (ret && bam_handle)
+		dev->use_tx_msgqs = MSM_MSGQ_DISABLED;
+
+	if (dev->use_tx_msgqs == MSM_MSGQ_DISABLED &&
+		dev->use_rx_msgqs == MSM_MSGQ_DISABLED && bam_handle) {
 		sps_deregister_bam_device(bam_handle);
 		dev->bam.hdl = 0L;
 	}
+
 	return ret;
 }
 
+void msm_slim_disconnect_endp(struct msm_slim_ctrl *dev,
+					struct msm_slim_endp *endpoint,
+					enum msm_slim_msgq *msgq_flag)
+{
+	if (*msgq_flag == MSM_MSGQ_ENABLED) {
+		sps_disconnect(endpoint->sps);
+		*msgq_flag = MSM_MSGQ_RESET;
+	}
+}
+
+static void msm_slim_remove_ep(struct msm_slim_ctrl *dev,
+					struct msm_slim_endp *endpoint,
+					enum msm_slim_msgq *msgq_flag)
+{
+	struct sps_connect *config = &endpoint->config;
+	struct sps_mem_buffer *descr = &config->desc;
+	struct sps_mem_buffer *mem = &endpoint->buf;
+	struct sps_register_event sps_event;
+	memset(&sps_event, 0x00, sizeof(sps_event));
+	msm_slim_sps_mem_free(dev, mem);
+	sps_register_event(endpoint->sps, &sps_event);
+	if (*msgq_flag == MSM_MSGQ_ENABLED) {
+		msm_slim_disconnect_endp(dev, endpoint, msgq_flag);
+		msm_slim_free_endpoint(endpoint);
+	}
+	msm_slim_sps_mem_free(dev, descr);
+}
+
 void msm_slim_sps_exit(struct msm_slim_ctrl *dev, bool dereg)
 {
-	if (dev->use_rx_msgqs >= MSM_MSGQ_ENABLED) {
-		struct msm_slim_endp *endpoint = &dev->rx_msgq;
-		struct sps_connect *config = &endpoint->config;
-		struct sps_mem_buffer *descr = &config->desc;
-		struct sps_mem_buffer *mem = &endpoint->buf;
-		struct sps_register_event sps_event;
-		memset(&sps_event, 0x00, sizeof(sps_event));
-		msm_slim_sps_mem_free(dev, mem);
-		sps_register_event(endpoint->sps, &sps_event);
-		if (dev->use_rx_msgqs == MSM_MSGQ_ENABLED) {
-			sps_disconnect(endpoint->sps);
-			msm_slim_free_endpoint(endpoint);
-			dev->use_rx_msgqs = MSM_MSGQ_RESET;
-		}
-		msm_slim_sps_mem_free(dev, descr);
-	}
+	if (dev->use_rx_msgqs >= MSM_MSGQ_ENABLED)
+		msm_slim_remove_ep(dev, &dev->rx_msgq, &dev->use_rx_msgqs);
+	if (dev->use_tx_msgqs >= MSM_MSGQ_ENABLED)
+		msm_slim_remove_ep(dev, &dev->tx_msgq, &dev->use_tx_msgqs);
 	if (dereg) {
 		sps_deregister_bam_device(dev->bam.hdl);
 		dev->bam.hdl = 0L;
diff --git a/drivers/slimbus/slim-msm.h b/drivers/slimbus/slim-msm.h
index cf2d26f..f8f625e 100644
--- a/drivers/slimbus/slim-msm.h
+++ b/drivers/slimbus/slim-msm.h
@@ -17,7 +17,9 @@
 #include <mach/msm_qmi_interface.h>
 
 /* Per spec.max 40 bytes per received message */
-#define SLIM_RX_MSGQ_BUF_LEN	40
+#define SLIM_MSGQ_BUF_LEN	40
+
+#define MSM_TX_BUFS	2
 
 #define SLIM_USR_MC_GENERIC_ACK		0x25
 #define SLIM_USR_MC_MASTER_CAPABILITY	0x0
@@ -200,7 +202,8 @@
 	u32			curr_bw;
 	u8			msg_cnt;
 	u32			tx_buf[10];
-	u8			rx_msgs[MSM_CONCUR_MSG][SLIM_RX_MSGQ_BUF_LEN];
+	u8			rx_msgs[MSM_CONCUR_MSG][SLIM_MSGQ_BUF_LEN];
+	int			tx_idx;
 	spinlock_t		rx_lock;
 	int			head;
 	int			tail;
@@ -211,6 +214,7 @@
 	struct msm_slim_sat	*satd[MSM_MAX_NSATS];
 	struct msm_slim_endp	pipes[7];
 	struct msm_slim_sps_bam	bam;
+	struct msm_slim_endp	tx_msgq;
 	struct msm_slim_endp	rx_msgq;
 	struct completion	rx_msgq_notify;
 	struct task_struct	*rx_msgq_thread;
@@ -219,6 +223,7 @@
 	struct mutex		tx_lock;
 	u8			pgdla;
 	enum msm_slim_msgq	use_rx_msgqs;
+	enum msm_slim_msgq	use_tx_msgqs;
 	int			pipe_b;
 	struct completion	reconf;
 	bool			reconf_busy;
@@ -285,6 +290,9 @@
 int msm_slim_connect_endp(struct msm_slim_ctrl *dev,
 				struct msm_slim_endp *endpoint,
 				struct completion *notify);
+void msm_slim_disconnect_endp(struct msm_slim_ctrl *dev,
+					struct msm_slim_endp *endpoint,
+					enum msm_slim_msgq *msgq_flag);
 void msm_slim_qmi_exit(struct msm_slim_ctrl *dev);
 int msm_slim_qmi_init(struct msm_slim_ctrl *dev, bool apps_is_master);
 int msm_slim_qmi_power_request(struct msm_slim_ctrl *dev, bool active);