tspp: improve tspp_open_stream/tspp_close_stream behavior

The TSPP driver's kernel API supports opening and closing a TSPP stream
using the tspp_open_stream/tspp_close_stream API functions. This commit
fixes a few minor issues with the behavior and usage of this API.

tspp_open_stream and tspp_close_stream were fixed to connect/disconnect
the appropriate TSIF source based on the TSIF reference count.

The call to tspp_close_stream from within tspp_close_channel was removed,
since a kernel driver that uses the TSPP driver API is expected to call
tspp_close_stream explicitly, and not rely on the TSPP driver to close
the stream implicitly when the channel is closed.

An ioctl was added to allow user-space application to close the stream.

Change-Id: If49b440d9d83c8bba54aeabc18e8f06b3cc11b3e
Signed-off-by: Liron Kuch <lkuch@codeaurora.org>
diff --git a/drivers/media/dvb/mpq/demux/mpq_dmx_plugin_tspp_v1.c b/drivers/media/dvb/mpq/demux/mpq_dmx_plugin_tspp_v1.c
index 49f87ba..632e864 100644
--- a/drivers/media/dvb/mpq/demux/mpq_dmx_plugin_tspp_v1.c
+++ b/drivers/media/dvb/mpq/demux/mpq_dmx_plugin_tspp_v1.c
@@ -1433,8 +1433,8 @@
 	if (*channel_ref_count == 0) {
 		/* channel is not used any more, release it */
 		tspp_unregister_notification(0, channel_id);
-		tspp_close_channel(0, channel_id);
 		tspp_close_stream(0, channel_id);
+		tspp_close_channel(0, channel_id);
 		atomic_set(data_cnt, 0);
 
 		if (allocation_mode == MPQ_DMX_TSPP_CONTIGUOUS_PHYS_ALLOC)
diff --git a/drivers/misc/tspp.c b/drivers/misc/tspp.c
index ef23871..9a53817 100644
--- a/drivers/misc/tspp.c
+++ b/drivers/misc/tspp.c
@@ -1319,10 +1319,12 @@
 			pr_err("tspp: error starting tsif0");
 			return -EBUSY;
 		}
-		val = readl_relaxed(pdev->base + TSPP_CONTROL);
-		writel_relaxed(val & ~TSPP_CONTROL_TSP_TSIF0_SRC_DIS,
-			pdev->base + TSPP_CONTROL);
-		wmb();
+		if (pdev->tsif[0].ref_count == 1) {
+			val = readl_relaxed(pdev->base + TSPP_CONTROL);
+			writel_relaxed(val & ~TSPP_CONTROL_TSP_TSIF0_SRC_DIS,
+				pdev->base + TSPP_CONTROL);
+			wmb();
+		}
 		break;
 	case TSPP_SOURCE_TSIF1:
 		if (tspp_config_gpios(pdev, channel->src, 1) != 0) {
@@ -1334,10 +1336,12 @@
 			pr_err("tspp: error starting tsif1");
 			return -EBUSY;
 		}
-		val = readl_relaxed(pdev->base + TSPP_CONTROL);
-		writel_relaxed(val & ~TSPP_CONTROL_TSP_TSIF1_SRC_DIS,
-			pdev->base + TSPP_CONTROL);
-		wmb();
+		if (pdev->tsif[1].ref_count == 1) {
+			val = readl_relaxed(pdev->base + TSPP_CONTROL);
+			writel_relaxed(val & ~TSPP_CONTROL_TSP_TSIF1_SRC_DIS,
+				pdev->base + TSPP_CONTROL);
+			wmb();
+		}
 		break;
 	case TSPP_SOURCE_MEM:
 		break;
@@ -1363,6 +1367,7 @@
 int tspp_close_stream(u32 dev, u32 channel_id)
 {
 	u32 val;
+	u32 prev_ref_count;
 	struct tspp_device *pdev;
 	struct tspp_channel *channel;
 
@@ -1379,23 +1384,30 @@
 
 	switch (channel->src) {
 	case TSPP_SOURCE_TSIF0:
+		prev_ref_count = pdev->tsif[0].ref_count;
 		tspp_stop_tsif(&pdev->tsif[0]);
 		if (tspp_config_gpios(pdev, channel->src, 0) != 0)
 			pr_err("tspp: error disabling tsif0 GPIOs\n");
 
-		val = readl_relaxed(pdev->base + TSPP_CONTROL);
-		writel_relaxed(val | TSPP_CONTROL_TSP_TSIF0_SRC_DIS,
-			pdev->base + TSPP_CONTROL);
-		wmb();
+		if (prev_ref_count == 1) {
+			val = readl_relaxed(pdev->base + TSPP_CONTROL);
+			writel_relaxed(val | TSPP_CONTROL_TSP_TSIF0_SRC_DIS,
+				pdev->base + TSPP_CONTROL);
+			wmb();
+		}
 		break;
 	case TSPP_SOURCE_TSIF1:
+		prev_ref_count = pdev->tsif[1].ref_count;
 		tspp_stop_tsif(&pdev->tsif[1]);
 		if (tspp_config_gpios(pdev, channel->src, 0) != 0)
 			pr_err("tspp: error disabling tsif0 GPIOs\n");
 
-		val = readl_relaxed(pdev->base + TSPP_CONTROL);
-		writel_relaxed(val | TSPP_CONTROL_TSP_TSIF1_SRC_DIS,
-			pdev->base + TSPP_CONTROL);
+		if (prev_ref_count == 1) {
+			val = readl_relaxed(pdev->base + TSPP_CONTROL);
+			writel_relaxed(val | TSPP_CONTROL_TSP_TSIF1_SRC_DIS,
+				pdev->base + TSPP_CONTROL);
+			wmb();
+		}
 		break;
 	case TSPP_SOURCE_MEM:
 		break;
@@ -1595,9 +1607,6 @@
 	}
 	channel->filter_count = 0;
 
-	/* stop the stream */
-	tspp_close_stream(dev, channel->id);
-
 	/* disconnect the bam */
 	if (sps_disconnect(channel->pipe) != 0)
 		pr_warn("tspp: Error freeing sps endpoint (%i)", channel->id);
@@ -2435,7 +2444,7 @@
 	channel = filp->private_data;
 	dev = channel->pdev->pdev->id;
 
-	if (!param1)
+	if ((param0 != TSPP_IOCTL_CLOSE_STREAM) && !param1)
 		return -EINVAL;
 
 	switch (param0) {
@@ -2502,6 +2511,9 @@
 			sizeof(struct tspp_buffer)) == 0)
 			rc = tspp_set_buffer_size(channel, &b);
 		break;
+	case TSPP_IOCTL_CLOSE_STREAM:
+		rc = tspp_close_stream(dev, channel->id);
+		break;
 	default:
 		pr_err("tspp: Unknown ioctl %i", param0);
 	}
diff --git a/include/linux/tspp.h b/include/linux/tspp.h
index 551fbb0..c790c28 100644
--- a/include/linux/tspp.h
+++ b/include/linux/tspp.h
@@ -88,5 +88,7 @@
 	_IOW(TSPP_IOCTL_BASE, 5, struct tspp_system_keys)
 #define TSPP_IOCTL_BUFFER_SIZE		\
 	_IOW(TSPP_IOCTL_BASE, 6, struct tspp_buffer)
+#define TSPP_IOCTL_CLOSE_STREAM		\
+	_IO(TSPP_IOCTL_BASE, 7)
 
 #endif /* _TSPP_H_ */