Commit e15d1c12 authored by Hans Verkuil's avatar Hans Verkuil Committed by Mauro Carvalho Chehab

[media] tw68: refactor and cleanup the tw68 driver

Refactor and clean up the tw68 driver. It's now using the proper
V4L2 core frameworks.

Tested with my Techwell tw6805a and tw6816 grabber boards.
Signed-off-by: default avatarHans Verkuil <hans.verkuil@cisco.com>
Signed-off-by: default avatarMauro Carvalho Chehab <m.chehab@samsung.com>
parent 5740f4e7
......@@ -20,6 +20,7 @@ source "drivers/media/pci/ivtv/Kconfig"
source "drivers/media/pci/zoran/Kconfig"
source "drivers/media/pci/saa7146/Kconfig"
source "drivers/media/pci/solo6x10/Kconfig"
source "drivers/media/pci/tw68/Kconfig"
endif
if MEDIA_ANALOG_TV_SUPPORT || MEDIA_DIGITAL_TV_SUPPORT
......
......@@ -21,6 +21,7 @@ obj-$(CONFIG_VIDEO_CX88) += cx88/
obj-$(CONFIG_VIDEO_BT848) += bt8xx/
obj-$(CONFIG_VIDEO_SAA7134) += saa7134/
obj-$(CONFIG_VIDEO_SAA7164) += saa7164/
obj-$(CONFIG_VIDEO_TW68) += tw68/
obj-$(CONFIG_VIDEO_MEYE) += meye/
obj-$(CONFIG_STA2X11_VIP) += sta2x11/
obj-$(CONFIG_VIDEO_SOLO6X10) += solo6x10/
config VIDEO_TW68
tristate "Techwell tw68x Video For Linux"
depends on VIDEO_DEV && PCI && VIDEO_V4L2
select I2C_ALGOBIT
select VIDEOBUF2_DMA_SG
---help---
Support for Techwell tw68xx based frame grabber boards.
To compile this driver as a module, choose M here: the
module will be called tw68.
tw68-objs := tw68-core.o tw68-video.o tw68-risc.o
obj-$(CONFIG_VIDEO_TW68) += tw68.o
/*
* 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;
}
......@@ -9,7 +9,11 @@
* acknowledged. Full credit goes to them - any problems within this code
* are mine.
*
* Copyright (C) 2009 William M. Brack <wbrack@mmm.com.hk>
* Copyright (C) 2009 William M. Brack
*
* Refactored and updated to the latest v4l core frameworks:
*
* Copyright (C) 2014 Hans Verkuil <hverkuil@xs4all.nl>
*
* 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
......@@ -20,10 +24,6 @@
* 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>
......@@ -44,320 +44,44 @@
#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_AUTHOR("William M. Brack");
MODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>");
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);
static unsigned int card[] = {[0 ... (TW68_MAXBOARDS - 1)] = UNSET };
module_param_array(card, int, NULL, 0444);
MODULE_PARM_DESC(card, "card type");
#define dprintk(level, fmt, arg...) if (core_debug & (level)) \
printk(KERN_DEBUG "%s: " fmt, dev->name , ## arg)
static atomic_t tw68_instance = ATOMIC_INIT(0);
/* ------------------------------------------------------------------ */
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.
* 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.
*/
/* 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);
}
struct pci_device_id tw68_pci_tbl[] = {
{PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6800)},
{PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6801)},
{PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6804)},
{PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6816_1)},
{PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6816_2)},
{PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6816_3)},
{PCI_DEVICE(PCI_VENDOR_ID_TECHWELL, PCI_DEVICE_ID_6816_4)},
{0,}
};
/* ------------------------------------------------------------------ */
/* 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,
......@@ -367,7 +91,6 @@ static int tw68_hw_enable1(struct tw68_dev *dev)
*/
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 */
......@@ -415,7 +138,7 @@ static int tw68_hw_init1(struct tw68_dev *dev)
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, 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 */
......@@ -465,80 +188,9 @@ static int tw68_hw_init1(struct tw68_dev *dev)
/* 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;
......@@ -548,126 +200,39 @@ static irqreturn_t tw68_irq(int irq, void *dev_id)
status = orig = tw_readl(TW68_INTSTAT) & dev->pci_irqmask;
/* Check if anything to do */
if (0 == status)
return IRQ_RETVAL(0); /* Nope - return */
return IRQ_NONE; /* 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;
return IRQ_HANDLED;
}
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);
dev_dbg(&dev->pci->dev, "%s: **** INTERRUPT NOT HANDLED - clearing mask (orig 0x%08x, cur 0x%08x)",
dev->name, orig, tw_readl(TW68_INTSTAT));
dev_dbg(&dev->pci->dev, "%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;
return IRQ_HANDLED;
}
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,
static int tw68_initdev(struct pci_dev *pci_dev,
const struct pci_device_id *pci_id)
{
struct tw68_dev *dev;
struct tw68_mpeg_ops *mops;
int vidnr = -1;
int err;
if (tw68_devcount == TW68_MAXBOARDS)
return -ENOMEM;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
dev = devm_kzalloc(&pci_dev->dev, sizeof(*dev), GFP_KERNEL);
if (NULL == dev)
return -ENOMEM;
dev->instance = v4l2_device_set_name(&dev->v4l2_dev, "tw68",
&tw68_instance);
err = v4l2_device_register(&pci_dev->dev, &dev->v4l2_dev);
if (err)
goto fail0;
return err;
/* pci init */
dev->pci = pci_dev;
......@@ -676,33 +241,10 @@ static int __devinit tw68_initdev(struct pci_dev *pci_dev,
goto fail1;
}
dev->nr = tw68_devcount;
sprintf(dev->name, "tw%x[%d]", pci_dev->device, dev->nr);
dev->name = dev->v4l2_dev.name;
/* 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",
pr_info("%s: setting pci latency timer to %d\n",
dev->name, latency);
pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, latency);
}
......@@ -710,13 +252,12 @@ static int __devinit tw68_initdev(struct pci_dev *pci_dev,
/* 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));
pr_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, (u64)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);
pr_info("%s: Oops: no 32bit PCI DMA ???\n", dev->name);
err = -EIO;
goto fail1;
}
......@@ -730,7 +271,7 @@ static int __devinit tw68_initdev(struct pci_dev *pci_dev,
dev->vdecoder = TW6801;
dev->board_virqmask = TW68_VID_INTS | TW68_VID_INTSX;
break;
case PCI_DEVICE_ID_6804: /* Video decoder for TW6805 */
case PCI_DEVICE_ID_6804: /* Video decoder for TW6804 */
dev->vdecoder = TW6804;
dev->board_virqmask = TW68_VID_INTS | TW68_VID_INTSX;
break;
......@@ -739,35 +280,13 @@ static int __devinit tw68_initdev(struct pci_dev *pci_dev,
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",
pr_err("%s: can't get MMIO memory @ 0x%llx\n",
dev->name,
(unsigned long long)pci_resource_start(pci_dev, 0));
goto fail1;
......@@ -777,185 +296,75 @@ static int __devinit tw68_initdev(struct pci_dev *pci_dev,
dev->bmmio = (__u8 __iomem *)dev->lmmio;
if (NULL == dev->lmmio) {
err = -EIO;
printk(KERN_ERR "%s: can't ioremap() MMIO memory\n",
pr_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,
err = devm_request_irq(&pci_dev->dev, 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",
pr_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 (dev->instance < TW68_MAXBOARDS)
vidnr = video_nr[dev->instance];
/* initialise video function first */
err = tw68_video_init2(dev, vidnr);
if (err < 0) {
printk(KERN_INFO "%s: can't register video device\n",
pr_err("%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++;
tw_setl(TW68_INTMASK, dev->pci_irqmask);
if (tw68_dmasound_init && !dev->dmasound.priv_data)
tw68_dmasound_init(dev);
pr_info("%s: registered device %s\n",
dev->name, video_device_node_name(&dev->vdev));
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);
fail4:
video_unregister_device(&dev->vdev);
fail3:
iounmap(dev->lmmio);
fail2:
fail2:
release_mem_region(pci_resource_start(pci_dev, 0),
pci_resource_len(pci_dev, 0));
fail1:
fail1:
v4l2_device_unregister(&dev->v4l2_dev);
fail0:
kfree(dev);
return err;
}
static void __devexit tw68_finidev(struct pci_dev *pci_dev)
static void 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;
}
video_unregister_device(&dev->vdev);
v4l2_ctrl_handler_free(&dev->hdl);
/* 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
......@@ -966,28 +375,15 @@ static int tw68_suspend(struct pci_dev *pci_dev , pm_message_t state)
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));
vb2_discard_done(&dev->vidq);
return 0;
}
......@@ -997,54 +393,25 @@ 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);
struct tw68_buf *buf;
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*/
buf = container_of(dev->active.next, struct tw68_buf, list);
dev->dmasound.dma_running = 0;
tw68_video_start_dma(dev, buf);
/* start DMA now*/
dev->insuspend = 0;
smp_wmb();
tw68_set_dmabits(dev);
spin_unlock_irqrestore(&dev->slock, flags);
return 0;
......@@ -1057,35 +424,11 @@ static struct pci_driver tw68_pci_driver = {
.name = "tw68",
.id_table = tw68_pci_tbl,
.probe = tw68_initdev,
.remove = __devexit_p(tw68_finidev),
.remove = 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);
module_pci_driver(tw68_pci_driver);
/*
* 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;
}
......@@ -8,7 +8,11 @@
* acknowledged. Full credit goes to them - any problems within this code
* are mine.
*
* Copyright (C) William M. Brack <wbrack@mmm.com.hk>
* Copyright (C) William M. Brack
*
* Refactored and updated to the latest v4l core frameworks:
*
* Copyright (C) 2014 Hans Verkuil <hverkuil@xs4all.nl>
*
* 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
......@@ -19,10 +23,6 @@
* 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_
......
......@@ -9,7 +9,11 @@
* acknowledged. Full credit goes to them - any problems within this code
* are mine.
*
* Copyright (C) 2009 William M. Brack <wbrack@mmm.com.hk>
* Copyright (C) 2009 William M. Brack
*
* Refactored and updated to the latest v4l core frameworks:
*
* Copyright (C) 2014 Hans Verkuil <hverkuil@xs4all.nl>
*
* 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
......@@ -20,16 +24,10 @@
* 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
......@@ -38,32 +36,35 @@
* @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
* @jump insert a jump at the start
*/
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)
unsigned int lines, bool jump)
{
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);
if (jump) {
*(rp++) = cpu_to_le32(RISC_JUMP);
*(rp++) = 0;
}
/* sync instruction */
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++;
sg = sg_next(sg);
}
if (bpl <= sg_dma_len(sg) - offset) {
/* fits into current chunk */
......@@ -86,7 +87,7 @@ static __le32 *tw68_risc_field(__le32 *rp, struct scatterlist *sglist,
done);
*(rp++) = cpu_to_le32(sg_dma_address(sg) + offset);
todo -= done;
sg++;
sg = sg_next(sg);
/* succeeding fragments have no offset */
while (todo > sg_dma_len(sg)) {
*(rp++) = cpu_to_le32(RISC_INLINE |
......@@ -94,7 +95,7 @@ static __le32 *tw68_risc_field(__le32 *rp, struct scatterlist *sglist,
sg_dma_len(sg));
*(rp++) = cpu_to_le32(sg_dma_address(sg));
todo -= sg_dma_len(sg);
sg++;
sg = sg_next(sg);
done += sg_dma_len(sg);
}
if (todo) {
......@@ -107,9 +108,6 @@ static __le32 *tw68_risc_field(__le32 *rp, struct scatterlist *sglist,
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;
......@@ -118,25 +116,25 @@ static __le32 *tw68_risc_field(__le32 *rp, struct scatterlist *sglist,
/**
* 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".
* 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
* @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 tw68_buf *buf,
struct scatterlist *sglist,
unsigned int top_offset,
unsigned int bottom_offset,
......@@ -146,7 +144,6 @@ int tw68_risc_buffer(struct pci_dev *pci,
{
u32 instructions, fields;
__le32 *rp;
int rc;
fields = 0;
if (UNSET != top_offset)
......@@ -155,29 +152,31 @@ int tw68_risc_buffer(struct pci_dev *pci,
fields++;
/*
* estimate risc mem: worst case is one write per page border +
* one write per scan line + syncs + jump (all 2 dwords).
* one write per scan line + syncs + 2 jumps (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;
PAGE_SIZE) + lines) + 4;
buf->size = instructions * 8;
buf->cpu = pci_alloc_consistent(pci, buf->size, &buf->dma);
if (buf->cpu == NULL)
return -ENOMEM;
/* write risc instructions */
rp = risc->cpu;
rp = buf->cpu;
if (UNSET != top_offset) /* generates SYNCO */
rp = tw68_risc_field(rp, sglist, top_offset, 1,
bpl, padding, lines, 0);
bpl, padding, lines, true);
if (UNSET != bottom_offset) /* generates SYNCE */
rp = tw68_risc_field(rp, sglist, bottom_offset, 2,
bpl, padding, lines, 0);
bpl, padding, lines, top_offset == UNSET);
/* save pointer to jmp instruction address */
risc->jmp = rp;
buf->jmp = rp;
buf->cpu[1] = cpu_to_le32(buf->dma + 8);
/* assure risc buffer hasn't overflowed */
BUG_ON((risc->jmp - risc->cpu + 2) * sizeof(*risc->cpu) > risc->size);
BUG_ON((buf->jmp - buf->cpu + 2) * sizeof(buf->cpu[0]) > buf->size);
return 0;
}
......@@ -204,65 +203,28 @@ static void tw68_risc_decode(u32 risc, u32 addr)
p = RISC_OP(risc);
if (!(risc & 0x80000000) || !instr[p].name) {
printk(KERN_DEBUG "0x%08x [ INVALID ]\n", risc);
pr_debug("0x%08x [ INVALID ]\n", risc);
return;
}
printk(KERN_DEBUG "0x%08x %-9s IRQ=%d",
pr_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);
pr_debug(" Type=%d", (risc >> 24) & 7);
if (instr[p].has_byte_info)
printk(KERN_DEBUG " Start=0x%03x Count=%03u",
pr_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");
pr_debug(" StartAddr=0x%08x", addr);
pr_debug("\n");
}
void tw68_risc_program_dump(struct tw68_core *core,
struct btcx_riscmem *risc)
void tw68_risc_program_dump(struct tw68_core *core, struct tw68_buf *buf)
{
__le32 *addr;
const __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)
pr_debug("%s: risc_program_dump: risc=%p, buf->cpu=0x%p, buf->jmp=0x%p\n",
core->name, buf, buf->cpu, buf->jmp);
for (addr = buf->cpu; addr <= buf->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;
}
/*
* 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;
}
/*
* 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;
}
/*
* 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;
}
......@@ -8,7 +8,11 @@
* acknowledged. Full credit goes to them - any problems within this code
* are mine.
*
* Copyright (C) 2009 William M. Brack <wbrack@mmm.com.hk>
* Copyright (C) 2009 William M. Brack
*
* Refactored and updated to the latest v4l core frameworks:
*
* Copyright (C) 2014 Hans Verkuil <hverkuil@xs4all.nl>
*
* 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
......@@ -19,39 +23,16 @@
* 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 <media/v4l2-event.h>
#include <media/videobuf2-dma-sg.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 */
/*
......@@ -60,7 +41,7 @@ MODULE_PARM_DESC(secam, "force SECAM variant, either DK,L or Lc");
* 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[] = {
static const struct tw68_format formats[] = {
{
.name = "15 bpp RGB, le",
.fourcc = V4L2_PIX_FMT_RGB555,
......@@ -145,47 +126,8 @@ static struct tw68_format formats[] = {
* 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[] = {
static const 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,
......@@ -197,7 +139,6 @@ static struct tw68_tvnorm tvnorms[] = {
.chroma_ctrl2 = 0x06,
.vgate_misc = 0x1c,
.format = VideoFormatPALBDGHI,
}, {
.name = "NTSC",
.id = V4L2_STD_NTSC,
......@@ -210,46 +151,6 @@ static struct tw68_tvnorm tvnorms[] = {
.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,
......@@ -262,7 +163,6 @@ static struct tw68_tvnorm tvnorms[] = {
.chroma_ctrl2 = 0x00,
.vgate_misc = 0x1c,
.format = VideoFormatSECAM,
}, {
.name = "PAL-M",
.id = V4L2_STD_PAL_M,
......@@ -275,7 +175,6 @@ static struct tw68_tvnorm tvnorms[] = {
.chroma_ctrl2 = 0x0e,
.vgate_misc = 0x18,
.format = VideoFormatPALM,
}, {
.name = "PAL-Nc",
.id = V4L2_STD_PAL_Nc,
......@@ -288,7 +187,6 @@ static struct tw68_tvnorm tvnorms[] = {
.chroma_ctrl2 = 0x06,
.vgate_misc = 0x1c,
.format = VideoFormatPALNC,
}, {
.name = "PAL-60",
.id = V4L2_STD_PAL_60,
......@@ -309,127 +207,11 @@ static struct tw68_tvnorm tvnorms[] = {
.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)
static const struct tw68_format *format_by_fourcc(unsigned int fourcc)
{
unsigned int i;
......@@ -439,99 +221,22 @@ static struct tw68_format *format_by_fourcc(unsigned int fourcc)
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)
static void set_tvnorm(struct tw68_dev *dev, const 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->width = 720;
dev->height = (norm->id & V4L2_STD_525_60) ? 480 : 576;
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
*
......@@ -544,7 +249,7 @@ static void video_mux(struct tw68_dev *dev, int input)
* 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
* 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
......@@ -555,16 +260,16 @@ static void video_mux(struct tw68_dev *dev, int input)
* 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
* @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)
{
const struct tw68_tvnorm *norm = dev->tvnorm;
/* set individually for debugging clarity */
int hactive, hdelay, hscale;
int vactive, vdelay, vscale;
......@@ -573,41 +278,38 @@ static int tw68_set_scale(struct tw68_dev *dev, unsigned int width,
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__,
pr_debug("%s: width=%d, height=%d, both=%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);
norm->h_delay, norm->h_start, norm->h_stop,
norm->v_delay, norm->video_v_start,
norm->video_v_stop);
switch (dev->vdecoder) {
case TW6800:
hdelay = dev->tvnorm->h_delay0;
hdelay = norm->h_delay0;
break;
default:
hdelay = dev->tvnorm->h_delay;
hdelay = norm->h_delay;
break;
}
hdelay += dev->crop_bounds.left;
hactive = dev->crop_bounds.width;
hdelay += norm->h_start;
hactive = norm->h_stop - norm->h_start + 1;
hscale = (hactive * 256) / (width);
vdelay = dev->tvnorm->v_delay + dev->crop_bounds.top -
dev->crop_defrect.top;
vactive = dev->crop_bounds.height;
vdelay = norm->v_delay;
vactive = ((norm->id & V4L2_STD_525_60) ? 524 : 624) / 2 - norm->video_v_start;
vscale = (vactive * 256) / height;
dprintk(DBG_FLOW, "%s: %dx%d [%s%s,%s]\n", __func__,
pr_debug("%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; "
pr_debug("%s: hactive=%d, hdelay=%d, hscale=%d; "
"vactive=%d, vdelay=%d, vscale=%d\n", __func__,
hactive, hdelay, hscale, vactive, vdelay, vscale);
......@@ -615,7 +317,7 @@ static int tw68_set_scale(struct tw68_dev *dev, unsigned int width,
((vactive & 0x300) >> 4) |
((hdelay & 0x300) >> 6) |
((hactive & 0x300) >> 8);
dprintk(DBG_FLOW, "%s: setting CROP_HI=%02x, VDELAY_LO=%02x, "
pr_debug("%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);
......@@ -625,7 +327,7 @@ static int tw68_set_scale(struct tw68_dev *dev, unsigned int width,
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, "
pr_debug("%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);
......@@ -636,28 +338,21 @@ static int tw68_set_scale(struct tw68_dev *dev, unsigned int width,
/* ------------------------------------------------------------------ */
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);
}
int tw68_video_start_dma(struct tw68_dev *dev, struct tw68_buf *buf)
{
/* Set cropping and scaling */
tw68_set_scale(dev, buf->vb.width, buf->vb.height, buf->vb.field);
tw68_set_scale(dev, dev->width, dev->height, dev->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));
tw_writel(TW68_DMAP_SA, cpu_to_le32(buf->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 |
tw_andorl(TW68_DMAC, 0xff, dev->fmt->twformat |
ColorFormatGamma | TW68_DMAP_EN | TW68_FIFO_EN);
dev->pci_irqmask |= dev->board_virqmask;
tw_setl(TW68_INTMASK, dev->pci_irqmask);
......@@ -665,693 +360,295 @@ static int tw68_video_start_dma(struct tw68_dev *dev, struct tw68_dmaqueue *q,
}
/* ------------------------------------------------------------------ */
/* 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)
/* nr of (tw68-)pages for the given buffer size */
static int tw68_buffer_pages(int size)
{
return (prev->vb.width == buf->vb.width &&
prev->vb.height == buf->vb.height &&
prev->fmt == buf->fmt);
size = PAGE_ALIGN(size);
size += PAGE_SIZE; /* for non-page-aligned buffers */
size /= 4096;
return size;
}
/*
* 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)
/* calc max # of buffers from size (must not exceed the 4MB virtual
* address space per DMA channel) */
static int tw68_buffer_count(unsigned int size, unsigned int count)
{
struct tw68_fh *fh = q->priv_data;
unsigned int maxcount;
*size = fh->fmt->depth * fh->width * fh->height >> 3;
if (0 == *count)
*count = gbuffers;
*count = tw68_buffer_count(*size, *count);
return 0;
maxcount = 1024 / tw68_buffer_pages(size);
if (count > maxcount)
count = maxcount;
return count;
}
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;
}
/* ------------------------------------------------------------- */
/* vb2 queue operations */
/*
* 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)
static int tw68_queue_setup(struct vb2_queue *q, const struct v4l2_format *fmt,
unsigned int *num_buffers, unsigned int *num_planes,
unsigned int sizes[], void *alloc_ctxs[])
{
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;
struct tw68_dev *dev = vb2_get_drv_priv(q);
unsigned tot_bufs = q->num_buffers + *num_buffers;
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);
sizes[0] = (dev->fmt->depth * dev->width * dev->height) >> 3;
/*
* We allow create_bufs, but only if the sizeimage is the same as the
* current sizeimage. The tw68_buffer_count calculation becomes quite
* difficult otherwise.
*/
if (fmt && fmt->fmt.pix.sizeimage < sizes[0])
return -EINVAL;
*num_planes = 1;
if (tot_bufs < 2)
tot_bufs = 2;
tot_bufs = tw68_buffer_count(sizes[0], tot_bufs);
*num_buffers = tot_bufs - q->num_buffers;
buf->vb.state = VIDEOBUF_PREPARED;
buf->activate = buffer_activate;
return 0;
fail:
tw68_dma_free(q, buf);
return rc;
}
/*
* buffer_queue
* The risc program for each buffers works as follows: it starts with a simple
* 'JUMP to addr + 8', which is effectively a NOP. Then the program to DMA the
* buffer follows and at the end we have a JUMP back to the start + 8 (skipping
* the initial JUMP).
*
* This is the program of the first buffer to be queued if the active list is
* empty and it just keeps DMAing this buffer without generating any interrupts.
*
* If a new buffer is added then the initial JUMP in the program generates an
* interrupt as well which signals that the previous buffer has been DMAed
* successfully and that it can be returned to userspace.
*
* It also sets the final jump of the previous buffer to the start of the new
* buffer, thus chaining the new buffer into the DMA chain. This is a single
* atomic u32 write, so there is no race condition.
*
* Callback whenever a buffer has been requested (by read() or QBUF)
* The end-result of all this that you only get an interrupt when a buffer
* is ready, so the control flow is very easy.
*/
static void
buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb)
static void tw68_buf_queue(struct vb2_buffer *vb)
{
struct tw68_fh *fh = q->priv_data;
struct tw68_buf *buf = container_of(vb, struct tw68_buf, vb);
struct vb2_queue *vq = vb->vb2_queue;
struct tw68_dev *dev = vb2_get_drv_priv(vq);
struct tw68_buf *buf = container_of(vb, struct tw68_buf, vb);
struct tw68_buf *prev;
unsigned long flags;
spin_lock_irqsave(&dev->slock, flags);
tw68_buffer_queue(fh->dev, &fh->dev->video_q, buf);
/* append a 'JUMP to start of buffer' to the buffer risc program */
buf->jmp[0] = cpu_to_le32(RISC_JUMP);
buf->jmp[1] = cpu_to_le32(buf->dma + 8);
if (!list_empty(&dev->active)) {
prev = list_entry(dev->active.prev, struct tw68_buf, list);
buf->cpu[0] |= cpu_to_le32(RISC_INT_BIT);
prev->jmp[1] = cpu_to_le32(buf->dma);
}
list_add_tail(&buf->list, &dev->active);
spin_unlock_irqrestore(&dev->slock, flags);
}
/*
* buffer_release
* buffer_prepare
*
* Free a buffer previously allocated.
* 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 void buffer_release(struct videobuf_queue *q,
struct videobuf_buffer *vb)
static int tw68_buf_prepare(struct vb2_buffer *vb)
{
struct vb2_queue *vq = vb->vb2_queue;
struct tw68_dev *dev = vb2_get_drv_priv(vq);
struct tw68_buf *buf = container_of(vb, struct tw68_buf, vb);
struct sg_table *dma = vb2_dma_sg_plane_desc(vb, 0);
unsigned size, bpl;
int rc;
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,
};
/* ------------------------------------------------------------------ */
size = (dev->width * dev->height * dev->fmt->depth) >> 3;
if (vb2_plane_size(vb, 0) < size)
return -EINVAL;
vb2_set_plane_payload(vb, 0, size);
static int tw68_g_ctrl_internal(struct tw68_dev *dev, struct tw68_fh *fh,
struct v4l2_control *c)
{
const struct v4l2_queryctrl *ctrl;
rc = dma_map_sg(&dev->pci->dev, dma->sgl, dma->nents, DMA_FROM_DEVICE);
if (!rc)
return -EIO;
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);
bpl = (dev->width * dev->fmt->depth) >> 3;
switch (dev->field) {
case V4L2_FIELD_TOP:
tw68_risc_buffer(dev->pci, buf, dma->sgl,
0, UNSET, bpl, 0, dev->height);
break;
case V4L2_CID_CHROMA_AGC:
c->value = 0 != (tw_readb(TW68_LOOP) & 0x30);
case V4L2_FIELD_BOTTOM:
tw68_risc_buffer(dev->pci, buf, dma->sgl,
UNSET, 0, bpl, 0, dev->height);
break;
case V4L2_CID_AUDIO_MUTE:
/*hack to suppresss tvtime complaint */
c->value = 0;
case V4L2_FIELD_SEQ_TB:
tw68_risc_buffer(dev->pci, buf, dma->sgl,
0, bpl * (dev->height >> 1),
bpl, 0, dev->height >> 1);
break;
#if 0
case V4L2_CID_AUDIO_VOLUME:
c->value = dev->ctl_volume;
case V4L2_FIELD_SEQ_BT:
tw68_risc_buffer(dev->pci, buf, dma->sgl,
bpl * (dev->height >> 1), 0,
bpl, 0, dev->height >> 1);
break;
#endif
case V4L2_FIELD_INTERLACED:
default:
return -EINVAL;
tw68_risc_buffer(dev->pci, buf, dma->sgl,
0, bpl, bpl, bpl, dev->height >> 1);
break;
}
return 0;
}
static int tw68_g_ctrl(struct file *file, void *priv, struct v4l2_control *c)
static void tw68_buf_finish(struct vb2_buffer *vb)
{
struct vb2_queue *vq = vb->vb2_queue;
struct tw68_dev *dev = vb2_get_drv_priv(vq);
struct sg_table *dma = vb2_dma_sg_plane_desc(vb, 0);
struct tw68_buf *buf = container_of(vb, struct tw68_buf, vb);
dma_unmap_sg(&dev->pci->dev, dma->sgl, dma->nents, DMA_FROM_DEVICE);
pci_free_consistent(dev->pci, buf->size, buf->cpu, buf->dma);
}
static int tw68_start_streaming(struct vb2_queue *q, unsigned int count)
{
struct tw68_dev *dev = vb2_get_drv_priv(q);
struct tw68_buf *buf =
container_of(dev->active.next, struct tw68_buf, list);
dev->seqnr = 0;
tw68_video_start_dma(dev, buf);
return 0;
}
static void tw68_stop_streaming(struct vb2_queue *q)
{
struct tw68_fh *fh = priv;
struct tw68_dev *dev = vb2_get_drv_priv(q);
/* Stop risc & fifo */
tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN);
while (!list_empty(&dev->active)) {
struct tw68_buf *buf =
container_of(dev->active.next, struct tw68_buf, list);
return tw68_g_ctrl_internal(fh->dev, fh, c);
list_del(&buf->list);
vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
}
}
static int tw68_s_ctrl_value(struct tw68_dev *dev, __u32 id, int val)
static struct vb2_ops tw68_video_qops = {
.queue_setup = tw68_queue_setup,
.buf_queue = tw68_buf_queue,
.buf_prepare = tw68_buf_prepare,
.buf_finish = tw68_buf_finish,
.start_streaming = tw68_start_streaming,
.stop_streaming = tw68_stop_streaming,
.wait_prepare = vb2_ops_wait_prepare,
.wait_finish = vb2_ops_wait_finish,
};
/* ------------------------------------------------------------------ */
static int tw68_s_ctrl(struct v4l2_ctrl *ctrl)
{
int err = 0;
struct tw68_dev *dev =
container_of(ctrl->handler, struct tw68_dev, hdl);
dprintk(DBG_FLOW, "%s\n", __func__);
switch (id) {
switch (ctrl->id) {
case V4L2_CID_BRIGHTNESS:
tw_writeb(TW68_BRIGHT, val);
tw_writeb(TW68_BRIGHT, ctrl->val);
break;
case V4L2_CID_HUE:
tw_writeb(TW68_HUE, val);
tw_writeb(TW68_HUE, ctrl->val);
break;
case V4L2_CID_CONTRAST:
tw_writeb(TW68_CONTRAST, val);
tw_writeb(TW68_CONTRAST, ctrl->val);
break;
case V4L2_CID_SATURATION:
tw_writeb(TW68_SAT_U, val);
tw_writeb(TW68_SAT_V, val);
tw_writeb(TW68_SAT_U, ctrl->val);
tw_writeb(TW68_SAT_V, ctrl->val);
break;
case V4L2_CID_COLOR_KILLER:
if (val)
if (ctrl->val)
tw_andorb(TW68_MISC2, 0xe0, 0xe0);
else
tw_andorb(TW68_MISC2, 0xe0, 0x00);
break;
case V4L2_CID_CHROMA_AGC:
if (val)
if (ctrl->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;
return 0;
}
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);
/*
* 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_dev *dev = video_drvdata(file);
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.width = dev->width;
f->fmt.pix.height = dev->height;
f->fmt.pix.field = dev->field;
f->fmt.pix.pixelformat = dev->fmt->fourcc;
f->fmt.pix.bytesperline =
(f->fmt.pix.width * (fh->fmt->depth)) >> 3;
(f->fmt.pix.width * (dev->fmt->depth)) >> 3;
f->fmt.pix.sizeimage =
f->fmt.pix.height * f->fmt.pix.bytesperline;
f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
f->fmt.pix.priv = 0;
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;
struct tw68_dev *dev = video_drvdata(file);
const struct tw68_format *fmt;
enum v4l2_field field;
unsigned int maxw, maxh;
unsigned int 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);
maxh = (dev->tvnorm->id & V4L2_STD_525_60) ? 480 : 576;
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:
case V4L2_FIELD_SEQ_BT:
case V4L2_FIELD_SEQ_TB:
maxh = maxh * 2;
break;
default:
return -EINVAL;
field = (f->fmt.pix.height > maxh / 2)
? V4L2_FIELD_INTERLACED
: V4L2_FIELD_BOTTOM;
break;
}
f->fmt.pix.field = field;
......@@ -1359,8 +656,8 @@ static int tw68_try_fmt_vid_cap(struct file *file, void *priv,
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.width > 720)
f->fmt.pix.width = 720;
if (f->fmt.pix.height > maxh)
f->fmt.pix.height = maxh;
f->fmt.pix.width &= ~0x03;
......@@ -1368,7 +665,7 @@ static int tw68_try_fmt_vid_cap(struct file *file, void *priv,
(f->fmt.pix.width * (fmt->depth)) >> 3;
f->fmt.pix.sizeimage =
f->fmt.pix.height * f->fmt.pix.bytesperline;
f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
return 0;
}
......@@ -1381,76 +678,35 @@ static int tw68_try_fmt_vid_cap(struct file *file, void *priv,
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;
struct tw68_dev *dev = video_drvdata(file);
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;
dev->fmt = format_by_fourcc(f->fmt.pix.pixelformat);
dev->width = f->fmt.pix.width;
dev->height = f->fmt.pix.height;
dev->field = f->fmt.pix.field;
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;
struct tw68_dev *dev = video_drvdata(file);
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__);
if (n >= TW68_INPUT_MAX)
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;
i->type = V4L2_INPUT_TYPE_CAMERA;
snprintf(i->name, sizeof(i->name), "Composite %d", n);
/* If the query is for the current input, get live data */
if (n == dev->hw_input->vmux) {
if (n == dev->input) {
int v1 = tw_readb(TW68_STATUS1);
int v2 = tw_readb(TW68_MVSN);
......@@ -1465,305 +721,86 @@ static int tw68_enum_input(struct file *file, void *priv,
if (0 != (v2 & (1 << 2)))
i->status |= V4L2_IN_ST_MACROVISION;
}
i->std = TW68_NORMS;
i->std = video_devdata(file)->tvnorms;
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;
struct tw68_dev *dev = video_drvdata(file);
dprintk(DBG_FLOW, "%s\n", __func__);
*i = dev->input->vmux;
*i = dev->input;
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;
struct tw68_dev *dev = video_drvdata(file);
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)
if (i >= TW68_INPUT_MAX)
return -EINVAL;
mutex_lock(&dev->lock);
video_mux(dev, i);
mutex_unlock(&dev->lock);
dev->input = i;
tw_andorb(TW68_INFORM, 0x03 << 2, dev->input << 2);
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;
struct tw68_dev *dev = video_drvdata(file);
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,
strlcpy(cap->card, "Techwell Capture Card",
sizeof(cap->card));
sprintf(cap->bus_info, "PCI:%s", pci_name(dev->pci));
cap->version = TW68_VERSION_CODE;
cap->capabilities =
cap->device_caps =
V4L2_CAP_VIDEO_CAPTURE |
V4L2_CAP_VBI_CAPTURE |
V4L2_CAP_READWRITE |
V4L2_CAP_STREAMING |
V4L2_CAP_TUNER;
V4L2_CAP_STREAMING;
if ((tuner_type == TUNER_ABSENT) || (tuner_type == UNSET))
cap->capabilities &= ~V4L2_CAP_TUNER;
cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
return 0;
}
static int tw68_s_std_internal(struct tw68_dev *dev, struct tw68_fh *fh,
v4l2_std_id *id)
static int tw68_s_std(struct file *file, void *priv, v4l2_std_id id)
{
/* unsigned long flags; */
struct tw68_dev *dev = video_drvdata(file);
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;
}
if (vb2_is_busy(&dev->vidq))
return -EBUSY;
/* Look for match on complete norm id (may have mult bits) */
for (i = 0; i < TVNORMS; i++) {
if (*id == tvnorms[i].id)
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)
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;
struct tw68_dev *dev = video_drvdata(file);
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;
......@@ -1775,149 +812,6 @@ static int tw68_enum_fmt_vid_cap(struct file *file, void *priv,
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.
......@@ -1928,7 +822,7 @@ static void tw68_dump_regs(struct tw68_dev *dev)
int i, j, k;
unsigned char *cptr;
printk(KERN_DEBUG "Full dump of TW68 registers:\n");
pr_info("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;
......@@ -1941,7 +835,7 @@ static void tw68_dump_regs(struct tw68_dev *dev)
cptr += sprintf(cptr, "%08x ", tw_readl(j));
*cptr++ = '\n';
*cptr = 0;
printk(KERN_DEBUG "%s", line);
pr_info("%s", line);
}
/* Next the control regs, which are single-byte, address mod 4 */
while (i < 0x400) {
......@@ -1958,29 +852,24 @@ static void tw68_dump_regs(struct tw68_dev *dev)
}
*cptr++ = '\n';
*cptr = 0;
printk(KERN_DEBUG "%s", line);
pr_info("%s", line);
}
}
static int vidioc_log_status(struct file *file, void *priv)
{
struct tw68_fh *fh = priv;
struct tw68_dev *dev = fh->dev;
struct tw68_dev *dev = video_drvdata(file);
tw68_dump_regs(dev);
return 0;
return v4l2_ctrl_log_status(file, priv);
}
#ifdef CONFIG_VIDEO_ADV_DEBUG
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 */
struct tw68_dev *dev = video_drvdata(file);
dprintk(DBG_FLOW, "%s\n", __func__);
if (!v4l2_chip_match_host(&reg->match))
dprintk(DBG_UNEXPECTED, "%s: match failed\n", __func__);
return -EINVAL;
if (reg->size == 1)
reg->val = tw_readb(reg->reg);
else
......@@ -1989,17 +878,10 @@ static int vidioc_g_register(struct file *file, void *priv,
}
static int vidioc_s_register(struct file *file, void *priv,
struct v4l2_dbg_register *reg)
const struct v4l2_dbg_register *reg)
{
struct tw68_fh *fh = priv;
struct tw68_dev *dev = fh->dev; /* needed for tw_writeb */
struct tw68_dev *dev = video_drvdata(file);
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(&reg->match)) {
dprintk(DBG_UNEXPECTED, "%s: match failed\n", __func__);
return -EINVAL;
}
if (reg->size == 1)
tw_writeb(reg->reg, reg->val);
else
......@@ -2008,151 +890,120 @@ static int vidioc_s_register(struct file *file, void *priv,
}
#endif
static const struct v4l2_ctrl_ops tw68_ctrl_ops = {
.s_ctrl = tw68_s_ctrl,
};
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,
.open = v4l2_fh_open,
.release = vb2_fop_release,
.read = vb2_fop_read,
.poll = vb2_fop_poll,
.mmap = vb2_fop_mmap,
.unlocked_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_reqbufs = vb2_ioctl_reqbufs,
.vidioc_create_bufs = vb2_ioctl_create_bufs,
.vidioc_querybuf = vb2_ioctl_querybuf,
.vidioc_qbuf = vb2_ioctl_qbuf,
.vidioc_dqbuf = vb2_ioctl_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_streamon = vb2_ioctl_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,
.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_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
#ifdef CONFIG_VIDEO_ADV_DEBUG
.vidioc_g_register = vidioc_g_register,
.vidioc_s_register = vidioc_s_register,
#endif
};
/* ------------------------------------------------------------------ */
/* exported stuff */
struct video_device tw68_video_template = {
static struct video_device tw68_video_template = {
.name = "tw68_video",
.fops = &video_fops,
.ioctl_ops = &video_ioctl_ops,
.minor = -1,
.release = video_device_release_empty,
.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;
}
/* ------------------------------------------------------------------ */
/* exported stuff */
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);
struct v4l2_ctrl_handler *hdl = &dev->hdl;
v4l2_ctrl_handler_init(hdl, 6);
v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
V4L2_CID_BRIGHTNESS, -128, 127, 1, 20);
v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
V4L2_CID_CONTRAST, 0, 255, 1, 100);
v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
V4L2_CID_SATURATION, 0, 255, 1, 128);
/* NTSC only */
v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
V4L2_CID_HUE, -128, 127, 1, 0);
v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
V4L2_CID_COLOR_KILLER, 0, 1, 1, 0);
v4l2_ctrl_new_std(hdl, &tw68_ctrl_ops,
V4L2_CID_CHROMA_AGC, 0, 1, 1, 1);
if (hdl->error) {
v4l2_ctrl_handler_free(hdl);
return hdl->error;
}
dev->v4l2_dev.ctrl_handler = hdl;
v4l2_ctrl_handler_setup(hdl);
return 0;
}
int tw68_video_init2(struct tw68_dev *dev)
int tw68_video_init2(struct tw68_dev *dev, int video_nr)
{
dprintk(DBG_FLOW, "%s\n", __func__);
int ret;
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;
dev->fmt = format_by_fourcc(V4L2_PIX_FMT_BGR24);
dev->width = 720;
dev->height = 576;
dev->field = V4L2_FIELD_INTERLACED;
INIT_LIST_HEAD(&dev->active);
dev->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
dev->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
dev->vidq.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ | VB2_DMABUF;
dev->vidq.ops = &tw68_video_qops;
dev->vidq.mem_ops = &vb2_dma_sg_memops;
dev->vidq.drv_priv = dev;
dev->vidq.gfp_flags = __GFP_DMA32;
dev->vidq.buf_struct_size = sizeof(struct tw68_buf);
dev->vidq.lock = &dev->lock;
dev->vidq.min_buffers_needed = 2;
ret = vb2_queue_init(&dev->vidq);
if (ret)
return ret;
dev->vdev = tw68_video_template;
dev->vdev.v4l2_dev = &dev->v4l2_dev;
dev->vdev.lock = &dev->lock;
dev->vdev.queue = &dev->vidq;
video_set_drvdata(&dev->vdev, dev);
return video_register_device(&dev->vdev, VFL_TYPE_GRABBER, video_nr);
}
/*
......@@ -2171,60 +1022,39 @@ void tw68_irq_video_done(struct tw68_dev *dev, unsigned long status)
* for the current buffer.
*/
if (status & TW68_DMAPI) {
struct tw68_dmaqueue *q = &dev->video_q;
dprintk(DBG_FLOW | DBG_TESTING, "DMAPI interrupt\n");
struct tw68_buf *buf;
spin_lock(&dev->slock);
/*
* tw68_wakeup will take care of the buffer handling,
* plus any non-video requirements.
*/
tw68_wakeup(q, &dev->video_fieldcount);
buf = list_entry(dev->active.next, struct tw68_buf, list);
list_del(&buf->list);
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__);
}
v4l2_get_timestamp(&buf->vb.v4l2_buf.timestamp);
buf->vb.v4l2_buf.field = dev->field;
buf->vb.v4l2_buf.sequence = dev->seqnr++;
vb2_buffer_done(&buf->vb, VB2_BUF_STATE_DONE);
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
}
if (status & (TW68_VLOCK | TW68_HLOCK))
dev_dbg(&dev->pci->dev, "Lost sync\n");
if (status & TW68_PABORT)
dev_err(&dev->pci->dev, "PABORT interrupt\n");
if (status & TW68_DMAPERR)
dev_err(&dev->pci->dev, "DMAPERR interrupt\n");
/*
* 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 */
if (status & TW68_FDMIS)
dev_dbg(&dev->pci->dev, "FDMIS interrupt\n");
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");
dev_dbg(&dev->pci->dev, "FFOF interrupt\n");
tw_setl(TW68_DMAC, reg);
}
if (status & TW68_FFERR)
dprintk(DBG_UNEXPECTED, "FFERR interrupt\n");
return;
dev_dbg(&dev->pci->dev, "FFERR interrupt\n");
}
......@@ -8,7 +8,11 @@
* acknowledged. Full credit goes to them - any problems within this code
* are mine.
*
* Copyright (C) 2009 William M. Brack <wbrack@mmm.com.hk>
* Copyright (C) 2009 William M. Brack
*
* Refactored and updated to the latest v4l core frameworks:
*
* Copyright (C) 2014 Hans Verkuil <hverkuil@xs4all.nl>
*
* 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
......@@ -19,54 +23,26 @@
* 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 <linux/io.h>
#include <media/v4l2-common.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/videobuf2-dma-sg.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
......@@ -83,15 +59,9 @@
#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_NORMS ( \
V4L2_STD_NTSC | V4L2_STD_PAL | V4L2_STD_SECAM | \
V4L2_STD_PAL_M | V4L2_STD_PAL_Nc | V4L2_STD_PAL_60)
#define TW68_VID_INTS (TW68_FFERR | TW68_PABORT | TW68_DMAPERR | \
TW68_FFOF | TW68_DMAPI)
......@@ -101,12 +71,13 @@
#define TW68_I2C_INTS (TW68_SBERR | TW68_SBDONE | TW68_SBERR2 | \
TW68_SBDONE2)
typedef enum {
enum tw68_decoder_type {
TW6800,
TW6801,
TW6804,
TWXXXX,
} TW68_DECODER_TYPE;
};
/* ----------------------------------------------------------- */
/* static data */
......@@ -153,164 +124,24 @@ struct tw68_format {
#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])
#define TW68_INPUT_MAX 4
/* ----------------------------------------------------------- */
/* 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 vb2_buffer vb;
struct list_head list;
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;
unsigned int size;
__le32 *cpu;
__le32 *jmp;
dma_addr_t dma;
};
struct tw68_fmt {
......@@ -321,58 +152,20 @@ struct tw68_fmt {
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;
u16 instance;
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
enum tw68_decoder_type vdecoder;
struct video_device vdev;
struct v4l2_ctrl_handler hdl;
/* pci i/o */
char name[32];
int nr;
char *name;
struct pci_dev *pci;
unsigned char pci_rev, pci_lat;
u32 __iomem *lmmio;
......@@ -381,75 +174,18 @@ struct tw68_dev {
/* 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;
/* video capture */
const struct tw68_format *fmt;
unsigned width, height;
unsigned seqnr;
unsigned field;
struct vb2_queue vidq;
struct list_head active;
/* 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);
const struct tw68_tvnorm *tvnorm; /* video */
int input;
};
/* ----------------------------------------------------------- */
......@@ -473,116 +209,23 @@ struct tw68_dev {
#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);
int tw68_video_init2(struct tw68_dev *dev, int video_nr);
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);
int tw68_video_start_dma(struct tw68_dev *dev, struct tw68_buf *buf);
/* ----------------------------------------------------------- */
/* tw68-risc.c */
int tw68_risc_buffer(struct pci_dev *pci, struct btcx_riscmem *risc,
int tw68_risc_buffer(struct pci_dev *pci, struct tw68_buf *buf,
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);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment