diff options
author | Hans Verkuil <hans.verkuil@cisco.com> | 2014-09-03 03:31:07 -0300 |
---|---|---|
committer | Mauro Carvalho Chehab <m.chehab@samsung.com> | 2014-09-04 11:25:01 -0300 |
commit | 5740f4e75f713015067e2667a52bd3b35ef91e07 (patch) | |
tree | 9b58a95433a0e3db4fa4f152e8ef2e76e036efc3 /drivers/media/pci | |
parent | 89fffac802c18caebdf4e91c0785b522c9f6399a (diff) |
[media] tw68: add original tw68 code
This tw68 driver has been out-of-tree for many years on gitorious:
https://gitorious.org/tw68/tw68-v2.
This copies that code to the kernel as a record of that original code.
Note that William Brack's email address in these sources is no longer
valid and I have not been able to contact him. However, all the code is
standard GPL.
Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
Signed-off-by: Mauro Carvalho Chehab <m.chehab@samsung.com>
Diffstat (limited to 'drivers/media/pci')
-rw-r--r-- | drivers/media/pci/tw68/tw68-cards.c | 172 | ||||
-rw-r--r-- | drivers/media/pci/tw68/tw68-core.c | 1091 | ||||
-rw-r--r-- | drivers/media/pci/tw68/tw68-i2c.c | 245 | ||||
-rw-r--r-- | drivers/media/pci/tw68/tw68-reg.h | 195 | ||||
-rw-r--r-- | drivers/media/pci/tw68/tw68-risc.c | 268 | ||||
-rw-r--r-- | drivers/media/pci/tw68/tw68-ts.c | 66 | ||||
-rw-r--r-- | drivers/media/pci/tw68/tw68-tvaudio.c | 80 | ||||
-rw-r--r-- | drivers/media/pci/tw68/tw68-vbi.c | 76 | ||||
-rw-r--r-- | drivers/media/pci/tw68/tw68-video.c | 2230 | ||||
-rw-r--r-- | drivers/media/pci/tw68/tw68.h | 588 |
10 files changed, 5011 insertions, 0 deletions
diff --git a/drivers/media/pci/tw68/tw68-cards.c b/drivers/media/pci/tw68/tw68-cards.c new file mode 100644 index 000000000000..62aec4faa0d1 --- /dev/null +++ b/drivers/media/pci/tw68/tw68-cards.c @@ -0,0 +1,172 @@ +/* + * device driver for Techwell 68xx based cards + * + * Much of this code is derived from the cx88 and sa7134 drivers, which + * were in turn derived from the bt87x driver. The original work was by + * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab, + * Hans Verkuil, Andy Walls and many others. Their work is gratefully + * acknowledged. Full credit goes to them - any problems within this code + * are mine. + * + * Copyright (C) 2009 William M. Brack <wbrack@mmm.com.hk> + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/i2c.h> /* must appear before i2c-algo-bit.h */ +#include <linux/i2c-algo-bit.h> + +#include <media/v4l2-common.h> +#include <media/tveeprom.h> + +#include "tw68.h" +#include "tw68-reg.h" + +/* commly used strings */ +#if 0 +static char name_mute[] = "mute"; +static char name_radio[] = "Radio"; +static char name_tv[] = "Television"; +static char name_tv_mono[] = "TV (mono only)"; +static char name_svideo[] = "S-Video"; +static char name_comp[] = "Composite"; +#endif +static char name_comp1[] = "Composite1"; +static char name_comp2[] = "Composite2"; +static char name_comp3[] = "Composite3"; +static char name_comp4[] = "Composite4"; + +/* ------------------------------------------------------------------ */ +/* board config info */ + +/* If radio_type !=UNSET, radio_addr should be specified + */ + +struct tw68_board tw68_boards[] = { + [TW68_BOARD_UNKNOWN] = { + .name = "GENERIC", + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + + .inputs = { + { + .name = name_comp1, + .vmux = 0, + }, { + .name = name_comp2, + .vmux = 1, + }, { + .name = name_comp3, + .vmux = 2, + }, { + .name = name_comp4, + .vmux = 3, + }, { /* Must have a NULL entry at end of list */ + .name = NULL, + .vmux = 0, + } + }, + }, +}; + +const unsigned int tw68_bcount = ARRAY_SIZE(tw68_boards); + +/* + * Please add any new PCI IDs to: http://pci-ids.ucw.cz. This keeps + * the PCI ID database up to date. Note that the entries must be + * added under vendor 0x1797 (Techwell Inc.) as subsystem IDs. + */ +struct pci_device_id tw68_pci_tbl[] = { + { + .vendor = PCI_VENDOR_ID_TECHWELL, + .device = PCI_DEVICE_ID_6800, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = TW68_BOARD_UNKNOWN, + }, { + .vendor = PCI_VENDOR_ID_TECHWELL, + .device = PCI_DEVICE_ID_6801, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = TW68_BOARD_UNKNOWN, + }, { + .vendor = PCI_VENDOR_ID_TECHWELL, + .device = PCI_DEVICE_ID_6804, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = TW68_BOARD_UNKNOWN, + }, { + .vendor = PCI_VENDOR_ID_TECHWELL, + .device = PCI_DEVICE_ID_6816_1, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = TW68_BOARD_UNKNOWN, + }, { + .vendor = PCI_VENDOR_ID_TECHWELL, + .device = PCI_DEVICE_ID_6816_2, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = TW68_BOARD_UNKNOWN, + }, { + .vendor = PCI_VENDOR_ID_TECHWELL, + .device = PCI_DEVICE_ID_6816_3, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = TW68_BOARD_UNKNOWN, + }, { + .vendor = PCI_VENDOR_ID_TECHWELL, + .device = PCI_DEVICE_ID_6816_4, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = TW68_BOARD_UNKNOWN, + }, { + /* end of list */ + } +}; +MODULE_DEVICE_TABLE(pci, tw68_pci_tbl); + +/* ------------------------------------------------------------ */ +/* stuff done before i2c enabled */ +int tw68_board_init1(struct tw68_dev *dev) +{ + /* Clear GPIO outputs */ + tw_writel(TW68_GPOE, 0); + /* Remainder of setup according to board ID */ + switch (dev->board) { + case TW68_BOARD_UNKNOWN: + printk(KERN_INFO "%s: Unable to determine board type, " + "using generic values\n", dev->name); + break; + } + dev->input = dev->hw_input = &card_in(dev,0); + return 0; +} + +int tw68_tuner_setup(struct tw68_dev *dev) +{ + return 0; +} + +/* stuff which needs working i2c */ +int tw68_board_init2(struct tw68_dev *dev) +{ + return 0; +} + + diff --git a/drivers/media/pci/tw68/tw68-core.c b/drivers/media/pci/tw68/tw68-core.c new file mode 100644 index 000000000000..2c5d7a5f3f8e --- /dev/null +++ b/drivers/media/pci/tw68/tw68-core.c @@ -0,0 +1,1091 @@ +/* + * tw68-core.c + * Core functions for the Techwell 68xx driver + * + * Much of this code is derived from the cx88 and sa7134 drivers, which + * were in turn derived from the bt87x driver. The original work was by + * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab, + * Hans Verkuil, Andy Walls and many others. Their work is gratefully + * acknowledged. Full credit goes to them - any problems within this code + * are mine. + * + * Copyright (C) 2009 William M. Brack <wbrack@mmm.com.hk> + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/kmod.h> +#include <linux/sound.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/dma-mapping.h> +#include <linux/pm.h> + +#include <media/v4l2-dev.h> +#include "tw68.h" +#include "tw68-reg.h" + +MODULE_DESCRIPTION("v4l2 driver module for tw6800 based video capture cards"); +MODULE_AUTHOR("William M. Brack <wbrack@mmm.com.hk>"); +MODULE_LICENSE("GPL"); + +static unsigned int core_debug; +module_param(core_debug, int, 0644); +MODULE_PARM_DESC(core_debug, "enable debug messages [core]"); + +static unsigned int gpio_tracking; +module_param(gpio_tracking, int, 0644); +MODULE_PARM_DESC(gpio_tracking, "enable debug messages [gpio]"); + +static unsigned int alsa = 1; +module_param(alsa, int, 0644); +MODULE_PARM_DESC(alsa, "enable/disable ALSA DMA sound [dmasound]"); + +static unsigned int latency = UNSET; +module_param(latency, int, 0444); +MODULE_PARM_DESC(latency, "pci latency timer"); + +static unsigned int nocomb; +module_param(nocomb, int, 0644); +MODULE_PARM_DESC(nocomb, "disable comb filter"); + +static unsigned int video_nr[] = {[0 ... (TW68_MAXBOARDS - 1)] = UNSET }; +static unsigned int vbi_nr[] = {[0 ... (TW68_MAXBOARDS - 1)] = UNSET }; +static unsigned int radio_nr[] = {[0 ... (TW68_MAXBOARDS - 1)] = UNSET }; +static unsigned int tuner[] = {[0 ... (TW68_MAXBOARDS - 1)] = UNSET }; +static unsigned int card[] = {[0 ... (TW68_MAXBOARDS - 1)] = UNSET }; + +module_param_array(video_nr, int, NULL, 0444); +module_param_array(vbi_nr, int, NULL, 0444); +module_param_array(radio_nr, int, NULL, 0444); +module_param_array(tuner, int, NULL, 0444); +module_param_array(card, int, NULL, 0444); + +MODULE_PARM_DESC(video_nr, "video device number"); +MODULE_PARM_DESC(vbi_nr, "vbi device number"); +MODULE_PARM_DESC(radio_nr, "radio device number"); +MODULE_PARM_DESC(tuner, "tuner type"); +MODULE_PARM_DESC(card, "card type"); + +LIST_HEAD(tw68_devlist); +EXPORT_SYMBOL(tw68_devlist); +DEFINE_MUTEX(tw68_devlist_lock); +EXPORT_SYMBOL(tw68_devlist_lock); +static LIST_HEAD(mops_list); +static unsigned int tw68_devcount; /* curr tot num of devices present */ + +int (*tw68_dmasound_init)(struct tw68_dev *dev); +EXPORT_SYMBOL(tw68_dmasound_init); +int (*tw68_dmasound_exit)(struct tw68_dev *dev); +EXPORT_SYMBOL(tw68_dmasound_exit); + +#define dprintk(level, fmt, arg...) if (core_debug & (level)) \ + printk(KERN_DEBUG "%s: " fmt, dev->name , ## arg) + +/* ------------------------------------------------------------------ */ + +void tw68_dma_free(struct videobuf_queue *q, struct tw68_buf *buf) +{ + struct videobuf_dmabuf *dma = videobuf_to_dma(&buf->vb); + + if (core_debug & DBG_FLOW) + printk(KERN_DEBUG "%s: called\n", __func__); + BUG_ON(in_interrupt()); + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,36) + videobuf_waiton(&buf->vb, 0, 0); +#else + videobuf_waiton(q, &buf->vb, 0, 0); +#endif +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,35) + videobuf_dma_unmap(q, dma); +#else + videobuf_dma_unmap(q->dev, dma); +#endif + videobuf_dma_free(dma); + /* if no risc area allocated, btcx_riscmem_free just returns */ + btcx_riscmem_free(to_pci_dev(q->dev), &buf->risc); + buf->vb.state = VIDEOBUF_NEEDS_INIT; +} + +/* ------------------------------------------------------------------ */ +/* ------------- placeholders for later development ----------------- */ + +static int tw68_input_init1(struct tw68_dev *dev) +{ + return 0; +} + +static void tw68_input_fini(struct tw68_dev *dev) +{ + return; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38) +static void tw68_ir_start(struct tw68_dev *dev, struct card_ir *ir) +{ + return; +} + +static void tw68_ir_stop(struct tw68_dev *dev) +{ + return; +} +#endif + +/* ------------------------------------------------------------------ */ +/* + * Buffer handling routines + * + * These routines are "generic", i.e. are intended to be used by more + * than one module, e.g. the video and the transport stream modules. + * To accomplish this generality, callbacks are used whenever some + * module-specific test or action is required. + */ + +/* resends a current buffer in queue after resume */ +int tw68_buffer_requeue(struct tw68_dev *dev, + struct tw68_dmaqueue *q) +{ + struct tw68_buf *buf, *prev; + + dprintk(DBG_FLOW | DBG_TESTING, "%s: called\n", __func__); + if (!list_empty(&q->active)) { + buf = list_entry(q->active.next, struct tw68_buf, vb.queue); + dprintk(DBG_BUFF, "%s: [%p/%d] restart dma\n", __func__, + buf, buf->vb.i); + q->start_dma(dev, q, buf); + mod_timer(&q->timeout, jiffies + BUFFER_TIMEOUT); + return 0; + } + + prev = NULL; + for (;;) { + if (list_empty(&q->queued)) + return 0; + buf = list_entry(q->queued.next, struct tw68_buf, vb.queue); + /* if nothing precedes this one */ + if (NULL == prev) { + list_move_tail(&buf->vb.queue, &q->active); + q->start_dma(dev, q, buf); + buf->activate(dev, buf, NULL); + dprintk(DBG_BUFF, "%s: [%p/%d] first active\n", + __func__, buf, buf->vb.i); + + } else if (q->buf_compat(prev, buf) && + (prev->fmt == buf->fmt)) { + list_move_tail(&buf->vb.queue, &q->active); + buf->activate(dev, buf, NULL); + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + dprintk(DBG_BUFF, "%s: [%p/%d] move to active\n", + __func__, buf, buf->vb.i); + } else { + dprintk(DBG_BUFF, "%s: no action taken\n", __func__); + return 0; + } + prev = buf; + } +} + +/* nr of (tw68-)pages for the given buffer size */ +static int tw68_buffer_pages(int size) +{ + size = PAGE_ALIGN(size); + size += PAGE_SIZE; /* for non-page-aligned buffers */ + size /= 4096; + return size; +} + +/* calc max # of buffers from size (must not exceed the 4MB virtual + * address space per DMA channel) */ +int tw68_buffer_count(unsigned int size, unsigned int count) +{ + unsigned int maxcount; + + maxcount = 1024 / tw68_buffer_pages(size); + if (count > maxcount) + count = maxcount; + return count; +} + +/* + * tw68_wakeup + * + * Called when the driver completes filling a buffer, and tasks waiting + * for the data need to be awakened. + */ +void tw68_wakeup(struct tw68_dmaqueue *q, unsigned int *fc) +{ + struct tw68_dev *dev = q->dev; + struct tw68_buf *buf; + + dprintk(DBG_FLOW, "%s: called\n", __func__); + if (list_empty(&q->active)) { + dprintk(DBG_BUFF | DBG_TESTING, "%s: active list empty", + __func__); + del_timer(&q->timeout); + return; + } + buf = list_entry(q->active.next, struct tw68_buf, vb.queue); + do_gettimeofday(&buf->vb.ts); + buf->vb.field_count = (*fc)++; + dprintk(DBG_BUFF | DBG_TESTING, "%s: [%p/%d] field_count=%d\n", + __func__, buf, buf->vb.i, *fc); + buf->vb.state = VIDEOBUF_DONE; + list_del(&buf->vb.queue); + wake_up(&buf->vb.done); + mod_timer(&q->timeout, jiffies + BUFFER_TIMEOUT); +} + +/* + * tw68_buffer_queue + * + * Add specified buffer to specified queue + */ +void tw68_buffer_queue(struct tw68_dev *dev, + struct tw68_dmaqueue *q, + struct tw68_buf *buf) +{ + struct tw68_buf *prev; + + dprintk(DBG_FLOW, "%s: called\n", __func__); + assert_spin_locked(&dev->slock); + dprintk(DBG_BUFF, "%s: queuing buffer %p\n", __func__, buf); + + /* append a 'JUMP to stopper' to the buffer risc program */ + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_INT_BIT); + buf->risc.jmp[1] = cpu_to_le32(q->stopper.dma); + + /* if this buffer is not "compatible" (in dimensions and format) + * with the currently active chain of buffers, we must change + * settings before filling it; if a previous buffer has already + * been determined to require changes, this buffer must follow + * it. To do this, we maintain a "queued" chain. If that + * chain exists, append this buffer to it */ + if (!list_empty(&q->queued)) { + list_add_tail(&buf->vb.queue, &q->queued); + buf->vb.state = VIDEOBUF_QUEUED; + dprintk(DBG_BUFF, "%s: [%p/%d] appended to queued\n", + __func__, buf, buf->vb.i); + + /* else if the 'active' chain doesn't yet exist we create it now */ + } else if (list_empty(&q->active)) { + dprintk(DBG_BUFF, "%s: [%p/%d] first active\n", + __func__, buf, buf->vb.i); + list_add_tail(&buf->vb.queue, &q->active); + q->start_dma(dev, q, buf); /* 1st one - start dma */ + /* TODO - why have we removed buf->count and q->count? */ + buf->activate(dev, buf, NULL); + + /* else we would like to put this buffer on the tail of the + * active chain, provided it is "compatible". */ + } else { + /* "compatibility" depends upon the type of buffer */ + prev = list_entry(q->active.prev, struct tw68_buf, vb.queue); + if (q->buf_compat(prev, buf)) { + /* If "compatible", append to active chain */ + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + /* the param 'prev' is only for debug printing */ + buf->activate(dev, buf, prev); + list_add_tail(&buf->vb.queue, &q->active); + dprintk(DBG_BUFF, "%s: [%p/%d] appended to active\n", + __func__, buf, buf->vb.i); + } else { + /* If "incompatible", append to queued chain */ + list_add_tail(&buf->vb.queue, &q->queued); + buf->vb.state = VIDEOBUF_QUEUED; + dprintk(DBG_BUFF, "%s: [%p/%d] incompatible - appended " + "to queued\n", __func__, buf, buf->vb.i); + } + } +} + +/* + * tw68_buffer_timeout + * + * This routine is set as the video_q.timeout.function + * + * Log the event, try to reset the h/w. + * Flag the current buffer as failed, try to start again with next buff + */ +void tw68_buffer_timeout(unsigned long data) +{ + struct tw68_dmaqueue *q = (struct tw68_dmaqueue *)data; + struct tw68_dev *dev = q->dev; + struct tw68_buf *buf; + unsigned long flags; + + dprintk(DBG_FLOW, "%s: called\n", __func__); + spin_lock_irqsave(&dev->slock, flags); + + /* flag all current active buffers as failed */ + while (!list_empty(&q->active)) { + buf = list_entry(q->active.next, struct tw68_buf, vb.queue); + list_del(&buf->vb.queue); + buf->vb.state = VIDEOBUF_ERROR; + wake_up(&buf->vb.done); + printk(KERN_INFO "%s/0: [%p/%d] timeout - dma=0x%08lx\n", + dev->name, buf, buf->vb.i, + (unsigned long)buf->risc.dma); + } + tw68_buffer_requeue(dev, q); + spin_unlock_irqrestore(&dev->slock, flags); +} + +/* ------------------------------------------------------------------ */ +/* early init (no i2c, no irq) */ + +/* Called from tw68_hw_init1 and tw68_resume */ +static int tw68_hw_enable1(struct tw68_dev *dev) +{ + return 0; +} + +/* + * The device is given a "soft reset". According to the specifications, + * after this "all register content remain unchanged", so we also write + * to all specified registers manually as well (mostly to manufacturer's + * specified reset values) + */ +static int tw68_hw_init1(struct tw68_dev *dev) +{ + dprintk(DBG_FLOW, "%s: called\n", __func__); + /* Assure all interrupts are disabled */ + tw_writel(TW68_INTMASK, 0); /* 020 */ + /* Clear any pending interrupts */ + tw_writel(TW68_INTSTAT, 0xffffffff); /* 01C */ + /* Stop risc processor, set default buffer level */ + tw_writel(TW68_DMAC, 0x1600); + + tw_writeb(TW68_ACNTL, 0x80); /* 218 soft reset */ + msleep(100); + + tw_writeb(TW68_INFORM, 0x40); /* 208 mux0, 27mhz xtal */ + tw_writeb(TW68_OPFORM, 0x04); /* 20C analog line-lock */ + tw_writeb(TW68_HSYNC, 0); /* 210 color-killer high sens */ + tw_writeb(TW68_ACNTL, 0x42); /* 218 int vref #2, chroma adc off */ + + tw_writeb(TW68_CROP_HI, 0x02); /* 21C Hactive m.s. bits */ + tw_writeb(TW68_VDELAY_LO, 0x12);/* 220 Mfg specified reset value */ + tw_writeb(TW68_VACTIVE_LO, 0xf0); + tw_writeb(TW68_HDELAY_LO, 0x0f); + tw_writeb(TW68_HACTIVE_LO, 0xd0); + + tw_writeb(TW68_CNTRL1, 0xcd); /* 230 Wide Chroma BPF B/W + * Secam reduction, Adap comb for + * NTSC, Op Mode 1 */ + + tw_writeb(TW68_VSCALE_LO, 0); /* 234 */ + tw_writeb(TW68_SCALE_HI, 0x11); /* 238 */ + tw_writeb(TW68_HSCALE_LO, 0); /* 23c */ + tw_writeb(TW68_BRIGHT, 0); /* 240 */ + tw_writeb(TW68_CONTRAST, 0x5c); /* 244 */ + tw_writeb(TW68_SHARPNESS, 0x51);/* 248 */ + tw_writeb(TW68_SAT_U, 0x80); /* 24C */ + tw_writeb(TW68_SAT_V, 0x80); /* 250 */ + tw_writeb(TW68_HUE, 0x00); /* 254 */ + + /* TODO - Check that none of these are set by control defaults */ + tw_writeb(TW68_SHARP2, 0x53); /* 258 Mfg specified reset val */ + tw_writeb(TW68_VSHARP, 0x80); /* 25C Sharpness Coring val 8 */ + tw_writeb(TW68_CORING, 0x44); /* 260 CTI and Vert Peak coring */ + tw_writeb(TW68_CNTRL2, 0x00); /* 268 No power saving enabled */ + tw_writeb(TW68_SDT, 0x07); /* 270 Enable shadow reg, auto-det */ + tw_writeb(TW68_SDTR, 0x7f); /* 274 All stds recog, don't start */ + tw_writeb(TW68_CLMPG, 0x50); /* 280 Clamp end at 40 sys clocks */ + tw_writeb(TW68_IAGC, 0x22); /* 284 Mfg specified reset val */ + tw_writeb(TW68_AGCGAIN, 0xf0); /* 288 AGC gain when loop disabled */ + tw_writeb(TW68_PEAKWT, 0xd8); /* 28C White peak threshold */ + tw_writeb(TW68_CLMPL, 0x3c); /* 290 Y channel clamp level */ +// tw_writeb(TW68_SYNCT, 0x38); /* 294 Sync amplitude */ + tw_writeb(TW68_SYNCT, 0x30); /* 294 Sync amplitude */ + tw_writeb(TW68_MISSCNT, 0x44); /* 298 Horiz sync, VCR detect sens */ + tw_writeb(TW68_PCLAMP, 0x28); /* 29C Clamp pos from PLL sync */ + /* Bit DETV of VCNTL1 helps sync multi cams/chip board */ + tw_writeb(TW68_VCNTL1, 0x04); /* 2A0 */ + tw_writeb(TW68_VCNTL2, 0); /* 2A4 */ + tw_writeb(TW68_CKILL, 0x68); /* 2A8 Mfg specified reset val */ + tw_writeb(TW68_COMB, 0x44); /* 2AC Mfg specified reset val */ + tw_writeb(TW68_LDLY, 0x30); /* 2B0 Max positive luma delay */ + tw_writeb(TW68_MISC1, 0x14); /* 2B4 Mfg specified reset val */ + tw_writeb(TW68_LOOP, 0xa5); /* 2B8 Mfg specified reset val */ + tw_writeb(TW68_MISC2, 0xe0); /* 2BC Enable colour killer */ + tw_writeb(TW68_MVSN, 0); /* 2C0 */ + tw_writeb(TW68_CLMD, 0x05); /* 2CC slice level auto, clamp med. */ + tw_writeb(TW68_IDCNTL, 0); /* 2D0 Writing zero to this register + * selects NTSC ID detection, + * but doesn't change the + * sensitivity (which has a reset + * value of 1E). Since we are + * not doing auto-detection, it + * has no real effect */ + tw_writeb(TW68_CLCNTL1, 0); /* 2D4 */ + tw_writel(TW68_VBIC, 0x03); /* 010 */ + tw_writel(TW68_CAP_CTL, 0x03); /* 040 Enable both even & odd flds */ + tw_writel(TW68_DMAC, 0x2000); /* patch set had 0x2080 */ + tw_writel(TW68_TESTREG, 0); /* 02C */ + + /* + * Some common boards, especially inexpensive single-chip models, + * use the GPIO bits 0-3 to control an on-board video-output mux. + * For these boards, we need to set up the GPIO register into + * "normal" mode, set bits 0-3 as output, and then set those bits + * zero. + * + * Eventually, it would be nice if we could identify these boards + * uniquely, and only do this initialisation if the board has been + * identify. For the moment, however, it shouldn't hurt anything + * to do these steps. + */ + tw_writel(TW68_GPIOC, 0); /* Set the GPIO to "normal", no ints */ + tw_writel(TW68_GPOE, 0x0f); /* Set bits 0-3 to "output" */ + tw_writel(TW68_GPDATA, 0); /* Set all bits to low state */ + + /* Initialize the device control structures */ + mutex_init(&dev->lock); + spin_lock_init(&dev->slock); + + /* Initialize any subsystems */ + tw68_video_init1(dev); + tw68_vbi_init1(dev); + if (card_has_mpeg(dev)) + tw68_ts_init1(dev); + tw68_input_init1(dev); + + /* Do any other h/w early initialisation at this point */ + tw68_hw_enable1(dev); + + return 0; +} + +/* late init (with i2c + irq) */ +static int tw68_hw_enable2(struct tw68_dev *dev) +{ + + dprintk(DBG_FLOW, "%s: called\n", __func__); +#ifdef TW68_TESTING + dev->pci_irqmask |= TW68_I2C_INTS; +#endif + tw_setl(TW68_INTMASK, dev->pci_irqmask); + return 0; +} + +static int tw68_hw_init2(struct tw68_dev *dev) +{ + dprintk(DBG_FLOW, "%s: called\n", __func__); + tw68_video_init2(dev); /* initialise video function first */ + tw68_tvaudio_init2(dev);/* audio next */ + + /* all other board-related things, incl. enabling interrupts */ + tw68_hw_enable2(dev); + return 0; +} + +/* shutdown */ +static int tw68_hwfini(struct tw68_dev *dev) +{ + dprintk(DBG_FLOW, "%s: called\n", __func__); + if (card_has_mpeg(dev)) + tw68_ts_fini(dev); + tw68_input_fini(dev); + tw68_vbi_fini(dev); + tw68_tvaudio_fini(dev); + return 0; +} + +static void __devinit must_configure_manually(void) +{ + unsigned int i, p; + + printk(KERN_WARNING + "tw68: <rant>\n" + "tw68: Congratulations! Your TV card vendor saved a few\n" + "tw68: cents for a eeprom, thus your pci board has no\n" + "tw68: subsystem ID and I can't identify it automatically\n" + "tw68: </rant>\n" + "tw68: I feel better now. Ok, here is the good news:\n" + "tw68: You can use the card=<nr> insmod option to specify\n" + "tw68: which board you have. The list:\n"); + for (i = 0; i < tw68_bcount; i++) { + printk(KERN_WARNING "tw68: card=%d -> %-40.40s", + i, tw68_boards[i].name); + for (p = 0; tw68_pci_tbl[p].driver_data; p++) { + if (tw68_pci_tbl[p].driver_data != i) + continue; + printk(" %04x:%04x", + tw68_pci_tbl[p].subvendor, + tw68_pci_tbl[p].subdevice); + } + printk("\n"); + } +} + + +static irqreturn_t tw68_irq(int irq, void *dev_id) +{ + struct tw68_dev *dev = dev_id; + u32 status, orig; + int loop; + + status = orig = tw_readl(TW68_INTSTAT) & dev->pci_irqmask; + /* Check if anything to do */ + if (0 == status) + return IRQ_RETVAL(0); /* Nope - return */ + for (loop = 0; loop < 10; loop++) { + if (status & dev->board_virqmask) /* video interrupt */ + tw68_irq_video_done(dev, status); +#ifdef TW68_TESTING + if (status & TW68_I2C_INTS) + tw68_irq_i2c(dev, status); +#endif + status = tw_readl(TW68_INTSTAT) & dev->pci_irqmask; + if (0 == status) + goto out; + } + dprintk(DBG_UNEXPECTED, "%s: **** INTERRUPT NOT HANDLED - clearing mask" + " (orig 0x%08x, cur 0x%08x)", + dev->name, orig, tw_readl(TW68_INTSTAT)); + dprintk(DBG_UNEXPECTED, "%s: pci_irqmask 0x%08x; board_virqmask " + "0x%08x ****\n", dev->name, + dev->pci_irqmask, dev->board_virqmask); + tw_clearl(TW68_INTMASK, dev->pci_irqmask); +out: + return IRQ_RETVAL(1); +} + +int tw68_set_dmabits(struct tw68_dev *dev) +{ + return 0; +} + +static struct video_device *vdev_init(struct tw68_dev *dev, + struct video_device *template, + char *type) +{ + struct video_device *vfd; + + dprintk(DBG_FLOW, "%s: called\n", __func__); + vfd = video_device_alloc(); + if (NULL == vfd) + return NULL; + *vfd = *template; + vfd->minor = -1; + vfd->parent = &dev->pci->dev; + vfd->release = video_device_release; + /* vfd->debug = tw_video_debug; */ + snprintf(vfd->name, sizeof(vfd->name), "%s %s (%s)", + dev->name, type, tw68_boards[dev->board].name); + return vfd; +} + +static void tw68_unregister_video(struct tw68_dev *dev) +{ + + dprintk(DBG_FLOW, "%s: called\n", __func__); + if (dev->video_dev) { + if (-1 != dev->video_dev->minor) + video_unregister_device(dev->video_dev); + else + video_device_release(dev->video_dev); + dev->video_dev = NULL; + } + if (dev->vbi_dev) { + if (-1 != dev->vbi_dev->minor) + video_unregister_device(dev->vbi_dev); + else + video_device_release(dev->vbi_dev); + dev->vbi_dev = NULL; + } + if (dev->radio_dev) { + if (-1 != dev->radio_dev->minor) + video_unregister_device(dev->radio_dev); + else + video_device_release(dev->radio_dev); + dev->radio_dev = NULL; + } +} + +static void mpeg_ops_attach(struct tw68_mpeg_ops *ops, + struct tw68_dev *dev) +{ + int err; + + dprintk(DBG_FLOW, "%s: called\n", __func__); + if (NULL != dev->mops) + return; + if (tw68_boards[dev->board].mpeg != ops->type) + return; + err = ops->init(dev); + if (0 != err) + return; + dev->mops = ops; +} + +static void mpeg_ops_detach(struct tw68_mpeg_ops *ops, + struct tw68_dev *dev) +{ + + if (NULL == dev->mops) + return; + if (dev->mops != ops) + return; + dev->mops->fini(dev); + dev->mops = NULL; +} + +static int __devinit tw68_initdev(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct tw68_dev *dev; + struct tw68_mpeg_ops *mops; + int err; + + if (tw68_devcount == TW68_MAXBOARDS) + return -ENOMEM; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (NULL == dev) + return -ENOMEM; + + err = v4l2_device_register(&pci_dev->dev, &dev->v4l2_dev); + if (err) + goto fail0; + + /* pci init */ + dev->pci = pci_dev; + if (pci_enable_device(pci_dev)) { + err = -EIO; + goto fail1; + } + + dev->nr = tw68_devcount; + sprintf(dev->name, "tw%x[%d]", pci_dev->device, dev->nr); + + /* pci quirks */ + if (pci_pci_problems) { + if (pci_pci_problems & PCIPCI_TRITON) + printk(KERN_INFO "%s: quirk: PCIPCI_TRITON\n", + dev->name); + if (pci_pci_problems & PCIPCI_NATOMA) + printk(KERN_INFO "%s: quirk: PCIPCI_NATOMA\n", + dev->name); + if (pci_pci_problems & PCIPCI_VIAETBF) + printk(KERN_INFO "%s: quirk: PCIPCI_VIAETBF\n", + dev->name); + if (pci_pci_problems & PCIPCI_VSFX) + printk(KERN_INFO "%s: quirk: PCIPCI_VSFX\n", + dev->name); +#ifdef PCIPCI_ALIMAGIK + if (pci_pci_problems & PCIPCI_ALIMAGIK) { + printk(KERN_INFO "%s: quirk: PCIPCI_ALIMAGIK " + "-- latency fixup\n", dev->name); + latency = 0x0A; + } +#endif + } + if (UNSET != latency) { + printk(KERN_INFO "%s: setting pci latency timer to %d\n", + dev->name, latency); + pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, latency); + } + + /* print pci info */ + pci_read_config_byte(pci_dev, PCI_CLASS_REVISION, &dev->pci_rev); + pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &dev->pci_lat); + printk(KERN_INFO "%s: found at %s, rev: %d, irq: %d, " + "latency: %d, mmio: 0x%llx\n", dev->name, + pci_name(pci_dev), dev->pci_rev, pci_dev->irq, dev->pci_lat, + (unsigned long long)pci_resource_start(pci_dev, 0)); + pci_set_master(pci_dev); + if (!pci_dma_supported(pci_dev, DMA_BIT_MASK(32))) { + printk("%s: Oops: no 32bit PCI DMA ???\n", dev->name); + err = -EIO; + goto fail1; + } + + switch (pci_id->device) { + case PCI_DEVICE_ID_6800: /* TW6800 */ + dev->vdecoder = TW6800; + dev->board_virqmask = TW68_VID_INTS; + break; + case PCI_DEVICE_ID_6801: /* Video decoder for TW6802 */ + dev->vdecoder = TW6801; + dev->board_virqmask = TW68_VID_INTS | TW68_VID_INTSX; + break; + case PCI_DEVICE_ID_6804: /* Video decoder for TW6805 */ + dev->vdecoder = TW6804; + dev->board_virqmask = TW68_VID_INTS | TW68_VID_INTSX; + break; + default: + dev->vdecoder = TWXXXX; /* To be announced */ + dev->board_virqmask = TW68_VID_INTS | TW68_VID_INTSX; + break; + } + /* board config */ + dev->board = pci_id->driver_data; + if (card[dev->nr] >= 0 && + card[dev->nr] < tw68_bcount) + dev->board = card[dev->nr]; + if (TW68_BOARD_NOAUTO == dev->board) { + must_configure_manually(); + dev->board = TW68_BOARD_UNKNOWN; + } + dev->autodetected = card[dev->nr] != dev->board; + dev->tuner_type = tw68_boards[dev->board].tuner_type; + dev->tuner_addr = tw68_boards[dev->board].tuner_addr; + dev->radio_type = tw68_boards[dev->board].radio_type; + dev->radio_addr = tw68_boards[dev->board].radio_addr; + dev->tda9887_conf = tw68_boards[dev->board].tda9887_conf; + if (UNSET != tuner[dev->nr]) + dev->tuner_type = tuner[dev->nr]; + printk(KERN_INFO "%s: subsystem: %04x:%04x, board: %s [card=%d,%s]\n", + dev->name, pci_dev->subsystem_vendor, + pci_dev->subsystem_device, tw68_boards[dev->board].name, + dev->board, dev->autodetected ? + "autodetected" : "insmod option"); + + /* get mmio */ + if (!request_mem_region(pci_resource_start(pci_dev, 0), + pci_resource_len(pci_dev, 0), + dev->name)) { + err = -EBUSY; + printk(KERN_ERR "%s: can't get MMIO memory @ 0x%llx\n", + dev->name, + (unsigned long long)pci_resource_start(pci_dev, 0)); + goto fail1; + } + dev->lmmio = ioremap(pci_resource_start(pci_dev, 0), + pci_resource_len(pci_dev, 0)); + dev->bmmio = (__u8 __iomem *)dev->lmmio; + if (NULL == dev->lmmio) { + err = -EIO; + printk(KERN_ERR "%s: can't ioremap() MMIO memory\n", + dev->name); + goto fail2; + } + /* initialize hardware #1 */ + /* First, take care of anything unique to a particular card */ + tw68_board_init1(dev); + /* Then do any initialisation wanted before interrupts are on */ + tw68_hw_init1(dev); + + /* get irq */ + err = request_irq(pci_dev->irq, tw68_irq, + IRQF_SHARED | IRQF_DISABLED, dev->name, dev); + if (err < 0) { + printk(KERN_ERR "%s: can't get IRQ %d\n", + dev->name, pci_dev->irq); + goto fail3; + } + +#ifdef TW68_TESTING + dev->pci_irqmask |= TW68_SBDONE; + tw_setl(TW68_INTMASK, dev->pci_irqmask); + printk(KERN_INFO "Calling tw68_i2c_register\n"); + /* Register the i2c bus */ + tw68_i2c_register(dev); +#endif + + /* + * Now do remainder of initialisation, first for + * things unique for this card, then for general board + */ + tw68_board_init2(dev); + + tw68_hw_init2(dev); + +#if 0 + /* load i2c helpers */ + if (card_is_empress(dev)) { + struct v4l2_subdev *sd = + v4l2_i2c_new_subdev(&dev->i2c_adap, "saa6752hs", + "saa6752hs", 0x20); + + if (sd) + sd->grp_id = GRP_EMPRESS; + } + + request_submodules(dev); +#endif + + v4l2_prio_init(&dev->prio); + + mutex_lock(&tw68_devlist_lock); + list_for_each_entry(mops, &mops_list, next) + mpeg_ops_attach(mops, dev); + list_add_tail(&dev->devlist, &tw68_devlist); + mutex_unlock(&tw68_devlist_lock); + + /* check for signal */ + tw68_irq_video_signalchange(dev); + +#if 0 + if (TUNER_ABSENT != dev->tuner_type) + tw_call_all(dev, core, s_standby, 0); +#endif + + dev->video_dev = vdev_init(dev, &tw68_video_template, "video"); + err = video_register_device(dev->video_dev, VFL_TYPE_GRABBER, + video_nr[dev->nr]); + if (err < 0) { + printk(KERN_INFO "%s: can't register video device\n", + dev->name); + goto fail4; + } + printk(KERN_INFO "%s: registered device video%d [v4l2]\n", + dev->name, dev->video_dev->num); + + dev->vbi_dev = vdev_init(dev, &tw68_video_template, "vbi"); + + err = video_register_device(dev->vbi_dev, VFL_TYPE_VBI, + vbi_nr[dev->nr]); + if (err < 0) { + printk(KERN_INFO "%s: can't register vbi device\n", + dev->name); + goto fail4; + } + printk(KERN_INFO "%s: registered device vbi%d\n", + dev->name, dev->vbi_dev->num); + + if (card_has_radio(dev)) { + dev->radio_dev = vdev_init(dev, &tw68_radio_template, + "radio"); + err = video_register_device(dev->radio_dev, VFL_TYPE_RADIO, + radio_nr[dev->nr]); + if (err < 0) { + /* TODO - need to unregister vbi? */ + printk(KERN_INFO "%s: can't register radio device\n", + dev->name); + goto fail4; + } + printk(KERN_INFO "%s: registered device radio%d\n", + dev->name, dev->radio_dev->num); + } + + /* everything worked */ + tw68_devcount++; + + if (tw68_dmasound_init && !dev->dmasound.priv_data) + tw68_dmasound_init(dev); + + return 0; + + fail4: + tw68_unregister_video(dev); +#ifdef TW68_TESTING + tw68_i2c_unregister(dev); +#endif + free_irq(pci_dev->irq, dev); + fail3: + tw68_hwfini(dev); + iounmap(dev->lmmio); + fail2: + release_mem_region(pci_resource_start(pci_dev, 0), + pci_resource_len(pci_dev, 0)); + fail1: + v4l2_device_unregister(&dev->v4l2_dev); + fail0: + kfree(dev); + return err; +} + +static void __devexit tw68_finidev(struct pci_dev *pci_dev) +{ + struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); + struct tw68_dev *dev = + container_of(v4l2_dev, struct tw68_dev, v4l2_dev); + struct tw68_mpeg_ops *mops; + + dprintk(DBG_FLOW, "%s: called\n", __func__); + /* Release DMA sound modules if present */ + if (tw68_dmasound_exit && dev->dmasound.priv_data) + tw68_dmasound_exit(dev); + + /* shutdown subsystems */ + tw68_hwfini(dev); + tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN); + tw_writel(TW68_INTMASK, 0); + + /* unregister */ + mutex_lock(&tw68_devlist_lock); + list_del(&dev->devlist); + list_for_each_entry(mops, &mops_list, next) + mpeg_ops_detach(mops, dev); + mutex_unlock(&tw68_devlist_lock); + tw68_devcount--; + +#ifdef TW68_TESTING + tw68_i2c_unregister(dev); +#endif + tw68_unregister_video(dev); + + + /* the DMA sound modules should be unloaded before reaching + this, but just in case they are still present... */ + if (dev->dmasound.priv_data != NULL) { + free_irq(pci_dev->irq, &dev->dmasound); + dev->dmasound.priv_data = NULL; + } + + + /* release resources */ + free_irq(pci_dev->irq, dev); + iounmap(dev->lmmio); + release_mem_region(pci_resource_start(pci_dev, 0), + pci_resource_len(pci_dev, 0)); + + v4l2_device_unregister(&dev->v4l2_dev); + + /* free memory */ + kfree(dev); +} + +#ifdef CONFIG_PM + +static int tw68_suspend(struct pci_dev *pci_dev , pm_message_t state) +{ + struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); + struct tw68_dev *dev = container_of(v4l2_dev, + struct tw68_dev, v4l2_dev); + + dprintk(DBG_FLOW, "%s: called\n", __func__); + tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN); + dev->pci_irqmask &= ~TW68_VID_INTS; + tw_writel(TW68_INTMASK, 0); + + dev->insuspend = 1; + synchronize_irq(pci_dev->irq); + + /* Disable timeout timers - if we have active buffers, we will + fill them on resume*/ + + del_timer(&dev->video_q.timeout); + del_timer(&dev->vbi_q.timeout); + del_timer(&dev->ts_q.timeout); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38) + if (dev->remote) + tw68_ir_stop(dev); +#endif + + pci_save_state(pci_dev); + pci_set_power_state(pci_dev, pci_choose_state(pci_dev, state)); + + return 0; +} + +static int tw68_resume(struct pci_dev *pci_dev) +{ + struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); + struct tw68_dev *dev = container_of(v4l2_dev, + struct tw68_dev, v4l2_dev); + unsigned long flags; + + dprintk(DBG_FLOW, "%s: called\n", __func__); + pci_set_power_state(pci_dev, PCI_D0); + pci_restore_state(pci_dev); + + /* Do things that are done in tw68_initdev , + except of initializing memory structures.*/ + + tw68_board_init1(dev); + + /* tw68_hw_init1 */ + if (tw68_boards[dev->board].video_out) + tw68_videoport_init(dev); + if (card_has_mpeg(dev)) + tw68_ts_init_hw(dev); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38) + if (dev->remote) + tw68_ir_start(dev, dev->remote); +#endif + tw68_hw_enable1(dev); + + msleep(100); + + tw68_board_init2(dev); + + /*tw68_hw_init2*/ + tw68_set_tvnorm_hw(dev); + tw68_tvaudio_setmute(dev); +/* tw68_tvaudio_setvolume(dev, dev->ctl_volume); */ + tw68_tvaudio_init(dev); + tw68_irq_video_signalchange(dev); + + /*resume unfinished buffer(s)*/ + spin_lock_irqsave(&dev->slock, flags); + tw68_buffer_requeue(dev, &dev->video_q); + tw68_buffer_requeue(dev, &dev->vbi_q); + tw68_buffer_requeue(dev, &dev->ts_q); + + /* FIXME: Disable DMA audio sound - temporary till proper support + is implemented*/ + + dev->dmasound.dma_running = 0; + + /* start DMA now*/ + dev->insuspend = 0; + smp_wmb(); + tw68_set_dmabits(dev); + spin_unlock_irqrestore(&dev->slock, flags); + + return 0; +} +#endif + +/* ----------------------------------------------------------- */ + +static struct pci_driver tw68_pci_driver = { + .name = "tw68", + .id_table = tw68_pci_tbl, + .probe = tw68_initdev, + .remove = __devexit_p(tw68_finidev), +#ifdef CONFIG_PM + .suspend = tw68_suspend, + .resume = tw68_resume +#endif +}; + +static int tw68_init(void) +{ + if (core_debug & DBG_FLOW) + printk(KERN_DEBUG "%s: called\n", __func__); + INIT_LIST_HEAD(&tw68_devlist); + printk(KERN_INFO "tw68: v4l2 driver version %d.%d.%d loaded\n", + (TW68_VERSION_CODE >> 16) & 0xff, + (TW68_VERSION_CODE >> 8) & 0xff, + TW68_VERSION_CODE & 0xff); +#if 0 + printk(KERN_INFO "tw68: snapshot date %04d-%02d-%02d\n", + SNAPSHOT/10000, (SNAPSHOT/100)%100, SNAPSHOT%100); +#endif + return pci_register_driver(&tw68_pci_driver); +} + +static void module_cleanup(void) +{ + if (core_debug & DBG_FLOW) + printk(KERN_DEBUG "%s: called\n", __func__); + pci_unregister_driver(&tw68_pci_driver); +} + +module_init(tw68_init); +module_exit(module_cleanup); diff --git a/drivers/media/pci/tw68/tw68-i2c.c b/drivers/media/pci/tw68/tw68-i2c.c new file mode 100644 index 000000000000..38659d0b1e18 --- /dev/null +++ b/drivers/media/pci/tw68/tw68-i2c.c @@ -0,0 +1,245 @@ +/* + * tw68 code to handle the i2c interface. + * + * Much of this code is derived from the bt87x driver. The original + * work was by Gerd Knorr; more recently the code was enhanced by Mauro + * Carvalho Chehab. Their work is gratefully acknowledged. Full credit + * goes to them - any problems within this code are mine. + * + * Copyright (C) 2009 William M. Brack <wbrack@mmm.com.hk> + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/delay.h> + +#include "tw68.h" +#include <media/v4l2-common.h> +#include <linux/i2c-algo-bit.h> + +/*----------------------------------------------------------------*/ + +static unsigned int i2c_debug; +module_param(i2c_debug, int, 0644); +MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]"); + +#if 0 +static unsigned int i2c_scan; +module_param(i2c_scan, int, 0444); +MODULE_PARM_DESC(i2c_scan, "scan i2c bus at insmod time"); +#endif + +#define d1printk if (1 == i2c_debug) printk + +#define I2C_CLOCK 0xa6 /* 99.4 kHz */ + +/*----------------------------------------------------------------------*/ +/* Although the TW68xx i2c controller has a "hardware" mode, where all of + * the low-level i2c/smbbus is handled by the chip, it appears that mode + * is not suitable for linux i2c handling routines because extended "bursts" + * of data (sequences of bytes without intervening START/STOP bits) are + * not possible. Instead, we put the chip into "software" mode, and handle + * the i2c bus at a low level. To accomplish this, we use the routines + * from the i2c modules. + * + * Because the particular boards which I had for testing did not have any + * devices attached to the i2c bus, I have been unable to test these + * routines. + */ + +/*----------------------------------------------------------------------*/ +/* I2C functions - "bit-banging" adapter (software i2c) */ + +/* tw68_bit_setcl + * Handles "toggling" the i2c clock bit + */ +static void tw68_bit_setscl(void *data, int state) +{ + struct tw68_dev *dev = data; + + tw_andorb(TW68_SBUSC, (state ? 1 : 0) << TW68_SSCLK, TW68_SSCLK_B); +} + +/* tw68_bit_setsda + * Handles "toggling" the i2c data bit + */ +static void tw68_bit_setsda(void *data, int state) +{ + struct tw68_dev *dev = data; + + tw_andorb(TW68_SBUSC, (state ? 1 : 0) << TW68_SSDAT, TW68_SSDAT_B); +} + +/* tw68_bit_getscl + * + * Returns the current state of the clock bit + */ +static int tw68_bit_getscl(void *data) +{ + struct tw68_dev *dev = data; + + return (tw_readb(TW68_SBUSC) & TW68_SSCLK_B) ? 1 : 0; +} + +/* tw68_bit_getsda + * + * Returns the current state of the data bit + */ +static int tw68_bit_getsda(void *data) +{ + struct tw68_dev *dev = data; + + return (tw_readb(TW68_SBUSC) & TW68_SSDAT_B) ? 1 : 0; +} + +static struct i2c_algo_bit_data __devinitdata tw68_i2c_algo_bit_template = { + .setsda = tw68_bit_setsda, + .setscl = tw68_bit_setscl, + .getsda = tw68_bit_getsda, + .getscl = tw68_bit_getscl, + .udelay = 16, + .timeout = 200, +}; + +static struct i2c_client tw68_client_template = { + .name = "tw68 internal", +}; + +/*----------------------------------------------------------------*/ + +static int attach_inform(struct i2c_client *client) +{ +/* struct tw68_dev *dev = client->adapter->algo_data; */ + + d1printk("%s i2c attach [addr=0x%x,client=%s]\n", + client->driver->driver.name, client->addr, client->name); + + switch (client->addr) { + /* No info yet on what addresses to expect */ + } + + return 0; +} + +static struct i2c_adapter tw68_adap_sw_template = { + .owner = THIS_MODULE, + .name = "tw68_sw", + .client_register = attach_inform, +}; + +static int tw68_i2c_eeprom(struct tw68_dev *dev, unsigned char *eedata, + int len) +{ + unsigned char buf; + int i, err; + + dev->i2c_client.addr = 0xa0 >> 1; + buf = 256 - len; + + err = i2c_master_send(&dev->i2c_client, &buf, 1); + if (1 != err) { + printk(KERN_INFO "%s: Huh, no eeprom present (err = %d)?\n", + dev->name, err); + return -1; + } + err = i2c_master_recv(&dev->i2c_client, eedata, len); + if (len != err) { + printk(KERN_WARNING "%s: i2c eeprom read error (err=%d)\n", + dev->name, err); + return -1; + } + + for (i = 0; i < len; i++) { + if (0 == (i % 16)) + printk(KERN_INFO "%s: i2c eeprom %02x:", + dev->name, i); + printk(KERN_INFO " %02x", eedata[i]); + if (15 == (i % 16)) + printk("\n"); + } + return 0; +} + +#if 0 +static char *i2c_devs[128] = { + [0xa0 >> 1] = "eeprom", +}; + +static void do_i2c_scan(char *name, struct i2c_client *c) +{ + unsigned char buf; + int i, rc; + + for (i = 0; i < ARRAY_SIZE(i2c_devs); i++) { + c->addr = i; + rc = i2c_master_recv(c, &buf, 1); + if (rc < 0) + continue; + printk(KERN_INFO "%s: i2c scan: found device " + "@ 0x%x [%s]\n", name, i << 1, + i2c_devs[i] ? i2c_devs[i] : "???"); + } +} +#endif + +int __devinit tw68_i2c_register(struct tw68_dev *dev) +{ + int rc; + +printk(KERN_DEBUG "%s: Registering i2c module\n", __func__); + tw_writeb(TW68_I2C_RST, 1); /* reset the i2c module */ + + memcpy(&dev->i2c_client, &tw68_client_template, + sizeof(tw68_client_template)); + + memcpy(&dev->i2c_adap, &tw68_adap_sw_template, + sizeof(tw68_adap_sw_template)); + dev->i2c_adap.algo_data = &dev->i2c_algo; + dev->i2c_adap.dev.parent = &dev->pci->dev; + + memcpy(&dev->i2c_algo, &tw68_i2c_algo_bit_template, + sizeof(tw68_i2c_algo_bit_template)); + dev->i2c_algo.data = dev; + /* TODO - may want to set better name (see bttv code) */ + + i2c_set_adapdata(&dev->i2c_adap, &dev->v4l2_dev); + dev->i2c_client.adapter = &dev->i2c_adap; + + /* Assure chip is in "software" mode */ + tw_writel(TW68_SBUSC, TW68_SSDAT | TW68_SSCLK); + tw68_bit_setscl(dev, 1); + tw68_bit_setsda(dev, 1); + + rc = i2c_bit_add_bus(&dev->i2c_adap); + + tw68_i2c_eeprom(dev, dev->eedata, sizeof(dev->eedata)); +#if 0 + if (i2c_scan) + do_i2c_scan(dev->name, &dev->i2c_client); +#endif + + return rc; +} + +int tw68_i2c_unregister(struct tw68_dev *dev) +{ + i2c_del_adapter(&dev->i2c_adap); + return 0; +} diff --git a/drivers/media/pci/tw68/tw68-reg.h b/drivers/media/pci/tw68/tw68-reg.h new file mode 100644 index 000000000000..314bc43cd9d3 --- /dev/null +++ b/drivers/media/pci/tw68/tw68-reg.h @@ -0,0 +1,195 @@ +/* + * tw68-reg.h - TW68xx register offsets + * + * Much of this code is derived from the cx88 and sa7134 drivers, which + * were in turn derived from the bt87x driver. The original work was by + * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab, + * Hans Verkuil, Andy Walls and many others. Their work is gratefully + * acknowledged. Full credit goes to them - any problems within this code + * are mine. + * + * Copyright (C) William M. Brack <wbrack@mmm.com.hk> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef _TW68_REG_H_ +#define _TW68_REG_H_ + +/* ---------------------------------------------------------------------- */ +#define TW68_DMAC 0x000 +#define TW68_DMAP_SA 0x004 +#define TW68_DMAP_EXE 0x008 +#define TW68_DMAP_PP 0x00c +#define TW68_VBIC 0x010 +#define TW68_SBUSC 0x014 +#define TW68_SBUSSD 0x018 +#define TW68_INTSTAT 0x01C +#define TW68_INTMASK 0x020 +#define TW68_GPIOC 0x024 +#define TW68_GPOE 0x028 +#define TW68_TESTREG 0x02C +#define TW68_SBUSRD 0x030 +#define TW68_SBUS_TRIG 0x034 +#define TW68_CAP_CTL 0x040 +#define TW68_SUBSYS 0x054 +#define TW68_I2C_RST 0x064 +#define TW68_VBIINST 0x06C +/* define bits in FIFO and DMAP Control reg */ +#define TW68_DMAP_EN (1 << 0) +#define TW68_FIFO_EN (1 << 1) +/* define the Interrupt Status Register bits */ +#define TW68_SBDONE (1 << 0) +#define TW68_DMAPI (1 << 1) +#define TW68_GPINT (1 << 2) +#define TW68_FFOF (1 << 3) +#define TW68_FDMIS (1 << 4) +#define TW68_DMAPERR (1 << 5) +#define TW68_PABORT (1 << 6) +#define TW68_SBDONE2 (1 << 12) +#define TW68_SBERR2 (1 << 13) +#define TW68_PPERR (1 << 14) +#define TW68_FFERR (1 << 15) +#define TW68_DET50 (1 << 16) +#define TW68_FLOCK (1 << 17) +#define TW68_CCVALID (1 << 18) +#define TW68_VLOCK (1 << 19) +#define TW68_FIELD (1 << 20) +#define TW68_SLOCK (1 << 21) +#define TW68_HLOCK (1 << 22) +#define TW68_VDLOSS (1 << 23) +#define TW68_SBERR (1 << 24) +/* define the i2c control register bits */ +#define TW68_SBMODE (0) +#define TW68_WREN (1) +#define TW68_SSCLK (6) +#define TW68_SSDAT (7) +#define TW68_SBCLK (8) +#define TW68_WDLEN (16) +#define TW68_RDLEN (20) +#define TW68_SBRW (24) +#define TW68_SBDEV (25) + +#define TW68_SBMODE_B (1 << TW68_SBMODE) +#define TW68_WREN_B (1 << TW68_WREN) +#define TW68_SSCLK_B (1 << TW68_SSCLK) +#define TW68_SSDAT_B (1 << TW68_SSDAT) +#define TW68_SBRW_B (1 << TW68_SBRW) + +#define TW68_GPDATA 0x100 +#define TW68_STATUS1 0x204 +#define TW68_INFORM 0x208 +#define TW68_OPFORM 0x20C +#define TW68_HSYNC 0x210 +#define TW68_ACNTL 0x218 +#define TW68_CROP_HI 0x21C +#define TW68_VDELAY_LO 0x220 +#define TW68_VACTIVE_LO 0x224 +#define TW68_HDELAY_LO 0x228 +#define TW68_HACTIVE_LO 0x22C +#define TW68_CNTRL1 0x230 +#define TW68_VSCALE_LO 0x234 +#define TW68_SCALE_HI 0x238 +#define TW68_HSCALE_LO 0x23C +#define TW68_BRIGHT 0x240 +#define TW68_CONTRAST 0x244 +#define TW68_SHARPNESS 0x248 +#define TW68_SAT_U 0x24C +#define TW68_SAT_V 0x250 +#define TW68_HUE 0x254 +#define TW68_SHARP2 0x258 +#define TW68_VSHARP 0x25C +#define TW68_CORING 0x260 +#define TW68_VBICNTL 0x264 +#define TW68_CNTRL2 0x268 +#define TW68_CC_DATA 0x26C +#define TW68_SDT 0x270 +#define TW68_SDTR 0x274 +#define TW68_RESERV2 0x278 +#define TW68_RESERV3 0x27C +#define TW68_CLMPG 0x280 +#define TW68_IAGC 0x284 +#define TW68_AGCGAIN 0x288 +#define TW68_PEAKWT 0x28C +#define TW68_CLMPL 0x290 +#define TW68_SYNCT 0x294 +#define TW68_MISSCNT 0x298 +#define TW68_PCLAMP 0x29C +#define TW68_VCNTL1 0x2A0 +#define TW68_VCNTL2 0x2A4 +#define TW68_CKILL 0x2A8 +#define TW68_COMB 0x2AC +#define TW68_LDLY 0x2B0 +#define TW68_MISC1 0x2B4 +#define TW68_LOOP 0x2B8 +#define TW68_MISC2 0x2BC +#define TW68_MVSN 0x2C0 +#define TW68_STATUS2 0x2C4 +#define TW68_HFREF 0x2C8 +#define TW68_CLMD 0x2CC +#define TW68_IDCNTL 0x2D0 +#define TW68_CLCNTL1 0x2D4 + +/* Audio */ +#define TW68_ACKI1 0x300 +#define TW68_ACKI2 0x304 +#define TW68_ACKI3 0x308 +#define TW68_ACKN1 0x30C +#define TW68_ACKN2 0x310 +#define TW68_ACKN3 0x314 +#define TW68_SDIV 0x318 +#define TW68_LRDIV 0x31C +#define TW68_ACCNTL 0x320 + +#define TW68_VSCTL 0x3B8 +#define TW68_CHROMAGVAL 0x3BC + +#define TW68_F2CROP_HI 0x3DC +#define TW68_F2VDELAY_LO 0x3E0 +#define TW68_F2VACTIVE_LO 0x3E4 +#define TW68_F2HDELAY_LO 0x3E8 +#define TW68_F2HACTIVE_LO 0x3EC +#define TW68_F2CNT 0x3F0 +#define TW68_F2VSCALE_LO 0x3F4 +#define TW68_F2SCALE_HI 0x3F8 +#define TW68_F2HSCALE_LO 0x3FC + +#define RISC_INT_BIT 0x08000000 +#define RISC_SYNCO 0xC0000000 +#define RISC_SYNCE 0xD0000000 +#define RISC_JUMP 0xB0000000 +#define RISC_LINESTART 0x90000000 +#define RISC_INLINE 0xA0000000 + +#define VideoFormatNTSC 0 +#define VideoFormatNTSCJapan 0 +#define VideoFormatPALBDGHI 1 +#define VideoFormatSECAM 2 +#define VideoFormatNTSC443 3 +#define VideoFormatPALM 4 +#define VideoFormatPALN 5 +#define VideoFormatPALNC 5 +#define VideoFormatPAL60 6 +#define VideoFormatAuto 7 + +#define ColorFormatRGB32 0x00 +#define ColorFormatRGB24 0x10 +#define ColorFormatRGB16 0x20 +#define ColorFormatRGB15 0x30 +#define ColorFormatYUY2 0x40 +#define ColorFormatBSWAP 0x04 +#define ColorFormatWSWAP 0x08 +#define ColorFormatGamma 0x80 +#endif diff --git a/drivers/media/pci/tw68/tw68-risc.c b/drivers/media/pci/tw68/tw68-risc.c new file mode 100644 index 000000000000..66273bbd51c5 --- /dev/null +++ b/drivers/media/pci/tw68/tw68-risc.c @@ -0,0 +1,268 @@ +/* + * tw68_risc.c + * Part of the device driver for Techwell 68xx based cards + * + * Much of this code is derived from the cx88 and sa7134 drivers, which + * were in turn derived from the bt87x driver. The original work was by + * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab, + * Hans Verkuil, Andy Walls and many others. Their work is gratefully + * acknowledged. Full credit goes to them - any problems within this code + * are mine. + * + * Copyright (C) 2009 William M. Brack <wbrack@mmm.com.hk> + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "tw68.h" + +#define NO_SYNC_LINE (-1U) + +/** + * @rp pointer to current risc program position + * @sglist pointer to "scatter-gather list" of buffer pointers + * @offset offset to target memory buffer + * @sync_line 0 -> no sync, 1 -> odd sync, 2 -> even sync + * @bpl number of bytes per scan line + * @padding number of bytes of padding to add + * @lines number of lines in field + * @lpi lines per IRQ, or 0 to not generate irqs + * Note: IRQ to be generated _after_ lpi lines are transferred + */ +static __le32 *tw68_risc_field(__le32 *rp, struct scatterlist *sglist, + unsigned int offset, u32 sync_line, + unsigned int bpl, unsigned int padding, + unsigned int lines, unsigned int lpi) +{ + struct scatterlist *sg; + unsigned int line, todo, done; + + /* sync instruction */ + if (sync_line != NO_SYNC_LINE) { + if (sync_line == 1) + *(rp++) = cpu_to_le32(RISC_SYNCO); + else + *(rp++) = cpu_to_le32(RISC_SYNCE); + *(rp++) = 0; + } + /* scan lines */ + sg = sglist; + for (line = 0; line < lines; line++) { + /* calculate next starting position */ + while (offset && offset >= sg_dma_len(sg)) { + offset -= sg_dma_len(sg); + sg++; + } + if (bpl <= sg_dma_len(sg) - offset) { + /* fits into current chunk */ + *(rp++) = cpu_to_le32(RISC_LINESTART | + /* (offset<<12) |*/ bpl); + *(rp++) = cpu_to_le32(sg_dma_address(sg) + offset); + offset += bpl; + } else { + /* + * scanline needs to be split. Put the start in + * whatever memory remains using RISC_LINESTART, + * then the remainder into following addresses + * given by the scatter-gather list. + */ + todo = bpl; /* one full line to be done */ + /* first fragment */ + done = (sg_dma_len(sg) - offset); + *(rp++) = cpu_to_le32(RISC_LINESTART | + (7 << 24) | + done); + *(rp++) = cpu_to_le32(sg_dma_address(sg) + offset); + todo -= done; + sg++; + /* succeeding fragments have no offset */ + while (todo > sg_dma_len(sg)) { + *(rp++) = cpu_to_le32(RISC_INLINE | + (done << 12) | + sg_dma_len(sg)); + *(rp++) = cpu_to_le32(sg_dma_address(sg)); + todo -= sg_dma_len(sg); + sg++; + done += sg_dma_len(sg); + } + if (todo) { + /* final chunk - offset 0, count 'todo' */ + *(rp++) = cpu_to_le32(RISC_INLINE | + (done << 12) | + todo); + *(rp++) = cpu_to_le32(sg_dma_address(sg)); + } + offset = todo; + } + offset += padding; + /* If this line needs an interrupt, put it in */ + if (lpi && line > 0 && !(line % lpi)) + *(rp-2) |= RISC_INT_BIT; + } + + return rp; +} + +/** + * tw68_risc_buffer + * + * This routine is called by tw68-video. It allocates + * memory for the dma controller "program" and then fills in that + * memory with the appropriate "instructions". + * + * @pci_dev structure with info about the pci + * slot which our device is in. + * @risc structure with info about the memory + * used for our controller program. + * @sglist scatter-gather list entry + * @top_offset offset within the risc program area for the + * first odd frame line + * @bottom_offset offset within the risc program area for the + * first even frame line + * @bpl number of data bytes per scan line + * @padding number of extra bytes to add at end of line + * @lines number of scan lines + */ +int tw68_risc_buffer(struct pci_dev *pci, + struct btcx_riscmem *risc, + struct scatterlist *sglist, + unsigned int top_offset, + unsigned int bottom_offset, + unsigned int bpl, + unsigned int padding, + unsigned int lines) +{ + u32 instructions, fields; + __le32 *rp; + int rc; + + fields = 0; + if (UNSET != top_offset) + fields++; + if (UNSET != bottom_offset) + fields++; + /* + * estimate risc mem: worst case is one write per page border + + * one write per scan line + syncs + jump (all 2 dwords). + * Padding can cause next bpl to start close to a page border. + * First DMA region may be smaller than PAGE_SIZE + */ + instructions = fields * (1 + (((bpl + padding) * lines) / + PAGE_SIZE) + lines) + 2; + rc = btcx_riscmem_alloc(pci, risc, instructions * 8); + if (rc < 0) + return rc; + + /* write risc instructions */ + rp = risc->cpu; + if (UNSET != top_offset) /* generates SYNCO */ + rp = tw68_risc_field(rp, sglist, top_offset, 1, + bpl, padding, lines, 0); + if (UNSET != bottom_offset) /* generates SYNCE */ + rp = tw68_risc_field(rp, sglist, bottom_offset, 2, + bpl, padding, lines, 0); + + /* save pointer to jmp instruction address */ + risc->jmp = rp; + /* assure risc buffer hasn't overflowed */ + BUG_ON((risc->jmp - risc->cpu + 2) * sizeof(*risc->cpu) > risc->size); + return 0; +} + +#if 0 +/* ------------------------------------------------------------------ */ +/* debug helper code */ + +static void tw68_risc_decode(u32 risc, u32 addr) +{ +#define RISC_OP(reg) (((reg) >> 28) & 7) + static struct instr_details { + char *name; + u8 has_data_type; + u8 has_byte_info; + u8 has_addr; + } instr[8] = { + [RISC_OP(RISC_SYNCO)] = {"syncOdd", 0, 0, 0}, + [RISC_OP(RISC_SYNCE)] = {"syncEven", 0, 0, 0}, + [RISC_OP(RISC_JUMP)] = {"jump", 0, 0, 1}, + [RISC_OP(RISC_LINESTART)] = {"lineStart", 1, 1, 1}, + [RISC_OP(RISC_INLINE)] = {"inline", 1, 1, 1}, + }; + u32 p; + + p = RISC_OP(risc); + if (!(risc & 0x80000000) || !instr[p].name) { + printk(KERN_DEBUG "0x%08x [ INVALID ]\n", risc); + return; + } + printk(KERN_DEBUG "0x%08x %-9s IRQ=%d", + risc, instr[p].name, (risc >> 27) & 1); + if (instr[p].has_data_type) + printk(KERN_DEBUG " Type=%d", (risc >> 24) & 7); + if (instr[p].has_byte_info) + printk(KERN_DEBUG " Start=0x%03x Count=%03u", + (risc >> 12) & 0xfff, risc & 0xfff); + if (instr[p].has_addr) + printk(KERN_DEBUG " StartAddr=0x%08x", addr); + printk(KERN_DEBUG "\n"); +} + +void tw68_risc_program_dump(struct tw68_core *core, + struct btcx_riscmem *risc) +{ + __le32 *addr; + + printk(KERN_DEBUG "%s: risc_program_dump: risc=%p, " + "risc->cpu=0x%p, risc->jmp=0x%p\n", + core->name, risc, risc->cpu, risc->jmp); + for (addr = risc->cpu; addr <= risc->jmp; addr += 2) + tw68_risc_decode(*addr, *(addr+1)); +} +EXPORT_SYMBOL_GPL(tw68_risc_program_dump); +#endif + +/* + * tw68_risc_stopper + * Normally, the risc code generated for a buffer ends with a + * JUMP instruction to direct the DMAP processor to the code for + * the next buffer. However, when there is no additional buffer + * currently available, the code instead jumps to this routine. + * + * My first try for a "stopper" program was just a simple + * "jump to self" instruction. Unfortunately, this caused the + * video FIFO to overflow. My next attempt was to just disable + * the DMAP processor. Unfortunately, this caused the video + * decoder to lose its synchronization. The solution to this was to + * add a "Sync-Odd" instruction, which "eats" all the video data + * until the start of the next odd field. + */ +int tw68_risc_stopper(struct pci_dev *pci, struct btcx_riscmem *risc) +{ + __le32 *rp; + int rc; + + rc = btcx_riscmem_alloc(pci, risc, 8*4); + if (rc < 0) + return rc; + + /* write risc inststructions */ + rp = risc->cpu; + *(rp++) = cpu_to_le32(RISC_SYNCO); + *(rp++) = 0; + *(rp++) = cpu_to_le32(RISC_JUMP); + *(rp++) = cpu_to_le32(risc->dma); + risc->jmp = risc->cpu; + return 0; +} diff --git a/drivers/media/pci/tw68/tw68-ts.c b/drivers/media/pci/tw68/tw68-ts.c new file mode 100644 index 000000000000..dacd6e621bae --- /dev/null +++ b/drivers/media/pci/tw68/tw68-ts.c @@ -0,0 +1,66 @@ +/* + * tw68_ts.c + * Part of the device driver for Techwell 68xx based cards + * + * Much of this code is derived from the cx88 and sa7134 drivers, which + * were in turn derived from the bt87x driver. The original work was by + * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab, + * Hans Verkuil, Andy Walls and many others. Their work is gratefully + * acknowledged. Full credit goes to them - any problems within this code + * are mine. + * + * Copyright (C) 2009 William M. Brack <wbrack@mmm.com.hk> + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "tw68.h" + +int tw68_ts_init1(struct tw68_dev *dev) +{ + return 0; +} + +int tw68_ts_ini(struct tw68_dev *dev) +{ + return 0; +} + +int tw68_ts_fini(struct tw68_dev *dev) +{ + return 0; +} + +void tw68_irq_ts_done(struct tw68_dev *dev, unsigned long status) +{ + return; +} + +int tw68_ts_register(struct tw68_mpeg_ops *ops) +{ + return 0; +} + +void tw68_ts_unregister(struct tw68_mpeg_ops *ops) +{ + return; +} + +int tw68_ts_init_hw(struct tw68_dev *dev) +{ + return 0; +} + + diff --git a/drivers/media/pci/tw68/tw68-tvaudio.c b/drivers/media/pci/tw68/tw68-tvaudio.c new file mode 100644 index 000000000000..656d462196f4 --- /dev/null +++ b/drivers/media/pci/tw68/tw68-tvaudio.c @@ -0,0 +1,80 @@ +/* + * tw68_controls.c + * Part of the device driver for Techwell 68xx based cards + * + * Much of this code is derived from the cx88 and sa7134 drivers, which + * were in turn derived from the bt87x driver. The original work was by + * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab, + * Hans Verkuil, Andy Walls and many others. Their work is gratefully + * acknowledged. Full credit goes to them - any problems within this code + * are mine. + * + * Copyright (C) 2009 William M. Brack <wbrack@mmm.com.hk> + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "tw68.h" + +int tw68_tvaudio_rx2mode(u32 rx) +{ + return 0; +} + +void tw68_tvaudio_setmute(struct tw68_dev *dev) +{ + return; +} + +void tw68_tvaudio_setinput(struct tw68_dev *dev, struct tw68_input *in) +{ + return; +} + +void tw68_tvaudio_setvolume(struct tw68_dev *dev, int level) +{ + return; +} + +int tw68_tvaudio_getstereo(struct tw68_dev *dev) +{ + return 0; +} + +void tw68_tvaudio_init(struct tw68_dev *dev) +{ + return; +} + +int tw68_tvaudio_init2(struct tw68_dev *dev) +{ + return 0; +} + +int tw68_tvaudio_fini(struct tw68_dev *dev) +{ + return 0; +} + +int tw68_tvaudio_do_scan(struct tw68_dev *dev) +{ + return 0; +} + +void tw68_enable_i2s(struct tw68_dev *dev) +{ + return; +} + diff --git a/drivers/media/pci/tw68/tw68-vbi.c b/drivers/media/pci/tw68/tw68-vbi.c new file mode 100644 index 000000000000..fbad3b998848 --- /dev/null +++ b/drivers/media/pci/tw68/tw68-vbi.c @@ -0,0 +1,76 @@ +/* + * tw68_controls.c + * Part of the device driver for Techwell 68xx based cards + * + * Much of this code is derived from the cx88 and sa7134 drivers, which + * were in turn derived from the bt87x driver. The original work was by + * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab, + * Hans Verkuil, Andy Walls and many others. Their work is gratefully + * acknowledged. Full credit goes to them - any problems within this code + * are mine. + * + * Copyright (C) 2009 William M. Brack <wbrack@mmm.com.hk> + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "tw68.h" + +static int buffer_setup(struct videobuf_queue *q, unsigned int *count, + unsigned int *size) { + printk(KERN_INFO "%s: shouldn't be here!\n", __func__); + return 0; +} +static int buffer_prepare(struct videobuf_queue *q, + struct videobuf_buffer *vb, + enum v4l2_field field) +{ + printk(KERN_INFO "%s: shouldn't be here!\n", __func__); + return 0; +} +static void buffer_queue(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + printk(KERN_INFO "%s: shouldn't be here!\n", __func__); +} +static void buffer_release(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + printk(KERN_INFO "%s: shouldn't be here!\n", __func__); +} +struct videobuf_queue_ops tw68_vbi_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +/* ------------------------------------------------------------------ */ + +int tw68_vbi_init1(struct tw68_dev *dev) +{ + return 0; +} + +int tw68_vbi_fini(struct tw68_dev *dev) +{ + return 0; +} + +void tw68_irq_vbi_done(struct tw68_dev *dev, unsigned long status) +{ + return; +} + diff --git a/drivers/media/pci/tw68/tw68-video.c b/drivers/media/pci/tw68/tw68-video.c new file mode 100644 index 000000000000..ca08ca38d3bd --- /dev/null +++ b/drivers/media/pci/tw68/tw68-video.c @@ -0,0 +1,2230 @@ +/* + * tw68 functions to handle video data + * + * Much of this code is derived from the cx88 and sa7134 drivers, which + * were in turn derived from the bt87x driver. The original work was by + * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab, + * Hans Verkuil, Andy Walls and many others. Their work is gratefully + * acknowledged. Full credit goes to them - any problems within this code + * are mine. + * + * Copyright (C) 2009 William M. Brack <wbrack@mmm.com.hk> + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/module.h> +#include <media/v4l2-common.h> +#include <linux/sort.h> + +#include "tw68.h" +#include "tw68-reg.h" + +unsigned int video_debug; + +static unsigned int gbuffers = 8; +static unsigned int noninterlaced; /* 0 */ +static unsigned int gbufsz = 768*576*4; +static unsigned int gbufsz_max = 768*576*4; +static char secam[] = "--"; + +module_param(video_debug, int, 0644); +MODULE_PARM_DESC(video_debug, "enable debug messages [video]"); +module_param(gbuffers, int, 0444); +MODULE_PARM_DESC(gbuffers, "number of capture buffers, range 2-32"); +module_param(noninterlaced, int, 0644); +MODULE_PARM_DESC(noninterlaced, "capture non interlaced video"); +module_param_string(secam, secam, sizeof(secam), 0644); +MODULE_PARM_DESC(secam, "force SECAM variant, either DK,L or Lc"); + +#define dprintk(level, fmt, arg...) if (video_debug & (level)) \ + printk(KERN_DEBUG "%s/0: " fmt, dev->name , ## arg) + +/* ------------------------------------------------------------------ */ +/* data structs for video */ +/* + * FIXME - + * Note that the saa7134 has formats, e.g. YUV420, which are classified + * as "planar". These affect overlay mode, and are flagged with a field + * ".planar" in the format. Do we need to implement this in this driver? + */ +static struct tw68_format formats[] = { + { + .name = "15 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_RGB555, + .depth = 16, + .twformat = ColorFormatRGB15, + }, { + .name = "15 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB555X, + .depth = 16, + .twformat = ColorFormatRGB15 | ColorFormatBSWAP, + }, { + .name = "16 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_RGB565, + .depth = 16, + .twformat = ColorFormatRGB16, + }, { + .name = "16 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB565X, + .depth = 16, + .twformat = ColorFormatRGB16 | ColorFormatBSWAP, + }, { + .name = "24 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_BGR24, + .depth = 24, + .twformat = ColorFormatRGB24, + }, { + .name = "24 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB24, + .depth = 24, + .twformat = ColorFormatRGB24 | ColorFormatBSWAP, + }, { + .name = "32 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_BGR32, + .depth = 32, + .twformat = ColorFormatRGB32, + }, { + .name = "32 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB32, + .depth = 32, + .twformat = ColorFormatRGB32 | ColorFormatBSWAP | + ColorFormatWSWAP, + }, { + .name = "4:2:2 packed, YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + .twformat = ColorFormatYUY2, + }, { + .name = "4:2:2 packed, UYVY", + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = 16, + .twformat = ColorFormatYUY2 | ColorFormatBSWAP, + } +}; +#define FORMATS ARRAY_SIZE(formats) + +#define NORM_625_50 \ + .h_delay = 3, \ + .h_delay0 = 133, \ + .h_start = 0, \ + .h_stop = 719, \ + .v_delay = 24, \ + .vbi_v_start_0 = 7, \ + .vbi_v_stop_0 = 22, \ + .video_v_start = 24, \ + .video_v_stop = 311, \ + .vbi_v_start_1 = 319 + +#define NORM_525_60 \ + .h_delay = 8, \ + .h_delay0 = 138, \ + .h_start = 0, \ + .h_stop = 719, \ + .v_delay = 22, \ + .vbi_v_start_0 = 10, \ + .vbi_v_stop_0 = 21, \ + .video_v_start = 22, \ + .video_v_stop = 262, \ + .vbi_v_start_1 = 273 + +/* + * The following table is searched by tw68_s_std, first for a specific + * match, then for an entry which contains the desired id. The table + * entries should therefore be ordered in ascending order of specificity. + */ +static struct tw68_tvnorm tvnorms[] = { + { + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + .format = VideoFormatPALBDGHI, + + }, { + .name = "PAL-I", + .id = V4L2_STD_PAL_I, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + .format = VideoFormatPALBDGHI, + + }, { + .name = "PAL-DK", + .id = V4L2_STD_PAL_DK, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + .format = VideoFormatPALBDGHI, + + }, { + .name = "PAL", /* autodetect */ + .id = V4L2_STD_PAL, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + .format = VideoFormatPALBDGHI, + + }, { + .name = "NTSC", + .id = V4L2_STD_NTSC, + NORM_525_60, + + .sync_control = 0x59, + .luma_control = 0x40, + .chroma_ctrl1 = 0x89, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x0e, + .vgate_misc = 0x18, + .format = VideoFormatNTSC, + + }, { + .name = "SECAM-DK", + .id = V4L2_STD_SECAM_DK, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x1b, + .chroma_ctrl1 = 0xd1, + .chroma_gain = 0x80, + .chroma_ctrl2 = 0x00, + .vgate_misc = 0x1c, + .format = VideoFormatSECAM, + + }, { + .name = "SECAM-L", + .id = V4L2_STD_SECAM_L, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x1b, + .chroma_ctrl1 = 0xd1, + .chroma_gain = 0x80, + .chroma_ctrl2 = 0x00, + .vgate_misc = 0x1c, + .format = VideoFormatSECAM, + + }, { + .name = "SECAM-LC", + .id = V4L2_STD_SECAM_LC, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x1b, + .chroma_ctrl1 = 0xd1, + .chroma_gain = 0x80, + .chroma_ctrl2 = 0x00, + .vgate_misc = 0x1c, + .format = VideoFormatSECAM, + + }, { + .name = "SECAM", + .id = V4L2_STD_SECAM, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x1b, + .chroma_ctrl1 = 0xd1, + .chroma_gain = 0x80, + .chroma_ctrl2 = 0x00, + .vgate_misc = 0x1c, + .format = VideoFormatSECAM, + + }, { + .name = "PAL-M", + .id = V4L2_STD_PAL_M, + NORM_525_60, + + .sync_control = 0x59, + .luma_control = 0x40, + .chroma_ctrl1 = 0xb9, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x0e, + .vgate_misc = 0x18, + .format = VideoFormatPALM, + + }, { + .name = "PAL-Nc", + .id = V4L2_STD_PAL_Nc, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0xa1, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + .format = VideoFormatPALNC, + + }, { + .name = "PAL-60", + .id = V4L2_STD_PAL_60, + .h_delay = 186, + .h_start = 0, + .h_stop = 719, + .v_delay = 26, + .video_v_start = 23, + .video_v_stop = 262, + .vbi_v_start_0 = 10, + .vbi_v_stop_0 = 21, + .vbi_v_start_1 = 273, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + .format = VideoFormatPAL60, + + }, { +/* + * FIXME: The following are meant to be "catch-all", and need + * to be further thought out! + */ + .name = "STD-525-60", + .id = V4L2_STD_525_60, + NORM_525_60, + + .sync_control = 0x59, + .luma_control = 0x40, + .chroma_ctrl1 = 0x89, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x0e, + .vgate_misc = 0x18, + .format = VideoFormatNTSC, + + }, { + .name = "STD-625-50", + .id = V4L2_STD_625_50, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + .format = VideoFormatPALBDGHI, + } +}; +#define TVNORMS ARRAY_SIZE(tvnorms) + +static const struct v4l2_queryctrl no_ctrl = { + .name = "42", + .flags = V4L2_CTRL_FLAG_DISABLED, +}; +static const struct v4l2_queryctrl video_ctrls[] = { + /* --- video --- */ + { + .id = V4L2_CID_BRIGHTNESS, + .name = "Brightness", + .minimum = -128, + .maximum = 127, + .step = 1, + .default_value = 20, + .type = V4L2_CTRL_TYPE_INTEGER, + }, { + .id = V4L2_CID_CONTRAST, + .name = "Contrast", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 100, + .type = V4L2_CTRL_TYPE_INTEGER, + }, { + .id = V4L2_CID_SATURATION, + .name = "Saturation", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 128, + .type = V4L2_CTRL_TYPE_INTEGER, + }, { + .id = V4L2_CID_HUE, + .name = "Hue", + .minimum = -128, + .maximum = 127, + .step = 1, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, { + .id = V4L2_CID_COLOR_KILLER, + .name = "Color Killer", + .minimum = 0, + .maximum = 1, + .default_value = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + }, { + .id = V4L2_CID_CHROMA_AGC, + .name = "Chroma AGC", + .minimum = 0, + .maximum = 1, + .default_value = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + }, + /* --- audio --- */ + { + .id = V4L2_CID_AUDIO_MUTE, + .name = "Mute", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + }, { + .id = V4L2_CID_AUDIO_VOLUME, + .name = "Volume", + .minimum = -15, + .maximum = 15, + .step = 1, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + } +}; +static const unsigned int CTRLS = ARRAY_SIZE(video_ctrls); + +/* + * Routine to lookup a control by its ID, and return a pointer + * to the entry in the video_ctrls array for that control. + */ +static const struct v4l2_queryctrl *ctrl_by_id(unsigned int id) +{ + unsigned int i; + + for (i = 0; i < CTRLS; i++) + if (video_ctrls[i].id == id) + return video_ctrls+i; + return NULL; +} + +static struct tw68_format *format_by_fourcc(unsigned int fourcc) +{ + unsigned int i; + + for (i = 0; i < FORMATS; i++) + if (formats[i].fourcc == fourcc) + return formats+i; + return NULL; +} + +/* ----------------------------------------------------------------------- */ +/* resource management */ + +static int res_get(struct tw68_fh *fh, unsigned int bit) +{ + struct tw68_dev *dev = fh->dev; + + if (fh->resources & bit) + /* have it already allocated */ + return 1; + + /* is it free? */ + mutex_lock(&dev->lock); + if (dev->resources & bit) { + /* no, someone else uses it */ + mutex_unlock(&fh->dev->lock); + return 0; + } + /* it's free, grab it */ + fh->resources |= bit; + dev->resources |= bit; + dprintk(DBG_FLOW, "%s: %d\n", __func__, bit); + mutex_unlock(&dev->lock); + return 1; +} + +static int res_check(struct tw68_fh *fh, unsigned int bit) +{ + return fh->resources & bit; +} + +static int res_locked(struct tw68_dev *dev, unsigned int bit) +{ + return dev->resources & bit; +} + +static void res_free(struct tw68_fh *fh, + unsigned int bits) +{ + struct tw68_dev *dev = fh->dev; + + BUG_ON((fh->resources & bits) != bits); + + mutex_lock(&fh->dev->lock); + fh->resources &= ~bits; + fh->dev->resources &= ~bits; + dprintk(DBG_FLOW, "%s: %d\n", __func__, bits); + mutex_unlock(&fh->dev->lock); +} + +/* ------------------------------------------------------------------ */ +/* + * Note that the cropping rectangles are described in terms of a single + * frame, i.e. line positions are only 1/2 the interlaced equivalent + */ +static void set_tvnorm(struct tw68_dev *dev, struct tw68_tvnorm *norm) +{ + dprintk(DBG_FLOW, "%s: %s\n", __func__, norm->name); + dev->tvnorm = norm; + + /* setup cropping */ + dev->crop_bounds.left = norm->h_start; + dev->crop_defrect.left = norm->h_start; + dev->crop_bounds.width = norm->h_stop - norm->h_start + 1; + dev->crop_defrect.width = norm->h_stop - norm->h_start + 1; + + dev->crop_bounds.top = norm->video_v_start; + dev->crop_defrect.top = norm->video_v_start; + dev->crop_bounds.height = (((norm->id & V4L2_STD_525_60) ? + 524 : 624)) / 2 - dev->crop_bounds.top; + dev->crop_defrect.height = (norm->video_v_stop - + norm->video_v_start + 1); + + dev->crop_current = dev->crop_defrect; + + if (norm != dev->tvnorm) { + dev->tvnorm = norm; + tw68_set_tvnorm_hw(dev); + } +} + +static void video_mux(struct tw68_dev *dev, int input) +{ + dprintk(DBG_FLOW, "%s: input = %d [%s]\n", __func__, input, + card_in(dev, input).name); + /* + * dev->input shows current application request, + * dev->hw_input shows current hardware setting + */ + dev->input = &card_in(dev, input); + tw68_tvaudio_setinput(dev, &card_in(dev, input)); +} + +/* + * tw68_set_scale + * + * Scaling and Cropping for video decoding + * + * We are working with 3 values for horizontal and vertical - scale, + * delay and active. + * + * HACTIVE represent the actual number of pixels in the "usable" image, + * before scaling. HDELAY represents the number of pixels skipped + * between the start of the horizontal sync and the start of the image. + * HSCALE is calculated using the formula + * HSCALE = (HACTIVE / (#pixels desired)) * 256 + * + * The vertical registers are similar, except based upon the total number + * of lines in the image, and the first line of the image (i.e. ignoring + * vertical sync and VBI). + * + * Note that the number of bytes reaching the FIFO (and hence needing + * to be processed by the DMAP program) is completely dependent upon + * these values, especially HSCALE. + * + * Parameters: + * @dev pointer to the device structure, needed for + * getting current norm (as well as debug print) + * @width actual image width (from user buffer) + * @height actual image height + * @field indicates Top, Bottom or Interlaced + */ +static int tw68_set_scale(struct tw68_dev *dev, unsigned int width, + unsigned int height, enum v4l2_field field) +{ + + /* set individually for debugging clarity */ + int hactive, hdelay, hscale; + int vactive, vdelay, vscale; + int comb; + + if (V4L2_FIELD_HAS_BOTH(field)) /* if field is interlaced */ + height /= 2; /* we must set for 1-frame */ + + dprintk(DBG_FLOW, "%s: width=%d, height=%d, both=%d\n Crop rect: " + "top=%d, left=%d, width=%d height=%d\n" + " tvnorm h_delay=%d, h_start=%d, h_stop=%d, " + "v_delay=%d, v_start=%d, v_stop=%d\n" , __func__, + width, height, V4L2_FIELD_HAS_BOTH(field), + dev->crop_bounds.top, dev->crop_bounds.left, + dev->crop_bounds.width, dev->crop_bounds.height, + dev->tvnorm->h_delay, dev->tvnorm->h_start, dev->tvnorm->h_stop, + dev->tvnorm->v_delay, dev->tvnorm->video_v_start, + dev->tvnorm->video_v_stop); + + switch (dev->vdecoder) { + case TW6800: + hdelay = dev->tvnorm->h_delay0; + break; + default: + hdelay = dev->tvnorm->h_delay; + break; + } + hdelay += dev->crop_bounds.left; + hactive = dev->crop_bounds.width; + + hscale = (hactive * 256) / (width); + + vdelay = dev->tvnorm->v_delay + dev->crop_bounds.top - + dev->crop_defrect.top; + vactive = dev->crop_bounds.height; + vscale = (vactive * 256) / height; + + dprintk(DBG_FLOW, "%s: %dx%d [%s%s,%s]\n", __func__, + width, height, + V4L2_FIELD_HAS_TOP(field) ? "T" : "", + V4L2_FIELD_HAS_BOTTOM(field) ? "B" : "", + v4l2_norm_to_name(dev->tvnorm->id)); + dprintk(DBG_FLOW, "%s: hactive=%d, hdelay=%d, hscale=%d; " + "vactive=%d, vdelay=%d, vscale=%d\n", __func__, + hactive, hdelay, hscale, vactive, vdelay, vscale); + + comb = ((vdelay & 0x300) >> 2) | + ((vactive & 0x300) >> 4) | + ((hdelay & 0x300) >> 6) | + ((hactive & 0x300) >> 8); + dprintk(DBG_FLOW, "%s: setting CROP_HI=%02x, VDELAY_LO=%02x, " + "VACTIVE_LO=%02x, HDELAY_LO=%02x, HACTIVE_LO=%02x\n", + __func__, comb, vdelay, vactive, hdelay, hactive); + tw_writeb(TW68_CROP_HI, comb); + tw_writeb(TW68_VDELAY_LO, vdelay & 0xff); + tw_writeb(TW68_VACTIVE_LO, vactive & 0xff); + tw_writeb(TW68_HDELAY_LO, hdelay & 0xff); + tw_writeb(TW68_HACTIVE_LO, hactive & 0xff); + + comb = ((vscale & 0xf00) >> 4) | ((hscale & 0xf00) >> 8); + dprintk(DBG_FLOW, "%s: setting SCALE_HI=%02x, VSCALE_LO=%02x, " + "HSCALE_LO=%02x\n", __func__, comb, vscale, hscale); + tw_writeb(TW68_SCALE_HI, comb); + tw_writeb(TW68_VSCALE_LO, vscale); + tw_writeb(TW68_HSCALE_LO, hscale); + + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int tw68_video_start_dma(struct tw68_dev *dev, struct tw68_dmaqueue *q, + struct tw68_buf *buf) { + + dprintk(DBG_FLOW, "%s: Starting risc program\n", __func__); + /* Assure correct input */ + if (dev->hw_input != dev->input) { + dev->hw_input = dev->input; + tw_andorb(TW68_INFORM, 0x03 << 2, dev->input->vmux << 2); + } + /* Set cropping and scaling */ + tw68_set_scale(dev, buf->vb.width, buf->vb.height, buf->vb.field); + /* + * Set start address for RISC program. Note that if the DMAP + * processor is currently running, it must be stopped before + * a new address can be set. + */ + tw_clearl(TW68_DMAC, TW68_DMAP_EN); + tw_writel(TW68_DMAP_SA, cpu_to_le32(buf->risc.dma)); + /* Clear any pending interrupts */ + tw_writel(TW68_INTSTAT, dev->board_virqmask); + /* Enable the risc engine and the fifo */ + tw_andorl(TW68_DMAC, 0xff, buf->fmt->twformat | + ColorFormatGamma | TW68_DMAP_EN | TW68_FIFO_EN); + dev->pci_irqmask |= dev->board_virqmask; + tw_setl(TW68_INTMASK, dev->pci_irqmask); + return 0; +} + +/* ------------------------------------------------------------------ */ +/* videobuf queue operations */ + +/* + * check_buf_fmt + * + * callback from tw68-core buffer_queue to determine whether the + * current buffer and the previous one are "compatible" (i.e. the + * risc programs can be chained without requiring a format change) + */ +static int tw68_check_video_fmt(struct tw68_buf *prev, struct tw68_buf *buf) +{ + return (prev->vb.width == buf->vb.width && + prev->vb.height == buf->vb.height && + prev->fmt == buf->fmt); +} + +/* + * buffer_setup + * + * Calculate required size of buffer and maximum number allowed + */ +static int +buffer_setup(struct videobuf_queue *q, unsigned int *count, + unsigned int *size) +{ + struct tw68_fh *fh = q->priv_data; + + *size = fh->fmt->depth * fh->width * fh->height >> 3; + if (0 == *count) + *count = gbuffers; + *count = tw68_buffer_count(*size, *count); + return 0; +} + +static int buffer_activate(struct tw68_dev *dev, struct tw68_buf *buf, + struct tw68_buf *next) +{ + dprintk(DBG_BUFF, "%s: dev=%p, buf=%p, next=%p\n", + __func__, dev, buf, next); + if (dev->hw_input != dev->input) { + dev->hw_input = dev->input; + tw_andorb(TW68_INFORM, 0x03 << 2, + dev->hw_input->vmux << 2); + } + buf->vb.state = VIDEOBUF_ACTIVE; + /* TODO - need to assure scaling/cropping are set correctly */ + mod_timer(&dev->video_q.timeout, jiffies+BUFFER_TIMEOUT); + return 0; +} + +/* +* buffer_prepare +* +* Set the ancilliary information into the buffer structure. This +* includes generating the necessary risc program if it hasn't already +* been done for the current buffer format. +* The structure fh contains the details of the format requested by the +* user - type, width, height and #fields. This is compared with the +* last format set for the current buffer. If they differ, the risc +* code (which controls the filling of the buffer) is (re-)generated. +*/ +static int +buffer_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct tw68_fh *fh = q->priv_data; + struct tw68_dev *dev = fh->dev; + struct tw68_buf *buf = container_of(vb, struct tw68_buf, vb); + struct videobuf_dmabuf *dma = videobuf_to_dma(&buf->vb); + int rc, init_buffer = 0; + unsigned int maxw, maxh; + + BUG_ON(NULL == fh->fmt); + maxw = dev->tvnorm->h_stop - dev->tvnorm->h_start + 1; + maxh = 2*(dev->tvnorm->video_v_stop - dev->tvnorm->video_v_start + 1); + if (fh->width < 48 || fh->width > maxw || fh->height > maxh + || fh->height < 16) { + dprintk(DBG_UNEXPECTED, "%s: invalid dimensions - " + "fh->width=%d, fh->height=%d, maxw=%d, maxh=%d\n", + __func__, fh->width, fh->height, maxw, maxh); + return -EINVAL; + } + buf->vb.size = (fh->width * fh->height * (fh->fmt->depth)) >> 3; + if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size) + return -EINVAL; + + if (buf->fmt != fh->fmt || + buf->vb.width != fh->width || + buf->vb.height != fh->height || + buf->vb.field != field) { + dprintk(DBG_BUFF, "%s: buf - fmt=%p, width=%3d, height=%3d, " + "field=%d\n%s: fh - fmt=%p, width=%3d, height=%3d, " + "field=%d\n", __func__, buf->fmt, buf->vb.width, + buf->vb.height, buf->vb.field, __func__, fh->fmt, + fh->width, fh->height, field); + buf->fmt = fh->fmt; + buf->vb.width = fh->width; + buf->vb.height = fh->height; + buf->vb.field = field; + init_buffer = 1; /* force risc code re-generation */ + } + buf->input = dev->input; + + if (VIDEOBUF_NEEDS_INIT == buf->vb.state) { + rc = videobuf_iolock(q, &buf->vb, NULL); + if (0 != rc) + goto fail; + init_buffer = 1; /* force risc code re-generation */ + } + dprintk(DBG_BUFF, "%s: q=%p, vb=%p, init_buffer=%d\n", + __func__, q, vb, init_buffer); + + if (init_buffer) { + buf->bpl = buf->vb.width * (buf->fmt->depth) >> 3; + dprintk(DBG_TESTING, "%s: Generating new risc code " + "[%dx%dx%d](%d)\n", __func__, buf->vb.width, + buf->vb.height, buf->fmt->depth, buf->bpl); + switch (buf->vb.field) { + case V4L2_FIELD_TOP: + tw68_risc_buffer(dev->pci, &buf->risc, + dma->sglist, + 0, UNSET, + buf->bpl, 0, + buf->vb.height); + break; + case V4L2_FIELD_BOTTOM: + tw68_risc_buffer(dev->pci, &buf->risc, + dma->sglist, + UNSET, 0, + buf->bpl, 0, + buf->vb.height); + break; + case V4L2_FIELD_INTERLACED: + tw68_risc_buffer(dev->pci, &buf->risc, + dma->sglist, + 0, buf->bpl, + buf->bpl, buf->bpl, + buf->vb.height >> 1); + break; + case V4L2_FIELD_SEQ_TB: + tw68_risc_buffer(dev->pci, &buf->risc, + dma->sglist, + 0, buf->bpl * (buf->vb.height >> 1), + buf->bpl, 0, + buf->vb.height >> 1); + break; + case V4L2_FIELD_SEQ_BT: + tw68_risc_buffer(dev->pci, &buf->risc, + dma->sglist, + buf->bpl * (buf->vb.height >> 1), 0, + buf->bpl, 0, + buf->vb.height >> 1); + break; + default: + BUG(); + } + } + dprintk(DBG_BUFF, "%s: [%p/%d] - %dx%d %dbpp \"%s\" - dma=0x%08lx\n", + __func__, buf, buf->vb.i, fh->width, fh->height, + fh->fmt->depth, fh->fmt->name, (unsigned long)buf->risc.dma); + + buf->vb.state = VIDEOBUF_PREPARED; + buf->activate = buffer_activate; + return 0; + + fail: + tw68_dma_free(q, buf); + return rc; +} + +/* + * buffer_queue + * + * Callback whenever a buffer has been requested (by read() or QBUF) + */ +static void +buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct tw68_fh *fh = q->priv_data; + struct tw68_buf *buf = container_of(vb, struct tw68_buf, vb); + + tw68_buffer_queue(fh->dev, &fh->dev->video_q, buf); +} + +/* + * buffer_release + * + * Free a buffer previously allocated. + */ +static void buffer_release(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + struct tw68_buf *buf = container_of(vb, struct tw68_buf, vb); + + tw68_dma_free(q, buf); +} + +static struct videobuf_queue_ops video_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +/* ------------------------------------------------------------------ */ + +static int tw68_g_ctrl_internal(struct tw68_dev *dev, struct tw68_fh *fh, + struct v4l2_control *c) +{ + const struct v4l2_queryctrl *ctrl; + + dprintk(DBG_FLOW, "%s\n", __func__); + ctrl = ctrl_by_id(c->id); + if (NULL == ctrl) + return -EINVAL; + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + c->value = (char)tw_readb(TW68_BRIGHT); + break; + case V4L2_CID_HUE: + c->value = (char)tw_readb(TW68_HUE); + break; + case V4L2_CID_CONTRAST: + c->value = tw_readb(TW68_CONTRAST); + break; + case V4L2_CID_SATURATION: + c->value = tw_readb(TW68_SAT_U); + break; + case V4L2_CID_COLOR_KILLER: + c->value = 0 != (tw_readb(TW68_MISC2) & 0xe0); + break; + case V4L2_CID_CHROMA_AGC: + c->value = 0 != (tw_readb(TW68_LOOP) & 0x30); + break; + case V4L2_CID_AUDIO_MUTE: + /*hack to suppresss tvtime complaint */ + c->value = 0; + break; +#if 0 + case V4L2_CID_AUDIO_VOLUME: + c->value = dev->ctl_volume; + break; +#endif + default: + return -EINVAL; + } + return 0; +} + +static int tw68_g_ctrl(struct file *file, void *priv, struct v4l2_control *c) +{ + struct tw68_fh *fh = priv; + + return tw68_g_ctrl_internal(fh->dev, fh, c); +} + +static int tw68_s_ctrl_value(struct tw68_dev *dev, __u32 id, int val) +{ + int err = 0; + + dprintk(DBG_FLOW, "%s\n", __func__); + switch (id) { + case V4L2_CID_BRIGHTNESS: + tw_writeb(TW68_BRIGHT, val); + break; + case V4L2_CID_HUE: + tw_writeb(TW68_HUE, val); + break; + case V4L2_CID_CONTRAST: + tw_writeb(TW68_CONTRAST, val); + break; + case V4L2_CID_SATURATION: + tw_writeb(TW68_SAT_U, val); + tw_writeb(TW68_SAT_V, val); + break; + case V4L2_CID_COLOR_KILLER: + if (val) + tw_andorb(TW68_MISC2, 0xe0, 0xe0); + else + tw_andorb(TW68_MISC2, 0xe0, 0x00); + break; + case V4L2_CID_CHROMA_AGC: + if (val) + tw_andorb(TW68_LOOP, 0x30, 0x20); + else + tw_andorb(TW68_LOOP, 0x30, 0x00); + break; + case V4L2_CID_AUDIO_MUTE: + /* hack to suppress tvtime complaint */ + break; +#if 0 + case V4L2_CID_AUDIO_VOLUME: + dev->ctl_volume = val; + tw68_tvaudio_setvolume(dev, dev->ctl_volume); + break; + case V4L2_CID_HFLIP: + dev->ctl_mirror = val; + break; + case V4L2_CID_PRIVATE_AUTOMUTE: + { + struct v4l2_priv_tun_config tda9887_cfg; + + tda9887_cfg.tuner = TUNER_TDA9887; + tda9887_cfg.priv = &dev->tda9887_conf; + + dev->ctl_automute = val; + if (dev->tda9887_conf) { + if (dev->ctl_automute) + dev->tda9887_conf |= TDA9887_AUTOMUTE; + else + dev->tda9887_conf &= ~TDA9887_AUTOMUTE; + + tw_call_all(dev, tuner, s_config, &tda9887_cfg); + } + break; + } +#endif + default: + err = -EINVAL; + } + return err; +} + +static int tw68_s_ctrl_internal(struct tw68_dev *dev, struct tw68_fh *fh, + struct v4l2_control *c) +{ + const struct v4l2_queryctrl *ctrl; + int err; + + dprintk(DBG_FLOW, "%s\n", __func__); + if (fh) { +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,34) + err = v4l2_prio_check(&dev->prio, &fh->prio); +#else + err = v4l2_prio_check(&dev->prio, fh->prio); +#endif + if (0 != err) + return err; + } + + mutex_lock(&dev->lock); + + ctrl = ctrl_by_id(c->id); + if (NULL == ctrl) { + err = -EINVAL; + goto error; + } + + dprintk(DBG_BUFF, "%s: name=%s val=%d\n", __func__, + ctrl->name, c->value); + switch (ctrl->type) { + case V4L2_CTRL_TYPE_BOOLEAN: + case V4L2_CTRL_TYPE_MENU: + case V4L2_CTRL_TYPE_INTEGER: + if (c->value < ctrl->minimum) + c->value = ctrl->minimum; + if (c->value > ctrl->maximum) + c->value = ctrl->maximum; + break; + default: + /* nothing */; + }; + err = tw68_s_ctrl_value(dev, c->id, c->value); + +error: + mutex_unlock(&dev->lock); + return err; +} + +static int tw68_s_ctrl(struct file *file, void *f, struct v4l2_control *c) +{ + struct tw68_fh *fh = f; + + return tw68_s_ctrl_internal(fh->dev, fh, c); +} + +/* ------------------------------------------------------------------ */ + +/* + * Returns a pointer to the currently used queue (e.g. video, vbi, etc.) + */ +static struct videobuf_queue *tw68_queue(struct tw68_fh *fh) +{ + struct videobuf_queue *q = NULL; + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + q = &fh->cap; + break; + case V4L2_BUF_TYPE_VBI_CAPTURE: + q = &fh->vbi; + break; + default: + BUG(); + } + return q; +} + +static int tw68_resource(struct tw68_fh *fh) +{ + if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + return RESOURCE_VIDEO; + + if (fh->type == V4L2_BUF_TYPE_VBI_CAPTURE) + return RESOURCE_VBI; + + BUG(); + return 0; +} + +static int video_open(struct file *file) +{ + int minor = video_devdata(file)->minor; + struct tw68_dev *dev; + struct tw68_fh *fh; + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + int radio = 0; + + mutex_lock(&tw68_devlist_lock); + list_for_each_entry(dev, &tw68_devlist, devlist) { + if (dev->video_dev && (dev->video_dev->minor == minor)) + goto found; + if (dev->radio_dev && (dev->radio_dev->minor == minor)) { + radio = 1; + goto found; + } + if (dev->vbi_dev && (dev->vbi_dev->minor == minor)) { + type = V4L2_BUF_TYPE_VBI_CAPTURE; + goto found; + } + } + mutex_unlock(&tw68_devlist_lock); + return -ENODEV; + +found: + mutex_unlock(&tw68_devlist_lock); + + dprintk(DBG_FLOW, "%s: minor=%d radio=%d type=%s\n", __func__, minor, + radio, v4l2_type_names[type]); + + /* allocate + initialize per filehandle data */ + fh = kzalloc(sizeof(*fh), GFP_KERNEL); + if (NULL == fh) + return -ENOMEM; + + file->private_data = fh; + fh->dev = dev; + fh->radio = radio; + fh->type = type; + fh->fmt = format_by_fourcc(V4L2_PIX_FMT_BGR24); + fh->width = 720; + fh->height = 576; + v4l2_prio_open(&dev->prio, &fh->prio); + + videobuf_queue_sg_init(&fh->cap, &video_qops, + &dev->pci->dev, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct tw68_buf), +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,37) + fh +#else + fh, &dev->lock +#endif + ); + videobuf_queue_sg_init(&fh->vbi, &tw68_vbi_qops, + &dev->pci->dev, &dev->slock, + V4L2_BUF_TYPE_VBI_CAPTURE, + V4L2_FIELD_SEQ_TB, + sizeof(struct tw68_buf), +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,37) + fh +#else + fh, &dev->lock +#endif + ); + if (fh->radio) { + /* switch to radio mode */ + tw68_tvaudio_setinput(dev, &card(dev).radio); + tw_call_all(dev, tuner, s_radio); + } else { + /* switch to video/vbi mode */ + tw68_tvaudio_setinput(dev, dev->input); + } + return 0; +} + +static ssize_t +video_read(struct file *file, char __user *data, size_t count, loff_t *ppos) +{ + struct tw68_fh *fh = file->private_data; + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (res_locked(fh->dev, RESOURCE_VIDEO)) + return -EBUSY; + return videobuf_read_one(tw68_queue(fh), + data, count, ppos, + file->f_flags & O_NONBLOCK); + case V4L2_BUF_TYPE_VBI_CAPTURE: + if (!res_get(fh, RESOURCE_VBI)) + return -EBUSY; + return videobuf_read_stream(tw68_queue(fh), + data, count, ppos, 1, + file->f_flags & O_NONBLOCK); + break; + default: + BUG(); + return 0; + } +} + +static unsigned int +video_poll(struct file *file, struct poll_table_struct *wait) +{ + struct tw68_fh *fh = file->private_data; + struct videobuf_buffer *buf = NULL; + + if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) + return videobuf_poll_stream(file, &fh->vbi, wait); + + if (res_check(fh, RESOURCE_VIDEO)) { + if (!list_empty(&fh->cap.stream)) + buf = list_entry(fh->cap.stream.next, + struct videobuf_buffer, stream); + } else { + mutex_lock(&fh->cap.vb_lock); + if (UNSET == fh->cap.read_off) { + /* need to capture a new frame */ + if (res_locked(fh->dev, RESOURCE_VIDEO)) + goto err; + if (0 != fh->cap.ops->buf_prepare(&fh->cap, + fh->cap.read_buf, fh->cap.field)) + goto err; + fh->cap.ops->buf_queue(&fh->cap, fh->cap.read_buf); + fh->cap.read_off = 0; + } + mutex_unlock(&fh->cap.vb_lock); + buf = fh->cap.read_buf; + } + + if (!buf) + return POLLERR; + + poll_wait(file, &buf->done, wait); + if (buf->state == VIDEOBUF_DONE || + buf->state == VIDEOBUF_ERROR) + return POLLIN | POLLRDNORM; + return 0; + +err: + mutex_unlock(&fh->cap.vb_lock); + return POLLERR; +} + +static int video_release(struct file *file) +{ + struct tw68_fh *fh = file->private_data; + struct tw68_dev *dev = fh->dev; + + /* stop video capture */ + if (res_check(fh, RESOURCE_VIDEO)) { + videobuf_streamoff(&fh->cap); + res_free(fh , RESOURCE_VIDEO); + } + if (fh->cap.read_buf) { + buffer_release(&fh->cap, fh->cap.read_buf); + kfree(fh->cap.read_buf); + } + + /* stop vbi capture */ + if (res_check(fh, RESOURCE_VBI)) { + videobuf_stop(&fh->vbi); + res_free(fh, RESOURCE_VBI); + } + +#if 0 + tw_call_all(dev, core, s_standby, 0); +#endif + + /* free stuff */ + videobuf_mmap_free(&fh->cap); + videobuf_mmap_free(&fh->vbi); + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,34) + v4l2_prio_close(&dev->prio, &fh->prio); +#else + v4l2_prio_close(&dev->prio, fh->prio); +#endif + file->private_data = NULL; + kfree(fh); + return 0; +} + +static int video_mmap(struct file *file, struct vm_area_struct * vma) +{ + struct tw68_fh *fh = file->private_data; + + return videobuf_mmap_mapper(tw68_queue(fh), vma); +} + +/* ------------------------------------------------------------------ */ + +#if 0 +static int tw68_try_get_set_fmt_vbi_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tw68_fh *fh = priv; + struct tw68_dev *dev = fh->dev; + struct tw68_tvnorm *norm = dev->tvnorm; + + f->fmt.vbi.sampling_rate = 6750000 * 4; + f->fmt.vbi.samples_per_line = 2048 /* VBI_LINE_LENGTH */; + f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY; + f->fmt.vbi.offset = 64 * 4; + f->fmt.vbi.start[0] = norm->vbi_v_start_0; + f->fmt.vbi.count[0] = norm->vbi_v_stop_0 - norm->vbi_v_start_0 + 1; + f->fmt.vbi.start[1] = norm->vbi_v_start_1; + f->fmt.vbi.count[1] = f->fmt.vbi.count[0]; + f->fmt.vbi.flags = 0; /* VBI_UNSYNC VBI_INTERLACED */ + +#if 0 + if (V4L2_STD_PAL == norm->id) { + /* FIXME */ + f->fmt.vbi.start[0] += 3; + f->fmt.vbi.start[1] += 3*2; + } +#endif + return 0; +} +#endif + +/* + * Note that this routine returns what is stored in the fh structure, and + * does not interrogate any of the device registers. + */ +static int tw68_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tw68_fh *fh = priv; + struct tw68_dev *dev = fh->dev; + + dprintk(DBG_FLOW, "%s\n", __func__); + f->fmt.pix.width = fh->width; + f->fmt.pix.height = fh->height; + f->fmt.pix.field = fh->cap.field; + f->fmt.pix.pixelformat = fh->fmt->fourcc; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * (fh->fmt->depth)) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + return 0; +} + +static int tw68_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tw68_fh *fh = priv; + struct tw68_dev *dev = fh->dev; + struct tw68_format *fmt; + enum v4l2_field field; + unsigned int maxw, maxh; + + dprintk(DBG_FLOW, "%s\n", __func__); + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + if (NULL == fmt) + return -EINVAL; + + field = f->fmt.pix.field; + maxw = min(dev->crop_current.width*4, dev->crop_bounds.width); + maxh = min(dev->crop_current.height*4, dev->crop_bounds.height); + + if (V4L2_FIELD_ANY == field) { + field = (f->fmt.pix.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_BOTTOM; + } + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + break; + case V4L2_FIELD_INTERLACED: + maxh = maxh * 2; + break; + default: + return -EINVAL; + } + + f->fmt.pix.field = field; + if (f->fmt.pix.width < 48) + f->fmt.pix.width = 48; + if (f->fmt.pix.height < 32) + f->fmt.pix.height = 32; + 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.width &= ~0x03; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * (fmt->depth)) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; +} + +/* + * Note that tw68_s_fmt_vid_cap sets the information into the fh structure, + * and it will be used for all future new buffers. However, there could be + * some number of buffers on the "active" chain which will be filled before + * the change takes place. + */ +static int tw68_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tw68_fh *fh = priv; + struct tw68_dev *dev = fh->dev; + int err; + + dprintk(DBG_FLOW, "%s\n", __func__); + err = tw68_try_fmt_vid_cap(file, priv, f); + if (0 != err) + return err; + + fh->fmt = format_by_fourcc(f->fmt.pix.pixelformat); + fh->width = f->fmt.pix.width; + fh->height = f->fmt.pix.height; + fh->cap.field = f->fmt.pix.field; + /* + * The following lines are to make v4l2-test program happy. + * The docs should be checked to assure they make sense. + */ + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + f->fmt.pix.priv = 0; + return 0; +} + +static int tw68_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *c) +{ + const struct v4l2_queryctrl *ctrl; + struct tw68_fh *fh = priv; + struct tw68_dev *dev = fh->dev; + + dprintk(DBG_FLOW, "%s\n", __func__); + if ((c->id < V4L2_CID_BASE || c->id >= V4L2_CID_LASTP1) +#if 0 + && (c->id < V4L2_CID_PRIVATE_BASE || + c->id >= V4L2_CID_PRIVATE_LASTP1) +#endif + ) + return -EINVAL; + ctrl = ctrl_by_id(c->id); + if (NULL == ctrl) + return -EINVAL; + *c = *ctrl; + return 0; +} + +static int tw68_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + struct tw68_fh *fh = priv; + struct tw68_dev *dev = fh->dev; + unsigned int n; + + n = i->index; + dprintk(DBG_FLOW, "%s: index is %d\n", __func__, n); + if (n >= TW68_INPUT_MAX) { + dprintk(DBG_FLOW, "%s: INPUT_MAX reached\n", __func__); + return -EINVAL; + } + if (NULL == card_in(dev, n).name) { + dprintk(DBG_FLOW, "%s: End of list\n", __func__); + return -EINVAL; + } + memset(i, 0, sizeof(*i)); + i->index = n; + i->type = V4L2_INPUT_TYPE_CAMERA; + strcpy(i->name, card_in(dev, n).name); + if (card_in(dev, n).tv) + i->type = V4L2_INPUT_TYPE_TUNER; + i->audioset = 1; + /* If the query is for the current input, get live data */ + if (n == dev->hw_input->vmux) { + int v1 = tw_readb(TW68_STATUS1); + int v2 = tw_readb(TW68_MVSN); + + if (0 != (v1 & (1 << 7))) + i->status |= V4L2_IN_ST_NO_SYNC; + if (0 != (v1 & (1 << 6))) + i->status |= V4L2_IN_ST_NO_H_LOCK; + if (0 != (v1 & (1 << 2))) + i->status |= V4L2_IN_ST_NO_SIGNAL; + if (0 != (v1 & 1 << 1)) + i->status |= V4L2_IN_ST_NO_COLOR; + if (0 != (v2 & (1 << 2))) + i->status |= V4L2_IN_ST_MACROVISION; + } + i->std = TW68_NORMS; + return 0; +} + +static int tw68_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct tw68_fh *fh = priv; + struct tw68_dev *dev = fh->dev; + + dprintk(DBG_FLOW, "%s\n", __func__); + *i = dev->input->vmux; + return 0; +} + +static int tw68_s_input(struct file *file, void *priv, unsigned int i) +{ + struct tw68_fh *fh = priv; + struct tw68_dev *dev = fh->dev; + int err; + + dprintk(DBG_FLOW, "%s\n", __func__); +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,34) + err = v4l2_prio_check(&dev->prio, &fh->prio); +#else + err = v4l2_prio_check(&dev->prio, fh->prio); +#endif + if (0 != err) + if (0 != err) + return err; + + if (i < 0 || i >= TW68_INPUT_MAX) + return -EINVAL; + if (NULL == card_in(dev, i).name) + return -EINVAL; + mutex_lock(&dev->lock); + video_mux(dev, i); + mutex_unlock(&dev->lock); + return 0; +} + +static int tw68_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct tw68_fh *fh = priv; + struct tw68_dev *dev = fh->dev; + + unsigned int tuner_type = dev->tuner_type; + + dprintk(DBG_FLOW, "%s\n", __func__); + strcpy(cap->driver, "tw68"); + strlcpy(cap->card, tw68_boards[dev->board].name, + sizeof(cap->card)); + sprintf(cap->bus_info, "PCI:%s", pci_name(dev->pci)); + cap->version = TW68_VERSION_CODE; + cap->capabilities = + V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VBI_CAPTURE | + V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING | + V4L2_CAP_TUNER; + + if ((tuner_type == TUNER_ABSENT) || (tuner_type == UNSET)) + cap->capabilities &= ~V4L2_CAP_TUNER; + return 0; +} + +static int tw68_s_std_internal(struct tw68_dev *dev, struct tw68_fh *fh, + v4l2_std_id *id) +{ +/* unsigned long flags; */ + unsigned int i; + v4l2_std_id fixup; + int err; + + dprintk(DBG_FLOW, "%s\n", __func__); + if (fh) { +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,34) + err = v4l2_prio_check(&dev->prio, &fh->prio); +#else + err = v4l2_prio_check(&dev->prio, fh->prio); +#endif + if (0 != err) + if (0 != err) + return err; + } + + /* Look for match on complete norm id (may have mult bits) */ + for (i = 0; i < TVNORMS; i++) { + if (*id == tvnorms[i].id) + break; + } + + /* If no exact match, look for norm which contains this one */ + if (i == TVNORMS) + for (i = 0; i < TVNORMS; i++) { + if (*id & tvnorms[i].id) + break; + } + /* If still not matched, give up */ + if (i == TVNORMS) + return -EINVAL; + + /* TODO - verify this additional work with SECAM applies to TW */ + if ((*id & V4L2_STD_SECAM) && (secam[0] != '-')) { + if (secam[0] == 'L' || secam[0] == 'l') { + if (secam[1] == 'C' || secam[1] == 'c') + fixup = V4L2_STD_SECAM_LC; + else + fixup = V4L2_STD_SECAM_L; + } else { + if (secam[0] == 'D' || secam[0] == 'd') + fixup = V4L2_STD_SECAM_DK; + else + fixup = V4L2_STD_SECAM; + } + for (i = 0; i < TVNORMS; i++) + if (fixup == tvnorms[i].id) + break; + } + + *id = tvnorms[i].id; + mutex_lock(&dev->lock); + set_tvnorm(dev, &tvnorms[i]); /* do the actual setting */ + tw68_tvaudio_do_scan(dev); + mutex_unlock(&dev->lock); + return 0; +} + +static int tw68_s_std(struct file *file, void *priv, v4l2_std_id *id) +{ + struct tw68_fh *fh = priv; + struct tw68_dev *dev = fh->dev; + + dprintk(DBG_FLOW, "%s\n", __func__); + return tw68_s_std_internal(fh->dev, fh, id); +} + +static int tw68_g_std(struct file *file, void *priv, v4l2_std_id *id) +{ + struct tw68_fh *fh = priv; + struct tw68_dev *dev = fh->dev; + + dprintk(DBG_FLOW, "%s\n", __func__); + *id = dev->tvnorm->id; + return 0; +} + +static int tw68_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct tw68_fh *fh = priv; + struct tw68_dev *dev = fh->dev; + int n; + + if (unlikely(UNSET == dev->tuner_type)) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + memset(t, 0, sizeof(*t)); + for (n = 0; n < TW68_INPUT_MAX; n++) + if (card_in(dev, n).tv) + break; + if (n == TW68_INPUT_MAX) + return -EINVAL; +#if 0 + if (NULL != card_in(dev, n).name) { + strcpy(t->name, "Television"); + t->type = V4L2_TUNER_ANALOG_TV; + t->capability = V4L2_TUNER_CAP_NORM | + V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_LANG1 | + V4L2_TUNER_CAP_LANG2; + t->rangehigh = 0xffffffffUL; + t->rxsubchans = tw68_tvaudio_getstereo(dev); + t->audmode = tw68_tvaudio_rx2mode(t->rxsubchans); + } + if (0 != (saa_readb(TW68_STATUS_VIDEO1) & 0x03)) + t->signal = 0xffff; +#endif + return 0; +} + +static int tw68_s_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct tw68_fh *fh = priv; + struct tw68_dev *dev = fh->dev; + int err; +#if 0 + int rx, mode +#endif + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,34) + err = v4l2_prio_check(&dev->prio, &fh->prio); +#else + err = v4l2_prio_check(&dev->prio, fh->prio); +#endif + if (0 != err) + if (0 != err) + return err; + +#if 0 + mode = dev->thread.mode; + if (UNSET == mode) { + rx = tw68_tvaudio_getstereo(dev); + mode = tw68_tvaudio_rx2mode(t->rxsubchans); + } + if (mode != t->audmode) + dev->thread.mode = t->audmode; +#endif + return 0; +} + +static int tw68_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct tw68_fh *fh = priv; + struct tw68_dev *dev = fh->dev; + + if (unlikely(dev->tuner_type)) + return -EINVAL; + f->type = fh->radio ? V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV; +/* f->frequency = dev->ctl_freq; */ + + return 0; +} + +static int tw68_s_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct tw68_fh *fh = priv; + struct tw68_dev *dev = fh->dev; + int err; + + if (unlikely(UNSET == dev->tuner_type)) + return -EINVAL; +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,34) + err = v4l2_prio_check(&dev->prio, &fh->prio); +#else + err = v4l2_prio_check(&dev->prio, fh->prio); +#endif + if (0 != err) + if (0 != err) + return err; + + if (0 != f->tuner) + return -EINVAL; + if (0 == fh->radio && V4L2_TUNER_ANALOG_TV != f->type) + return -EINVAL; + if (1 == fh->radio && V4L2_TUNER_RADIO != f->type) + return -EINVAL; + mutex_lock(&dev->lock); +/* dev->ctl_freq = f->frequency; */ + + tw_call_all(dev, tuner, s_frequency, f); + + tw68_tvaudio_do_scan(dev); + mutex_unlock(&dev->lock); + return 0; +} + +static int tw68_g_audio(struct file *file, void *priv, struct v4l2_audio *a) +{ + strcpy(a->name, "audio"); + return 0; +} + +static int tw68_s_audio(struct file *file, void *priv, struct v4l2_audio *a) +{ + return 0; +} + +static int tw68_g_priority(struct file *file, void *f, enum v4l2_priority *p) +{ + struct tw68_fh *fh = f; + struct tw68_dev *dev = fh->dev; + + dprintk(DBG_FLOW, "%s\n", __func__); + *p = v4l2_prio_max(&dev->prio); + return 0; +} + +static int tw68_s_priority(struct file *file, void *f, + enum v4l2_priority prio) +{ + struct tw68_fh *fh = f; + struct tw68_dev *dev = fh->dev; + + dprintk(DBG_FLOW, "%s\n", __func__); + return v4l2_prio_change(&dev->prio, &fh->prio, prio); +} + +static int tw68_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct tw68_fh *fh = priv; + struct tw68_dev *dev = fh->dev; + + dprintk(DBG_FLOW, "%s\n", __func__); + if (f->index >= FORMATS) + return -EINVAL; + + strlcpy(f->description, formats[f->index].name, + sizeof(f->description)); + + f->pixelformat = formats[f->index].fourcc; + + return 0; +} + +static int tw68_cropcap(struct file *file, void *priv, + struct v4l2_cropcap *cap) +{ + struct tw68_fh *fh = priv; + struct tw68_dev *dev = fh->dev; + + dprintk(DBG_FLOW, "%s\n", __func__); + if (cap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + cap->bounds = dev->crop_bounds; + cap->defrect = dev->crop_defrect; + cap->pixelaspect.numerator = 1; + cap->pixelaspect.denominator = 1; + if (dev->tvnorm->id & V4L2_STD_525_60) { + cap->pixelaspect.numerator = 11; + cap->pixelaspect.denominator = 10; + } + if (dev->tvnorm->id & V4L2_STD_625_50) { + cap->pixelaspect.numerator = 54; + cap->pixelaspect.denominator = 59; + } + return 0; +} + +static int tw68_g_crop(struct file *file, void *f, struct v4l2_crop *crop) +{ + struct tw68_fh *fh = f; + struct tw68_dev *dev = fh->dev; + + dprintk(DBG_FLOW, "%s\n", __func__); + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + crop->c = dev->crop_current; + return 0; +} + +static int tw68_s_crop(struct file *file, void *f, struct v4l2_crop *crop) +{ + struct tw68_fh *fh = f; + struct tw68_dev *dev = fh->dev; + struct v4l2_rect *b = &dev->crop_bounds; + + dprintk(DBG_FLOW, "%s\n", __func__); + if (res_locked(fh->dev, RESOURCE_VIDEO)) + return -EBUSY; + + if ((crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) || + (crop->c.height < 0) || (crop->c.width < 0)) { + dprintk(DBG_UNEXPECTED, "%s: invalid request\n", __func__); + return -EINVAL; + } + + if (crop->c.top < b->top) + crop->c.top = b->top; + if (crop->c.top > b->top + b->height) + crop->c.top = b->top + b->height; + if (crop->c.height > b->top - crop->c.top + b->height) + crop->c.height = b->top - crop->c.top + b->height; + + if (crop->c.left < b->left) + crop->c.left = b->left; + if (crop->c.left > b->left + b->width) + crop->c.left = b->left + b->width; + if (crop->c.width > b->left - crop->c.left + b->width) + crop->c.width = b->left - crop->c.left + b->width; + + dprintk(DBG_FLOW, "%s: setting cropping rectangle: top=%d, left=%d, " + "width=%d, height=%d\n", __func__, crop->c.top, + crop->c.left, crop->c.width, crop->c.height); + dev->crop_current = crop->c; + return 0; +} + +/* + * Wrappers for the v4l2_ioctl_ops functions + */ +#ifdef CONFIG_VIDEO_V4L1_COMPAT +static int vidiocgmbuf(struct file *file, void *priv, struct video_mbuf *mbuf) +{ + struct tw68_fh *fh = file->private_data; + return videobuf_cgmbuf(tw68_queue(fh), mbuf, 8); +} +#endif + +static int tw68_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *p) +{ + struct tw68_fh *fh = priv; + return videobuf_reqbufs(tw68_queue(fh), p); +} + +static int tw68_querybuf(struct file *file, void *priv, + struct v4l2_buffer *b) +{ + struct tw68_fh *fh = priv; + return videobuf_querybuf(tw68_queue(fh), b); +} + +static int tw68_qbuf(struct file *file, void *priv, struct v4l2_buffer *b) +{ + struct tw68_fh *fh = priv; + return videobuf_qbuf(tw68_queue(fh), b); +} + +static int tw68_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b) +{ + struct tw68_fh *fh = priv; + return videobuf_dqbuf(tw68_queue(fh), b, + file->f_flags & O_NONBLOCK); +} + +static int tw68_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct tw68_fh *fh = priv; + struct tw68_dev *dev = fh->dev; + int res = tw68_resource(fh); + + dprintk(DBG_FLOW, "%s\n", __func__); + if (!res_get(fh, res)) + return -EBUSY; + + tw68_buffer_requeue(dev, &dev->video_q); + return videobuf_streamon(tw68_queue(fh)); +} + +static int tw68_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + int err; + struct tw68_fh *fh = priv; + struct tw68_dev *dev = fh->dev; + int res = tw68_resource(fh); + + dprintk(DBG_FLOW, "%s\n", __func__); + err = videobuf_streamoff(tw68_queue(fh)); + if (err < 0) + return err; + res_free(fh, res); + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +/* + * Used strictly for internal development and debugging, this routine + * prints out the current register contents for the tw68xx device. + */ +static void tw68_dump_regs(struct tw68_dev *dev) +{ + unsigned char line[80]; + int i, j, k; + unsigned char *cptr; + + printk(KERN_DEBUG "Full dump of TW68 registers:\n"); + /* First we do the PCI regs, 8 4-byte regs per line */ + for (i = 0; i < 0x100; i += 32) { + cptr = line; + cptr += sprintf(cptr, "%03x ", i); + /* j steps through the next 4 words */ + for (j = i; j < i + 16; j += 4) + cptr += sprintf(cptr, "%08x ", tw_readl(j)); + *cptr++ = ' '; + for (; j < i + 32; j += 4) + cptr += sprintf(cptr, "%08x ", tw_readl(j)); + *cptr++ = '\n'; + *cptr = 0; + printk(KERN_DEBUG "%s", line); + } + /* Next the control regs, which are single-byte, address mod 4 */ + while (i < 0x400) { + cptr = line; + cptr += sprintf(cptr, "%03x ", i); + /* Print out 4 groups of 4 bytes */ + for (j = 0; j < 4; j++) { + for (k = 0; k < 4; k++) { + cptr += sprintf(cptr, "%02x ", + tw_readb(i)); + i += 4; + } + *cptr++ = ' '; + } + *cptr++ = '\n'; + *cptr = 0; + printk(KERN_DEBUG "%s", line); + } +} + +static int vidioc_log_status(struct file *file, void *priv) +{ + struct tw68_fh *fh = priv; + struct tw68_dev *dev = fh->dev; + + tw68_dump_regs(dev); + return 0; +} + +static int vidioc_g_register(struct file *file, void *priv, + struct v4l2_dbg_register *reg) +{ + struct tw68_fh *fh = priv; + struct tw68_dev *dev = fh->dev; /* needed for tw_readb */ + + dprintk(DBG_FLOW, "%s\n", __func__); + if (!v4l2_chip_match_host(®->match)) + dprintk(DBG_UNEXPECTED, "%s: match failed\n", __func__); + return -EINVAL; + if (reg->size == 1) + reg->val = tw_readb(reg->reg); + else + reg->val = tw_readl(reg->reg); + return 0; +} + +static int vidioc_s_register(struct file *file, void *priv, + struct v4l2_dbg_register *reg) +{ + struct tw68_fh *fh = priv; + struct tw68_dev *dev = fh->dev; /* needed for tw_writeb */ + + dprintk(DBG_FLOW, "%s: request to set reg 0x%04x to 0x%02x\n", + __func__, (unsigned int)reg->reg, (unsigned int)reg->val); + if (!v4l2_chip_match_host(®->match)) { + dprintk(DBG_UNEXPECTED, "%s: match failed\n", __func__); + return -EINVAL; + } + if (reg->size == 1) + tw_writeb(reg->reg, reg->val); + else + tw_writel(reg->reg & 0xffff, reg->val); + return 0; +} +#endif + +static const struct v4l2_file_operations video_fops = { + .owner = THIS_MODULE, + .open = video_open, + .release = video_release, + .read = video_read, + .poll = video_poll, + .mmap = video_mmap, + .ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops video_ioctl_ops = { + .vidioc_querycap = tw68_querycap, + .vidioc_enum_fmt_vid_cap = tw68_enum_fmt_vid_cap, + .vidioc_reqbufs = tw68_reqbufs, + .vidioc_querybuf = tw68_querybuf, + .vidioc_qbuf = tw68_qbuf, + .vidioc_dqbuf = tw68_dqbuf, + .vidioc_s_std = tw68_s_std, + .vidioc_g_std = tw68_g_std, + .vidioc_enum_input = tw68_enum_input, + .vidioc_g_input = tw68_g_input, + .vidioc_s_input = tw68_s_input, + .vidioc_queryctrl = tw68_queryctrl, + .vidioc_g_ctrl = tw68_g_ctrl, + .vidioc_s_ctrl = tw68_s_ctrl, + .vidioc_streamon = tw68_streamon, + .vidioc_streamoff = tw68_streamoff, + .vidioc_g_priority = tw68_g_priority, + .vidioc_s_priority = tw68_s_priority, + .vidioc_g_fmt_vid_cap = tw68_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = tw68_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = tw68_s_fmt_vid_cap, + .vidioc_cropcap = tw68_cropcap, + .vidioc_g_crop = tw68_g_crop, + .vidioc_s_crop = tw68_s_crop, +/* + * Functions not yet implemented / not yet passing tests. + */ + +#if 0 + .vidioc_g_fmt_vbi_cap = tw68_try_get_set_fmt_vbi_cap, + .vidioc_try_fmt_vbi_cap = tw68_try_get_set_fmt_vbi_cap, + .vidioc_s_fmt_vbi_cap = tw68_try_get_set_fmt_vbi_cap, +#endif + .vidioc_g_audio = tw68_g_audio, + .vidioc_s_audio = tw68_s_audio, + .vidioc_g_tuner = tw68_g_tuner, + .vidioc_s_tuner = tw68_s_tuner, + .vidioc_g_frequency = tw68_g_frequency, + .vidioc_s_frequency = tw68_s_frequency, +#ifdef CONFIG_VIDEO_V4L1_COMPAT + .vidiocgmbuf = vidiocgmbuf, +#endif +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_log_status = vidioc_log_status, + .vidioc_g_register = vidioc_g_register, + .vidioc_s_register = vidioc_s_register, +#endif +}; + +/* ------------------------------------------------------------------ */ +/* exported stuff */ +struct video_device tw68_video_template = { + .name = "tw68_video", + .fops = &video_fops, + .ioctl_ops = &video_ioctl_ops, + .minor = -1, + .tvnorms = TW68_NORMS, + .current_norm = V4L2_STD_PAL, +}; + +struct video_device tw68_radio_template = { + .name = "tw68_radio", +}; + +int tw68_videoport_init(struct tw68_dev *dev) +{ + return 0; +} + +void tw68_set_tvnorm_hw(struct tw68_dev *dev) +{ + tw_andorb(TW68_SDT, 0x07, dev->tvnorm->format); + return; +} + +int tw68_video_init1(struct tw68_dev *dev) +{ + int i; + + dprintk(DBG_FLOW, "%s\n", __func__); + /* sanitycheck insmod options */ + if (gbuffers < 2 || gbuffers > VIDEO_MAX_FRAME) + gbuffers = 2; + if (gbufsz < 0 || gbufsz > gbufsz_max) + gbufsz = gbufsz_max; + gbufsz = (gbufsz + PAGE_SIZE - 1) & PAGE_MASK; + + /* put some sensible defaults into the data structures ... */ + for (i = 0; i < CTRLS; i++) + tw68_s_ctrl_value(dev, video_ctrls[i].id, + video_ctrls[i].default_value); +#if 0 + if (dev->tda9887_conf && dev->ctl_automute) + dev->tda9887_conf |= TDA9887_AUTOMUTE; + dev->automute = 0; +#endif + INIT_LIST_HEAD(&dev->video_q.queued); + INIT_LIST_HEAD(&dev->video_q.active); + init_timer(&dev->video_q.timeout); + dev->video_q.timeout.function = tw68_buffer_timeout; + dev->video_q.timeout.data = (unsigned long)(&dev->video_q); + dev->video_q.dev = dev; + dev->video_q.buf_compat = tw68_check_video_fmt; + dev->video_q.start_dma = tw68_video_start_dma; + tw68_risc_stopper(dev->pci, &dev->video_q.stopper); + + if (tw68_boards[dev->board].video_out) + tw68_videoport_init(dev); + + return 0; +} + +int tw68_video_init2(struct tw68_dev *dev) +{ + dprintk(DBG_FLOW, "%s\n", __func__); + set_tvnorm(dev, &tvnorms[0]); + video_mux(dev, 0); +/* + tw68_tvaudio_setmut(dev); + tw68_tvaudio_setvolume(dev, dev->ctl_volume); +*/ + return 0; +} + +/* + * tw68_irq_video_signalchange + * + * TODO: + * Check for presence of video signal. If not present, mute audio. + * If present, log type of signal present. + */ +void tw68_irq_video_signalchange(struct tw68_dev *dev) +{ + return; +} + +/* + * tw68_irq_video_done + */ +void tw68_irq_video_done(struct tw68_dev *dev, unsigned long status) +{ + __u32 reg; + + /* reset interrupts handled by this routine */ + tw_writel(TW68_INTSTAT, status); + /* + * Check most likely first + * + * DMAPI shows we have reached the end of the risc code + * for the current buffer. + */ + if (status & TW68_DMAPI) { + struct tw68_dmaqueue *q = &dev->video_q; + dprintk(DBG_FLOW | DBG_TESTING, "DMAPI interrupt\n"); + spin_lock(&dev->slock); + /* + * tw68_wakeup will take care of the buffer handling, + * plus any non-video requirements. + */ + tw68_wakeup(q, &dev->video_fieldcount); + spin_unlock(&dev->slock); + /* Check whether we have gotten into 'stopper' code */ + reg = tw_readl(TW68_DMAP_PP); + if ((reg >= q->stopper.dma) && + (reg < q->stopper.dma + q->stopper.size)) { + /* Yes - log the information */ + dprintk(DBG_FLOW | DBG_TESTING, + "%s: stopper risc code entered\n", __func__); + } + status &= ~(TW68_DMAPI); + if (0 == status) + return; + } + if (status & (TW68_VLOCK | TW68_HLOCK)) { /* lost sync */ + dprintk(DBG_UNUSUAL, "Lost sync\n"); + } + if (status & TW68_PABORT) { /* TODO - what should we do? */ + dprintk(DBG_UNEXPECTED, "PABORT interrupt\n"); + } + if (status & TW68_DMAPERR) { + dprintk(DBG_UNEXPECTED, "DMAPERR interrupt\n"); +#if 0 + /* Stop risc & fifo */ + tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN); + tw_clearl(TW68_INTMASK, dev->board_virqmask); + dev->pci_irqmask &= ~dev->board_virqmask; +#endif + } + /* + * On TW6800, FDMIS is apparently generated if video input is switched + * during operation. Therefore, it is not enabled for that chip. + */ + if (status & TW68_FDMIS) { /* logic error somewhere */ + dprintk(DBG_UNEXPECTED, "FDMIS interrupt\n"); + /* Stop risc & fifo */ +// tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN); +// tw_clearl(TW68_INTMASK, dev->board_virqmask); +// dev->pci_irqmask &= ~dev->board_virqmask; + } + if (status & TW68_FFOF) { /* probably a logic error */ + reg = tw_readl(TW68_DMAC) & TW68_FIFO_EN; + tw_clearl(TW68_DMAC, TW68_FIFO_EN); + dprintk(DBG_UNUSUAL, "FFOF interrupt\n"); + tw_setl(TW68_DMAC, reg); + } + if (status & TW68_FFERR) + dprintk(DBG_UNEXPECTED, "FFERR interrupt\n"); + return; +} diff --git a/drivers/media/pci/tw68/tw68.h b/drivers/media/pci/tw68/tw68.h new file mode 100644 index 000000000000..e723efb5e623 --- /dev/null +++ b/drivers/media/pci/tw68/tw68.h @@ -0,0 +1,588 @@ +/* + * tw68 driver common header file + * + * Much of this code is derived from the cx88 and sa7134 drivers, which + * were in turn derived from the bt87x driver. The original work was by + * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab, + * Hans Verkuil, Andy Walls and many others. Their work is gratefully + * acknowledged. Full credit goes to them - any problems within this code + * are mine. + * + * Copyright (C) 2009 William M. Brack <wbrack@mmm.com.hk> + * + * 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., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +#include <linux/version.h> +#define TW68_VERSION_CODE KERNEL_VERSION(0, 0, 8) + +#include <linux/pci.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <linux/videodev2.h> +#include <linux/kdev_t.h> +#include <linux/input.h> +#include <linux/notifier.h> +#include <linux/delay.h> +#include <linux/mutex.h> + +#include <asm/io.h> + +#include <media/v4l2-common.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-device.h> + +#include <media/tuner.h> +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38) +# include <media/ir-common.h> +#endif +#include <media/ir-kbd-i2c.h> +#include <media/videobuf-dma-sg.h> + +#include "btcx-risc.h" +#include "tw68-reg.h" + +#define UNSET (-1U) + +/* + * dprintk statement within the code use a 'level' argument. For + * our purposes, we use the following levels: + */ +#define DBG_UNEXPECTED (1 << 0) +#define DBG_UNUSUAL (1 << 1) +#define DBG_TESTING (1 << 2) +#define DBG_BUFF (1 << 3) +#define DBG_FLOW (1 << 15) + +/* system vendor and device ID's */ +#define PCI_VENDOR_ID_TECHWELL 0x1797 +#define PCI_DEVICE_ID_6800 0x6800 +#define PCI_DEVICE_ID_6801 0x6801 +#define PCI_DEVICE_ID_AUDIO2 0x6802 +#define PCI_DEVICE_ID_TS3 0x6803 +#define PCI_DEVICE_ID_6804 0x6804 +#define PCI_DEVICE_ID_AUDIO5 0x6805 +#define PCI_DEVICE_ID_TS6 0x6806 + +/* tw6816 based cards */ +#define PCI_DEVICE_ID_6816_1 0x6810 +#define PCI_DEVICE_ID_6816_2 0x6811 +#define PCI_DEVICE_ID_6816_3 0x6812 +#define PCI_DEVICE_ID_6816_4 0x6813 + +/* subsystem vendor ID's */ +#define TW68_PCI_ID_TECHWELL 0x1797 + +#define TW68_NORMS (\ + V4L2_STD_NTSC | V4L2_STD_PAL | V4L2_STD_SECAM | \ + V4L2_STD_PAL_BG | V4L2_STD_PAL_DK | V4L2_STD_PAL_I | \ + V4L2_STD_PAL_M | V4L2_STD_PAL_Nc | V4L2_STD_PAL_60 | \ + V4L2_STD_525_60 | V4L2_STD_625_50 | \ + V4L2_STD_SECAM_L| V4L2_STD_SECAM_LC | V4L2_STD_SECAM_DK) + +#define TW68_VID_INTS (TW68_FFERR | TW68_PABORT | TW68_DMAPERR | \ + TW68_FFOF | TW68_DMAPI) +/* TW6800 chips have trouble with these, so we don't set them for that chip */ +#define TW68_VID_INTSX (TW68_FDMIS | TW68_HLOCK | TW68_VLOCK) + +#define TW68_I2C_INTS (TW68_SBERR | TW68_SBDONE | TW68_SBERR2 | \ + TW68_SBDONE2) + +typedef enum { + TW6800, + TW6801, + TW6804, + TWXXXX, +} TW68_DECODER_TYPE; +/* ----------------------------------------------------------- */ +/* static data */ + +struct tw68_tvnorm { + char *name; + v4l2_std_id id; + + /* video decoder */ + u32 sync_control; + u32 luma_control; + u32 chroma_ctrl1; + u32 chroma_gain; + u32 chroma_ctrl2; + u32 vgate_misc; + + /* video scaler */ + u32 h_delay; + u32 h_delay0; /* for TW6800 */ + u32 h_start; + u32 h_stop; + u32 v_delay; + u32 video_v_start; + u32 video_v_stop; + u32 vbi_v_start_0; + u32 vbi_v_stop_0; + u32 vbi_v_start_1; + + /* Techwell specific */ + u32 format; +}; + +struct tw68_format { + char *name; + u32 fourcc; + u32 depth; + u32 twformat; +}; + +/* ----------------------------------------------------------- */ +/* card configuration */ + +#define TW68_BOARD_NOAUTO UNSET +#define TW68_BOARD_UNKNOWN 0 +#define TW68_BOARD_GENERIC_6802 1 + +#define TW68_MAXBOARDS 16 +#define TW68_INPUT_MAX 8 + +/* ----------------------------------------------------------- */ +/* enums */ + +enum tw68_mpeg_type { + TW68_MPEG_UNUSED, + TW68_MPEG_EMPRESS, + TW68_MPEG_DVB, +}; + +enum tw68_audio_in { + TV = 1, + LINE1 = 2, + LINE2 = 3, + LINE2_LEFT, +}; + +enum tw68_video_out { + CCIR656 = 1, +}; + +/* Structs for card definition */ +struct tw68_input { + char *name; /* text description */ + unsigned int vmux; /* mux value */ + enum tw68_audio_in mux; + unsigned int gpio; + unsigned int tv:1; +}; + +struct tw68_board { + char *name; + unsigned int audio_clock; + + /* input switching */ + unsigned int gpiomask; + struct tw68_input inputs[TW68_INPUT_MAX]; + struct tw68_input radio; + struct tw68_input mute; + + /* i2c chip info */ + unsigned int tuner_type; + unsigned int radio_type; + unsigned char tuner_addr; + unsigned char radio_addr; + + unsigned int tda9887_conf; + unsigned int tuner_config; + + enum tw68_video_out video_out; + enum tw68_mpeg_type mpeg; + unsigned int vid_port_opts; +}; + +#define card_has_radio(dev) (NULL != tw68_boards[dev->board].radio.name) +#define card_has_mpeg(dev) (TW68_MPEG_UNUSED != \ + tw68_boards[dev->board].mpeg) +#define card_in(dev, n) (tw68_boards[dev->board].inputs[n]) +#define card(dev) (tw68_boards[dev->board]) + +/* ----------------------------------------------------------- */ +/* device / file handle status */ + +#define RESOURCE_VIDEO 1 +#define RESOURCE_VBI 2 + +#define INTERLACE_AUTO 0 +#define INTERLACE_ON 1 +#define INTERLACE_OFF 2 + +#define BUFFER_TIMEOUT msecs_to_jiffies(500) /* 0.5 seconds */ + +struct tw68_dev; /* forward delclaration */ + +/* tvaudio thread status */ +struct tw68_thread { + struct task_struct *thread; + unsigned int scan1; + unsigned int scan2; + unsigned int mode; + unsigned int stopped; +}; + +/* buffer for one video/vbi/ts frame */ +struct tw68_buf { + /* common v4l buffer stuff -- must be first */ + struct videobuf_buffer vb; + + /* tw68 specific */ + struct tw68_format *fmt; + struct tw68_input *input; + unsigned int top_seen; + int (*activate)(struct tw68_dev *dev, + struct tw68_buf *buf, + struct tw68_buf *next); + struct btcx_riscmem risc; + unsigned int bpl; +}; + +struct tw68_dmaqueue { + struct tw68_dev *dev; + struct list_head active; + struct list_head queued; + struct timer_list timeout; + struct btcx_riscmem stopper; + int (*buf_compat)(struct tw68_buf *prev, + struct tw68_buf *buf); + int (*start_dma)(struct tw68_dev *dev, + struct tw68_dmaqueue *q, + struct tw68_buf *buf); +}; + +/* video filehandle status */ +struct tw68_fh { + struct tw68_dev *dev; + unsigned int radio; + enum v4l2_buf_type type; + unsigned int resources; + enum v4l2_priority prio; + + /* video capture */ + struct tw68_format *fmt; + unsigned int width, height; + struct videobuf_queue cap; /* also used for overlay */ + + /* vbi capture */ + struct videobuf_queue vbi; +}; + +/* dmasound dsp status */ +struct tw68_dmasound { + struct mutex lock; + int minor_mixer; + int minor_dsp; + unsigned int users_dsp; + + /* mixer */ + enum tw68_audio_in input; + unsigned int count; + unsigned int line1; + unsigned int line2; + + /* dsp */ + unsigned int afmt; + unsigned int rate; + unsigned int channels; + unsigned int recording_on; + unsigned int dma_running; + unsigned int blocks; + unsigned int blksize; + unsigned int bufsize; + struct videobuf_dmabuf dma; + unsigned int dma_blk; + unsigned int read_offset; + unsigned int read_count; + void *priv_data; + struct snd_pcm_substream *substream; +}; + +struct tw68_fmt { + char *name; + u32 fourcc; /* v4l2 format id */ + int depth; + int flags; + u32 twformat; +}; + +/* ts/mpeg status */ +struct tw68_ts { + /* TS capture */ + int nr_packets; + int nr_bufs; +}; + +/* ts/mpeg ops */ +struct tw68_mpeg_ops { + enum tw68_mpeg_type type; + struct list_head next; + int (*init)(struct tw68_dev *dev); + int (*fini)(struct tw68_dev *dev); + void (*signal_change)(struct tw68_dev *dev); +}; + +enum tw68_ts_status { + TW68_TS_STOPPED, + TW68_TS_BUFF_DONE, + TW68_TS_STARTED, +}; + +/* global device status */ +struct tw68_dev { + struct list_head devlist; + struct mutex lock; + spinlock_t slock; + struct v4l2_prio_state prio; + struct v4l2_device v4l2_dev; + /* workstruct for loading modules */ + struct work_struct request_module_wk; + + /* insmod option/autodetected */ + int autodetected; + + /* various device info */ + TW68_DECODER_TYPE vdecoder; + unsigned int resources; + struct video_device *video_dev; + struct video_device *radio_dev; + struct video_device *vbi_dev; + struct tw68_dmasound dmasound; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0) + /* infrared remote */ + int has_remote; + struct card_ir *remote; +#endif + + /* pci i/o */ + char name[32]; + int nr; + struct pci_dev *pci; + unsigned char pci_rev, pci_lat; + u32 __iomem *lmmio; + u8 __iomem *bmmio; + u32 pci_irqmask; + /* The irq mask to be used will depend upon the chip type */ + u32 board_virqmask; + + /* config info */ + unsigned int board; + unsigned int tuner_type; + unsigned int radio_type; + unsigned char tuner_addr; + unsigned char radio_addr; + + unsigned int tda9887_conf; + unsigned int gpio_value; + + /* i2c i/o */ + struct i2c_algo_bit_data i2c_algo; + struct i2c_adapter i2c_adap; + struct i2c_client i2c_client; + u32 i2c_state; + u32 i2c_done; + wait_queue_head_t i2c_queue; + int i2c_rc; + unsigned char eedata[256]; + + /* video+ts+vbi capture */ + struct tw68_dmaqueue video_q; + struct tw68_dmaqueue vbi_q; + unsigned int video_fieldcount; + unsigned int vbi_fieldcount; + + /* various v4l controls */ + struct tw68_tvnorm *tvnorm; /* video */ + struct tw68_tvaudio *tvaudio; +#if 0 + unsigned int ctl_input; + int ctl_bright; + int ctl_contrast; + int ctl_hue; + int ctl_saturation; + int ctl_freq; + int ctl_mute; /* audio */ + int ctl_volume; + int ctl_invert; /* private */ + int ctl_mirror; + int ctl_y_odd; + int ctl_y_even; + int ctl_automute; +#endif + + /* crop */ + struct v4l2_rect crop_bounds; + struct v4l2_rect crop_defrect; + struct v4l2_rect crop_current; + + /* other global state info */ + unsigned int automute; + struct tw68_thread thread; + /* input is latest requested by app, hw_input is current hw setting */ + struct tw68_input *input; + struct tw68_input *hw_input; + unsigned int hw_mute; + int last_carrier; + int nosignal; + unsigned int insuspend; + + /* TW68_MPEG_* */ + struct tw68_ts ts; + struct tw68_dmaqueue ts_q; + enum tw68_ts_status ts_state; + unsigned int buff_cnt; + struct tw68_mpeg_ops *mops; + + void (*gate_ctrl)(struct tw68_dev *dev, int open); +}; + +/* ----------------------------------------------------------- */ + +#define tw_readl(reg) readl(dev->lmmio + ((reg) >> 2)) +#define tw_readb(reg) readb(dev->bmmio + (reg)) +#define tw_writel(reg, value) writel((value), dev->lmmio + ((reg) >> 2)) +#define tw_writeb(reg, value) writeb((value), dev->bmmio + (reg)) + +#define tw_andorl(reg, mask, value) \ + writel((readl(dev->lmmio+((reg)>>2)) & ~(mask)) |\ + ((value) & (mask)), dev->lmmio+((reg)>>2)) +#define tw_andorb(reg, mask, value) \ + writeb((readb(dev->bmmio + (reg)) & ~(mask)) |\ + ((value) & (mask)), dev->bmmio+(reg)) +#define tw_setl(reg, bit) tw_andorl((reg), (bit), (bit)) +#define tw_setb(reg, bit) tw_andorb((reg), (bit), (bit)) +#define tw_clearl(reg, bit) \ + writel((readl(dev->lmmio + ((reg) >> 2)) & ~(bit)), \ + dev->lmmio + ((reg) >> 2)) +#define tw_clearb(reg, bit) \ + writeb((readb(dev->bmmio+(reg)) & ~(bit)), \ + dev->bmmio + (reg)) +#define tw_call_all(dev, o, f, args...) do { \ + if (dev->gate_ctrl) \ + dev->gate_ctrl(dev, 1); \ + v4l2_device_call_all(&(dev)->v4l2_dev, 0, o, f , ##args); \ + if (dev->gate_ctrl) \ + dev->gate_ctrl(dev, 0); \ +} while (0) + +#define tw_wait(us) { udelay(us); } + +static inline struct tw68_dev *to_tw68_dev(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct tw68_dev, v4l2_dev); +} + +/* ----------------------------------------------------------- */ +/* tw68-core.c */ + +extern struct list_head tw68_devlist; +extern struct mutex tw68_devlist_lock; +extern unsigned int irq_debug; + +int tw68_buffer_count(unsigned int size, unsigned int count); +void tw68_buffer_queue(struct tw68_dev *dev, struct tw68_dmaqueue *q, + struct tw68_buf *buf); +void tw68_buffer_timeout(unsigned long data); +int tw68_set_dmabits(struct tw68_dev *dev); +void tw68_dma_free(struct videobuf_queue *q, struct tw68_buf *buf); +void tw68_wakeup(struct tw68_dmaqueue *q, unsigned int *field_count); +int tw68_buffer_requeue(struct tw68_dev *dev, struct tw68_dmaqueue *q); + +/* ----------------------------------------------------------- */ +/* tw68-cards.c */ + +extern struct tw68_board tw68_boards[]; +extern const unsigned int tw68_bcount; +extern struct pci_device_id __devinitdata tw68_pci_tbl[]; + +int tw68_board_init1(struct tw68_dev *dev); +int tw68_board_init2(struct tw68_dev *dev); +int tw68_tuner_callback(void *priv, int component, int command, int arg); + +/* ----------------------------------------------------------- */ +/* tw68-i2c.c */ + +int tw68_i2c_register(struct tw68_dev *dev); +int tw68_i2c_unregister(struct tw68_dev *dev); +void tw68_irq_i2c(struct tw68_dev *dev, int status); + +/* ----------------------------------------------------------- */ +/* tw68-video.c */ + +extern unsigned int video_debug; +extern struct video_device tw68_video_template; +extern struct video_device tw68_radio_template; + +int tw68_videoport_init(struct tw68_dev *dev); +void tw68_set_tvnorm_hw(struct tw68_dev *dev); + +int tw68_video_init1(struct tw68_dev *dev); +int tw68_video_init2(struct tw68_dev *dev); +void tw68_irq_video_signalchange(struct tw68_dev *dev); +void tw68_irq_video_done(struct tw68_dev *dev, unsigned long status); + +/* ----------------------------------------------------------- */ +/* tw68-ts.c */ + +int tw68_ts_init1(struct tw68_dev *dev); +int tw68_ts_fini(struct tw68_dev *dev); +void tw68_irq_ts_done(struct tw68_dev *dev, unsigned long status); + +int tw68_ts_register(struct tw68_mpeg_ops *ops); +void tw68_ts_unregister(struct tw68_mpeg_ops *ops); + +int tw68_ts_init_hw(struct tw68_dev *dev); + +/* ----------------------------------------------------------- */ +/* tw68-vbi.c */ + +extern struct videobuf_queue_ops tw68_vbi_qops; +extern struct video_device tw68_vbi_template; + +int tw68_vbi_init1(struct tw68_dev *dev); +int tw68_vbi_fini(struct tw68_dev *dev); +void tw68_irq_vbi_done(struct tw68_dev *dev, unsigned long status); + +/* ----------------------------------------------------------- */ +/* tw68-tvaudio.c */ + +int tw68_tvaudio_rx2mode(u32 rx); + +void tw68_tvaudio_setmute(struct tw68_dev *dev); +void tw68_tvaudio_setinput(struct tw68_dev *dev, + struct tw68_input *in); +void tw68_tvaudio_setvolume(struct tw68_dev *dev, int level); +int tw68_tvaudio_getstereo(struct tw68_dev *dev); +void tw68_tvaudio_init(struct tw68_dev *dev); +int tw68_tvaudio_init2(struct tw68_dev *dev); +int tw68_tvaudio_fini(struct tw68_dev *dev); +int tw68_tvaudio_do_scan(struct tw68_dev *dev); +int tw_dsp_writel(struct tw68_dev *dev, int reg, u32 value); +void tw68_enable_i2s(struct tw68_dev *dev); + +/* ----------------------------------------------------------- */ +/* tw68-risc.c */ + +int tw68_risc_buffer(struct pci_dev *pci, struct btcx_riscmem *risc, + struct scatterlist *sglist, unsigned int top_offset, + unsigned int bottom_offset, unsigned int bpl, + unsigned int padding, unsigned int lines); +int tw68_risc_stopper(struct pci_dev *pci, struct btcx_riscmem *risc); +int tw68_risc_overlay(struct tw68_fh *fh, struct btcx_riscmem *risc, + int field_type); |