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: