1798 lines
49 KiB
C
1798 lines
49 KiB
C
|
/*
|
||
|
* 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 <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 <linux/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 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");
|
||
|
|