diff options
author | Eric Nelson <eric.nelson@boundarydevices.com> | 2013-02-22 18:50:43 -0700 |
---|---|---|
committer | Eric Nelson <eric.nelson@boundarydevices.com> | 2013-09-03 14:12:26 -0700 |
commit | 45ddf45f67072ea2ac2e2a9f561be602bc1609cb (patch) | |
tree | fd1fc36b99e6724f48051282c5cef846738bfb82 /drivers | |
parent | 4b6121db76250f74ad26fb5e7d80f97ef00128fe (diff) |
Add tw686x-0.1.5 from Techwell/Intersil package
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/pci/pcie/Kconfig | 7 | ||||
-rw-r--r-- | drivers/pci/pcie/Makefile | 2 | ||||
-rw-r--r-- | drivers/pci/pcie/tw6869/Makefile | 8 | ||||
-rw-r--r-- | drivers/pci/pcie/tw6869/i2c-sw.c | 321 | ||||
-rw-r--r-- | drivers/pci/pcie/tw6869/i2c-sw.h | 60 | ||||
-rw-r--r-- | drivers/pci/pcie/tw6869/tw686x-alsa.c | 658 | ||||
-rw-r--r-- | drivers/pci/pcie/tw6869/tw686x-audio.c | 300 | ||||
-rw-r--r-- | drivers/pci/pcie/tw6869/tw686x-core.c | 578 | ||||
-rw-r--r-- | drivers/pci/pcie/tw6869/tw686x-device.c | 1172 | ||||
-rw-r--r-- | drivers/pci/pcie/tw6869/tw686x-device.h | 56 | ||||
-rw-r--r-- | drivers/pci/pcie/tw6869/tw686x-i2c.c | 213 | ||||
-rw-r--r-- | drivers/pci/pcie/tw6869/tw686x-reg.h | 593 | ||||
-rw-r--r-- | drivers/pci/pcie/tw6869/tw686x-video.c | 1670 | ||||
-rw-r--r-- | drivers/pci/pcie/tw6869/tw686x.h | 415 | ||||
-rw-r--r-- | drivers/pci/pcie/tw6869/videobuf-dma-contig-tw.c | 897 | ||||
-rw-r--r-- | drivers/pci/pcie/tw6869/videobuf-dma-contig-tw.h | 46 |
16 files changed, 6996 insertions, 0 deletions
diff --git a/drivers/pci/pcie/Kconfig b/drivers/pci/pcie/Kconfig index dc29348264c6..41860efad080 100644 --- a/drivers/pci/pcie/Kconfig +++ b/drivers/pci/pcie/Kconfig @@ -58,3 +58,10 @@ config PCIEASPM_DEBUG config PCIE_PME def_bool y depends on PCIEPORTBUS && PM_RUNTIME && EXPERIMENTAL && ACPI + + +config PCIE_TW6869 + tristate "Techwell/Intersil TW6869" + select VIDEOBUF_DMA_CONTIG + select VIDEOBUF_DMA_SG + depends on PCIEPORTBUS && PM_RUNTIME diff --git a/drivers/pci/pcie/Makefile b/drivers/pci/pcie/Makefile index 00c62df5a9fc..1ffa63c0e492 100644 --- a/drivers/pci/pcie/Makefile +++ b/drivers/pci/pcie/Makefile @@ -14,3 +14,5 @@ obj-$(CONFIG_PCIEPORTBUS) += pcieportdrv.o obj-$(CONFIG_PCIEAER) += aer/ obj-$(CONFIG_PCIE_PME) += pme.o + +obj-$(CONFIG_PCIE_TW6869) += tw6869/ diff --git a/drivers/pci/pcie/tw6869/Makefile b/drivers/pci/pcie/tw6869/Makefile new file mode 100644 index 000000000000..268a69bd7141 --- /dev/null +++ b/drivers/pci/pcie/tw6869/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for Techwell/Intersil TW6869 +# + +tw6869-objs := tw686x-core.o tw686x-video.o tw686x-device.o \ + tw686x-alsa.o videobuf-dma-contig-tw.o tw686x-audio.o + +obj-m += tw6869.o diff --git a/drivers/pci/pcie/tw6869/i2c-sw.c b/drivers/pci/pcie/tw6869/i2c-sw.c new file mode 100644 index 000000000000..40007e4d19b7 --- /dev/null +++ b/drivers/pci/pcie/tw6869/i2c-sw.c @@ -0,0 +1,321 @@ +/* + * Driver for Techwell TW6864/68 based DVR cards + * + * (c) 2009-10 liran <jlee@techwellinc.com.cn> [Techwell China] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/time.h> +#include "i2c-sw.h" + +static struct timeval i2csw_tv_begin; + +//-------------------------------------------------------- +// 获得时钟 +//-------------------------------------------------------- +static u32 tick_count( void ) +{ + struct timeval tv_now; + u64 tv_diff = 0; + + do_gettimeofday( &tv_now ); + tv_diff = 1000000 * ( tv_now.tv_sec - i2csw_tv_begin.tv_sec ) + + tv_now.tv_usec - i2csw_tv_begin.tv_usec; + + return (((u32)tv_diff)/1000); +} + +static void i2csw_bit_delay( struct i2c_sw *i2c ) +{ + volatile u32 i; + for ( i = i2c->cycle; i > 0; i-- ) ; +} + +static bool i2csw_start(struct i2c_sw *i2c) +{ + // SendStart - send an I2c start + // i.e. SDA 1 -> 0 with SCL = 1 + + if ( !i2c->s_sda( i2c, LevelHi ) ) { i2c->err_code = I2CERR_SDA; return false; } + if ( !i2c->s_scl( i2c, LevelHi ) ) { i2c->err_code = I2CERR_SCL; return false; } + if ( !i2c->s_sda( i2c, LevelLow ) ) { i2c->err_code = I2CERR_SDA; return false; } + if ( !i2c->s_scl( i2c, LevelLow ) ) { i2c->err_code = I2CERR_SCL; return false; } + i2c->err_code = I2CERR_OK; + + return true; +} + +static bool i2csw_stop(struct i2c_sw *i2c) +{ + // SendStop - sends an I2C stop, releasing the bus. + // i.e. SDA 0 -> 1 with SCL = 1 + + if ( !i2c->s_scl( i2c, LevelLow ) ) { i2c->err_code = I2CERR_SCL; return false; } + if ( !i2c->s_sda( i2c, LevelLow ) ) { i2c->err_code = I2CERR_SDA; return false; } + if ( !i2c->s_scl( i2c, LevelHi ) ) { i2c->err_code = I2CERR_SCL; return false; } + if ( !i2c->s_sda( i2c, LevelHi ) ) { i2c->err_code = I2CERR_SDA; return false; } + i2c->err_code = I2CERR_OK; + + return true; +} + +static bool i2csw_send_ack( struct i2c_sw *i2c ) +{ + // Generate ACK signal + // i.e. SDA = 0 with SCL = 1 + + if ( !i2c->s_scl( i2c, LevelLow ) ) { i2c->err_code = I2CERR_SCL; return false; } + if ( !i2c->s_sda( i2c, LevelLow ) ) { i2c->err_code = I2CERR_SDA; return false; } + if ( !i2c->s_scl( i2c, LevelHi ) ) { i2c->err_code = I2CERR_SCL; return false; } + if ( !i2c->s_scl( i2c, LevelLow ) ) { i2c->err_code = I2CERR_SCL; return false; } + if ( !i2c->s_sda( i2c, LevelHi ) ) { i2c->err_code = I2CERR_SDA; return false; } + i2c->err_code = I2CERR_OK; + + return true; +} + +static bool i2csw_send_nack( struct i2c_sw *i2c ) +{ + // Generate NACK signal + // i.e. SDA = 1 with SCL = 1 + + if ( !i2c->s_scl( i2c, LevelLow ) ) { i2c->err_code = I2CERR_SCL; return false; } + if ( !i2c->s_sda( i2c, LevelHi ) ) { i2c->err_code = I2CERR_SDA; return false; } + if ( !i2c->s_scl( i2c, LevelHi ) ) { i2c->err_code = I2CERR_SCL; return false; } + if ( !i2c->s_scl( i2c, LevelLow ) ) { i2c->err_code = I2CERR_SCL; return false; } + if ( !i2c->s_sda( i2c, LevelLow ) ) { i2c->err_code = I2CERR_SDA; return false; } + i2c->err_code = I2CERR_OK; + + return true; +} + +static bool i2csw_wait_ack( struct i2c_sw *i2c ) +{ + u32 startTime; + u32 maxWait = 500; // 500 mSec + + if ( !i2c->s_scl( i2c, LevelLow ) ){ i2c->err_code = I2CERR_SCL; return false; } + if ( !i2c->s_sda( i2c, LevelHi ) ) { i2c->err_code = I2CERR_SDA; return false; } + + // loop until either ACK or timeout + startTime = tick_count(); + + while (1) + { + // SDA pin == 0 means the slave ACK'd + if ( i2c->g_sda( i2c ) == 0 ) + { + if ( !i2c->s_scl( i2c, LevelHi ) ) { i2c->err_code = I2CERR_SCL; return false; } + if ( !i2c->s_scl( i2c, LevelLow ) ){ i2c->err_code = I2CERR_SCL; return false; } + i2c->err_code = I2CERR_OK; + return true; + } + + // timeout? + if ( tick_count() - startTime > (u32)maxWait ) + { + i2csw_stop(i2c); + i2c->err_code = I2CERR_TIMEOUT; + return false; + } + } // while + + return true; +} + +static bool i2csw_read(struct i2c_sw *i2c, u8 *value) +{ + u8 mask; + + *value = 0x00; + + // read 8 bits from I2c into Accumulator + for(mask = 0x80; mask > 0; mask = (u8)(mask >> 1)) { + if ( !i2c->s_scl( i2c, LevelLow ) ) { i2c->err_code = I2CERR_SCL; return false; } + if ( !i2c->s_scl( i2c, LevelHi ) ) { i2c->err_code = I2CERR_SCL; return false; } + if ( i2c->g_sda( i2c ) == LevelHi ){ *value=(u8)( *value | mask ); }// set the bit + } + i2c->err_code = I2CERR_OK; + + return true; +} + +static bool i2csw_write(struct i2c_sw *i2c, u8 value) +{ + u8 mask; + + // generate bit patterns by setting SCL and SDA lines + for(mask = 0x80; mask > 0; mask = (u8)(mask >> 1)) { + if(!i2c->s_scl( i2c,LevelLow)) {i2c->err_code = I2CERR_SCL; return false;} + + // Send one data bit. + if(value & mask) { // Put data bit on pin. + if(!i2c->s_sda( i2c,LevelHi)) {i2c->err_code = I2CERR_SDA; return false;} + } + else if(!i2c->s_sda( i2c,LevelLow)) {i2c->err_code = I2CERR_SDA; return false;} + + if(!i2c->s_scl( i2c,LevelHi)) {i2c->err_code = I2CERR_SCL; return false;} + } + + return i2csw_wait_ack(i2c); +} + +bool i2csw_init(struct i2c_sw* i2c, long freq) +{ + u32 elapsed = 0L; + u32 start; + volatile u32 i; + + if( !i2c ) { + return false; + } + + i2c->cycle = 10000L; // use a large number to start + + while(elapsed < 5) // loop until delay is long enough for calculation + { + i2c->cycle *= 10; + start = tick_count(); + + for(i = i2c->cycle; i > 0; i--) ; + elapsed = tick_count() - start; + } + + if(freq > 1) + i2c->cycle = i2c->cycle / elapsed * 1000L / freq / 2; + + spin_lock_init(&i2c->slock); + + i2c->err_code = I2CERR_OK; + + return true; +} + +bool i2csw_s_scl(struct i2c_sw *i2c, TLevel scl) +{ + // loop until SCL really changes or timeout + u32 maxWait = 500; // 500 mSec + u32 startTime = tick_count(); + + while(1) + { + // has SCL changed yet? + if(i2c->g_scl(i2c) == scl) + break; + + // timeout? + if(tick_count() - startTime > (u32)maxWait) + { + i2c->err_code = I2CERR_TIMEOUT; + return false; + } + } + + i2csw_bit_delay(i2c); + i2c->err_code = I2CERR_OK; + + return true; +} + +bool i2csw_s_sda(struct i2c_sw *i2c, TLevel sda) +{ + i2csw_bit_delay(i2c); + i2c->err_code = I2CERR_OK; + + return true; +} + +//-------------------------------------------------------- +// 写数据 +//-------------------------------------------------------- +bool i2csw_write_bytes(struct i2c_sw *i2c, u8* data, int length) +{ + int i; + + spin_lock( &i2c->slock ); + + if( i2csw_start(i2c) ) { + for(i = 0; i < length;i++) { + if(!i2csw_write(i2c, *(data+i))) { + break; + } + } + } + + i2csw_stop(i2c); + + spin_unlock( &i2c->slock ); + + return (i2c->err_code==I2CERR_OK); +} + +//-------------------------------------------------------- +// 写数据 +//-------------------------------------------------------- +bool i2csw_write_bytes_nostop(struct i2c_sw *i2c, u8* data, int length) +{ + int i; + + spin_lock( &i2c->slock ); + + if( i2csw_start(i2c) ) { + for(i = 0; i < length;i++) { + if(!i2csw_write(i2c, *(data+i))) { + break; + } + } + } + + spin_unlock( &i2c->slock ); + + return (i2c->err_code==I2CERR_OK); +} + +//-------------------------------------------------------- +// 读数据 +//-------------------------------------------------------- +bool i2csw_read_bytes(struct i2c_sw *i2c, u8 *data_w, int length_w, u8 *data_r, int length_r) +{ + int i; + + spin_lock( &i2c->slock ); + + if( i2csw_start(i2c) ) { + for(i = 0; i < length_w;i++) { + if(!i2csw_write(i2c, *(data_w+i))) { + break; + } + } + + if(i >= length_w) { + for(i = 0; i < length_r; i++) { + if(!i2csw_read(i2c, data_r+i)) { + break; + } + if(i < length_r - 1) { + i2csw_send_ack(i2c); + } + } + i2csw_send_nack(i2c); + } + } + + spin_unlock( &i2c->slock ); + + return (i2c->err_code==I2CERR_OK); +} diff --git a/drivers/pci/pcie/tw6869/i2c-sw.h b/drivers/pci/pcie/tw6869/i2c-sw.h new file mode 100644 index 000000000000..cbb140279fc3 --- /dev/null +++ b/drivers/pci/pcie/tw6869/i2c-sw.h @@ -0,0 +1,60 @@ +/* + * Driver for Techwell TW6864/68 based DVR cards + * + * (c) 2009-10 liran <jlee@techwellinc.com.cn> [Techwell China] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define I2C_WRITE 0 // write operation +#define I2C_READ 1 // read operation + +#define I2CDIV_MAX 15 // I2C maximum divider + +#define TIMEOUT 500 // timeout value (500ms) to wait for an I2C operation + // passing 3 values to I2C takes ~450ms + +typedef enum { LevelLow, LevelHi } TLevel; +enum { + I2CERR_OK = 0, // no error + I2CERR_INIT = 1, // error in initialization + I2CERR_MODE = 2, // invalid I2C mode (must be either HW or SW) + I2CERR_NOACK = 3, // no ACK received from slave + I2CERR_TIMEOUT = 4, // timeout error + I2CERR_SCL = 5, // unable to change SCL line + I2CERR_SDA = 6, // unable to change SDA line + I2CERR_NODEVICE = 9, // the address is error -- for tuner +}; + +struct i2c_sw { + void* chip; + u32 cycle; // software control of frequency + int err_code; + spinlock_t slock; + + bool (*s_scl)(struct i2c_sw *i2c, TLevel); + bool (*s_sda)(struct i2c_sw *i2c, TLevel); + int (*g_scl)(struct i2c_sw *i2c); + int (*g_sda)(struct i2c_sw *i2c); +}; + +bool i2csw_s_scl(struct i2c_sw *i2c, TLevel scl); +bool i2csw_s_sda(struct i2c_sw *i2c, TLevel sda); + +bool i2csw_init(struct i2c_sw* i2c, long freq); +bool i2csw_write_bytes(struct i2c_sw *i2c, u8* data, int length); +bool i2csw_write_bytes_nostop(struct i2c_sw *i2c, u8* data, int length); +bool i2csw_read_bytes(struct i2c_sw *i2c, u8 *data_w, int length_w, u8 *data_r, int length_r); diff --git a/drivers/pci/pcie/tw6869/tw686x-alsa.c b/drivers/pci/pcie/tw6869/tw686x-alsa.c new file mode 100644 index 000000000000..0863aa6cf40a --- /dev/null +++ b/drivers/pci/pcie/tw6869/tw686x-alsa.c @@ -0,0 +1,658 @@ +/* + * + * Driver for Intersil|Techwell TW6869 based DVR cards + * + * (c) 2011-12 liran <jli11@intersil.com> [Intersil|Techwell China] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Thanks to yiliang for variable audio packet length and more audio + * formats support. + */ + +#include <sound/core.h> +#include <sound/control.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <linux/module.h> + +#include "tw686x.h" +#include "tw686x-device.h" +#include "tw686x-reg.h" + +MODULE_DESCRIPTION("alsa driver module for tw6869 based DVR cards"); +MODULE_AUTHOR("liran <jli11@intersil.com>"); +MODULE_LICENSE("GPL"); + +static long tw686x_audio_nr = 0; + +/* + * PCM structure + */ + +typedef struct snd_card_tw686x_pcm { + struct tw686x_adev *dev; + + spinlock_t lock; + + struct snd_pcm_substream *substream; +} snd_card_tw686x_pcm_t; + + +/* + * tw686x audio DMA IRQ handler + * + * Called whenever we get an tw686x audio interrupt + * Handles shifting between the 2 buffers, manages the read counters, + * and notifies ALSA when periods elapse + * + */ + +static void tw686x_alsa_advance_one_period(struct tw686x_adev *dev) +{ + dev->read_offset = dev->period_idx * dev->blksize; + dev->period_idx ++; + if (dev->period_idx == dev->blocks) + dev->period_idx = 0; + + // configure the next DMA buffer + // period_idx - 1, just transferred, + // period_idx, being transferred + // period_idx + 1, next buffer + dev->dma_blk += dev->blksize; + if (dev->period_idx + 1 == dev->blocks) + dev->dma_blk -= dev->bufsize; +} + +void tw686x_alsa_irq(struct tw686x_adev *dev, u32 dma_status, u32 pb_status) +{ + int pb = (pb_status>>(dev->channel_id+TW686X_AUDIO_BEGIN)) & 1; + + //daprintk(DPRT_LEVEL0, dev, "%s(%d)\n", __func__, pb); + + /* next block addr */ + + if(dev->running) + { + /* update status & wake waiting readers */ + tw686x_alsa_advance_one_period(dev); + + if (dev->pb_flag != pb) + { + daprintk(1, dev, "pb flag mismatch %d\n", pb); + + tw686x_dev_set_adma_buffer (dev, dev->dma_blk, dev->pb_flag); + tw686x_alsa_advance_one_period(dev); + dev->pb_flag = 1 - dev->pb_flag; + + snd_pcm_period_elapsed(dev->substream); + } + + tw686x_dev_set_adma_buffer (dev, dev->dma_blk, dev->pb_flag); + dev->pb_flag = 1 - dev->pb_flag; + + snd_pcm_period_elapsed(dev->substream); + } +} + +int tw686x_alsa_resetdma(struct tw686x_chip *chip, u32 dma_cmd) +{ + struct tw686x_adev *dev = NULL; + int i=0; + + for(i=0; i<chip->aud_count; i++) { + if(dma_cmd & (1<<(TW686X_AUDIO_BEGIN+i))) { + dev = chip->aud_dev[i]; + dev->pb_flag = 0; + tw686x_dev_run_audio( dev, true ); + } + } + + return 0; +} + +/* + * ALSA capture trigger + * + * - One of the ALSA capture callbacks. + * + * Called whenever a capture is started or stopped. Must be defined, + * but there's nothing we want to do here + * + */ + +static int snd_card_tw686x_capture_trigger(struct snd_pcm_substream * substream, + int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_card_tw686x_pcm_t *pcm = runtime->private_data; + struct tw686x_adev *dev=pcm->dev; + int err = 0; + unsigned long flags; + + daprintk(DPRT_LEVEL0, dev, "%s(%d)\n", __func__, (cmd == SNDRV_PCM_TRIGGER_START)); + +//liran for test spin_lock(dev->slock); //!!! this lock may cause dead lock + spin_lock_irqsave(dev->slock, flags); + if (cmd == SNDRV_PCM_TRIGGER_START) { + /* start dma */ + dev->pb_flag = 0; + + //将音频格式设置挪到这里 + tw686x_dev_set_audio(dev->chip, runtime->rate, runtime->sample_bits, runtime->channels, dev->blksize); + + tw686x_dev_run_audio(dev, true); + dev->running = true; + } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + /* stop dma */ + tw686x_dev_run_audio(dev, false); + dev->running = false; + } else { + err = -EINVAL; + } + spin_unlock_irqrestore(dev->slock, flags); +//liran for test spin_unlock(dev->slock); + + return err; +} + +/* + * DMA buffer initialization + * + * Uses V4L functions to initialize the DMA. Shouldn't be necessary in + * ALSA, but I was unable to use ALSA's own DMA, and had to force the + * usage of V4L's + * + * - Copied verbatim from saa7134-oss. + * + */ + +static int dsp_buffer_init(struct tw686x_adev *dev) +{ + int err; + + BUG_ON(!dev->bufsize); + + videobuf_dma_init(&dev->dma); + err = videobuf_dma_init_kernel(&dev->dma, PCI_DMA_FROMDEVICE, + (dev->bufsize + PAGE_SIZE) >> PAGE_SHIFT); + return err; +} + +/* + * DMA buffer release + * + * Called after closing the device, during snd_card_tw686x_capture_close + * + */ + +static int dsp_buffer_free(struct tw686x_adev *dev) +{ + BUG_ON(!dev->blksize); + + videobuf_dma_free(&dev->dma); + + dev->blocks = 0; + dev->blksize = 0; + dev->bufsize = 0; + + return 0; +} + +/* + * ALSA PCM preparation + * + * - One of the ALSA capture callbacks. + * + * Called right after the capture device is opened, this function configures + * the buffer using the previously defined functions, allocates the memory, + * sets up the hardware registers, and then starts the DMA. When this function + * returns, the audio should be flowing. + * + */ + +static int snd_card_tw686x_capture_prepare(struct snd_pcm_substream * substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_card_tw686x_t *card_tw686x = snd_pcm_substream_chip(substream); + struct tw686x_adev *dev = card_tw686x->dev; + snd_card_tw686x_pcm_t *pcm = runtime->private_data; + + daprintk(DPRT_LEVEL0, dev, "%s(%d, %d, %d)\n", __func__, runtime->rate, runtime->channels, runtime->sample_bits); + + pcm->dev->substream = substream; + + //liran:: moved this to trigger function, to avoid the audio format changed by system when some channels are capturing. + //tw686x_dev_set_audio(dev->chip, runtime->rate, runtime->sample_bits, runtime->channels, dev->blksize); + + dev->rate = runtime->rate; + + return 0; +} + +/* + * ALSA pointer fetching + * + * - One of the ALSA capture callbacks. + * + * Called whenever a period elapses, it must return the current hardware + * position of the buffer. + * Also resets the read counter used to prevent overruns + * + */ + +static snd_pcm_uframes_t +snd_card_tw686x_capture_pointer(struct snd_pcm_substream * substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_card_tw686x_pcm_t *pcm = runtime->private_data; + struct tw686x_adev *dev=pcm->dev; + + //daprintk(DPRT_LEVEL0, dev, "%s(%d)\n", __func__, dev->read_offset); + + return bytes_to_frames(runtime, dev->read_offset); +} + +/* + * ALSA hardware capabilities definition + */ + +static struct snd_pcm_hardware snd_card_tw686x_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8, + .rates = SNDRV_PCM_RATE_KNOT, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = 4096, + .period_bytes_min = 256, + .period_bytes_max = 1024, + .periods_min = 4, + .periods_max = 8, +}; + +static void snd_card_tw686x_runtime_free(struct snd_pcm_runtime *runtime) +{ + snd_card_tw686x_pcm_t *pcm = runtime->private_data; + struct tw686x_adev *dev = pcm->dev; + + daprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + kfree(pcm); +} + + +/* + * ALSA hardware params + * + * - One of the ALSA capture callbacks. + * + * Called on initialization, right before the PCM preparation + * + */ + +static int snd_card_tw686x_hw_params(struct snd_pcm_substream * substream, + struct snd_pcm_hw_params * hw_params) +{ + snd_card_tw686x_t *card_tw686x = snd_pcm_substream_chip(substream); + struct tw686x_adev *dev = card_tw686x->dev; + unsigned int period_size, periods; + struct scatterlist *list; + int err; + + period_size = params_period_bytes(hw_params); + periods = params_periods(hw_params); + + if (period_size < 0x100 || period_size > 0x10000) + return -EINVAL; + if (periods < 2) + return -EINVAL; + if (period_size * periods > 1024 * 1024) + return -EINVAL; + + daprintk(DPRT_LEVEL0, dev, "%s(bufsize=%d)\n", __func__, period_size * periods); + + if (dev->blocks == periods && + dev->blksize == period_size) + return 0; + + /* release the old buffer */ + if (substream->runtime->dma_area) { +#if(LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)) + videobuf_dma_unmap(&dev->chip->pci->dev, &dev->dma); +#else + videobuf_sg_dma_unmap(&dev->chip->pci->dev, &dev->dma); +#endif + dsp_buffer_free(dev); + substream->runtime->dma_area = NULL; + } + + dev->pb_flag = 0; + dev->period_idx = 0; + dev->blocks = periods; + dev->blksize = period_size; + dev->bufsize = period_size * periods; + dev->running = false; + + err = dsp_buffer_init(dev); + if (0 != err) { + dev->blocks = 0; + dev->blksize = 0; + dev->bufsize = 0; + return err; + } + +#if(LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)) + if (0 != (err = videobuf_dma_map(&dev->chip->pci->dev, &dev->dma))) { +#else + if (0 != (err = videobuf_sg_dma_map(&dev->chip->pci->dev, &dev->dma))) { +#endif + dsp_buffer_free(dev); + return err; + } + + list = dev->dma.sglist; + + daprintk(DPRT_LEVEL0, dev, "%s_%d: period_size %d, periods %d, sglen %d.\n", + __func__, + dev->channel_id, + period_size, + periods, + dev->dma.sglen); + + dev->dma_blk = cpu_to_le32(sg_dma_address(list) - list->offset); + tw686x_dev_set_adma_buffer (dev, dev->dma_blk, 0); + + dev->dma_blk += dev->blksize; + tw686x_dev_set_adma_buffer (dev, dev->dma_blk, 1); + + /* I should be able to use runtime->dma_addr in the control + byte, but it doesn't work. So I allocate the DMA using the + V4L functions, and force ALSA to use that as the DMA area */ + +#if(LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)) + substream->runtime->dma_area = dev->dma.vaddr; +#else + substream->runtime->dma_area = dev->dma.vmalloc; +#endif + substream->runtime->dma_bytes = dev->bufsize; + substream->runtime->dma_addr = 0; + + return 0; +} + +/* + * ALSA hardware release + * + * - One of the ALSA capture callbacks. + * + * Called after closing the device, but before snd_card_tw686x_capture_close + * It stops the DMA audio and releases the buffers. + * + */ + +static int snd_card_tw686x_hw_free(struct snd_pcm_substream * substream) +{ + snd_card_tw686x_t *card_tw686x = snd_pcm_substream_chip(substream); + struct tw686x_adev *dev; + + dev = card_tw686x->dev; + + daprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + if (substream->runtime->dma_area) { +#if(LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)) + videobuf_dma_unmap(&dev->chip->pci->dev, &dev->dma); +#else + videobuf_sg_dma_unmap(&dev->chip->pci->dev, &dev->dma); +#endif + dsp_buffer_free(dev); + substream->runtime->dma_area = NULL; + } + + return 0; +} + +/* + * ALSA capture finish + * + * - One of the ALSA capture callbacks. + * + * Called after closing the device. + * + */ + +static int snd_card_tw686x_capture_close(struct snd_pcm_substream * substream) +{ + snd_card_tw686x_t *card_tw686x = snd_pcm_substream_chip(substream); + struct tw686x_adev *dev = card_tw686x->dev; + + daprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + if (card_tw686x->mute_was_on) { + } + return 0; +} + +/* + * ALSA capture start + * + * - One of the ALSA capture callbacks. + * + * Called when opening the device. It creates and populates the PCM + * structure + * + */ + +static int snd_card_tw686x_capture_open(struct snd_pcm_substream * substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_card_tw686x_pcm_t *pcm; + snd_card_tw686x_t *card_tw686x = snd_pcm_substream_chip(substream); + struct tw686x_adev *dev; + int err; + + if (!card_tw686x) { + printk(KERN_ERR "BUG: tw686x can't find device struct." + " Can't proceed with open\n"); + return -ENODEV; + } + dev = card_tw686x->dev; + + daprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + mutex_lock(&dev->lock); + + dev->read_offset = 0; + + mutex_unlock(&dev->lock); + + pcm = kzalloc(sizeof(*pcm), GFP_KERNEL); + if (pcm == NULL) + return -ENOMEM; + + pcm->dev=dev; + + spin_lock_init(&pcm->lock); + + pcm->substream = substream; + runtime->private_data = pcm; + runtime->private_free = snd_card_tw686x_runtime_free; + runtime->hw = snd_card_tw686x_capture; + + err = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIODS, 2); + if (err < 0) + return err; + + return 0; +} + +/* + * page callback (needed for mmap) + */ + +static struct page *snd_card_tw686x_page(struct snd_pcm_substream *substream, + unsigned long offset) +{ + void *pageptr = substream->runtime->dma_area + offset; + + snd_card_tw686x_t *card_tw686x = snd_pcm_substream_chip(substream); + struct tw686x_adev *dev = card_tw686x->dev; + + daprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + return vmalloc_to_page(pageptr); +} + +/* + * ALSA capture callbacks definition + */ + +static struct snd_pcm_ops snd_card_tw686x_capture_ops = { + .open = snd_card_tw686x_capture_open, + .close = snd_card_tw686x_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_card_tw686x_hw_params, + .hw_free = snd_card_tw686x_hw_free, + .prepare = snd_card_tw686x_capture_prepare, + .trigger = snd_card_tw686x_capture_trigger, + .pointer = snd_card_tw686x_capture_pointer, + .page = snd_card_tw686x_page, +}; + +/* + * ALSA PCM setup + * + * Called when initializing the board. Sets up the name and hooks up + * the callbacks + * + */ + +static int snd_card_tw686x_pcm(snd_card_tw686x_t *card_tw686x, long device) +{ + struct snd_pcm *pcm; + int err; + + daprintk(DPRT_LEVEL0, card_tw686x->dev, "%s()\n", __func__); + + if ((err = snd_pcm_new(card_tw686x->card, "TW6869 PCM", device, 0, 1, &pcm)) < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_card_tw686x_capture_ops); + pcm->private_data = card_tw686x; + pcm->info_flags = 0; + strcpy(pcm->name, "TW6869 PCM"); + + return 0; +} + +static void snd_tw686x_free(struct snd_card * card) +{ + snd_card_tw686x_t *card_tw686x = (snd_card_tw686x_t*)card->private_data; + struct tw686x_adev* dev = card_tw686x->dev; + + daprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); +} + +/* + * ALSA initialization + * + * Called by the init routine, once for each tw686x device present, + * it creates the basic structures and registers the ALSA devices + * + */ + +int tw686x_alsa_create(struct tw686x_adev *dev) +{ + + struct snd_card *card = NULL; + snd_card_tw686x_t *card_tw686x; + int err; + + daprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + if(tw686x_audio_nr > (SNDRV_CARDS-2)) + return -ENODEV; + +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)) + err = snd_card_create(tw686x_audio_nr+1, NULL, THIS_MODULE, + sizeof(snd_card_tw686x_t), &card); + if (err < 0) + return err; +#else + card = snd_card_new(-2, NULL, THIS_MODULE, sizeof(snd_card_tw686x_t)); + if (card == NULL) + return -ENOMEM; +#endif + + strcpy(card->driver, "TW6869"); + + /* Card "creation" */ + + card->private_free = snd_tw686x_free; + card_tw686x = (snd_card_tw686x_t *) card->private_data; + + spin_lock_init(&card_tw686x->lock); + + card_tw686x->dev = dev; + card_tw686x->card= card; + dev->card = card_tw686x; + + mutex_init(&dev->lock); + + if ((err = snd_card_tw686x_pcm(card_tw686x, 0)) < 0) + goto __nodev; + + snd_card_set_dev(card, &dev->chip->pci->dev); + + /* End of "creation" */ + + strcpy(card->shortname, "TW6869"); + sprintf(card->longname, "%s at 0x%p irq %d", + dev->chip->name, dev->chip->bmmio, dev->chip->pci->irq); + + daprintk(1, dev, "alsa: %s registered as card %d\n",card->longname,dev->channel_id); + + if ((err = snd_card_register(card)) == 0) { + tw686x_audio_nr++; + return 0; + } + +__nodev: + snd_card_free(card); + return err; +} + +int tw686x_alsa_free(struct tw686x_adev *dev) +{ + if(dev->card) { + snd_card_free(dev->card->card); + dev->card = NULL; + } + + tw686x_audio_nr--; + + return 1; +} diff --git a/drivers/pci/pcie/tw6869/tw686x-audio.c b/drivers/pci/pcie/tw6869/tw686x-audio.c new file mode 100644 index 000000000000..2c2df98f5a91 --- /dev/null +++ b/drivers/pci/pcie/tw6869/tw686x-audio.c @@ -0,0 +1,300 @@ +/* + * + * Driver for Intersil|Techwell TW6869 based DVR cards + * + * (c) 2011-12 liran <jli11@intersil.com> [Intersil|Techwell China] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Thanks to yiliang for variable audio packet length and more audio + * formats support. + */ + +#include <linux/module.h> +#include "tw686x.h" +#include "tw686x-device.h" +#include "tw686x-reg.h" + +#define TW686X_AUDIO_PERIOD_SIZE 4096 + +MODULE_DESCRIPTION("audio driver module for tw6869 based DVR cards"); +MODULE_AUTHOR("liran <jli11@intersil.com>"); +MODULE_LICENSE("GPL"); + +static int tw686x_audio_buffer_init(struct tw686x_adev *dev) +{ + int err; + + BUG_ON(!dev->bufsize); + + videobuf_dma_init(&dev->dma); + err = videobuf_dma_init_kernel(&dev->dma, PCI_DMA_FROMDEVICE, + (dev->bufsize + PAGE_SIZE) >> PAGE_SHIFT); + return err; +} + +/* + * DMA buffer release + * + * Called after closing the device, during snd_card_tw686x_capture_close + * + */ + +static int tw686x_audio_buffer_free(struct tw686x_adev *dev) +{ + BUG_ON(!dev->blksize); + + videobuf_dma_free(&dev->dma); + + dev->blocks = 0; + dev->blksize = 0; + dev->bufsize = 0; + + return 0; +} + +//static +int tw686x_audio_trigger(struct tw686x_adev *dev, int cmd) +{ + unsigned int dma_addr; + struct scatterlist *list; + + daprintk(DPRT_LEVEL0, dev, "%s(%d)\n", __func__, cmd); + + if(cmd) + { + dev->pb_flag = 0; + + list = dev->dma.sglist + dev->dma_blk; + + dma_addr = cpu_to_le32(sg_dma_address(list) - list->offset); + tw686x_dev_set_adma_buffer (dev, dma_addr, 0); + + dev->dma_blk++; + if(dev->dma_blk >= dev->blocks) + { + dev->dma_blk = 0; + } + + list = dev->dma.sglist + dev->dma_blk; + dma_addr = cpu_to_le32(sg_dma_address(list) - list->offset); + tw686x_dev_set_adma_buffer (dev, dma_addr, 1); + + dev->dma_blk++; + if(dev->dma_blk >= dev->blocks) + { + dev->dma_blk = 0; + } + } + + /* start dma */ + dev->substream = (struct snd_pcm_substream*)(NULL+cmd); + tw686x_dev_run_audio(dev, cmd>0); + + return 0; +} + +static void tw686x_audio_advance_one_period(struct tw686x_adev *dev) +{ + dev->dma_blk++; + if(dev->dma_blk >= dev->blocks) + dev->dma_blk = 0; + + dev->period_idx++; + if(dev->period_idx >= dev->blocks) + { + tw686x_audio_trigger(dev, 0); + } +} + +void tw686x_audio_irq(struct tw686x_adev *dev, u32 dma_status, u32 pb_status) +{ + int pb = (pb_status>>(dev->channel_id+TW686X_AUDIO_BEGIN)) & 1; + unsigned int dma_addr; + struct scatterlist *list; + + daprintk(DPRT_LEVEL0, dev, "%s(%d)\n", __func__, pb); + + /* next block addr */ + + /* update status & wake waiting readers */ + tw686x_audio_advance_one_period(dev); + + list = dev->dma.sglist + dev->dma_blk; + dma_addr = cpu_to_le32(sg_dma_address(list) - list->offset); + + if (dev->pb_flag != pb) + { + daprintk(1, dev, "pb flag mismatch\n"); + + tw686x_dev_set_adma_buffer (dev, dma_addr, dev->pb_flag); + tw686x_audio_advance_one_period(dev); + dev->pb_flag = 1 - dev->pb_flag; + + list = dev->dma.sglist + dev->dma_blk; + dma_addr = cpu_to_le32(sg_dma_address(list) - list->offset); + + //snd_pcm_period_elapsed(dev->substream); + } + + tw686x_dev_set_adma_buffer (dev, dma_addr, dev->pb_flag); + dev->pb_flag = 1 - dev->pb_flag; + + //snd_pcm_period_elapsed(dev->substream); +} + +int tw686x_audio_param(struct tw686x_adev *dev, struct tw686x_aparam* ap) +{ + unsigned long flags; + daprintk(DPRT_LEVEL0, dev, "%s(%d %d %d %d)\n", __func__, ap->sample_rate, ap->sample_bits, ap->channels, ap->blksize); + + spin_lock_irqsave(dev->slock, flags); + + dev->period_idx = 0; + dev->read_offset = 0; + dev->substream = NULL; + dev->dma_blk = 0; + + if(ap->blksize < 0) + { + tw686x_audio_trigger(dev, 0); + } + else + { + ap->blksize = min(ap->blksize, TW686X_AUDIO_PERIOD_SIZE); + tw686x_dev_set_audio(dev->chip, ap->sample_rate, ap->sample_bits, ap->channels, ap->blksize); + dev->blksize = ap->blksize; + } + + spin_unlock_irqrestore(dev->slock, flags); + + return 0; +} + +int tw686x_audio_data(struct tw686x_adev *dev, struct tw686x_adata *ad) +{ + unsigned long flags; + struct scatterlist *list; + + spin_lock_irqsave(dev->slock, flags); + + if(dev->period_idx > 0) + { + list = dev->dma.sglist + dev->read_offset; + ad->len = min(ad->len, (int)dev->blksize); + memcpy(ad->data, sg_virt(list), ad->len); + + dev->period_idx--; + dev->read_offset ++; + if (dev->read_offset >= dev->blocks) + dev->read_offset = 0; + } + else + { + ad->len = 0; + } + if((dev->period_idx<(dev->blocks-1)) && (dev->substream==NULL)) + { + tw686x_audio_trigger(dev, 1); + } + + spin_unlock_irqrestore(dev->slock, flags); + + return 0; +} + +int tw686x_audio_resetdma(struct tw686x_chip *chip, u32 dma_cmd) +{ +#if(0) + struct tw686x_adev *dev = NULL; + int i=0; + + for(i=0; i<chip->aud_count; i++) { + if(dma_cmd & (1<<(TW686X_AUDIO_BEGIN+i))) { + dev = chip->aud_dev[i]; + dev->period_idx = 0; + dev->read_offset = 0; + dev->substream = NULL; + dev->dma_blk = 0; + tw686x_audio_trigger( dev, 1 ); + } + } +#endif + return 0; +} + +int tw686x_audio_create(struct tw686x_adev *dev) +{ + unsigned int period_size, periods; + int err; + + daprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + mutex_init(&dev->lock); + + period_size = TW686X_AUDIO_PERIOD_SIZE; //固定为4096 + periods = 4; //最小为2 + + daprintk(DPRT_LEVEL0, dev, "%s(bufsize=%d)\n", __func__, period_size * periods); + + dev->blocks = periods; + dev->blksize = period_size; + dev->bufsize = period_size * periods; + dev->period_idx = 0; + dev->read_offset = 0; + dev->substream = NULL; + dev->card = NULL; + + err = tw686x_audio_buffer_init(dev); + if (0 != err) { + dev->blocks = 0; + dev->blksize = 0; + dev->bufsize = 0; + return err; + } + +#if(LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)) + if (0 != (err = videobuf_dma_map(&dev->chip->pci->dev, &dev->dma))) { +#else + if (0 != (err = videobuf_sg_dma_map(&dev->chip->pci->dev, &dev->dma))) { +#endif + tw686x_audio_buffer_free(dev); + return err; + } + + daprintk(DPRT_LEVEL0, dev, "%s_%d: period_size %d, periods %d, sglen %d.\n", + __func__, + dev->channel_id, + period_size, + periods, + dev->dma.sglen); + + return 0; +} + +int tw686x_audio_free(struct tw686x_adev *dev) +{ + if (dev->substream != NULL) { +#if(LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)) + videobuf_dma_unmap(&dev->chip->pci->dev, &dev->dma); +#else + videobuf_sg_dma_unmap(&dev->chip->pci->dev, &dev->dma); +#endif + tw686x_audio_buffer_free(dev); + dev->substream = NULL; + } + + return 0; +} diff --git a/drivers/pci/pcie/tw6869/tw686x-core.c b/drivers/pci/pcie/tw6869/tw686x-core.c new file mode 100644 index 000000000000..61a358769871 --- /dev/null +++ b/drivers/pci/pcie/tw6869/tw686x-core.c @@ -0,0 +1,578 @@ +/* + * + * Driver for Techwell TW6864/68 based DVR cards + * + * (c) 2009-10 liran <jlee@techwellinc.com.cn> [Techwell China] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kmod.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <asm/div64.h> + +#include "tw686x-reg.h" +#include "tw686x.h" +#include "tw686x-device.h" + +MODULE_DESCRIPTION("Driver for tw6864/68 based DVR cards"); +MODULE_AUTHOR("liran<jli11@intersil.com>"); +MODULE_LICENSE("GPL"); + +unsigned int tw686x_debug; +module_param(tw686x_debug, int, 0644); +MODULE_PARM_DESC(tw686x_debug, "enable debug messages"); + +unsigned int tw686x_dmamode = TW686X_DMA_MODE_BLOCK; +module_param(tw686x_dmamode, int, 0644); +MODULE_PARM_DESC(tw686x_dmamode, "tw686x dma mode"); + +unsigned int tw686x_audio = 1; +module_param(tw686x_audio, int, 0644); +MODULE_PARM_DESC(tw686x_audio, "tw686x audio enable"); + +unsigned int tw686x_restart_timer = 1; +module_param(tw686x_restart_timer, int, 0644); +MODULE_PARM_DESC(tw686x_restart_timer, "tw686x dma restart timer enable"); + +unsigned int tw686x_bcsi = 0; +module_param(tw686x_bcsi, int, 0644); +MODULE_PARM_DESC(tw686x_bcsi, "tw686x driver for BCSI enable"); + +//zxx_20120712 added for TW6865 +unsigned int tw686x_6865 = 0; +module_param(tw686x_6865, int, 0644); +MODULE_PARM_DESC(tw686x_6865, "tw6865 mode"); + +static unsigned int tw686x_chipcount = 0; + +DEFINE_MUTEX(tw686x_chiplist_lock); +LIST_HEAD(tw686x_chiplist); + +void tw686x_dmabuf_free(struct tw686x_chip *chip, + struct tw686x_dmabuf *buf) +{ + struct pci_dev *pci = chip->pci; + + if (NULL == buf->cpu) + return; + pci_free_consistent(pci, buf->size, buf->cpu, buf->dma); + + dprintk(DPRT_LEVEL0, chip, " dmamem free dma=%lx cpu=%p size=%d\n", + (unsigned long)buf->dma, buf->cpu, buf->size); + + memset(buf,0,sizeof(*buf)); +} + +int tw686x_dmabuf_alloc(struct tw686x_chip *chip, + struct tw686x_dmabuf *buf, + unsigned int size) +{ + struct pci_dev *pci = chip->pci; + __le32 *cpu; + dma_addr_t dma = 0; + + if (NULL != buf->cpu && buf->size < size) + tw686x_dmabuf_free(chip,buf); + if (NULL == buf->cpu) { + size = PAGE_ALIGN(size); + cpu = pci_alloc_consistent(pci, size, &dma); + if (NULL == cpu) { + dprintk(DPRT_LEVEL0, chip, "dmamem alloc failed size=%d\n", size); + return -ENOMEM; + } + buf->cpu = cpu; + buf->dma = dma; + buf->size = size; + dprintk(DPRT_LEVEL0, chip, "dmamem alloc dma=%lx cpu=%p size=%d\n", + (unsigned long)dma, cpu, size); + } + memset(buf->cpu,0,buf->size); + return 0; +} + +static void tw686x_shutdown(struct tw686x_chip *chip) +{ + /* Disable Video DMA */ + /* Disable Audio activity */ + /* Disable Interrupts */ + + tw686x_dev_uninit( chip ); +} + +static void tw686x_reset(struct tw686x_chip *chip) +{ + dprintk(1, chip, "%s()\n", __func__); + + tw686x_dev_uninit( chip ); + tw686x_dev_init( chip ); +} + +static int get_resources(struct tw686x_chip *chip) +{ + if (request_mem_region(pci_resource_start(chip->pci, 0), + pci_resource_len(chip->pci, 0), + chip->name)) + return 0; + + printk(KERN_ERR "%s: can't get MMIO memory @ 0x%llx\n", + chip->name, (unsigned long long)pci_resource_start(chip->pci, 0)); + + return -EBUSY; +} + +static void tw686x_assist_timeout(unsigned long data) +{ + struct tw686x_chip* chip = (struct tw686x_chip*)data; + int i=0; + + u32 tmp = tw_read(TW6864_R_DMA_CMD); + u32 tmp1 = tw_read(TW6864_R_DMA_CHANNEL_ENABLE); + dprintk(DPRT_LEVEL0, chip, "%s(), %x %x %x\n", __func__, tmp, tmp1, chip->dma_restart); + + if(chip->dma_restart > 0) + { + unsigned long flags; + struct tw686x_vdev* dev; + struct tw686x_adev* adev; + + tw_write(TW6864_R_DMA_CMD, 0); + + spin_lock_irqsave(&chip->slock, flags); + for(i=0; i<chip->vid_count; i++) { + dev = chip->vid_dev[i]; + if(chip->dma_restart & (1<<i)) { + dev->dma_started = false; + dev->pb_next = 0; + dev->pb_curr = 0; + tw686x_video_start(dev); + } + } + if(CaptureFLV) + { + for(i=0; i<chip->aud_count; i++) { + if(chip->dma_restart & (1<<(TW686X_AUDIO_BEGIN+i))) { + adev = chip->aud_dev[i]; + if(adev->running) + { + adev->period_idx = 0; + adev->read_offset = 0; + adev->substream = NULL; + adev->dma_blk = 0; + tw686x_audio_trigger( adev, 1 ); + } + } + } + } + else + { + for(i=0; i<chip->aud_count; i++) { + if(chip->dma_restart & (1<<(TW686X_AUDIO_BEGIN+i))) { + adev = chip->aud_dev[i]; + if(adev->running) + { + adev->pb_flag = 0; + tw686x_dev_run_audio(adev, true); + } + } + } + } + chip->dma_restart = 0; + spin_unlock_irqrestore(&chip->slock, flags); + } +#if(0) + else + { + for(i=0; i<chip->vid_count; i++) { + tw686x_video_status(chip->vid_dev[i]); + } + } + + mod_timer(&chip->tm_assist, jiffies+BUFFER_TIMEOUT*2); +#endif +} + +static int tw686x_setup(struct tw686x_chip *chip) +{ + struct tw686x_vdev *vdev; + struct tw686x_adev *adev; + int i = 0; + + chip->nr = tw686x_chipcount++; + sprintf(chip->name, "tw6869[%d]", chip->nr); + + mutex_lock(&tw686x_chiplist_lock); + list_add_tail(&chip->chiplist, &tw686x_chiplist); + mutex_unlock(&tw686x_chiplist_lock); + + if (get_resources(chip) < 0) { + printk(KERN_ERR "%s No more PCIe resources for " + "subsystem: %04x:%04x\n", + chip->name, chip->pci->subsystem_vendor, + chip->pci->subsystem_device); + + tw686x_chipcount--; + return -ENODEV; + } + + /* PCIe stuff */ + chip->bmmio = ioremap(pci_resource_start(chip->pci, 0), + pci_resource_len(chip->pci, 0)); + + printk(KERN_INFO "%s: subsystem: %04x:%04x\n", + chip->name, chip->pci->subsystem_vendor, + chip->pci->subsystem_device); + + //zxx_20120712 added for TW6865 + int nSubDevNum = TW686X_DECODER_COUNT; + if (tw686x_6865) { + nSubDevNum = TW686X_DECODER_COUNT / 2; + } + + chip->vid_count = nSubDevNum; + + /* init hardware */ + tw686x_reset(chip); + chip->aud_count = chip->vid_count; + + for( i=0; i<chip->vid_count; i++ ) { + vdev = kzalloc(sizeof(struct tw686x_vdev), GFP_KERNEL); + if (NULL == vdev) { + return -ENOMEM; + } + + vdev->chip = chip; + vdev->channel_id = i; + vdev->slock= &chip->slock; + chip->vid_dev[i]= vdev; + + if (tw686x_video_register(vdev) < 0) { + printk(KERN_ERR "%s: %s() Failed to register " + "video adapters %d\n", chip->name, __func__, i); + return -ENOMEM; + } + + if( tw686x_dmabuf_alloc(chip, &vdev->dma_desc[TW686X_DMA_DESC_P], TW686X_DMA_DESC_LEN)<0 ) { + printk(KERN_INFO "%s: allocate dma p-buffer descriptor memory failed\n", chip->name); + return -ENOMEM; + } + if( tw686x_dmabuf_alloc(chip, &vdev->dma_desc[TW686X_DMA_DESC_B], TW686X_DMA_DESC_LEN)<0 ) { + printk(KERN_INFO "%s: allocate dma b-buffer descriptor memory failed\n", chip->name); + return -ENOMEM; + } + tw686x_dev_set_pbdesc(vdev); + } + + if(tw686x_audio) { + for( i=0; i<chip->aud_count; i++ ) { + adev = kzalloc(sizeof(struct tw686x_adev), GFP_KERNEL); + if (NULL == adev) { + return -ENOMEM; + } + + adev->chip = chip; + adev->channel_id = i; + adev->slock= &chip->slock; + chip->aud_dev[i]= adev; + + if(CaptureFLV) + { + if (tw686x_audio_create(adev) < 0) { + printk(KERN_ERR "%s: %s() Failed to create " + "audio adapters %d\n", chip->name, __func__, i); + kfree( adev ); + chip->aud_dev[i] = NULL; + break; + } + } + else + { + if (tw686x_alsa_create(adev) < 0) { + printk(KERN_ERR "%s: %s() Failed to create " + "audio adapters %d\n", chip->name, __func__, i); + kfree( adev ); + chip->aud_dev[i] = NULL; + break; + } + } + } + } + + if(chip->vid_count > 0) { + chip->tm_assist.function = tw686x_assist_timeout; + chip->tm_assist.data = (unsigned long)chip; + init_timer(&chip->tm_assist); + //mod_timer(&chip->tm_assist, jiffies+BUFFER_TIMEOUT*2); + } + chip->dma_restart = 0; + + return 0; +} + +static void tw686x_unregister(struct tw686x_chip *chip) +{ + int i=0; + + release_mem_region(pci_resource_start(chip->pci, 0), + pci_resource_len(chip->pci, 0)); + + if(chip->vid_count > 0) { + chip->dma_restart = 0; + del_timer(&chip->tm_assist); + } + + for( i=0; i<chip->vid_count; i++ ) { + if(chip->vid_dev[i]) { + tw686x_video_unregister( chip->vid_dev[i] ); + tw686x_dmabuf_free( chip, &chip->vid_dev[i]->dma_desc[TW686X_DMA_DESC_P] ); + tw686x_dmabuf_free( chip, &chip->vid_dev[i]->dma_desc[TW686X_DMA_DESC_B] ); + kfree( chip->vid_dev[i] ); + chip->vid_dev[i] = NULL; + } + } + + for( i=0; i<chip->aud_count; i++ ) { + if(chip->aud_dev[i]) { + if(CaptureFLV) + { + tw686x_audio_free( chip->aud_dev[i] ); + } + else + { + tw686x_alsa_free( chip->aud_dev[i] ); + } + kfree( chip->aud_dev[i] ); + chip->aud_dev[i] = NULL; + } + } + + iounmap(chip->bmmio); +} + +static irqreturn_t tw686x_irq(int irq, void *chip_id) +{ + struct tw686x_chip *chip = chip_id; + struct tw686x_vdev *vdev = NULL; + struct tw686x_adev *adev = NULL; + int handled = 0; + int i; + u32 dma_status, pb_status, dma_error, dma_cmd; + + dma_status = tw_read( TW6864_R_DMA_INT_STATUS ); + pb_status = tw_read( TW6864_R_DMA_PB_STATUS ); + dma_error = tw_read( TW6864_R_DMA_INT_ERROR ); + dma_cmd = tw_read( TW6864_R_DMA_CMD ); + + if( (dma_cmd & TW6864_R_DMA_CMD_RESETALL) && (dma_status || dma_error) ) { + spin_lock(&chip->slock); + + for( i=0; i<chip->vid_count; i++ ) { + if( (dma_status & TW6864_R_DMA_INT_STATUS_DMA(i))/* || (dma_error & TW6864_R_DMA_VINT_FIFO(i))*/ ) { + vdev = chip->vid_dev[i]; + tw686x_video_irq( vdev, dma_status, pb_status ); + } + else if( dma_error & TW6864_R_DMA_VINT_FIFO(i) ) { + vdev = chip->vid_dev[i]; + tw686x_dev_run( vdev, false ); + //dvprintk(DPRT_LEVEL0, vdev, "irq %x %x\n", dma_status, dma_error); + } + } + for( i=0; i<chip->aud_count; i++ ) { + if( dma_status & TW6864_R_DMA_INT_STATUS_DMA(i+TW686X_AUDIO_BEGIN) ) { + adev = chip->aud_dev[i]; +#if(CaptureFLV) + tw686x_audio_irq( adev, dma_status, pb_status ); +#else + tw686x_alsa_irq( adev, dma_status, pb_status ); +#endif + } + } + + if( dma_status & TW6864_R_DMA_INT_STATUS_TOUT ) + dprintk(DPRT_LEVEL0, chip, "irq timeout %x %x %x\n", dma_status, dma_error, dma_cmd); + + if( dma_status & TW6864_R_DMA_INT_STATUS_TOUT ) + { + //DMA time out + tw686x_dev_setdma(chip, 0); + tw686x_video_resetdma( chip, dma_cmd ); +#if(CaptureFLV) + //tw686x_audio_resetdma( chip, dma_cmd ); +#else + //tw686x_alsa_resetdma( chip, dma_cmd ); +#endif + } + + handled = 1; + spin_unlock(&chip->slock); + } + + return IRQ_RETVAL(handled); +} + +static int __devinit tw686x_initchip(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct tw686x_chip *chip; + int err; + u8 pci_rev,pci_lat; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (NULL == chip) + return -ENOMEM; + + spin_lock_init(&chip->slock); + +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)) + err = v4l2_device_register(&pci_dev->dev, &chip->v4l2_dev); + if (err < 0) + goto fail_free; +#endif + + /* pci init */ + chip->pci = pci_dev; + if (pci_enable_device(pci_dev)) { + err = -EIO; + goto fail_unreg; + } + + if (tw686x_setup(chip) < 0) { + err = -EINVAL; + goto fail_unreg; + } + + /* print pci info */ + pci_read_config_byte(pci_dev, PCI_CLASS_REVISION, &pci_rev); + pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &pci_lat); + printk(KERN_INFO "%s: found at %s, bus: %d, rev: %d, irq: %d, " + "latency: %d, mmio: 0x%llx\n", chip->name, + pci_name(pci_dev), pci_dev->bus->number, pci_rev, pci_dev->irq, pci_lat, + (unsigned long long)pci_resource_start(pci_dev, 0)); + + pci_set_master(pci_dev); + if (!pci_dma_supported(pci_dev, 0xffffffff)) { + printk("%s: Oops: no 32bit PCI DMA ???\n", chip->name); + err = -EIO; + goto fail_irq; + } + + err = request_irq(pci_dev->irq, tw686x_irq, + IRQF_SHARED | IRQF_DISABLED, chip->name, chip); + if (err < 0) { + printk(KERN_ERR "%s: can't get IRQ %d\n", + chip->name, pci_dev->irq); + goto fail_irq; + } + +#if(LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,30)) + pci_set_drvdata(pci_dev, chip); +#endif + + return 0; + +fail_irq: + tw686x_unregister(chip); +fail_unreg: +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)) + v4l2_device_unregister(&chip->v4l2_dev); +fail_free: +#endif + kfree(chip); + return err; +} + +static void __devexit tw686x_finichip(struct pci_dev *pci_dev) +{ +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)) + struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); + struct tw686x_chip *chip = to_tw686x(v4l2_dev); +#else + struct tw686x_chip *chip = pci_get_drvdata(pci_dev); +#endif + + tw686x_shutdown(chip); + + pci_disable_device(pci_dev); +#if(LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,30)) + pci_set_drvdata(pci_dev, NULL); +#endif + + /* unregister stuff */ + free_irq(pci_dev->irq, chip); + + mutex_lock(&tw686x_chiplist_lock); + list_del(&chip->chiplist); + mutex_unlock(&tw686x_chiplist_lock); + + tw686x_unregister(chip); +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)) + v4l2_device_unregister(v4l2_dev); +#endif + kfree(chip); +} + +static struct pci_device_id tw686x_pci_tbl[] = { + { + /* TW6869 */ + .vendor = 0x1797, + .device = 0x6869, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, { + /* --- end of list --- */ + } +}; +MODULE_DEVICE_TABLE(pci, tw686x_pci_tbl); + +static struct pci_driver tw686x_pci_driver = { + .name = "tw6869", + .id_table = tw686x_pci_tbl, + .probe = tw686x_initchip, + .remove = __devexit_p(tw686x_finichip), + /* TODO */ + .suspend = NULL, + .resume = NULL, +}; + +static int tw686x_init(void) +{ + printk(KERN_INFO "tw6869 driver version %d.%d.%d loaded\n", + (TW686X_VERSION_CODE >> 16) & 0xff, + (TW686X_VERSION_CODE >> 8) & 0xff, + TW686X_VERSION_CODE & 0xff); + printk(KERN_INFO "tw6869 driver dma_mode=%x, debug=%x\n", tw686x_dmamode, tw686x_debug); + + return pci_register_driver(&tw686x_pci_driver); +} + +static void tw686x_fini(void) +{ + pci_unregister_driver(&tw686x_pci_driver); + + printk(KERN_INFO "tw6869 driver version %d.%d.%d unloaded\n", + (TW686X_VERSION_CODE >> 16) & 0xff, + (TW686X_VERSION_CODE >> 8) & 0xff, + TW686X_VERSION_CODE & 0xff); +} + +module_init(tw686x_init); +module_exit(tw686x_fini); diff --git a/drivers/pci/pcie/tw6869/tw686x-device.c b/drivers/pci/pcie/tw6869/tw686x-device.c new file mode 100644 index 000000000000..5c2c064c86df --- /dev/null +++ b/drivers/pci/pcie/tw6869/tw686x-device.c @@ -0,0 +1,1172 @@ +/* + * Driver for Techwell TW6864/68 based DVR cards + * + * (c) 2009-10 liran <jlee@techwellinc.com.cn> [Techwell China] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/videodev2.h> +#include <linux/delay.h> +#include <asm/div64.h> + +#include "tw686x.h" +#include "tw686x-reg.h" +#include "tw686x-device.h" + +int tw686x_dev_init( struct tw686x_chip *chip ) +{ + u32 tmp; + int i = 0; + u32 page_table_p[TW6864_MAX_NUM_SG_DMA] = { + TW6869_R_DMA_PAGE_TABLE0, TW6869_R_DMA_PAGE_TABLE2, TW6869_R_DMA_PAGE_TABLE4, TW6869_R_DMA_PAGE_TABLE6, + TW6869_R_DMA_PAGE_TABLE8, TW6869_R_DMA_PAGE_TABLEA, TW6869_R_DMA_PAGE_TABLEC, TW6869_R_DMA_PAGE_TABLEE + }; + u32 page_table_b[TW6864_MAX_NUM_SG_DMA] = { + TW6869_R_DMA_PAGE_TABLE1, TW6869_R_DMA_PAGE_TABLE3, TW6869_R_DMA_PAGE_TABLE5, TW6869_R_DMA_PAGE_TABLE7, + TW6869_R_DMA_PAGE_TABLE9, TW6869_R_DMA_PAGE_TABLEB, TW6869_R_DMA_PAGE_TABLED, TW6869_R_DMA_PAGE_TABLEF + }; + + dprintk(DPRT_LEVEL0, chip, "%s()\n", __func__); + + if( pci_read_config_dword( chip->pci, 0x04, &tmp) == 0) { + tmp |= 7; + pci_write_config_dword( chip->pci, 0x04, tmp); + } + + if( pci_read_config_dword( chip->pci, 0x78, &tmp) == 0) { + tmp = (0x8<<5) | (tmp & 0xFFFFFE1F); //8=128B, 9=256B, A=512B, B=1024B + pci_write_config_dword( chip->pci, 0x78, tmp); + } + + //Software Reset + tw_write(TW6864_R_SYS_SOFT_RST, 0x01); + tw_write(TW6864_R_SYS_SOFT_RST, 0x0F); + + for(i=0;i<TW6864_MAX_NUM_SG_DMA;i++) { + //S&G, Common Buffer DMA memory setup + tw_write(page_table_p[i], 0x00000000); + tw_write(page_table_b[i], 0x00000000); + } + + for(tmp=0; tmp<TW6864_MAX_NUM_DATA_DMA; tmp++) { + tw_write(TW6864_R_CH8to15_CONFIG_REG_P(tmp),0); + tw_write(TW6864_R_CH8to15_CONFIG_REG_B(tmp),0); + } + + if( pci_read_config_dword(chip->pci, 0x04, &tmp) == 0) { + dprintk(DPRT_LEVEL0, chip, "enable INTx\n"); + tmp &= 0xFFFFFBFF; // Enable INTx + pci_write_config_dword(chip->pci, 0x04, tmp); + } + + if( pci_read_config_dword(chip->pci, 0x50, &tmp) == 0) { //CFG_MSI_CAP = 0x50 + tmp &= 0xFFFEFFFF; //Disable MSI + pci_write_config_dword( chip->pci, 0x50, tmp ); + } + + if( pci_read_config_dword(chip->pci, 0xAC, &tmp) == 0) { //CFG_MSIX_CAP = 0xAC + tmp &= 0x7FFFFFFF; //Disable MSI-X + pci_write_config_dword(chip->pci, 0xAC, tmp); + } + + tw_write(TW6864_R_AVSRST,0x3F); //Reset Internal video decoders + tw_write(TW6864_R_DMA_CMD, 0); //Reset all DMA channels + tw_write(TW6864_R_DMA_CHANNEL_ENABLE, 0); //Disable all DMA channels + tw_write(TW6864_R_DMA_INT_REF, 0x4c4b6/2);//0x4c4b6); //Set INT interval for 2.0ms CPU占用率100%时,这个值设大了可能会造成DMA timeout + + tmp = 0x00FF0004; //(TW6864_MAXSWITCH>1) ? 0x00000004 : 0x00FF0004; + tw_write(TW6864_R_DMA_CONFIG, tmp); //enable vsync reset dma_operator fifo,INTx, MSI disabled, + //little endian, bad/vsync protection on + + tw_write(TW6864_R_DMA_CHANNEL_TIMEOUT, 0x3FFFFFFF);//0x14FF0FF0);//0x140c8b08); + + tmp = 0x15DC; //512*(2*125)/(27*720/858) + tw_write(TW6864_R_PHASE_REF_CONFIG, tmp); + + tmp = 1; + tmp |= TW6864_R_VIDEO_CTRL1_STANDARDALL; //pal, 4 D1 TDM format, 108MHz + tw_write(TW6864_R_VIDEO_CTRL1, tmp); + + //set gpio mode + tmp = tw_read(TW6869_R_PIN_CFG_CTRL); + tmp&= ~0x03; + tmp|= 0x02; + tw_write(TW6869_R_PIN_CFG_CTRL,tmp); + + tmp = 0x40a0; + tw_write(TW6864_R_CSR_REG,tmp); + + tmp = TW6864_R_VIDEO_CTRL2_GENRSTALL | (TW6864_VIDEO_GEN_PATTERNS <<8) | TW6864_R_VIDEO_CTRL2_GENALL; + tw_write(TW6864_R_VIDEO_CTRL2, tmp); + + tmp = (TW6864_AUDIO_DMA_LENGTH << 19) | + (TW6864_AUDIO_SAMP_RATE_INT(TW6864_AUDIO_SAMPLE_RATE)<<5) | + (TW6864_AUDIO_GEN_PATTERN << 4) | + (TW6864_AUDIO_GEN_MIX_SEL <<1 ) | + TW6864_AUDIO_GEN; + tw_write(TW6864_R_AUDIO_CTRL1, tmp); + + tmp = (u32)TW6864_AUDIO_SAMP_RATE_EXT(TW6864_AUDIO_SAMPLE_RATE); + tw_write(TW6864_R_AUDIO_CTRL2, tmp); + + tmp = 0x40; + if(TW6864_AUDIO_SAMPLE_BIT == 8) { + tmp |= BIT(8); + } + tw_write(TW6864_R_AUDIO_CTRL3, tmp); + + //Show Blue background if no signal + tw_write(TW6864_R_MISC_CTRLII1, 0xE7); + + tmp = 0x40; + tw_write(TW6864_R_AUDIO_GAIN_0, tmp); + tw_write(TW6864_R_AUDIO_GAIN_1, tmp); + tw_write(TW6864_R_AUDIO_GAIN_2, tmp); + tw_write(TW6864_R_AUDIO_GAIN_3, tmp); + tw_write(TW6869_R_AUDIO_GAIN_4, tmp); + tw_write(TW6869_R_AUDIO_GAIN_5, tmp); + tw_write(TW6869_R_AUDIO_GAIN_6, tmp); + tw_write(TW6869_R_AUDIO_GAIN_7, tmp); + + tw_write(TW6864_R_VERTICAL_CTRL,0x00); //0x26 will cause ch0 and ch1 have dma_error. + tw_write(TW6864_R_LOOP_CTRL, 0xa5); + tw_write(TW6864_R_GPIO_REG, 0xFFFF0000); + tw_write(TW6864_R_HFLT1, 0x00);//0xBB); //NOTE: 这个会影响CIF的清晰度,针对CIF和4CIF需分别设置 + tw_write(TW6864_R_HFLT2, 0x00);//0xBB); + + return 0; +} + +void tw686x_dev_uninit( struct tw686x_chip *chip ) +{ + int i = 0; + u32 page_table_p[TW6864_MAX_NUM_SG_DMA] = { + TW6869_R_DMA_PAGE_TABLE0, TW6869_R_DMA_PAGE_TABLE2, TW6869_R_DMA_PAGE_TABLE4, TW6869_R_DMA_PAGE_TABLE6, + TW6869_R_DMA_PAGE_TABLE8, TW6869_R_DMA_PAGE_TABLEA, TW6869_R_DMA_PAGE_TABLEC, TW6869_R_DMA_PAGE_TABLEE + }; + u32 page_table_b[TW6864_MAX_NUM_SG_DMA] = { + TW6869_R_DMA_PAGE_TABLE1, TW6869_R_DMA_PAGE_TABLE3, TW6869_R_DMA_PAGE_TABLE5, TW6869_R_DMA_PAGE_TABLE7, + TW6869_R_DMA_PAGE_TABLE9, TW6869_R_DMA_PAGE_TABLEB, TW6869_R_DMA_PAGE_TABLED, TW6869_R_DMA_PAGE_TABLEF + }; + + dprintk(DPRT_LEVEL0, chip, "%s()\n", __func__); + + tw_write(TW6864_R_DMA_CMD, 0); //Reset all DMA channels + tw_write(TW6864_R_DMA_CHANNEL_ENABLE, 0); //Disable all DMA channels + + for(i=0; i<TW6864_MAX_NUM_SG_DMA; i++) { + tw_write(TW6864_R_VC_CTRL_REG(i), 0x00000000); + tw_write(page_table_p[i], 0x00000000); + tw_write(page_table_b[i], 0x00000000); + } +} + +void tw686x_dev_setdma( struct tw686x_chip *chip, u32 dma_cmd) +{ + u32 dma_en = dma_cmd; + + if(dma_cmd != 0) { + dma_en |= (1<<31); + } + + tw_write(TW6864_R_DMA_CHANNEL_ENABLE, dma_en); + //tw_write(TW6864_R_DMA_CMD, dma_cmd); +} + +int tw686x_dev_set_mux(struct tw686x_vdev *dev, unsigned int input) +{ + struct tw686x_chip *chip = dev->chip; + int ch = dev->channel_id; + u32 uConfig = tw_read(TW6864_R_CH0to7_CONFIG_REG(ch)); + + dvprintk(DPRT_LEVEL0, dev, "%s(%d)\n", __func__, (int)input); + + uConfig &= ~(3 << 30); + uConfig |= (input&3) << 30; + tw_write(TW6864_R_CH0to7_CONFIG_REG(ch), uConfig); + + return 0; +} + +void tw686x_dev_set_standard(struct tw686x_vdev *dev, v4l2_std_id value, bool b_auto) +{ + struct tw686x_chip *chip = dev->chip; + int ch = dev->channel_id; + u32 tmp = tw_read(TW6864_R_VIDEO_CTRL1); + + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + tmp &= ~TW6864_R_VIDEO_CTRL1_STANDARD(ch); + if(IS_PAL(value)) { + tmp |= TW6864_R_VIDEO_CTRL1_STANDARD(ch); //pal, 4 D1 TDM format, 108MHz + } + + tw_write(TW6864_R_VIDEO_CTRL1, tmp); + + if(ch < 4) { + if(b_auto) { + tmp = 0x07; + } + else { + tmp = tw_read(TW6864_R_CHANNEL(TW6864_R_STANDARD_0, ch)); + tmp&= 0xF8; + tmp|= IS_PAL(value) ? 1 : 0; + } + tw_write(TW6864_R_CHANNEL(TW6864_R_STANDARD_0, ch), tmp); + } + else { + ch -= 4; + if(b_auto) { + tmp = 0x07; + } + else { + tmp = tw_read(TW6864_R_CHANNEL(TW6869_R_STANDARD_4, ch)); + tmp&= 0xF8; + tmp|= IS_PAL(value) ? 1 : 0; + } + tw_write(TW6864_R_CHANNEL(TW6869_R_STANDARD_4, ch), tmp); + } +} + +static u32 tw686x_dev_map_pixelformat(u32 fourcc) +{ + switch(fourcc) { + case V4L2_PIX_FMT_YUYV : + fourcc = (TW6864_VIDEO_FORMAT_YUYV & 7); + break; + case V4L2_PIX_FMT_UYVY : + fourcc = (TW6864_VIDEO_FORMAT_UYVY & 7); + break; + case V4L2_PIX_FMT_YVU420 : + fourcc = (TW6864_VIDEO_FORMAT_YUV420 & 7); + break; + case V4L2_PIX_FMT_YUV411P : + fourcc = (TW6864_VIDEO_FORMAT_Y411 & 7); + break; + case V4L2_PIX_FMT_Y41P : + fourcc = (TW6864_VIDEO_FORMAT_Y41P & 7); + break; + case V4L2_PIX_FMT_RGB565: + fourcc = (TW6864_VIDEO_FORMAT_RGB565 & 7); + break; + case V4L2_PIX_FMT_RGB555: + fourcc = (TW6864_VIDEO_FORMAT_RGB555 & 7); + break; + default: + fourcc = (TW6864_VIDEO_FORMAT_YUYV & 7); + } + + return fourcc; +} + +void tw686x_dev_set_pixelformat(struct tw686x_vdev *dev, u32 fourcc) +{ + struct tw686x_chip *chip = dev->chip; + int ch = dev->channel_id; + u32 uConfig = tw_read(TW6864_R_CH0to7_CONFIG_REG(ch)); + + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + uConfig &= ~(7 << 20); + uConfig |= tw686x_dev_map_pixelformat(fourcc) << 20; + tw_write(TW6864_R_CH0to7_CONFIG_REG(ch), uConfig); +} + +void tw686x_dev_set_size(struct tw686x_vdev *dev, int width, int height) +{ + struct tw686x_chip *chip = dev->chip; + int ch = dev->channel_id; + u32 tmp, tmp1, tmp2; + int maxWidth = TW686X_REMOVE_BLACKSTRIPE ? 720 : 704; + bool isPal = IS_PAL(dev->tvnorm); + + dvprintk(DPRT_LEVEL0, dev, "%s(%d, %d)\n", __func__, width, height); + + //SH_Scaler + //setup for Black stripe remover + tmp1 = width-12; //EndPos + tmp2 = 4; //StartPos + tmp = (tmp1 - tmp2)*(1<<16)/width; + tmp = (tmp2 & 0x1F) | ((tmp1 & 0x3FF) << 5) | (tmp <<15); + tmp |= TW686X_REMOVE_BLACKSTRIPE; + tw_write(TW6864_R_SHSCALER_REG(ch), tmp); + + tmp = (u32)TW686X_VIDEO_SIZE(width,height); + tmp1 = tmp2 = tmp; + tw_write(TW6864_R_VIDEO_SIZE_REGA, tmp); + tw_write(TW6864_R_VIDEO_SIZE_REG(ch), tmp); //for Rev.B or later only + + //H/V Scale + tmp1 &= 0x7FF; + tmp2 = (tmp2>>16)&0x1FF; //V + tmp1 = (maxWidth*256)/tmp1; //H + tmp2 = ((isPal?288:240)*256)/tmp2; + tmp = tmp1 & 0xFF; + + if(ch < 4) { + tw_write(TW6864_R_CHANNEL(TW6864_R_HSCALE_LO_0,ch), tmp); + + tmp = tmp2 & 0xFF; + tw_write(TW6864_R_CHANNEL(TW6864_R_VSCALE_LO_0,ch), tmp); + + tmp = (((tmp2 >> 8)& 0xF) << 4) | ( (tmp1>>8) & 0xF ); + tw_write(TW6864_R_CHANNEL(TW6864_R_VHSCALE_HI_0,ch), tmp); + + if(tmp1 > 1) { + if(width==352) { tmp = 0x01; } + else if(width==176) { tmp = 0x02; } + else { tmp = 0x00; } + } + else { tmp = 0x0B; } + tmp1 = tw_read(TW6864_R_HFLT1 + (ch/2)*4); + tmp1&= ~(0x0F << ((ch%2)*4)); + tmp1|= ((tmp&0x0F) << ((ch%2)*4)); + tw_write(TW6864_R_HFLT1 + (ch/2)*4, tmp1); + + if(isPal) { + tw_write(TW6864_R_CHANNEL(TW6864_R_VDELAY_0,ch), 0x17); + tw_write(TW6864_R_CHANNEL(TW6864_R_HDELAY_0,ch), TW686X_REMOVE_BLACKSTRIPE?0x0A:0x00E); + } + else { + tw_write(TW6864_R_CHANNEL(TW6864_R_VDELAY_0,ch), 0x14); + tw_write(TW6864_R_CHANNEL(TW6864_R_HDELAY_0,ch), 0x0E);//TW6864_REMOVE_BLACKSTRIPE?0x0A:0x00C); + } + tw_write(TW6864_R_CHANNEL(TW6864_R_F2_CNT_0,ch), 0x00); + } + else { + ch -= 4; + tw_write(TW6864_R_CHANNEL(TW6869_R_HSCALE_LO_4,ch),tmp); + + tmp = tmp2 & 0xFF; + tw_write(TW6864_R_CHANNEL(TW6869_R_VSCALE_LO_4,ch),tmp); + + tmp = (((tmp2 >> 8)& 0xF) << 4) | ( (tmp1>>8) & 0xF ); + tw_write(TW6864_R_CHANNEL(TW6869_R_VHSCALE_HI_4,ch),tmp); + + if(tmp1 > 1) { + if(width==352) { tmp = 0x01; } + else if(width==176) { tmp = 0x02; } + else { tmp = 0x00; } + } + else { tmp = 0x0B; } + tmp1 = tw_read(TW6869_R_HFLT1 + (ch/2)*4); + tmp1&= ~(0x0F << ((ch%2)*4)); + tmp1|= ((tmp&0x0F) << ((ch%2)*4)); + tw_write(TW6869_R_HFLT1 + (ch/2)*4, tmp1); + + if(isPal) { + tw_write(TW6864_R_CHANNEL(TW6869_R_VDELAY_4,ch), 0x17); + tw_write(TW6864_R_CHANNEL(TW6869_R_HDELAY_4,ch), TW686X_REMOVE_BLACKSTRIPE?0x0A:0x0E); + } + else { + tw_write(TW6864_R_CHANNEL(TW6869_R_VDELAY_4,ch), 0x14); + tw_write(TW6864_R_CHANNEL(TW6869_R_HDELAY_4,ch), 0x0E);//TW6864_REMOVE_BLACKSTRIPE?0x0A:0x00C); + } + tw_write(TW6864_R_CHANNEL(TW6869_R_F2_CNT_4,ch), 0x00); + } +} + +void tw686x_dev_set_size2(struct tw686x_vdev *dev, int width, int height) +{ + struct tw686x_chip *chip = dev->chip; + int ch = dev->channel_id; + u32 tmp, tmp1, tmp2; + int maxWidth = TW686X_REMOVE_BLACKSTRIPE ? 720 : 704; + + dvprintk(DPRT_LEVEL0, dev, "%s(%d, %d)\n", __func__, width, height); + + tmp = (u32)TW686X_VIDEO_SIZE_F2(width,height); + tmp1 = tmp2 = tmp; + tw_write(TW6864_R_VIDEO_SIZE_REG_F2(ch), tmp); //for Rev.B or later only + + //H/V Scale + tmp1&= 0x7FF; + tmp2 = (tmp2>>16)&0x1FF; //V + tmp1 = (maxWidth*256)/tmp1; //H + tmp2 = ((IS_PAL(dev->tvnorm)?288:240)*256)/tmp2; + tmp = tmp1 & 0xFF; + + if(ch < 4) { + tw_write(TW6864_R_CHANNEL(TW6864_R_HSCALE_LO_F2_0,ch), tmp); + + tmp = tmp2 & 0xFF; + tw_write(TW6864_R_CHANNEL(TW6864_R_VSCALE_LO_F2_0,ch), tmp); + + tmp = (((tmp2 >> 8)& 0xF) << 4) | ( (tmp1>>8) & 0xF ); + tw_write(TW6864_R_CHANNEL(TW6864_R_VHSCALE_HI_F2_0,ch), tmp); + + if(IS_PAL(dev->tvnorm)) { + tw_write(TW6864_R_CHANNEL(TW6864_R_VDELAY_F2_0,ch), 0x17); + tw_write(TW6864_R_CHANNEL(TW6864_R_HDELAY_F2_0,ch), TW686X_REMOVE_BLACKSTRIPE?0x0A:0x00E); + } + else { + tw_write(TW6864_R_CHANNEL(TW6864_R_VDELAY_F2_0,ch), 0x14); + tw_write(TW6864_R_CHANNEL(TW6864_R_HDELAY_F2_0,ch), 0x0E);//TW686X_REMOVE_BLACKSTRIPE?0x0A:0x00C); + } + + tmp = tw_read(TW6864_R_CHANNEL(TW6864_R_CROPPING_0,ch)); + tw_write(TW6864_R_CHANNEL(TW6864_R_CROPPING_F2_0,ch), tmp); + + tmp = tw_read(TW6864_R_CHANNEL(TW6864_R_HACTIVE_0,ch)); + tw_write(TW6864_R_CHANNEL(TW6864_R_HACTIVE_F2_0,ch), tmp); + + tmp = tw_read(TW6864_R_CHANNEL(TW6864_R_VACTIVE_0,ch)); + tw_write(TW6864_R_CHANNEL(TW6864_R_VACTIVE_F2_0,ch), tmp); + + tw_write(TW6864_R_CHANNEL(TW6864_R_F2_CNT_0,ch), 0x1); + } + else + { + ch -= 4; + tw_write(TW6864_R_CHANNEL(TW6869_R_HSCALE_LO_F2_4,ch), tmp); + + tmp = tmp2 & 0xFF; + tw_write(TW6864_R_CHANNEL(TW6869_R_VSCALE_LO_F2_4,ch), tmp); + + tmp = (((tmp2 >> 8)& 0xF) << 4) | ( (tmp1>>8) & 0xF ); + tw_write(TW6864_R_CHANNEL(TW6869_R_VHSCALE_HI_F2_4,ch), tmp); + + if(IS_PAL(dev->tvnorm)) { + tw_write(TW6864_R_CHANNEL(TW6869_R_VDELAY_F2_4,ch), 0x17); + tw_write(TW6864_R_CHANNEL(TW6869_R_HDELAY_F2_4,ch), TW686X_REMOVE_BLACKSTRIPE?0x0A:0x00E); + } + else { + tw_write(TW6864_R_CHANNEL(TW6869_R_VDELAY_F2_4,ch), 0x14); + tw_write(TW6864_R_CHANNEL(TW6869_R_HDELAY_F2_4,ch), 0x0E);//TW686X_REMOVE_BLACKSTRIPE?0x0A:0x00C); + } + + tmp = tw_read(TW6864_R_CHANNEL(TW6869_R_CROPPING_4,ch)); + tw_write(TW6864_R_CHANNEL(TW6869_R_CROPPING_F2_4,ch), tmp); + + tmp = tw_read(TW6864_R_CHANNEL(TW6869_R_HACTIVE_4,ch)); + tw_write(TW6864_R_CHANNEL(TW6869_R_HACTIVE_F2_4,ch), tmp); + + tmp = tw_read(TW6864_R_CHANNEL(TW6869_R_VACTIVE_4,ch)); + tw_write(TW6864_R_CHANNEL(TW6869_R_VACTIVE_F2_4,ch), tmp); + + tw_write(TW6864_R_CHANNEL(TW6869_R_F2_CNT_4,ch), 0x1); + } + + tmp = tw_read(TW6864_R_VIDEO_SIZE_REG(ch)); + tmp |= (1 << 30); + tw_write(TW6864_R_VIDEO_SIZE_REG(ch), tmp); +} + +void tw686x_dev_set_vdelay(struct tw686x_vdev *dev, int value) +{ + struct tw686x_chip *chip = dev->chip; + int ch = dev->channel_id; + + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + if(ch < 4) { + tw_write(TW6864_R_CHANNEL(TW6864_R_VDELAY_0,ch), (u32)value); + tw_write(TW6864_R_CHANNEL(TW6864_R_VDELAY_F2_0,ch), (u32)value); + } + else { + ch -= 4; + tw_write(TW6864_R_CHANNEL(TW6869_R_VDELAY_4,ch), (u32)value); + tw_write(TW6864_R_CHANNEL(TW6869_R_VDELAY_F2_4,ch), (u32)value); + } +} + +void tw686x_dev_set_bright(struct tw686x_vdev *dev, int val) +{ + struct tw686x_chip *chip = dev->chip; + int ch = dev->channel_id; + + //if(1) { val = 0x00; } + + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + if(ch<4) { + tw_write(TW6864_R_BRIGHT(ch), val); + } + else { + ch -= 4; + tw_write(TW6864_R_CHANNEL(TW6869_R_BRIGHT_4,ch), val); + } +} + +void tw686x_dev_set_contrast(struct tw686x_vdev *dev, int val) +{ + struct tw686x_chip *chip = dev->chip; + int ch = dev->channel_id; + + //if(1) { val = 0x64; } + + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + if(ch<4) { + tw_write(TW6864_R_CONTRAST(ch), val); + } + else { + ch -= 4; + tw_write(TW6864_R_CHANNEL(TW6869_R_CONTRAST_4,ch), val); + } +} + +void tw686x_dev_set_hue(struct tw686x_vdev *dev, int val) +{ + struct tw686x_chip *chip = dev->chip; + int ch = dev->channel_id; + + //if(1) { val = 0; } + + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + if(ch<4) { + tw_write(TW6864_R_HUE(ch), val); + } + else { + ch -= 4; + tw_write(TW6864_R_CHANNEL(TW6869_R_HUE_4,ch), val); + } +} + +void tw686x_dev_set_saturation(struct tw686x_vdev *dev, int val) +{ + struct tw686x_chip *chip = dev->chip; + int ch = dev->channel_id; + + //if(1) { val = 0x80; } + + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + if(ch<4) { + tw_write(TW6864_R_SAT_U(ch), val); + tw_write(TW6864_R_SAT_V(ch), val); + } + else { + ch -= 4; + tw_write(TW6864_R_CHANNEL(TW6869_R_SAT_U_4,ch), val); + tw_write(TW6864_R_CHANNEL(TW6869_R_SAT_V_4,ch), val); + } +} + +void tw686x_dev_set_sharpness(struct tw686x_vdev *dev, int val) +{ + struct tw686x_chip *chip = dev->chip; + int ch = dev->channel_id; + + val &= 0x0F; + val |= 0x10;//0xA0; + + //if(1) { val = 0x11; } + + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + if(ch<4) { + tw_write(TW6864_R_SHARPNESS(ch), val); + } + else { + ch -= 4; + tw_write(TW6864_R_CHANNEL(TW6869_R_SHARPNESS_4,ch), val); + } +} + +u32 tw686x_dev_get_decoderstatus(struct tw686x_vdev *dev) +{ + struct tw686x_chip *chip = dev->chip; + int ch = dev->channel_id; + + if(ch < 4) { + return tw_read(TW6864_R_CHANNEL(TW6864_R_VSTATUS_0, ch)); + } + + return tw_read(TW6864_R_CHANNEL(TW6869_R_VSTATUS_4, ch-4)); +} + +int tw686x_dev_get_signal(struct tw686x_vdev *dev) +{ + u32 vstatus = tw686x_dev_get_decoderstatus( dev ); + + //dvprintk(DPRT_LEVEL0, dev, "%s() vstatus=%x\n", __func__, vstatus); + + return (!(vstatus&BIT(7)) && (vstatus&BIT(6))); +} + +u32 tw686x_dev_map_fieldrate(int nFieldRate, bool bNTSC) +{ + u32 uRate = 0; + + if(!bNTSC) { + switch(nFieldRate) { + case 0: + uRate = 0; + break; + case 1: + case 2: + uRate = 0x00000001; + break; + case 3: + uRate = 0x00004001;//0x10001000; + break; + case 4: + case 5: + uRate = 0x00104001;//0x10010010; + break; + case 6: + case 7: + uRate = 0x00404041;//0x10101010; + break; + case 8: + case 9: + uRate = 0x01041041;//0x11011010; + break; + case 10: + uRate = 0x01104411;//0x11101110; + break; + case 11: + case 12: + uRate = 0x01111111;//0x11111110; + break; + case 13: + case 14: + uRate = 0x04444445;//0x11111111; + break; + case 15: + uRate = 0x04511445;//0x15111111; + break; + case 16: + case 17: + uRate = 0x05145145;//0x15111511; + break; + case 18: + case 19: + uRate = 0x05151515;//0x15151511; + break; + case 20: + uRate = 0x05515455;//0x15151515; + break; + case 21: + case 22: + uRate = 0x05551555;//0x15515515; + break; + case 23: + case 24: + uRate = 0x05555555;//0x15551555; + break; + default: + uRate = 0x15555555; + break; + } + } + else { + switch(nFieldRate) { + case 0: + uRate = 0; + break; + case 1: + case 2: + case 3: + uRate = 0x00000001; + break; + case 4: + case 5: + uRate = 0x00004001;//0x10001000; + break; + case 6: + case 7: + uRate = 0x00104001;//0x10010010; + break; + case 8: + case 9: + uRate = 0x00404041;//0x10101010; + break; + case 10: + case 11: + uRate = 0x01041041;//0x11011010; + break; + case 12: + case 13: + uRate = 0x01104411;//0x11101110; + break; + case 14: + case 15: + uRate = 0x01111111;//0x11111110; + break; + case 16: + case 17: + uRate = 0x04444445;//0x11111111; + break; + case 18: + case 19: + uRate = 0x04511445;//0x15111111; + break; + case 20: + case 21: + uRate = 0x05145145;//0x15111511; + break; + case 22: + case 23: + uRate = 0x05151515;//0x15151511; + break; + case 24: + case 25: + uRate = 0x05515455;//0x15151515; + break; + case 26: + case 27: + uRate = 0x05551555;//0x15551555; + break; + case 28: + case 29: + uRate = 0x05555555;//0x15551555; + break; + default: + uRate = 0x15555555; + break; + } + } + + return uRate; +} + +u32 tw686x_dev_map_framerate(int nFieldRate1, int nFieldRate2, bool bNTSC, bool b6864) +{ + u32 uRate = 0; + + if(nFieldRate1 == 0) { + uRate = tw686x_dev_map_fieldrate(nFieldRate2, bNTSC); + } + else if(nFieldRate2 == 0) { + uRate = (tw686x_dev_map_fieldrate(nFieldRate1, bNTSC) << 1); + } + else { + if(b6864) { + uRate = bNTSC ? 30 : 25; + if(nFieldRate1>(int)uRate) nFieldRate1 = (int)uRate; + if(nFieldRate2>(int)uRate) nFieldRate2 = (int)uRate; + if(uRate==nFieldRate1) { + uRate = tw686x_dev_map_fieldrate(nFieldRate1, bNTSC) | + (tw686x_dev_map_fieldrate(nFieldRate2, bNTSC) << 1); + } + else { + uRate = (tw686x_dev_map_fieldrate(nFieldRate1, bNTSC)<<1) | + (tw686x_dev_map_fieldrate(nFieldRate2, bNTSC)<<2); + } + } + else { + uRate = tw686x_dev_map_fieldrate(nFieldRate1, bNTSC) | + (tw686x_dev_map_fieldrate(nFieldRate2, bNTSC) << 1); + } + } + if(uRate > 0) { + uRate |= 0x80000000; + } + + return uRate; +} + +void tw686x_dev_set_framerate(struct tw686x_vdev *dev, int val, int field) +{ + struct tw686x_chip *chip = dev->chip; + int ch = dev->channel_id; + u32 uRate = 0; + + if(field == V4L2_FIELD_TOP) { + uRate = tw686x_dev_map_framerate(val, 0, !IS_PAL(dev->tvnorm), false); + } + else if(field == V4L2_FIELD_BOTTOM) { + uRate = tw686x_dev_map_framerate(0,val, !IS_PAL(dev->tvnorm), false); + } + else { + uRate = tw686x_dev_map_framerate(val, val, !IS_PAL(dev->tvnorm), true); + } + + tw_write(TW6864_R_DROP_FIELD_REG(ch), uRate); //0x95555555 for even only, 0xd5555555 for odd only +} + +//---------------------------------------------- +// 运行或者停止DMA program +//---------------------------------------------- +void tw686x_dev_run(struct tw686x_vdev *dev, bool b_run) +{ + struct tw686x_chip *chip = dev->chip; + int ch = dev->channel_id; + u32 tmp = tw_read(TW6864_R_DMA_CMD); + u32 tmp1 = tw_read(TW6864_R_DMA_CHANNEL_ENABLE); + + dvprintk(DPRT_LEVEL0, dev, "%s(%d %d)\n", __func__, ch, b_run); + + if(b_run) { + tmp |= (1<<31); + tmp |= (1<<ch); + tmp1|= (1<<ch); + } + else { + if(!(tmp&(1<<ch)) && !(tmp1&(1<<ch))) { + return; + } + tmp &= ~(1<<ch); + tmp1&= ~(1<<ch); + if( tmp1 == 0 ) { + tmp = 0; + } + } + + tw_write(TW6864_R_DMA_CHANNEL_ENABLE, tmp1); + tw_write(TW6864_R_DMA_CMD, tmp); +} + +void tw686x_dev_set_dma(struct tw686x_vdev *dev, int width, int height, int linewidth) +{ + struct tw686x_dmadesc * dma_desc; + struct tw686x_chip *chip = dev->chip; + int ch = dev->channel_id; + bool is4CIF = IS_PAL(dev->tvnorm) ? (height>288) : (height>240); + int nHeight = !is4CIF ? height : height/2; + u32 uTemp, uConfig; + int linepitch = linewidth; + int idx_begin = ch*TW686X_DMA_DESC_UNIT; + int idx_end = (ch+1)*TW686X_DMA_DESC_UNIT-1; + + dvprintk(DPRT_LEVEL0, dev, "%s(%d) is4CIF=%d\n", __func__, ch, is4CIF); + + /* + * 0 -- scatter gather, 2 -- frame block mode, 3 -- field block mode + */ + uTemp = (!IS_TW686X_DMA_MODE_BLOCK) ? 0 : (!is4CIF ? 3 : 2); + + uConfig = tw_read( TW6864_R_PHASE_REF_CONFIG ); + uConfig &= ~(3<<(16+(ch<<1))); + uConfig |= (uTemp<<(16+(ch<<1))); + tw_write( TW6864_R_PHASE_REF_CONFIG, uConfig ); + + uConfig = tw_read(TW6864_R_CH0to7_CONFIG_REG(ch)); + uConfig = (uConfig & ~0x1FF) | (idx_begin&0x1FF); //m_StartIdx + uConfig = (uConfig & ~(0x3FF<<10)) | ((idx_end&0x3FF)<<10); //m_EndIdx + uConfig = (uConfig & ~(1<<23)) | ((false&1)<<23); //m_bHorizontalDecimate + uConfig = (uConfig & ~(1<<24)) | ((false&1)<<24); //m_bVerticalDecimate + uConfig = (uConfig & ~(3<<25)) | ((0&3)<<25); //m_nDropChannelNum + uConfig = (uConfig & ~(1<<27)) | ((1&1)<<27); //m_bDropMasterOrSlave //((((ch&1)?FALSE:TRUE)&1)<<27) + uConfig = (uConfig & ~(1<<28)) | ((false&1)<<28); //m_bDropField //((bDropField&1)<<28) + uConfig = (uConfig & ~(1<<29)) | ((false&1)<<29); //m_bDropOddOrEven + //uConfig = (uConfig & ~(3<<30)) | ((0&3)<<30); //m_nCurVideoChannelNum -- mux + + tw_write(TW6864_R_CH0to7_CONFIG_REG(ch), uConfig); + tw_write(TW6864_R_DROP_FIELD_REG(ch), 0); //0x95555555 for even only, 0xd5555555 for odd only + + if( !IS_TW686X_DMA_MODE_BLOCK ) { +#define DMA_STATUS_HOST_NOT_AVAIABLE 0 + dma_desc = TW686X_DMA_DESC(dev, P); + for( uTemp=0; uTemp<TW686X_DMA_DESC_UNIT; uTemp++ ) { + dma_desc[uTemp].ctrl = (DMA_STATUS_HOST_NOT_AVAIABLE&3)<<30; + } + dma_desc = TW686X_DMA_DESC(dev, B); + for(uTemp=0; uTemp<TW686X_DMA_DESC_UNIT; uTemp++) { + dma_desc[uTemp].ctrl = (DMA_STATUS_HOST_NOT_AVAIABLE&3)<<30; + } + } + else { + tw_write( TW6864_R_BDMA(TW6864_R_BDMA_ADDR_P_0, ch), 0 ); + tw_write( TW6864_R_BDMA(TW6864_R_BDMA_ADDR_B_0, ch), 0 ); + tw_write( TW6864_R_BDMA(TW6864_R_BDMA_WHP_0, ch), + (linewidth&0x7FF) | //line width + ((linepitch&0x7FF)<<11) | //pitch + ((nHeight&0x7FF)<<22) //height + ); + tw_write( TW6864_R_BDMA(TW6864_R_BDMA_ADDR_P_F2_0, ch), 0 ); + tw_write( TW6864_R_BDMA(TW6864_R_BDMA_ADDR_B_F2_0, ch), 0 ); + tw_write( TW6864_R_BDMA(TW6864_R_BDMA_WHP_F2_0, ch), + (linewidth&0x7FF) | //line width + ((linepitch&0x7FF)<<11) | //pitch + ((nHeight&0x7FF)<<22) //height + ); + } + + //field 2 + if(!is4CIF) { + tw686x_dev_set_size(dev, width, nHeight); + tw686x_dev_set_size2(dev, width, nHeight); + } + else { + tw686x_dev_set_size2(dev, width, nHeight); + tw686x_dev_set_size(dev, width, nHeight); + } +} + +int tw686x_dev_set_dmabuf(struct tw686x_vdev *dev, int nbuf, struct tw686x_buf *buf) +{ + struct tw686x_chip *chip = dev->chip; + int ch = dev->channel_id; + dma_addr_t dma_addr = buf ? videobuf_to_dma_contig_isl(&buf->vb) : 0; + + dvprintk(DPRT_LEVEL0, dev, "%s(n_buf=%d, buf=%d 0x%08x)\n", __func__, nbuf, buf->vb.i, (u32)dma_addr); + + if( V4L2_FIELD_HAS_BOTH(dev->field) ) { + switch( nbuf ) { + case 0: + tw_write( TW6864_R_BDMA(TW6864_R_BDMA_ADDR_P_0, ch), (u32)dma_addr ); + break; + case 1: + tw_write( TW6864_R_BDMA(TW6864_R_BDMA_ADDR_B_0, ch), (u32)dma_addr ); + break; + } + } + else { + switch( nbuf ) { + case 0: + tw_write( TW6864_R_BDMA(TW6864_R_BDMA_ADDR_P_0, ch), (u32)dma_addr ); + break; + case 1: + tw_write( TW6864_R_BDMA(TW6864_R_BDMA_ADDR_P_F2_0, ch), (u32)dma_addr ); + break; + case 2: + tw_write( TW6864_R_BDMA(TW6864_R_BDMA_ADDR_B_0, ch), (u32)dma_addr ); + break; + case 3: + tw_write( TW6864_R_BDMA(TW6864_R_BDMA_ADDR_B_F2_0, ch), (u32)dma_addr ); + break; + } + } + + return 0; +} + +#define TW686X_DISABLE_DESCRIPTOR(desc) \ +{ \ + (desc)->ctrl &= 0x3FFFFFFF; \ + (desc)->ctrl |= 0x20000000; \ +} + +#define TW686X_FILL_DESCRIPTOR(desc, phy_addr, byte_len, field_start, next_start, new_frame, check_point, status) \ +{ \ + (desc)->addr = phy_addr; \ + (desc)->ctrl = ((status & 0x3) << 30) | \ + ((new_frame&1)<<29) | \ + ((next_start & 0xFF) << 21) | \ + ((field_start & 0x7F) << 14) | \ + ((check_point&1)<<13) | \ + (byte_len & 0x1FFF); \ +} + +#define DMA_STATUS_HOST_READY 1 + +int tw686x_dev_set_dmadesc(struct tw686x_vdev *dev, int nbuf, struct tw686x_buf *buf) +{ + int fld_start = false; + int i, j, bytes_to; + int offset=0, n_desc=1; + struct videobuf_dmabuf *dma = videobuf_to_dma(&buf->vb); + struct tw686x_dmadesc *desc; + u32 phy_addr = 0; + int fld_num = V4L2_FIELD_HAS_BOTH(buf->vb.field) + 1; + int fld_cnt = 0; + + n_desc += (dev->pb_next==0); + + dvprintk(DPRT_LEVEL0, dev, "%s(%d %d), %d %p %p %p, %x\n", __func__, nbuf, buf->vb.i, + n_desc, TW686X_DMA_DESC(dev, P), TW686X_DMA_DESC(dev, B), buf->sg, buf->sg_offset); + + for( j=0; j<n_desc; j++ ) { + if((buf->sg==NULL) || (buf->sg_bytes_pb==buf->vb.size)) { + buf->sg = dma->sglist; + buf->sg_offset = 0; + buf->sg_bytes_to = buf->vb.size; + fld_start = true; + fld_cnt = 0; + offset= 0; + } + else { + offset = buf->sg_offset; + } + bytes_to= buf->sg_bytes_pb/fld_num; + desc = (nbuf==0) ? TW686X_DMA_DESC(dev, P) : TW686X_DMA_DESC(dev, B); + nbuf = (nbuf + 1) % dev->buf_needs; + for(i=0; i<TW686X_DMA_DESC_UNIT; i++) { + if(bytes_to == 0) { + TW686X_DISABLE_DESCRIPTOR(&desc[i]); + } + else { + while (offset && offset >= sg_dma_len(buf->sg)) { + offset -= sg_dma_len(buf->sg); + buf->sg++; + } + phy_addr = cpu_to_le32(sg_dma_address(buf->sg)+offset); + if(offset && (offset<TW686X_DMA_DESC_LEN)) { + offset = min(TW686X_DMA_DESC_LEN-offset, bytes_to); + TW686X_FILL_DESCRIPTOR(&desc[i], phy_addr, offset, 0, + 0, fld_start, 1, DMA_STATUS_HOST_READY); + bytes_to-= offset; + offset = TW686X_DMA_DESC_LEN; + //dvprintk(1, dev, "%s+(%x %x)\n", (nbuf==0)?"B":"P", desc[i].ctrl, phy_addr); + fld_start = false; + } + else if(bytes_to > TW686X_DMA_DESC_LEN) { + TW686X_FILL_DESCRIPTOR(&desc[i], phy_addr, TW686X_DMA_DESC_LEN, 0, + 0, fld_start, 1, DMA_STATUS_HOST_READY); + bytes_to-= TW686X_DMA_DESC_LEN; + offset += TW686X_DMA_DESC_LEN; + //dvprintk(1, dev, "%s (%x %x)\n", (nbuf==0)?"B":"P", desc[i].ctrl, phy_addr); + fld_start = false; + } + else { + TW686X_FILL_DESCRIPTOR(&desc[i], phy_addr, bytes_to, 0, + 0, fld_start, 1, DMA_STATUS_HOST_READY); + offset += bytes_to; + bytes_to = 0; + //dvprintk(1, dev, "%s-(%x %x)\n", (nbuf==0)?"B":"P", desc[i].ctrl, phy_addr); + //dvprintk(1, dev, "field %d/%d %s-(%x %x)\n", fld_cnt, fld_num, (nbuf==0)?"B":"P", desc[i].ctrl, phy_addr); + + fld_cnt++; + if(fld_cnt < fld_num) + { + fld_start = true; + bytes_to = buf->sg_bytes_pb/fld_num; + } + } + } + } + buf->sg_offset = offset; + } + + return 0; +} + +//------------------------------------------------------------------- +// 允许GPIO输出 +//------------------------------------------------------------------- +bool tw686x_dev_enable_gpiooutput(struct tw686x_chip *chip, bool enable, int gpio, int bits) +{ + u32 mask= ((1 << bits) - 1) << (gpio+16); + u32 old = tw_read(TW6864_R_GPIO_REG); + tw_write(TW6864_R_GPIO_REG, enable ? (old&~mask) : (old|mask)); + + dprintk(DPRT_LEVEL0, chip, "%s()\n", __func__); + + return true; +} + +//------------------------------------------------------------------- +// 输出GPIO电平 +//------------------------------------------------------------------- +bool tw686x_dev_set_gpiobits(struct tw686x_chip *chip, int gpio, int bits, u32* value) +{ + u32 mask= ((1 << bits) - 1) << gpio; + u32 old = tw_read(TW6864_R_GPIO_REG) & ~mask; + tw_write(TW6864_R_GPIO_REG, old|((*value<<gpio) & 0xFFFF0000)); + + dprintk(DPRT_LEVEL0, chip, "%s()\n", __func__); + + return true; +} + +//------------------------------------------------------------------- +// 获取GPIO电平 +//------------------------------------------------------------------- +bool tw686x_dev_get_gpiobits(struct tw686x_chip *chip, int gpio, int bits, u32* pValue) +{ + *pValue = (((tw_read(TW6864_R_GPIO_REG) & 0xFFFF0000) & (((1 << bits) - 1) << gpio)) >> gpio); + + dprintk(DPRT_LEVEL0, chip, "%s()\n", __func__); + + return true; +} + +int tw686x_dev_set_audio(struct tw686x_chip *chip, int samplerate, int bits, int channels, int blksize) +{ + u32 tmp1, tmp2, tmp3; + + tmp1 = tw_read(TW6864_R_AUDIO_CTRL1); + + tmp1 &= 0x0000001F; + tmp1 |= (125000000/samplerate) << 5; + tmp1 |= blksize << 19; + + tw_write(TW6864_R_AUDIO_CTRL1, tmp1); + + tmp2 = ((125000000 / samplerate) << 16) + + ((125000000 % samplerate) << 16)/samplerate; + + tw_write(TW6864_R_AUDIO_CTRL2, tmp2); + + tmp3 = tw_read(TW6864_R_AUDIO_CTRL3); + tmp3 &= ~BIT(8); + tmp3 |= (bits==8) ? BIT(8) : 0; + tw_write(TW6864_R_AUDIO_CTRL3, tmp3); + + return 0; +} + +int tw686x_dev_set_adma_buffer(struct tw686x_adev *dev, u32 buf_addr, int pb) +{ + struct tw686x_chip *chip = dev->chip; + + if(pb == 0) { + tw_write(TW6864_R_CH8to15_CONFIG_REG_P(dev->channel_id), buf_addr); + } + else { + tw_write(TW6864_R_CH8to15_CONFIG_REG_B(dev->channel_id), buf_addr); + } + + //daprintk(DPRT_LEVEL0, dev, "%s(%x %d)\n", __func__, buf_addr, pb); + + return 0; +} + +//---------------------------------------------- +// 运行或者停止audio DMA program +//---------------------------------------------- +void tw686x_dev_run_audio(struct tw686x_adev *dev, bool b_run) +{ + struct tw686x_chip *chip = dev->chip; + int ch = dev->channel_id+TW686X_AUDIO_BEGIN; + u32 tmp = tw_read(TW6864_R_DMA_CMD); + u32 tmp1 = tw_read(TW6864_R_DMA_CHANNEL_ENABLE); + + daprintk(DPRT_LEVEL0, dev, "%s(%d %d)\n", __func__, ch, b_run); + + if(b_run) { + tmp |= (1<<31); + tmp |= (1<<ch); + tmp1|= (1<<ch); + } + else { + if(!(tmp&(1<<ch)) && !(tmp1&(1<<ch))) { + return; + } + tmp &= ~(1<<ch); + tmp1&= ~(1<<ch); + if( tmp1 == 0 ) { + tmp = 0; + } + } + + tw_write(TW6864_R_DMA_CHANNEL_ENABLE, tmp1); + tw_write(TW6864_R_DMA_CMD, tmp); +} + +//---------------------------------------------- +// Set dma p/b buffer desc +//---------------------------------------------- +void tw686x_dev_set_pbdesc(struct tw686x_vdev *dev) +{ + struct tw686x_chip *chip = dev->chip; + u32 page_table_p[TW6864_MAX_NUM_SG_DMA] = { + TW6869_R_DMA_PAGE_TABLE0, TW6869_R_DMA_PAGE_TABLE2, TW6869_R_DMA_PAGE_TABLE4, TW6869_R_DMA_PAGE_TABLE6, + TW6869_R_DMA_PAGE_TABLE8, TW6869_R_DMA_PAGE_TABLEA, TW6869_R_DMA_PAGE_TABLEC, TW6869_R_DMA_PAGE_TABLEE + }; + u32 page_table_b[TW6864_MAX_NUM_SG_DMA] = { + TW6869_R_DMA_PAGE_TABLE1, TW6869_R_DMA_PAGE_TABLE3, TW6869_R_DMA_PAGE_TABLE5, TW6869_R_DMA_PAGE_TABLE7, + TW6869_R_DMA_PAGE_TABLE9, TW6869_R_DMA_PAGE_TABLEB, TW6869_R_DMA_PAGE_TABLED, TW6869_R_DMA_PAGE_TABLEF + }; + + tw_write(page_table_p[dev->channel_id], (u32)dev->dma_desc[TW686X_DMA_DESC_P].dma); + tw_write(page_table_b[dev->channel_id], (u32)dev->dma_desc[TW686X_DMA_DESC_B].dma); +} diff --git a/drivers/pci/pcie/tw6869/tw686x-device.h b/drivers/pci/pcie/tw6869/tw686x-device.h new file mode 100644 index 000000000000..202cd46c7d15 --- /dev/null +++ b/drivers/pci/pcie/tw6869/tw686x-device.h @@ -0,0 +1,56 @@ +/* + * Driver for Techwell TW6864/68 based DVR cards + * + * (c) 2009-10 liran <jlee@techwellinc.com.cn> [Techwell China] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef TW686X_DEVICE_H_INCLUDED +#define TW686X_DEVICE_H_INCLUDED + +int tw686x_dev_init( struct tw686x_chip *chip ); +int tw686x_dev_init_286x( struct tw686x_chip *chip ); +void tw686x_dev_uninit( struct tw686x_chip *chip ); +void tw686x_dev_setdma( struct tw686x_chip *chip, u32 dma_cmd); +int tw686x_dev_set_mux( struct tw686x_vdev *dev, unsigned int input ); +void tw686x_dev_set_standard( struct tw686x_vdev *dev, v4l2_std_id value, bool b_auto ); +void tw686x_dev_set_pixelformat( struct tw686x_vdev *dev, u32 fourcc ); +void tw686x_dev_set_size(struct tw686x_vdev *dev, int width, int height); +void tw686x_dev_set_size2(struct tw686x_vdev *dev, int width, int height); +void tw686x_dev_set_vdelay(struct tw686x_vdev *dev, int value); +void tw686x_dev_set_bright(struct tw686x_vdev *dev, int val); +void tw686x_dev_set_contrast(struct tw686x_vdev *dev, int val); +void tw686x_dev_set_hue(struct tw686x_vdev *dev, int val); +void tw686x_dev_set_saturation(struct tw686x_vdev *dev, int val); +void tw686x_dev_set_sharpness(struct tw686x_vdev *dev, int val); +void tw686x_dev_set_framerate(struct tw686x_vdev *dev, int val, int field); +u32 tw686x_dev_get_decoderstatus(struct tw686x_vdev *dev); +int tw686x_dev_get_signal(struct tw686x_vdev *dev); +void tw686x_dev_run(struct tw686x_vdev *dev, bool b_run); +void tw686x_dev_set_dma(struct tw686x_vdev *dev, int width, int height, int linewidth); +int tw686x_dev_set_dmabuf(struct tw686x_vdev *dev, int nbuf, struct tw686x_buf *buf); +void tw686x_dev_set_pbdesc(struct tw686x_vdev *dev); +int tw686x_dev_set_dmadesc(struct tw686x_vdev *dev, int nbuf, struct tw686x_buf *buf); + +bool tw686x_dev_enable_gpiooutput(struct tw686x_chip *chip, bool enable, int gpio, int bits); +bool tw686x_dev_set_gpiobits(struct tw686x_chip *chip, int gpio, int bits, u32* value); +bool tw686x_dev_get_gpiobits(struct tw686x_chip *chip, int gpio, int bits, u32* pValue); +int tw686x_dev_set_audio(struct tw686x_chip *chip, int samplerate, int bits, int channels, int blksize); +int tw686x_dev_set_adma_buffer(struct tw686x_adev *dev, u32 buf_addr, int pb); +void tw686x_dev_run_audio(struct tw686x_adev *dev, bool b_run); + +#endif // TW686X_DEVICE_H_INCLUDED diff --git a/drivers/pci/pcie/tw6869/tw686x-i2c.c b/drivers/pci/pcie/tw6869/tw686x-i2c.c new file mode 100644 index 000000000000..9cc0cb300ecf --- /dev/null +++ b/drivers/pci/pcie/tw6869/tw686x-i2c.c @@ -0,0 +1,213 @@ +/* + * Driver for Techwell TW6864/68 based DVR cards + * + * (c) 2009-10 liran <jlee@techwellinc.com.cn> [Techwell China] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> + +#include "tw686x.h" +#include "tw686x-reg.h" + +#define TW6864_I2C_SCLK_BIT BIT(0) +#define TW6864_I2C_SDA_DATA_BIT BIT(1) +#define TW6864_I2C_SCLK_CTRL_BIT BIT(16) //1:Input,0=Output +#define TW6864_I2C_SDA_CTRL_BIT BIT(17) //1:Input,0=Output + +static bool tw686x_i2c_s_scl( struct i2c_sw *i2c, TLevel scl ) +{ + struct tw686x_chip *chip = (struct tw686x_chip *)i2c->chip; + struct tw686x_i2c *i2c_686x = &chip->i2c_686x; + + i2c_686x->control_word &= ~TW6864_I2C_SCLK_CTRL_BIT; + if(scl == LevelHi) { + i2c_686x->control_word |= TW6864_I2C_SCLK_BIT; + } + else { + i2c_686x->control_word &= ~TW6864_I2C_SCLK_BIT; + } + + tw_write(TW6864_R_GPIO_REG, i2c_686x->control_word); + + return i2csw_s_scl(&i2c_686x->i2c, scl); +} + +static bool tw686x_i2c_s_sda( struct i2c_sw *i2c, TLevel sda ) +{ + struct tw686x_chip *chip = (struct tw686x_chip *)i2c->chip; + struct tw686x_i2c *i2c_686x = &chip->i2c_686x; + + i2c_686x->control_word &= ~TW6864_I2C_SDA_CTRL_BIT; + if(sda == LevelHi) { + i2c_686x->control_word |= TW6864_I2C_SDA_DATA_BIT; + } + else { + i2c_686x->control_word &= ~TW6864_I2C_SDA_DATA_BIT; + } + + tw_write(TW6864_R_GPIO_REG, i2c_686x->control_word); + + return i2csw_s_sda(&i2c_686x->i2c, sda); +} + +static int tw686x_i2c_g_scl( struct i2c_sw * i2c ) +{ + struct tw686x_chip *chip = (struct tw686x_chip *)i2c->chip; + + return (tw_read(TW6864_R_GPIO_REG) & TW6864_I2C_SCLK_BIT); +} + +static int tw686x_i2c_g_sda( struct i2c_sw * i2c ) +{ + struct tw686x_chip *chip = (struct tw686x_chip *)i2c->chip; + struct tw686x_i2c *i2c_686x = &chip->i2c_686x; + + i2c_686x->control_word |= TW6864_I2C_SDA_CTRL_BIT; + tw_write(TW6864_R_GPIO_REG, i2c_686x->control_word); + + return ((tw_read(TW6864_R_GPIO_REG) & TW6864_I2C_SDA_DATA_BIT)>>1); +} + +static int i2c_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg *msgs, int num) +{ + struct tw686x_chip *chip = i2c_adap->algo_data; + struct i2c_sw *i2c = &chip->i2c_686x.i2c; + int i, retval = 0; + + dprintk(1, chip, "%s(num = %d)\n", __func__, num); + + for (i = 0 ; i < num; i++) { + dprintk(1, chip, "%s(num = %d) addr = 0x%02x len = 0x%x\n", + __func__, num, msgs[i].addr, msgs[i].len); + if (msgs[i].flags & I2C_M_RD) { + /* read */ + retval = i2csw_read_bytes(i2c, (u8*)&msgs[i].addr, 1, msgs[i].buf, msgs[i].len); + } else { + /* write */ + retval = i2csw_write_bytes(i2c, msgs[i].buf, msgs[i].len); + } + if (!retval) { + retval = -i2c->err_code; + goto err; + } + } + return num; + + err: + return retval; +} + +static u32 tw686x_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_SMBUS_EMUL | I2C_FUNC_I2C; +} + +static struct i2c_algorithm tw686x_i2c_algo_template = { + .master_xfer = i2c_xfer, + .functionality = tw686x_functionality, +}; + +static struct i2c_adapter tw686x_i2c_adap_template = { + .name = "tw6869", + .owner = THIS_MODULE, +/* .id = I2C_SW_TW686X, */ + .algo = &tw686x_i2c_algo_template, +}; + +static struct i2c_client tw686x_client_template = { + .name = "tw6869 internal", +}; + +static struct i2c_sw tw686x_i2csw_template = { + .s_scl = tw686x_i2c_s_scl, + .s_sda = tw686x_i2c_s_sda, + .g_scl = tw686x_i2c_g_scl, + .g_sda = tw686x_i2c_g_sda, +}; + +static int tw686x_i2c_init( struct tw686x_chip *chip ) +{ + struct tw686x_i2c *i2c_686x = &chip->i2c_686x; + + i2c_686x->i2c = tw686x_i2csw_template; + i2c_686x->i2c.chip = chip; + + i2c_686x->control_word = tw_read(TW6864_R_GPIO_REG); + i2c_686x->control_word &= ~TW6864_I2C_SCLK_CTRL_BIT; + i2c_686x->control_word &= ~TW6864_I2C_SDA_CTRL_BIT; + i2c_686x->control_word |= TW6864_I2C_SCLK_BIT; + i2c_686x->control_word |= TW6864_I2C_SDA_DATA_BIT; + tw_write(TW6864_R_GPIO_REG, i2c_686x->control_word); + + i2csw_init( &i2c_686x->i2c, 400000);//2000000); + + return 0; +} + +/* init + register i2c algo-bit adapter */ +int tw686x_i2c_register(struct tw686x_chip *chip) +{ +// u8 buf; +// struct i2c_board_info info; +// struct i2c_client *c; +// const unsigned short addr_list[] = { +// TW2864_R_I2C_ADDR0, I2C_CLIENT_END +// }; + + dprintk(1, chip, "%s()\n", __func__); + + chip->i2c_adap = tw686x_i2c_adap_template; + chip->i2c_adap.dev.parent = &chip->pci->dev; + strcpy(chip->i2c_adap.name, chip->name); + chip->i2c_adap.algo_data = chip; +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)) + i2c_set_adapdata(&chip->i2c_adap, &chip->v4l2_dev); +#else + i2c_set_adapdata(&chip->i2c_adap, chip); +#endif + i2c_add_adapter(&chip->i2c_adap); + + chip->i2c_client = tw686x_client_template; + chip->i2c_client.adapter = &chip->i2c_adap; + chip->i2c_client.addr = TW2864_R_I2C_ADDR0; + + tw686x_i2c_init( chip ); + +// if( i2c_master_recv(&chip->i2c_client, &buf, 0)<0 ) { +// printk("%s: no tw286x found\n", chip->name); +// } +// else { +// printk("%s: found tw286x\n", chip->name); +// } +// +// memset(&info, 0, sizeof(struct i2c_board_info)); +// strlcpy(info.type, "tw286x", I2C_NAME_SIZE); +// c = i2c_new_probed_device(&chip->i2c_adap, &info, addr_list); +// dprintk(1, chip, "%s() client=%p\n", __func__, c); + + return 0; +} + +int tw686x_i2c_unregister(struct tw686x_chip* chip) +{ + i2c_del_adapter(&chip->i2c_adap); + return 0; +} diff --git a/drivers/pci/pcie/tw6869/tw686x-reg.h b/drivers/pci/pcie/tw6869/tw686x-reg.h new file mode 100644 index 000000000000..57bacd1d4c91 --- /dev/null +++ b/drivers/pci/pcie/tw6869/tw686x-reg.h @@ -0,0 +1,593 @@ +/* + * Driver for Techwell TW6864/68 based DVR cards + * + * (c) 2009-10 liran <jlee@techwellinc.com.cn> [Techwell China] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef TW686XREG_H_INCLUDED +#define TW686XREG_H_INCLUDED + +//#define BIT(x) (1<<(x)) + +////////////////////////////////////////////////// +//NO NOT CHANGE +// Register definitions +// IMPORTANT: These defines are DWORD-based. +#define TW6864_R_DMA_INT_STATUS 0x000 //0x00 //RO +#define TW6864_R_DMA_INT_STATUS_ERR(ch) (BIT(ch)<<24) +#define TW6864_R_DMA_INT_STATUS_ERRALL 0xFF000000 +#define TW6864_R_DMA_INT_STATUS_TOUT BIT(17) +#define TW6864_R_DMA_INT_STATUS_DMA(ch) BIT(ch) +#define TW6864_R_DMA_INT_STATUS_DMAALL 0x1FFFF +#define TW6864_R_DMA_PB_STATUS 0x004 //0x01 //RO +#define TW6864_R_DMA_CMD 0x008 //0x02 +#define TW6864_R_DMA_CMD_TRIG BIT(31) +#define TW6864_R_DMA_CMD_RESET(ch) BIT(ch) +#define TW6864_R_DMA_CMD_RESETALL 0x01FFFF +#define TW6864_R_DMA_INT_ERROR 0x00C //0x03 +#define TW6864_R_DMA_VINT_FIFO(ch) (BIT(ch)<<24) +#define TW6864_R_DMA_VINT_PTR(ch) (BIT(ch)<<16) +#define TW6864_R_DMA_XINT_ERROR(ch) BIT(ch) +#define TW6864_R_VIDEO_CHID 0x010 //0x04 +#define TW6864_R_VIDEO_PARSER_STATUS 0x014 //0x05 +#define TW6864_R_SYS_SOFT_RST 0x018 //0x06 + +#define TW6864_R_DMA_PAGE_TABLE0 0x020 //0x08 //RW +#define TW6864_R_DMA_PAGE_TABLE1 0x024 //0x09 +#define TW6864_R_DMA_CHANNEL_ENABLE 0x028 //0x0a +#define TW6864_R_DMA_CONFIG 0x02C //0x0b +#define TW6864_R_DMA_INT_REF 0x030 //0x0c +#define TW6864_R_DMA_CHANNEL_TIMEOUT 0x034 //0x0d + +#define TW6864_R_DMA_CH0_CONFIG 0x040 //0x10 //DMA_CH0_CONFIG ~ DMA_CH7_CONFIG are continusly +#define TW6864_R_DMA_CH1_CONFIG 0x044 //0x11 +#define TW6864_R_DMA_CH2_CONFIG 0x048 //0x12 +#define TW6864_R_DMA_CH3_CONFIG 0x04C //0x13 +#define TW6864_R_DMA_CH4_CONFIG 0x050 //0x14 +#define TW6864_R_DMA_CH5_CONFIG 0x054 //0x15 +#define TW6864_R_DMA_CH6_CONFIG 0x058 //0x16 +#define TW6864_R_DMA_CH7_CONFIG 0x05C //0x17 +#define TW6864_R_DMA_CH8_CONFIG_P 0x060 //0x18 // DMA_CH8_CONFIG_P ~ DMA_CH10_CONFIG_B are continusly +#define TW6864_R_DMA_CH8_CONFIG_B 0x064 //0x19 +#define TW6864_R_DMA_CH9_CONFIG_P 0x068 //0x1A +#define TW6864_R_DMA_CH9_CONFIG_B 0x06C //0x1B +#define TW6864_R_DMA_CHA_CONFIG_P 0x070 //0x1C +#define TW6864_R_DMA_CHA_CONFIG_B 0x074 //0x1D +#define TW6864_R_DMA_CHB_CONFIG_P 0x078 //0x1E +#define TW6864_R_DMA_CHB_CONFIG_B 0x07C //0x1F +#define TW6864_R_DMA_CHC_CONFIG_P 0x080 //0x20 +#define TW6864_R_DMA_CHC_CONFIG_B 0x084 //0x21 +#define TW6864_R_DMA_CHD_CONFIG_P 0x088 //0x22 +#define TW6864_R_DMA_CHD_CONFIG_B 0x08C //0x23 +#define TW6864_R_DMA_CHE_CONFIG_P 0x090 //0x24 +#define TW6864_R_DMA_CHE_CONFIG_B 0x094 //0x25 +#define TW6864_R_DMA_CHF_CONFIG_P 0x098 //0x26 +#define TW6864_R_DMA_CHF_CONFIG_B 0x09C //0x27 +#define TW6864_R_DMA_CH10_CONFIG_P 0x0A0 //0x28 +#define TW6864_R_DMA_CH10_CONFIG_B 0x0A4 //0x29 +#define TW6864_R_CH0to7_CONFIG_REG(idx) (TW6864_R_DMA_CH0_CONFIG+((idx)*4)) +#define TW6864_R_CH8to15_CONFIG_REG_P(idx) (TW6864_R_DMA_CH8_CONFIG_P+((idx)*8)) +#define TW6864_R_CH8to15_CONFIG_REG_B(idx) (TW6864_R_DMA_CH8_CONFIG_B+((idx)*8)) + +#define TW6864_R_VIDEO_CTRL1 0x0A8 //0x2A +#define TW6864_R_VIDEO_CTRL1_STANDARD(ch) (BIT(ch)<<13) +#define TW6864_R_VIDEO_CTRL1_STANDARDALL (0xFF<<13) +#define TW6864_R_VIDEO_CTRL2 0x0AC //0x2B +#define TW6864_R_VIDEO_CTRL2_GENRST(ch) (BIT(ch)<<16) +#define TW6864_R_VIDEO_CTRL2_GENRSTALL 0xFF0000 +#define TW6864_R_VIDEO_CTRL2_PATTERNS(ch) (BIT(ch)<<8) +#define TW6864_R_VIDEO_CTRL2_PATTERNSALL 0xFF00 +#define TW6864_R_VIDEO_CTRL2_GEN(ch) BIT(ch) +#define TW6864_R_VIDEO_CTRL2_GENALL 0xFF +#define TW6864_R_AUDIO_CTRL1 0x0B0 //0x2C +#define TW6864_R_AUDIO_CTRL2 0x0B4 //0x2D +#define TW6864_R_PHASE_REF_CONFIG 0x0B8 //0x2E +#define TW6864_R_GPIO_REG 0x0BC //0x2F + +#define TW6864_R_INTL_HBAR0_CTRL 0x0C0 //0x30 +#define TW6864_R_INTL_HBAR1_CTRL 0x0C4 //0x31 +#define TW6864_R_INTL_HBAR2_CTRL 0x0C8 //0x32 +#define TW6864_R_INTL_HBAR3_CTRL 0x0CC //0x33 +#define TW6864_R_INTL_HBAR4_CTRL 0x0D0 //0x34 +#define TW6864_R_INTL_HBAR5_CTRL 0x0D4 //0x35 +#define TW6864_R_INTL_HBAR6_CTRL 0x0D8 //0x36 +#define TW6864_R_INTL_HBAR7_CTRL 0x0DC //0x37 + +#define TW6864_R_AUDIO_CTRL3 0x0E0 //0x38 +#define TW6864_R_DROP_FIELD_REG0 0x0E4 //0x39 +#define TW6864_R_DROP_FIELD_REG1 0x0E8 //0x3A +#define TW6864_R_DROP_FIELD_REG2 0x0EC //0x3B +#define TW6864_R_DROP_FIELD_REG3 0x0F0 //0x3C +#define TW6864_R_DROP_FIELD_REG4 0x0F4 //0x3D +#define TW6864_R_DROP_FIELD_REG5 0x0F8 //0x3E +#define TW6864_R_DROP_FIELD_REG6 0x0FC //0x3F +#define TW6864_R_DROP_FIELD_REG7 0x100 //0x40 +#define TW6864_R_VIDEO_SIZE_REGA 0x104 //0x41 //Rev.A only +#define TW6864_R_SHSCALER_REG0 0x108 //0x42 +#define TW6864_R_SHSCALER_REG1 0x10C //0x43 +#define TW6864_R_SHSCALER_REG2 0x110 //0x44 +#define TW6864_R_SHSCALER_REG3 0x114 //0x45 +#define TW6864_R_SHSCALER_REG4 0x118 //0x46 +#define TW6864_R_SHSCALER_REG5 0x11C //0x47 +#define TW6864_R_SHSCALER_REG6 0x120 //0x48 +#define TW6864_R_SHSCALER_REG7 0x124 //0x49 +#define TW6864_R_VIDEO_SIZE_REG0 0x128 //0x4A //Rev.B or later +#define TW6864_R_VIDEO_SIZE_REG1 0x12C //0x4B +#define TW6864_R_VIDEO_SIZE_REG2 0x130 //0x4C +#define TW6864_R_VIDEO_SIZE_REG3 0x134 //0x4D +#define TW6864_R_VIDEO_SIZE_REG4 0x138 //0x4E +#define TW6864_R_VIDEO_SIZE_REG5 0x13C //0x4F +#define TW6864_R_VIDEO_SIZE_REG6 0x140 //0x50 +#define TW6864_R_VIDEO_SIZE_REG7 0x144 //0x51 +#define TW6864_R_VIDEO_SIZE_REG0_F2 0x148 //0x52 //Rev.B or later +#define TW6864_R_VIDEO_SIZE_REG1_F2 0x14C //0x53 +#define TW6864_R_VIDEO_SIZE_REG2_F2 0x150 //0x54 +#define TW6864_R_VIDEO_SIZE_REG3_F2 0x154 //0x55 +#define TW6864_R_VIDEO_SIZE_REG4_F2 0x158 //0x56 +#define TW6864_R_VIDEO_SIZE_REG5_F2 0x15C //0x57 +#define TW6864_R_VIDEO_SIZE_REG6_F2 0x160 //0x58 +#define TW6864_R_VIDEO_SIZE_REG7_F2 0x164 //0x59 +#define TW6864_R_VC_CTRL_REG0 0x1C0 //0x70 +#define TW6864_R_VC_CTRL_REG1 0x1C4 //0x71 +#define TW6864_R_VC_CTRL_REG2 0x1C8 //0x72 +#define TW6864_R_VC_CTRL_REG3 0x1D0 //0x73 +#define TW6864_R_VC_CTRL_REG4 0x1D4 //0x74 +#define TW6864_R_VC_CTRL_REG5 0x1D8 //0x75 +#define TW6864_R_VC_CTRL_REG6 0x1DC //0x76 +#define TW6864_R_VC_CTRL_REG7 0x1E0 //0x77 +#define TW6864_R_BDMA_ADDR_P_0 0x200 //0x80 //0x80 ~ 0xBF,Rev.B or later +#define TW6864_R_BDMA_WHP_0 0x204 //0x81 +#define TW6864_R_BDMA_ADDR_B_0 0x208 //0x82 +#define TW6864_R_BDMA_ADDR_P_F2_0 0x210 //0x84 +#define TW6864_R_BDMA_WHP_F2_0 0x214 //0x85 +#define TW6864_R_BDMA_ADDR_B_F2_0 0x218 //0x86 +#define TW6864_R_CSR_REG 0x3F4 //0xFD +#define TW6864_R_EP_REG_ADDR 0x3F8 //0xFE +#define TW6864_R_EP_REG_DATA 0x3FC //0xFF +#define TW6864_R_DROP_FIELD_REG(idx) (TW6864_R_DROP_FIELD_REG0+((idx)*4)) +#define TW6864_R_SHSCALER_REG(idx) (TW6864_R_SHSCALER_REG0+((idx)*4)) +#define TW6864_R_VIDEO_SIZE_REG(idx) (TW6864_R_VIDEO_SIZE_REG0+((idx)*4)) +#define TW6864_R_VIDEO_SIZE_REG_F2(idx) (TW6864_R_VIDEO_SIZE_REG0_F2+((idx)*4)) +#define TW6864_R_VC_CTRL_REG(idx) (TW6864_R_VC_CTRL_REG0+((idx)*4)) +#define TW6864_R_BDMA(reg, idx) ((reg)+(idx)*0x20) + +////////////////////////////////////////////////// +// external 2864 registers definitions +#define TW2864_R_I2C_ADDR0 0x50 +#define TW2864_R_I2C_ADDR1 0x52 +#define TW2864_R_VSTATUS_0 0x00 +#define TW2864_R_VSTATUS_1 0x10 +#define TW2864_R_VSTATUS_2 0x20 +#define TW2864_R_VSTATUS_3 0x30 +#define TW2864_R_VDELAY_0 0x08 +#define TW2864_R_VDELAY_1 0x18 +#define TW2864_R_VDELAY_2 0x28 +#define TW2864_R_VDELAY_3 0x38 +#define TW2864_R_HDELAY_0 0x0A +#define TW2864_R_HDELAY_1 0x1A +#define TW2864_R_HDELAY_2 0x2A +#define TW2864_R_HDELAY_3 0x3A +#define TW2864_R_STANDARD_0 0x0E +#define TW2864_R_STANDARD_1 0x1E +#define TW2864_R_STANDARD_2 0x2E +#define TW2864_R_STANDARD_3 0x3E +#define TW2864_R_CLK_SEL_2865 0x61 //specfic to TW2865 +#define TW2864_R_VERTICAL_SHARPNESS 0x85 +#define TW2864_R_VERTICAL_CTRL 0x8F +#define TW2864_R_LOOP_CTRL 0x95 +#define TW2864_R_MISC_CTRLII1 0x96 +#define TW2864_R_MISC_CTRLII2 0x97 +#define TW2864_R_MPP_MODE0 0xC8 +#define TW2864_R_MPP_MODE1 0xC9 +#define TW2864_R_NOVID 0x9E +#define TW2864_R_CLKODEL 0x9F +#define TW2864_R_HFLT1 0xA8 +#define TW2864_R_HFLT2 0xA9 +#define TW2864_R_VIDEO_OUT_CTRL 0xCA +#define TW2864_R_SMD 0xCF +#define TW2864_R_MASTER_CTRL 0xDB +#define TW2864_R_AUDIO_GAIN0 0xD0 +#define TW2864_R_AUDIO_GAIN1 0xD1 +#define TW2864_R_NUM_ADUIOR 0xD2 +#define TW2864_R_SEQ_ADUIOR 0xD7 +#define TW2864_R_MIX_OUT_SEL 0xE0 +#define TW2864_R_VSCALE_LO_0 0xE4 +#define TW2864_R_VHSCALE_HI_0 0xE5 +#define TW2864_R_HSCALE_LO_0 0xE6 +#define TW2864_R_VSCALE_LO_1 0xE7 +#define TW2864_R_VHSCALE_HI_1 0xE8 +#define TW2864_R_HSCALE_LO_1 0xE9 +#define TW2864_R_VSCALE_LO_2 0xEA +#define TW2864_R_VHSCALE_HI_2 0xEB +#define TW2864_R_HSCALE_LO_2 0xEC +#define TW2864_R_VSCALE_LO_3 0xED +#define TW2864_R_VHSCALE_HI_3 0xEE +#define TW2864_R_HSCALE_LO_3 0xEF +#define TW2864_R_VIDEO_MISC 0xF9 +#define TW2864_R_CLKOCTRL 0xFA +#define TW2864_R_CLKOPOL 0xFB +#define TW2864_R_AVDET_ENA 0xFC +#define TW2864_R_AVDET_STATE 0xFD +#define TW2864_R_DEV_REV 0xFE +#define TW2864_R_DEV_REV_LO 0xFF +#define TW2864_R_CHANNEL(reg, ch) ((reg)+(ch)*0x10) + +//0x01 0x11 0x21 0x31 BRIGHT BRIGHTNESS +#define TW2864_R_BRIGHT_0 0x01 +#define TW2864_R_BRIGHT_1 0x11 +#define TW2864_R_BRIGHT_2 0x21 +#define TW2864_R_BRIGHT_3 0x31 + +//0x02 0x12 0x22 0x32 CONTRAST CONTRAST +#define TW2864_R_CONTRAST_0 0x02 +#define TW2864_R_CONTRAST_1 0x12 +#define TW2864_R_CONTRAST_2 0x22 +#define TW2864_R_CONTRAST_3 0x32 + +//0x03 0x13 0x23 0x33 SHARPNESS SCURVE VSF CTI SHARPNESS +#define TW2864_R_SHARPNESS_0 0x03 +#define TW2864_R_SHARPNESS_1 0x13 +#define TW2864_R_SHARPNESS_2 0x23 +#define TW2864_R_SHARPNESS_3 0x33 + +//0x04 0x14 0x24 0x34 SAT_U SAT_U +#define TW2864_R_SAT_U_0 0x04 +#define TW2864_R_SAT_U_1 0x14 +#define TW2864_R_SAT_U_2 0x24 +#define TW2864_R_SAT_U_3 0x34 + +//0x05 0x15 0x25 0x35 SAT_V SAT_V +#define TW2864_R_SAT_V_0 0x05 +#define TW2864_R_SAT_V_1 0x15 +#define TW2864_R_SAT_V_2 0x25 +#define TW2864_R_SAT_V_3 0x35 + +//0x06 0x16 0x26 0x36 HUE HUE +#define TW2864_R_HUE_0 0x06 +#define TW2864_R_HUE_1 0x16 +#define TW2864_R_HUE_2 0x26 +#define TW2864_R_HUE_3 0x36 + +////////////////////////////////////////////////// +// internal 2864 registers definitions +#define TW6864_R_VSTATUS_0 0x400 //0x100 +#define TW6864_R_VSTATUS_1 0x440 //0x110 +#define TW6864_R_VSTATUS_2 0x480 //0x120 +#define TW6864_R_VSTATUS_3 0x4C0 //0x130 +#define TW6864_R_BRIGHT_0 0x404 //0x101 +#define TW6864_R_BRIGHT_1 0x444 //0x111 +#define TW6864_R_BRIGHT_2 0x484 //0x121 +#define TW6864_R_BRIGHT_3 0x4C4 //0x131 +#define TW6864_R_CONTRAST_0 0x408 //0x102 +#define TW6864_R_CONTRAST_1 0x448 //0x112 +#define TW6864_R_CONTRAST_2 0x488 //0x122 +#define TW6864_R_CONTRAST_3 0x4C8 //0x132 +#define TW6864_R_SHARPNESS_0 0x40C //0x103 +#define TW6864_R_SHARPNESS_1 0x44C //0x113 +#define TW6864_R_SHARPNESS_2 0x48C //0x123 +#define TW6864_R_SHARPNESS_3 0x4CC //0x133 +#define TW6864_R_SAT_U_0 0x410 //0x104 +#define TW6864_R_SAT_U_1 0x450 //0x114 +#define TW6864_R_SAT_U_2 0x490 //0x124 +#define TW6864_R_SAT_U_3 0x4D0 //0x134 +#define TW6864_R_SAT_V_0 0x414 //0x105 +#define TW6864_R_SAT_V_1 0x454 //0x115 +#define TW6864_R_SAT_V_2 0x494 //0x125 +#define TW6864_R_SAT_V_3 0x4D4 //0x135 +#define TW6864_R_HUE_0 0x418 //0x106 +#define TW6864_R_HUE_1 0x458 //0x116 +#define TW6864_R_HUE_2 0x498 //0x126 +#define TW6864_R_HUE_3 0x4D8 //0x136 +#define TW6864_R_CROPPING_0 0x41C //0x107 +#define TW6864_R_CROPPING_1 0x45C //0x117 +#define TW6864_R_CROPPING_2 0x49C //0x127 +#define TW6864_R_CROPPING_3 0x4DC //0x137 +#define TW6864_R_VDELAY_0 0x420 //0x108 +#define TW6864_R_VDELAY_1 0x460 //0x118 +#define TW6864_R_VDELAY_2 0x4A0 //0x128 +#define TW6864_R_VDELAY_3 0x4E0 //0x138 +#define TW6864_R_VACTIVE_0 0x424 //0x109 +#define TW6864_R_VACTIVE_1 0x464 //0x119 +#define TW6864_R_VACTIVE_2 0x4A4 //0x129 +#define TW6864_R_VACTIVE_3 0x4E4 //0x139 +#define TW6864_R_HDELAY_0 0x428 //0x10A +#define TW6864_R_HDELAY_1 0x468 //0x11A +#define TW6864_R_HDELAY_2 0x4A8 //0x12A +#define TW6864_R_HDELAY_3 0x4E8 //0x13A +#define TW6864_R_HACTIVE_0 0x42C //0x10B +#define TW6864_R_HACTIVE_1 0x46C //0x11B +#define TW6864_R_HACTIVE_2 0x4AC //0x12B +#define TW6864_R_HACTIVE_3 0x4EC //0x13B +#define TW6864_R_MACROVISION_0 0x430 //0x10C +#define TW6864_R_MACROVISION_1 0x470 //0x11C +#define TW6864_R_MACROVISION_2 0x4B0 //0x12C +#define TW6864_R_MACROVISION_3 0x4F0 //0x13C +#define TW6864_R_CHIPSTATUSII_0 0x434 //0x10D +#define TW6864_R_CHIPSTATUSII_1 0x474 //0x11D +#define TW6864_R_CHIPSTATUSII_2 0x4B4 //0x12D +#define TW6864_R_CHIPSTATUSII_3 0x4F4 //0x13D +#define TW6864_R_STANDARD_0 0x438 //0x10E +#define TW6864_R_STANDARD_1 0x478 //0x11E +#define TW6864_R_STANDARD_2 0x4B8 //0x12E +#define TW6864_R_STANDARD_3 0x4F8 //0x13E +#define TW6864_R_STDDETECT_0 0x43C //0x10F +#define TW6864_R_STDDETECT_1 0x47C //0x11F +#define TW6864_R_STDDETECT_2 0x4BC //0x12F +#define TW6864_R_STDDETECT_3 0x4FC //0x13F +#define TW6864_R_VSCALE_LO_0 0x510 //0x144 +#define TW6864_R_VSCALE_LO_1 0x550 //0x154 +#define TW6864_R_VSCALE_LO_2 0x590 //0x164 +#define TW6864_R_VSCALE_LO_3 0x5D0 //0x174 +#define TW6864_R_VHSCALE_HI_0 0x514 //0x145 +#define TW6864_R_VHSCALE_HI_1 0x554 //0x155 +#define TW6864_R_VHSCALE_HI_2 0x594 //0x165 +#define TW6864_R_VHSCALE_HI_3 0x5D4 //0x175 +#define TW6864_R_HSCALE_LO_0 0x518 //0x146 +#define TW6864_R_HSCALE_LO_1 0x558 //0x156 +#define TW6864_R_HSCALE_LO_2 0x598 //0x166 +#define TW6864_R_HSCALE_LO_3 0x5D8 //0x176 +#define TW6864_R_CROPPING_F2_0 0x51C //0x147 +#define TW6864_R_CROPPING_F2_1 0x55C //0x157 +#define TW6864_R_CROPPING_F2_2 0x59C //0x167 +#define TW6864_R_CROPPING_F2_3 0x5DC //0x177 +#define TW6864_R_VDELAY_F2_0 0x520 //0x148 +#define TW6864_R_VDELAY_F2_1 0x560 //0x158 +#define TW6864_R_VDELAY_F2_2 0x5A0 //0x168 +#define TW6864_R_VDELAY_F2_3 0x5E0 //0x178 +#define TW6864_R_VACTIVE_F2_0 0x524 //0x149 +#define TW6864_R_VACTIVE_F2_1 0x564 //0x159 +#define TW6864_R_VACTIVE_F2_2 0x5A4 //0x169 +#define TW6864_R_VACTIVE_F2_3 0x5E4 //0x179 +#define TW6864_R_HDELAY_F2_0 0x528 //0x14A +#define TW6864_R_HDELAY_F2_1 0x568 //0x15A +#define TW6864_R_HDELAY_F2_2 0x5A8 //0x16A +#define TW6864_R_HDELAY_F2_3 0x5E8 //0x17A +#define TW6864_R_HACTIVE_F2_0 0x52C //0x14B +#define TW6864_R_HACTIVE_F2_1 0x56C //0x15B +#define TW6864_R_HACTIVE_F2_2 0x5AC //0x16B +#define TW6864_R_HACTIVE_F2_3 0x5EC //0x17B +#define TW6864_R_VSCALE_LO_F2_0 0x530 //0x14C +#define TW6864_R_VSCALE_LO_F2_1 0x570 //0x15C +#define TW6864_R_VSCALE_LO_F2_2 0x5B0 //0x16C +#define TW6864_R_VSCALE_LO_F2_3 0x5F0 //0x17C +#define TW6864_R_VHSCALE_HI_F2_0 0x534 //0x14D +#define TW6864_R_VHSCALE_HI_F2_1 0x574 //0x15D +#define TW6864_R_VHSCALE_HI_F2_2 0x5B4 //0x16D +#define TW6864_R_VHSCALE_HI_F2_3 0x5F4 //0x17D +#define TW6864_R_HSCALE_LO_F2_0 0x538 //0x14E +#define TW6864_R_HSCALE_LO_F2_1 0x578 //0x15E +#define TW6864_R_HSCALE_LO_F2_2 0x5B8 //0x16E +#define TW6864_R_HSCALE_LO_F2_3 0x5F8 //0x17E +#define TW6864_R_F2_CNT_0 0x53C //0x14F +#define TW6864_R_F2_CNT_1 0x57C //0x15F +#define TW6864_R_F2_CNT_2 0x5BC //0x16F +#define TW6864_R_F2_CNT_3 0x5FC //0x17F +#define TW6864_R_AVSRST 0x600 //0x180 +#define TW6864_R_COLORKILL_HY 0x610 //0x184 +#define TW6864_R_VERTICAL_CTRL 0x63C //0x18F +#define TW6864_R_LOOP_CTRL 0x654 //0x195 +#define TW6864_R_MISC_CTRLII1 0x658 //0x196 +#define TW6864_R_MISC_CTRLII2 0x65C //0x197 +#define TW6864_R_HFLT1 0x6A0 //0x1A8 +#define TW6864_R_HFLT2 0x6A4 //0x1A9 +#define TW6864_R_AUDIO_GAIN_0 0x740 //0x1D0 +#define TW6864_R_AUDIO_GAIN_1 0x744 //0x1D1 +#define TW6864_R_AUDIO_GAIN_2 0x748 //0x1D2 +#define TW6864_R_AUDIO_GAIN_3 0x74C //0x1D3 + +#define TW6864_R_VSTATUS(ch) (TW6864_R_VSTATUS_0 + (ch)*0x40) +#define TW6864_R_BRIGHT(ch) (TW6864_R_BRIGHT_0 + (ch)*0x40) +#define TW6864_R_CONTRAST(ch) (TW6864_R_CONTRAST_0 + (ch)*0x40) +#define TW6864_R_SHARPNESS(ch) (TW6864_R_SHARPNESS_0 + (ch)*0x40) +#define TW6864_R_SAT_U(ch) (TW6864_R_SAT_U_0 + (ch)*0x40) +#define TW6864_R_SAT_V(ch) (TW6864_R_SAT_V_0 + (ch)*0x40) +#define TW6864_R_HUE(ch) (TW6864_R_HUE_0 + (ch)*0x40) +#define TW6864_R_CROPPING(ch) (TW6864_R_CROPPING_0 + (ch)*0x40) +#define TW6864_R_VDELAY(ch) (TW6864_R_VDELAY_0 + (ch)*0x40) +#define TW6864_R_VACTIVE(ch) (TW6864_R_VACTIVE_0 + (ch)*0x40) +#define TW6864_R_HDELAY(ch) (TW6864_R_HDELAY_0 + (ch)*0x40) +#define TW6864_R_HACTIVE(ch) (TW6864_R_HACTIVE_0 + (ch)*0x40) +#define TW6864_R_CROPPING_F2(ch) (TW6864_R_CROPPING_F2_0 + (ch)*0x40) +#define TW6864_R_VDELAY_F2(ch) (TW6864_R_VDELAY_F2_0 + (ch)*0x40) +#define TW6864_R_VACTIVE_F2(ch) (TW6864_R_VACTIVE_F2_0 + (ch)*0x40) +#define TW6864_R_HDELAY_F2(ch) (TW6864_R_HDELAY_F2_0 + (ch)*0x40) +#define TW6864_R_HACTIVE_F2(ch) (TW6864_R_HACTIVE_F2_0 + (ch)*0x40) +#define TW6864_R_MACROVISION(ch) (TW6864_R_MACROVISION_0 + (ch)*0x40) +#define TW6864_R_CHIPSTATUSII(ch) (TW6864_R_CHIPSTATUSII_0 + (ch)*0x40) +#define TW6864_R_STANDARD(ch) (TW6864_R_STANDARD_0 + (ch)*0x40) +#define TW6864_R_STDDETECT(ch) (TW6864_R_STDDETECT_0 + (ch)*0x40) +#define TW6864_R_CHANNEL(reg, ch) ((reg)+(ch)*0x40) + +////////////////////////////////////////////////// +//NO NOT CHANGE +//These 3 definitions are matched with hardware +#define TW6864_MAX_NUM_SG_DMA 8 +#define TW6864_MAX_NUM_DATA_DMA 9 +#define TW6864_MAX_NUM_DMA (TW6864_MAX_NUM_SG_DMA+TW6864_MAX_NUM_DATA_DMA) + +#define TW6864_VIDEO_GEN 0xFF //1=External,0=internal +#define TW6864_VIDEO_GEN_PATTERNS 0x00 //0=ColorBar, 1=SEQ DATA , 8-bit +#define TW6864_VIDEO_FORMAT_UYVY 0 +#define TW6864_VIDEO_FORMAT_YUV420 1 +#define TW6864_VIDEO_FORMAT_Y411 2 //Use VIDEO_FORMAT_IYU1 instead, LEAD does not accept this, even they are the same +#define TW6864_VIDEO_FORMAT_Y41P 3 +#define TW6864_VIDEO_FORMAT_RGB555 4 +#define TW6864_VIDEO_FORMAT_RGB565 5 +#define TW6864_VIDEO_FORMAT_IYU1 0xA //same as VIDEO_FORMAT_Y411, m_nVideoFormat will be masked by 0x7 +#define TW6864_VIDEO_FORMAT_YUYV 6 //YUYV=YUY2, only available for Rev.B or later +#define TW6864_VIDEO_FORMAT_UYVY_FRAME 0x8 //VideoInfoHeader2 + +#define CaptureFLV 0 + +#define TW6864_AUDIO_DMA_LENGTH 4096 //in bytes, should not > PAGE_SIZE(4096) +#define TW6864_AUDIO_GEN 1 //1=External,0=internal +#define TW6864_AUDIO_GEN_PATTERN 0 //0=WAVE,1=SEQ DATA , 1-bit +#define TW6864_AUDIO_GEN_MIX_SEL 0 //(0~7) +#define TW6864_AUDIO_SAMPLE_RATE 8 //in KHz +#define TW6864_AUDIO_SAMPLE_BIT (CaptureFLV*8+8) //8bit or 16bit +#define TW6864_AUDIO_SAMP_RATE_INT(rate) (125000/(rate)) +#define TW6864_AUDIO_SAMP_RATE_EXT(rate) ((((unsigned long long)125000)<<16)/(rate)) + +//for TW6869 +//#define FPGA + +#define TW6869_R_MD_CONF0 0x180 //0x60 //TW6869 or later, 0x60 ~ 0x67 +#define TW6869_R_MD_INIT0 0x1A0 //0x68 //TW6869 or later, 0x68 ~ 0x6F +#define TW6869_R_MD_MAP0 0x1C0 //0x70 //TW6869 or later, 0x70 ~ 0x77 + +#define TW6869_R_DMA_PAGE_TABLE0 TW6864_R_DMA_PAGE_TABLE0 //0x020 //0x08 //RW +#define TW6869_R_DMA_PAGE_TABLE1 TW6864_R_DMA_PAGE_TABLE1 //0x024 //0x09 +#define TW6869_R_DMA_PAGE_TABLE2 0x340 //0xD0 +#define TW6869_R_DMA_PAGE_TABLE3 0x344 //0xD1 +#define TW6869_R_DMA_PAGE_TABLE4 0x348 //0xD2 +#define TW6869_R_DMA_PAGE_TABLE5 0x34C //0xD3 +#define TW6869_R_DMA_PAGE_TABLE6 0x350 //0xD4 +#define TW6869_R_DMA_PAGE_TABLE7 0x354 //0xD5 +#define TW6869_R_DMA_PAGE_TABLE8 0x358 //0xD6 +#define TW6869_R_DMA_PAGE_TABLE9 0x35C //0xD7 +#define TW6869_R_DMA_PAGE_TABLEA 0x360 //0xD8 +#define TW6869_R_DMA_PAGE_TABLEB 0x364 //0xD9 +#define TW6869_R_DMA_PAGE_TABLEC 0x368 //0xDA +#define TW6869_R_DMA_PAGE_TABLED 0x36C //0xDB +#define TW6869_R_DMA_PAGE_TABLEE 0x370 //0xDC +#define TW6869_R_DMA_PAGE_TABLEF 0x374 //0xDD +#define TW6869_R_GPIO_REG_1 0x380 //0xE0 +#define TW6869_R_GPIO_REG_2 0x384 //0xE1 +#define TW6869_R_PIN_CFG_CTRL 0x3EC //0xFB +#define TW6869_R_VSTATUS_4 0x800 //0x200 +#define TW6869_R_VSTATUS_5 0x840 //0x210 +#define TW6869_R_VSTATUS_6 0x880 //0x220 +#define TW6869_R_VSTATUS_7 0x8C0 //0x230 +#define TW6869_R_BRIGHT_4 0x804 //0x201 +#define TW6869_R_BRIGHT_5 0x844 //0x211 +#define TW6869_R_BRIGHT_6 0x884 //0x221 +#define TW6869_R_BRIGHT_7 0x8C4 //0x231 +#define TW6869_R_CONTRAST_4 0x808 //0x202 +#define TW6869_R_CONTRAST_5 0x848 //0x212 +#define TW6869_R_CONTRAST_6 0x888 //0x222 +#define TW6869_R_CONTRAST_7 0x8C8 //0x232 +#define TW6869_R_SHARPNESS_4 0x80C //0x203 +#define TW6869_R_SHARPNESS_5 0x84C //0x213 +#define TW6869_R_SHARPNESS_6 0x88C //0x223 +#define TW6869_R_SHARPNESS_7 0x8CC //0x233 +#define TW6869_R_SAT_U_4 0x810 //0x204 +#define TW6869_R_SAT_U_5 0x850 //0x214 +#define TW6869_R_SAT_U_6 0x890 //0x224 +#define TW6869_R_SAT_U_7 0x8D0 //0x234 +#define TW6869_R_SAT_V_4 0x814 //0x205 +#define TW6869_R_SAT_V_5 0x854 //0x215 +#define TW6869_R_SAT_V_6 0x894 //0x225 +#define TW6869_R_SAT_V_7 0x8D4 //0x235 +#define TW6869_R_HUE_4 0x818 //0x206 +#define TW6869_R_HUE_5 0x858 //0x216 +#define TW6869_R_HUE_6 0x898 //0x226 +#define TW6869_R_HUE_7 0x8D8 //0x236 +#define TW6869_R_CROPPING_4 0x81C //0x207 +#define TW6869_R_CROPPING_5 0x85C //0x217 +#define TW6869_R_CROPPING_6 0x89C //0x227 +#define TW6869_R_CROPPING_7 0x8DC //0x237 +#define TW6869_R_VDELAY_4 0x820 //0x208 +#define TW6869_R_VDELAY_5 0x860 //0x218 +#define TW6869_R_VDELAY_6 0x8A0 //0x228 +#define TW6869_R_VDELAY_7 0x8E0 //0x238 +#define TW6869_R_VACTIVE_4 0x824 //0x209 +#define TW6869_R_VACTIVE_5 0x864 //0x219 +#define TW6869_R_VACTIVE_6 0x8A4 //0x229 +#define TW6869_R_VACTIVE_7 0x8E4 //0x239 +#define TW6869_R_HDELAY_4 0x828 //0x20A +#define TW6869_R_HDELAY_5 0x868 //0x21A +#define TW6869_R_HDELAY_6 0x8A8 //0x22A +#define TW6869_R_HDELAY_7 0x8E8 //0x23A +#define TW6869_R_HACTIVE_4 0x82C //0x20B +#define TW6869_R_HACTIVE_5 0x86C //0x21B +#define TW6869_R_HACTIVE_6 0x8AC //0x22B +#define TW6869_R_HACTIVE_7 0x8EC //0x23B +#define TW6869_R_MACROVISION_4 0x830 //0x20C +#define TW6869_R_MACROVISION_5 0x870 //0x21C +#define TW6869_R_MACROVISION_6 0x8B0 //0x22C +#define TW6869_R_MACROVISION_7 0x8F0 //0x23C +#define TW6869_R_CHIPSTATUSII_4 0x834 //0x20D +#define TW6869_R_CHIPSTATUSII_5 0x874 //0x21D +#define TW6869_R_CHIPSTATUSII_6 0x8B4 //0x22D +#define TW6869_R_CHIPSTATUSII_7 0x8F4 //0x23D +#define TW6869_R_STANDARD_4 0x838 //0x20E +#define TW6869_R_STANDARD_5 0x878 //0x21E +#define TW6869_R_STANDARD_6 0x8B8 //0x22E +#define TW6869_R_STANDARD_7 0x8F8 //0x23E +#define TW6869_R_STDDETECT_4 0x83C //0x20F +#define TW6869_R_STDDETECT_5 0x87C //0x21F +#define TW6869_R_STDDETECT_6 0x8BC //0x22F +#define TW6869_R_STDDETECT_7 0x8FC //0x23F +#define TW6869_R_VSCALE_LO_4 0x910 //0x244 +#define TW6869_R_VSCALE_LO_5 0x950 //0x254 +#define TW6869_R_VSCALE_LO_6 0x990 //0x264 +#define TW6869_R_VSCALE_LO_7 0x9D0 //0x274 +#define TW6869_R_VHSCALE_HI_4 0x914 //0x245 +#define TW6869_R_VHSCALE_HI_5 0x954 //0x255 +#define TW6869_R_VHSCALE_HI_6 0x994 //0x265 +#define TW6869_R_VHSCALE_HI_7 0x9D4 //0x275 +#define TW6869_R_HSCALE_LO_4 0x918 //0x246 +#define TW6869_R_HSCALE_LO_5 0x958 //0x256 +#define TW6869_R_HSCALE_LO_6 0x998 //0x266 +#define TW6869_R_HSCALE_LO_7 0x9D8 //0x276 +#define TW6869_R_CROPPING_F2_4 0x91C //0x247 +#define TW6869_R_CROPPING_F2_5 0x95C //0x257 +#define TW6869_R_CROPPING_F2_6 0x99C //0x267 +#define TW6869_R_CROPPING_F2_7 0x9DC //0x277 +#define TW6869_R_VDELAY_F2_4 0x920 //0x248 +#define TW6869_R_VDELAY_F2_5 0x960 //0x258 +#define TW6869_R_VDELAY_F2_6 0x9A0 //0x268 +#define TW6869_R_VDELAY_F2_7 0x9E0 //0x278 +#define TW6869_R_VACTIVE_F2_4 0x924 //0x249 +#define TW6869_R_VACTIVE_F2_5 0x964 //0x259 +#define TW6869_R_VACTIVE_F2_6 0x9A4 //0x269 +#define TW6869_R_VACTIVE_F2_7 0x9E4 //0x279 +#define TW6869_R_HDELAY_F2_4 0x928 //0x24A +#define TW6869_R_HDELAY_F2_5 0x968 //0x25A +#define TW6869_R_HDELAY_F2_6 0x9A8 //0x26A +#define TW6869_R_HDELAY_F2_7 0x9E8 //0x27A +#define TW6869_R_HACTIVE_F2_4 0x92C //0x24B +#define TW6869_R_HACTIVE_F2_5 0x96C //0x25B +#define TW6869_R_HACTIVE_F2_6 0x9AC //0x26B +#define TW6869_R_HACTIVE_F2_7 0x9EC //0x27B +#define TW6869_R_VSCALE_LO_F2_4 0x930 //0x24C +#define TW6869_R_VSCALE_LO_F2_5 0x970 //0x25C +#define TW6869_R_VSCALE_LO_F2_6 0x9B0 //0x26C +#define TW6869_R_VSCALE_LO_F2_7 0x9F0 //0x27C +#define TW6869_R_VHSCALE_HI_F2_4 0x934 //0x24D +#define TW6869_R_VHSCALE_HI_F2_5 0x974 //0x25D +#define TW6869_R_VHSCALE_HI_F2_6 0x9B4 //0x26D +#define TW6869_R_VHSCALE_HI_F2_7 0x9F4 //0x27D +#define TW6869_R_HSCALE_LO_F2_4 0x938 //0x24E +#define TW6869_R_HSCALE_LO_F2_5 0x978 //0x25E +#define TW6869_R_HSCALE_LO_F2_6 0x9B8 //0x26E +#define TW6869_R_HSCALE_LO_F2_7 0x9F8 //0x27E +#define TW6869_R_F2_CNT_4 0x93C //0x24F +#define TW6869_R_F2_CNT_5 0x97C //0x25F +#define TW6869_R_F2_CNT_6 0x9BC //0x26F +#define TW6869_R_F2_CNT_7 0x9FC //0x27F +#define TW6869_R_VERTICAL_CTRL 0xA3C //0x28F +#define TW6869_R_LOOP_CTRL 0xA54 //0x295 +#define TW6869_R_HFLT1 0xAA0 //0x2A8 +#define TW6869_R_HFLT2 0xAA4 //0x2A9 +#define TW6869_R_AUDIO_GAIN_4 0xB40 //0x2D0 +#define TW6869_R_AUDIO_GAIN_5 0xB44 //0x2D1 +#define TW6869_R_AUDIO_GAIN_6 0xB48 //0x2D2 +#define TW6869_R_AUDIO_GAIN_7 0xB4C //0x2D3 + +#endif // TW686XREG_H_INCLUDED diff --git a/drivers/pci/pcie/tw6869/tw686x-video.c b/drivers/pci/pcie/tw6869/tw686x-video.c new file mode 100644 index 000000000000..41eacf10a49d --- /dev/null +++ b/drivers/pci/pcie/tw6869/tw686x-video.c @@ -0,0 +1,1670 @@ +/* + * Driver for Intersil|Techwell TW6869 based DVR cards + * + * (c) 2011-12 liran <jli11@intersil.com> [Intersil|Techwell China] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/list.h> +#include <linux/pagemap.h> +#include <linux/module.h> + +#include <media/v4l2-common.h> +#include <media/v4l2-ioctl.h> + +#include "tw686x.h" +#include "tw686x-device.h" +#include "tw686x-reg.h" + +MODULE_DESCRIPTION("v4l2 driver module for tw686x based DVR cards"); +MODULE_AUTHOR("liran <jli11@intersil.com>"); +MODULE_LICENSE("GPL"); + +static int tw686x_vidcount = 0; + +/* ------------------------------------------------------------------- */ +/* static data */ + +#define FORMAT_FLAGS_PACKED 0x01 + +static struct tw686x_fmt formats[] = { + { + .name = "4:2:2, packed, YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + }, { + .name = "4:2:2, packed, UYVY", + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + }, { + .name = "15 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_RGB555, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + }, { + .name = "16 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_RGB565, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + }, { + .name = "4:1:1, packed, Y41P", + .fourcc = V4L2_PIX_FMT_Y41P, + .depth = 12, + .flags = FORMAT_FLAGS_PACKED, + }, +}; + +static const struct v4l2_queryctrl no_ctrl = { + .name = "42", + .flags = V4L2_CTRL_FLAG_DISABLED, +}; +static const struct v4l2_queryctrl video_ctrls[] = { + /* --- video --- */ + { + .id = V4L2_CID_BRIGHTNESS, + .name = "Brightness", + .minimum = -128, + .maximum = 127, + .step = 1, + .default_value = 20, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_CONTRAST, + .name = "Contrast", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 100, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_SATURATION, + .name = "Saturation", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 128, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_HUE, + .name = "Hue", + .minimum = -128, + .maximum = 127, + .step = 1, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, +}; +static const unsigned int CTRLS = ARRAY_SIZE(video_ctrls); + +static const struct v4l2_queryctrl* ctrl_by_id(unsigned int id) +{ + unsigned int i; + + for (i = 0; i < CTRLS; i++) + if (video_ctrls[i].id == id) + return video_ctrls+i; + return NULL; +} + +static struct tw686x_fmt *format_by_fourcc(unsigned int fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); i++) + if (formats[i].fourcc == fourcc) + return formats+i; + + printk(KERN_ERR "%s(0x%08x) NOT FOUND\n", __func__, fourcc); + return NULL; +} + +/* ----------------------------------------------------------------------- */ +/* resource management */ + +static int res_get(struct tw686x_vdev *dev, struct tw686x_fh *fh, unsigned int bit) +{ + if (fh->resources & bit) + /* have it already allocated */ + return 1; + + /* is it free? */ + mutex_lock(&dev->lock); + if (dev->resources & bit) { + /* no, someone else uses it */ + mutex_unlock(&dev->lock); + return 0; + } + /* it's free, grab it */ + fh->resources |= bit; + dev->resources |= bit; + dvprintk(DPRT_LEVEL0, dev, "res: get %d\n",bit); + mutex_unlock(&dev->lock); + + return 1; +} + +static int res_check(struct tw686x_fh *fh, unsigned int bit) +{ + return (fh->resources & bit); +} + +static int res_locked(struct tw686x_vdev *dev, unsigned int bit) +{ + return (dev->resources & bit); +} + +static +void res_free(struct tw686x_vdev *dev, struct tw686x_fh *fh, unsigned int bits) +{ + BUG_ON((fh->resources & bits) != bits); + + mutex_lock(&dev->lock); + fh->resources &= ~bits; + dev->resources &= ~bits; + dvprintk(DPRT_LEVEL0, dev, "res: put %d\n",bits); + mutex_unlock(&dev->lock); +} + +void tw686x_video_start(struct tw686x_vdev* dev) +{ + struct tw686x_dmaqueue *q = &dev->video_q; + struct tw686x_buf *buf; + struct list_head *item, *item_temp; + bool b_active = false; + + //dvprintk(1, dev, "%s()\n", __func__); + + if( !res_locked(dev, RESOURCE_VIDEO) ) { + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); //主要目的是在没有中断产生时,停止采集后释放所有buffer,否则可能streamoff死等 + return; + } + + list_for_each(item, &q->active) { + buf = list_entry(item, struct tw686x_buf, active); + b_active = IS_TW686X_DMA_MODE_BLOCK ? + (dev->pb_next < dev->buf_needs) : (dev->pb_next<=buf->sg_ofo) ; + if( b_active ) { + buf->activate( dev, buf, dev->pb_curr, true ); + } + dev->pb_next++; + } + + list_for_each(item, &q->queued) { + buf = list_entry(item, struct tw686x_buf, vb.queue); + b_active = IS_TW686X_DMA_MODE_BLOCK ? + (dev->pb_next < dev->buf_needs) : (dev->pb_next<=buf->sg_ofo) ; + if( b_active ) { + item_temp = item; + item = item->prev; + list_del(item_temp); + buf->activate( dev, buf, dev->pb_curr, false ); + } + dev->pb_next++; + } + + if(dev->pb_next > 0) { + tw686x_dev_run( dev, true ); + dev->dma_started = true; + } +} + +static void tw686x_video_restart_timeout(unsigned long data) +{ + struct tw686x_vdev* dev = (struct tw686x_vdev*)data; + unsigned long flags; + + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + spin_lock_irqsave(dev->slock, flags); + dev->dma_started = false; + dev->pb_next = 0; + dev->pb_curr = 0; + tw686x_video_start(dev); + spin_unlock_irqrestore(dev->slock, flags); +} + +int tw686x_video_restart(struct tw686x_vdev *dev, bool b_framerate) +{ + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + +// assert_spin_locked(dev->slock); + + tw686x_dev_run( dev, false ); + dev->dma_started = false; + dev->pb_next = 0; + dev->pb_curr = 0; + + if(b_framerate) { + tw686x_dev_set_framerate(dev, dev->framerate, dev->field); + } + + if(tw686x_restart_timer) { + mod_timer(&dev->tm_restart, jiffies+(BUFFER_TIMEOUT/50)); + } + else { + tw686x_video_start(dev); + } + + return 0; +} + +static int tw686x_set_tvnorm(struct tw686x_vdev *dev, v4l2_std_id norm) +{ + dvprintk(DPRT_LEVEL0, dev, "%s(norm = 0x%08x) name: [%s]\n", + __func__, + (unsigned int)norm, + v4l2_norm_to_name(norm)); + + mutex_lock(&dev->lock); + dev->tvnorm = norm; + tw686x_dev_set_standard( dev, norm, true ); + +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)) + call_all(dev->chip, core, s_std, norm); +#endif + mutex_unlock(&dev->lock); + + return 0; +} + +static int tw686x_buffer_next(struct tw686x_vdev *dev, + struct tw686x_dmaqueue *q, int dma_pb) +{ + struct tw686x_buf *buf; + int ret = -EINVAL; + + dvprintk(DPRT_LEVEL0, dev, "%s(%d)\n", __func__, dev->pb_next); + +// assert_spin_locked(dev->slock); + if (!list_empty(&q->queued)) { + /* activate next one from queue */ + buf = list_entry(q->queued.next,struct tw686x_buf,vb.queue); + list_del(&buf->vb.queue); + ret = buf->activate(dev, buf, dma_pb, false); + } + else { + dvprintk(DPRT_LEVEL0, dev, "buffer_next %p\n",NULL); + } + + return ret; +} + +static bool tw686x_buffer_wakeup(struct tw686x_vdev *dev, struct tw686x_dmaqueue *q, u32 state) +{ + struct tw686x_buf *buf; + bool ret = false; + + dvprintk(DPRT_LEVEL0, dev, "%s(%d)\n", __func__, state); + +// assert_spin_locked(dev->slock); + if( !list_empty(&q->active) ) { + /* finish current buffer */ + buf = list_entry(q->active.next, struct tw686x_buf, active); + if( IS_TW686X_DMA_MODE_BLOCK || (state==VIDEOBUF_ERROR) ) { + buf->vb.state = state; + do_gettimeofday(&buf->vb.ts); + list_del(&buf->active); + wake_up(&buf->vb.done); + dev->pb_next--; + ret = true; + } + else { + buf->sg_bytes_to -= buf->sg_bytes_pb; + if( buf->sg_bytes_to>buf->sg_bytes_pb ) { + tw686x_dev_set_dmadesc( dev, dev->pb_curr, buf ); + } + else if( buf->sg_bytes_to<=0 ) { + buf->vb.state = state; + do_gettimeofday(&buf->vb.ts); + list_del(&buf->active); + wake_up(&buf->vb.done); + dev->pb_next--; + if( !list_empty(&q->active) ) { + /* finish current buffer */ + //notice!!! 这个地方与buffer_active里边同时开启的话,会造成dma descriptor重复填充 + //buf = list_entry(q->active.next, struct tw686x_buf, active); + //tw686x_dev_set_dmadesc( dev, dev->pb_curr, buf ); + } + else { + ret = true; + } + } + else { + ret = true; + } + } + dvprintk(DPRT_LEVEL0, dev, "wake up buffer %d, %d\n", buf->vb.i, buf->vb.state); + } + if( list_empty(&q->active) ) { + del_timer(&q->timeout); + } + + return ret; +} + +static int tw686x_buffer_cancel(struct tw686x_vdev *dev, struct tw686x_dmaqueue *q, u32 state) +{ + struct tw686x_buf *buf; + int ret = -EINVAL; + + dvprintk(DPRT_LEVEL0, dev, "%s(%d)\n", __func__, dev->pb_next); + +// assert_spin_locked(dev->slock); + if( !list_empty(&q->queued) ) { + /* finish current buffer */ + buf = list_entry(q->queued.next, struct tw686x_buf, vb.queue); + buf->vb.state = state; + do_gettimeofday(&buf->vb.ts); + list_del(&buf->vb.queue); + wake_up(&buf->vb.done); + dev->pb_next--; + ret = true; + } + + return ret; +} + +static void tw686x_buffer_timeout(unsigned long data) +{ + struct tw686x_vdev *dev = (struct tw686x_vdev *)data; + struct tw686x_dmaqueue *q = &dev->video_q; + unsigned long flags; + + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + spin_lock_irqsave(dev->slock, flags); + +#if(1) + if( !res_locked(dev, RESOURCE_VIDEO) ) { + while (!list_empty(&q->active)) { + tw686x_buffer_wakeup( dev, q, VIDEOBUF_ERROR ); + } + while (!list_empty(&q->queued)) { + tw686x_buffer_cancel( dev, q, VIDEOBUF_ERROR ); + } + } + else { + tw686x_video_restart( dev, false ); + } +#else +{ + int pb_status = dev->vstatus; + if( !res_locked(dev, RESOURCE_VIDEO) ) { + while (!list_empty(&q->active)) { + tw686x_buffer_wakeup( dev, q, VIDEOBUF_ERROR ); + } + while (!list_empty(&q->queued)) { + tw686x_buffer_cancel( dev, q, VIDEOBUF_ERROR ); + } + pb_status = 0; + } + else { + if( !IS_TW686X_DMA_MODE_BLOCK ) { + dev->pb_curr = pb_status; + pb_status = !pb_status; + } + if( tw686x_buffer_wakeup(dev, &dev->video_q, VIDEOBUF_DONE) && + (dev->pb_next>=dev->buf_needs) ) { + tw686x_buffer_next(dev, &dev->video_q, dev->pb_curr); + } + else if( dev->pb_next<=0 ) { + tw686x_dev_run( dev, false ); + dev->dma_started = false; + dev->pb_next = 0; + dev->pb_curr = 0; + } + mod_timer(&q->timeout, jiffies+(BUFFER_TIMEOUT/20)); + } + dev->vstatus = pb_status; +} +#endif + + spin_unlock_irqrestore(dev->slock, flags); +} + +static int buffer_activate(struct tw686x_vdev *dev, struct tw686x_buf *buf, + int dma_pb, bool queued) +{ + struct tw686x_dmaqueue *q = &dev->video_q; + + dvprintk(DPRT_LEVEL0, dev, "activate buf=%d\n", buf->vb.i); + + buf->vb.state = VIDEOBUF_ACTIVE; + if(!queued) { + list_add_tail(&buf->active, &q->active); + } + + if( IS_TW686X_DMA_MODE_BLOCK ) { + tw686x_dev_set_dmabuf(dev, dma_pb, buf); + } + else { + buf->sg = NULL; + buf->sg_offset = 0; + buf->sg_bytes_to = 0; + //if((dev->pb_next==0) || (buf->sg_ofo==0)) //notice!!! 这里可能造成dmadesc重复填充,需要再仔细斟酌 + tw686x_dev_set_dmadesc(dev, dma_pb, buf); + } + dev->pb_curr = (dev->pb_curr + 1) % dev->buf_needs; + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + + return 0; +} + +static void buffer_release(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct videobuf_dmabuf *dma; + struct tw686x_fh *fh = vq->priv_data; + struct tw686x_vdev *dev = fh->dev; + + dvprintk(DPRT_LEVEL0, dev, "%s(%d) state=%d\n", __func__, vb->i, vb->state); + + BUG_ON(in_interrupt()); + +#if(LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)) + videobuf_waiton(vq, vb, 0, 0); +#else + videobuf_waiton(vb, 0, 0); +#endif + + if( IS_TW686X_DMA_MODE_BLOCK ) { + videobuf_dma_contig_free_isl(vq, vb); + } + else { + dma = videobuf_to_dma(vb); +#if(LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)) + videobuf_dma_unmap(vq->dev, dma); +#else + videobuf_dma_unmap(vq, dma); +#endif + videobuf_dma_free(dma); + } + + vb->state = VIDEOBUF_NEEDS_INIT; +} + +static int buffer_setup(struct videobuf_queue *vq, unsigned int *count, + unsigned int *size) +{ + struct tw686x_fh *fh = vq->priv_data; + struct tw686x_vdev *dev = fh->dev; + + dvprintk(DPRT_LEVEL0, dev, "%s(%d)\n", __func__, *count); + + *size = (fh->fmt->depth*fh->width*fh->height) >> 3; + + if (!*count || (*count > TW686X_BUFFER_COUNT)) { + *count = TW686X_BUFFER_COUNT; + } + + return 0; +} + +static int buffer_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct tw686x_fh *fh = q->priv_data; + struct tw686x_vdev *dev = fh->dev; + struct tw686x_buf *buf= container_of(vb, struct tw686x_buf, vb); + int rc; + + dvprintk(DPRT_LEVEL0, dev, "%s(%d)\n", __func__, vb->i); + + BUG_ON(NULL == fh->fmt); + + if (fh->width < TW686X_MIN_WIDTH || fh->width > norm_maxw(dev->tvnorm) || + fh->height < TW686X_MIN_HEIGHT || fh->height > norm_maxh(dev->tvnorm)) + return -EINVAL; + + buf->vb.size = (fh->width * fh->height * fh->fmt->depth) >> 3; + if(buf->vb.size <= TW686X_SG_PBLEN) { + buf->sg_ofo = 1; + buf->sg_bytes_pb = buf->vb.size; + } + else { + buf->sg_ofo = 0; + buf->sg_bytes_pb = buf->vb.size / ((buf->vb.size<=TW686X_SG_PBLEN*2) ? 2 : 4); + } + if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size) + return -EINVAL; + + if (buf->fmt != fh->fmt || + buf->vb.width != fh->width || + buf->vb.height != fh->height || + buf->vb.field != field) { + buf->fmt = fh->fmt; + buf->vb.width = fh->width; + buf->vb.height = fh->height; + buf->vb.field = field; + buf->vb.state = VIDEOBUF_NEEDS_INIT; + } + + if (VIDEOBUF_NEEDS_INIT == buf->vb.state) { + rc = videobuf_iolock(q, &buf->vb, NULL); + if (0 != rc) + goto fail; + } + + dvprintk(DPRT_LEVEL0, dev, "[%p/%d] buffer_prepare - %dx%d %dbpp \"%s\"\n", + buf, buf->vb.i, + fh->width, fh->height, fh->fmt->depth, fh->fmt->name); + + buf->vb.state = VIDEOBUF_PREPARED; + buf->activate = buffer_activate; + + return 0; + + fail: + buffer_release(q, vb); + return rc; +} + +static void buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb) +{ + struct tw686x_buf *buf = container_of(vb, struct tw686x_buf, vb); + struct tw686x_fh *fh = vq->priv_data; + struct tw686x_vdev *dev= fh->dev; + struct tw686x_dmaqueue *q = &dev->video_q; + bool b_active = false; + + dvprintk(DPRT_LEVEL0, dev, "%s(%d)\n", __func__, vb->i); + +// assert_spin_locked(dev->slock); + + b_active = IS_TW686X_DMA_MODE_BLOCK ? + (dev->pb_next < dev->buf_needs) : (dev->pb_next<=buf->sg_ofo) ; + if( b_active ) { + buf->activate( dev, buf, dev->pb_curr, false ); + if( !dev->dma_started ) { + tw686x_dev_run( dev, true ); + dev->dma_started = true; + } + } + else { + list_add_tail(&buf->vb.queue, &q->queued); + buf->vb.state = VIDEOBUF_QUEUED; + } + dev->pb_next++; +} + +static struct videobuf_queue_ops tw686x_video_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +static int video_open(struct file *file) +{ + struct tw686x_chip *chip; + struct tw686x_vdev *h=video_drvdata(file); + struct tw686x_vdev *dev = NULL; + struct tw686x_fh *fh; + struct list_head *list; + enum v4l2_buf_type type = 0; + int i = 0; + + mutex_lock(&tw686x_chiplist_lock); + list_for_each(list, &tw686x_chiplist) { + chip = list_entry(list, struct tw686x_chip, chiplist); + for( i=0; i<chip->vid_count; i++ ) { + if (chip->vid_dev[i] == h) { + dev = h; + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + break; + } + } + } + mutex_unlock(&tw686x_chiplist_lock); + if (NULL == dev) { + return -ENODEV; + } + chip = dev->chip; + + dvprintk(DPRT_LEVEL0, dev, "%s() minor=%d type=%s\n", __func__, + dev->video_dev->minor, v4l2_type_names[type]); + + /* allocate + initialize per filehandle data */ + fh = kzalloc(sizeof(*fh), GFP_KERNEL); + if (NULL == fh) { + return -ENOMEM; + } + file->private_data = fh; + fh->dev = dev; + fh->type = type; + fh->width = TW686X_DEFAULT_WIDTH; + fh->height = TW686X_DEFAULT_HEIGHT; + fh->fmt = format_by_fourcc(V4L2_PIX_FMT_YUYV); + fh->bytesperline = fh->width*2; + fh->ctl_bright = 0x00; + fh->ctl_contrast= 0x64; + fh->ctl_hue = 0x00; + fh->ctl_saturation = 0x80; + //v4l2_prio_open(&dev->prio,&fh->prio); + + #if(LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)) + if( IS_TW686X_DMA_MODE_BLOCK ) { + videobuf_queue_dma_contig_init_isl(&fh->vidq, &tw686x_video_qops, &chip->pci->dev, + dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct tw686x_buf), + fh, NULL); + } + else { + videobuf_queue_sg_init(&fh->vidq, &tw686x_video_qops, + &chip->pci->dev, dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct tw686x_buf), + fh, NULL); + } +#else + if( IS_TW686X_DMA_MODE_BLOCK ) { + videobuf_queue_dma_contig_init_isl(&fh->vidq, &tw686x_video_qops, &chip->pci->dev, + dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct tw686x_buf), + fh); + } + else { + videobuf_queue_sg_init(&fh->vidq, &tw686x_video_qops, + &chip->pci->dev, dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct tw686x_buf), + fh); + } +#endif + + return 0; +} + +static ssize_t video_read(struct file *file, char __user *data, + size_t count, loff_t *ppos) +{ + struct tw686x_fh *fh = file->private_data; + struct tw686x_vdev *dev= fh->dev; + + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + if (res_locked(dev,RESOURCE_VIDEO)) + return -EBUSY; + + return videobuf_read_one(&fh->vidq, data, count, ppos, + file->f_flags & O_NONBLOCK); +} + +static unsigned int video_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct tw686x_fh *fh = file->private_data; + struct tw686x_buf *buf= NULL; + unsigned int rc = POLLERR; + struct tw686x_vdev *dev= fh->dev; + + mutex_lock(&fh->vidq.vb_lock); + + if (res_check(fh, RESOURCE_VIDEO)) { + /* streaming capture */ + if (list_empty(&fh->vidq.stream)) + goto done; + buf = list_entry(fh->vidq.stream.next, + struct tw686x_buf, vb.stream); + } else { + /* read() capture */ + buf = (struct tw686x_buf *)fh->vidq.read_buf; + if (NULL == buf) + goto done; + } + + poll_wait(file, &buf->vb.done, wait); + if (buf->vb.state == VIDEOBUF_DONE || + buf->vb.state == VIDEOBUF_ERROR) + rc = POLLIN|POLLRDNORM; + else + rc = 0; + +done: + mutex_unlock(&fh->vidq.vb_lock); + dvprintk(DPRT_LEVEL0, dev, "%s() return %d\n", __func__, rc); + return rc; +} + +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)) +static int video_release(struct file *file) +#else +static int video_release(struct inode *inode, struct file *file) +#endif +{ + struct tw686x_fh *fh = file->private_data; + struct tw686x_vdev *dev = fh->dev; + + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + /* stop video capture */ + if (res_check(fh, RESOURCE_VIDEO)) { + res_free(dev,fh,RESOURCE_VIDEO); + tw686x_dev_run(dev, false); + videobuf_streamoff(&fh->vidq); + } + + if (fh->vidq.read_buf) { + buffer_release(&fh->vidq, fh->vidq.read_buf); + kfree(fh->vidq.read_buf); + } + + videobuf_mmap_free(&fh->vidq); + file->private_data = NULL; + kfree(fh); + + return 0; +} + +static int video_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct tw686x_fh *fh = file->private_data; + struct tw686x_vdev *dev= fh->dev; + + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + return videobuf_mmap_mapper(&fh->vidq, vma); +} + +/* ------------------------------------------------------------------ */ +/* VIDEO IOCTLS */ + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tw686x_fh *fh = priv; + struct tw686x_vdev *dev= fh->dev; + + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + f->fmt.pix.width = fh->width; + f->fmt.pix.height = fh->height; + f->fmt.pix.field = fh->vidq.field; + f->fmt.pix.pixelformat = fh->fmt->fourcc; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fh->fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tw686x_fh *fh = priv; + struct tw686x_vdev *dev= fh->dev; + struct tw686x_fmt *fmt; + enum v4l2_field field; + unsigned int maxw, maxh; + + dvprintk(DPRT_LEVEL0, dev, "%s(fourcc=0x%x, field=%d, %dx%d)\n", __func__, + f->fmt.pix.pixelformat, f->fmt.pix.field, f->fmt.pix.width, f->fmt.pix.height); + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + if (NULL == fmt) + return -EINVAL; + + field = f->fmt.pix.field; + maxw = norm_maxw(dev->tvnorm); + maxh = norm_maxh(dev->tvnorm); + if(f->fmt.pix.width == -1) { + f->fmt.pix.width = fh->width; + } + if(f->fmt.pix.height == -1) { + f->fmt.pix.height = fh->height; + } + + if (V4L2_FIELD_ANY == field) { + field = (f->fmt.pix.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_BOTTOM; + } + + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + break; + default: + return -EINVAL; + } + + f->fmt.pix.field = field; + f->fmt.pix.width &= ~0x0F; + f->fmt.pix.height&= ~0x0F; + +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)) + v4l_bound_align_image(&f->fmt.pix.width, TW686X_MIN_WIDTH, maxw, 2, + &f->fmt.pix.height, TW686X_MIN_HEIGHT, maxh, 0, 0); +#else + if (f->fmt.pix.height < TW686X_MIN_HEIGHT) + f->fmt.pix.height = TW686X_MIN_HEIGHT; + if (f->fmt.pix.height > maxh) + f->fmt.pix.height = maxh; + if (f->fmt.pix.width < TW686X_MIN_WIDTH) + f->fmt.pix.width = TW686X_MIN_WIDTH; + if (f->fmt.pix.width > maxw) + f->fmt.pix.width = maxw; + f->fmt.pix.width &= ~0x03; +#endif + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tw686x_fh *fh = priv; + struct tw686x_vdev *dev= fh->dev; + int err; + + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + err = vidioc_try_fmt_vid_cap(file, priv, f); + if (0 != err) + return err; + + fh->fmt = format_by_fourcc(f->fmt.pix.pixelformat); + fh->width = f->fmt.pix.width; + fh->height = f->fmt.pix.height; + fh->bytesperline = f->fmt.pix.bytesperline; + fh->vidq.field = f->fmt.pix.field; + dev->field = fh->vidq.field; + if( IS_TW686X_DMA_MODE_BLOCK ) { + dev->buf_needs = (V4L2_FIELD_HAS_BOTH(dev->field) ? 2 : 4); + } + else { + dev->buf_needs = 2; + } + + dvprintk(2, dev, "%s() width=%d height=%d field=%d\n", __func__, + fh->width, fh->height, fh->vidq.field); + + tw686x_dev_set_pixelformat( dev, f->fmt.pix.pixelformat ); + +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)) +#if(LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35)) + call_all(dev->chip, video, s_fmt, f); +#endif +#endif + + return 0; +} + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct tw686x_vdev *dev = ((struct tw686x_fh *)priv)->dev; + + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + sprintf(cap->driver, "%s", "tw6869"); + sprintf(cap->card, "%s", "TW6869 PC-DVR"); + sprintf(cap->bus_info, "PCIe:%s", pci_name(dev->chip->pci)); + cap->version = TW686X_VERSION_CODE; + cap->capabilities = + V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; + + return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct tw686x_fh *fh = priv; + struct tw686x_vdev *dev= fh->dev; + + dvprintk(DPRT_LEVEL0, dev, "%s(%d)\n", __func__, f->index); + + if (unlikely(f->index >= ARRAY_SIZE(formats))) + return -EINVAL; + + strlcpy(f->description, formats[f->index].name, + sizeof(f->description)); + f->pixelformat = formats[f->index].fourcc; + + return 0; +} + +#ifdef CONFIG_VIDEO_V4L1_COMPAT +static int vidiocgmbuf(struct file *file, void *priv, + struct video_mbuf *mbuf) +{ + struct tw686x_fh *fh = priv; + struct tw686x_vdev *dev= fh->dev; + + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + return 0; +} +#endif + +static int vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *p) +{ + struct tw686x_fh *fh = priv; + struct tw686x_vdev *dev= fh->dev; + + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + return videobuf_reqbufs(&fh->vidq, p); +} + +static int vidioc_querybuf(struct file *file, void *priv, + struct v4l2_buffer *p) +{ + struct tw686x_fh *fh = priv; + struct tw686x_vdev *dev= fh->dev; + + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + return videobuf_querybuf(&fh->vidq, p); +} + +static int vidioc_qbuf(struct file *file, void *priv, + struct v4l2_buffer *p) +{ + struct tw686x_fh *fh = priv; + struct tw686x_vdev *dev= fh->dev; + + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + return videobuf_qbuf(&fh->vidq, p); +} + +static int vidioc_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *p) +{ + struct tw686x_fh *fh = priv; + struct tw686x_vdev *dev= fh->dev; + + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + return videobuf_dqbuf(&fh->vidq, p, + file->f_flags & O_NONBLOCK); +} + +static int vidioc_streamon(struct file *file, void *priv, + enum v4l2_buf_type i) +{ + struct tw686x_fh *fh = priv; + struct tw686x_vdev *dev= fh->dev; + + dvprintk(1, dev, "%s()\n", __func__); + + if (unlikely(fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)) + return -EINVAL; + if (unlikely(i != fh->type)) + return -EINVAL; + if (unlikely(!res_get(dev, fh, RESOURCE_VIDEO))) + return -EBUSY; + + dev->pb_curr = 0; + dev->pb_next = 0; + dev->dma_started = false; + dev->chip->daemon_nr = 0; + dev->chip->daemon_tor= 0; + do_gettimeofday( &dev->chip->daemon_tv ); + if( dev->framerate==0 ) { + dev->framerate = TW686X_MAX_FRAMERATE(dev->tvnorm); + } + + tw686x_dev_set_dma( dev, fh->width, fh->height, (fh->width*fh->fmt->depth)>>3 ); + tw686x_dev_set_framerate( dev, dev->framerate, fh->vidq.field ); + + return videobuf_streamon(&fh->vidq); +} + +static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct tw686x_fh *fh = priv; + struct tw686x_vdev *dev= fh->dev; + + dvprintk(1, dev, "%s(%d)\n", __func__, fh->vidq.streaming); + + if (!res_check(fh, RESOURCE_VIDEO)) + return 0; + + res_free(dev, fh, RESOURCE_VIDEO); + + if( IS_TW686X_DMA_MODE_BLOCK && !fh->vidq.streaming) { + return 0; + } + + return videobuf_streamoff(&fh->vidq); +} + +static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *tvnorms) +{ + struct tw686x_fh *fh = priv; + struct tw686x_vdev *dev = fh->dev; + + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + *tvnorms = dev->tvnorm; + + return 0; +} + +static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id* id) +{ + struct tw686x_fh *fh = priv; + struct tw686x_vdev *dev = fh->dev; + + dvprintk(DPRT_LEVEL0, dev, "%s(%08x)\n", __func__, (u32)*id); + + tw686x_set_tvnorm(dev, *id); + if( dev->framerate==0 ) { + dev->framerate = TW686X_MAX_FRAMERATE(*id); + } + + return 0; +} + +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + unsigned int n; + struct tw686x_fh *fh = priv; + struct tw686x_vdev *dev= fh->dev; + u32 vstatus = 0; + + dvprintk(DPRT_LEVEL0, dev, "%s(%d-%d)\n", __func__, i->index, dev->input); + + n = i->index; + if(tw686x_bcsi) { + n -= 1; + } + + if(dev->channel_id<TW686X_DECODER_COUNT) { + if (n >= TW6864_MUX_COUNT) + return -EINVAL; + } + else { + return -EINVAL; + } + + memset(i, 0, sizeof(*i)); + i->index = n; + if(tw686x_bcsi) { + i->index += 1; + } + i->type = V4L2_INPUT_TYPE_CAMERA; + sprintf(i->name, "Composite%d", n); + i->std = TW686X_NORMS; + if(n == dev->input) { + vstatus = dev->vstatus; + if (0 != (vstatus & BIT(7))) + i->status |= V4L2_IN_ST_NO_SIGNAL; + if (0 == (vstatus & BIT(6))) + i->status |= V4L2_IN_ST_NO_H_LOCK; + if (0 != (vstatus & BIT(1))) + i->status |= V4L2_IN_ST_NO_COLOR; + } + + return 0; +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct tw686x_fh *fh = priv; + struct tw686x_vdev *dev= fh->dev; + + *i = dev->input; + + dvprintk(DPRT_LEVEL0, dev, "%s() returns %d\n", __func__, *i); + + return 0; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + struct tw686x_fh *fh = priv; + struct tw686x_vdev *dev= fh->dev; + + dvprintk(DPRT_LEVEL0, dev, "%s(%d)\n", __func__, i); + + if( dev->channel_id<TW686X_DECODER_COUNT ) { + if (i >= TW6864_MUX_COUNT) { + dvprintk(1, dev, "%s(%d) -EINVAL\n", __func__, i); + return -EINVAL; + } + } + else if (i >= TW2865_MUX_COUNT) { + dvprintk(1, dev, "%s(%d) -EINVAL\n", __func__, i); + return -EINVAL; + } + + mutex_lock(&dev->lock); + dev->input = i; + tw686x_dev_set_mux(dev, i); + mutex_unlock(&dev->lock); + + return 0; +} + +static int vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *c) +{ + struct tw686x_fh *fh = priv; + struct tw686x_vdev *dev= fh->dev; + const struct v4l2_queryctrl *ctrl; + + dvprintk(DPRT_LEVEL0, dev, "%s()\n", __func__); + + if ((c->id < V4L2_CID_BASE || + c->id >= V4L2_CID_LASTP1)) + return -EINVAL; + ctrl = ctrl_by_id(c->id); + *c = (NULL != ctrl) ? *ctrl : no_ctrl; + + return 0; +} + +static int vidioc_g_ctrl(struct file *file, void *priv, + struct v4l2_control *c) +{ + struct tw686x_fh *fh = priv; + struct tw686x_vdev *dev= fh->dev; + const struct v4l2_queryctrl* ctrl; + + ctrl = ctrl_by_id(c->id); + if (NULL == ctrl) + return -EINVAL; + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + c->value = fh->ctl_bright; + break; + case V4L2_CID_HUE: + c->value = fh->ctl_hue; + break; + case V4L2_CID_CONTRAST: + c->value = fh->ctl_contrast; + break; + case V4L2_CID_SATURATION: + c->value = fh->ctl_saturation; + break; + default: + return -EINVAL; + } + + dvprintk(DPRT_LEVEL0, dev, "%s(id=%x) return %d\n", __func__, c->id, c->value); + + return 0; +} + +static int vidioc_s_ctrl(struct file *file, void *priv, + struct v4l2_control *c) +{ + const struct v4l2_queryctrl* ctrl; + struct tw686x_fh *fh = priv; + struct tw686x_vdev *dev= fh->dev; + + mutex_lock(&dev->lock); + ctrl = ctrl_by_id(c->id); + if (NULL == ctrl) { + mutex_unlock(&dev->lock); + return -EINVAL; + } + + dvprintk(DPRT_LEVEL0, dev, "set_control name=%s val=%d\n",ctrl->name,c->value); + + switch (ctrl->type) { + case V4L2_CTRL_TYPE_BOOLEAN: + case V4L2_CTRL_TYPE_MENU: + case V4L2_CTRL_TYPE_INTEGER: + if (c->value < ctrl->minimum) + c->value = ctrl->minimum; + if (c->value > ctrl->maximum) + c->value = ctrl->maximum; + break; + default: + /* nothing */; + }; + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + fh->ctl_bright = c->value; + tw686x_dev_set_bright( dev, fh->ctl_bright ); + break; + case V4L2_CID_HUE: + fh->ctl_hue = c->value; + tw686x_dev_set_hue( dev, fh->ctl_hue ); + break; + case V4L2_CID_CONTRAST: + fh->ctl_contrast = c->value; + tw686x_dev_set_contrast( dev, fh->ctl_contrast ); + break; + case V4L2_CID_SATURATION: + fh->ctl_saturation = c->value; + tw686x_dev_set_saturation( dev, fh->ctl_saturation ); + break; + default: + break; + } + mutex_unlock(&dev->lock); + + return 0; +} + +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)) +static int vidioc_g_register(struct file *file, void *priv, + struct v4l2_dbg_register *reg) +#else +static int vidioc_g_register(struct file *file, void *priv, + struct v4l2_register *reg) +#endif +{ + struct tw686x_fh *fh = priv; + struct tw686x_vdev *dev= fh->dev; + struct tw686x_chip *chip = dev->chip; + +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)) + if (!v4l2_chip_match_host(®->match)) + return -EINVAL; +#else + if (!v4l2_chip_match_host(reg->match_type, reg->match_chip)) + return -EINVAL; +#endif + + if( reg->reg >= 0x800 ) { + return -EINVAL; + } + + reg->reg &= ~0x03; + +#ifdef CONFIG_VIDEO_ADV_DEBUG + call_all(dev, core, g_register, reg); +#else + reg->val = tw_read( reg->reg ); +#endif + + //dvprintk(1, dev, "%s(0x%08x) = 0x%08x\n", __func__, (u32)reg->reg, (u32)reg->val); + + return 0; +} + +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)) +static int vidioc_s_register(struct file *file, void *priv, + struct v4l2_dbg_register *reg) +#else +static int vidioc_s_register(struct file *file, void *priv, + struct v4l2_register *reg) +#endif +{ + struct tw686x_fh *fh = priv; + struct tw686x_vdev *dev= fh->dev; + struct tw686x_chip *chip = dev->chip; + + //dvprintk(1, dev, "%s(0x%08x, 0x%08x)\n", __func__, (u32)reg->reg, (u32)reg->val); + +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)) + if (!v4l2_chip_match_host(®->match)) + return -EINVAL; +#else + if (!v4l2_chip_match_host(reg->match_type, reg->match_chip)) + return -EINVAL; +#endif + + if( reg->reg >= 0x800 ) { + return -EINVAL; + } + + reg->reg &= ~0x03; + +#ifdef CONFIG_VIDEO_ADV_DEBUG + call_all(dev, core, s_register, reg); +#else + tw_write( reg->reg, reg->val ); +#endif + + return 0; +} + +/* For other private ioctls */ +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,38)) +static long vidioc_tw686x(struct file *file, void *priv, + bool valid_prio, int cmd, void *arg) +#elif(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)) +static long vidioc_tw686x(struct file *file, void *priv, + int cmd, void *arg) +#else +static int vidioc_tw686x(struct file *file, void *priv, + int cmd, void *arg) +#endif +{ + struct tw686x_fh *fh = priv; + struct tw686x_vdev *dev= fh->dev; + long ret = -EINVAL; + + //dvprintk(1, dev, "%s()\n", __func__); + + switch( cmd ) { + case TW686X_S_REGISTER: + ret = vidioc_s_register( file, priv, arg ); + break; + case TW686X_G_REGISTER: + ret = vidioc_g_register( file, priv, arg ); + break; + case TW686X_S_FRAMERATE: + dev->framerate = *(long*)arg; + tw686x_dev_set_framerate( dev, dev->framerate, fh->vidq.field ); + ret = 0; + break; + case TW686X_G_FRAMERATE: + *(long*)arg = dev->framerate; + ret = 0; + break; + case TW686X_G_SIGNAL: + *(long*)arg = tw686x_dev_get_signal( dev ); + ret = 0; + break; +#if(CaptureFLV) + case TW686X_S_AUDIO: + ret = tw686x_audio_param( dev->chip->aud_dev[dev->channel_id], (struct tw686x_aparam*)arg); + break; + case TW686X_G_AUDIO: + ret = tw686x_audio_data( dev->chip->aud_dev[dev->channel_id], (struct tw686x_adata*)arg); + break; +#endif + default: + break; + } + + return ret; +} + +int tw686x_video_resetdma(struct tw686x_chip *chip, u32 dma_cmd) +{ +#if(TW6869_RESETDMA) + chip->dma_restart = dma_cmd; + mod_timer(&chip->tm_assist, jiffies+(BUFFER_TIMEOUT/25)); +#else + struct tw686x_vdev *dev = NULL; + int i=0; + + for(i=0; i<chip->vid_count; i++) { + if(dma_cmd & (1<<i)) { + dev = chip->vid_dev[i]; + tw686x_video_restart( dev, false ); + } + } +#endif + + return 0; +} + +int tw686x_video_daemon(struct tw686x_chip *chip, u32 dma_error) +{ + int i, should_dropdown = 0; + struct tw686x_vdev *dev = NULL; + struct timeval tv_now; + long long tv_diff = 0; + long ms_diff = 0; + + do_gettimeofday( &tv_now ); + tv_diff = 1000000 * ( tv_now.tv_sec - chip->daemon_tv.tv_sec ) + + tv_now.tv_usec - chip->daemon_tv.tv_usec; + ms_diff = (long)tv_diff; + ms_diff/= 1000; + + for( i=0; i<chip->vid_count; i++ ) { + if( dma_error&TW6864_R_DMA_VINT_FIFO(i) ) { + chip->daemon_nr++; + } + } + + if( chip->daemon_nr >= TW686X_DAEMON_THRESHOLD ) { + chip->daemon_tor++; + chip->daemon_nr = 0; + } + + if(ms_diff >= TW686X_DAEMON_PERIOD) { + should_dropdown = (chip->daemon_tor >= TW686X_DAEMON_TOLERANCE); + chip->daemon_tv = tv_now; + chip->daemon_tor= 0;; + chip->daemon_nr = 0; + } + + if( !should_dropdown ) { + for( i=0; i<chip->vid_count; i++ ) { + dev = chip->vid_dev[i]; + if( dma_error&TW6864_R_DMA_VINT_FIFO(i) ) { + tw686x_video_restart( dev, false ); + } + } + } + else { + for( i=0; i<chip->vid_count; i++ ) { + dev = chip->vid_dev[i]; + if(dev->framerate > TW686X_DAEMON_MIN) { + dev->framerate--; + tw686x_video_restart( dev, true ); + } + else if( dma_error&TW6864_R_DMA_VINT_FIFO(i) ) { + tw686x_video_restart( dev, false ); + } + } + } + + return 0; +} + +/* ----------------------------------------------------------- */ + +int tw686x_video_irq(struct tw686x_vdev *dev, u32 dma_status, u32 pb_status) +{ +/*liran for debug + struct tw686x_chip *chip = dev->chip; + + dvprintk(DPRT_LEVEL0, dev, "irq buf=0x%08x\n", ibuf==0 ? + tw_read(TW6864_R_BDMA(TW6864_R_BDMA_ADDR_P_0, dev->channel_id)) : + tw_read(TW6864_R_BDMA(TW6864_R_BDMA_ADDR_B_0, dev->channel_id))); +*/ + + if( !res_locked(dev, RESOURCE_VIDEO) ) { + tw686x_dev_run( dev, false ); + while (!list_empty(&dev->video_q.active)) { + tw686x_buffer_wakeup( dev, &dev->video_q, VIDEOBUF_ERROR ); + } + while (!list_empty(&dev->video_q.queued)) { + tw686x_buffer_cancel( dev, &dev->video_q, VIDEOBUF_ERROR ); + } + } + else { + if( !IS_TW686X_DMA_MODE_BLOCK ) { + dev->pb_curr = (pb_status >> dev->channel_id) & 1; + } + if( tw686x_buffer_wakeup(dev, &dev->video_q, VIDEOBUF_DONE) ) { + } + if(dev->pb_next>0) { + tw686x_buffer_next(dev, &dev->video_q, dev->pb_curr); + } + else { + tw686x_dev_run( dev, false ); + dev->dma_started = false; + dev->pb_next = 0; + dev->pb_curr = 0; + } + } + + return 0; +} + +int tw686x_video_status(struct tw686x_vdev *dev) +{ + dev->vstatus = tw686x_dev_get_decoderstatus(dev); + + return 0; +} + +/* ----------------------------------------------------------- */ +/* exported stuff */ + +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)) +static const struct v4l2_file_operations video_fops = { + .owner = THIS_MODULE, + .open = video_open, + .release = video_release, + .read = video_read, + .poll = video_poll, + .mmap = video_mmap, + .ioctl = video_ioctl2, +}; +#else +static const struct file_operations video_fops = { + .owner = THIS_MODULE, + .open = video_open, + .release = video_release, + .read = video_read, + .poll = video_poll, + .mmap = video_mmap, + .ioctl = video_ioctl2, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; +#endif + +static const struct v4l2_ioctl_ops video_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_g_std = vidioc_g_std, + .vidioc_s_std = vidioc_s_std, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_queryctrl = vidioc_queryctrl, + .vidioc_g_ctrl = vidioc_g_ctrl, + .vidioc_s_ctrl = vidioc_s_ctrl, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, +#ifdef CONFIG_VIDEO_V4L1_COMPAT + .vidiocgmbuf = vidiocgmbuf, +#endif +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = vidioc_g_register, + .vidioc_s_register = vidioc_s_register, +#endif + .vidioc_default = vidioc_tw686x, +}; + +static struct video_device tw686x_video_template = { + .name = "tw6869-video", + .fops = &video_fops, + .minor = -1, + .ioctl_ops = &video_ioctl_ops, + .tvnorms = TW686X_NORMS, + .current_norm = V4L2_STD_NTSC, +}; + +int tw686x_video_register(struct tw686x_vdev *dev) +{ + int err; + struct video_device *vfd; + + dvprintk(1, dev, "%s()\n", __func__); + + mutex_init(&dev->lock); + + /* init video dma queues */ + INIT_LIST_HEAD(&dev->video_q.active); + INIT_LIST_HEAD(&dev->video_q.queued); + dev->video_q.timeout.function = tw686x_buffer_timeout; + dev->video_q.timeout.data = (unsigned long)dev; + init_timer(&dev->video_q.timeout); + dev->tm_restart.function = tw686x_video_restart_timeout; + dev->tm_restart.data = (unsigned long)dev; + init_timer(&dev->tm_restart); + + dev->tvnorm = TW686X_DEFAULT_STANDARD; + dev->field = V4L2_FIELD_TOP; + + /* create v4l devices */ + vfd = video_device_alloc(); + if (vfd) { + *vfd = tw686x_video_template; + vfd->minor = -1; +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)) + vfd->v4l2_dev = &dev->chip->v4l2_dev; +#endif + vfd->release = video_device_release; + snprintf(vfd->name, sizeof(vfd->name), "%s %s %d", + dev->chip->name, "video", dev->channel_id); + video_set_drvdata(vfd, dev); + } + + /* register v4l devices */ + dev->video_dev = vfd; + err = video_register_device(dev->video_dev, VFL_TYPE_GRABBER, tw686x_vidcount++); + if (err < 0) { + printk(KERN_INFO "%s: can't register video device %d\n", dev->chip->name, tw686x_vidcount); + goto fail_unreg; + } +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)) + printk(KERN_INFO "%s: registered device video %d [v4l2]\n", + dev->chip->name, dev->video_dev->num); +#else + printk(KERN_INFO "%s: registered device video %d [v4l2]\n", + dev->chip->name, dev->video_dev->minor & 0x1f); +#endif + + /* initial device configuration */ + tw686x_set_tvnorm(dev, dev->tvnorm); + tw686x_dev_set_mux(dev, 0); + + return 0; + +fail_unreg: + tw686x_video_unregister(dev); + return err; +} + +void tw686x_video_unregister(struct tw686x_vdev *dev) +{ + dvprintk(1, dev, "%s()\n", __func__); + + if (dev->video_dev) { + if (-1 != dev->video_dev->minor) + video_unregister_device(dev->video_dev); + else + video_device_release(dev->video_dev); + dev->video_dev = NULL; + } +} diff --git a/drivers/pci/pcie/tw6869/tw686x.h b/drivers/pci/pcie/tw6869/tw686x.h new file mode 100644 index 000000000000..6a7f58b58e8f --- /dev/null +++ b/drivers/pci/pcie/tw6869/tw686x.h @@ -0,0 +1,415 @@ +/* + * Driver for Techwell TW6864/68 based DVR cards + * + * (c) 2009-10 liran <jlee@techwellinc.com.cn> [Techwell China] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/pci.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <linux/videodev2.h> +#include <linux/kdev_t.h> +#include <linux/input.h> +#include <linux/notifier.h> +#include <linux/delay.h> +#include <linux/mutex.h> + +#include <asm/io.h> +#include <linux/version.h> + +#include <media/v4l2-common.h> +#include <media/v4l2-ioctl.h> +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)) +#include <media/v4l2-device.h> +#endif +#include <media/videobuf-dma-sg.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include "videobuf-dma-contig-tw.h" + +#define TW686X_VERSION_CODE KERNEL_VERSION(0, 1, 4) +#define TW6869_RESETDMA 1 + +#include "i2c-sw.h" + +#define I2C_SW_TW686X 0x0A0000 /* TW686X video decoder bus */ + +extern unsigned int tw686x_bcsi; /* for BCSI */ +extern unsigned int tw686x_restart_timer; /* dma restart timer */ +extern unsigned int tw686x_dmamode; +extern unsigned int tw686x_debug; +extern unsigned int tw686x_6865; //zxx_20120712 added for TW6865 +#define dprintk(level, chip, fmt, arg...)\ + do { if (tw686x_debug >= level)\ + printk(KERN_DEBUG "%s: " fmt, (chip)->name, ## arg);\ + } while (0) +#define dvprintk(level, dev, fmt, arg...)\ + do { if (tw686x_debug >= level)\ + printk(KERN_DEBUG "%s[v-%d]: " fmt, ((dev)->chip)->name, (dev)->channel_id, ## arg);\ + } while (0) +#define daprintk(level, dev, fmt, arg...)\ + do { if (tw686x_debug >= level)\ + printk(KERN_DEBUG "%s[a-%d]: " fmt, ((dev)->chip)->name, (dev)->channel_id, ## arg);\ + } while (0) + +#define dvcount(level, dev, cnt, cnt_out) \ +{ \ + if(tw686x_debug >= level) { \ + static struct timeval _tv_begin_##cnt = {0,0}; \ + static long _cnt_##cnt = 0; \ + struct timeval _tv_now_##cnt; \ + long long _tv_diff_##cnt = 0; \ + long _ms_diff_##cnt = 0; \ + do_gettimeofday( &_tv_now_##cnt ); \ + _tv_diff_##cnt = 1000000 * ( _tv_now_##cnt.tv_sec - _tv_begin_##cnt.tv_sec ) + \ + _tv_now_##cnt.tv_usec - _tv_begin_##cnt.tv_usec; \ + _ms_diff_##cnt = (long)_tv_diff_##cnt; \ + _ms_diff_##cnt /= 1000; \ + _cnt_##cnt++; \ + if(_ms_diff_##cnt >= 1000) { \ + dvprintk(1, dev, "%s-%d %d\n", cnt_out, (int)_cnt_##cnt, (int)_ms_diff_##cnt); \ + _tv_begin_##cnt = _tv_now_##cnt; \ + _cnt_##cnt = 0; \ + } \ + } \ +} + +#define DPRT_LEVEL0 11 + +#define RESOURCE_VIDEO 2 +#define BUFFER_TIMEOUT (HZ) /* 0.5 seconds */ + +/* ioctl for tw686x */ +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)) +#define TW686X_S_REGISTER _IOWR('V', (BASE_VIDIOC_PRIVATE + 1), struct v4l2_dbg_register) +#define TW686X_G_REGISTER _IOWR('V', (BASE_VIDIOC_PRIVATE + 2), struct v4l2_dbg_register) +#else +#define TW686X_S_REGISTER _IOWR('V', (BASE_VIDIOC_PRIVATE + 1), struct v4l2_register) +#define TW686X_G_REGISTER _IOWR('V', (BASE_VIDIOC_PRIVATE + 2), struct v4l2_register) +#endif +#define TW686X_S_FRAMERATE _IOWR('V', (BASE_VIDIOC_PRIVATE + 3), long) +#define TW686X_G_FRAMERATE _IOWR('V', (BASE_VIDIOC_PRIVATE + 4), long) +#define TW686X_G_SIGNAL _IOWR('V', (BASE_VIDIOC_PRIVATE + 5), long) + +#define TW686X_S_AUDIO _IOWR('V', (BASE_VIDIOC_PRIVATE + 6), struct tw686x_aparam) +#define TW686X_G_AUDIO _IOWR('V', (BASE_VIDIOC_PRIVATE + 7), struct tw686x_adata) + +#define TW686X_DAEMON_PERIOD 2000 /*2s*/ +#define TW686X_DAEMON_THRESHOLD 16 /*2s*/ +#define TW686X_DAEMON_TOLERANCE 2 /*2 times*/ +#define TW686X_DAEMON_MIN 15 /*min frame rate*/ + +#define TW686X_DMA_MODE_SG 0 /* 0--scatter gather, 1--block dma*/ +#define TW686X_DMA_MODE_BLOCK 1 +#define IS_TW686X_DMA_MODE_BLOCK (tw686x_dmamode & TW686X_DMA_MODE_BLOCK) +#define TW686X_DMA_DESC_LEN 4096 +#define TW686X_DMA_DESC_P 0 +#define TW686X_DMA_DESC_B 1 +#define TW686X_DMA_DESC_NUM 2 +#define TW686X_DMA_DESC_UNIT 512 +#define TW686X_DMA_DESC_UNIT_LEN (TW686X_DMA_DESC_UNIT*8) +#define TW686X_DMA_DESC(dev, pb) ((struct tw686x_dmadesc*)((unsigned long)(dev)->dma_desc[TW686X_DMA_DESC_##pb].cpu)) +#define TW686X_SG_PBLEN (TW686X_DMA_DESC_UNIT*TW686X_DMA_DESC_LEN) + +#define TW686X_DECODER_COUNT 8 +#define TW6864_MUX_COUNT 1 +#define TW2865_MUX_COUNT 1 +#define TW2864_MUX_COUNT 1 +#define TW686X_VIDEO_COUNT 8 +#define TW686X_AUDIO_COUNT 8 +#define TW686X_AUDIO_BEGIN 8 +#define TW686X_BUFFER_COUNT 9 +#define TW686X_DEFAULT_STANDARD V4L2_STD_NTSC +#define TW686X_DEFAULT_WIDTH 704 +#define TW686X_DEFAULT_HEIGHT 480 +#define TW686X_REMOVE_BLACKSTRIPE 0 //(1 << 31) +#define TW686X_VIDEO_SIZE(w,h) ((w) | ((h)<<16) | (1<<31)) +#define TW686X_VIDEO_SIZE_F2(w,h) ((w) | ((h)<<16)) //Field2 size, Available only for Rev.B or later +#define TW686X_MAX_FRAMERATE(std) (IS_PAL(std) ? 25 : 30) +#define TW686X_MIN_WIDTH 160 +#define TW686X_MIN_HEIGHT 120 + +/* Currently unsupported by the driver: PAL/H, NTSC/Kr*/ +#define TW686X_NORMS (V4L2_STD_NTSC | V4L2_STD_PAL) + +#define IS_PAL( std ) ( (std) & V4L2_STD_PAL ) + +struct tw686x_fmt { + char *name; + u32 fourcc; /* v4l2 format id */ + int depth; + int flags; +}; + +struct tw686x_dmadesc { + u32 ctrl; //DO NOT CHANGE SEQUENCE, and it is valid for little endian target PC only + u32 addr; +}; + +struct tw686x_fh { + struct tw686x_vdev *dev; + enum v4l2_buf_type type; + u32 resources; + struct videobuf_queue vidq; + + /* video capture */ + struct tw686x_fmt *fmt; + int width; + int height; + int bytesperline; + int ctl_bright; + int ctl_contrast; + int ctl_hue; + int ctl_saturation; +}; + +/* buffer for one video frame */ +struct tw686x_buf { + /* common v4l buffer stuff -- must be first */ + struct videobuf_buffer vb; + + /* tw686x specific */ + struct tw686x_fmt* fmt; + struct list_head active; + + int sg_bytes_to; + int sg_bytes_pb; + struct scatterlist *sg; + int sg_offset; + int sg_ofo; /*one p/b buffer for one frame*/ + + int (*activate)(struct tw686x_vdev *dev, struct tw686x_buf *buf, + int dma_pb, bool queued); +}; + +struct tw686x_dmaqueue { + struct list_head active; + struct list_head queued; + struct timer_list timeout; +}; + +/* buffer for dma */ +struct tw686x_dmabuf { + unsigned int size; + __le32 *cpu; + dma_addr_t dma; + struct vm_area_struct *vma; +}; + +struct tw686x_i2c { + struct i2c_sw i2c; + u32 control_word; +}; + +/* + * audio main chip structure + */ +typedef struct snd_card_tw686x { + struct snd_card *card; +// struct pci_dev *pci; + struct tw686x_adev *dev; + + u16 mute_was_on; + + spinlock_t lock; +} snd_card_tw686x_t; + +struct tw686x_chip { + struct list_head chiplist; + spinlock_t slock; + + /* pci i/o */ + char name[32]; + int nr; + u32 dma_restart; + +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)) + struct v4l2_device v4l2_dev; +#endif + struct pci_dev *pci; + __u8 __iomem *bmmio; + + /* i2c i/o */ + struct i2c_adapter i2c_adap; + struct i2c_client i2c_client; + struct tw686x_i2c i2c_686x; + + struct tw686x_vdev *vid_dev[TW686X_VIDEO_COUNT]; + int vid_count; + struct timeval daemon_tv; + int daemon_nr; + int daemon_tor; + + struct tw686x_adev *aud_dev[TW686X_AUDIO_COUNT]; + int aud_count; + + //assist timer, for video decoder status checking + struct timer_list tm_assist; +}; + +/* global device status */ +struct tw686x_vdev { + struct tw686x_chip* chip; + + struct list_head devlist; + struct mutex lock; + spinlock_t* slock; + struct v4l2_subdev *sd_tw286x; + + /* various device info */ + unsigned int resources; + struct video_device *video_dev; + struct tw686x_dmaqueue video_q; + struct tw686x_dmabuf dma_vb[TW686X_BUFFER_COUNT]; + struct tw686x_dmabuf dma_desc[TW686X_DMA_DESC_NUM]; + struct timer_list tm_restart; + + /* various v4l controls */ + v4l2_std_id tvnorm; + unsigned int input; + + /* other global state info */ + u32 vstatus; + + /* for dma buffer queue */ + int buf_needs; + int pb_next; + int pb_curr; + int dma_started; + int channel_id; + int field; + int framerate; +}; + +/* dmasound dsp status */ +struct tw686x_adev { + struct tw686x_chip* chip; + int channel_id; + snd_card_tw686x_t * card; + + struct mutex lock; + spinlock_t* slock; + int minor_dsp; + unsigned int users_dsp; + + /* dsp */ + unsigned int rate; + unsigned int blocks; + unsigned int blksize; + unsigned int bufsize; + struct videobuf_dmabuf dma; + unsigned int read_offset; + struct snd_pcm_substream *substream; + + unsigned int dma_blk; + unsigned int pb_flag; + unsigned int period_idx; + bool running; +}; + +struct tw686x_aparam { + int sample_rate; + int sample_bits; + int channels; + int blksize; +}; + +struct tw686x_adata { + unsigned char data[4092]; + int len; +}; + +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)) +static inline struct tw686x_chip *to_tw686x(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct tw686x_chip, v4l2_dev); +} +#endif + +/* ----------------------------------------------------------- */ + +#define tw_read(reg) readl(chip->bmmio + (reg)) +#define tw_write(reg, value) writel((value), chip->bmmio + (reg)) + +#define tw_andor(reg, mask, value) \ + writel((readl(chip->bmmio+(reg)) & ~(mask)) |\ + ((value) & (mask)), chip->bmmio+(reg)) + +#define tw_set(reg, bit) tw_andor((reg), (bit), (bit)) +#define tw_clear(reg, bit) tw_andor((reg), (bit), 0) + +#define call_all(dev, o, f, args...) \ + v4l2_device_call_all(&dev->v4l2_dev, 0, o, f, ##args) + +extern struct mutex tw686x_chiplist_lock; +extern struct list_head tw686x_chiplist; + +/* ----------------------------------------------------------- */ +/* tw686x-core.c */ +int tw686x_dmabuf_alloc(struct tw686x_chip *chip, + struct tw686x_dmabuf *buf, unsigned int size); +void tw686x_dmabuf_free(struct tw686x_chip *chip, struct tw686x_dmabuf *buf); + +/* ----------------------------------------------------------- */ +/* tw686x-video.c */ +/* Video */ +extern int tw686x_video_register(struct tw686x_vdev *dev); +extern void tw686x_video_unregister(struct tw686x_vdev *dev); +extern int tw686x_video_irq(struct tw686x_vdev *dev, u32 dma_status, u32 pb_status); +extern int tw686x_video_daemon(struct tw686x_chip *chip, u32 dma_error); +extern int tw686x_video_status(struct tw686x_vdev *dev); +extern int tw686x_video_resetdma(struct tw686x_chip* chip, u32 dma_cmd); +extern int tw686x_video_restart(struct tw686x_vdev *dev, bool b_framerate); +extern void tw686x_video_start(struct tw686x_vdev* dev); + +/* ----------------------------------------------------------- */ +/* tw686x-alsa.c */ +/* Audio */ +extern int tw686x_alsa_create(struct tw686x_adev *dev); +extern int tw686x_alsa_free(struct tw686x_adev *dev); +extern void tw686x_alsa_irq(struct tw686x_adev *dev, u32 dma_status, u32 pb_status); +extern int tw686x_alsa_resetdma(struct tw686x_chip *chip, u32 dma_cmd); + +/* tw686x-i2c.c */ +extern int tw686x_i2c_register(struct tw686x_chip *chip); +extern int tw686x_i2c_unregister(struct tw686x_chip *chip); + +/* ----------------------------------------------------------- */ +/* tw686x-audio.c */ +/* Audio */ +extern int tw686x_audio_create(struct tw686x_adev *dev); +extern int tw686x_audio_free(struct tw686x_adev *dev); +extern void tw686x_audio_irq(struct tw686x_adev *dev, u32 dma_status, u32 pb_status); +extern int tw686x_audio_param(struct tw686x_adev *dev, struct tw686x_aparam* ap); +extern int tw686x_audio_data(struct tw686x_adev *dev, struct tw686x_adata* ap); +extern int tw686x_audio_resetdma(struct tw686x_chip *chip, u32 dma_cmd); +extern int tw686x_audio_trigger(struct tw686x_adev *dev, int cmd); + +/* ----------------------------------------------------------- */ +/* tv norms */ + +static unsigned int inline norm_maxw(v4l2_std_id norm) +{ + //return (norm & (V4L2_STD_MN & ~V4L2_STD_PAL_Nc)) ? 720 : 768; + return 704; +} + +static unsigned int inline norm_maxh(v4l2_std_id norm) +{ + return (norm & V4L2_STD_625_50) ? 576 : 480; +} diff --git a/drivers/pci/pcie/tw6869/videobuf-dma-contig-tw.c b/drivers/pci/pcie/tw6869/videobuf-dma-contig-tw.c new file mode 100644 index 000000000000..1de92c0067cb --- /dev/null +++ b/drivers/pci/pcie/tw6869/videobuf-dma-contig-tw.c @@ -0,0 +1,897 @@ +/* + * helper functions for physically contiguous capture buffers + * + * The functions support hardware lacking scatter gather support + * (i.e. the buffers must be linear in physical memory) + * + * Copyright (c) 2008 Magnus Damm + * + * Based on videobuf-vmalloc.c, + * (c) 2007 Mauro Carvalho Chehab, <mchehab@infradead.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/pagemap.h> +#include <linux/dma-mapping.h> +#include <linux/version.h> + +#if(LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)) +#include <linux/sched.h> +#include <linux/slab.h> +#include "videobuf-dma-contig-tw.h" + +struct videobuf_dma_contig_memory { + u32 magic; + void *vaddr; + dma_addr_t dma_handle; + unsigned long size; +}; + +#define MAGIC_DC_MEM 0x0733ac61 +#define MAGIC_CHECK(is, should) \ + if (unlikely((is) != (should))) { \ + pr_err("magic mismatch: %x expected %x\n", (is), (should)); \ + BUG(); \ + } + +static void +videobuf_vm_open(struct vm_area_struct *vma) +{ + struct videobuf_mapping *map = vma->vm_private_data; + + dev_dbg(map->q->dev, "vm_open %p [count=%u,vma=%08lx-%08lx]\n", + map, map->count, vma->vm_start, vma->vm_end); + + map->count++; +} + +static void videobuf_vm_close(struct vm_area_struct *vma) +{ + struct videobuf_mapping *map = vma->vm_private_data; + struct videobuf_queue *q = map->q; + int i; + + dev_dbg(q->dev, "vm_close %p [count=%u,vma=%08lx-%08lx]\n", + map, map->count, vma->vm_start, vma->vm_end); + + map->count--; + if (0 == map->count) { + struct videobuf_dma_contig_memory *mem; + + dev_dbg(q->dev, "munmap %p q=%p\n", map, q); + videobuf_queue_lock(q); + + /* We need first to cancel streams, before unmapping */ + if (q->streaming) + videobuf_queue_cancel(q); + + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + + if (q->bufs[i]->map != map) + continue; + + mem = q->bufs[i]->priv; + if (mem) { + /* This callback is called only if kernel has + allocated memory and this memory is mmapped. + In this case, memory should be freed, + in order to do memory unmap. + */ + + MAGIC_CHECK(mem->magic, MAGIC_DC_MEM); + + /* vfree is not atomic - can't be + called with IRQ's disabled + */ + dev_dbg(q->dev, "buf[%d] freeing %p\n", + i, mem->vaddr); + + dma_free_coherent(q->dev, mem->size, + mem->vaddr, mem->dma_handle); + mem->vaddr = NULL; + } + + q->bufs[i]->map = NULL; + q->bufs[i]->baddr = 0; + } + + kfree(map); + + videobuf_queue_unlock(q); + } +} + +static const struct vm_operations_struct videobuf_vm_ops = { + .open = videobuf_vm_open, + .close = videobuf_vm_close, +}; + +/** + * videobuf_dma_contig_user_put() - reset pointer to user space buffer + * @mem: per-buffer private videobuf-dma-contig data + * + * This function resets the user space pointer + */ +static void videobuf_dma_contig_user_put(struct videobuf_dma_contig_memory *mem) +{ + mem->dma_handle = 0; + mem->size = 0; +} + +/** + * videobuf_dma_contig_user_get() - setup user space memory pointer + * @mem: per-buffer private videobuf-dma-contig data + * @vb: video buffer to map + * + * This function validates and sets up a pointer to user space memory. + * Only physically contiguous pfn-mapped memory is accepted. + * + * Returns 0 if successful. + */ +static int videobuf_dma_contig_user_get(struct videobuf_dma_contig_memory *mem, + struct videobuf_buffer *vb) +{ + struct mm_struct *mm = current->mm; + struct vm_area_struct *vma; + unsigned long prev_pfn, this_pfn; + unsigned long pages_done, user_address; + unsigned int offset; + int ret; + + offset = vb->baddr & ~PAGE_MASK; + mem->size = PAGE_ALIGN(vb->size + offset); + ret = -EINVAL; + + down_read(&mm->mmap_sem); + + vma = find_vma(mm, vb->baddr); + if (!vma) + goto out_up; + + if ((vb->baddr + mem->size) > vma->vm_end) + goto out_up; + + pages_done = 0; + prev_pfn = 0; /* kill warning */ + user_address = vb->baddr; + + while (pages_done < (mem->size >> PAGE_SHIFT)) { + ret = follow_pfn(vma, user_address, &this_pfn); + if (ret) + break; + + if (pages_done == 0) + mem->dma_handle = (this_pfn << PAGE_SHIFT) + offset; + else if (this_pfn != (prev_pfn + 1)) + ret = -EFAULT; + + if (ret) + break; + + prev_pfn = this_pfn; + user_address += PAGE_SIZE; + pages_done++; + } + + out_up: + up_read(¤t->mm->mmap_sem); + + return ret; +} + +static struct videobuf_buffer *__videobuf_alloc_vb(size_t size) +{ + struct videobuf_dma_contig_memory *mem; + struct videobuf_buffer *vb; + + vb = kzalloc(size + sizeof(*mem), GFP_KERNEL); + if (vb) { + mem = vb->priv = ((char *)vb) + size; + mem->magic = MAGIC_DC_MEM; + } + + return vb; +} + +static void *__videobuf_to_vaddr(struct videobuf_buffer *buf) +{ + struct videobuf_dma_contig_memory *mem = buf->priv; + + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_DC_MEM); + + return mem->vaddr; +} + +static int __videobuf_iolock(struct videobuf_queue *q, + struct videobuf_buffer *vb, + struct v4l2_framebuffer *fbuf) +{ + struct videobuf_dma_contig_memory *mem = vb->priv; + + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_DC_MEM); + + switch (vb->memory) { + case V4L2_MEMORY_MMAP: + dev_dbg(q->dev, "%s memory method MMAP\n", __func__); + + /* All handling should be done by __videobuf_mmap_mapper() */ + if (!mem->vaddr) { + dev_err(q->dev, "memory is not alloced/mmapped.\n"); + return -EINVAL; + } + break; + case V4L2_MEMORY_USERPTR: + dev_dbg(q->dev, "%s memory method USERPTR\n", __func__); + + /* handle pointer from user space */ + if (vb->baddr) + return videobuf_dma_contig_user_get(mem, vb); + + /* allocate memory for the read() method */ + mem->size = PAGE_ALIGN(vb->size); + mem->vaddr = dma_alloc_coherent(q->dev, mem->size, + &mem->dma_handle, GFP_KERNEL); + if (!mem->vaddr) { + dev_err(q->dev, "dma_alloc_coherent %ld failed\n", + mem->size); + return -ENOMEM; + } + + dev_dbg(q->dev, "dma_alloc_coherent data is at %p (%ld)\n", + mem->vaddr, mem->size); + break; + case V4L2_MEMORY_OVERLAY: + default: + dev_dbg(q->dev, "%s memory method OVERLAY/unknown\n", + __func__); + return -EINVAL; + } + + return 0; +} + +static int __videobuf_mmap_mapper(struct videobuf_queue *q, + struct videobuf_buffer *buf, + struct vm_area_struct *vma) +{ + struct videobuf_dma_contig_memory *mem; + struct videobuf_mapping *map; + int retval; + unsigned long size; + + dev_dbg(q->dev, "%s\n", __func__); + + /* create mapping + update buffer list */ + map = kzalloc(sizeof(struct videobuf_mapping), GFP_KERNEL); + if (!map) + return -ENOMEM; + + buf->map = map; + map->q = q; + + buf->baddr = vma->vm_start; + + mem = buf->priv; + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_DC_MEM); + + mem->size = PAGE_ALIGN(buf->bsize); + mem->vaddr = dma_alloc_coherent(q->dev, mem->size, + &mem->dma_handle, GFP_KERNEL); + if (!mem->vaddr) { + dev_err(q->dev, "dma_alloc_coherent size %ld failed\n", + mem->size); + goto error; + } + dev_dbg(q->dev, "dma_alloc_coherent data is at addr %p (size %ld)\n", + mem->vaddr, mem->size); + + /* Try to remap memory */ + + size = vma->vm_end - vma->vm_start; + size = (size < mem->size) ? size : mem->size; + + //vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); //notice!!!这里加上后会造成上层读内存CPU占用狂高 + retval = remap_pfn_range(vma, vma->vm_start, + mem->dma_handle >> PAGE_SHIFT, + size, vma->vm_page_prot); + if (retval) { + dev_err(q->dev, "mmap: remap failed with error %d. ", retval); + dma_free_coherent(q->dev, mem->size, + mem->vaddr, mem->dma_handle); + goto error; + } + + vma->vm_ops = &videobuf_vm_ops; + vma->vm_flags |= VM_DONTEXPAND; + vma->vm_private_data = map; + + dev_dbg(q->dev, "mmap %p: q=%p %08lx-%08lx (%lx) pgoff %08lx buf %d\n", + map, q, vma->vm_start, vma->vm_end, + (long int)buf->bsize, + vma->vm_pgoff, buf->i); + + videobuf_vm_open(vma); + + return 0; + +error: + kfree(map); + return -ENOMEM; +} + +static struct videobuf_qtype_ops qops = { + .magic = MAGIC_QTYPE_OPS, + + .alloc_vb = __videobuf_alloc_vb, + .iolock = __videobuf_iolock, + .mmap_mapper = __videobuf_mmap_mapper, + .vaddr = __videobuf_to_vaddr, +}; + +void videobuf_queue_dma_contig_init_isl(struct videobuf_queue *q, + const struct videobuf_queue_ops *ops, + struct device *dev, + spinlock_t *irqlock, + enum v4l2_buf_type type, + enum v4l2_field field, + unsigned int msize, + void *priv, + struct mutex *ext_lock) +{ + videobuf_queue_core_init(q, ops, dev, irqlock, type, field, msize, + priv, &qops, ext_lock); +} +EXPORT_SYMBOL_GPL(videobuf_queue_dma_contig_init_isl); + +dma_addr_t videobuf_to_dma_contig_isl(struct videobuf_buffer *buf) +{ + struct videobuf_dma_contig_memory *mem = buf->priv; + + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_DC_MEM); + + return mem->dma_handle; +} +EXPORT_SYMBOL_GPL(videobuf_to_dma_contig_isl); + +void* videobuf_to_vaddr_isl(struct videobuf_buffer *buf) +{ + struct videobuf_dma_contig_memory *mem = buf->priv; + + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_DC_MEM); + + return mem->vaddr; +} +EXPORT_SYMBOL_GPL(videobuf_to_vaddr_isl); + +void videobuf_dma_contig_free_isl(struct videobuf_queue *q, + struct videobuf_buffer *buf) +{ + struct videobuf_dma_contig_memory *mem = buf->priv; + + /* mmapped memory can't be freed here, otherwise mmapped region + would be released, while still needed. In this case, the memory + release should happen inside videobuf_vm_close(). + So, it should free memory only if the memory were allocated for + read() operation. + */ + if (buf->memory != V4L2_MEMORY_USERPTR) + return; + + if (!mem) + return; + + MAGIC_CHECK(mem->magic, MAGIC_DC_MEM); + + /* handle user space pointer case */ + if (buf->baddr) { + videobuf_dma_contig_user_put(mem); + return; + } + + /* read() method */ + if (mem->vaddr) { + dma_free_coherent(q->dev, mem->size, mem->vaddr, mem->dma_handle); + mem->vaddr = NULL; + } +} +EXPORT_SYMBOL_GPL(videobuf_dma_contig_free_isl); +#else + +#include "videobuf-dma-contig-tw.h" + +struct videobuf_dma_contig_memory { + u32 magic; + void *vaddr; + dma_addr_t dma_handle; + unsigned long size; + int is_userptr; +}; + +#define MAGIC_DC_MEM 0x0733ac61 +#define MAGIC_CHECK(is, should) \ + if (unlikely((is) != (should))) { \ + pr_err("magic mismatch: %x expected %x\n", (is), (should)); \ + BUG(); \ + } + +static void +videobuf_vm_open(struct vm_area_struct *vma) +{ + struct videobuf_mapping *map = vma->vm_private_data; + + dev_dbg(map->q->dev, "vm_open %p [count=%u,vma=%08lx-%08lx]\n", + map, map->count, vma->vm_start, vma->vm_end); + + map->count++; +} + +static void videobuf_vm_close(struct vm_area_struct *vma) +{ + struct videobuf_mapping *map = vma->vm_private_data; + struct videobuf_queue *q = map->q; + int i; + + dev_dbg(map->q->dev, "vm_close %p [count=%u,vma=%08lx-%08lx]\n", + map, map->count, vma->vm_start, vma->vm_end); + + map->count--; + if (0 == map->count) { + struct videobuf_dma_contig_memory *mem; + + dev_dbg(map->q->dev, "munmap %p q=%p\n", map, q); + mutex_lock(&q->vb_lock); + + /* We need first to cancel streams, before unmapping */ + if (q->streaming) + videobuf_queue_cancel(q); + + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + + if (q->bufs[i]->map != map) + continue; + + mem = q->bufs[i]->priv; + if (mem) { + /* This callback is called only if kernel has + allocated memory and this memory is mmapped. + In this case, memory should be freed, + in order to do memory unmap. + */ + + MAGIC_CHECK(mem->magic, MAGIC_DC_MEM); + + /* vfree is not atomic - can't be + called with IRQ's disabled + */ + dev_dbg(map->q->dev, "buf[%d] freeing %p\n", + i, mem->vaddr); + + dma_free_coherent(q->dev, mem->size, + mem->vaddr, mem->dma_handle); + mem->vaddr = NULL; + } + + q->bufs[i]->map = NULL; + q->bufs[i]->baddr = 0; + } + + kfree(map); + + mutex_unlock(&q->vb_lock); + } +} + +static struct vm_operations_struct videobuf_vm_ops = { + .open = videobuf_vm_open, + .close = videobuf_vm_close, +}; + +/** + * videobuf_dma_contig_user_put() - reset pointer to user space buffer + * @mem: per-buffer private videobuf-dma-contig data + * + * This function resets the user space pointer + */ +static void videobuf_dma_contig_user_put(struct videobuf_dma_contig_memory *mem) +{ + mem->is_userptr = 0; + mem->dma_handle = 0; + mem->size = 0; +} + +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,32)) +/** + * videobuf_dma_contig_user_get() - setup user space memory pointer + * @mem: per-buffer private videobuf-dma-contig data + * @vb: video buffer to map + * + * This function validates and sets up a pointer to user space memory. + * Only physically contiguous pfn-mapped memory is accepted. + * + * Returns 0 if successful. + */ +static int videobuf_dma_contig_user_get(struct videobuf_dma_contig_memory *mem, + struct videobuf_buffer *vb) +{ + struct mm_struct *mm = current->mm; + struct vm_area_struct *vma; + unsigned long prev_pfn, this_pfn; + unsigned long pages_done, user_address; + int ret; + + mem->size = PAGE_ALIGN(vb->size); + mem->is_userptr = 0; + ret = -EINVAL; + + down_read(&mm->mmap_sem); + + vma = find_vma(mm, vb->baddr); + if (!vma) + goto out_up; + + if ((vb->baddr + mem->size) > vma->vm_end) + goto out_up; + + pages_done = 0; + prev_pfn = 0; /* kill warning */ + user_address = vb->baddr; + + while (pages_done < (mem->size >> PAGE_SHIFT)) { + ret = follow_pfn(vma, user_address, &this_pfn); + if (ret) + break; + + if (pages_done == 0) + mem->dma_handle = this_pfn << PAGE_SHIFT; + else if (this_pfn != (prev_pfn + 1)) + ret = -EFAULT; + + if (ret) + break; + + prev_pfn = this_pfn; + user_address += PAGE_SIZE; + pages_done++; + } + + if (!ret) + mem->is_userptr = 1; + + out_up: + up_read(¤t->mm->mmap_sem); + + return ret; +} +#endif + +static void *__videobuf_alloc(size_t size) +{ + struct videobuf_dma_contig_memory *mem; + struct videobuf_buffer *vb; + + vb = kzalloc(size + sizeof(*mem), GFP_KERNEL); + if (vb) { + mem = vb->priv = ((char *)vb) + size; + mem->magic = MAGIC_DC_MEM; + } + + return vb; +} + +static void *__videobuf_to_vmalloc(struct videobuf_buffer *buf) +{ + struct videobuf_dma_contig_memory *mem = buf->priv; + + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_DC_MEM); + + return mem->vaddr; +} + +static int __videobuf_iolock(struct videobuf_queue *q, + struct videobuf_buffer *vb, + struct v4l2_framebuffer *fbuf) +{ + struct videobuf_dma_contig_memory *mem = vb->priv; + + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_DC_MEM); + + switch (vb->memory) { + case V4L2_MEMORY_MMAP: + dev_dbg(q->dev, "%s memory method MMAP\n", __func__); + + /* All handling should be done by __videobuf_mmap_mapper() */ + if (!mem->vaddr) { + dev_err(q->dev, "memory is not alloced/mmapped.\n"); + return -EINVAL; + } + break; + case V4L2_MEMORY_USERPTR: + dev_dbg(q->dev, "%s memory method USERPTR\n", __func__); + + /* handle pointer from user space */ + if (vb->baddr) +#if(LINUX_VERSION_CODE > KERNEL_VERSION(2,6,32)) + return videobuf_dma_contig_user_get(mem, vb); +#else + return -EINVAL; +#endif + + /* allocate memory for the read() method */ + mem->size = PAGE_ALIGN(vb->size); + mem->vaddr = dma_alloc_coherent(q->dev, mem->size, + &mem->dma_handle, GFP_KERNEL); + if (!mem->vaddr) { + dev_err(q->dev, "dma_alloc_coherent %ld failed\n", + mem->size); + return -ENOMEM; + } + + dev_dbg(q->dev, "dma_alloc_coherent data is at %p (%ld)\n", + mem->vaddr, mem->size); + break; + case V4L2_MEMORY_OVERLAY: + default: + dev_dbg(q->dev, "%s memory method OVERLAY/unknown\n", + __func__); + return -EINVAL; + } + + return 0; +} + +static int __videobuf_mmap_free(struct videobuf_queue *q) +{ + unsigned int i; + + dev_dbg(q->dev, "%s\n", __func__); + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (q->bufs[i] && q->bufs[i]->map) + return -EBUSY; + } + + return 0; +} + +static int __videobuf_mmap_mapper(struct videobuf_queue *q, + struct vm_area_struct *vma) +{ + struct videobuf_dma_contig_memory *mem; + struct videobuf_mapping *map; + unsigned int first; + int retval; + unsigned long size, offset = vma->vm_pgoff << PAGE_SHIFT; + + dev_dbg(q->dev, "%s\n", __func__); + if (!(vma->vm_flags & VM_WRITE) || !(vma->vm_flags & VM_SHARED)) + return -EINVAL; + + /* look for first buffer to map */ + for (first = 0; first < VIDEO_MAX_FRAME; first++) { + if (!q->bufs[first]) + continue; + + if (V4L2_MEMORY_MMAP != q->bufs[first]->memory) + continue; + if (q->bufs[first]->boff == offset) + break; + } + if (VIDEO_MAX_FRAME == first) { + dev_dbg(q->dev, "invalid user space offset [offset=0x%lx]\n", + offset); + return -EINVAL; + } + + /* create mapping + update buffer list */ + map = kzalloc(sizeof(struct videobuf_mapping), GFP_KERNEL); + if (!map) + return -ENOMEM; + + q->bufs[first]->map = map; + map->start = vma->vm_start; + map->end = vma->vm_end; + map->q = q; + + q->bufs[first]->baddr = vma->vm_start; + + mem = q->bufs[first]->priv; + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_DC_MEM); + + mem->size = PAGE_ALIGN(q->bufs[first]->bsize); + mem->vaddr = dma_alloc_coherent(q->dev, mem->size, + &mem->dma_handle, GFP_KERNEL); + if (!mem->vaddr) { + dev_err(q->dev, "dma_alloc_coherent size %ld failed\n", + mem->size); + goto error; + } + dev_dbg(q->dev, "dma_alloc_coherent data is at addr %p (size %ld)\n", + mem->vaddr, mem->size); + + /* Try to remap memory */ + + size = vma->vm_end - vma->vm_start; + size = (size < mem->size) ? size : mem->size; + + //vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); //notice!!!这里加上后会造成上层读内存CPU占用狂高 + retval = remap_pfn_range(vma, vma->vm_start, + mem->dma_handle >> PAGE_SHIFT, + size, vma->vm_page_prot); + if (retval) { + dev_err(q->dev, "mmap: remap failed with error %d. ", retval); + dma_free_coherent(q->dev, mem->size, + mem->vaddr, mem->dma_handle); + goto error; + } + + vma->vm_ops = &videobuf_vm_ops; + vma->vm_flags |= VM_DONTEXPAND; + vma->vm_private_data = map; + + dev_dbg(q->dev, "mmap %p: q=%p %08lx-%08lx (%lx) pgoff %08lx buf %d\n", + map, q, vma->vm_start, vma->vm_end, + (long int) q->bufs[first]->bsize, + vma->vm_pgoff, first); + + videobuf_vm_open(vma); + + return 0; + +error: + kfree(map); + return -ENOMEM; +} + +static int __videobuf_copy_to_user(struct videobuf_queue *q, + char __user *data, size_t count, + int nonblocking) +{ + struct videobuf_dma_contig_memory *mem = q->read_buf->priv; + void *vaddr; + + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_DC_MEM); + BUG_ON(!mem->vaddr); + + /* copy to userspace */ + if (count > q->read_buf->size - q->read_off) + count = q->read_buf->size - q->read_off; + + vaddr = mem->vaddr; + + if (copy_to_user(data, vaddr + q->read_off, count)) + return -EFAULT; + + return count; +} + +static int __videobuf_copy_stream(struct videobuf_queue *q, + char __user *data, size_t count, size_t pos, + int vbihack, int nonblocking) +{ + unsigned int *fc; + struct videobuf_dma_contig_memory *mem = q->read_buf->priv; + + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_DC_MEM); + + if (vbihack) { + /* dirty, undocumented hack -- pass the frame counter + * within the last four bytes of each vbi data block. + * We need that one to maintain backward compatibility + * to all vbi decoding software out there ... */ + fc = (unsigned int *)mem->vaddr; + fc += (q->read_buf->size >> 2) - 1; + *fc = q->read_buf->field_count >> 1; + dev_dbg(q->dev, "vbihack: %d\n", *fc); + } + + /* copy stuff using the common method */ + count = __videobuf_copy_to_user(q, data, count, nonblocking); + + if ((count == -EFAULT) && (pos == 0)) + return -EFAULT; + + return count; +} + +static struct videobuf_qtype_ops qops = { + .magic = MAGIC_QTYPE_OPS, + + .alloc = __videobuf_alloc, + .iolock = __videobuf_iolock, + .mmap_free = __videobuf_mmap_free, + .mmap_mapper = __videobuf_mmap_mapper, + .video_copy_to_user = __videobuf_copy_to_user, + .copy_stream = __videobuf_copy_stream, + .vmalloc = __videobuf_to_vmalloc, +}; + +void videobuf_queue_dma_contig_init_isl(struct videobuf_queue *q, + struct videobuf_queue_ops *ops, + struct device *dev, + spinlock_t *irqlock, + enum v4l2_buf_type type, + enum v4l2_field field, + unsigned int msize, + void *priv) +{ + videobuf_queue_core_init(q, ops, dev, irqlock, type, field, msize, + priv, &qops); +} +EXPORT_SYMBOL_GPL(videobuf_queue_dma_contig_init_isl); + +dma_addr_t videobuf_to_dma_contig_isl(struct videobuf_buffer *buf) +{ + struct videobuf_dma_contig_memory *mem = buf->priv; + + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_DC_MEM); + + return mem->dma_handle; +} +EXPORT_SYMBOL_GPL(videobuf_to_dma_contig_isl); + +void* videobuf_to_vaddr_isl(struct videobuf_buffer *buf) +{ + struct videobuf_dma_contig_memory *mem = buf->priv; + + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_DC_MEM); + + return mem->vaddr; +} +EXPORT_SYMBOL_GPL(videobuf_to_vaddr_isl); + +void videobuf_dma_contig_free_isl(struct videobuf_queue *q, + struct videobuf_buffer *buf) +{ + struct videobuf_dma_contig_memory *mem = buf->priv; + + /* mmapped memory can't be freed here, otherwise mmapped region + would be released, while still needed. In this case, the memory + release should happen inside videobuf_vm_close(). + So, it should free memory only if the memory were allocated for + read() operation. + */ + if (buf->memory != V4L2_MEMORY_USERPTR) + return; + + if (!mem) + return; + + MAGIC_CHECK(mem->magic, MAGIC_DC_MEM); + + /* handle user space pointer case */ + if (buf->baddr) { + videobuf_dma_contig_user_put(mem); + return; + } + + /* read() method */ + dma_free_coherent(q->dev, mem->size, mem->vaddr, mem->dma_handle); + mem->vaddr = NULL; +} +EXPORT_SYMBOL_GPL(videobuf_dma_contig_free_isl); +#endif + +MODULE_DESCRIPTION("helper module to manage video4linux dma contig buffers"); +MODULE_AUTHOR("Magnus Damm"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pci/pcie/tw6869/videobuf-dma-contig-tw.h b/drivers/pci/pcie/tw6869/videobuf-dma-contig-tw.h new file mode 100644 index 000000000000..1d4df8d54bbe --- /dev/null +++ b/drivers/pci/pcie/tw6869/videobuf-dma-contig-tw.h @@ -0,0 +1,46 @@ +/* + * helper functions for physically contiguous capture buffers + * + * The functions support hardware lacking scatter gather support + * (i.e. the buffers must be linear in physical memory) + * + * Copyright (c) 2008 Magnus Damm + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 + */ +#ifndef _VIDEOBUF_DMA_CONTIG_TW_H +#define _VIDEOBUF_DMA_CONTIG_TW_H + +#include <linux/dma-mapping.h> +#include <media/videobuf-core.h> +#include <linux/version.h> + +#if(LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)) +void videobuf_queue_dma_contig_init_isl(struct videobuf_queue *q, + const struct videobuf_queue_ops *ops, + struct device *dev, + spinlock_t *irqlock, + enum v4l2_buf_type type, + enum v4l2_field field, + unsigned int msize, + void *priv, + struct mutex *ext_lock); +#else +void videobuf_queue_dma_contig_init_isl(struct videobuf_queue *q, + struct videobuf_queue_ops *ops, + struct device *dev, + spinlock_t *irqlock, + enum v4l2_buf_type type, + enum v4l2_field field, + unsigned int msize, + void *priv); +#endif + +dma_addr_t videobuf_to_dma_contig_isl(struct videobuf_buffer *buf); +void* videobuf_to_vaddr_isl(struct videobuf_buffer *buf); +void videobuf_dma_contig_free_isl(struct videobuf_queue *q, + struct videobuf_buffer *buf); + +#endif /* _VIDEOBUF_DMA_CONTIG_TW_H */ |