Merge "media: dvb: mpq: TSPP output buffer allocation by demux plugin"
diff --git a/arch/arm/mach-msm/include/mach/msm_tspp.h b/arch/arm/mach-msm/include/mach/msm_tspp.h
index 5395b88..a024a99 100644
--- a/arch/arm/mach-msm/include/mach/msm_tspp.h
+++ b/arch/arm/mach-msm/include/mach/msm_tspp.h
@@ -25,31 +25,34 @@
 struct tspp_data_descriptor {
 	void *virt_base;   /* logical address of the actual data */
 	u32 phys_base;     /* physical address of the actual data */
-	int size;			 /* size of buffer in bytes */
+	u32 size;          /* size of buffer in bytes */
 	int id;            /* unique identifier */
 	void *user;        /* user-defined data */
 };
 
-typedef void (tspp_notifier)(int channel, void *user);
-typedef void* (tspp_allocator)(int channel, int size,
+typedef void (tspp_notifier)(int channel_id, void *user);
+typedef void* (tspp_allocator)(int channel_id, u32 size,
 	u32 *phys_base, void *user);
+typedef void (tspp_memfree)(int channel_id, u32 size,
+	void *virt_base, u32 phys_base, void *user);
 
 /* Kernel API functions */
 int tspp_open_stream(u32 dev, u32 channel_id,
-	struct tspp_select_source *source);
+			struct tspp_select_source *source);
 int tspp_close_stream(u32 dev, u32 channel_id);
 int tspp_open_channel(u32 dev, u32 channel_id);
 int tspp_close_channel(u32 dev, u32 channel_id);
-int tspp_add_filter(u32 dev, u32 channel_id,	struct tspp_filter *filter);
+int tspp_add_filter(u32 dev, u32 channel_id, struct tspp_filter *filter);
 int tspp_remove_filter(u32 dev, u32 channel_id,	struct tspp_filter *filter);
 int tspp_set_key(u32 dev, u32 channel_id, struct tspp_key *key);
-int tspp_register_notification(u32 dev, u32 channel, tspp_notifier *notify,
+int tspp_register_notification(u32 dev, u32 channel_id, tspp_notifier *notify,
 	void *data, u32 timer_ms);
-int tspp_unregister_notification(u32 dev, u32 channel);
-const struct tspp_data_descriptor *tspp_get_buffer(u32 dev, u32 channel);
-int tspp_release_buffer(u32 dev, u32 channel, u32 descriptor_id);
+int tspp_unregister_notification(u32 dev, u32 channel_id);
+const struct tspp_data_descriptor *tspp_get_buffer(u32 dev, u32 channel_id);
+int tspp_release_buffer(u32 dev, u32 channel_id, u32 descriptor_id);
 int tspp_allocate_buffers(u32 dev, u32 channel_id, u32 count,
-	u32 size, u32 int_freq, tspp_allocator *alloc, void *user);
+	u32 size, u32 int_freq, tspp_allocator *alloc,
+	tspp_memfree *memfree, void *user);
 
 #endif /* _MSM_TSPP_H_ */
 
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 191a0c4..9fb6004 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
@@ -18,15 +18,15 @@
 #include "mpq_dmx_plugin_common.h"
 
 
-#define TSIF_COUNT					2
+#define TSIF_COUNT			2
 
 #define TSPP_MAX_PID_FILTER_NUM		16
 
 /* Max number of section filters */
-#define TSPP_MAX_SECTION_FILTER_NUM		64
+#define TSPP_MAX_SECTION_FILTER_NUM	64
 
 /* For each TSIF we allocate two pipes, one for PES and one for sections */
-#define TSPP_PES_CHANNEL				0
+#define TSPP_PES_CHANNEL			0
 #define TSPP_SECTION_CHANNEL			1
 
 /* the channel_id set to TSPP driver based on TSIF number and channel type */
@@ -35,7 +35,7 @@
 #define TSPP_GET_TSIF_NUM(ch_id)		(ch_id >> 1)
 
 /* mask that set to care for all bits in pid filter */
-#define TSPP_PID_MASK					0x1FFF
+#define TSPP_PID_MASK				0x1FFF
 
 /* dvb-demux defines pid 0x2000 as full capture pid */
 #define TSPP_PASS_THROUGH_PID			0x2000
@@ -53,6 +53,7 @@
  * Meanning about 82 notifications per second.
  */
 #define MAX_BAM_DESCRIPTOR_SIZE		(32*1024 - 1)
+
 #define TSPP_BUFFER_SIZE			\
 	((MAX_BAM_DESCRIPTOR_SIZE / TSPP_RAW_TTS_SIZE) * TSPP_RAW_TTS_SIZE)
 
@@ -65,11 +66,18 @@
 /* Channel timeout in msec */
 #define TSPP_CHANNEL_TIMEOUT			16
 
+enum mem_buffer_allocation_mode {
+	MPQ_DMX_TSPP_INTERNAL_ALLOC = 0,
+	MPQ_DMX_TSPP_CONTIGUOUS_PHYS_ALLOC = 1
+};
+
 /* module parameters for load time configuration */
 static int clock_inv;
 static int tsif_mode = 2;
+static int allocation_mode = MPQ_DMX_TSPP_INTERNAL_ALLOC;
 module_param(tsif_mode, int, S_IRUGO);
 module_param(clock_inv, int, S_IRUGO);
+module_param(allocation_mode, int, S_IRUGO);
 
 /*
  * Work scheduled each time TSPP notifies dmx
@@ -97,6 +105,15 @@
 		/* work used to submit to workqueue to process pes channel */
 		struct tspp_work pes_work;
 
+		/* ION handle used for TSPP data buffer allocation */
+		struct ion_handle *pes_mem_heap_handle;
+		/* TSPP data buffer heap virtual base address */
+		void *pes_mem_heap_virt_base;
+		/* TSPP data buffer heap physical base address */
+		ion_phys_addr_t pes_mem_heap_phys_base;
+		/* buffer allocation index */
+		int pes_index;
+
 		/*
 		 * TSPP pipe holding all TS packets with section data.
 		 * The following is reference count for number of feeds
@@ -107,6 +124,15 @@
 		/* work used to submit to workqueue to process pes channel */
 		struct tspp_work section_work;
 
+		/* ION handle used for TSPP data buffer allocation */
+		struct ion_handle *section_mem_heap_handle;
+		/* TSPP data buffer heap virtual base address */
+		void *section_mem_heap_virt_base;
+		/* TSPP data buffer heap physical base address */
+		ion_phys_addr_t section_mem_heap_phys_base;
+		/* buffer allocation index */
+		int section_index;
+
 		/*
 		 * Holds PIDs of allocated TSPP filters along with
 		 * how many feeds are opened on same PID.
@@ -128,8 +154,65 @@
 		/* mutex protecting the data-structure */
 		struct mutex mutex;
 	} tsif[TSIF_COUNT];
