Initial Contribution

msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142

Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/drivers/misc/tsif.c b/drivers/misc/tsif.c
new file mode 100644
index 0000000..53d4ef2
--- /dev/null
+++ b/drivers/misc/tsif.c
@@ -0,0 +1,1564 @@
+/*
+ * TSIF Driver
+ *
+ * Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <linux/module.h>       /* Needed by all modules */
+#include <linux/kernel.h>       /* Needed for KERN_INFO */
+#include <linux/init.h>         /* Needed for the macros */
+#include <linux/err.h>          /* IS_ERR etc. */
+#include <linux/platform_device.h>
+
+#include <linux/ioport.h>       /* XXX_mem_region */
+#include <linux/debugfs.h>
+#include <linux/dma-mapping.h>  /* dma_XXX */
+#include <linux/delay.h>        /* msleep */
+
+#include <linux/io.h>             /* ioXXX */
+#include <linux/uaccess.h>        /* copy_from_user */
+#include <linux/clk.h>
+#include <linux/wakelock.h>
+#include <linux/tsif_api.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>          /* kfree, kzalloc */
+
+#include <mach/gpio.h>
+#include <mach/dma.h>
+#include <mach/msm_tsif.h>
+
+/*
+ * TSIF register offsets
+ */
+#define TSIF_STS_CTL_OFF               (0x0)
+#define TSIF_TIME_LIMIT_OFF            (0x4)
+#define TSIF_CLK_REF_OFF               (0x8)
+#define TSIF_LPBK_FLAGS_OFF            (0xc)
+#define TSIF_LPBK_DATA_OFF            (0x10)
+#define TSIF_TEST_CTL_OFF             (0x14)
+#define TSIF_TEST_MODE_OFF            (0x18)
+#define TSIF_TEST_RESET_OFF           (0x1c)
+#define TSIF_TEST_EXPORT_OFF          (0x20)
+#define TSIF_TEST_CURRENT_OFF         (0x24)
+
+#define TSIF_DATA_PORT_OFF            (0x100)
+
+/* bits for TSIF_STS_CTL register */
+#define TSIF_STS_CTL_EN_IRQ       (1 << 28)
+#define TSIF_STS_CTL_PACK_AVAIL   (1 << 27)
+#define TSIF_STS_CTL_1ST_PACKET   (1 << 26)
+#define TSIF_STS_CTL_OVERFLOW     (1 << 25)
+#define TSIF_STS_CTL_LOST_SYNC    (1 << 24)
+#define TSIF_STS_CTL_TIMEOUT      (1 << 23)
+#define TSIF_STS_CTL_INV_SYNC     (1 << 21)
+#define TSIF_STS_CTL_INV_NULL     (1 << 20)
+#define TSIF_STS_CTL_INV_ERROR    (1 << 19)
+#define TSIF_STS_CTL_INV_ENABLE   (1 << 18)
+#define TSIF_STS_CTL_INV_DATA     (1 << 17)
+#define TSIF_STS_CTL_INV_CLOCK    (1 << 16)
+#define TSIF_STS_CTL_SPARE        (1 << 15)
+#define TSIF_STS_CTL_EN_NULL      (1 << 11)
+#define TSIF_STS_CTL_EN_ERROR     (1 << 10)
+#define TSIF_STS_CTL_LAST_BIT     (1 <<  9)
+#define TSIF_STS_CTL_EN_TIME_LIM  (1 <<  8)
+#define TSIF_STS_CTL_EN_TCR       (1 <<  7)
+#define TSIF_STS_CTL_TEST_MODE    (3 <<  5)
+#define TSIF_STS_CTL_EN_DM        (1 <<  4)
+#define TSIF_STS_CTL_STOP         (1 <<  3)
+#define TSIF_STS_CTL_START        (1 <<  0)
+
+/*
+ * Data buffering parameters
+ *
+ * Data stored in cyclic buffer;
+ *
+ * Data organized in chunks of packets.
+ * One chunk processed at a time by the data mover
+ *
+ */
+#define TSIF_PKTS_IN_CHUNK_DEFAULT  (16)  /**< packets in one DM chunk */
+#define TSIF_CHUNKS_IN_BUF_DEFAULT   (8)
+#define TSIF_PKTS_IN_CHUNK        (tsif_device->pkts_per_chunk)
+#define TSIF_CHUNKS_IN_BUF        (tsif_device->chunks_per_buf)
+#define TSIF_PKTS_IN_BUF          (TSIF_PKTS_IN_CHUNK * TSIF_CHUNKS_IN_BUF)
+#define TSIF_BUF_SIZE             (TSIF_PKTS_IN_BUF * TSIF_PKT_SIZE)
+
+#define ROW_RESET                 (MSM_CLK_CTL_BASE + 0x214)
+#define GLBL_CLK_ENA              (MSM_CLK_CTL_BASE + 0x000)
+#define CLK_HALT_STATEB           (MSM_CLK_CTL_BASE + 0x104)
+#define TSIF_NS_REG               (MSM_CLK_CTL_BASE + 0x0b4)
+#define TV_NS_REG                 (MSM_CLK_CTL_BASE + 0x0bc)
+
+/* used to create debugfs entries */
+static const struct {
+	const char *name;
+	mode_t mode;
+	int offset;
+} debugfs_tsif_regs[] = {
+	{"sts_ctl",      S_IRUGO | S_IWUSR, TSIF_STS_CTL_OFF},
+	{"time_limit",   S_IRUGO | S_IWUSR, TSIF_TIME_LIMIT_OFF},
+	{"clk_ref",      S_IRUGO | S_IWUSR, TSIF_CLK_REF_OFF},
+	{"lpbk_flags",   S_IRUGO | S_IWUSR, TSIF_LPBK_FLAGS_OFF},
+	{"lpbk_data",    S_IRUGO | S_IWUSR, TSIF_LPBK_DATA_OFF},
+	{"test_ctl",     S_IRUGO | S_IWUSR, TSIF_TEST_CTL_OFF},
+	{"test_mode",    S_IRUGO | S_IWUSR, TSIF_TEST_MODE_OFF},
+	{"test_reset",             S_IWUSR, TSIF_TEST_RESET_OFF},
+	{"test_export",  S_IRUGO | S_IWUSR, TSIF_TEST_EXPORT_OFF},
+	{"test_current", S_IRUGO,           TSIF_TEST_CURRENT_OFF},
+	{"data_port",    S_IRUSR,           TSIF_DATA_PORT_OFF},
+};
+
+/* structures for Data Mover */
+struct tsif_dmov_cmd {
+	dmov_box box;
+	dma_addr_t box_ptr;
+};
+
+struct msm_tsif_device;
+
+struct tsif_xfer {
+	struct msm_dmov_cmd hdr;
+	struct msm_tsif_device *tsif_device;
+	int busy;
+	int wi;   /**< set devices's write index after xfer */
+};
+
+struct msm_tsif_device {
+	struct list_head devlist;
+	struct platform_device *pdev;
+	struct resource *memres;
+	void __iomem *base;
+	unsigned int irq;
+	int mode;
+	u32 time_limit;
+	enum tsif_state state;
+	struct wake_lock wake_lock;
+	/* clocks */
+	struct clk *tsif_clk;
+	struct clk *tsif_pclk;
+	struct clk *tsif_ref_clk;
+	/* debugfs */
+	struct dentry *dent_tsif;
+	struct dentry *debugfs_tsif_regs[ARRAY_SIZE(debugfs_tsif_regs)];
+	struct dentry *debugfs_gpio;
+	struct dentry *debugfs_action;
+	struct dentry *debugfs_dma;
+	struct dentry *debugfs_databuf;
+	struct debugfs_blob_wrapper blob_wrapper_databuf;
+	/* DMA related */
+	int dma;
+	int crci;
+	void *data_buffer;
+	dma_addr_t data_buffer_dma;
+	u32 pkts_per_chunk;
+	u32 chunks_per_buf;
+	int ri;
+	int wi;
+	int dmwi;  /**< DataMover write index */
+	struct tsif_dmov_cmd *dmov_cmd[2];
+	dma_addr_t dmov_cmd_dma[2];
+	struct tsif_xfer xfer[2];
+	struct tasklet_struct dma_refill;
+	/* statistics */
+	u32 stat_rx;
+	u32 stat_overflow;
+	u32 stat_lost_sync;
+	u32 stat_timeout;
+	u32 stat_dmov_err;
+	u32 stat_soft_drop;
+	int stat_ifi; /* inter frame interval */
+	u32 stat0, stat1;
+	/* client */
+	void *client_data;
+	void (*client_notify)(void *client_data);
+};
+
+/* ===clocks begin=== */
+
+static void tsif_put_clocks(struct msm_tsif_device *tsif_device)
+{
+	if (tsif_device->tsif_clk) {
+		clk_put(tsif_device->tsif_clk);
+		tsif_device->tsif_clk = NULL;
+	}
+	if (tsif_device->tsif_pclk) {
+		clk_put(tsif_device->tsif_pclk);
+		tsif_device->tsif_pclk = NULL;
+	}
+
+	if (tsif_device->tsif_ref_clk) {
+		clk_put(tsif_device->tsif_ref_clk);
+		tsif_device->tsif_ref_clk = NULL;
+	}
+}
+
+static int tsif_get_clocks(struct msm_tsif_device *tsif_device)
+{
+	struct msm_tsif_platform_data *pdata =
+		tsif_device->pdev->dev.platform_data;
+	int rc = 0;
+
+	if (pdata->tsif_clk) {
+		tsif_device->tsif_clk = clk_get(NULL, pdata->tsif_clk);
+		if (IS_ERR(tsif_device->tsif_clk)) {
+			dev_err(&tsif_device->pdev->dev, "failed to get %s\n",
+				pdata->tsif_clk);
+			rc = PTR_ERR(tsif_device->tsif_clk);
+			tsif_device->tsif_clk = NULL;
+			goto ret;
+		}
+	}
+	if (pdata->tsif_pclk) {
+		tsif_device->tsif_pclk = clk_get(NULL, pdata->tsif_pclk);
+		if (IS_ERR(tsif_device->tsif_pclk)) {
+			dev_err(&tsif_device->pdev->dev, "failed to get %s\n",
+				pdata->tsif_pclk);
+			rc = PTR_ERR(tsif_device->tsif_pclk);
+			tsif_device->tsif_pclk = NULL;
+			goto ret;
+		}
+	}
+	if (pdata->tsif_ref_clk) {
+		tsif_device->tsif_ref_clk = clk_get(NULL, pdata->tsif_ref_clk);
+		if (IS_ERR(tsif_device->tsif_ref_clk)) {
+			dev_err(&tsif_device->pdev->dev, "failed to get %s\n",
+				pdata->tsif_ref_clk);
+			rc = PTR_ERR(tsif_device->tsif_ref_clk);
+			tsif_device->tsif_ref_clk = NULL;
+			goto ret;
+		}
+	}
+	return 0;
+ret:
+	tsif_put_clocks(tsif_device);
+	return rc;
+}
+
+static void tsif_clock(struct msm_tsif_device *tsif_device, int on)
+{
+	if (on) {
+		if (tsif_device->tsif_clk)
+			clk_enable(tsif_device->tsif_clk);
+		if (tsif_device->tsif_pclk)
+			clk_enable(tsif_device->tsif_pclk);
+		clk_enable(tsif_device->tsif_ref_clk);
+	} else {
+		if (tsif_device->tsif_clk)
+			clk_disable(tsif_device->tsif_clk);
+		if (tsif_device->tsif_pclk)
+			clk_disable(tsif_device->tsif_pclk);
+		clk_disable(tsif_device->tsif_ref_clk);
+	}
+}
+/* ===clocks end=== */
+/* ===gpio begin=== */
+
+static void tsif_gpios_free(const struct msm_gpio *table, int size)
+{
+	int i;
+	const struct msm_gpio *g;
+	for (i = size-1; i >= 0; i--) {
+		g = table + i;
+		gpio_free(GPIO_PIN(g->gpio_cfg));
+	}
+}
+
+static int tsif_gpios_request(const struct msm_gpio *table, int size)
+{
+	int rc;
+	int i;
+	const struct msm_gpio *g;
+	for (i = 0; i < size; i++) {
+		g = table + i;
+		rc = gpio_request(GPIO_PIN(g->gpio_cfg), g->label);
+		if (rc) {
+			pr_err("gpio_request(%d) <%s> failed: %d\n",
+			       GPIO_PIN(g->gpio_cfg), g->label ?: "?", rc);
+			goto err;
+		}
+	}
+	return 0;
+err:
+	tsif_gpios_free(table, i);
+	return rc;
+}
+
+static int tsif_gpios_disable(const struct msm_gpio *table, int size)
+{
+	int rc = 0;
+	int i;
+	const struct msm_gpio *g;
+	for (i = size-1; i >= 0; i--) {
+		int tmp;
+		g = table + i;
+		tmp = gpio_tlmm_config(g->gpio_cfg, GPIO_CFG_DISABLE);
+		if (tmp) {
+			pr_err("gpio_tlmm_config(0x%08x, GPIO_CFG_DISABLE)"
+			       " <%s> failed: %d\n",
+			       g->gpio_cfg, g->label ?: "?", rc);
+			pr_err("pin %d func %d dir %d pull %d drvstr %d\n",
+			       GPIO_PIN(g->gpio_cfg), GPIO_FUNC(g->gpio_cfg),
+			       GPIO_DIR(g->gpio_cfg), GPIO_PULL(g->gpio_cfg),
+			       GPIO_DRVSTR(g->gpio_cfg));
+			if (!rc)
+				rc = tmp;
+		}
+	}
+
+	return rc;
+}
+
+static int tsif_gpios_enable(const struct msm_gpio *table, int size)
+{
+	int rc;
+	int i;
+	const struct msm_gpio *g;
+	for (i = 0; i < size; i++) {
+		g = table + i;
+		rc = gpio_tlmm_config(g->gpio_cfg, GPIO_CFG_ENABLE);
+		if (rc) {
+			pr_err("gpio_tlmm_config(0x%08x, GPIO_CFG_ENABLE)"
+			       " <%s> failed: %d\n",
+			       g->gpio_cfg, g->label ?: "?", rc);
+			pr_err("pin %d func %d dir %d pull %d drvstr %d\n",
+			       GPIO_PIN(g->gpio_cfg), GPIO_FUNC(g->gpio_cfg),
+			       GPIO_DIR(g->gpio_cfg), GPIO_PULL(g->gpio_cfg),
+			       GPIO_DRVSTR(g->gpio_cfg));
+			goto err;
+		}
+	}
+	return 0;
+err:
+	tsif_gpios_disable(table, i);
+	return rc;
+}
+
+static int tsif_gpios_request_enable(const struct msm_gpio *table, int size)
+{
+	int rc = tsif_gpios_request(table, size);
+	if (rc)
+		return rc;
+	rc = tsif_gpios_enable(table, size);
+	if (rc)
+		tsif_gpios_free(table, size);
+	return rc;
+}
+
+static void tsif_gpios_disable_free(const struct msm_gpio *table, int size)
+{
+	tsif_gpios_disable(table, size);
+	tsif_gpios_free(table, size);
+}
+
+static int tsif_start_gpios(struct msm_tsif_device *tsif_device)
+{
+	struct msm_tsif_platform_data *pdata =
+		tsif_device->pdev->dev.platform_data;
+	return tsif_gpios_request_enable(pdata->gpios, pdata->num_gpios);
+}
+
+static void tsif_stop_gpios(struct msm_tsif_device *tsif_device)
+{
+	struct msm_tsif_platform_data *pdata =
+		tsif_device->pdev->dev.platform_data;
+	tsif_gpios_disable_free(pdata->gpios, pdata->num_gpios);
+}
+
+/* ===gpio end=== */
+
+static int tsif_start_hw(struct msm_tsif_device *tsif_device)
+{
+	u32 ctl = TSIF_STS_CTL_EN_IRQ |
+		  TSIF_STS_CTL_EN_TIME_LIM |
+		  TSIF_STS_CTL_EN_TCR |
+		  TSIF_STS_CTL_EN_DM;
+	dev_info(&tsif_device->pdev->dev, "%s\n", __func__);
+	switch (tsif_device->mode) {
+	case 1: /* mode 1 */
+		ctl |= (0 << 5);
+		break;
+	case 2: /* mode 2 */
+		ctl |= (1 << 5);
+		break;
+	case 3: /* manual - control from debugfs */
+		return 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+	iowrite32(ctl, tsif_device->base + TSIF_STS_CTL_OFF);
+	iowrite32(tsif_device->time_limit,
+		  tsif_device->base + TSIF_TIME_LIMIT_OFF);
+	wmb();
+	iowrite32(ctl | TSIF_STS_CTL_START,
+		  tsif_device->base + TSIF_STS_CTL_OFF);
+	wmb();
+	ctl = ioread32(tsif_device->base + TSIF_STS_CTL_OFF);
+	return (ctl & TSIF_STS_CTL_START) ? 0 : -EFAULT;
+}
+
+static void tsif_stop_hw(struct msm_tsif_device *tsif_device)
+{
+	iowrite32(TSIF_STS_CTL_STOP, tsif_device->base + TSIF_STS_CTL_OFF);
+	wmb();
+}
+
+/* ===DMA begin=== */
+/**
+ * TSIF DMA theory of operation
+ *
+ * Circular memory buffer \a tsif_mem_buffer allocated;
+ * 4 pointers points to and moved forward on:
+ * - \a ri index of first ready to read packet.
+ *      Updated by client's call to tsif_reclaim_packets()
+ * - \a wi points to the next packet to be written by DM.
+ *      Data below is valid and will not be overriden by DMA.
+ *      Moved on DM callback
+ * - \a dmwi points to the next packet not scheduled yet for DM
+ *      moved when packet scheduled for DM
+ *
+ * In addition, DM xfer keep internal \a wi - copy of \a tsif_device->dmwi
+ * at time immediately after scheduling.
+ *
+ * Initially, 2 packets get scheduled for the DM.
+ *
+ * Upon packet receive, DM writes packet to the pre-programmed
+ * location and invoke its callback.
+ *
+ * DM callback moves sets wi pointer to \a xfer->wi;
+ * then it schedules next packet for DM and moves \a dmwi pointer.
+ *
+ * Buffer overflow handling
+ *
+ * If \a dmwi == \a ri-1, buffer is full and \a dmwi can't be advanced.
+ * DMA re-scheduled to the same index.
+ * Callback check and not move \a wi to become equal to \a ri
+ *
+ * On \a read request, data between \a ri and \a wi pointers may be read;
+ * \ri pointer moved accordingly.
+ *
+ * It is always granted, on modulo sizeof(tsif_mem_buffer), that
+ * \a wi is between [\a ri, \a dmwi]
+ *
+ * Amount of data available is (wi-ri)*TSIF_PKT_SIZE
+ *
+ * Number of scheduled packets for DM: (dmwi-wi)
+ */
+
+/**
+ * tsif_dma_schedule - schedule DMA transfers
+ *
+ * @tsif_device: device
+ *
+ * Executed from process context on init, or from tasklet when
+ * re-scheduling upon DMA completion.
+ * This prevent concurrent execution from several CPU's
+ */
+static void tsif_dma_schedule(struct msm_tsif_device *tsif_device)
+{
+	int i, dmwi0, dmwi1, found = 0;
+	/* find free entry */
+	for (i = 0; i < 2; i++) {
+		struct tsif_xfer *xfer = &tsif_device->xfer[i];
+		if (xfer->busy)
+			continue;
+		found++;
+		xfer->busy = 1;
+		dmwi0 = tsif_device->dmwi;
+		tsif_device->dmov_cmd[i]->box.dst_row_addr =
+			tsif_device->data_buffer_dma + TSIF_PKT_SIZE * dmwi0;
+		/* proposed value for dmwi */
+		dmwi1 = (dmwi0 + TSIF_PKTS_IN_CHUNK) % TSIF_PKTS_IN_BUF;
+		/**
+		 * If dmwi going to overlap with ri,
+		 * overflow occurs because data was not read.
+		 * Still get this packet, to not interrupt TSIF
+		 * hardware, but do not advance dmwi.
+		 *
+		 * Upon receive, packet will be dropped.
+		 */
+		if (dmwi1 != tsif_device->ri) {
+			tsif_device->dmwi = dmwi1;
+		} else {
+			dev_info(&tsif_device->pdev->dev,
+				 "Overflow detected\n");
+		}
+		xfer->wi = tsif_device->dmwi;
+#ifdef CONFIG_TSIF_DEBUG
+		dev_info(&tsif_device->pdev->dev,
+			"schedule xfer[%d] -> [%2d]{%2d}\n",
+			i, dmwi0, xfer->wi);
+#endif
+		/* complete all the writes to box */
+		dma_coherent_pre_ops();
+		msm_dmov_enqueue_cmd(tsif_device->dma, &xfer->hdr);
+	}
+	if (!found)
+		dev_info(&tsif_device->pdev->dev,
+			 "All xfer entries are busy\n");
+}
+
+/**
+ * tsif_dmov_complete_func - DataMover completion callback
+ *
+ * @cmd:      original DM command
+ * @result:   DM result
+ * @err:      optional error buffer
+ *
+ * Executed in IRQ context (Data Mover's IRQ)
+ * DataMover's spinlock @msm_dmov_lock held.
+ */
+static void tsif_dmov_complete_func(struct msm_dmov_cmd *cmd,
+				    unsigned int result,
+				    struct msm_dmov_errdata *err)
+{
+	int i;
+	u32 data_offset;
+	struct tsif_xfer *xfer;
+	struct msm_tsif_device *tsif_device;
+	int reschedule = 0;
+	if (!(result & DMOV_RSLT_VALID)) { /* can I trust to @cmd? */
+		pr_err("Invalid DMOV result: rc=0x%08x, cmd = %p", result, cmd);
+		return;
+	}
+	/* restore original context */
+	xfer = container_of(cmd, struct tsif_xfer, hdr);
+	tsif_device = xfer->tsif_device;
+	i = xfer - tsif_device->xfer;
+	data_offset = tsif_device->dmov_cmd[i]->box.dst_row_addr -
+		      tsif_device->data_buffer_dma;
+
+	/* order reads from the xferred buffer */
+	dma_coherent_post_ops();
+	if (result & DMOV_RSLT_DONE) {
+		int w = data_offset / TSIF_PKT_SIZE;
+		tsif_device->stat_rx++;
+		/*
+		 * sowtware overflow when I was scheduled?
+		 *
+		 * @w is where this xfer was actually written to;
+		 * @xfer->wi is where device's @wi will be set;
+		 *
+		 * if these 2 are equal, we are short in space and
+		 * going to overwrite this xfer - this is "soft drop"
+		 */
+		if (w == xfer->wi)
+			tsif_device->stat_soft_drop++;
+		reschedule = (tsif_device->state == tsif_state_running);
+#ifdef CONFIG_TSIF_DEBUG
+		/* IFI calculation */
+		/*
+		 * update stat_ifi (inter frame interval)
+		 *
+		 * Calculate time difference between last and 1-st
+		 * packets in chunk
+		 *
+		 * To be removed after tuning
+		 */
+		if (TSIF_PKTS_IN_CHUNK > 1) {
+			void *ptr = tsif_device->data_buffer + data_offset;
+			u32 *p0 = ptr;
+			u32 *p1 = ptr + (TSIF_PKTS_IN_CHUNK - 1) *
+				TSIF_PKT_SIZE;
+			u32 tts0 = TSIF_STATUS_TTS(tsif_device->stat0 =
+						   tsif_pkt_status(p0));
+			u32 tts1 = TSIF_STATUS_TTS(tsif_device->stat1 =
+						   tsif_pkt_status(p1));
+			tsif_device->stat_ifi = (tts1 - tts0) /
+				(TSIF_PKTS_IN_CHUNK - 1);
+		}
+#endif
+	} else {
+		/**
+		 *  Error or flush
+		 *
+		 *  To recover - re-open TSIF device.
+		 */
+		/* mark status "not valid" in data buffer */
+		int n;
+		void *ptr = tsif_device->data_buffer + data_offset;
+		for (n = 0; n < TSIF_PKTS_IN_CHUNK; n++) {
+			u32 *p = ptr + (n * TSIF_PKT_SIZE);
+			/* last dword is status + TTS */
+			p[TSIF_PKT_SIZE / sizeof(*p) - 1] = 0;
+		}
+		if (result & DMOV_RSLT_ERROR) {
+			dev_err(&tsif_device->pdev->dev,
+				"DMA error (0x%08x)\n", result);
+			tsif_device->stat_dmov_err++;
+			/* force device close */
+			if (tsif_device->state == tsif_state_running) {
+				tsif_stop_hw(tsif_device);
+				/*
+				 * Clocks _may_ be stopped right from IRQ
+				 * context. This is far from optimal w.r.t
+				 * latency.
+				 *
+				 * But, this branch taken only in case of
+				 * severe hardware problem (I don't even know
+				 * what should happens for DMOV_RSLT_ERROR);
+				 * thus I prefer code simplicity over
+				 * performance.
+				 */
+				tsif_clock(tsif_device, 0);
+				tsif_device->state = tsif_state_flushing;
+			}
+		}
+		if (result & DMOV_RSLT_FLUSH) {
+			/*
+			 * Flushing normally happens in process of
+			 * @tsif_stop(), when we are waiting for outstanding
+			 * DMA commands to be flushed.
+			 */
+			dev_info(&tsif_device->pdev->dev,
+				 "DMA channel flushed (0x%08x)\n", result);
+			if (tsif_device->state == tsif_state_flushing) {
+				if ((!tsif_device->xfer[0].busy) &&
+				    (!tsif_device->xfer[1].busy)) {
+					tsif_device->state = tsif_state_stopped;
+				}
+			}
+		}
+		if (err)
+			dev_err(&tsif_device->pdev->dev,
+				"Flush data: %08x %08x %08x %08x %08x %08x\n",
+				err->flush[0], err->flush[1], err->flush[2],
+				err->flush[3], err->flush[4], err->flush[5]);
+	}
+	tsif_device->wi = xfer->wi;
+	xfer->busy = 0;
+	if (tsif_device->client_notify)
+		tsif_device->client_notify(tsif_device->client_data);
+	/*
+	 * Can't schedule next DMA -
+	 * DataMover driver still hold its semaphore,
+	 * deadlock will occur.
+	 */
+	if (reschedule)
+		tasklet_schedule(&tsif_device->dma_refill);
+}
+
+/**
+ * tsif_dma_refill - tasklet function for tsif_device->dma_refill
+ *
+ * @data:   tsif_device
+ *
+ * Reschedule DMA requests
+ *
+ * Executed in tasklet
+ */
+static void tsif_dma_refill(unsigned long data)
+{
+	struct msm_tsif_device *tsif_device = (struct msm_tsif_device *) data;
+	if (tsif_device->state == tsif_state_running)
+		tsif_dma_schedule(tsif_device);
+}
+
+/**
+ * tsif_dma_flush - flush DMA channel
+ *
+ * @tsif_device:
+ *
+ * busy wait till DMA flushed
+ */
+static void tsif_dma_flush(struct msm_tsif_device *tsif_device)
+{
+	if (tsif_device->xfer[0].busy || tsif_device->xfer[1].busy) {
+		tsif_device->state = tsif_state_flushing;
+		while (tsif_device->xfer[0].busy ||
+		       tsif_device->xfer[1].busy) {
+			msm_dmov_flush(tsif_device->dma);
+			msleep(10);
+		}
+	}
+	tsif_device->state = tsif_state_stopped;
+	if (tsif_device->client_notify)
+		tsif_device->client_notify(tsif_device->client_data);
+}
+
+static void tsif_dma_exit(struct msm_tsif_device *tsif_device)
+{
+	int i;
+	tsif_device->state = tsif_state_flushing;
+	tasklet_kill(&tsif_device->dma_refill);
+	tsif_dma_flush(tsif_device);
+	for (i = 0; i < 2; i++) {
+		if (tsif_device->dmov_cmd[i]) {
+			dma_free_coherent(NULL, sizeof(struct tsif_dmov_cmd),
+					  tsif_device->dmov_cmd[i],
+					  tsif_device->dmov_cmd_dma[i]);
+			tsif_device->dmov_cmd[i] = NULL;
+		}
+	}
+	if (tsif_device->data_buffer) {
+		tsif_device->blob_wrapper_databuf.data = NULL;
+		tsif_device->blob_wrapper_databuf.size = 0;
+		dma_free_coherent(NULL, TSIF_BUF_SIZE,
+				  tsif_device->data_buffer,
+				  tsif_device->data_buffer_dma);
+		tsif_device->data_buffer = NULL;
+	}
+}
+
+static int tsif_dma_init(struct msm_tsif_device *tsif_device)
+{
+	int i;
+	/* TODO: allocate all DMA memory in one buffer */
+	/* Note: don't pass device,
+	   it require coherent_dma_mask id device definition */
+	tsif_device->data_buffer = dma_alloc_coherent(NULL, TSIF_BUF_SIZE,
+				&tsif_device->data_buffer_dma, GFP_KERNEL);
+	if (!tsif_device->data_buffer)
+		goto err;
+	dev_info(&tsif_device->pdev->dev, "data_buffer: %p phys 0x%08x\n",
+		 tsif_device->data_buffer, tsif_device->data_buffer_dma);
+	tsif_device->blob_wrapper_databuf.data = tsif_device->data_buffer;
+	tsif_device->blob_wrapper_databuf.size = TSIF_BUF_SIZE;
+	tsif_device->ri = 0;
+	tsif_device->wi = 0;
+	tsif_device->dmwi = 0;
+	for (i = 0; i < 2; i++) {
+		dmov_box *box;
+		struct msm_dmov_cmd *hdr;
+		tsif_device->dmov_cmd[i] = dma_alloc_coherent(NULL,
+			sizeof(struct tsif_dmov_cmd),
+			&tsif_device->dmov_cmd_dma[i], GFP_KERNEL);
+		if (!tsif_device->dmov_cmd[i])
+			goto err;
+		dev_info(&tsif_device->pdev->dev, "dma[%i]: %p phys 0x%08x\n",
+			 i, tsif_device->dmov_cmd[i],
+			 tsif_device->dmov_cmd_dma[i]);
+		/* dst in 16 LSB, src in 16 MSB */
+		box = &(tsif_device->dmov_cmd[i]->box);
+		box->cmd = CMD_MODE_BOX | CMD_LC |
+			   CMD_SRC_CRCI(tsif_device->crci);
+		box->src_row_addr =
+			tsif_device->memres->start + TSIF_DATA_PORT_OFF;
+		box->src_dst_len = (TSIF_PKT_SIZE << 16) | TSIF_PKT_SIZE;
+		box->num_rows = (TSIF_PKTS_IN_CHUNK << 16) | TSIF_PKTS_IN_CHUNK;
+		box->row_offset = (0 << 16) | TSIF_PKT_SIZE;
+
+		tsif_device->dmov_cmd[i]->box_ptr = CMD_PTR_LP |
+			DMOV_CMD_ADDR(tsif_device->dmov_cmd_dma[i] +
+				      offsetof(struct tsif_dmov_cmd, box));
+		tsif_device->xfer[i].tsif_device = tsif_device;
+		hdr = &tsif_device->xfer[i].hdr;
+		hdr->cmdptr = DMOV_CMD_ADDR(tsif_device->dmov_cmd_dma[i] +
+			      offsetof(struct tsif_dmov_cmd, box_ptr));
+		hdr->complete_func = tsif_dmov_complete_func;
+	}
+	msm_dmov_flush(tsif_device->dma);
+	return 0;
+err:
+	dev_err(&tsif_device->pdev->dev, "Failed to allocate DMA buffers\n");
+	tsif_dma_exit(tsif_device);
+	return -ENOMEM;
+}
+
+/* ===DMA end=== */
+
+/* ===IRQ begin=== */
+
+static irqreturn_t tsif_irq(int irq, void *dev_id)
+{
+	struct msm_tsif_device *tsif_device = dev_id;
+	u32 sts_ctl = ioread32(tsif_device->base + TSIF_STS_CTL_OFF);
+	if (!(sts_ctl & (TSIF_STS_CTL_PACK_AVAIL |
+			 TSIF_STS_CTL_OVERFLOW |
+			 TSIF_STS_CTL_LOST_SYNC |
+			 TSIF_STS_CTL_TIMEOUT))) {
+		dev_warn(&tsif_device->pdev->dev, "Spurious interrupt\n");
+		return IRQ_NONE;
+	}
+	if (sts_ctl & TSIF_STS_CTL_PACK_AVAIL) {
+		dev_info(&tsif_device->pdev->dev, "TSIF IRQ: PACK_AVAIL\n");
+		tsif_device->stat_rx++;
+	}
+	if (sts_ctl & TSIF_STS_CTL_OVERFLOW) {
+		dev_info(&tsif_device->pdev->dev, "TSIF IRQ: OVERFLOW\n");
+		tsif_device->stat_overflow++;
+	}
+	if (sts_ctl & TSIF_STS_CTL_LOST_SYNC) {
+		dev_info(&tsif_device->pdev->dev, "TSIF IRQ: LOST SYNC\n");
+		tsif_device->stat_lost_sync++;
+	}
+	if (sts_ctl & TSIF_STS_CTL_TIMEOUT) {
+		dev_info(&tsif_device->pdev->dev, "TSIF IRQ: TIMEOUT\n");
+		tsif_device->stat_timeout++;
+	}
+	iowrite32(sts_ctl, tsif_device->base + TSIF_STS_CTL_OFF);
+	wmb();
+	return IRQ_HANDLED;
+}
+
+/* ===IRQ end=== */
+
+/* ===Device attributes begin=== */
+
+static ssize_t show_stats(struct device *dev, struct device_attribute *attr,
+			  char *buf)
+{
+	struct msm_tsif_device *tsif_device = dev_get_drvdata(dev);
+	char *state_string;
+	switch (tsif_device->state) {
+	case tsif_state_stopped:
+		state_string = "stopped";
+		break;
+	case tsif_state_running:
+		state_string = "running";
+		break;
+	case tsif_state_flushing:
+		state_string = "flushing";
+		break;
+	default:
+		state_string = "???";
+	}
+	return snprintf(buf, PAGE_SIZE,
+			"Device       %s\n"
+			"Mode       = %d\n"
+			"Time limit = %d\n"
+			"State        %s\n"
+			"Client     = %p\n"
+			"Pkt/Buf    = %d\n"
+			"Pkt/chunk  = %d\n"
+			"--statistics--\n"
+			"Rx chunks  = %d\n"
+			"Overflow   = %d\n"
+			"Lost sync  = %d\n"
+			"Timeout    = %d\n"
+			"DMA error  = %d\n"
+			"Soft drop  = %d\n"
+			"IFI        = %d\n"
+			"(0x%08x - 0x%08x) / %d\n"
+			"--debug--\n"
+			"GLBL_CLK_ENA     = 0x%08x\n"
+			"ROW_RESET        = 0x%08x\n"
+			"CLK_HALT_STATEB  = 0x%08x\n"
+			"TV_NS_REG        = 0x%08x\n"
+			"TSIF_NS_REG      = 0x%08x\n",
+			dev_name(dev),
+			tsif_device->mode,
+			tsif_device->time_limit,
+			state_string,
+			tsif_device->client_data,
+			TSIF_PKTS_IN_BUF,
+			TSIF_PKTS_IN_CHUNK,
+			tsif_device->stat_rx,
+			tsif_device->stat_overflow,
+			tsif_device->stat_lost_sync,
+			tsif_device->stat_timeout,
+			tsif_device->stat_dmov_err,
+			tsif_device->stat_soft_drop,
+			tsif_device->stat_ifi,
+			tsif_device->stat1,
+			tsif_device->stat0,
+			TSIF_PKTS_IN_CHUNK - 1,
+			ioread32(GLBL_CLK_ENA),
+			ioread32(ROW_RESET),
+			ioread32(CLK_HALT_STATEB),
+			ioread32(TV_NS_REG),
+			ioread32(TSIF_NS_REG)
+			);
+}
+/**
+ * set_stats - reset statistics on write
+ *
+ * @dev:
+ * @attr:
+ * @buf:
+ * @count:
+ */
+static ssize_t set_stats(struct device *dev, struct device_attribute *attr,
+			 const char *buf, size_t count)
+{
+	struct msm_tsif_device *tsif_device = dev_get_drvdata(dev);
+	tsif_device->stat_rx = 0;
+	tsif_device->stat_overflow = 0;
+	tsif_device->stat_lost_sync = 0;
+	tsif_device->stat_timeout = 0;
+	tsif_device->stat_dmov_err = 0;
+	tsif_device->stat_soft_drop = 0;
+	tsif_device->stat_ifi = 0;
+	return count;
+}
+static DEVICE_ATTR(stats, S_IRUGO | S_IWUSR, show_stats, set_stats);
+
+static ssize_t show_mode(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct msm_tsif_device *tsif_device = dev_get_drvdata(dev);
+	return snprintf(buf, PAGE_SIZE, "%d\n", tsif_device->mode);
+}
+
+static ssize_t set_mode(struct device *dev, struct device_attribute *attr,
+			const char *buf, size_t count)
+{
+	struct msm_tsif_device *tsif_device = dev_get_drvdata(dev);
+	int value;
+	int rc;
+	if (1 != sscanf(buf, "%d", &value)) {
+		dev_err(&tsif_device->pdev->dev,
+			"Failed to parse integer: <%s>\n", buf);
+		return -EINVAL;
+	}
+	rc = tsif_set_mode(tsif_device, value);
+	if (!rc)
+		rc = count;
+	return rc;
+}
+static DEVICE_ATTR(mode, S_IRUGO | S_IWUSR, show_mode, set_mode);
+
+static ssize_t show_time_limit(struct device *dev,
+			       struct device_attribute *attr,
+			       char *buf)
+{
+	struct msm_tsif_device *tsif_device = dev_get_drvdata(dev);
+	return snprintf(buf, PAGE_SIZE, "%d\n", tsif_device->time_limit);
+}
+
+static ssize_t set_time_limit(struct device *dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct msm_tsif_device *tsif_device = dev_get_drvdata(dev);
+	int value;
+	int rc;
+	if (1 != sscanf(buf, "%d", &value)) {
+		dev_err(&tsif_device->pdev->dev,
+			"Failed to parse integer: <%s>\n", buf);
+		return -EINVAL;
+	}
+	rc = tsif_set_time_limit(tsif_device, value);
+	if (!rc)
+		rc = count;
+	return rc;
+}
+static DEVICE_ATTR(time_limit, S_IRUGO | S_IWUSR,
+		   show_time_limit, set_time_limit);
+
+static ssize_t show_buf_config(struct device *dev,
+			       struct device_attribute *attr,
+			       char *buf)
+{
+	struct msm_tsif_device *tsif_device = dev_get_drvdata(dev);
+	return snprintf(buf, PAGE_SIZE, "%d * %d\n",
+			tsif_device->pkts_per_chunk,
+			tsif_device->chunks_per_buf);
+}
+
+static ssize_t set_buf_config(struct device *dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct msm_tsif_device *tsif_device = dev_get_drvdata(dev);
+	u32 p, c;
+	int rc;
+	if (2 != sscanf(buf, "%d * %d", &p, &c)) {
+		dev_err(&tsif_device->pdev->dev,
+			"Failed to parse integer: <%s>\n", buf);
+		return -EINVAL;
+	}
+	rc = tsif_set_buf_config(tsif_device, p, c);
+	if (!rc)
+		rc = count;
+	return rc;
+}
+static DEVICE_ATTR(buf_config, S_IRUGO | S_IWUSR,
+		   show_buf_config, set_buf_config);
+
+static struct attribute *dev_attrs[] = {
+	&dev_attr_stats.attr,
+	&dev_attr_mode.attr,
+	&dev_attr_time_limit.attr,
+	&dev_attr_buf_config.attr,
+	NULL,
+};
+static struct attribute_group dev_attr_grp = {
+	.attrs = dev_attrs,
+};
+/* ===Device attributes end=== */
+
+/* ===debugfs begin=== */
+
+static int debugfs_iomem_x32_set(void *data, u64 val)
+{
+	iowrite32(val, data);
+	wmb();
+	return 0;
+}
+
+static int debugfs_iomem_x32_get(void *data, u64 *val)
+{
+	*val = ioread32(data);
+	return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(fops_iomem_x32, debugfs_iomem_x32_get,
+			debugfs_iomem_x32_set, "0x%08llx\n");
+
+struct dentry *debugfs_create_iomem_x32(const char *name, mode_t mode,
+					struct dentry *parent, u32 *value)
+{
+	return debugfs_create_file(name, mode, parent, value, &fops_iomem_x32);
+}
+
+static int action_open(struct msm_tsif_device *tsif_device)
+{
+	int rc = -EINVAL;
+	int result;
+
+	struct msm_tsif_platform_data *pdata =
+		tsif_device->pdev->dev.platform_data;
+	dev_info(&tsif_device->pdev->dev, "%s\n", __func__);
+	if (tsif_device->state != tsif_state_stopped)
+		return -EAGAIN;
+	rc = tsif_dma_init(tsif_device);
+	if (rc) {
+		dev_err(&tsif_device->pdev->dev, "failed to init DMA\n");
+		return rc;
+	}
+	tsif_device->state = tsif_state_running;
+	/*
+	 * DMA should be scheduled prior to TSIF hardware initialization,
+	 * otherwise "bus error" will be reported by Data Mover
+	 */
+	enable_irq(tsif_device->irq);
+	tsif_clock(tsif_device, 1);
+	tsif_dma_schedule(tsif_device);
+	/*
+	 * init the device if required
+	 */
+	if (pdata->init)
+		pdata->init(pdata);
+	rc = tsif_start_hw(tsif_device);
+	if (rc) {
+		dev_err(&tsif_device->pdev->dev, "Unable to start HW\n");
+		tsif_dma_exit(tsif_device);
+		tsif_clock(tsif_device, 0);
+		return rc;
+	}
+
+	result = pm_runtime_get(&tsif_device->pdev->dev);
+	if (result < 0) {
+		dev_err(&tsif_device->pdev->dev,
+			"Runtime PM: Unable to wake up the device, rc = %d\n",
+			result);
+		return result;
+	}
+
+	wake_lock(&tsif_device->wake_lock);
+	return rc;
+}
+
+static int action_close(struct msm_tsif_device *tsif_device)
+{
+	dev_info(&tsif_device->pdev->dev, "%s, state %d\n", __func__,
+		 (int)tsif_device->state);
+	/*
+	 * DMA should be flushed/stopped prior to TSIF hardware stop,
+	 * otherwise "bus error" will be reported by Data Mover
+	 */
+	tsif_stop_hw(tsif_device);
+	tsif_dma_exit(tsif_device);
+	tsif_clock(tsif_device, 0);
+	disable_irq(tsif_device->irq);
+
+	pm_runtime_put(&tsif_device->pdev->dev);
+	wake_unlock(&tsif_device->wake_lock);
+	return 0;
+}
+
+
+static struct {
+	int (*func)(struct msm_tsif_device *);
+	const char *name;
+} actions[] = {
+	{ action_open,  "open"},
+	{ action_close, "close"},
+};
+
+static ssize_t tsif_debugfs_action_write(struct file *filp,
+					 const char __user *userbuf,
+					 size_t count, loff_t *f_pos)
+{
+	int i;
+	struct msm_tsif_device *tsif_device = filp->private_data;
+	char s[40];
+	int len = min(sizeof(s) - 1, count);
+	if (copy_from_user(s, userbuf, len))
+		return -EFAULT;
+	s[len] = '\0';
+	dev_info(&tsif_device->pdev->dev, "%s:%s\n", __func__, s);
+	for (i = 0; i < ARRAY_SIZE(actions); i++) {
+		if (!strncmp(s, actions[i].name,
+		    min(count, strlen(actions[i].name)))) {
+			int rc = actions[i].func(tsif_device);
+			if (!rc)
+				rc = count;
+			return rc;
+		}
+	}
+	return -EINVAL;
+}
+
+static int tsif_debugfs_generic_open(struct inode *inode, struct file *filp)
+{
+	filp->private_data = inode->i_private;
+	return 0;
+}
+
+static const struct file_operations fops_debugfs_action = {
+	.open  = tsif_debugfs_generic_open,
+	.write = tsif_debugfs_action_write,
+};
+
+static ssize_t tsif_debugfs_dma_read(struct file *filp, char __user *userbuf,
+				     size_t count, loff_t *f_pos)
+{
+	static char bufa[200];
+	static char *buf = bufa;
+	int sz = sizeof(bufa);
+	struct msm_tsif_device *tsif_device = filp->private_data;
+	int len = 0;
+	if (tsif_device) {
+		int i;
+		len += snprintf(buf + len, sz - len,
+				"ri %3d | wi %3d | dmwi %3d |",
+				tsif_device->ri, tsif_device->wi,
+				tsif_device->dmwi);
+		for (i = 0; i < 2; i++) {
+			struct tsif_xfer *xfer = &tsif_device->xfer[i];
+			if (xfer->busy) {
+				u32 dst =
+				    tsif_device->dmov_cmd[i]->box.dst_row_addr;
+				u32 base = tsif_device->data_buffer_dma;
+				int w = (dst - base) / TSIF_PKT_SIZE;
+				len += snprintf(buf + len, sz - len,
+						" [%3d]{%3d}",
+						w, xfer->wi);
+			} else {
+				len += snprintf(buf + len, sz - len,
+						" ---idle---");
+			}
+		}
+			len += snprintf(buf + len, sz - len, "\n");
+	} else {
+		len += snprintf(buf + len, sz - len, "No TSIF device???\n");
+	}
+	return simple_read_from_buffer(userbuf, count, f_pos, buf, len);
+}
+
+static const struct file_operations fops_debugfs_dma = {
+	.open = tsif_debugfs_generic_open,
+	.read = tsif_debugfs_dma_read,
+};
+
+static ssize_t tsif_debugfs_gpios_read(struct file *filp, char __user *userbuf,
+				       size_t count, loff_t *f_pos)
+{
+	static char bufa[300];
+	static char *buf = bufa;
+	int sz = sizeof(bufa);
+	struct msm_tsif_device *tsif_device = filp->private_data;
+	int len = 0;
+	if (tsif_device) {
+		struct msm_tsif_platform_data *pdata =
+			tsif_device->pdev->dev.platform_data;
+		int i;
+		for (i = 0; i < pdata->num_gpios; i++) {
+			if (pdata->gpios[i].gpio_cfg) {
+				int x = !!gpio_get_value(GPIO_PIN(
+					pdata->gpios[i].gpio_cfg));
+				len += snprintf(buf + len, sz - len,
+						"%15s: %d\n",
+						pdata->gpios[i].label, x);
+			}
+		}
+	} else {
+		len += snprintf(buf + len, sz - len, "No TSIF device???\n");
+	}
+	return simple_read_from_buffer(userbuf, count, f_pos, buf, len);
+}
+
+static const struct file_operations fops_debugfs_gpios = {
+	.open = tsif_debugfs_generic_open,
+	.read = tsif_debugfs_gpios_read,
+};
+
+
+static void tsif_debugfs_init(struct msm_tsif_device *tsif_device)
+{
+	tsif_device->dent_tsif = debugfs_create_dir(
+	      dev_name(&tsif_device->pdev->dev), NULL);
+	if (tsif_device->dent_tsif) {
+		int i;
+		void __iomem *base = tsif_device->base;
+		for (i = 0; i < ARRAY_SIZE(debugfs_tsif_regs); i++) {
+			tsif_device->debugfs_tsif_regs[i] =
+			   debugfs_create_iomem_x32(
+				debugfs_tsif_regs[i].name,
+				debugfs_tsif_regs[i].mode,
+				tsif_device->dent_tsif,
+				base + debugfs_tsif_regs[i].offset);
+		}
+		tsif_device->debugfs_gpio = debugfs_create_file("gpios",
+		    S_IRUGO,
+		    tsif_device->dent_tsif, tsif_device, &fops_debugfs_gpios);
+		tsif_device->debugfs_action = debugfs_create_file("action",
+		    S_IWUSR,
+		    tsif_device->dent_tsif, tsif_device, &fops_debugfs_action);
+		tsif_device->debugfs_dma = debugfs_create_file("dma",
+		    S_IRUGO,
+		    tsif_device->dent_tsif, tsif_device, &fops_debugfs_dma);
+		tsif_device->debugfs_databuf = debugfs_create_blob("data_buf",
+		    S_IRUGO,
+		    tsif_device->dent_tsif, &tsif_device->blob_wrapper_databuf);
+	}
+}
+
+static void tsif_debugfs_exit(struct msm_tsif_device *tsif_device)
+{
+	if (tsif_device->dent_tsif) {
+		int i;
+		debugfs_remove_recursive(tsif_device->dent_tsif);
+		tsif_device->dent_tsif = NULL;
+		for (i = 0; i < ARRAY_SIZE(debugfs_tsif_regs); i++)
+			tsif_device->debugfs_tsif_regs[i] = NULL;
+		tsif_device->debugfs_gpio = NULL;
+		tsif_device->debugfs_action = NULL;
+		tsif_device->debugfs_dma = NULL;
+		tsif_device->debugfs_databuf = NULL;
+	}
+}
+/* ===debugfs end=== */
+
+/* ===module begin=== */
+static LIST_HEAD(tsif_devices);
+
+static struct msm_tsif_device *tsif_find_by_id(int id)
+{
+	struct msm_tsif_device *tsif_device;
+	list_for_each_entry(tsif_device, &tsif_devices, devlist) {
+		if (tsif_device->pdev->id == id)
+			return tsif_device;
+	}
+	return NULL;
+}
+
+static int __devinit msm_tsif_probe(struct platform_device *pdev)
+{
+	int rc = -ENODEV;
+	struct msm_tsif_platform_data *plat = pdev->dev.platform_data;
+	struct msm_tsif_device *tsif_device;
+	struct resource *res;
+	/* check device validity */
+	/* must have platform data */
+	if (!plat) {
+		dev_err(&pdev->dev, "Platform data not available\n");
+		rc = -EINVAL;
+		goto out;
+	}
+/*TODO macro for max. id*/
+	if ((pdev->id < 0) || (pdev->id > 0)) {
+		dev_err(&pdev->dev, "Invalid device ID %d\n", pdev->id);
+		rc = -EINVAL;
+		goto out;
+	}
+	/* OK, we will use this device */
+	tsif_device = kzalloc(sizeof(struct msm_tsif_device), GFP_KERNEL);
+	if (!tsif_device) {
+		dev_err(&pdev->dev, "Failed to allocate memory for device\n");
+		rc = -ENOMEM;
+		goto out;
+	}
+	/* cross links */
+	tsif_device->pdev = pdev;
+	platform_set_drvdata(pdev, tsif_device);
+	tsif_device->mode = 1;
+	tsif_device->pkts_per_chunk = TSIF_PKTS_IN_CHUNK_DEFAULT;
+	tsif_device->chunks_per_buf = TSIF_CHUNKS_IN_BUF_DEFAULT;
+	tasklet_init(&tsif_device->dma_refill, tsif_dma_refill,
+		     (unsigned long)tsif_device);
+	if (tsif_get_clocks(tsif_device))
+		goto err_clocks;
+/* map I/O memory */
+	tsif_device->memres = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!tsif_device->memres) {
+		dev_err(&pdev->dev, "Missing MEM resource\n");
+		rc = -ENXIO;
+		goto err_rgn;
+	}
+	res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "Missing DMA resource\n");
+		rc = -ENXIO;
+		goto err_rgn;
+	}
+	tsif_device->dma = res->start;
+	tsif_device->crci = res->end;
+	tsif_device->base = ioremap(tsif_device->memres->start,
+				    resource_size(tsif_device->memres));
+	if (!tsif_device->base) {
+		dev_err(&pdev->dev, "ioremap failed\n");
+		goto err_ioremap;
+	}
+	dev_info(&pdev->dev, "remapped phys 0x%08x => virt %p\n",
+		 tsif_device->memres->start, tsif_device->base);
+	rc = tsif_start_gpios(tsif_device);
+	if (rc)
+		goto err_gpio;
+
+	pm_runtime_set_active(&pdev->dev);
+	pm_runtime_enable(&pdev->dev);
+
+	tsif_debugfs_init(tsif_device);
+	rc = platform_get_irq(pdev, 0);
+	if (rc > 0) {
+		tsif_device->irq = rc;
+		rc = request_irq(tsif_device->irq, tsif_irq, IRQF_SHARED,
+				 dev_name(&pdev->dev), tsif_device);
+		disable_irq(tsif_device->irq);
+	}
+	if (rc) {
+		dev_err(&pdev->dev, "failed to request IRQ %d : %d\n",
+			tsif_device->irq, rc);
+		goto err_irq;
+	}
+	rc = sysfs_create_group(&pdev->dev.kobj, &dev_attr_grp);
+	if (rc) {
+		dev_err(&pdev->dev, "failed to create dev. attrs : %d\n", rc);
+		goto err_attrs;
+	}
+	wake_lock_init(&tsif_device->wake_lock, WAKE_LOCK_SUSPEND,
+		       dev_name(&pdev->dev));
+	dev_info(&pdev->dev, "Configured irq %d memory 0x%08x DMA %d CRCI %d\n",
+		 tsif_device->irq, tsif_device->memres->start,
+		 tsif_device->dma, tsif_device->crci);
+	list_add(&tsif_device->devlist, &tsif_devices);
+	return 0;
+/* error path */
+	sysfs_remove_group(&pdev->dev.kobj, &dev_attr_grp);
+err_attrs:
+	free_irq(tsif_device->irq, tsif_device);
+err_irq:
+	tsif_debugfs_exit(tsif_device);
+	tsif_stop_gpios(tsif_device);
+err_gpio:
+	iounmap(tsif_device->base);
+err_ioremap:
+err_rgn:
+	tsif_put_clocks(tsif_device);
+err_clocks:
+	kfree(tsif_device);
+out:
+	return rc;
+}
+
+static int __devexit msm_tsif_remove(struct platform_device *pdev)
+{
+	struct msm_tsif_device *tsif_device = platform_get_drvdata(pdev);
+	dev_info(&pdev->dev, "Unload\n");
+	list_del(&tsif_device->devlist);
+	wake_lock_destroy(&tsif_device->wake_lock);
+	sysfs_remove_group(&pdev->dev.kobj, &dev_attr_grp);
+	free_irq(tsif_device->irq, tsif_device);
+	tsif_debugfs_exit(tsif_device);
+	tsif_dma_exit(tsif_device);
+	tsif_stop_gpios(tsif_device);
+	iounmap(tsif_device->base);
+	tsif_put_clocks(tsif_device);
+
+	pm_runtime_put(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+	kfree(tsif_device);
+	return 0;
+}
+
+static int tsif_runtime_suspend(struct device *dev)
+{
+	dev_dbg(dev, "pm_runtime: suspending...\n");
+	return 0;
+}
+
+static int tsif_runtime_resume(struct device *dev)
+{
+	dev_dbg(dev, "pm_runtime: resuming...\n");
+	return 0;
+}
+
+static const struct dev_pm_ops tsif_dev_pm_ops = {
+	.runtime_suspend = tsif_runtime_suspend,
+	.runtime_resume = tsif_runtime_resume,
+};
+
+
+static struct platform_driver msm_tsif_driver = {
+	.probe          = msm_tsif_probe,
+	.remove         = __exit_p(msm_tsif_remove),
+	.driver         = {
+		.name   = "msm_tsif",
+		.pm     = &tsif_dev_pm_ops,
+	},
+};
+
+static int __init mod_init(void)
+{
+	int rc = platform_driver_register(&msm_tsif_driver);
+	if (rc)
+		pr_err("TSIF: platform_driver_register failed: %d\n", rc);
+	return rc;
+}
+
+static void __exit mod_exit(void)
+{
+	platform_driver_unregister(&msm_tsif_driver);
+}
+/* ===module end=== */
+
+/* public API */
+
+void *tsif_attach(int id, void (*notify)(void *client_data), void *data)
+{
+	struct msm_tsif_device *tsif_device = tsif_find_by_id(id);
+	if (tsif_device->client_notify || tsif_device->client_data)
+		return ERR_PTR(-EBUSY);
+	tsif_device->client_notify = notify;
+	tsif_device->client_data = data;
+	/* prevent from unloading */
+	get_device(&tsif_device->pdev->dev);
+	return tsif_device;
+}
+EXPORT_SYMBOL(tsif_attach);
+
+void tsif_detach(void *cookie)
+{
+	struct msm_tsif_device *tsif_device = cookie;
+	tsif_device->client_notify = NULL;
+	tsif_device->client_data = NULL;
+	put_device(&tsif_device->pdev->dev);
+}
+EXPORT_SYMBOL(tsif_detach);
+
+void tsif_get_info(void *cookie, void **pdata, int *psize)
+{
+	struct msm_tsif_device *tsif_device = cookie;
+	if (pdata)
+		*pdata = tsif_device->data_buffer;
+	if (psize)
+		*psize = TSIF_PKTS_IN_BUF;
+}
+EXPORT_SYMBOL(tsif_get_info);
+
+int tsif_set_mode(void *cookie, int mode)
+{
+	struct msm_tsif_device *tsif_device = cookie;
+	if (tsif_device->state != tsif_state_stopped) {
+		dev_err(&tsif_device->pdev->dev,
+			"Can't change mode while device is active\n");
+		return -EBUSY;
+	}
+	switch (mode) {
+	case 1:
+	case 2:
+	case 3:
+		tsif_device->mode = mode;
+		break;
+	default:
+		dev_err(&tsif_device->pdev->dev, "Invalid mode: %d\n", mode);
+		return -EINVAL;
+	}
+	return 0;
+}
+EXPORT_SYMBOL(tsif_set_mode);
+
+int tsif_set_time_limit(void *cookie, u32 value)
+{
+	struct msm_tsif_device *tsif_device = cookie;
+	if (tsif_device->state != tsif_state_stopped) {
+		dev_err(&tsif_device->pdev->dev,
+			"Can't change time limit while device is active\n");
+		return -EBUSY;
+	}
+	if (value != (value & 0xFFFFFF)) {
+		dev_err(&tsif_device->pdev->dev,
+			"Invalid time limit (should be 24 bit): %#x\n", value);
+		return -EINVAL;
+	}
+	tsif_device->time_limit = value;
+	return 0;
+}
+EXPORT_SYMBOL(tsif_set_time_limit);
+
+int tsif_set_buf_config(void *cookie, u32 pkts_in_chunk, u32 chunks_in_buf)
+{
+	struct msm_tsif_device *tsif_device = cookie;
+	if (tsif_device->data_buffer) {
+		dev_err(&tsif_device->pdev->dev,
+			"Data buffer already allocated: %p\n",
+			tsif_device->data_buffer);
+		return -EBUSY;
+	}
+	/* check for crazy user */
+	if (pkts_in_chunk * chunks_in_buf > 10240) {
+		dev_err(&tsif_device->pdev->dev,
+			"Buffer requested is too large: %d * %d\n",
+			pkts_in_chunk,
+			chunks_in_buf);
+		return -EINVAL;
+	}
+	/* parameters are OK, execute */
+	tsif_device->pkts_per_chunk = pkts_in_chunk;
+	tsif_device->chunks_per_buf = chunks_in_buf;
+	return 0;
+}
+EXPORT_SYMBOL(tsif_set_buf_config);
+
+void tsif_get_state(void *cookie, int *ri, int *wi, enum tsif_state *state)
+{
+	struct msm_tsif_device *tsif_device = cookie;
+	if (ri)
+		*ri    = tsif_device->ri;
+	if (wi)
+		*wi    = tsif_device->wi;
+	if (state)
+		*state = tsif_device->state;
+}
+EXPORT_SYMBOL(tsif_get_state);
+
+int tsif_start(void *cookie)
+{
+	struct msm_tsif_device *tsif_device = cookie;
+	return action_open(tsif_device);
+}
+EXPORT_SYMBOL(tsif_start);
+
+void tsif_stop(void *cookie)
+{
+	struct msm_tsif_device *tsif_device = cookie;
+	action_close(tsif_device);
+}
+EXPORT_SYMBOL(tsif_stop);
+
+void tsif_reclaim_packets(void *cookie, int read_index)
+{
+	struct msm_tsif_device *tsif_device = cookie;
+	tsif_device->ri = read_index;
+}
+EXPORT_SYMBOL(tsif_reclaim_packets);
+
+module_init(mod_init);
+module_exit(mod_exit);
+
+MODULE_DESCRIPTION("TSIF (Transport Stream Interface)"
+		   " Driver for the MSM chipset");
+MODULE_LICENSE("GPL v2");
+