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