+
+	/* ION client used for TSPP data buffer allocation */
+	struct ion_client *ion_client;
 } mpq_dmx_tspp_info;
 
+static void *tspp_mem_allocator(int channel_id, u32 size,
+				u32 *phys_base, void *user)
+{
+	void *virt_addr = NULL;
+	int i = TSPP_GET_TSIF_NUM(channel_id);
+
+	if (TSPP_IS_PES_CHANNEL(channel_id)) {
+		if (mpq_dmx_tspp_info.tsif[i].pes_index == TSPP_BUFFER_COUNT)
+			return NULL;
+		virt_addr =
+			(mpq_dmx_tspp_info.tsif[i].pes_mem_heap_virt_base +
+			(mpq_dmx_tspp_info.tsif[i].pes_index * size));
+		*phys_base =
+			(mpq_dmx_tspp_info.tsif[i].pes_mem_heap_phys_base +
+			(mpq_dmx_tspp_info.tsif[i].pes_index * size));
+		mpq_dmx_tspp_info.tsif[i].pes_index++;
+	} else {
+		if (mpq_dmx_tspp_info.tsif[i].section_index ==
+						TSPP_BUFFER_COUNT)
+			return NULL;
+		virt_addr =
+			(mpq_dmx_tspp_info.tsif[i].section_mem_heap_virt_base +
+			(mpq_dmx_tspp_info.tsif[i].section_index * size));
+		*phys_base =
+			(mpq_dmx_tspp_info.tsif[i].section_mem_heap_phys_base +
+			(mpq_dmx_tspp_info.tsif[i].section_index * size));
+		mpq_dmx_tspp_info.tsif[i].section_index++;
+	}
+
+	return virt_addr;
+}
+
+static void tspp_mem_free(int channel_id, u32 size,
+			void *virt_base, u32 phys_base, void *user)
+{
+	int i = TSPP_GET_TSIF_NUM(channel_id);
+
+	/*
+	 * actual buffer heap free is done in mpq_dmx_tspp_plugin_exit().
+	 * we update index here, so if this function is called repetitively
+	 * for all the buffers, then afterwards tspp_mem_allocator()
+	 * can be called again.
+	 * Note: it would be incorrect to call tspp_mem_allocator()
+	 * a few times, then call tspp_mem_free(), then call
+	 * tspp_mem_allocator() again.
+	 */
+	if (TSPP_IS_PES_CHANNEL(channel_id)) {
+		if (mpq_dmx_tspp_info.tsif[i].pes_index > 0)
+			mpq_dmx_tspp_info.tsif[i].pes_index--;
+	} else {
+		if (mpq_dmx_tspp_info.tsif[i].section_index > 0)
+			mpq_dmx_tspp_info.tsif[i].section_index--;
+	}
+}
 
 /**
  * Returns a free filter slot that can be used.
@@ -374,20 +457,34 @@
 					   (void *)tsif,
 					   TSPP_CHANNEL_TIMEOUT);
 
-		/* TODO: register allocater and provide allocation function
-		 * that allocate from continous memory so that we can have
+		/* register allocater and provide allocation function
+		 * that allocates from continous memory so that we can have
 		 * big notification size, smallest descriptor, and still provide
 		 * TZ with single big buffer based on notification size.
 		 */
 
