| /******************************************************************************* |
| Copyright (C) Marvell International Ltd. and its affiliates |
| |
| This software file (the "File") is owned and distributed by Marvell |
| International Ltd. and/or its affiliates ("Marvell") under the following |
| alternative licensing terms. Once you have made an election to distribute the |
| File under one of the following license alternatives, please (i) delete this |
| introductory statement regarding license alternatives, (ii) delete the two |
| license alternatives that you have not elected to use and (iii) preserve the |
| Marvell copyright notice above. |
| |
| |
| ******************************************************************************** |
| Marvell GPL License Option |
| |
| If you received this File from Marvell, you may opt to use, redistribute and/or |
| modify this File in accordance with the terms and conditions of the General |
| Public License Version 2, June 1991 (the "GPL License"), a copy of which is |
| available along with the File in the license.txt file or by writing to the Free |
| Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 or |
| on the worldwide web at http://www.gnu.org/licenses/gpl.txt. |
| |
| THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE IMPLIED |
| WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE ARE EXPRESSLY |
| DISCLAIMED. The GPL License provides additional details about this warranty |
| disclaimer. |
| *******************************************************************************/ |
| |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/kernel.h> |
| #include <linux/errno.h> |
| #include <linux/types.h> |
| #include <linux/proc_fs.h> |
| #include <linux/cdev.h> |
| #include <linux/version.h> |
| |
| #include "mvCommon.h" |
| #include "mvOs.h" |
| #include "ctrlEnv/mvCtrlEnvLib.h" |
| #include "ts/mvTsu.h" |
| #include "ts/mvTsuRegs.h" |
| #include "mvSysTsApi.h" |
| #include "mv_tsu_ioctl.h" |
| #include "mv_tsu.h" |
| |
| /* |
| * Local Macros and Definiions. |
| */ |
| #undef TSU_DEBUG |
| //#define TSU_DEBUG |
| #define TSU_DBG_OFF 0x00 |
| #define TSU_DBG_INIT 0x01 |
| #define TSU_DBG_OPEN 0x02 |
| #define TSU_DBG_RELEASE 0x04 |
| #define TSU_DBG_READ 0x08 |
| #define TSU_DBG_WRITE 0x10 |
| #define TSU_DBG_IOCTL 0x20 |
| #define TSU_DBG_PROC 0x40 |
| #define TSU_DBG_INT 0x80 |
| #define TSU_DBG_ALL 0xFFFFFFFF |
| |
| #ifdef TSU_DEBUG |
| static u32 mvtsu_dbg = TSU_DBG_READ; |
| #define TSU_DPRINT(f, x) if((mvtsu_dbg & (f)) == (f)) printk x |
| #else |
| #define TSU_DPRINT(f, x) |
| #endif |
| #define TSU_ENTER(f, fname) TSU_DPRINT(f, ("Entering " fname "()\n")) |
| #define TSU_LEAVE(f, fname) TSU_DPRINT(f, ("Leaving " fname "()\n")) |
| |
| #undef TSU_UNCACHED_DATA_BUFFERS |
| |
| #define TSU_DATA_BUFF_HW_2_SW(buf) (((MV_U32)(buf)) & ~(TSU_DMA_ALIGN - 1)) |
| |
| #define TSU_IS_DEC_DIGIT(c) (((c) >= '0') && ((c) <= '9')) |
| |
| #define TSU_DEV_NAME "tsu" |
| #define TSU_NUM_DEVICES 2 |
| #ifdef CONFIG_TSU_SERIAL_IF |
| #define DEF_TSU_MODE TSU_MODE_SERIAL |
| #else |
| #define DEF_TSU_MODE TSU_MODE_PARALLEL |
| #endif |
| |
| #if defined(CONFIG_TSU_CORE_CLK_71MHZ) |
| #define DEF_TSU_CORE_CLOCK TSU_CORE_CLK_71_MHZ |
| #elif defined(CONFIG_TSU_CORE_CLK_83MHZ) |
| #define DEF_TSU_CORE_CLOCK TSU_CORE_CLK_83_MHZ |
| #elif defined(CONFIG_TSU_CORE_CLK_91MHZ) |
| #define DEF_TSU_CORE_CLOCK TSU_CORE_CLK_91_MHZ |
| #elif defined(CONFIG_TSU_CORE_CLK_100MHZ) |
| #define DEF_TSU_CORE_CLOCK TSU_CORE_CLK_100_MHZ |
| #endif |
| |
| #define TSU_MIN_READ_SIZE 8 |
| |
| /* |
| * Local data-structures. |
| */ |
| struct mvtsu_dev { |
| u8 port; |
| struct cdev cdev; |
| MV_TSU_BUFF_INFO buff_info; |
| MV_TSU_SIGNAL_CONFIG signal_cfg; |
| MV_U32 serial_sig_flags; |
| MV_U8 sync_loss; |
| MV_U8 sync_detect; |
| MV_TSU_PORT_DIRECTION port_dir; |
| u32 valid_data_size; /* Size of data in HW buffers. */ |
| u32 rd_rw_data_size; /* Size of buffer for read / write. */ |
| u8 *data_buff; |
| u32 *stat_buff; |
| u32 stat_buff_size; |
| u32 data_offs; |
| u32 buff_handle; |
| u8 read_all_at_once; |
| u32 clockrate; |
| u32 rd_wr_timeout; |
| u32 tx_tms_gap; |
| u32 tx_tms_val; |
| u8 auto_tms_mode; |
| spinlock_t lock; |
| struct tsu_stat int_stat; |
| }; |
| |
| |
| /* |
| * Local control variables. |
| */ |
| static MV_TSU_PORTS_MODE cfg_tsu_mode; |
| static MV_TSU_CORE_CLOCK cfg_core_clk; |
| static int cfg_pkt_size; |
| static struct mvtsu_dev mvtsu_devs[TSU_NUM_DEVICES]; |
| static dev_t mvtsu_device; |
| char *mvtsu_cmdline; |
| |
| int mvtsu_cmdline_config(char *s); |
| static int mvtsu_parse_cmdline(void); |
| __setup("mv_tsu_config=", mvtsu_cmdline_config); |
| |
| #ifdef CONFIG_MV_TSU_PROC |
| static struct proc_dir_entry *mvtsu_proc_entry; |
| |
| static int mvtsu_proc_init(void); |
| #endif /* CONFIG_MV_TSU_PROC */ |
| |
| #ifdef TSU_DEBUG |
| static void mvtsu_dump_regs(int port) |
| { |
| int i; |
| |
| for(i = 0x0; i <= 0x68; i+=4) { |
| printk("Reg 0x%08x --> 0x%08x.\n",TSU_REG_BASE(port) + i, |
| MV_REG_READ(TSU_REG_BASE(port) + i)); |
| } |
| return; |
| } |
| #endif /* TSU_DEBUG */ |
| |
| /* |
| * Calculate the timestamp gap between two packets. |
| */ |
| static void mvtsu_tx_timestamp_calc(struct mvtsu_dev *dev) |
| { |
| u32 base_clock = 4166660; |
| |
| dev->tx_tms_gap = (base_clock * cfg_pkt_size) / (dev->clockrate / 10); |
| dev->tx_tms_val = 0; |
| return; |
| } |
| |
| |
| /* |
| * Calculate the timeout needed between packets. |
| */ |
| static void mvtsu_rd_wr_timeout_calc(struct mvtsu_dev *dev) |
| { |
| dev->rd_wr_timeout = 0; |
| if(dev->clockrate >= 1000) { |
| /* How much time (in milisec) is needed for a single */ |
| /* block of data. */ |
| dev->rd_wr_timeout = ((dev->valid_data_size * 8) / |
| (dev->clockrate / 1000)); |
| } |
| /* Double the timeout to be on the safe side. */ |
| dev->rd_wr_timeout *= 2; |
| if(dev->rd_wr_timeout < 50) |
| dev->rd_wr_timeout = 50; |
| return; |
| } |
| |
| |
| static void mvtsu_set_defaults(struct mvtsu_dev *dev) |
| { |
| TSU_ENTER(TSU_DBG_INIT, "mvtsu_set_defaults"); |
| |
| /* Set buffer parameters. */ |
| dev->buff_info.aggrMode = TSU_DFLT_AGGR_MODE; |
| dev->buff_info.aggrMode2TmstmpOff = TSU_DFLT_TMSTMP_OFFS; |
| dev->buff_info.aggrNumPackets = TSU_DFLT_AGGR_PCKT_NUM; |
| dev->buff_info.numTsDesc = TSU_DFLT_TS_DESC_NUM; |
| dev->buff_info.numDoneQEntry = TSU_DFLT_TS_DONEQ_NUM; |
| dev->buff_info.tsDataBuff = NULL; |
| dev->buff_info.tsDoneBuff = NULL; |
| dev->buff_info.dataBlockSize = 0; |
| |
| /* Set signal config. */ |
| dev->signal_cfg.tsDataEdge = TSU_SIGNAL_EDGE_KEEP_DEF; |
| dev->signal_cfg.tsError = TSU_SIGNAL_KEEP_DEF; |
| dev->signal_cfg.tsSync = TSU_SIGNAL_KEEP_DEF; |
| dev->signal_cfg.tsValid = TSU_SIGNAL_KEEP_DEF; |
| dev->serial_sig_flags = 0; |
| |
| /* TS Sync detection parameters. */ |
| mvTsuRxSyncDetectionGet(dev->port,&dev->sync_detect, &dev->sync_loss); |
| |
| dev->read_all_at_once = TSU_DFLT_DATA_READ_AT_ONCE; |
| dev->clockrate = TSU_DFLT_CLOCK_RATE; |
| |
| TSU_LEAVE(TSU_DBG_INIT, "mvtsu_set_defaults"); |
| return; |
| } |
| |
| static irqreturn_t mvtsu_interrupt_handler(int irq , void *arg) |
| { |
| struct mvtsu_dev* dev = (struct mvtsu_dev*)arg; |
| u32 cause; |
| |
| TSU_ENTER(TSU_DBG_INT, "mvtsu_interrupt_handler"); |
| |
| cause = MV_REG_READ(MV_TSU_INTERRUPT_SRC_REG(dev->port)); |
| TSU_DPRINT(TSU_DBG_INT, ("\tPort %d, Cause = 0x%08x.\n",dev->port,cause)); |
| |
| if(cause & TSU_INT_TS_IF_ERROR) |
| dev->int_stat.ts_if_err++; |
| if(cause & TSU_INT_FIFO_OVFL_ERROR) |
| dev->int_stat.fifo_ovfl++; |
| if(cause & TSU_INT_TS_CONN_ERROR) |
| dev->int_stat.ts_conn_err++; |
| if(cause & TSU_INT_CLOCK_SYNC_EXP) |
| dev->int_stat.clk_sync_exp++; |
| |
| TSU_LEAVE(TSU_DBG_INT, "mvtsu_interrupt_handler"); |
| return IRQ_HANDLED; |
| } |
| |
| int mvtsu_open (struct inode *inode, struct file *filp) |
| { |
| struct mvtsu_dev *dev; |
| MV_TSU_PORT_CONFIG port_cfg; |
| MV_TSU_BUFF_INFO *binfo = NULL; |
| MV_STATUS status; |
| int result = 0; |
| int stat_size = 0; |
| int data_size = 0; |
| int data_buff_offs; |
| |
| TSU_ENTER(TSU_DBG_OPEN, "mvtsu_open"); |
| |
| if(MV_FALSE == mvCtrlPwrClckGet(TS_UNIT_ID, 0)) { |
| printk("Transport Stream interface is powered off.\n"); |
| return 0; |
| } |
| |
| /* Find the device structure. */ |
| dev = container_of(inode->i_cdev, struct mvtsu_dev, cdev); |
| |
| /* Determine the port direction according to the read / write flag.*/ |
| if ((filp->f_mode & (FMODE_WRITE | FMODE_READ)) == FMODE_WRITE) { |
| port_cfg.portDir = TSU_PORT_OUTPUT; |
| } else if ((filp->f_mode & (FMODE_WRITE | FMODE_READ)) == FMODE_READ) { |
| port_cfg.portDir = TSU_PORT_INPUT; |
| } else { |
| result = -EINVAL; |
| goto fail_init; |
| } |
| |
| /* Reset the port. */ |
| mvTsuPortReset(dev->port); |
| |
| /* Initialize the port. */ |
| port_cfg.pktSize = cfg_pkt_size; |
| status = mvTsuPortInit(dev->port,&port_cfg); |
| if(status != MV_OK) { |
| result = -EINVAL; |
| goto fail_init; |
| } |
| |
| TSU_DPRINT(TSU_DBG_OPEN, ("\tTSU unit initialized successfully.\n")); |
| |
| /* Initialize the port buffers. */ |
| binfo = &dev->buff_info; |
| switch(binfo->aggrMode) |
| { |
| case (MV_TSU_AGGR_MODE_DISABLED): |
| binfo->dataBlockSize = port_cfg.pktSize; |
| dev->valid_data_size = port_cfg.pktSize; |
| dev->rd_rw_data_size = binfo->dataBlockSize + TSU_DONE_STATUS_ENTRY_SIZE; |
| binfo->aggrNumPackets = 1; |
| break; |
| case (MV_TSU_AGGR_MODE_1): |
| binfo->dataBlockSize = port_cfg.pktSize * binfo->aggrNumPackets; |
| if(port_cfg.portDir == TSU_PORT_OUTPUT) { |
| binfo->dataBlockSize += MV_MAX(TSU_DMA_ALIGN,TSU_MODE1_OUT_TMS_SIZE); |
| dev->rd_rw_data_size = binfo->dataBlockSize; |
| } |
| else { |
| dev->rd_rw_data_size = binfo->dataBlockSize + |
| (binfo->aggrNumPackets * TSU_DONE_STATUS_ENTRY_SIZE); |
| } |
| dev->valid_data_size = port_cfg.pktSize * binfo->aggrNumPackets; |
| break; |
| case (MV_TSU_AGGR_MODE_2): |
| binfo->aggrMode2TmstmpOff = TSU_DMA_ALIGN - |
| (port_cfg.pktSize & (TSU_DMA_ALIGN - 1)); |
| binfo->dataBlockSize = |
| (binfo->aggrNumPackets * |
| (port_cfg.pktSize + binfo->aggrMode2TmstmpOff) + |
| TSU_DMA_ALIGN); |
| dev->valid_data_size = (binfo->aggrNumPackets * |
| (port_cfg.pktSize + binfo->aggrMode2TmstmpOff)); |
| dev->rd_rw_data_size = dev->valid_data_size; |
| default: |
| break; |
| } |
| |
| dev->port_dir = port_cfg.portDir; |
| |
| /* Align the data block size to a cache line. */ |
| binfo->dataBlockSize = MV_ALIGN_UP(binfo->dataBlockSize,32); |
| data_size = binfo->dataBlockSize * binfo->numTsDesc; |
| #ifdef TSU_UNCACHED_DATA_BUFFERS |
| binfo->tsDataBuff = |
| mvOsIoUncachedMalloc(NULL,data_size,(MV_ULONG*)(&binfo->tsDataBuffPhys), |
| NULL); |
| #else |
| binfo->tsDataBuff = |
| mvOsIoCachedMalloc(NULL,data_size,(MV_ULONG*)(&binfo->tsDataBuffPhys), |
| NULL); |
| #endif /* TSU_UNCACHED_DATA_BUFFERS */ |
| if(binfo->tsDataBuff == NULL) { |
| result = -ENOMEM; |
| goto fail_init; |
| } |
| // memset(binfo->tsDataBuff,0x88,data_size); |
| #ifndef TSU_UNCACHED_DATA_BUFFERS |
| mvOsCacheClear(NULL,(MV_U32*)TSU_DATA_BUFF_HW_2_SW(binfo->tsDataBuff), |
| data_size); |
| #endif /* TSU_UNCACHED_DATA_BUFFERS */ |
| |
| /* Align tsDataBuff according to the HW limitation. */ |
| if(binfo->aggrMode == MV_TSU_AGGR_MODE_2) { |
| data_buff_offs = port_cfg.pktSize & (TSU_DMA_ALIGN - 1); |
| } |
| else if((binfo->aggrMode == MV_TSU_AGGR_MODE_1) && |
| (port_cfg.portDir == TSU_PORT_OUTPUT)) { |
| data_buff_offs = TSU_DMA_ALIGN - TSU_MODE1_OUT_TMS_SIZE; |
| } |
| else { |
| data_buff_offs = 0; |
| } |
| |
| binfo->tsDataBuff = (MV_U32*)((MV_U32)binfo->tsDataBuff + data_buff_offs); |
| binfo->tsDataBuffPhys += data_buff_offs; |
| |
| TSU_DPRINT(TSU_DBG_OPEN, ("\tTSU Data buffer allocated successfully " |
| "(%p, %d).\n",binfo->tsDataBuff, data_size)); |
| /* Allocate memory for done queue. */ |
| stat_size = TSU_DONE_STATUS_ENTRY_SIZE * binfo->numDoneQEntry; |
| dev->stat_buff_size = stat_size; |
| binfo->tsDoneBuff = |
| mvOsIoUncachedMalloc(NULL,stat_size, |
| (MV_ULONG*)(&binfo->tsDoneBuffPhys),NULL); |
| if(binfo->tsDoneBuff == NULL) { |
| result = -ENOMEM; |
| goto fail_init; |
| } |
| |
| TSU_DPRINT(TSU_DBG_OPEN, ("\tTSU Done buffer allocated successfully" |
| "(%p, %d).\n",binfo->tsDoneBuff, stat_size)); |
| |
| status = mvTsuBuffersInit(dev->port,&dev->buff_info); |
| if(status != MV_OK) { |
| TSU_DPRINT(TSU_DBG_OPEN, ("\tmvTsuBuffersInit() Failed (%d).", |
| status)); |
| result = -EINVAL; |
| goto fail_init; |
| } |
| TSU_DPRINT(TSU_DBG_OPEN, ("\tHAL Buffers initialized successfully.\n")); |
| |
| status = mvTsuPortSignalCfgSet(dev->port,&(dev->signal_cfg),dev->serial_sig_flags); |
| if(status != MV_OK) { |
| TSU_DPRINT(TSU_DBG_OPEN, ("\tmvTsuPortSignalCfgSet() Failed (%d).", |
| status)); |
| result = -EINVAL; |
| goto fail_init; |
| } |
| TSU_DPRINT(TSU_DBG_OPEN, ("\tPort signal parameters set successfully.\n")); |
| |
| status = mvTsuRxSyncDetectionSet(dev->port,dev->sync_detect,dev->sync_loss); |
| if(status != MV_OK) { |
| TSU_DPRINT(TSU_DBG_OPEN, ("\tmvTsuRxSyncDetectionSet() Failed (%d).", |
| status)); |
| result = -EINVAL; |
| goto fail_init; |
| } |
| TSU_DPRINT(TSU_DBG_OPEN, ("\tRx sync parameters set successfully.\n")); |
| |
| mvtsu_rd_wr_timeout_calc(dev); |
| |
| if(dev->port_dir == TSU_PORT_OUTPUT) { |
| mvtsu_tx_timestamp_calc(dev); |
| } |
| |
| /* Register IRQ. */ |
| MV_REG_WRITE(MV_TSU_INTERRUPT_MASK_REG(dev->port),TSU_DFLT_INT_MASK); |
| if(request_irq(IRQ_TS_INT(dev->port),mvtsu_interrupt_handler, |
| IRQF_DISABLED | IRQF_SAMPLE_RANDOM,"tsu",dev)) { |
| printk(KERN_ERR "Cannot assign irq%d to TSU port%d\n", |
| IRQ_TS_INT(dev->port), dev->port); |
| goto fail_init; |
| } |
| TSU_DPRINT(TSU_DBG_OPEN, ("\tTSU interrupt registered at IRQ %d.\n", |
| IRQ_TS_INT(dev->port))); |
| |
| if(port_cfg.portDir == TSU_PORT_INPUT) { |
| /* Enable Rx timestamp. */ |
| mvTsuRxTimestampCntEn(dev->port,MV_TRUE); |
| mvTsuDmaWatermarkSet(dev->port,0x8); |
| } |
| |
| /* Make the private_data hold the pointer to the device data. */ |
| filp->private_data = dev; |
| |
| TSU_LEAVE(TSU_DBG_OPEN, "mvtsu_open"); |
| return 0; |
| |
| fail_init: |
| if(binfo != NULL) { |
| if(binfo->tsDataBuff != NULL) |
| #ifdef TSU_UNCACHED_DATA_BUFFERS |
| mvOsIoUncachedFree( |
| NULL,data_size, |
| TSU_DATA_BUFF_HW_2_SW(binfo->tsDataBuffPhys), |
| (MV_U32*)TSU_DATA_BUFF_HW_2_SW(binfo->tsDataBuff),0); |
| #else |
| mvOsIoCachedFree( |
| NULL,data_size, |
| TSU_DATA_BUFF_HW_2_SW(binfo->tsDataBuffPhys), |
| (MV_U32*)TSU_DATA_BUFF_HW_2_SW(binfo->tsDataBuff),0); |
| #endif /* TSU_UNCACHED_DATA_BUFFERS */ |
| if(binfo->tsDoneBuff != NULL) |
| mvOsIoUncachedFree(NULL,stat_size,binfo->tsDoneBuffPhys, |
| binfo->tsDoneBuff,0); |
| binfo->tsDataBuff = NULL; |
| binfo->tsDoneBuff = NULL; |
| } |
| TSU_LEAVE(TSU_DBG_OPEN, "mvtsu_open"); |
| return result; |
| } |
| |
| |
| int mvtsu_release (struct inode *inode, struct file *filp) |
| { |
| struct mvtsu_dev *dev = (struct mvtsu_dev*)filp->private_data; |
| MV_TSU_BUFF_INFO *binfo; |
| int size; |
| |
| TSU_ENTER(TSU_DBG_RELEASE, "mvtsu_release"); |
| |
| free_irq(IRQ_TS_INT(dev->port),dev); |
| |
| if(dev->port_dir == TSU_PORT_INPUT) { |
| /* Stop Rx timestamp. */ |
| mvTsuRxTimestampCntEn(dev->port,MV_FALSE); |
| } |
| |
| /* Shutdown the port. */ |
| mvTsuPortShutdown(dev->port); |
| |
| /* Clear interrupt mask. */ |
| MV_REG_WRITE(MV_TSU_INTERRUPT_MASK_REG(dev->port),0); |
| |
| /* Free previously allocated buffers. */ |
| binfo = &dev->buff_info; |
| if(binfo->tsDataBuff != NULL) { |
| size = binfo->dataBlockSize * binfo->numTsDesc; |
| #ifdef TSU_UNCACHED_DATA_BUFFERS |
| mvOsIoUncachedFree(NULL,size, |
| TSU_DATA_BUFF_HW_2_SW(binfo->tsDataBuffPhys), |
| (MV_U32*)TSU_DATA_BUFF_HW_2_SW(binfo->tsDataBuff),0); |
| #else |
| mvOsIoCachedFree(NULL,size, |
| TSU_DATA_BUFF_HW_2_SW(binfo->tsDataBuffPhys), |
| (MV_U32*)TSU_DATA_BUFF_HW_2_SW(binfo->tsDataBuff),0); |
| #endif /* TSU_UNCACHED_DATA_BUFFERS */ |
| } |
| if(binfo->tsDoneBuff != NULL) { |
| mvOsIoUncachedFree(NULL,dev->stat_buff_size,binfo->tsDoneBuffPhys, |
| binfo->tsDoneBuff,0); |
| } |
| binfo->tsDataBuff = NULL; |
| binfo->tsDoneBuff = NULL; |
| |
| mvtsu_set_defaults(dev); |
| |
| TSU_LEAVE(TSU_DBG_RELEASE, "mvtsu_release"); |
| return 0; |
| } |
| |
| |
| /* |
| * Helper function for retrying read buffer requests. |
| * Assume that the device spinlock is held. |
| */ |
| static int mvtsu_next_rx_buff_get(struct file *filp, u32 **data_buff, |
| u32 **stat_buff, u32 *buff_handle, |
| unsigned long *flags) |
| { |
| struct mvtsu_dev *dev = (struct mvtsu_dev*)filp->private_data; |
| int timeout = 0; |
| int cnt = 0; |
| MV_STATUS status; |
| |
| // timeout = (dev->rd_wr_timeout == 0) ? 2000 : (dev->rd_wr_timeout + 100); |
| timeout = dev->rd_wr_timeout; |
| |
| while(cnt < timeout) { |
| status = mvTsuRxNextBuffGet(dev->port,data_buff,stat_buff, |
| buff_handle); |
| if(status != MV_OK) { |
| if(status != MV_NO_MORE) |
| return -EIO; |
| } |
| else { |
| #if !defined(TSU_UNCACHED_DATA_BUFFERS) && defined(CONFIG_MV_SP_I_FTCH_DB_INV) |
| dma_unmap_single(NULL, mvOsIoVirtToPhy(NULL, *data_buff) , |
| dev->buff_info.dataBlockSize, DMA_FROM_DEVICE); |
| #endif |
| break; |
| } |
| if (filp->f_flags & O_NONBLOCK) |
| break; |
| |
| spin_unlock_irqrestore(&(dev->lock), *flags); |
| if(dev->rd_wr_timeout) |
| msleep_interruptible(1); |
| else |
| udelay(1000); |
| |
| cnt += 1; |
| spin_lock_irqsave(&(dev->lock), *flags); |
| } |
| if(cnt >= timeout) { |
| printk(KERN_INFO "TSU: Read timeout.\n"); |
| return -EAGAIN; |
| } |
| return 0; |
| } |
| |
| |
| /* |
| * Helper function for copying timestamp info to user buffer. |
| */ |
| inline static void mvtsu_rx_tmsstmp_copy(struct mvtsu_dev *dev, u32 *stat_buff, |
| char *out_buff, int *buf_offs, |
| int *buf_size) |
| { |
| MV_U32 size; |
| MV_U32 all; |
| MV_U32 avail; |
| |
| all = TSU_DONE_STATUS_ENTRY_SIZE * dev->buff_info.aggrNumPackets; |
| /* Calculate avilable data at end of buffer. */ |
| avail = dev->stat_buff_size - |
| ((MV_U32)stat_buff - (MV_U32)dev->buff_info.tsDoneBuff); |
| if(avail < all) |
| size = avail; |
| else |
| size = all; |
| |
| if(copy_to_user(out_buff,stat_buff,size)) |
| panic("TSU: copy_to_user failed."); |
| avail = all - size; |
| if(avail != 0) { |
| if(copy_to_user(out_buff + size,dev->buff_info.tsDoneBuff,avail)) |
| panic("TSU: copy_to_user failed."); |
| } |
| |
| *buf_offs += all; |
| *buf_size -= all; |
| return; |
| } |
| |
| /* |
| * TSU data read |
| */ |
| ssize_t mvtsu_read(struct file *filp, char __user *buf, size_t count, |
| loff_t *f_pos) |
| { |
| struct mvtsu_dev *dev = filp->private_data; |
| MV_U8 port = dev->port; |
| MV_U8 *data_buff; |
| MV_U32 size; |
| int status; |
| int buf_offs = 0; |
| unsigned long flags; |
| |
| TSU_ENTER(TSU_DBG_READ, "mvtsu_read"); |
| spin_lock_irqsave(&(dev->lock), flags); |
| |
| if((dev->buff_info.aggrMode != MV_TSU_AGGR_MODE_2) && |
| (count < (TSU_DONE_STATUS_ENTRY_SIZE * dev->buff_info.aggrNumPackets))) { |
| printk(KERN_ERR "TSU: Read operations must be at least of size " |
| "%d", |
| TSU_DONE_STATUS_ENTRY_SIZE * dev->buff_info.aggrNumPackets); |
| status = -EINVAL; |
| goto no_data; |
| } |
| |
| if(dev->data_buff == NULL) { |
| TSU_DPRINT(TSU_DBG_READ, ("\tGet new data buffer...")); |
| status = mvtsu_next_rx_buff_get(filp,(MV_U32**)(&dev->data_buff), |
| &dev->stat_buff, |
| &dev->buff_handle,&flags); |
| if(status) { |
| TSU_DPRINT(TSU_DBG_READ, ("FAILED.\n")); |
| goto no_data; |
| } |
| |
| TSU_DPRINT(TSU_DBG_READ, ("OK.\n")); |
| if(dev->buff_info.aggrMode != MV_TSU_AGGR_MODE_2) |
| /* Copy the timestamp before the packet's data. */ |
| mvtsu_rx_tmsstmp_copy(dev,dev->stat_buff,buf,&buf_offs, |
| &count); |
| dev->data_offs = 0; |
| } |
| data_buff = dev->data_buff; |
| size = dev->rd_rw_data_size - dev->data_offs; |
| |
| if(dev->read_all_at_once && (size > count)) { |
| status = -EINVAL; |
| printk(KERN_INFO "TSU: Read buffer too small, " |
| "(Read All At Once mode)"); |
| goto no_data; |
| } |
| |
| /* Valid data in data_buff. */ |
| if(size < count) |
| count = size; |
| TSU_DPRINT(TSU_DBG_READ, ("\tCopy %d Bytes (%d, %p, %d).\n", count, |
| buf_offs,data_buff,dev->data_offs)); |
| if(copy_to_user(buf + buf_offs,data_buff + dev->data_offs,count)) |
| panic("TSU: copy_to_user failed."); |
| |
| dev->data_offs += count; |
| *f_pos += (count + buf_offs); |
| |
| if(dev->data_offs == dev->valid_data_size) { |
| TSU_DPRINT(TSU_DBG_READ, ("\tFree RX buffer.\n")); |
| // memset(dev->data_buff,0x88,dev->buff_info.dataBlockSize); |
| #ifndef TSU_UNCACHED_DATA_BUFFERS |
| mvOsCacheClear(NULL, |
| (MV_U32*)TSU_DATA_BUFF_HW_2_SW(dev->data_buff), |
| dev->buff_info.dataBlockSize); |
| #endif /* TSU_UNCACHED_DATA_BUFFERS */ |
| status = mvTsuRxBuffFree(port,(MV_U32*)dev->data_buff, |
| dev->stat_buff,dev->buff_handle); |
| if(status != MV_OK) { |
| panic("TSU: Error in state machine, mvTsuRxBuffFree() " |
| "Failed."); |
| } |
| dev->data_buff = NULL; |
| dev->stat_buff = NULL; |
| } |
| |
| spin_unlock_irqrestore(&(dev->lock), flags); |
| TSU_LEAVE(TSU_DBG_READ, "mvtsu_read"); |
| return count + buf_offs; |
| |
| no_data: |
| spin_unlock_irqrestore(&(dev->lock), flags); |
| printk(KERN_DEBUG "TSU: Failed to read.\n"); |
| TSU_LEAVE(TSU_DBG_READ, "mvtsu_read"); |
| return status; |
| } |
| |
| |
| /* |
| * Helper function for retrying write buffer requests. |
| * Assume that the device spinlock is held. |
| */ |
| static int mvtsu_next_tx_buff_get(struct file *filp, u32 **data_buff, |
| u32 *buff_handle, unsigned long *flags) |
| { |
| struct mvtsu_dev *dev = (struct mvtsu_dev*)filp->private_data; |
| int timeout = 0; |
| int cnt = 0; |
| MV_STATUS status; |
| |
| // timeout = (dev->rd_wr_timeout == 0) ? 2000000 : (dev->rd_wr_timeout + 100); |
| timeout = dev->rd_wr_timeout; |
| |
| while(cnt < timeout) { |
| status = mvTsuTxNextBuffGet(dev->port,data_buff,buff_handle); |
| if(status != MV_OK) { |
| if(status != MV_NO_MORE) |
| return -EIO; |
| } |
| else { |
| break; |
| } |
| |
| if (filp->f_flags & O_NONBLOCK) |
| break; |
| spin_unlock_irqrestore(&(dev->lock), *flags); |
| if(dev->rd_wr_timeout) |
| msleep_interruptible(10); |
| else |
| udelay(1000); |
| |
| cnt += 10; |
| spin_lock_irqsave(&(dev->lock), *flags); |
| } |
| if(cnt >= timeout) { |
| printk(KERN_INFO "TSU: Write timeout.\n"); |
| return -EAGAIN; |
| } |
| return 0; |
| } |
| |
| |
| /* |
| * TSU data write |
| */ |
| ssize_t mvtsu_write (struct file *filp, const char __user *buf, size_t count, |
| loff_t *f_pos) |
| { |
| struct mvtsu_dev *dev = filp->private_data; |
| MV_U8 port = dev->port; |
| int status; |
| int buf_offs = 0; |
| MV_BOOL tsErr = MV_FALSE; |
| MV_U32 tms = 0; |
| MV_U32 data; |
| unsigned long flags; |
| size_t orig_cnt = count; |
| |
| TSU_ENTER(TSU_DBG_WRITE, "mvtsu_write"); |
| spin_lock_irqsave(&(dev->lock), flags); |
| |
| if(count < dev->rd_rw_data_size) { |
| printk(KERN_ERR "TSU: Write operations must hold at least a " |
| "single data block of data(%d , %d)", |
| dev->rd_rw_data_size, count); |
| status = -EINVAL; |
| goto no_tx; |
| } |
| |
| TSU_DPRINT(TSU_DBG_WRITE, ("\tGet new data buffer...")); |
| status = mvtsu_next_tx_buff_get(filp,(MV_U32**)(&dev->data_buff), |
| &dev->buff_handle,&flags); |
| if(status) { |
| TSU_DPRINT(TSU_DBG_WRITE, ("FAILED.\n")); |
| goto no_tx; |
| } |
| |
| TSU_DPRINT(TSU_DBG_WRITE, ("OK.\n")); |
| |
| if(dev->buff_info.aggrMode == MV_TSU_AGGR_MODE_DISABLED) { |
| if(dev->auto_tms_mode) { |
| tms = dev->tx_tms_val; |
| dev->tx_tms_val += dev->tx_tms_gap; |
| } else { |
| TSU_DPRINT(TSU_DBG_WRITE, ("\tGet timestamp info.\n")); |
| /* Copy the timestamp from the beginning of data buffer. */ |
| if(copy_from_user(&data,buf,TSU_DONE_STATUS_ENTRY_SIZE)) |
| panic("TSU: copy_to_user failed."); |
| |
| tsErr = TSU_STATUS_ERROR_GET(data); |
| tms = TSU_STATUS_TMSSTMP_GET(data); |
| TSU_DPRINT(TSU_DBG_WRITE, ("\tTimestamp = %d.\n",tms)); |
| } |
| buf_offs = TSU_DONE_STATUS_ENTRY_SIZE; |
| count -= TSU_DONE_STATUS_ENTRY_SIZE; |
| } |
| |
| /* Valid data in data_buff. */ |
| if(dev->valid_data_size < count) |
| count = dev->valid_data_size; |
| |
| TSU_DPRINT(TSU_DBG_WRITE, ("\tCopy %d Bytes.\n", count)); |
| if(copy_from_user(dev->data_buff, buf + buf_offs, count)) |
| panic("TSU Write: copy_to_user failed."); |
| *f_pos += orig_cnt; |
| |
| TSU_DPRINT(TSU_DBG_WRITE, ("\tFree TX buffer.\n")); |
| #ifndef TSU_UNCACHED_DATA_BUFFERS |
| mvOsCacheFlush(NULL,(MV_U32*)TSU_DATA_BUFF_HW_2_SW(dev->data_buff), |
| dev->buff_info.dataBlockSize); |
| #endif /* TSU_UNCACHED_DATA_BUFFERS */ |
| |
| status = mvTsuTxBuffPut(port,(MV_U32*)dev->data_buff,tms,tsErr, |
| dev->buff_handle); |
| if(status != MV_OK) |
| panic("TSU: Error in state machine, mvTsuTxBuffPut() Failed."); |
| |
| spin_unlock_irqrestore(&(dev->lock), flags); |
| TSU_LEAVE(TSU_DBG_WRITE, "mvtsu_write"); |
| return orig_cnt; |
| |
| no_tx: |
| spin_unlock_irqrestore(&(dev->lock), flags); |
| printk(KERN_DEBUG "TSU: Failed to write.\n"); |
| TSU_LEAVE(TSU_DBG_WRITE, "mvtsu_write"); |
| return status; |
| } |
| |
| /* |
| * TSU ioctl() |
| */ |
| int mvtsu_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, |
| unsigned long arg) |
| { |
| struct mvtsu_dev *dev = filp->private_data; |
| int ret = 0; |
| u32 val; |
| unsigned long flags; |
| MV_STATUS status = MV_OK; |
| struct tsu_tmstmp_info tms_info; |
| struct tsu_buff_info buf_info; |
| |
| TSU_ENTER(TSU_DBG_IOCTL, "mvtsu_ioctl"); |
| TSU_DPRINT(TSU_DBG_IOCTL, ("\targ = 0x%08x.\n",(unsigned int)arg)); |
| |
| spin_lock_irqsave(&(dev->lock), flags); |
| |
| switch(cmd) { |
| case MVTSU_IOCFREQSET: |
| get_user(val,(u32 __user *)arg); |
| TSU_DPRINT(TSU_DBG_IOCTL, ("\tFrequency set to %d.\n",val)); |
| if(dev->port_dir == TSU_PORT_OUTPUT) |
| status = mvTsuTxClockFreqSet(dev->port,val,MV_FALSE); |
| if(status == MV_OK) { |
| dev->clockrate = val; |
| mvtsu_rd_wr_timeout_calc(dev); |
| mvtsu_tx_timestamp_calc(dev); |
| } else { |
| ret = -EINVAL; |
| } |
| break; |
| case MVTSU_IOCTXTMSSET: |
| if(copy_from_user(&tms_info,(struct tsu_tmstmp_info*)arg, |
| sizeof(tms_info))) |
| panic("TSU IOCTL: copy_from_user failed.\n"); |
| TSU_DPRINT(TSU_DBG_IOCTL, ("\tTx timestamp set to (%d,%d).\n", |
| tms_info.timestamp, |
| tms_info.enable_tms)); |
| status = mvTsuTxInitTimeStampSet( |
| dev->port, (tms_info.enable_tms ? MV_TRUE : MV_FALSE), |
| tms_info.timestamp); |
| if(status != MV_OK) |
| ret = -EINVAL; |
| break; |
| case MVTSU_IOCTXDONE: |
| TSU_DPRINT(TSU_DBG_IOCTL, ("\tTx Done.\n")); |
| if(mvTsuTxDone(dev->port) != MV_OK) |
| ret = -EINVAL; |
| break; |
| case MVTSU_IOCRDPKTATONCE: |
| TSU_DPRINT(TSU_DBG_IOCTL, ("\tRx Read Packet At Once.\n")); |
| get_user(val,(u32 __user *)arg); |
| dev->read_all_at_once = val; |
| break; |
| case MVTSU_IOCBUFFPARAMGET: |
| TSU_DPRINT(TSU_DBG_IOCTL, ("\tGet Buffer Params.\n")); |
| switch(dev->buff_info.aggrMode) { |
| case MV_TSU_AGGR_MODE_DISABLED: |
| buf_info.aggr_mode = aggrModeDisabled; |
| break; |
| case MV_TSU_AGGR_MODE_1: |
| buf_info.aggr_mode = aggrMode1; |
| break; |
| case MV_TSU_AGGR_MODE_2: |
| buf_info.aggr_mode = aggrMode2; |
| break; |
| default: |
| ret = -EINVAL; |
| } |
| buf_info.aggr_mode2_tmstmp_off = |
| dev->buff_info.aggrMode2TmstmpOff; |
| buf_info.aggr_num_packets = |
| dev->buff_info.aggrNumPackets; |
| buf_info.num_done_q_entries = |
| dev->buff_info.numDoneQEntry; |
| buf_info.num_ts_desc = |
| dev->buff_info.numTsDesc; |
| buf_info.pkt_size = cfg_pkt_size; |
| |
| if(copy_to_user((struct tsu_buff_info*)arg,&buf_info, |
| sizeof(buf_info))) |
| panic("TSU IOCTL: copy_to_user failed.\n"); |
| break; |
| case MVTSU_IOCGETSTAT: |
| TSU_DPRINT(TSU_DBG_IOCTL, ("\tGet Statistics.\n")); |
| if(copy_to_user((struct tsu_stat*)arg,&dev->int_stat, |
| sizeof(struct tsu_stat))) |
| panic("TSU IOCTL: copy_to_user failed.\n"); |
| break; |
| case MVTSU_IOCCLEARSTAT: |
| TSU_DPRINT(TSU_DBG_IOCTL, ("\tClear Statistics.\n")); |
| memset(&(dev->int_stat),0,sizeof(dev->int_stat)); |
| break; |
| case MVTSU_IOCAUTOTMS: |
| TSU_DPRINT(TSU_DBG_IOCTL, ("\tAuto timestamp mode.\n")); |
| get_user(val,(u32 __user *)arg); |
| dev->auto_tms_mode = val; |
| break; |
| default: |
| TSU_DPRINT(TSU_DBG_IOCTL, ("\tInvalid request.\n")); |
| ret = -EINVAL; |
| } |
| |
| spin_unlock_irqrestore(&(dev->lock), flags); |
| |
| TSU_LEAVE(TSU_DBG_IOCTL, "mvtsu_ioctl"); |
| return ret; |
| } |
| |
| |
| #ifdef CONFIG_MV_TSU_PROC |
| |
| /* |
| Parameter Port Value Possible Values |
| |
| sync_detect x x int |
| sync_loss x x int |
| aggr_mode x x (mode1 / mode2 / dis) |
| aggr_mode2_off x x int |
| aggr_num_pckts x x int |
| num_desc x x int |
| num_done_queue x x int |
| sync_sig x x (dis / high / low) |
| valid_sig x x (dis / high / low) |
| error_sig x x (dis / high / low) |
| data_edge x x (fall / rise) |
| data_order x x (msb / lsb) |
| sync_act x x (1 / 8) |
| clk_mode x x (cont / gap) |
| */ |
| |
| #define TSU_AGGR_MODE_2_STR(mode) \ |
| ((mode == MV_TSU_AGGR_MODE_DISABLED) ? "dis" : \ |
| ((mode == MV_TSU_AGGR_MODE_1) ? "mode1" : "mode2")) |
| |
| #define TSU_SIGNAL_MODE_2_STR(mode) \ |
| ((mode == TSU_SIGNAL_DIS) ? "dis" : \ |
| ((mode == TSU_SIGNAL_EN_ACT_LOW) ? "low" : "high")) |
| |
| #define TSU_STR_2_SIGNAL_MODE(mode,str) \ |
| { \ |
| if(!strncmp((str),"dis",3)) \ |
| mode = TSU_SIGNAL_DIS; \ |
| else if(!strncmp((str),"low",3)) \ |
| mode = TSU_SIGNAL_EN_ACT_LOW; \ |
| else if(!strncmp((str),"high",4)) \ |
| mode = TSU_SIGNAL_EN_ACT_HIGH; \ |
| else \ |
| mode = TSU_SIGNAL_KEEP_DEF; \ |
| } |
| |
| |
| |
| int mvtsu_proc_write(struct file *file, const char *buffer,unsigned long count, |
| void *data) |
| { |
| MV_TSU_SIGNAL_CONFIG signal_cfg; |
| MV_U32 flags; |
| MV_U8 sync_detect; |
| MV_U8 sync_loss; |
| MV_BOOL write_signal = MV_FALSE; |
| MV_BOOL write_sync = MV_FALSE; |
| MV_U8 port; |
| int len = 0; |
| int tmp; |
| char *str; |
| struct mvtsu_dev *dev; |
| |
| TSU_ENTER(TSU_DBG_PROC, "mvtsu_proc_write"); |
| |
| len = sscanf(buffer,"%d ",&tmp); |
| |
| if(tmp >= MV_TSU_NUM_PORTS) |
| return count; |
| port = (MV_U8)tmp; |
| len++; /* Skip over the space. */ |
| |
| signal_cfg.tsDataEdge = TSU_SIGNAL_EDGE_FALL; |
| signal_cfg.tsError = TSU_SIGNAL_KEEP_DEF; |
| signal_cfg.tsSync = TSU_SIGNAL_KEEP_DEF; |
| signal_cfg.tsValid = TSU_SIGNAL_KEEP_DEF; |
| flags = 0; |
| |
| if(mvTsuRxSyncDetectionGet(port,&sync_detect,&sync_loss) != MV_OK) |
| return count; |
| dev = &mvtsu_devs[port]; |
| |
| TSU_DPRINT(TSU_DBG_PROC, ("\tbuffer = %s.\n",buffer+len)); |
| str = "sync_detect "; |
| if(!strncmp(buffer+len, str,strlen(str))) { |
| len += strlen(str); |
| len += sscanf(buffer+len,"%d",&tmp); |
| sync_detect = (MV_U8)tmp; |
| TSU_DPRINT(TSU_DBG_PROC, ("\tsync_detect = %d.\n",sync_detect)); |
| write_sync = MV_TRUE; |
| goto done; |
| } |
| str = "sync_loss "; |
| if(!strncmp(buffer+len, str,strlen(str))) { |
| len += strlen(str); |
| len += sscanf(buffer+len,"%d",&tmp); |
| sync_loss = (MV_U8)tmp; |
| TSU_DPRINT(TSU_DBG_PROC, ("\tsync_loss = %d.\n",sync_loss)); |
| write_sync = MV_TRUE; |
| goto done; |
| } |
| |
| str = "aggr_mode "; |
| if(!strncmp(buffer+len, str,strlen(str))) { |
| len += strlen(str); |
| str = "dis"; |
| if(!strncmp(buffer+len, str,strlen(str))) { |
| len += strlen(str); |
| dev->buff_info.aggrMode = MV_TSU_AGGR_MODE_DISABLED; |
| goto done; |
| } |
| str = "mode1"; |
| if(!strncmp(buffer+len, str,strlen(str))) { |
| len += strlen(str); |
| dev->buff_info.aggrMode = MV_TSU_AGGR_MODE_1; |
| goto done; |
| } |
| str = "mode2"; |
| if(!strncmp(buffer+len, str,strlen(str))) { |
| len += strlen(str); |
| dev->buff_info.aggrMode = MV_TSU_AGGR_MODE_2; |
| goto done; |
| } |
| goto done; |
| } |
| |
| str = "aggr_mode2_off "; |
| if(!strncmp(buffer+len, str,strlen(str))) { |
| len += strlen(str); |
| len += sscanf(buffer+len,"%d",&tmp); |
| dev->buff_info.aggrMode2TmstmpOff = tmp; |
| goto done; |
| } |
| |
| str = "aggr_num_pckts "; |
| if(!strncmp(buffer+len, str,strlen(str))) { |
| len += strlen(str); |
| len += sscanf(buffer+len,"%d",&tmp); |
| dev->buff_info.aggrNumPackets = tmp; |
| goto done; |
| } |
| |
| str = "num_desc "; |
| if(!strncmp(buffer+len, str,strlen(str))) { |
| len += strlen(str); |
| len += sscanf(buffer+len,"%d",&tmp); |
| dev->buff_info.numTsDesc = tmp; |
| goto done; |
| } |
| |
| str = "num_done_queue "; |
| if(!strncmp(buffer+len, str,strlen(str))) { |
| len += strlen(str); |
| len += sscanf(buffer+len,"%d",&tmp); |
| dev->buff_info.numDoneQEntry = tmp; |
| goto done; |
| } |
| |
| str = "sync_sig "; |
| if(!strncmp(buffer+len, str,strlen(str))) { |
| len += strlen(str); |
| TSU_STR_2_SIGNAL_MODE(signal_cfg.tsSync,buffer+len); |
| write_signal = MV_TRUE; |
| goto done; |
| } |
| |
| str = "valid_sig "; |
| if(!strncmp(buffer+len, str,strlen(str))) { |
| len += strlen(str); |
| TSU_STR_2_SIGNAL_MODE(signal_cfg.tsValid,buffer+len); |
| write_signal = MV_TRUE; |
| goto done; |
| } |
| |
| str = "error_sig "; |
| if(!strncmp(buffer+len, str,strlen(str))) { |
| len += strlen(str); |
| TSU_STR_2_SIGNAL_MODE(signal_cfg.tsError,buffer+len); |
| write_signal = MV_TRUE; |
| goto done; |
| } |
| |
| str = "data_edge "; |
| if(!strncmp(buffer+len, str,strlen(str))) { |
| len += strlen(str); |
| str = "fall"; |
| if(!strncmp(buffer+len, str,strlen(str))) { |
| len += strlen(str); |
| signal_cfg.tsDataEdge = TSU_SIGNAL_EDGE_FALL; |
| write_signal = MV_TRUE; |
| goto done; |
| } |
| str = "rise"; |
| if(!strncmp(buffer+len, str,strlen(str))) { |
| len += strlen(str); |
| signal_cfg.tsDataEdge = TSU_SIGNAL_EDGE_RISE; |
| write_signal = MV_TRUE; |
| goto done; |
| } |
| goto done; |
| } |
| |
| str = "data_order "; |
| if(!strncmp(buffer+len, str,strlen(str))) { |
| len += strlen(str); |
| str = "msb"; |
| if(!strncmp(buffer+len, str,strlen(str))) { |
| len += strlen(str); |
| flags |= MV_TSU_SER_DATA_ORDER_MSB; |
| write_signal = MV_TRUE; |
| goto done; |
| } |
| str = "lsb"; |
| if(!strncmp(buffer+len, str,strlen(str))) { |
| len += strlen(str); |
| flags |= MV_TSU_SER_DATA_ORDER_LSB; |
| write_signal = MV_TRUE; |
| goto done; |
| } |
| goto done; |
| } |
| |
| str = "sync_act "; |
| if(!strncmp(buffer+len, str,strlen(str))) { |
| len += strlen(str); |
| len += sscanf(buffer+len,"%d",&tmp); |
| write_signal = MV_TRUE; |
| if(tmp == 1) |
| flags |= MV_TSU_SER_SYNC_ACT_1_BIT; |
| else if(tmp == 8) |
| flags |= MV_TSU_SER_SYNC_ACT_8_BIT; |
| else |
| write_signal = MV_FALSE; |
| goto done; |
| } |
| |
| str = "clk_mode "; |
| if(!strncmp(buffer+len, str,strlen(str))) { |
| len += strlen(str); |
| str = "cont"; |
| if(!strncmp(buffer+len, str,strlen(str))) { |
| len += strlen(str); |
| flags |= MV_TSU_SER_TX_CLK_MODE_CONT; |
| write_signal = MV_TRUE; |
| goto done; |
| } |
| str = "gap"; |
| if(!strncmp(buffer+len, str,strlen(str))) { |
| len += strlen(str); |
| flags |= MV_TSU_SER_TX_CLK_MODE_GAPPED; |
| write_signal = MV_TRUE; |
| goto done; |
| } |
| goto done; |
| } |
| |
| done: |
| if(write_signal == MV_TRUE){ |
| mvTsuPortSignalCfgSet(port,&signal_cfg,flags); |
| dev->serial_sig_flags = flags; |
| dev->signal_cfg = signal_cfg; |
| } |
| if(write_sync == MV_TRUE){ |
| mvTsuRxSyncDetectionSet(port,sync_detect,sync_loss); |
| dev->sync_loss = sync_loss; |
| dev->sync_detect = sync_detect; |
| } |
| |
| TSU_LEAVE(TSU_DBG_PROC, "mvtsu_proc_write"); |
| return count; |
| } |
| |
| |
| int mvtsu_proc_read_port(int port, char *buf) |
| { |
| MV_U8 sync_detect, sync_loss; |
| MV_TSU_SIGNAL_CONFIG signal_cfg; |
| MV_U32 flags; |
| int len=0; |
| |
| if(mvTsuRxSyncDetectionGet(port,&sync_detect,&sync_loss) != MV_OK) |
| return -1; |
| len += sprintf(buf,"\tsync_detect\t\t%d\t%d\tint\n",port,sync_detect); |
| len += sprintf(buf+len,"\tsync_loss\t\t%d\t%d\tint\n",port,sync_loss); |
| |
| len += sprintf(buf+len,"\taggr_mode\t\t%d\t%s\t(mode1 / mode2 / dis)\n", |
| port, |
| TSU_AGGR_MODE_2_STR(mvtsu_devs[port].buff_info.aggrMode)); |
| len += sprintf(buf+len,"\taggr_mode2_off\t\t%d\t%d\tint\n", |
| port,mvtsu_devs[port].buff_info.aggrMode2TmstmpOff); |
| len += sprintf(buf+len,"\taggr_num_pckts\t\t%d\t%d\tint\n", |
| port,mvtsu_devs[port].buff_info.aggrNumPackets); |
| len += sprintf(buf+len,"\tnum_desc\t\t%d\t%d\tint\n", |
| port,mvtsu_devs[port].buff_info.numTsDesc); |
| len += sprintf(buf+len,"\tnum_done_queue\t\t%d\t%d\tint\n", |
| port,mvtsu_devs[port].buff_info.numDoneQEntry); |
| |
| if(mvTsuPortSignalCfgGet(port,&signal_cfg,&flags) != MV_OK) |
| return -1; |
| len += sprintf(buf+len,"\tsync_sig\t\t%d\t%s\t(dis / high / low)\n", |
| port,TSU_SIGNAL_MODE_2_STR(signal_cfg.tsSync)); |
| len += sprintf(buf+len,"\tvalid_sig\t\t%d\t%s\t(dis / high / low)\n", |
| port,TSU_SIGNAL_MODE_2_STR(signal_cfg.tsValid)); |
| len += sprintf(buf+len,"\terror_sig\t\t%d\t%s\t(dis / high / low)\n", |
| port,TSU_SIGNAL_MODE_2_STR(signal_cfg.tsError)); |
| len += sprintf(buf+len,"\tdata_edge\t\t%d\t%s\t(fall / rise)\n", |
| port,(signal_cfg.tsDataEdge == TSU_SIGNAL_EDGE_FALL) ? |
| "fall" : "rise"); |
| |
| if(cfg_tsu_mode == TSU_MODE_SERIAL) { |
| len += sprintf(buf+len,"\tdata_order\t\t%d\t%s\t(msb / lsb)\n", |
| port,(flags & MV_TSU_SER_DATA_ORDER_MSB) ? "msb" : "lsb"); |
| len += sprintf(buf+len,"\tsync_act\t\t%d\t%d\t(1 / 8)\n", |
| port,(flags & MV_TSU_SER_SYNC_ACT_1_BIT) ? 1 : 8); |
| len += sprintf(buf+len,"\tclk_mode\t\t%d\t%s\t(cont / gap)\n", |
| port, |
| (flags & MV_TSU_SER_TX_CLK_MODE_CONT) ? "cont" : "gap"); |
| } |
| return len; |
| } |
| |
| |
| int mvtsu_proc_read(char* page, char** start, off_t off, int count,int* eof, |
| void* data) |
| { |
| int len = 0; |
| int i; |
| int res; |
| |
| if(off > 0) |
| return 0; |
| len += sprintf(page,"\tParameter\t\tPort\tValue\tPossible Values\n\n"); |
| |
| for(i = 0; i < MV_TSU_NUM_PORTS; i++) { |
| res = mvtsu_proc_read_port(i,page+len); |
| if(res < 0) { |
| len = -1; |
| break; |
| } |
| len += res; |
| } |
| if(len != -1) |
| len += sprintf(page+len,"\nConfig String: <port> <attr> <val>\n"); |
| |
| return len; |
| } |
| |
| |
| /* |
| * Create TSU proc entry. |
| */ |
| static int mvtsu_proc_init(void) |
| { |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26) |
| mvtsu_proc_entry = create_proc_entry("tsu", 0666, &proc_root); |
| mvtsu_proc_entry->owner = THIS_MODULE; |
| #else |
| mvtsu_proc_entry = create_proc_entry("tsu", 0666, NULL); |
| #endif |
| mvtsu_proc_entry->read_proc = mvtsu_proc_read; |
| mvtsu_proc_entry->write_proc = mvtsu_proc_write; |
| return 0; |
| } |
| |
| #endif /* CONFIG_MV_TSU_PROC */ |
| |
| |
| /* |
| * Parse the TSU command line. |
| * The command line looks as follows: |
| * mv_tsu_config=<interface mode>,<packet size>,<core clock in MHz> |
| * e.g. serial,188,73 |
| */ |
| int mvtsu_cmdline_config(char *s) |
| { |
| mvtsu_cmdline = s; |
| return 1; |
| } |
| |
| static int mvtsu_parse_core_clk(char **cmdline) |
| { |
| char *str = *cmdline; |
| int num = 0; |
| |
| while(*str != '\0') { |
| if(!TSU_IS_DEC_DIGIT(*str)) |
| return -1; |
| num = (num * 10) + (*str - '0'); |
| str++; |
| } |
| |
| switch (num) { |
| case(71): |
| cfg_core_clk = TSU_CORE_CLK_71_MHZ; |
| break; |
| case(83): |
| cfg_core_clk = TSU_CORE_CLK_83_MHZ; |
| break; |
| case(91): |
| cfg_core_clk = TSU_CORE_CLK_91_MHZ; |
| break; |
| case(100): |
| cfg_core_clk = TSU_CORE_CLK_100_MHZ; |
| break; |
| default: |
| return -1; |
| } |
| |
| *cmdline = str; |
| return 0; |
| } |
| |
| |
| static int mvtsu_parse_pkt_size(char **cmdline) |
| { |
| char *str = *cmdline; |
| int num = 0; |
| |
| while((*str != '\0') && (*str != ',')) { |
| if(!TSU_IS_DEC_DIGIT(*str)) |
| return -1; |
| num = (num * 10) + (*str - '0'); |
| str++; |
| } |
| if((*str == ',') && (num != 0)) { |
| cfg_pkt_size = num; |
| *cmdline = str; |
| return 0; |
| } |
| return -1; |
| } |
| |
| |
| static int mvtsu_parse_mode(char **cmdline) |
| { |
| char *str; |
| |
| str = "serial"; |
| if(!strncmp(*cmdline,str,strlen(str))) { |
| cfg_tsu_mode = TSU_MODE_SERIAL; |
| *cmdline += strlen(str); |
| } else { |
| str = "parallel"; |
| if(!strncmp(*cmdline,str,strlen(str))) { |
| cfg_tsu_mode = TSU_MODE_PARALLEL; |
| *cmdline += strlen(str); |
| return 0; |
| } |
| } |
| return 0; |
| } |
| |
| |
| static int mvtsu_parse_cmdline(void) |
| { |
| char *cmdline; |
| |
| if(mvtsu_cmdline) { |
| TSU_DPRINT(TSU_DBG_INIT, ("TSU command line: %s.\n",mvtsu_cmdline)); |
| cmdline = mvtsu_cmdline; |
| if(mvtsu_parse_mode(&cmdline) != 0) { |
| printk(KERN_ERR "TSU: Bad interface mode option in command line, using default.\n"); |
| goto set_default; |
| } |
| if(cmdline[0] != ',') { |
| printk(KERN_ERR "TSU: Bad command line format (Expected ',' found %c).\n", |
| cmdline[0]); |
| } |
| cmdline++; |
| if(mvtsu_parse_pkt_size(&cmdline) != 0) { |
| printk(KERN_ERR "TSU: Bad packet size option in command line, using default.\n"); |
| goto set_default; |
| } |
| if(cmdline[0] != ',') { |
| printk(KERN_ERR "TSU: Bad command line format (Expected ',' found %c).\n", |
| cmdline[0]); |
| } |
| cmdline++; |
| if(mvtsu_parse_core_clk(&cmdline) != 0) { |
| printk(KERN_ERR "TSU: Bad core-clock option in command line " |
| "(Expected 71 / 83 / 91 / 100), using default.\n"); |
| goto set_default; |
| } |
| goto success; |
| } else { |
| printk(KERN_INFO "TSU: No command line parameters, using default.\n"); |
| } |
| |
| set_default: |
| cfg_tsu_mode = DEF_TSU_MODE; |
| cfg_core_clk = DEF_TSU_CORE_CLOCK; |
| cfg_pkt_size = CONFIG_MV_TSU_PKT_SIZE; |
| success: |
| return 0; |
| } |
| |
| |
| struct file_operations mvtsu_fops = { |
| .owner = THIS_MODULE, |
| .read = mvtsu_read, |
| .write = mvtsu_write, |
| .ioctl = mvtsu_ioctl, |
| .open = mvtsu_open, |
| .release = mvtsu_release, |
| }; |
| |
| /* |
| * Initialize the TSU driver |
| */ |
| int mvtsu_init(void) |
| { |
| int result, i; |
| dev_t dev; |
| |
| TSU_ENTER(TSU_DBG_INIT, "mvtsu_init"); |
| |
| /* Check unit power mode. */ |
| if(mvCtrlPwrClckGet(TS_UNIT_ID,0) == MV_FALSE) { |
| printk("Warning: TS unit is powered off.\n"); |
| TSU_LEAVE(TSU_DBG_INIT, "mvtsu_init"); |
| return 0; |
| } |
| |
| /* Parse command line parameters. */ |
| mvtsu_parse_cmdline(); |
| |
| dev = MKDEV(MV_TSU_MAJOR, 0); |
| result = register_chrdev_region(dev, TSU_NUM_DEVICES, TSU_DEV_NAME); |
| |
| if (result < 0) { |
| printk("Failed to register char device (%d,%d)\n",result, TSU_NUM_DEVICES); |
| TSU_LEAVE(TSU_DBG_INIT, "mvtsu_init"); |
| return result; |
| } |
| |
| /* Perform unit initialization. */ |
| result = mvSysTsuInit(cfg_core_clk, cfg_tsu_mode ,NULL); |
| if(result != MV_OK) { |
| goto fail_init; |
| result = -EINVAL; |
| } |
| TSU_DPRINT(TSU_DBG_INIT, ("\tTSU unit initialized successfully.\n")); |
| |
| /* Create the char device. */ |
| for (i = 0; i < TSU_NUM_DEVICES; i++) { |
| mvtsu_devs[i].port = i; |
| mvtsu_set_defaults(&mvtsu_devs[i]); |
| cdev_init(&mvtsu_devs[i].cdev, &mvtsu_fops); |
| mvtsu_devs[i].cdev.owner = THIS_MODULE; |
| mvtsu_devs[i].cdev.ops = &mvtsu_fops; |
| dev = MKDEV(MV_TSU_MAJOR, i); |
| result = cdev_add (&mvtsu_devs[i].cdev, dev, 1); |
| if (result) { |
| printk(KERN_ERR "Error %d adding tsu%d", result, i); |
| goto fail_add; |
| } |
| spin_lock_init(&mvtsu_devs[i].lock); |
| TSU_DPRINT(TSU_DBG_INIT, ("\tChar device %d initialized.\n",i)); |
| } |
| |
| #ifdef CONFIG_MV_TSU_PROC |
| TSU_DPRINT(TSU_DBG_INIT, ("\tCreating Proc entry.\n")); |
| mvtsu_proc_init(); |
| #endif /* CONFIG_MV_TSU_PROC */ |
| |
| printk("Transport Stream interface registered.\n"); |
| printk(" o %s Mode.\n", |
| (cfg_tsu_mode == TSU_MODE_PARALLEL) ? "Parallel" : "Serial"); |
| printk(" o Core-Clock - "); |
| switch (cfg_core_clk) { |
| case (TSU_CORE_CLK_83_MHZ): |
| printk("83"); |
| break; |
| case (TSU_CORE_CLK_71_MHZ): |
| printk("71"); |
| break; |
| case (TSU_CORE_CLK_91_MHZ): |
| printk("91"); |
| break; |
| case (TSU_CORE_CLK_100_MHZ): |
| printk("100"); |
| break; |
| } |
| printk(" MHz.\n"); |
| printk(" o Packet Size - %d Bytes.\n",cfg_pkt_size); |
| TSU_LEAVE(TSU_DBG_INIT, "mvtsu_init"); |
| return 0; |
| |
| fail_add: |
| while(i > 0) { |
| cdev_del(&mvtsu_devs[i-1].cdev); |
| i--; |
| } |
| |
| fail_init: |
| dev = MKDEV(MV_TSU_MAJOR, 0); |
| unregister_chrdev_region(dev, TSU_NUM_DEVICES); |
| |
| TSU_LEAVE(TSU_DBG_INIT, "mvtsu_init"); |
| return result; |
| } |
| |
| |
| /* |
| * Cleanup the TSU driver. |
| */ |
| void mvtsu_cleanup(void) |
| { |
| int i; |
| |
| TSU_ENTER(TSU_DBG_INIT, "mvtsu_cleanup"); |
| for(i = 0; i < TSU_NUM_DEVICES; i++) { |
| cdev_del(&mvtsu_devs[i].cdev); |
| } |
| |
| unregister_chrdev_region(mvtsu_device, TSU_NUM_DEVICES); |
| |
| mvTsuShutdown(); |
| |
| TSU_LEAVE(TSU_DBG_INIT, "mvtsu_cleanup"); |
| } |
| |
| module_init(mvtsu_init); |
| module_exit(mvtsu_cleanup); |
| MODULE_LICENSE("GPL"); |
| |