diff options
author | Michael Hunold <devnull@localhost> | 2002-12-17 14:56:17 +0000 |
---|---|---|
committer | Michael Hunold <devnull@localhost> | 2002-12-17 14:56:17 +0000 |
commit | 1f0cdfa2a5aba72c3c69340a21ff9e8e9afd3fe1 (patch) | |
tree | ae992b2fd4a53a84fb8a829f39fbea15174e7742 /linux/drivers/media | |
parent | bb7af53caa24320a10340061af6b9c3f7f4ceb26 (diff) | |
download | mediapointer-dvb-s2-1f0cdfa2a5aba72c3c69340a21ff9e8e9afd3fe1.tar.gz mediapointer-dvb-s2-1f0cdfa2a5aba72c3c69340a21ff9e8e9afd3fe1.tar.bz2 |
Add the new saa7146 driver core to media/common.
Diffstat (limited to 'linux/drivers/media')
-rw-r--r-- | linux/drivers/media/common/Kconfig | 11 | ||||
-rw-r--r-- | linux/drivers/media/common/Makefile | 8 | ||||
-rw-r--r-- | linux/drivers/media/common/saa7146.h | 645 | ||||
-rw-r--r-- | linux/drivers/media/common/saa7146_core.c | 910 | ||||
-rw-r--r-- | linux/drivers/media/common/saa7146_hlp.c | 974 | ||||
-rw-r--r-- | linux/drivers/media/common/saa7146_i2c.c | 426 | ||||
-rw-r--r-- | linux/drivers/media/common/saa7146_vbi.c | 440 | ||||
-rw-r--r-- | linux/drivers/media/common/saa7146_video.c | 1297 |
8 files changed, 4711 insertions, 0 deletions
diff --git a/linux/drivers/media/common/Kconfig b/linux/drivers/media/common/Kconfig new file mode 100644 index 000000000..9061b1af1 --- /dev/null +++ b/linux/drivers/media/common/Kconfig @@ -0,0 +1,11 @@ +config VIDEO_SAA7146 + tristate + default y if DVB_AV7110=y + default m if DVB_AV7110=m + depends on VIDEO_DEV && PCI && I2C + +config VIDEO_VIDEOBUF + tristate + default y if VIDEO_SAA7134=y || VIDEO_BT848=y || VIDEO_SAA7146=y + default m if VIDEO_SAA7134=m || VIDEO_BT848=m || VIDEO_SAA7146=m + depends on VIDEO_DEV diff --git a/linux/drivers/media/common/Makefile b/linux/drivers/media/common/Makefile new file mode 100644 index 000000000..5ececd228 --- /dev/null +++ b/linux/drivers/media/common/Makefile @@ -0,0 +1,8 @@ +export-objs := video-buf.o + +saa7146-objs := saa7146_core.o saa7146_hlp.o saa7146_i2c.o \ + saa7146_vbi.o saa7146_video.o + +obj-$(CONFIG_VIDEO_VIDEOBUF) += video-buf.o +obj-$(CONFIG_VIDEO_SAA7146) += saa7146.o + diff --git a/linux/drivers/media/common/saa7146.h b/linux/drivers/media/common/saa7146.h new file mode 100644 index 000000000..27889ec19 --- /dev/null +++ b/linux/drivers/media/common/saa7146.h @@ -0,0 +1,645 @@ +#ifndef __SAA7146__ +#define __SAA7146__ + +/* fixme: this triggers bugs in video_read() */ +//#define DEBUG_SPINLOCKS 1 + +#include <linux/module.h> /* for module-version */ +#include <linux/delay.h> /* for delay-stuff */ +#include <linux/slab.h> /* for kmalloc/kfree */ +#include <linux/pci.h> /* for pci-config-stuff, vendor ids etc. */ +#include <linux/wrapper.h> /* for mem_map_reserve */ +#include <linux/init.h> /* for "__init" */ +#include <linux/interrupt.h> /* for IMMEDIATE_BH */ +#include <linux/kmod.h> /* for kernel module loader */ +#include <linux/i2c.h> /* for i2c subsystem */ +#include <linux/i2c-id.h> +#include <asm/io.h> /* for accessing devices */ + +#include <linux/videodev2.h> +#include "video-buf.h" + +#define MAX_SAA7146_CAPTURE_BUFFERS 32 /* arbitrary */ + +#define BUFFER_TIMEOUT (HZ/2) /* 0.5 seconds */ + +#define SAA7146_VERSION_CODE KERNEL_VERSION(0,5,0) + +#define saa7146_write(sxy,adr,dat) writel((dat),(sxy->mem+(adr))) +#define saa7146_read(sxy,adr) readl(sxy->mem+(adr)) + +#define BUG_ON(condition) do { if ((condition)!=0) BUG(); } while(0) +#define BOARD_CAN_DO_VBI(dev) (dev->revision != 0 && 0 != dev->ext->vbi) + +extern int debug; +// #define DEBUG_PROLOG printk("(0x%08x): %s: %s(): ",dev == 0 ? 0 : (dev->mem == 0?0:saa7146_read(dev,IER)),KBUILD_MODNAME,__FUNCTION__) +#define DEBUG_PROLOG printk("%s: %s(): ",KBUILD_MODNAME,__FUNCTION__) + +#define DEB_S(x) if (0!=(debug&0x01)) { DEBUG_PROLOG; printk x; } /* simple debug messages */ +#define DEB_D(x) if (0!=(debug&0x02)) { DEBUG_PROLOG; printk x; } /* more detailed debug messages */ +#define DEB_EE(x) if (0!=(debug&0x04)) { DEBUG_PROLOG; printk x; } /* print enter and exit of functions */ +#define DEB_I2C(x) if (0!=(debug&0x08)) { DEBUG_PROLOG; printk x; } /* i2c debug messages */ +#define DEB_VBI(x) if (0!=(debug&0x10)) { DEBUG_PROLOG; printk x; } /* vbi debug messages */ +#define DEB_INT(x) if (0!=(debug&0x20)) { DEBUG_PROLOG; printk x; } /* interrupt debug messages */ +#define DEB_CAP(x) if (0!=(debug&0x40)) { DEBUG_PROLOG; printk x; } /* capture debug messages */ + +#define ERR(x) { DEBUG_PROLOG; printk x; } +#define INFO(x) { printk("%s: ",KBUILD_MODNAME); printk x; } + +#define IER_DISABLE(x,y) \ + saa7146_write(x, IER, saa7146_read(x, IER) & ~(y)); +#define IER_ENABLE(x,y) \ + saa7146_write(x, IER, saa7146_read(x, IER) | (y)); + +struct saa7146_dev; +struct saa7146_fh; +struct saa7146_standard; + +struct saa7146_extension_ioctls +{ + unsigned int cmd; + int flags; +}; + +struct saa7146_sub_info { + unsigned int subvendor, subdevice; +}; + +#define SAA7146_EXCLUSIVE 0x1 +#define SAA7146_BEFORE 0x2 +#define SAA7146_AFTER 0x4 + +struct saa7146_extension +{ + char name[32]; /* name of the device */ + int inputs; + int audios; + u32 capabilities; + + /* pairs of subvendor and subdevice ids for + supported devices, last entry 0xffff, 0xfff */ + struct saa7146_sub_info *devices; + + struct module* module; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) + void (*inc_use)(struct saa7146_dev*); + void (*dec_use)(struct saa7146_dev*); +#endif + + /* additionally supported transmission standards */ + struct saa7146_standard *stds; + int num_stds; + int (*std_callback)(struct saa7146_dev*, struct saa7146_standard *); + + int use_kernel_i2c; + int (*vbi)(struct saa7146_dev*); // want vbi + + struct saa7146_extension_ioctls *ioctls; + + /* extension functions */ + int (*preinit)(struct saa7146_dev*); + int (*probe)(struct saa7146_dev*, unsigned int subvendor, unsigned int subdevice); + + int (*attach)(struct saa7146_dev*); + int (*detach)(struct saa7146_dev*); + + int (*ioctl)(struct saa7146_dev*, unsigned int cmd, void *arg); + + u32 irq_mask; /* mask to indicate, which irq-events are handled by the extension */ + void (*irq_func)(struct saa7146_dev*, u32* irq_mask); +}; + +struct saa7146_video_dma { + u32 base_odd; + u32 base_even; + u32 prot_addr; + u32 pitch; + u32 base_page; + u32 num_line_byte; +}; + +struct saa7146_format { + char *name; + int pixelformat; + u32 trans; + u8 depth; + unsigned long flags; +}; + +struct saa7146_standard +{ + char *name; + v4l2_std_id id; + + int v_offset; + int v_field; + int v_calc; + + int h_offset; + int h_pixels; + int h_calc; + + int v_max_out; + int h_max_out; +}; + +/* saa7146 page table */ +struct saa7146_pgtable { + unsigned int size; + u32 *cpu; + dma_addr_t dma; + /* used for offsets for u,v planes for planar capture modes */ + unsigned long offset; +}; + +/* buffer for one video/vbi frame */ +struct saa7146_buf { + /* common v4l buffer stuff -- must be first */ + struct videobuf_buffer vb; + + /* saa7146 specific */ + struct v4l2_pix_format *fmt; + int (*activate)(struct saa7146_dev *dev, + struct saa7146_buf *buf, + struct saa7146_buf *next); + + /* page tables */ + struct saa7146_pgtable pt[3]; +}; + +struct saa7146_dmaqueue { + struct saa7146_dev *dev; + struct saa7146_buf *curr; + struct list_head queue; + struct timer_list timeout; +}; + +struct saa7146_overlay { + struct saa7146_fh *fh; + struct v4l2_window win; + struct v4l2_clip clips[16]; + int nclips; +}; + +/* per open data */ +struct saa7146_fh { + struct saa7146_dev *dev; + /* if this is a vbi or capture open */ + enum v4l2_buf_type type; + + /* video overlay */ + struct saa7146_overlay ov; + + /* video capture */ + struct videobuf_queue video_q; + struct v4l2_pix_format video_fmt; + + /* vbi capture */ + struct videobuf_queue vbi_q; + struct v4l2_vbi_format vbi_fmt; + struct timer_list vbi_read_timeout; +}; + +struct saa7146_dev +{ + struct video_device video_dev; + struct video_device vbi_dev; + + /* used for loadable modules */ + struct module *module; + + struct list_head item; + + /* different device locks */ + spinlock_t slock; + struct semaphore lock; + struct semaphore i2c_lock; + + unsigned char* mem; /* pointer to mapped IO memory */ + int revision; /* chip revision; needed for bug-workarounds*/ + + /* pci-device & irq stuff*/ + char name[32]; + struct pci_dev *pci; + + /* extension handling */ + struct saa7146_extension* ext; /* indicates if handled by extension */ + void* ext_priv; /* pointer for extension private use (most likely some private data) */ + + /* i2c-stuff */ + struct i2c_adapter *i2c_adapter; + u32 i2c_bitrate; + u32* i2c_mem; /* pointer to i2c memory */ + + /* memories */ + u32* clipping; /* pointer to clipping memory */ + u32* rps0; + u32* rps1; + + /* vbi capture */ + struct saa7146_dmaqueue vbi_q; + /* vbi workaround interrupt queue */ + wait_queue_head_t vbi_wq; + int vbi_fieldcount; + struct saa7146_fh *vbi_streaming; + + /* video overlay */ + struct v4l2_framebuffer ov_fb; + struct saa7146_format *ov_fmt; + struct saa7146_overlay *ov_data; + + /* video capture */ + struct saa7146_dmaqueue video_q; + struct saa7146_fh *streaming; + + /* common: fixme? shouldn't this be in saa7146_fh? + (this leads to a more complicated question: shall the driver + store the different settings (for example S_INPUT) for every open + and restore it appropriately, or should all settings be common for + all opens? currently, we do the latter, like all other + drivers do... */ + struct saa7146_standard *standard; + int vflip; + int hflip; + int current_hps_source; + int current_hps_sync; + +}; + +struct saa7146_use_ops { + void (*init)(struct saa7146_dev *dev); + void(*open)(struct saa7146_dev *dev, struct saa7146_fh *fh); + void (*release)(struct saa7146_dev *dev, struct saa7146_fh *fh,struct file *file); + void (*irq_done)(struct saa7146_dev *dev, unsigned long status); + ssize_t (*read)(struct file *file, char *data, size_t count, loff_t *ppos); + int (*capture_begin)(struct saa7146_fh *fh); + int (*capture_end)(struct saa7146_fh *fh); +}; + +/* from saa7146_i2c.c */ +int saa7146_i2c_adapter_prepare(struct saa7146_dev *dev); +int saa7146_i2c_transfer(struct saa7146_dev *saa, const struct i2c_msg msgs[], int num, int retries); + +/* from saa7146_hlp.c */ +void saa7146_set_overlay(struct saa7146_dev *dev, struct saa7146_fh *fh, int v); +void saa7146_set_capture(struct saa7146_dev *dev, struct saa7146_buf *buf, struct saa7146_buf *next); +void saa7146_write_out_dma(struct saa7146_dev* dev, int which, struct saa7146_video_dma* vdma) ; + +void saa7146_set_hps_source_and_sync(struct saa7146_dev *saa, int source, int sync); +void saa7146_set_gpio(struct saa7146_dev *saa, u8 pin, u8 data); + +/* from saa7146_core.c */ +extern struct list_head devices; +int saa7146_register_extension(struct saa7146_extension*); +int saa7146_unregister_extension(struct saa7146_extension*); +struct saa7146_format* format_by_fourcc(struct saa7146_dev *dev, int fourcc); + +void saa7146_buffer_finish(struct saa7146_dev *dev, struct saa7146_dmaqueue *q, int state); +void saa7146_buffer_next(struct saa7146_dev *dev, struct saa7146_dmaqueue *q,int vbi); +int saa7146_buffer_queue(struct saa7146_dev *dev, struct saa7146_dmaqueue *q, struct saa7146_buf *buf); +void saa7146_buffer_timeout(unsigned long data); +void saa7146_dma_free(struct saa7146_dev *dev,struct saa7146_buf *buf); + +int saa7146_pgtable_alloc(struct pci_dev *pci, struct saa7146_pgtable *pt); +void saa7146_pgtable_free(struct pci_dev *pci, struct saa7146_pgtable *pt); +void saa7146_pgtable_build_single(struct pci_dev *pci, struct saa7146_pgtable *pt, struct scatterlist *list, int length ); + +/* from saa7146_video.c */ +extern struct saa7146_use_ops saa7146_video_uops; +int video_do_ioctl(struct inode *inode, struct file *file, unsigned int cmd, void *arg); + +/* from saa7146_vbi.c */ +extern struct saa7146_use_ops saa7146_vbi_uops; + +/* saa7146 source inputs */ +#define SAA7146_HPS_SOURCE_PORT_A 0x00 +#define SAA7146_HPS_SOURCE_PORT_B 0x01 +#define SAA7146_HPS_SOURCE_YPB_CPA 0x02 +#define SAA7146_HPS_SOURCE_YPA_CPB 0x03 + +/* sync inputs */ +#define SAA7146_HPS_SYNC_PORT_A 0x00 +#define SAA7146_HPS_SYNC_PORT_B 0x01 + +/* number of vertical active lines */ +#define V_ACTIVE_LINES_PAL 576 +#define V_ACTIVE_LINES_NTSC 480 +#define V_ACTIVE_LINES_SECAM 576 + +/* number of lines in a field for HPS to process */ +#define V_FIELD_PAL 288 +#define V_FIELD_NTSC 240 +#define V_FIELD_SECAM 288 + +/* number of lines of vertical offset before processing */ +#define V_OFFSET_PAL 0x17 +#define V_OFFSET_NTSC 0x16 +#define V_OFFSET_SECAM 0x14 + +/* number of horizontal pixels to process */ +#define H_PIXELS_PAL 680 +#define H_PIXELS_NTSC 708 +#define H_PIXELS_SECAM 720 + +/* horizontal offset of processing window */ +#define H_OFFSET_PAL 0x14 +#define H_OFFSET_NTSC 0x06 +#define H_OFFSET_SECAM 0x14 + +#define SAA7146_PAL_VALUES V_OFFSET_PAL, V_FIELD_PAL, V_ACTIVE_LINES_PAL, H_OFFSET_PAL, H_PIXELS_PAL, H_PIXELS_PAL+1, V_ACTIVE_LINES_PAL, 768 +#define SAA7146_NTSC_VALUES V_OFFSET_NTSC, V_FIELD_NTSC, V_ACTIVE_LINES_NTSC, H_OFFSET_NTSC, H_PIXELS_NTSC, H_PIXELS_NTSC+1, V_ACTIVE_LINES_NTSC, 640 +#define SAA7146_SECAM_VALUES V_OFFSET_SECAM, V_FIELD_SECAM, V_ACTIVE_LINES_SECAM, H_OFFSET_SECAM, H_PIXELS_SECAM, H_PIXELS_SECAM+1, V_ACTIVE_LINES_SECAM, 768 + +/* some memory sizes */ +#define SAA7146_CLIPPING_MEM (14*PAGE_SIZE) +#define SAA7146_RPS_MEM ( 1*PAGE_SIZE) +#define SAA7146_I2C_MEM ( 1*PAGE_SIZE) +#define SAA7146_DEBI_MEM ( 8*PAGE_SIZE) + +/* some i2c constants */ +#define SAA7146_I2C_TIMEOUT 100 /* i2c-timeout-value in ms */ +#define SAA7146_I2C_RETRIES 3 /* how many times shall we retry an i2c-operation? */ +#define SAA7146_I2C_DELAY 5 /* time we wait after certain i2c-operations */ + +/* unsorted defines */ +#define ME1 0x0000000800 +#define PV1 0x0000000008 + +/* some defines for the various clipping-modes */ +#define SAA7146_CLIPPING_RECT 0x4 +#define SAA7146_CLIPPING_RECT_INVERTED 0x5 +#define SAA7146_CLIPPING_MASK 0x6 +#define SAA7146_CLIPPING_MASK_INVERTED 0x7 + +/* define for the register programming sequencer (rps) */ +#define CMD_NOP 0x00000000 /* No operation */ +#define CMD_CLR_EVENT 0x00000000 /* Clear event */ +#define CMD_SET_EVENT 0x10000000 /* Set signal event */ +#define CMD_PAUSE 0x20000000 /* Pause */ +#define CMD_CHECK_LATE 0x30000000 /* Check late */ +#define CMD_UPLOAD 0x40000000 /* Upload */ +#define CMD_STOP 0x50000000 /* Stop */ +#define CMD_INTERRUPT 0x60000000 /* Interrupt */ +#define CMD_JUMP 0x80000000 /* Jump */ +#define CMD_WR_REG 0x90000000 /* Write (load) register */ +#define CMD_RD_REG 0xa0000000 /* Read (store) register */ +#define CMD_WR_REG_MASK 0xc0000000 /* Write register with mask */ + +#define CMD_OAN MASK_27 +#define CMD_INV MASK_26 +#define CMD_SIG4 MASK_25 +#define CMD_SIG3 MASK_24 +#define CMD_SIG2 MASK_23 +#define CMD_SIG1 MASK_22 +#define CMD_SIG0 MASK_21 +#define CMD_O_FID_B MASK_14 +#define CMD_E_FID_B MASK_13 +#define CMD_O_FID_A MASK_12 +#define CMD_E_FID_A MASK_11 + +/* output formats: each entry holds four informations */ +#define RGB08_COMPOSED 0x0217 /* composed is used in the sense of "not-planar" */ +/* this means: planar?=0, yuv2rgb-conversation-mode=2, dither=yes(=1), format-mode = 7 */ +#define RGB15_COMPOSED 0x0213 +#define RGB16_COMPOSED 0x0210 +#define RGB24_COMPOSED 0x0201 +#define RGB32_COMPOSED 0x0202 + +#define Y8 0x0006 +#define YUV411_COMPOSED 0x0003 +#define YUV422_COMPOSED 0x0000 +/* this means: planar?=1, yuv2rgb-conversion-mode=0, dither=no(=0), format-mode = b */ +#define YUV411_DECOMPOSED 0x100b +#define YUV422_DECOMPOSED 0x1009 +#define YUV420_DECOMPOSED 0x100a + +#define IS_PLANAR(x) (x & 0xf000) + +/* misc defines */ +#define SAA7146_NO_SWAP (0x0) +#define SAA7146_TWO_BYTE_SWAP (0x1) +#define SAA7146_FOUR_BYTE_SWAP (0x2) + +/* Bit mask constants */ +#define MASK_00 0x00000001 /* Mask value for bit 0 */ +#define MASK_01 0x00000002 /* Mask value for bit 1 */ +#define MASK_02 0x00000004 /* Mask value for bit 2 */ +#define MASK_03 0x00000008 /* Mask value for bit 3 */ +#define MASK_04 0x00000010 /* Mask value for bit 4 */ +#define MASK_05 0x00000020 /* Mask value for bit 5 */ +#define MASK_06 0x00000040 /* Mask value for bit 6 */ +#define MASK_07 0x00000080 /* Mask value for bit 7 */ +#define MASK_08 0x00000100 /* Mask value for bit 8 */ +#define MASK_09 0x00000200 /* Mask value for bit 9 */ +#define MASK_10 0x00000400 /* Mask value for bit 10 */ +#define MASK_11 0x00000800 /* Mask value for bit 11 */ +#define MASK_12 0x00001000 /* Mask value for bit 12 */ +#define MASK_13 0x00002000 /* Mask value for bit 13 */ +#define MASK_14 0x00004000 /* Mask value for bit 14 */ +#define MASK_15 0x00008000 /* Mask value for bit 15 */ +#define MASK_16 0x00010000 /* Mask value for bit 16 */ +#define MASK_17 0x00020000 /* Mask value for bit 17 */ +#define MASK_18 0x00040000 /* Mask value for bit 18 */ +#define MASK_19 0x00080000 /* Mask value for bit 19 */ +#define MASK_20 0x00100000 /* Mask value for bit 20 */ +#define MASK_21 0x00200000 /* Mask value for bit 21 */ +#define MASK_22 0x00400000 /* Mask value for bit 22 */ +#define MASK_23 0x00800000 /* Mask value for bit 23 */ +#define MASK_24 0x01000000 /* Mask value for bit 24 */ +#define MASK_25 0x02000000 /* Mask value for bit 25 */ +#define MASK_26 0x04000000 /* Mask value for bit 26 */ +#define MASK_27 0x08000000 /* Mask value for bit 27 */ +#define MASK_28 0x10000000 /* Mask value for bit 28 */ +#define MASK_29 0x20000000 /* Mask value for bit 29 */ +#define MASK_30 0x40000000 /* Mask value for bit 30 */ +#define MASK_31 0x80000000 /* Mask value for bit 31 */ + +#define MASK_B0 0x000000ff /* Mask value for byte 0 */ +#define MASK_B1 0x0000ff00 /* Mask value for byte 1 */ +#define MASK_B2 0x00ff0000 /* Mask value for byte 2 */ +#define MASK_B3 0xff000000 /* Mask value for byte 3 */ + +#define MASK_W0 0x0000ffff /* Mask value for word 0 */ +#define MASK_W1 0xffff0000 /* Mask value for word 1 */ + +#define MASK_PA 0xfffffffc /* Mask value for physical address */ +#define MASK_PR 0xfffffffe /* Mask value for protection register */ +#define MASK_ER 0xffffffff /* Mask value for the entire register */ + +#define MASK_NONE 0x00000000 /* No mask */ + +/* register aliases */ +#define BASE_ODD1 0x00 /* Video DMA 1 registers */ +#define BASE_EVEN1 0x04 +#define PROT_ADDR1 0x08 +#define PITCH1 0x0C +#define BASE_PAGE1 0x10 /* Video DMA 1 base page */ +#define NUM_LINE_BYTE1 0x14 + +#define BASE_ODD2 0x18 /* Video DMA 2 registers */ +#define BASE_EVEN2 0x1C +#define PROT_ADDR2 0x20 +#define PITCH2 0x24 +#define BASE_PAGE2 0x28 /* Video DMA 2 base page */ +#define NUM_LINE_BYTE2 0x2C + +#define BASE_ODD3 0x30 /* Video DMA 3 registers */ +#define BASE_EVEN3 0x34 +#define PROT_ADDR3 0x38 +#define PITCH3 0x3C +#define BASE_PAGE3 0x40 /* Video DMA 3 base page */ +#define NUM_LINE_BYTE3 0x44 + +#define PCI_BT_V1 0x48 /* Video/FIFO 1 */ +#define PCI_BT_V2 0x49 /* Video/FIFO 2 */ +#define PCI_BT_V3 0x4A /* Video/FIFO 3 */ +#define PCI_BT_DEBI 0x4B /* DEBI */ +#define PCI_BT_A 0x4C /* Audio */ + +#define DD1_INIT 0x50 /* Init setting of DD1 interface */ + +#define DD1_STREAM_B 0x54 /* DD1 B video data stream handling */ +#define DD1_STREAM_A 0x56 /* DD1 A video data stream handling */ + +#define BRS_CTRL 0x58 /* BRS control register */ +#define HPS_CTRL 0x5C /* HPS control register */ +#define HPS_V_SCALE 0x60 /* HPS vertical scale */ +#define HPS_V_GAIN 0x64 /* HPS vertical ACL and gain */ +#define HPS_H_PRESCALE 0x68 /* HPS horizontal prescale */ +#define HPS_H_SCALE 0x6C /* HPS horizontal scale */ +#define BCS_CTRL 0x70 /* BCS control */ +#define CHROMA_KEY_RANGE 0x74 +#define CLIP_FORMAT_CTRL 0x78 /* HPS outputs formats & clipping */ + +#define DEBI_CONFIG 0x7C +#define DEBI_COMMAND 0x80 +#define DEBI_PAGE 0x84 +#define DEBI_AD 0x88 + +#define I2C_TRANSFER 0x8C +#define I2C_STATUS 0x90 + +#define BASE_A1_IN 0x94 /* Audio 1 input DMA */ +#define PROT_A1_IN 0x98 +#define PAGE_A1_IN 0x9C + +#define BASE_A1_OUT 0xA0 /* Audio 1 output DMA */ +#define PROT_A1_OUT 0xA4 +#define PAGE_A1_OUT 0xA8 + +#define BASE_A2_IN 0xAC /* Audio 2 input DMA */ +#define PROT_A2_IN 0xB0 +#define PAGE_A2_IN 0xB4 + +#define BASE_A2_OUT 0xB8 /* Audio 2 output DMA */ +#define PROT_A2_OUT 0xBC +#define PAGE_A2_OUT 0xC0 + +#define RPS_PAGE0 0xC4 /* RPS task 0 page register */ +#define RPS_PAGE1 0xC8 /* RPS task 1 page register */ + +#define RPS_THRESH0 0xCC /* HBI threshold for task 0 */ +#define RPS_THRESH1 0xD0 /* HBI threshold for task 1 */ + +#define RPS_TOV0 0xD4 /* RPS timeout for task 0 */ +#define RPS_TOV1 0xD8 /* RPS timeout for task 1 */ + +#define IER 0xDC /* Interrupt enable register */ + +#define GPIO_CTRL 0xE0 /* GPIO 0-3 register */ + +#define EC1SSR 0xE4 /* Event cnt set 1 source select */ +#define EC2SSR 0xE8 /* Event cnt set 2 source select */ +#define ECT1R 0xEC /* Event cnt set 1 thresholds */ +#define ECT2R 0xF0 /* Event cnt set 2 thresholds */ + +#define ACON1 0xF4 +#define ACON2 0xF8 + +#define MC1 0xFC /* Main control register 1 */ +#define MC2 0x100 /* Main control register 2 */ + +#define RPS_ADDR0 0x104 /* RPS task 0 address register */ +#define RPS_ADDR1 0x108 /* RPS task 1 address register */ + +#define ISR 0x10C /* Interrupt status register */ +#define PSR 0x110 /* Primary status register */ +#define SSR 0x114 /* Secondary status register */ + +#define EC1R 0x118 /* Event counter set 1 register */ +#define EC2R 0x11C /* Event counter set 2 register */ + +#define PCI_VDP1 0x120 /* Video DMA pointer of FIFO 1 */ +#define PCI_VDP2 0x124 /* Video DMA pointer of FIFO 2 */ +#define PCI_VDP3 0x128 /* Video DMA pointer of FIFO 3 */ +#define PCI_ADP1 0x12C /* Audio DMA pointer of audio out 1 */ +#define PCI_ADP2 0x130 /* Audio DMA pointer of audio in 1 */ +#define PCI_ADP3 0x134 /* Audio DMA pointer of audio out 2 */ +#define PCI_ADP4 0x138 /* Audio DMA pointer of audio in 2 */ +#define PCI_DMA_DDP 0x13C /* DEBI DMA pointer */ + +#define LEVEL_REP 0x140, +#define A_TIME_SLOT1 0x180, /* from 180 - 1BC */ +#define A_TIME_SLOT2 0x1C0, /* from 1C0 - 1FC */ + +/* isr masks */ +#define SPCI_PPEF 0x80000000 /* PCI parity error */ +#define SPCI_PABO 0x40000000 /* PCI access error (target or master abort) */ +#define SPCI_PPED 0x20000000 /* PCI parity error on 'real time data' */ +#define SPCI_RPS_I1 0x10000000 /* Interrupt issued by RPS1 */ +#define SPCI_RPS_I0 0x08000000 /* Interrupt issued by RPS0 */ +#define SPCI_RPS_LATE1 0x04000000 /* RPS task 1 is late */ +#define SPCI_RPS_LATE0 0x02000000 /* RPS task 0 is late */ +#define SPCI_RPS_E1 0x01000000 /* RPS error from task 1 */ +#define SPCI_RPS_E0 0x00800000 /* RPS error from task 0 */ +#define SPCI_RPS_TO1 0x00400000 /* RPS timeout task 1 */ +#define SPCI_RPS_TO0 0x00200000 /* RPS timeout task 0 */ +#define SPCI_UPLD 0x00100000 /* RPS in upload */ +#define SPCI_DEBI_S 0x00080000 /* DEBI status */ +#define SPCI_DEBI_E 0x00040000 /* DEBI error */ +#define SPCI_IIC_S 0x00020000 /* I2C status */ +#define SPCI_IIC_E 0x00010000 /* I2C error */ +#define SPCI_A2_IN 0x00008000 /* Audio 2 input DMA protection / limit */ +#define SPCI_A2_OUT 0x00004000 /* Audio 2 output DMA protection / limit */ +#define SPCI_A1_IN 0x00002000 /* Audio 1 input DMA protection / limit */ +#define SPCI_A1_OUT 0x00001000 /* Audio 1 output DMA protection / limit */ +#define SPCI_AFOU 0x00000800 /* Audio FIFO over- / underflow */ +#define SPCI_V_PE 0x00000400 /* Video protection address */ +#define SPCI_VFOU 0x00000200 /* Video FIFO over- / underflow */ +#define SPCI_FIDA 0x00000100 /* Field ID video port A */ +#define SPCI_FIDB 0x00000080 /* Field ID video port B */ +#define SPCI_PIN3 0x00000040 /* GPIO pin 3 */ +#define SPCI_PIN2 0x00000020 /* GPIO pin 2 */ +#define SPCI_PIN1 0x00000010 /* GPIO pin 1 */ +#define SPCI_PIN0 0x00000008 /* GPIO pin 0 */ +#define SPCI_ECS 0x00000004 /* Event counter 1, 2, 4, 5 */ +#define SPCI_EC3S 0x00000002 /* Event counter 3 */ +#define SPCI_EC0S 0x00000001 /* Event counter 0 */ + +/* i2c */ +#define SAA7146_I2C_ABORT (1<<7) +#define SAA7146_I2C_SPERR (1<<6) +#define SAA7146_I2C_APERR (1<<5) +#define SAA7146_I2C_DTERR (1<<4) +#define SAA7146_I2C_DRERR (1<<3) +#define SAA7146_I2C_AL (1<<2) +#define SAA7146_I2C_ERR (1<<1) +#define SAA7146_I2C_BUSY (1<<0) + +#define SAA7146_I2C_START (0x3) +#define SAA7146_I2C_CONT (0x2) +#define SAA7146_I2C_STOP (0x1) +#define SAA7146_I2C_NOP (0x0) + +#define SAA7146_I2C_BUS_BIT_RATE_6400 (0x500) +#define SAA7146_I2C_BUS_BIT_RATE_3200 (0x100) +#define SAA7146_I2C_BUS_BIT_RATE_480 (0x400) +#define SAA7146_I2C_BUS_BIT_RATE_320 (0x600) +#define SAA7146_I2C_BUS_BIT_RATE_240 (0x700) +#define SAA7146_I2C_BUS_BIT_RATE_120 (0x000) +#define SAA7146_I2C_BUS_BIT_RATE_80 (0x200) +#define SAA7146_I2C_BUS_BIT_RATE_60 (0x300) + +#endif + diff --git a/linux/drivers/media/common/saa7146_core.c b/linux/drivers/media/common/saa7146_core.c new file mode 100644 index 000000000..3c4717755 --- /dev/null +++ b/linux/drivers/media/common/saa7146_core.c @@ -0,0 +1,910 @@ +/* + saa7146.c - video4linux driver for saa7146-based video hardware + + Copyright (C) 1998-2002 Michael Hunold <michael@mihu.de> + + With ideas taken from: "device driver for philips saa7134 based TV cards" + (c) 2001,02 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + + 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 "saa7146.h" +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) + #define KBUILD_MODNAME "saa7146" +#endif + +/* global variables */ +struct list_head devices; +struct semaphore devices_lock; + +int saa7146_num = 0; +/* fixme */ +int debug = 255; + +#ifdef MODULE +MODULE_PARM(debug,"i"); +MODULE_PARM_DESC(debug, "debug level (default: 0)"); +#endif + +static void dump_registers(struct saa7146_dev* dev) +{ + int i = 0; + + INFO((" @ %li jiffies:\n",jiffies)); + for(i = 0; i <= 0x148; i+=4) { + printk("0x%03x: 0x%08x\n",i,saa7146_read(dev,i)); + } +} + +/********************************************************************************/ +/* common dma and page table functions */ + +void saa7146_dma_free(struct saa7146_dev *dev,struct saa7146_buf *buf) +{ + DEB_EE(("dev:%p, buf:%p\n",dev,buf)); + + if (in_interrupt()) + BUG(); + + videobuf_waiton(&buf->vb,0,0); + videobuf_dma_pci_unmap(dev->pci, &buf->vb.dma); + videobuf_dma_free(&buf->vb.dma); + buf->vb.state = STATE_NEEDS_INIT; +} + +#define SAA7146_PGTABLE_SIZE 4096 + +void saa7146_pgtable_free(struct pci_dev *pci, struct saa7146_pgtable *pt) +{ +//fm DEB_EE(("pci:%p, pt:%p\n",pci,pt)); + + if (NULL == pt->cpu) + return; + pci_free_consistent(pci, pt->size, pt->cpu, pt->dma); + pt->cpu = NULL; +} + +int saa7146_pgtable_alloc(struct pci_dev *pci, struct saa7146_pgtable *pt) +{ + u32 *cpu; + dma_addr_t dma_addr; + +//fm DEB_EE(("pci:%p, pt:%p\n",pci,pt)); + + cpu = pci_alloc_consistent(pci, SAA7146_PGTABLE_SIZE, &dma_addr); + if (NULL == cpu) { +//fm ERR(("pci_alloc_consistent() failed.")); + return -ENOMEM; + } + pt->size = SAA7146_PGTABLE_SIZE; + pt->cpu = cpu; + pt->dma = dma_addr; + + return 0; +} + +void saa7146_pgtable_build_single(struct pci_dev *pci, struct saa7146_pgtable *pt, struct scatterlist *list, int length ) +{ + u32 *ptr, fill; + int i,p; + +//fm DEB_EE(("pci:%p, pt:%p, sl:%p, len:%d\n",pci,pt,list,length)); + + /* if we have a user buffer, the first page may not be + aligned to a page boundary. */ + pt->offset = list->offset; + + ptr = pt->cpu; + for (i = 0; i < length; i++, list++) { + for (p = 0; p * 4096 < list->length; p++, ptr++) { + *ptr = sg_dma_address(list) - list->offset; + } + } + + + /* safety; fill the page table up with the last valid page */ + fill = *(ptr-1); + for(;i<1024;i++) { + *ptr++ = fill; + } +/* + ptr = pt->cpu; + for(j=0;j<60;j++) { + printk("ptr1 %d: 0x%08x\n",j,ptr[j]); + } +*/ +} + +/********************************************************************************/ +/* file operations */ + +static int video_open(struct inode *inode, struct file *file) +{ + unsigned int minor = minor(inode->i_rdev); + struct saa7146_dev *h = NULL, *dev = NULL; + struct list_head *list; + struct saa7146_fh *fh = NULL; + int result = 0; + + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + DEB_EE(("inode:%p, file:%p, minor:%d\n",inode,file,minor)); + + down(&devices_lock); + list_for_each(list,&devices) { + h = list_entry(list, struct saa7146_dev, item); + DEB_D(("trying: %p @ major %d,%d\n",h,h->video_dev.minor,h->vbi_dev.minor)); + if (h->video_dev.minor == minor) { + dev = h; + } + if (h->vbi_dev.minor == minor) { + type = V4L2_BUF_TYPE_VBI_CAPTURE; + dev = h; + } + } + DEB_D(("using: %p\n",dev)); + + if (NULL == dev) { + DEB_S(("no such video device.\n")); + result = -ENODEV; + goto out; + } + + /* check if an extension is registered */ + if( NULL == dev->ext ) { + DEB_S(("no extension registered for this device.\n")); + result = -ENODEV; + goto out; + } + + /* allocate per open data */ + fh = kmalloc(sizeof(*fh),GFP_KERNEL); + if (NULL == fh) { + DEB_S(("cannot allocate memory for per open data.\n")); + result = -ENOMEM; + goto out; + } + memset(fh,0,sizeof(*fh)); + + /* fixme: increase some usage counts */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) + dev->ext->inc_use(dev); + MOD_INC_USE_COUNT; +#else + if( 0 == try_module_get(dev->ext->module)) { + result = -EINVAL; + goto out; + } +#endif + file->private_data = fh; + fh->dev = dev; + fh->type = type; + + dev->current_hps_source = SAA7146_HPS_SOURCE_PORT_A; + dev->current_hps_sync = SAA7146_HPS_SYNC_PORT_A; + + saa7146_video_uops.open(dev,fh); + if( 0 != BOARD_CAN_DO_VBI(dev) ) { + saa7146_vbi_uops.open(dev,fh); + } + + result = 0; +out: + if( fh != 0 && result != 0 ) { + kfree(fh); + } + up(&devices_lock); + return result; +} + +static int video_release(struct inode *inode, struct file *file) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + + DEB_EE(("inode:%p, file:%p\n",inode,file)); + + down(&devices_lock); + + saa7146_video_uops.release(dev,fh,file); + if( 0 != BOARD_CAN_DO_VBI(dev) ) { + saa7146_vbi_uops.release(dev,fh,file); + } + + /* fixme: decrease some usage counts */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) + dev->ext->dec_use(dev); + MOD_DEC_USE_COUNT; +#else + module_put(dev->ext->module); +#endif + file->private_data = NULL; + kfree(fh); + + up(&devices_lock); + + return 0; +} + +static int video_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ +/* + DEB_EE(("inode:%p, file:%p, cmd:%d, arg:%li\n",inode, file, cmd, arg)); +*/ + return video_usercopy(inode, file, cmd, arg, video_do_ioctl); +} + +static int video_mmap(struct file *file, struct vm_area_struct * vma) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + struct videobuf_queue *q; + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: { + DEB_EE(("V4L2_BUF_TYPE_VIDEO_CAPTURE: file:%p, vma:%p\n",file, vma)); + q = &fh->video_q; + break; + } + case V4L2_BUF_TYPE_VBI_CAPTURE: { + DEB_EE(("V4L2_BUF_TYPE_VBI_CAPTURE: file:%p, vma:%p\n",file, vma)); + q = &fh->vbi_q; + break; + } + default: + BUG(); + return 0; + } + return videobuf_mmap_mapper(vma,q); +} + +static unsigned int video_poll(struct file *file, struct poll_table_struct *wait) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + struct videobuf_buffer *buf = NULL; + struct videobuf_queue *q; + + DEB_EE(("file:%p, poll:%p\n",file, wait)); + + if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) { + if( 0 == fh->vbi_q.streaming ) + return videobuf_poll_stream(file, &fh->vbi_q, wait); + q = &fh->vbi_q; + } else { + q = &fh->video_q; + } + + if (!list_empty(&q->stream)) + buf = list_entry(q->stream.next, struct videobuf_buffer, stream); + + if (!buf) { + return POLLERR; + } + + poll_wait(file, &buf->done, wait); + if (buf->state == STATE_DONE || buf->state == STATE_ERROR) { + return POLLIN|POLLRDNORM; + } + + return 0; +} + +static ssize_t video_read(struct file *file, char *data, size_t count, loff_t *ppos) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: { + DEB_EE(("V4L2_BUF_TYPE_VIDEO_CAPTURE: file:%p, data:%p, count:%d\n",file, data, count)); + return saa7146_video_uops.read(file,data,count,ppos); + } + case V4L2_BUF_TYPE_VBI_CAPTURE: { + DEB_EE(("V4L2_BUF_TYPE_VBI_CAPTURE: file:%p, data:%p, count:%d\n",file, data, count)); + return saa7146_vbi_uops.read(file,data,count,ppos); + } + break; + default: + BUG(); + return 0; + } +} + +static 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_ioctl, + llseek: no_llseek, +}; + +/********************************************************************************/ +/* interrupt handler */ + +static void interrupt_hw(int irq, void *dev_id, struct pt_regs *regs) +{ + struct saa7146_dev *dev = (struct saa7146_dev*)dev_id; + u32 isr = 0; + + /* read out the interrupt status register */ + isr = saa7146_read(dev, ISR); + + /* is this our interrupt? */ + if ( 0 == isr ) { + /* nope, some other device */ + return; + } + + saa7146_write(dev, ISR, isr); + + if( 0 != (dev->ext->irq_mask & isr )) { + if( 0 != dev->ext->irq_func ) { + dev->ext->irq_func(dev, &isr); + } + isr &= ~dev->ext->irq_mask; + } + + if (0 != (isr & (MASK_27))) { + DEB_INT(("irq: RPS0.\n")); + saa7146_video_uops.irq_done(dev,isr); + isr &= ~MASK_27; + } + if (0 != (isr & (MASK_28))) { + u32 mc2 = saa7146_read(dev, MC2); + isr &= ~MASK_28; + if( 0 != (mc2 & MASK_15)) { + DEB_INT(("irq: RPS1 vbi workaround.\n")); + wake_up(&dev->vbi_wq); + saa7146_write(dev,MC2, MASK_31); + return; + } + DEB_INT(("irq: RPS1.\n")); + saa7146_vbi_uops.irq_done(dev,isr); + } + if( 0 != isr ) { + ERR(("warning: interrupt enabled, but not handled properly. (0x%08x)\n",isr)); + } +} + +/********************************************************************************/ +/* common buffer functions */ + +int saa7146_buffer_queue(struct saa7146_dev *dev, + struct saa7146_dmaqueue *q, + struct saa7146_buf *buf) +{ +#if DEBUG_SPINLOCKS + BUG_ON(!spin_is_locked(&dev->slock)); +#endif + DEB_EE(("dev:%p, dmaq:%p, buf:%p\n", dev, q, buf)); + + if( NULL == q ) { + ERR(("internal error: fatal NULL pointer for q.\n")); + return 0; + } + + if (NULL == q->curr) { + q->curr = buf; + DEB_D(("immediately activating buffer %p\n", buf)); + buf->activate(dev,buf,NULL); + } else { + list_add_tail(&buf->vb.queue,&q->queue); + buf->vb.state = STATE_QUEUED; + DEB_D(("adding buffer %p to queue. (active buffer present)\n", buf)); + } + return 0; +} + +void saa7146_buffer_finish(struct saa7146_dev *dev, + struct saa7146_dmaqueue *q, + int state) +{ +#if DEBUG_SPINLOCKS + BUG_ON(!spin_is_locked(&dev->slock)); +#endif + if( NULL == q->curr ) { + ERR(("internal error: fatal NULL pointer for q->curr.\n")); + return; + } + + DEB_EE(("dev:%p, dmaq:%p, state:%d\n", dev, q, state)); + + /* finish current buffer */ + q->curr->vb.state = state; + do_gettimeofday(&q->curr->vb.ts); + wake_up(&q->curr->vb.done); + + q->curr = NULL; +} + +void saa7146_buffer_next(struct saa7146_dev *dev, + struct saa7146_dmaqueue *q, int vbi) +{ + struct saa7146_buf *buf,*next = NULL; + + if( NULL == q ) { + ERR(("internal error: fatal NULL pointer for q.\n")); + return; + } + + DEB_EE(("dev:%p, dmaq:%p, vbi:%d\n", dev, q, vbi)); + +#if DEBUG_SPINLOCKS + BUG_ON(!spin_is_locked(&dev->slock)); +#endif + if (!list_empty(&q->queue)) { + /* activate next one from queue */ + buf = list_entry(q->queue.next,struct saa7146_buf,vb.queue); + list_del(&buf->vb.queue); + if (!list_empty(&q->queue)) + next = list_entry(q->queue.next,struct saa7146_buf, vb.queue); + q->curr = buf; + DEB_D(("next buffer: buf:%p, prev:%p, next:%p\n", buf, q->queue.prev,q->queue.next)); + buf->activate(dev,buf,next); + } else { + DEB_D(("no next buffer. stopping.\n")); + if( 0 != vbi ) { + /* turn off video-dma3 */ + saa7146_write(dev,MC1, MASK_20); + } else { + /* nothing to do -- just prevent next video-dma1 transfer + by lowering the protection address */ + + // fixme: fix this for vflip != 0 + + saa7146_write(dev, PROT_ADDR1, 0); + /* write the address of the rps-program */ + saa7146_write(dev, RPS_ADDR0, virt_to_bus(&dev->rps0[ 0])); + /* turn on rps */ + saa7146_write(dev, MC1, (MASK_12 | MASK_28)); + } + del_timer(&q->timeout); + } +} + +void saa7146_buffer_timeout(unsigned long data) +{ + struct saa7146_dmaqueue *q = (struct saa7146_dmaqueue*)data; + struct saa7146_dev *dev = q->dev; + unsigned long flags; + + DEB_EE(("dev:%p, dmaq:%p\n", dev, q)); + + spin_lock_irqsave(&dev->slock,flags); + if (q->curr) { + DEB_D(("timeout on %p\n", q->curr)); + saa7146_buffer_finish(dev,q,STATE_ERROR); + } + + /* we don't restart the transfer here like other drivers do. when + a streaming capture is disabled, the timeout function will be + called for the current buffer. if we activate the next buffer now, + we mess up our capture logic. if a timeout occurs on another buffer, + then something is seriously broken before, so no need to buffer the + next capture IMHO... */ +/* + saa7146_buffer_next(dev,q); +*/ + spin_unlock_irqrestore(&dev->slock,flags); +} + +/*********************************************************************************/ +/* extension handling functions */ + +static struct video_device device_template = +{ + .hardware = VID_HARDWARE_SAA7146, + .fops = &video_fops, + .minor = -1, +}; + +int saa7146_register_extension(struct saa7146_extension* ext) +{ + struct saa7146_dev *dev = NULL; + struct list_head *list = NULL;; + + DEB_EE(("ext:%p\n",ext)); + + down(&devices_lock); + list_for_each(list,&devices) { + int i = 0; + int found = 0; + dev = list_entry(list, struct saa7146_dev, item); + + /* check if already handled by extension */ + if( 0 != dev->ext ) { + continue; + } + + dev->vbi_dev.minor = -1; + dev->video_dev.minor = -1; + + DEB_S(("Trying device %p...\n",dev)); + + /* first check the subvendor and subdevice ids */ + for(i = 0;;i++) { + if( 0xffff == ext->devices[i].subvendor + && 0xffff == ext->devices[i].subdevice ) { + break; + } + if( ext->devices[i].subvendor == dev->pci->subsystem_vendor + && ext->devices[i].subdevice == dev->pci->subsystem_device ) { + found = 1; + break; + } + } + if( 0 == found ) { + DEB_S(("extension %p does not handle this device. skipping.\n",ext)); + continue; + } + + if( 0 != ext->preinit(dev) ) { + DEB_S(("ext->preinit() failed for %p. skipping.\n",dev)); + continue; + } + + if( 0 != ext->use_kernel_i2c ) { + /* register new i2c-bus */ + if(i2c_add_adapter(dev->i2c_adapter) < 0) { + DEB_S(("cannot register i2c-device. skipping.\n")); + continue; + } + } + + if( 0 != ext->probe(dev, dev->pci->subsystem_vendor, dev->pci->subsystem_device) ) { + DEB_D(("ext->probe() failed for %p. skipping device.\n",dev)); + if( 0 != ext->use_kernel_i2c ) + i2c_del_adapter(dev->i2c_adapter); + continue; + } + + dev->ext = ext; + + if( 0 != ext->attach(dev) ) { + DEB_D(("ext->attach() failed for %p. skipping device.\n",dev)); + if( 0 != ext->use_kernel_i2c ) + i2c_del_adapter(dev->i2c_adapter); + dev->ext = NULL; + continue; + } + + /* v4l2 initialization stuff */ + dev->video_dev = device_template; + strncpy(dev->video_dev.name, ext->name, 32); + dev->video_dev.priv = dev; + + // fixme: -1 should be an insmod parameter *for the extension* (like "video_nr"); + if (video_register_device(&dev->video_dev,VFL_TYPE_GRABBER,-1) < 0) { + ERR(("cannot register capture v4l2 device. skipping.\n")); + /* in this case the extension has probably + already "done something", but the v4l2-device + could not be registered. this is not as bad as + it looks - you cannot access the device. + we simply call the 'release' function. */ + ext->detach(dev); + if( 0 != ext->use_kernel_i2c ) + i2c_del_adapter(dev->i2c_adapter); + dev->ext = NULL; + continue; + } + INFO(("%s: registered device video%d [v4l2],%d\n", dev->name,dev->video_dev.minor & 0x1f,dev->vbi_dev.minor & 0x1f)); + + /* initialization stuff (vbi) (only for revision > 0 and for extensions which want it)*/ + if( 0 != BOARD_CAN_DO_VBI(dev)) { + dev->vbi_dev = device_template; + strncpy(dev->vbi_dev.name, ext->name, 32); + dev->vbi_dev.priv = dev; + if (video_register_device(&dev->vbi_dev,VFL_TYPE_VBI,-1) < 0) { + ERR(("cannot register vbi v4l2 device. skipping.\n")); + } + INFO(("%s: registered device vbi%d [v4l2]\n", dev->name,dev->vbi_dev.minor & 0x1f)); + } + } + up(&devices_lock); + + return 0; +} + +int saa7146_unregister_extension(struct saa7146_extension* ext) +{ + struct saa7146_dev *dev = NULL; + struct list_head *list = NULL;; + + DEB_EE(("ext:%p\n",ext)); + + down(&devices_lock); + list_for_each(list,&devices) { + dev = list_entry(list, struct saa7146_dev, item); + + /* check if handled by this extension */ + if( ext != dev->ext ) { + continue; + } + + if( 0 != ext->detach(dev) ) { + DEB_D(("ext->detach() failed. ignoring.\n")); + } + + if( 0 != ext->use_kernel_i2c ) + i2c_del_adapter(dev->i2c_adapter); + + video_unregister_device(&dev->video_dev); + if( 0 != BOARD_CAN_DO_VBI(dev)) { + video_unregister_device(&dev->vbi_dev); + } + + dev->ext = NULL; + + /* make sure to erase the minor number... */ + dev->video_dev = device_template; + +// dump_registers(dev); + + } + up(&devices_lock); + + return 0; +} + +/*********************************************************************************/ +/* configuration-functions */ + +static int config_a_device(struct pci_dev *pci) +{ + unsigned long adr = 0, len = 0; + struct saa7146_dev* dev = (struct saa7146_dev*)kmalloc(sizeof(struct saa7146_dev),GFP_KERNEL); + + if( NULL == dev) { + ERR(("out of memory.\n")); + return -ENOMEM; + } + /* clear out mem for sure */ + memset(dev, 0x0, sizeof(struct saa7146_dev)); + + DEB_EE(("pci:%p\n",pci)); + + if (pci_enable_device(pci)) { + ERR(("pci_enable_device() failed.\n")); + kfree(dev); + return -EIO; + } + + /* enable bus-mastering */ + pci_set_master(pci); + + dev->pci = pci; + /* get chip-revision; this is needed to enable bug-fixes */ + if( 0 > pci_read_config_dword(dev->pci, PCI_CLASS_REVISION, &dev->revision)) { + ERR(("pci_read_config_dword() failed.\n")); + kfree(dev); + return -ENODEV; + } + dev->revision &= 0xf; + + /* remap the memory from virtual to physical adress */ + adr = pci_resource_start(pci,0); + len = pci_resource_len(pci,0); + + if (!request_mem_region(pci_resource_start(pci,0), pci_resource_len(pci,0), "saa7146")) { + ERR(("request_mem_region() failed.\n")); + kfree(dev); + return -ENODEV; + } + + dev->mem=ioremap(adr,len); + if ( 0 == dev->mem ) { + ERR(("ioremap() failed.\n")); + release_mem_region(adr,len); + kfree(dev); + return -ENODEV; + } + + /* we don't do a master reset here anymore, it screws up + some boards that don't have an i2c-eeprom for configuration + values */ +/* + saa7146_write(dev, MC1, MASK_31); +*/ + + /* disable alle irqs, clear irq-mask */ + saa7146_write(dev, ISR, 0xffffffff); + + /* shut down all dma transfers */ + saa7146_write(dev, MC1, 0x00ff0000); + + /* clear out any rps-signals pending */ + saa7146_write(dev, MC2, 0xf8000000); + + /* request an interrupt for the saa7146 */ + if( 0 != request_irq(dev->pci->irq, interrupt_hw, SA_SHIRQ | SA_INTERRUPT, dev->name, (void *)dev)) { + ERR(("request_irq() failed.\n")); + iounmap(dev->mem); + release_mem_region(adr,len); + kfree(dev); + return -EINVAL; + } + + /* get memory for various stuff */ + dev->rps0 = (u32*)kmalloc(SAA7146_RPS_MEM, GFP_KERNEL); + if( NULL == dev->rps0 ) + goto kmalloc_error_1; + memset(dev->rps0, 0x0, SAA7146_RPS_MEM); + + dev->rps1 = (u32*)kmalloc(SAA7146_RPS_MEM, GFP_KERNEL); + if( NULL == dev->rps1 ) + goto kmalloc_error_2; + memset(dev->rps1, 0x0, SAA7146_RPS_MEM); + + dev->clipping = (u32*)kmalloc(SAA7146_CLIPPING_MEM, GFP_KERNEL); + if( NULL == dev->clipping ) + goto kmalloc_error_3; + memset(dev->clipping, 0x0, SAA7146_CLIPPING_MEM); + + dev->i2c_adapter = kmalloc(sizeof(struct i2c_adapter), GFP_KERNEL); + if ( NULL == dev->i2c_adapter ) + goto kmalloc_error_4; + memset(dev->i2c_adapter, 0x0, sizeof(struct i2c_adapter)); + + dev->i2c_mem = (u32*)kmalloc(SAA7146_I2C_MEM, GFP_KERNEL); + if( NULL == dev->i2c_mem ) + goto kmalloc_error_5; + memset(dev->i2c_mem, 0x00, SAA7146_I2C_MEM); + + /* i2c-adapter preparations */ + saa7146_i2c_adapter_prepare(dev); + + /* enable i2c-port pins, video-port-pins */ + saa7146_write(dev, MC1, (MASK_08 | MASK_24 | MASK_10 | MASK_26)); + + /* the rest + print status message */ + + /* create a nice device name */ + sprintf(&dev->name[0], "saa7146 (%d)",saa7146_num); + + INFO(("found saa7146 @ mem 0x%08x (revision %d, irq %d) (0x%04x,0x%04x).\n", (unsigned int)dev->mem, dev->revision,dev->pci->irq,dev->pci->subsystem_vendor,dev->pci->subsystem_device)); + dev->ext = NULL; + + pci_set_drvdata(pci,dev); + + init_MUTEX(&dev->lock); + dev->slock = SPIN_LOCK_UNLOCKED; + + init_MUTEX(&dev->i2c_lock); + + INIT_LIST_HEAD(&dev->item); + list_add_tail(&dev->item,&devices); + saa7146_num++; + + saa7146_video_uops.init(dev); + saa7146_vbi_uops.init(dev); + + dev->module = THIS_MODULE; + + return 0; + +kmalloc_error_5: + kfree( dev->i2c_adapter ); +kmalloc_error_4: + kfree( dev->clipping ); +kmalloc_error_3: + kfree( dev->rps1 ); +kmalloc_error_2: + kfree( dev->rps0 ); +kmalloc_error_1: + ERR(("kmalloc() failed.\n")); + iounmap(dev->mem); + release_mem_region(adr,len); + kfree(dev); + + return -ENOMEM; +} + +static void unconfig_a_device(struct saa7146_dev* dev) +{ + DEB_EE(("dev:%p\n",dev)); + + /* shut down all video dma transfers */ + saa7146_write(dev, MC1, 0x00ff0000); + + /* disable all irqs, release irq-routine */ + saa7146_write(dev, IER, 0); + saa7146_write(dev, ISR, 0xffffffff); + + free_irq(dev->pci->irq, (void *)dev); + + /* free kernel memory */ + kfree(dev->rps0 ); + kfree(dev->rps1 ); + kfree(dev->clipping); + kfree(dev->i2c_mem); + kfree(dev->i2c_adapter); + + iounmap(dev->mem); + release_mem_region(pci_resource_start(dev->pci,0), pci_resource_len(dev->pci,0)); + + list_del(&dev->item); + kfree(dev); + + saa7146_num--; +} + +#ifdef MODULE +int __devinit saa7146_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + if( 0 != config_a_device(pdev)) { + return -EINVAL; + } + + return 0; +} + +void __devexit saa7146_remove_one(struct pci_dev *pdev) +{ + struct saa7146_dev* dev = (struct saa7146_dev*)pci_get_drvdata(pdev); + + unconfig_a_device(dev); +} + +struct pci_device_id saa7146_pci_tbl[] __devinitdata = { + { PCI_VENDOR_ID_PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7146, + PCI_ANY_ID, PCI_ANY_ID, }, + { 0,}, +}; + +MODULE_DEVICE_TABLE(pci, saa7146_pci_tbl); + +struct pci_driver saa7146_driver = { + name: "saa7146", + id_table: saa7146_pci_tbl, + probe: saa7146_init_one, + remove: saa7146_remove_one, +}; + +int __init saa7146_init_module(void) +{ + INIT_LIST_HEAD(&devices); + init_MUTEX(&devices_lock); + return pci_module_init(&saa7146_driver); +} + +void __exit saa7146_cleanup_module(void) +{ + pci_unregister_driver(&saa7146_driver); +} + +module_init(saa7146_init_module); +module_exit(saa7146_cleanup_module); + +EXPORT_SYMBOL_GPL(saa7146_register_extension); +EXPORT_SYMBOL_GPL(saa7146_unregister_extension); + +/* misc functions used by extension modules */ +EXPORT_SYMBOL_GPL(saa7146_set_hps_source_and_sync); +EXPORT_SYMBOL_GPL(saa7146_pgtable_alloc); +EXPORT_SYMBOL_GPL(saa7146_pgtable_free); +EXPORT_SYMBOL_GPL(saa7146_pgtable_build_single); +EXPORT_SYMBOL_GPL(saa7146_i2c_transfer); + +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_DESCRIPTION("video4linux driver for saa7146-based video hardware"); +MODULE_LICENSE("GPL"); +#endif + diff --git a/linux/drivers/media/common/saa7146_hlp.c b/linux/drivers/media/common/saa7146_hlp.c new file mode 100644 index 000000000..89959ae93 --- /dev/null +++ b/linux/drivers/media/common/saa7146_hlp.c @@ -0,0 +1,974 @@ +#include "saa7146.h" +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) + #define KBUILD_MODNAME "saa7146" +#endif + +#define my_min(type,x,y) \ + ({ type __x = (x), __y = (y); __x < __y ? __x: __y; }) +#define my_max(type,x,y) \ + ({ type __x = (x), __y = (y); __x > __y ? __x: __y; }) + +void calculate_output_format_register(struct saa7146_dev* saa, u32 palette, u32* clip_format) +{ + /* clear out the necessary bits */ + *clip_format &= 0x0000ffff; + /* set these bits new */ + *clip_format |= (( ((palette&0xf00)>>8) << 30) | ((palette&0x00f) << 24) | (((palette&0x0f0)>>4) << 16)); +} + +void calculate_bcs_ctrl_register(struct saa7146_dev *dev, int brightness, int contrast, int colour, u32 *bcs_ctrl) +{ + *bcs_ctrl = ((brightness << 24) | (contrast << 16) | (colour << 0)); +} + +void calculate_hps_source_and_sync(struct saa7146_dev *dev, int source, int sync, u32* hps_ctrl) +{ + *hps_ctrl &= ~(MASK_30 | MASK_31 | MASK_28); + *hps_ctrl |= (source << 30) | (sync << 28); +} + +void calculate_hxo_and_hyo(struct saa7146_dev *dev, u32* hps_h_scale, u32* hps_ctrl) +{ + int hyo = 0, hxo = 0; + + hyo = dev->standard->v_offset; + hxo = dev->standard->h_offset; + + *hps_h_scale &= ~(MASK_B0 | 0xf00); + *hps_h_scale |= (hxo << 0); + + *hps_ctrl &= ~(MASK_W0 | MASK_B2); + *hps_ctrl |= (hyo << 12); +} + +/* helper functions for the calculation of the horizontal- and vertical + scaling registers, clip-format-register etc ... + these functions take pointers to the (most-likely read-out + original-values) and manipulate them according to the requested + changes. +*/ + +/* hps_coeff used for CXY and CXUV; scale 1/1 -> scale 1/64 */ +struct { + u16 hps_coeff; + u16 weight_sum; +} hps_h_coeff_tab [] = { + {0x00, 2}, {0x02, 4}, {0x00, 4}, {0x06, 8}, {0x02, 8}, + {0x08, 8}, {0x00, 8}, {0x1E, 16}, {0x0E, 8}, {0x26, 8}, + {0x06, 8}, {0x42, 8}, {0x02, 8}, {0x80, 8}, {0x00, 8}, + {0xFE, 16}, {0xFE, 8}, {0x7E, 8}, {0x7E, 8}, {0x3E, 8}, + {0x3E, 8}, {0x1E, 8}, {0x1E, 8}, {0x0E, 8}, {0x0E, 8}, + {0x06, 8}, {0x06, 8}, {0x02, 8}, {0x02, 8}, {0x00, 8}, + {0x00, 8}, {0xFE, 16}, {0xFE, 8}, {0xFE, 8}, {0xFE, 8}, + {0xFE, 8}, {0xFE, 8}, {0xFE, 8}, {0xFE, 8}, {0xFE, 8}, + {0xFE, 8}, {0xFE, 8}, {0xFE, 8}, {0xFE, 8}, {0xFE, 8}, + {0xFE, 8}, {0xFE, 8}, {0xFE, 8}, {0xFE, 8}, {0x7E, 8}, + {0x7E, 8}, {0x3E, 8}, {0x3E, 8}, {0x1E, 8}, {0x1E, 8}, + {0x0E, 8}, {0x0E, 8}, {0x06, 8}, {0x06, 8}, {0x02, 8}, + {0x02, 8}, {0x00, 8}, {0x00, 8}, {0xFE, 16} +}; + +/* table of attenuation values for horizontal scaling */ +u8 h_attenuation[] = { 1, 2, 4, 8, 2, 4, 8, 16, 0}; + +/* calculate horizontal scale registers */ +int calculate_h_scale_registers(struct saa7146_dev *dev, + int in_x, int out_x, int flip_lr, + u32* hps_ctrl, u32* hps_v_gain, u32* hps_h_prescale, u32* hps_h_scale) +{ + /* horizontal prescaler */ + u32 dcgx = 0, xpsc = 0, xacm = 0, cxy = 0, cxuv = 0; + /* horizontal scaler */ + u32 xim = 0, xp = 0, xsci =0; + /* vertical scale & gain */ + u32 pfuv = 0; + + /* helper variables */ + u32 h_atten = 0, i = 0; + + if ( 0 == out_x ) { + return -EINVAL; + } + + /* mask out vanity-bit */ + *hps_ctrl &= ~MASK_29; + + /* calculate prescale-(xspc)-value: [n .. 1/2) : 1 + [1/2 .. 1/3) : 2 + [1/3 .. 1/4) : 3 + ... */ + if (in_x > out_x) { + xpsc = in_x / out_x; + } + else { + /* zooming */ + xpsc = 1; + } + + /* if flip_lr-bit is set, number of pixels after + horizontal prescaling must be < 384 */ + if ( 0 != flip_lr ) { + + /* set vanity bit */ + *hps_ctrl |= MASK_29; + + while (in_x / xpsc >= 384 ) + xpsc++; + } + /* if zooming is wanted, number of pixels after + horizontal prescaling must be < 768 */ + else { + while ( in_x / xpsc >= 768 ) + xpsc++; + } + + /* maximum prescale is 64 (p.69) */ + if ( xpsc > 64 ) + xpsc = 64; + + /* keep xacm clear*/ + xacm = 0; + + /* set horizontal filter parameters (CXY = CXUV) */ + cxy = hps_h_coeff_tab[( (xpsc - 1) < 63 ? (xpsc - 1) : 63 )].hps_coeff; + cxuv = cxy; + + /* calculate and set horizontal fine scale (xsci) */ + + /* bypass the horizontal scaler ? */ + if ( (in_x == out_x) && ( 1 == xpsc ) ) + xsci = 0x400; + else + xsci = ( (1024 * in_x) / (out_x * xpsc) ) + xpsc; + + /* set start phase for horizontal fine scale (xp) to 0 */ + xp = 0; + + /* set xim, if we bypass the horizontal scaler */ + if ( 0x400 == xsci ) + xim = 1; + else + xim = 0; + + /* if the prescaler is bypassed, enable horizontal + accumulation mode (xacm) and clear dcgx */ + if( 1 == xpsc ) { + xacm = 1; + dcgx = 0; + } + else { + xacm = 0; + /* get best match in the table of attenuations + for horizontal scaling */ + h_atten = hps_h_coeff_tab[( (xpsc - 1) < 63 ? (xpsc - 1) : 63 )].weight_sum; + + for (i = 0; h_attenuation[i] != 0; i++) { + if (h_attenuation[i] >= h_atten) + break; + } + + dcgx = i; + } + + /* the horizontal scaling increment controls the UV filter + to reduce the bandwith to improve the display quality, + so set it ... */ + if ( xsci == 0x400) + pfuv = 0x00; + else if ( xsci < 0x600) + pfuv = 0x01; + else if ( xsci < 0x680) + pfuv = 0x11; + else if ( xsci < 0x700) + pfuv = 0x22; + else + pfuv = 0x33; + + + *hps_v_gain &= MASK_W0|MASK_B2; + *hps_v_gain |= (pfuv << 24); + + *hps_h_scale &= ~(MASK_W1 | 0xf000); + *hps_h_scale |= (xim << 31) | (xp << 24) | (xsci << 12); + + *hps_h_prescale |= (dcgx << 27) | ((xpsc-1) << 18) | (xacm << 17) | (cxy << 8) | (cxuv << 0); + + return 0; +} + +struct { + u16 hps_coeff; + u16 weight_sum; +} hps_v_coeff_tab [] = { + {0x0100, 2}, {0x0102, 4}, {0x0300, 4}, {0x0106, 8}, {0x0502, 8}, + {0x0708, 8}, {0x0F00, 8}, {0x011E, 16}, {0x110E, 16}, {0x1926, 16}, + {0x3906, 16}, {0x3D42, 16}, {0x7D02, 16}, {0x7F80, 16}, {0xFF00, 16}, + {0x01FE, 32}, {0x01FE, 32}, {0x817E, 32}, {0x817E, 32}, {0xC13E, 32}, + {0xC13E, 32}, {0xE11E, 32}, {0xE11E, 32}, {0xF10E, 32}, {0xF10E, 32}, + {0xF906, 32}, {0xF906, 32}, {0xFD02, 32}, {0xFD02, 32}, {0xFF00, 32}, + {0xFF00, 32}, {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, + {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, + {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, + {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, {0x01FE, 64}, {0x817E, 64}, + {0x817E, 64}, {0xC13E, 64}, {0xC13E, 64}, {0xE11E, 64}, {0xE11E, 64}, + {0xF10E, 64}, {0xF10E, 64}, {0xF906, 64}, {0xF906, 64}, {0xFD02, 64}, + {0xFD02, 64}, {0xFF00, 64}, {0xFF00, 64}, {0x01FE, 128} +}; + +/* table of attenuation values for vertical scaling */ +u16 v_attenuation[] = { 2, 4, 8, 16, 32, 64, 128, 256, 0}; + +/* calculate vertical scale registers */ +int calculate_v_scale_registers(struct saa7146_dev *dev, enum v4l2_field field, + int in_y, int out_y, u32* hps_v_scale, u32* hps_v_gain) +{ + int lpi = 0; + + /* vertical scaling */ + u32 yacm = 0, ysci = 0, yacl = 0, ypo = 0, ype = 0; + /* vertical scale & gain */ + u32 dcgy = 0, cya_cyb = 0; + + /* helper variables */ + u32 v_atten = 0, i = 0; + + /* error, if vertical zooming */ + if ( in_y < out_y ) { + return -EINVAL; + } + + /* linear phase interpolation may be used + if scaling is between 1 and 1/2 (both fields used) + or scaling is between 1/2 and 1/4 (if only one field is used) */ + + if (V4L2_FIELD_HAS_BOTH(field)) { + if( 2*out_y >= in_y) { + lpi = 1; + } + } else if (field == V4L2_FIELD_TOP || field == V4L2_FIELD_BOTTOM) { + if( 4*out_y >= in_y ) { + lpi = 1; + } + out_y *= 2; + } + if( 0 != lpi ) { + + yacm = 0; + yacl = 0; + cya_cyb = 0x00ff; + + /* calculate scaling increment */ + if ( in_y > out_y ) + ysci = ((1024 * in_y) / (out_y + 1)) - 1024; + else + ysci = 0; + + dcgy = 0; + + /* calculate ype and ypo */ + ype = ysci / 16; + ypo = ype + (ysci / 64); + + } + else { + yacm = 1; + + /* calculate scaling increment */ + ysci = (((10 * 1024 * (in_y - out_y - 1)) / in_y) + 9) / 10; + + /* calculate ype and ypo */ + ypo = ype = ((ysci + 15) / 16); + + /* the sequence length interval (yacl) has to be set according + to the prescale value, e.g. [n .. 1/2) : 0 + [1/2 .. 1/3) : 1 + [1/3 .. 1/4) : 2 + ... */ + if ( ysci < 512) { + yacl = 0; + } + else { + yacl = ( ysci / (1024 - ysci) ); + } + + /* get filter coefficients for cya, cyb from table hps_v_coeff_tab */ + cya_cyb = hps_v_coeff_tab[ (yacl < 63 ? yacl : 63 ) ].hps_coeff; + + /* get best match in the table of attenuations for vertical scaling */ + v_atten = hps_v_coeff_tab[ (yacl < 63 ? yacl : 63 ) ].weight_sum; + + for (i = 0; v_attenuation[i] != 0; i++) { + if (v_attenuation[i] >= v_atten) + break; + } + + dcgy = i; + } + + /* ypo and ype swapped in spec ? */ + *hps_v_scale |= (yacm << 31) | (ysci << 21) | (yacl << 15) | (ypo << 8 ) | (ype << 1); + + *hps_v_gain &= ~(MASK_W0|MASK_B2); + *hps_v_gain |= (dcgy << 16) | (cya_cyb << 0); + + return 0; +} + +/* simple bubble-sort algorithm with duplicate elimination */ +int sort_and_eliminate(u32* values, int* count) +{ + int low = 0, high = 0, top = 0, temp = 0; + int cur = 0, next = 0; + + /* sanity checks */ + if( (0 > *count) || (NULL == values) ) { + return -EINVAL; + } + + /* bubble sort the first ´count´ items of the array ´values´ */ + for( top = *count; top > 0; top--) { + for( low = 0, high = 1; high < top; low++, high++) { + if( values[low] > values[high] ) { + temp = values[low]; + values[low] = values[high]; + values[high] = temp; + } + } + } + + /* remove duplicate items */ + for( cur = 0, next = 1; next < *count; next++) { + if( values[cur] != values[next]) + values[++cur] = values[next]; + } + + *count = cur + 1; + + return 0; +} + +void calculate_clipping_registers_rect(struct saa7146_dev *dev, struct saa7146_fh *fh, + struct saa7146_video_dma *vdma2, u32* clip_format, u32* arbtr_ctrl, enum v4l2_field field) +{ + int td_flip = dev->vflip; + int width = fh->ov.win.w.width; + int height = fh->ov.win.w.height; + int clipcount = fh->ov.nclips; + u32 *clipping = dev->clipping; + + u32 line_list[32]; + u32 pixel_list[32]; + int numdwords = 0; + + int i = 0, j = 0; + int cnt_line = 0, cnt_pixel = 0; + + int x[32], y[32], w[32], h[32]; + + /* clear out memory */ + memset(&line_list[0], 0x00, sizeof(u32)*32); + memset(&pixel_list[0], 0x00, sizeof(u32)*32); + memset(clipping, 0x00, SAA7146_CLIPPING_MEM); + + + /* fill the line and pixel-lists */ + for(i = 0; i < clipcount; i++) { + int l = 0, r = 0, t = 0, b = 0; + + x[i] = fh->ov.clips[i].c.left; + y[i] = fh->ov.clips[i].c.top; + w[i] = fh->ov.clips[i].c.width; + h[i] = fh->ov.clips[i].c.height; + + if( w[i] < 0) { + x[i] += w[i]; w[i] = -w[i]; + } + if( h[i] < 0) { + y[i] += h[i]; h[i] = -h[i]; + } + if( x[i] < 0) { + w[i] += x[i]; x[i] = 0; + } + if( y[i] < 0) { + h[i] += y[i]; y[i] = 0; + } + if( 0 != td_flip ) { + y[i] = height - y[i] - h[i]; + } + + l = x[i]; + r = x[i]+w[i]; + t = y[i]; + b = y[i]+h[i]; + + /* insert left/right coordinates */ + pixel_list[ 2*i ] = my_min(int, l, width); + pixel_list[(2*i)+1] = my_min(int, r, width); + /* insert top/bottom coordinates */ + line_list[ 2*i ] = my_min(int, t, height); + line_list[(2*i)+1] = my_min(int, b, height); + } + + /* sort and eliminate lists */ + cnt_line = cnt_pixel = 2*clipcount; + sort_and_eliminate( &pixel_list[0], &cnt_pixel ); + sort_and_eliminate( &line_list[0], &cnt_line ); + + /* calculate the number of used u32s */ + numdwords = my_max(int, (cnt_line+1), (cnt_pixel+1))*2; + numdwords = my_max(int, 4, numdwords); + numdwords = my_min(int, 64, numdwords); + + /* fill up cliptable */ + for(i = 0; i < cnt_pixel; i++) { + clipping[2*i] |= (pixel_list[i] << 16); + } + for(i = 0; i < cnt_line; i++) { + clipping[(2*i)+1] |= (line_list[i] << 16); + } + + /* fill up cliptable with the display infos */ + for(j = 0; j < clipcount; j++) { + + for(i = 0; i < cnt_pixel; i++) { + + if( x[j] < 0) + x[j] = 0; + + if( pixel_list[i] < (x[j] + w[j])) { + + if ( pixel_list[i] >= x[j] ) { + clipping[2*i] |= (1 << j); + } + } + } + for(i = 0; i < cnt_line; i++) { + + if( y[j] < 0) + y[j] = 0; + + if( line_list[i] < (y[j] + h[j]) ) { + + if( line_list[i] >= y[j] ) { + clipping[(2*i)+1] |= (1 << j); + } + } + } + } + + /* adjust arbitration control register */ + *arbtr_ctrl &= 0xffff00ff; + *arbtr_ctrl |= 0x00001c00; + + vdma2->base_even = virt_to_bus(clipping); + vdma2->base_odd = virt_to_bus(clipping); + vdma2->prot_addr = virt_to_bus(clipping)+((sizeof(u32))*(numdwords)); + vdma2->base_page = 0x04; + vdma2->pitch = 0x00; + vdma2->num_line_byte = (0 << 16 | (sizeof(u32))*(numdwords-1) ); + + /* set clipping-mode. this depends on the field(s) used */ + *clip_format &= 0xfffffff7; + if (V4L2_FIELD_HAS_BOTH(field)) { + *clip_format |= 0x00000008; + } else if (field == V4L2_FIELD_TOP) { + *clip_format |= 0x00000000; + } else if (field == V4L2_FIELD_BOTTOM) { + *clip_format |= 0x00000000; + } +} + +/* disable clipping */ +void saa7146_disable_clipping(struct saa7146_dev *dev) +{ + u32 clip_format = saa7146_read(dev, CLIP_FORMAT_CTRL); + + /* mask out relevant bits (=lower word)*/ + clip_format &= MASK_W1; + + /* upload clipping-registers*/ + saa7146_write(dev, CLIP_FORMAT_CTRL,clip_format); + saa7146_write(dev, MC2, (MASK_05 | MASK_21)); + + /* disable video dma2 */ + saa7146_write(dev, MC1, (MASK_21)); +} + +void saa7146_set_clipping_rect(struct saa7146_dev *dev, struct saa7146_fh *fh) +{ + enum v4l2_field field = fh->ov.win.field; + int clipcount = fh->ov.nclips; + + struct saa7146_video_dma vdma2; + + u32 clip_format = saa7146_read(dev, CLIP_FORMAT_CTRL); + u32 arbtr_ctrl = saa7146_read(dev, PCI_BT_V1); + + // fixme: is this used at all? SAA7146_CLIPPING_RECT_INVERTED; + u32 type = SAA7146_CLIPPING_RECT; + + /* check clipcount, disable clipping if clipcount == 0*/ + if( clipcount == 0 ) { + saa7146_disable_clipping(dev); + return; + } + + calculate_clipping_registers_rect(dev, fh, &vdma2, &clip_format, &arbtr_ctrl, field); + + /* set clipping format */ + clip_format &= 0xffff0008; + clip_format |= (type << 4); + + /* prepare video dma2 */ + saa7146_write(dev, BASE_EVEN2, vdma2.base_even); + saa7146_write(dev, BASE_ODD2, vdma2.base_odd); + saa7146_write(dev, PROT_ADDR2, vdma2.prot_addr); + saa7146_write(dev, BASE_PAGE2, vdma2.base_page); + saa7146_write(dev, PITCH2, vdma2.pitch); + saa7146_write(dev, NUM_LINE_BYTE2, vdma2.num_line_byte); + + /* prepare the rest */ + saa7146_write(dev, CLIP_FORMAT_CTRL,clip_format); + saa7146_write(dev, PCI_BT_V1, arbtr_ctrl); + + /* upload clip_control-register, clipping-registers, enable video dma2 */ + saa7146_write(dev, MC2, (MASK_05 | MASK_21 | MASK_03 | MASK_19)); + saa7146_write(dev, MC1, (MASK_05 | MASK_21)); +} + +void saa7146_set_window(struct saa7146_dev *dev, int width, int height, enum v4l2_field field) +{ + int flip_lr = dev->hflip; + int source = dev->current_hps_source; + int sync = dev->current_hps_sync; + + u32 hps_v_scale = 0, hps_v_gain = 0, hps_ctrl = 0, hps_h_prescale = 0, hps_h_scale = 0; + + /* set vertical scale */ + hps_v_scale = 0; /* all bits get set by the function-call */ + hps_v_gain = 0; /* fixme: saa7146_read(dev, HPS_V_GAIN);*/ + calculate_v_scale_registers(dev, field, dev->standard->v_calc, height, &hps_v_scale, &hps_v_gain); + + /* set horizontal scale */ + hps_ctrl = 0; + hps_h_prescale = 0; /* all bits get set in the function */ + hps_h_scale = 0; + calculate_h_scale_registers(dev, dev->standard->h_calc, width, flip_lr, &hps_ctrl, &hps_v_gain, &hps_h_prescale, &hps_h_scale); + + /* set hyo and hxo */ + calculate_hxo_and_hyo(dev, &hps_h_scale, &hps_ctrl); + calculate_hps_source_and_sync(dev, source, sync, &hps_ctrl); + + /* write out new register contents */ + saa7146_write(dev, HPS_V_SCALE, hps_v_scale); + saa7146_write(dev, HPS_V_GAIN, hps_v_gain); + saa7146_write(dev, HPS_CTRL, hps_ctrl); + saa7146_write(dev, HPS_H_PRESCALE,hps_h_prescale); + saa7146_write(dev, HPS_H_SCALE, hps_h_scale); + + /* upload shadow-ram registers */ + saa7146_write(dev, MC2, (MASK_05 | MASK_06 | MASK_21 | MASK_22) ); +} + +/* calculate the new memory offsets for a desired position */ +void saa7146_set_position(struct saa7146_dev *dev, int w_x, int w_y, int w_height, enum v4l2_field field) +{ + int td_flip = dev->vflip; + int b_depth = dev->ov_fmt->depth; + int b_bpl = dev->ov_fb.fmt.bytesperline; + u32 base = (u32)dev->ov_fb.base; + + struct saa7146_video_dma vdma1; + + /* calculate memory offsets for picture, look if we shall top-down-flip */ + vdma1.pitch = 2*b_bpl; + if ( 0 == td_flip ) { + vdma1.base_odd = (u32)base + (w_y * (vdma1.pitch/2)) + (w_x * (b_depth / 8)); + vdma1.base_even = vdma1.base_odd + (vdma1.pitch / 2); + vdma1.prot_addr = vdma1.base_odd + (w_height * (vdma1.pitch / 2)); + } + else { + vdma1.base_even = (u32)base + ((w_y+w_height) * (vdma1.pitch/2)) + (w_x * (b_depth / 8)); + vdma1.base_odd = vdma1.base_even - (vdma1.pitch / 2); + vdma1.prot_addr = vdma1.base_odd - (w_height * (vdma1.pitch / 2)); + } + + if (V4L2_FIELD_HAS_BOTH(field)) { + } else if (field == V4L2_FIELD_TOP) { + vdma1.base_odd = vdma1.prot_addr; + vdma1.pitch /= 2; + } else if (field == V4L2_FIELD_BOTTOM) { + vdma1.base_odd = vdma1.base_even; + vdma1.base_even = vdma1.prot_addr; + vdma1.pitch /= 2; + } + + if ( 0 != td_flip ) { + vdma1.pitch *= -1; + } + + vdma1.base_page = 0; + vdma1.num_line_byte = (dev->standard->v_field<<16)+dev->standard->h_pixels; + + saa7146_write(dev, BASE_EVEN1, vdma1.base_even); + saa7146_write(dev, BASE_ODD1, vdma1.base_odd); + saa7146_write(dev, PROT_ADDR1, vdma1.prot_addr); + saa7146_write(dev, BASE_PAGE1, vdma1.base_page); + saa7146_write(dev, PITCH1, vdma1.pitch); + saa7146_write(dev, NUM_LINE_BYTE1, vdma1.num_line_byte); + + /* update the video dma 1 registers */ + saa7146_write(dev, MC2, (MASK_02 | MASK_18)); +} + +void saa7146_set_output_format(struct saa7146_dev *dev, unsigned long palette) +{ + u32 clip_format = saa7146_read(dev, CLIP_FORMAT_CTRL); + + /* call helper function */ + calculate_output_format_register(dev,palette,&clip_format); + + /* update the hps registers */ + saa7146_write(dev, CLIP_FORMAT_CTRL, clip_format); + saa7146_write(dev, MC2, (MASK_05 | MASK_21)); +} + +void saa7146_set_picture_prop(struct saa7146_dev *dev, int brightness, int contrast, int colour) +{ + u32 bcs_ctrl = 0; + + calculate_bcs_ctrl_register(dev, brightness, contrast, colour, &bcs_ctrl); + saa7146_write(dev, BCS_CTRL, bcs_ctrl); + + /* update the bcs register */ + saa7146_write(dev, MC2, (MASK_06 | MASK_22)); +} + + +/* select input-source */ +void saa7146_set_hps_source_and_sync(struct saa7146_dev *dev, int source, int sync) +{ + u32 hps_ctrl = 0; + + /* read old state */ + hps_ctrl = saa7146_read(dev, HPS_CTRL); + + hps_ctrl &= ~( MASK_31 | MASK_30 | MASK_28 ); + hps_ctrl |= (source << 30) | (sync << 28); + + /* write back & upload register */ + saa7146_write(dev, HPS_CTRL, hps_ctrl); + saa7146_write(dev, MC2, (MASK_05 | MASK_21)); + + dev->current_hps_source = source; + dev->current_hps_sync = sync; +} + +/* write "data" to the gpio-pin "pin" */ +void saa7146_set_gpio(struct saa7146_dev *dev, u8 pin, u8 data) +{ + u32 value = 0; + + /* sanity check */ + if(pin > 3) + return; + + /* read old register contents */ + value = saa7146_read(dev, GPIO_CTRL ); + + value &= ~(0xff << (8*pin)); + value |= (data << (8*pin)); + + saa7146_write(dev, GPIO_CTRL, value); +} + +/* reprogram hps, enable(1) / disable(0) video */ +void saa7146_set_overlay(struct saa7146_dev *dev, struct saa7146_fh *fh, int v) +{ + /* enable ? */ + if( 0 == v) { + /* disable video dma1 */ + saa7146_write(dev, MC1, MASK_22); + return; + } + + saa7146_set_window(dev, fh->ov.win.w.width, fh->ov.win.w.height, fh->ov.win.field); + saa7146_set_position(dev, fh->ov.win.w.left, fh->ov.win.w.top, fh->ov.win.w.height, fh->ov.win.field); + saa7146_set_output_format(dev, dev->ov_fmt->trans); + saa7146_set_clipping_rect(dev, fh); + + /* enable video dma1 */ + saa7146_write(dev, MC1, (MASK_06 | MASK_22)); +} + +void saa7146_write_out_dma(struct saa7146_dev* dev, int which, struct saa7146_video_dma* vdma) +{ + int where = 0; + + if( which < 1 || which > 3) { + return; + } + + /* calculate starting address */ + where = (which-1)*0x18; + + saa7146_write(dev, where, vdma->base_odd); + saa7146_write(dev, where+0x04, vdma->base_even); + saa7146_write(dev, where+0x08, vdma->prot_addr); + saa7146_write(dev, where+0x0c, vdma->pitch); + saa7146_write(dev, where+0x10, vdma->base_page); + saa7146_write(dev, where+0x14, vdma->num_line_byte); + + /* upload */ + saa7146_write(dev, MC2, (MASK_02<<(which-1))|(MASK_18<<(which-1))); +/* + printk("vdma%d.base_even: 0x%08x\n", which,vdma->base_even); + printk("vdma%d.base_odd: 0x%08x\n", which,vdma->base_odd); + printk("vdma%d.prot_addr: 0x%08x\n", which,vdma->prot_addr); + printk("vdma%d.base_page: 0x%08x\n", which,vdma->base_page); + printk("vdma%d.pitch: 0x%08x\n", which,vdma->pitch); + printk("vdma%d.num_line_byte: 0x%08x\n", which,vdma->num_line_byte); +*/ +} + +int calculate_video_dma_grab_packed(struct saa7146_dev* dev, struct saa7146_buf *buf) +{ + struct saa7146_video_dma vdma1; + + struct saa7146_format *sfmt = format_by_fourcc(dev,buf->fmt->pixelformat); + + int flip_td = dev->vflip; + + int width = buf->fmt->width; + int height = buf->fmt->height; + enum v4l2_field field = buf->fmt->field; + + int depth = sfmt->depth; + unsigned long flags = sfmt->flags; + + DEB_CAP(("[size=%dx%d,fields=%s,flags=%lu]\n", + width,height,v4l2_field_names[field],flags)); + + vdma1.pitch = (width*depth*2)/8; + vdma1.num_line_byte = ((dev->standard->v_field<<16) + dev->standard->h_pixels); + vdma1.base_page = buf->pt[0].dma | ME1 | flags; + + if( 0 != flip_td ) { + vdma1.prot_addr = buf->pt[0].offset; + vdma1.base_even = buf->pt[0].offset+(vdma1.pitch/2)*height; + vdma1.base_odd = vdma1.base_even - (vdma1.pitch/2); + } else { + vdma1.base_even = buf->pt[0].offset; + vdma1.base_odd = vdma1.base_even + (vdma1.pitch/2); + vdma1.prot_addr = buf->pt[0].offset+(vdma1.pitch/2)*height; + } + + if (V4L2_FIELD_HAS_BOTH(field)) { + } else if (field == V4L2_FIELD_TOP) { + vdma1.base_odd = vdma1.prot_addr; + vdma1.pitch /= 2; + } else if (field == V4L2_FIELD_BOTTOM) { + vdma1.base_odd = vdma1.base_even; + vdma1.base_even = vdma1.prot_addr; + vdma1.pitch /= 2; + } + + if( 0 != flip_td ) { + vdma1.pitch *= -1; + } + + saa7146_write_out_dma(dev, 1, &vdma1); + return 0; +} + +int calculate_video_dma_grab_planar(struct saa7146_dev* dev, struct saa7146_buf *buf) +{ + struct saa7146_video_dma vdma1; + struct saa7146_video_dma vdma2; + struct saa7146_video_dma vdma3; + + struct saa7146_format *sfmt = format_by_fourcc(dev,buf->fmt->pixelformat); + + int flip_td = dev->vflip; + + int width = buf->fmt->width; + int height = buf->fmt->height; + enum v4l2_field field = buf->fmt->field; + + unsigned long flags = sfmt->flags; + + BUG_ON(0 == buf->pt[0].dma); + BUG_ON(0 == buf->pt[1].dma); + BUG_ON(0 == buf->pt[2].dma); + + DEB_CAP(("[size=%dx%d,fields=%s,flags=%lu]\n", + width,height,v4l2_field_names[field],flags)); + + vdma1.pitch = width*2; + vdma1.num_line_byte = ((dev->standard->v_field<<16) + dev->standard->h_pixels); + vdma1.base_page = buf->pt[0].dma | ME1 | flags; + + /* fscking saa7146! due to the "byte-swap bug", video-dma2 + writes the v data and not the u data, video-dma3 writes + the u data, not the v data. but because we have a planar + format, we can simply switch the video-dmas... */ + + vdma3.pitch = width; + vdma3.num_line_byte = 0; /* unused */ + vdma3.base_page = buf->pt[1].dma | ME1 | flags; + + vdma2.pitch = width; + vdma2.num_line_byte = 0; /* unused */ + vdma2.base_page = buf->pt[2].dma | ME1 | flags; + + /* fixme: what happens for user space buffers here?. The offsets are + most likely wrong, this version here only works for page-aligned + buffers, modifications to the pagetable-functions are necessary...*/ + + if( 0 != flip_td ) { + vdma1.prot_addr = buf->pt[0].offset; + vdma1.base_even = ((vdma1.pitch/2)*height)+buf->pt[0].offset; + vdma1.base_odd = vdma1.base_even - (vdma1.pitch/2); + + vdma2.prot_addr = buf->pt[2].offset; + vdma2.base_even = ((vdma2.pitch/2)*height)+buf->pt[2].offset; + vdma2.base_odd = vdma2.base_even - (vdma2.pitch/2); + + vdma3.prot_addr = buf->pt[1].offset; + vdma3.base_even = ((vdma3.pitch/2)*height)+buf->pt[1].offset; + vdma3.base_odd = vdma3.base_even - (vdma3.pitch/2); + + } else { + vdma1.base_even = buf->pt[0].offset; + vdma1.base_odd = vdma1.base_even + (vdma1.pitch/2); + vdma1.prot_addr = (vdma1.pitch/2)*height+buf->pt[0].offset; + + /* switched, see above */ + vdma3.base_even = buf->pt[1].offset; + vdma3.base_odd = vdma3.base_even + (vdma3.pitch/2); + vdma3.prot_addr = (vdma3.pitch/2)*height+buf->pt[1].offset; + + vdma2.base_even = buf->pt[2].offset; + vdma2.base_odd = vdma2.base_even + (vdma2.pitch/2); + vdma2.prot_addr = (vdma2.pitch/2)*height+buf->pt[2].offset; + } + + if (V4L2_FIELD_HAS_BOTH(field)) { + } else if (field == V4L2_FIELD_TOP) { + vdma1.base_odd = vdma1.prot_addr; + vdma1.pitch /= 2; + vdma2.base_odd = vdma2.prot_addr; + vdma2.pitch /= 2; + vdma3.base_odd = vdma3.prot_addr; + vdma3.pitch /= 2; + } else if (field == V4L2_FIELD_BOTTOM) { + vdma1.base_odd = vdma1.base_even; + vdma1.base_even = vdma1.prot_addr; + vdma1.pitch /= 2; + vdma2.base_odd = vdma2.base_even; + vdma2.base_even = vdma2.prot_addr; + vdma2.pitch /= 2; + vdma3.base_odd = vdma3.base_even; + vdma3.base_even = vdma3.prot_addr; + vdma3.pitch /= 2; + } + + if( 0 != flip_td ) { + vdma1.pitch *= -1; + vdma2.pitch *= -1; + vdma3.pitch *= -1; + } + + saa7146_write_out_dma(dev, 1, &vdma1); + saa7146_write_out_dma(dev, 2, &vdma2); + saa7146_write_out_dma(dev, 3, &vdma3); + + return 0; +} + +void program_capture_engine(struct saa7146_dev *dev, int planar) +{ + int count = 0; + + unsigned long e_wait = dev->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? CMD_E_FID_A : CMD_E_FID_B; + unsigned long o_wait = dev->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? CMD_O_FID_A : CMD_O_FID_B; + + /* write beginning of rps-program */ + count = 0; + + /* wait for o_fid_a/b / e_fid_a/b toggle only if bit 0 is not set*/ + dev->rps0[ count++ ] = CMD_PAUSE | CMD_OAN | CMD_SIG0 | e_wait; + dev->rps0[ count++ ] = CMD_PAUSE | CMD_OAN | CMD_SIG0 | o_wait; + + /* set bit 0 */ + dev->rps0[ count++ ] = CMD_WR_REG | (1 << 8) | (MC2/4); + dev->rps0[ count++ ] = MASK_27 | MASK_11; + + /* turn on video-dma1 */ + dev->rps0[ count++ ] = CMD_WR_REG_MASK | (MC1/4); + dev->rps0[ count++ ] = MASK_06 | MASK_22; /* => mask */ + dev->rps0[ count++ ] = MASK_06 | MASK_22; /* => values */ + if( 0 != planar ) { + /* turn on video-dma2 */ + dev->rps0[ count++ ] = CMD_WR_REG_MASK | (MC1/4); + dev->rps0[ count++ ] = MASK_05 | MASK_21; /* => mask */ + dev->rps0[ count++ ] = MASK_05 | MASK_21; /* => values */ + + /* turn on video-dma3 */ + dev->rps0[ count++ ] = CMD_WR_REG_MASK | (MC1/4); + dev->rps0[ count++ ] = MASK_04 | MASK_20; /* => mask */ + dev->rps0[ count++ ] = MASK_04 | MASK_20; /* => values */ + } + + /* wait for o_fid_a/b / e_fid_a/b toggle */ + dev->rps0[ count++ ] = CMD_PAUSE | e_wait; + dev->rps0[ count++ ] = CMD_PAUSE | o_wait; + + /* turn off video-dma1 */ + dev->rps0[ count++ ] = CMD_WR_REG_MASK | (MC1/4); + dev->rps0[ count++ ] = MASK_22 | MASK_06; /* => mask */ + dev->rps0[ count++ ] = MASK_22; /* => values */ + if( 0 != planar ) { + /* turn off video-dma2 */ + dev->rps0[ count++ ] = CMD_WR_REG_MASK | (MC1/4); + dev->rps0[ count++ ] = MASK_05 | MASK_21; /* => mask */ + dev->rps0[ count++ ] = MASK_21; /* => values */ + + /* turn off video-dma3 */ + dev->rps0[ count++ ] = CMD_WR_REG_MASK | (MC1/4); + dev->rps0[ count++ ] = MASK_04 | MASK_20; /* => mask */ + dev->rps0[ count++ ] = MASK_20; /* => values */ + } + + /* generate interrupt */ + dev->rps0[ count++ ] = CMD_INTERRUPT; + + /* stop */ + dev->rps0[ count++ ] = CMD_STOP; +} + +void saa7146_set_capture(struct saa7146_dev *dev, struct saa7146_buf *buf, struct saa7146_buf *next) +{ + struct saa7146_format *sfmt = format_by_fourcc(dev,buf->fmt->pixelformat); + + DEB_CAP(("buf:%p, next:%p\n",buf,next)); + + saa7146_set_window(dev, buf->fmt->width, buf->fmt->height, buf->fmt->field); + saa7146_set_output_format(dev, sfmt->trans); + saa7146_disable_clipping(dev); + + if( 0 != IS_PLANAR(sfmt->trans)) { + calculate_video_dma_grab_planar(dev, buf); + program_capture_engine(dev,1); + } else { + calculate_video_dma_grab_packed(dev, buf); + program_capture_engine(dev,0); + } + + /* write the address of the rps-program */ + saa7146_write(dev, RPS_ADDR0, virt_to_bus(&dev->rps0[ 0])); + + /* turn on rps */ + saa7146_write(dev, MC1, (MASK_12 | MASK_28)); +} + diff --git a/linux/drivers/media/common/saa7146_i2c.c b/linux/drivers/media/common/saa7146_i2c.c new file mode 100644 index 000000000..995ef5883 --- /dev/null +++ b/linux/drivers/media/common/saa7146_i2c.c @@ -0,0 +1,426 @@ +#include "saa7146.h" +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) + #define KBUILD_MODNAME "saa7146" +#endif + +/* helper function */ +void my_wait(struct saa7146_dev *dev, long ms) +{ + unsigned long timeout = jiffies + ((ms+9)/10); + while(time_before(jiffies, timeout)) { + schedule(); + } +} + +u32 saa7146_i2c_func(struct i2c_adapter *adapter) +{ +//fm DEB_I2C(("'%s'.\n", adapter->name)); + + return I2C_FUNC_I2C + | I2C_FUNC_SMBUS_QUICK + | I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE + | I2C_FUNC_SMBUS_READ_BYTE_DATA | I2C_FUNC_SMBUS_WRITE_BYTE_DATA; +} + +/* this function returns the status-register of our i2c-device */ +u32 saa7146_i2c_status(struct saa7146_dev *dev) +{ + u32 iicsta = saa7146_read(dev, I2C_STATUS); +/* + DEB_I2C(("status: 0x%08x\n",iicsta)); +*/ + return iicsta; +} + +/* this function runs through the i2c-messages and prepares the data to be + sent through the saa7146. have a look at the specifications p. 122 ff + to understand this. it returns the number of u32s to send, or -1 + in case of an error. */ +int saa7146_i2c_msg_prepare(const struct i2c_msg m[], int num, u32 *op) +{ + int h1, h2; + int i, j, addr; + int mem = 0, op_count = 0; + + /* first determine size of needed memory */ + for(i = 0; i < num; i++) { + mem += m[i].len + 1; + } + + /* worst case: we need one u32 for three bytes to be send + plus one extra byte to address the device */ + mem = 1 + ((mem-1) / 3); + + /* we assume that op points to a memory of at least SAA7146_I2C_MEM bytes + size. if we exceed this limit... */ + if ( (4*mem) > SAA7146_I2C_MEM ) { +//fm DEB_I2C(("cannot prepare i2c-message.\n")); + return -1; + } + + /* be careful: clear out the i2c-mem first */ + memset(op,0,sizeof(u32)*mem); + + /* loop through all messages */ + for(i = 0; i < num; i++) { + + /* insert the address of the i2c-slave. + note: we get 7 bit i2c-addresses, so we have to perform a translation */ + addr = (m[i].addr*2) + ( (0 != (m[i].flags & I2C_M_RD)) ? 1 : 0); + h1 = op_count/3; h2 = op_count%3; + op[h1] |= ( (u8)addr << ((3-h2)*8)); + op[h1] |= (SAA7146_I2C_START << ((3-h2)*2)); + op_count++; + + /* loop through all bytes of message i */ + for(j = 0; j < m[i].len; j++) { + + /* insert the data bytes */ + h1 = op_count/3; h2 = op_count%3; + op[h1] |= ( (u32)((u8)m[i].buf[j]) << ((3-h2)*8)); + op[h1] |= ( SAA7146_I2C_CONT << ((3-h2)*2)); + op_count++; + } + + } + + /* have a look at the last byte inserted: + if it was: ...CONT change it to ...STOP */ + h1 = (op_count-1)/3; h2 = (op_count-1)%3; + if ( SAA7146_I2C_CONT == (0x3 & (op[h1] >> ((3-h2)*2))) ) { + op[h1] &= ~(0x2 << ((3-h2)*2)); + op[h1] |= (SAA7146_I2C_STOP << ((3-h2)*2)); + } + + /* return the number of u32s to send */ + return mem; +} + +/* this functions loops through all i2c-messages. normally, it should determine + which bytes were read through the adapter and write them back to the corresponding + i2c-message. but instead, we simply write back all bytes. + fixme: this could be improved. */ +int saa7146_i2c_msg_cleanup(const struct i2c_msg m[], int num, u32 *op) +{ + int i, j; + int op_count = 0; + + /* loop through all messages */ + for(i = 0; i < num; i++) { + + op_count++; + + /* loop throgh all bytes of message i */ + for(j = 0; j < m[i].len; j++) { + /* write back all bytes that could have been read */ + m[i].buf[j] = (op[op_count/3] >> ((3-(op_count%3))*8)); + op_count++; + } + } + + return 0; +} + +/* this functions resets the i2c-device and returns 0 if everything was fine, otherwise -1 */ +int saa7146_i2c_reset(struct saa7146_dev *dev) +{ + /* get current status */ + u32 status = saa7146_i2c_status(dev); + + /* clear registers for sure */ + saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate); + saa7146_write(dev, I2C_TRANSFER, 0); + + /* check if any operation is still in progress */ + if ( 0 != ( status & SAA7146_I2C_BUSY) ) { + + /* yes, kill ongoing operation */ + DEB_I2C(("busy_state detected.\n")); + + /* set "ABORT-OPERATION"-bit (bit 7)*/ + saa7146_write(dev, I2C_STATUS, (dev->i2c_bitrate | MASK_07)); + saa7146_write(dev, MC2, (MASK_00 | MASK_16)); + my_wait(dev,SAA7146_I2C_DELAY); + + /* clear all error-bits pending; this is needed because p.123, note 1 */ + saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate); + saa7146_write(dev, MC2, (MASK_00 | MASK_16)); + my_wait(dev,SAA7146_I2C_DELAY); + } + + /* check if any error is (still) present. (this can be necessary because p.123, note 1) */ + status = saa7146_i2c_status(dev); + + if ( dev->i2c_bitrate != status ) { + + DEB_I2C(("error_state detected. status:0x%08x\n",status)); + + /* Repeat the abort operation. This seems to be necessary + after serious protocol errors caused by e.g. the SAA7740 */ + saa7146_write(dev, I2C_STATUS, (dev->i2c_bitrate | MASK_07)); + saa7146_write(dev, MC2, (MASK_00 | MASK_16)); + my_wait(dev,SAA7146_I2C_DELAY); + + /* clear all error-bits pending */ + saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate); + saa7146_write(dev, MC2, (MASK_00 | MASK_16)); + my_wait(dev,SAA7146_I2C_DELAY); + + /* the data sheet says it might be necessary to clear the status + twice after an abort */ + saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate); + saa7146_write(dev, MC2, (MASK_00 | MASK_16)); + my_wait(dev,SAA7146_I2C_DELAY); + } + + /* if any error is still present, a fatal error has occured ... */ + status = saa7146_i2c_status(dev); + if ( dev->i2c_bitrate != status ) { + DEB_I2C(("fatal error. status:0x%08x\n",status)); + return -1; + } + + return 0; +} + +/* this functions writes out the data-byte 'dword' to the i2c-device. + it returns 0 if ok, -1 if the transfer failed, -2 if the transfer + failed badly (e.g. address error) */ +int saa7146_i2c_writeout(struct saa7146_dev *dev, u32* dword) +{ + int i = 0; + u32 status = 0, mc2 = 0; + + DEB_I2C(("before: 0x%08x\n",*dword)); + + /* write out i2c-command */ + saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate); + saa7146_write(dev, I2C_TRANSFER, *dword); + saa7146_write(dev, MC2, (MASK_00 | MASK_16)); + + /* do not poll for i2c-status before upload is complete */ + for (i = 5; i > 0; i--) { + mc2 = (saa7146_read(dev, MC2) & 0x1); + if( 0 != mc2 ) + break; + my_wait(dev,SAA7146_I2C_DELAY); + } + if (0 == i) { + DEB_I2C(("timeout error. #1\n")); + return -1; + } + + /* wait until busy flag becomes inactive or we time out */ + for (i = 5; i > 0; i--) { + status = saa7146_i2c_status(dev); + /* check busy flag */ + if ( 0 == (status & SAA7146_I2C_BUSY)) + break; + /* check error flag */ + if ( 0 != (status & SAA7146_I2C_ERR)) { + break; + } + my_wait(dev,SAA7146_I2C_DELAY); + } + if (0 == i) { + DEB_I2C(("timeout error. #2\n")); + return -1; + } + + /* give a detailed status report */ + if ( 0 != (status & SAA7146_I2C_ERR)) { + + if( 0 != (status & SAA7146_I2C_SPERR) ) { + DEB_I2C(("error due to invalid start/stop condition.\n")); + } + if( 0 != (status & SAA7146_I2C_DTERR) ) { + DEB_I2C(("error in data transmission.\n")); + } + if( 0 != (status & SAA7146_I2C_DRERR) ) { + DEB_I2C(("error when receiving data.\n")); + } + if( 0 != (status & SAA7146_I2C_AL) ) { + DEB_I2C(("error because arbitration lost.\n")); + } + + /* we handle address-errors here */ + if( 0 != (status & SAA7146_I2C_APERR) ) { + DEB_I2C(("error in address phase.\n")); + return -2; + } + return -1; + } + + /* read back data, just in case we were reading ... */ + *dword = saa7146_read(dev, I2C_TRANSFER); + + DEB_I2C(("after: 0x%08x\n",*dword)); + + saa7146_i2c_status(dev); + + return 0; +} + +int saa7146_i2c_transfer(struct saa7146_dev *dev, const struct i2c_msg msgs[], int num, int retries) +{ + int i = 0, result = 0, count = 0; + u32* buffer = dev->i2c_mem; + int err = 0; + + if (down_interruptible (&dev->i2c_lock)) + return -ERESTARTSYS; + + for(i=0;i<num;i++) { + DEB_I2C(("msg:%d/%d\n",i+1,num)); + } + + /* prepare the message(s), get number of u32s to transfer */ + count = saa7146_i2c_msg_prepare(msgs, num, buffer); + if ( 0 > count ) { + err = -1; + goto out; + } + + /* loop through number of retries ... */ + for(; retries >= 0; retries--) { + + /* reset the i2c-device if necessary */ + result = saa7146_i2c_reset(dev); + if ( 0 > result ) { + DEB_I2C(("could not reset i2c-device.\n")); + err = -1; + goto out; + } + + /* write out the u32s one after another */ + for(i = 0; i < count; i++) { + + result = saa7146_i2c_writeout(dev, &buffer[i] ); + if ( 0 != result) { + /* if address-error occured, don´t retry */ + if ( -2 == result ) { + err = -1; + goto out; + } + DEB_I2C(("error while sending message(s). starting again.\n")); + break; + } + } + + /* see if an error occured & the last retry failed */ + if( (0 != result) && (0 == retries) ) { + DEB_I2C(("could not transfer i2c-message(s).\n")); + err = -1; + goto out; + } + + if( 0 == result ) + break; + } + + /* if any things had to be read, get the results */ + if ( 0 != saa7146_i2c_msg_cleanup(msgs, num, buffer)) { + DEB_I2C(("could not cleanup i2c-message.\n")); + err = -1; + goto out; + } + + saa7146_i2c_status(dev); + + /* another bug in revision 0: the i2c-registers get uploaded randomly by other + uploads, so we better clear them out before continueing */ + if( 0 == dev->revision ) { + u32 zero = 0; + if( 0 != saa7146_i2c_writeout(dev, &zero)) { + INFO(("revision 0 error. this should never happen.\n")); + } + } + + /* return the number of delivered messages */ + err = num; + DEB_I2C(("transmission successful. (msg:%d).\n",err)); +out: + up(&dev->i2c_lock); + return err; +} + +/* utility functions */ +int saa7146_i2c_xfer(struct i2c_adapter* adapter, struct i2c_msg msg[], int num) +{ + struct saa7146_dev* dev = (struct saa7146_dev*)adapter->data; + + DEB_I2C(("adapter: '%s'.\n", adapter->name)); + + /* use helper function to transfer data */ + return saa7146_i2c_transfer(dev, msg, num, adapter->retries); +} + +/* these are just for completeness */ +int saa7146_i2c_reg(struct i2c_client *client) +{ + return 0; +} + +int saa7146_i2c_unreg(struct i2c_client *client) +{ + return 0; +} + +/* fixme +void saa7146_i2c_inc_use(struct i2c_adapter *adap) +{ +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif +} + +void saa7146_i2c_dec_use(struct i2c_adapter *adap) +{ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif +} +*/ + +/*****************************************************************************/ +/* i2c-adapter helper functions */ + +/* exported algorithm data */ +struct i2c_algorithm saa7146_algo = { + "saa7146 i2c algorithm", + I2C_ALGO_SAA7146, + saa7146_i2c_xfer, + NULL, + NULL, /* slave_xmit */ + NULL, /* slave_recv */ + NULL, /* ioctl */ + saa7146_i2c_func, /* functionality */ +}; + +int saa7146_i2c_adapter_prepare(struct saa7146_dev *dev) +{ + /* fixme: this should be adjusted by the extension */ + dev->i2c_bitrate = SAA7146_I2C_BUS_BIT_RATE_480; + + saa7146_i2c_reset(dev); + + memset(dev->i2c_adapter,0,sizeof(struct i2c_adapter)); + strcpy(dev->i2c_adapter->name, dev->name); + + dev->i2c_adapter->data = dev; + dev->i2c_adapter->algo = &saa7146_algo; + dev->i2c_adapter->algo_data = NULL; + dev->i2c_adapter->id = I2C_ALGO_SAA7146; + +/* fixme + dev->i2c_adapter->inc_use = saa7146_i2c_inc_use; + dev->i2c_adapter->dec_use = saa7146_i2c_dec_use; +*/ + dev->i2c_adapter->client_register = saa7146_i2c_reg; + dev->i2c_adapter->client_unregister = saa7146_i2c_unreg; + + dev->i2c_adapter->timeout = SAA7146_I2C_TIMEOUT; + dev->i2c_adapter->retries = SAA7146_I2C_RETRIES; + + return 0; +} diff --git a/linux/drivers/media/common/saa7146_vbi.c b/linux/drivers/media/common/saa7146_vbi.c new file mode 100644 index 000000000..4a8528295 --- /dev/null +++ b/linux/drivers/media/common/saa7146_vbi.c @@ -0,0 +1,440 @@ +#include "saa7146.h" +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) + #define KBUILD_MODNAME "saa7146" +#endif + +static int vbi_pixel_to_capture = 720 * 2; + +int vbi_workaround(struct saa7146_dev *dev) +{ + u32 *cpu; + dma_addr_t dma_addr; + + int i, index; + + DECLARE_WAITQUEUE(wait, current); + + DEB_VBI(("dev:%p",dev)); + + /* once again, a bug in the saa7146: the brs acquisition + is buggy and especially the BXO-counter does not work + as specified. there is this workaround, but please + don't let me explain it. ;-) */ + + if(0 != dev->ext->vbi) { + dev->ext->vbi(dev); + } + + cpu = pci_alloc_consistent(dev->pci, 4096, &dma_addr); + if (NULL == cpu) + return -ENOMEM; + + /* setup some basic programming, just for the workaround */ + saa7146_write(dev, BASE_EVEN3, dma_addr); + saa7146_write(dev, BASE_ODD3, dma_addr+vbi_pixel_to_capture); + saa7146_write(dev, PROT_ADDR3, dma_addr+4096); + saa7146_write(dev, PITCH3, vbi_pixel_to_capture); + saa7146_write(dev, BASE_PAGE3, 0x0); + saa7146_write(dev, NUM_LINE_BYTE3, (2<<16)|((vbi_pixel_to_capture)<<0)); + saa7146_write(dev, MC2, MASK_04|MASK_20); + + + /* we have to do the workaround two times to be sure that + everything is ok */ + for(i = 0; i < 2; i++) { + + /* indicate to the irq handler that we do the workaround */ + saa7146_write(dev, MC2, MASK_31|MASK_15); + + saa7146_write(dev, NUM_LINE_BYTE3, (1<<16)|(2<<0)); + saa7146_write(dev, MC2, MASK_04|MASK_20); + + index = 0; + + /* load brs-control register */ + dev->rps1[index++] = CMD_WR_REG | (1 << 8) | (BRS_CTRL/4); + /* BXO = 1h, BRS to outbound */ + dev->rps1[index++]=0xc000008c; + /* wait for vbi_a */ + dev->rps1[index++] = CMD_PAUSE | MASK_10; + /* upload brs */ + dev->rps1[index++] = CMD_UPLOAD | MASK_08; + /* load brs-control register */ + dev->rps1[index++] = CMD_WR_REG | (1 << 8) | (BRS_CTRL/4); + /* BYO = 1, BXO = NQBIL (=1728 for PAL, for NTSC this is 858*2) - NumByte3 (=1440) = 288 */ + dev->rps1[index++] = ((1728-(vbi_pixel_to_capture)) << 7) | MASK_19; + /* wait for brs_done */ + dev->rps1[index++] = CMD_PAUSE | MASK_08; + /* upload brs */ + dev->rps1[index++] = CMD_UPLOAD | MASK_08; + /* load video-dma3 NumLines3 and NumBytes3 */ + dev->rps1[index++] = CMD_WR_REG | (1 << 8) | (NUM_LINE_BYTE3/4); + /* dev->vbi_count*2 lines, 720 pixel (= 1440 Bytes) */ + dev->rps1[index++]= (2 << 16) | (vbi_pixel_to_capture); + /* load brs-control register */ + dev->rps1[index++] = CMD_WR_REG | (1 << 8) | (BRS_CTRL/4); + /* Set BRS right: note: this is an experimental value for BXO (=> PAL!) */ + dev->rps1[index++] = (540 << 7) | (5 << 19); // 5 == vbi_start + /* wait for brs_done */ + dev->rps1[index++] = CMD_PAUSE | MASK_08; + /* upload brs and video-dma3*/ + dev->rps1[index++] = CMD_UPLOAD | MASK_08 | MASK_04; + /* load mc2 register: enable dma3 */ + dev->rps1[index++] = CMD_WR_REG | (1 << 8) | (MC1/4); + dev->rps1[index++] = MASK_20 | MASK_04; + /* generate interrupt */ + dev->rps1[index++] = CMD_INTERRUPT; + /* stop rps1 */ + dev->rps1[index++] = CMD_STOP; + + /* enable rps1 irqs */ + IER_ENABLE(dev,MASK_28); + + /* prepare to wait to be woken up by the irq-handler */ + add_wait_queue(&dev->vbi_wq, &wait); + current->state = TASK_INTERRUPTIBLE; + + /* start rps1 to enable workaround */ + saa7146_write(dev, RPS_ADDR1, virt_to_bus(&dev->rps1[ 0])); + saa7146_write(dev, MC1, (MASK_13 | MASK_29)); + + schedule(); + + DEB_VBI(("brs bug workaround %d/1.\n",i)); + + remove_wait_queue(&dev->vbi_wq, &wait); + current->state = TASK_RUNNING; + + /* disable rps1 irqs */ + IER_DISABLE(dev,MASK_28); + + /* stop video-dma3 */ + saa7146_write(dev, MC1, MASK_20); + + if(signal_pending(current)) { + + DEB_VBI(("aborted.\n")); + + /* stop rps1 for sure */ + saa7146_write(dev, MC1, MASK_29); + + pci_free_consistent(dev->pci, 4096, cpu, dma_addr); + return -EINTR; + } + } + + pci_free_consistent(dev->pci, 4096, cpu, dma_addr); + return 0; +} + +void saa7146_set_vbi_capture(struct saa7146_dev *dev, struct saa7146_buf *buf, struct saa7146_buf *next) +{ + struct saa7146_video_dma vdma3; + + int count = 0; + unsigned long e_wait = dev->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? CMD_E_FID_A : CMD_E_FID_B; + unsigned long o_wait = dev->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? CMD_O_FID_A : CMD_O_FID_B; + +/* + vdma3.base_even = (u32)dev->ov_fb.base+2048*70; + vdma3.base_odd = (u32)dev->ov_fb.base; + vdma3.prot_addr = (u32)dev->ov_fb.base+2048*164; + vdma3.pitch = 2048; + vdma3.base_page = 0; + vdma3.num_line_byte = (64<<16)|((vbi_pixel_to_capture)<<0); // set above! +*/ + vdma3.base_even = buf->pt[2].offset; + vdma3.base_odd = buf->pt[2].offset + 16 * vbi_pixel_to_capture; + vdma3.prot_addr = buf->pt[2].offset + 16 * 2 * vbi_pixel_to_capture; + vdma3.pitch = vbi_pixel_to_capture; + vdma3.base_page = buf->pt[2].dma | ME1; + vdma3.num_line_byte = (16 << 16) | vbi_pixel_to_capture; + saa7146_write_out_dma(dev, 3, &vdma3); + + /* write beginning of rps-program */ + count = 0; + + /* wait for o_fid_a/b / e_fid_a/b toggle only if bit 1 is not set */ + + /* we don't wait here for the first field anymore. this is different from the video + capture and might cause that the first buffer is only half filled (with only + one field). but since this is some sort of streaming data, this is not that negative. + but by doing this, we can use the whole engine from video-buf.c... */ + +/* + dev->rps1[ count++ ] = CMD_PAUSE | CMD_OAN | CMD_SIG1 | e_wait; + dev->rps1[ count++ ] = CMD_PAUSE | CMD_OAN | CMD_SIG1 | o_wait; +*/ + /* set bit 1 */ + dev->rps1[ count++ ] = CMD_WR_REG | (1 << 8) | (MC2/4); + dev->rps1[ count++ ] = MASK_28 | MASK_12; + + /* turn on video-dma3 */ + dev->rps1[ count++ ] = CMD_WR_REG_MASK | (MC1/4); + dev->rps1[ count++ ] = MASK_04 | MASK_20; /* => mask */ + dev->rps1[ count++ ] = MASK_04 | MASK_20; /* => values */ + + /* wait for o_fid_a/b / e_fid_a/b toggle */ + dev->rps1[ count++ ] = CMD_PAUSE | o_wait; + dev->rps1[ count++ ] = CMD_PAUSE | e_wait; + + /* generate interrupt */ + dev->rps1[ count++ ] = CMD_INTERRUPT; + + /* stop */ + dev->rps1[ count++ ] = CMD_STOP; + + /* enable rps1 irqs */ + IER_ENABLE(dev, MASK_28); + + /* write the address of the rps-program */ + saa7146_write(dev, RPS_ADDR1, virt_to_bus(&dev->rps1[ 0])); + + /* turn on rps */ + saa7146_write(dev, MC1, (MASK_13 | MASK_29)); +} + +static int buffer_activate(struct saa7146_dev *dev, + struct saa7146_buf *buf, + struct saa7146_buf *next) +{ + buf->vb.state = STATE_ACTIVE; + + DEB_VBI(("dev:%p, buf:%p, next:%p\n",dev,buf,next)); + saa7146_set_vbi_capture(dev,buf,next); + + mod_timer(&dev->vbi_q.timeout, jiffies+BUFFER_TIMEOUT); + return 0; +} + +static int buffer_prepare(struct file *file, struct videobuf_buffer *vb) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + struct saa7146_buf *buf = (struct saa7146_buf *)vb; + + int err = 0; + int lines, llength, size; + + lines = 16 * 2 ; /* 2 fields */ + llength = vbi_pixel_to_capture; + size = lines * llength; + + DEB_VBI(("vb:%p\n",vb)); + + if (0 != buf->vb.baddr && buf->vb.bsize < size) { + DEB_VBI(("size mismatch.\n")); + return -EINVAL; + } + + if (buf->vb.size != size) + saa7146_dma_free(dev,buf); + + if (STATE_NEEDS_INIT == buf->vb.state) { + buf->vb.width = llength; + buf->vb.height = lines; + buf->vb.size = size; + + saa7146_pgtable_free(dev->pci, &buf->pt[2]); + saa7146_pgtable_alloc(dev->pci, &buf->pt[2]); + + err = videobuf_iolock(dev->pci,&buf->vb); + if (err) + goto oops; + saa7146_pgtable_build_single(dev->pci, &buf->pt[2], buf->vb.dma.sglist, buf->vb.dma.sglen); + } + buf->vb.state = STATE_PREPARED; + buf->activate = buffer_activate; + + return 0; + + oops: + DEB_VBI(("error out.\n")); + saa7146_dma_free(dev,buf); + + return err; +} +static int +buffer_setup(struct file *file, int *count, int *size) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + + int llength,lines; + + lines = 16 * 2 ; /* 2 fields */ + llength = vbi_pixel_to_capture; + + *size = lines * llength; + *count = 2; + + DEB_VBI(("count:%d, size:%d\n",*count,*size)); + + return 0; +} + +static void buffer_queue(struct file *file, struct videobuf_buffer *vb) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + struct saa7146_buf *buf = (struct saa7146_buf *)vb; + + DEB_VBI(("vb:%p\n",vb)); + saa7146_buffer_queue(dev,&dev->vbi_q,buf); +} + +static void buffer_release(struct file *file, struct videobuf_buffer *vb) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + struct saa7146_buf *buf = (struct saa7146_buf *)vb; + + DEB_VBI(("vb:%p\n",vb)); + saa7146_dma_free(dev,buf); +} + +struct videobuf_queue_ops vbi_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +/* ------------------------------------------------------------------ */ + +void vbi_stop(struct saa7146_fh *fh) +{ + struct saa7146_dev *dev = fh->dev; + unsigned long flags; + DEB_VBI(("dev:%p, fh:%p\n",dev, fh)); + + spin_lock_irqsave(&dev->slock,flags); + + /* disable rps1 */ + saa7146_write(dev, MC1, MASK_29); + + /* disable rps1 irqs */ + IER_DISABLE(dev, MASK_28); + + /* shut down dma 3 transfers */ + saa7146_write(dev, MC1, MASK_20); + + dev->vbi_streaming = NULL; + spin_unlock_irqrestore(&dev->slock, flags); +} + +void vbi_read_timeout(unsigned long data) +{ + struct saa7146_fh *fh = (struct saa7146_fh *)data; + struct saa7146_dev *dev = fh->dev; + + DEB_VBI(("dev:%p, fh:%p\n",dev, fh)); + + vbi_stop(fh); +} + +void vbi_init(struct saa7146_dev *dev) +{ + INIT_LIST_HEAD(&dev->vbi_q.queue); + + init_timer(&dev->vbi_q.timeout); + dev->vbi_q.timeout.function = saa7146_buffer_timeout; + dev->vbi_q.timeout.data = (unsigned long)(&dev->vbi_q); + dev->vbi_q.dev = dev; + + init_waitqueue_head(&dev->vbi_wq); +} + +void vbi_open(struct saa7146_dev *dev, struct saa7146_fh *fh) +{ + memset(&fh->vbi_fmt,0,sizeof(fh->vbi_fmt)); + + fh->vbi_fmt.sampling_rate = 27000000; + fh->vbi_fmt.offset = 248; /* todo */ + fh->vbi_fmt.samples_per_line = vbi_pixel_to_capture; + fh->vbi_fmt.sample_format = V4L2_PIX_FMT_GREY; + + /* fixme: this only works for PAL */ + fh->vbi_fmt.start[0] = 5; + fh->vbi_fmt.count[0] = 16; + fh->vbi_fmt.start[1] = 312; + fh->vbi_fmt.count[1] = 16; + + videobuf_queue_init(&fh->vbi_q, &vbi_qops, + dev->pci, &dev->slock, + V4L2_BUF_TYPE_VBI_CAPTURE, + sizeof(struct saa7146_buf)); + init_MUTEX(&fh->vbi_q.lock); + + init_timer(&fh->vbi_read_timeout); + fh->vbi_read_timeout.function = vbi_read_timeout; + fh->vbi_read_timeout.data = (unsigned long)fh; + + vbi_workaround(dev); +} + +void vbi_close(struct saa7146_dev *dev, struct saa7146_fh *fh, struct file *file) +{ + if( fh == dev->vbi_streaming ) { + vbi_stop(fh); + } +} + +void vbi_irq_done(struct saa7146_dev *dev, unsigned long status) +{ + spin_lock(&dev->slock); + + if (dev->vbi_q.curr) { + DEB_VBI(("dev:%p, curr:%p\n",dev,dev->vbi_q.curr)); + /* this must be += 2, one count for each field */ + dev->vbi_fieldcount+=2; + dev->vbi_q.curr->vb.field_count = dev->vbi_fieldcount; + saa7146_buffer_finish(dev,&dev->vbi_q,STATE_DONE); + } else { + DEB_VBI(("dev:%p\n",dev)); + } + saa7146_buffer_next(dev,&dev->vbi_q,1); + + spin_unlock(&dev->slock); +} + +static ssize_t vbi_read(struct file *file, char *data, size_t count, loff_t *ppos) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + ssize_t ret = 0; + + DEB_VBI(("dev:%p, fh:%p\n",dev,fh)); + + if( NULL == dev->vbi_streaming ) { + // fixme: check if dma3 is available + // fixme: activate vbi engine here if necessary. (really?) + dev->vbi_streaming = fh; + } + + if( fh != dev->vbi_streaming ) { + DEB_VBI(("open %p is already using vbi capture.",dev->vbi_streaming)); + return -EBUSY; + } + + mod_timer(&fh->vbi_read_timeout, jiffies+BUFFER_TIMEOUT); + ret = videobuf_read_stream(file, &fh->vbi_q, data, count, ppos, 1); +/* + printk("BASE_ODD3: 0x%08x\n", saa7146_read(dev, BASE_ODD3)); + printk("BASE_EVEN3: 0x%08x\n", saa7146_read(dev, BASE_EVEN3)); + printk("PROT_ADDR3: 0x%08x\n", saa7146_read(dev, PROT_ADDR3)); + printk("PITCH3: 0x%08x\n", saa7146_read(dev, PITCH3)); + printk("BASE_PAGE3: 0x%08x\n", saa7146_read(dev, BASE_PAGE3)); + printk("NUM_LINE_BYTE3: 0x%08x\n", saa7146_read(dev, NUM_LINE_BYTE3)); + printk("BRS_CTRL: 0x%08x\n", saa7146_read(dev, BRS_CTRL)); +*/ + return ret; +} + +struct saa7146_use_ops saa7146_vbi_uops = { + .init = vbi_init, + .open = vbi_open, + .release = vbi_close, + .irq_done = vbi_irq_done, + .read = vbi_read, +}; diff --git a/linux/drivers/media/common/saa7146_video.c b/linux/drivers/media/common/saa7146_video.c new file mode 100644 index 000000000..b92f623eb --- /dev/null +++ b/linux/drivers/media/common/saa7146_video.c @@ -0,0 +1,1297 @@ +#include "saa7146.h" +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) + #define KBUILD_MODNAME "saa7146" +#endif + +int memory = 32; + +#ifdef MODULE +MODULE_PARM(memory,"i"); +MODULE_PARM_DESC(memory, "maximum memory usage for capture buffers (default: 32Mb)"); +#endif + +/* format descriptions for capture and preview */ +struct saa7146_format formats[] = +{ + {"RGB-8 (3-3-2)", V4L2_PIX_FMT_RGB332, RGB08_COMPOSED, 8, 0}, + {"RGB-16 (5-6-5)", V4L2_PIX_FMT_RGB565, RGB16_COMPOSED, 16, 0}, + {"RGB-24 (B-G-R)", V4L2_PIX_FMT_BGR24, RGB24_COMPOSED, 24, 0}, + {"RGB-32 (B-G-R)", V4L2_PIX_FMT_BGR32, RGB32_COMPOSED, 32, 0}, + {"Greyscale-8", V4L2_PIX_FMT_GREY, Y8, 8, 0}, + {"YUV 4:2:2 planar (Y-Cb-Cr)", V4L2_PIX_FMT_YUV422P, YUV422_DECOMPOSED, 16, 0}, + {"YUV 4:2:0 planar (Y-Cb-Cr)", V4L2_PIX_FMT_YVU420, YUV420_DECOMPOSED, 12, 0}, + {"YUV 4:2:2 (U-Y-V-Y)", V4L2_PIX_FMT_UYVY, YUV422_COMPOSED, 16, 0}, + /* V4L2_PIX_FMT_YUYV must always be the last format in the list. the saa7146 (without "a") + is not capable of on-the-fly "byte swapping" (see below), so it does not support this + format... */ + {"YUV 4:2:2 (Y-U-Y-V)", V4L2_PIX_FMT_YUYV, YUV422_COMPOSED, 16, SAA7146_TWO_BYTE_SWAP}, +}; + +/* this is only true for saa7146a. for the saa7146 (without "a") this is NUM_CAPPVFMT-1 */ +int NUM_FORMATS = sizeof(formats)/sizeof(struct saa7146_format); + +struct saa7146_format* format_by_fourcc(struct saa7146_dev *dev, int fourcc) +{ + int i, j = NUM_FORMATS; + + if( 0 == dev->revision ) { + j = NUM_FORMATS-1; + } + + for (i = 0; i < j; i++) { + if (formats[i].pixelformat == fourcc) { + return formats+i; + } + } + + DEB_D(("unknown pixelformat:'%4.4s'\n",(char *)&fourcc)); + return NULL; +} + +static int g_fmt(struct saa7146_fh *fh, struct v4l2_format *f) +{ + struct saa7146_dev *dev = fh->dev; + DEB_EE(("dev:%p, fh:%p\n",dev,fh)); + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + f->fmt.pix = fh->video_fmt; + return 0; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + f->fmt.win = fh->ov.win; + return 0; + case V4L2_BUF_TYPE_VBI_CAPTURE: + { + if( 0 == BOARD_CAN_DO_VBI(dev)) { + DEB_D(("this device does not support vbi.\n")); + return -ENODEV; + } + f->fmt.vbi = fh->vbi_fmt; + return 0; + } + default: + DEB_D(("invalid format type '%d'.\n",f->type)); + return -EINVAL; + } +} + +static int try_win(struct saa7146_dev *dev, struct v4l2_window *win) +{ + enum v4l2_field field; + int maxw, maxh; + + DEB_EE(("dev:%p\n",dev)); + + if (NULL == dev->ov_fb.base) { + DEB_D(("no fb base set.\n")); + return -EINVAL; + } + if (NULL == dev->ov_fmt) { + DEB_D(("no fb fmt set.\n")); + return -EINVAL; + } + if (win->w.width < 64 || win->w.height < 64) { + DEB_D(("min width/height. (%d,%d)\n",win->w.width,win->w.height)); + return -EINVAL; + } + if (win->clipcount > 16) { + DEB_D(("clipcount too big.\n")); + return -EINVAL; + } + + field = win->field; + maxw = dev->standard->h_max_out; + maxh = dev->standard->v_max_out; + + if (V4L2_FIELD_ANY == field) { + field = (win->w.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_TOP; + } + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + break; + default: { + DEB_D(("no known field mode '%d'.\n",field)); + return -EINVAL; + } + } + + win->field = field; + if (win->w.width > maxw) + win->w.width = maxw; + if (win->w.height > maxh) + win->w.height = maxh; + + return 0; +} + +static int try_fmt(struct saa7146_fh *fh, struct v4l2_format *f) +{ + struct saa7146_dev *dev = fh->dev; + int err; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + { + struct saa7146_format *fmt; + enum v4l2_field field; + int maxw, maxh; + + DEB_EE(("V4L2_BUF_TYPE_VIDEO_CAPTURE: dev:%p, fh:%p\n",dev,fh)); + + fmt = format_by_fourcc(dev,f->fmt.pix.pixelformat); + if (NULL == fmt) { + return -EINVAL; + } + + field = f->fmt.pix.field; + maxw = dev->standard->h_max_out; + maxh = dev->standard->v_max_out; + + 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: { + DEB_D(("no known field mode '%d'.\n",field)); + return -EINVAL; + } + } + + f->fmt.pix.field = field; + if (f->fmt.pix.width > maxw) + f->fmt.pix.width = maxw; + if (f->fmt.pix.height > maxh) + f->fmt.pix.height = maxh; + f->fmt.pix.sizeimage = + (f->fmt.pix.width * f->fmt.pix.height * fmt->depth)/8; + return 0; + } + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + DEB_EE(("V4L2_BUF_TYPE_VIDEO_OVERLAY: dev:%p, fh:%p\n",dev,fh)); + err = try_win(dev,&f->fmt.win); + if (0 != err) { + return err; + } + return 0; + default: + DEB_EE(("unknown format type '%d'\n",f->type)); + return -EINVAL; + } +} + +static int start_preview(struct saa7146_fh *fh) +{ + struct saa7146_dev *dev = fh->dev; + int err = 0; + + DEB_EE(("dev:%p, fh:%p\n",dev,fh)); + + /* check if we have overlay informations */ + if( NULL == fh->ov.fh ) { + DEB_D(("not overlay data available. try S_FMT first.\n")); + return -EAGAIN; + } + + /* check if overlay is running */ + if( 0 != dev->ov_data ) { + if( fh != dev->ov_data->fh ) { + DEB_D(("overlay is running in another open.\n")); + return -EAGAIN; + } + DEB_D(("overlay is already active.\n")); + return 0; + } + + if( 0 != dev->streaming ) { + DEB_D(("streaming capture is active.\n")); + return -EBUSY; + } + + err = try_win(dev,&fh->ov.win); + if (0 != err) { + return err; + } + + dev->ov_data = &fh->ov; + + DEB_D(("%dx%d+%d+%d %s field=%s\n", + fh->ov.win.w.width,fh->ov.win.w.height, + fh->ov.win.w.left,fh->ov.win.w.top, + dev->ov_fmt->name,v4l2_field_names[fh->ov.win.field])); + + saa7146_set_overlay(dev, fh, 1); + + return 0; +} + +static int stop_preview(struct saa7146_fh *fh) +{ + struct saa7146_dev *dev = fh->dev; + DEB_EE(("saa7146.o: stop_preview()\n")); + + /* check if overlay is running */ + if( 0 == dev->ov_data ) { + DEB_D(("overlay is not active.\n")); + return 0; + } + + if( fh != dev->ov_data->fh ) { + DEB_D(("overlay is active, but for another open.\n")); + return -EBUSY; + } + + saa7146_set_overlay(dev, fh, 0); + dev->ov_data = NULL; + + return 0; +} + +static int s_fmt(struct saa7146_fh *fh, struct v4l2_format *f) +{ + struct saa7146_dev *dev = fh->dev; + + unsigned long flags; + int err; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + DEB_EE(("V4L2_BUF_TYPE_VIDEO_CAPTURE: dev:%p, fh:%p\n",dev,fh)); + if( fh == dev->streaming ) { + DEB_EE(("streaming capture is active")); + return -EAGAIN; + } + err = try_fmt(fh,f); + if (0 != err) + return err; + fh->video_fmt = f->fmt.pix; + DEB_EE(("set to pixelformat '%4.4s'\n",(char *)&fh->video_fmt.pixelformat)); + return 0; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + DEB_EE(("V4L2_BUF_TYPE_VIDEO_OVERLAY: dev:%p, fh:%p\n",dev,fh)); + err = try_win(dev,&f->fmt.win); + if (0 != err) + return err; + down(&dev->lock); + fh->ov.win = f->fmt.win; + fh->ov.nclips = f->fmt.win.clipcount; + if (fh->ov.nclips > 16) + fh->ov.nclips = 16; + if (copy_from_user(fh->ov.clips,f->fmt.win.clips,sizeof(struct v4l2_clip)*fh->ov.nclips)) { + up(&dev->lock); + return -EFAULT; + } + + /* fh->ov.fh is used to indicate that we have valid overlay informations, too */ + fh->ov.fh = fh; + + /* check if we have an active overlay */ + if( dev->ov_data != NULL ) { + if( fh == dev->ov_data->fh) { + spin_lock_irqsave(&dev->slock,flags); + stop_preview(fh); + start_preview(fh); + spin_unlock_irqrestore(&dev->slock,flags); + } + } + up(&dev->lock); + return 0; + default: + DEB_D(("unknown format type '%d'\n",f->type)); + return -EINVAL; + } +} + +/********************************************************************************/ +/* device controls */ + +static struct v4l2_queryctrl controls[] = { + { + id: V4L2_CID_BRIGHTNESS, + name: "Brightness", + minimum: 0, + maximum: 255, + step: 1, + default_value: 128, + type: V4L2_CTRL_TYPE_INTEGER, + },{ + id: V4L2_CID_CONTRAST, + name: "Contrast", + minimum: 0, + maximum: 127, + step: 1, + default_value: 64, + type: V4L2_CTRL_TYPE_INTEGER, + },{ + id: V4L2_CID_SATURATION, + name: "Saturation", + minimum: 0, + maximum: 127, + step: 1, + default_value: 64, + type: V4L2_CTRL_TYPE_INTEGER, + },{ + id: V4L2_CID_VFLIP, + name: "Vertical flip", + minimum: 0, + maximum: 1, + type: V4L2_CTRL_TYPE_BOOLEAN, + },{ + id: V4L2_CID_HFLIP, + name: "Horizontal flip", + minimum: 0, + maximum: 1, + type: V4L2_CTRL_TYPE_BOOLEAN, + }, +}; +static int NUM_CONTROLS = sizeof(controls)/sizeof(struct v4l2_queryctrl); + +#define V4L2_CID_PRIVATE_LASTP1 (V4L2_CID_PRIVATE_BASE + 0) + +static struct v4l2_queryctrl* ctrl_by_id(int id) +{ + int i; + + for (i = 0; i < NUM_CONTROLS; i++) + if (controls[i].id == id) + return controls+i; + return NULL; +} + +static int get_control(struct saa7146_fh *fh, struct v4l2_control *c) +{ + struct saa7146_dev *dev = fh->dev; + const struct v4l2_queryctrl* ctrl; + u32 value = 0; + + ctrl = ctrl_by_id(c->id); + if (NULL == ctrl) + return -EINVAL; + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + value = saa7146_read(dev, BCS_CTRL); + c->value = 0xff & (value >> 24); + break; + case V4L2_CID_CONTRAST: + value = saa7146_read(dev, BCS_CTRL); + c->value = 0x7f & (value >> 16); + break; + case V4L2_CID_SATURATION: + value = saa7146_read(dev, BCS_CTRL); + c->value = 0x7f & (value >> 0); + break; + case V4L2_CID_VFLIP: + c->value = dev->vflip; + break; + case V4L2_CID_HFLIP: + c->value = dev->hflip; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int set_control(struct saa7146_fh *fh, struct v4l2_control *c) +{ + struct saa7146_dev *dev = fh->dev; + const struct v4l2_queryctrl* ctrl; + unsigned long flags; + int restart_overlay = 0; + + ctrl = ctrl_by_id(c->id); + if (NULL == ctrl) { + DEB_D(("unknown control %d\n",c->id)); + return -EINVAL; + } + + 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: { + u32 value = saa7146_read(dev, BCS_CTRL); + value &= 0x00ffffff; + value |= (c->value << 24); + saa7146_write(dev, BCS_CTRL, value); + saa7146_write(dev, MC2, MASK_22 | MASK_06 ); + break; + } + case V4L2_CID_CONTRAST: { + u32 value = saa7146_read(dev, BCS_CTRL); + value &= 0xff00ffff; + value |= (c->value << 16); + saa7146_write(dev, BCS_CTRL, value); + saa7146_write(dev, MC2, MASK_22 | MASK_06 ); + break; + } + case V4L2_CID_SATURATION: { + u32 value = saa7146_read(dev, BCS_CTRL); + value &= 0xffffff00; + value |= (c->value << 0); + saa7146_write(dev, BCS_CTRL, value); + saa7146_write(dev, MC2, MASK_22 | MASK_06 ); + break; + } + case V4L2_CID_HFLIP: + /* fixme: we can supfhrt changing VFLIP and HFLIP here... */ + if( 0 != dev->streaming ) { + DEB_D(("V4L2_CID_HFLIP while active capture.\n")); + return -EINVAL; + } + dev->hflip = c->value; + restart_overlay = 1; + break; + case V4L2_CID_VFLIP: + if( 0 != dev->streaming ) { + DEB_D(("V4L2_CID_VFLIP while active capture.\n")); + return -EINVAL; + } + dev->vflip = c->value; + restart_overlay = 1; + break; + default: { + return -EINVAL; + } + } + if( 0 != restart_overlay ) { + if( 0 != dev->ov_data ) { + if( fh == dev->ov_data->fh ) { + spin_lock_irqsave(&dev->slock,flags); + stop_preview(fh); + start_preview(fh); + spin_unlock_irqrestore(&dev->slock,flags); + } + } + } + return 0; +} + +/********************************************************************************/ +/* common pagetable functions */ + +int saa7146_pgtable_build(struct saa7146_dev *dev, struct saa7146_buf *buf) +{ + struct pci_dev *pci = dev->pci; + struct scatterlist *list = buf->vb.dma.sglist; + int length = buf->vb.dma.sglen; + struct saa7146_format *sfmt = format_by_fourcc(dev,buf->fmt->pixelformat); + + DEB_EE(("dev:%p, buf:%p\n",dev,buf)); + + if( 0 != IS_PLANAR(sfmt->trans)) { + struct saa7146_pgtable *pt1 = &buf->pt[0]; + struct saa7146_pgtable *pt2 = &buf->pt[1]; + struct saa7146_pgtable *pt3 = &buf->pt[2]; + u32 *ptr1, *ptr2, *ptr3; + u32 fill; + + int size = ((buf->fmt->width*buf->fmt->height*sfmt->depth)/8)/2; + int i,p,m1,m2,m3,o1,o2; + + ptr1 = pt1->cpu; + ptr2 = pt2->cpu; + ptr3 = pt3->cpu; + + /* walk all pages, copy all page addresses to ptr1 */ + for (i = 0; i < length; i++, list++) { + for (p = 0; p * 4096 < list->length; p++, ptr1++) { + *ptr1 = sg_dma_address(list) - list->offset; + } + } +/* + ptr1 = pt1->cpu; + for(j=0;j<40;j++) { + printk("ptr1 %d: 0x%08x\n",j,ptr1[j]); + } +*/ + /* create some offsets inside the page table */ + m1 = ((size+PAGE_SIZE-1)/PAGE_SIZE)-1; + m2 = ((size+(size/2)+PAGE_SIZE-1)/PAGE_SIZE)-1; + m3 = ((2*size+PAGE_SIZE-1)/PAGE_SIZE)-1; + + o1 = size%PAGE_SIZE; + o2 = (size+(size/2))%PAGE_SIZE; + + /* if we have a user buffer, the first page may not be + aligned to a page boundary. */ + pt1->offset = buf->vb.dma.sglist->offset; + pt2->offset = pt1->offset+o1; + pt3->offset = pt1->offset+o2; + + /* create video-dma2 page table */ + ptr1 = pt1->cpu; + for(i = m1; i <= m2 ; i++, ptr2++) { + *ptr2 = ptr1[i]; + } + fill = *(ptr2-1); + for(;i<1024;i++,ptr2++) { + *ptr2 = fill; + } + /* create video-dma3 page table */ + ptr1 = pt1->cpu; + for(i = m2; i <= m3; i++,ptr3++) { + *ptr3 = ptr1[i]; + } + fill = *(ptr3-1); + for(;i<1024;i++,ptr3++) { + *ptr3 = fill; + } + /* finally: finish up video-dma1 page table */ + ptr1 = pt1->cpu+m1; + fill = pt1->cpu[m1]; + for(i=m1;i<1024;i++,ptr1++) { + *ptr1 = fill; + } +/* + ptr1 = pt1->cpu; + ptr2 = pt2->cpu; + ptr3 = pt3->cpu; + for(j=0;j<40;j++) { + printk("ptr1 %d: 0x%08x\n",j,ptr1[j]); + } + for(j=0;j<40;j++) { + printk("ptr2 %d: 0x%08x\n",j,ptr2[j]); + } + for(j=0;j<40;j++) { + printk("ptr3 %d: 0x%08x\n",j,ptr3[j]); + } +*/ + } else { + struct saa7146_pgtable *pt = &buf->pt[0]; + saa7146_pgtable_build_single(pci, pt, list, length); + } + + return 0; +} + + +/********************************************************************************/ +/* file operations */ + +int video_begin(struct saa7146_fh *fh) +{ + struct saa7146_dev *dev = fh->dev; + unsigned long flags; + + DEB_EE(("dev:%p, fh:%p\n",dev,fh)); + + if( fh == dev->streaming ) { + DEB_S(("already capturing.\n")); + return 0; + } + if( dev->streaming != 0 ) { + DEB_S(("already capturing, but in another open.\n")); + return -EBUSY; + } + + /* fixme: check for planar formats here, if we will interfere with + vbi capture for example */ + + spin_lock_irqsave(&dev->slock,flags); + + /* clear out beginning of streaming bit */ + saa7146_write(dev, MC2, MASK_27 ); + + /* enable rps0 irqs */ + IER_ENABLE(dev, MASK_27); + + dev->streaming = fh; + spin_unlock_irqrestore(&dev->slock,flags); + return 0; +} + +int video_end(struct saa7146_fh *fh) +{ + struct saa7146_dev *dev = fh->dev; + unsigned long flags; + + DEB_EE(("dev:%p, fh:%p\n",dev,fh)); + + if( dev->streaming != fh ) { + DEB_S(("not capturing.\n")); + return -EINVAL; + } + + spin_lock_irqsave(&dev->slock,flags); + + /* disable rps0 */ + saa7146_write(dev, MC1, MASK_28); + + /* disable rps0 irqs */ + IER_DISABLE(dev, MASK_27); + + // fixme: only used formats here! + /* fixme: look at planar formats here, especially at the + shutdown of planar formats! */ + + /* shut down all used video dma transfers */ + /* fixme: what about the budget-dvb cards? they use + video-dma3, but video_end should not get called anyway ...*/ + saa7146_write(dev, MC1, 0x00700000); + + dev->streaming = NULL; + spin_unlock_irqrestore(&dev->slock, flags); + + return 0; +} + +/* + * This function is _not_ called directly, but from + * video_generic_ioctl (and maybe others). userspace + * copying is done already, arg is a kernel fhinter. + */ + +int video_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + + unsigned long flags; + int err = 0, result = 0, ee = 0; + + struct saa7146_use_ops *ops; + struct videobuf_queue *q; + + /* check if extension handles the command */ + for(ee = 0; dev->ext->ioctls[ee].flags != 0; ee++) { + if( cmd == dev->ext->ioctls[ee].cmd ) + break; + } + + if( 0 != (dev->ext->ioctls[ee].flags & SAA7146_EXCLUSIVE) ) { + DEB_D(("extension handles ioctl exclusive.\n")); + result = dev->ext->ioctl(dev, cmd, arg); + return result; + } + if( 0 != (dev->ext->ioctls[ee].flags & SAA7146_BEFORE) ) { + DEB_D(("extension handles ioctl before.\n")); + result = dev->ext->ioctl(dev, cmd, arg); + if( -EAGAIN != result ) { + return result; + } + } + + /* fixme: add handle "after" case (is it still needed?) */ + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: { + ops = &saa7146_video_uops; + q = &fh->video_q; + break; + } + case V4L2_BUF_TYPE_VBI_CAPTURE: { + ops = &saa7146_vbi_uops; + q = &fh->vbi_q; + break; + } + default: + BUG(); + return 0; + } + + switch (cmd) { + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + memset(cap,0,sizeof(*cap)); + + DEB_EE(("VIDIOC_QUERYCAP\n")); + + strcpy(cap->driver, "saa7146 v4l2"); + strncpy(cap->card, dev->ext->name, sizeof(cap->card)); + sprintf(cap->bus_info,"PCI:%s",dev->pci->slot_name); + cap->version = SAA7146_VERSION_CODE; + cap->capabilities = + V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VIDEO_OVERLAY | + V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; + if( BOARD_CAN_DO_VBI(dev) != 0) { + cap->capabilities |= V4L2_CAP_VBI_CAPTURE; + } + cap->capabilities |= dev->ext->capabilities; + return 0; + } + case VIDIOC_G_FBUF: + { + struct v4l2_framebuffer *fb = arg; + + DEB_EE(("VIDIOC_G_FBUF\n")); + + *fb = dev->ov_fb; + fb->capability = V4L2_FBUF_CAP_LIST_CLIPPING; + return 0; + } + case VIDIOC_S_FBUF: + { + struct v4l2_framebuffer *fb = arg; + struct saa7146_format *fmt; + + DEB_EE(("VIDIOC_S_FBUF\n")); + +/* + if(!capable(CAP_SYS_ADMIN)) { // && !capable(CAP_SYS_RAWIO)) { + DEB_D(("VIDIOC_S_FBUF: not CAP_SYS_ADMIN or CAP_SYS_RAWIO.\n")); + return -EPERM; + } +*/ + if( 0 != dev->ov_data ) { + DEB_D(("VIDIOC_S_FBUF: overlay is active.\n")); + return -EPERM; + } + + /* check args */ + fmt = format_by_fourcc(dev,fb->fmt.pixelformat); + if (NULL == fmt) { + return -EINVAL; + } + + /* ok, accept it */ + dev->ov_fb = *fb; + dev->ov_fmt = fmt; + if (0 == dev->ov_fb.fmt.bytesperline) + dev->ov_fb.fmt.bytesperline = + dev->ov_fb.fmt.width*fmt->depth/8; + return 0; + } + case VIDIOC_ENUM_FMT: + { + struct v4l2_fmtdesc *f = arg; + int index; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + index = f->index; + if (index < 0 || index >= NUM_FORMATS) { + return -EINVAL; + } + memset(f,0,sizeof(*f)); + f->index = index; + strncpy(f->description,formats[index].name,31); + f->pixelformat = formats[index].pixelformat; + break; + default: + return -EINVAL; + } + + DEB_EE(("VIDIOC_ENUMSTD: type:%d, index:%d\n",f->type,f->index)); + return 0; + } + case VIDIOC_QUERYCTRL: + { + const struct v4l2_queryctrl *ctrl; + struct v4l2_queryctrl *c = arg; + + if ((c->id < V4L2_CID_BASE || + c->id >= V4L2_CID_LASTP1) && + (c->id < V4L2_CID_PRIVATE_BASE || + c->id >= V4L2_CID_PRIVATE_LASTP1)) + return -EINVAL; + + ctrl = ctrl_by_id(c->id); + if( NULL == ctrl ) { + c->flags = V4L2_CTRL_FLAG_DISABLED; + return 0; + } + + DEB_EE(("VIDIOC_QUERYCTRL: id:%d\n",c->id)); + *c = *ctrl; + return 0; + } + case VIDIOC_G_CTRL: { + DEB_EE(("VIDIOC_G_CTRL\n")); + return get_control(fh,arg); + } + case VIDIOC_S_CTRL: + { + DEB_EE(("VIDIOC_S_CTRL\n")); + down(&dev->lock); + err = set_control(fh,arg); + up(&dev->lock); + return err; + } + case VIDIOC_G_PARM: + { + struct v4l2_streamparm *parm = arg; + if( parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ) { + return -EINVAL; + } + memset(&parm->parm.capture,0,sizeof(struct v4l2_captureparm)); + parm->parm.capture.readbuffers = 1; + // fixme: only for PAL! + parm->parm.capture.timeperframe.numerator = 1; + parm->parm.capture.timeperframe.denominator = 25; + return 0; + } + case VIDIOC_G_FMT: + { + struct v4l2_format *f = arg; + DEB_EE(("VIDIOC_G_FMT\n")); + return g_fmt(fh,f); + } + case VIDIOC_S_FMT: + { + struct v4l2_format *f = arg; + DEB_EE(("VIDIOC_S_FMT\n")); + return s_fmt(fh,f); + } + case VIDIOC_TRY_FMT: + { + struct v4l2_format *f = arg; + DEB_EE(("VIDIOC_TRY_FMT\n")); + return try_fmt(fh,f); + } + case VIDIOC_G_STD: + { + v4l2_std_id *id = arg; + DEB_EE(("VIDIOC_G_STD\n")); + *id = dev->standard->id; + return 0; + } + /* the saa7146 supfhrts (used in conjunction with the saa7111a for example) + PAL / NTSC / SECAM. if your hardware does not (or does more) + -- override this function in your extension */ + case VIDIOC_ENUMSTD: + { + struct v4l2_standard *e = arg; + if (e->index < 0 ) + return -EINVAL; + if( e->index < dev->ext->num_stds ) { + DEB_EE(("VIDIOC_ENUMSTD: index:%d\n",e->index)); + return v4l2_video_std_construct(e, dev->ext->stds[e->index].id, dev->ext->stds[e->index].name); + } + return -EINVAL; + } + case VIDIOC_S_STD: + { + v4l2_std_id *id = arg; + int i; + + int restart_overlay = 0; + int found = 0; + + struct saa7146_fh *ov_fh = NULL; + + if( 0 != dev->streaming ) { + return -EBUSY; + } + + down(&dev->lock); + + if( dev->ov_data != NULL ) { + ov_fh = dev->ov_data->fh; + stop_preview(ov_fh); + restart_overlay = 1; + } + + for(i = 0; i < dev->ext->num_stds; i++) + if (*id & dev->ext->stds[i].id) + break; + if (i != dev->ext->num_stds) { + dev->standard = &dev->ext->stds[i]; + if( NULL != dev->ext->std_callback ) + dev->ext->std_callback(dev, dev->standard); + found = 1; + } + + if( 0 != restart_overlay ) { + start_preview(ov_fh); + } + up(&dev->lock); + + if( 0 == found ) { + DEB_EE(("VIDIOC_S_STD: standard not found.\n")); + return -EINVAL; + } + + DEB_EE(("VIDIOC_S_STD: set to standard to '%s'\n",dev->standard->name)); + return 0; + } + case VIDIOC_OVERLAY: + { + int on = *(int *)arg; + int err = 0; + + if( NULL == dev->ov_fmt ) { + DEB_D(("VIDIOC_OVERLAY: no framebuffer informations. call S_FBUF first!\n")); + return -EAGAIN; + } + + DEB_D(("VIDIOC_OVERLAY on:%d\n",on)); + if( 0 != on ) { + if( dev->ov_data != NULL ) { + if( fh != dev->ov_data->fh) { + return -EAGAIN; + } + } + spin_lock_irqsave(&dev->slock,flags); + err = start_preview(fh); + spin_unlock_irqrestore(&dev->slock,flags); + } else { + if( dev->ov_data != NULL ) { + if( fh != dev->ov_data->fh) { + return -EAGAIN; + } + } + spin_lock_irqsave(&dev->slock,flags); + err = stop_preview(fh); + spin_unlock_irqrestore(&dev->slock,flags); + } + return err; + } + case VIDIOC_REQBUFS: { + DEB_D(("VIDIOC_REQBUFS \n")); + return videobuf_reqbufs(file,q,arg); + } + case VIDIOC_QUERYBUF: { + DEB_D(("VIDIOC_QUERYBUF \n")); + return videobuf_querybuf(q,arg); + } + case VIDIOC_QBUF: { + DEB_D(("VIDIOC_QBUF \n")); + return videobuf_qbuf(file,q,arg); + } + case VIDIOC_DQBUF: { + DEB_D(("VIDIOC_DQBUF \n")); + return videobuf_dqbuf(file,q,arg); + } + case VIDIOC_STREAMON: { + DEB_D(("VIDIOC_STREAMON \n")); + if( 0 != ops->capture_begin ) { + if( 0 != (err = ops->capture_begin(fh))) { + return err; + } + } + err = videobuf_streamon(file,q); + return err; + } + case VIDIOC_STREAMOFF: { + DEB_D(("VIDIOC_STREAMOFF \n")); + if( 0 != ops->capture_end ) { + ops->capture_end(fh); + } + err = videobuf_streamoff(file,q); + return 0; + } + case VIDIOCGMBUF: + { + struct video_mbuf *mbuf = arg; + struct videobuf_queue *q; + int i; + + /* fixme: number of capture buffers and sizes for v4l apps */ + int gbuffers = 2; + int gbufsize = 768*576*4; + + DEB_D(("VIDIOCGMBUF \n")); + + q = &fh->video_q; + down(&q->lock); + err = videobuf_mmap_setup(file,q,gbuffers,gbufsize); + if (err < 0) { + up(&q->lock); + return err; + } + memset(mbuf,0,sizeof(*mbuf)); + mbuf->frames = gbuffers; + mbuf->size = gbuffers * gbufsize; + for (i = 0; i < gbuffers; i++) + mbuf->offsets[i] = i * gbufsize; + up(&q->lock); + return 0; + } + default: + return v4l_compat_translate_ioctl(inode,file,cmd,arg, + video_do_ioctl); + } + return 0; +#ifdef axa + not supfhrted: + VIDIOC_RESERVED + VIDIOC_S_PARM + VIDIOC_G_OUTPUT + VIDIOC_S_OUTPUT + VIDIOC_CROPCAP + VIDIOC_G_CROP + VIDIOC_S_CROP + VIDIOC_G_JPEGCOMP + VIDIOC_S_JPEGCOMP + VIDIOC_ENUMOUTPUT + VIDIOC_G_AUDOUT + VIDIOC_S_AUDOUT + + VIDIOC_QUERYMENU + VIDIOC_G_MODULATOR + VIDIOC_S_MODULATOR + VIDIOC_QUERYSTD + /* --- control ioctls ---------------------------------------- */ +#endif +} + + +/*********************************************************************************/ +/* buffer handling functions */ + +static int buffer_activate(struct saa7146_dev *dev, + struct saa7146_buf *buf, + struct saa7146_buf *next) +{ + buf->vb.state = STATE_ACTIVE; + saa7146_set_capture(dev,buf,next); + + mod_timer(&dev->video_q.timeout, jiffies+BUFFER_TIMEOUT); + return 0; +} + +static int buffer_prepare(struct file *file, struct videobuf_buffer *vb) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + struct saa7146_buf *buf = (struct saa7146_buf *)vb; + int size,err = 0; + + /* sanity checks */ + if (fh->video_fmt.width < 64 || + fh->video_fmt.height < 64 || + fh->video_fmt.width > dev->standard->h_max_out || + fh->video_fmt.height > dev->standard->v_max_out) { + DEB_D(("w (%d) / h (%d) out of bounds.\n",fh->video_fmt.width,fh->video_fmt.height)); + return -EINVAL; + } + + size = fh->video_fmt.sizeimage; + if (0 != buf->vb.baddr && buf->vb.bsize < size) { + DEB_D(("size mismatch.\n")); + return -EINVAL; + } + + DEB_CAP(("buffer_prepare [size=%dx%d,bytes=%d,fields=%s]\n", + fh->video_fmt.width,fh->video_fmt.height,size,v4l2_field_names[fh->video_fmt.field])); + if (buf->vb.width != fh->video_fmt.width || + buf->vb.height != fh->video_fmt.height || + buf->vb.size != size || + buf->vb.field != fh->video_fmt.field || + buf->fmt != &fh->video_fmt) { + saa7146_dma_free(dev,buf); + } + + if (STATE_NEEDS_INIT == buf->vb.state) { + struct saa7146_format *sfmt; + + buf->vb.width = fh->video_fmt.width; + buf->vb.height = fh->video_fmt.height; + buf->vb.size = size; + buf->fmt = &fh->video_fmt; + buf->vb.field = fh->video_fmt.field; + + sfmt = format_by_fourcc(dev,buf->fmt->pixelformat); + + if( 0 != IS_PLANAR(sfmt->trans)) { + saa7146_pgtable_free(dev->pci, &buf->pt[0]); + saa7146_pgtable_free(dev->pci, &buf->pt[1]); + saa7146_pgtable_free(dev->pci, &buf->pt[2]); + + saa7146_pgtable_alloc(dev->pci, &buf->pt[0]); + saa7146_pgtable_alloc(dev->pci, &buf->pt[1]); + saa7146_pgtable_alloc(dev->pci, &buf->pt[2]); + } else { + saa7146_pgtable_free(dev->pci, &buf->pt[0]); + saa7146_pgtable_alloc(dev->pci, &buf->pt[0]); + } + + err = videobuf_iolock(dev->pci,&buf->vb); + if (err) + goto oops; + err = saa7146_pgtable_build(dev,buf); + if (err) + goto oops; + } + buf->vb.state = STATE_PREPARED; + buf->activate = buffer_activate; + + return 0; + + oops: + DEB_D(("error out.\n")); + saa7146_dma_free(dev,buf); + + return err; +} + +static int buffer_setup(struct file *file, int *count, int *size) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + + if (0 == *count || *count > MAX_SAA7146_CAPTURE_BUFFERS) + *count = MAX_SAA7146_CAPTURE_BUFFERS; + + *size = fh->video_fmt.sizeimage; + + /* check if we exceed the "memory" parameter */ + if( (*count * *size) > (memory*1048576) ) { + *count = (memory*1048576) / *size; + } + + DEB_CAP(("%d buffers, %d bytes each.\n",*count,*size)); + + return 0; +} + +static void buffer_queue(struct file *file, struct videobuf_buffer *vb) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + struct saa7146_buf *buf = (struct saa7146_buf *)vb; + + DEB_CAP(("vbuf:%p\n",vb)); + saa7146_buffer_queue(fh->dev,&fh->dev->video_q,buf); +} + +static void buffer_release(struct file *file, struct videobuf_buffer *vb) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + struct saa7146_buf *buf = (struct saa7146_buf *)vb; + + DEB_CAP(("vbuf:%p\n",vb)); + saa7146_dma_free(dev,buf); +} + +static struct videobuf_queue_ops video_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +/********************************************************************************/ +/* file operations */ + +struct saa7146_standard standard[] = { + { "PAL-BG", V4L2_STD_PAL_BG, SAA7146_PAL_VALUES }, +}; + +void video_init(struct saa7146_dev *dev) +{ + INIT_LIST_HEAD(&dev->video_q.queue); + dev->video_q.timeout.function = saa7146_buffer_timeout; + dev->video_q.timeout.data = (unsigned long)(&dev->video_q); + dev->video_q.dev = dev; + + /* set some default values */ + dev->standard = &standard[0]; +} + +void video_open(struct saa7146_dev *dev, struct saa7146_fh *fh) +{ + struct saa7146_format *sfmt; + + fh->video_fmt.width = 384; + fh->video_fmt.height = 288; + fh->video_fmt.pixelformat = V4L2_PIX_FMT_BGR24; + fh->video_fmt.field = V4L2_FIELD_ANY; + sfmt = format_by_fourcc(dev,fh->video_fmt.pixelformat); + fh->video_fmt.sizeimage = (fh->video_fmt.width * fh->video_fmt.height * sfmt->depth)/8; + + videobuf_queue_init(&fh->video_q, &video_qops, + dev->pci, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + sizeof(struct saa7146_buf)); + + init_MUTEX(&fh->video_q.lock); +} + +void video_close(struct saa7146_dev *dev, struct saa7146_fh *fh, struct file *file) +{ + unsigned long flags; + + if( 0 != dev->ov_data ) { + if( fh == dev->ov_data->fh ) { + spin_lock_irqsave(&dev->slock,flags); + stop_preview(fh); + spin_unlock_irqrestore(&dev->slock,flags); + } + } + + if( fh == dev->streaming ) { + video_end(fh); + } + + videobuf_queue_cancel(file,&fh->video_q); +} + +void video_irq_done(struct saa7146_dev *dev, unsigned long st) +{ + struct saa7146_dmaqueue *q = &dev->video_q; + + spin_lock(&dev->slock); + DEB_CAP(("called.\n")); + + /* only finish the buffer if we have one... */ + if( NULL != q->curr ) { + saa7146_buffer_finish(dev,q,STATE_DONE); + } + saa7146_buffer_next(dev,q,0); + + spin_unlock(&dev->slock); +} + +static ssize_t video_read(struct file *file, char *data, size_t count, loff_t *ppos) +{ + struct saa7146_fh *fh = file->private_data; + struct saa7146_dev *dev = fh->dev; + ssize_t ret = 0; + + DEB_EE(("called.\n")); + + if( 0 != video_begin(fh)) { + return -EAGAIN; + } + ret = videobuf_read_one(file,&fh->video_q , data, count, ppos); + video_end(fh); + + /* restart overlay if it was active before */ + if( 0 != dev->ov_data) { + start_preview(dev->ov_data->fh); + } + + return ret; +} + +struct saa7146_use_ops saa7146_video_uops = { + .init = video_init, + .open = video_open, + .release = video_close, + .irq_done = video_irq_done, + .read = video_read, + .capture_begin = video_begin, + .capture_end = video_end, +}; |