/* * TSIF Driver * * Copyright (c) 2012-2013, The Linux Foundation. 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 /* Needed by all modules */ #include /* Needed for KERN_INFO */ #include /* Needed for the macros */ #include /* IS_ERR etc. */ #include #include /* XXX_mem_region */ #include #include /* dma_XXX */ #include /* msleep */ #include /* ioXXX */ #include /* copy_from_user */ #include #include #include #include #include /* kfree, kzalloc */ #include #include #include /* * 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 TSIF_MAX_ID 1 #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; int clock_inverse; int data_inverse; int sync_inverse; int enable_inverse; 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; struct tasklet_struct clocks_off; /* 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(&tsif_device->pdev->dev, pdata->tsif_clk); if (IS_ERR(tsif_device->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(&tsif_device->pdev->dev, pdata->tsif_pclk); if (IS_ERR(tsif_device->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(&tsif_device->pdev->dev, pdata->tsif_ref_clk); if (IS_ERR(tsif_device->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_prepare_enable(tsif_device->tsif_clk); if (tsif_device->tsif_pclk) clk_prepare_enable(tsif_device->tsif_pclk); clk_prepare_enable(tsif_device->tsif_ref_clk); } else { if (tsif_device->tsif_clk) clk_disable_unprepare(tsif_device->tsif_clk); if (tsif_device->tsif_pclk) clk_disable_unprepare(tsif_device->tsif_pclk); clk_disable_unprepare(tsif_device->tsif_ref_clk); } } static void tsif_clocks_off(unsigned long data) { struct msm_tsif_device *tsif_device = (struct msm_tsif_device *) data; tsif_clock(tsif_device, 0); } /* ===clocks end=== */ /* ===gpio begin=== */ 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(GPIO_CFG(GPIO_PIN(g->gpio_cfg), 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), 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; rc = tsif_gpios_enable(table, size); return rc; } static void tsif_gpios_disable_free(const struct msm_gpio *table, int size) { tsif_gpios_disable(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; if (tsif_device->clock_inverse) ctl |= TSIF_STS_CTL_INV_CLOCK; if (tsif_device->data_inverse) ctl |= TSIF_STS_CTL_INV_DATA; if (tsif_device->sync_inverse) ctl |= TSIF_STS_CTL_INV_SYNC; if (tsif_device->enable_inverse) ctl |= TSIF_STS_CTL_INV_ENABLE; 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); /* * This branch is taken only in case of * severe hardware problem (I don't even know * what should happen for DMOV_RSLT_ERROR); * thus I prefer code simplicity over * performance. * Clocks are turned off from outside the * interrupt context. */ tasklet_schedule(&tsif_device->clocks_off); 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, 1); usleep(10000); } } 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, 1); 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" "Clock inv = %d\n" "Data inv = %d\n" "Sync inv = %d\n" "Enable inv = %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->clock_inverse, tsif_device->data_inverse, tsif_device->sync_inverse, tsif_device->enable_inverse, 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 ssize_t show_clk_inverse(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->clock_inverse); } static ssize_t set_clk_inverse(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_clk_inverse(tsif_device, value); if (!rc) rc = count; return rc; } static DEVICE_ATTR(clk_inverse, S_IRUGO | S_IWUSR, show_clk_inverse, set_clk_inverse); static ssize_t show_data_inverse(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->data_inverse); } static ssize_t set_data_inverse(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_data_inverse(tsif_device, value); if (!rc) rc = count; return rc; } static DEVICE_ATTR(data_inverse, S_IRUGO | S_IWUSR, show_data_inverse, set_data_inverse); static ssize_t show_sync_inverse(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->sync_inverse); } static ssize_t set_sync_inverse(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_sync_inverse(tsif_device, value); if (!rc) rc = count; return rc; } static DEVICE_ATTR(sync_inverse, S_IRUGO | S_IWUSR, show_sync_inverse, set_sync_inverse); static ssize_t show_enable_inverse(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->enable_inverse); } static ssize_t set_enable_inverse(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_enable_inverse(tsif_device, value); if (!rc) rc = count; return rc; } static DEVICE_ATTR(enable_inverse, S_IRUGO | S_IWUSR, show_enable_inverse, set_enable_inverse); static struct attribute *dev_attrs[] = { &dev_attr_stats.attr, &dev_attr_mode.attr, &dev_attr_time_limit.attr, &dev_attr_buf_config.attr, &dev_attr_clk_inverse.attr, &dev_attr_data_inverse.attr, &dev_attr_sync_inverse.attr, &dev_attr_enable_inverse.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); disable_irq(tsif_device->irq); return rc; } /* make sure the GPIO's are set up */ rc = tsif_start_gpios(tsif_device); if (rc) { dev_err(&tsif_device->pdev->dev, "failed to start GPIOs\n"); tsif_stop_hw(tsif_device); tsif_dma_exit(tsif_device); tsif_clock(tsif_device, 0); disable_irq(tsif_device->irq); 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); tsif_stop_gpios(tsif_device); tsif_stop_hw(tsif_device); tsif_dma_exit(tsif_device); tsif_clock(tsif_device, 0); disable_irq(tsif_device->irq); return result; } wake_lock(&tsif_device->wake_lock); return 0; } 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); /* turn off the GPIO's to prevent new data from entering */ tsif_stop_gpios(tsif_device); /* we unfortunately must sleep here to give the ADM time to * complete any outstanding reads after the GPIO's are turned * off. There is no indication from the ADM hardware that * there are any outstanding reads on the bus, and if we * stop the TSIF too quickly, it can cause a bus error. */ msleep(250); /* now we can stop the core */ 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; } if ((pdev->id < 0) || (pdev->id > TSIF_MAX_ID)) { 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->clock_inverse = 0; tsif_device->data_inverse = 0; tsif_device->sync_inverse = 0; tsif_device->enable_inverse = 0; 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); tasklet_init(&tsif_device->clocks_off, tsif_clocks_off, (unsigned long)tsif_device); rc = tsif_get_clocks(tsif_device); if (rc) 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); 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); 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 */ int tsif_get_active(void) { struct msm_tsif_device *tsif_device; list_for_each_entry(tsif_device, &tsif_devices, devlist) { return tsif_device->pdev->id; } return -ENODEV; } EXPORT_SYMBOL(tsif_get_active); 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) return ERR_PTR(-ENODEV); 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); int tsif_set_clk_inverse(void *cookie, int value) { struct msm_tsif_device *tsif_device = cookie; if (tsif_device->state != tsif_state_stopped) { dev_err(&tsif_device->pdev->dev, "Can't change clock inverse while device is active\n"); return -EBUSY; } if ((value != 0) && (value != 1)) { dev_err(&tsif_device->pdev->dev, "Invalid parameter, either 0 or 1: %#x\n", value); return -EINVAL; } tsif_device->clock_inverse = value; return 0; } EXPORT_SYMBOL(tsif_set_clk_inverse); int tsif_set_data_inverse(void *cookie, int value) { struct msm_tsif_device *tsif_device = cookie; if (tsif_device->state != tsif_state_stopped) { dev_err(&tsif_device->pdev->dev, "Can't change data inverse while device is active\n"); return -EBUSY; } if ((value != 0) && (value != 1)) { dev_err(&tsif_device->pdev->dev, "Invalid parameter, either 0 or 1: %#x\n", value); return -EINVAL; } tsif_device->data_inverse = value; return 0; } EXPORT_SYMBOL(tsif_set_data_inverse); int tsif_set_sync_inverse(void *cookie, int value) { struct msm_tsif_device *tsif_device = cookie; if (tsif_device->state != tsif_state_stopped) { dev_err(&tsif_device->pdev->dev, "Can't change sync inverse while device is active\n"); return -EBUSY; } if ((value != 0) && (value != 1)) { dev_err(&tsif_device->pdev->dev, "Invalid parameter, either 0 or 1: %#x\n", value); return -EINVAL; } tsif_device->sync_inverse = value; return 0; } EXPORT_SYMBOL(tsif_set_sync_inverse); int tsif_set_enable_inverse(void *cookie, int value) { struct msm_tsif_device *tsif_device = cookie; if (tsif_device->state != tsif_state_stopped) { dev_err(&tsif_device->pdev->dev, "Can't change enable inverse while device is active\n"); return -EBUSY; } if ((value != 0) && (value != 1)) { dev_err(&tsif_device->pdev->dev, "Invalid parameter, either 0 or 1: %#x\n", value); return -EINVAL; } tsif_device->enable_inverse = value; return 0; } EXPORT_SYMBOL(tsif_set_enable_inverse); 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); int tsif_get_ref_clk_counter(void *cookie, u32 *tcr_counter) { struct msm_tsif_device *tsif_device = cookie; if (!tsif_device || !tcr_counter) return -EINVAL; if (tsif_device->state == tsif_state_running) *tcr_counter = ioread32(tsif_device->base + TSIF_CLK_REF_OFF); else *tcr_counter = 0; return 0; } EXPORT_SYMBOL(tsif_get_ref_clk_counter); 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");