-		/* set buffer/descriptor size and count */
-		ret = tspp_allocate_buffers(0,
-					    channel_id,
-					    TSPP_BUFFER_COUNT,
-					    TSPP_BUFFER_SIZE,
-					    TSPP_NOTIFICATION_SIZE,
-					    NULL,
-					    NULL);
+		/* set buffer/descriptor size and count,
+		 * allocate TSPP data buffers
+		 */
+		if (allocation_mode == MPQ_DMX_TSPP_CONTIGUOUS_PHYS_ALLOC) {
+			ret = tspp_allocate_buffers(0,
+						    channel_id,
+						    TSPP_BUFFER_COUNT,
+						    TSPP_BUFFER_SIZE,
+						    TSPP_NOTIFICATION_SIZE,
+						    tspp_mem_allocator,
+						    tspp_mem_free,
+						    NULL);
+		} else {
+			ret = tspp_allocate_buffers(0,
+						    channel_id,
+						    TSPP_BUFFER_COUNT,
+						    TSPP_BUFFER_SIZE,
+						    TSPP_NOTIFICATION_SIZE,
+						    NULL,
+						    NULL,
+						    NULL);
+		}
 		if (ret < 0) {
 			MPQ_DVB_ERR_PRINT(
 				"%s: tspp_allocate_buffers(%d) failed (%d)\n",
@@ -745,20 +842,152 @@
 	return 0;
 }
 
+static void mpq_dmx_tsif_ion_cleanup(int i)
+{
+	mpq_dmx_tspp_info.tsif[i].pes_mem_heap_phys_base = 0;
+	mpq_dmx_tspp_info.tsif[i].section_mem_heap_phys_base = 0;
+
+	if (!IS_ERR_OR_NULL(mpq_dmx_tspp_info.tsif[i].pes_mem_heap_handle)) {
+		if (!IS_ERR_OR_NULL(mpq_dmx_tspp_info.tsif[i].
+				pes_mem_heap_virt_base))
+			ion_unmap_kernel(mpq_dmx_tspp_info.ion_client,
+				mpq_dmx_tspp_info.tsif[i].pes_mem_heap_handle);
+
+		ion_free(mpq_dmx_tspp_info.ion_client,
+			mpq_dmx_tspp_info.tsif[i].pes_mem_heap_handle);
+	}
+
+	if (!IS_ERR_OR_NULL(mpq_dmx_tspp_info.tsif[i].
+			section_mem_heap_handle)) {
+		if (!IS_ERR_OR_NULL(mpq_dmx_tspp_info.tsif[i].
+					section_mem_heap_virt_base))
+			ion_unmap_kernel(mpq_dmx_tspp_info.ion_client,
+					mpq_dmx_tspp_info.tsif[i].
+						section_mem_heap_handle);
+
+		ion_free(mpq_dmx_tspp_info.ion_client,
+			mpq_dmx_tspp_info.tsif[i].section_mem_heap_handle);
+	}
+
+	mpq_dmx_tspp_info.tsif[i].pes_mem_heap_virt_base = NULL;
+	mpq_dmx_tspp_info.tsif[i].section_mem_heap_virt_base = NULL;
+	mpq_dmx_tspp_info.tsif[i].pes_mem_heap_handle = NULL;
+	mpq_dmx_tspp_info.tsif[i].section_mem_heap_handle = NULL;
+}
+
+static void mpq_dmx_tspp_ion_cleanup(void)
+{
+	int i;
+
+	for (i = 0; i < TSIF_COUNT; i++)
+		mpq_dmx_tsif_ion_cleanup(i);
+}
+
 static int mpq_tspp_dmx_init(
 			struct dvb_adapter *mpq_adapter,
 			struct mpq_demux *mpq_demux)
 {
-	int result;
+	int i, result;
+	size_t len;
 
 	MPQ_DVB_DBG_PRINT("%s executed\n", __func__);
 
+	if (allocation_mode == MPQ_DMX_TSPP_CONTIGUOUS_PHYS_ALLOC) {
+		/*
+		 * Save ION client, used to allocate memory
+		 * for TSPP's buffers.
+		 */
+		mpq_dmx_tspp_info.ion_client = mpq_demux->ion_client;
+
+		if (IS_ERR_OR_NULL(mpq_dmx_tspp_info.ion_client))
+			return -EINVAL;
+
+		for (i = 0; i < TSIF_COUNT; i++) {
+			mpq_dmx_tspp_info.tsif[i].pes_mem_heap_handle =
+				ion_alloc(mpq_dmx_tspp_info.ion_client,
+					(TSPP_BUFFER_COUNT * TSPP_BUFFER_SIZE),
+					TSPP_RAW_TTS_SIZE,
+					ION_HEAP(ION_CP_MM_HEAP_ID),
+					0); /* non-cached */
+			if (IS_ERR_OR_NULL(mpq_dmx_tspp_info.tsif[i].
+						pes_mem_heap_handle)) {
+				MPQ_DVB_ERR_PRINT("%s: ion_alloc() failed\n",
+						__func__);
+				mpq_dmx_tspp_ion_cleanup();
+				return -ENOMEM;
+			}
+			/* save virtual base address of heap */
+			mpq_dmx_tspp_info.tsif[i].pes_mem_heap_virt_base =
+				ion_map_kernel(mpq_dmx_tspp_info.ion_client,
+					mpq_dmx_tspp_info.tsif[i].
+						pes_mem_heap_handle);
+			if (IS_ERR_OR_NULL(mpq_dmx_tspp_info.tsif[i].
+						pes_mem_heap_virt_base)) {
+				MPQ_DVB_ERR_PRINT(
+					"%s: ion_map_kernel() failed\n",
+					__func__);
+				mpq_dmx_tspp_ion_cleanup();
+				return -ENOMEM;
+			}
+			/* save physical base address of heap */
+			result = ion_phys(mpq_dmx_tspp_info.ion_client,
+				mpq_dmx_tspp_info.tsif[i].pes_mem_heap_handle,
+				&(mpq_dmx_tspp_info.tsif[i].
+					pes_mem_heap_phys_base), &len);
+			if (result < 0) {
+				MPQ_DVB_ERR_PRINT("%s: ion_phys() failed\n",
+						__func__);
+				mpq_dmx_tspp_ion_cleanup();
+				return -ENOMEM;
+			}
+
+			mpq_dmx_tspp_info.tsif[i].section_mem_heap_handle =
+				ion_alloc(mpq_dmx_tspp_info.ion_client,
+					(TSPP_BUFFER_COUNT * TSPP_BUFFER_SIZE),
+					TSPP_RAW_TTS_SIZE,
+					ION_HEAP(ION_CP_MM_HEAP_ID),
+					0); /* non-cached */
+			if (IS_ERR_OR_NULL(mpq_dmx_tspp_info.tsif[i].
+						section_mem_heap_handle)) {
+				MPQ_DVB_ERR_PRINT("%s: ion_alloc() failed\n",
+						__func__);
+				mpq_dmx_tspp_ion_cleanup();
+				return -ENOMEM;
+			}
+			/* save virtual base address of heap */
+			mpq_dmx_tspp_info.tsif[i].section_mem_heap_virt_base =
+				ion_map_kernel(mpq_dmx_tspp_info.ion_client,
+				mpq_dmx_tspp_info.tsif[i].
+					section_mem_heap_handle);
+			if (IS_ERR_OR_NULL(mpq_dmx_tspp_info.tsif[i].
+					section_mem_heap_virt_base)) {
+				MPQ_DVB_ERR_PRINT(
+					"%s: ion_map_kernel() failed\n",
+					__func__);
+				mpq_dmx_tspp_ion_cleanup();
+				return -ENOMEM;
+			}
+			/* save physical base address of heap */
+			result = ion_phys(mpq_dmx_tspp_info.ion_client,
+				mpq_dmx_tspp_info.tsif[i].
+					section_mem_heap_handle,
+				&(mpq_dmx_tspp_info.tsif[i].
+					section_mem_heap_phys_base), &len);
+			if (result < 0) {
+				MPQ_DVB_ERR_PRINT("%s: ion_phys() failed\n",
+						__func__);
+				mpq_dmx_tspp_ion_cleanup();
+				return -ENOMEM;
+			}
+		}
+	}
+
 	/* Set the kernel-demux object capabilities */
 	mpq_demux->demux.dmx.capabilities =
 		DMX_TS_FILTERING			|
 		DMX_PES_FILTERING			|
-		DMX_SECTION_FILTERING		|
-		DMX_MEMORY_BASED_FILTERING	|
+		DMX_SECTION_FILTERING			|
+		DMX_MEMORY_BASED_FILTERING		|
 		DMX_CRC_CHECKING			|
 		DMX_TS_DESCRAMBLING;
 
@@ -818,6 +1047,7 @@
 init_failed_dmx_release:
 	dvb_dmx_release(&mpq_demux->demux);
 init_failed:
+	mpq_dmx_tspp_ion_cleanup();
 	return result;
 }
 
@@ -831,6 +1061,10 @@
 
 	for (i = 0; i < TSIF_COUNT; i++) {
 		mpq_dmx_tspp_info.tsif[i].pes_channel_ref = 0;
+		mpq_dmx_tspp_info.tsif[i].pes_index = 0;
+		mpq_dmx_tspp_info.tsif[i].pes_mem_heap_handle = NULL;
+		mpq_dmx_tspp_info.tsif[i].pes_mem_heap_virt_base = NULL;
+		mpq_dmx_tspp_info.tsif[i].pes_mem_heap_phys_base = 0;
 
 		mpq_dmx_tspp_info.tsif[i].pes_work.channel_id =
 			TSPP_CHANNEL_ID(i, TSPP_PES_CHANNEL);
@@ -839,6 +1073,10 @@
 				  mpq_dmx_tspp_work);
 
 		mpq_dmx_tspp_info.tsif[i].section_channel_ref = 0;
+		mpq_dmx_tspp_info.tsif[i].section_index = 0;
+		mpq_dmx_tspp_info.tsif[i].section_mem_heap_handle = NULL;
+		mpq_dmx_tspp_info.tsif[i].section_mem_heap_virt_base = NULL;
+		mpq_dmx_tspp_info.tsif[i].section_mem_heap_phys_base = 0;
 
 		mpq_dmx_tspp_info.tsif[i].section_work.channel_id =
 			TSPP_CHANNEL_ID(i, TSPP_SECTION_CHANNEL);
@@ -861,14 +1099,12 @@
 				mpq_dmx_tspp_info.tsif[i].name);
 
 		if (mpq_dmx_tspp_info.tsif[i].workqueue == NULL) {
-
 			for (j = 0; j < i; j++) {
 				destroy_workqueue(
 					mpq_dmx_tspp_info.tsif[j].workqueue);
 
 				mutex_destroy(&mpq_dmx_tspp_info.tsif[j].mutex);
 			}
-
 			MPQ_DVB_ERR_PRINT(
 				"%s: create_singlethread_workqueue failed\n",
 				__func__);
@@ -905,6 +1141,11 @@
 	for (i = 0; i < TSIF_COUNT; i++) {
 		mutex_lock(&mpq_dmx_tspp_info.tsif[i].mutex);
 
+		/*
+		 * Note: tspp_close_channel will also free the TSPP buffers
+		 * even if we allocated them ourselves,
+		 * using our free function.
+		 */
 		if (mpq_dmx_tspp_info.tsif[i].pes_channel_ref) {
 			tspp_unregister_notification(0, TSPP_PES_CHANNEL);
 			tspp_close_channel(0,
@@ -917,9 +1158,11 @@
 				TSPP_CHANNEL_ID(i, TSPP_SECTION_CHANNEL));
 		}
 
-		/* TODO: if we allocate buffer
-		 * to TSPP ourself, need to free those as well
+		/* if we allocated buffer pools
+		 * to TSPP, need to free those as well
 		 */
+		if (allocation_mode == MPQ_DMX_TSPP_CONTIGUOUS_PHYS_ALLOC)
+			mpq_dmx_tsif_ion_cleanup(i);
 
 		mutex_unlock(&mpq_dmx_tspp_info.tsif[i].mutex);
 		flush_workqueue(mpq_dmx_tspp_info.tsif[i].workqueue);
diff --git a/drivers/misc/tspp.c b/drivers/misc/tspp.c
index 8a1e0da..3b678c5 100644
--- a/drivers/misc/tspp.c
+++ b/drivers/misc/tspp.c
@@ -103,29 +103,29 @@
 /*
  * TSPP register offsets
  */
-#define TSPP_RST					0x00
+#define TSPP_RST			0x00
 #define TSPP_CLK_CONTROL		0x04
-#define TSPP_CONFIG				0x08
-#define TSPP_CONTROL				0x0C
+#define TSPP_CONFIG			0x08
+#define TSPP_CONTROL			0x0C
 #define TSPP_PS_DISABLE			0x10
-#define TSPP_MSG_IRQ_STATUS	0x14
+#define TSPP_MSG_IRQ_STATUS		0x14
 #define TSPP_MSG_IRQ_MASK		0x18
 #define TSPP_IRQ_STATUS			0x1C
 #define TSPP_IRQ_MASK			0x20
 #define TSPP_IRQ_CLEAR			0x24
 #define TSPP_PIPE_ERROR_STATUS(_n)	(0x28 + (_n << 2))
-#define TSPP_STATUS				0x68
-#define TSPP_CURR_TSP_HEADER	0x6C
-#define TSPP_CURR_PID_FILTER	0x70
-#define TSPP_SYSTEM_KEY(_n)	(0x74 + (_n << 2))
-#define TSPP_CBC_INIT_VAL(_n)	(0x94 + (_n << 2))
-#define TSPP_DATA_KEY_RESET	0x9C
+#define TSPP_STATUS			0x68
+#define TSPP_CURR_TSP_HEADER		0x6C
+#define TSPP_CURR_PID_FILTER		0x70
+#define TSPP_SYSTEM_KEY(_n)		(0x74 + (_n << 2))
+#define TSPP_CBC_INIT_VAL(_n)		(0x94 + (_n << 2))
+#define TSPP_DATA_KEY_RESET		0x9C
 #define TSPP_KEY_VALID			0xA0
 #define TSPP_KEY_ERROR			0xA4
 #define TSPP_TEST_CTRL			0xA8
-#define TSPP_VERSION				0xAC
+#define TSPP_VERSION			0xAC
 #define TSPP_GENERICS			0xB0
-#define TSPP_NOP					0xB4
+#define TSPP_NOP			0xB4
 
 /*
  * Register bit definitions
@@ -172,30 +172,30 @@
 #define TSPP_MSG_TSIF_0_IRQ               BIT(0)
 
 /* TSPP_IRQ_STATUS + TSPP_IRQ_MASK + TSPP_IRQ_CLEAR */
-#define TSPP_IRQ_STATUS_TSP_RD_CMPL			BIT(19)
-#define TSPP_IRQ_STATUS_KEY_ERROR			BIT(18)
+#define TSPP_IRQ_STATUS_TSP_RD_CMPL		BIT(19)
+#define TSPP_IRQ_STATUS_KEY_ERROR		BIT(18)
 #define TSPP_IRQ_STATUS_KEY_SWITCHED_BAD	BIT(17)
 #define TSPP_IRQ_STATUS_KEY_SWITCHED		BIT(16)
 #define TSPP_IRQ_STATUS_PS_BROKEN(_n)		BIT((_n))
 
 /* TSPP_PIPE_ERROR_STATUS */
-#define TSPP_PIPE_PES_SYNC_ERROR				BIT(3)
-#define TSPP_PIPE_PS_LENGTH_ERROR			BIT(2)
+#define TSPP_PIPE_PES_SYNC_ERROR		BIT(3)
+#define TSPP_PIPE_PS_LENGTH_ERROR		BIT(2)
 #define TSPP_PIPE_PS_CONTINUITY_ERROR		BIT(1)
-#define TSPP_PIP_PS_LOST_START				BIT(0)
+#define TSPP_PIP_PS_LOST_START			BIT(0)
 
 /* TSPP_STATUS			*/
-#define TSPP_STATUS_TSP_PKT_AVAIL			BIT(10)
-#define TSPP_STATUS_TSIF1_DM_REQ				BIT(6)
-#define TSPP_STATUS_TSIF0_DM_REQ				BIT(2)
-#define TSPP_CURR_FILTER_TABLE				BIT(0)
+#define TSPP_STATUS_TSP_PKT_AVAIL		BIT(10)
+#define TSPP_STATUS_TSIF1_DM_REQ		BIT(6)
+#define TSPP_STATUS_TSIF0_DM_REQ		BIT(2)
+#define TSPP_CURR_FILTER_TABLE			BIT(0)
 
 /* TSPP_GENERICS		*/
-#define TSPP_GENERICS_CRYPTO_GEN				BIT(12)
+#define TSPP_GENERICS_CRYPTO_GEN		BIT(12)
 #define TSPP_GENERICS_MAX_CONS_PIPES		BIT(7)
-#define TSPP_GENERICS_MAX_PIPES				BIT(2)
-#define TSPP_GENERICS_TSIF_1_GEN				BIT(1)
-#define TSPP_GENERICS_TSIF_0_GEN				BIT(0)
+#define TSPP_GENERICS_MAX_PIPES			BIT(2)
+#define TSPP_GENERICS_TSIF_1_GEN		BIT(1)
+#define TSPP_GENERICS_TSIF_0_GEN		BIT(0)
 
 /*
  * TSPP memory regions
@@ -375,6 +375,8 @@
 	tspp_notifier *notifier; /* used only with kernel api */
 	void *notify_data;       /* data to be passed with the notifier */
 	u32 notify_timer;        /* notification for partially filled buffers */
+	tspp_memfree *memfree;   /* user defined memory free function */
+	void *user_info; /* user cookie passed to memory alloc/free function */
 };
 
 struct tspp_pid_filter_table {
@@ -584,8 +586,7 @@
 		g = table + i;
 		tmp = gpio_tlmm_config(g->gpio_cfg, GPIO_CFG_DISABLE);
 		if (tmp) {
-			pr_err("tspp_gpios_disable(0x%08x, GPIO_CFG_DISABLE)"
-			       " <%s> failed: %d\n",
+			pr_err("tspp_gpios_disable(0x%08x, GPIO_CFG_DISABLE) <%s> failed: %d\n",
 			       g->gpio_cfg, g->label ?: "?", rc);
 			pr_err("tspp: pin %d func %d dir %d pull %d drvstr %d\n",
 			       GPIO_PIN(g->gpio_cfg), GPIO_FUNC(g->gpio_cfg),
@@ -608,8 +609,7 @@
 		g = table + i;
 		rc = gpio_tlmm_config(g->gpio_cfg, GPIO_CFG_ENABLE);
 		if (rc) {
-			pr_err("tspp: gpio_tlmm_config(0x%08x, GPIO_CFG_ENABLE)"
-			       " <%s> failed: %d\n",
+			pr_err("tspp: gpio_tlmm_config(0x%08x, GPIO_CFG_ENABLE) <%s> failed: %d\n",
 			       g->gpio_cfg, g->label ?: "?", rc);
 			pr_err("tspp: pin %d func %d dir %d pull %d drvstr %d\n",
 			       GPIO_PIN(g->gpio_cfg), GPIO_FUNC(g->gpio_cfg),
@@ -820,12 +820,12 @@
 		desc->virt_base = alloc(channel_id, size,
 			&desc->phys_base, user);
 	} else {
-	desc->virt_base = dma_alloc_coherent(NULL, size,
-		&desc->phys_base, GFP_KERNEL);
-	if (desc->virt_base == 0) {
-		pr_err("tspp dma alloc coherent failed %i", size);
-		return -ENOMEM;
-	}
+		desc->virt_base = dma_alloc_coherent(NULL, size,
+			&desc->phys_base, GFP_KERNEL);
+		if (desc->virt_base == 0) {
+			pr_err("tspp dma alloc coherent failed %i", size);
+			return -ENOMEM;
+		}
 	}
 
 	desc->size = size;
@@ -977,9 +977,13 @@
 	channel->buffer_count = 0;
 	channel->filter_count = 0;
 	channel->int_freq = 1;
+	channel->src = TSPP_SOURCE_NONE;
+	channel->mode = TSPP_MODE_DISABLED;
 	channel->notifier = NULL;
 	channel->notify_data = NULL;
 	channel->notify_timer = 0;
+	channel->memfree = NULL;
+	channel->user_info = NULL;
 	init_waitqueue_head(&channel->in_queue);
 
 	if (cdev_add(&channel->cdev, tspp_minor++, 1) != 0) {
@@ -1002,6 +1006,11 @@
 static int tspp_set_buffer_size(struct tspp_channel *channel,
 	struct tspp_buffer *buf)
 {
+	if (channel->buffer_count > 0) {
+		pr_err("tspp: cannot set buffer size - buffers already allocated\n");
+		return -EPERM;
+	}
+
 	if (buf->size < TSPP_MIN_BUFFER_SIZE)
 		channel->buffer_size = TSPP_MIN_BUFFER_SIZE;
 	else if (buf->size > TSPP_MAX_BUFFER_SIZE)
@@ -1033,8 +1042,8 @@
 }
 
 static void tspp_set_signal_inversion(struct tspp_channel *channel,
-	int clock_inverse, int data_inverse,
-	int sync_inverse, int enable_inverse)
+					int clock_inverse, int data_inverse,
+					int sync_inverse, int enable_inverse)
 {
 	int index;
 
@@ -1054,8 +1063,106 @@
 	channel->pdev->tsif[index].enable_inverse = enable_inverse;
 }
 
+static int tspp_is_buffer_size_aligned(u32 size, enum tspp_mode mode)
+{
+	u32 alignment;
+
+	switch (mode) {
+	case TSPP_MODE_RAW:
+		/* must be a multiple of 192 */
+		alignment = (TSPP_PACKET_LENGTH + 4);
+		if (size % alignment)
+			return 0;
+		return 1;
+
+	case TSPP_MODE_RAW_NO_SUFFIX:
+		/* must be a multiple of 188 */
+		alignment = TSPP_PACKET_LENGTH;
+		if (size % alignment)
+			return 0;
+		return 1;
+
+	case TSPP_MODE_DISABLED:
+	case TSPP_MODE_PES:
+	default:
+		/* no alignment requirement */
+		return 1;
+	}
+
+}
+
+static u32 tspp_align_buffer_size_by_mode(u32 size, enum tspp_mode mode)
+{
+	u32 new_size;
+	u32 alignment;
+
+	switch (mode) {
+	case TSPP_MODE_RAW:
+		/* must be a multiple of 192 */
+		alignment = (TSPP_PACKET_LENGTH + 4);
+		break;
+
+	case TSPP_MODE_RAW_NO_SUFFIX:
+		/* must be a multiple of 188 */
+		alignment = TSPP_PACKET_LENGTH;
+		break;
+
+	case TSPP_MODE_DISABLED:
+	case TSPP_MODE_PES:
+	default:
+		/* no alignment requirement - give the user what he asks for */
+		alignment = 1;
+		break;
+	}
+	/* align up */
+	new_size = (((size + alignment - 1) / alignment) * alignment);
+	return new_size;
+}
+
+static void tspp_destroy_buffers(u32 channel_id, struct tspp_channel *channel)
+{
+	int i;
+	struct tspp_mem_buffer *pbuf, *temp;
+
+	pbuf = channel->data;
+	for (i = 0; i < channel->buffer_count; i++) {
+		if (pbuf->desc.phys_base) {
+			if (channel->memfree) {
+				channel->memfree(channel_id,
+					pbuf->desc.size,
+					pbuf->desc.virt_base,
+					pbuf->desc.phys_base,
+					channel->user_info);
+			} else {
+				dma_free_coherent(NULL,
+					pbuf->desc.size,
+					pbuf->desc.virt_base,
+					pbuf->desc.phys_base);
+			}
+			pbuf->desc.phys_base = 0;
+		}
+		pbuf->desc.virt_base = 0;
+		pbuf->state = TSPP_BUF_STATE_EMPTY;
+		temp = pbuf;
+		pbuf = pbuf->next;
+		kfree(temp);
+	}
+}
+
 /*** TSPP API functions ***/
-int tspp_open_stream(u32 dev, u32 channel_id, struct tspp_select_source *source)
+
+/**
+ * tspp_open_stream - open a TSPP stream for use.
+ *
+ * @dev: TSPP device (up to TSPP_MAX_DEVICES)
+ * @channel_id: Channel ID number (up to TSPP_NUM_CHANNELS)
+ * @source: stream source parameters.
+ *
+ * Return  error status
+ *
+ */
+int tspp_open_stream(u32 dev, u32 channel_id,
+			struct tspp_select_source *source)
 {
 	u32 val;
 	struct tspp_device *pdev;
@@ -1063,6 +1170,7 @@
 
 	TSPP_DEBUG("tspp_open_stream %i %i %i %i",
 		dev, channel_id, source->source, source->mode);
+
 	if (dev >= TSPP_MAX_DEVICES) {
 		pr_err("tspp: device id out of range");
 		return -ENODEV;
@@ -1082,8 +1190,8 @@
 	channel->src = source->source;
 	tspp_set_tsif_mode(channel, source->mode);
 	tspp_set_signal_inversion(channel, source->clk_inverse,
-		source->data_inverse, source->sync_inverse,
-		source->enable_inverse);
+			source->data_inverse, source->sync_inverse,
+			source->enable_inverse);
 
 	switch (source->source) {
 	case TSPP_SOURCE_TSIF0:
@@ -1120,6 +1228,15 @@
 }
 EXPORT_SYMBOL(tspp_open_stream);
 
+/**
+ * tspp_close_stream - close a TSPP stream.
+ *
+ * @dev: TSPP device (up to TSPP_MAX_DEVICES)
+ * @channel_id: Channel ID number (up to TSPP_NUM_CHANNELS)
+ *
+ * Return  error status
+ *
+ */
 int tspp_close_stream(u32 dev, u32 channel_id)
 {
 	u32 val;
@@ -1162,6 +1279,15 @@
 }
 EXPORT_SYMBOL(tspp_close_stream);
 
+/**
+ * tspp_open_channel - open a TSPP channel.
+ *
+ * @dev: TSPP device (up to TSPP_MAX_DEVICES)
+ * @channel_id: Channel ID number (up to TSPP_NUM_CHANNELS)
+ *
+ * Return  error status
+ *
+ */
 int tspp_open_channel(u32 dev, u32 channel_id)
 {
 	int rc = 0;
@@ -1269,6 +1395,15 @@
 }
 EXPORT_SYMBOL(tspp_open_channel);
 
+/**
+ * tspp_close_channel - close a TSPP channel.
+ *
+ * @dev: TSPP device (up to TSPP_MAX_DEVICES)
+ * @channel_id: Channel ID number (up to TSPP_NUM_CHANNELS)
+ *
+ * Return  error status
+ *
+ */
 int tspp_close_channel(u32 dev, u32 channel_id)
 {
 	int i;
@@ -1278,7 +1413,6 @@
 	struct sps_connect *config;
 	struct tspp_device *pdev;
 	struct tspp_channel *channel;
-	struct tspp_mem_buffer *pbuf, *temp;
 
 	if (channel_id >= TSPP_NUM_CHANNELS) {
 		pr_err("tspp: channel id out of range");
@@ -1333,21 +1467,12 @@
 	dma_free_coherent(NULL, config->desc.size, config->desc.base,
 		config->desc.phys_base);
 
-	pbuf = channel->data;
-	for (i = 0; i < channel->buffer_count; i++) {
-		if (pbuf->desc.phys_base) {
-			dma_free_coherent(NULL,
-				pbuf->desc.size,
-				pbuf->desc.virt_base,
-				pbuf->desc.phys_base);
-			pbuf->desc.phys_base = 0;
-		}
-		pbuf->desc.virt_base = 0;
-		pbuf->state = TSPP_BUF_STATE_EMPTY;
-		temp = pbuf;
-		pbuf = pbuf->next;
-		kfree(temp);
-	}
+	tspp_destroy_buffers(channel_id, channel);
+
+	channel->src = TSPP_SOURCE_NONE;
+	channel->mode = TSPP_MODE_DISABLED;
+	channel->memfree = NULL;
+	channel->user_info = NULL;
 	channel->buffer_count = 0;
 	channel->data = NULL;
 	channel->read = NULL;
@@ -1363,10 +1488,20 @@
 }
 EXPORT_SYMBOL(tspp_close_channel);
 
+/**
+ * tspp_add_filter - add a TSPP filter to a channel.
+ *
+ * @dev: TSPP device (up to TSPP_MAX_DEVICES)
+ * @channel_id: Channel ID number (up to TSPP_NUM_CHANNELS)
+ * @filter: TSPP filter parameters
+ *
+ * Return  error status
+ *
+ */
 int tspp_add_filter(u32 dev, u32 channel_id,
 	struct tspp_filter *filter)
 {
-	int i;
+	int i, rc;
 	int other_channel;
 	int entry;
 	u32 val, pid, enabled;
@@ -1397,19 +1532,14 @@
 		return -ENOSR;
 	}
 
-	/* make sure this filter mode matches the channel mode */
-	switch (channel->mode) {
-	case TSPP_MODE_DISABLED:
-		channel->mode = filter->mode;
-		break;
-	case TSPP_MODE_RAW:
-	case TSPP_MODE_PES:
-	case TSPP_MODE_RAW_NO_SUFFIX:
-		if (filter->mode != channel->mode) {
-			pr_err("tspp: wrong filter mode");
-			return -EBADSLT;
-		}
-	}
+	channel->mode = filter->mode;
+	/*
+	 * if buffers are already allocated, verify they fulfil
+	 * the alignment requirements.
+	 */
+	if ((channel->buffer_count > 0) &&
+	   (!tspp_is_buffer_size_aligned(channel->buffer_size, channel->mode)))
+		pr_warn("tspp: buffers allocated with incorrect alignment\n");
 
 	if (filter->mode == TSPP_MODE_PES) {
 		for (i = 0; i < TSPP_NUM_PRIORITIES; i++) {
@@ -1468,13 +1598,22 @@
 	pdev->filters[channel->src]->
 		filter[filter->priority].filter = p.filter;
 
-	/* allocate buffers if needed */
-	tspp_allocate_buffers(dev, channel->id, channel->max_buffers,
-		channel->buffer_size, channel->int_freq, 0, 0);
-	if (channel->buffer_count < MIN_ACCEPTABLE_BUFFER_COUNT) {
-		pr_err("tspp: failed to allocate at least %i buffers",
-			MIN_ACCEPTABLE_BUFFER_COUNT);
-		return -ENOMEM;
+	/*
+	 * allocate buffers if needed (i.e. if user did has not already called
+	 * tspp_allocate_buffers() explicitly).
+	 */
+	if (channel->buffer_count == 0) {
+		channel->buffer_size =
+			tspp_align_buffer_size_by_mode(channel->buffer_size,
+							channel->mode);
+		rc = tspp_allocate_buffers(dev, channel->id,
+					channel->max_buffers,
+					channel->buffer_size,
+					channel->int_freq, NULL, NULL, NULL);
+		if (rc != 0) {
+			pr_err("tspp: tspp_allocate_buffers failed\n");
+			return rc;
+		}
 	}
 
 	/* reenable pipe */
@@ -1489,6 +1628,16 @@
 }
 EXPORT_SYMBOL(tspp_add_filter);
 
+/**
+ * tspp_remove_filter - remove a TSPP filter from a channel.
+ *
+ * @dev: TSPP device (up to TSPP_MAX_DEVICES)
+ * @channel_id: Channel ID number (up to TSPP_NUM_CHANNELS)
+ * @filter: TSPP filter parameters
+ *
+ * Return  error status
+ *
+ */
 int tspp_remove_filter(u32 dev, u32 channel_id,
 	struct tspp_filter *filter)
 {
@@ -1541,6 +1690,16 @@
 }
 EXPORT_SYMBOL(tspp_remove_filter);
 
+/**
+ * tspp_set_key - set TSPP key in key table.
+ *
+ * @dev: TSPP device (up to TSPP_MAX_DEVICES)
+ * @channel_id: Channel ID number (up to TSPP_NUM_CHANNELS)
+ * @key: TSPP key parameters
+ *
+ * Return  error status
+ *
+ */
 int tspp_set_key(u32 dev, u32 channel_id, struct tspp_key *key)
 {
 	int i;
@@ -1591,6 +1750,18 @@
 }
 EXPORT_SYMBOL(tspp_set_key);
 
+/**
+ * tspp_register_notification - register TSPP channel notification function.
+ *
+ * @dev: TSPP device (up to TSPP_MAX_DEVICES)
+ * @channel_id: Channel ID number (up to TSPP_NUM_CHANNELS)
+ * @pNotify: notification function
+ * @userdata: user data to pass to notification function
+ * @timer_ms: notification for partially filled buffers
+ *
+ * Return  error status
+ *
+ */
 int tspp_register_notification(u32 dev, u32 channel_id,
 	tspp_notifier *pNotify, void *userdata, u32 timer_ms)
 {
@@ -1614,6 +1785,15 @@
 }
 EXPORT_SYMBOL(tspp_register_notification);
 
+/**
+ * tspp_unregister_notification - unregister TSPP channel notification function.
+ *
+ * @dev: TSPP device (up to TSPP_MAX_DEVICES)
+ * @channel_id: Channel ID number (up to TSPP_NUM_CHANNELS)
+ *
+ * Return  error status
+ *
+ */
 int tspp_unregister_notification(u32 dev, u32 channel_id)
 {
 	struct tspp_channel *channel;
@@ -1635,6 +1815,15 @@
 }
 EXPORT_SYMBOL(tspp_unregister_notification);
 
+/**
+ * tspp_get_buffer - get TSPP data buffer.
+ *
+ * @dev: TSPP device (up to TSPP_MAX_DEVICES)
+ * @channel_id: Channel ID number (up to TSPP_NUM_CHANNELS)
+ *
+ * Return  error status
+ *
+ */
 const struct tspp_data_descriptor *tspp_get_buffer(u32 dev, u32 channel_id)
 {
 	struct tspp_mem_buffer *buffer;
@@ -1675,6 +1864,16 @@
 }
 EXPORT_SYMBOL(tspp_get_buffer);
 
+/**
+ * tspp_release_buffer - release TSPP data buffer back to TSPP.
+ *
+ * @dev: TSPP device (up to TSPP_MAX_DEVICES)
+ * @channel_id: Channel ID number (up to TSPP_NUM_CHANNELS)
+ * @descriptor_id: buffer descriptor ID
+ *
+ * Return  error status
+ *
+ */
 int tspp_release_buffer(u32 dev, u32 channel_id, u32 descriptor_id)
 {
 	int i, found = 0;
@@ -1726,8 +1925,27 @@
 }
 EXPORT_SYMBOL(tspp_release_buffer);
 
-int tspp_allocate_buffers(u32 dev, u32 channel_id,	u32 count,
-	u32 size, u32 int_freq, tspp_allocator *alloc, void *user)
+/**
+ * tspp_allocate_buffers - allocate TSPP data buffers.
+ *
+ * @dev: TSPP device (up to TSPP_MAX_DEVICES)
+ * @channel_id: Channel ID number (up to TSPP_NUM_CHANNELS)
+ * @count: number of buffers to allocate
+ * @size: size of each buffer to allocate
+ * @int_freq: interrupt frequency
+ * @alloc: user defined memory allocator function. Pass NULL for default.
+ * @memfree: user defined memory free function. Pass NULL for default.
+ * @user: user data to pass to the memory allocator/free function
+ *
+ * Return  error status
+ *
+ * The user can optionally call this function explicitly to allocate the TSPP
+ * data buffers. Alternatively, if the user did not call this function, it
+ * is called implicitly by tspp_add_filter().
+ */
+int tspp_allocate_buffers(u32 dev, u32 channel_id, u32 count, u32 size,
+			u32 int_freq, tspp_allocator *alloc,
+			tspp_memfree *memfree, void *user)
 {
 	struct tspp_channel *channel;
 	struct tspp_device *pdev;
@@ -1736,56 +1954,62 @@
 	TSPP_DEBUG("tspp_allocate_buffers");
 
 	if (channel_id >= TSPP_NUM_CHANNELS) {
-		pr_err("tspp: channel id out of range");
+		pr_err("%s: channel id out of range", __func__);
 		return -ECHRNG;
 	}
+
 	pdev = tspp_find_by_id(dev);
 	if (!pdev) {
-		pr_err("tspp_alloc: can't find device %i", dev);
+		pr_err("%s: can't find device %i", __func__, dev);
 		return -ENODEV;
 	}
+
+	if (count < MIN_ACCEPTABLE_BUFFER_COUNT) {
+		pr_err("%s: tspp requires a minimum of %i buffers\n",
+			__func__, MIN_ACCEPTABLE_BUFFER_COUNT);
+		return -EINVAL;
+	}
+
 	channel = &pdev->channels[channel_id];
+	/* allow buffer allocation only if there was no previous buffer
+	 * allocation for this channel.
+	 */
+	if (channel->buffer_count > 0) {
+		pr_err("%s: buffers already allocated for channel %u",
+			__func__, channel_id);
+		return -EINVAL;
+	}
 
 	channel->max_buffers = count;
 
 	/* set up interrupt frequency */
-	if (int_freq > channel->max_buffers)
+	if (int_freq > channel->max_buffers) {
 		int_freq = channel->max_buffers;
-	channel->int_freq = int_freq;
-
-	switch (channel->mode) {
-	case TSPP_MODE_DISABLED:
-	case TSPP_MODE_PES:
-		/* give the user what he asks for */
-		channel->buffer_size = size;
-		break;
-
-	case TSPP_MODE_RAW:
-		/* must be a multiple of 192 */
-		if (size < (TSPP_PACKET_LENGTH+4))
-			channel->buffer_size = (TSPP_PACKET_LENGTH+4);
-		else
-			channel->buffer_size = (size /
-				(TSPP_PACKET_LENGTH+4)) *
-				(TSPP_PACKET_LENGTH+4);
-		break;
-
-	case TSPP_MODE_RAW_NO_SUFFIX:
-		/* must be a multiple of 188 */
-		channel->buffer_size = (size / TSPP_PACKET_LENGTH) *
-			TSPP_PACKET_LENGTH;
-		break;
+		pr_warn("%s: setting interrupt frequency to %u\n",
+			__func__, int_freq);
 	}
+	channel->int_freq = int_freq;
+	/*
+	 * it is the responsibility of the caller to tspp_allocate_buffers(),
+	 * whether it's the user or the driver, to make sure the size parameter
+	 * is compatible to the channel mode.
+	 */
+	channel->buffer_size = size;
 
-	for (; channel->buffer_count < channel->max_buffers;
+	/* save user defined memory free function for later use */
+	channel->memfree = memfree;
+	channel->user_info = user;
+
+	for (channel->buffer_count = 0;
+		channel->buffer_count < channel->max_buffers;
 		channel->buffer_count++) {
 
 		/* allocate the descriptor */
 		struct tspp_mem_buffer *desc = (struct tspp_mem_buffer *)
 			kmalloc(sizeof(struct tspp_mem_buffer), GFP_KERNEL);
 		if (!desc) {
-			pr_warn("tspp: Can't allocate desc %i",
-			channel->buffer_count);
+			pr_warn("%s: Can't allocate desc %i",
+				__func__, channel->buffer_count);
 			break;
 		}
 
@@ -1794,8 +2018,8 @@
 		if (tspp_alloc_buffer(channel_id, &desc->desc,
 			channel->buffer_size, alloc, user) != 0) {
 			kfree(desc);
-			pr_warn("tspp: Can't allocate buffer %i",
-				channel->buffer_count);
+			pr_warn("%s: Can't allocate buffer %i",
+				__func__, channel->buffer_count);
 			break;
 		}
 
@@ -1818,12 +2042,24 @@
 
 		/* start the transfer */
 		if (tspp_queue_buffer(channel, desc))
-			pr_err("tspp: can't queue buffer %i", desc->desc.id);
+			pr_err("%s: can't queue buffer %i",
+				__func__, desc->desc.id);
+	}
+
+	if (channel->buffer_count < channel->max_buffers) {
+		/*
+		 * we failed to allocate the requested number of buffers.
+		 * we don't allow a partial success, so need to clean up here.
+		 */
+		tspp_destroy_buffers(channel_id, channel);
+		channel->buffer_count = 0;
+		return -ENOMEM;
 	}
 
 	channel->waiting = channel->data;
 	channel->read = channel->data;
 	channel->locked = channel->data;
+
 	return 0;
 }
 EXPORT_SYMBOL(tspp_allocate_buffers);
@@ -1942,8 +2178,10 @@
 		transferred += size;
 		buffer->read_index += size;
 
-		/* after reading the end of the buffer, requeue it,
-			and set up for reading the next one */
+		/*
+		 * after reading the end of the buffer, requeue it,
+		 * and set up for reading the next one
+		 */
 		if (buffer->read_index == buffer->filled) {
 			buffer->state = TSPP_BUF_STATE_WAITING;
 			if (tspp_queue_buffer(channel, buffer))
@@ -2042,8 +2280,10 @@
 		pr_err("tspp: Unknown ioctl %i", param0);
 	}
 
-	/* normalize the return code in case one of the subfunctions does
-		something weird */
+	/*
+	 * normalize the return code in case one of the subfunctions does
+	 * something weird
+	 */
 	if (rc != 0)
 		rc = -ENOIOCTLCMD;
 
@@ -2136,13 +2376,14 @@
 {
 	int rc = -ENODEV;
 	u32 version;
-	u32 i;
+	u32 i, j;
 	struct msm_tspp_platform_data *data;
 	struct tspp_device *device;
 	struct resource *mem_tsif0;
 	struct resource *mem_tsif1;
 	struct resource *mem_tspp;
 	struct resource *mem_bam;
+	struct tspp_channel *channel;
 
 	/* must have platform data */
 	data = pdev->dev.platform_data;
@@ -2338,6 +2579,12 @@
 	return 0;
 
 err_channel:
+	/* uninitialize channels */
+	for (j = 0; j < i; j++) {
+		channel = &(device->channels[i]);
+		device_destroy(tspp_class, channel->cdev.dev);
+		cdev_del(&channel->cdev);
+	}
 err_clock:
 	sps_deregister_bam_device(device->bam_handle);
 err_bam: