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

[media] tw68: add original tw68 code

This tw68 driver has been out-of-tree for many years on gitorious:
https://gitorious.org/tw68/tw68-v2.

This copies that code to the kernel as a record of that original code.

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