Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

52
drivers/media/pci/Kconfig Normal file
View file

@ -0,0 +1,52 @@
if PCI && MEDIA_SUPPORT
menuconfig MEDIA_PCI_SUPPORT
bool "Media PCI Adapters"
help
Enable media drivers for PCI/PCIe bus.
If you have such devices, say Y.
if MEDIA_PCI_SUPPORT
if MEDIA_CAMERA_SUPPORT
comment "Media capture support"
source "drivers/media/pci/meye/Kconfig"
source "drivers/media/pci/sta2x11/Kconfig"
endif
if MEDIA_ANALOG_TV_SUPPORT
comment "Media capture/analog TV support"
source "drivers/media/pci/ivtv/Kconfig"
source "drivers/media/pci/zoran/Kconfig"
source "drivers/media/pci/saa7146/Kconfig"
source "drivers/media/pci/solo6x10/Kconfig"
source "drivers/media/pci/tw68/Kconfig"
endif
if MEDIA_ANALOG_TV_SUPPORT || MEDIA_DIGITAL_TV_SUPPORT
comment "Media capture/analog/hybrid TV support"
source "drivers/media/pci/cx18/Kconfig"
source "drivers/media/pci/cx23885/Kconfig"
source "drivers/media/pci/cx25821/Kconfig"
source "drivers/media/pci/cx88/Kconfig"
source "drivers/media/pci/bt8xx/Kconfig"
source "drivers/media/pci/saa7134/Kconfig"
source "drivers/media/pci/saa7164/Kconfig"
endif
if MEDIA_DIGITAL_TV_SUPPORT
comment "Media digital TV PCI Adapters"
source "drivers/media/pci/ttpci/Kconfig"
source "drivers/media/pci/b2c2/Kconfig"
source "drivers/media/pci/pluto2/Kconfig"
source "drivers/media/pci/dm1105/Kconfig"
source "drivers/media/pci/pt1/Kconfig"
source "drivers/media/pci/pt3/Kconfig"
source "drivers/media/pci/mantis/Kconfig"
source "drivers/media/pci/ngene/Kconfig"
source "drivers/media/pci/ddbridge/Kconfig"
endif
endif #MEDIA_PCI_SUPPORT
endif #PCI

View file

@ -0,0 +1,28 @@
#
# Makefile for the kernel multimedia device drivers.
#
obj-y += ttpci/ \
b2c2/ \
pluto2/ \
dm1105/ \
pt1/ \
pt3/ \
mantis/ \
ngene/ \
ddbridge/ \
saa7146/
obj-$(CONFIG_VIDEO_IVTV) += ivtv/
obj-$(CONFIG_VIDEO_ZORAN) += zoran/
obj-$(CONFIG_VIDEO_CX18) += cx18/
obj-$(CONFIG_VIDEO_CX23885) += cx23885/
obj-$(CONFIG_VIDEO_CX25821) += cx25821/
obj-$(CONFIG_VIDEO_CX88) += cx88/
obj-$(CONFIG_VIDEO_BT848) += bt8xx/
obj-$(CONFIG_VIDEO_SAA7134) += saa7134/
obj-$(CONFIG_VIDEO_SAA7164) += saa7164/
obj-$(CONFIG_VIDEO_TW68) += tw68/
obj-$(CONFIG_VIDEO_MEYE) += meye/
obj-$(CONFIG_STA2X11_VIP) += sta2x11/
obj-$(CONFIG_VIDEO_SOLO6X10) += solo6x10/

View file

@ -0,0 +1,15 @@
config DVB_B2C2_FLEXCOP_PCI
tristate "Technisat/B2C2 Air/Sky/Cable2PC PCI"
depends on DVB_CORE && I2C
help
Support for the Air/Sky/CableStar2 PCI card (DVB/ATSC) by Technisat/B2C2.
Say Y if you own such a device and want to use it.
config DVB_B2C2_FLEXCOP_PCI_DEBUG
bool "Enable debug for the B2C2 FlexCop drivers"
depends on DVB_B2C2_FLEXCOP_PCI
select DVB_B2C2_FLEXCOP_DEBUG
help
Say Y if you want to enable the module option to control debug messages
of all B2C2 FlexCop drivers.

View file

@ -0,0 +1,9 @@
ifneq ($(CONFIG_DVB_B2C2_FLEXCOP_PCI),)
b2c2-flexcop-pci-objs += flexcop-dma.o
endif
b2c2-flexcop-pci-objs += flexcop-pci.o
obj-$(CONFIG_DVB_B2C2_FLEXCOP_PCI) += b2c2-flexcop-pci.o
ccflags-y += -Idrivers/media/dvb-core/
ccflags-y += -Idrivers/media/common/b2c2/

View file

@ -0,0 +1,172 @@
/*
* Linux driver for digital TV devices equipped with B2C2 FlexcopII(b)/III
* flexcop-dma.c - configuring and controlling the DMA of the FlexCop
* see flexcop.c for copyright information
*/
#include "flexcop.h"
int flexcop_dma_allocate(struct pci_dev *pdev,
struct flexcop_dma *dma, u32 size)
{
u8 *tcpu;
dma_addr_t tdma = 0;
if (size % 2) {
err("dma buffersize has to be even.");
return -EINVAL;
}
if ((tcpu = pci_alloc_consistent(pdev, size, &tdma)) != NULL) {
dma->pdev = pdev;
dma->cpu_addr0 = tcpu;
dma->dma_addr0 = tdma;
dma->cpu_addr1 = tcpu + size/2;
dma->dma_addr1 = tdma + size/2;
dma->size = size/2;
return 0;
}
return -ENOMEM;
}
EXPORT_SYMBOL(flexcop_dma_allocate);
void flexcop_dma_free(struct flexcop_dma *dma)
{
pci_free_consistent(dma->pdev, dma->size*2,
dma->cpu_addr0, dma->dma_addr0);
memset(dma,0,sizeof(struct flexcop_dma));
}
EXPORT_SYMBOL(flexcop_dma_free);
int flexcop_dma_config(struct flexcop_device *fc,
struct flexcop_dma *dma,
flexcop_dma_index_t dma_idx)
{
flexcop_ibi_value v0x0,v0x4,v0xc;
v0x0.raw = v0x4.raw = v0xc.raw = 0;
v0x0.dma_0x0.dma_address0 = dma->dma_addr0 >> 2;
v0xc.dma_0xc.dma_address1 = dma->dma_addr1 >> 2;
v0x4.dma_0x4_write.dma_addr_size = dma->size / 4;
if ((dma_idx & FC_DMA_1) == dma_idx) {
fc->write_ibi_reg(fc,dma1_000,v0x0);
fc->write_ibi_reg(fc,dma1_004,v0x4);
fc->write_ibi_reg(fc,dma1_00c,v0xc);
} else if ((dma_idx & FC_DMA_2) == dma_idx) {
fc->write_ibi_reg(fc,dma2_010,v0x0);
fc->write_ibi_reg(fc,dma2_014,v0x4);
fc->write_ibi_reg(fc,dma2_01c,v0xc);
} else {
err("either DMA1 or DMA2 can be configured within one "
"flexcop_dma_config call.");
return -EINVAL;
}
return 0;
}
EXPORT_SYMBOL(flexcop_dma_config);
/* start the DMA transfers, but not the DMA IRQs */
int flexcop_dma_xfer_control(struct flexcop_device *fc,
flexcop_dma_index_t dma_idx,
flexcop_dma_addr_index_t index,
int onoff)
{
flexcop_ibi_value v0x0,v0xc;
flexcop_ibi_register r0x0,r0xc;
if ((dma_idx & FC_DMA_1) == dma_idx) {
r0x0 = dma1_000;
r0xc = dma1_00c;
} else if ((dma_idx & FC_DMA_2) == dma_idx) {
r0x0 = dma2_010;
r0xc = dma2_01c;
} else {
err("either transfer DMA1 or DMA2 can be started within one "
"flexcop_dma_xfer_control call.");
return -EINVAL;
}
v0x0 = fc->read_ibi_reg(fc,r0x0);
v0xc = fc->read_ibi_reg(fc,r0xc);
deb_rdump("reg: %03x: %x\n",r0x0,v0x0.raw);
deb_rdump("reg: %03x: %x\n",r0xc,v0xc.raw);
if (index & FC_DMA_SUBADDR_0)
v0x0.dma_0x0.dma_0start = onoff;
if (index & FC_DMA_SUBADDR_1)
v0xc.dma_0xc.dma_1start = onoff;
fc->write_ibi_reg(fc,r0x0,v0x0);
fc->write_ibi_reg(fc,r0xc,v0xc);
deb_rdump("reg: %03x: %x\n",r0x0,v0x0.raw);
deb_rdump("reg: %03x: %x\n",r0xc,v0xc.raw);
return 0;
}
EXPORT_SYMBOL(flexcop_dma_xfer_control);
static int flexcop_dma_remap(struct flexcop_device *fc,
flexcop_dma_index_t dma_idx,
int onoff)
{
flexcop_ibi_register r = (dma_idx & FC_DMA_1) ? dma1_00c : dma2_01c;
flexcop_ibi_value v = fc->read_ibi_reg(fc,r);
deb_info("%s\n",__func__);
v.dma_0xc.remap_enable = onoff;
fc->write_ibi_reg(fc,r,v);
return 0;
}
int flexcop_dma_control_size_irq(struct flexcop_device *fc,
flexcop_dma_index_t no,
int onoff)
{
flexcop_ibi_value v = fc->read_ibi_reg(fc,ctrl_208);
if (no & FC_DMA_1)
v.ctrl_208.DMA1_IRQ_Enable_sig = onoff;
if (no & FC_DMA_2)
v.ctrl_208.DMA2_IRQ_Enable_sig = onoff;
fc->write_ibi_reg(fc,ctrl_208,v);
return 0;
}
EXPORT_SYMBOL(flexcop_dma_control_size_irq);
int flexcop_dma_control_timer_irq(struct flexcop_device *fc,
flexcop_dma_index_t no,
int onoff)
{
flexcop_ibi_value v = fc->read_ibi_reg(fc,ctrl_208);
if (no & FC_DMA_1)
v.ctrl_208.DMA1_Timer_Enable_sig = onoff;
if (no & FC_DMA_2)
v.ctrl_208.DMA2_Timer_Enable_sig = onoff;
fc->write_ibi_reg(fc,ctrl_208,v);
return 0;
}
EXPORT_SYMBOL(flexcop_dma_control_timer_irq);
/* 1 cycles = 1.97 msec */
int flexcop_dma_config_timer(struct flexcop_device *fc,
flexcop_dma_index_t dma_idx, u8 cycles)
{
flexcop_ibi_register r = (dma_idx & FC_DMA_1) ? dma1_004 : dma2_014;
flexcop_ibi_value v = fc->read_ibi_reg(fc,r);
flexcop_dma_remap(fc,dma_idx,0);
deb_info("%s\n",__func__);
v.dma_0x4_write.dmatimer = cycles;
fc->write_ibi_reg(fc,r,v);
return 0;
}
EXPORT_SYMBOL(flexcop_dma_config_timer);

View file

@ -0,0 +1,437 @@
/*
* Linux driver the digital TV devices equipped with B2C2 FlexcopII(b)/III
* flexcop-pci.c - covers the PCI part including DMA transfers
* see flexcop.c for copyright information
*/
#define FC_LOG_PREFIX "flexcop-pci"
#include "flexcop-common.h"
static int enable_pid_filtering = 1;
module_param(enable_pid_filtering, int, 0444);
MODULE_PARM_DESC(enable_pid_filtering,
"enable hardware pid filtering: supported values: 0 (fullts), 1");
static int irq_chk_intv = 100;
module_param(irq_chk_intv, int, 0644);
MODULE_PARM_DESC(irq_chk_intv, "set the interval for IRQ streaming watchdog.");
#ifdef CONFIG_DVB_B2C2_FLEXCOP_DEBUG
#define dprintk(level,args...) \
do { if ((debug & level)) printk(args); } while (0)
#define DEBSTATUS ""
#else
#define dprintk(level,args...)
#define DEBSTATUS " (debugging is not enabled)"
#endif
#define deb_info(args...) dprintk(0x01, args)
#define deb_reg(args...) dprintk(0x02, args)
#define deb_ts(args...) dprintk(0x04, args)
#define deb_irq(args...) dprintk(0x08, args)
#define deb_chk(args...) dprintk(0x10, args)
static int debug;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug,
"set debug level (1=info,2=regs,4=TS,8=irqdma,16=check (|-able))."
DEBSTATUS);
#define DRIVER_VERSION "0.1"
#define DRIVER_NAME "flexcop-pci"
#define DRIVER_AUTHOR "Patrick Boettcher <patrick.boettcher@desy.de>"
struct flexcop_pci {
struct pci_dev *pdev;
#define FC_PCI_INIT 0x01
#define FC_PCI_DMA_INIT 0x02
int init_state;
void __iomem *io_mem;
u32 irq;
/* buffersize (at least for DMA1, need to be % 188 == 0,
* this logic is required */
#define FC_DEFAULT_DMA1_BUFSIZE (1280 * 188)
#define FC_DEFAULT_DMA2_BUFSIZE (10 * 188)
struct flexcop_dma dma[2];
int active_dma1_addr; /* 0 = addr0 of dma1; 1 = addr1 of dma1 */
u32 last_dma1_cur_pos;
/* position of the pointer last time the timer/packet irq occurred */
int count;
int count_prev;
int stream_problem;
spinlock_t irq_lock;
unsigned long last_irq;
struct delayed_work irq_check_work;
struct flexcop_device *fc_dev;
};
static int lastwreg, lastwval, lastrreg, lastrval;
static flexcop_ibi_value flexcop_pci_read_ibi_reg(struct flexcop_device *fc,
flexcop_ibi_register r)
{
struct flexcop_pci *fc_pci = fc->bus_specific;
flexcop_ibi_value v;
v.raw = readl(fc_pci->io_mem + r);
if (lastrreg != r || lastrval != v.raw) {
lastrreg = r; lastrval = v.raw;
deb_reg("new rd: %3x: %08x\n", r, v.raw);
}
return v;
}
static int flexcop_pci_write_ibi_reg(struct flexcop_device *fc,
flexcop_ibi_register r, flexcop_ibi_value v)
{
struct flexcop_pci *fc_pci = fc->bus_specific;
if (lastwreg != r || lastwval != v.raw) {
lastwreg = r; lastwval = v.raw;
deb_reg("new wr: %3x: %08x\n", r, v.raw);
}
writel(v.raw, fc_pci->io_mem + r);
return 0;
}
static void flexcop_pci_irq_check_work(struct work_struct *work)
{
struct flexcop_pci *fc_pci =
container_of(work, struct flexcop_pci, irq_check_work.work);
struct flexcop_device *fc = fc_pci->fc_dev;
if (fc->feedcount) {
if (fc_pci->count == fc_pci->count_prev) {
deb_chk("no IRQ since the last check\n");
if (fc_pci->stream_problem++ == 3) {
struct dvb_demux_feed *feed;
deb_info("flexcop-pci: stream problem, resetting pid filter\n");
spin_lock_irq(&fc->demux.lock);
list_for_each_entry(feed, &fc->demux.feed_list,
list_head) {
flexcop_pid_feed_control(fc, feed, 0);
}
list_for_each_entry(feed, &fc->demux.feed_list,
list_head) {
flexcop_pid_feed_control(fc, feed, 1);
}
spin_unlock_irq(&fc->demux.lock);
fc_pci->stream_problem = 0;
}
} else {
fc_pci->stream_problem = 0;
fc_pci->count_prev = fc_pci->count;
}
}
schedule_delayed_work(&fc_pci->irq_check_work,
msecs_to_jiffies(irq_chk_intv < 100 ? 100 : irq_chk_intv));
}
/* When PID filtering is turned on, we use the timer IRQ, because small amounts
* of data need to be passed to the user space instantly as well. When PID
* filtering is turned off, we use the page-change-IRQ */
static irqreturn_t flexcop_pci_isr(int irq, void *dev_id)
{
struct flexcop_pci *fc_pci = dev_id;
struct flexcop_device *fc = fc_pci->fc_dev;
unsigned long flags;
flexcop_ibi_value v;
irqreturn_t ret = IRQ_HANDLED;
spin_lock_irqsave(&fc_pci->irq_lock, flags);
v = fc->read_ibi_reg(fc, irq_20c);
/* errors */
if (v.irq_20c.Data_receiver_error)
deb_chk("data receiver error\n");
if (v.irq_20c.Continuity_error_flag)
deb_chk("Contunuity error flag is set\n");
if (v.irq_20c.LLC_SNAP_FLAG_set)
deb_chk("LLC_SNAP_FLAG_set is set\n");
if (v.irq_20c.Transport_Error)
deb_chk("Transport error\n");
if ((fc_pci->count % 1000) == 0)
deb_chk("%d valid irq took place so far\n", fc_pci->count);
if (v.irq_20c.DMA1_IRQ_Status == 1) {
if (fc_pci->active_dma1_addr == 0)
flexcop_pass_dmx_packets(fc_pci->fc_dev,
fc_pci->dma[0].cpu_addr0,
fc_pci->dma[0].size / 188);
else
flexcop_pass_dmx_packets(fc_pci->fc_dev,
fc_pci->dma[0].cpu_addr1,
fc_pci->dma[0].size / 188);
deb_irq("page change to page: %d\n",!fc_pci->active_dma1_addr);
fc_pci->active_dma1_addr = !fc_pci->active_dma1_addr;
/* for the timer IRQ we only can use buffer dmx feeding, because we don't have
* complete TS packets when reading from the DMA memory */
} else if (v.irq_20c.DMA1_Timer_Status == 1) {
dma_addr_t cur_addr =
fc->read_ibi_reg(fc,dma1_008).dma_0x8.dma_cur_addr << 2;
u32 cur_pos = cur_addr - fc_pci->dma[0].dma_addr0;
deb_irq("%u irq: %08x cur_addr: %llx: cur_pos: %08x, "
"last_cur_pos: %08x ",
jiffies_to_usecs(jiffies - fc_pci->last_irq),
v.raw, (unsigned long long)cur_addr, cur_pos,
fc_pci->last_dma1_cur_pos);
fc_pci->last_irq = jiffies;
/* buffer end was reached, restarted from the beginning
* pass the data from last_cur_pos to the buffer end to the demux
*/
if (cur_pos < fc_pci->last_dma1_cur_pos) {
deb_irq(" end was reached: passing %d bytes ",
(fc_pci->dma[0].size*2 - 1) -
fc_pci->last_dma1_cur_pos);
flexcop_pass_dmx_data(fc_pci->fc_dev,
fc_pci->dma[0].cpu_addr0 +
fc_pci->last_dma1_cur_pos,
(fc_pci->dma[0].size*2) -
fc_pci->last_dma1_cur_pos);
fc_pci->last_dma1_cur_pos = 0;
}
if (cur_pos > fc_pci->last_dma1_cur_pos) {
deb_irq(" passing %d bytes ",
cur_pos - fc_pci->last_dma1_cur_pos);
flexcop_pass_dmx_data(fc_pci->fc_dev,
fc_pci->dma[0].cpu_addr0 +
fc_pci->last_dma1_cur_pos,
cur_pos - fc_pci->last_dma1_cur_pos);
}
deb_irq("\n");
fc_pci->last_dma1_cur_pos = cur_pos;
fc_pci->count++;
} else {
deb_irq("isr for flexcop called, "
"apparently without reason (%08x)\n", v.raw);
ret = IRQ_NONE;
}
spin_unlock_irqrestore(&fc_pci->irq_lock, flags);
return ret;
}
static int flexcop_pci_stream_control(struct flexcop_device *fc, int onoff)
{
struct flexcop_pci *fc_pci = fc->bus_specific;
if (onoff) {
flexcop_dma_config(fc, &fc_pci->dma[0], FC_DMA_1);
flexcop_dma_config(fc, &fc_pci->dma[1], FC_DMA_2);
flexcop_dma_config_timer(fc, FC_DMA_1, 0);
flexcop_dma_xfer_control(fc, FC_DMA_1,
FC_DMA_SUBADDR_0 | FC_DMA_SUBADDR_1, 1);
deb_irq("DMA xfer enabled\n");
fc_pci->last_dma1_cur_pos = 0;
flexcop_dma_control_timer_irq(fc, FC_DMA_1, 1);
deb_irq("IRQ enabled\n");
fc_pci->count_prev = fc_pci->count;
} else {
flexcop_dma_control_timer_irq(fc, FC_DMA_1, 0);
deb_irq("IRQ disabled\n");
flexcop_dma_xfer_control(fc, FC_DMA_1,
FC_DMA_SUBADDR_0 | FC_DMA_SUBADDR_1, 0);
deb_irq("DMA xfer disabled\n");
}
return 0;
}
static int flexcop_pci_dma_init(struct flexcop_pci *fc_pci)
{
int ret;
ret = flexcop_dma_allocate(fc_pci->pdev, &fc_pci->dma[0],
FC_DEFAULT_DMA1_BUFSIZE);
if (ret != 0)
return ret;
ret = flexcop_dma_allocate(fc_pci->pdev, &fc_pci->dma[1],
FC_DEFAULT_DMA2_BUFSIZE);
if (ret != 0) {
flexcop_dma_free(&fc_pci->dma[0]);
return ret;
}
flexcop_sram_set_dest(fc_pci->fc_dev, FC_SRAM_DEST_MEDIA |
FC_SRAM_DEST_NET, FC_SRAM_DEST_TARGET_DMA1);
flexcop_sram_set_dest(fc_pci->fc_dev, FC_SRAM_DEST_CAO |
FC_SRAM_DEST_CAI, FC_SRAM_DEST_TARGET_DMA2);
fc_pci->init_state |= FC_PCI_DMA_INIT;
return ret;
}
static void flexcop_pci_dma_exit(struct flexcop_pci *fc_pci)
{
if (fc_pci->init_state & FC_PCI_DMA_INIT) {
flexcop_dma_free(&fc_pci->dma[0]);
flexcop_dma_free(&fc_pci->dma[1]);
}
fc_pci->init_state &= ~FC_PCI_DMA_INIT;
}
static int flexcop_pci_init(struct flexcop_pci *fc_pci)
{
int ret;
info("card revision %x", fc_pci->pdev->revision);
if ((ret = pci_enable_device(fc_pci->pdev)) != 0)
return ret;
pci_set_master(fc_pci->pdev);
if ((ret = pci_request_regions(fc_pci->pdev, DRIVER_NAME)) != 0)
goto err_pci_disable_device;
fc_pci->io_mem = pci_iomap(fc_pci->pdev, 0, 0x800);
if (!fc_pci->io_mem) {
err("cannot map io memory\n");
ret = -EIO;
goto err_pci_release_regions;
}
pci_set_drvdata(fc_pci->pdev, fc_pci);
spin_lock_init(&fc_pci->irq_lock);
if ((ret = request_irq(fc_pci->pdev->irq, flexcop_pci_isr,
IRQF_SHARED, DRIVER_NAME, fc_pci)) != 0)
goto err_pci_iounmap;
fc_pci->init_state |= FC_PCI_INIT;
return ret;
err_pci_iounmap:
pci_iounmap(fc_pci->pdev, fc_pci->io_mem);
err_pci_release_regions:
pci_release_regions(fc_pci->pdev);
err_pci_disable_device:
pci_disable_device(fc_pci->pdev);
return ret;
}
static void flexcop_pci_exit(struct flexcop_pci *fc_pci)
{
if (fc_pci->init_state & FC_PCI_INIT) {
free_irq(fc_pci->pdev->irq, fc_pci);
pci_iounmap(fc_pci->pdev, fc_pci->io_mem);
pci_release_regions(fc_pci->pdev);
pci_disable_device(fc_pci->pdev);
}
fc_pci->init_state &= ~FC_PCI_INIT;
}
static int flexcop_pci_probe(struct pci_dev *pdev,
const struct pci_device_id *ent)
{
struct flexcop_device *fc;
struct flexcop_pci *fc_pci;
int ret = -ENOMEM;
if ((fc = flexcop_device_kmalloc(sizeof(struct flexcop_pci))) == NULL) {
err("out of memory\n");
return -ENOMEM;
}
/* general flexcop init */
fc_pci = fc->bus_specific;
fc_pci->fc_dev = fc;
fc->read_ibi_reg = flexcop_pci_read_ibi_reg;
fc->write_ibi_reg = flexcop_pci_write_ibi_reg;
fc->i2c_request = flexcop_i2c_request;
fc->get_mac_addr = flexcop_eeprom_check_mac_addr;
fc->stream_control = flexcop_pci_stream_control;
if (enable_pid_filtering)
info("will use the HW PID filter.");
else
info("will pass the complete TS to the demuxer.");
fc->pid_filtering = enable_pid_filtering;
fc->bus_type = FC_PCI;
fc->dev = &pdev->dev;
fc->owner = THIS_MODULE;
/* bus specific part */
fc_pci->pdev = pdev;
if ((ret = flexcop_pci_init(fc_pci)) != 0)
goto err_kfree;
/* init flexcop */
if ((ret = flexcop_device_initialize(fc)) != 0)
goto err_pci_exit;
/* init dma */
if ((ret = flexcop_pci_dma_init(fc_pci)) != 0)
goto err_fc_exit;
INIT_DELAYED_WORK(&fc_pci->irq_check_work, flexcop_pci_irq_check_work);
if (irq_chk_intv > 0)
schedule_delayed_work(&fc_pci->irq_check_work,
msecs_to_jiffies(irq_chk_intv < 100 ?
100 :
irq_chk_intv));
return ret;
err_fc_exit:
flexcop_device_exit(fc);
err_pci_exit:
flexcop_pci_exit(fc_pci);
err_kfree:
flexcop_device_kfree(fc);
return ret;
}
/* in theory every _exit function should be called exactly two times,
* here and in the bail-out-part of the _init-function
*/
static void flexcop_pci_remove(struct pci_dev *pdev)
{
struct flexcop_pci *fc_pci = pci_get_drvdata(pdev);
if (irq_chk_intv > 0)
cancel_delayed_work(&fc_pci->irq_check_work);
flexcop_pci_dma_exit(fc_pci);
flexcop_device_exit(fc_pci->fc_dev);
flexcop_pci_exit(fc_pci);
flexcop_device_kfree(fc_pci->fc_dev);
}
static struct pci_device_id flexcop_pci_tbl[] = {
{ PCI_DEVICE(0x13d0, 0x2103) },
{ },
};
MODULE_DEVICE_TABLE(pci, flexcop_pci_tbl);
static struct pci_driver flexcop_pci_driver = {
.name = "b2c2_flexcop_pci",
.id_table = flexcop_pci_tbl,
.probe = flexcop_pci_probe,
.remove = flexcop_pci_remove,
};
module_pci_driver(flexcop_pci_driver);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_NAME);
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,43 @@
config VIDEO_BT848
tristate "BT848 Video For Linux"
depends on VIDEO_DEV && PCI && I2C && VIDEO_V4L2
select I2C_ALGOBIT
select VIDEO_BTCX
select VIDEOBUF_DMA_SG
depends on RC_CORE
select VIDEO_TUNER
select VIDEO_TVEEPROM
select VIDEO_MSP3400 if MEDIA_SUBDRV_AUTOSELECT
select VIDEO_TVAUDIO if MEDIA_SUBDRV_AUTOSELECT
select VIDEO_TDA7432 if MEDIA_SUBDRV_AUTOSELECT
select VIDEO_SAA6588 if MEDIA_SUBDRV_AUTOSELECT
---help---
Support for BT848 based frame grabber/overlay boards. This includes
the Miro, Hauppauge and STB boards. Please read the material in
<file:Documentation/video4linux/bttv/> for more information.
To compile this driver as a module, choose M here: the
module will be called bttv.
config DVB_BT8XX
tristate "DVB/ATSC Support for bt878 based TV cards"
depends on DVB_CORE && PCI && I2C && VIDEO_BT848
select DVB_MT352 if MEDIA_SUBDRV_AUTOSELECT
select DVB_SP887X if MEDIA_SUBDRV_AUTOSELECT
select DVB_NXT6000 if MEDIA_SUBDRV_AUTOSELECT
select DVB_CX24110 if MEDIA_SUBDRV_AUTOSELECT
select DVB_OR51211 if MEDIA_SUBDRV_AUTOSELECT
select DVB_LGDT330X if MEDIA_SUBDRV_AUTOSELECT
select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT
select MEDIA_TUNER_SIMPLE if MEDIA_SUBDRV_AUTOSELECT
help
Support for PCI cards based on the Bt8xx PCI bridge. Examples are
the Nebula cards, the Pinnacle PCTV cards, the Twinhan DST cards,
the pcHDTV HD2000 cards, the DViCO FusionHDTV Lite cards, and
some AVerMedia cards.
Since these cards have no MPEG decoder onboard, they transmit
only compressed MPEG data over the PCI bus, so you need
an external software decoder to watch TV on your computer.
Say Y if you own such a device and want to use it.

View file

@ -0,0 +1,12 @@
bttv-objs := bttv-driver.o bttv-cards.o bttv-if.o \
bttv-risc.o bttv-vbi.o bttv-i2c.o bttv-gpio.o \
bttv-input.o bttv-audio-hook.o
obj-$(CONFIG_VIDEO_BT848) += bttv.o
obj-$(CONFIG_DVB_BT8XX) += bt878.o dvb-bt8xx.o dst.o dst_ca.o
ccflags-y += -Idrivers/media/dvb-core
ccflags-y += -Idrivers/media/dvb-frontends
ccflags-y += -Idrivers/media/i2c
ccflags-y += -Idrivers/media/common
ccflags-y += -Idrivers/media/tuners

View file

@ -0,0 +1,369 @@
/*
bt848.h - Bt848 register offsets
Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de)
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 _BT848_H_
#define _BT848_H_
#ifndef PCI_VENDOR_ID_BROOKTREE
#define PCI_VENDOR_ID_BROOKTREE 0x109e
#endif
#ifndef PCI_DEVICE_ID_BT848
#define PCI_DEVICE_ID_BT848 0x350
#endif
#ifndef PCI_DEVICE_ID_BT849
#define PCI_DEVICE_ID_BT849 0x351
#endif
#ifndef PCI_DEVICE_ID_FUSION879
#define PCI_DEVICE_ID_FUSION879 0x36c
#endif
#ifndef PCI_DEVICE_ID_BT878
#define PCI_DEVICE_ID_BT878 0x36e
#endif
#ifndef PCI_DEVICE_ID_BT879
#define PCI_DEVICE_ID_BT879 0x36f
#endif
/* Brooktree 848 registers */
#define BT848_DSTATUS 0x000
#define BT848_DSTATUS_PRES (1<<7)
#define BT848_DSTATUS_HLOC (1<<6)
#define BT848_DSTATUS_FIELD (1<<5)
#define BT848_DSTATUS_NUML (1<<4)
#define BT848_DSTATUS_CSEL (1<<3)
#define BT848_DSTATUS_PLOCK (1<<2)
#define BT848_DSTATUS_LOF (1<<1)
#define BT848_DSTATUS_COF (1<<0)
#define BT848_IFORM 0x004
#define BT848_IFORM_HACTIVE (1<<7)
#define BT848_IFORM_MUXSEL (3<<5)
#define BT848_IFORM_MUX0 (2<<5)
#define BT848_IFORM_MUX1 (3<<5)
#define BT848_IFORM_MUX2 (1<<5)
#define BT848_IFORM_XTSEL (3<<3)
#define BT848_IFORM_XT0 (1<<3)
#define BT848_IFORM_XT1 (2<<3)
#define BT848_IFORM_XTAUTO (3<<3)
#define BT848_IFORM_XTBOTH (3<<3)
#define BT848_IFORM_NTSC 1
#define BT848_IFORM_NTSC_J 2
#define BT848_IFORM_PAL_BDGHI 3
#define BT848_IFORM_PAL_M 4
#define BT848_IFORM_PAL_N 5
#define BT848_IFORM_SECAM 6
#define BT848_IFORM_PAL_NC 7
#define BT848_IFORM_AUTO 0
#define BT848_IFORM_NORM 7
#define BT848_TDEC 0x008
#define BT848_TDEC_DEC_FIELD (1<<7)
#define BT848_TDEC_FLDALIGN (1<<6)
#define BT848_TDEC_DEC_RAT (0x1f)
#define BT848_E_CROP 0x00C
#define BT848_O_CROP 0x08C
#define BT848_E_VDELAY_LO 0x010
#define BT848_O_VDELAY_LO 0x090
#define BT848_E_VACTIVE_LO 0x014
#define BT848_O_VACTIVE_LO 0x094
#define BT848_E_HDELAY_LO 0x018
#define BT848_O_HDELAY_LO 0x098
#define BT848_E_HACTIVE_LO 0x01C
#define BT848_O_HACTIVE_LO 0x09C
#define BT848_E_HSCALE_HI 0x020
#define BT848_O_HSCALE_HI 0x0A0
#define BT848_E_HSCALE_LO 0x024
#define BT848_O_HSCALE_LO 0x0A4
#define BT848_BRIGHT 0x028
#define BT848_E_CONTROL 0x02C
#define BT848_O_CONTROL 0x0AC
#define BT848_CONTROL_LNOTCH (1<<7)
#define BT848_CONTROL_COMP (1<<6)
#define BT848_CONTROL_LDEC (1<<5)
#define BT848_CONTROL_CBSENSE (1<<4)
#define BT848_CONTROL_CON_MSB (1<<2)
#define BT848_CONTROL_SAT_U_MSB (1<<1)
#define BT848_CONTROL_SAT_V_MSB (1<<0)
#define BT848_CONTRAST_LO 0x030
#define BT848_SAT_U_LO 0x034
#define BT848_SAT_V_LO 0x038
#define BT848_HUE 0x03C
#define BT848_E_SCLOOP 0x040
#define BT848_O_SCLOOP 0x0C0
#define BT848_SCLOOP_CAGC (1<<6)
#define BT848_SCLOOP_CKILL (1<<5)
#define BT848_SCLOOP_HFILT_AUTO (0<<3)
#define BT848_SCLOOP_HFILT_CIF (1<<3)
#define BT848_SCLOOP_HFILT_QCIF (2<<3)
#define BT848_SCLOOP_HFILT_ICON (3<<3)
#define BT848_SCLOOP_PEAK (1<<7)
#define BT848_SCLOOP_HFILT_MINP (1<<3)
#define BT848_SCLOOP_HFILT_MEDP (2<<3)
#define BT848_SCLOOP_HFILT_MAXP (3<<3)
#define BT848_OFORM 0x048
#define BT848_OFORM_RANGE (1<<7)
#define BT848_OFORM_CORE0 (0<<5)
#define BT848_OFORM_CORE8 (1<<5)
#define BT848_OFORM_CORE16 (2<<5)
#define BT848_OFORM_CORE32 (3<<5)
#define BT848_E_VSCALE_HI 0x04C
#define BT848_O_VSCALE_HI 0x0CC
#define BT848_VSCALE_YCOMB (1<<7)
#define BT848_VSCALE_COMB (1<<6)
#define BT848_VSCALE_INT (1<<5)
#define BT848_VSCALE_HI 15
#define BT848_E_VSCALE_LO 0x050
#define BT848_O_VSCALE_LO 0x0D0
#define BT848_TEST 0x054
#define BT848_ADELAY 0x060
#define BT848_BDELAY 0x064
#define BT848_ADC 0x068
#define BT848_ADC_RESERVED (2<<6)
#define BT848_ADC_SYNC_T (1<<5)
#define BT848_ADC_AGC_EN (1<<4)
#define BT848_ADC_CLK_SLEEP (1<<3)
#define BT848_ADC_Y_SLEEP (1<<2)
#define BT848_ADC_C_SLEEP (1<<1)
#define BT848_ADC_CRUSH (1<<0)
#define BT848_WC_UP 0x044
#define BT848_WC_DOWN 0x078
#define BT848_E_VTC 0x06C
#define BT848_O_VTC 0x0EC
#define BT848_VTC_HSFMT (1<<7)
#define BT848_VTC_VFILT_2TAP 0
#define BT848_VTC_VFILT_3TAP 1
#define BT848_VTC_VFILT_4TAP 2
#define BT848_VTC_VFILT_5TAP 3
#define BT848_SRESET 0x07C
#define BT848_COLOR_FMT 0x0D4
#define BT848_COLOR_FMT_O_RGB32 (0<<4)
#define BT848_COLOR_FMT_O_RGB24 (1<<4)
#define BT848_COLOR_FMT_O_RGB16 (2<<4)
#define BT848_COLOR_FMT_O_RGB15 (3<<4)
#define BT848_COLOR_FMT_O_YUY2 (4<<4)
#define BT848_COLOR_FMT_O_BtYUV (5<<4)
#define BT848_COLOR_FMT_O_Y8 (6<<4)
#define BT848_COLOR_FMT_O_RGB8 (7<<4)
#define BT848_COLOR_FMT_O_YCrCb422 (8<<4)
#define BT848_COLOR_FMT_O_YCrCb411 (9<<4)
#define BT848_COLOR_FMT_O_RAW (14<<4)
#define BT848_COLOR_FMT_E_RGB32 0
#define BT848_COLOR_FMT_E_RGB24 1
#define BT848_COLOR_FMT_E_RGB16 2
#define BT848_COLOR_FMT_E_RGB15 3
#define BT848_COLOR_FMT_E_YUY2 4
#define BT848_COLOR_FMT_E_BtYUV 5
#define BT848_COLOR_FMT_E_Y8 6
#define BT848_COLOR_FMT_E_RGB8 7
#define BT848_COLOR_FMT_E_YCrCb422 8
#define BT848_COLOR_FMT_E_YCrCb411 9
#define BT848_COLOR_FMT_E_RAW 14
#define BT848_COLOR_FMT_RGB32 0x00
#define BT848_COLOR_FMT_RGB24 0x11
#define BT848_COLOR_FMT_RGB16 0x22
#define BT848_COLOR_FMT_RGB15 0x33
#define BT848_COLOR_FMT_YUY2 0x44
#define BT848_COLOR_FMT_BtYUV 0x55
#define BT848_COLOR_FMT_Y8 0x66
#define BT848_COLOR_FMT_RGB8 0x77
#define BT848_COLOR_FMT_YCrCb422 0x88
#define BT848_COLOR_FMT_YCrCb411 0x99
#define BT848_COLOR_FMT_RAW 0xee
#define BT848_VTOTAL_LO 0xB0
#define BT848_VTOTAL_HI 0xB4
#define BT848_COLOR_CTL 0x0D8
#define BT848_COLOR_CTL_EXT_FRMRATE (1<<7)
#define BT848_COLOR_CTL_COLOR_BARS (1<<6)
#define BT848_COLOR_CTL_RGB_DED (1<<5)
#define BT848_COLOR_CTL_GAMMA (1<<4)
#define BT848_COLOR_CTL_WSWAP_ODD (1<<3)
#define BT848_COLOR_CTL_WSWAP_EVEN (1<<2)
#define BT848_COLOR_CTL_BSWAP_ODD (1<<1)
#define BT848_COLOR_CTL_BSWAP_EVEN (1<<0)
#define BT848_CAP_CTL 0x0DC
#define BT848_CAP_CTL_DITH_FRAME (1<<4)
#define BT848_CAP_CTL_CAPTURE_VBI_ODD (1<<3)
#define BT848_CAP_CTL_CAPTURE_VBI_EVEN (1<<2)
#define BT848_CAP_CTL_CAPTURE_ODD (1<<1)
#define BT848_CAP_CTL_CAPTURE_EVEN (1<<0)
#define BT848_VBI_PACK_SIZE 0x0E0
#define BT848_VBI_PACK_DEL 0x0E4
#define BT848_VBI_PACK_DEL_VBI_HDELAY 0xfc
#define BT848_VBI_PACK_DEL_EXT_FRAME 2
#define BT848_VBI_PACK_DEL_VBI_PKT_HI 1
#define BT848_INT_STAT 0x100
#define BT848_INT_MASK 0x104
#define BT848_INT_ETBF (1<<23)
#define BT848_INT_RISCS (0xf<<28)
#define BT848_INT_RISC_EN (1<<27)
#define BT848_INT_RACK (1<<25)
#define BT848_INT_FIELD (1<<24)
#define BT848_INT_SCERR (1<<19)
#define BT848_INT_OCERR (1<<18)
#define BT848_INT_PABORT (1<<17)
#define BT848_INT_RIPERR (1<<16)
#define BT848_INT_PPERR (1<<15)
#define BT848_INT_FDSR (1<<14)
#define BT848_INT_FTRGT (1<<13)
#define BT848_INT_FBUS (1<<12)
#define BT848_INT_RISCI (1<<11)
#define BT848_INT_GPINT (1<<9)
#define BT848_INT_I2CDONE (1<<8)
#define BT848_INT_VPRES (1<<5)
#define BT848_INT_HLOCK (1<<4)
#define BT848_INT_OFLOW (1<<3)
#define BT848_INT_HSYNC (1<<2)
#define BT848_INT_VSYNC (1<<1)
#define BT848_INT_FMTCHG (1<<0)
#define BT848_GPIO_DMA_CTL 0x10C
#define BT848_GPIO_DMA_CTL_GPINTC (1<<15)
#define BT848_GPIO_DMA_CTL_GPINTI (1<<14)
#define BT848_GPIO_DMA_CTL_GPWEC (1<<13)
#define BT848_GPIO_DMA_CTL_GPIOMODE (3<<11)
#define BT848_GPIO_DMA_CTL_GPCLKMODE (1<<10)
#define BT848_GPIO_DMA_CTL_PLTP23_4 (0<<6)
#define BT848_GPIO_DMA_CTL_PLTP23_8 (1<<6)
#define BT848_GPIO_DMA_CTL_PLTP23_16 (2<<6)
#define BT848_GPIO_DMA_CTL_PLTP23_32 (3<<6)
#define BT848_GPIO_DMA_CTL_PLTP1_4 (0<<4)
#define BT848_GPIO_DMA_CTL_PLTP1_8 (1<<4)
#define BT848_GPIO_DMA_CTL_PLTP1_16 (2<<4)
#define BT848_GPIO_DMA_CTL_PLTP1_32 (3<<4)
#define BT848_GPIO_DMA_CTL_PKTP_4 (0<<2)
#define BT848_GPIO_DMA_CTL_PKTP_8 (1<<2)
#define BT848_GPIO_DMA_CTL_PKTP_16 (2<<2)
#define BT848_GPIO_DMA_CTL_PKTP_32 (3<<2)
#define BT848_GPIO_DMA_CTL_RISC_ENABLE (1<<1)
#define BT848_GPIO_DMA_CTL_FIFO_ENABLE (1<<0)
#define BT848_I2C 0x110
#define BT878_I2C_MODE (1<<7)
#define BT878_I2C_RATE (1<<6)
#define BT878_I2C_NOSTOP (1<<5)
#define BT878_I2C_NOSTART (1<<4)
#define BT848_I2C_DIV (0xf<<4)
#define BT848_I2C_SYNC (1<<3)
#define BT848_I2C_W3B (1<<2)
#define BT848_I2C_SCL (1<<1)
#define BT848_I2C_SDA (1<<0)
#define BT848_RISC_STRT_ADD 0x114
#define BT848_GPIO_OUT_EN 0x118
#define BT848_GPIO_REG_INP 0x11C
#define BT848_RISC_COUNT 0x120
#define BT848_GPIO_DATA 0x200
/* Bt848 RISC commands */
/* only for the SYNC RISC command */
#define BT848_FIFO_STATUS_FM1 0x06
#define BT848_FIFO_STATUS_FM3 0x0e
#define BT848_FIFO_STATUS_SOL 0x02
#define BT848_FIFO_STATUS_EOL4 0x01
#define BT848_FIFO_STATUS_EOL3 0x0d
#define BT848_FIFO_STATUS_EOL2 0x09
#define BT848_FIFO_STATUS_EOL1 0x05
#define BT848_FIFO_STATUS_VRE 0x04
#define BT848_FIFO_STATUS_VRO 0x0c
#define BT848_FIFO_STATUS_PXV 0x00
#define BT848_RISC_RESYNC (1<<15)
/* WRITE and SKIP */
/* disable which bytes of each DWORD */
#define BT848_RISC_BYTE0 (1U<<12)
#define BT848_RISC_BYTE1 (1U<<13)
#define BT848_RISC_BYTE2 (1U<<14)
#define BT848_RISC_BYTE3 (1U<<15)
#define BT848_RISC_BYTE_ALL (0x0fU<<12)
#define BT848_RISC_BYTE_NONE 0
/* cause RISCI */
#define BT848_RISC_IRQ (1U<<24)
/* RISC command is last one in this line */
#define BT848_RISC_EOL (1U<<26)
/* RISC command is first one in this line */
#define BT848_RISC_SOL (1U<<27)
#define BT848_RISC_WRITE (0x01U<<28)
#define BT848_RISC_SKIP (0x02U<<28)
#define BT848_RISC_WRITEC (0x05U<<28)
#define BT848_RISC_JUMP (0x07U<<28)
#define BT848_RISC_SYNC (0x08U<<28)
#define BT848_RISC_WRITE123 (0x09U<<28)
#define BT848_RISC_SKIP123 (0x0aU<<28)
#define BT848_RISC_WRITE1S23 (0x0bU<<28)
/* Bt848A and higher only !! */
#define BT848_TGLB 0x080
#define BT848_TGCTRL 0x084
#define BT848_FCAP 0x0E8
#define BT848_PLL_F_LO 0x0F0
#define BT848_PLL_F_HI 0x0F4
#define BT848_PLL_XCI 0x0F8
#define BT848_PLL_X (1<<7)
#define BT848_PLL_C (1<<6)
#define BT848_DVSIF 0x0FC
/* Bt878 register */
#define BT878_DEVCTRL 0x40
#define BT878_EN_TBFX 0x02
#define BT878_EN_VSFX 0x04
#endif

View file

@ -0,0 +1,598 @@
/*
* bt878.c: part of the driver for the Pinnacle PCTV Sat DVB PCI card
*
* Copyright (C) 2002 Peter Hettkamp <peter.hettkamp@htp-tel.de>
*
* large parts based on the bttv driver
* Copyright (C) 1996,97,98 Ralph Metzler (rjkm@metzlerbros.de)
* & Marcus Metzler (mocm@metzlerbros.de)
* (c) 1999,2000 Gerd Knorr <kraxel@goldbach.in-berlin.de>
*
* 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.
* Or, point your browser to http://www.gnu.org/copyleft/gpl.html
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <asm/io.h>
#include <linux/ioport.h>
#include <asm/pgtable.h>
#include <asm/page.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/kmod.h>
#include <linux/vmalloc.h>
#include <linux/init.h>
#include "dmxdev.h"
#include "dvbdev.h"
#include "bt878.h"
#include "dst_priv.h"
/**************************************/
/* Miscellaneous utility definitions */
/**************************************/
static unsigned int bt878_verbose = 1;
static unsigned int bt878_debug;
module_param_named(verbose, bt878_verbose, int, 0444);
MODULE_PARM_DESC(verbose,
"verbose startup messages, default is 1 (yes)");
module_param_named(debug, bt878_debug, int, 0644);
MODULE_PARM_DESC(debug, "Turn on/off debugging, default is 0 (off).");
int bt878_num;
struct bt878 bt878[BT878_MAX];
EXPORT_SYMBOL(bt878_num);
EXPORT_SYMBOL(bt878);
#define btwrite(dat,adr) bmtwrite((dat), (bt->bt878_mem+(adr)))
#define btread(adr) bmtread(bt->bt878_mem+(adr))
#define btand(dat,adr) btwrite((dat) & btread(adr), adr)
#define btor(dat,adr) btwrite((dat) | btread(adr), adr)
#define btaor(dat,mask,adr) btwrite((dat) | ((mask) & btread(adr)), adr)
#if defined(dprintk)
#undef dprintk
#endif
#define dprintk(fmt, arg...) \
do { \
if (bt878_debug) \
printk(KERN_DEBUG fmt, ##arg); \
} while (0)
static void bt878_mem_free(struct bt878 *bt)
{
if (bt->buf_cpu) {
pci_free_consistent(bt->dev, bt->buf_size, bt->buf_cpu,
bt->buf_dma);
bt->buf_cpu = NULL;
}
if (bt->risc_cpu) {
pci_free_consistent(bt->dev, bt->risc_size, bt->risc_cpu,
bt->risc_dma);
bt->risc_cpu = NULL;
}
}
static int bt878_mem_alloc(struct bt878 *bt)
{
if (!bt->buf_cpu) {
bt->buf_size = 128 * 1024;
bt->buf_cpu = pci_zalloc_consistent(bt->dev, bt->buf_size,
&bt->buf_dma);
if (!bt->buf_cpu)
return -ENOMEM;
}
if (!bt->risc_cpu) {
bt->risc_size = PAGE_SIZE;
bt->risc_cpu = pci_zalloc_consistent(bt->dev, bt->risc_size,
&bt->risc_dma);
if (!bt->risc_cpu) {
bt878_mem_free(bt);
return -ENOMEM;
}
}
return 0;
}
/* RISC instructions */
#define RISC_WRITE (0x01 << 28)
#define RISC_JUMP (0x07 << 28)
#define RISC_SYNC (0x08 << 28)
/* RISC bits */
#define RISC_WR_SOL (1 << 27)
#define RISC_WR_EOL (1 << 26)
#define RISC_IRQ (1 << 24)
#define RISC_STATUS(status) ((((~status) & 0x0F) << 20) | ((status & 0x0F) << 16))
#define RISC_SYNC_RESYNC (1 << 15)
#define RISC_SYNC_FM1 0x06
#define RISC_SYNC_VRO 0x0C
#define RISC_FLUSH() bt->risc_pos = 0
#define RISC_INSTR(instr) bt->risc_cpu[bt->risc_pos++] = cpu_to_le32(instr)
static int bt878_make_risc(struct bt878 *bt)
{
bt->block_bytes = bt->buf_size >> 4;
bt->block_count = 1 << 4;
bt->line_bytes = bt->block_bytes;
bt->line_count = bt->block_count;
while (bt->line_bytes > 4095) {
bt->line_bytes >>= 1;
bt->line_count <<= 1;
}
if (bt->line_count > 255) {
printk(KERN_ERR "bt878: buffer size error!\n");
return -EINVAL;
}
return 0;
}
static void bt878_risc_program(struct bt878 *bt, u32 op_sync_orin)
{
u32 buf_pos = 0;
u32 line;
RISC_FLUSH();
RISC_INSTR(RISC_SYNC | RISC_SYNC_FM1 | op_sync_orin);
RISC_INSTR(0);
dprintk("bt878: risc len lines %u, bytes per line %u\n",
bt->line_count, bt->line_bytes);
for (line = 0; line < bt->line_count; line++) {
// At the beginning of every block we issue an IRQ with previous (finished) block number set
if (!(buf_pos % bt->block_bytes))
RISC_INSTR(RISC_WRITE | RISC_WR_SOL | RISC_WR_EOL |
RISC_IRQ |
RISC_STATUS(((buf_pos /
bt->block_bytes) +
(bt->block_count -
1)) %
bt->block_count) | bt->
line_bytes);
else
RISC_INSTR(RISC_WRITE | RISC_WR_SOL | RISC_WR_EOL |
bt->line_bytes);
RISC_INSTR(bt->buf_dma + buf_pos);
buf_pos += bt->line_bytes;
}
RISC_INSTR(RISC_SYNC | op_sync_orin | RISC_SYNC_VRO);
RISC_INSTR(0);
RISC_INSTR(RISC_JUMP);
RISC_INSTR(bt->risc_dma);
btwrite((bt->line_count << 16) | bt->line_bytes, BT878_APACK_LEN);
}
/*****************************/
/* Start/Stop grabbing funcs */
/*****************************/
void bt878_start(struct bt878 *bt, u32 controlreg, u32 op_sync_orin,
u32 irq_err_ignore)
{
u32 int_mask;
dprintk("bt878 debug: bt878_start (ctl=%8.8x)\n", controlreg);
/* complete the writing of the risc dma program now we have
* the card specifics
*/
bt878_risc_program(bt, op_sync_orin);
controlreg &= ~0x1f;
controlreg |= 0x1b;
btwrite(bt->risc_dma, BT878_ARISC_START);
/* original int mask had :
* 6 2 8 4 0
* 1111 1111 1000 0000 0000
* SCERR|OCERR|PABORT|RIPERR|FDSR|FTRGT|FBUS|RISCI
* Hacked for DST to:
* SCERR | OCERR | FDSR | FTRGT | FBUS | RISCI
*/
int_mask = BT878_ASCERR | BT878_AOCERR | BT878_APABORT |
BT878_ARIPERR | BT878_APPERR | BT878_AFDSR | BT878_AFTRGT |
BT878_AFBUS | BT878_ARISCI;
/* ignore pesky bits */
int_mask &= ~irq_err_ignore;
btwrite(int_mask, BT878_AINT_MASK);
btwrite(controlreg, BT878_AGPIO_DMA_CTL);
}
void bt878_stop(struct bt878 *bt)
{
u32 stat;
int i = 0;
dprintk("bt878 debug: bt878_stop\n");
btwrite(0, BT878_AINT_MASK);
btand(~0x13, BT878_AGPIO_DMA_CTL);
do {
stat = btread(BT878_AINT_STAT);
if (!(stat & BT878_ARISC_EN))
break;
i++;
} while (i < 500);
dprintk("bt878(%d) debug: bt878_stop, i=%d, stat=0x%8.8x\n",
bt->nr, i, stat);
}
EXPORT_SYMBOL(bt878_start);
EXPORT_SYMBOL(bt878_stop);
/*****************************/
/* Interrupt service routine */
/*****************************/
static irqreturn_t bt878_irq(int irq, void *dev_id)
{
u32 stat, astat, mask;
int count;
struct bt878 *bt;
bt = (struct bt878 *) dev_id;
count = 0;
while (1) {
stat = btread(BT878_AINT_STAT);
mask = btread(BT878_AINT_MASK);
if (!(astat = (stat & mask)))
return IRQ_NONE; /* this interrupt is not for me */
/* dprintk("bt878(%d) debug: irq count %d, stat 0x%8.8x, mask 0x%8.8x\n",bt->nr,count,stat,mask); */
btwrite(astat, BT878_AINT_STAT); /* try to clear interrupt condition */
if (astat & (BT878_ASCERR | BT878_AOCERR)) {
if (bt878_verbose) {
printk(KERN_INFO
"bt878(%d): irq%s%s risc_pc=%08x\n",
bt->nr,
(astat & BT878_ASCERR) ? " SCERR" :
"",
(astat & BT878_AOCERR) ? " OCERR" :
"", btread(BT878_ARISC_PC));
}
}
if (astat & (BT878_APABORT | BT878_ARIPERR | BT878_APPERR)) {
if (bt878_verbose) {
printk(KERN_INFO
"bt878(%d): irq%s%s%s risc_pc=%08x\n",
bt->nr,
(astat & BT878_APABORT) ? " PABORT" :
"",
(astat & BT878_ARIPERR) ? " RIPERR" :
"",
(astat & BT878_APPERR) ? " PPERR" :
"", btread(BT878_ARISC_PC));
}
}
if (astat & (BT878_AFDSR | BT878_AFTRGT | BT878_AFBUS)) {
if (bt878_verbose) {
printk(KERN_INFO
"bt878(%d): irq%s%s%s risc_pc=%08x\n",
bt->nr,
(astat & BT878_AFDSR) ? " FDSR" : "",
(astat & BT878_AFTRGT) ? " FTRGT" :
"",
(astat & BT878_AFBUS) ? " FBUS" : "",
btread(BT878_ARISC_PC));
}
}
if (astat & BT878_ARISCI) {
bt->finished_block = (stat & BT878_ARISCS) >> 28;
tasklet_schedule(&bt->tasklet);
break;
}
count++;
if (count > 20) {
btwrite(0, BT878_AINT_MASK);
printk(KERN_ERR
"bt878(%d): IRQ lockup, cleared int mask\n",
bt->nr);
break;
}
}
return IRQ_HANDLED;
}
int
bt878_device_control(struct bt878 *bt, unsigned int cmd, union dst_gpio_packet *mp)
{
int retval;
retval = 0;
if (mutex_lock_interruptible(&bt->gpio_lock))
return -ERESTARTSYS;
/* special gpio signal */
switch (cmd) {
case DST_IG_ENABLE:
// dprintk("dvb_bt8xx: dst enable mask 0x%02x enb 0x%02x \n", mp->dstg.enb.mask, mp->dstg.enb.enable);
retval = bttv_gpio_enable(bt->bttv_nr,
mp->enb.mask,
mp->enb.enable);
break;
case DST_IG_WRITE:
// dprintk("dvb_bt8xx: dst write gpio mask 0x%02x out 0x%02x\n", mp->dstg.outp.mask, mp->dstg.outp.highvals);
retval = bttv_write_gpio(bt->bttv_nr,
mp->outp.mask,
mp->outp.highvals);
break;
case DST_IG_READ:
/* read */
retval = bttv_read_gpio(bt->bttv_nr, &mp->rd.value);
// dprintk("dvb_bt8xx: dst read gpio 0x%02x\n", (unsigned)mp->dstg.rd.value);
break;
case DST_IG_TS:
/* Set packet size */
bt->TS_Size = mp->psize;
break;
default:
retval = -EINVAL;
break;
}
mutex_unlock(&bt->gpio_lock);
return retval;
}
EXPORT_SYMBOL(bt878_device_control);
#define BROOKTREE_878_DEVICE(vend, dev, name) \
{ \
.vendor = PCI_VENDOR_ID_BROOKTREE, \
.device = PCI_DEVICE_ID_BROOKTREE_878, \
.subvendor = (vend), .subdevice = (dev), \
.driver_data = (unsigned long) name \
}
static struct pci_device_id bt878_pci_tbl[] = {
BROOKTREE_878_DEVICE(0x0071, 0x0101, "Nebula Electronics DigiTV"),
BROOKTREE_878_DEVICE(0x1461, 0x0761, "AverMedia AverTV DVB-T 761"),
BROOKTREE_878_DEVICE(0x11bd, 0x001c, "Pinnacle PCTV Sat"),
BROOKTREE_878_DEVICE(0x11bd, 0x0026, "Pinnacle PCTV SAT CI"),
BROOKTREE_878_DEVICE(0x1822, 0x0001, "Twinhan VisionPlus DVB"),
BROOKTREE_878_DEVICE(0x270f, 0xfc00,
"ChainTech digitop DST-1000 DVB-S"),
BROOKTREE_878_DEVICE(0x1461, 0x0771, "AVermedia AverTV DVB-T 771"),
BROOKTREE_878_DEVICE(0x18ac, 0xdb10, "DViCO FusionHDTV DVB-T Lite"),
BROOKTREE_878_DEVICE(0x18ac, 0xdb11, "Ultraview DVB-T Lite"),
BROOKTREE_878_DEVICE(0x18ac, 0xd500, "DViCO FusionHDTV 5 Lite"),
BROOKTREE_878_DEVICE(0x7063, 0x2000, "pcHDTV HD-2000 TV"),
BROOKTREE_878_DEVICE(0x1822, 0x0026, "DNTV Live! Mini"),
{ }
};
MODULE_DEVICE_TABLE(pci, bt878_pci_tbl);
static const char * card_name(const struct pci_device_id *id)
{
return id->driver_data ? (const char *)id->driver_data : "Unknown";
}
/***********************/
/* PCI device handling */
/***********************/
static int bt878_probe(struct pci_dev *dev, const struct pci_device_id *pci_id)
{
int result = 0;
unsigned char lat;
struct bt878 *bt;
#if defined(__powerpc__)
unsigned int cmd;
#endif
unsigned int cardid;
printk(KERN_INFO "bt878: Bt878 AUDIO function found (%d).\n",
bt878_num);
if (bt878_num >= BT878_MAX) {
printk(KERN_ERR "bt878: Too many devices inserted\n");
result = -ENOMEM;
goto fail0;
}
if (pci_enable_device(dev))
return -EIO;
cardid = dev->subsystem_device << 16;
cardid |= dev->subsystem_vendor;
printk(KERN_INFO "%s: card id=[0x%x],[ %s ] has DVB functions.\n",
__func__, cardid, card_name(pci_id));
bt = &bt878[bt878_num];
bt->dev = dev;
bt->nr = bt878_num;
bt->shutdown = 0;
bt->id = dev->device;
bt->irq = dev->irq;
bt->bt878_adr = pci_resource_start(dev, 0);
if (!request_mem_region(pci_resource_start(dev, 0),
pci_resource_len(dev, 0), "bt878")) {
result = -EBUSY;
goto fail0;
}
bt->revision = dev->revision;
pci_read_config_byte(dev, PCI_LATENCY_TIMER, &lat);
printk(KERN_INFO "bt878(%d): Bt%x (rev %d) at %02x:%02x.%x, ",
bt878_num, bt->id, bt->revision, dev->bus->number,
PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn));
printk("irq: %d, latency: %d, memory: 0x%lx\n",
bt->irq, lat, bt->bt878_adr);
#if defined(__powerpc__)
/* on OpenFirmware machines (PowerMac at least), PCI memory cycle */
/* response on cards with no firmware is not enabled by OF */
pci_read_config_dword(dev, PCI_COMMAND, &cmd);
cmd = (cmd | PCI_COMMAND_MEMORY);
pci_write_config_dword(dev, PCI_COMMAND, cmd);
#endif
#ifdef __sparc__
bt->bt878_mem = (unsigned char *) bt->bt878_adr;
#else
bt->bt878_mem = ioremap(bt->bt878_adr, 0x1000);
#endif
/* clear interrupt mask */
btwrite(0, BT848_INT_MASK);
result = request_irq(bt->irq, bt878_irq,
IRQF_SHARED, "bt878", (void *) bt);
if (result == -EINVAL) {
printk(KERN_ERR "bt878(%d): Bad irq number or handler\n",
bt878_num);
goto fail1;
}
if (result == -EBUSY) {
printk(KERN_ERR
"bt878(%d): IRQ %d busy, change your PnP config in BIOS\n",
bt878_num, bt->irq);
goto fail1;
}
if (result < 0)
goto fail1;
pci_set_master(dev);
pci_set_drvdata(dev, bt);
if ((result = bt878_mem_alloc(bt))) {
printk(KERN_ERR "bt878: failed to allocate memory!\n");
goto fail2;
}
bt878_make_risc(bt);
btwrite(0, BT878_AINT_MASK);
bt878_num++;
return 0;
fail2:
free_irq(bt->irq, bt);
fail1:
release_mem_region(pci_resource_start(bt->dev, 0),
pci_resource_len(bt->dev, 0));
fail0:
pci_disable_device(dev);
return result;
}
static void bt878_remove(struct pci_dev *pci_dev)
{
u8 command;
struct bt878 *bt = pci_get_drvdata(pci_dev);
if (bt878_verbose)
printk(KERN_INFO "bt878(%d): unloading\n", bt->nr);
/* turn off all capturing, DMA and IRQs */
btand(~0x13, BT878_AGPIO_DMA_CTL);
/* first disable interrupts before unmapping the memory! */
btwrite(0, BT878_AINT_MASK);
btwrite(~0U, BT878_AINT_STAT);
/* disable PCI bus-mastering */
pci_read_config_byte(bt->dev, PCI_COMMAND, &command);
/* Should this be &=~ ?? */
command &= ~PCI_COMMAND_MASTER;
pci_write_config_byte(bt->dev, PCI_COMMAND, command);
free_irq(bt->irq, bt);
printk(KERN_DEBUG "bt878_mem: 0x%p.\n", bt->bt878_mem);
if (bt->bt878_mem)
iounmap(bt->bt878_mem);
release_mem_region(pci_resource_start(bt->dev, 0),
pci_resource_len(bt->dev, 0));
/* wake up any waiting processes
because shutdown flag is set, no new processes (in this queue)
are expected
*/
bt->shutdown = 1;
bt878_mem_free(bt);
pci_disable_device(pci_dev);
return;
}
static struct pci_driver bt878_pci_driver = {
.name = "bt878",
.id_table = bt878_pci_tbl,
.probe = bt878_probe,
.remove = bt878_remove,
};
/*******************************/
/* Module management functions */
/*******************************/
static int __init bt878_init_module(void)
{
bt878_num = 0;
printk(KERN_INFO "bt878: AUDIO driver version %d.%d.%d loaded\n",
(BT878_VERSION_CODE >> 16) & 0xff,
(BT878_VERSION_CODE >> 8) & 0xff,
BT878_VERSION_CODE & 0xff);
return pci_register_driver(&bt878_pci_driver);
}
static void __exit bt878_cleanup_module(void)
{
pci_unregister_driver(&bt878_pci_driver);
}
module_init(bt878_init_module);
module_exit(bt878_cleanup_module);
MODULE_LICENSE("GPL");
/*
* Local variables:
* c-basic-offset: 8
* End:
*/

View file

@ -0,0 +1,159 @@
/*
bt878.h - Bt878 audio module (register offsets)
Copyright (C) 2002 Peter Hettkamp <peter.hettkamp@htp-tel.de>
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 _BT878_H_
#define _BT878_H_
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/sched.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include "bt848.h"
#include "bttv.h"
#define BT878_VERSION_CODE 0x000000
#define BT878_AINT_STAT 0x100
#define BT878_ARISCS (0xf<<28)
#define BT878_ARISC_EN (1<<27)
#define BT878_ASCERR (1<<19)
#define BT878_AOCERR (1<<18)
#define BT878_APABORT (1<<17)
#define BT878_ARIPERR (1<<16)
#define BT878_APPERR (1<<15)
#define BT878_AFDSR (1<<14)
#define BT878_AFTRGT (1<<13)
#define BT878_AFBUS (1<<12)
#define BT878_ARISCI (1<<11)
#define BT878_AOFLOW (1<<3)
#define BT878_AINT_MASK 0x104
#define BT878_AGPIO_DMA_CTL 0x10c
#define BT878_A_GAIN (0xf<<28)
#define BT878_A_G2X (1<<27)
#define BT878_A_PWRDN (1<<26)
#define BT878_A_SEL (3<<24)
#define BT878_DA_SCE (1<<23)
#define BT878_DA_LRI (1<<22)
#define BT878_DA_MLB (1<<21)
#define BT878_DA_LRD (0x1f<<16)
#define BT878_DA_DPM (1<<15)
#define BT878_DA_SBR (1<<14)
#define BT878_DA_ES2 (1<<13)
#define BT878_DA_LMT (1<<12)
#define BT878_DA_SDR (0xf<<8)
#define BT878_DA_IOM (3<<6)
#define BT878_DA_APP (1<<5)
#define BT878_ACAP_EN (1<<4)
#define BT878_PKTP (3<<2)
#define BT878_RISC_EN (1<<1)
#define BT878_FIFO_EN 1
#define BT878_APACK_LEN 0x110
#define BT878_AFP_LEN (0xff<<16)
#define BT878_ALP_LEN 0xfff
#define BT878_ARISC_START 0x114
#define BT878_ARISC_PC 0x120
/* BT878 FUNCTION 0 REGISTERS */
#define BT878_GPIO_DMA_CTL 0x10c
/* Interrupt register */
#define BT878_INT_STAT 0x100
#define BT878_INT_MASK 0x104
#define BT878_I2CRACK (1<<25)
#define BT878_I2CDONE (1<<8)
#define BT878_MAX 4
#define BT878_RISC_SYNC_MASK (1 << 15)
#define BTTV_BOARD_UNKNOWN 0x00
#define BTTV_BOARD_PINNACLESAT 0x5e
#define BTTV_BOARD_NEBULA_DIGITV 0x68
#define BTTV_BOARD_PC_HDTV 0x70
#define BTTV_BOARD_TWINHAN_DST 0x71
#define BTTV_BOARD_AVDVBT_771 0x7b
#define BTTV_BOARD_AVDVBT_761 0x7c
#define BTTV_BOARD_DVICO_DVBT_LITE 0x80
#define BTTV_BOARD_DVICO_FUSIONHDTV_5_LITE 0x87
extern int bt878_num;
struct bt878 {
struct mutex gpio_lock;
unsigned int nr;
unsigned int bttv_nr;
struct i2c_adapter *adapter;
struct pci_dev *dev;
unsigned int id;
unsigned int TS_Size;
unsigned char revision;
unsigned int irq;
unsigned long bt878_adr;
volatile void __iomem *bt878_mem; /* function 1 */
volatile u32 finished_block;
volatile u32 last_block;
u32 block_count;
u32 block_bytes;
u32 line_bytes;
u32 line_count;
u32 buf_size;
u8 *buf_cpu;
dma_addr_t buf_dma;
u32 risc_size;
__le32 *risc_cpu;
dma_addr_t risc_dma;
u32 risc_pos;
struct tasklet_struct tasklet;
int shutdown;
};
extern struct bt878 bt878[BT878_MAX];
void bt878_start(struct bt878 *bt, u32 controlreg, u32 op_sync_orin,
u32 irq_err_ignore);
void bt878_stop(struct bt878 *bt);
#if defined(__powerpc__) /* big-endian */
static inline void io_st_le32(volatile unsigned __iomem *addr, unsigned val)
{
st_le32(addr, val);
eieio();
}
#define bmtwrite(dat,adr) io_st_le32((adr),(dat))
#define bmtread(adr) ld_le32((adr))
#else
#define bmtwrite(dat,adr) writel((dat), (adr))
#define bmtread(adr) readl(adr)
#endif
#endif

View file

@ -0,0 +1,382 @@
/*
* Handlers for board audio hooks, splitted from bttv-cards
*
* Copyright (c) 2006 Mauro Carvalho Chehab (mchehab@infradead.org)
* This code is placed under the terms of the GNU General Public License
*/
#include "bttv-audio-hook.h"
#include <linux/delay.h>
/* ----------------------------------------------------------------------- */
/* winview */
void winview_volume(struct bttv *btv, __u16 volume)
{
/* PT2254A programming Jon Tombs, jon@gte.esi.us.es */
int bits_out, loops, vol, data;
/* 32 levels logarithmic */
vol = 32 - ((volume>>11));
/* units */
bits_out = (PT2254_DBS_IN_2>>(vol%5));
/* tens */
bits_out |= (PT2254_DBS_IN_10>>(vol/5));
bits_out |= PT2254_L_CHANNEL | PT2254_R_CHANNEL;
data = gpio_read();
data &= ~(WINVIEW_PT2254_CLK| WINVIEW_PT2254_DATA|
WINVIEW_PT2254_STROBE);
for (loops = 17; loops >= 0 ; loops--) {
if (bits_out & (1<<loops))
data |= WINVIEW_PT2254_DATA;
else
data &= ~WINVIEW_PT2254_DATA;
gpio_write(data);
udelay(5);
data |= WINVIEW_PT2254_CLK;
gpio_write(data);
udelay(5);
data &= ~WINVIEW_PT2254_CLK;
gpio_write(data);
}
data |= WINVIEW_PT2254_STROBE;
data &= ~WINVIEW_PT2254_DATA;
gpio_write(data);
udelay(10);
data &= ~WINVIEW_PT2254_STROBE;
gpio_write(data);
}
/* ----------------------------------------------------------------------- */
/* mono/stereo control for various cards (which don't use i2c chips but */
/* connect something to the GPIO pins */
void gvbctv3pci_audio(struct bttv *btv, struct v4l2_tuner *t, int set)
{
unsigned int con = 0;
if (set) {
gpio_inout(0x300, 0x300);
if (t->audmode & V4L2_TUNER_MODE_LANG1)
con = 0x000;
if (t->audmode & V4L2_TUNER_MODE_LANG2)
con = 0x300;
if (t->audmode & V4L2_TUNER_MODE_STEREO)
con = 0x200;
/* if (t->audmode & V4L2_TUNER_MODE_MONO)
* con = 0x100; */
gpio_bits(0x300, con);
} else {
t->audmode = V4L2_TUNER_MODE_STEREO |
V4L2_TUNER_MODE_LANG1 | V4L2_TUNER_MODE_LANG2;
}
}
void gvbctv5pci_audio(struct bttv *btv, struct v4l2_tuner *t, int set)
{
unsigned int val, con;
if (btv->radio_user)
return;
val = gpio_read();
if (set) {
con = 0x000;
if (t->audmode & V4L2_TUNER_MODE_LANG2) {
if (t->audmode & V4L2_TUNER_MODE_LANG1) {
/* LANG1 + LANG2 */
con = 0x100;
}
else {
/* LANG2 */
con = 0x300;
}
}
if (con != (val & 0x300)) {
gpio_bits(0x300, con);
if (bttv_gpio)
bttv_gpio_tracking(btv,"gvbctv5pci");
}
} else {
switch (val & 0x70) {
case 0x10:
t->rxsubchans = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2;
break;
case 0x30:
t->rxsubchans = V4L2_TUNER_SUB_LANG2;
break;
case 0x50:
t->rxsubchans = V4L2_TUNER_SUB_LANG1;
break;
case 0x60:
t->rxsubchans = V4L2_TUNER_SUB_STEREO;
break;
case 0x70:
t->rxsubchans = V4L2_TUNER_SUB_MONO;
break;
default:
t->rxsubchans = V4L2_TUNER_SUB_MONO |
V4L2_TUNER_SUB_STEREO |
V4L2_TUNER_SUB_LANG1 |
V4L2_TUNER_SUB_LANG2;
}
t->audmode = V4L2_TUNER_MODE_STEREO |
V4L2_TUNER_MODE_LANG1 | V4L2_TUNER_MODE_LANG2;
}
}
/*
* Mario Medina Nussbaum <medisoft@alohabbs.org.mx>
* I discover that on BT848_GPIO_DATA address a byte 0xcce enable stereo,
* 0xdde enables mono and 0xccd enables sap
*
* Petr Vandrovec <VANDROVE@vc.cvut.cz>
* P.S.: At least mask in line above is wrong - GPIO pins 3,2 select
* input/output sound connection, so both must be set for output mode.
*
* Looks like it's needed only for the "tvphone", the "tvphone 98"
* handles this with a tda9840
*
*/
void avermedia_tvphone_audio(struct bttv *btv, struct v4l2_tuner *t, int set)
{
int val = 0;
if (set) {
if (t->audmode & V4L2_TUNER_MODE_LANG2) /* SAP */
val = 0x02;
if (t->audmode & V4L2_TUNER_MODE_STEREO)
val = 0x01;
if (val) {
gpio_bits(0x03,val);
if (bttv_gpio)
bttv_gpio_tracking(btv,"avermedia");
}
} else {
t->audmode = V4L2_TUNER_MODE_MONO | V4L2_TUNER_MODE_STEREO |
V4L2_TUNER_MODE_LANG1;
return;
}
}
void avermedia_tv_stereo_audio(struct bttv *btv, struct v4l2_tuner *t, int set)
{
int val = 0;
if (set) {
if (t->audmode & V4L2_TUNER_MODE_LANG2) /* SAP */
val = 0x01;
if (t->audmode & V4L2_TUNER_MODE_STEREO) /* STEREO */
val = 0x02;
btaor(val, ~0x03, BT848_GPIO_DATA);
if (bttv_gpio)
bttv_gpio_tracking(btv,"avermedia");
} else {
t->audmode = V4L2_TUNER_MODE_MONO | V4L2_TUNER_MODE_STEREO |
V4L2_TUNER_MODE_LANG1 | V4L2_TUNER_MODE_LANG2;
return;
}
}
/* Lifetec 9415 handling */
void lt9415_audio(struct bttv *btv, struct v4l2_tuner *t, int set)
{
int val = 0;
if (gpio_read() & 0x4000) {
t->audmode = V4L2_TUNER_MODE_MONO;
return;
}
if (set) {
if (t->audmode & V4L2_TUNER_MODE_LANG2) /* A2 SAP */
val = 0x0080;
if (t->audmode & V4L2_TUNER_MODE_STEREO) /* A2 stereo */
val = 0x0880;
if ((t->audmode & V4L2_TUNER_MODE_LANG1) ||
(t->audmode & V4L2_TUNER_MODE_MONO))
val = 0;
gpio_bits(0x0880, val);
if (bttv_gpio)
bttv_gpio_tracking(btv,"lt9415");
} else {
/* autodetect doesn't work with this card :-( */
t->audmode = V4L2_TUNER_MODE_MONO | V4L2_TUNER_MODE_STEREO |
V4L2_TUNER_MODE_LANG1 | V4L2_TUNER_MODE_LANG2;
return;
}
}
/* TDA9821 on TerraTV+ Bt848, Bt878 */
void terratv_audio(struct bttv *btv, struct v4l2_tuner *t, int set)
{
unsigned int con = 0;
if (set) {
gpio_inout(0x180000,0x180000);
if (t->audmode & V4L2_TUNER_MODE_LANG2)
con = 0x080000;
if (t->audmode & V4L2_TUNER_MODE_STEREO)
con = 0x180000;
gpio_bits(0x180000, con);
if (bttv_gpio)
bttv_gpio_tracking(btv,"terratv");
} else {
t->audmode = V4L2_TUNER_MODE_MONO | V4L2_TUNER_MODE_STEREO |
V4L2_TUNER_MODE_LANG1 | V4L2_TUNER_MODE_LANG2;
}
}
void winfast2000_audio(struct bttv *btv, struct v4l2_tuner *t, int set)
{
unsigned long val = 0;
if (set) {
/*btor (0xc32000, BT848_GPIO_OUT_EN);*/
if (t->audmode & V4L2_TUNER_MODE_MONO) /* Mono */
val = 0x420000;
if (t->audmode & V4L2_TUNER_MODE_LANG1) /* Mono */
val = 0x420000;
if (t->audmode & V4L2_TUNER_MODE_LANG2) /* SAP */
val = 0x410000;
if (t->audmode & V4L2_TUNER_MODE_STEREO) /* Stereo */
val = 0x020000;
if (val) {
gpio_bits(0x430000, val);
if (bttv_gpio)
bttv_gpio_tracking(btv,"winfast2000");
}
} else {
t->audmode = V4L2_TUNER_MODE_MONO | V4L2_TUNER_MODE_STEREO |
V4L2_TUNER_MODE_LANG1 | V4L2_TUNER_MODE_LANG2;
}
}
/*
* Dariusz Kowalewski <darekk@automex.pl>
* sound control for Prolink PV-BT878P+9B (PixelView PlayTV Pro FM+NICAM
* revision 9B has on-board TDA9874A sound decoder).
*
* Note: There are card variants without tda9874a. Forcing the "stereo sound route"
* will mute this cards.
*/
void pvbt878p9b_audio(struct bttv *btv, struct v4l2_tuner *t, int set)
{
unsigned int val = 0;
if (btv->radio_user)
return;
if (set) {
if (t->audmode & V4L2_TUNER_MODE_MONO) {
val = 0x01;
}
if ((t->audmode & (V4L2_TUNER_MODE_LANG1 | V4L2_TUNER_MODE_LANG2))
|| (t->audmode & V4L2_TUNER_MODE_STEREO)) {
val = 0x02;
}
if (val) {
gpio_bits(0x03,val);
if (bttv_gpio)
bttv_gpio_tracking(btv,"pvbt878p9b");
}
} else {
t->audmode = V4L2_TUNER_MODE_MONO | V4L2_TUNER_MODE_STEREO |
V4L2_TUNER_MODE_LANG1 | V4L2_TUNER_MODE_LANG2;
}
}
/*
* Dariusz Kowalewski <darekk@automex.pl>
* sound control for FlyVideo 2000S (with tda9874 decoder)
* based on pvbt878p9b_audio() - this is not tested, please fix!!!
*/
void fv2000s_audio(struct bttv *btv, struct v4l2_tuner *t, int set)
{
unsigned int val = 0xffff;
if (btv->radio_user)
return;
if (set) {
if (t->audmode & V4L2_TUNER_MODE_MONO) {
val = 0x0000;
}
if ((t->audmode & (V4L2_TUNER_MODE_LANG1 | V4L2_TUNER_MODE_LANG2))
|| (t->audmode & V4L2_TUNER_MODE_STEREO)) {
val = 0x1080; /*-dk-???: 0x0880, 0x0080, 0x1800 ... */
}
if (val != 0xffff) {
gpio_bits(0x1800, val);
if (bttv_gpio)
bttv_gpio_tracking(btv,"fv2000s");
}
} else {
t->audmode = V4L2_TUNER_MODE_MONO | V4L2_TUNER_MODE_STEREO |
V4L2_TUNER_MODE_LANG1 | V4L2_TUNER_MODE_LANG2;
}
}
/*
* sound control for Canopus WinDVR PCI
* Masaki Suzuki <masaki@btree.org>
*/
void windvr_audio(struct bttv *btv, struct v4l2_tuner *t, int set)
{
unsigned long val = 0;
if (set) {
if (t->audmode & V4L2_TUNER_MODE_MONO)
val = 0x040000;
if (t->audmode & V4L2_TUNER_MODE_LANG1)
val = 0;
if (t->audmode & V4L2_TUNER_MODE_LANG2)
val = 0x100000;
if (t->audmode & V4L2_TUNER_MODE_STEREO)
val = 0;
if (val) {
gpio_bits(0x140000, val);
if (bttv_gpio)
bttv_gpio_tracking(btv,"windvr");
}
} else {
t->audmode = V4L2_TUNER_MODE_MONO | V4L2_TUNER_MODE_STEREO |
V4L2_TUNER_MODE_LANG1 | V4L2_TUNER_MODE_LANG2;
}
}
/*
* sound control for AD-TVK503
* Hiroshi Takekawa <sian@big.or.jp>
*/
void adtvk503_audio(struct bttv *btv, struct v4l2_tuner *t, int set)
{
unsigned int con = 0xffffff;
/* btaor(0x1e0000, ~0x1e0000, BT848_GPIO_OUT_EN); */
if (set) {
/* btor(***, BT848_GPIO_OUT_EN); */
if (t->audmode & V4L2_TUNER_MODE_LANG1)
con = 0x00000000;
if (t->audmode & V4L2_TUNER_MODE_LANG2)
con = 0x00180000;
if (t->audmode & V4L2_TUNER_MODE_STEREO)
con = 0x00000000;
if (t->audmode & V4L2_TUNER_MODE_MONO)
con = 0x00060000;
if (con != 0xffffff) {
gpio_bits(0x1e0000,con);
if (bttv_gpio)
bttv_gpio_tracking(btv, "adtvk503");
}
} else {
t->audmode = V4L2_TUNER_MODE_MONO | V4L2_TUNER_MODE_STEREO |
V4L2_TUNER_MODE_LANG1 | V4L2_TUNER_MODE_LANG2;
}
}

View file

@ -0,0 +1,23 @@
/*
* Handlers for board audio hooks, splitted from bttv-cards
*
* Copyright (c) 2006 Mauro Carvalho Chehab (mchehab@infradead.org)
* This code is placed under the terms of the GNU General Public License
*/
#include "bttvp.h"
void winview_volume (struct bttv *btv, __u16 volume);
void lt9415_audio(struct bttv *btv, struct v4l2_tuner *tuner, int set);
void avermedia_tvphone_audio(struct bttv *btv, struct v4l2_tuner *tuner, int set);
void avermedia_tv_stereo_audio(struct bttv *btv, struct v4l2_tuner *tuner, int set);
void terratv_audio(struct bttv *btv, struct v4l2_tuner *tuner, int set);
void gvbctv3pci_audio(struct bttv *btv, struct v4l2_tuner *tuner, int set);
void gvbctv5pci_audio(struct bttv *btv, struct v4l2_tuner *tuner, int set);
void winfast2000_audio(struct bttv *btv, struct v4l2_tuner *tuner, int set);
void pvbt878p9b_audio(struct bttv *btv, struct v4l2_tuner *tuner, int set);
void fv2000s_audio(struct bttv *btv, struct v4l2_tuner *tuner, int set);
void windvr_audio(struct bttv *btv, struct v4l2_tuner *tuner, int set);
void adtvk503_audio(struct bttv *btv, struct v4l2_tuner *tuner, int set);

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,189 @@
/*
bttv-gpio.c -- gpio sub drivers
sysfs-based sub driver interface for bttv
mainly intended for gpio access
Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de)
& Marcus Metzler (mocm@thp.uni-koeln.de)
(c) 1999-2003 Gerd Knorr <kraxel@bytesex.org>
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.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <asm/io.h>
#include "bttvp.h"
/* ----------------------------------------------------------------------- */
/* internal: the bttv "bus" */
static int bttv_sub_bus_match(struct device *dev, struct device_driver *drv)
{
struct bttv_sub_driver *sub = to_bttv_sub_drv(drv);
int len = strlen(sub->wanted);
if (0 == strncmp(dev_name(dev), sub->wanted, len))
return 1;
return 0;
}
static int bttv_sub_probe(struct device *dev)
{
struct bttv_sub_device *sdev = to_bttv_sub_dev(dev);
struct bttv_sub_driver *sub = to_bttv_sub_drv(dev->driver);
return sub->probe ? sub->probe(sdev) : -ENODEV;
}
static int bttv_sub_remove(struct device *dev)
{
struct bttv_sub_device *sdev = to_bttv_sub_dev(dev);
struct bttv_sub_driver *sub = to_bttv_sub_drv(dev->driver);
if (sub->remove)
sub->remove(sdev);
return 0;
}
struct bus_type bttv_sub_bus_type = {
.name = "bttv-sub",
.match = &bttv_sub_bus_match,
.probe = bttv_sub_probe,
.remove = bttv_sub_remove,
};
static void release_sub_device(struct device *dev)
{
struct bttv_sub_device *sub = to_bttv_sub_dev(dev);
kfree(sub);
}
int bttv_sub_add_device(struct bttv_core *core, char *name)
{
struct bttv_sub_device *sub;
int err;
sub = kzalloc(sizeof(*sub),GFP_KERNEL);
if (NULL == sub)
return -ENOMEM;
sub->core = core;
sub->dev.parent = &core->pci->dev;
sub->dev.bus = &bttv_sub_bus_type;
sub->dev.release = release_sub_device;
dev_set_name(&sub->dev, "%s%d", name, core->nr);
err = device_register(&sub->dev);
if (0 != err) {
put_device(&sub->dev);
return err;
}
pr_info("%d: add subdevice \"%s\"\n", core->nr, dev_name(&sub->dev));
list_add_tail(&sub->list,&core->subs);
return 0;
}
int bttv_sub_del_devices(struct bttv_core *core)
{
struct bttv_sub_device *sub, *save;
list_for_each_entry_safe(sub, save, &core->subs, list) {
list_del(&sub->list);
device_unregister(&sub->dev);
}
return 0;
}
/* ----------------------------------------------------------------------- */
/* external: sub-driver register/unregister */
int bttv_sub_register(struct bttv_sub_driver *sub, char *wanted)
{
sub->drv.bus = &bttv_sub_bus_type;
snprintf(sub->wanted,sizeof(sub->wanted),"%s",wanted);
return driver_register(&sub->drv);
}
EXPORT_SYMBOL(bttv_sub_register);
int bttv_sub_unregister(struct bttv_sub_driver *sub)
{
driver_unregister(&sub->drv);
return 0;
}
EXPORT_SYMBOL(bttv_sub_unregister);
/* ----------------------------------------------------------------------- */
/* external: gpio access functions */
void bttv_gpio_inout(struct bttv_core *core, u32 mask, u32 outbits)
{
struct bttv *btv = container_of(core, struct bttv, c);
unsigned long flags;
u32 data;
spin_lock_irqsave(&btv->gpio_lock,flags);
data = btread(BT848_GPIO_OUT_EN);
data = data & ~mask;
data = data | (mask & outbits);
btwrite(data,BT848_GPIO_OUT_EN);
spin_unlock_irqrestore(&btv->gpio_lock,flags);
}
u32 bttv_gpio_read(struct bttv_core *core)
{
struct bttv *btv = container_of(core, struct bttv, c);
u32 value;
value = btread(BT848_GPIO_DATA);
return value;
}
void bttv_gpio_write(struct bttv_core *core, u32 value)
{
struct bttv *btv = container_of(core, struct bttv, c);
btwrite(value,BT848_GPIO_DATA);
}
void bttv_gpio_bits(struct bttv_core *core, u32 mask, u32 bits)
{
struct bttv *btv = container_of(core, struct bttv, c);
unsigned long flags;
u32 data;
spin_lock_irqsave(&btv->gpio_lock,flags);
data = btread(BT848_GPIO_DATA);
data = data & ~mask;
data = data | (mask & bits);
btwrite(data,BT848_GPIO_DATA);
spin_unlock_irqrestore(&btv->gpio_lock,flags);
}
/*
* Local variables:
* c-basic-offset: 8
* End:
*/

View file

@ -0,0 +1,404 @@
/*
bttv-i2c.c -- all the i2c code is here
bttv - Bt848 frame grabber driver
Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de)
& Marcus Metzler (mocm@thp.uni-koeln.de)
(c) 1999-2003 Gerd Knorr <kraxel@bytesex.org>
(c) 2005 Mauro Carvalho Chehab <mchehab@infradead.org>
- Multituner support and i2c address binding
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.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#include "bttvp.h"
#include <media/v4l2-common.h>
#include <linux/jiffies.h>
#include <asm/io.h>
static int i2c_debug;
static int i2c_hw;
static int i2c_scan;
module_param(i2c_debug, int, 0644);
MODULE_PARM_DESC(i2c_debug, "configure i2c debug level");
module_param(i2c_hw, int, 0444);
MODULE_PARM_DESC(i2c_hw,"force use of hardware i2c support, "
"instead of software bitbang");
module_param(i2c_scan, int, 0444);
MODULE_PARM_DESC(i2c_scan,"scan i2c bus at insmod time");
static unsigned int i2c_udelay = 5;
module_param(i2c_udelay, int, 0444);
MODULE_PARM_DESC(i2c_udelay,"soft i2c delay at insmod time, in usecs "
"(should be 5 or higher). Lower value means higher bus speed.");
/* ----------------------------------------------------------------------- */
/* I2C functions - bitbanging adapter (software i2c) */
static void bttv_bit_setscl(void *data, int state)
{
struct bttv *btv = (struct bttv*)data;
if (state)
btv->i2c_state |= 0x02;
else
btv->i2c_state &= ~0x02;
btwrite(btv->i2c_state, BT848_I2C);
btread(BT848_I2C);
}
static void bttv_bit_setsda(void *data, int state)
{
struct bttv *btv = (struct bttv*)data;
if (state)
btv->i2c_state |= 0x01;
else
btv->i2c_state &= ~0x01;
btwrite(btv->i2c_state, BT848_I2C);
btread(BT848_I2C);
}
static int bttv_bit_getscl(void *data)
{
struct bttv *btv = (struct bttv*)data;
int state;
state = btread(BT848_I2C) & 0x02 ? 1 : 0;
return state;
}
static int bttv_bit_getsda(void *data)
{
struct bttv *btv = (struct bttv*)data;
int state;
state = btread(BT848_I2C) & 0x01;
return state;
}
static struct i2c_algo_bit_data bttv_i2c_algo_bit_template = {
.setsda = bttv_bit_setsda,
.setscl = bttv_bit_setscl,
.getsda = bttv_bit_getsda,
.getscl = bttv_bit_getscl,
.udelay = 16,
.timeout = 200,
};
/* ----------------------------------------------------------------------- */
/* I2C functions - hardware i2c */
static u32 functionality(struct i2c_adapter *adap)
{
return I2C_FUNC_SMBUS_EMUL;
}
static int
bttv_i2c_wait_done(struct bttv *btv)
{
int rc = 0;
/* timeout */
if (wait_event_interruptible_timeout(btv->i2c_queue,
btv->i2c_done, msecs_to_jiffies(85)) == -ERESTARTSYS)
rc = -EIO;
if (btv->i2c_done & BT848_INT_RACK)
rc = 1;
btv->i2c_done = 0;
return rc;
}
#define I2C_HW (BT878_I2C_MODE | BT848_I2C_SYNC |\
BT848_I2C_SCL | BT848_I2C_SDA)
static int
bttv_i2c_sendbytes(struct bttv *btv, const struct i2c_msg *msg, int last)
{
u32 xmit;
int retval,cnt;
/* sanity checks */
if (0 == msg->len)
return -EINVAL;
/* start, address + first byte */
xmit = (msg->addr << 25) | (msg->buf[0] << 16) | I2C_HW;
if (msg->len > 1 || !last)
xmit |= BT878_I2C_NOSTOP;
btwrite(xmit, BT848_I2C);
retval = bttv_i2c_wait_done(btv);
if (retval < 0)
goto err;
if (retval == 0)
goto eio;
if (i2c_debug) {
pr_cont(" <W %02x %02x", msg->addr << 1, msg->buf[0]);
}
for (cnt = 1; cnt < msg->len; cnt++ ) {
/* following bytes */
xmit = (msg->buf[cnt] << 24) | I2C_HW | BT878_I2C_NOSTART;
if (cnt < msg->len-1 || !last)
xmit |= BT878_I2C_NOSTOP;
btwrite(xmit, BT848_I2C);
retval = bttv_i2c_wait_done(btv);
if (retval < 0)
goto err;
if (retval == 0)
goto eio;
if (i2c_debug)
pr_cont(" %02x", msg->buf[cnt]);
}
if (i2c_debug && !(xmit & BT878_I2C_NOSTOP))
pr_cont(">\n");
return msg->len;
eio:
retval = -EIO;
err:
if (i2c_debug)
pr_cont(" ERR: %d\n",retval);
return retval;
}
static int
bttv_i2c_readbytes(struct bttv *btv, const struct i2c_msg *msg, int last)
{
u32 xmit;
u32 cnt;
int retval;
for (cnt = 0; cnt < msg->len; cnt++) {
xmit = (msg->addr << 25) | (1 << 24) | I2C_HW;
if (cnt < msg->len-1)
xmit |= BT848_I2C_W3B;
if (cnt < msg->len-1 || !last)
xmit |= BT878_I2C_NOSTOP;
if (cnt)
xmit |= BT878_I2C_NOSTART;
if (i2c_debug) {
if (!(xmit & BT878_I2C_NOSTART))
pr_cont(" <R %02x", (msg->addr << 1) +1);
}
btwrite(xmit, BT848_I2C);
retval = bttv_i2c_wait_done(btv);
if (retval < 0)
goto err;
if (retval == 0)
goto eio;
msg->buf[cnt] = ((u32)btread(BT848_I2C) >> 8) & 0xff;
if (i2c_debug) {
pr_cont(" =%02x", msg->buf[cnt]);
}
if (i2c_debug && !(xmit & BT878_I2C_NOSTOP))
pr_cont(" >\n");
}
return msg->len;
eio:
retval = -EIO;
err:
if (i2c_debug)
pr_cont(" ERR: %d\n",retval);
return retval;
}
static int bttv_i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg *msgs, int num)
{
struct v4l2_device *v4l2_dev = i2c_get_adapdata(i2c_adap);
struct bttv *btv = to_bttv(v4l2_dev);
int retval = 0;
int i;
if (i2c_debug)
pr_debug("bt-i2c:");
btwrite(BT848_INT_I2CDONE|BT848_INT_RACK, BT848_INT_STAT);
for (i = 0 ; i < num; i++) {
if (msgs[i].flags & I2C_M_RD) {
/* read */
retval = bttv_i2c_readbytes(btv, &msgs[i], i+1 == num);
if (retval < 0)
goto err;
} else {
/* write */
retval = bttv_i2c_sendbytes(btv, &msgs[i], i+1 == num);
if (retval < 0)
goto err;
}
}
return num;
err:
return retval;
}
static const struct i2c_algorithm bttv_algo = {
.master_xfer = bttv_i2c_xfer,
.functionality = functionality,
};
/* ----------------------------------------------------------------------- */
/* I2C functions - common stuff */
/* read I2C */
int bttv_I2CRead(struct bttv *btv, unsigned char addr, char *probe_for)
{
unsigned char buffer = 0;
if (0 != btv->i2c_rc)
return -1;
if (bttv_verbose && NULL != probe_for)
pr_info("%d: i2c: checking for %s @ 0x%02x... ",
btv->c.nr, probe_for, addr);
btv->i2c_client.addr = addr >> 1;
if (1 != i2c_master_recv(&btv->i2c_client, &buffer, 1)) {
if (NULL != probe_for) {
if (bttv_verbose)
pr_cont("not found\n");
} else
pr_warn("%d: i2c read 0x%x: error\n",
btv->c.nr, addr);
return -1;
}
if (bttv_verbose && NULL != probe_for)
pr_cont("found\n");
return buffer;
}
/* write I2C */
int bttv_I2CWrite(struct bttv *btv, unsigned char addr, unsigned char b1,
unsigned char b2, int both)
{
unsigned char buffer[2];
int bytes = both ? 2 : 1;
if (0 != btv->i2c_rc)
return -1;
btv->i2c_client.addr = addr >> 1;
buffer[0] = b1;
buffer[1] = b2;
if (bytes != i2c_master_send(&btv->i2c_client, buffer, bytes))
return -1;
return 0;
}
/* read EEPROM content */
void bttv_readee(struct bttv *btv, unsigned char *eedata, int addr)
{
memset(eedata, 0, 256);
if (0 != btv->i2c_rc)
return;
btv->i2c_client.addr = addr >> 1;
tveeprom_read(&btv->i2c_client, eedata, 256);
}
static char *i2c_devs[128] = {
[ 0x1c >> 1 ] = "lgdt330x",
[ 0x30 >> 1 ] = "IR (hauppauge)",
[ 0x80 >> 1 ] = "msp34xx",
[ 0x86 >> 1 ] = "tda9887",
[ 0xa0 >> 1 ] = "eeprom",
[ 0xc0 >> 1 ] = "tuner (analog)",
[ 0xc2 >> 1 ] = "tuner (analog)",
};
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,0);
if (rc < 0)
continue;
pr_info("%s: i2c scan: found device @ 0x%x [%s]\n",
name, i << 1, i2c_devs[i] ? i2c_devs[i] : "???");
}
}
/* init + register i2c adapter */
int init_bttv_i2c(struct bttv *btv)
{
strlcpy(btv->i2c_client.name, "bttv internal", I2C_NAME_SIZE);
if (i2c_hw)
btv->use_i2c_hw = 1;
if (btv->use_i2c_hw) {
/* bt878 */
strlcpy(btv->c.i2c_adap.name, "bt878",
sizeof(btv->c.i2c_adap.name));
btv->c.i2c_adap.algo = &bttv_algo;
} else {
/* bt848 */
/* Prevents usage of invalid delay values */
if (i2c_udelay<5)
i2c_udelay=5;
strlcpy(btv->c.i2c_adap.name, "bttv",
sizeof(btv->c.i2c_adap.name));
btv->i2c_algo = bttv_i2c_algo_bit_template;
btv->i2c_algo.udelay = i2c_udelay;
btv->i2c_algo.data = btv;
btv->c.i2c_adap.algo_data = &btv->i2c_algo;
}
btv->c.i2c_adap.owner = THIS_MODULE;
btv->c.i2c_adap.dev.parent = &btv->c.pci->dev;
snprintf(btv->c.i2c_adap.name, sizeof(btv->c.i2c_adap.name),
"bt%d #%d [%s]", btv->id, btv->c.nr,
btv->use_i2c_hw ? "hw" : "sw");
i2c_set_adapdata(&btv->c.i2c_adap, &btv->c.v4l2_dev);
btv->i2c_client.adapter = &btv->c.i2c_adap;
if (btv->use_i2c_hw) {
btv->i2c_rc = i2c_add_adapter(&btv->c.i2c_adap);
} else {
bttv_bit_setscl(btv,1);
bttv_bit_setsda(btv,1);
btv->i2c_rc = i2c_bit_add_bus(&btv->c.i2c_adap);
}
if (0 == btv->i2c_rc && i2c_scan)
do_i2c_scan(btv->c.v4l2_dev.name, &btv->i2c_client);
return btv->i2c_rc;
}
int fini_bttv_i2c(struct bttv *btv)
{
if (btv->i2c_rc == 0)
i2c_del_adapter(&btv->c.i2c_adap);
return 0;
}

View file

@ -0,0 +1,121 @@
/*
bttv-if.c -- old gpio interface to other kernel modules
don't use in new code, will go away in 2.7
have a look at bttv-gpio.c instead.
bttv - Bt848 frame grabber driver
Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de)
& Marcus Metzler (mocm@thp.uni-koeln.de)
(c) 1999-2003 Gerd Knorr <kraxel@bytesex.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/io.h>
#include "bttvp.h"
EXPORT_SYMBOL(bttv_get_pcidev);
EXPORT_SYMBOL(bttv_gpio_enable);
EXPORT_SYMBOL(bttv_read_gpio);
EXPORT_SYMBOL(bttv_write_gpio);
/* ----------------------------------------------------------------------- */
/* Exported functions - for other modules which want to access the */
/* gpio ports (IR for example) */
/* see bttv.h for comments */
struct pci_dev* bttv_get_pcidev(unsigned int card)
{
if (card >= bttv_num)
return NULL;
if (!bttvs[card])
return NULL;
return bttvs[card]->c.pci;
}
int bttv_gpio_enable(unsigned int card, unsigned long mask, unsigned long data)
{
struct bttv *btv;
if (card >= bttv_num) {
return -EINVAL;
}
btv = bttvs[card];
if (!btv)
return -ENODEV;
gpio_inout(mask,data);
if (bttv_gpio)
bttv_gpio_tracking(btv,"extern enable");
return 0;
}
int bttv_read_gpio(unsigned int card, unsigned long *data)
{
struct bttv *btv;
if (card >= bttv_num) {
return -EINVAL;
}
btv = bttvs[card];
if (!btv)
return -ENODEV;
if(btv->shutdown) {
return -ENODEV;
}
/* prior setting BT848_GPIO_REG_INP is (probably) not needed
because we set direct input on init */
*data = gpio_read();
return 0;
}
int bttv_write_gpio(unsigned int card, unsigned long mask, unsigned long data)
{
struct bttv *btv;
if (card >= bttv_num) {
return -EINVAL;
}
btv = bttvs[card];
if (!btv)
return -ENODEV;
/* prior setting BT848_GPIO_REG_INP is (probably) not needed
because direct input is set on init */
gpio_bits(mask,data);
if (bttv_gpio)
bttv_gpio_tracking(btv,"extern write");
return 0;
}
/*
* Local variables:
* c-basic-offset: 8
* End:
*/

View file

@ -0,0 +1,590 @@
/*
*
* Copyright (c) 2003 Gerd Knorr
* Copyright (c) 2003 Pavel Machek
*
* 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
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/slab.h>
#include "bttv.h"
#include "bttvp.h"
static int ir_debug;
module_param(ir_debug, int, 0644);
static int ir_rc5_remote_gap = 885;
module_param(ir_rc5_remote_gap, int, 0644);
#undef dprintk
#define dprintk(fmt, ...) \
do { \
if (ir_debug >= 1) \
pr_info(fmt, ##__VA_ARGS__); \
} while (0)
#define DEVNAME "bttv-input"
#define MODULE_NAME "bttv"
/* ---------------------------------------------------------------------- */
static void ir_handle_key(struct bttv *btv)
{
struct bttv_ir *ir = btv->remote;
u32 gpio,data;
/* read gpio value */
gpio = bttv_gpio_read(&btv->c);
if (ir->polling) {
if (ir->last_gpio == gpio)
return;
ir->last_gpio = gpio;
}
/* extract data */
data = ir_extract_bits(gpio, ir->mask_keycode);
dprintk("irq gpio=0x%x code=%d | %s%s%s\n",
gpio, data,
ir->polling ? "poll" : "irq",
(gpio & ir->mask_keydown) ? " down" : "",
(gpio & ir->mask_keyup) ? " up" : "");
if ((ir->mask_keydown && (gpio & ir->mask_keydown)) ||
(ir->mask_keyup && !(gpio & ir->mask_keyup))) {
rc_keydown_notimeout(ir->dev, RC_TYPE_UNKNOWN, data, 0);
} else {
/* HACK: Probably, ir->mask_keydown is missing
for this board */
if (btv->c.type == BTTV_BOARD_WINFAST2000)
rc_keydown_notimeout(ir->dev, RC_TYPE_UNKNOWN, data, 0);
rc_keyup(ir->dev);
}
}
static void ir_enltv_handle_key(struct bttv *btv)
{
struct bttv_ir *ir = btv->remote;
u32 gpio, data, keyup;
/* read gpio value */
gpio = bttv_gpio_read(&btv->c);
/* extract data */
data = ir_extract_bits(gpio, ir->mask_keycode);
/* Check if it is keyup */
keyup = (gpio & ir->mask_keyup) ? 1 << 31 : 0;
if ((ir->last_gpio & 0x7f) != data) {
dprintk("gpio=0x%x code=%d | %s\n",
gpio, data,
(gpio & ir->mask_keyup) ? " up" : "up/down");
rc_keydown_notimeout(ir->dev, RC_TYPE_UNKNOWN, data, 0);
if (keyup)
rc_keyup(ir->dev);
} else {
if ((ir->last_gpio & 1 << 31) == keyup)
return;
dprintk("(cnt) gpio=0x%x code=%d | %s\n",
gpio, data,
(gpio & ir->mask_keyup) ? " up" : "down");
if (keyup)
rc_keyup(ir->dev);
else
rc_keydown_notimeout(ir->dev, RC_TYPE_UNKNOWN, data, 0);
}
ir->last_gpio = data | keyup;
}
static int bttv_rc5_irq(struct bttv *btv);
void bttv_input_irq(struct bttv *btv)
{
struct bttv_ir *ir = btv->remote;
if (ir->rc5_gpio)
bttv_rc5_irq(btv);
else if (!ir->polling)
ir_handle_key(btv);
}
static void bttv_input_timer(unsigned long data)
{
struct bttv *btv = (struct bttv*)data;
struct bttv_ir *ir = btv->remote;
if (btv->c.type == BTTV_BOARD_ENLTV_FM_2)
ir_enltv_handle_key(btv);
else
ir_handle_key(btv);
mod_timer(&ir->timer, jiffies + msecs_to_jiffies(ir->polling));
}
/*
* FIXME: Nebula digi uses the legacy way to decode RC5, instead of relying
* on the rc-core way. As we need to be sure that both IRQ transitions are
* properly triggered, Better to touch it only with this hardware for
* testing.
*/
#define RC5_START(x) (((x) >> 12) & 0x03)
#define RC5_TOGGLE(x) (((x) >> 11) & 0x01)
#define RC5_ADDR(x) (((x) >> 6) & 0x1f)
#define RC5_INSTR(x) (((x) >> 0) & 0x3f)
/* decode raw bit pattern to RC5 code */
static u32 bttv_rc5_decode(unsigned int code)
{
unsigned int org_code = code;
unsigned int pair;
unsigned int rc5 = 0;
int i;
for (i = 0; i < 14; ++i) {
pair = code & 0x3;
code >>= 2;
rc5 <<= 1;
switch (pair) {
case 0:
case 2:
break;
case 1:
rc5 |= 1;
break;
case 3:
dprintk("rc5_decode(%x) bad code\n",
org_code);
return 0;
}
}
dprintk("code=%x, rc5=%x, start=%x, toggle=%x, address=%x, "
"instr=%x\n", rc5, org_code, RC5_START(rc5),
RC5_TOGGLE(rc5), RC5_ADDR(rc5), RC5_INSTR(rc5));
return rc5;
}
static void bttv_rc5_timer_end(unsigned long data)
{
struct bttv_ir *ir = (struct bttv_ir *)data;
struct timeval tv;
u32 gap, rc5, scancode;
u8 toggle, command, system;
/* get time */
do_gettimeofday(&tv);
/* avoid overflow with gap >1s */
if (tv.tv_sec - ir->base_time.tv_sec > 1) {
gap = 200000;
} else {
gap = 1000000 * (tv.tv_sec - ir->base_time.tv_sec) +
tv.tv_usec - ir->base_time.tv_usec;
}
/* signal we're ready to start a new code */
ir->active = false;
/* Allow some timer jitter (RC5 is ~24ms anyway so this is ok) */
if (gap < 28000) {
dprintk("spurious timer_end\n");
return;
}
if (ir->last_bit < 20) {
/* ignore spurious codes (caused by light/other remotes) */
dprintk("short code: %x\n", ir->code);
return;
}
ir->code = (ir->code << ir->shift_by) | 1;
rc5 = bttv_rc5_decode(ir->code);
toggle = RC5_TOGGLE(rc5);
system = RC5_ADDR(rc5);
command = RC5_INSTR(rc5);
switch (RC5_START(rc5)) {
case 0x3:
break;
case 0x2:
command += 0x40;
break;
default:
return;
}
scancode = RC_SCANCODE_RC5(system, command);
rc_keydown(ir->dev, RC_TYPE_RC5, scancode, toggle);
dprintk("scancode %x, toggle %x\n", scancode, toggle);
}
static int bttv_rc5_irq(struct bttv *btv)
{
struct bttv_ir *ir = btv->remote;
struct timeval tv;
u32 gpio;
u32 gap;
unsigned long current_jiffies;
/* read gpio port */
gpio = bttv_gpio_read(&btv->c);
/* get time of bit */
current_jiffies = jiffies;
do_gettimeofday(&tv);
/* avoid overflow with gap >1s */
if (tv.tv_sec - ir->base_time.tv_sec > 1) {
gap = 200000;
} else {
gap = 1000000 * (tv.tv_sec - ir->base_time.tv_sec) +
tv.tv_usec - ir->base_time.tv_usec;
}
dprintk("RC5 IRQ: gap %d us for %s\n",
gap, (gpio & 0x20) ? "mark" : "space");
/* remote IRQ? */
if (!(gpio & 0x20))
return 0;
/* active code => add bit */
if (ir->active) {
/* only if in the code (otherwise spurious IRQ or timer
late) */
if (ir->last_bit < 28) {
ir->last_bit = (gap - ir_rc5_remote_gap / 2) /
ir_rc5_remote_gap;
ir->code |= 1 << ir->last_bit;
}
/* starting new code */
} else {
ir->active = true;
ir->code = 0;
ir->base_time = tv;
ir->last_bit = 0;
mod_timer(&ir->timer, current_jiffies + msecs_to_jiffies(30));
}
/* toggle GPIO pin 4 to reset the irq */
bttv_gpio_write(&btv->c, gpio & ~(1 << 4));
bttv_gpio_write(&btv->c, gpio | (1 << 4));
return 1;
}
/* ---------------------------------------------------------------------- */
static void bttv_ir_start(struct bttv *btv, struct bttv_ir *ir)
{
if (ir->polling) {
setup_timer(&ir->timer, bttv_input_timer, (unsigned long)btv);
ir->timer.expires = jiffies + msecs_to_jiffies(1000);
add_timer(&ir->timer);
} else if (ir->rc5_gpio) {
/* set timer_end for code completion */
setup_timer(&ir->timer, bttv_rc5_timer_end, (unsigned long)ir);
ir->shift_by = 1;
ir->rc5_remote_gap = ir_rc5_remote_gap;
}
}
static void bttv_ir_stop(struct bttv *btv)
{
if (btv->remote->polling)
del_timer_sync(&btv->remote->timer);
if (btv->remote->rc5_gpio) {
u32 gpio;
del_timer_sync(&btv->remote->timer);
gpio = bttv_gpio_read(&btv->c);
bttv_gpio_write(&btv->c, gpio & ~(1 << 4));
}
}
/*
* Get_key functions used by I2C remotes
*/
static int get_key_pv951(struct IR_i2c *ir, enum rc_type *protocol,
u32 *scancode, u8 *toggle)
{
unsigned char b;
/* poll IR chip */
if (1 != i2c_master_recv(ir->c, &b, 1)) {
dprintk("read error\n");
return -EIO;
}
/* ignore 0xaa */
if (b==0xaa)
return 0;
dprintk("key %02x\n", b);
/*
* NOTE:
* lirc_i2c maps the pv951 code as:
* addr = 0x61D6
* cmd = bit_reverse (b)
* So, it seems that this device uses NEC extended
* I decided to not fix the table, due to two reasons:
* 1) Without the actual device, this is only a guess;
* 2) As the addr is not reported via I2C, nor can be changed,
* the device is bound to the vendor-provided RC.
*/
*protocol = RC_TYPE_UNKNOWN;
*scancode = b;
*toggle = 0;
return 1;
}
/* Instantiate the I2C IR receiver device, if present */
void init_bttv_i2c_ir(struct bttv *btv)
{
const unsigned short addr_list[] = {
0x1a, 0x18, 0x64, 0x30, 0x71,
I2C_CLIENT_END
};
struct i2c_board_info info;
struct i2c_client *i2c_dev;
if (0 != btv->i2c_rc)
return;
memset(&info, 0, sizeof(struct i2c_board_info));
memset(&btv->init_data, 0, sizeof(btv->init_data));
strlcpy(info.type, "ir_video", I2C_NAME_SIZE);
switch (btv->c.type) {
case BTTV_BOARD_PV951:
btv->init_data.name = "PV951";
btv->init_data.get_key = get_key_pv951;
btv->init_data.ir_codes = RC_MAP_PV951;
info.addr = 0x4b;
break;
}
if (btv->init_data.name) {
info.platform_data = &btv->init_data;
i2c_dev = i2c_new_device(&btv->c.i2c_adap, &info);
} else {
/*
* The external IR receiver is at i2c address 0x34 (0x35 for
* reads). Future Hauppauge cards will have an internal
* receiver at 0x30 (0x31 for reads). In theory, both can be
* fitted, and Hauppauge suggest an external overrides an
* internal.
* That's why we probe 0x1a (~0x34) first. CB
*/
i2c_dev = i2c_new_probed_device(&btv->c.i2c_adap, &info, addr_list, NULL);
}
if (NULL == i2c_dev)
return;
#if defined(CONFIG_MODULES) && defined(MODULE)
request_module("ir-kbd-i2c");
#endif
}
int bttv_input_init(struct bttv *btv)
{
struct bttv_ir *ir;
char *ir_codes = NULL;
struct rc_dev *rc;
int err = -ENOMEM;
if (!btv->has_remote)
return -ENODEV;
ir = kzalloc(sizeof(*ir),GFP_KERNEL);
rc = rc_allocate_device();
if (!ir || !rc)
goto err_out_free;
/* detect & configure */
switch (btv->c.type) {
case BTTV_BOARD_AVERMEDIA:
case BTTV_BOARD_AVPHONE98:
case BTTV_BOARD_AVERMEDIA98:
ir_codes = RC_MAP_AVERMEDIA;
ir->mask_keycode = 0xf88000;
ir->mask_keydown = 0x010000;
ir->polling = 50; // ms
break;
case BTTV_BOARD_AVDVBT_761:
case BTTV_BOARD_AVDVBT_771:
ir_codes = RC_MAP_AVERMEDIA_DVBT;
ir->mask_keycode = 0x0f00c0;
ir->mask_keydown = 0x000020;
ir->polling = 50; // ms
break;
case BTTV_BOARD_PXELVWPLTVPAK:
ir_codes = RC_MAP_PIXELVIEW;
ir->mask_keycode = 0x003e00;
ir->mask_keyup = 0x010000;
ir->polling = 50; // ms
break;
case BTTV_BOARD_PV_M4900:
case BTTV_BOARD_PV_BT878P_9B:
case BTTV_BOARD_PV_BT878P_PLUS:
ir_codes = RC_MAP_PIXELVIEW;
ir->mask_keycode = 0x001f00;
ir->mask_keyup = 0x008000;
ir->polling = 50; // ms
break;
case BTTV_BOARD_WINFAST2000:
ir_codes = RC_MAP_WINFAST;
ir->mask_keycode = 0x1f8;
break;
case BTTV_BOARD_MAGICTVIEW061:
case BTTV_BOARD_MAGICTVIEW063:
ir_codes = RC_MAP_WINFAST;
ir->mask_keycode = 0x0008e000;
ir->mask_keydown = 0x00200000;
break;
case BTTV_BOARD_APAC_VIEWCOMP:
ir_codes = RC_MAP_APAC_VIEWCOMP;
ir->mask_keycode = 0x001f00;
ir->mask_keyup = 0x008000;
ir->polling = 50; // ms
break;
case BTTV_BOARD_ASKEY_CPH03X:
case BTTV_BOARD_CONCEPTRONIC_CTVFMI2:
case BTTV_BOARD_CONTVFMI:
case BTTV_BOARD_KWORLD_VSTREAM_XPERT:
ir_codes = RC_MAP_PIXELVIEW;
ir->mask_keycode = 0x001F00;
ir->mask_keyup = 0x006000;
ir->polling = 50; // ms
break;
case BTTV_BOARD_NEBULA_DIGITV:
ir_codes = RC_MAP_NEBULA;
ir->rc5_gpio = true;
break;
case BTTV_BOARD_MACHTV_MAGICTV:
ir_codes = RC_MAP_APAC_VIEWCOMP;
ir->mask_keycode = 0x001F00;
ir->mask_keyup = 0x004000;
ir->polling = 50; /* ms */
break;
case BTTV_BOARD_KOZUMI_KTV_01C:
ir_codes = RC_MAP_PCTV_SEDNA;
ir->mask_keycode = 0x001f00;
ir->mask_keyup = 0x006000;
ir->polling = 50; /* ms */
break;
case BTTV_BOARD_ENLTV_FM_2:
ir_codes = RC_MAP_ENCORE_ENLTV2;
ir->mask_keycode = 0x00fd00;
ir->mask_keyup = 0x000080;
ir->polling = 1; /* ms */
ir->last_gpio = ir_extract_bits(bttv_gpio_read(&btv->c),
ir->mask_keycode);
break;
}
if (!ir_codes) {
dprintk("Ooops: IR config error [card=%d]\n", btv->c.type);
err = -ENODEV;
goto err_out_free;
}
if (ir->rc5_gpio) {
u32 gpio;
/* enable remote irq */
bttv_gpio_inout(&btv->c, (1 << 4), 1 << 4);
gpio = bttv_gpio_read(&btv->c);
bttv_gpio_write(&btv->c, gpio & ~(1 << 4));
bttv_gpio_write(&btv->c, gpio | (1 << 4));
} else {
/* init hardware-specific stuff */
bttv_gpio_inout(&btv->c, ir->mask_keycode | ir->mask_keydown, 0);
}
/* init input device */
ir->dev = rc;
snprintf(ir->name, sizeof(ir->name), "bttv IR (card=%d)",
btv->c.type);
snprintf(ir->phys, sizeof(ir->phys), "pci-%s/ir0",
pci_name(btv->c.pci));
rc->input_name = ir->name;
rc->input_phys = ir->phys;
rc->input_id.bustype = BUS_PCI;
rc->input_id.version = 1;
if (btv->c.pci->subsystem_vendor) {
rc->input_id.vendor = btv->c.pci->subsystem_vendor;
rc->input_id.product = btv->c.pci->subsystem_device;
} else {
rc->input_id.vendor = btv->c.pci->vendor;
rc->input_id.product = btv->c.pci->device;
}
rc->dev.parent = &btv->c.pci->dev;
rc->map_name = ir_codes;
rc->driver_name = MODULE_NAME;
btv->remote = ir;
bttv_ir_start(btv, ir);
/* all done */
err = rc_register_device(rc);
if (err)
goto err_out_stop;
return 0;
err_out_stop:
bttv_ir_stop(btv);
btv->remote = NULL;
err_out_free:
rc_free_device(rc);
kfree(ir);
return err;
}
void bttv_input_fini(struct bttv *btv)
{
if (btv->remote == NULL)
return;
bttv_ir_stop(btv);
rc_unregister_device(btv->remote->dev);
kfree(btv->remote);
btv->remote = NULL;
}

View file

@ -0,0 +1,909 @@
/*
bttv-risc.c -- interfaces to other kernel modules
bttv risc code handling
- memory management
- generation
(c) 2000-2003 Gerd Knorr <kraxel@bytesex.org>
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.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <linux/vmalloc.h>
#include <linux/interrupt.h>
#include <asm/page.h>
#include <asm/pgtable.h>
#include <media/v4l2-ioctl.h>
#include "bttvp.h"
#define VCR_HACK_LINES 4
/* ---------------------------------------------------------- */
/* risc code generators */
int
bttv_risc_packed(struct bttv *btv, struct btcx_riscmem *risc,
struct scatterlist *sglist,
unsigned int offset, unsigned int bpl,
unsigned int padding, unsigned int skip_lines,
unsigned int store_lines)
{
u32 instructions,line,todo;
struct scatterlist *sg;
__le32 *rp;
int rc;
/* estimate risc mem: worst case is one write per page border +
one write per scan line + sync + 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 = skip_lines * 4;
instructions += (1 + ((bpl + padding) * store_lines)
/ PAGE_SIZE + store_lines) * 8;
instructions += 2 * 8;
if ((rc = btcx_riscmem_alloc(btv->c.pci,risc,instructions)) < 0)
return rc;
/* sync instruction */
rp = risc->cpu;
*(rp++) = cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1);
*(rp++) = cpu_to_le32(0);
while (skip_lines-- > 0) {
*(rp++) = cpu_to_le32(BT848_RISC_SKIP | BT848_RISC_SOL |
BT848_RISC_EOL | bpl);
}
/* scan lines */
sg = sglist;
for (line = 0; line < store_lines; line++) {
if ((btv->opt_vcr_hack) &&
(line >= (store_lines - VCR_HACK_LINES)))
continue;
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(BT848_RISC_WRITE|BT848_RISC_SOL|
BT848_RISC_EOL|bpl);
*(rp++)=cpu_to_le32(sg_dma_address(sg)+offset);
offset+=bpl;
} else {
/* scanline needs to be splitted */
todo = bpl;
*(rp++)=cpu_to_le32(BT848_RISC_WRITE|BT848_RISC_SOL|
(sg_dma_len(sg)-offset));
*(rp++)=cpu_to_le32(sg_dma_address(sg)+offset);
todo -= (sg_dma_len(sg)-offset);
offset = 0;
sg++;
while (todo > sg_dma_len(sg)) {
*(rp++)=cpu_to_le32(BT848_RISC_WRITE|
sg_dma_len(sg));
*(rp++)=cpu_to_le32(sg_dma_address(sg));
todo -= sg_dma_len(sg);
sg++;
}
*(rp++)=cpu_to_le32(BT848_RISC_WRITE|BT848_RISC_EOL|
todo);
*(rp++)=cpu_to_le32(sg_dma_address(sg));
offset += todo;
}
offset += padding;
}
/* save pointer to jmp instruction address */
risc->jmp = rp;
BUG_ON((risc->jmp - risc->cpu + 2) * sizeof(*risc->cpu) > risc->size);
return 0;
}
static int
bttv_risc_planar(struct bttv *btv, struct btcx_riscmem *risc,
struct scatterlist *sglist,
unsigned int yoffset, unsigned int ybpl,
unsigned int ypadding, unsigned int ylines,
unsigned int uoffset, unsigned int voffset,
unsigned int hshift, unsigned int vshift,
unsigned int cpadding)
{
unsigned int instructions,line,todo,ylen,chroma;
__le32 *rp;
u32 ri;
struct scatterlist *ysg;
struct scatterlist *usg;
struct scatterlist *vsg;
int topfield = (0 == yoffset);
int rc;
/* estimate risc mem: worst case is one write per page border +
one write per scan line (5 dwords)
plus sync + jump (2 dwords) */
instructions = ((3 + (ybpl + ypadding) * ylines * 2)
/ PAGE_SIZE) + ylines;
instructions += 2;
if ((rc = btcx_riscmem_alloc(btv->c.pci,risc,instructions*4*5)) < 0)
return rc;
/* sync instruction */
rp = risc->cpu;
*(rp++) = cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM3);
*(rp++) = cpu_to_le32(0);
/* scan lines */
ysg = sglist;
usg = sglist;
vsg = sglist;
for (line = 0; line < ylines; line++) {
if ((btv->opt_vcr_hack) &&
(line >= (ylines - VCR_HACK_LINES)))
continue;
switch (vshift) {
case 0:
chroma = 1;
break;
case 1:
if (topfield)
chroma = ((line & 1) == 0);
else
chroma = ((line & 1) == 1);
break;
case 2:
if (topfield)
chroma = ((line & 3) == 0);
else
chroma = ((line & 3) == 2);
break;
default:
chroma = 0;
break;
}
for (todo = ybpl; todo > 0; todo -= ylen) {
/* go to next sg entry if needed */
while (yoffset && yoffset >= sg_dma_len(ysg)) {
yoffset -= sg_dma_len(ysg);
ysg++;
}
while (uoffset && uoffset >= sg_dma_len(usg)) {
uoffset -= sg_dma_len(usg);
usg++;
}
while (voffset && voffset >= sg_dma_len(vsg)) {
voffset -= sg_dma_len(vsg);
vsg++;
}
/* calculate max number of bytes we can write */
ylen = todo;
if (yoffset + ylen > sg_dma_len(ysg))
ylen = sg_dma_len(ysg) - yoffset;
if (chroma) {
if (uoffset + (ylen>>hshift) > sg_dma_len(usg))
ylen = (sg_dma_len(usg) - uoffset) << hshift;
if (voffset + (ylen>>hshift) > sg_dma_len(vsg))
ylen = (sg_dma_len(vsg) - voffset) << hshift;
ri = BT848_RISC_WRITE123;
} else {
ri = BT848_RISC_WRITE1S23;
}
if (ybpl == todo)
ri |= BT848_RISC_SOL;
if (ylen == todo)
ri |= BT848_RISC_EOL;
/* write risc instruction */
*(rp++)=cpu_to_le32(ri | ylen);
*(rp++)=cpu_to_le32(((ylen >> hshift) << 16) |
(ylen >> hshift));
*(rp++)=cpu_to_le32(sg_dma_address(ysg)+yoffset);
yoffset += ylen;
if (chroma) {
*(rp++)=cpu_to_le32(sg_dma_address(usg)+uoffset);
uoffset += ylen >> hshift;
*(rp++)=cpu_to_le32(sg_dma_address(vsg)+voffset);
voffset += ylen >> hshift;
}
}
yoffset += ypadding;
if (chroma) {
uoffset += cpadding;
voffset += cpadding;
}
}
/* save pointer to jmp instruction address */
risc->jmp = rp;
BUG_ON((risc->jmp - risc->cpu + 2) * sizeof(*risc->cpu) > risc->size);
return 0;
}
static int
bttv_risc_overlay(struct bttv *btv, struct btcx_riscmem *risc,
const struct bttv_format *fmt, struct bttv_overlay *ov,
int skip_even, int skip_odd)
{
int dwords, rc, line, maxy, start, end;
unsigned skip, nskips;
struct btcx_skiplist *skips;
__le32 *rp;
u32 ri,ra;
u32 addr;
/* skip list for window clipping */
if (NULL == (skips = kmalloc(sizeof(*skips) * ov->nclips,GFP_KERNEL)))
return -ENOMEM;
/* estimate risc mem: worst case is (1.5*clip+1) * lines instructions
+ sync + jump (all 2 dwords) */
dwords = (3 * ov->nclips + 2) *
((skip_even || skip_odd) ? (ov->w.height+1)>>1 : ov->w.height);
dwords += 4;
if ((rc = btcx_riscmem_alloc(btv->c.pci,risc,dwords*4)) < 0) {
kfree(skips);
return rc;
}
/* sync instruction */
rp = risc->cpu;
*(rp++) = cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1);
*(rp++) = cpu_to_le32(0);
addr = (unsigned long)btv->fbuf.base;
addr += btv->fbuf.fmt.bytesperline * ov->w.top;
addr += (fmt->depth >> 3) * ov->w.left;
/* scan lines */
for (maxy = -1, line = 0; line < ov->w.height;
line++, addr += btv->fbuf.fmt.bytesperline) {
if ((btv->opt_vcr_hack) &&
(line >= (ov->w.height - VCR_HACK_LINES)))
continue;
if ((line%2) == 0 && skip_even)
continue;
if ((line%2) == 1 && skip_odd)
continue;
/* calculate clipping */
if (line > maxy)
btcx_calc_skips(line, ov->w.width, &maxy,
skips, &nskips, ov->clips, ov->nclips);
/* write out risc code */
for (start = 0, skip = 0; start < ov->w.width; start = end) {
if (skip >= nskips) {
ri = BT848_RISC_WRITE;
end = ov->w.width;
} else if (start < skips[skip].start) {
ri = BT848_RISC_WRITE;
end = skips[skip].start;
} else {
ri = BT848_RISC_SKIP;
end = skips[skip].end;
skip++;
}
if (BT848_RISC_WRITE == ri)
ra = addr + (fmt->depth>>3)*start;
else
ra = 0;
if (0 == start)
ri |= BT848_RISC_SOL;
if (ov->w.width == end)
ri |= BT848_RISC_EOL;
ri |= (fmt->depth>>3) * (end-start);
*(rp++)=cpu_to_le32(ri);
if (0 != ra)
*(rp++)=cpu_to_le32(ra);
}
}
/* save pointer to jmp instruction address */
risc->jmp = rp;
BUG_ON((risc->jmp - risc->cpu + 2) * sizeof(*risc->cpu) > risc->size);
kfree(skips);
return 0;
}
/* ---------------------------------------------------------- */
static void
bttv_calc_geo_old(struct bttv *btv, struct bttv_geometry *geo,
int width, int height, int interleaved,
const struct bttv_tvnorm *tvnorm)
{
u32 xsf, sr;
int vdelay;
int swidth = tvnorm->swidth;
int totalwidth = tvnorm->totalwidth;
int scaledtwidth = tvnorm->scaledtwidth;
if (btv->input == btv->dig) {
swidth = 720;
totalwidth = 858;
scaledtwidth = 858;
}
vdelay = tvnorm->vdelay;
xsf = (width*scaledtwidth)/swidth;
geo->hscale = ((totalwidth*4096UL)/xsf-4096);
geo->hdelay = tvnorm->hdelayx1;
geo->hdelay = (geo->hdelay*width)/swidth;
geo->hdelay &= 0x3fe;
sr = ((tvnorm->sheight >> (interleaved?0:1))*512)/height - 512;
geo->vscale = (0x10000UL-sr) & 0x1fff;
geo->crop = ((width>>8)&0x03) | ((geo->hdelay>>6)&0x0c) |
((tvnorm->sheight>>4)&0x30) | ((vdelay>>2)&0xc0);
geo->vscale |= interleaved ? (BT848_VSCALE_INT<<8) : 0;
geo->vdelay = vdelay;
geo->width = width;
geo->sheight = tvnorm->sheight;
geo->vtotal = tvnorm->vtotal;
if (btv->opt_combfilter) {
geo->vtc = (width < 193) ? 2 : ((width < 385) ? 1 : 0);
geo->comb = (width < 769) ? 1 : 0;
} else {
geo->vtc = 0;
geo->comb = 0;
}
}
static void
bttv_calc_geo (struct bttv * btv,
struct bttv_geometry * geo,
unsigned int width,
unsigned int height,
int both_fields,
const struct bttv_tvnorm * tvnorm,
const struct v4l2_rect * crop)
{
unsigned int c_width;
unsigned int c_height;
u32 sr;
if ((crop->left == tvnorm->cropcap.defrect.left
&& crop->top == tvnorm->cropcap.defrect.top
&& crop->width == tvnorm->cropcap.defrect.width
&& crop->height == tvnorm->cropcap.defrect.height
&& width <= tvnorm->swidth /* see PAL-Nc et al */)
|| btv->input == btv->dig) {
bttv_calc_geo_old(btv, geo, width, height,
both_fields, tvnorm);
return;
}
/* For bug compatibility the image size checks permit scale
factors > 16. See bttv_crop_calc_limits(). */
c_width = min((unsigned int) crop->width, width * 16);
c_height = min((unsigned int) crop->height, height * 16);
geo->width = width;
geo->hscale = (c_width * 4096U + (width >> 1)) / width - 4096;
/* Even to store Cb first, odd for Cr. */
geo->hdelay = ((crop->left * width + c_width) / c_width) & ~1;
geo->sheight = c_height;
geo->vdelay = crop->top - tvnorm->cropcap.bounds.top + MIN_VDELAY;
sr = c_height >> !both_fields;
sr = (sr * 512U + (height >> 1)) / height - 512;
geo->vscale = (0x10000UL - sr) & 0x1fff;
geo->vscale |= both_fields ? (BT848_VSCALE_INT << 8) : 0;
geo->vtotal = tvnorm->vtotal;
geo->crop = (((geo->width >> 8) & 0x03) |
((geo->hdelay >> 6) & 0x0c) |
((geo->sheight >> 4) & 0x30) |
((geo->vdelay >> 2) & 0xc0));
if (btv->opt_combfilter) {
geo->vtc = (width < 193) ? 2 : ((width < 385) ? 1 : 0);
geo->comb = (width < 769) ? 1 : 0;
} else {
geo->vtc = 0;
geo->comb = 0;
}
}
static void
bttv_apply_geo(struct bttv *btv, struct bttv_geometry *geo, int odd)
{
int off = odd ? 0x80 : 0x00;
if (geo->comb)
btor(BT848_VSCALE_COMB, BT848_E_VSCALE_HI+off);
else
btand(~BT848_VSCALE_COMB, BT848_E_VSCALE_HI+off);
btwrite(geo->vtc, BT848_E_VTC+off);
btwrite(geo->hscale >> 8, BT848_E_HSCALE_HI+off);
btwrite(geo->hscale & 0xff, BT848_E_HSCALE_LO+off);
btaor((geo->vscale>>8), 0xe0, BT848_E_VSCALE_HI+off);
btwrite(geo->vscale & 0xff, BT848_E_VSCALE_LO+off);
btwrite(geo->width & 0xff, BT848_E_HACTIVE_LO+off);
btwrite(geo->hdelay & 0xff, BT848_E_HDELAY_LO+off);
btwrite(geo->sheight & 0xff, BT848_E_VACTIVE_LO+off);
btwrite(geo->vdelay & 0xff, BT848_E_VDELAY_LO+off);
btwrite(geo->crop, BT848_E_CROP+off);
btwrite(geo->vtotal>>8, BT848_VTOTAL_HI);
btwrite(geo->vtotal & 0xff, BT848_VTOTAL_LO);
}
/* ---------------------------------------------------------- */
/* risc group / risc main loop / dma management */
void
bttv_set_dma(struct bttv *btv, int override)
{
unsigned long cmd;
int capctl;
btv->cap_ctl = 0;
if (NULL != btv->curr.top) btv->cap_ctl |= 0x02;
if (NULL != btv->curr.bottom) btv->cap_ctl |= 0x01;
if (NULL != btv->cvbi) btv->cap_ctl |= 0x0c;
capctl = 0;
capctl |= (btv->cap_ctl & 0x03) ? 0x03 : 0x00; /* capture */
capctl |= (btv->cap_ctl & 0x0c) ? 0x0c : 0x00; /* vbi data */
capctl |= override;
d2printk("%d: capctl=%x lirq=%d top=%08llx/%08llx even=%08llx/%08llx\n",
btv->c.nr,capctl,btv->loop_irq,
btv->cvbi ? (unsigned long long)btv->cvbi->top.dma : 0,
btv->curr.top ? (unsigned long long)btv->curr.top->top.dma : 0,
btv->cvbi ? (unsigned long long)btv->cvbi->bottom.dma : 0,
btv->curr.bottom ? (unsigned long long)btv->curr.bottom->bottom.dma : 0);
cmd = BT848_RISC_JUMP;
if (btv->loop_irq) {
cmd |= BT848_RISC_IRQ;
cmd |= (btv->loop_irq & 0x0f) << 16;
cmd |= (~btv->loop_irq & 0x0f) << 20;
}
if (btv->curr.frame_irq || btv->loop_irq || btv->cvbi) {
mod_timer(&btv->timeout, jiffies+BTTV_TIMEOUT);
} else {
del_timer(&btv->timeout);
}
btv->main.cpu[RISC_SLOT_LOOP] = cpu_to_le32(cmd);
btaor(capctl, ~0x0f, BT848_CAP_CTL);
if (capctl) {
if (btv->dma_on)
return;
btwrite(btv->main.dma, BT848_RISC_STRT_ADD);
btor(3, BT848_GPIO_DMA_CTL);
btv->dma_on = 1;
} else {
if (!btv->dma_on)
return;
btand(~3, BT848_GPIO_DMA_CTL);
btv->dma_on = 0;
}
return;
}
int
bttv_risc_init_main(struct bttv *btv)
{
int rc;
if ((rc = btcx_riscmem_alloc(btv->c.pci,&btv->main,PAGE_SIZE)) < 0)
return rc;
dprintk("%d: risc main @ %08llx\n",
btv->c.nr, (unsigned long long)btv->main.dma);
btv->main.cpu[0] = cpu_to_le32(BT848_RISC_SYNC | BT848_RISC_RESYNC |
BT848_FIFO_STATUS_VRE);
btv->main.cpu[1] = cpu_to_le32(0);
btv->main.cpu[2] = cpu_to_le32(BT848_RISC_JUMP);
btv->main.cpu[3] = cpu_to_le32(btv->main.dma + (4<<2));
/* top field */
btv->main.cpu[4] = cpu_to_le32(BT848_RISC_JUMP);
btv->main.cpu[5] = cpu_to_le32(btv->main.dma + (6<<2));
btv->main.cpu[6] = cpu_to_le32(BT848_RISC_JUMP);
btv->main.cpu[7] = cpu_to_le32(btv->main.dma + (8<<2));
btv->main.cpu[8] = cpu_to_le32(BT848_RISC_SYNC | BT848_RISC_RESYNC |
BT848_FIFO_STATUS_VRO);
btv->main.cpu[9] = cpu_to_le32(0);
/* bottom field */
btv->main.cpu[10] = cpu_to_le32(BT848_RISC_JUMP);
btv->main.cpu[11] = cpu_to_le32(btv->main.dma + (12<<2));
btv->main.cpu[12] = cpu_to_le32(BT848_RISC_JUMP);
btv->main.cpu[13] = cpu_to_le32(btv->main.dma + (14<<2));
/* jump back to top field */
btv->main.cpu[14] = cpu_to_le32(BT848_RISC_JUMP);
btv->main.cpu[15] = cpu_to_le32(btv->main.dma + (0<<2));
return 0;
}
int
bttv_risc_hook(struct bttv *btv, int slot, struct btcx_riscmem *risc,
int irqflags)
{
unsigned long cmd;
unsigned long next = btv->main.dma + ((slot+2) << 2);
if (NULL == risc) {
d2printk("%d: risc=%p slot[%d]=NULL\n", btv->c.nr, risc, slot);
btv->main.cpu[slot+1] = cpu_to_le32(next);
} else {
d2printk("%d: risc=%p slot[%d]=%08llx irq=%d\n",
btv->c.nr, risc, slot,
(unsigned long long)risc->dma, irqflags);
cmd = BT848_RISC_JUMP;
if (irqflags) {
cmd |= BT848_RISC_IRQ;
cmd |= (irqflags & 0x0f) << 16;
cmd |= (~irqflags & 0x0f) << 20;
}
risc->jmp[0] = cpu_to_le32(cmd);
risc->jmp[1] = cpu_to_le32(next);
btv->main.cpu[slot+1] = cpu_to_le32(risc->dma);
}
return 0;
}
void
bttv_dma_free(struct videobuf_queue *q,struct bttv *btv, struct bttv_buffer *buf)
{
struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb);
BUG_ON(in_interrupt());
videobuf_waiton(q, &buf->vb, 0, 0);
videobuf_dma_unmap(q->dev, dma);
videobuf_dma_free(dma);
btcx_riscmem_free(btv->c.pci,&buf->bottom);
btcx_riscmem_free(btv->c.pci,&buf->top);
buf->vb.state = VIDEOBUF_NEEDS_INIT;
}
int
bttv_buffer_activate_vbi(struct bttv *btv,
struct bttv_buffer *vbi)
{
struct btcx_riscmem *top;
struct btcx_riscmem *bottom;
int top_irq_flags;
int bottom_irq_flags;
top = NULL;
bottom = NULL;
top_irq_flags = 0;
bottom_irq_flags = 0;
if (vbi) {
unsigned int crop, vdelay;
vbi->vb.state = VIDEOBUF_ACTIVE;
list_del(&vbi->vb.queue);
/* VDELAY is start of video, end of VBI capturing. */
crop = btread(BT848_E_CROP);
vdelay = btread(BT848_E_VDELAY_LO) + ((crop & 0xc0) << 2);
if (vbi->geo.vdelay > vdelay) {
vdelay = vbi->geo.vdelay & 0xfe;
crop = (crop & 0x3f) | ((vbi->geo.vdelay >> 2) & 0xc0);
btwrite(vdelay, BT848_E_VDELAY_LO);
btwrite(crop, BT848_E_CROP);
btwrite(vdelay, BT848_O_VDELAY_LO);
btwrite(crop, BT848_O_CROP);
}
if (vbi->vbi_count[0] > 0) {
top = &vbi->top;
top_irq_flags = 4;
}
if (vbi->vbi_count[1] > 0) {
top_irq_flags = 0;
bottom = &vbi->bottom;
bottom_irq_flags = 4;
}
}
bttv_risc_hook(btv, RISC_SLOT_O_VBI, top, top_irq_flags);
bttv_risc_hook(btv, RISC_SLOT_E_VBI, bottom, bottom_irq_flags);
return 0;
}
int
bttv_buffer_activate_video(struct bttv *btv,
struct bttv_buffer_set *set)
{
/* video capture */
if (NULL != set->top && NULL != set->bottom) {
if (set->top == set->bottom) {
set->top->vb.state = VIDEOBUF_ACTIVE;
if (set->top->vb.queue.next)
list_del(&set->top->vb.queue);
} else {
set->top->vb.state = VIDEOBUF_ACTIVE;
set->bottom->vb.state = VIDEOBUF_ACTIVE;
if (set->top->vb.queue.next)
list_del(&set->top->vb.queue);
if (set->bottom->vb.queue.next)
list_del(&set->bottom->vb.queue);
}
bttv_apply_geo(btv, &set->top->geo, 1);
bttv_apply_geo(btv, &set->bottom->geo,0);
bttv_risc_hook(btv, RISC_SLOT_O_FIELD, &set->top->top,
set->top_irq);
bttv_risc_hook(btv, RISC_SLOT_E_FIELD, &set->bottom->bottom,
set->frame_irq);
btaor((set->top->btformat & 0xf0) | (set->bottom->btformat & 0x0f),
~0xff, BT848_COLOR_FMT);
btaor((set->top->btswap & 0x0a) | (set->bottom->btswap & 0x05),
~0x0f, BT848_COLOR_CTL);
} else if (NULL != set->top) {
set->top->vb.state = VIDEOBUF_ACTIVE;
if (set->top->vb.queue.next)
list_del(&set->top->vb.queue);
bttv_apply_geo(btv, &set->top->geo,1);
bttv_apply_geo(btv, &set->top->geo,0);
bttv_risc_hook(btv, RISC_SLOT_O_FIELD, &set->top->top,
set->frame_irq);
bttv_risc_hook(btv, RISC_SLOT_E_FIELD, NULL, 0);
btaor(set->top->btformat & 0xff, ~0xff, BT848_COLOR_FMT);
btaor(set->top->btswap & 0x0f, ~0x0f, BT848_COLOR_CTL);
} else if (NULL != set->bottom) {
set->bottom->vb.state = VIDEOBUF_ACTIVE;
if (set->bottom->vb.queue.next)
list_del(&set->bottom->vb.queue);
bttv_apply_geo(btv, &set->bottom->geo,1);
bttv_apply_geo(btv, &set->bottom->geo,0);
bttv_risc_hook(btv, RISC_SLOT_O_FIELD, NULL, 0);
bttv_risc_hook(btv, RISC_SLOT_E_FIELD, &set->bottom->bottom,
set->frame_irq);
btaor(set->bottom->btformat & 0xff, ~0xff, BT848_COLOR_FMT);
btaor(set->bottom->btswap & 0x0f, ~0x0f, BT848_COLOR_CTL);
} else {
bttv_risc_hook(btv, RISC_SLOT_O_FIELD, NULL, 0);
bttv_risc_hook(btv, RISC_SLOT_E_FIELD, NULL, 0);
}
return 0;
}
/* ---------------------------------------------------------- */
/* calculate geometry, build risc code */
int
bttv_buffer_risc(struct bttv *btv, struct bttv_buffer *buf)
{
const struct bttv_tvnorm *tvnorm = bttv_tvnorms + buf->tvnorm;
struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb);
dprintk("%d: buffer field: %s format: %s size: %dx%d\n",
btv->c.nr, v4l2_field_names[buf->vb.field],
buf->fmt->name, buf->vb.width, buf->vb.height);
/* packed pixel modes */
if (buf->fmt->flags & FORMAT_FLAGS_PACKED) {
int bpl = (buf->fmt->depth >> 3) * buf->vb.width;
int bpf = bpl * (buf->vb.height >> 1);
bttv_calc_geo(btv,&buf->geo,buf->vb.width,buf->vb.height,
V4L2_FIELD_HAS_BOTH(buf->vb.field),
tvnorm,&buf->crop);
switch (buf->vb.field) {
case V4L2_FIELD_TOP:
bttv_risc_packed(btv,&buf->top,dma->sglist,
/* offset */ 0,bpl,
/* padding */ 0,/* skip_lines */ 0,
buf->vb.height);
break;
case V4L2_FIELD_BOTTOM:
bttv_risc_packed(btv,&buf->bottom,dma->sglist,
0,bpl,0,0,buf->vb.height);
break;
case V4L2_FIELD_INTERLACED:
bttv_risc_packed(btv,&buf->top,dma->sglist,
0,bpl,bpl,0,buf->vb.height >> 1);
bttv_risc_packed(btv,&buf->bottom,dma->sglist,
bpl,bpl,bpl,0,buf->vb.height >> 1);
break;
case V4L2_FIELD_SEQ_TB:
bttv_risc_packed(btv,&buf->top,dma->sglist,
0,bpl,0,0,buf->vb.height >> 1);
bttv_risc_packed(btv,&buf->bottom,dma->sglist,
bpf,bpl,0,0,buf->vb.height >> 1);
break;
default:
BUG();
}
}
/* planar modes */
if (buf->fmt->flags & FORMAT_FLAGS_PLANAR) {
int uoffset, voffset;
int ypadding, cpadding, lines;
/* calculate chroma offsets */
uoffset = buf->vb.width * buf->vb.height;
voffset = buf->vb.width * buf->vb.height;
if (buf->fmt->flags & FORMAT_FLAGS_CrCb) {
/* Y-Cr-Cb plane order */
uoffset >>= buf->fmt->hshift;
uoffset >>= buf->fmt->vshift;
uoffset += voffset;
} else {
/* Y-Cb-Cr plane order */
voffset >>= buf->fmt->hshift;
voffset >>= buf->fmt->vshift;
voffset += uoffset;
}
switch (buf->vb.field) {
case V4L2_FIELD_TOP:
bttv_calc_geo(btv,&buf->geo,buf->vb.width,
buf->vb.height,/* both_fields */ 0,
tvnorm,&buf->crop);
bttv_risc_planar(btv, &buf->top, dma->sglist,
0,buf->vb.width,0,buf->vb.height,
uoffset,voffset,buf->fmt->hshift,
buf->fmt->vshift,0);
break;
case V4L2_FIELD_BOTTOM:
bttv_calc_geo(btv,&buf->geo,buf->vb.width,
buf->vb.height,0,
tvnorm,&buf->crop);
bttv_risc_planar(btv, &buf->bottom, dma->sglist,
0,buf->vb.width,0,buf->vb.height,
uoffset,voffset,buf->fmt->hshift,
buf->fmt->vshift,0);
break;
case V4L2_FIELD_INTERLACED:
bttv_calc_geo(btv,&buf->geo,buf->vb.width,
buf->vb.height,1,
tvnorm,&buf->crop);
lines = buf->vb.height >> 1;
ypadding = buf->vb.width;
cpadding = buf->vb.width >> buf->fmt->hshift;
bttv_risc_planar(btv,&buf->top,
dma->sglist,
0,buf->vb.width,ypadding,lines,
uoffset,voffset,
buf->fmt->hshift,
buf->fmt->vshift,
cpadding);
bttv_risc_planar(btv,&buf->bottom,
dma->sglist,
ypadding,buf->vb.width,ypadding,lines,
uoffset+cpadding,
voffset+cpadding,
buf->fmt->hshift,
buf->fmt->vshift,
cpadding);
break;
case V4L2_FIELD_SEQ_TB:
bttv_calc_geo(btv,&buf->geo,buf->vb.width,
buf->vb.height,1,
tvnorm,&buf->crop);
lines = buf->vb.height >> 1;
ypadding = buf->vb.width;
cpadding = buf->vb.width >> buf->fmt->hshift;
bttv_risc_planar(btv,&buf->top,
dma->sglist,
0,buf->vb.width,0,lines,
uoffset >> 1,
voffset >> 1,
buf->fmt->hshift,
buf->fmt->vshift,
0);
bttv_risc_planar(btv,&buf->bottom,
dma->sglist,
lines * ypadding,buf->vb.width,0,lines,
lines * ypadding + (uoffset >> 1),
lines * ypadding + (voffset >> 1),
buf->fmt->hshift,
buf->fmt->vshift,
0);
break;
default:
BUG();
}
}
/* raw data */
if (buf->fmt->flags & FORMAT_FLAGS_RAW) {
/* build risc code */
buf->vb.field = V4L2_FIELD_SEQ_TB;
bttv_calc_geo(btv,&buf->geo,tvnorm->swidth,tvnorm->sheight,
1,tvnorm,&buf->crop);
bttv_risc_packed(btv, &buf->top, dma->sglist,
/* offset */ 0, RAW_BPL, /* padding */ 0,
/* skip_lines */ 0, RAW_LINES);
bttv_risc_packed(btv, &buf->bottom, dma->sglist,
buf->vb.size/2 , RAW_BPL, 0, 0, RAW_LINES);
}
/* copy format info */
buf->btformat = buf->fmt->btformat;
buf->btswap = buf->fmt->btswap;
return 0;
}
/* ---------------------------------------------------------- */
/* calculate geometry, build risc code */
int
bttv_overlay_risc(struct bttv *btv,
struct bttv_overlay *ov,
const struct bttv_format *fmt,
struct bttv_buffer *buf)
{
/* check interleave, bottom+top fields */
dprintk("%d: overlay fields: %s format: %s size: %dx%d\n",
btv->c.nr, v4l2_field_names[buf->vb.field],
fmt->name, ov->w.width, ov->w.height);
/* calculate geometry */
bttv_calc_geo(btv,&buf->geo,ov->w.width,ov->w.height,
V4L2_FIELD_HAS_BOTH(ov->field),
&bttv_tvnorms[ov->tvnorm],&buf->crop);
/* build risc code */
switch (ov->field) {
case V4L2_FIELD_TOP:
bttv_risc_overlay(btv, &buf->top, fmt, ov, 0, 0);
break;
case V4L2_FIELD_BOTTOM:
bttv_risc_overlay(btv, &buf->bottom, fmt, ov, 0, 0);
break;
case V4L2_FIELD_INTERLACED:
bttv_risc_overlay(btv, &buf->top, fmt, ov, 0, 1);
bttv_risc_overlay(btv, &buf->bottom, fmt, ov, 1, 0);
break;
default:
BUG();
}
/* copy format info */
buf->btformat = fmt->btformat;
buf->btswap = fmt->btswap;
buf->vb.field = ov->field;
return 0;
}
/*
* Local variables:
* c-basic-offset: 8
* End:
*/

View file

@ -0,0 +1,459 @@
/*
bttv - Bt848 frame grabber driver
vbi interface
(c) 2002 Gerd Knorr <kraxel@bytesex.org>
Copyright (C) 2005, 2006 Michael H. Schimek <mschimek@gmx.at>
Sponsored by OPQ Systems AB
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.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/kdev_t.h>
#include <media/v4l2-ioctl.h>
#include <asm/io.h>
#include "bttvp.h"
/* Offset from line sync pulse leading edge (0H) to start of VBI capture,
in fCLKx2 pixels. According to the datasheet, VBI capture starts
VBI_HDELAY fCLKx1 pixels from the tailing edgeof /HRESET, and /HRESET
is 64 fCLKx1 pixels wide. VBI_HDELAY is set to 0, so this should be
(64 + 0) * 2 = 128 fCLKx2 pixels. But it's not! The datasheet is
Just Plain Wrong. The real value appears to be different for
different revisions of the bt8x8 chips, and to be affected by the
horizontal scaling factor. Experimentally, the value is measured
to be about 244. */
#define VBI_OFFSET 244
/* 2048 for compatibility with earlier driver versions. The driver
really stores 1024 + tvnorm->vbipack * 4 samples per line in the
buffer. Note tvnorm->vbipack is <= 0xFF (limit of VBIPACK_LO + HI
is 0x1FF DWORDs) and VBI read()s store a frame counter in the last
four bytes of the VBI image. */
#define VBI_BPL 2048
/* Compatibility. */
#define VBI_DEFLINES 16
static unsigned int vbibufs = 4;
static unsigned int vbi_debug;
module_param(vbibufs, int, 0444);
module_param(vbi_debug, int, 0644);
MODULE_PARM_DESC(vbibufs,"number of vbi buffers, range 2-32, default 4");
MODULE_PARM_DESC(vbi_debug,"vbi code debug messages, default is 0 (no)");
#ifdef dprintk
# undef dprintk
#endif
#define dprintk(fmt, ...) \
do { \
if (vbi_debug) \
pr_debug("%d: " fmt, btv->c.nr, ##__VA_ARGS__); \
} while (0)
#define IMAGE_SIZE(fmt) \
(((fmt)->count[0] + (fmt)->count[1]) * (fmt)->samples_per_line)
/* ----------------------------------------------------------------------- */
/* vbi risc code + mm */
static int vbi_buffer_setup(struct videobuf_queue *q,
unsigned int *count, unsigned int *size)
{
struct bttv_fh *fh = q->priv_data;
struct bttv *btv = fh->btv;
if (0 == *count)
*count = vbibufs;
*size = IMAGE_SIZE(&fh->vbi_fmt.fmt);
dprintk("setup: samples=%u start=%d,%d count=%u,%u\n",
fh->vbi_fmt.fmt.samples_per_line,
fh->vbi_fmt.fmt.start[0],
fh->vbi_fmt.fmt.start[1],
fh->vbi_fmt.fmt.count[0],
fh->vbi_fmt.fmt.count[1]);
return 0;
}
static int vbi_buffer_prepare(struct videobuf_queue *q,
struct videobuf_buffer *vb,
enum v4l2_field field)
{
struct bttv_fh *fh = q->priv_data;
struct bttv *btv = fh->btv;
struct bttv_buffer *buf = container_of(vb,struct bttv_buffer,vb);
const struct bttv_tvnorm *tvnorm;
unsigned int skip_lines0, skip_lines1, min_vdelay;
int redo_dma_risc;
int rc;
buf->vb.size = IMAGE_SIZE(&fh->vbi_fmt.fmt);
if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size)
return -EINVAL;
tvnorm = fh->vbi_fmt.tvnorm;
/* There's no VBI_VDELAY register, RISC must skip the lines
we don't want. With default parameters we skip zero lines
as earlier driver versions did. The driver permits video
standard changes while capturing, so we use vbi_fmt.tvnorm
instead of btv->tvnorm to skip zero lines after video
standard changes as well. */
skip_lines0 = 0;
skip_lines1 = 0;
if (fh->vbi_fmt.fmt.count[0] > 0)
skip_lines0 = max(0, (fh->vbi_fmt.fmt.start[0]
- tvnorm->vbistart[0]));
if (fh->vbi_fmt.fmt.count[1] > 0)
skip_lines1 = max(0, (fh->vbi_fmt.fmt.start[1]
- tvnorm->vbistart[1]));
redo_dma_risc = 0;
if (buf->vbi_skip[0] != skip_lines0 ||
buf->vbi_skip[1] != skip_lines1 ||
buf->vbi_count[0] != fh->vbi_fmt.fmt.count[0] ||
buf->vbi_count[1] != fh->vbi_fmt.fmt.count[1]) {
buf->vbi_skip[0] = skip_lines0;
buf->vbi_skip[1] = skip_lines1;
buf->vbi_count[0] = fh->vbi_fmt.fmt.count[0];
buf->vbi_count[1] = fh->vbi_fmt.fmt.count[1];
redo_dma_risc = 1;
}
if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {
redo_dma_risc = 1;
if (0 != (rc = videobuf_iolock(q, &buf->vb, NULL)))
goto fail;
}
if (redo_dma_risc) {
unsigned int bpl, padding, offset;
struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb);
bpl = 2044; /* max. vbipack */
padding = VBI_BPL - bpl;
if (fh->vbi_fmt.fmt.count[0] > 0) {
rc = bttv_risc_packed(btv, &buf->top,
dma->sglist,
/* offset */ 0, bpl,
padding, skip_lines0,
fh->vbi_fmt.fmt.count[0]);
if (0 != rc)
goto fail;
}
if (fh->vbi_fmt.fmt.count[1] > 0) {
offset = fh->vbi_fmt.fmt.count[0] * VBI_BPL;
rc = bttv_risc_packed(btv, &buf->bottom,
dma->sglist,
offset, bpl,
padding, skip_lines1,
fh->vbi_fmt.fmt.count[1]);
if (0 != rc)
goto fail;
}
}
/* VBI capturing ends at VDELAY, start of video capturing,
no matter where the RISC program ends. VDELAY minimum is 2,
bounds.top is the corresponding first field line number
times two. VDELAY counts half field lines. */
min_vdelay = MIN_VDELAY;
if (fh->vbi_fmt.end >= tvnorm->cropcap.bounds.top)
min_vdelay += fh->vbi_fmt.end - tvnorm->cropcap.bounds.top;
/* For bttv_buffer_activate_vbi(). */
buf->geo.vdelay = min_vdelay;
buf->vb.state = VIDEOBUF_PREPARED;
buf->vb.field = field;
dprintk("buf prepare %p: top=%p bottom=%p field=%s\n",
vb, &buf->top, &buf->bottom,
v4l2_field_names[buf->vb.field]);
return 0;
fail:
bttv_dma_free(q,btv,buf);
return rc;
}
static void
vbi_buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb)
{
struct bttv_fh *fh = q->priv_data;
struct bttv *btv = fh->btv;
struct bttv_buffer *buf = container_of(vb,struct bttv_buffer,vb);
dprintk("queue %p\n",vb);
buf->vb.state = VIDEOBUF_QUEUED;
list_add_tail(&buf->vb.queue,&btv->vcapture);
if (NULL == btv->cvbi) {
fh->btv->loop_irq |= 4;
bttv_set_dma(btv,0x0c);
}
}
static void vbi_buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb)
{
struct bttv_fh *fh = q->priv_data;
struct bttv *btv = fh->btv;
struct bttv_buffer *buf = container_of(vb,struct bttv_buffer,vb);
dprintk("free %p\n",vb);
bttv_dma_free(q,fh->btv,buf);
}
struct videobuf_queue_ops bttv_vbi_qops = {
.buf_setup = vbi_buffer_setup,
.buf_prepare = vbi_buffer_prepare,
.buf_queue = vbi_buffer_queue,
.buf_release = vbi_buffer_release,
};
/* ----------------------------------------------------------------------- */
static int try_fmt(struct v4l2_vbi_format *f, const struct bttv_tvnorm *tvnorm,
__s32 crop_start)
{
__s32 min_start, max_start, max_end, f2_offset;
unsigned int i;
/* For compatibility with earlier driver versions we must pretend
the VBI and video capture window may overlap. In reality RISC
magic aborts VBI capturing at the first line of video capturing,
leaving the rest of the buffer unchanged, usually all zero.
VBI capturing must always start before video capturing. >> 1
because cropping counts field lines times two. */
min_start = tvnorm->vbistart[0];
max_start = (crop_start >> 1) - 1;
max_end = (tvnorm->cropcap.bounds.top
+ tvnorm->cropcap.bounds.height) >> 1;
if (min_start > max_start)
return -EBUSY;
BUG_ON(max_start >= max_end);
f->sampling_rate = tvnorm->Fsc;
f->samples_per_line = VBI_BPL;
f->sample_format = V4L2_PIX_FMT_GREY;
f->offset = VBI_OFFSET;
f2_offset = tvnorm->vbistart[1] - tvnorm->vbistart[0];
for (i = 0; i < 2; ++i) {
if (0 == f->count[i]) {
/* No data from this field. We leave f->start[i]
alone because VIDIOCSVBIFMT is w/o and EINVALs
when a driver does not support exactly the
requested parameters. */
} else {
s64 start, count;
start = clamp(f->start[i], min_start, max_start);
/* s64 to prevent overflow. */
count = (s64) f->start[i] + f->count[i] - start;
f->start[i] = start;
f->count[i] = clamp(count, (s64) 1,
max_end - start);
}
min_start += f2_offset;
max_start += f2_offset;
max_end += f2_offset;
}
if (0 == (f->count[0] | f->count[1])) {
/* As in earlier driver versions. */
f->start[0] = tvnorm->vbistart[0];
f->start[1] = tvnorm->vbistart[1];
f->count[0] = 1;
f->count[1] = 1;
}
f->flags = 0;
f->reserved[0] = 0;
f->reserved[1] = 0;
return 0;
}
int bttv_try_fmt_vbi_cap(struct file *file, void *f, struct v4l2_format *frt)
{
struct bttv_fh *fh = f;
struct bttv *btv = fh->btv;
const struct bttv_tvnorm *tvnorm;
__s32 crop_start;
mutex_lock(&btv->lock);
tvnorm = &bttv_tvnorms[btv->tvnorm];
crop_start = btv->crop_start;
mutex_unlock(&btv->lock);
return try_fmt(&frt->fmt.vbi, tvnorm, crop_start);
}
int bttv_s_fmt_vbi_cap(struct file *file, void *f, struct v4l2_format *frt)
{
struct bttv_fh *fh = f;
struct bttv *btv = fh->btv;
const struct bttv_tvnorm *tvnorm;
__s32 start1, end;
int rc;
mutex_lock(&btv->lock);
rc = -EBUSY;
if (fh->resources & RESOURCE_VBI)
goto fail;
tvnorm = &bttv_tvnorms[btv->tvnorm];
rc = try_fmt(&frt->fmt.vbi, tvnorm, btv->crop_start);
if (0 != rc)
goto fail;
start1 = frt->fmt.vbi.start[1] - tvnorm->vbistart[1] +
tvnorm->vbistart[0];
/* First possible line of video capturing. Should be
max(f->start[0] + f->count[0], start1 + f->count[1]) * 2
when capturing both fields. But for compatibility we must
pretend the VBI and video capture window may overlap,
so end = start + 1, the lowest possible value, times two
because vbi_fmt.end counts field lines times two. */
end = max(frt->fmt.vbi.start[0], start1) * 2 + 2;
mutex_lock(&fh->vbi.vb_lock);
fh->vbi_fmt.fmt = frt->fmt.vbi;
fh->vbi_fmt.tvnorm = tvnorm;
fh->vbi_fmt.end = end;
mutex_unlock(&fh->vbi.vb_lock);
rc = 0;
fail:
mutex_unlock(&btv->lock);
return rc;
}
int bttv_g_fmt_vbi_cap(struct file *file, void *f, struct v4l2_format *frt)
{
struct bttv_fh *fh = f;
const struct bttv_tvnorm *tvnorm;
frt->fmt.vbi = fh->vbi_fmt.fmt;
tvnorm = &bttv_tvnorms[fh->btv->tvnorm];
if (tvnorm != fh->vbi_fmt.tvnorm) {
__s32 max_end;
unsigned int i;
/* As in vbi_buffer_prepare() this imitates the
behaviour of earlier driver versions after video
standard changes, with default parameters anyway. */
max_end = (tvnorm->cropcap.bounds.top
+ tvnorm->cropcap.bounds.height) >> 1;
frt->fmt.vbi.sampling_rate = tvnorm->Fsc;
for (i = 0; i < 2; ++i) {
__s32 new_start;
new_start = frt->fmt.vbi.start[i]
+ tvnorm->vbistart[i]
- fh->vbi_fmt.tvnorm->vbistart[i];
frt->fmt.vbi.start[i] = min(new_start, max_end - 1);
frt->fmt.vbi.count[i] =
min((__s32) frt->fmt.vbi.count[i],
max_end - frt->fmt.vbi.start[i]);
max_end += tvnorm->vbistart[1]
- tvnorm->vbistart[0];
}
}
return 0;
}
void bttv_vbi_fmt_reset(struct bttv_vbi_fmt *f, unsigned int norm)
{
const struct bttv_tvnorm *tvnorm;
unsigned int real_samples_per_line;
unsigned int real_count;
tvnorm = &bttv_tvnorms[norm];
f->fmt.sampling_rate = tvnorm->Fsc;
f->fmt.samples_per_line = VBI_BPL;
f->fmt.sample_format = V4L2_PIX_FMT_GREY;
f->fmt.offset = VBI_OFFSET;
f->fmt.start[0] = tvnorm->vbistart[0];
f->fmt.start[1] = tvnorm->vbistart[1];
f->fmt.count[0] = VBI_DEFLINES;
f->fmt.count[1] = VBI_DEFLINES;
f->fmt.flags = 0;
f->fmt.reserved[0] = 0;
f->fmt.reserved[1] = 0;
/* For compatibility the buffer size must be 2 * VBI_DEFLINES *
VBI_BPL regardless of the current video standard. */
real_samples_per_line = 1024 + tvnorm->vbipack * 4;
real_count = ((tvnorm->cropcap.defrect.top >> 1)
- tvnorm->vbistart[0]);
BUG_ON(real_samples_per_line > VBI_BPL);
BUG_ON(real_count > VBI_DEFLINES);
f->tvnorm = tvnorm;
/* See bttv_vbi_fmt_set(). */
f->end = tvnorm->vbistart[0] * 2 + 2;
}
/* ----------------------------------------------------------------------- */
/*
* Local variables:
* c-basic-offset: 8
* End:
*/

View file

@ -0,0 +1,385 @@
/*
*
* bttv - Bt848 frame grabber driver
*
* card ID's and external interfaces of the bttv driver
* basically stuff needed by other drivers (i2c, lirc, ...)
* and is supported not to change much over time.
*
* Copyright (C) 1996,97 Ralph Metzler (rjkm@thp.uni-koeln.de)
* (c) 1999,2000 Gerd Knorr <kraxel@goldbach.in-berlin.de>
*
*/
#ifndef _BTTV_H_
#define _BTTV_H_
#include <linux/videodev2.h>
#include <linux/i2c.h>
#include <media/v4l2-device.h>
#include <media/i2c-addr.h>
#include <media/tuner.h>
/* ---------------------------------------------------------- */
/* exported by bttv-cards.c */
#define BTTV_BOARD_UNKNOWN 0x00
#define BTTV_BOARD_MIRO 0x01
#define BTTV_BOARD_HAUPPAUGE 0x02
#define BTTV_BOARD_STB 0x03
#define BTTV_BOARD_INTEL 0x04
#define BTTV_BOARD_DIAMOND 0x05
#define BTTV_BOARD_AVERMEDIA 0x06
#define BTTV_BOARD_MATRIX_VISION 0x07
#define BTTV_BOARD_FLYVIDEO 0x08
#define BTTV_BOARD_TURBOTV 0x09
#define BTTV_BOARD_HAUPPAUGE878 0x0a
#define BTTV_BOARD_MIROPRO 0x0b
#define BTTV_BOARD_ADSTECH_TV 0x0c
#define BTTV_BOARD_AVERMEDIA98 0x0d
#define BTTV_BOARD_VHX 0x0e
#define BTTV_BOARD_ZOLTRIX 0x0f
#define BTTV_BOARD_PIXVIEWPLAYTV 0x10
#define BTTV_BOARD_WINVIEW_601 0x11
#define BTTV_BOARD_AVEC_INTERCAP 0x12
#define BTTV_BOARD_LIFE_FLYKIT 0x13
#define BTTV_BOARD_CEI_RAFFLES 0x14
#define BTTV_BOARD_CONFERENCETV 0x15
#define BTTV_BOARD_PHOEBE_TVMAS 0x16
#define BTTV_BOARD_MODTEC_205 0x17
#define BTTV_BOARD_MAGICTVIEW061 0x18
#define BTTV_BOARD_VOBIS_BOOSTAR 0x19
#define BTTV_BOARD_HAUPPAUG_WCAM 0x1a
#define BTTV_BOARD_MAXI 0x1b
#define BTTV_BOARD_TERRATV 0x1c
#define BTTV_BOARD_PXC200 0x1d
#define BTTV_BOARD_FLYVIDEO_98 0x1e
#define BTTV_BOARD_IPROTV 0x1f
#define BTTV_BOARD_INTEL_C_S_PCI 0x20
#define BTTV_BOARD_TERRATVALUE 0x21
#define BTTV_BOARD_WINFAST2000 0x22
#define BTTV_BOARD_CHRONOS_VS2 0x23
#define BTTV_BOARD_TYPHOON_TVIEW 0x24
#define BTTV_BOARD_PXELVWPLTVPRO 0x25
#define BTTV_BOARD_MAGICTVIEW063 0x26
#define BTTV_BOARD_PINNACLE 0x27
#define BTTV_BOARD_STB2 0x28
#define BTTV_BOARD_AVPHONE98 0x29
#define BTTV_BOARD_PV951 0x2a
#define BTTV_BOARD_ONAIR_TV 0x2b
#define BTTV_BOARD_SIGMA_TVII_FM 0x2c
#define BTTV_BOARD_MATRIX_VISION2 0x2d
#define BTTV_BOARD_ZOLTRIX_GENIE 0x2e
#define BTTV_BOARD_TERRATVRADIO 0x2f
#define BTTV_BOARD_DYNALINK 0x30
#define BTTV_BOARD_GVBCTV3PCI 0x31
#define BTTV_BOARD_PXELVWPLTVPAK 0x32
#define BTTV_BOARD_EAGLE 0x33
#define BTTV_BOARD_PINNACLEPRO 0x34
#define BTTV_BOARD_TVIEW_RDS_FM 0x35
#define BTTV_BOARD_LIFETEC_9415 0x36
#define BTTV_BOARD_BESTBUY_EASYTV 0x37
#define BTTV_BOARD_FLYVIDEO_98FM 0x38
#define BTTV_BOARD_GRANDTEC 0x39
#define BTTV_BOARD_ASKEY_CPH060 0x3a
#define BTTV_BOARD_ASKEY_CPH03X 0x3b
#define BTTV_BOARD_MM100PCTV 0x3c
#define BTTV_BOARD_GMV1 0x3d
#define BTTV_BOARD_BESTBUY_EASYTV2 0x3e
#define BTTV_BOARD_ATI_TVWONDER 0x3f
#define BTTV_BOARD_ATI_TVWONDERVE 0x40
#define BTTV_BOARD_FLYVIDEO2000 0x41
#define BTTV_BOARD_TERRATVALUER 0x42
#define BTTV_BOARD_GVBCTV4PCI 0x43
#define BTTV_BOARD_VOODOOTV_FM 0x44
#define BTTV_BOARD_AIMMS 0x45
#define BTTV_BOARD_PV_BT878P_PLUS 0x46
#define BTTV_BOARD_FLYVIDEO98EZ 0x47
#define BTTV_BOARD_PV_BT878P_9B 0x48
#define BTTV_BOARD_SENSORAY311_611 0x49
#define BTTV_BOARD_RV605 0x4a
#define BTTV_BOARD_POWERCLR_MTV878 0x4b
#define BTTV_BOARD_WINDVR 0x4c
#define BTTV_BOARD_GRANDTEC_MULTI 0x4d
#define BTTV_BOARD_KWORLD 0x4e
#define BTTV_BOARD_DSP_TCVIDEO 0x4f
#define BTTV_BOARD_HAUPPAUGEPVR 0x50
#define BTTV_BOARD_GVBCTV5PCI 0x51
#define BTTV_BOARD_OSPREY1x0 0x52
#define BTTV_BOARD_OSPREY1x0_848 0x53
#define BTTV_BOARD_OSPREY101_848 0x54
#define BTTV_BOARD_OSPREY1x1 0x55
#define BTTV_BOARD_OSPREY1x1_SVID 0x56
#define BTTV_BOARD_OSPREY2xx 0x57
#define BTTV_BOARD_OSPREY2x0_SVID 0x58
#define BTTV_BOARD_OSPREY2x0 0x59
#define BTTV_BOARD_OSPREY500 0x5a
#define BTTV_BOARD_OSPREY540 0x5b
#define BTTV_BOARD_OSPREY2000 0x5c
#define BTTV_BOARD_IDS_EAGLE 0x5d
#define BTTV_BOARD_PINNACLESAT 0x5e
#define BTTV_BOARD_FORMAC_PROTV 0x5f
#define BTTV_BOARD_MACHTV 0x60
#define BTTV_BOARD_EURESYS_PICOLO 0x61
#define BTTV_BOARD_PV150 0x62
#define BTTV_BOARD_AD_TVK503 0x63
#define BTTV_BOARD_HERCULES_SM_TV 0x64
#define BTTV_BOARD_PACETV 0x65
#define BTTV_BOARD_IVC200 0x66
#define BTTV_BOARD_XGUARD 0x67
#define BTTV_BOARD_NEBULA_DIGITV 0x68
#define BTTV_BOARD_PV143 0x69
#define BTTV_BOARD_VD009X1_VD011_MINIDIN 0x6a
#define BTTV_BOARD_VD009X1_VD011_COMBI 0x6b
#define BTTV_BOARD_VD009_MINIDIN 0x6c
#define BTTV_BOARD_VD009_COMBI 0x6d
#define BTTV_BOARD_IVC100 0x6e
#define BTTV_BOARD_IVC120 0x6f
#define BTTV_BOARD_PC_HDTV 0x70
#define BTTV_BOARD_TWINHAN_DST 0x71
#define BTTV_BOARD_WINFASTVC100 0x72
#define BTTV_BOARD_TEV560 0x73
#define BTTV_BOARD_SIMUS_GVC1100 0x74
#define BTTV_BOARD_NGSTV_PLUS 0x75
#define BTTV_BOARD_LMLBT4 0x76
#define BTTV_BOARD_TEKRAM_M205 0x77
#define BTTV_BOARD_CONTVFMI 0x78
#define BTTV_BOARD_PICOLO_TETRA_CHIP 0x79
#define BTTV_BOARD_SPIRIT_TV 0x7a
#define BTTV_BOARD_AVDVBT_771 0x7b
#define BTTV_BOARD_AVDVBT_761 0x7c
#define BTTV_BOARD_MATRIX_VISIONSQ 0x7d
#define BTTV_BOARD_MATRIX_VISIONSLC 0x7e
#define BTTV_BOARD_APAC_VIEWCOMP 0x7f
#define BTTV_BOARD_DVICO_DVBT_LITE 0x80
#define BTTV_BOARD_VGEAR_MYVCD 0x81
#define BTTV_BOARD_SUPER_TV 0x82
#define BTTV_BOARD_TIBET_CS16 0x83
#define BTTV_BOARD_KODICOM_4400R 0x84
#define BTTV_BOARD_KODICOM_4400R_SL 0x85
#define BTTV_BOARD_ADLINK_RTV24 0x86
#define BTTV_BOARD_DVICO_FUSIONHDTV_5_LITE 0x87
#define BTTV_BOARD_ACORP_Y878F 0x88
#define BTTV_BOARD_CONCEPTRONIC_CTVFMI2 0x89
#define BTTV_BOARD_PV_BT878P_2E 0x8a
#define BTTV_BOARD_PV_M4900 0x8b
#define BTTV_BOARD_OSPREY440 0x8c
#define BTTV_BOARD_ASOUND_SKYEYE 0x8d
#define BTTV_BOARD_SABRENT_TVFM 0x8e
#define BTTV_BOARD_HAUPPAUGE_IMPACTVCB 0x8f
#define BTTV_BOARD_MACHTV_MAGICTV 0x90
#define BTTV_BOARD_SSAI_SECURITY 0x91
#define BTTV_BOARD_SSAI_ULTRASOUND 0x92
#define BTTV_BOARD_VOODOOTV_200 0x93
#define BTTV_BOARD_DVICO_FUSIONHDTV_2 0x94
#define BTTV_BOARD_TYPHOON_TVTUNERPCI 0x95
#define BTTV_BOARD_GEOVISION_GV600 0x96
#define BTTV_BOARD_KOZUMI_KTV_01C 0x97
#define BTTV_BOARD_ENLTV_FM_2 0x98
#define BTTV_BOARD_VD012 0x99
#define BTTV_BOARD_VD012_X1 0x9a
#define BTTV_BOARD_VD012_X2 0x9b
#define BTTV_BOARD_IVCE8784 0x9c
#define BTTV_BOARD_GEOVISION_GV800S 0x9d
#define BTTV_BOARD_GEOVISION_GV800S_SL 0x9e
#define BTTV_BOARD_PV183 0x9f
#define BTTV_BOARD_TVT_TD3116 0xa0
#define BTTV_BOARD_APOSONIC_WDVR 0xa1
#define BTTV_BOARD_ADLINK_MPG24 0xa2
#define BTTV_BOARD_BT848_CAP_14 0xa3
#define BTTV_BOARD_CYBERVISION_CV06 0xa4
#define BTTV_BOARD_KWORLD_VSTREAM_XPERT 0xa5
#define BTTV_BOARD_PCI_8604PW 0xa6
/* more card-specific defines */
#define PT2254_L_CHANNEL 0x10
#define PT2254_R_CHANNEL 0x08
#define PT2254_DBS_IN_2 0x400
#define PT2254_DBS_IN_10 0x20000
#define WINVIEW_PT2254_CLK 0x40
#define WINVIEW_PT2254_DATA 0x20
#define WINVIEW_PT2254_STROBE 0x80
struct bttv_core {
/* device structs */
struct v4l2_device v4l2_dev;
struct pci_dev *pci;
struct i2c_adapter i2c_adap;
struct list_head subs; /* struct bttv_sub_device */
/* device config */
unsigned int nr; /* dev nr (for printk("bttv%d: ..."); */
unsigned int type; /* card type (pointer into tvcards[]) */
};
struct bttv;
struct tvcard {
char *name;
void (*volume_gpio)(struct bttv *btv, __u16 volume);
void (*audio_mode_gpio)(struct bttv *btv, struct v4l2_tuner *tuner, int set);
void (*muxsel_hook)(struct bttv *btv, unsigned int input);
/* MUX bits for each input, two bits per input starting with the LSB */
u32 muxsel; /* Use MUXSEL() to set */
u32 gpiomask;
u32 gpiomux[4]; /* Tuner, Radio, external, internal */
u32 gpiomute; /* GPIO mute setting */
u32 gpiomask2; /* GPIO MUX mask */
unsigned int tuner_type;
u8 tuner_addr;
u8 video_inputs; /* Number of inputs */
unsigned int svhs:4; /* Which input is s-video */
#define NO_SVHS 15
unsigned int pll:2;
#define PLL_NONE 0
#define PLL_28 1
#define PLL_35 2
#define PLL_14 3
/* i2c audio flags */
unsigned int no_msp34xx:1;
unsigned int no_tda7432:1;
unsigned int msp34xx_alt:1;
/* Note: currently no card definition needs to mark the presence
of a RDS saa6588 chip. If this is ever needed, then add a new
'has_saa6588' bit here. */
unsigned int no_video:1; /* video pci function is unused */
unsigned int has_dvb:1;
unsigned int has_remote:1;
unsigned int has_radio:1;
unsigned int has_dig_in:1; /* Has digital input (always last input) */
unsigned int no_gpioirq:1;
};
extern struct tvcard bttv_tvcards[];
/*
* This bit of cpp voodoo is used to create a macro with a variable number of
* arguments (1 to 16). It will pack each argument into a word two bits at a
* time. It can't be a function because it needs to be compile time constant to
* initialize structures. Since each argument must fit in two bits, it's ok
* that they are changed to octal. One should not use hex number, macros, or
* anything else with this macro. Just use plain integers from 0 to 3.
*/
#define _MUXSELf(a) 0##a << 30
#define _MUXSELe(a, b...) 0##a << 28 | _MUXSELf(b)
#define _MUXSELd(a, b...) 0##a << 26 | _MUXSELe(b)
#define _MUXSELc(a, b...) 0##a << 24 | _MUXSELd(b)
#define _MUXSELb(a, b...) 0##a << 22 | _MUXSELc(b)
#define _MUXSELa(a, b...) 0##a << 20 | _MUXSELb(b)
#define _MUXSEL9(a, b...) 0##a << 18 | _MUXSELa(b)
#define _MUXSEL8(a, b...) 0##a << 16 | _MUXSEL9(b)
#define _MUXSEL7(a, b...) 0##a << 14 | _MUXSEL8(b)
#define _MUXSEL6(a, b...) 0##a << 12 | _MUXSEL7(b)
#define _MUXSEL5(a, b...) 0##a << 10 | _MUXSEL6(b)
#define _MUXSEL4(a, b...) 0##a << 8 | _MUXSEL5(b)
#define _MUXSEL3(a, b...) 0##a << 6 | _MUXSEL4(b)
#define _MUXSEL2(a, b...) 0##a << 4 | _MUXSEL3(b)
#define _MUXSEL1(a, b...) 0##a << 2 | _MUXSEL2(b)
#define MUXSEL(a, b...) (a | _MUXSEL1(b))
/* identification / initialization of the card */
extern void bttv_idcard(struct bttv *btv);
extern void bttv_init_card1(struct bttv *btv);
extern void bttv_init_card2(struct bttv *btv);
extern void bttv_init_tuner(struct bttv *btv);
/* card-specific funtions */
extern void tea5757_set_freq(struct bttv *btv, unsigned short freq);
extern u32 bttv_tda9880_setnorm(struct bttv *btv, u32 gpiobits);
/* extra tweaks for some chipsets */
extern void bttv_check_chipset(void);
extern int bttv_handle_chipset(struct bttv *btv);
/* ---------------------------------------------------------- */
/* exported by bttv-if.c */
/* this obsolete -- please use the sysfs-based
interface below for new code */
extern struct pci_dev* bttv_get_pcidev(unsigned int card);
/* sets GPOE register (BT848_GPIO_OUT_EN) to new value:
data | (current_GPOE_value & ~mask)
returns negative value if error occurred
*/
extern int bttv_gpio_enable(unsigned int card,
unsigned long mask, unsigned long data);
/* fills data with GPDATA register contents
returns negative value if error occurred
*/
extern int bttv_read_gpio(unsigned int card, unsigned long *data);
/* sets GPDATA register to new value:
(data & mask) | (current_GPDATA_value & ~mask)
returns negative value if error occurred
*/
extern int bttv_write_gpio(unsigned int card,
unsigned long mask, unsigned long data);
/* ---------------------------------------------------------- */
/* sysfs/driver-moded based gpio access interface */
struct bttv_sub_device {
struct device dev;
struct bttv_core *core;
struct list_head list;
};
#define to_bttv_sub_dev(x) container_of((x), struct bttv_sub_device, dev)
struct bttv_sub_driver {
struct device_driver drv;
char wanted[20];
int (*probe)(struct bttv_sub_device *sub);
void (*remove)(struct bttv_sub_device *sub);
};
#define to_bttv_sub_drv(x) container_of((x), struct bttv_sub_driver, drv)
int bttv_sub_register(struct bttv_sub_driver *drv, char *wanted);
int bttv_sub_unregister(struct bttv_sub_driver *drv);
/* gpio access functions */
void bttv_gpio_inout(struct bttv_core *core, u32 mask, u32 outbits);
u32 bttv_gpio_read(struct bttv_core *core);
void bttv_gpio_write(struct bttv_core *core, u32 value);
void bttv_gpio_bits(struct bttv_core *core, u32 mask, u32 bits);
#define gpio_inout(mask,bits) bttv_gpio_inout(&btv->c, mask, bits)
#define gpio_read() bttv_gpio_read(&btv->c)
#define gpio_write(value) bttv_gpio_write(&btv->c, value)
#define gpio_bits(mask,bits) bttv_gpio_bits(&btv->c, mask, bits)
/* ---------------------------------------------------------- */
/* i2c */
#define bttv_call_all(btv, o, f, args...) \
v4l2_device_call_all(&btv->c.v4l2_dev, 0, o, f, ##args)
#define bttv_call_all_err(btv, o, f, args...) \
v4l2_device_call_until_err(&btv->c.v4l2_dev, 0, o, f, ##args)
extern int bttv_I2CRead(struct bttv *btv, unsigned char addr, char *probe_for);
extern int bttv_I2CWrite(struct bttv *btv, unsigned char addr, unsigned char b1,
unsigned char b2, int both);
extern void bttv_readee(struct bttv *btv, unsigned char *eedata, int addr);
extern int bttv_input_init(struct bttv *dev);
extern void bttv_input_fini(struct bttv *dev);
extern void bttv_input_irq(struct bttv *dev);
#endif /* _BTTV_H_ */
/*
* Local variables:
* c-basic-offset: 8
* End:
*/

View file

@ -0,0 +1,539 @@
/*
bttv - Bt848 frame grabber driver
bttv's *private* header file -- nobody other than bttv itself
should ever include this file.
(c) 2000-2002 Gerd Knorr <kraxel@bytesex.org>
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 _BTTVP_H_
#define _BTTVP_H_
#include <linux/types.h>
#include <linux/wait.h>
#include <linux/i2c.h>
#include <linux/i2c-algo-bit.h>
#include <linux/pci.h>
#include <linux/input.h>
#include <linux/mutex.h>
#include <linux/scatterlist.h>
#include <linux/device.h>
#include <asm/io.h>
#include <media/v4l2-common.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-fh.h>
#include <media/videobuf-dma-sg.h>
#include <media/tveeprom.h>
#include <media/rc-core.h>
#include <media/ir-kbd-i2c.h>
#include "bt848.h"
#include "bttv.h"
#include "btcx-risc.h"
#ifdef __KERNEL__
#define FORMAT_FLAGS_DITHER 0x01
#define FORMAT_FLAGS_PACKED 0x02
#define FORMAT_FLAGS_PLANAR 0x04
#define FORMAT_FLAGS_RAW 0x08
#define FORMAT_FLAGS_CrCb 0x10
#define RISC_SLOT_O_VBI 4
#define RISC_SLOT_O_FIELD 6
#define RISC_SLOT_E_VBI 10
#define RISC_SLOT_E_FIELD 12
#define RISC_SLOT_LOOP 14
#define RESOURCE_OVERLAY 1
#define RESOURCE_VIDEO_STREAM 2
#define RESOURCE_VBI 4
#define RESOURCE_VIDEO_READ 8
#define RAW_LINES 640
#define RAW_BPL 1024
#define UNSET (-1U)
/* Min. value in VDELAY register. */
#define MIN_VDELAY 2
/* Even to get Cb first, odd for Cr. */
#define MAX_HDELAY (0x3FF & -2)
/* Limits scaled width, which must be a multiple of 4. */
#define MAX_HACTIVE (0x3FF & -4)
#define BTTV_NORMS (\
V4L2_STD_PAL | V4L2_STD_PAL_N | \
V4L2_STD_PAL_Nc | V4L2_STD_SECAM | \
V4L2_STD_NTSC | V4L2_STD_PAL_M | \
V4L2_STD_PAL_60)
/* ---------------------------------------------------------- */
struct bttv_tvnorm {
int v4l2_id;
char *name;
u32 Fsc;
u16 swidth, sheight; /* scaled standard width, height */
u16 totalwidth;
u8 adelay, bdelay, iform;
u32 scaledtwidth;
u16 hdelayx1, hactivex1;
u16 vdelay;
u8 vbipack;
u16 vtotal;
int sram;
/* ITU-R frame line number of the first VBI line we can
capture, of the first and second field. The last possible line
is determined by cropcap.bounds. */
u16 vbistart[2];
/* Horizontally this counts fCLKx1 samples following the leading
edge of the horizontal sync pulse, vertically ITU-R frame line
numbers of the first field times two (2, 4, 6, ... 524 or 624). */
struct v4l2_cropcap cropcap;
};
extern const struct bttv_tvnorm bttv_tvnorms[];
struct bttv_format {
char *name;
int fourcc; /* video4linux 2 */
int btformat; /* BT848_COLOR_FMT_* */
int btswap; /* BT848_COLOR_CTL_* */
int depth; /* bit/pixel */
int flags;
int hshift,vshift; /* for planar modes */
};
struct bttv_ir {
struct rc_dev *dev;
struct timer_list timer;
char name[32];
char phys[32];
/* Usual gpio signalling */
u32 mask_keycode;
u32 mask_keydown;
u32 mask_keyup;
u32 polling;
u32 last_gpio;
int shift_by;
int rc5_remote_gap;
/* RC5 gpio */
bool rc5_gpio; /* Is RC5 legacy GPIO enabled? */
u32 last_bit; /* last raw bit seen */
u32 code; /* raw code under construction */
struct timeval base_time; /* time of last seen code */
bool active; /* building raw code */
};
/* ---------------------------------------------------------- */
struct bttv_geometry {
u8 vtc,crop,comb;
u16 width,hscale,hdelay;
u16 sheight,vscale,vdelay,vtotal;
};
struct bttv_buffer {
/* common v4l buffer stuff -- must be first */
struct videobuf_buffer vb;
/* bttv specific */
const struct bttv_format *fmt;
unsigned int tvnorm;
int btformat;
int btswap;
struct bttv_geometry geo;
struct btcx_riscmem top;
struct btcx_riscmem bottom;
struct v4l2_rect crop;
unsigned int vbi_skip[2];
unsigned int vbi_count[2];
};
struct bttv_buffer_set {
struct bttv_buffer *top; /* top field buffer */
struct bttv_buffer *bottom; /* bottom field buffer */
unsigned int top_irq;
unsigned int frame_irq;
};
struct bttv_overlay {
unsigned int tvnorm;
struct v4l2_rect w;
enum v4l2_field field;
struct v4l2_clip *clips;
int nclips;
int setup_ok;
};
struct bttv_vbi_fmt {
struct v4l2_vbi_format fmt;
/* fmt.start[] and count[] refer to this video standard. */
const struct bttv_tvnorm *tvnorm;
/* Earliest possible start of video capturing with this
v4l2_vbi_format, in struct bttv_crop.rect units. */
__s32 end;
};
/* bttv-vbi.c */
void bttv_vbi_fmt_reset(struct bttv_vbi_fmt *f, unsigned int norm);
struct bttv_crop {
/* A cropping rectangle in struct bttv_tvnorm.cropcap units. */
struct v4l2_rect rect;
/* Scaled image size limits with this crop rect. Divide
max_height, but not min_height, by two when capturing
single fields. See also bttv_crop_reset() and
bttv_crop_adjust() in bttv-driver.c. */
__s32 min_scaled_width;
__s32 min_scaled_height;
__s32 max_scaled_width;
__s32 max_scaled_height;
};
struct bttv_fh {
/* This must be the first field in this struct */
struct v4l2_fh fh;
struct bttv *btv;
int resources;
enum v4l2_buf_type type;
/* video capture */
struct videobuf_queue cap;
const struct bttv_format *fmt;
int width;
int height;
/* video overlay */
const struct bttv_format *ovfmt;
struct bttv_overlay ov;
/* Application called VIDIOC_S_CROP. */
int do_crop;
/* vbi capture */
struct videobuf_queue vbi;
/* Current VBI capture window as seen through this fh (cannot
be global for compatibility with earlier drivers). Protected
by struct bttv.lock and struct bttv_fh.vbi.lock. */
struct bttv_vbi_fmt vbi_fmt;
};
/* ---------------------------------------------------------- */
/* bttv-risc.c */
/* risc code generators - capture */
int bttv_risc_packed(struct bttv *btv, struct btcx_riscmem *risc,
struct scatterlist *sglist,
unsigned int offset, unsigned int bpl,
unsigned int pitch, unsigned int skip_lines,
unsigned int store_lines);
/* control dma register + risc main loop */
void bttv_set_dma(struct bttv *btv, int override);
int bttv_risc_init_main(struct bttv *btv);
int bttv_risc_hook(struct bttv *btv, int slot, struct btcx_riscmem *risc,
int irqflags);
/* capture buffer handling */
int bttv_buffer_risc(struct bttv *btv, struct bttv_buffer *buf);
int bttv_buffer_activate_video(struct bttv *btv,
struct bttv_buffer_set *set);
int bttv_buffer_activate_vbi(struct bttv *btv,
struct bttv_buffer *vbi);
void bttv_dma_free(struct videobuf_queue *q, struct bttv *btv,
struct bttv_buffer *buf);
/* overlay handling */
int bttv_overlay_risc(struct bttv *btv, struct bttv_overlay *ov,
const struct bttv_format *fmt,
struct bttv_buffer *buf);
/* ---------------------------------------------------------- */
/* bttv-vbi.c */
int bttv_try_fmt_vbi_cap(struct file *file, void *fh, struct v4l2_format *f);
int bttv_g_fmt_vbi_cap(struct file *file, void *fh, struct v4l2_format *f);
int bttv_s_fmt_vbi_cap(struct file *file, void *fh, struct v4l2_format *f);
extern struct videobuf_queue_ops bttv_vbi_qops;
/* ---------------------------------------------------------- */
/* bttv-gpio.c */
extern struct bus_type bttv_sub_bus_type;
int bttv_sub_add_device(struct bttv_core *core, char *name);
int bttv_sub_del_devices(struct bttv_core *core);
/* ---------------------------------------------------------- */
/* bttv-cards.c */
extern int no_overlay;
/* ---------------------------------------------------------- */
/* bttv-input.c */
extern void init_bttv_i2c_ir(struct bttv *btv);
/* ---------------------------------------------------------- */
/* bttv-i2c.c */
extern int init_bttv_i2c(struct bttv *btv);
extern int fini_bttv_i2c(struct bttv *btv);
/* ---------------------------------------------------------- */
/* bttv-driver.c */
/* insmod options */
extern unsigned int bttv_verbose;
extern unsigned int bttv_debug;
extern unsigned int bttv_gpio;
extern void bttv_gpio_tracking(struct bttv *btv, char *comment);
#define dprintk(fmt, ...) \
do { \
if (bttv_debug >= 1) \
pr_debug(fmt, ##__VA_ARGS__); \
} while (0)
#define dprintk_cont(fmt, ...) \
do { \
if (bttv_debug >= 1) \
pr_cont(fmt, ##__VA_ARGS__); \
} while (0)
#define d2printk(fmt, ...) \
do { \
if (bttv_debug >= 2) \
printk(fmt, ##__VA_ARGS__); \
} while (0)
#define BTTV_MAX_FBUF 0x208000
#define BTTV_TIMEOUT msecs_to_jiffies(500) /* 0.5 seconds */
#define BTTV_FREE_IDLE msecs_to_jiffies(1000) /* one second */
struct bttv_pll_info {
unsigned int pll_ifreq; /* PLL input frequency */
unsigned int pll_ofreq; /* PLL output frequency */
unsigned int pll_crystal; /* Crystal used for input */
unsigned int pll_current; /* Currently programmed ofreq */
};
/* for gpio-connected remote control */
struct bttv_input {
struct input_dev *dev;
char name[32];
char phys[32];
u32 mask_keycode;
u32 mask_keydown;
};
struct bttv_suspend_state {
u32 gpio_enable;
u32 gpio_data;
int disabled;
int loop_irq;
struct bttv_buffer_set video;
struct bttv_buffer *vbi;
};
struct bttv {
struct bttv_core c;
/* pci device config */
unsigned short id;
unsigned char revision;
unsigned char __iomem *bt848_mmio; /* pointer to mmio */
/* card configuration info */
unsigned int cardid; /* pci subsystem id (bt878 based ones) */
unsigned int tuner_type; /* tuner chip type */
unsigned int tda9887_conf;
unsigned int svhs, dig;
unsigned int has_saa6588:1;
struct bttv_pll_info pll;
int triton1;
int gpioirq;
int use_i2c_hw;
/* old gpio interface */
int shutdown;
void (*volume_gpio)(struct bttv *btv, __u16 volume);
void (*audio_mode_gpio)(struct bttv *btv, struct v4l2_tuner *tuner, int set);
/* new gpio interface */
spinlock_t gpio_lock;
/* i2c layer */
struct i2c_algo_bit_data i2c_algo;
struct i2c_client i2c_client;
int i2c_state, i2c_rc;
int i2c_done;
wait_queue_head_t i2c_queue;
struct v4l2_subdev *sd_msp34xx;
struct v4l2_subdev *sd_tvaudio;
struct v4l2_subdev *sd_tda7432;
/* video4linux (1) */
struct video_device *video_dev;
struct video_device *radio_dev;
struct video_device *vbi_dev;
/* controls */
struct v4l2_ctrl_handler ctrl_handler;
struct v4l2_ctrl_handler radio_ctrl_handler;
/* infrared remote */
int has_remote;
struct bttv_ir *remote;
/* I2C remote data */
struct IR_i2c_init_data init_data;
/* locking */
spinlock_t s_lock;
struct mutex lock;
int resources;
/* video state */
unsigned int input;
unsigned int audio_input;
unsigned int mute;
unsigned long tv_freq;
unsigned int tvnorm;
v4l2_std_id std;
int hue, contrast, bright, saturation;
struct v4l2_framebuffer fbuf;
unsigned int field_count;
/* various options */
int opt_combfilter;
int opt_automute;
int opt_vcr_hack;
int opt_uv_ratio;
/* radio data/state */
int has_radio;
int has_radio_tuner;
int radio_user;
int radio_uses_msp_demodulator;
unsigned long radio_freq;
/* miro/pinnacle + Aimslab VHX
philips matchbox (tea5757 radio tuner) support */
int has_matchbox;
int mbox_we;
int mbox_data;
int mbox_clk;
int mbox_most;
int mbox_mask;
/* ISA stuff (Terratec Active Radio Upgrade) */
int mbox_ior;
int mbox_iow;
int mbox_csel;
/* switch status for multi-controller cards */
char sw_status[4];
/* risc memory management data
- must acquire s_lock before changing these
- only the irq handler is supported to touch top + bottom + vcurr */
struct btcx_riscmem main;
struct bttv_buffer *screen; /* overlay */
struct list_head capture; /* video capture queue */
struct list_head vcapture; /* vbi capture queue */
struct bttv_buffer_set curr; /* active buffers */
struct bttv_buffer *cvbi; /* active vbi buffer */
int loop_irq;
int new_input;
unsigned long cap_ctl;
unsigned long dma_on;
struct timer_list timeout;
struct bttv_suspend_state state;
/* stats */
unsigned int errors;
unsigned int framedrop;
unsigned int irq_total;
unsigned int irq_me;
unsigned int users;
struct bttv_fh init;
/* used to make dvb-bt8xx autoloadable */
struct work_struct request_module_wk;
/* Default (0) and current (1) video capturing and overlay
cropping parameters in bttv_tvnorm.cropcap units. Protected
by bttv.lock. */
struct bttv_crop crop[2];
/* Earliest possible start of video capturing in
bttv_tvnorm.cropcap line units. Set by check_alloc_btres()
and free_btres(). Protected by bttv.lock. */
__s32 vbi_end;
/* Latest possible end of VBI capturing (= crop[x].rect.top when
VIDEO_RESOURCES are locked). Set by check_alloc_btres()
and free_btres(). Protected by bttv.lock. */
__s32 crop_start;
};
static inline struct bttv *to_bttv(struct v4l2_device *v4l2_dev)
{
return container_of(v4l2_dev, struct bttv, c.v4l2_dev);
}
/* our devices */
#define BTTV_MAX 32
extern unsigned int bttv_num;
extern struct bttv *bttvs[BTTV_MAX];
static inline unsigned int bttv_muxsel(const struct bttv *btv,
unsigned int input)
{
return (bttv_tvcards[btv->c.type].muxsel >> (input * 2)) & 3;
}
#endif
#define btwrite(dat,adr) writel((dat), btv->bt848_mmio+(adr))
#define btread(adr) readl(btv->bt848_mmio+(adr))
#define btand(dat,adr) btwrite((dat) & btread(adr), adr)
#define btor(dat,adr) btwrite((dat) | btread(adr), adr)
#define btaor(dat,mask,adr) btwrite((dat) | ((mask) & btread(adr)), adr)
#endif /* _BTTVP_H_ */
/*
* Local variables:
* c-basic-offset: 8
* End:
*/

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,724 @@
/*
CA-driver for TwinHan DST Frontend/Card
Copyright (C) 2004, 2005 Manu Abraham (manu@kromtek.com)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/mutex.h>
#include <linux/string.h>
#include <linux/dvb/ca.h>
#include "dvbdev.h"
#include "dvb_frontend.h"
#include "dst_ca.h"
#include "dst_common.h"
#define DST_CA_ERROR 0
#define DST_CA_NOTICE 1
#define DST_CA_INFO 2
#define DST_CA_DEBUG 3
#define dprintk(x, y, z, format, arg...) do { \
if (z) { \
if ((x > DST_CA_ERROR) && (x > y)) \
printk(KERN_ERR "%s: " format "\n", __func__ , ##arg); \
else if ((x > DST_CA_NOTICE) && (x > y)) \
printk(KERN_NOTICE "%s: " format "\n", __func__ , ##arg); \
else if ((x > DST_CA_INFO) && (x > y)) \
printk(KERN_INFO "%s: " format "\n", __func__ , ##arg); \
else if ((x > DST_CA_DEBUG) && (x > y)) \
printk(KERN_DEBUG "%s: " format "\n", __func__ , ##arg); \
} else { \
if (x > y) \
printk(format, ## arg); \
} \
} while(0)
static DEFINE_MUTEX(dst_ca_mutex);
static unsigned int verbose = 5;
module_param(verbose, int, 0644);
MODULE_PARM_DESC(verbose, "verbose startup messages, default is 1 (yes)");
/* Need some more work */
static int ca_set_slot_descr(void)
{
/* We could make this more graceful ? */
return -EOPNOTSUPP;
}
/* Need some more work */
static int ca_set_pid(void)
{
/* We could make this more graceful ? */
return -EOPNOTSUPP;
}
static void put_command_and_length(u8 *data, int command, int length)
{
data[0] = (command >> 16) & 0xff;
data[1] = (command >> 8) & 0xff;
data[2] = command & 0xff;
data[3] = length;
}
static void put_checksum(u8 *check_string, int length)
{
dprintk(verbose, DST_CA_DEBUG, 1, " Computing string checksum.");
dprintk(verbose, DST_CA_DEBUG, 1, " -> string length : 0x%02x", length);
check_string[length] = dst_check_sum (check_string, length);
dprintk(verbose, DST_CA_DEBUG, 1, " -> checksum : 0x%02x", check_string[length]);
}
static int dst_ci_command(struct dst_state* state, u8 * data, u8 *ca_string, u8 len, int read)
{
u8 reply;
mutex_lock(&state->dst_mutex);
dst_comm_init(state);
msleep(65);
if (write_dst(state, data, len)) {
dprintk(verbose, DST_CA_INFO, 1, " Write not successful, trying to recover");
dst_error_recovery(state);
goto error;
}
if ((dst_pio_disable(state)) < 0) {
dprintk(verbose, DST_CA_ERROR, 1, " DST PIO disable failed.");
goto error;
}
if (read_dst(state, &reply, GET_ACK) < 0) {
dprintk(verbose, DST_CA_INFO, 1, " Read not successful, trying to recover");
dst_error_recovery(state);
goto error;
}
if (read) {
if (! dst_wait_dst_ready(state, LONG_DELAY)) {
dprintk(verbose, DST_CA_NOTICE, 1, " 8820 not ready");
goto error;
}
if (read_dst(state, ca_string, 128) < 0) { /* Try to make this dynamic */
dprintk(verbose, DST_CA_INFO, 1, " Read not successful, trying to recover");
dst_error_recovery(state);
goto error;
}
}
mutex_unlock(&state->dst_mutex);
return 0;
error:
mutex_unlock(&state->dst_mutex);
return -EIO;
}
static int dst_put_ci(struct dst_state *state, u8 *data, int len, u8 *ca_string, int read)
{
u8 dst_ca_comm_err = 0;
while (dst_ca_comm_err < RETRIES) {
dprintk(verbose, DST_CA_NOTICE, 1, " Put Command");
if (dst_ci_command(state, data, ca_string, len, read)) { // If error
dst_error_recovery(state);
dst_ca_comm_err++; // work required here.
} else {
break;
}
}
if(dst_ca_comm_err == RETRIES)
return -1;
return 0;
}
static int ca_get_app_info(struct dst_state *state)
{
int length, str_length;
static u8 command[8] = {0x07, 0x40, 0x01, 0x00, 0x01, 0x00, 0x00, 0xff};
put_checksum(&command[0], command[0]);
if ((dst_put_ci(state, command, sizeof(command), state->messages, GET_REPLY)) < 0) {
dprintk(verbose, DST_CA_ERROR, 1, " -->dst_put_ci FAILED !");
return -1;
}
dprintk(verbose, DST_CA_INFO, 1, " -->dst_put_ci SUCCESS !");
dprintk(verbose, DST_CA_INFO, 1, " ================================ CI Module Application Info ======================================");
dprintk(verbose, DST_CA_INFO, 1, " Application Type=[%d], Application Vendor=[%d], Vendor Code=[%d]\n%s: Application info=[%s]",
state->messages[7], (state->messages[8] << 8) | state->messages[9],
(state->messages[10] << 8) | state->messages[11], __func__, (char *)(&state->messages[12]));
dprintk(verbose, DST_CA_INFO, 1, " ==================================================================================================");
// Transform dst message to correct application_info message
length = state->messages[5];
str_length = length - 6;
if (str_length < 0) {
str_length = 0;
dprintk(verbose, DST_CA_ERROR, 1, "Invalid string length returned in ca_get_app_info(). Recovering.");
}
// First, the command and length fields
put_command_and_length(&state->messages[0], CA_APP_INFO, length);
// Copy application_type, application_manufacturer and manufacturer_code
memmove(&state->messages[4], &state->messages[7], 5);
// Set string length and copy string
state->messages[9] = str_length;
memmove(&state->messages[10], &state->messages[12], str_length);
return 0;
}
static int ca_get_ca_info(struct dst_state *state)
{
int srcPtr, dstPtr, i, num_ids;
static u8 slot_command[8] = {0x07, 0x40, 0x00, 0x00, 0x02, 0x00, 0x00, 0xff};
const int in_system_id_pos = 8, out_system_id_pos = 4, in_num_ids_pos = 7;
put_checksum(&slot_command[0], slot_command[0]);
if ((dst_put_ci(state, slot_command, sizeof (slot_command), state->messages, GET_REPLY)) < 0) {
dprintk(verbose, DST_CA_ERROR, 1, " -->dst_put_ci FAILED !");
return -1;
}
dprintk(verbose, DST_CA_INFO, 1, " -->dst_put_ci SUCCESS !");
// Print raw data
dprintk(verbose, DST_CA_INFO, 0, " DST data = [");
for (i = 0; i < state->messages[0] + 1; i++) {
dprintk(verbose, DST_CA_INFO, 0, " 0x%02x", state->messages[i]);
}
dprintk(verbose, DST_CA_INFO, 0, "]\n");
// Set the command and length of the output
num_ids = state->messages[in_num_ids_pos];
if (num_ids >= 100) {
num_ids = 100;
dprintk(verbose, DST_CA_ERROR, 1, "Invalid number of ids (>100). Recovering.");
}
put_command_and_length(&state->messages[0], CA_INFO, num_ids * 2);
dprintk(verbose, DST_CA_INFO, 0, " CA_INFO = [");
srcPtr = in_system_id_pos;
dstPtr = out_system_id_pos;
for(i = 0; i < num_ids; i++) {
dprintk(verbose, DST_CA_INFO, 0, " 0x%02x%02x", state->messages[srcPtr + 0], state->messages[srcPtr + 1]);
// Append to output
state->messages[dstPtr + 0] = state->messages[srcPtr + 0];
state->messages[dstPtr + 1] = state->messages[srcPtr + 1];
srcPtr += 2;
dstPtr += 2;
}
dprintk(verbose, DST_CA_INFO, 0, "]\n");
return 0;
}
static int ca_get_slot_caps(struct dst_state *state, struct ca_caps *p_ca_caps, void __user *arg)
{
int i;
u8 slot_cap[256];
static u8 slot_command[8] = {0x07, 0x40, 0x02, 0x00, 0x02, 0x00, 0x00, 0xff};
put_checksum(&slot_command[0], slot_command[0]);
if ((dst_put_ci(state, slot_command, sizeof (slot_command), slot_cap, GET_REPLY)) < 0) {
dprintk(verbose, DST_CA_ERROR, 1, " -->dst_put_ci FAILED !");
return -1;
}
dprintk(verbose, DST_CA_NOTICE, 1, " -->dst_put_ci SUCCESS !");
/* Will implement the rest soon */
dprintk(verbose, DST_CA_INFO, 1, " Slot cap = [%d]", slot_cap[7]);
dprintk(verbose, DST_CA_INFO, 0, "===================================\n");
for (i = 0; i < slot_cap[0] + 1; i++)
dprintk(verbose, DST_CA_INFO, 0, " %d", slot_cap[i]);
dprintk(verbose, DST_CA_INFO, 0, "\n");
p_ca_caps->slot_num = 1;
p_ca_caps->slot_type = 1;
p_ca_caps->descr_num = slot_cap[7];
p_ca_caps->descr_type = 1;
if (copy_to_user(arg, p_ca_caps, sizeof (struct ca_caps)))
return -EFAULT;
return 0;
}
/* Need some more work */
static int ca_get_slot_descr(struct dst_state *state, struct ca_msg *p_ca_message, void __user *arg)
{
return -EOPNOTSUPP;
}
static int ca_get_slot_info(struct dst_state *state, struct ca_slot_info *p_ca_slot_info, void __user *arg)
{
int i;
static u8 slot_command[8] = {0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff};
u8 *slot_info = state->messages;
put_checksum(&slot_command[0], 7);
if ((dst_put_ci(state, slot_command, sizeof (slot_command), slot_info, GET_REPLY)) < 0) {
dprintk(verbose, DST_CA_ERROR, 1, " -->dst_put_ci FAILED !");
return -1;
}
dprintk(verbose, DST_CA_INFO, 1, " -->dst_put_ci SUCCESS !");
/* Will implement the rest soon */
dprintk(verbose, DST_CA_INFO, 1, " Slot info = [%d]", slot_info[3]);
dprintk(verbose, DST_CA_INFO, 0, "===================================\n");
for (i = 0; i < 8; i++)
dprintk(verbose, DST_CA_INFO, 0, " %d", slot_info[i]);
dprintk(verbose, DST_CA_INFO, 0, "\n");
if (slot_info[4] & 0x80) {
p_ca_slot_info->flags = CA_CI_MODULE_PRESENT;
p_ca_slot_info->num = 1;
p_ca_slot_info->type = CA_CI;
} else if (slot_info[4] & 0x40) {
p_ca_slot_info->flags = CA_CI_MODULE_READY;
p_ca_slot_info->num = 1;
p_ca_slot_info->type = CA_CI;
} else
p_ca_slot_info->flags = 0;
if (copy_to_user(arg, p_ca_slot_info, sizeof (struct ca_slot_info)))
return -EFAULT;
return 0;
}
static int ca_get_message(struct dst_state *state, struct ca_msg *p_ca_message, void __user *arg)
{
u8 i = 0;
u32 command = 0;
if (copy_from_user(p_ca_message, arg, sizeof (struct ca_msg)))
return -EFAULT;
if (p_ca_message->msg) {
dprintk(verbose, DST_CA_NOTICE, 1, " Message = [%*ph]",
3, p_ca_message->msg);
for (i = 0; i < 3; i++) {
command = command | p_ca_message->msg[i];
if (i < 2)
command = command << 8;
}
dprintk(verbose, DST_CA_NOTICE, 1, " Command=[0x%x]", command);
switch (command) {
case CA_APP_INFO:
memcpy(p_ca_message->msg, state->messages, 128);
if (copy_to_user(arg, p_ca_message, sizeof (struct ca_msg)) )
return -EFAULT;
break;
case CA_INFO:
memcpy(p_ca_message->msg, state->messages, 128);
if (copy_to_user(arg, p_ca_message, sizeof (struct ca_msg)) )
return -EFAULT;
break;
}
}
return 0;
}
static int handle_dst_tag(struct dst_state *state, struct ca_msg *p_ca_message, struct ca_msg *hw_buffer, u32 length)
{
if (state->dst_hw_cap & DST_TYPE_HAS_SESSION) {
hw_buffer->msg[2] = p_ca_message->msg[1]; /* MSB */
hw_buffer->msg[3] = p_ca_message->msg[2]; /* LSB */
} else {
if (length > 247) {
dprintk(verbose, DST_CA_ERROR, 1, " Message too long ! *** Bailing Out *** !");
return -1;
}
hw_buffer->msg[0] = (length & 0xff) + 7;
hw_buffer->msg[1] = 0x40;
hw_buffer->msg[2] = 0x03;
hw_buffer->msg[3] = 0x00;
hw_buffer->msg[4] = 0x03;
hw_buffer->msg[5] = length & 0xff;
hw_buffer->msg[6] = 0x00;
/*
* Need to compute length for EN50221 section 8.3.2, for the time being
* assuming 8.3.2 is not applicable
*/
memcpy(&hw_buffer->msg[7], &p_ca_message->msg[4], length);
}
return 0;
}
static int write_to_8820(struct dst_state *state, struct ca_msg *hw_buffer, u8 length, u8 reply)
{
if ((dst_put_ci(state, hw_buffer->msg, length, hw_buffer->msg, reply)) < 0) {
dprintk(verbose, DST_CA_ERROR, 1, " DST-CI Command failed.");
dprintk(verbose, DST_CA_NOTICE, 1, " Resetting DST.");
rdc_reset_state(state);
return -1;
}
dprintk(verbose, DST_CA_NOTICE, 1, " DST-CI Command success.");
return 0;
}
static u32 asn_1_decode(u8 *asn_1_array)
{
u8 length_field = 0, word_count = 0, count = 0;
u32 length = 0;
length_field = asn_1_array[0];
dprintk(verbose, DST_CA_DEBUG, 1, " Length field=[%02x]", length_field);
if (length_field < 0x80) {
length = length_field & 0x7f;
dprintk(verbose, DST_CA_DEBUG, 1, " Length=[%02x]\n", length);
} else {
word_count = length_field & 0x7f;
for (count = 0; count < word_count; count++) {
length = length << 8;
length += asn_1_array[count + 1];
dprintk(verbose, DST_CA_DEBUG, 1, " Length=[%04x]", length);
}
}
return length;
}
static int debug_string(u8 *msg, u32 length, u32 offset)
{
u32 i;
dprintk(verbose, DST_CA_DEBUG, 0, " String=[ ");
for (i = offset; i < length; i++)
dprintk(verbose, DST_CA_DEBUG, 0, "%02x ", msg[i]);
dprintk(verbose, DST_CA_DEBUG, 0, "]\n");
return 0;
}
static int ca_set_pmt(struct dst_state *state, struct ca_msg *p_ca_message, struct ca_msg *hw_buffer, u8 reply, u8 query)
{
u32 length = 0;
u8 tag_length = 8;
length = asn_1_decode(&p_ca_message->msg[3]);
dprintk(verbose, DST_CA_DEBUG, 1, " CA Message length=[%d]", length);
debug_string(&p_ca_message->msg[4], length, 0); /* length is excluding tag & length */
memset(hw_buffer->msg, '\0', length);
handle_dst_tag(state, p_ca_message, hw_buffer, length);
put_checksum(hw_buffer->msg, hw_buffer->msg[0]);
debug_string(hw_buffer->msg, (length + tag_length), 0); /* tags too */
write_to_8820(state, hw_buffer, (length + tag_length), reply);
return 0;
}
/* Board supports CA PMT reply ? */
static int dst_check_ca_pmt(struct dst_state *state, struct ca_msg *p_ca_message, struct ca_msg *hw_buffer)
{
int ca_pmt_reply_test = 0;
/* Do test board */
/* Not there yet but soon */
/* CA PMT Reply capable */
if (ca_pmt_reply_test) {
if ((ca_set_pmt(state, p_ca_message, hw_buffer, 1, GET_REPLY)) < 0) {
dprintk(verbose, DST_CA_ERROR, 1, " ca_set_pmt.. failed !");
return -1;
}
/* Process CA PMT Reply */
/* will implement soon */
dprintk(verbose, DST_CA_ERROR, 1, " Not there yet");
}
/* CA PMT Reply not capable */
if (!ca_pmt_reply_test) {
if ((ca_set_pmt(state, p_ca_message, hw_buffer, 0, NO_REPLY)) < 0) {
dprintk(verbose, DST_CA_ERROR, 1, " ca_set_pmt.. failed !");
return -1;
}
dprintk(verbose, DST_CA_NOTICE, 1, " ca_set_pmt.. success !");
/* put a dummy message */
}
return 0;
}
static int ca_send_message(struct dst_state *state, struct ca_msg *p_ca_message, void __user *arg)
{
int i = 0;
u32 command = 0;
struct ca_msg *hw_buffer;
int result = 0;
if ((hw_buffer = kmalloc(sizeof (struct ca_msg), GFP_KERNEL)) == NULL) {
dprintk(verbose, DST_CA_ERROR, 1, " Memory allocation failure");
return -ENOMEM;
}
dprintk(verbose, DST_CA_DEBUG, 1, " ");
if (copy_from_user(p_ca_message, arg, sizeof (struct ca_msg))) {
result = -EFAULT;
goto free_mem_and_exit;
}
if (p_ca_message->msg) {
/* EN50221 tag */
command = 0;
for (i = 0; i < 3; i++) {
command = command | p_ca_message->msg[i];
if (i < 2)
command = command << 8;
}
dprintk(verbose, DST_CA_DEBUG, 1, " Command=[0x%x]\n", command);
switch (command) {
case CA_PMT:
dprintk(verbose, DST_CA_DEBUG, 1, "Command = SEND_CA_PMT");
if ((ca_set_pmt(state, p_ca_message, hw_buffer, 0, 0)) < 0) { // code simplification started
dprintk(verbose, DST_CA_ERROR, 1, " -->CA_PMT Failed !");
result = -1;
goto free_mem_and_exit;
}
dprintk(verbose, DST_CA_INFO, 1, " -->CA_PMT Success !");
break;
case CA_PMT_REPLY:
dprintk(verbose, DST_CA_INFO, 1, "Command = CA_PMT_REPLY");
/* Have to handle the 2 basic types of cards here */
if ((dst_check_ca_pmt(state, p_ca_message, hw_buffer)) < 0) {
dprintk(verbose, DST_CA_ERROR, 1, " -->CA_PMT_REPLY Failed !");
result = -1;
goto free_mem_and_exit;
}
dprintk(verbose, DST_CA_INFO, 1, " -->CA_PMT_REPLY Success !");
break;
case CA_APP_INFO_ENQUIRY: // only for debugging
dprintk(verbose, DST_CA_INFO, 1, " Getting Cam Application information");
if ((ca_get_app_info(state)) < 0) {
dprintk(verbose, DST_CA_ERROR, 1, " -->CA_APP_INFO_ENQUIRY Failed !");
result = -1;
goto free_mem_and_exit;
}
dprintk(verbose, DST_CA_INFO, 1, " -->CA_APP_INFO_ENQUIRY Success !");
break;
case CA_INFO_ENQUIRY:
dprintk(verbose, DST_CA_INFO, 1, " Getting CA Information");
if ((ca_get_ca_info(state)) < 0) {
dprintk(verbose, DST_CA_ERROR, 1, " -->CA_INFO_ENQUIRY Failed !");
result = -1;
goto free_mem_and_exit;
}
dprintk(verbose, DST_CA_INFO, 1, " -->CA_INFO_ENQUIRY Success !");
break;
}
}
free_mem_and_exit:
kfree (hw_buffer);
return result;
}
static long dst_ca_ioctl(struct file *file, unsigned int cmd, unsigned long ioctl_arg)
{
struct dvb_device *dvbdev;
struct dst_state *state;
struct ca_slot_info *p_ca_slot_info;
struct ca_caps *p_ca_caps;
struct ca_msg *p_ca_message;
void __user *arg = (void __user *)ioctl_arg;
int result = 0;
mutex_lock(&dst_ca_mutex);
dvbdev = file->private_data;
state = (struct dst_state *)dvbdev->priv;
p_ca_message = kmalloc(sizeof (struct ca_msg), GFP_KERNEL);
p_ca_slot_info = kmalloc(sizeof (struct ca_slot_info), GFP_KERNEL);
p_ca_caps = kmalloc(sizeof (struct ca_caps), GFP_KERNEL);
if (!p_ca_message || !p_ca_slot_info || !p_ca_caps) {
dprintk(verbose, DST_CA_ERROR, 1, " Memory allocation failure");
result = -ENOMEM;
goto free_mem_and_exit;
}
/* We have now only the standard ioctl's, the driver is upposed to handle internals. */
switch (cmd) {
case CA_SEND_MSG:
dprintk(verbose, DST_CA_INFO, 1, " Sending message");
if ((ca_send_message(state, p_ca_message, arg)) < 0) {
dprintk(verbose, DST_CA_ERROR, 1, " -->CA_SEND_MSG Failed !");
result = -1;
goto free_mem_and_exit;
}
break;
case CA_GET_MSG:
dprintk(verbose, DST_CA_INFO, 1, " Getting message");
if ((ca_get_message(state, p_ca_message, arg)) < 0) {
dprintk(verbose, DST_CA_ERROR, 1, " -->CA_GET_MSG Failed !");
result = -1;
goto free_mem_and_exit;
}
dprintk(verbose, DST_CA_INFO, 1, " -->CA_GET_MSG Success !");
break;
case CA_RESET:
dprintk(verbose, DST_CA_ERROR, 1, " Resetting DST");
dst_error_bailout(state);
msleep(4000);
break;
case CA_GET_SLOT_INFO:
dprintk(verbose, DST_CA_INFO, 1, " Getting Slot info");
if ((ca_get_slot_info(state, p_ca_slot_info, arg)) < 0) {
dprintk(verbose, DST_CA_ERROR, 1, " -->CA_GET_SLOT_INFO Failed !");
result = -1;
goto free_mem_and_exit;
}
dprintk(verbose, DST_CA_INFO, 1, " -->CA_GET_SLOT_INFO Success !");
break;
case CA_GET_CAP:
dprintk(verbose, DST_CA_INFO, 1, " Getting Slot capabilities");
if ((ca_get_slot_caps(state, p_ca_caps, arg)) < 0) {
dprintk(verbose, DST_CA_ERROR, 1, " -->CA_GET_CAP Failed !");
result = -1;
goto free_mem_and_exit;
}
dprintk(verbose, DST_CA_INFO, 1, " -->CA_GET_CAP Success !");
break;
case CA_GET_DESCR_INFO:
dprintk(verbose, DST_CA_INFO, 1, " Getting descrambler description");
if ((ca_get_slot_descr(state, p_ca_message, arg)) < 0) {
dprintk(verbose, DST_CA_ERROR, 1, " -->CA_GET_DESCR_INFO Failed !");
result = -1;
goto free_mem_and_exit;
}
dprintk(verbose, DST_CA_INFO, 1, " -->CA_GET_DESCR_INFO Success !");
break;
case CA_SET_DESCR:
dprintk(verbose, DST_CA_INFO, 1, " Setting descrambler");
if ((ca_set_slot_descr()) < 0) {
dprintk(verbose, DST_CA_ERROR, 1, " -->CA_SET_DESCR Failed !");
result = -1;
goto free_mem_and_exit;
}
dprintk(verbose, DST_CA_INFO, 1, " -->CA_SET_DESCR Success !");
break;
case CA_SET_PID:
dprintk(verbose, DST_CA_INFO, 1, " Setting PID");
if ((ca_set_pid()) < 0) {
dprintk(verbose, DST_CA_ERROR, 1, " -->CA_SET_PID Failed !");
result = -1;
goto free_mem_and_exit;
}
dprintk(verbose, DST_CA_INFO, 1, " -->CA_SET_PID Success !");
default:
result = -EOPNOTSUPP;
}
free_mem_and_exit:
kfree (p_ca_message);
kfree (p_ca_slot_info);
kfree (p_ca_caps);
mutex_unlock(&dst_ca_mutex);
return result;
}
static int dst_ca_open(struct inode *inode, struct file *file)
{
dprintk(verbose, DST_CA_DEBUG, 1, " Device opened [%p] ", file);
try_module_get(THIS_MODULE);
return 0;
}
static int dst_ca_release(struct inode *inode, struct file *file)
{
dprintk(verbose, DST_CA_DEBUG, 1, " Device closed.");
module_put(THIS_MODULE);
return 0;
}
static ssize_t dst_ca_read(struct file *file, char __user *buffer, size_t length, loff_t *offset)
{
dprintk(verbose, DST_CA_DEBUG, 1, " Device read.");
return 0;
}
static ssize_t dst_ca_write(struct file *file, const char __user *buffer, size_t length, loff_t *offset)
{
dprintk(verbose, DST_CA_DEBUG, 1, " Device write.");
return 0;
}
static const struct file_operations dst_ca_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = dst_ca_ioctl,
.open = dst_ca_open,
.release = dst_ca_release,
.read = dst_ca_read,
.write = dst_ca_write,
.llseek = noop_llseek,
};
static struct dvb_device dvbdev_ca = {
.priv = NULL,
.users = 1,
.readers = 1,
.writers = 1,
.fops = &dst_ca_fops
};
struct dvb_device *dst_ca_attach(struct dst_state *dst, struct dvb_adapter *dvb_adapter)
{
struct dvb_device *dvbdev;
dprintk(verbose, DST_CA_ERROR, 1, "registering DST-CA device");
if (dvb_register_device(dvb_adapter, &dvbdev, &dvbdev_ca, dst, DVB_DEVICE_CA) == 0) {
dst->dst_ca = dvbdev;
return dst->dst_ca;
}
return NULL;
}
EXPORT_SYMBOL(dst_ca_attach);
MODULE_DESCRIPTION("DST DVB-S/T/C Combo CA driver");
MODULE_AUTHOR("Manu Abraham");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,58 @@
/*
CA-driver for TwinHan DST Frontend/Card
Copyright (C) 2004, 2005 Manu Abraham (manu@kromtek.com)
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 _DST_CA_H_
#define _DST_CA_H_
#define RETRIES 5
#define CA_APP_INFO_ENQUIRY 0x9f8020
#define CA_APP_INFO 0x9f8021
#define CA_ENTER_MENU 0x9f8022
#define CA_INFO_ENQUIRY 0x9f8030
#define CA_INFO 0x9f8031
#define CA_PMT 0x9f8032
#define CA_PMT_REPLY 0x9f8033
#define CA_CLOSE_MMI 0x9f8800
#define CA_DISPLAY_CONTROL 0x9f8801
#define CA_DISPLAY_REPLY 0x9f8802
#define CA_TEXT_LAST 0x9f8803
#define CA_TEXT_MORE 0x9f8804
#define CA_KEYPAD_CONTROL 0x9f8805
#define CA_KEYPRESS 0x9f8806
#define CA_ENQUIRY 0x9f8807
#define CA_ANSWER 0x9f8808
#define CA_MENU_LAST 0x9f8809
#define CA_MENU_MORE 0x9f880a
#define CA_MENU_ANSWER 0x9f880b
#define CA_LIST_LAST 0x9f880c
#define CA_LIST_MORE 0x9f880d
struct dst_ca_private {
struct dst_state *dst;
struct dvb_device *dvbdev;
};
#endif

View file

@ -0,0 +1,182 @@
/*
Frontend-driver for TwinHan DST Frontend
Copyright (C) 2003 Jamie Honan
Copyright (C) 2004, 2005 Manu Abraham (manu@kromtek.com)
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 DST_COMMON_H
#define DST_COMMON_H
#include <linux/dvb/frontend.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include "bt878.h"
#include "dst_ca.h"
#define NO_DELAY 0
#define LONG_DELAY 1
#define DEVICE_INIT 2
#define DELAY 1
#define DST_TYPE_IS_SAT 0
#define DST_TYPE_IS_TERR 1
#define DST_TYPE_IS_CABLE 2
#define DST_TYPE_IS_ATSC 3
#define DST_TYPE_HAS_TS188 1
#define DST_TYPE_HAS_TS204 2
#define DST_TYPE_HAS_SYMDIV 4
#define DST_TYPE_HAS_FW_1 8
#define DST_TYPE_HAS_FW_2 16
#define DST_TYPE_HAS_FW_3 32
#define DST_TYPE_HAS_FW_BUILD 64
#define DST_TYPE_HAS_OBS_REGS 128
#define DST_TYPE_HAS_INC_COUNT 256
#define DST_TYPE_HAS_MULTI_FE 512
#define DST_TYPE_HAS_NEWTUNE_2 1024
#define DST_TYPE_HAS_DBOARD 2048
#define DST_TYPE_HAS_VLF 4096
/* Card capability list */
#define DST_TYPE_HAS_MAC 1
#define DST_TYPE_HAS_DISEQC3 2
#define DST_TYPE_HAS_DISEQC4 4
#define DST_TYPE_HAS_DISEQC5 8
#define DST_TYPE_HAS_MOTO 16
#define DST_TYPE_HAS_CA 32
#define DST_TYPE_HAS_ANALOG 64 /* Analog inputs */
#define DST_TYPE_HAS_SESSION 128
#define TUNER_TYPE_MULTI 1
#define TUNER_TYPE_UNKNOWN 2
/* DVB-S */
#define TUNER_TYPE_L64724 4
#define TUNER_TYPE_STV0299 8
#define TUNER_TYPE_MB86A15 16
/* DVB-T */
#define TUNER_TYPE_TDA10046 32
/* ATSC */
#define TUNER_TYPE_NXT200x 64
#define RDC_8820_PIO_0_DISABLE 0
#define RDC_8820_PIO_0_ENABLE 1
#define RDC_8820_INT 2
#define RDC_8820_RESET 4
/* DST Communication */
#define GET_REPLY 1
#define NO_REPLY 0
#define GET_ACK 1
#define FIXED_COMM 8
#define ACK 0xff
struct dst_state {
struct i2c_adapter* i2c;
struct bt878* bt;
/* configuration settings */
const struct dst_config* config;
struct dvb_frontend frontend;
/* private ASIC data */
u8 tx_tuna[10];
u8 rx_tuna[10];
u8 rxbuffer[10];
u8 diseq_flags;
u8 dst_type;
u32 type_flags;
u32 frequency; /* intermediate frequency in kHz for QPSK */
fe_spectral_inversion_t inversion;
u32 symbol_rate; /* symbol rate in Symbols per second */
fe_code_rate_t fec;
fe_sec_voltage_t voltage;
fe_sec_tone_mode_t tone;
u32 decode_freq;
u8 decode_lock;
u16 decode_strength;
u16 decode_snr;
unsigned long cur_jiff;
u8 k22;
u32 bandwidth;
u32 dst_hw_cap;
u8 dst_fw_version;
fe_sec_mini_cmd_t minicmd;
fe_modulation_t modulation;
u8 messages[256];
u8 mac_address[8];
u8 fw_version[8];
u8 card_info[8];
u8 vendor[8];
u8 board_info[8];
u32 tuner_type;
char *tuner_name;
struct mutex dst_mutex;
u8 fw_name[8];
struct dvb_device *dst_ca;
};
struct tuner_types {
u32 tuner_type;
char *tuner_name;
char *board_name;
char *fw_name;
};
struct dst_types {
char *device_id;
int offset;
u8 dst_type;
u32 type_flags;
u32 dst_feature;
u32 tuner_type;
};
struct dst_config
{
/* the ASIC i2c address */
u8 demod_address;
};
int rdc_reset_state(struct dst_state *state);
int dst_wait_dst_ready(struct dst_state *state, u8 delay_mode);
int dst_pio_disable(struct dst_state *state);
int dst_error_recovery(struct dst_state* state);
int dst_error_bailout(struct dst_state *state);
int dst_comm_init(struct dst_state* state);
int write_dst(struct dst_state *state, u8 * data, u8 len);
int read_dst(struct dst_state *state, u8 * ret, u8 len);
u8 dst_check_sum(u8 * buf, u32 len);
struct dst_state* dst_attach(struct dst_state* state, struct dvb_adapter *dvb_adapter);
struct dvb_device *dst_ca_attach(struct dst_state *state, struct dvb_adapter *dvb_adapter);
#endif // DST_COMMON_H

View file

@ -0,0 +1,35 @@
/*
* dst-bt878.h: part of the DST driver for the TwinHan DST Frontend
*
* Copyright (C) 2003 Jamie Honan
*/
struct dst_gpio_enable {
u32 mask;
u32 enable;
};
struct dst_gpio_output {
u32 mask;
u32 highvals;
};
struct dst_gpio_read {
unsigned long value;
};
union dst_gpio_packet {
struct dst_gpio_enable enb;
struct dst_gpio_output outp;
struct dst_gpio_read rd;
int psize;
};
#define DST_IG_ENABLE 0
#define DST_IG_WRITE 1
#define DST_IG_READ 2
#define DST_IG_TS 3
struct bt878;
int bt878_device_control(struct bt878 *bt, unsigned int cmd, union dst_gpio_packet *mp);

View file

@ -0,0 +1,976 @@
/*
* Bt8xx based DVB adapter driver
*
* Copyright (C) 2002,2003 Florian Schirmer <jolt@tuxbox.org>
*
* 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.
*
*/
#define pr_fmt(fmt) "dvb_bt8xx: " fmt
#include <linux/bitops.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include "dmxdev.h"
#include "dvbdev.h"
#include "dvb_demux.h"
#include "dvb_frontend.h"
#include "dvb-bt8xx.h"
#include "bt878.h"
static int debug;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off).");
DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
#define dprintk( args... ) \
do { \
if (debug) printk(KERN_DEBUG args); \
} while (0)
#define IF_FREQUENCYx6 217 /* 6 * 36.16666666667MHz */
static void dvb_bt8xx_task(unsigned long data)
{
struct dvb_bt8xx_card *card = (struct dvb_bt8xx_card *)data;
//printk("%d ", card->bt->finished_block);
while (card->bt->last_block != card->bt->finished_block) {
(card->bt->TS_Size ? dvb_dmx_swfilter_204 : dvb_dmx_swfilter)
(&card->demux,
&card->bt->buf_cpu[card->bt->last_block *
card->bt->block_bytes],
card->bt->block_bytes);
card->bt->last_block = (card->bt->last_block + 1) %
card->bt->block_count;
}
}
static int dvb_bt8xx_start_feed(struct dvb_demux_feed *dvbdmxfeed)
{
struct dvb_demux*dvbdmx = dvbdmxfeed->demux;
struct dvb_bt8xx_card *card = dvbdmx->priv;
int rc;
dprintk("dvb_bt8xx: start_feed\n");
if (!dvbdmx->dmx.frontend)
return -EINVAL;
mutex_lock(&card->lock);
card->nfeeds++;
rc = card->nfeeds;
if (card->nfeeds == 1)
bt878_start(card->bt, card->gpio_mode,
card->op_sync_orin, card->irq_err_ignore);
mutex_unlock(&card->lock);
return rc;
}
static int dvb_bt8xx_stop_feed(struct dvb_demux_feed *dvbdmxfeed)
{
struct dvb_demux *dvbdmx = dvbdmxfeed->demux;
struct dvb_bt8xx_card *card = dvbdmx->priv;
dprintk("dvb_bt8xx: stop_feed\n");
if (!dvbdmx->dmx.frontend)
return -EINVAL;
mutex_lock(&card->lock);
card->nfeeds--;
if (card->nfeeds == 0)
bt878_stop(card->bt);
mutex_unlock(&card->lock);
return 0;
}
static int is_pci_slot_eq(struct pci_dev* adev, struct pci_dev* bdev)
{
if ((adev->subsystem_vendor == bdev->subsystem_vendor) &&
(adev->subsystem_device == bdev->subsystem_device) &&
(adev->bus->number == bdev->bus->number) &&
(PCI_SLOT(adev->devfn) == PCI_SLOT(bdev->devfn)))
return 1;
return 0;
}
static struct bt878 *dvb_bt8xx_878_match(unsigned int bttv_nr,
struct pci_dev* bttv_pci_dev)
{
unsigned int card_nr;
/* Hmm, n squared. Hope n is small */
for (card_nr = 0; card_nr < bt878_num; card_nr++)
if (is_pci_slot_eq(bt878[card_nr].dev, bttv_pci_dev))
return &bt878[card_nr];
return NULL;
}
static int thomson_dtt7579_demod_init(struct dvb_frontend* fe)
{
static u8 mt352_clock_config [] = { 0x89, 0x38, 0x38 };
static u8 mt352_reset [] = { 0x50, 0x80 };
static u8 mt352_adc_ctl_1_cfg [] = { 0x8E, 0x40 };
static u8 mt352_agc_cfg [] = { 0x67, 0x28, 0x20 };
static u8 mt352_gpp_ctl_cfg [] = { 0x8C, 0x33 };
static u8 mt352_capt_range_cfg[] = { 0x75, 0x32 };
mt352_write(fe, mt352_clock_config, sizeof(mt352_clock_config));
udelay(2000);
mt352_write(fe, mt352_reset, sizeof(mt352_reset));
mt352_write(fe, mt352_adc_ctl_1_cfg, sizeof(mt352_adc_ctl_1_cfg));
mt352_write(fe, mt352_agc_cfg, sizeof(mt352_agc_cfg));
mt352_write(fe, mt352_gpp_ctl_cfg, sizeof(mt352_gpp_ctl_cfg));
mt352_write(fe, mt352_capt_range_cfg, sizeof(mt352_capt_range_cfg));
return 0;
}
static int thomson_dtt7579_tuner_calc_regs(struct dvb_frontend *fe, u8* pllbuf, int buf_len)
{
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
u32 div;
unsigned char bs = 0;
unsigned char cp = 0;
if (buf_len < 5)
return -EINVAL;
div = (((c->frequency + 83333) * 3) / 500000) + IF_FREQUENCYx6;
if (c->frequency < 542000000)
cp = 0xb4;
else if (c->frequency < 771000000)
cp = 0xbc;
else
cp = 0xf4;
if (c->frequency == 0)
bs = 0x03;
else if (c->frequency < 443250000)
bs = 0x02;
else
bs = 0x08;
pllbuf[0] = 0x60;
pllbuf[1] = div >> 8;
pllbuf[2] = div & 0xff;
pllbuf[3] = cp;
pllbuf[4] = bs;
return 5;
}
static struct mt352_config thomson_dtt7579_config = {
.demod_address = 0x0f,
.demod_init = thomson_dtt7579_demod_init,
};
static struct zl10353_config thomson_dtt7579_zl10353_config = {
.demod_address = 0x0f,
};
static int cx24108_tuner_set_params(struct dvb_frontend *fe)
{
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
u32 freq = c->frequency;
int i, a, n, pump;
u32 band, pll;
u32 osci[]={950000,1019000,1075000,1178000,1296000,1432000,
1576000,1718000,1856000,2036000,2150000};
u32 bandsel[]={0,0x00020000,0x00040000,0x00100800,0x00101000,
0x00102000,0x00104000,0x00108000,0x00110000,
0x00120000,0x00140000};
#define XTAL 1011100 /* Hz, really 1.0111 MHz and a /10 prescaler */
dprintk("cx24108 debug: entering SetTunerFreq, freq=%d\n", freq);
/* This is really the bit driving the tuner chip cx24108 */
if (freq<950000)
freq = 950000; /* kHz */
else if (freq>2150000)
freq = 2150000; /* satellite IF is 950..2150MHz */
/* decide which VCO to use for the input frequency */
for(i = 1; (i < ARRAY_SIZE(osci) - 1) && (osci[i] < freq); i++);
dprintk("cx24108 debug: select vco #%d (f=%d)\n", i, freq);
band=bandsel[i];
/* the gain values must be set by SetSymbolrate */
/* compute the pll divider needed, from Conexant data sheet,
resolved for (n*32+a), remember f(vco) is f(receive) *2 or *4,
depending on the divider bit. It is set to /4 on the 2 lowest
bands */
n=((i<=2?2:1)*freq*10L)/(XTAL/100);
a=n%32; n/=32; if(a==0) n--;
pump=(freq<(osci[i-1]+osci[i])/2);
pll=0xf8000000|
((pump?1:2)<<(14+11))|
((n&0x1ff)<<(5+11))|
((a&0x1f)<<11);
/* everything is shifted left 11 bits to left-align the bits in the
32bit word. Output to the tuner goes MSB-aligned, after all */
dprintk("cx24108 debug: pump=%d, n=%d, a=%d\n", pump, n, a);
cx24110_pll_write(fe,band);
/* set vga and vca to their widest-band settings, as a precaution.
SetSymbolrate might not be called to set this up */
cx24110_pll_write(fe,0x500c0000);
cx24110_pll_write(fe,0x83f1f800);
cx24110_pll_write(fe,pll);
//writereg(client,0x56,0x7f);
return 0;
}
static int pinnsat_tuner_init(struct dvb_frontend* fe)
{
struct dvb_bt8xx_card *card = fe->dvb->priv;
bttv_gpio_enable(card->bttv_nr, 1, 1); /* output */
bttv_write_gpio(card->bttv_nr, 1, 1); /* relay on */
return 0;
}
static int pinnsat_tuner_sleep(struct dvb_frontend* fe)
{
struct dvb_bt8xx_card *card = fe->dvb->priv;
bttv_write_gpio(card->bttv_nr, 1, 0); /* relay off */
return 0;
}
static struct cx24110_config pctvsat_config = {
.demod_address = 0x55,
};
static int microtune_mt7202dtf_tuner_set_params(struct dvb_frontend *fe)
{
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
struct dvb_bt8xx_card *card = (struct dvb_bt8xx_card *) fe->dvb->priv;
u8 cfg, cpump, band_select;
u8 data[4];
u32 div;
struct i2c_msg msg = { .addr = 0x60, .flags = 0, .buf = data, .len = sizeof(data) };
div = (36000000 + c->frequency + 83333) / 166666;
cfg = 0x88;
if (c->frequency < 175000000)
cpump = 2;
else if (c->frequency < 390000000)
cpump = 1;
else if (c->frequency < 470000000)
cpump = 2;
else if (c->frequency < 750000000)
cpump = 2;
else
cpump = 3;
if (c->frequency < 175000000)
band_select = 0x0e;
else if (c->frequency < 470000000)
band_select = 0x05;
else
band_select = 0x03;
data[0] = (div >> 8) & 0x7f;
data[1] = div & 0xff;
data[2] = ((div >> 10) & 0x60) | cfg;
data[3] = (cpump << 6) | band_select;
if (fe->ops.i2c_gate_ctrl)
fe->ops.i2c_gate_ctrl(fe, 1);
i2c_transfer(card->i2c_adapter, &msg, 1);
return (div * 166666 - 36000000);
}
static int microtune_mt7202dtf_request_firmware(struct dvb_frontend* fe, const struct firmware **fw, char* name)
{
struct dvb_bt8xx_card* bt = (struct dvb_bt8xx_card*) fe->dvb->priv;
return request_firmware(fw, name, &bt->bt->dev->dev);
}
static struct sp887x_config microtune_mt7202dtf_config = {
.demod_address = 0x70,
.request_firmware = microtune_mt7202dtf_request_firmware,
};
static int advbt771_samsung_tdtc9251dh0_demod_init(struct dvb_frontend* fe)
{
static u8 mt352_clock_config [] = { 0x89, 0x38, 0x2d };
static u8 mt352_reset [] = { 0x50, 0x80 };
static u8 mt352_adc_ctl_1_cfg [] = { 0x8E, 0x40 };
static u8 mt352_agc_cfg [] = { 0x67, 0x10, 0x23, 0x00, 0xFF, 0xFF,
0x00, 0xFF, 0x00, 0x40, 0x40 };
static u8 mt352_av771_extra[] = { 0xB5, 0x7A };
static u8 mt352_capt_range_cfg[] = { 0x75, 0x32 };
mt352_write(fe, mt352_clock_config, sizeof(mt352_clock_config));
udelay(2000);
mt352_write(fe, mt352_reset, sizeof(mt352_reset));
mt352_write(fe, mt352_adc_ctl_1_cfg, sizeof(mt352_adc_ctl_1_cfg));
mt352_write(fe, mt352_agc_cfg,sizeof(mt352_agc_cfg));
udelay(2000);
mt352_write(fe, mt352_av771_extra,sizeof(mt352_av771_extra));
mt352_write(fe, mt352_capt_range_cfg, sizeof(mt352_capt_range_cfg));
return 0;
}
static int advbt771_samsung_tdtc9251dh0_tuner_calc_regs(struct dvb_frontend *fe, u8 *pllbuf, int buf_len)
{
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
u32 div;
unsigned char bs = 0;
unsigned char cp = 0;
if (buf_len < 5) return -EINVAL;
div = (((c->frequency + 83333) * 3) / 500000) + IF_FREQUENCYx6;
if (c->frequency < 150000000)
cp = 0xB4;
else if (c->frequency < 173000000)
cp = 0xBC;
else if (c->frequency < 250000000)
cp = 0xB4;
else if (c->frequency < 400000000)
cp = 0xBC;
else if (c->frequency < 420000000)
cp = 0xF4;
else if (c->frequency < 470000000)
cp = 0xFC;
else if (c->frequency < 600000000)
cp = 0xBC;
else if (c->frequency < 730000000)
cp = 0xF4;
else
cp = 0xFC;
if (c->frequency < 150000000)
bs = 0x01;
else if (c->frequency < 173000000)
bs = 0x01;
else if (c->frequency < 250000000)
bs = 0x02;
else if (c->frequency < 400000000)
bs = 0x02;
else if (c->frequency < 420000000)
bs = 0x02;
else if (c->frequency < 470000000)
bs = 0x02;
else if (c->frequency < 600000000)
bs = 0x08;
else if (c->frequency < 730000000)
bs = 0x08;
else
bs = 0x08;
pllbuf[0] = 0x61;
pllbuf[1] = div >> 8;
pllbuf[2] = div & 0xff;
pllbuf[3] = cp;
pllbuf[4] = bs;
return 5;
}
static struct mt352_config advbt771_samsung_tdtc9251dh0_config = {
.demod_address = 0x0f,
.demod_init = advbt771_samsung_tdtc9251dh0_demod_init,
};
static struct dst_config dst_config = {
.demod_address = 0x55,
};
static int or51211_request_firmware(struct dvb_frontend* fe, const struct firmware **fw, char* name)
{
struct dvb_bt8xx_card* bt = (struct dvb_bt8xx_card*) fe->dvb->priv;
return request_firmware(fw, name, &bt->bt->dev->dev);
}
static void or51211_setmode(struct dvb_frontend * fe, int mode)
{
struct dvb_bt8xx_card *bt = fe->dvb->priv;
bttv_write_gpio(bt->bttv_nr, 0x0002, mode); /* Reset */
msleep(20);
}
static void or51211_reset(struct dvb_frontend * fe)
{
struct dvb_bt8xx_card *bt = fe->dvb->priv;
/* RESET DEVICE
* reset is controlled by GPIO-0
* when set to 0 causes reset and when to 1 for normal op
* must remain reset for 128 clock cycles on a 50Mhz clock
* also PRM1 PRM2 & PRM4 are controlled by GPIO-1,GPIO-2 & GPIO-4
* We assume that the reset has be held low long enough or we
* have been reset by a power on. When the driver is unloaded
* reset set to 0 so if reloaded we have been reset.
*/
/* reset & PRM1,2&4 are outputs */
int ret = bttv_gpio_enable(bt->bttv_nr, 0x001F, 0x001F);
if (ret != 0)
printk(KERN_WARNING "or51211: Init Error - Can't Reset DVR (%i)\n", ret);
bttv_write_gpio(bt->bttv_nr, 0x001F, 0x0000); /* Reset */
msleep(20);
/* Now set for normal operation */
bttv_write_gpio(bt->bttv_nr, 0x0001F, 0x0001);
/* wait for operation to begin */
msleep(500);
}
static void or51211_sleep(struct dvb_frontend * fe)
{
struct dvb_bt8xx_card *bt = fe->dvb->priv;
bttv_write_gpio(bt->bttv_nr, 0x0001, 0x0000);
}
static struct or51211_config or51211_config = {
.demod_address = 0x15,
.request_firmware = or51211_request_firmware,
.setmode = or51211_setmode,
.reset = or51211_reset,
.sleep = or51211_sleep,
};
static int vp3021_alps_tded4_tuner_set_params(struct dvb_frontend *fe)
{
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
struct dvb_bt8xx_card *card = (struct dvb_bt8xx_card *) fe->dvb->priv;
u8 buf[4];
u32 div;
struct i2c_msg msg = { .addr = 0x60, .flags = 0, .buf = buf, .len = sizeof(buf) };
div = (c->frequency + 36166667) / 166667;
buf[0] = (div >> 8) & 0x7F;
buf[1] = div & 0xFF;
buf[2] = 0x85;
if ((c->frequency >= 47000000) && (c->frequency < 153000000))
buf[3] = 0x01;
else if ((c->frequency >= 153000000) && (c->frequency < 430000000))
buf[3] = 0x02;
else if ((c->frequency >= 430000000) && (c->frequency < 824000000))
buf[3] = 0x0C;
else if ((c->frequency >= 824000000) && (c->frequency < 863000000))
buf[3] = 0x8C;
else
return -EINVAL;
if (fe->ops.i2c_gate_ctrl)
fe->ops.i2c_gate_ctrl(fe, 1);
i2c_transfer(card->i2c_adapter, &msg, 1);
return 0;
}
static struct nxt6000_config vp3021_alps_tded4_config = {
.demod_address = 0x0a,
.clock_inversion = 1,
};
static int digitv_alps_tded4_demod_init(struct dvb_frontend* fe)
{
static u8 mt352_clock_config [] = { 0x89, 0x38, 0x2d };
static u8 mt352_reset [] = { 0x50, 0x80 };
static u8 mt352_adc_ctl_1_cfg [] = { 0x8E, 0x40 };
static u8 mt352_agc_cfg [] = { 0x67, 0x20, 0xa0 };
static u8 mt352_capt_range_cfg[] = { 0x75, 0x32 };
mt352_write(fe, mt352_clock_config, sizeof(mt352_clock_config));
udelay(2000);
mt352_write(fe, mt352_reset, sizeof(mt352_reset));
mt352_write(fe, mt352_adc_ctl_1_cfg, sizeof(mt352_adc_ctl_1_cfg));
mt352_write(fe, mt352_agc_cfg,sizeof(mt352_agc_cfg));
mt352_write(fe, mt352_capt_range_cfg, sizeof(mt352_capt_range_cfg));
return 0;
}
static int digitv_alps_tded4_tuner_calc_regs(struct dvb_frontend *fe, u8 *pllbuf, int buf_len)
{
u32 div;
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
if (buf_len < 5)
return -EINVAL;
div = (((c->frequency + 83333) * 3) / 500000) + IF_FREQUENCYx6;
pllbuf[0] = 0x61;
pllbuf[1] = (div >> 8) & 0x7F;
pllbuf[2] = div & 0xFF;
pllbuf[3] = 0x85;
dprintk("frequency %u, div %u\n", c->frequency, div);
if (c->frequency < 470000000)
pllbuf[4] = 0x02;
else if (c->frequency > 823000000)
pllbuf[4] = 0x88;
else
pllbuf[4] = 0x08;
if (c->bandwidth_hz == 8000000)
pllbuf[4] |= 0x04;
return 5;
}
static void digitv_alps_tded4_reset(struct dvb_bt8xx_card *bt)
{
/*
* Reset the frontend, must be called before trying
* to initialise the MT352 or mt352_attach
* will fail. Same goes for the nxt6000 frontend.
*
*/
int ret = bttv_gpio_enable(bt->bttv_nr, 0x08, 0x08);
if (ret != 0)
printk(KERN_WARNING "digitv_alps_tded4: Init Error - Can't Reset DVR (%i)\n", ret);
/* Pulse the reset line */
bttv_write_gpio(bt->bttv_nr, 0x08, 0x08); /* High */
bttv_write_gpio(bt->bttv_nr, 0x08, 0x00); /* Low */
msleep(100);
bttv_write_gpio(bt->bttv_nr, 0x08, 0x08); /* High */
}
static struct mt352_config digitv_alps_tded4_config = {
.demod_address = 0x0a,
.demod_init = digitv_alps_tded4_demod_init,
};
static struct lgdt330x_config tdvs_tua6034_config = {
.demod_address = 0x0e,
.demod_chip = LGDT3303,
.serial_mpeg = 0x40, /* TPSERIAL for 3303 in TOP_CONTROL */
};
static void lgdt330x_reset(struct dvb_bt8xx_card *bt)
{
/* Set pin 27 of the lgdt3303 chip high to reset the frontend */
/* Pulse the reset line */
bttv_write_gpio(bt->bttv_nr, 0x00e00007, 0x00000001); /* High */
bttv_write_gpio(bt->bttv_nr, 0x00e00007, 0x00000000); /* Low */
msleep(100);
bttv_write_gpio(bt->bttv_nr, 0x00e00007, 0x00000001); /* High */
msleep(100);
}
static void frontend_init(struct dvb_bt8xx_card *card, u32 type)
{
struct dst_state* state = NULL;
switch(type) {
case BTTV_BOARD_DVICO_DVBT_LITE:
card->fe = dvb_attach(mt352_attach, &thomson_dtt7579_config, card->i2c_adapter);
if (card->fe == NULL)
card->fe = dvb_attach(zl10353_attach, &thomson_dtt7579_zl10353_config,
card->i2c_adapter);
if (card->fe != NULL) {
card->fe->ops.tuner_ops.calc_regs = thomson_dtt7579_tuner_calc_regs;
card->fe->ops.info.frequency_min = 174000000;
card->fe->ops.info.frequency_max = 862000000;
}
break;
case BTTV_BOARD_DVICO_FUSIONHDTV_5_LITE:
lgdt330x_reset(card);
card->fe = dvb_attach(lgdt330x_attach, &tdvs_tua6034_config, card->i2c_adapter);
if (card->fe != NULL) {
dvb_attach(simple_tuner_attach, card->fe,
card->i2c_adapter, 0x61,
TUNER_LG_TDVS_H06XF);
dprintk ("dvb_bt8xx: lgdt330x detected\n");
}
break;
case BTTV_BOARD_NEBULA_DIGITV:
/*
* It is possible to determine the correct frontend using the I2C bus (see the Nebula SDK);
* this would be a cleaner solution than trying each frontend in turn.
*/
/* Old Nebula (marked (c)2003 on high profile pci card) has nxt6000 demod */
digitv_alps_tded4_reset(card);
card->fe = dvb_attach(nxt6000_attach, &vp3021_alps_tded4_config, card->i2c_adapter);
if (card->fe != NULL) {
card->fe->ops.tuner_ops.set_params = vp3021_alps_tded4_tuner_set_params;
dprintk ("dvb_bt8xx: an nxt6000 was detected on your digitv card\n");
break;
}
/* New Nebula (marked (c)2005 on low profile pci card) has mt352 demod */
digitv_alps_tded4_reset(card);
card->fe = dvb_attach(mt352_attach, &digitv_alps_tded4_config, card->i2c_adapter);
if (card->fe != NULL) {
card->fe->ops.tuner_ops.calc_regs = digitv_alps_tded4_tuner_calc_regs;
dprintk ("dvb_bt8xx: an mt352 was detected on your digitv card\n");
}
break;
case BTTV_BOARD_AVDVBT_761:
card->fe = dvb_attach(sp887x_attach, &microtune_mt7202dtf_config, card->i2c_adapter);
if (card->fe) {
card->fe->ops.tuner_ops.set_params = microtune_mt7202dtf_tuner_set_params;
}
break;
case BTTV_BOARD_AVDVBT_771:
card->fe = dvb_attach(mt352_attach, &advbt771_samsung_tdtc9251dh0_config, card->i2c_adapter);
if (card->fe != NULL) {
card->fe->ops.tuner_ops.calc_regs = advbt771_samsung_tdtc9251dh0_tuner_calc_regs;
card->fe->ops.info.frequency_min = 174000000;
card->fe->ops.info.frequency_max = 862000000;
}
break;
case BTTV_BOARD_TWINHAN_DST:
/* DST is not a frontend driver !!! */
state = kmalloc(sizeof (struct dst_state), GFP_KERNEL);
if (!state) {
pr_err("No memory\n");
break;
}
/* Setup the Card */
state->config = &dst_config;
state->i2c = card->i2c_adapter;
state->bt = card->bt;
state->dst_ca = NULL;
/* DST is not a frontend, attaching the ASIC */
if (dvb_attach(dst_attach, state, &card->dvb_adapter) == NULL) {
pr_err("%s: Could not find a Twinhan DST\n", __func__);
break;
}
/* Attach other DST peripherals if any */
/* Conditional Access device */
card->fe = &state->frontend;
if (state->dst_hw_cap & DST_TYPE_HAS_CA)
dvb_attach(dst_ca_attach, state, &card->dvb_adapter);
break;
case BTTV_BOARD_PINNACLESAT:
card->fe = dvb_attach(cx24110_attach, &pctvsat_config, card->i2c_adapter);
if (card->fe) {
card->fe->ops.tuner_ops.init = pinnsat_tuner_init;
card->fe->ops.tuner_ops.sleep = pinnsat_tuner_sleep;
card->fe->ops.tuner_ops.set_params = cx24108_tuner_set_params;
}
break;
case BTTV_BOARD_PC_HDTV:
card->fe = dvb_attach(or51211_attach, &or51211_config, card->i2c_adapter);
if (card->fe != NULL)
dvb_attach(simple_tuner_attach, card->fe,
card->i2c_adapter, 0x61,
TUNER_PHILIPS_FCV1236D);
break;
}
if (card->fe == NULL)
pr_err("A frontend driver was not found for device [%04x:%04x] subsystem [%04x:%04x]\n",
card->bt->dev->vendor,
card->bt->dev->device,
card->bt->dev->subsystem_vendor,
card->bt->dev->subsystem_device);
else
if (dvb_register_frontend(&card->dvb_adapter, card->fe)) {
pr_err("Frontend registration failed!\n");
dvb_frontend_detach(card->fe);
card->fe = NULL;
}
}
static int dvb_bt8xx_load_card(struct dvb_bt8xx_card *card, u32 type)
{
int result;
result = dvb_register_adapter(&card->dvb_adapter, card->card_name,
THIS_MODULE, &card->bt->dev->dev,
adapter_nr);
if (result < 0) {
pr_err("dvb_register_adapter failed (errno = %d)\n", result);
return result;
}
card->dvb_adapter.priv = card;
card->bt->adapter = card->i2c_adapter;
memset(&card->demux, 0, sizeof(struct dvb_demux));
card->demux.dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING | DMX_MEMORY_BASED_FILTERING;
card->demux.priv = card;
card->demux.filternum = 256;
card->demux.feednum = 256;
card->demux.start_feed = dvb_bt8xx_start_feed;
card->demux.stop_feed = dvb_bt8xx_stop_feed;
card->demux.write_to_decoder = NULL;
result = dvb_dmx_init(&card->demux);
if (result < 0) {
pr_err("dvb_dmx_init failed (errno = %d)\n", result);
goto err_unregister_adaptor;
}
card->dmxdev.filternum = 256;
card->dmxdev.demux = &card->demux.dmx;
card->dmxdev.capabilities = 0;
result = dvb_dmxdev_init(&card->dmxdev, &card->dvb_adapter);
if (result < 0) {
pr_err("dvb_dmxdev_init failed (errno = %d)\n", result);
goto err_dmx_release;
}
card->fe_hw.source = DMX_FRONTEND_0;
result = card->demux.dmx.add_frontend(&card->demux.dmx, &card->fe_hw);
if (result < 0) {
pr_err("dvb_dmx_init failed (errno = %d)\n", result);
goto err_dmxdev_release;
}
card->fe_mem.source = DMX_MEMORY_FE;
result = card->demux.dmx.add_frontend(&card->demux.dmx, &card->fe_mem);
if (result < 0) {
pr_err("dvb_dmx_init failed (errno = %d)\n", result);
goto err_remove_hw_frontend;
}
result = card->demux.dmx.connect_frontend(&card->demux.dmx, &card->fe_hw);
if (result < 0) {
pr_err("dvb_dmx_init failed (errno = %d)\n", result);
goto err_remove_mem_frontend;
}
result = dvb_net_init(&card->dvb_adapter, &card->dvbnet, &card->demux.dmx);
if (result < 0) {
pr_err("dvb_net_init failed (errno = %d)\n", result);
goto err_disconnect_frontend;
}
tasklet_init(&card->bt->tasklet, dvb_bt8xx_task, (unsigned long) card);
frontend_init(card, type);
return 0;
err_disconnect_frontend:
card->demux.dmx.disconnect_frontend(&card->demux.dmx);
err_remove_mem_frontend:
card->demux.dmx.remove_frontend(&card->demux.dmx, &card->fe_mem);
err_remove_hw_frontend:
card->demux.dmx.remove_frontend(&card->demux.dmx, &card->fe_hw);
err_dmxdev_release:
dvb_dmxdev_release(&card->dmxdev);
err_dmx_release:
dvb_dmx_release(&card->demux);
err_unregister_adaptor:
dvb_unregister_adapter(&card->dvb_adapter);
return result;
}
static int dvb_bt8xx_probe(struct bttv_sub_device *sub)
{
struct dvb_bt8xx_card *card;
struct pci_dev* bttv_pci_dev;
int ret;
if (!(card = kzalloc(sizeof(struct dvb_bt8xx_card), GFP_KERNEL)))
return -ENOMEM;
mutex_init(&card->lock);
card->bttv_nr = sub->core->nr;
strlcpy(card->card_name, sub->core->v4l2_dev.name, sizeof(card->card_name));
card->i2c_adapter = &sub->core->i2c_adap;
switch(sub->core->type) {
case BTTV_BOARD_PINNACLESAT:
card->gpio_mode = 0x0400c060;
/* should be: BT878_A_GAIN=0,BT878_A_PWRDN,BT878_DA_DPM,BT878_DA_SBR,
BT878_DA_IOM=1,BT878_DA_APP to enable serial highspeed mode. */
card->op_sync_orin = BT878_RISC_SYNC_MASK;
card->irq_err_ignore = BT878_AFBUS | BT878_AFDSR;
break;
case BTTV_BOARD_DVICO_DVBT_LITE:
card->gpio_mode = 0x0400C060;
card->op_sync_orin = BT878_RISC_SYNC_MASK;
card->irq_err_ignore = BT878_AFBUS | BT878_AFDSR;
/* 26, 15, 14, 6, 5
* A_PWRDN DA_DPM DA_SBR DA_IOM_DA
* DA_APP(parallel) */
break;
case BTTV_BOARD_DVICO_FUSIONHDTV_5_LITE:
card->gpio_mode = 0x0400c060;
card->op_sync_orin = BT878_RISC_SYNC_MASK;
card->irq_err_ignore = BT878_AFBUS | BT878_AFDSR;
break;
case BTTV_BOARD_NEBULA_DIGITV:
case BTTV_BOARD_AVDVBT_761:
card->gpio_mode = (1 << 26) | (1 << 14) | (1 << 5);
card->op_sync_orin = BT878_RISC_SYNC_MASK;
card->irq_err_ignore = BT878_AFBUS | BT878_AFDSR;
/* A_PWRDN DA_SBR DA_APP (high speed serial) */
break;
case BTTV_BOARD_AVDVBT_771: //case 0x07711461:
card->gpio_mode = 0x0400402B;
card->op_sync_orin = BT878_RISC_SYNC_MASK;
card->irq_err_ignore = BT878_AFBUS | BT878_AFDSR;
/* A_PWRDN DA_SBR DA_APP[0] PKTP=10 RISC_ENABLE FIFO_ENABLE*/
break;
case BTTV_BOARD_TWINHAN_DST:
card->gpio_mode = 0x2204f2c;
card->op_sync_orin = BT878_RISC_SYNC_MASK;
card->irq_err_ignore = BT878_APABORT | BT878_ARIPERR |
BT878_APPERR | BT878_AFBUS;
/* 25,21,14,11,10,9,8,3,2 then
* 0x33 = 5,4,1,0
* A_SEL=SML, DA_MLB, DA_SBR,
* DA_SDR=f, fifo trigger = 32 DWORDS
* IOM = 0 == audio A/D
* DPM = 0 == digital audio mode
* == async data parallel port
* then 0x33 (13 is set by start_capture)
* DA_APP = async data parallel port,
* ACAP_EN = 1,
* RISC+FIFO ENABLE */
break;
case BTTV_BOARD_PC_HDTV:
card->gpio_mode = 0x0100EC7B;
card->op_sync_orin = BT878_RISC_SYNC_MASK;
card->irq_err_ignore = BT878_AFBUS | BT878_AFDSR;
break;
default:
pr_err("Unknown bttv card type: %d\n", sub->core->type);
kfree(card);
return -ENODEV;
}
dprintk("dvb_bt8xx: identified card%d as %s\n", card->bttv_nr, card->card_name);
if (!(bttv_pci_dev = bttv_get_pcidev(card->bttv_nr))) {
pr_err("no pci device for card %d\n", card->bttv_nr);
kfree(card);
return -ENODEV;
}
if (!(card->bt = dvb_bt8xx_878_match(card->bttv_nr, bttv_pci_dev))) {
pr_err("unable to determine DMA core of card %d,\n", card->bttv_nr);
pr_err("if you have the ALSA bt87x audio driver installed, try removing it.\n");
kfree(card);
return -ENODEV;
}
mutex_init(&card->bt->gpio_lock);
card->bt->bttv_nr = sub->core->nr;
if ( (ret = dvb_bt8xx_load_card(card, sub->core->type)) ) {
kfree(card);
return ret;
}
dev_set_drvdata(&sub->dev, card);
return 0;
}
static void dvb_bt8xx_remove(struct bttv_sub_device *sub)
{
struct dvb_bt8xx_card *card = dev_get_drvdata(&sub->dev);
dprintk("dvb_bt8xx: unloading card%d\n", card->bttv_nr);
bt878_stop(card->bt);
tasklet_kill(&card->bt->tasklet);
dvb_net_release(&card->dvbnet);
card->demux.dmx.remove_frontend(&card->demux.dmx, &card->fe_mem);
card->demux.dmx.remove_frontend(&card->demux.dmx, &card->fe_hw);
dvb_dmxdev_release(&card->dmxdev);
dvb_dmx_release(&card->demux);
if (card->fe) {
dvb_unregister_frontend(card->fe);
dvb_frontend_detach(card->fe);
}
dvb_unregister_adapter(&card->dvb_adapter);
kfree(card);
}
static struct bttv_sub_driver driver = {
.drv = {
.name = "dvb-bt8xx",
},
.probe = dvb_bt8xx_probe,
.remove = dvb_bt8xx_remove,
/* FIXME:
* .shutdown = dvb_bt8xx_shutdown,
* .suspend = dvb_bt8xx_suspend,
* .resume = dvb_bt8xx_resume,
*/
};
static int __init dvb_bt8xx_init(void)
{
return bttv_sub_register(&driver, "dvb");
}
static void __exit dvb_bt8xx_exit(void)
{
bttv_sub_unregister(&driver);
}
module_init(dvb_bt8xx_init);
module_exit(dvb_bt8xx_exit);
MODULE_DESCRIPTION("Bt8xx based DVB adapter driver");
MODULE_AUTHOR("Florian Schirmer <jolt@tuxbox.org>");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,63 @@
/*
* Bt8xx based DVB adapter driver
*
* Copyright (C) 2002,2003 Florian Schirmer <jolt@tuxbox.org>
* Copyright (C) 2002 Peter Hettkamp <peter.hettkamp@htp-tel.de>
* Copyright (C) 1999-2001 Ralph Metzler & Marcus Metzler for convergence integrated media GmbH
* Copyright (C) 1998,1999 Christian Theiss <mistert@rz.fh-augsburg.de>
*
* 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 DVB_BT8XX_H
#define DVB_BT8XX_H
#include <linux/i2c.h>
#include <linux/mutex.h>
#include "dvbdev.h"
#include "dvb_net.h"
#include "bttv.h"
#include "mt352.h"
#include "sp887x.h"
#include "dst_common.h"
#include "nxt6000.h"
#include "cx24110.h"
#include "or51211.h"
#include "lgdt330x.h"
#include "zl10353.h"
#include "tuner-simple.h"
struct dvb_bt8xx_card {
struct mutex lock;
int nfeeds;
char card_name[32];
struct dvb_adapter dvb_adapter;
struct bt878 *bt;
unsigned int bttv_nr;
struct dvb_demux demux;
struct dmxdev dmxdev;
struct dmx_frontend fe_hw;
struct dmx_frontend fe_mem;
u32 gpio_mode;
u32 op_sync_orin;
u32 irq_err_ignore;
struct i2c_adapter *i2c_adapter;
struct dvb_net dvbnet;
struct dvb_frontend* fe;
};
#endif /* DVB_BT8XX_H */

View file

@ -0,0 +1,35 @@
config VIDEO_CX18
tristate "Conexant cx23418 MPEG encoder support"
depends on VIDEO_V4L2 && DVB_CORE && PCI && I2C
select I2C_ALGOBIT
select VIDEOBUF_VMALLOC
depends on RC_CORE
select VIDEO_TUNER
select VIDEO_TVEEPROM
select VIDEO_CX2341X
select VIDEO_CS5345
select DVB_S5H1409 if MEDIA_SUBDRV_AUTOSELECT
select MEDIA_TUNER_MXL5005S if MEDIA_SUBDRV_AUTOSELECT
select DVB_S5H1411 if MEDIA_SUBDRV_AUTOSELECT
select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT
select MEDIA_TUNER_TDA8290 if MEDIA_SUBDRV_AUTOSELECT
---help---
This is a video4linux driver for Conexant cx23418 based
PCI combo video recorder devices.
This is used in devices such as the Hauppauge HVR-1600
cards.
To compile this driver as a module, choose M here: the
module will be called cx18.
config VIDEO_CX18_ALSA
tristate "Conexant 23418 DMA audio support"
depends on VIDEO_CX18 && SND
select SND_PCM
---help---
This is a video4linux driver for direct (DMA) audio on
Conexant 23418 based TV cards using ALSA.
To compile this driver as a module, choose M here: the
module will be called cx18-alsa.

View file

@ -0,0 +1,13 @@
cx18-objs := cx18-driver.o cx18-cards.o cx18-i2c.o cx18-firmware.o cx18-gpio.o \
cx18-queue.o cx18-streams.o cx18-fileops.o cx18-ioctl.o cx18-controls.o \
cx18-mailbox.o cx18-vbi.o cx18-audio.o cx18-video.o cx18-irq.o \
cx18-av-core.o cx18-av-audio.o cx18-av-firmware.o cx18-av-vbi.o cx18-scb.o \
cx18-dvb.o cx18-io.o
cx18-alsa-objs := cx18-alsa-main.o cx18-alsa-pcm.o
obj-$(CONFIG_VIDEO_CX18) += cx18.o
obj-$(CONFIG_VIDEO_CX18_ALSA) += cx18-alsa.o
ccflags-y += -Idrivers/media/dvb-core
ccflags-y += -Idrivers/media/dvb-frontends
ccflags-y += -Idrivers/media/tuners

View file

@ -0,0 +1,296 @@
/*
* ALSA interface to cx18 PCM capture streams
*
* Copyright (C) 2009 Andy Walls <awalls@md.metrocast.net>
* Copyright (C) 2009 Devin Heitmueller <dheitmueller@kernellabs.com>
*
* Portions of this work were sponsored by ONELAN Limited.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/spinlock.h>
#include <media/v4l2-device.h>
#include <sound/core.h>
#include <sound/initval.h>
#include "cx18-driver.h"
#include "cx18-version.h"
#include "cx18-alsa.h"
#include "cx18-alsa-mixer.h"
#include "cx18-alsa-pcm.h"
int cx18_alsa_debug;
#define CX18_DEBUG_ALSA_INFO(fmt, arg...) \
do { \
if (cx18_alsa_debug & 2) \
printk(KERN_INFO "%s: " fmt, "cx18-alsa", ## arg); \
} while (0);
module_param_named(debug, cx18_alsa_debug, int, 0644);
MODULE_PARM_DESC(debug,
"Debug level (bitmask). Default: 0\n"
"\t\t\t 1/0x0001: warning\n"
"\t\t\t 2/0x0002: info\n");
MODULE_AUTHOR("Andy Walls");
MODULE_DESCRIPTION("CX23418 ALSA Interface");
MODULE_SUPPORTED_DEVICE("CX23418 MPEG2 encoder");
MODULE_LICENSE("GPL");
MODULE_VERSION(CX18_VERSION);
static inline
struct snd_cx18_card *to_snd_cx18_card(struct v4l2_device *v4l2_dev)
{
return to_cx18(v4l2_dev)->alsa;
}
static inline
struct snd_cx18_card *p_to_snd_cx18_card(struct v4l2_device **v4l2_dev)
{
return container_of(v4l2_dev, struct snd_cx18_card, v4l2_dev);
}
static void snd_cx18_card_free(struct snd_cx18_card *cxsc)
{
if (cxsc == NULL)
return;
if (cxsc->v4l2_dev != NULL)
to_cx18(cxsc->v4l2_dev)->alsa = NULL;
/* FIXME - take any other stopping actions needed */
kfree(cxsc);
}
static void snd_cx18_card_private_free(struct snd_card *sc)
{
if (sc == NULL)
return;
snd_cx18_card_free(sc->private_data);
sc->private_data = NULL;
sc->private_free = NULL;
}
static int snd_cx18_card_create(struct v4l2_device *v4l2_dev,
struct snd_card *sc,
struct snd_cx18_card **cxsc)
{
*cxsc = kzalloc(sizeof(struct snd_cx18_card), GFP_KERNEL);
if (*cxsc == NULL)
return -ENOMEM;
(*cxsc)->v4l2_dev = v4l2_dev;
(*cxsc)->sc = sc;
sc->private_data = *cxsc;
sc->private_free = snd_cx18_card_private_free;
return 0;
}
static int snd_cx18_card_set_names(struct snd_cx18_card *cxsc)
{
struct cx18 *cx = to_cx18(cxsc->v4l2_dev);
struct snd_card *sc = cxsc->sc;
/* sc->driver is used by alsa-lib's configurator: simple, unique */
strlcpy(sc->driver, "CX23418", sizeof(sc->driver));
/* sc->shortname is a symlink in /proc/asound: CX18-M -> cardN */
snprintf(sc->shortname, sizeof(sc->shortname), "CX18-%d",
cx->instance);
/* sc->longname is read from /proc/asound/cards */
snprintf(sc->longname, sizeof(sc->longname),
"CX23418 #%d %s TV/FM Radio/Line-In Capture",
cx->instance, cx->card_name);
return 0;
}
static int snd_cx18_init(struct v4l2_device *v4l2_dev)
{
struct cx18 *cx = to_cx18(v4l2_dev);
struct snd_card *sc = NULL;
struct snd_cx18_card *cxsc;
int ret;
/* Numbrs steps from "Writing an ALSA Driver" by Takashi Iwai */
/* (1) Check and increment the device index */
/* This is a no-op for us. We'll use the cx->instance */
/* (2) Create a card instance */
ret = snd_card_new(&cx->pci_dev->dev,
SNDRV_DEFAULT_IDX1, /* use first available id */
SNDRV_DEFAULT_STR1, /* xid from end of shortname*/
THIS_MODULE, 0, &sc);
if (ret) {
CX18_ALSA_ERR("%s: snd_card_new() failed with err %d\n",
__func__, ret);
goto err_exit;
}
/* (3) Create a main component */
ret = snd_cx18_card_create(v4l2_dev, sc, &cxsc);
if (ret) {
CX18_ALSA_ERR("%s: snd_cx18_card_create() failed with err %d\n",
__func__, ret);
goto err_exit_free;
}
/* (4) Set the driver ID and name strings */
snd_cx18_card_set_names(cxsc);
ret = snd_cx18_pcm_create(cxsc);
if (ret) {
CX18_ALSA_ERR("%s: snd_cx18_pcm_create() failed with err %d\n",
__func__, ret);
goto err_exit_free;
}
/* FIXME - proc files */
/* (7) Set the driver data and return 0 */
/* We do this out of normal order for PCI drivers to avoid races */
cx->alsa = cxsc;
/* (6) Register the card instance */
ret = snd_card_register(sc);
if (ret) {
cx->alsa = NULL;
CX18_ALSA_ERR("%s: snd_card_register() failed with err %d\n",
__func__, ret);
goto err_exit_free;
}
return 0;
err_exit_free:
if (sc != NULL)
snd_card_free(sc);
kfree(cxsc);
err_exit:
return ret;
}
static int cx18_alsa_load(struct cx18 *cx)
{
struct v4l2_device *v4l2_dev = &cx->v4l2_dev;
struct cx18_stream *s;
if (v4l2_dev == NULL) {
printk(KERN_ERR "cx18-alsa: %s: struct v4l2_device * is NULL\n",
__func__);
return 0;
}
cx = to_cx18(v4l2_dev);
if (cx == NULL) {
printk(KERN_ERR "cx18-alsa cx is NULL\n");
return 0;
}
s = &cx->streams[CX18_ENC_STREAM_TYPE_PCM];
if (s->video_dev == NULL) {
CX18_DEBUG_ALSA_INFO("%s: PCM stream for card is disabled - "
"skipping\n", __func__);
return 0;
}
if (cx->alsa != NULL) {
CX18_ALSA_ERR("%s: struct snd_cx18_card * already exists\n",
__func__);
return 0;
}
if (snd_cx18_init(v4l2_dev)) {
CX18_ALSA_ERR("%s: failed to create struct snd_cx18_card\n",
__func__);
} else {
CX18_DEBUG_ALSA_INFO("%s: created cx18 ALSA interface instance "
"\n", __func__);
}
return 0;
}
static int __init cx18_alsa_init(void)
{
printk(KERN_INFO "cx18-alsa: module loading...\n");
cx18_ext_init = &cx18_alsa_load;
return 0;
}
static void __exit snd_cx18_exit(struct snd_cx18_card *cxsc)
{
struct cx18 *cx = to_cx18(cxsc->v4l2_dev);
/* FIXME - pointer checks & shutdown cxsc */
snd_card_free(cxsc->sc);
cx->alsa = NULL;
}
static int __exit cx18_alsa_exit_callback(struct device *dev, void *data)
{
struct v4l2_device *v4l2_dev = dev_get_drvdata(dev);
struct snd_cx18_card *cxsc;
if (v4l2_dev == NULL) {
printk(KERN_ERR "cx18-alsa: %s: struct v4l2_device * is NULL\n",
__func__);
return 0;
}
cxsc = to_snd_cx18_card(v4l2_dev);
if (cxsc == NULL) {
CX18_ALSA_WARN("%s: struct snd_cx18_card * is NULL\n",
__func__);
return 0;
}
snd_cx18_exit(cxsc);
return 0;
}
static void __exit cx18_alsa_exit(void)
{
struct device_driver *drv;
int ret;
printk(KERN_INFO "cx18-alsa: module unloading...\n");
drv = driver_find("cx18", &pci_bus_type);
ret = driver_for_each_device(drv, NULL, NULL, cx18_alsa_exit_callback);
(void)ret; /* suppress compiler warning */
cx18_ext_init = NULL;
printk(KERN_INFO "cx18-alsa: module unload complete\n");
}
module_init(cx18_alsa_init);
module_exit(cx18_alsa_exit);

View file

@ -0,0 +1,175 @@
/*
* ALSA mixer controls for the
* ALSA interface to cx18 PCM capture streams
*
* Copyright (C) 2009 Andy Walls <awalls@md.metrocast.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/spinlock.h>
#include <linux/videodev2.h>
#include <media/v4l2-device.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/tlv.h>
#include "cx18-alsa.h"
#include "cx18-driver.h"
/*
* Note the cx18-av-core volume scale is funny, due to the alignment of the
* scale with another chip's range:
*
* v4l2_control value /512 indicated dB actual dB reg 0x8d4
* 0x0000 - 0x01ff 0 -119 -96 228
* 0x0200 - 0x02ff 1 -118 -96 228
* ...
* 0x2c00 - 0x2dff 22 -97 -96 228
* 0x2e00 - 0x2fff 23 -96 -96 228
* 0x3000 - 0x31ff 24 -95 -95 226
* ...
* 0xee00 - 0xefff 119 0 0 36
* ...
* 0xfe00 - 0xffff 127 +8 +8 20
*/
static inline int dB_to_cx18_av_vol(int dB)
{
if (dB < -96)
dB = -96;
else if (dB > 8)
dB = 8;
return (dB + 119) << 9;
}
static inline int cx18_av_vol_to_dB(int v)
{
if (v < (23 << 9))
v = (23 << 9);
else if (v > (127 << 9))
v = (127 << 9);
return (v >> 9) - 119;
}
static int snd_cx18_mixer_tv_vol_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
/* We're already translating values, just keep this control in dB */
uinfo->value.integer.min = -96;
uinfo->value.integer.max = 8;
uinfo->value.integer.step = 1;
return 0;
}
static int snd_cx18_mixer_tv_vol_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uctl)
{
struct snd_cx18_card *cxsc = snd_kcontrol_chip(kctl);
struct cx18 *cx = to_cx18(cxsc->v4l2_dev);
struct v4l2_control vctrl;
int ret;
vctrl.id = V4L2_CID_AUDIO_VOLUME;
vctrl.value = dB_to_cx18_av_vol(uctl->value.integer.value[0]);
snd_cx18_lock(cxsc);
ret = v4l2_subdev_call(cx->sd_av, core, g_ctrl, &vctrl);
snd_cx18_unlock(cxsc);
if (!ret)
uctl->value.integer.value[0] = cx18_av_vol_to_dB(vctrl.value);
return ret;
}
static int snd_cx18_mixer_tv_vol_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uctl)
{
struct snd_cx18_card *cxsc = snd_kcontrol_chip(kctl);
struct cx18 *cx = to_cx18(cxsc->v4l2_dev);
struct v4l2_control vctrl;
int ret;
vctrl.id = V4L2_CID_AUDIO_VOLUME;
vctrl.value = dB_to_cx18_av_vol(uctl->value.integer.value[0]);
snd_cx18_lock(cxsc);
/* Fetch current state */
ret = v4l2_subdev_call(cx->sd_av, core, g_ctrl, &vctrl);
if (ret ||
(cx18_av_vol_to_dB(vctrl.value) != uctl->value.integer.value[0])) {
/* Set, if needed */
vctrl.value = dB_to_cx18_av_vol(uctl->value.integer.value[0]);
ret = v4l2_subdev_call(cx->sd_av, core, s_ctrl, &vctrl);
if (!ret)
ret = 1; /* Indicate control was changed w/o error */
}
snd_cx18_unlock(cxsc);
return ret;
}
/* This is a bit of overkill, the slider is already in dB internally */
static DECLARE_TLV_DB_SCALE(snd_cx18_mixer_tv_vol_db_scale, -9600, 100, 0);
static struct snd_kcontrol_new snd_cx18_mixer_tv_vol __initdata = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Analog TV Capture Volume",
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ,
.info = snd_cx18_mixer_tv_volume_info,
.get = snd_cx18_mixer_tv_volume_get,
.put = snd_cx18_mixer_tv_volume_put,
.tlv.p = snd_cx18_mixer_tv_vol_db_scale
};
/* FIXME - add mute switch and balance, bass, treble sliders:
V4L2_CID_AUDIO_MUTE
V4L2_CID_AUDIO_BALANCE
V4L2_CID_AUDIO_BASS
V4L2_CID_AUDIO_TREBLE
*/
/* FIXME - add stereo, lang1, lang2, mono menu */
/* FIXME - add CS5345 I2S volume for HVR-1600 */
int __init snd_cx18_mixer_create(struct snd_cx18_card *cxsc)
{
struct v4l2_device *v4l2_dev = cxsc->v4l2_dev;
struct snd_card *sc = cxsc->sc;
int ret;
strlcpy(sc->mixername, "CX23418 Mixer", sizeof(sc->mixername));
ret = snd_ctl_add(sc, snd_ctl_new1(snd_cx18_mixer_tv_vol, cxsc));
if (ret) {
CX18_ALSA_WARN("%s: failed to add %s control, err %d\n",
__func__, snd_cx18_mixer_tv_vol.name, ret);
}
return ret;
}

View file

@ -0,0 +1,23 @@
/*
* ALSA mixer controls for the
* ALSA interface to cx18 PCM capture streams
*
* Copyright (C) 2009 Andy Walls <awalls@md.metrocast.net>
*
* 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
*/
int __init snd_cx18_mixer_create(struct snd_cx18_card *cxsc);

View file

@ -0,0 +1,357 @@
/*
* ALSA PCM device for the
* ALSA interface to cx18 PCM capture streams
*
* Copyright (C) 2009 Andy Walls <awalls@md.metrocast.net>
* Copyright (C) 2009 Devin Heitmueller <dheitmueller@kernellabs.com>
*
* Portions of this work were sponsored by ONELAN Limited.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/vmalloc.h>
#include <media/v4l2-device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include "cx18-driver.h"
#include "cx18-queue.h"
#include "cx18-streams.h"
#include "cx18-fileops.h"
#include "cx18-alsa.h"
#include "cx18-alsa-pcm.h"
static unsigned int pcm_debug;
module_param(pcm_debug, int, 0644);
MODULE_PARM_DESC(pcm_debug, "enable debug messages for pcm");
#define dprintk(fmt, arg...) do { \
if (pcm_debug) \
printk(KERN_INFO "cx18-alsa-pcm %s: " fmt, \
__func__, ##arg); \
} while (0)
static struct snd_pcm_hardware snd_cx18_hw_capture = {
.info = SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP_VALID,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_48000,
.rate_min = 48000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 62720 * 8, /* just about the value in usbaudio.c */
.period_bytes_min = 64, /* 12544/2, */
.period_bytes_max = 12544,
.periods_min = 2,
.periods_max = 98, /* 12544, */
};
void cx18_alsa_announce_pcm_data(struct snd_cx18_card *cxsc, u8 *pcm_data,
size_t num_bytes)
{
struct snd_pcm_substream *substream;
struct snd_pcm_runtime *runtime;
unsigned int oldptr;
unsigned int stride;
int period_elapsed = 0;
int length;
dprintk("cx18 alsa announce ptr=%p data=%p num_bytes=%zu\n", cxsc,
pcm_data, num_bytes);
substream = cxsc->capture_pcm_substream;
if (substream == NULL) {
dprintk("substream was NULL\n");
return;
}
runtime = substream->runtime;
if (runtime == NULL) {
dprintk("runtime was NULL\n");
return;
}
stride = runtime->frame_bits >> 3;
if (stride == 0) {
dprintk("stride is zero\n");
return;
}
length = num_bytes / stride;
if (length == 0) {
dprintk("%s: length was zero\n", __func__);
return;
}
if (runtime->dma_area == NULL) {
dprintk("dma area was NULL - ignoring\n");
return;
}
oldptr = cxsc->hwptr_done_capture;
if (oldptr + length >= runtime->buffer_size) {
unsigned int cnt =
runtime->buffer_size - oldptr;
memcpy(runtime->dma_area + oldptr * stride, pcm_data,
cnt * stride);
memcpy(runtime->dma_area, pcm_data + cnt * stride,
length * stride - cnt * stride);
} else {
memcpy(runtime->dma_area + oldptr * stride, pcm_data,
length * stride);
}
snd_pcm_stream_lock(substream);
cxsc->hwptr_done_capture += length;
if (cxsc->hwptr_done_capture >=
runtime->buffer_size)
cxsc->hwptr_done_capture -=
runtime->buffer_size;
cxsc->capture_transfer_done += length;
if (cxsc->capture_transfer_done >=
runtime->period_size) {
cxsc->capture_transfer_done -=
runtime->period_size;
period_elapsed = 1;
}
snd_pcm_stream_unlock(substream);
if (period_elapsed)
snd_pcm_period_elapsed(substream);
}
static int snd_cx18_pcm_capture_open(struct snd_pcm_substream *substream)
{
struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct v4l2_device *v4l2_dev = cxsc->v4l2_dev;
struct cx18 *cx = to_cx18(v4l2_dev);
struct cx18_stream *s;
struct cx18_open_id item;
int ret;
/* Instruct the cx18 to start sending packets */
snd_cx18_lock(cxsc);
s = &cx->streams[CX18_ENC_STREAM_TYPE_PCM];
item.cx = cx;
item.type = s->type;
item.open_id = cx->open_id++;
/* See if the stream is available */
if (cx18_claim_stream(&item, item.type)) {
/* No, it's already in use */
snd_cx18_unlock(cxsc);
return -EBUSY;
}
if (test_bit(CX18_F_S_STREAMOFF, &s->s_flags) ||
test_and_set_bit(CX18_F_S_STREAMING, &s->s_flags)) {
/* We're already streaming. No additional action required */
snd_cx18_unlock(cxsc);
return 0;
}
runtime->hw = snd_cx18_hw_capture;
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
cxsc->capture_pcm_substream = substream;
runtime->private_data = cx;
cx->pcm_announce_callback = cx18_alsa_announce_pcm_data;
/* Not currently streaming, so start it up */
set_bit(CX18_F_S_STREAMING, &s->s_flags);
ret = cx18_start_v4l2_encode_stream(s);
snd_cx18_unlock(cxsc);
return ret;
}
static int snd_cx18_pcm_capture_close(struct snd_pcm_substream *substream)
{
struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream);
struct v4l2_device *v4l2_dev = cxsc->v4l2_dev;
struct cx18 *cx = to_cx18(v4l2_dev);
struct cx18_stream *s;
/* Instruct the cx18 to stop sending packets */
snd_cx18_lock(cxsc);
s = &cx->streams[CX18_ENC_STREAM_TYPE_PCM];
cx18_stop_v4l2_encode_stream(s, 0);
clear_bit(CX18_F_S_STREAMING, &s->s_flags);
cx18_release_stream(s);
cx->pcm_announce_callback = NULL;
snd_cx18_unlock(cxsc);
return 0;
}
static int snd_cx18_pcm_ioctl(struct snd_pcm_substream *substream,
unsigned int cmd, void *arg)
{
struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream);
int ret;
snd_cx18_lock(cxsc);
ret = snd_pcm_lib_ioctl(substream, cmd, arg);
snd_cx18_unlock(cxsc);
return ret;
}
static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs,
size_t size)
{
struct snd_pcm_runtime *runtime = subs->runtime;
dprintk("Allocating vbuffer\n");
if (runtime->dma_area) {
if (runtime->dma_bytes > size)
return 0;
vfree(runtime->dma_area);
}
runtime->dma_area = vmalloc(size);
if (!runtime->dma_area)
return -ENOMEM;
runtime->dma_bytes = size;
return 0;
}
static int snd_cx18_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
dprintk("%s called\n", __func__);
return snd_pcm_alloc_vmalloc_buffer(substream,
params_buffer_bytes(params));
}
static int snd_cx18_pcm_hw_free(struct snd_pcm_substream *substream)
{
struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream);
unsigned long flags;
spin_lock_irqsave(&cxsc->slock, flags);
if (substream->runtime->dma_area) {
dprintk("freeing pcm capture region\n");
vfree(substream->runtime->dma_area);
substream->runtime->dma_area = NULL;
}
spin_unlock_irqrestore(&cxsc->slock, flags);
return 0;
}
static int snd_cx18_pcm_prepare(struct snd_pcm_substream *substream)
{
struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream);
cxsc->hwptr_done_capture = 0;
cxsc->capture_transfer_done = 0;
return 0;
}
static int snd_cx18_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
return 0;
}
static
snd_pcm_uframes_t snd_cx18_pcm_pointer(struct snd_pcm_substream *substream)
{
unsigned long flags;
snd_pcm_uframes_t hwptr_done;
struct snd_cx18_card *cxsc = snd_pcm_substream_chip(substream);
spin_lock_irqsave(&cxsc->slock, flags);
hwptr_done = cxsc->hwptr_done_capture;
spin_unlock_irqrestore(&cxsc->slock, flags);
return hwptr_done;
}
static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs,
unsigned long offset)
{
void *pageptr = subs->runtime->dma_area + offset;
return vmalloc_to_page(pageptr);
}
static struct snd_pcm_ops snd_cx18_pcm_capture_ops = {
.open = snd_cx18_pcm_capture_open,
.close = snd_cx18_pcm_capture_close,
.ioctl = snd_cx18_pcm_ioctl,
.hw_params = snd_cx18_pcm_hw_params,
.hw_free = snd_cx18_pcm_hw_free,
.prepare = snd_cx18_pcm_prepare,
.trigger = snd_cx18_pcm_trigger,
.pointer = snd_cx18_pcm_pointer,
.page = snd_pcm_get_vmalloc_page,
};
int snd_cx18_pcm_create(struct snd_cx18_card *cxsc)
{
struct snd_pcm *sp;
struct snd_card *sc = cxsc->sc;
struct v4l2_device *v4l2_dev = cxsc->v4l2_dev;
struct cx18 *cx = to_cx18(v4l2_dev);
int ret;
ret = snd_pcm_new(sc, "CX23418 PCM",
0, /* PCM device 0, the only one for this card */
0, /* 0 playback substreams */
1, /* 1 capture substream */
&sp);
if (ret) {
CX18_ALSA_ERR("%s: snd_cx18_pcm_create() failed with err %d\n",
__func__, ret);
goto err_exit;
}
spin_lock_init(&cxsc->slock);
snd_pcm_set_ops(sp, SNDRV_PCM_STREAM_CAPTURE,
&snd_cx18_pcm_capture_ops);
sp->info_flags = 0;
sp->private_data = cxsc;
strlcpy(sp->name, cx->card_name, sizeof(sp->name));
return 0;
err_exit:
return ret;
}

View file

@ -0,0 +1,27 @@
/*
* ALSA PCM device for the
* ALSA interface to cx18 PCM capture streams
*
* Copyright (C) 2009 Andy Walls <awalls@md.metrocast.net>
*
* 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
*/
int snd_cx18_pcm_create(struct snd_cx18_card *cxsc);
/* Used by cx18-mailbox to announce the PCM data to the module */
void cx18_alsa_announce_pcm_data(struct snd_cx18_card *card, u8 *pcm_data,
size_t num_bytes);

View file

@ -0,0 +1,74 @@
/*
* ALSA interface to cx18 PCM capture streams
*
* Copyright (C) 2009 Andy Walls <awalls@md.metrocast.net>
*
* 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
*/
struct snd_card;
struct snd_cx18_card {
struct v4l2_device *v4l2_dev;
struct snd_card *sc;
unsigned int capture_transfer_done;
unsigned int hwptr_done_capture;
struct snd_pcm_substream *capture_pcm_substream;
spinlock_t slock;
};
extern int cx18_alsa_debug;
/*
* File operations that manipulate the encoder or video or audio subdevices
* need to be serialized. Use the same lock we use for v4l2 file ops.
*/
static inline void snd_cx18_lock(struct snd_cx18_card *cxsc)
{
struct cx18 *cx = to_cx18(cxsc->v4l2_dev);
mutex_lock(&cx->serialize_lock);
}
static inline void snd_cx18_unlock(struct snd_cx18_card *cxsc)
{
struct cx18 *cx = to_cx18(cxsc->v4l2_dev);
mutex_unlock(&cx->serialize_lock);
}
#define CX18_ALSA_DBGFLG_WARN (1 << 0)
#define CX18_ALSA_DBGFLG_INFO (1 << 1)
#define CX18_ALSA_DEBUG(x, type, fmt, args...) \
do { \
if ((x) & cx18_alsa_debug) \
printk(KERN_INFO "%s-alsa: " type ": " fmt, \
v4l2_dev->name , ## args); \
} while (0)
#define CX18_ALSA_DEBUG_WARN(fmt, args...) \
CX18_ALSA_DEBUG(CX18_ALSA_DBGFLG_WARN, "warning", fmt , ## args)
#define CX18_ALSA_DEBUG_INFO(fmt, args...) \
CX18_ALSA_DEBUG(CX18_ALSA_DBGFLG_INFO, "info", fmt , ## args)
#define CX18_ALSA_ERR(fmt, args...) \
printk(KERN_ERR "%s-alsa: " fmt, v4l2_dev->name , ## args)
#define CX18_ALSA_WARN(fmt, args...) \
printk(KERN_WARNING "%s-alsa: " fmt, v4l2_dev->name , ## args)
#define CX18_ALSA_INFO(fmt, args...) \
printk(KERN_INFO "%s-alsa: " fmt, v4l2_dev->name , ## args)

View file

@ -0,0 +1,92 @@
/*
* cx18 audio-related functions
*
* Derived from ivtv-audio.c
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* 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 "cx18-driver.h"
#include "cx18-io.h"
#include "cx18-cards.h"
#include "cx18-audio.h"
#define CX18_AUDIO_ENABLE 0xc72014
#define CX18_AI1_MUX_MASK 0x30
#define CX18_AI1_MUX_I2S1 0x00
#define CX18_AI1_MUX_I2S2 0x10
#define CX18_AI1_MUX_843_I2S 0x20
/* Selects the audio input and output according to the current
settings. */
int cx18_audio_set_io(struct cx18 *cx)
{
const struct cx18_card_audio_input *in;
u32 u, v;
int err;
/* Determine which input to use */
if (test_bit(CX18_F_I_RADIO_USER, &cx->i_flags))
in = &cx->card->radio_input;
else
in = &cx->card->audio_inputs[cx->audio_input];
/* handle muxer chips */
v4l2_subdev_call(cx->sd_extmux, audio, s_routing,
(u32) in->muxer_input, 0, 0);
err = cx18_call_hw_err(cx, cx->card->hw_audio_ctrl,
audio, s_routing, in->audio_input, 0, 0);
if (err)
return err;
/* FIXME - this internal mux should be abstracted to a subdev */
u = cx18_read_reg(cx, CX18_AUDIO_ENABLE);
v = u & ~CX18_AI1_MUX_MASK;
switch (in->audio_input) {
case CX18_AV_AUDIO_SERIAL1:
v |= CX18_AI1_MUX_I2S1;
break;
case CX18_AV_AUDIO_SERIAL2:
v |= CX18_AI1_MUX_I2S2;
break;
default:
v |= CX18_AI1_MUX_843_I2S;
break;
}
if (v == u) {
/* force a toggle of some AI1 MUX control bits */
u &= ~CX18_AI1_MUX_MASK;
switch (in->audio_input) {
case CX18_AV_AUDIO_SERIAL1:
u |= CX18_AI1_MUX_843_I2S;
break;
case CX18_AV_AUDIO_SERIAL2:
u |= CX18_AI1_MUX_843_I2S;
break;
default:
u |= CX18_AI1_MUX_I2S1;
break;
}
cx18_write_reg_expect(cx, u | 0xb00, CX18_AUDIO_ENABLE,
u, CX18_AI1_MUX_MASK);
}
cx18_write_reg_expect(cx, v | 0xb00, CX18_AUDIO_ENABLE,
v, CX18_AI1_MUX_MASK);
return 0;
}

View file

@ -0,0 +1,24 @@
/*
* cx18 audio-related functions
*
* Derived from ivtv-audio.c
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* 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
*/
int cx18_audio_set_io(struct cx18 *cx);

View file

@ -0,0 +1,471 @@
/*
* cx18 ADEC audio functions
*
* Derived from cx25840-audio.c
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* 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 "cx18-driver.h"
static int set_audclk_freq(struct cx18 *cx, u32 freq)
{
struct cx18_av_state *state = &cx->av_state;
if (freq != 32000 && freq != 44100 && freq != 48000)
return -EINVAL;
/*
* The PLL parameters are based on the external crystal frequency that
* would ideally be:
*
* NTSC Color subcarrier freq * 8 =
* 4.5 MHz/286 * 455/2 * 8 = 28.63636363... MHz
*
* The accidents of history and rationale that explain from where this
* combination of magic numbers originate can be found in:
*
* [1] Abrahams, I. C., "Choice of Chrominance Subcarrier Frequency in
* the NTSC Standards", Proceedings of the I-R-E, January 1954, pp 79-80
*
* [2] Abrahams, I. C., "The 'Frequency Interleaving' Principle in the
* NTSC Standards", Proceedings of the I-R-E, January 1954, pp 81-83
*
* As Mike Bradley has rightly pointed out, it's not the exact crystal
* frequency that matters, only that all parts of the driver and
* firmware are using the same value (close to the ideal value).
*
* Since I have a strong suspicion that, if the firmware ever assumes a
* crystal value at all, it will assume 28.636360 MHz, the crystal
* freq used in calculations in this driver will be:
*
* xtal_freq = 28.636360 MHz
*
* an error of less than 0.13 ppm which is way, way better than any off
* the shelf crystal will have for accuracy anyway.
*
* Below I aim to run the PLLs' VCOs near 400 MHz to minimze error.
*
* Many thanks to Jeff Campbell and Mike Bradley for their extensive
* investigation, experimentation, testing, and suggested solutions of
* of audio/video sync problems with SVideo and CVBS captures.
*/
if (state->aud_input > CX18_AV_AUDIO_SERIAL2) {
switch (freq) {
case 32000:
/*
* VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04
* AUX_PLL Integer = 0x0d, AUX PLL Post Divider = 0x20
*/
cx18_av_write4(cx, 0x108, 0x200d040f);
/* VID_PLL Fraction = 0x2be2fe */
/* xtal * 0xf.15f17f0/4 = 108 MHz: 432 MHz pre-postdiv*/
cx18_av_write4(cx, 0x10c, 0x002be2fe);
/* AUX_PLL Fraction = 0x176740c */
/* xtal * 0xd.bb3a060/0x20 = 32000 * 384: 393 MHz p-pd*/
cx18_av_write4(cx, 0x110, 0x0176740c);
/* src3/4/6_ctl */
/* 0x1.f77f = (4 * xtal/8*2/455) / 32000 */
cx18_av_write4(cx, 0x900, 0x0801f77f);
cx18_av_write4(cx, 0x904, 0x0801f77f);
cx18_av_write4(cx, 0x90c, 0x0801f77f);
/* SA_MCLK_SEL=1, SA_MCLK_DIV=0x20 */
cx18_av_write(cx, 0x127, 0x60);
/* AUD_COUNT = 0x2fff = 8 samples * 4 * 384 - 1 */
cx18_av_write4(cx, 0x12c, 0x11202fff);
/*
* EN_AV_LOCK = 0
* VID_COUNT = 0x0d2ef8 = 107999.000 * 8 =
* ((8 samples/32,000) * (13,500,000 * 8) * 4 - 1) * 8
*/
cx18_av_write4(cx, 0x128, 0xa00d2ef8);
break;
case 44100:
/*
* VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04
* AUX_PLL Integer = 0x0e, AUX PLL Post Divider = 0x18
*/
cx18_av_write4(cx, 0x108, 0x180e040f);
/* VID_PLL Fraction = 0x2be2fe */
/* xtal * 0xf.15f17f0/4 = 108 MHz: 432 MHz pre-postdiv*/
cx18_av_write4(cx, 0x10c, 0x002be2fe);
/* AUX_PLL Fraction = 0x062a1f2 */
/* xtal * 0xe.3150f90/0x18 = 44100 * 384: 406 MHz p-pd*/
cx18_av_write4(cx, 0x110, 0x0062a1f2);
/* src3/4/6_ctl */
/* 0x1.6d59 = (4 * xtal/8*2/455) / 44100 */
cx18_av_write4(cx, 0x900, 0x08016d59);
cx18_av_write4(cx, 0x904, 0x08016d59);
cx18_av_write4(cx, 0x90c, 0x08016d59);
/* SA_MCLK_SEL=1, SA_MCLK_DIV=0x18 */
cx18_av_write(cx, 0x127, 0x58);
/* AUD_COUNT = 0x92ff = 49 samples * 2 * 384 - 1 */
cx18_av_write4(cx, 0x12c, 0x112092ff);
/*
* EN_AV_LOCK = 0
* VID_COUNT = 0x1d4bf8 = 239999.000 * 8 =
* ((49 samples/44,100) * (13,500,000 * 8) * 2 - 1) * 8
*/
cx18_av_write4(cx, 0x128, 0xa01d4bf8);
break;
case 48000:
/*
* VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04
* AUX_PLL Integer = 0x0e, AUX PLL Post Divider = 0x16
*/
cx18_av_write4(cx, 0x108, 0x160e040f);
/* VID_PLL Fraction = 0x2be2fe */
/* xtal * 0xf.15f17f0/4 = 108 MHz: 432 MHz pre-postdiv*/
cx18_av_write4(cx, 0x10c, 0x002be2fe);
/* AUX_PLL Fraction = 0x05227ad */
/* xtal * 0xe.2913d68/0x16 = 48000 * 384: 406 MHz p-pd*/
cx18_av_write4(cx, 0x110, 0x005227ad);
/* src3/4/6_ctl */
/* 0x1.4faa = (4 * xtal/8*2/455) / 48000 */
cx18_av_write4(cx, 0x900, 0x08014faa);
cx18_av_write4(cx, 0x904, 0x08014faa);
cx18_av_write4(cx, 0x90c, 0x08014faa);
/* SA_MCLK_SEL=1, SA_MCLK_DIV=0x16 */
cx18_av_write(cx, 0x127, 0x56);
/* AUD_COUNT = 0x5fff = 4 samples * 16 * 384 - 1 */
cx18_av_write4(cx, 0x12c, 0x11205fff);
/*
* EN_AV_LOCK = 0
* VID_COUNT = 0x1193f8 = 143999.000 * 8 =
* ((4 samples/48,000) * (13,500,000 * 8) * 16 - 1) * 8
*/
cx18_av_write4(cx, 0x128, 0xa01193f8);
break;
}
} else {
switch (freq) {
case 32000:
/*
* VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04
* AUX_PLL Integer = 0x0d, AUX PLL Post Divider = 0x30
*/
cx18_av_write4(cx, 0x108, 0x300d040f);
/* VID_PLL Fraction = 0x2be2fe */
/* xtal * 0xf.15f17f0/4 = 108 MHz: 432 MHz pre-postdiv*/
cx18_av_write4(cx, 0x10c, 0x002be2fe);
/* AUX_PLL Fraction = 0x176740c */
/* xtal * 0xd.bb3a060/0x30 = 32000 * 256: 393 MHz p-pd*/
cx18_av_write4(cx, 0x110, 0x0176740c);
/* src1_ctl */
/* 0x1.0000 = 32000/32000 */
cx18_av_write4(cx, 0x8f8, 0x08010000);
/* src3/4/6_ctl */
/* 0x2.0000 = 2 * (32000/32000) */
cx18_av_write4(cx, 0x900, 0x08020000);
cx18_av_write4(cx, 0x904, 0x08020000);
cx18_av_write4(cx, 0x90c, 0x08020000);
/* SA_MCLK_SEL=1, SA_MCLK_DIV=0x30 */
cx18_av_write(cx, 0x127, 0x70);
/* AUD_COUNT = 0x1fff = 8 samples * 4 * 256 - 1 */
cx18_av_write4(cx, 0x12c, 0x11201fff);
/*
* EN_AV_LOCK = 0
* VID_COUNT = 0x0d2ef8 = 107999.000 * 8 =
* ((8 samples/32,000) * (13,500,000 * 8) * 4 - 1) * 8
*/
cx18_av_write4(cx, 0x128, 0xa00d2ef8);
break;
case 44100:
/*
* VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04
* AUX_PLL Integer = 0x0e, AUX PLL Post Divider = 0x24
*/
cx18_av_write4(cx, 0x108, 0x240e040f);
/* VID_PLL Fraction = 0x2be2fe */
/* xtal * 0xf.15f17f0/4 = 108 MHz: 432 MHz pre-postdiv*/
cx18_av_write4(cx, 0x10c, 0x002be2fe);
/* AUX_PLL Fraction = 0x062a1f2 */
/* xtal * 0xe.3150f90/0x24 = 44100 * 256: 406 MHz p-pd*/
cx18_av_write4(cx, 0x110, 0x0062a1f2);
/* src1_ctl */
/* 0x1.60cd = 44100/32000 */
cx18_av_write4(cx, 0x8f8, 0x080160cd);
/* src3/4/6_ctl */
/* 0x1.7385 = 2 * (32000/44100) */
cx18_av_write4(cx, 0x900, 0x08017385);
cx18_av_write4(cx, 0x904, 0x08017385);
cx18_av_write4(cx, 0x90c, 0x08017385);
/* SA_MCLK_SEL=1, SA_MCLK_DIV=0x24 */
cx18_av_write(cx, 0x127, 0x64);
/* AUD_COUNT = 0x61ff = 49 samples * 2 * 256 - 1 */
cx18_av_write4(cx, 0x12c, 0x112061ff);
/*
* EN_AV_LOCK = 0
* VID_COUNT = 0x1d4bf8 = 239999.000 * 8 =
* ((49 samples/44,100) * (13,500,000 * 8) * 2 - 1) * 8
*/
cx18_av_write4(cx, 0x128, 0xa01d4bf8);
break;
case 48000:
/*
* VID_PLL Integer = 0x0f, VID_PLL Post Divider = 0x04
* AUX_PLL Integer = 0x0d, AUX PLL Post Divider = 0x20
*/
cx18_av_write4(cx, 0x108, 0x200d040f);
/* VID_PLL Fraction = 0x2be2fe */
/* xtal * 0xf.15f17f0/4 = 108 MHz: 432 MHz pre-postdiv*/
cx18_av_write4(cx, 0x10c, 0x002be2fe);
/* AUX_PLL Fraction = 0x176740c */
/* xtal * 0xd.bb3a060/0x20 = 48000 * 256: 393 MHz p-pd*/
cx18_av_write4(cx, 0x110, 0x0176740c);
/* src1_ctl */
/* 0x1.8000 = 48000/32000 */
cx18_av_write4(cx, 0x8f8, 0x08018000);
/* src3/4/6_ctl */
/* 0x1.5555 = 2 * (32000/48000) */
cx18_av_write4(cx, 0x900, 0x08015555);
cx18_av_write4(cx, 0x904, 0x08015555);
cx18_av_write4(cx, 0x90c, 0x08015555);
/* SA_MCLK_SEL=1, SA_MCLK_DIV=0x20 */
cx18_av_write(cx, 0x127, 0x60);
/* AUD_COUNT = 0x3fff = 4 samples * 16 * 256 - 1 */
cx18_av_write4(cx, 0x12c, 0x11203fff);
/*
* EN_AV_LOCK = 0
* VID_COUNT = 0x1193f8 = 143999.000 * 8 =
* ((4 samples/48,000) * (13,500,000 * 8) * 16 - 1) * 8
*/
cx18_av_write4(cx, 0x128, 0xa01193f8);
break;
}
}
state->audclk_freq = freq;
return 0;
}
void cx18_av_audio_set_path(struct cx18 *cx)
{
struct cx18_av_state *state = &cx->av_state;
u8 v;
/* stop microcontroller */
v = cx18_av_read(cx, 0x803) & ~0x10;
cx18_av_write_expect(cx, 0x803, v, v, 0x1f);
/* assert soft reset */
v = cx18_av_read(cx, 0x810) | 0x01;
cx18_av_write_expect(cx, 0x810, v, v, 0x0f);
/* Mute everything to prevent the PFFT! */
cx18_av_write(cx, 0x8d3, 0x1f);
if (state->aud_input <= CX18_AV_AUDIO_SERIAL2) {
/* Set Path1 to Serial Audio Input */
cx18_av_write4(cx, 0x8d0, 0x01011012);
/* The microcontroller should not be started for the
* non-tuner inputs: autodetection is specific for
* TV audio. */
} else {
/* Set Path1 to Analog Demod Main Channel */
cx18_av_write4(cx, 0x8d0, 0x1f063870);
}
set_audclk_freq(cx, state->audclk_freq);
/* deassert soft reset */
v = cx18_av_read(cx, 0x810) & ~0x01;
cx18_av_write_expect(cx, 0x810, v, v, 0x0f);
if (state->aud_input > CX18_AV_AUDIO_SERIAL2) {
/* When the microcontroller detects the
* audio format, it will unmute the lines */
v = cx18_av_read(cx, 0x803) | 0x10;
cx18_av_write_expect(cx, 0x803, v, v, 0x1f);
}
}
static void set_volume(struct cx18 *cx, int volume)
{
/* First convert the volume to msp3400 values (0-127) */
int vol = volume >> 9;
/* now scale it up to cx18_av values
* -114dB to -96dB maps to 0
* this should be 19, but in my testing that was 4dB too loud */
if (vol <= 23)
vol = 0;
else
vol -= 23;
/* PATH1_VOLUME */
cx18_av_write(cx, 0x8d4, 228 - (vol * 2));
}
static void set_bass(struct cx18 *cx, int bass)
{
/* PATH1_EQ_BASS_VOL */
cx18_av_and_or(cx, 0x8d9, ~0x3f, 48 - (bass * 48 / 0xffff));
}
static void set_treble(struct cx18 *cx, int treble)
{
/* PATH1_EQ_TREBLE_VOL */
cx18_av_and_or(cx, 0x8db, ~0x3f, 48 - (treble * 48 / 0xffff));
}
static void set_balance(struct cx18 *cx, int balance)
{
int bal = balance >> 8;
if (bal > 0x80) {
/* PATH1_BAL_LEFT */
cx18_av_and_or(cx, 0x8d5, 0x7f, 0x80);
/* PATH1_BAL_LEVEL */
cx18_av_and_or(cx, 0x8d5, ~0x7f, bal & 0x7f);
} else {
/* PATH1_BAL_LEFT */
cx18_av_and_or(cx, 0x8d5, 0x7f, 0x00);
/* PATH1_BAL_LEVEL */
cx18_av_and_or(cx, 0x8d5, ~0x7f, 0x80 - bal);
}
}
static void set_mute(struct cx18 *cx, int mute)
{
struct cx18_av_state *state = &cx->av_state;
u8 v;
if (state->aud_input > CX18_AV_AUDIO_SERIAL2) {
/* Must turn off microcontroller in order to mute sound.
* Not sure if this is the best method, but it does work.
* If the microcontroller is running, then it will undo any
* changes to the mute register. */
v = cx18_av_read(cx, 0x803);
if (mute) {
/* disable microcontroller */
v &= ~0x10;
cx18_av_write_expect(cx, 0x803, v, v, 0x1f);
cx18_av_write(cx, 0x8d3, 0x1f);
} else {
/* enable microcontroller */
v |= 0x10;
cx18_av_write_expect(cx, 0x803, v, v, 0x1f);
}
} else {
/* SRC1_MUTE_EN */
cx18_av_and_or(cx, 0x8d3, ~0x2, mute ? 0x02 : 0x00);
}
}
int cx18_av_s_clock_freq(struct v4l2_subdev *sd, u32 freq)
{
struct cx18 *cx = v4l2_get_subdevdata(sd);
struct cx18_av_state *state = &cx->av_state;
int retval;
u8 v;
if (state->aud_input > CX18_AV_AUDIO_SERIAL2) {
v = cx18_av_read(cx, 0x803) & ~0x10;
cx18_av_write_expect(cx, 0x803, v, v, 0x1f);
cx18_av_write(cx, 0x8d3, 0x1f);
}
v = cx18_av_read(cx, 0x810) | 0x1;
cx18_av_write_expect(cx, 0x810, v, v, 0x0f);
retval = set_audclk_freq(cx, freq);
v = cx18_av_read(cx, 0x810) & ~0x1;
cx18_av_write_expect(cx, 0x810, v, v, 0x0f);
if (state->aud_input > CX18_AV_AUDIO_SERIAL2) {
v = cx18_av_read(cx, 0x803) | 0x10;
cx18_av_write_expect(cx, 0x803, v, v, 0x1f);
}
return retval;
}
static int cx18_av_audio_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct v4l2_subdev *sd = to_sd(ctrl);
struct cx18 *cx = v4l2_get_subdevdata(sd);
switch (ctrl->id) {
case V4L2_CID_AUDIO_VOLUME:
set_volume(cx, ctrl->val);
break;
case V4L2_CID_AUDIO_BASS:
set_bass(cx, ctrl->val);
break;
case V4L2_CID_AUDIO_TREBLE:
set_treble(cx, ctrl->val);
break;
case V4L2_CID_AUDIO_BALANCE:
set_balance(cx, ctrl->val);
break;
case V4L2_CID_AUDIO_MUTE:
set_mute(cx, ctrl->val);
break;
default:
return -EINVAL;
}
return 0;
}
const struct v4l2_ctrl_ops cx18_av_audio_ctrl_ops = {
.s_ctrl = cx18_av_audio_s_ctrl,
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,390 @@
/*
* cx18 ADEC header
*
* Derived from cx25840-core.h
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* 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.
*/
#ifndef _CX18_AV_CORE_H_
#define _CX18_AV_CORE_H_
#include <media/v4l2-device.h>
#include <media/v4l2-ctrls.h>
struct cx18;
enum cx18_av_video_input {
/* Composite video inputs In1-In8 */
CX18_AV_COMPOSITE1 = 1,
CX18_AV_COMPOSITE2,
CX18_AV_COMPOSITE3,
CX18_AV_COMPOSITE4,
CX18_AV_COMPOSITE5,
CX18_AV_COMPOSITE6,
CX18_AV_COMPOSITE7,
CX18_AV_COMPOSITE8,
/* S-Video inputs consist of one luma input (In1-In8) ORed with one
chroma input (In5-In8) */
CX18_AV_SVIDEO_LUMA1 = 0x10,
CX18_AV_SVIDEO_LUMA2 = 0x20,
CX18_AV_SVIDEO_LUMA3 = 0x30,
CX18_AV_SVIDEO_LUMA4 = 0x40,
CX18_AV_SVIDEO_LUMA5 = 0x50,
CX18_AV_SVIDEO_LUMA6 = 0x60,
CX18_AV_SVIDEO_LUMA7 = 0x70,
CX18_AV_SVIDEO_LUMA8 = 0x80,
CX18_AV_SVIDEO_CHROMA4 = 0x400,
CX18_AV_SVIDEO_CHROMA5 = 0x500,
CX18_AV_SVIDEO_CHROMA6 = 0x600,
CX18_AV_SVIDEO_CHROMA7 = 0x700,
CX18_AV_SVIDEO_CHROMA8 = 0x800,
/* S-Video aliases for common luma/chroma combinations */
CX18_AV_SVIDEO1 = 0x510,
CX18_AV_SVIDEO2 = 0x620,
CX18_AV_SVIDEO3 = 0x730,
CX18_AV_SVIDEO4 = 0x840,
/* Component Video inputs consist of one luma input (In1-In8) ORed
with a red chroma (In4-In6) and blue chroma input (In7-In8) */
CX18_AV_COMPONENT_LUMA1 = 0x1000,
CX18_AV_COMPONENT_LUMA2 = 0x2000,
CX18_AV_COMPONENT_LUMA3 = 0x3000,
CX18_AV_COMPONENT_LUMA4 = 0x4000,
CX18_AV_COMPONENT_LUMA5 = 0x5000,
CX18_AV_COMPONENT_LUMA6 = 0x6000,
CX18_AV_COMPONENT_LUMA7 = 0x7000,
CX18_AV_COMPONENT_LUMA8 = 0x8000,
CX18_AV_COMPONENT_R_CHROMA4 = 0x40000,
CX18_AV_COMPONENT_R_CHROMA5 = 0x50000,
CX18_AV_COMPONENT_R_CHROMA6 = 0x60000,
CX18_AV_COMPONENT_B_CHROMA7 = 0x700000,
CX18_AV_COMPONENT_B_CHROMA8 = 0x800000,
/* Component Video aliases for common combinations */
CX18_AV_COMPONENT1 = 0x861000,
};
enum cx18_av_audio_input {
/* Audio inputs: serial or In4-In8 */
CX18_AV_AUDIO_SERIAL1,
CX18_AV_AUDIO_SERIAL2,
CX18_AV_AUDIO4 = 4,
CX18_AV_AUDIO5,
CX18_AV_AUDIO6,
CX18_AV_AUDIO7,
CX18_AV_AUDIO8,
};
struct cx18_av_state {
struct v4l2_subdev sd;
struct v4l2_ctrl_handler hdl;
struct v4l2_ctrl *volume;
int radio;
v4l2_std_id std;
enum cx18_av_video_input vid_input;
enum cx18_av_audio_input aud_input;
u32 audclk_freq;
int audmode;
u32 rev;
int is_initialized;
/*
* The VBI slicer starts operating and counting lines, beginning at
* slicer line count of 1, at D lines after the deassertion of VRESET.
* This staring field line, S, is 6 (& 319) or 10 (& 273) for 625 or 525
* line systems respectively. Sliced ancillary data captured on VBI
* slicer line M is inserted after the VBI slicer is done with line M,
* when VBI slicer line count is N = M+1. Thus when the VBI slicer
* reports a VBI slicer line number with ancillary data, the IDID0 byte
* indicates VBI slicer line N. The actual field line that the captured
* data comes from is
*
* L = M+(S+D-1) = N-1+(S+D-1) = N + (S+D-2).
*
* L is the line in the field, not frame, from which the VBI data came.
* N is the line reported by the slicer in the ancillary data.
* D is the slicer_line_delay value programmed into register 0x47f.
* S is 6 for 625 line systems or 10 for 525 line systems
* (S+D-2) is the slicer_line_offset used to convert slicer reported
* line counts to actual field lines.
*/
int slicer_line_delay;
int slicer_line_offset;
};
/* Registers */
#define CXADEC_CHIP_TYPE_TIGER 0x837
#define CXADEC_CHIP_TYPE_MAKO 0x843
#define CXADEC_HOST_REG1 0x000
#define CXADEC_HOST_REG2 0x001
#define CXADEC_CHIP_CTRL 0x100
#define CXADEC_AFE_CTRL 0x104
#define CXADEC_PLL_CTRL1 0x108
#define CXADEC_VID_PLL_FRAC 0x10C
#define CXADEC_AUX_PLL_FRAC 0x110
#define CXADEC_PIN_CTRL1 0x114
#define CXADEC_PIN_CTRL2 0x118
#define CXADEC_PIN_CFG1 0x11C
#define CXADEC_PIN_CFG2 0x120
#define CXADEC_PIN_CFG3 0x124
#define CXADEC_I2S_MCLK 0x127
#define CXADEC_AUD_LOCK1 0x128
#define CXADEC_AUD_LOCK2 0x12C
#define CXADEC_POWER_CTRL 0x130
#define CXADEC_AFE_DIAG_CTRL1 0x134
#define CXADEC_AFE_DIAG_CTRL2 0x138
#define CXADEC_AFE_DIAG_CTRL3 0x13C
#define CXADEC_PLL_DIAG_CTRL 0x140
#define CXADEC_TEST_CTRL1 0x144
#define CXADEC_TEST_CTRL2 0x148
#define CXADEC_BIST_STAT 0x14C
#define CXADEC_DLL1_DIAG_CTRL 0x158
#define CXADEC_DLL2_DIAG_CTRL 0x15C
/* IR registers */
#define CXADEC_IR_CTRL_REG 0x200
#define CXADEC_IR_TXCLK_REG 0x204
#define CXADEC_IR_RXCLK_REG 0x208
#define CXADEC_IR_CDUTY_REG 0x20C
#define CXADEC_IR_STAT_REG 0x210
#define CXADEC_IR_IRQEN_REG 0x214
#define CXADEC_IR_FILTER_REG 0x218
#define CXADEC_IR_FIFO_REG 0x21C
/* Video Registers */
#define CXADEC_MODE_CTRL 0x400
#define CXADEC_OUT_CTRL1 0x404
#define CXADEC_OUT_CTRL2 0x408
#define CXADEC_GEN_STAT 0x40C
#define CXADEC_INT_STAT_MASK 0x410
#define CXADEC_LUMA_CTRL 0x414
#define CXADEC_BRIGHTNESS_CTRL_BYTE 0x414
#define CXADEC_CONTRAST_CTRL_BYTE 0x415
#define CXADEC_LUMA_CTRL_BYTE_3 0x416
#define CXADEC_HSCALE_CTRL 0x418
#define CXADEC_VSCALE_CTRL 0x41C
#define CXADEC_CHROMA_CTRL 0x420
#define CXADEC_USAT_CTRL_BYTE 0x420
#define CXADEC_VSAT_CTRL_BYTE 0x421
#define CXADEC_HUE_CTRL_BYTE 0x422
#define CXADEC_VBI_LINE_CTRL1 0x424
#define CXADEC_VBI_LINE_CTRL2 0x428
#define CXADEC_VBI_LINE_CTRL3 0x42C
#define CXADEC_VBI_LINE_CTRL4 0x430
#define CXADEC_VBI_LINE_CTRL5 0x434
#define CXADEC_VBI_FC_CFG 0x438
#define CXADEC_VBI_MISC_CFG1 0x43C
#define CXADEC_VBI_MISC_CFG2 0x440
#define CXADEC_VBI_PAY1 0x444
#define CXADEC_VBI_PAY2 0x448
#define CXADEC_VBI_CUST1_CFG1 0x44C
#define CXADEC_VBI_CUST1_CFG2 0x450
#define CXADEC_VBI_CUST1_CFG3 0x454
#define CXADEC_VBI_CUST2_CFG1 0x458
#define CXADEC_VBI_CUST2_CFG2 0x45C
#define CXADEC_VBI_CUST2_CFG3 0x460
#define CXADEC_VBI_CUST3_CFG1 0x464
#define CXADEC_VBI_CUST3_CFG2 0x468
#define CXADEC_VBI_CUST3_CFG3 0x46C
#define CXADEC_HORIZ_TIM_CTRL 0x470
#define CXADEC_VERT_TIM_CTRL 0x474
#define CXADEC_SRC_COMB_CFG 0x478
#define CXADEC_CHROMA_VBIOFF_CFG 0x47C
#define CXADEC_FIELD_COUNT 0x480
#define CXADEC_MISC_TIM_CTRL 0x484
#define CXADEC_DFE_CTRL1 0x488
#define CXADEC_DFE_CTRL2 0x48C
#define CXADEC_DFE_CTRL3 0x490
#define CXADEC_PLL_CTRL2 0x494
#define CXADEC_HTL_CTRL 0x498
#define CXADEC_COMB_CTRL 0x49C
#define CXADEC_CRUSH_CTRL 0x4A0
#define CXADEC_SOFT_RST_CTRL 0x4A4
#define CXADEC_MV_DT_CTRL2 0x4A8
#define CXADEC_MV_DT_CTRL3 0x4AC
#define CXADEC_MISC_DIAG_CTRL 0x4B8
#define CXADEC_DL_CTL 0x800
#define CXADEC_DL_CTL_ADDRESS_LOW 0x800 /* Byte 1 in DL_CTL */
#define CXADEC_DL_CTL_ADDRESS_HIGH 0x801 /* Byte 2 in DL_CTL */
#define CXADEC_DL_CTL_DATA 0x802 /* Byte 3 in DL_CTL */
#define CXADEC_DL_CTL_CONTROL 0x803 /* Byte 4 in DL_CTL */
#define CXADEC_STD_DET_STATUS 0x804
#define CXADEC_STD_DET_CTL 0x808
#define CXADEC_STD_DET_CTL_AUD_CTL 0x808 /* Byte 1 in STD_DET_CTL */
#define CXADEC_STD_DET_CTL_PREF_MODE 0x809 /* Byte 2 in STD_DET_CTL */
#define CXADEC_DW8051_INT 0x80C
#define CXADEC_GENERAL_CTL 0x810
#define CXADEC_AAGC_CTL 0x814
#define CXADEC_IF_SRC_CTL 0x818
#define CXADEC_ANLOG_DEMOD_CTL 0x81C
#define CXADEC_ROT_FREQ_CTL 0x820
#define CXADEC_FM1_CTL 0x824
#define CXADEC_PDF_CTL 0x828
#define CXADEC_DFT1_CTL1 0x82C
#define CXADEC_DFT1_CTL2 0x830
#define CXADEC_DFT_STATUS 0x834
#define CXADEC_DFT2_CTL1 0x838
#define CXADEC_DFT2_CTL2 0x83C
#define CXADEC_DFT2_STATUS 0x840
#define CXADEC_DFT3_CTL1 0x844
#define CXADEC_DFT3_CTL2 0x848
#define CXADEC_DFT3_STATUS 0x84C
#define CXADEC_DFT4_CTL1 0x850
#define CXADEC_DFT4_CTL2 0x854
#define CXADEC_DFT4_STATUS 0x858
#define CXADEC_AM_MTS_DET 0x85C
#define CXADEC_ANALOG_MUX_CTL 0x860
#define CXADEC_DIG_PLL_CTL1 0x864
#define CXADEC_DIG_PLL_CTL2 0x868
#define CXADEC_DIG_PLL_CTL3 0x86C
#define CXADEC_DIG_PLL_CTL4 0x870
#define CXADEC_DIG_PLL_CTL5 0x874
#define CXADEC_DEEMPH_GAIN_CTL 0x878
#define CXADEC_DEEMPH_COEF1 0x87C
#define CXADEC_DEEMPH_COEF2 0x880
#define CXADEC_DBX1_CTL1 0x884
#define CXADEC_DBX1_CTL2 0x888
#define CXADEC_DBX1_STATUS 0x88C
#define CXADEC_DBX2_CTL1 0x890
#define CXADEC_DBX2_CTL2 0x894
#define CXADEC_DBX2_STATUS 0x898
#define CXADEC_AM_FM_DIFF 0x89C
/* NICAM registers go here */
#define CXADEC_NICAM_STATUS 0x8C8
#define CXADEC_DEMATRIX_CTL 0x8CC
#define CXADEC_PATH1_CTL1 0x8D0
#define CXADEC_PATH1_VOL_CTL 0x8D4
#define CXADEC_PATH1_EQ_CTL 0x8D8
#define CXADEC_PATH1_SC_CTL 0x8DC
#define CXADEC_PATH2_CTL1 0x8E0
#define CXADEC_PATH2_VOL_CTL 0x8E4
#define CXADEC_PATH2_EQ_CTL 0x8E8
#define CXADEC_PATH2_SC_CTL 0x8EC
#define CXADEC_SRC_CTL 0x8F0
#define CXADEC_SRC_LF_COEF 0x8F4
#define CXADEC_SRC1_CTL 0x8F8
#define CXADEC_SRC2_CTL 0x8FC
#define CXADEC_SRC3_CTL 0x900
#define CXADEC_SRC4_CTL 0x904
#define CXADEC_SRC5_CTL 0x908
#define CXADEC_SRC6_CTL 0x90C
#define CXADEC_BASEBAND_OUT_SEL 0x910
#define CXADEC_I2S_IN_CTL 0x914
#define CXADEC_I2S_OUT_CTL 0x918
#define CXADEC_AC97_CTL 0x91C
#define CXADEC_QAM_PDF 0x920
#define CXADEC_QAM_CONST_DEC 0x924
#define CXADEC_QAM_ROTATOR_FREQ 0x948
/* Bit definitions / settings used in Mako Audio */
#define CXADEC_PREF_MODE_MONO_LANGA 0
#define CXADEC_PREF_MODE_MONO_LANGB 1
#define CXADEC_PREF_MODE_MONO_LANGC 2
#define CXADEC_PREF_MODE_FALLBACK 3
#define CXADEC_PREF_MODE_STEREO 4
#define CXADEC_PREF_MODE_DUAL_LANG_AC 5
#define CXADEC_PREF_MODE_DUAL_LANG_BC 6
#define CXADEC_PREF_MODE_DUAL_LANG_AB 7
#define CXADEC_DETECT_STEREO 1
#define CXADEC_DETECT_DUAL 2
#define CXADEC_DETECT_TRI 4
#define CXADEC_DETECT_SAP 0x10
#define CXADEC_DETECT_NO_SIGNAL 0xFF
#define CXADEC_SELECT_AUDIO_STANDARD_BG 0xF0 /* NICAM BG and A2 BG */
#define CXADEC_SELECT_AUDIO_STANDARD_DK1 0xF1 /* NICAM DK and A2 DK */
#define CXADEC_SELECT_AUDIO_STANDARD_DK2 0xF2
#define CXADEC_SELECT_AUDIO_STANDARD_DK3 0xF3
#define CXADEC_SELECT_AUDIO_STANDARD_I 0xF4 /* NICAM I and A1 */
#define CXADEC_SELECT_AUDIO_STANDARD_L 0xF5 /* NICAM L and System L AM */
#define CXADEC_SELECT_AUDIO_STANDARD_BTSC 0xF6
#define CXADEC_SELECT_AUDIO_STANDARD_EIAJ 0xF7
#define CXADEC_SELECT_AUDIO_STANDARD_A2_M 0xF8 /* A2 M */
#define CXADEC_SELECT_AUDIO_STANDARD_FM 0xF9 /* FM radio */
#define CXADEC_SELECT_AUDIO_STANDARD_AUTO 0xFF /* Auto detect */
static inline struct cx18_av_state *to_cx18_av_state(struct v4l2_subdev *sd)
{
return container_of(sd, struct cx18_av_state, sd);
}
static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl)
{
return &container_of(ctrl->handler, struct cx18_av_state, hdl)->sd;
}
/* ----------------------------------------------------------------------- */
/* cx18_av-core.c */
int cx18_av_write(struct cx18 *cx, u16 addr, u8 value);
int cx18_av_write4(struct cx18 *cx, u16 addr, u32 value);
int cx18_av_write4_noretry(struct cx18 *cx, u16 addr, u32 value);
int cx18_av_write_expect(struct cx18 *cx, u16 addr, u8 value, u8 eval, u8 mask);
int cx18_av_write4_expect(struct cx18 *cx, u16 addr, u32 value, u32 eval,
u32 mask);
u8 cx18_av_read(struct cx18 *cx, u16 addr);
u32 cx18_av_read4(struct cx18 *cx, u16 addr);
int cx18_av_and_or(struct cx18 *cx, u16 addr, unsigned mask, u8 value);
int cx18_av_and_or4(struct cx18 *cx, u16 addr, u32 mask, u32 value);
void cx18_av_std_setup(struct cx18 *cx);
int cx18_av_probe(struct cx18 *cx);
/* ----------------------------------------------------------------------- */
/* cx18_av-firmware.c */
int cx18_av_loadfw(struct cx18 *cx);
/* ----------------------------------------------------------------------- */
/* cx18_av-audio.c */
int cx18_av_s_clock_freq(struct v4l2_subdev *sd, u32 freq);
void cx18_av_audio_set_path(struct cx18 *cx);
extern const struct v4l2_ctrl_ops cx18_av_audio_ctrl_ops;
/* ----------------------------------------------------------------------- */
/* cx18_av-vbi.c */
int cx18_av_decode_vbi_line(struct v4l2_subdev *sd,
struct v4l2_decode_vbi_line *vbi);
int cx18_av_s_raw_fmt(struct v4l2_subdev *sd, struct v4l2_vbi_format *fmt);
int cx18_av_g_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *fmt);
int cx18_av_s_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *fmt);
#endif

View file

@ -0,0 +1,225 @@
/*
* cx18 ADEC firmware functions
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* 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 "cx18-driver.h"
#include "cx18-io.h"
#include <linux/firmware.h>
#define CX18_AUDIO_ENABLE 0xc72014
#define CX18_AI1_MUX_MASK 0x30
#define CX18_AI1_MUX_I2S1 0x00
#define CX18_AI1_MUX_I2S2 0x10
#define CX18_AI1_MUX_843_I2S 0x20
#define CX18_AI1_MUX_INVALID 0x30
#define FWFILE "v4l-cx23418-dig.fw"
static int cx18_av_verifyfw(struct cx18 *cx, const struct firmware *fw)
{
struct v4l2_subdev *sd = &cx->av_state.sd;
int ret = 0;
const u8 *data;
u32 size;
int addr;
u32 expected, dl_control;
/* Ensure we put the 8051 in reset and enable firmware upload mode */
dl_control = cx18_av_read4(cx, CXADEC_DL_CTL);
do {
dl_control &= 0x00ffffff;
dl_control |= 0x0f000000;
cx18_av_write4_noretry(cx, CXADEC_DL_CTL, dl_control);
dl_control = cx18_av_read4(cx, CXADEC_DL_CTL);
} while ((dl_control & 0xff000000) != 0x0f000000);
/* Read and auto increment until at address 0x0000 */
while (dl_control & 0x3fff)
dl_control = cx18_av_read4(cx, CXADEC_DL_CTL);
data = fw->data;
size = fw->size;
for (addr = 0; addr < size; addr++) {
dl_control &= 0xffff3fff; /* ignore top 2 bits of address */
expected = 0x0f000000 | ((u32)data[addr] << 16) | addr;
if (expected != dl_control) {
CX18_ERR_DEV(sd, "verification of %s firmware load "
"failed: expected %#010x got %#010x\n",
FWFILE, expected, dl_control);
ret = -EIO;
break;
}
dl_control = cx18_av_read4(cx, CXADEC_DL_CTL);
}
if (ret == 0)
CX18_INFO_DEV(sd, "verified load of %s firmware (%d bytes)\n",
FWFILE, size);
return ret;
}
int cx18_av_loadfw(struct cx18 *cx)
{
struct v4l2_subdev *sd = &cx->av_state.sd;
const struct firmware *fw = NULL;
u32 size;
u32 u, v;
const u8 *ptr;
int i;
int retries1 = 0;
if (request_firmware(&fw, FWFILE, &cx->pci_dev->dev) != 0) {
CX18_ERR_DEV(sd, "unable to open firmware %s\n", FWFILE);
return -EINVAL;
}
/* The firmware load often has byte errors, so allow for several
retries, both at byte level and at the firmware load level. */
while (retries1 < 5) {
cx18_av_write4_expect(cx, CXADEC_CHIP_CTRL, 0x00010000,
0x00008430, 0xffffffff); /* cx25843 */
cx18_av_write_expect(cx, CXADEC_STD_DET_CTL, 0xf6, 0xf6, 0xff);
/* Reset the Mako core, Register is alias of CXADEC_CHIP_CTRL */
cx18_av_write4_expect(cx, 0x8100, 0x00010000,
0x00008430, 0xffffffff); /* cx25843 */
/* Put the 8051 in reset and enable firmware upload */
cx18_av_write4_noretry(cx, CXADEC_DL_CTL, 0x0F000000);
ptr = fw->data;
size = fw->size;
for (i = 0; i < size; i++) {
u32 dl_control = 0x0F000000 | i | ((u32)ptr[i] << 16);
u32 value = 0;
int retries2;
int unrec_err = 0;
for (retries2 = 0; retries2 < CX18_MAX_MMIO_WR_RETRIES;
retries2++) {
cx18_av_write4_noretry(cx, CXADEC_DL_CTL,
dl_control);
udelay(10);
value = cx18_av_read4(cx, CXADEC_DL_CTL);
if (value == dl_control)
break;
/* Check if we can correct the byte by changing
the address. We can only write the lower
address byte of the address. */
if ((value & 0x3F00) != (dl_control & 0x3F00)) {
unrec_err = 1;
break;
}
}
if (unrec_err || retries2 >= CX18_MAX_MMIO_WR_RETRIES)
break;
}
if (i == size)
break;
retries1++;
}
if (retries1 >= 5) {
CX18_ERR_DEV(sd, "unable to load firmware %s\n", FWFILE);
release_firmware(fw);
return -EIO;
}
cx18_av_write4_expect(cx, CXADEC_DL_CTL,
0x03000000 | fw->size, 0x03000000, 0x13000000);
CX18_INFO_DEV(sd, "loaded %s firmware (%d bytes)\n", FWFILE, size);
if (cx18_av_verifyfw(cx, fw) == 0)
cx18_av_write4_expect(cx, CXADEC_DL_CTL,
0x13000000 | fw->size, 0x13000000, 0x13000000);
/* Output to the 416 */
cx18_av_and_or4(cx, CXADEC_PIN_CTRL1, ~0, 0x78000);
/* Audio input control 1 set to Sony mode */
/* Audio output input 2 is 0 for slave operation input */
/* 0xC4000914[5]: 0 = left sample on WS=0, 1 = left sample on WS=1 */
/* 0xC4000914[7]: 0 = Philips mode, 1 = Sony mode (1st SCK rising edge
after WS transition for first bit of audio word. */
cx18_av_write4(cx, CXADEC_I2S_IN_CTL, 0x000000A0);
/* Audio output control 1 is set to Sony mode */
/* Audio output control 2 is set to 1 for master mode */
/* 0xC4000918[5]: 0 = left sample on WS=0, 1 = left sample on WS=1 */
/* 0xC4000918[7]: 0 = Philips mode, 1 = Sony mode (1st SCK rising edge
after WS transition for first bit of audio word. */
/* 0xC4000918[8]: 0 = slave operation, 1 = master (SCK_OUT and WS_OUT
are generated) */
cx18_av_write4(cx, CXADEC_I2S_OUT_CTL, 0x000001A0);
/* set alt I2s master clock to /0x16 and enable alt divider i2s
passthrough */
cx18_av_write4(cx, CXADEC_PIN_CFG3, 0x5600B687);
cx18_av_write4_expect(cx, CXADEC_STD_DET_CTL, 0x000000F6, 0x000000F6,
0x3F00FFFF);
/* CxDevWrReg(CXADEC_STD_DET_CTL, 0x000000FF); */
/* Set bit 0 in register 0x9CC to signify that this is MiniMe. */
/* Register 0x09CC is defined by the Merlin firmware, and doesn't
have a name in the spec. */
cx18_av_write4(cx, 0x09CC, 1);
v = cx18_read_reg(cx, CX18_AUDIO_ENABLE);
/* If bit 11 is 1, clear bit 10 */
if (v & 0x800)
cx18_write_reg_expect(cx, v & 0xFFFFFBFF, CX18_AUDIO_ENABLE,
0, 0x400);
/* Toggle the AI1 MUX */
v = cx18_read_reg(cx, CX18_AUDIO_ENABLE);
u = v & CX18_AI1_MUX_MASK;
v &= ~CX18_AI1_MUX_MASK;
if (u == CX18_AI1_MUX_843_I2S || u == CX18_AI1_MUX_INVALID) {
/* Switch to I2S1 */
v |= CX18_AI1_MUX_I2S1;
cx18_write_reg_expect(cx, v | 0xb00, CX18_AUDIO_ENABLE,
v, CX18_AI1_MUX_MASK);
/* Switch back to the A/V decoder core I2S output */
v = (v & ~CX18_AI1_MUX_MASK) | CX18_AI1_MUX_843_I2S;
} else {
/* Switch to the A/V decoder core I2S output */
v |= CX18_AI1_MUX_843_I2S;
cx18_write_reg_expect(cx, v | 0xb00, CX18_AUDIO_ENABLE,
v, CX18_AI1_MUX_MASK);
/* Switch back to I2S1 or I2S2 */
v = (v & ~CX18_AI1_MUX_MASK) | u;
}
cx18_write_reg_expect(cx, v | 0xb00, CX18_AUDIO_ENABLE,
v, CX18_AI1_MUX_MASK);
/* Enable WW auto audio standard detection */
v = cx18_av_read4(cx, CXADEC_STD_DET_CTL);
v |= 0xFF; /* Auto by default */
v |= 0x400; /* Stereo by default */
v |= 0x14000000;
cx18_av_write4_expect(cx, CXADEC_STD_DET_CTL, v, v, 0x3F00FFFF);
release_firmware(fw);
return 0;
}
MODULE_FIRMWARE(FWFILE);

View file

@ -0,0 +1,313 @@
/*
* cx18 ADEC VBI functions
*
* Derived from cx25840-vbi.c
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by 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 "cx18-driver.h"
/*
* For sliced VBI output, we set up to use VIP-1.1, 8-bit mode,
* NN counts 1 byte Dwords, an IDID with the VBI line # in it.
* Thus, according to the VIP-2 Spec, our VBI ancillary data lines
* (should!) look like:
* 4 byte EAV code: 0xff 0x00 0x00 0xRP
* unknown number of possible idle bytes
* 3 byte Anc data preamble: 0x00 0xff 0xff
* 1 byte data identifier: ne010iii (parity bits, 010, DID bits)
* 1 byte secondary data id: nessssss (parity bits, SDID bits)
* 1 byte data word count: necccccc (parity bits, NN Dword count)
* 2 byte Internal DID: VBI-line-# 0x80
* NN data bytes
* 1 byte checksum
* Fill bytes needed to fil out to 4*NN bytes of payload
*
* The RP codes for EAVs when in VIP-1.1 mode, not in raw mode, &
* in the vertical blanking interval are:
* 0xb0 (Task 0 VerticalBlank HorizontalBlank 0 0 0 0)
* 0xf0 (Task EvenField VerticalBlank HorizontalBlank 0 0 0 0)
*
* Since the V bit is only allowed to toggle in the EAV RP code, just
* before the first active region line and for active lines, they are:
* 0x90 (Task 0 0 HorizontalBlank 0 0 0 0)
* 0xd0 (Task EvenField 0 HorizontalBlank 0 0 0 0)
*
* The user application DID bytes we care about are:
* 0x91 (1 0 010 0 !ActiveLine AncDataPresent)
* 0x55 (0 1 010 2ndField !ActiveLine AncDataPresent)
*
*/
static const u8 sliced_vbi_did[2] = { 0x91, 0x55 };
struct vbi_anc_data {
/* u8 eav[4]; */
/* u8 idle[]; Variable number of idle bytes */
u8 preamble[3];
u8 did;
u8 sdid;
u8 data_count;
u8 idid[2];
u8 payload[1]; /* data_count of payload */
/* u8 checksum; */
/* u8 fill[]; Variable number of fill bytes */
};
static int odd_parity(u8 c)
{
c ^= (c >> 4);
c ^= (c >> 2);
c ^= (c >> 1);
return c & 1;
}
static int decode_vps(u8 *dst, u8 *p)
{
static const u8 biphase_tbl[] = {
0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4,
0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0,
0xd2, 0x5a, 0x52, 0xd2, 0x96, 0x1e, 0x16, 0x96,
0x92, 0x1a, 0x12, 0x92, 0xd2, 0x5a, 0x52, 0xd2,
0xd0, 0x58, 0x50, 0xd0, 0x94, 0x1c, 0x14, 0x94,
0x90, 0x18, 0x10, 0x90, 0xd0, 0x58, 0x50, 0xd0,
0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4,
0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0,
0xe1, 0x69, 0x61, 0xe1, 0xa5, 0x2d, 0x25, 0xa5,
0xa1, 0x29, 0x21, 0xa1, 0xe1, 0x69, 0x61, 0xe1,
0xc3, 0x4b, 0x43, 0xc3, 0x87, 0x0f, 0x07, 0x87,
0x83, 0x0b, 0x03, 0x83, 0xc3, 0x4b, 0x43, 0xc3,
0xc1, 0x49, 0x41, 0xc1, 0x85, 0x0d, 0x05, 0x85,
0x81, 0x09, 0x01, 0x81, 0xc1, 0x49, 0x41, 0xc1,
0xe1, 0x69, 0x61, 0xe1, 0xa5, 0x2d, 0x25, 0xa5,
0xa1, 0x29, 0x21, 0xa1, 0xe1, 0x69, 0x61, 0xe1,
0xe0, 0x68, 0x60, 0xe0, 0xa4, 0x2c, 0x24, 0xa4,
0xa0, 0x28, 0x20, 0xa0, 0xe0, 0x68, 0x60, 0xe0,
0xc2, 0x4a, 0x42, 0xc2, 0x86, 0x0e, 0x06, 0x86,
0x82, 0x0a, 0x02, 0x82, 0xc2, 0x4a, 0x42, 0xc2,
0xc0, 0x48, 0x40, 0xc0, 0x84, 0x0c, 0x04, 0x84,
0x80, 0x08, 0x00, 0x80, 0xc0, 0x48, 0x40, 0xc0,
0xe0, 0x68, 0x60, 0xe0, 0xa4, 0x2c, 0x24, 0xa4,
0xa0, 0x28, 0x20, 0xa0, 0xe0, 0x68, 0x60, 0xe0,
0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4,
0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0,
0xd2, 0x5a, 0x52, 0xd2, 0x96, 0x1e, 0x16, 0x96,
0x92, 0x1a, 0x12, 0x92, 0xd2, 0x5a, 0x52, 0xd2,
0xd0, 0x58, 0x50, 0xd0, 0x94, 0x1c, 0x14, 0x94,
0x90, 0x18, 0x10, 0x90, 0xd0, 0x58, 0x50, 0xd0,
0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4,
0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0,
};
u8 c, err = 0;
int i;
for (i = 0; i < 2 * 13; i += 2) {
err |= biphase_tbl[p[i]] | biphase_tbl[p[i + 1]];
c = (biphase_tbl[p[i + 1]] & 0xf) |
((biphase_tbl[p[i]] & 0xf) << 4);
dst[i / 2] = c;
}
return err & 0xf0;
}
int cx18_av_g_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *svbi)
{
struct cx18 *cx = v4l2_get_subdevdata(sd);
struct cx18_av_state *state = &cx->av_state;
static const u16 lcr2vbi[] = {
0, V4L2_SLICED_TELETEXT_B, 0, /* 1 */
0, V4L2_SLICED_WSS_625, 0, /* 4 */
V4L2_SLICED_CAPTION_525, /* 6 */
0, 0, V4L2_SLICED_VPS, 0, 0, /* 9 */
0, 0, 0, 0
};
int is_pal = !(state->std & V4L2_STD_525_60);
int i;
memset(svbi->service_lines, 0, sizeof(svbi->service_lines));
svbi->service_set = 0;
/* we're done if raw VBI is active */
if ((cx18_av_read(cx, 0x404) & 0x10) == 0)
return 0;
if (is_pal) {
for (i = 7; i <= 23; i++) {
u8 v = cx18_av_read(cx, 0x424 + i - 7);
svbi->service_lines[0][i] = lcr2vbi[v >> 4];
svbi->service_lines[1][i] = lcr2vbi[v & 0xf];
svbi->service_set |= svbi->service_lines[0][i] |
svbi->service_lines[1][i];
}
} else {
for (i = 10; i <= 21; i++) {
u8 v = cx18_av_read(cx, 0x424 + i - 10);
svbi->service_lines[0][i] = lcr2vbi[v >> 4];
svbi->service_lines[1][i] = lcr2vbi[v & 0xf];
svbi->service_set |= svbi->service_lines[0][i] |
svbi->service_lines[1][i];
}
}
return 0;
}
int cx18_av_s_raw_fmt(struct v4l2_subdev *sd, struct v4l2_vbi_format *fmt)
{
struct cx18 *cx = v4l2_get_subdevdata(sd);
struct cx18_av_state *state = &cx->av_state;
/* Setup standard */
cx18_av_std_setup(cx);
/* VBI Offset */
cx18_av_write(cx, 0x47f, state->slicer_line_delay);
cx18_av_write(cx, 0x404, 0x2e);
return 0;
}
int cx18_av_s_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_format *svbi)
{
struct cx18 *cx = v4l2_get_subdevdata(sd);
struct cx18_av_state *state = &cx->av_state;
int is_pal = !(state->std & V4L2_STD_525_60);
int i, x;
u8 lcr[24];
for (x = 0; x <= 23; x++)
lcr[x] = 0x00;
/* Setup standard */
cx18_av_std_setup(cx);
/* Sliced VBI */
cx18_av_write(cx, 0x404, 0x32); /* Ancillary data */
cx18_av_write(cx, 0x406, 0x13);
cx18_av_write(cx, 0x47f, state->slicer_line_delay);
/* Force impossible lines to 0 */
if (is_pal) {
for (i = 0; i <= 6; i++)
svbi->service_lines[0][i] =
svbi->service_lines[1][i] = 0;
} else {
for (i = 0; i <= 9; i++)
svbi->service_lines[0][i] =
svbi->service_lines[1][i] = 0;
for (i = 22; i <= 23; i++)
svbi->service_lines[0][i] =
svbi->service_lines[1][i] = 0;
}
/* Build register values for requested service lines */
for (i = 7; i <= 23; i++) {
for (x = 0; x <= 1; x++) {
switch (svbi->service_lines[1-x][i]) {
case V4L2_SLICED_TELETEXT_B:
lcr[i] |= 1 << (4 * x);
break;
case V4L2_SLICED_WSS_625:
lcr[i] |= 4 << (4 * x);
break;
case V4L2_SLICED_CAPTION_525:
lcr[i] |= 6 << (4 * x);
break;
case V4L2_SLICED_VPS:
lcr[i] |= 9 << (4 * x);
break;
}
}
}
if (is_pal) {
for (x = 1, i = 0x424; i <= 0x434; i++, x++)
cx18_av_write(cx, i, lcr[6 + x]);
} else {
for (x = 1, i = 0x424; i <= 0x430; i++, x++)
cx18_av_write(cx, i, lcr[9 + x]);
for (i = 0x431; i <= 0x434; i++)
cx18_av_write(cx, i, 0);
}
cx18_av_write(cx, 0x43c, 0x16);
/* Should match vblank set in cx18_av_std_setup() */
cx18_av_write(cx, 0x474, is_pal ? 38 : 26);
return 0;
}
int cx18_av_decode_vbi_line(struct v4l2_subdev *sd,
struct v4l2_decode_vbi_line *vbi)
{
struct cx18 *cx = v4l2_get_subdevdata(sd);
struct cx18_av_state *state = &cx->av_state;
struct vbi_anc_data *anc = (struct vbi_anc_data *)vbi->p;
u8 *p;
int did, sdid, l, err = 0;
/*
* Check for the ancillary data header for sliced VBI
*/
if (anc->preamble[0] ||
anc->preamble[1] != 0xff || anc->preamble[2] != 0xff ||
(anc->did != sliced_vbi_did[0] &&
anc->did != sliced_vbi_did[1])) {
vbi->line = vbi->type = 0;
return 0;
}
did = anc->did;
sdid = anc->sdid & 0xf;
l = anc->idid[0] & 0x3f;
l += state->slicer_line_offset;
p = anc->payload;
/* Decode the SDID set by the slicer */
switch (sdid) {
case 1:
sdid = V4L2_SLICED_TELETEXT_B;
break;
case 4:
sdid = V4L2_SLICED_WSS_625;
break;
case 6:
sdid = V4L2_SLICED_CAPTION_525;
err = !odd_parity(p[0]) || !odd_parity(p[1]);
break;
case 9:
sdid = V4L2_SLICED_VPS;
if (decode_vps(p, p) != 0)
err = 1;
break;
default:
sdid = 0;
err = 1;
break;
}
vbi->type = err ? 0 : sdid;
vbi->line = err ? 0 : l;
vbi->is_second_field = err ? 0 : (did == sliced_vbi_did[1]);
vbi->p = p;
return 0;
}

View file

@ -0,0 +1,638 @@
/*
* cx18 functions to query card hardware
*
* Derived from ivtv-cards.c
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* 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 "cx18-driver.h"
#include "cx18-cards.h"
#include "cx18-av-core.h"
#include "cx18-i2c.h"
#include <media/cs5345.h>
#define V4L2_STD_PAL_SECAM (V4L2_STD_PAL|V4L2_STD_SECAM)
/********************** card configuration *******************************/
/* usual i2c tuner addresses to probe */
static struct cx18_card_tuner_i2c cx18_i2c_std = {
.radio = { I2C_CLIENT_END },
.demod = { 0x43, I2C_CLIENT_END },
.tv = { 0x61, 0x60, I2C_CLIENT_END },
};
/*
* usual i2c tuner addresses to probe with additional demod address for
* an NXP TDA8295 at 0x42 (N.B. it can possibly be at 0x4b or 0x4c too).
*/
static struct cx18_card_tuner_i2c cx18_i2c_nxp = {
.radio = { I2C_CLIENT_END },
.demod = { 0x42, 0x43, I2C_CLIENT_END },
.tv = { 0x61, 0x60, I2C_CLIENT_END },
};
/* Please add 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 0x4444 (Conexant) as subsystem IDs.
New vendor IDs should still be added to the vendor ID list. */
/* Hauppauge HVR-1600 cards */
/* Note: for Hauppauge cards the tveeprom information is used instead
of PCI IDs */
static const struct cx18_card cx18_card_hvr1600_esmt = {
.type = CX18_CARD_HVR_1600_ESMT,
.name = "Hauppauge HVR-1600",
.comment = "Simultaneous Digital and Analog TV capture supported\n",
.v4l2_capabilities = CX18_CAP_ENCODER,
.hw_audio_ctrl = CX18_HW_418_AV,
.hw_muxer = CX18_HW_CS5345,
.hw_all = CX18_HW_TVEEPROM | CX18_HW_418_AV | CX18_HW_TUNER |
CX18_HW_CS5345 | CX18_HW_DVB | CX18_HW_GPIO_RESET_CTRL |
CX18_HW_Z8F0811_IR_HAUP,
.video_inputs = {
{ CX18_CARD_INPUT_VID_TUNER, 0, CX18_AV_COMPOSITE7 },
{ CX18_CARD_INPUT_SVIDEO1, 1, CX18_AV_SVIDEO1 },
{ CX18_CARD_INPUT_COMPOSITE1, 1, CX18_AV_COMPOSITE3 },
{ CX18_CARD_INPUT_SVIDEO2, 2, CX18_AV_SVIDEO2 },
{ CX18_CARD_INPUT_COMPOSITE2, 2, CX18_AV_COMPOSITE4 },
},
.audio_inputs = {
{ CX18_CARD_INPUT_AUD_TUNER,
CX18_AV_AUDIO8, CS5345_IN_1 | CS5345_MCLK_1_5 },
{ CX18_CARD_INPUT_LINE_IN1,
CX18_AV_AUDIO_SERIAL1, CS5345_IN_2 },
{ CX18_CARD_INPUT_LINE_IN2,
CX18_AV_AUDIO_SERIAL1, CS5345_IN_3 },
},
.radio_input = { CX18_CARD_INPUT_AUD_TUNER,
CX18_AV_AUDIO_SERIAL1, CS5345_IN_4 },
.ddr = {
/* ESMT M13S128324A-5B memory */
.chip_config = 0x003,
.refresh = 0x30c,
.timing1 = 0x44220e82,
.timing2 = 0x08,
.tune_lane = 0,
.initial_emrs = 0,
},
.gpio_init.initial_value = 0x3001,
.gpio_init.direction = 0x3001,
.gpio_i2c_slave_reset = {
.active_lo_mask = 0x3001,
.msecs_asserted = 10,
.msecs_recovery = 40,
.ir_reset_mask = 0x0001,
},
.i2c = &cx18_i2c_std,
};
static const struct cx18_card cx18_card_hvr1600_s5h1411 = {
.type = CX18_CARD_HVR_1600_S5H1411,
.name = "Hauppauge HVR-1600",
.comment = "Simultaneous Digital and Analog TV capture supported\n",
.v4l2_capabilities = CX18_CAP_ENCODER,
.hw_audio_ctrl = CX18_HW_418_AV,
.hw_muxer = CX18_HW_CS5345,
.hw_all = CX18_HW_TVEEPROM | CX18_HW_418_AV | CX18_HW_TUNER |
CX18_HW_CS5345 | CX18_HW_DVB | CX18_HW_GPIO_RESET_CTRL |
CX18_HW_Z8F0811_IR_HAUP,
.video_inputs = {
{ CX18_CARD_INPUT_VID_TUNER, 0, CX18_AV_COMPOSITE7 },
{ CX18_CARD_INPUT_SVIDEO1, 1, CX18_AV_SVIDEO1 },
{ CX18_CARD_INPUT_COMPOSITE1, 1, CX18_AV_COMPOSITE3 },
{ CX18_CARD_INPUT_SVIDEO2, 2, CX18_AV_SVIDEO2 },
{ CX18_CARD_INPUT_COMPOSITE2, 2, CX18_AV_COMPOSITE4 },
},
.audio_inputs = {
{ CX18_CARD_INPUT_AUD_TUNER,
CX18_AV_AUDIO8, CS5345_IN_1 | CS5345_MCLK_1_5 },
{ CX18_CARD_INPUT_LINE_IN1,
CX18_AV_AUDIO_SERIAL1, CS5345_IN_2 },
{ CX18_CARD_INPUT_LINE_IN2,
CX18_AV_AUDIO_SERIAL1, CS5345_IN_3 },
},
.radio_input = { CX18_CARD_INPUT_AUD_TUNER,
CX18_AV_AUDIO_SERIAL1, CS5345_IN_4 },
.ddr = {
/* ESMT M13S128324A-5B memory */
.chip_config = 0x003,
.refresh = 0x30c,
.timing1 = 0x44220e82,
.timing2 = 0x08,
.tune_lane = 0,
.initial_emrs = 0,
},
.gpio_init.initial_value = 0x3801,
.gpio_init.direction = 0x3801,
.gpio_i2c_slave_reset = {
.active_lo_mask = 0x3801,
.msecs_asserted = 10,
.msecs_recovery = 40,
.ir_reset_mask = 0x0001,
},
.i2c = &cx18_i2c_nxp,
};
static const struct cx18_card cx18_card_hvr1600_samsung = {
.type = CX18_CARD_HVR_1600_SAMSUNG,
.name = "Hauppauge HVR-1600 (Preproduction)",
.comment = "Simultaneous Digital and Analog TV capture supported\n",
.v4l2_capabilities = CX18_CAP_ENCODER,
.hw_audio_ctrl = CX18_HW_418_AV,
.hw_muxer = CX18_HW_CS5345,
.hw_all = CX18_HW_TVEEPROM | CX18_HW_418_AV | CX18_HW_TUNER |
CX18_HW_CS5345 | CX18_HW_DVB | CX18_HW_GPIO_RESET_CTRL |
CX18_HW_Z8F0811_IR_HAUP,
.video_inputs = {
{ CX18_CARD_INPUT_VID_TUNER, 0, CX18_AV_COMPOSITE7 },
{ CX18_CARD_INPUT_SVIDEO1, 1, CX18_AV_SVIDEO1 },
{ CX18_CARD_INPUT_COMPOSITE1, 1, CX18_AV_COMPOSITE3 },
{ CX18_CARD_INPUT_SVIDEO2, 2, CX18_AV_SVIDEO2 },
{ CX18_CARD_INPUT_COMPOSITE2, 2, CX18_AV_COMPOSITE4 },
},
.audio_inputs = {
{ CX18_CARD_INPUT_AUD_TUNER,
CX18_AV_AUDIO8, CS5345_IN_1 | CS5345_MCLK_1_5 },
{ CX18_CARD_INPUT_LINE_IN1,
CX18_AV_AUDIO_SERIAL1, CS5345_IN_2 },
{ CX18_CARD_INPUT_LINE_IN2,
CX18_AV_AUDIO_SERIAL1, CS5345_IN_3 },
},
.radio_input = { CX18_CARD_INPUT_AUD_TUNER,
CX18_AV_AUDIO_SERIAL1, CS5345_IN_4 },
.ddr = {
/* Samsung K4D263238G-VC33 memory */
.chip_config = 0x003,
.refresh = 0x30c,
.timing1 = 0x23230b73,
.timing2 = 0x08,
.tune_lane = 0,
.initial_emrs = 2,
},
.gpio_init.initial_value = 0x3001,
.gpio_init.direction = 0x3001,
.gpio_i2c_slave_reset = {
.active_lo_mask = 0x3001,
.msecs_asserted = 10,
.msecs_recovery = 40,
.ir_reset_mask = 0x0001,
},
.i2c = &cx18_i2c_std,
};
/* ------------------------------------------------------------------------- */
/* Compro VideoMate H900: note that this card is analog only! */
static const struct cx18_card_pci_info cx18_pci_h900[] = {
{ PCI_DEVICE_ID_CX23418, CX18_PCI_ID_COMPRO, 0xe100 },
{ 0, 0, 0 }
};
static const struct cx18_card cx18_card_h900 = {
.type = CX18_CARD_COMPRO_H900,
.name = "Compro VideoMate H900",
.comment = "Analog TV capture supported\n",
.v4l2_capabilities = CX18_CAP_ENCODER,
.hw_audio_ctrl = CX18_HW_418_AV,
.hw_all = CX18_HW_418_AV | CX18_HW_TUNER | CX18_HW_GPIO_RESET_CTRL,
.video_inputs = {
{ CX18_CARD_INPUT_VID_TUNER, 0, CX18_AV_COMPOSITE2 },
{ CX18_CARD_INPUT_SVIDEO1, 1,
CX18_AV_SVIDEO_LUMA3 | CX18_AV_SVIDEO_CHROMA4 },
{ CX18_CARD_INPUT_COMPOSITE1, 1, CX18_AV_COMPOSITE1 },
},
.audio_inputs = {
{ CX18_CARD_INPUT_AUD_TUNER,
CX18_AV_AUDIO5, 0 },
{ CX18_CARD_INPUT_LINE_IN1,
CX18_AV_AUDIO_SERIAL1, 0 },
},
.radio_input = { CX18_CARD_INPUT_AUD_TUNER,
CX18_AV_AUDIO_SERIAL1, 0 },
.tuners = {
{ .std = V4L2_STD_ALL, .tuner = TUNER_XC2028 },
},
.ddr = {
/* EtronTech EM6A9160TS-5G memory */
.chip_config = 0x50003,
.refresh = 0x753,
.timing1 = 0x24330e84,
.timing2 = 0x1f,
.tune_lane = 0,
.initial_emrs = 0,
},
.xceive_pin = 15,
.pci_list = cx18_pci_h900,
.i2c = &cx18_i2c_std,
};
/* ------------------------------------------------------------------------- */
/* Yuan MPC718: not working at the moment! */
static const struct cx18_card_pci_info cx18_pci_mpc718[] = {
{ PCI_DEVICE_ID_CX23418, CX18_PCI_ID_YUAN, 0x0718 },
{ 0, 0, 0 }
};
static const struct cx18_card cx18_card_mpc718 = {
.type = CX18_CARD_YUAN_MPC718,
.name = "Yuan MPC718 MiniPCI DVB-T/Analog",
.comment = "Experimenters needed for device to work well.\n"
"\tTo help, mail the ivtv-devel list (www.ivtvdriver.org).\n",
.v4l2_capabilities = CX18_CAP_ENCODER,
.hw_audio_ctrl = CX18_HW_418_AV,
.hw_muxer = CX18_HW_GPIO_MUX,
.hw_all = CX18_HW_TVEEPROM | CX18_HW_418_AV | CX18_HW_TUNER |
CX18_HW_GPIO_MUX | CX18_HW_DVB | CX18_HW_GPIO_RESET_CTRL,
.video_inputs = {
{ CX18_CARD_INPUT_VID_TUNER, 0, CX18_AV_COMPOSITE2 },
{ CX18_CARD_INPUT_SVIDEO1, 1,
CX18_AV_SVIDEO_LUMA3 | CX18_AV_SVIDEO_CHROMA4 },
{ CX18_CARD_INPUT_COMPOSITE1, 1, CX18_AV_COMPOSITE1 },
{ CX18_CARD_INPUT_SVIDEO2, 2,
CX18_AV_SVIDEO_LUMA7 | CX18_AV_SVIDEO_CHROMA8 },
{ CX18_CARD_INPUT_COMPOSITE2, 2, CX18_AV_COMPOSITE6 },
},
.audio_inputs = {
{ CX18_CARD_INPUT_AUD_TUNER, CX18_AV_AUDIO5, 0 },
{ CX18_CARD_INPUT_LINE_IN1, CX18_AV_AUDIO_SERIAL1, 1 },
{ CX18_CARD_INPUT_LINE_IN2, CX18_AV_AUDIO_SERIAL2, 1 },
},
.tuners = {
/* XC3028 tuner */
{ .std = V4L2_STD_ALL, .tuner = TUNER_XC2028 },
},
/* FIXME - the FM radio is just a guess and driver doesn't use SIF */
.radio_input = { CX18_CARD_INPUT_AUD_TUNER, CX18_AV_AUDIO5, 2 },
.ddr = {
/* Hynix HY5DU283222B DDR RAM */
.chip_config = 0x303,
.refresh = 0x3bd,
.timing1 = 0x36320966,
.timing2 = 0x1f,
.tune_lane = 0,
.initial_emrs = 2,
},
.gpio_init.initial_value = 0x1,
.gpio_init.direction = 0x3,
/* FIXME - these GPIO's are just guesses */
.gpio_audio_input = { .mask = 0x3,
.tuner = 0x1,
.linein = 0x3,
.radio = 0x1 },
.xceive_pin = 0,
.pci_list = cx18_pci_mpc718,
.i2c = &cx18_i2c_std,
};
/* ------------------------------------------------------------------------- */
/* GoTView PCI */
static const struct cx18_card_pci_info cx18_pci_gotview_dvd3[] = {
{ PCI_DEVICE_ID_CX23418, CX18_PCI_ID_GOTVIEW, 0x3343 },
{ 0, 0, 0 }
};
static const struct cx18_card cx18_card_gotview_dvd3 = {
.type = CX18_CARD_GOTVIEW_PCI_DVD3,
.name = "GoTView PCI DVD3 Hybrid",
.comment = "Experimenters needed for device to work well.\n"
"\tTo help, mail the ivtv-devel list (www.ivtvdriver.org).\n",
.v4l2_capabilities = CX18_CAP_ENCODER,
.hw_audio_ctrl = CX18_HW_418_AV,
.hw_muxer = CX18_HW_GPIO_MUX,
.hw_all = CX18_HW_TVEEPROM | CX18_HW_418_AV | CX18_HW_TUNER |
CX18_HW_GPIO_MUX | CX18_HW_DVB | CX18_HW_GPIO_RESET_CTRL,
.video_inputs = {
{ CX18_CARD_INPUT_VID_TUNER, 0, CX18_AV_COMPOSITE2 },
{ CX18_CARD_INPUT_SVIDEO1, 1,
CX18_AV_SVIDEO_LUMA3 | CX18_AV_SVIDEO_CHROMA4 },
{ CX18_CARD_INPUT_COMPOSITE1, 1, CX18_AV_COMPOSITE1 },
{ CX18_CARD_INPUT_SVIDEO2, 2,
CX18_AV_SVIDEO_LUMA7 | CX18_AV_SVIDEO_CHROMA8 },
{ CX18_CARD_INPUT_COMPOSITE2, 2, CX18_AV_COMPOSITE6 },
},
.audio_inputs = {
{ CX18_CARD_INPUT_AUD_TUNER, CX18_AV_AUDIO5, 0 },
{ CX18_CARD_INPUT_LINE_IN1, CX18_AV_AUDIO_SERIAL1, 1 },
{ CX18_CARD_INPUT_LINE_IN2, CX18_AV_AUDIO_SERIAL2, 1 },
},
.tuners = {
/* XC3028 tuner */
{ .std = V4L2_STD_ALL, .tuner = TUNER_XC2028 },
},
/* FIXME - the FM radio is just a guess and driver doesn't use SIF */
.radio_input = { CX18_CARD_INPUT_AUD_TUNER, CX18_AV_AUDIO5, 2 },
.ddr = {
/* Hynix HY5DU283222B DDR RAM */
.chip_config = 0x303,
.refresh = 0x3bd,
.timing1 = 0x36320966,
.timing2 = 0x1f,
.tune_lane = 0,
.initial_emrs = 2,
},
.gpio_init.initial_value = 0x1,
.gpio_init.direction = 0x3,
.gpio_audio_input = { .mask = 0x3,
.tuner = 0x1,
.linein = 0x2,
.radio = 0x1 },
.xceive_pin = 0,
.pci_list = cx18_pci_gotview_dvd3,
.i2c = &cx18_i2c_std,
};
/* ------------------------------------------------------------------------- */
/* Conexant Raptor PAL/SECAM: note that this card is analog only! */
static const struct cx18_card_pci_info cx18_pci_cnxt_raptor_pal[] = {
{ PCI_DEVICE_ID_CX23418, CX18_PCI_ID_CONEXANT, 0x0009 },
{ 0, 0, 0 }
};
static const struct cx18_card cx18_card_cnxt_raptor_pal = {
.type = CX18_CARD_CNXT_RAPTOR_PAL,
.name = "Conexant Raptor PAL/SECAM",
.comment = "Analog TV capture supported\n",
.v4l2_capabilities = CX18_CAP_ENCODER,
.hw_audio_ctrl = CX18_HW_418_AV,
.hw_muxer = CX18_HW_GPIO_MUX,
.hw_all = CX18_HW_418_AV | CX18_HW_TUNER | CX18_HW_GPIO_MUX,
.video_inputs = {
{ CX18_CARD_INPUT_VID_TUNER, 0, CX18_AV_COMPOSITE2 },
{ CX18_CARD_INPUT_SVIDEO1, 1,
CX18_AV_SVIDEO_LUMA3 | CX18_AV_SVIDEO_CHROMA4 },
{ CX18_CARD_INPUT_COMPOSITE1, 1, CX18_AV_COMPOSITE1 },
{ CX18_CARD_INPUT_SVIDEO2, 2,
CX18_AV_SVIDEO_LUMA7 | CX18_AV_SVIDEO_CHROMA8 },
{ CX18_CARD_INPUT_COMPOSITE2, 2, CX18_AV_COMPOSITE6 },
},
.audio_inputs = {
{ CX18_CARD_INPUT_AUD_TUNER, CX18_AV_AUDIO5, 0 },
{ CX18_CARD_INPUT_LINE_IN1, CX18_AV_AUDIO_SERIAL1, 1 },
{ CX18_CARD_INPUT_LINE_IN2, CX18_AV_AUDIO_SERIAL2, 1 },
},
.tuners = {
{ .std = V4L2_STD_PAL_SECAM, .tuner = TUNER_PHILIPS_FM1216ME_MK3 },
},
.radio_input = { CX18_CARD_INPUT_AUD_TUNER, CX18_AV_AUDIO_SERIAL1, 2 },
.ddr = {
/* MT 46V16M16 memory */
.chip_config = 0x50306,
.refresh = 0x753,
.timing1 = 0x33220953,
.timing2 = 0x09,
.tune_lane = 0,
.initial_emrs = 0,
},
.gpio_init.initial_value = 0x1002,
.gpio_init.direction = 0xf002,
.gpio_audio_input = { .mask = 0xf002,
.tuner = 0x1002, /* LED D1 Tuner AF */
.linein = 0x2000, /* LED D2 Line In 1 */
.radio = 0x4002 }, /* LED D3 Tuner AF */
.pci_list = cx18_pci_cnxt_raptor_pal,
.i2c = &cx18_i2c_std,
};
/* ------------------------------------------------------------------------- */
/* Toshiba Qosmio laptop internal DVB-T/Analog Hybrid Tuner */
static const struct cx18_card_pci_info cx18_pci_toshiba_qosmio_dvbt[] = {
{ PCI_DEVICE_ID_CX23418, CX18_PCI_ID_TOSHIBA, 0x0110 },
{ 0, 0, 0 }
};
static const struct cx18_card cx18_card_toshiba_qosmio_dvbt = {
.type = CX18_CARD_TOSHIBA_QOSMIO_DVBT,
.name = "Toshiba Qosmio DVB-T/Analog",
.comment = "Experimenters and photos needed for device to work well.\n"
"\tTo help, mail the ivtv-devel list (www.ivtvdriver.org).\n",
.v4l2_capabilities = CX18_CAP_ENCODER,
.hw_audio_ctrl = CX18_HW_418_AV,
.hw_all = CX18_HW_418_AV | CX18_HW_TUNER | CX18_HW_GPIO_RESET_CTRL,
.video_inputs = {
{ CX18_CARD_INPUT_VID_TUNER, 0, CX18_AV_COMPOSITE6 },
{ CX18_CARD_INPUT_SVIDEO1, 1,
CX18_AV_SVIDEO_LUMA3 | CX18_AV_SVIDEO_CHROMA4 },
{ CX18_CARD_INPUT_COMPOSITE1, 1, CX18_AV_COMPOSITE1 },
},
.audio_inputs = {
{ CX18_CARD_INPUT_AUD_TUNER, CX18_AV_AUDIO5, 0 },
{ CX18_CARD_INPUT_LINE_IN1, CX18_AV_AUDIO_SERIAL1, 1 },
},
.tuners = {
{ .std = V4L2_STD_ALL, .tuner = TUNER_XC2028 },
},
.ddr = {
.chip_config = 0x202,
.refresh = 0x3bb,
.timing1 = 0x33320a63,
.timing2 = 0x0a,
.tune_lane = 0,
.initial_emrs = 0x42,
},
.xceive_pin = 15,
.pci_list = cx18_pci_toshiba_qosmio_dvbt,
.i2c = &cx18_i2c_std,
};
/* ------------------------------------------------------------------------- */
/* Leadtek WinFast PVR2100 */
static const struct cx18_card_pci_info cx18_pci_leadtek_pvr2100[] = {
{ PCI_DEVICE_ID_CX23418, CX18_PCI_ID_LEADTEK, 0x6f27 }, /* PVR2100 */
{ 0, 0, 0 }
};
static const struct cx18_card cx18_card_leadtek_pvr2100 = {
.type = CX18_CARD_LEADTEK_PVR2100,
.name = "Leadtek WinFast PVR2100",
.comment = "Experimenters and photos needed for device to work well.\n"
"\tTo help, mail the ivtv-devel list (www.ivtvdriver.org).\n",
.v4l2_capabilities = CX18_CAP_ENCODER,
.hw_audio_ctrl = CX18_HW_418_AV,
.hw_muxer = CX18_HW_GPIO_MUX,
.hw_all = CX18_HW_418_AV | CX18_HW_TUNER | CX18_HW_GPIO_MUX |
CX18_HW_GPIO_RESET_CTRL,
.video_inputs = {
{ CX18_CARD_INPUT_VID_TUNER, 0, CX18_AV_COMPOSITE2 },
{ CX18_CARD_INPUT_SVIDEO1, 1,
CX18_AV_SVIDEO_LUMA3 | CX18_AV_SVIDEO_CHROMA4 },
{ CX18_CARD_INPUT_COMPOSITE1, 1, CX18_AV_COMPOSITE7 },
{ CX18_CARD_INPUT_COMPONENT1, 1, CX18_AV_COMPONENT1 },
},
.audio_inputs = {
{ CX18_CARD_INPUT_AUD_TUNER, CX18_AV_AUDIO5, 0 },
{ CX18_CARD_INPUT_LINE_IN1, CX18_AV_AUDIO_SERIAL1, 1 },
},
.tuners = {
/* XC2028 tuner */
{ .std = V4L2_STD_ALL, .tuner = TUNER_XC2028 },
},
.radio_input = { CX18_CARD_INPUT_AUD_TUNER, CX18_AV_AUDIO5, 2 },
.ddr = {
/* Pointer to proper DDR config values provided by Terry Wu */
.chip_config = 0x303,
.refresh = 0x3bb,
.timing1 = 0x24220e83,
.timing2 = 0x1f,
.tune_lane = 0,
.initial_emrs = 0x2,
},
.gpio_init.initial_value = 0x6,
.gpio_init.direction = 0x7,
.gpio_audio_input = { .mask = 0x7,
.tuner = 0x6, .linein = 0x2, .radio = 0x2 },
.xceive_pin = 1,
.pci_list = cx18_pci_leadtek_pvr2100,
.i2c = &cx18_i2c_std,
};
/* ------------------------------------------------------------------------- */
/* Leadtek WinFast DVR3100 H */
static const struct cx18_card_pci_info cx18_pci_leadtek_dvr3100h[] = {
{ PCI_DEVICE_ID_CX23418, CX18_PCI_ID_LEADTEK, 0x6690 }, /* DVR3100 H */
{ 0, 0, 0 }
};
static const struct cx18_card cx18_card_leadtek_dvr3100h = {
.type = CX18_CARD_LEADTEK_DVR3100H,
.name = "Leadtek WinFast DVR3100 H",
.comment = "Simultaneous DVB-T and Analog capture supported,\n"
"\texcept when capturing Analog from the antenna input.\n",
.v4l2_capabilities = CX18_CAP_ENCODER,
.hw_audio_ctrl = CX18_HW_418_AV,
.hw_muxer = CX18_HW_GPIO_MUX,
.hw_all = CX18_HW_418_AV | CX18_HW_TUNER | CX18_HW_GPIO_MUX |
CX18_HW_DVB | CX18_HW_GPIO_RESET_CTRL,
.video_inputs = {
{ CX18_CARD_INPUT_VID_TUNER, 0, CX18_AV_COMPOSITE2 },
{ CX18_CARD_INPUT_SVIDEO1, 1,
CX18_AV_SVIDEO_LUMA3 | CX18_AV_SVIDEO_CHROMA4 },
{ CX18_CARD_INPUT_COMPOSITE1, 1, CX18_AV_COMPOSITE7 },
{ CX18_CARD_INPUT_COMPONENT1, 1, CX18_AV_COMPONENT1 },
},
.audio_inputs = {
{ CX18_CARD_INPUT_AUD_TUNER, CX18_AV_AUDIO5, 0 },
{ CX18_CARD_INPUT_LINE_IN1, CX18_AV_AUDIO_SERIAL1, 1 },
},
.tuners = {
/* XC3028 tuner */
{ .std = V4L2_STD_ALL, .tuner = TUNER_XC2028 },
},
.radio_input = { CX18_CARD_INPUT_AUD_TUNER, CX18_AV_AUDIO5, 2 },
.ddr = {
/* Pointer to proper DDR config values provided by Terry Wu */
.chip_config = 0x303,
.refresh = 0x3bb,
.timing1 = 0x24220e83,
.timing2 = 0x1f,
.tune_lane = 0,
.initial_emrs = 0x2,
},
.gpio_init.initial_value = 0x6,
.gpio_init.direction = 0x7,
.gpio_audio_input = { .mask = 0x7,
.tuner = 0x6, .linein = 0x2, .radio = 0x2 },
.xceive_pin = 1,
.pci_list = cx18_pci_leadtek_dvr3100h,
.i2c = &cx18_i2c_std,
};
/* ------------------------------------------------------------------------- */
static const struct cx18_card *cx18_card_list[] = {
&cx18_card_hvr1600_esmt,
&cx18_card_hvr1600_samsung,
&cx18_card_h900,
&cx18_card_mpc718,
&cx18_card_cnxt_raptor_pal,
&cx18_card_toshiba_qosmio_dvbt,
&cx18_card_leadtek_pvr2100,
&cx18_card_leadtek_dvr3100h,
&cx18_card_gotview_dvd3,
&cx18_card_hvr1600_s5h1411
};
const struct cx18_card *cx18_get_card(u16 index)
{
if (index >= ARRAY_SIZE(cx18_card_list))
return NULL;
return cx18_card_list[index];
}
int cx18_get_input(struct cx18 *cx, u16 index, struct v4l2_input *input)
{
const struct cx18_card_video_input *card_input =
cx->card->video_inputs + index;
static const char * const input_strs[] = {
"Tuner 1",
"S-Video 1",
"S-Video 2",
"Composite 1",
"Composite 2",
"Component 1"
};
if (index >= cx->nof_inputs)
return -EINVAL;
input->index = index;
strlcpy(input->name, input_strs[card_input->video_type - 1],
sizeof(input->name));
input->type = (card_input->video_type == CX18_CARD_INPUT_VID_TUNER ?
V4L2_INPUT_TYPE_TUNER : V4L2_INPUT_TYPE_CAMERA);
input->audioset = (1 << cx->nof_audio_inputs) - 1;
input->std = (input->type == V4L2_INPUT_TYPE_TUNER) ?
cx->tuner_std : V4L2_STD_ALL;
return 0;
}
int cx18_get_audio_input(struct cx18 *cx, u16 index, struct v4l2_audio *audio)
{
const struct cx18_card_audio_input *aud_input =
cx->card->audio_inputs + index;
static const char * const input_strs[] = {
"Tuner 1",
"Line In 1",
"Line In 2"
};
memset(audio, 0, sizeof(*audio));
if (index >= cx->nof_audio_inputs)
return -EINVAL;
strlcpy(audio->name, input_strs[aud_input->audio_type - 1],
sizeof(audio->name));
audio->index = index;
audio->capability = V4L2_AUDCAP_STEREO;
return 0;
}

View file

@ -0,0 +1,157 @@
/*
* cx18 functions to query card hardware
*
* Derived from ivtv-cards.c
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* 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
*/
/* hardware flags */
#define CX18_HW_TUNER (1 << 0)
#define CX18_HW_TVEEPROM (1 << 1)
#define CX18_HW_CS5345 (1 << 2)
#define CX18_HW_DVB (1 << 3)
#define CX18_HW_418_AV (1 << 4)
#define CX18_HW_GPIO_MUX (1 << 5)
#define CX18_HW_GPIO_RESET_CTRL (1 << 6)
#define CX18_HW_Z8F0811_IR_TX_HAUP (1 << 7)
#define CX18_HW_Z8F0811_IR_RX_HAUP (1 << 8)
#define CX18_HW_Z8F0811_IR_HAUP (CX18_HW_Z8F0811_IR_RX_HAUP | \
CX18_HW_Z8F0811_IR_TX_HAUP)
#define CX18_HW_IR_ANY (CX18_HW_Z8F0811_IR_RX_HAUP | \
CX18_HW_Z8F0811_IR_TX_HAUP)
/* video inputs */
#define CX18_CARD_INPUT_VID_TUNER 1
#define CX18_CARD_INPUT_SVIDEO1 2
#define CX18_CARD_INPUT_SVIDEO2 3
#define CX18_CARD_INPUT_COMPOSITE1 4
#define CX18_CARD_INPUT_COMPOSITE2 5
#define CX18_CARD_INPUT_COMPONENT1 6
/* audio inputs */
#define CX18_CARD_INPUT_AUD_TUNER 1
#define CX18_CARD_INPUT_LINE_IN1 2
#define CX18_CARD_INPUT_LINE_IN2 3
#define CX18_CARD_MAX_VIDEO_INPUTS 6
#define CX18_CARD_MAX_AUDIO_INPUTS 3
#define CX18_CARD_MAX_TUNERS 2
/* V4L2 capability aliases */
#define CX18_CAP_ENCODER (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TUNER | \
V4L2_CAP_AUDIO | V4L2_CAP_READWRITE | \
V4L2_CAP_VBI_CAPTURE | V4L2_CAP_SLICED_VBI_CAPTURE)
struct cx18_card_video_input {
u8 video_type; /* video input type */
u8 audio_index; /* index in cx18_card_audio_input array */
u32 video_input; /* hardware video input */
};
struct cx18_card_audio_input {
u8 audio_type; /* audio input type */
u32 audio_input; /* hardware audio input */
u16 muxer_input; /* hardware muxer input for boards with a
multiplexer chip */
};
struct cx18_card_pci_info {
u16 device;
u16 subsystem_vendor;
u16 subsystem_device;
};
/* GPIO definitions */
/* The mask is the set of bits used by the operation */
struct cx18_gpio_init { /* set initial GPIO DIR and OUT values */
u32 direction; /* DIR setting. Leave to 0 if no init is needed */
u32 initial_value;
};
struct cx18_gpio_i2c_slave_reset {
u32 active_lo_mask; /* GPIO outputs that reset i2c chips when low */
u32 active_hi_mask; /* GPIO outputs that reset i2c chips when high */
int msecs_asserted; /* time period reset must remain asserted */
int msecs_recovery; /* time after deassert for chips to be ready */
u32 ir_reset_mask; /* GPIO to reset the Zilog Z8F0811 IR contoller */
};
struct cx18_gpio_audio_input { /* select tuner/line in input */
u32 mask; /* leave to 0 if not supported */
u32 tuner;
u32 linein;
u32 radio;
};
struct cx18_card_tuner {
v4l2_std_id std; /* standard for which the tuner is suitable */
int tuner; /* tuner ID (from tuner.h) */
};
struct cx18_card_tuner_i2c {
unsigned short radio[2];/* radio tuner i2c address to probe */
unsigned short demod[3];/* demodulator i2c address to probe */
unsigned short tv[4]; /* tv tuner i2c addresses to probe */
};
struct cx18_ddr { /* DDR config data */
u32 chip_config;
u32 refresh;
u32 timing1;
u32 timing2;
u32 tune_lane;
u32 initial_emrs;
};
/* for card information/parameters */
struct cx18_card {
int type;
char *name;
char *comment;
u32 v4l2_capabilities;
u32 hw_audio_ctrl; /* hardware used for the V4L2 controls (only
1 dev allowed currently) */
u32 hw_muxer; /* hardware used to multiplex audio input */
u32 hw_all; /* all hardware used by the board */
struct cx18_card_video_input video_inputs[CX18_CARD_MAX_VIDEO_INPUTS];
struct cx18_card_audio_input audio_inputs[CX18_CARD_MAX_AUDIO_INPUTS];
struct cx18_card_audio_input radio_input;
/* GPIO card-specific settings */
u8 xceive_pin; /* XCeive tuner GPIO reset pin */
struct cx18_gpio_init gpio_init;
struct cx18_gpio_i2c_slave_reset gpio_i2c_slave_reset;
struct cx18_gpio_audio_input gpio_audio_input;
struct cx18_card_tuner tuners[CX18_CARD_MAX_TUNERS];
struct cx18_card_tuner_i2c *i2c;
struct cx18_ddr ddr;
/* list of device and subsystem vendor/devices that
correspond to this card type. */
const struct cx18_card_pci_info *pci_list;
};
int cx18_get_input(struct cx18 *cx, u16 index, struct v4l2_input *input);
int cx18_get_audio_input(struct cx18 *cx, u16 index, struct v4l2_audio *input);
const struct cx18_card *cx18_get_card(u16 index);

View file

@ -0,0 +1,131 @@
/*
* cx18 ioctl control functions
*
* Derived from ivtv-controls.c
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include "cx18-driver.h"
#include "cx18-cards.h"
#include "cx18-ioctl.h"
#include "cx18-audio.h"
#include "cx18-mailbox.h"
#include "cx18-controls.h"
static int cx18_s_stream_vbi_fmt(struct cx2341x_handler *cxhdl, u32 fmt)
{
struct cx18 *cx = container_of(cxhdl, struct cx18, cxhdl);
int type = cxhdl->stream_type->val;
if (atomic_read(&cx->ana_capturing) > 0)
return -EBUSY;
if (fmt != V4L2_MPEG_STREAM_VBI_FMT_IVTV ||
!(type == V4L2_MPEG_STREAM_TYPE_MPEG2_PS ||
type == V4L2_MPEG_STREAM_TYPE_MPEG2_DVD ||
type == V4L2_MPEG_STREAM_TYPE_MPEG2_SVCD)) {
/* Only IVTV fmt VBI insertion & only MPEG-2 PS type streams */
cx->vbi.insert_mpeg = V4L2_MPEG_STREAM_VBI_FMT_NONE;
CX18_DEBUG_INFO("disabled insertion of sliced VBI data into "
"the MPEG stream\n");
return 0;
}
/* Allocate sliced VBI buffers if needed. */
if (cx->vbi.sliced_mpeg_data[0] == NULL) {
int i;
for (i = 0; i < CX18_VBI_FRAMES; i++) {
cx->vbi.sliced_mpeg_data[i] =
kmalloc(CX18_SLICED_MPEG_DATA_BUFSZ, GFP_KERNEL);
if (cx->vbi.sliced_mpeg_data[i] == NULL) {
while (--i >= 0) {
kfree(cx->vbi.sliced_mpeg_data[i]);
cx->vbi.sliced_mpeg_data[i] = NULL;
}
cx->vbi.insert_mpeg =
V4L2_MPEG_STREAM_VBI_FMT_NONE;
CX18_WARN("Unable to allocate buffers for "
"sliced VBI data insertion\n");
return -ENOMEM;
}
}
}
cx->vbi.insert_mpeg = fmt;
CX18_DEBUG_INFO("enabled insertion of sliced VBI data into the MPEG PS,"
"when sliced VBI is enabled\n");
/*
* If our current settings have no lines set for capture, store a valid,
* default set of service lines to capture, in our current settings.
*/
if (cx18_get_service_set(cx->vbi.sliced_in) == 0) {
if (cx->is_60hz)
cx->vbi.sliced_in->service_set =
V4L2_SLICED_CAPTION_525;
else
cx->vbi.sliced_in->service_set = V4L2_SLICED_WSS_625;
cx18_expand_service_set(cx->vbi.sliced_in, cx->is_50hz);
}
return 0;
}
static int cx18_s_video_encoding(struct cx2341x_handler *cxhdl, u32 val)
{
struct cx18 *cx = container_of(cxhdl, struct cx18, cxhdl);
int is_mpeg1 = val == V4L2_MPEG_VIDEO_ENCODING_MPEG_1;
struct v4l2_mbus_framefmt fmt;
/* fix videodecoder resolution */
fmt.width = cxhdl->width / (is_mpeg1 ? 2 : 1);
fmt.height = cxhdl->height;
fmt.code = V4L2_MBUS_FMT_FIXED;
v4l2_subdev_call(cx->sd_av, video, s_mbus_fmt, &fmt);
return 0;
}
static int cx18_s_audio_sampling_freq(struct cx2341x_handler *cxhdl, u32 idx)
{
static const u32 freqs[3] = { 44100, 48000, 32000 };
struct cx18 *cx = container_of(cxhdl, struct cx18, cxhdl);
/* The audio clock of the digitizer must match the codec sample
rate otherwise you get some very strange effects. */
if (idx < ARRAY_SIZE(freqs))
cx18_call_all(cx, audio, s_clock_freq, freqs[idx]);
return 0;
}
static int cx18_s_audio_mode(struct cx2341x_handler *cxhdl, u32 val)
{
struct cx18 *cx = container_of(cxhdl, struct cx18, cxhdl);
cx->dualwatch_stereo_mode = val;
return 0;
}
struct cx2341x_handler_ops cx18_cxhdl_ops = {
.s_audio_mode = cx18_s_audio_mode,
.s_audio_sampling_freq = cx18_s_audio_sampling_freq,
.s_video_encoding = cx18_s_video_encoding,
.s_stream_vbi_fmt = cx18_s_stream_vbi_fmt,
};

View file

@ -0,0 +1,24 @@
/*
* cx18 ioctl control functions
*
* Derived from ivtv-controls.h
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* 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
*/
extern struct cx2341x_handler_ops cx18_cxhdl_ops;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,730 @@
/*
* cx18 driver internal defines and structures
*
* Derived from ivtv-driver.h
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* 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
*/
#ifndef CX18_DRIVER_H
#define CX18_DRIVER_H
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/i2c.h>
#include <linux/i2c-algo-bit.h>
#include <linux/list.h>
#include <linux/unistd.h>
#include <linux/pagemap.h>
#include <linux/workqueue.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <asm/byteorder.h>
#include <media/v4l2-common.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-device.h>
#include <media/v4l2-fh.h>
#include <media/tuner.h>
#include <media/ir-kbd-i2c.h>
#include "cx18-mailbox.h"
#include "cx18-av-core.h"
#include "cx23418.h"
/* DVB */
#include "demux.h"
#include "dmxdev.h"
#include "dvb_demux.h"
#include "dvb_frontend.h"
#include "dvb_net.h"
#include "dvbdev.h"
/* Videobuf / YUV support */
#include <media/videobuf-core.h>
#include <media/videobuf-vmalloc.h>
#ifndef CONFIG_PCI
# error "This driver requires kernel PCI support."
#endif
#define CX18_MEM_OFFSET 0x00000000
#define CX18_MEM_SIZE 0x04000000
#define CX18_REG_OFFSET 0x02000000
/* Maximum cx18 driver instances. */
#define CX18_MAX_CARDS 32
/* Supported cards */
#define CX18_CARD_HVR_1600_ESMT 0 /* Hauppauge HVR 1600 (ESMT memory) */
#define CX18_CARD_HVR_1600_SAMSUNG 1 /* Hauppauge HVR 1600 (Samsung memory) */
#define CX18_CARD_COMPRO_H900 2 /* Compro VideoMate H900 */
#define CX18_CARD_YUAN_MPC718 3 /* Yuan MPC718 */
#define CX18_CARD_CNXT_RAPTOR_PAL 4 /* Conexant Raptor PAL */
#define CX18_CARD_TOSHIBA_QOSMIO_DVBT 5 /* Toshiba Qosmio Interal DVB-T/Analog*/
#define CX18_CARD_LEADTEK_PVR2100 6 /* Leadtek WinFast PVR2100 */
#define CX18_CARD_LEADTEK_DVR3100H 7 /* Leadtek WinFast DVR3100 H */
#define CX18_CARD_GOTVIEW_PCI_DVD3 8 /* GoTView PCI DVD3 Hybrid */
#define CX18_CARD_HVR_1600_S5H1411 9 /* Hauppauge HVR 1600 s5h1411/tda18271*/
#define CX18_CARD_LAST 9
#define CX18_ENC_STREAM_TYPE_MPG 0
#define CX18_ENC_STREAM_TYPE_TS 1
#define CX18_ENC_STREAM_TYPE_YUV 2
#define CX18_ENC_STREAM_TYPE_VBI 3
#define CX18_ENC_STREAM_TYPE_PCM 4
#define CX18_ENC_STREAM_TYPE_IDX 5
#define CX18_ENC_STREAM_TYPE_RAD 6
#define CX18_MAX_STREAMS 7
/* system vendor and device IDs */
#define PCI_VENDOR_ID_CX 0x14f1
#define PCI_DEVICE_ID_CX23418 0x5b7a
/* subsystem vendor ID */
#define CX18_PCI_ID_HAUPPAUGE 0x0070
#define CX18_PCI_ID_COMPRO 0x185b
#define CX18_PCI_ID_YUAN 0x12ab
#define CX18_PCI_ID_CONEXANT 0x14f1
#define CX18_PCI_ID_TOSHIBA 0x1179
#define CX18_PCI_ID_LEADTEK 0x107D
#define CX18_PCI_ID_GOTVIEW 0x5854
/* ======================================================================== */
/* ========================== START USER SETTABLE DMA VARIABLES =========== */
/* ======================================================================== */
/* DMA Buffers, Default size in MB allocated */
#define CX18_DEFAULT_ENC_TS_BUFFERS 1
#define CX18_DEFAULT_ENC_MPG_BUFFERS 2
#define CX18_DEFAULT_ENC_IDX_BUFFERS 1
#define CX18_DEFAULT_ENC_YUV_BUFFERS 2
#define CX18_DEFAULT_ENC_VBI_BUFFERS 1
#define CX18_DEFAULT_ENC_PCM_BUFFERS 1
/* Maximum firmware DMA buffers per stream */
#define CX18_MAX_FW_MDLS_PER_STREAM 63
/* YUV buffer sizes in bytes to ensure integer # of frames per buffer */
#define CX18_UNIT_ENC_YUV_BUFSIZE (720 * 32 * 3 / 2) /* bytes */
#define CX18_625_LINE_ENC_YUV_BUFSIZE (CX18_UNIT_ENC_YUV_BUFSIZE * 576/32)
#define CX18_525_LINE_ENC_YUV_BUFSIZE (CX18_UNIT_ENC_YUV_BUFSIZE * 480/32)
/* IDX buffer size should be a multiple of the index entry size from the chip */
struct cx18_enc_idx_entry {
__le32 length;
__le32 offset_low;
__le32 offset_high;
__le32 flags;
__le32 pts_low;
__le32 pts_high;
} __attribute__ ((packed));
#define CX18_UNIT_ENC_IDX_BUFSIZE \
(sizeof(struct cx18_enc_idx_entry) * V4L2_ENC_IDX_ENTRIES)
/* DMA buffer, default size in kB allocated */
#define CX18_DEFAULT_ENC_TS_BUFSIZE 32
#define CX18_DEFAULT_ENC_MPG_BUFSIZE 32
#define CX18_DEFAULT_ENC_IDX_BUFSIZE (CX18_UNIT_ENC_IDX_BUFSIZE * 1 / 1024 + 1)
#define CX18_DEFAULT_ENC_YUV_BUFSIZE (CX18_UNIT_ENC_YUV_BUFSIZE * 3 / 1024 + 1)
#define CX18_DEFAULT_ENC_PCM_BUFSIZE 4
/* i2c stuff */
#define I2C_CLIENTS_MAX 16
/* debugging */
/* Flag to turn on high volume debugging */
#define CX18_DBGFLG_WARN (1 << 0)
#define CX18_DBGFLG_INFO (1 << 1)
#define CX18_DBGFLG_API (1 << 2)
#define CX18_DBGFLG_DMA (1 << 3)
#define CX18_DBGFLG_IOCTL (1 << 4)
#define CX18_DBGFLG_FILE (1 << 5)
#define CX18_DBGFLG_I2C (1 << 6)
#define CX18_DBGFLG_IRQ (1 << 7)
/* Flag to turn on high volume debugging */
#define CX18_DBGFLG_HIGHVOL (1 << 8)
/* NOTE: extra space before comma in 'fmt , ## args' is required for
gcc-2.95, otherwise it won't compile. */
#define CX18_DEBUG(x, type, fmt, args...) \
do { \
if ((x) & cx18_debug) \
v4l2_info(&cx->v4l2_dev, " " type ": " fmt , ## args); \
} while (0)
#define CX18_DEBUG_WARN(fmt, args...) CX18_DEBUG(CX18_DBGFLG_WARN, "warning", fmt , ## args)
#define CX18_DEBUG_INFO(fmt, args...) CX18_DEBUG(CX18_DBGFLG_INFO, "info", fmt , ## args)
#define CX18_DEBUG_API(fmt, args...) CX18_DEBUG(CX18_DBGFLG_API, "api", fmt , ## args)
#define CX18_DEBUG_DMA(fmt, args...) CX18_DEBUG(CX18_DBGFLG_DMA, "dma", fmt , ## args)
#define CX18_DEBUG_IOCTL(fmt, args...) CX18_DEBUG(CX18_DBGFLG_IOCTL, "ioctl", fmt , ## args)
#define CX18_DEBUG_FILE(fmt, args...) CX18_DEBUG(CX18_DBGFLG_FILE, "file", fmt , ## args)
#define CX18_DEBUG_I2C(fmt, args...) CX18_DEBUG(CX18_DBGFLG_I2C, "i2c", fmt , ## args)
#define CX18_DEBUG_IRQ(fmt, args...) CX18_DEBUG(CX18_DBGFLG_IRQ, "irq", fmt , ## args)
#define CX18_DEBUG_HIGH_VOL(x, type, fmt, args...) \
do { \
if (((x) & cx18_debug) && (cx18_debug & CX18_DBGFLG_HIGHVOL)) \
v4l2_info(&cx->v4l2_dev, " " type ": " fmt , ## args); \
} while (0)
#define CX18_DEBUG_HI_WARN(fmt, args...) CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_WARN, "warning", fmt , ## args)
#define CX18_DEBUG_HI_INFO(fmt, args...) CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_INFO, "info", fmt , ## args)
#define CX18_DEBUG_HI_API(fmt, args...) CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_API, "api", fmt , ## args)
#define CX18_DEBUG_HI_DMA(fmt, args...) CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_DMA, "dma", fmt , ## args)
#define CX18_DEBUG_HI_IOCTL(fmt, args...) CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_IOCTL, "ioctl", fmt , ## args)
#define CX18_DEBUG_HI_FILE(fmt, args...) CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_FILE, "file", fmt , ## args)
#define CX18_DEBUG_HI_I2C(fmt, args...) CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_I2C, "i2c", fmt , ## args)
#define CX18_DEBUG_HI_IRQ(fmt, args...) CX18_DEBUG_HIGH_VOL(CX18_DBGFLG_IRQ, "irq", fmt , ## args)
/* Standard kernel messages */
#define CX18_ERR(fmt, args...) v4l2_err(&cx->v4l2_dev, fmt , ## args)
#define CX18_WARN(fmt, args...) v4l2_warn(&cx->v4l2_dev, fmt , ## args)
#define CX18_INFO(fmt, args...) v4l2_info(&cx->v4l2_dev, fmt , ## args)
/* Messages for internal subdevs to use */
#define CX18_DEBUG_DEV(x, dev, type, fmt, args...) \
do { \
if ((x) & cx18_debug) \
v4l2_info(dev, " " type ": " fmt , ## args); \
} while (0)
#define CX18_DEBUG_WARN_DEV(dev, fmt, args...) \
CX18_DEBUG_DEV(CX18_DBGFLG_WARN, dev, "warning", fmt , ## args)
#define CX18_DEBUG_INFO_DEV(dev, fmt, args...) \
CX18_DEBUG_DEV(CX18_DBGFLG_INFO, dev, "info", fmt , ## args)
#define CX18_DEBUG_API_DEV(dev, fmt, args...) \
CX18_DEBUG_DEV(CX18_DBGFLG_API, dev, "api", fmt , ## args)
#define CX18_DEBUG_DMA_DEV(dev, fmt, args...) \
CX18_DEBUG_DEV(CX18_DBGFLG_DMA, dev, "dma", fmt , ## args)
#define CX18_DEBUG_IOCTL_DEV(dev, fmt, args...) \
CX18_DEBUG_DEV(CX18_DBGFLG_IOCTL, dev, "ioctl", fmt , ## args)
#define CX18_DEBUG_FILE_DEV(dev, fmt, args...) \
CX18_DEBUG_DEV(CX18_DBGFLG_FILE, dev, "file", fmt , ## args)
#define CX18_DEBUG_I2C_DEV(dev, fmt, args...) \
CX18_DEBUG_DEV(CX18_DBGFLG_I2C, dev, "i2c", fmt , ## args)
#define CX18_DEBUG_IRQ_DEV(dev, fmt, args...) \
CX18_DEBUG_DEV(CX18_DBGFLG_IRQ, dev, "irq", fmt , ## args)
#define CX18_DEBUG_HIGH_VOL_DEV(x, dev, type, fmt, args...) \
do { \
if (((x) & cx18_debug) && (cx18_debug & CX18_DBGFLG_HIGHVOL)) \
v4l2_info(dev, " " type ": " fmt , ## args); \
} while (0)
#define CX18_DEBUG_HI_WARN_DEV(dev, fmt, args...) \
CX18_DEBUG_HIGH_VOL_DEV(CX18_DBGFLG_WARN, dev, "warning", fmt , ## args)
#define CX18_DEBUG_HI_INFO_DEV(dev, fmt, args...) \
CX18_DEBUG_HIGH_VOL_DEV(CX18_DBGFLG_INFO, dev, "info", fmt , ## args)
#define CX18_DEBUG_HI_API_DEV(dev, fmt, args...) \
CX18_DEBUG_HIGH_VOL_DEV(CX18_DBGFLG_API, dev, "api", fmt , ## args)
#define CX18_DEBUG_HI_DMA_DEV(dev, fmt, args...) \
CX18_DEBUG_HIGH_VOL_DEV(CX18_DBGFLG_DMA, dev, "dma", fmt , ## args)
#define CX18_DEBUG_HI_IOCTL_DEV(dev, fmt, args...) \
CX18_DEBUG_HIGH_VOL_DEV(CX18_DBGFLG_IOCTL, dev, "ioctl", fmt , ## args)
#define CX18_DEBUG_HI_FILE_DEV(dev, fmt, args...) \
CX18_DEBUG_HIGH_VOL_DEV(CX18_DBGFLG_FILE, dev, "file", fmt , ## args)
#define CX18_DEBUG_HI_I2C_DEV(dev, fmt, args...) \
CX18_DEBUG_HIGH_VOL_DEV(CX18_DBGFLG_I2C, dev, "i2c", fmt , ## args)
#define CX18_DEBUG_HI_IRQ_DEV(dev, fmt, args...) \
CX18_DEBUG_HIGH_VOL_DEV(CX18_DBGFLG_IRQ, dev, "irq", fmt , ## args)
#define CX18_ERR_DEV(dev, fmt, args...) v4l2_err(dev, fmt , ## args)
#define CX18_WARN_DEV(dev, fmt, args...) v4l2_warn(dev, fmt , ## args)
#define CX18_INFO_DEV(dev, fmt, args...) v4l2_info(dev, fmt , ## args)
extern int cx18_debug;
struct cx18_options {
int megabytes[CX18_MAX_STREAMS]; /* Size in megabytes of each stream */
int cardtype; /* force card type on load */
int tuner; /* set tuner on load */
int radio; /* enable/disable radio */
};
/* per-mdl bit flags */
#define CX18_F_M_NEED_SWAP 0 /* mdl buffer data must be endianness swapped */
/* per-stream, s_flags */
#define CX18_F_S_CLAIMED 3 /* this stream is claimed */
#define CX18_F_S_STREAMING 4 /* the fw is decoding/encoding this stream */
#define CX18_F_S_INTERNAL_USE 5 /* this stream is used internally (sliced VBI processing) */
#define CX18_F_S_STREAMOFF 7 /* signal end of stream EOS */
#define CX18_F_S_APPL_IO 8 /* this stream is used read/written by an application */
#define CX18_F_S_STOPPING 9 /* telling the fw to stop capturing */
/* per-cx18, i_flags */
#define CX18_F_I_LOADED_FW 0 /* Loaded firmware 1st time */
#define CX18_F_I_EOS 4 /* End of encoder stream */
#define CX18_F_I_RADIO_USER 5 /* radio tuner is selected */
#define CX18_F_I_ENC_PAUSED 13 /* the encoder is paused */
#define CX18_F_I_INITED 21 /* set after first open */
#define CX18_F_I_FAILED 22 /* set if first open failed */
/* These are the VBI types as they appear in the embedded VBI private packets. */
#define CX18_SLICED_TYPE_TELETEXT_B (1)
#define CX18_SLICED_TYPE_CAPTION_525 (4)
#define CX18_SLICED_TYPE_WSS_625 (5)
#define CX18_SLICED_TYPE_VPS (7)
/**
* list_entry_is_past_end - check if a previous loop cursor is off list end
* @pos: the type * previously used as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Check if the entry's list_head is the head of the list, thus it's not a
* real entry but was the loop cursor that walked past the end
*/
#define list_entry_is_past_end(pos, head, member) \
(&pos->member == (head))
struct cx18_buffer {
struct list_head list;
dma_addr_t dma_handle;
char *buf;
u32 bytesused;
u32 readpos;
};
struct cx18_mdl {
struct list_head list;
u32 id; /* index into cx->scb->cpu_mdl[] of 1st cx18_mdl_ent */
unsigned int skipped;
unsigned long m_flags;
struct list_head buf_list;
struct cx18_buffer *curr_buf; /* current buffer in list for reading */
u32 bytesused;
u32 readpos;
};
struct cx18_queue {
struct list_head list;
atomic_t depth;
u32 bytesused;
spinlock_t lock;
};
struct cx18_stream; /* forward reference */
struct cx18_dvb {
struct cx18_stream *stream;
struct dmx_frontend hw_frontend;
struct dmx_frontend mem_frontend;
struct dmxdev dmxdev;
struct dvb_adapter dvb_adapter;
struct dvb_demux demux;
struct dvb_frontend *fe;
struct dvb_net dvbnet;
int enabled;
int feeding;
struct mutex feedlock;
};
struct cx18; /* forward reference */
struct cx18_scb; /* forward reference */
#define CX18_MAX_MDL_ACKS 2
#define CX18_MAX_IN_WORK_ORDERS (CX18_MAX_FW_MDLS_PER_STREAM + 7)
/* CPU_DE_RELEASE_MDL can burst CX18_MAX_FW_MDLS_PER_STREAM orders in a group */
#define CX18_F_EWO_MB_STALE_UPON_RECEIPT 0x1
#define CX18_F_EWO_MB_STALE_WHILE_PROC 0x2
#define CX18_F_EWO_MB_STALE \
(CX18_F_EWO_MB_STALE_UPON_RECEIPT | CX18_F_EWO_MB_STALE_WHILE_PROC)
struct cx18_in_work_order {
struct work_struct work;
atomic_t pending;
struct cx18 *cx;
unsigned long flags;
int rpu;
struct cx18_mailbox mb;
struct cx18_mdl_ack mdl_ack[CX18_MAX_MDL_ACKS];
char *str;
};
#define CX18_INVALID_TASK_HANDLE 0xffffffff
struct cx18_stream {
/* These first five fields are always set, even if the stream
is not actually created. */
struct video_device *video_dev; /* NULL when stream not created */
struct cx18_dvb *dvb; /* DVB / Digital Transport */
struct cx18 *cx; /* for ease of use */
const char *name; /* name of the stream */
int type; /* stream type */
u32 handle; /* task handle */
unsigned int mdl_base_idx;
u32 id;
unsigned long s_flags; /* status flags, see above */
int dma; /* can be PCI_DMA_TODEVICE,
PCI_DMA_FROMDEVICE or
PCI_DMA_NONE */
wait_queue_head_t waitq;
/* Buffers */
struct list_head buf_pool; /* buffers not attached to an MDL */
u32 buffers; /* total buffers owned by this stream */
u32 buf_size; /* size in bytes of a single buffer */
/* MDL sizes - all stream MDLs are the same size */
u32 bufs_per_mdl;
u32 mdl_size; /* total bytes in all buffers in a mdl */
/* MDL Queues */
struct cx18_queue q_free; /* free - in rotation, not committed */
struct cx18_queue q_busy; /* busy - in use by firmware */
struct cx18_queue q_full; /* full - data for user apps */
struct cx18_queue q_idle; /* idle - not in rotation */
struct work_struct out_work_order;
/* Videobuf for YUV video */
u32 pixelformat;
u32 vb_bytes_per_frame;
struct list_head vb_capture; /* video capture queue */
spinlock_t vb_lock;
struct timer_list vb_timeout;
struct videobuf_queue vbuf_q;
spinlock_t vbuf_q_lock; /* Protect vbuf_q */
enum v4l2_buf_type vb_type;
};
struct cx18_videobuf_buffer {
/* Common video buffer sub-system struct */
struct videobuf_buffer vb;
v4l2_std_id tvnorm; /* selected tv norm */
u32 bytes_used;
};
struct cx18_open_id {
struct v4l2_fh fh;
u32 open_id;
int type;
struct cx18 *cx;
};
static inline struct cx18_open_id *fh2id(struct v4l2_fh *fh)
{
return container_of(fh, struct cx18_open_id, fh);
}
static inline struct cx18_open_id *file2id(struct file *file)
{
return fh2id(file->private_data);
}
/* forward declaration of struct defined in cx18-cards.h */
struct cx18_card;
/*
* A note about "sliced" VBI data as implemented in this driver:
*
* Currently we collect the sliced VBI in the form of Ancillary Data
* packets, inserted by the AV core decoder/digitizer/slicer in the
* horizontal blanking region of the VBI lines, in "raw" mode as far as
* the Encoder is concerned. We don't ever tell the Encoder itself
* to provide sliced VBI. (AV Core: sliced mode - Encoder: raw mode)
*
* We then process the ancillary data ourselves to send the sliced data
* to the user application directly or build up MPEG-2 private stream 1
* packets to splice into (only!) MPEG-2 PS streams for the user app.
*
* (That's how ivtv essentially does it.)
*
* The Encoder should be able to extract certain sliced VBI data for
* us and provide it in a separate stream or splice it into any type of
* MPEG PS or TS stream, but this isn't implemented yet.
*/
/*
* Number of "raw" VBI samples per horizontal line we tell the Encoder to
* grab from the decoder/digitizer/slicer output for raw or sliced VBI.
* It depends on the pixel clock and the horiz rate:
*
* (1/Fh)*(2*Fp) = Samples/line
* = 4 bytes EAV + Anc data in hblank + 4 bytes SAV + active samples
*
* Sliced VBI data is sent as ancillary data during horizontal blanking
* Raw VBI is sent as active video samples during vertcal blanking
*
* We use a BT.656 pxiel clock of 13.5 MHz and a BT.656 active line
* length of 720 pixels @ 4:2:2 sampling. Thus...
*
* For systems that use a 15.734 kHz horizontal rate, such as
* NTSC-M, PAL-M, PAL-60, and other 60 Hz/525 line systems, we have:
*
* (1/15.734 kHz) * 2 * 13.5 MHz = 1716 samples/line =
* 4 bytes SAV + 268 bytes anc data + 4 bytes SAV + 1440 active samples
*
* For systems that use a 15.625 kHz horizontal rate, such as
* PAL-B/G/H, PAL-I, SECAM-L and other 50 Hz/625 line systems, we have:
*
* (1/15.625 kHz) * 2 * 13.5 MHz = 1728 samples/line =
* 4 bytes SAV + 280 bytes anc data + 4 bytes SAV + 1440 active samples
*/
static const u32 vbi_active_samples = 1444; /* 4 byte SAV + 720 Y + 720 U/V */
static const u32 vbi_hblank_samples_60Hz = 272; /* 4 byte EAV + 268 anc/fill */
static const u32 vbi_hblank_samples_50Hz = 284; /* 4 byte EAV + 280 anc/fill */
#define CX18_VBI_FRAMES 32
struct vbi_info {
/* Current state of v4l2 VBI settings for this device */
struct v4l2_format in;
struct v4l2_sliced_vbi_format *sliced_in; /* pointer to in.fmt.sliced */
u32 count; /* Count of VBI data lines: 60 Hz: 12 or 50 Hz: 18 */
u32 start[2]; /* First VBI data line per field: 10 & 273 or 6 & 318 */
u32 frame; /* Count of VBI buffers/frames received from Encoder */
/*
* Vars for creation and insertion of MPEG Private Stream 1 packets
* of sliced VBI data into an MPEG PS
*/
/* Boolean: create and insert Private Stream 1 packets into the PS */
int insert_mpeg;
/*
* Buffer for the maximum of 2 * 18 * packet_size sliced VBI lines.
* Used in cx18-vbi.c only for collecting sliced data, and as a source
* during conversion of sliced VBI data into MPEG Priv Stream 1 packets.
* We don't need to save state here, but the array may have been a bit
* too big (2304 bytes) to alloc from the stack.
*/
struct v4l2_sliced_vbi_data sliced_data[36];
/*
* A ring buffer of driver-generated MPEG-2 PS
* Program Pack/Private Stream 1 packets for sliced VBI data insertion
* into the MPEG PS stream.
*
* In each sliced_mpeg_data[] buffer is:
* 16 byte MPEG-2 PS Program Pack Header
* 16 byte MPEG-2 Private Stream 1 PES Header
* 4 byte magic number: "itv0" or "ITV0"
* 4 byte first field line mask, if "itv0"
* 4 byte second field line mask, if "itv0"
* 36 lines, if "ITV0"; or <36 lines, if "itv0"; of sliced VBI data
*
* Each line in the payload is
* 1 byte line header derived from the SDID (WSS, CC, VPS, etc.)
* 42 bytes of line data
*
* That's a maximum 1552 bytes of payload in the Private Stream 1 packet
* which is the payload size a PVR-350 (CX23415) MPEG decoder will
* accept for VBI data. So, including the headers, it's a maximum 1584
* bytes total.
*/
#define CX18_SLICED_MPEG_DATA_MAXSZ 1584
/* copy_vbi_buf() needs 8 temp bytes on the end for the worst case */
#define CX18_SLICED_MPEG_DATA_BUFSZ (CX18_SLICED_MPEG_DATA_MAXSZ+8)
u8 *sliced_mpeg_data[CX18_VBI_FRAMES];
u32 sliced_mpeg_size[CX18_VBI_FRAMES];
/* Count of Program Pack/Program Stream 1 packets inserted into PS */
u32 inserted_frame;
/*
* A dummy driver stream transfer mdl & buffer with a copy of the next
* sliced_mpeg_data[] buffer for output to userland apps.
* Only used in cx18-fileops.c, but its state needs to persist at times.
*/
struct cx18_mdl sliced_mpeg_mdl;
struct cx18_buffer sliced_mpeg_buf;
};
/* Per cx23418, per I2C bus private algo callback data */
struct cx18_i2c_algo_callback_data {
struct cx18 *cx;
int bus_index; /* 0 or 1 for the cx23418's 1st or 2nd I2C bus */
};
#define CX18_MAX_MMIO_WR_RETRIES 10
/* Struct to hold info about cx18 cards */
struct cx18 {
int instance;
struct pci_dev *pci_dev;
struct v4l2_device v4l2_dev;
struct v4l2_subdev *sd_av; /* A/V decoder/digitizer sub-device */
struct v4l2_subdev *sd_extmux; /* External multiplexer sub-dev */
const struct cx18_card *card; /* card information */
const char *card_name; /* full name of the card */
const struct cx18_card_tuner_i2c *card_i2c; /* i2c addresses to probe for tuner */
u8 is_50hz;
u8 is_60hz;
u8 nof_inputs; /* number of video inputs */
u8 nof_audio_inputs; /* number of audio inputs */
u32 v4l2_cap; /* V4L2 capabilities of card */
u32 hw_flags; /* Hardware description of the board */
unsigned int free_mdl_idx;
struct cx18_scb __iomem *scb; /* pointer to SCB */
struct mutex epu2apu_mb_lock; /* protect driver to chip mailbox in SCB*/
struct mutex epu2cpu_mb_lock; /* protect driver to chip mailbox in SCB*/
struct cx18_av_state av_state;
/* codec settings */
struct cx2341x_handler cxhdl;
u32 filter_mode;
u32 temporal_strength;
u32 spatial_strength;
/* dualwatch */
unsigned long dualwatch_jiffies;
u32 dualwatch_stereo_mode;
struct mutex serialize_lock; /* mutex used to serialize open/close/start/stop/ioctl operations */
struct cx18_options options; /* User options */
int stream_buffers[CX18_MAX_STREAMS]; /* # of buffers for each stream */
int stream_buf_size[CX18_MAX_STREAMS]; /* Stream buffer size */
struct cx18_stream streams[CX18_MAX_STREAMS]; /* Stream data */
struct snd_cx18_card *alsa; /* ALSA interface for PCM capture stream */
void (*pcm_announce_callback)(struct snd_cx18_card *card, u8 *pcm_data,
size_t num_bytes);
unsigned long i_flags; /* global cx18 flags */
atomic_t ana_capturing; /* count number of active analog capture streams */
atomic_t tot_capturing; /* total count number of active capture streams */
int search_pack_header;
int open_id; /* incremented each time an open occurs, used as
unique ID. Starts at 1, so 0 can be used as
uninitialized value in the stream->id. */
resource_size_t base_addr;
u8 card_rev;
void __iomem *enc_mem, *reg_mem;
struct vbi_info vbi;
u64 mpg_data_received;
u64 vbi_data_inserted;
wait_queue_head_t mb_apu_waitq;
wait_queue_head_t mb_cpu_waitq;
wait_queue_head_t cap_w;
/* when the current DMA is finished this queue is woken up */
wait_queue_head_t dma_waitq;
u32 sw1_irq_mask;
u32 sw2_irq_mask;
u32 hw2_irq_mask;
struct workqueue_struct *in_work_queue;
char in_workq_name[11]; /* "cx18-NN-in" */
struct cx18_in_work_order in_work_order[CX18_MAX_IN_WORK_ORDERS];
char epu_debug_str[256]; /* CX18_EPU_DEBUG is rare: use shared space */
/* i2c */
struct i2c_adapter i2c_adap[2];
struct i2c_algo_bit_data i2c_algo[2];
struct cx18_i2c_algo_callback_data i2c_algo_cb_data[2];
struct IR_i2c_init_data ir_i2c_init_data;
/* gpio */
u32 gpio_dir;
u32 gpio_val;
struct mutex gpio_lock;
struct v4l2_subdev sd_gpiomux;
struct v4l2_subdev sd_resetctrl;
/* v4l2 and User settings */
/* codec settings */
u32 audio_input;
u32 active_input;
v4l2_std_id std;
v4l2_std_id tuner_std; /* The norm of the tuner (fixed) */
/* Used for cx18-alsa module loading */
struct work_struct request_module_wk;
};
static inline struct cx18 *to_cx18(struct v4l2_device *v4l2_dev)
{
return container_of(v4l2_dev, struct cx18, v4l2_dev);
}
/* cx18 extensions to be loaded */
extern int (*cx18_ext_init)(struct cx18 *);
/* Globals */
extern int cx18_first_minor;
/*==============Prototypes==================*/
/* Return non-zero if a signal is pending */
int cx18_msleep_timeout(unsigned int msecs, int intr);
/* Read Hauppauge eeprom */
struct tveeprom; /* forward reference */
void cx18_read_eeprom(struct cx18 *cx, struct tveeprom *tv);
/* First-open initialization: load firmware, etc. */
int cx18_init_on_first_open(struct cx18 *cx);
/* Test if the current VBI mode is raw (1) or sliced (0) */
static inline int cx18_raw_vbi(const struct cx18 *cx)
{
return cx->vbi.in.type == V4L2_BUF_TYPE_VBI_CAPTURE;
}
/* Call the specified callback for all subdevs with a grp_id bit matching the
* mask in hw (if 0, then match them all). Ignore any errors. */
#define cx18_call_hw(cx, hw, o, f, args...) \
do { \
struct v4l2_subdev *__sd; \
__v4l2_device_call_subdevs_p(&(cx)->v4l2_dev, __sd, \
!(hw) || (__sd->grp_id & (hw)), o, f , ##args); \
} while (0)
#define cx18_call_all(cx, o, f, args...) cx18_call_hw(cx, 0, o, f , ##args)
/* Call the specified callback for all subdevs with a grp_id bit matching the
* mask in hw (if 0, then match them all). If the callback returns an error
* other than 0 or -ENOIOCTLCMD, then return with that error code. */
#define cx18_call_hw_err(cx, hw, o, f, args...) \
({ \
struct v4l2_subdev *__sd; \
__v4l2_device_call_subdevs_until_err_p(&(cx)->v4l2_dev, \
__sd, !(hw) || (__sd->grp_id & (hw)), o, f, \
##args); \
})
#define cx18_call_all_err(cx, o, f, args...) \
cx18_call_hw_err(cx, 0, o, f , ##args)
#endif /* CX18_DRIVER_H */

View file

@ -0,0 +1,609 @@
/*
* cx18 functions for DVB support
*
* Copyright (c) 2008 Steven Toth <stoth@linuxtv.org>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
*
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "cx18-version.h"
#include "cx18-dvb.h"
#include "cx18-io.h"
#include "cx18-queue.h"
#include "cx18-streams.h"
#include "cx18-cards.h"
#include "cx18-gpio.h"
#include "s5h1409.h"
#include "mxl5005s.h"
#include "s5h1411.h"
#include "tda18271.h"
#include "zl10353.h"
#include <linux/firmware.h>
#include "mt352.h"
#include "mt352_priv.h"
#include "tuner-xc2028.h"
DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
#define FWFILE "dvb-cx18-mpc718-mt352.fw"
#define CX18_REG_DMUX_NUM_PORT_0_CONTROL 0xd5a000
#define CX18_CLOCK_ENABLE2 0xc71024
#define CX18_DMUX_CLK_MASK 0x0080
/*
* CX18_CARD_HVR_1600_ESMT
* CX18_CARD_HVR_1600_SAMSUNG
*/
static struct mxl5005s_config hauppauge_hvr1600_tuner = {
.i2c_address = 0xC6 >> 1,
.if_freq = IF_FREQ_5380000HZ,
.xtal_freq = CRYSTAL_FREQ_16000000HZ,
.agc_mode = MXL_SINGLE_AGC,
.tracking_filter = MXL_TF_C_H,
.rssi_enable = MXL_RSSI_ENABLE,
.cap_select = MXL_CAP_SEL_ENABLE,
.div_out = MXL_DIV_OUT_4,
.clock_out = MXL_CLOCK_OUT_DISABLE,
.output_load = MXL5005S_IF_OUTPUT_LOAD_200_OHM,
.top = MXL5005S_TOP_25P2,
.mod_mode = MXL_DIGITAL_MODE,
.if_mode = MXL_ZERO_IF,
.qam_gain = 0x02,
.AgcMasterByte = 0x00,
};
static struct s5h1409_config hauppauge_hvr1600_config = {
.demod_address = 0x32 >> 1,
.output_mode = S5H1409_SERIAL_OUTPUT,
.gpio = S5H1409_GPIO_ON,
.qam_if = 44000,
.inversion = S5H1409_INVERSION_OFF,
.status_mode = S5H1409_DEMODLOCKING,
.mpeg_timing = S5H1409_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK,
.hvr1600_opt = S5H1409_HVR1600_OPTIMIZE
};
/*
* CX18_CARD_HVR_1600_S5H1411
*/
static struct s5h1411_config hcw_s5h1411_config = {
.output_mode = S5H1411_SERIAL_OUTPUT,
.gpio = S5H1411_GPIO_OFF,
.vsb_if = S5H1411_IF_44000,
.qam_if = S5H1411_IF_4000,
.inversion = S5H1411_INVERSION_ON,
.status_mode = S5H1411_DEMODLOCKING,
.mpeg_timing = S5H1411_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK,
};
static struct tda18271_std_map hauppauge_tda18271_std_map = {
.atsc_6 = { .if_freq = 5380, .agc_mode = 3, .std = 3,
.if_lvl = 6, .rfagc_top = 0x37 },
.qam_6 = { .if_freq = 4000, .agc_mode = 3, .std = 0,
.if_lvl = 6, .rfagc_top = 0x37 },
};
static struct tda18271_config hauppauge_tda18271_config = {
.std_map = &hauppauge_tda18271_std_map,
.gate = TDA18271_GATE_DIGITAL,
.output_opt = TDA18271_OUTPUT_LT_OFF,
};
/*
* CX18_CARD_LEADTEK_DVR3100H
*/
/* Information/confirmation of proper config values provided by Terry Wu */
static struct zl10353_config leadtek_dvr3100h_demod = {
.demod_address = 0x1e >> 1, /* Datasheet suggested straps */
.if2 = 45600, /* 4.560 MHz IF from the XC3028 */
.parallel_ts = 1, /* Not a serial TS */
.no_tuner = 1, /* XC3028 is not behind the gate */
.disable_i2c_gate_ctrl = 1, /* Disable the I2C gate */
};
/*
* CX18_CARD_YUAN_MPC718
*/
/*
* Due to
*
* 1. an absence of information on how to prgram the MT352
* 2. the Linux mt352 module pushing MT352 initialzation off onto us here
*
* We have to use an init sequence that *you* must extract from the Windows
* driver (yuanrap.sys) and which we load as a firmware.
*
* If someone can provide me with a Zarlink MT352 (Intel CE6352?) Design Manual
* with chip programming details, then I can remove this annoyance.
*/
static int yuan_mpc718_mt352_reqfw(struct cx18_stream *stream,
const struct firmware **fw)
{
struct cx18 *cx = stream->cx;
const char *fn = FWFILE;
int ret;
ret = request_firmware(fw, fn, &cx->pci_dev->dev);
if (ret)
CX18_ERR("Unable to open firmware file %s\n", fn);
else {
size_t sz = (*fw)->size;
if (sz < 2 || sz > 64 || (sz % 2) != 0) {
CX18_ERR("Firmware %s has a bad size: %lu bytes\n",
fn, (unsigned long) sz);
ret = -EILSEQ;
release_firmware(*fw);
*fw = NULL;
}
}
if (ret) {
CX18_ERR("The MPC718 board variant with the MT352 DVB-T"
"demodualtor will not work without it\n");
CX18_ERR("Run 'linux/Documentation/dvb/get_dvb_firmware "
"mpc718' if you need the firmware\n");
}
return ret;
}
static int yuan_mpc718_mt352_init(struct dvb_frontend *fe)
{
struct cx18_dvb *dvb = container_of(fe->dvb,
struct cx18_dvb, dvb_adapter);
struct cx18_stream *stream = dvb->stream;
const struct firmware *fw = NULL;
int ret;
int i;
u8 buf[3];
ret = yuan_mpc718_mt352_reqfw(stream, &fw);
if (ret)
return ret;
/* Loop through all the register-value pairs in the firmware file */
for (i = 0; i < fw->size; i += 2) {
buf[0] = fw->data[i];
/* Intercept a few registers we want to set ourselves */
switch (buf[0]) {
case TRL_NOMINAL_RATE_0:
/* Set our custom OFDM bandwidth in the case below */
break;
case TRL_NOMINAL_RATE_1:
/* 6 MHz: 64/7 * 6/8 / 20.48 * 2^16 = 0x55b6.db6 */
/* 7 MHz: 64/7 * 7/8 / 20.48 * 2^16 = 0x6400 */
/* 8 MHz: 64/7 * 8/8 / 20.48 * 2^16 = 0x7249.249 */
buf[1] = 0x72;
buf[2] = 0x49;
mt352_write(fe, buf, 3);
break;
case INPUT_FREQ_0:
/* Set our custom IF in the case below */
break;
case INPUT_FREQ_1:
/* 4.56 MHz IF: (20.48 - 4.56)/20.48 * 2^14 = 0x31c0 */
buf[1] = 0x31;
buf[2] = 0xc0;
mt352_write(fe, buf, 3);
break;
default:
/* Pass through the register-value pair from the fw */
buf[1] = fw->data[i+1];
mt352_write(fe, buf, 2);
break;
}
}
buf[0] = (u8) TUNER_GO;
buf[1] = 0x01; /* Go */
mt352_write(fe, buf, 2);
release_firmware(fw);
return 0;
}
static struct mt352_config yuan_mpc718_mt352_demod = {
.demod_address = 0x1e >> 1,
.adc_clock = 20480, /* 20.480 MHz */
.if2 = 4560, /* 4.560 MHz */
.no_tuner = 1, /* XC3028 is not behind the gate */
.demod_init = yuan_mpc718_mt352_init,
};
static struct zl10353_config yuan_mpc718_zl10353_demod = {
.demod_address = 0x1e >> 1, /* Datasheet suggested straps */
.if2 = 45600, /* 4.560 MHz IF from the XC3028 */
.parallel_ts = 1, /* Not a serial TS */
.no_tuner = 1, /* XC3028 is not behind the gate */
.disable_i2c_gate_ctrl = 1, /* Disable the I2C gate */
};
static struct zl10353_config gotview_dvd3_zl10353_demod = {
.demod_address = 0x1e >> 1, /* Datasheet suggested straps */
.if2 = 45600, /* 4.560 MHz IF from the XC3028 */
.parallel_ts = 1, /* Not a serial TS */
.no_tuner = 1, /* XC3028 is not behind the gate */
.disable_i2c_gate_ctrl = 1, /* Disable the I2C gate */
};
static int dvb_register(struct cx18_stream *stream);
/* Kernel DVB framework calls this when the feed needs to start.
* The CX18 framework should enable the transport DMA handling
* and queue processing.
*/
static int cx18_dvb_start_feed(struct dvb_demux_feed *feed)
{
struct dvb_demux *demux = feed->demux;
struct cx18_stream *stream = (struct cx18_stream *) demux->priv;
struct cx18 *cx;
int ret;
u32 v;
if (!stream)
return -EINVAL;
cx = stream->cx;
CX18_DEBUG_INFO("Start feed: pid = 0x%x index = %d\n",
feed->pid, feed->index);
mutex_lock(&cx->serialize_lock);
ret = cx18_init_on_first_open(cx);
mutex_unlock(&cx->serialize_lock);
if (ret) {
CX18_ERR("Failed to initialize firmware starting DVB feed\n");
return ret;
}
ret = -EINVAL;
switch (cx->card->type) {
case CX18_CARD_HVR_1600_ESMT:
case CX18_CARD_HVR_1600_SAMSUNG:
case CX18_CARD_HVR_1600_S5H1411:
v = cx18_read_reg(cx, CX18_REG_DMUX_NUM_PORT_0_CONTROL);
v |= 0x00400000; /* Serial Mode */
v |= 0x00002000; /* Data Length - Byte */
v |= 0x00010000; /* Error - Polarity */
v |= 0x00020000; /* Error - Passthru */
v |= 0x000c0000; /* Error - Ignore */
cx18_write_reg(cx, v, CX18_REG_DMUX_NUM_PORT_0_CONTROL);
break;
case CX18_CARD_LEADTEK_DVR3100H:
case CX18_CARD_YUAN_MPC718:
case CX18_CARD_GOTVIEW_PCI_DVD3:
default:
/* Assumption - Parallel transport - Signalling
* undefined or default.
*/
break;
}
if (!demux->dmx.frontend)
return -EINVAL;
mutex_lock(&stream->dvb->feedlock);
if (stream->dvb->feeding++ == 0) {
CX18_DEBUG_INFO("Starting Transport DMA\n");
mutex_lock(&cx->serialize_lock);
set_bit(CX18_F_S_STREAMING, &stream->s_flags);
ret = cx18_start_v4l2_encode_stream(stream);
if (ret < 0) {
CX18_DEBUG_INFO("Failed to start Transport DMA\n");
stream->dvb->feeding--;
if (stream->dvb->feeding == 0)
clear_bit(CX18_F_S_STREAMING, &stream->s_flags);
}
mutex_unlock(&cx->serialize_lock);
} else
ret = 0;
mutex_unlock(&stream->dvb->feedlock);
return ret;
}
/* Kernel DVB framework calls this when the feed needs to stop. */
static int cx18_dvb_stop_feed(struct dvb_demux_feed *feed)
{
struct dvb_demux *demux = feed->demux;
struct cx18_stream *stream = (struct cx18_stream *)demux->priv;
struct cx18 *cx;
int ret = -EINVAL;
if (stream) {
cx = stream->cx;
CX18_DEBUG_INFO("Stop feed: pid = 0x%x index = %d\n",
feed->pid, feed->index);
mutex_lock(&stream->dvb->feedlock);
if (--stream->dvb->feeding == 0) {
CX18_DEBUG_INFO("Stopping Transport DMA\n");
mutex_lock(&cx->serialize_lock);
ret = cx18_stop_v4l2_encode_stream(stream, 0);
mutex_unlock(&cx->serialize_lock);
} else
ret = 0;
mutex_unlock(&stream->dvb->feedlock);
}
return ret;
}
int cx18_dvb_register(struct cx18_stream *stream)
{
struct cx18 *cx = stream->cx;
struct cx18_dvb *dvb = stream->dvb;
struct dvb_adapter *dvb_adapter;
struct dvb_demux *dvbdemux;
struct dmx_demux *dmx;
int ret;
if (!dvb)
return -EINVAL;
dvb->enabled = 0;
dvb->stream = stream;
ret = dvb_register_adapter(&dvb->dvb_adapter,
CX18_DRIVER_NAME,
THIS_MODULE, &cx->pci_dev->dev, adapter_nr);
if (ret < 0)
goto err_out;
dvb_adapter = &dvb->dvb_adapter;
dvbdemux = &dvb->demux;
dvbdemux->priv = (void *)stream;
dvbdemux->filternum = 256;
dvbdemux->feednum = 256;
dvbdemux->start_feed = cx18_dvb_start_feed;
dvbdemux->stop_feed = cx18_dvb_stop_feed;
dvbdemux->dmx.capabilities = (DMX_TS_FILTERING |
DMX_SECTION_FILTERING | DMX_MEMORY_BASED_FILTERING);
ret = dvb_dmx_init(dvbdemux);
if (ret < 0)
goto err_dvb_unregister_adapter;
dmx = &dvbdemux->dmx;
dvb->hw_frontend.source = DMX_FRONTEND_0;
dvb->mem_frontend.source = DMX_MEMORY_FE;
dvb->dmxdev.filternum = 256;
dvb->dmxdev.demux = dmx;
ret = dvb_dmxdev_init(&dvb->dmxdev, dvb_adapter);
if (ret < 0)
goto err_dvb_dmx_release;
ret = dmx->add_frontend(dmx, &dvb->hw_frontend);
if (ret < 0)
goto err_dvb_dmxdev_release;
ret = dmx->add_frontend(dmx, &dvb->mem_frontend);
if (ret < 0)
goto err_remove_hw_frontend;
ret = dmx->connect_frontend(dmx, &dvb->hw_frontend);
if (ret < 0)
goto err_remove_mem_frontend;
ret = dvb_register(stream);
if (ret < 0)
goto err_disconnect_frontend;
dvb_net_init(dvb_adapter, &dvb->dvbnet, dmx);
CX18_INFO("DVB Frontend registered\n");
CX18_INFO("Registered DVB adapter%d for %s (%d x %d.%02d kB)\n",
stream->dvb->dvb_adapter.num, stream->name,
stream->buffers, stream->buf_size/1024,
(stream->buf_size * 100 / 1024) % 100);
mutex_init(&dvb->feedlock);
dvb->enabled = 1;
return ret;
err_disconnect_frontend:
dmx->disconnect_frontend(dmx);
err_remove_mem_frontend:
dmx->remove_frontend(dmx, &dvb->mem_frontend);
err_remove_hw_frontend:
dmx->remove_frontend(dmx, &dvb->hw_frontend);
err_dvb_dmxdev_release:
dvb_dmxdev_release(&dvb->dmxdev);
err_dvb_dmx_release:
dvb_dmx_release(dvbdemux);
err_dvb_unregister_adapter:
dvb_unregister_adapter(dvb_adapter);
err_out:
return ret;
}
void cx18_dvb_unregister(struct cx18_stream *stream)
{
struct cx18 *cx = stream->cx;
struct cx18_dvb *dvb = stream->dvb;
struct dvb_adapter *dvb_adapter;
struct dvb_demux *dvbdemux;
struct dmx_demux *dmx;
CX18_INFO("unregister DVB\n");
if (dvb == NULL || !dvb->enabled)
return;
dvb_adapter = &dvb->dvb_adapter;
dvbdemux = &dvb->demux;
dmx = &dvbdemux->dmx;
dmx->close(dmx);
dvb_net_release(&dvb->dvbnet);
dmx->remove_frontend(dmx, &dvb->mem_frontend);
dmx->remove_frontend(dmx, &dvb->hw_frontend);
dvb_dmxdev_release(&dvb->dmxdev);
dvb_dmx_release(dvbdemux);
dvb_unregister_frontend(dvb->fe);
dvb_frontend_detach(dvb->fe);
dvb_unregister_adapter(dvb_adapter);
}
/* All the DVB attach calls go here, this function get's modified
* for each new card. cx18_dvb_start_feed() will also need changes.
*/
static int dvb_register(struct cx18_stream *stream)
{
struct cx18_dvb *dvb = stream->dvb;
struct cx18 *cx = stream->cx;
int ret = 0;
switch (cx->card->type) {
case CX18_CARD_HVR_1600_ESMT:
case CX18_CARD_HVR_1600_SAMSUNG:
dvb->fe = dvb_attach(s5h1409_attach,
&hauppauge_hvr1600_config,
&cx->i2c_adap[0]);
if (dvb->fe != NULL) {
dvb_attach(mxl5005s_attach, dvb->fe,
&cx->i2c_adap[0],
&hauppauge_hvr1600_tuner);
ret = 0;
}
break;
case CX18_CARD_HVR_1600_S5H1411:
dvb->fe = dvb_attach(s5h1411_attach,
&hcw_s5h1411_config,
&cx->i2c_adap[0]);
if (dvb->fe != NULL)
dvb_attach(tda18271_attach, dvb->fe,
0x60, &cx->i2c_adap[0],
&hauppauge_tda18271_config);
break;
case CX18_CARD_LEADTEK_DVR3100H:
dvb->fe = dvb_attach(zl10353_attach,
&leadtek_dvr3100h_demod,
&cx->i2c_adap[1]);
if (dvb->fe != NULL) {
struct dvb_frontend *fe;
struct xc2028_config cfg = {
.i2c_adap = &cx->i2c_adap[1],
.i2c_addr = 0xc2 >> 1,
.ctrl = NULL,
};
static struct xc2028_ctrl ctrl = {
.fname = XC2028_DEFAULT_FIRMWARE,
.max_len = 64,
.demod = XC3028_FE_ZARLINK456,
.type = XC2028_AUTO,
};
fe = dvb_attach(xc2028_attach, dvb->fe, &cfg);
if (fe != NULL && fe->ops.tuner_ops.set_config != NULL)
fe->ops.tuner_ops.set_config(fe, &ctrl);
}
break;
case CX18_CARD_YUAN_MPC718:
/*
* TODO
* Apparently, these cards also could instead have a
* DiBcom demod supported by one of the db7000 drivers
*/
dvb->fe = dvb_attach(mt352_attach,
&yuan_mpc718_mt352_demod,
&cx->i2c_adap[1]);
if (dvb->fe == NULL)
dvb->fe = dvb_attach(zl10353_attach,
&yuan_mpc718_zl10353_demod,
&cx->i2c_adap[1]);
if (dvb->fe != NULL) {
struct dvb_frontend *fe;
struct xc2028_config cfg = {
.i2c_adap = &cx->i2c_adap[1],
.i2c_addr = 0xc2 >> 1,
.ctrl = NULL,
};
static struct xc2028_ctrl ctrl = {
.fname = XC2028_DEFAULT_FIRMWARE,
.max_len = 64,
.demod = XC3028_FE_ZARLINK456,
.type = XC2028_AUTO,
};
fe = dvb_attach(xc2028_attach, dvb->fe, &cfg);
if (fe != NULL && fe->ops.tuner_ops.set_config != NULL)
fe->ops.tuner_ops.set_config(fe, &ctrl);
}
break;
case CX18_CARD_GOTVIEW_PCI_DVD3:
dvb->fe = dvb_attach(zl10353_attach,
&gotview_dvd3_zl10353_demod,
&cx->i2c_adap[1]);
if (dvb->fe != NULL) {
struct dvb_frontend *fe;
struct xc2028_config cfg = {
.i2c_adap = &cx->i2c_adap[1],
.i2c_addr = 0xc2 >> 1,
.ctrl = NULL,
};
static struct xc2028_ctrl ctrl = {
.fname = XC2028_DEFAULT_FIRMWARE,
.max_len = 64,
.demod = XC3028_FE_ZARLINK456,
.type = XC2028_AUTO,
};
fe = dvb_attach(xc2028_attach, dvb->fe, &cfg);
if (fe != NULL && fe->ops.tuner_ops.set_config != NULL)
fe->ops.tuner_ops.set_config(fe, &ctrl);
}
break;
default:
/* No Digital Tv Support */
break;
}
if (dvb->fe == NULL) {
CX18_ERR("frontend initialization failed\n");
return -1;
}
dvb->fe->callback = cx18_reset_tuner_gpio;
ret = dvb_register_frontend(&dvb->dvb_adapter, dvb->fe);
if (ret < 0) {
if (dvb->fe->ops.release)
dvb->fe->ops.release(dvb->fe);
return ret;
}
/*
* The firmware seems to enable the TS DMUX clock
* under various circumstances. However, since we know we
* might use it, let's just turn it on ourselves here.
*/
cx18_write_reg_expect(cx,
(CX18_DMUX_CLK_MASK << 16) | CX18_DMUX_CLK_MASK,
CX18_CLOCK_ENABLE2,
CX18_DMUX_CLK_MASK,
(CX18_DMUX_CLK_MASK << 16) | CX18_DMUX_CLK_MASK);
return ret;
}
MODULE_FIRMWARE(FWFILE);

View file

@ -0,0 +1,25 @@
/*
* cx18 functions for DVB support
*
* Copyright (c) 2008 Steven Toth <stoth@linuxtv.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
*
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "cx18-driver.h"
int cx18_dvb_register(struct cx18_stream *stream);
void cx18_dvb_unregister(struct cx18_stream *stream);

View file

@ -0,0 +1,881 @@
/*
* cx18 file operation functions
*
* Derived from ivtv-fileops.c
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* 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 "cx18-driver.h"
#include "cx18-fileops.h"
#include "cx18-i2c.h"
#include "cx18-queue.h"
#include "cx18-vbi.h"
#include "cx18-audio.h"
#include "cx18-mailbox.h"
#include "cx18-scb.h"
#include "cx18-streams.h"
#include "cx18-controls.h"
#include "cx18-ioctl.h"
#include "cx18-cards.h"
/* This function tries to claim the stream for a specific file descriptor.
If no one else is using this stream then the stream is claimed and
associated VBI and IDX streams are also automatically claimed.
Possible error returns: -EBUSY if someone else has claimed
the stream or 0 on success. */
int cx18_claim_stream(struct cx18_open_id *id, int type)
{
struct cx18 *cx = id->cx;
struct cx18_stream *s = &cx->streams[type];
struct cx18_stream *s_assoc;
/* Nothing should ever try to directly claim the IDX stream */
if (type == CX18_ENC_STREAM_TYPE_IDX) {
CX18_WARN("MPEG Index stream cannot be claimed "
"directly, but something tried.\n");
return -EINVAL;
}
if (test_and_set_bit(CX18_F_S_CLAIMED, &s->s_flags)) {
/* someone already claimed this stream */
if (s->id == id->open_id) {
/* yes, this file descriptor did. So that's OK. */
return 0;
}
if (s->id == -1 && type == CX18_ENC_STREAM_TYPE_VBI) {
/* VBI is handled already internally, now also assign
the file descriptor to this stream for external
reading of the stream. */
s->id = id->open_id;
CX18_DEBUG_INFO("Start Read VBI\n");
return 0;
}
/* someone else is using this stream already */
CX18_DEBUG_INFO("Stream %d is busy\n", type);
return -EBUSY;
}
s->id = id->open_id;
/*
* CX18_ENC_STREAM_TYPE_MPG needs to claim:
* CX18_ENC_STREAM_TYPE_VBI, if VBI insertion is on for sliced VBI, or
* CX18_ENC_STREAM_TYPE_IDX, if VBI insertion is off for sliced VBI
* (We don't yet fix up MPEG Index entries for our inserted packets).
*
* For all other streams we're done.
*/
if (type != CX18_ENC_STREAM_TYPE_MPG)
return 0;
s_assoc = &cx->streams[CX18_ENC_STREAM_TYPE_IDX];
if (cx->vbi.insert_mpeg && !cx18_raw_vbi(cx))
s_assoc = &cx->streams[CX18_ENC_STREAM_TYPE_VBI];
else if (!cx18_stream_enabled(s_assoc))
return 0;
set_bit(CX18_F_S_CLAIMED, &s_assoc->s_flags);
/* mark that it is used internally */
set_bit(CX18_F_S_INTERNAL_USE, &s_assoc->s_flags);
return 0;
}
EXPORT_SYMBOL(cx18_claim_stream);
/* This function releases a previously claimed stream. It will take into
account associated VBI streams. */
void cx18_release_stream(struct cx18_stream *s)
{
struct cx18 *cx = s->cx;
struct cx18_stream *s_assoc;
s->id = -1;
if (s->type == CX18_ENC_STREAM_TYPE_IDX) {
/*
* The IDX stream is only used internally, and can
* only be indirectly unclaimed by unclaiming the MPG stream.
*/
return;
}
if (s->type == CX18_ENC_STREAM_TYPE_VBI &&
test_bit(CX18_F_S_INTERNAL_USE, &s->s_flags)) {
/* this stream is still in use internally */
return;
}
if (!test_and_clear_bit(CX18_F_S_CLAIMED, &s->s_flags)) {
CX18_DEBUG_WARN("Release stream %s not in use!\n", s->name);
return;
}
cx18_flush_queues(s);
/*
* CX18_ENC_STREAM_TYPE_MPG needs to release the
* CX18_ENC_STREAM_TYPE_VBI and/or CX18_ENC_STREAM_TYPE_IDX streams.
*
* For all other streams we're done.
*/
if (s->type != CX18_ENC_STREAM_TYPE_MPG)
return;
/* Unclaim the associated MPEG Index stream */
s_assoc = &cx->streams[CX18_ENC_STREAM_TYPE_IDX];
if (test_and_clear_bit(CX18_F_S_INTERNAL_USE, &s_assoc->s_flags)) {
clear_bit(CX18_F_S_CLAIMED, &s_assoc->s_flags);
cx18_flush_queues(s_assoc);
}
/* Unclaim the associated VBI stream */
s_assoc = &cx->streams[CX18_ENC_STREAM_TYPE_VBI];
if (test_and_clear_bit(CX18_F_S_INTERNAL_USE, &s_assoc->s_flags)) {
if (s_assoc->id == -1) {
/*
* The VBI stream is not still claimed by a file
* descriptor, so completely unclaim it.
*/
clear_bit(CX18_F_S_CLAIMED, &s_assoc->s_flags);
cx18_flush_queues(s_assoc);
}
}
}
EXPORT_SYMBOL(cx18_release_stream);
static void cx18_dualwatch(struct cx18 *cx)
{
struct v4l2_tuner vt;
u32 new_stereo_mode;
const u32 dual = 0x0200;
new_stereo_mode = v4l2_ctrl_g_ctrl(cx->cxhdl.audio_mode);
memset(&vt, 0, sizeof(vt));
cx18_call_all(cx, tuner, g_tuner, &vt);
if (vt.audmode == V4L2_TUNER_MODE_LANG1_LANG2 &&
(vt.rxsubchans & V4L2_TUNER_SUB_LANG2))
new_stereo_mode = dual;
if (new_stereo_mode == cx->dualwatch_stereo_mode)
return;
CX18_DEBUG_INFO("dualwatch: change stereo flag from 0x%x to 0x%x.\n",
cx->dualwatch_stereo_mode, new_stereo_mode);
if (v4l2_ctrl_s_ctrl(cx->cxhdl.audio_mode, new_stereo_mode))
CX18_DEBUG_INFO("dualwatch: changing stereo flag failed\n");
}
static struct cx18_mdl *cx18_get_mdl(struct cx18_stream *s, int non_block,
int *err)
{
struct cx18 *cx = s->cx;
struct cx18_stream *s_vbi = &cx->streams[CX18_ENC_STREAM_TYPE_VBI];
struct cx18_mdl *mdl;
DEFINE_WAIT(wait);
*err = 0;
while (1) {
if (s->type == CX18_ENC_STREAM_TYPE_MPG) {
/* Process pending program updates and VBI data */
if (time_after(jiffies, cx->dualwatch_jiffies + msecs_to_jiffies(1000))) {
cx->dualwatch_jiffies = jiffies;
cx18_dualwatch(cx);
}
if (test_bit(CX18_F_S_INTERNAL_USE, &s_vbi->s_flags) &&
!test_bit(CX18_F_S_APPL_IO, &s_vbi->s_flags)) {
while ((mdl = cx18_dequeue(s_vbi,
&s_vbi->q_full))) {
/* byteswap and process VBI data */
cx18_process_vbi_data(cx, mdl,
s_vbi->type);
cx18_stream_put_mdl_fw(s_vbi, mdl);
}
}
mdl = &cx->vbi.sliced_mpeg_mdl;
if (mdl->readpos != mdl->bytesused)
return mdl;
}
/* do we have new data? */
mdl = cx18_dequeue(s, &s->q_full);
if (mdl) {
if (!test_and_clear_bit(CX18_F_M_NEED_SWAP,
&mdl->m_flags))
return mdl;
if (s->type == CX18_ENC_STREAM_TYPE_MPG)
/* byteswap MPG data */
cx18_mdl_swap(mdl);
else {
/* byteswap and process VBI data */
cx18_process_vbi_data(cx, mdl, s->type);
}
return mdl;
}
/* return if end of stream */
if (!test_bit(CX18_F_S_STREAMING, &s->s_flags)) {
CX18_DEBUG_INFO("EOS %s\n", s->name);
return NULL;
}
/* return if file was opened with O_NONBLOCK */
if (non_block) {
*err = -EAGAIN;
return NULL;
}
/* wait for more data to arrive */
prepare_to_wait(&s->waitq, &wait, TASK_INTERRUPTIBLE);
/* New buffers might have become available before we were added
to the waitqueue */
if (!atomic_read(&s->q_full.depth))
schedule();
finish_wait(&s->waitq, &wait);
if (signal_pending(current)) {
/* return if a signal was received */
CX18_DEBUG_INFO("User stopped %s\n", s->name);
*err = -EINTR;
return NULL;
}
}
}
static void cx18_setup_sliced_vbi_mdl(struct cx18 *cx)
{
struct cx18_mdl *mdl = &cx->vbi.sliced_mpeg_mdl;
struct cx18_buffer *buf = &cx->vbi.sliced_mpeg_buf;
int idx = cx->vbi.inserted_frame % CX18_VBI_FRAMES;
buf->buf = cx->vbi.sliced_mpeg_data[idx];
buf->bytesused = cx->vbi.sliced_mpeg_size[idx];
buf->readpos = 0;
mdl->curr_buf = NULL;
mdl->bytesused = cx->vbi.sliced_mpeg_size[idx];
mdl->readpos = 0;
}
static size_t cx18_copy_buf_to_user(struct cx18_stream *s,
struct cx18_buffer *buf, char __user *ubuf, size_t ucount, bool *stop)
{
struct cx18 *cx = s->cx;
size_t len = buf->bytesused - buf->readpos;
*stop = false;
if (len > ucount)
len = ucount;
if (cx->vbi.insert_mpeg && s->type == CX18_ENC_STREAM_TYPE_MPG &&
!cx18_raw_vbi(cx) && buf != &cx->vbi.sliced_mpeg_buf) {
/*
* Try to find a good splice point in the PS, just before
* an MPEG-2 Program Pack start code, and provide only
* up to that point to the user, so it's easy to insert VBI data
* the next time around.
*
* This will not work for an MPEG-2 TS and has only been
* verified by analysis to work for an MPEG-2 PS. Helen Buus
* pointed out this works for the CX23416 MPEG-2 DVD compatible
* stream, and research indicates both the MPEG 2 SVCD and DVD
* stream types use an MPEG-2 PS container.
*/
/*
* An MPEG-2 Program Stream (PS) is a series of
* MPEG-2 Program Packs terminated by an
* MPEG Program End Code after the last Program Pack.
* A Program Pack may hold a PS System Header packet and any
* number of Program Elementary Stream (PES) Packets
*/
const char *start = buf->buf + buf->readpos;
const char *p = start + 1;
const u8 *q;
u8 ch = cx->search_pack_header ? 0xba : 0xe0;
int stuffing, i;
while (start + len > p) {
/* Scan for a 0 to find a potential MPEG-2 start code */
q = memchr(p, 0, start + len - p);
if (q == NULL)
break;
p = q + 1;
/*
* Keep looking if not a
* MPEG-2 Pack header start code: 0x00 0x00 0x01 0xba
* or MPEG-2 video PES start code: 0x00 0x00 0x01 0xe0
*/
if ((char *)q + 15 >= buf->buf + buf->bytesused ||
q[1] != 0 || q[2] != 1 || q[3] != ch)
continue;
/* If expecting the primary video PES */
if (!cx->search_pack_header) {
/* Continue if it couldn't be a PES packet */
if ((q[6] & 0xc0) != 0x80)
continue;
/* Check if a PTS or PTS & DTS follow */
if (((q[7] & 0xc0) == 0x80 && /* PTS only */
(q[9] & 0xf0) == 0x20) || /* PTS only */
((q[7] & 0xc0) == 0xc0 && /* PTS & DTS */
(q[9] & 0xf0) == 0x30)) { /* DTS follows */
/* Assume we found the video PES hdr */
ch = 0xba; /* next want a Program Pack*/
cx->search_pack_header = 1;
p = q + 9; /* Skip this video PES hdr */
}
continue;
}
/* We may have found a Program Pack start code */
/* Get the count of stuffing bytes & verify them */
stuffing = q[13] & 7;
/* all stuffing bytes must be 0xff */
for (i = 0; i < stuffing; i++)
if (q[14 + i] != 0xff)
break;
if (i == stuffing && /* right number of stuffing bytes*/
(q[4] & 0xc4) == 0x44 && /* marker check */
(q[12] & 3) == 3 && /* marker check */
q[14 + stuffing] == 0 && /* PES Pack or Sys Hdr */
q[15 + stuffing] == 0 &&
q[16 + stuffing] == 1) {
/* We declare we actually found a Program Pack*/
cx->search_pack_header = 0; /* expect vid PES */
len = (char *)q - start;
cx18_setup_sliced_vbi_mdl(cx);
*stop = true;
break;
}
}
}
if (copy_to_user(ubuf, (u8 *)buf->buf + buf->readpos, len)) {
CX18_DEBUG_WARN("copy %zd bytes to user failed for %s\n",
len, s->name);
return -EFAULT;
}
buf->readpos += len;
if (s->type == CX18_ENC_STREAM_TYPE_MPG &&
buf != &cx->vbi.sliced_mpeg_buf)
cx->mpg_data_received += len;
return len;
}
static size_t cx18_copy_mdl_to_user(struct cx18_stream *s,
struct cx18_mdl *mdl, char __user *ubuf, size_t ucount)
{
size_t tot_written = 0;
int rc;
bool stop = false;
if (mdl->curr_buf == NULL)
mdl->curr_buf = list_first_entry(&mdl->buf_list,
struct cx18_buffer, list);
if (list_entry_is_past_end(mdl->curr_buf, &mdl->buf_list, list)) {
/*
* For some reason we've exhausted the buffers, but the MDL
* object still said some data was unread.
* Fix that and bail out.
*/
mdl->readpos = mdl->bytesused;
return 0;
}
list_for_each_entry_from(mdl->curr_buf, &mdl->buf_list, list) {
if (mdl->curr_buf->readpos >= mdl->curr_buf->bytesused)
continue;
rc = cx18_copy_buf_to_user(s, mdl->curr_buf, ubuf + tot_written,
ucount - tot_written, &stop);
if (rc < 0)
return rc;
mdl->readpos += rc;
tot_written += rc;
if (stop || /* Forced stopping point for VBI insertion */
tot_written >= ucount || /* Reader request statisfied */
mdl->curr_buf->readpos < mdl->curr_buf->bytesused ||
mdl->readpos >= mdl->bytesused) /* MDL buffers drained */
break;
}
return tot_written;
}
static ssize_t cx18_read(struct cx18_stream *s, char __user *ubuf,
size_t tot_count, int non_block)
{
struct cx18 *cx = s->cx;
size_t tot_written = 0;
int single_frame = 0;
if (atomic_read(&cx->ana_capturing) == 0 && s->id == -1) {
/* shouldn't happen */
CX18_DEBUG_WARN("Stream %s not initialized before read\n",
s->name);
return -EIO;
}
/* Each VBI buffer is one frame, the v4l2 API says that for VBI the
frames should arrive one-by-one, so make sure we never output more
than one VBI frame at a time */
if (s->type == CX18_ENC_STREAM_TYPE_VBI && !cx18_raw_vbi(cx))
single_frame = 1;
for (;;) {
struct cx18_mdl *mdl;
int rc;
mdl = cx18_get_mdl(s, non_block, &rc);
/* if there is no data available... */
if (mdl == NULL) {
/* if we got data, then return that regardless */
if (tot_written)
break;
/* EOS condition */
if (rc == 0) {
clear_bit(CX18_F_S_STREAMOFF, &s->s_flags);
clear_bit(CX18_F_S_APPL_IO, &s->s_flags);
cx18_release_stream(s);
}
/* set errno */
return rc;
}
rc = cx18_copy_mdl_to_user(s, mdl, ubuf + tot_written,
tot_count - tot_written);
if (mdl != &cx->vbi.sliced_mpeg_mdl) {
if (mdl->readpos == mdl->bytesused)
cx18_stream_put_mdl_fw(s, mdl);
else
cx18_push(s, mdl, &s->q_full);
} else if (mdl->readpos == mdl->bytesused) {
int idx = cx->vbi.inserted_frame % CX18_VBI_FRAMES;
cx->vbi.sliced_mpeg_size[idx] = 0;
cx->vbi.inserted_frame++;
cx->vbi_data_inserted += mdl->bytesused;
}
if (rc < 0)
return rc;
tot_written += rc;
if (tot_written == tot_count || single_frame)
break;
}
return tot_written;
}
static ssize_t cx18_read_pos(struct cx18_stream *s, char __user *ubuf,
size_t count, loff_t *pos, int non_block)
{
ssize_t rc = count ? cx18_read(s, ubuf, count, non_block) : 0;
struct cx18 *cx = s->cx;
CX18_DEBUG_HI_FILE("read %zd from %s, got %zd\n", count, s->name, rc);
if (rc > 0)
pos += rc;
return rc;
}
int cx18_start_capture(struct cx18_open_id *id)
{
struct cx18 *cx = id->cx;
struct cx18_stream *s = &cx->streams[id->type];
struct cx18_stream *s_vbi;
struct cx18_stream *s_idx;
if (s->type == CX18_ENC_STREAM_TYPE_RAD) {
/* you cannot read from these stream types. */
return -EPERM;
}
/* Try to claim this stream. */
if (cx18_claim_stream(id, s->type))
return -EBUSY;
/* If capture is already in progress, then we also have to
do nothing extra. */
if (test_bit(CX18_F_S_STREAMOFF, &s->s_flags) ||
test_and_set_bit(CX18_F_S_STREAMING, &s->s_flags)) {
set_bit(CX18_F_S_APPL_IO, &s->s_flags);
return 0;
}
/* Start associated VBI or IDX stream capture if required */
s_vbi = &cx->streams[CX18_ENC_STREAM_TYPE_VBI];
s_idx = &cx->streams[CX18_ENC_STREAM_TYPE_IDX];
if (s->type == CX18_ENC_STREAM_TYPE_MPG) {
/*
* The VBI and IDX streams should have been claimed
* automatically, if for internal use, when the MPG stream was
* claimed. We only need to start these streams capturing.
*/
if (test_bit(CX18_F_S_INTERNAL_USE, &s_idx->s_flags) &&
!test_and_set_bit(CX18_F_S_STREAMING, &s_idx->s_flags)) {
if (cx18_start_v4l2_encode_stream(s_idx)) {
CX18_DEBUG_WARN("IDX capture start failed\n");
clear_bit(CX18_F_S_STREAMING, &s_idx->s_flags);
goto start_failed;
}
CX18_DEBUG_INFO("IDX capture started\n");
}
if (test_bit(CX18_F_S_INTERNAL_USE, &s_vbi->s_flags) &&
!test_and_set_bit(CX18_F_S_STREAMING, &s_vbi->s_flags)) {
if (cx18_start_v4l2_encode_stream(s_vbi)) {
CX18_DEBUG_WARN("VBI capture start failed\n");
clear_bit(CX18_F_S_STREAMING, &s_vbi->s_flags);
goto start_failed;
}
CX18_DEBUG_INFO("VBI insertion started\n");
}
}
/* Tell the card to start capturing */
if (!cx18_start_v4l2_encode_stream(s)) {
/* We're done */
set_bit(CX18_F_S_APPL_IO, &s->s_flags);
/* Resume a possibly paused encoder */
if (test_and_clear_bit(CX18_F_I_ENC_PAUSED, &cx->i_flags))
cx18_vapi(cx, CX18_CPU_CAPTURE_PAUSE, 1, s->handle);
return 0;
}
start_failed:
CX18_DEBUG_WARN("Failed to start capturing for stream %s\n", s->name);
/*
* The associated VBI and IDX streams for internal use are released
* automatically when the MPG stream is released. We only need to stop
* the associated stream.
*/
if (s->type == CX18_ENC_STREAM_TYPE_MPG) {
/* Stop the IDX stream which is always for internal use */
if (test_bit(CX18_F_S_STREAMING, &s_idx->s_flags)) {
cx18_stop_v4l2_encode_stream(s_idx, 0);
clear_bit(CX18_F_S_STREAMING, &s_idx->s_flags);
}
/* Stop the VBI stream, if only running for internal use */
if (test_bit(CX18_F_S_STREAMING, &s_vbi->s_flags) &&
!test_bit(CX18_F_S_APPL_IO, &s_vbi->s_flags)) {
cx18_stop_v4l2_encode_stream(s_vbi, 0);
clear_bit(CX18_F_S_STREAMING, &s_vbi->s_flags);
}
}
clear_bit(CX18_F_S_STREAMING, &s->s_flags);
cx18_release_stream(s); /* Also releases associated streams */
return -EIO;
}
ssize_t cx18_v4l2_read(struct file *filp, char __user *buf, size_t count,
loff_t *pos)
{
struct cx18_open_id *id = file2id(filp);
struct cx18 *cx = id->cx;
struct cx18_stream *s = &cx->streams[id->type];
int rc;
CX18_DEBUG_HI_FILE("read %zd bytes from %s\n", count, s->name);
mutex_lock(&cx->serialize_lock);
rc = cx18_start_capture(id);
mutex_unlock(&cx->serialize_lock);
if (rc)
return rc;
if ((s->vb_type == V4L2_BUF_TYPE_VIDEO_CAPTURE) &&
(id->type == CX18_ENC_STREAM_TYPE_YUV)) {
return videobuf_read_stream(&s->vbuf_q, buf, count, pos, 0,
filp->f_flags & O_NONBLOCK);
}
return cx18_read_pos(s, buf, count, pos, filp->f_flags & O_NONBLOCK);
}
unsigned int cx18_v4l2_enc_poll(struct file *filp, poll_table *wait)
{
struct cx18_open_id *id = file2id(filp);
struct cx18 *cx = id->cx;
struct cx18_stream *s = &cx->streams[id->type];
int eof = test_bit(CX18_F_S_STREAMOFF, &s->s_flags);
/* Start a capture if there is none */
if (!eof && !test_bit(CX18_F_S_STREAMING, &s->s_flags)) {
int rc;
mutex_lock(&cx->serialize_lock);
rc = cx18_start_capture(id);
mutex_unlock(&cx->serialize_lock);
if (rc) {
CX18_DEBUG_INFO("Could not start capture for %s (%d)\n",
s->name, rc);
return POLLERR;
}
CX18_DEBUG_FILE("Encoder poll started capture\n");
}
if ((s->vb_type == V4L2_BUF_TYPE_VIDEO_CAPTURE) &&
(id->type == CX18_ENC_STREAM_TYPE_YUV)) {
int videobuf_poll = videobuf_poll_stream(filp, &s->vbuf_q, wait);
if (eof && videobuf_poll == POLLERR)
return POLLHUP;
else
return videobuf_poll;
}
/* add stream's waitq to the poll list */
CX18_DEBUG_HI_FILE("Encoder poll\n");
poll_wait(filp, &s->waitq, wait);
if (atomic_read(&s->q_full.depth))
return POLLIN | POLLRDNORM;
if (eof)
return POLLHUP;
return 0;
}
int cx18_v4l2_mmap(struct file *file, struct vm_area_struct *vma)
{
struct cx18_open_id *id = file->private_data;
struct cx18 *cx = id->cx;
struct cx18_stream *s = &cx->streams[id->type];
int eof = test_bit(CX18_F_S_STREAMOFF, &s->s_flags);
if ((s->vb_type == V4L2_BUF_TYPE_VIDEO_CAPTURE) &&
(id->type == CX18_ENC_STREAM_TYPE_YUV)) {
/* Start a capture if there is none */
if (!eof && !test_bit(CX18_F_S_STREAMING, &s->s_flags)) {
int rc;
mutex_lock(&cx->serialize_lock);
rc = cx18_start_capture(id);
mutex_unlock(&cx->serialize_lock);
if (rc) {
CX18_DEBUG_INFO(
"Could not start capture for %s (%d)\n",
s->name, rc);
return -EINVAL;
}
CX18_DEBUG_FILE("Encoder mmap started capture\n");
}
return videobuf_mmap_mapper(&s->vbuf_q, vma);
}
return -EINVAL;
}
void cx18_vb_timeout(unsigned long data)
{
struct cx18_stream *s = (struct cx18_stream *)data;
struct cx18_videobuf_buffer *buf;
unsigned long flags;
/* Return all of the buffers in error state, so the vbi/vid inode
* can return from blocking.
*/
spin_lock_irqsave(&s->vb_lock, flags);
while (!list_empty(&s->vb_capture)) {
buf = list_entry(s->vb_capture.next,
struct cx18_videobuf_buffer, vb.queue);
list_del(&buf->vb.queue);
buf->vb.state = VIDEOBUF_ERROR;
wake_up(&buf->vb.done);
}
spin_unlock_irqrestore(&s->vb_lock, flags);
}
void cx18_stop_capture(struct cx18_open_id *id, int gop_end)
{
struct cx18 *cx = id->cx;
struct cx18_stream *s = &cx->streams[id->type];
struct cx18_stream *s_vbi = &cx->streams[CX18_ENC_STREAM_TYPE_VBI];
struct cx18_stream *s_idx = &cx->streams[CX18_ENC_STREAM_TYPE_IDX];
CX18_DEBUG_IOCTL("close() of %s\n", s->name);
/* 'Unclaim' this stream */
/* Stop capturing */
if (test_bit(CX18_F_S_STREAMING, &s->s_flags)) {
CX18_DEBUG_INFO("close stopping capture\n");
if (id->type == CX18_ENC_STREAM_TYPE_MPG) {
/* Stop internal use associated VBI and IDX streams */
if (test_bit(CX18_F_S_STREAMING, &s_vbi->s_flags) &&
!test_bit(CX18_F_S_APPL_IO, &s_vbi->s_flags)) {
CX18_DEBUG_INFO("close stopping embedded VBI "
"capture\n");
cx18_stop_v4l2_encode_stream(s_vbi, 0);
}
if (test_bit(CX18_F_S_STREAMING, &s_idx->s_flags)) {
CX18_DEBUG_INFO("close stopping IDX capture\n");
cx18_stop_v4l2_encode_stream(s_idx, 0);
}
}
if (id->type == CX18_ENC_STREAM_TYPE_VBI &&
test_bit(CX18_F_S_INTERNAL_USE, &s->s_flags))
/* Also used internally, don't stop capturing */
s->id = -1;
else
cx18_stop_v4l2_encode_stream(s, gop_end);
}
if (!gop_end) {
clear_bit(CX18_F_S_APPL_IO, &s->s_flags);
clear_bit(CX18_F_S_STREAMOFF, &s->s_flags);
cx18_release_stream(s);
}
}
int cx18_v4l2_close(struct file *filp)
{
struct v4l2_fh *fh = filp->private_data;
struct cx18_open_id *id = fh2id(fh);
struct cx18 *cx = id->cx;
struct cx18_stream *s = &cx->streams[id->type];
CX18_DEBUG_IOCTL("close() of %s\n", s->name);
mutex_lock(&cx->serialize_lock);
/* Stop radio */
if (id->type == CX18_ENC_STREAM_TYPE_RAD &&
v4l2_fh_is_singular_file(filp)) {
/* Closing radio device, return to TV mode */
cx18_mute(cx);
/* Mark that the radio is no longer in use */
clear_bit(CX18_F_I_RADIO_USER, &cx->i_flags);
/* Switch tuner to TV */
cx18_call_all(cx, video, s_std, cx->std);
/* Select correct audio input (i.e. TV tuner or Line in) */
cx18_audio_set_io(cx);
if (atomic_read(&cx->ana_capturing) > 0) {
/* Undo video mute */
cx18_vapi(cx, CX18_CPU_SET_VIDEO_MUTE, 2, s->handle,
(v4l2_ctrl_g_ctrl(cx->cxhdl.video_mute) |
(v4l2_ctrl_g_ctrl(cx->cxhdl.video_mute_yuv) << 8)));
}
/* Done! Unmute and continue. */
cx18_unmute(cx);
}
v4l2_fh_del(fh);
v4l2_fh_exit(fh);
/* 'Unclaim' this stream */
if (s->id == id->open_id)
cx18_stop_capture(id, 0);
kfree(id);
mutex_unlock(&cx->serialize_lock);
return 0;
}
static int cx18_serialized_open(struct cx18_stream *s, struct file *filp)
{
struct cx18 *cx = s->cx;
struct cx18_open_id *item;
CX18_DEBUG_FILE("open %s\n", s->name);
/* Allocate memory */
item = kzalloc(sizeof(struct cx18_open_id), GFP_KERNEL);
if (NULL == item) {
CX18_DEBUG_WARN("nomem on v4l2 open\n");
return -ENOMEM;
}
v4l2_fh_init(&item->fh, s->video_dev);
item->cx = cx;
item->type = s->type;
item->open_id = cx->open_id++;
filp->private_data = &item->fh;
v4l2_fh_add(&item->fh);
if (item->type == CX18_ENC_STREAM_TYPE_RAD &&
v4l2_fh_is_singular_file(filp)) {
if (!test_bit(CX18_F_I_RADIO_USER, &cx->i_flags)) {
if (atomic_read(&cx->ana_capturing) > 0) {
/* switching to radio while capture is
in progress is not polite */
v4l2_fh_del(&item->fh);
v4l2_fh_exit(&item->fh);
kfree(item);
return -EBUSY;
}
}
/* Mark that the radio is being used. */
set_bit(CX18_F_I_RADIO_USER, &cx->i_flags);
/* We have the radio */
cx18_mute(cx);
/* Switch tuner to radio */
cx18_call_all(cx, tuner, s_radio);
/* Select the correct audio input (i.e. radio tuner) */
cx18_audio_set_io(cx);
/* Done! Unmute and continue. */
cx18_unmute(cx);
}
return 0;
}
int cx18_v4l2_open(struct file *filp)
{
int res;
struct video_device *video_dev = video_devdata(filp);
struct cx18_stream *s = video_get_drvdata(video_dev);
struct cx18 *cx = s->cx;
mutex_lock(&cx->serialize_lock);
if (cx18_init_on_first_open(cx)) {
CX18_ERR("Failed to initialize on %s\n",
video_device_node_name(video_dev));
mutex_unlock(&cx->serialize_lock);
return -ENXIO;
}
res = cx18_serialized_open(s, filp);
mutex_unlock(&cx->serialize_lock);
return res;
}
void cx18_mute(struct cx18 *cx)
{
u32 h;
if (atomic_read(&cx->ana_capturing)) {
h = cx18_find_handle(cx);
if (h != CX18_INVALID_TASK_HANDLE)
cx18_vapi(cx, CX18_CPU_SET_AUDIO_MUTE, 2, h, 1);
else
CX18_ERR("Can't find valid task handle for mute\n");
}
CX18_DEBUG_INFO("Mute\n");
}
void cx18_unmute(struct cx18 *cx)
{
u32 h;
if (atomic_read(&cx->ana_capturing)) {
h = cx18_find_handle(cx);
if (h != CX18_INVALID_TASK_HANDLE) {
cx18_msleep_timeout(100, 0);
cx18_vapi(cx, CX18_CPU_SET_MISC_PARAMETERS, 2, h, 12);
cx18_vapi(cx, CX18_CPU_SET_AUDIO_MUTE, 2, h, 0);
} else
CX18_ERR("Can't find valid task handle for unmute\n");
}
CX18_DEBUG_INFO("Unmute\n");
}

View file

@ -0,0 +1,41 @@
/*
* cx18 file operation functions
*
* Derived from ivtv-fileops.h
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* 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
*/
/* Testing/Debugging */
int cx18_v4l2_open(struct file *filp);
ssize_t cx18_v4l2_read(struct file *filp, char __user *buf, size_t count,
loff_t *pos);
ssize_t cx18_v4l2_write(struct file *filp, const char __user *buf, size_t count,
loff_t *pos);
int cx18_v4l2_close(struct file *filp);
unsigned int cx18_v4l2_enc_poll(struct file *filp, poll_table *wait);
int cx18_start_capture(struct cx18_open_id *id);
void cx18_stop_capture(struct cx18_open_id *id, int gop_end);
void cx18_mute(struct cx18 *cx);
void cx18_unmute(struct cx18 *cx);
int cx18_v4l2_mmap(struct file *file, struct vm_area_struct *vma);
void cx18_vb_timeout(unsigned long data);
/* Shared with cx18-alsa module */
int cx18_claim_stream(struct cx18_open_id *id, int type);
void cx18_release_stream(struct cx18_stream *s);

View file

@ -0,0 +1,459 @@
/*
* cx18 firmware functions
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* 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 "cx18-driver.h"
#include "cx18-io.h"
#include "cx18-scb.h"
#include "cx18-irq.h"
#include "cx18-firmware.h"
#include "cx18-cards.h"
#include <linux/firmware.h>
#define CX18_PROC_SOFT_RESET 0xc70010
#define CX18_DDR_SOFT_RESET 0xc70014
#define CX18_CLOCK_SELECT1 0xc71000
#define CX18_CLOCK_SELECT2 0xc71004
#define CX18_HALF_CLOCK_SELECT1 0xc71008
#define CX18_HALF_CLOCK_SELECT2 0xc7100C
#define CX18_CLOCK_POLARITY1 0xc71010
#define CX18_CLOCK_POLARITY2 0xc71014
#define CX18_ADD_DELAY_ENABLE1 0xc71018
#define CX18_ADD_DELAY_ENABLE2 0xc7101C
#define CX18_CLOCK_ENABLE1 0xc71020
#define CX18_CLOCK_ENABLE2 0xc71024
#define CX18_REG_BUS_TIMEOUT_EN 0xc72024
#define CX18_FAST_CLOCK_PLL_INT 0xc78000
#define CX18_FAST_CLOCK_PLL_FRAC 0xc78004
#define CX18_FAST_CLOCK_PLL_POST 0xc78008
#define CX18_FAST_CLOCK_PLL_PRESCALE 0xc7800C
#define CX18_FAST_CLOCK_PLL_ADJUST_BANDWIDTH 0xc78010
#define CX18_SLOW_CLOCK_PLL_INT 0xc78014
#define CX18_SLOW_CLOCK_PLL_FRAC 0xc78018
#define CX18_SLOW_CLOCK_PLL_POST 0xc7801C
#define CX18_MPEG_CLOCK_PLL_INT 0xc78040
#define CX18_MPEG_CLOCK_PLL_FRAC 0xc78044
#define CX18_MPEG_CLOCK_PLL_POST 0xc78048
#define CX18_PLL_POWER_DOWN 0xc78088
#define CX18_SW1_INT_STATUS 0xc73104
#define CX18_SW1_INT_ENABLE_PCI 0xc7311C
#define CX18_SW2_INT_SET 0xc73140
#define CX18_SW2_INT_STATUS 0xc73144
#define CX18_ADEC_CONTROL 0xc78120
#define CX18_DDR_REQUEST_ENABLE 0xc80000
#define CX18_DDR_CHIP_CONFIG 0xc80004
#define CX18_DDR_REFRESH 0xc80008
#define CX18_DDR_TIMING1 0xc8000C
#define CX18_DDR_TIMING2 0xc80010
#define CX18_DDR_POWER_REG 0xc8001C
#define CX18_DDR_TUNE_LANE 0xc80048
#define CX18_DDR_INITIAL_EMRS 0xc80054
#define CX18_DDR_MB_PER_ROW_7 0xc8009C
#define CX18_DDR_BASE_63_ADDR 0xc804FC
#define CX18_WMB_CLIENT02 0xc90108
#define CX18_WMB_CLIENT05 0xc90114
#define CX18_WMB_CLIENT06 0xc90118
#define CX18_WMB_CLIENT07 0xc9011C
#define CX18_WMB_CLIENT08 0xc90120
#define CX18_WMB_CLIENT09 0xc90124
#define CX18_WMB_CLIENT10 0xc90128
#define CX18_WMB_CLIENT11 0xc9012C
#define CX18_WMB_CLIENT12 0xc90130
#define CX18_WMB_CLIENT13 0xc90134
#define CX18_WMB_CLIENT14 0xc90138
#define CX18_DSP0_INTERRUPT_MASK 0xd0004C
#define APU_ROM_SYNC1 0x6D676553 /* "mgeS" */
#define APU_ROM_SYNC2 0x72646548 /* "rdeH" */
struct cx18_apu_rom_seghdr {
u32 sync1;
u32 sync2;
u32 addr;
u32 size;
};
static int load_cpu_fw_direct(const char *fn, u8 __iomem *mem, struct cx18 *cx)
{
const struct firmware *fw = NULL;
int i, j;
unsigned size;
u32 __iomem *dst = (u32 __iomem *)mem;
const u32 *src;
if (request_firmware(&fw, fn, &cx->pci_dev->dev)) {
CX18_ERR("Unable to open firmware %s\n", fn);
CX18_ERR("Did you put the firmware in the hotplug firmware directory?\n");
return -ENOMEM;
}
src = (const u32 *)fw->data;
for (i = 0; i < fw->size; i += 4096) {
cx18_setup_page(cx, i);
for (j = i; j < fw->size && j < i + 4096; j += 4) {
/* no need for endianness conversion on the ppc */
cx18_raw_writel(cx, *src, dst);
if (cx18_raw_readl(cx, dst) != *src) {
CX18_ERR("Mismatch at offset %x\n", i);
release_firmware(fw);
cx18_setup_page(cx, 0);
return -EIO;
}
dst++;
src++;
}
}
if (!test_bit(CX18_F_I_LOADED_FW, &cx->i_flags))
CX18_INFO("loaded %s firmware (%zu bytes)\n", fn, fw->size);
size = fw->size;
release_firmware(fw);
cx18_setup_page(cx, SCB_OFFSET);
return size;
}
static int load_apu_fw_direct(const char *fn, u8 __iomem *dst, struct cx18 *cx,
u32 *entry_addr)
{
const struct firmware *fw = NULL;
int i, j;
unsigned size;
const u32 *src;
struct cx18_apu_rom_seghdr seghdr;
const u8 *vers;
u32 offset = 0;
u32 apu_version = 0;
int sz;
if (request_firmware(&fw, fn, &cx->pci_dev->dev)) {
CX18_ERR("unable to open firmware %s\n", fn);
CX18_ERR("did you put the firmware in the hotplug firmware directory?\n");
cx18_setup_page(cx, 0);
return -ENOMEM;
}
*entry_addr = 0;
src = (const u32 *)fw->data;
vers = fw->data + sizeof(seghdr);
sz = fw->size;
apu_version = (vers[0] << 24) | (vers[4] << 16) | vers[32];
while (offset + sizeof(seghdr) < fw->size) {
const __le32 *shptr = (__force __le32 *)src + offset / 4;
seghdr.sync1 = le32_to_cpu(shptr[0]);
seghdr.sync2 = le32_to_cpu(shptr[1]);
seghdr.addr = le32_to_cpu(shptr[2]);
seghdr.size = le32_to_cpu(shptr[3]);
offset += sizeof(seghdr);
if (seghdr.sync1 != APU_ROM_SYNC1 ||
seghdr.sync2 != APU_ROM_SYNC2) {
offset += seghdr.size;
continue;
}
CX18_DEBUG_INFO("load segment %x-%x\n", seghdr.addr,
seghdr.addr + seghdr.size - 1);
if (*entry_addr == 0)
*entry_addr = seghdr.addr;
if (offset + seghdr.size > sz)
break;
for (i = 0; i < seghdr.size; i += 4096) {
cx18_setup_page(cx, seghdr.addr + i);
for (j = i; j < seghdr.size && j < i + 4096; j += 4) {
/* no need for endianness conversion on the ppc */
cx18_raw_writel(cx, src[(offset + j) / 4],
dst + seghdr.addr + j);
if (cx18_raw_readl(cx, dst + seghdr.addr + j)
!= src[(offset + j) / 4]) {
CX18_ERR("Mismatch at offset %x\n",
offset + j);
release_firmware(fw);
cx18_setup_page(cx, 0);
return -EIO;
}
}
}
offset += seghdr.size;
}
if (!test_bit(CX18_F_I_LOADED_FW, &cx->i_flags))
CX18_INFO("loaded %s firmware V%08x (%zu bytes)\n",
fn, apu_version, fw->size);
size = fw->size;
release_firmware(fw);
cx18_setup_page(cx, 0);
return size;
}
void cx18_halt_firmware(struct cx18 *cx)
{
CX18_DEBUG_INFO("Preparing for firmware halt.\n");
cx18_write_reg_expect(cx, 0x000F000F, CX18_PROC_SOFT_RESET,
0x0000000F, 0x000F000F);
cx18_write_reg_expect(cx, 0x00020002, CX18_ADEC_CONTROL,
0x00000002, 0x00020002);
}
void cx18_init_power(struct cx18 *cx, int lowpwr)
{
/* power-down Spare and AOM PLLs */
/* power-up fast, slow and mpeg PLLs */
cx18_write_reg(cx, 0x00000008, CX18_PLL_POWER_DOWN);
/* ADEC out of sleep */
cx18_write_reg_expect(cx, 0x00020000, CX18_ADEC_CONTROL,
0x00000000, 0x00020002);
/*
* The PLL parameters are based on the external crystal frequency that
* would ideally be:
*
* NTSC Color subcarrier freq * 8 =
* 4.5 MHz/286 * 455/2 * 8 = 28.63636363... MHz
*
* The accidents of history and rationale that explain from where this
* combination of magic numbers originate can be found in:
*
* [1] Abrahams, I. C., "Choice of Chrominance Subcarrier Frequency in
* the NTSC Standards", Proceedings of the I-R-E, January 1954, pp 79-80
*
* [2] Abrahams, I. C., "The 'Frequency Interleaving' Principle in the
* NTSC Standards", Proceedings of the I-R-E, January 1954, pp 81-83
*
* As Mike Bradley has rightly pointed out, it's not the exact crystal
* frequency that matters, only that all parts of the driver and
* firmware are using the same value (close to the ideal value).
*
* Since I have a strong suspicion that, if the firmware ever assumes a
* crystal value at all, it will assume 28.636360 MHz, the crystal
* freq used in calculations in this driver will be:
*
* xtal_freq = 28.636360 MHz
*
* an error of less than 0.13 ppm which is way, way better than any off
* the shelf crystal will have for accuracy anyway.
*
* Below I aim to run the PLLs' VCOs near 400 MHz to minimze errors.
*
* Many thanks to Jeff Campbell and Mike Bradley for their extensive
* investigation, experimentation, testing, and suggested solutions of
* of audio/video sync problems with SVideo and CVBS captures.
*/
/* the fast clock is at 200/245 MHz */
/* 1 * xtal_freq * 0x0d.f7df9b8 / 2 = 200 MHz: 400 MHz pre post-divide*/
/* 1 * xtal_freq * 0x11.1c71eb8 / 2 = 245 MHz: 490 MHz pre post-divide*/
cx18_write_reg(cx, lowpwr ? 0xD : 0x11, CX18_FAST_CLOCK_PLL_INT);
cx18_write_reg(cx, lowpwr ? 0x1EFBF37 : 0x038E3D7,
CX18_FAST_CLOCK_PLL_FRAC);
cx18_write_reg(cx, 2, CX18_FAST_CLOCK_PLL_POST);
cx18_write_reg(cx, 1, CX18_FAST_CLOCK_PLL_PRESCALE);
cx18_write_reg(cx, 4, CX18_FAST_CLOCK_PLL_ADJUST_BANDWIDTH);
/* set slow clock to 125/120 MHz */
/* xtal_freq * 0x0d.1861a20 / 3 = 125 MHz: 375 MHz before post-divide */
/* xtal_freq * 0x0c.92493f8 / 3 = 120 MHz: 360 MHz before post-divide */
cx18_write_reg(cx, lowpwr ? 0xD : 0xC, CX18_SLOW_CLOCK_PLL_INT);
cx18_write_reg(cx, lowpwr ? 0x30C344 : 0x124927F,
CX18_SLOW_CLOCK_PLL_FRAC);
cx18_write_reg(cx, 3, CX18_SLOW_CLOCK_PLL_POST);
/* mpeg clock pll 54MHz */
/* xtal_freq * 0xf.15f17f0 / 8 = 54 MHz: 432 MHz before post-divide */
cx18_write_reg(cx, 0xF, CX18_MPEG_CLOCK_PLL_INT);
cx18_write_reg(cx, 0x2BE2FE, CX18_MPEG_CLOCK_PLL_FRAC);
cx18_write_reg(cx, 8, CX18_MPEG_CLOCK_PLL_POST);
/* Defaults */
/* APU = SC or SC/2 = 125/62.5 */
/* EPU = SC = 125 */
/* DDR = FC = 180 */
/* ENC = SC = 125 */
/* AI1 = SC = 125 */
/* VIM2 = disabled */
/* PCI = FC/2 = 90 */
/* AI2 = disabled */
/* DEMUX = disabled */
/* AO = SC/2 = 62.5 */
/* SER = 54MHz */
/* VFC = disabled */
/* USB = disabled */
if (lowpwr) {
cx18_write_reg_expect(cx, 0xFFFF0020, CX18_CLOCK_SELECT1,
0x00000020, 0xFFFFFFFF);
cx18_write_reg_expect(cx, 0xFFFF0004, CX18_CLOCK_SELECT2,
0x00000004, 0xFFFFFFFF);
} else {
/* This doesn't explicitly set every clock select */
cx18_write_reg_expect(cx, 0x00060004, CX18_CLOCK_SELECT1,
0x00000004, 0x00060006);
cx18_write_reg_expect(cx, 0x00060006, CX18_CLOCK_SELECT2,
0x00000006, 0x00060006);
}
cx18_write_reg_expect(cx, 0xFFFF0002, CX18_HALF_CLOCK_SELECT1,
0x00000002, 0xFFFFFFFF);
cx18_write_reg_expect(cx, 0xFFFF0104, CX18_HALF_CLOCK_SELECT2,
0x00000104, 0xFFFFFFFF);
cx18_write_reg_expect(cx, 0xFFFF9026, CX18_CLOCK_ENABLE1,
0x00009026, 0xFFFFFFFF);
cx18_write_reg_expect(cx, 0xFFFF3105, CX18_CLOCK_ENABLE2,
0x00003105, 0xFFFFFFFF);
}
void cx18_init_memory(struct cx18 *cx)
{
cx18_msleep_timeout(10, 0);
cx18_write_reg_expect(cx, 0x00010000, CX18_DDR_SOFT_RESET,
0x00000000, 0x00010001);
cx18_msleep_timeout(10, 0);
cx18_write_reg(cx, cx->card->ddr.chip_config, CX18_DDR_CHIP_CONFIG);
cx18_msleep_timeout(10, 0);
cx18_write_reg(cx, cx->card->ddr.refresh, CX18_DDR_REFRESH);
cx18_write_reg(cx, cx->card->ddr.timing1, CX18_DDR_TIMING1);
cx18_write_reg(cx, cx->card->ddr.timing2, CX18_DDR_TIMING2);
cx18_msleep_timeout(10, 0);
/* Initialize DQS pad time */
cx18_write_reg(cx, cx->card->ddr.tune_lane, CX18_DDR_TUNE_LANE);
cx18_write_reg(cx, cx->card->ddr.initial_emrs, CX18_DDR_INITIAL_EMRS);
cx18_msleep_timeout(10, 0);
cx18_write_reg_expect(cx, 0x00020000, CX18_DDR_SOFT_RESET,
0x00000000, 0x00020002);
cx18_msleep_timeout(10, 0);
/* use power-down mode when idle */
cx18_write_reg(cx, 0x00000010, CX18_DDR_POWER_REG);
cx18_write_reg_expect(cx, 0x00010001, CX18_REG_BUS_TIMEOUT_EN,
0x00000001, 0x00010001);
cx18_write_reg(cx, 0x48, CX18_DDR_MB_PER_ROW_7);
cx18_write_reg(cx, 0xE0000, CX18_DDR_BASE_63_ADDR);
cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT02); /* AO */
cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT09); /* AI2 */
cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT05); /* VIM1 */
cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT06); /* AI1 */
cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT07); /* 3D comb */
cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT10); /* ME */
cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT12); /* ENC */
cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT13); /* PK */
cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT11); /* RC */
cx18_write_reg(cx, 0x00000101, CX18_WMB_CLIENT14); /* AVO */
}
#define CX18_CPU_FIRMWARE "v4l-cx23418-cpu.fw"
#define CX18_APU_FIRMWARE "v4l-cx23418-apu.fw"
int cx18_firmware_init(struct cx18 *cx)
{
u32 fw_entry_addr;
int sz, retries;
u32 api_args[MAX_MB_ARGUMENTS];
/* Allow chip to control CLKRUN */
cx18_write_reg(cx, 0x5, CX18_DSP0_INTERRUPT_MASK);
/* Stop the firmware */
cx18_write_reg_expect(cx, 0x000F000F, CX18_PROC_SOFT_RESET,
0x0000000F, 0x000F000F);
cx18_msleep_timeout(1, 0);
/* If the CPU is still running */
if ((cx18_read_reg(cx, CX18_PROC_SOFT_RESET) & 8) == 0) {
CX18_ERR("%s: couldn't stop CPU to load firmware\n", __func__);
return -EIO;
}
cx18_sw1_irq_enable(cx, IRQ_CPU_TO_EPU | IRQ_APU_TO_EPU);
cx18_sw2_irq_enable(cx, IRQ_CPU_TO_EPU_ACK | IRQ_APU_TO_EPU_ACK);
sz = load_cpu_fw_direct(CX18_CPU_FIRMWARE, cx->enc_mem, cx);
if (sz <= 0)
return sz;
/* The SCB & IPC area *must* be correct before starting the firmwares */
cx18_init_scb(cx);
fw_entry_addr = 0;
sz = load_apu_fw_direct(CX18_APU_FIRMWARE, cx->enc_mem, cx,
&fw_entry_addr);
if (sz <= 0)
return sz;
/* Start the CPU. The CPU will take care of the APU for us. */
cx18_write_reg_expect(cx, 0x00080000, CX18_PROC_SOFT_RESET,
0x00000000, 0x00080008);
/* Wait up to 500 ms for the APU to come out of reset */
for (retries = 0;
retries < 50 && (cx18_read_reg(cx, CX18_PROC_SOFT_RESET) & 1) == 1;
retries++)
cx18_msleep_timeout(10, 0);
cx18_msleep_timeout(200, 0);
if (retries == 50 &&
(cx18_read_reg(cx, CX18_PROC_SOFT_RESET) & 1) == 1) {
CX18_ERR("Could not start the CPU\n");
return -EIO;
}
/*
* The CPU had once before set up to receive an interrupt for it's
* outgoing IRQ_CPU_TO_EPU_ACK to us. If it ever does this, we get an
* interrupt when it sends us an ack, but by the time we process it,
* that flag in the SW2 status register has been cleared by the CPU
* firmware. We'll prevent that not so useful condition from happening
* by clearing the CPU's interrupt enables for Ack IRQ's we want to
* process.
*/
cx18_sw2_irq_disable_cpu(cx, IRQ_CPU_TO_EPU_ACK | IRQ_APU_TO_EPU_ACK);
/* Try a benign command to see if the CPU is alive and well */
sz = cx18_vapi_result(cx, api_args, CX18_CPU_DEBUG_PEEK32, 1, 0);
if (sz < 0)
return sz;
/* initialize GPIO */
cx18_write_reg_expect(cx, 0x14001400, 0xc78110, 0x00001400, 0x14001400);
return 0;
}
MODULE_FIRMWARE(CX18_CPU_FIRMWARE);
MODULE_FIRMWARE(CX18_APU_FIRMWARE);

View file

@ -0,0 +1,25 @@
/*
* cx18 firmware functions
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* 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
*/
int cx18_firmware_init(struct cx18 *cx);
void cx18_halt_firmware(struct cx18 *cx);
void cx18_init_memory(struct cx18 *cx);
void cx18_init_power(struct cx18 *cx, int lowpwr);

View file

@ -0,0 +1,351 @@
/*
* cx18 gpio functions
*
* Derived from ivtv-gpio.c
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* 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 "cx18-driver.h"
#include "cx18-io.h"
#include "cx18-cards.h"
#include "cx18-gpio.h"
#include "tuner-xc2028.h"
/********************* GPIO stuffs *********************/
/* GPIO registers */
#define CX18_REG_GPIO_IN 0xc72010
#define CX18_REG_GPIO_OUT1 0xc78100
#define CX18_REG_GPIO_DIR1 0xc78108
#define CX18_REG_GPIO_OUT2 0xc78104
#define CX18_REG_GPIO_DIR2 0xc7810c
/*
* HVR-1600 GPIO pins, courtesy of Hauppauge:
*
* gpio0: zilog ir process reset pin
* gpio1: zilog programming pin (you should never use this)
* gpio12: cx24227 reset pin
* gpio13: cs5345 reset pin
*/
/*
* File scope utility functions
*/
static void gpio_write(struct cx18 *cx)
{
u32 dir_lo = cx->gpio_dir & 0xffff;
u32 val_lo = cx->gpio_val & 0xffff;
u32 dir_hi = cx->gpio_dir >> 16;
u32 val_hi = cx->gpio_val >> 16;
cx18_write_reg_expect(cx, dir_lo << 16,
CX18_REG_GPIO_DIR1, ~dir_lo, dir_lo);
cx18_write_reg_expect(cx, (dir_lo << 16) | val_lo,
CX18_REG_GPIO_OUT1, val_lo, dir_lo);
cx18_write_reg_expect(cx, dir_hi << 16,
CX18_REG_GPIO_DIR2, ~dir_hi, dir_hi);
cx18_write_reg_expect(cx, (dir_hi << 16) | val_hi,
CX18_REG_GPIO_OUT2, val_hi, dir_hi);
}
static void gpio_update(struct cx18 *cx, u32 mask, u32 data)
{
if (mask == 0)
return;
mutex_lock(&cx->gpio_lock);
cx->gpio_val = (cx->gpio_val & ~mask) | (data & mask);
gpio_write(cx);
mutex_unlock(&cx->gpio_lock);
}
static void gpio_reset_seq(struct cx18 *cx, u32 active_lo, u32 active_hi,
unsigned int assert_msecs,
unsigned int recovery_msecs)
{
u32 mask;
mask = active_lo | active_hi;
if (mask == 0)
return;
/*
* Assuming that active_hi and active_lo are a subsets of the bits in
* gpio_dir. Also assumes that active_lo and active_hi don't overlap
* in any bit position
*/
/* Assert */
gpio_update(cx, mask, ~active_lo);
schedule_timeout_uninterruptible(msecs_to_jiffies(assert_msecs));
/* Deassert */
gpio_update(cx, mask, ~active_hi);
schedule_timeout_uninterruptible(msecs_to_jiffies(recovery_msecs));
}
/*
* GPIO Multiplexer - logical device
*/
static int gpiomux_log_status(struct v4l2_subdev *sd)
{
struct cx18 *cx = v4l2_get_subdevdata(sd);
mutex_lock(&cx->gpio_lock);
CX18_INFO_DEV(sd, "GPIO: direction 0x%08x, value 0x%08x\n",
cx->gpio_dir, cx->gpio_val);
mutex_unlock(&cx->gpio_lock);
return 0;
}
static int gpiomux_s_radio(struct v4l2_subdev *sd)
{
struct cx18 *cx = v4l2_get_subdevdata(sd);
/*
* FIXME - work out the cx->active/audio_input mess - this is
* intended to handle the switch to radio mode and set the
* audio routing, but we need to update the state in cx
*/
gpio_update(cx, cx->card->gpio_audio_input.mask,
cx->card->gpio_audio_input.radio);
return 0;
}
static int gpiomux_s_std(struct v4l2_subdev *sd, v4l2_std_id norm)
{
struct cx18 *cx = v4l2_get_subdevdata(sd);
u32 data;
switch (cx->card->audio_inputs[cx->audio_input].muxer_input) {
case 1:
data = cx->card->gpio_audio_input.linein;
break;
case 0:
data = cx->card->gpio_audio_input.tuner;
break;
default:
/*
* FIXME - work out the cx->active/audio_input mess - this is
* intended to handle the switch from radio mode and set the
* audio routing, but we need to update the state in cx
*/
data = cx->card->gpio_audio_input.tuner;
break;
}
gpio_update(cx, cx->card->gpio_audio_input.mask, data);
return 0;
}
static int gpiomux_s_audio_routing(struct v4l2_subdev *sd,
u32 input, u32 output, u32 config)
{
struct cx18 *cx = v4l2_get_subdevdata(sd);
u32 data;
switch (input) {
case 0:
data = cx->card->gpio_audio_input.tuner;
break;
case 1:
data = cx->card->gpio_audio_input.linein;
break;
case 2:
data = cx->card->gpio_audio_input.radio;
break;
default:
return -EINVAL;
}
gpio_update(cx, cx->card->gpio_audio_input.mask, data);
return 0;
}
static const struct v4l2_subdev_core_ops gpiomux_core_ops = {
.log_status = gpiomux_log_status,
};
static const struct v4l2_subdev_tuner_ops gpiomux_tuner_ops = {
.s_radio = gpiomux_s_radio,
};
static const struct v4l2_subdev_audio_ops gpiomux_audio_ops = {
.s_routing = gpiomux_s_audio_routing,
};
static const struct v4l2_subdev_video_ops gpiomux_video_ops = {
.s_std = gpiomux_s_std,
};
static const struct v4l2_subdev_ops gpiomux_ops = {
.core = &gpiomux_core_ops,
.tuner = &gpiomux_tuner_ops,
.audio = &gpiomux_audio_ops,
.video = &gpiomux_video_ops,
};
/*
* GPIO Reset Controller - logical device
*/
static int resetctrl_log_status(struct v4l2_subdev *sd)
{
struct cx18 *cx = v4l2_get_subdevdata(sd);
mutex_lock(&cx->gpio_lock);
CX18_INFO_DEV(sd, "GPIO: direction 0x%08x, value 0x%08x\n",
cx->gpio_dir, cx->gpio_val);
mutex_unlock(&cx->gpio_lock);
return 0;
}
static int resetctrl_reset(struct v4l2_subdev *sd, u32 val)
{
struct cx18 *cx = v4l2_get_subdevdata(sd);
const struct cx18_gpio_i2c_slave_reset *p;
p = &cx->card->gpio_i2c_slave_reset;
switch (val) {
case CX18_GPIO_RESET_I2C:
gpio_reset_seq(cx, p->active_lo_mask, p->active_hi_mask,
p->msecs_asserted, p->msecs_recovery);
break;
case CX18_GPIO_RESET_Z8F0811:
/*
* Assert timing for the Z8F0811 on HVR-1600 boards:
* 1. Assert RESET for min of 4 clock cycles at 18.432 MHz to
* initiate
* 2. Reset then takes 66 WDT cycles at 10 kHz + 16 xtal clock
* cycles (6,601,085 nanoseconds ~= 7 milliseconds)
* 3. DBG pin must be high before chip exits reset for normal
* operation. DBG is open drain and hopefully pulled high
* since we don't normally drive it (GPIO 1?) for the
* HVR-1600
* 4. Z8F0811 won't exit reset until RESET is deasserted
* 5. Zilog comes out of reset, loads reset vector address and
* executes from there. Required recovery delay unknown.
*/
gpio_reset_seq(cx, p->ir_reset_mask, 0,
p->msecs_asserted, p->msecs_recovery);
break;
case CX18_GPIO_RESET_XC2028:
if (cx->card->tuners[0].tuner == TUNER_XC2028)
gpio_reset_seq(cx, (1 << cx->card->xceive_pin), 0,
1, 1);
break;
}
return 0;
}
static const struct v4l2_subdev_core_ops resetctrl_core_ops = {
.log_status = resetctrl_log_status,
.reset = resetctrl_reset,
};
static const struct v4l2_subdev_ops resetctrl_ops = {
.core = &resetctrl_core_ops,
};
/*
* External entry points
*/
void cx18_gpio_init(struct cx18 *cx)
{
mutex_lock(&cx->gpio_lock);
cx->gpio_dir = cx->card->gpio_init.direction;
cx->gpio_val = cx->card->gpio_init.initial_value;
if (cx->card->tuners[0].tuner == TUNER_XC2028) {
cx->gpio_dir |= 1 << cx->card->xceive_pin;
cx->gpio_val |= 1 << cx->card->xceive_pin;
}
if (cx->gpio_dir == 0) {
mutex_unlock(&cx->gpio_lock);
return;
}
CX18_DEBUG_INFO("GPIO initial dir: %08x/%08x out: %08x/%08x\n",
cx18_read_reg(cx, CX18_REG_GPIO_DIR1),
cx18_read_reg(cx, CX18_REG_GPIO_DIR2),
cx18_read_reg(cx, CX18_REG_GPIO_OUT1),
cx18_read_reg(cx, CX18_REG_GPIO_OUT2));
gpio_write(cx);
mutex_unlock(&cx->gpio_lock);
}
int cx18_gpio_register(struct cx18 *cx, u32 hw)
{
struct v4l2_subdev *sd;
const struct v4l2_subdev_ops *ops;
char *str;
switch (hw) {
case CX18_HW_GPIO_MUX:
sd = &cx->sd_gpiomux;
ops = &gpiomux_ops;
str = "gpio-mux";
break;
case CX18_HW_GPIO_RESET_CTRL:
sd = &cx->sd_resetctrl;
ops = &resetctrl_ops;
str = "gpio-reset-ctrl";
break;
default:
return -EINVAL;
}
v4l2_subdev_init(sd, ops);
v4l2_set_subdevdata(sd, cx);
snprintf(sd->name, sizeof(sd->name), "%s %s", cx->v4l2_dev.name, str);
sd->grp_id = hw;
return v4l2_device_register_subdev(&cx->v4l2_dev, sd);
}
void cx18_reset_ir_gpio(void *data)
{
struct cx18 *cx = to_cx18((struct v4l2_device *)data);
if (cx->card->gpio_i2c_slave_reset.ir_reset_mask == 0)
return;
CX18_DEBUG_INFO("Resetting IR microcontroller\n");
v4l2_subdev_call(&cx->sd_resetctrl,
core, reset, CX18_GPIO_RESET_Z8F0811);
}
EXPORT_SYMBOL(cx18_reset_ir_gpio);
/* This symbol is exported for use by lirc_pvr150 for the IR-blaster */
/* Xceive tuner reset function */
int cx18_reset_tuner_gpio(void *dev, int component, int cmd, int value)
{
struct i2c_algo_bit_data *algo = dev;
struct cx18_i2c_algo_callback_data *cb_data = algo->data;
struct cx18 *cx = cb_data->cx;
if (cmd != XC2028_TUNER_RESET ||
cx->card->tuners[0].tuner != TUNER_XC2028)
return 0;
CX18_DEBUG_INFO("Resetting XCeive tuner\n");
return v4l2_subdev_call(&cx->sd_resetctrl,
core, reset, CX18_GPIO_RESET_XC2028);
}

View file

@ -0,0 +1,34 @@
/*
* cx18 gpio functions
*
* Derived from ivtv-gpio.h
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* 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
*/
void cx18_gpio_init(struct cx18 *cx);
int cx18_gpio_register(struct cx18 *cx, u32 hw);
enum cx18_gpio_reset_type {
CX18_GPIO_RESET_I2C = 0,
CX18_GPIO_RESET_Z8F0811 = 1,
CX18_GPIO_RESET_XC2028 = 2,
};
void cx18_reset_ir_gpio(void *data);
int cx18_reset_tuner_gpio(void *dev, int component, int cmd, int value);

View file

@ -0,0 +1,325 @@
/*
* cx18 I2C functions
*
* Derived from ivtv-i2c.c
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* 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 "cx18-driver.h"
#include "cx18-io.h"
#include "cx18-cards.h"
#include "cx18-gpio.h"
#include "cx18-i2c.h"
#include "cx18-irq.h"
#define CX18_REG_I2C_1_WR 0xf15000
#define CX18_REG_I2C_1_RD 0xf15008
#define CX18_REG_I2C_2_WR 0xf25100
#define CX18_REG_I2C_2_RD 0xf25108
#define SETSCL_BIT 0x0001
#define SETSDL_BIT 0x0002
#define GETSCL_BIT 0x0004
#define GETSDL_BIT 0x0008
#define CX18_CS5345_I2C_ADDR 0x4c
#define CX18_Z8F0811_IR_TX_I2C_ADDR 0x70
#define CX18_Z8F0811_IR_RX_I2C_ADDR 0x71
/* This array should match the CX18_HW_ defines */
static const u8 hw_addrs[] = {
0, /* CX18_HW_TUNER */
0, /* CX18_HW_TVEEPROM */
CX18_CS5345_I2C_ADDR, /* CX18_HW_CS5345 */
0, /* CX18_HW_DVB */
0, /* CX18_HW_418_AV */
0, /* CX18_HW_GPIO_MUX */
0, /* CX18_HW_GPIO_RESET_CTRL */
CX18_Z8F0811_IR_TX_I2C_ADDR, /* CX18_HW_Z8F0811_IR_TX_HAUP */
CX18_Z8F0811_IR_RX_I2C_ADDR, /* CX18_HW_Z8F0811_IR_RX_HAUP */
};
/* This array should match the CX18_HW_ defines */
/* This might well become a card-specific array */
static const u8 hw_bus[] = {
1, /* CX18_HW_TUNER */
0, /* CX18_HW_TVEEPROM */
0, /* CX18_HW_CS5345 */
0, /* CX18_HW_DVB */
0, /* CX18_HW_418_AV */
0, /* CX18_HW_GPIO_MUX */
0, /* CX18_HW_GPIO_RESET_CTRL */
0, /* CX18_HW_Z8F0811_IR_TX_HAUP */
0, /* CX18_HW_Z8F0811_IR_RX_HAUP */
};
/* This array should match the CX18_HW_ defines */
static const char * const hw_devicenames[] = {
"tuner",
"tveeprom",
"cs5345",
"cx23418_DTV",
"cx23418_AV",
"gpio_mux",
"gpio_reset_ctrl",
"ir_tx_z8f0811_haup",
"ir_rx_z8f0811_haup",
};
static int cx18_i2c_new_ir(struct cx18 *cx, struct i2c_adapter *adap, u32 hw,
const char *type, u8 addr)
{
struct i2c_board_info info;
struct IR_i2c_init_data *init_data = &cx->ir_i2c_init_data;
unsigned short addr_list[2] = { addr, I2C_CLIENT_END };
memset(&info, 0, sizeof(struct i2c_board_info));
strlcpy(info.type, type, I2C_NAME_SIZE);
/* Our default information for ir-kbd-i2c.c to use */
switch (hw) {
case CX18_HW_Z8F0811_IR_RX_HAUP:
init_data->ir_codes = RC_MAP_HAUPPAUGE;
init_data->internal_get_key_func = IR_KBD_GET_KEY_HAUP_XVR;
init_data->type = RC_BIT_RC5;
init_data->name = cx->card_name;
info.platform_data = init_data;
break;
}
return i2c_new_probed_device(adap, &info, addr_list, NULL) == NULL ?
-1 : 0;
}
int cx18_i2c_register(struct cx18 *cx, unsigned idx)
{
struct v4l2_subdev *sd;
int bus = hw_bus[idx];
struct i2c_adapter *adap = &cx->i2c_adap[bus];
const char *type = hw_devicenames[idx];
u32 hw = 1 << idx;
if (hw == CX18_HW_TUNER) {
/* special tuner group handling */
sd = v4l2_i2c_new_subdev(&cx->v4l2_dev,
adap, type, 0, cx->card_i2c->radio);
if (sd != NULL)
sd->grp_id = hw;
sd = v4l2_i2c_new_subdev(&cx->v4l2_dev,
adap, type, 0, cx->card_i2c->demod);
if (sd != NULL)
sd->grp_id = hw;
sd = v4l2_i2c_new_subdev(&cx->v4l2_dev,
adap, type, 0, cx->card_i2c->tv);
if (sd != NULL)
sd->grp_id = hw;
return sd != NULL ? 0 : -1;
}
if (hw & CX18_HW_IR_ANY)
return cx18_i2c_new_ir(cx, adap, hw, type, hw_addrs[idx]);
/* Is it not an I2C device or one we do not wish to register? */
if (!hw_addrs[idx])
return -1;
/* It's an I2C device other than an analog tuner or IR chip */
sd = v4l2_i2c_new_subdev(&cx->v4l2_dev, adap, type, hw_addrs[idx],
NULL);
if (sd != NULL)
sd->grp_id = hw;
return sd != NULL ? 0 : -1;
}
/* Find the first member of the subdev group id in hw */
struct v4l2_subdev *cx18_find_hw(struct cx18 *cx, u32 hw)
{
struct v4l2_subdev *result = NULL;
struct v4l2_subdev *sd;
spin_lock(&cx->v4l2_dev.lock);
v4l2_device_for_each_subdev(sd, &cx->v4l2_dev) {
if (sd->grp_id == hw) {
result = sd;
break;
}
}
spin_unlock(&cx->v4l2_dev.lock);
return result;
}
static void cx18_setscl(void *data, int state)
{
struct cx18 *cx = ((struct cx18_i2c_algo_callback_data *)data)->cx;
int bus_index = ((struct cx18_i2c_algo_callback_data *)data)->bus_index;
u32 addr = bus_index ? CX18_REG_I2C_2_WR : CX18_REG_I2C_1_WR;
u32 r = cx18_read_reg(cx, addr);
if (state)
cx18_write_reg(cx, r | SETSCL_BIT, addr);
else
cx18_write_reg(cx, r & ~SETSCL_BIT, addr);
}
static void cx18_setsda(void *data, int state)
{
struct cx18 *cx = ((struct cx18_i2c_algo_callback_data *)data)->cx;
int bus_index = ((struct cx18_i2c_algo_callback_data *)data)->bus_index;
u32 addr = bus_index ? CX18_REG_I2C_2_WR : CX18_REG_I2C_1_WR;
u32 r = cx18_read_reg(cx, addr);
if (state)
cx18_write_reg(cx, r | SETSDL_BIT, addr);
else
cx18_write_reg(cx, r & ~SETSDL_BIT, addr);
}
static int cx18_getscl(void *data)
{
struct cx18 *cx = ((struct cx18_i2c_algo_callback_data *)data)->cx;
int bus_index = ((struct cx18_i2c_algo_callback_data *)data)->bus_index;
u32 addr = bus_index ? CX18_REG_I2C_2_RD : CX18_REG_I2C_1_RD;
return cx18_read_reg(cx, addr) & GETSCL_BIT;
}
static int cx18_getsda(void *data)
{
struct cx18 *cx = ((struct cx18_i2c_algo_callback_data *)data)->cx;
int bus_index = ((struct cx18_i2c_algo_callback_data *)data)->bus_index;
u32 addr = bus_index ? CX18_REG_I2C_2_RD : CX18_REG_I2C_1_RD;
return cx18_read_reg(cx, addr) & GETSDL_BIT;
}
/* template for i2c-bit-algo */
static struct i2c_adapter cx18_i2c_adap_template = {
.name = "cx18 i2c driver",
.algo = NULL, /* set by i2c-algo-bit */
.algo_data = NULL, /* filled from template */
.owner = THIS_MODULE,
};
#define CX18_SCL_PERIOD (10) /* usecs. 10 usec is period for a 100 KHz clock */
#define CX18_ALGO_BIT_TIMEOUT (2) /* seconds */
static struct i2c_algo_bit_data cx18_i2c_algo_template = {
.setsda = cx18_setsda,
.setscl = cx18_setscl,
.getsda = cx18_getsda,
.getscl = cx18_getscl,
.udelay = CX18_SCL_PERIOD/2, /* 1/2 clock period in usec*/
.timeout = CX18_ALGO_BIT_TIMEOUT*HZ /* jiffies */
};
/* init + register i2c adapter */
int init_cx18_i2c(struct cx18 *cx)
{
int i, err;
CX18_DEBUG_I2C("i2c init\n");
for (i = 0; i < 2; i++) {
/* Setup algorithm for adapter */
cx->i2c_algo[i] = cx18_i2c_algo_template;
cx->i2c_algo_cb_data[i].cx = cx;
cx->i2c_algo_cb_data[i].bus_index = i;
cx->i2c_algo[i].data = &cx->i2c_algo_cb_data[i];
/* Setup adapter */
cx->i2c_adap[i] = cx18_i2c_adap_template;
cx->i2c_adap[i].algo_data = &cx->i2c_algo[i];
sprintf(cx->i2c_adap[i].name + strlen(cx->i2c_adap[i].name),
" #%d-%d", cx->instance, i);
i2c_set_adapdata(&cx->i2c_adap[i], &cx->v4l2_dev);
cx->i2c_adap[i].dev.parent = &cx->pci_dev->dev;
}
if (cx18_read_reg(cx, CX18_REG_I2C_2_WR) != 0x0003c02f) {
/* Reset/Unreset I2C hardware block */
/* Clock select 220MHz */
cx18_write_reg_expect(cx, 0x10000000, 0xc71004,
0x00000000, 0x10001000);
/* Clock Enable */
cx18_write_reg_expect(cx, 0x10001000, 0xc71024,
0x00001000, 0x10001000);
}
/* courtesy of Steven Toth <stoth@hauppauge.com> */
cx18_write_reg_expect(cx, 0x00c00000, 0xc7001c, 0x00000000, 0x00c000c0);
mdelay(10);
cx18_write_reg_expect(cx, 0x00c000c0, 0xc7001c, 0x000000c0, 0x00c000c0);
mdelay(10);
cx18_write_reg_expect(cx, 0x00c00000, 0xc7001c, 0x00000000, 0x00c000c0);
mdelay(10);
/* Set to edge-triggered intrs. */
cx18_write_reg(cx, 0x00c00000, 0xc730c8);
/* Clear any stale intrs */
cx18_write_reg_expect(cx, HW2_I2C1_INT|HW2_I2C2_INT, HW2_INT_CLR_STATUS,
~(HW2_I2C1_INT|HW2_I2C2_INT), HW2_I2C1_INT|HW2_I2C2_INT);
/* Hw I2C1 Clock Freq ~100kHz */
cx18_write_reg(cx, 0x00021c0f & ~4, CX18_REG_I2C_1_WR);
cx18_setscl(&cx->i2c_algo_cb_data[0], 1);
cx18_setsda(&cx->i2c_algo_cb_data[0], 1);
/* Hw I2C2 Clock Freq ~100kHz */
cx18_write_reg(cx, 0x00021c0f & ~4, CX18_REG_I2C_2_WR);
cx18_setscl(&cx->i2c_algo_cb_data[1], 1);
cx18_setsda(&cx->i2c_algo_cb_data[1], 1);
cx18_call_hw(cx, CX18_HW_GPIO_RESET_CTRL,
core, reset, (u32) CX18_GPIO_RESET_I2C);
err = i2c_bit_add_bus(&cx->i2c_adap[0]);
if (err)
goto err;
err = i2c_bit_add_bus(&cx->i2c_adap[1]);
if (err)
goto err_del_bus_0;
return 0;
err_del_bus_0:
i2c_del_adapter(&cx->i2c_adap[0]);
err:
return err;
}
void exit_cx18_i2c(struct cx18 *cx)
{
int i;
CX18_DEBUG_I2C("i2c exit\n");
cx18_write_reg(cx, cx18_read_reg(cx, CX18_REG_I2C_1_WR) | 4,
CX18_REG_I2C_1_WR);
cx18_write_reg(cx, cx18_read_reg(cx, CX18_REG_I2C_2_WR) | 4,
CX18_REG_I2C_2_WR);
for (i = 0; i < 2; i++) {
i2c_del_adapter(&cx->i2c_adap[i]);
}
}
/*
Hauppauge HVR1600 should have:
32 cx24227
98 unknown
a0 eeprom
c2 tuner
e? zilog ir
*/

View file

@ -0,0 +1,29 @@
/*
* cx18 I2C functions
*
* Derived from ivtv-i2c.h
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* 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
*/
int cx18_i2c_register(struct cx18 *cx, unsigned idx);
struct v4l2_subdev *cx18_find_hw(struct cx18 *cx, u32 hw);
/* init + register i2c adapter */
int init_cx18_i2c(struct cx18 *cx);
void exit_cx18_i2c(struct cx18 *cx);

View file

@ -0,0 +1,97 @@
/*
* cx18 driver PCI memory mapped IO access routines
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* 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 "cx18-driver.h"
#include "cx18-io.h"
#include "cx18-irq.h"
void cx18_memset_io(struct cx18 *cx, void __iomem *addr, int val, size_t count)
{
u8 __iomem *dst = addr;
u16 val2 = val | (val << 8);
u32 val4 = val2 | (val2 << 16);
/* Align writes on the CX23418's addresses */
if ((count > 0) && ((unsigned long)dst & 1)) {
cx18_writeb(cx, (u8) val, dst);
count--;
dst++;
}
if ((count > 1) && ((unsigned long)dst & 2)) {
cx18_writew(cx, val2, dst);
count -= 2;
dst += 2;
}
while (count > 3) {
cx18_writel(cx, val4, dst);
count -= 4;
dst += 4;
}
if (count > 1) {
cx18_writew(cx, val2, dst);
count -= 2;
dst += 2;
}
if (count > 0)
cx18_writeb(cx, (u8) val, dst);
}
void cx18_sw1_irq_enable(struct cx18 *cx, u32 val)
{
cx18_write_reg_expect(cx, val, SW1_INT_STATUS, ~val, val);
cx->sw1_irq_mask = cx18_read_reg(cx, SW1_INT_ENABLE_PCI) | val;
cx18_write_reg(cx, cx->sw1_irq_mask, SW1_INT_ENABLE_PCI);
}
void cx18_sw1_irq_disable(struct cx18 *cx, u32 val)
{
cx->sw1_irq_mask = cx18_read_reg(cx, SW1_INT_ENABLE_PCI) & ~val;
cx18_write_reg(cx, cx->sw1_irq_mask, SW1_INT_ENABLE_PCI);
}
void cx18_sw2_irq_enable(struct cx18 *cx, u32 val)
{
cx18_write_reg_expect(cx, val, SW2_INT_STATUS, ~val, val);
cx->sw2_irq_mask = cx18_read_reg(cx, SW2_INT_ENABLE_PCI) | val;
cx18_write_reg(cx, cx->sw2_irq_mask, SW2_INT_ENABLE_PCI);
}
void cx18_sw2_irq_disable(struct cx18 *cx, u32 val)
{
cx->sw2_irq_mask = cx18_read_reg(cx, SW2_INT_ENABLE_PCI) & ~val;
cx18_write_reg(cx, cx->sw2_irq_mask, SW2_INT_ENABLE_PCI);
}
void cx18_sw2_irq_disable_cpu(struct cx18 *cx, u32 val)
{
u32 r;
r = cx18_read_reg(cx, SW2_INT_ENABLE_CPU);
cx18_write_reg(cx, r & ~val, SW2_INT_ENABLE_CPU);
}
void cx18_setup_page(struct cx18 *cx, u32 addr)
{
u32 val;
val = cx18_read_reg(cx, 0xD000F8);
val = (val & ~0x1f00) | ((addr >> 17) & 0x1f00);
cx18_write_reg(cx, val, 0xD000F8);
}

View file

@ -0,0 +1,191 @@
/*
* cx18 driver PCI memory mapped IO access routines
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* 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
*/
#ifndef CX18_IO_H
#define CX18_IO_H
#include "cx18-driver.h"
/*
* Readback and retry of MMIO access for reliability:
* The concept was suggested by Steve Toth <stoth@linuxtv.org>.
* The implmentation is the fault of Andy Walls <awalls@md.metrocast.net>.
*
* *write* functions are implied to retry the mmio unless suffixed with _noretry
* *read* functions never retry the mmio (it never helps to do so)
*/
/* Non byteswapping memory mapped IO */
static inline u32 cx18_raw_readl(struct cx18 *cx, const void __iomem *addr)
{
return __raw_readl(addr);
}
static inline
void cx18_raw_writel_noretry(struct cx18 *cx, u32 val, void __iomem *addr)
{
__raw_writel(val, addr);
}
static inline void cx18_raw_writel(struct cx18 *cx, u32 val, void __iomem *addr)
{
int i;
for (i = 0; i < CX18_MAX_MMIO_WR_RETRIES; i++) {
cx18_raw_writel_noretry(cx, val, addr);
if (val == cx18_raw_readl(cx, addr))
break;
}
}
/* Normal memory mapped IO */
static inline u32 cx18_readl(struct cx18 *cx, const void __iomem *addr)
{
return readl(addr);
}
static inline
void cx18_writel_noretry(struct cx18 *cx, u32 val, void __iomem *addr)
{
writel(val, addr);
}
static inline void cx18_writel(struct cx18 *cx, u32 val, void __iomem *addr)
{
int i;
for (i = 0; i < CX18_MAX_MMIO_WR_RETRIES; i++) {
cx18_writel_noretry(cx, val, addr);
if (val == cx18_readl(cx, addr))
break;
}
}
static inline
void cx18_writel_expect(struct cx18 *cx, u32 val, void __iomem *addr,
u32 eval, u32 mask)
{
int i;
u32 r;
eval &= mask;
for (i = 0; i < CX18_MAX_MMIO_WR_RETRIES; i++) {
cx18_writel_noretry(cx, val, addr);
r = cx18_readl(cx, addr);
if (r == 0xffffffff && eval != 0xffffffff)
continue;
if (eval == (r & mask))
break;
}
}
static inline u16 cx18_readw(struct cx18 *cx, const void __iomem *addr)
{
return readw(addr);
}
static inline
void cx18_writew_noretry(struct cx18 *cx, u16 val, void __iomem *addr)
{
writew(val, addr);
}
static inline void cx18_writew(struct cx18 *cx, u16 val, void __iomem *addr)
{
int i;
for (i = 0; i < CX18_MAX_MMIO_WR_RETRIES; i++) {
cx18_writew_noretry(cx, val, addr);
if (val == cx18_readw(cx, addr))
break;
}
}
static inline u8 cx18_readb(struct cx18 *cx, const void __iomem *addr)
{
return readb(addr);
}
static inline
void cx18_writeb_noretry(struct cx18 *cx, u8 val, void __iomem *addr)
{
writeb(val, addr);
}
static inline void cx18_writeb(struct cx18 *cx, u8 val, void __iomem *addr)
{
int i;
for (i = 0; i < CX18_MAX_MMIO_WR_RETRIES; i++) {
cx18_writeb_noretry(cx, val, addr);
if (val == cx18_readb(cx, addr))
break;
}
}
static inline
void cx18_memcpy_fromio(struct cx18 *cx, void *to,
const void __iomem *from, unsigned int len)
{
memcpy_fromio(to, from, len);
}
void cx18_memset_io(struct cx18 *cx, void __iomem *addr, int val, size_t count);
/* Access "register" region of CX23418 memory mapped I/O */
static inline void cx18_write_reg_noretry(struct cx18 *cx, u32 val, u32 reg)
{
cx18_writel_noretry(cx, val, cx->reg_mem + reg);
}
static inline void cx18_write_reg(struct cx18 *cx, u32 val, u32 reg)
{
cx18_writel(cx, val, cx->reg_mem + reg);
}
static inline void cx18_write_reg_expect(struct cx18 *cx, u32 val, u32 reg,
u32 eval, u32 mask)
{
cx18_writel_expect(cx, val, cx->reg_mem + reg, eval, mask);
}
static inline u32 cx18_read_reg(struct cx18 *cx, u32 reg)
{
return cx18_readl(cx, cx->reg_mem + reg);
}
/* Access "encoder memory" region of CX23418 memory mapped I/O */
static inline void cx18_write_enc(struct cx18 *cx, u32 val, u32 addr)
{
cx18_writel(cx, val, cx->enc_mem + addr);
}
static inline u32 cx18_read_enc(struct cx18 *cx, u32 addr)
{
return cx18_readl(cx, cx->enc_mem + addr);
}
void cx18_sw1_irq_enable(struct cx18 *cx, u32 val);
void cx18_sw1_irq_disable(struct cx18 *cx, u32 val);
void cx18_sw2_irq_enable(struct cx18 *cx, u32 val);
void cx18_sw2_irq_disable(struct cx18 *cx, u32 val);
void cx18_sw2_irq_disable_cpu(struct cx18 *cx, u32 val);
void cx18_setup_page(struct cx18 *cx, u32 addr);
#endif /* CX18_IO_H */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,31 @@
/*
* cx18 ioctl system call
*
* Derived from ivtv-ioctl.h
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* 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
*/
u16 cx18_service2vbi(int type);
void cx18_expand_service_set(struct v4l2_sliced_vbi_format *fmt, int is_pal);
u16 cx18_get_service_set(struct v4l2_sliced_vbi_format *fmt);
void cx18_set_funcs(struct video_device *vdev);
int cx18_s_std(struct file *file, void *fh, v4l2_std_id std);
int cx18_s_frequency(struct file *file, void *fh, const struct v4l2_frequency *vf);
int cx18_s_input(struct file *file, void *fh, unsigned int inp);

View file

@ -0,0 +1,81 @@
/*
* cx18 interrupt handling
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* 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 "cx18-driver.h"
#include "cx18-io.h"
#include "cx18-irq.h"
#include "cx18-mailbox.h"
#include "cx18-scb.h"
static void xpu_ack(struct cx18 *cx, u32 sw2)
{
if (sw2 & IRQ_CPU_TO_EPU_ACK)
wake_up(&cx->mb_cpu_waitq);
if (sw2 & IRQ_APU_TO_EPU_ACK)
wake_up(&cx->mb_apu_waitq);
}
static void epu_cmd(struct cx18 *cx, u32 sw1)
{
if (sw1 & IRQ_CPU_TO_EPU)
cx18_api_epu_cmd_irq(cx, CPU);
if (sw1 & IRQ_APU_TO_EPU)
cx18_api_epu_cmd_irq(cx, APU);
}
irqreturn_t cx18_irq_handler(int irq, void *dev_id)
{
struct cx18 *cx = (struct cx18 *)dev_id;
u32 sw1, sw2, hw2;
sw1 = cx18_read_reg(cx, SW1_INT_STATUS) & cx->sw1_irq_mask;
sw2 = cx18_read_reg(cx, SW2_INT_STATUS) & cx->sw2_irq_mask;
hw2 = cx18_read_reg(cx, HW2_INT_CLR_STATUS) & cx->hw2_irq_mask;
if (sw1)
cx18_write_reg_expect(cx, sw1, SW1_INT_STATUS, ~sw1, sw1);
if (sw2)
cx18_write_reg_expect(cx, sw2, SW2_INT_STATUS, ~sw2, sw2);
if (hw2)
cx18_write_reg_expect(cx, hw2, HW2_INT_CLR_STATUS, ~hw2, hw2);
if (sw1 || sw2 || hw2)
CX18_DEBUG_HI_IRQ("received interrupts "
"SW1: %x SW2: %x HW2: %x\n", sw1, sw2, hw2);
/*
* SW1 responses have to happen first. The sending XPU times out the
* incoming mailboxes on us rather rapidly.
*/
if (sw1)
epu_cmd(cx, sw1);
/* To do: interrupt-based I2C handling
if (hw2 & (HW2_I2C1_INT|HW2_I2C2_INT)) {
}
*/
if (sw2)
xpu_ack(cx, sw2);
return (sw1 || sw2 || hw2) ? IRQ_HANDLED : IRQ_NONE;
}

View file

@ -0,0 +1,35 @@
/*
* cx18 interrupt handling
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* 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
*/
#define HW2_I2C1_INT (1 << 22)
#define HW2_I2C2_INT (1 << 23)
#define HW2_INT_CLR_STATUS 0xc730c4
#define HW2_INT_MASK5_PCI 0xc730e4
#define SW1_INT_SET 0xc73100
#define SW1_INT_STATUS 0xc73104
#define SW1_INT_ENABLE_PCI 0xc7311c
#define SW2_INT_SET 0xc73140
#define SW2_INT_STATUS 0xc73144
#define SW2_INT_ENABLE_CPU 0xc73158
#define SW2_INT_ENABLE_PCI 0xc7315c
irqreturn_t cx18_irq_handler(int irq, void *dev_id);

View file

@ -0,0 +1,870 @@
/*
* cx18 mailbox functions
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* 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 <stdarg.h>
#include "cx18-driver.h"
#include "cx18-io.h"
#include "cx18-scb.h"
#include "cx18-irq.h"
#include "cx18-mailbox.h"
#include "cx18-queue.h"
#include "cx18-streams.h"
#include "cx18-alsa-pcm.h" /* FIXME make configurable */
static const char *rpu_str[] = { "APU", "CPU", "EPU", "HPU" };
#define API_FAST (1 << 2) /* Short timeout */
#define API_SLOW (1 << 3) /* Additional 300ms timeout */
struct cx18_api_info {
u32 cmd;
u8 flags; /* Flags, see above */
u8 rpu; /* Processing unit */
const char *name; /* The name of the command */
};
#define API_ENTRY(rpu, x, f) { (x), (f), (rpu), #x }
static const struct cx18_api_info api_info[] = {
/* MPEG encoder API */
API_ENTRY(CPU, CX18_CPU_SET_CHANNEL_TYPE, 0),
API_ENTRY(CPU, CX18_EPU_DEBUG, 0),
API_ENTRY(CPU, CX18_CREATE_TASK, 0),
API_ENTRY(CPU, CX18_DESTROY_TASK, 0),
API_ENTRY(CPU, CX18_CPU_CAPTURE_START, API_SLOW),
API_ENTRY(CPU, CX18_CPU_CAPTURE_STOP, API_SLOW),
API_ENTRY(CPU, CX18_CPU_CAPTURE_PAUSE, 0),
API_ENTRY(CPU, CX18_CPU_CAPTURE_RESUME, 0),
API_ENTRY(CPU, CX18_CPU_SET_CHANNEL_TYPE, 0),
API_ENTRY(CPU, CX18_CPU_SET_STREAM_OUTPUT_TYPE, 0),
API_ENTRY(CPU, CX18_CPU_SET_VIDEO_IN, 0),
API_ENTRY(CPU, CX18_CPU_SET_VIDEO_RATE, 0),
API_ENTRY(CPU, CX18_CPU_SET_VIDEO_RESOLUTION, 0),
API_ENTRY(CPU, CX18_CPU_SET_FILTER_PARAM, 0),
API_ENTRY(CPU, CX18_CPU_SET_SPATIAL_FILTER_TYPE, 0),
API_ENTRY(CPU, CX18_CPU_SET_MEDIAN_CORING, 0),
API_ENTRY(CPU, CX18_CPU_SET_INDEXTABLE, 0),
API_ENTRY(CPU, CX18_CPU_SET_AUDIO_PARAMETERS, 0),
API_ENTRY(CPU, CX18_CPU_SET_VIDEO_MUTE, 0),
API_ENTRY(CPU, CX18_CPU_SET_AUDIO_MUTE, 0),
API_ENTRY(CPU, CX18_CPU_SET_MISC_PARAMETERS, 0),
API_ENTRY(CPU, CX18_CPU_SET_RAW_VBI_PARAM, API_SLOW),
API_ENTRY(CPU, CX18_CPU_SET_CAPTURE_LINE_NO, 0),
API_ENTRY(CPU, CX18_CPU_SET_COPYRIGHT, 0),
API_ENTRY(CPU, CX18_CPU_SET_AUDIO_PID, 0),
API_ENTRY(CPU, CX18_CPU_SET_VIDEO_PID, 0),
API_ENTRY(CPU, CX18_CPU_SET_VER_CROP_LINE, 0),
API_ENTRY(CPU, CX18_CPU_SET_GOP_STRUCTURE, 0),
API_ENTRY(CPU, CX18_CPU_SET_SCENE_CHANGE_DETECTION, 0),
API_ENTRY(CPU, CX18_CPU_SET_ASPECT_RATIO, 0),
API_ENTRY(CPU, CX18_CPU_SET_SKIP_INPUT_FRAME, 0),
API_ENTRY(CPU, CX18_CPU_SET_SLICED_VBI_PARAM, 0),
API_ENTRY(CPU, CX18_CPU_SET_USERDATA_PLACE_HOLDER, 0),
API_ENTRY(CPU, CX18_CPU_GET_ENC_PTS, 0),
API_ENTRY(CPU, CX18_CPU_SET_VFC_PARAM, 0),
API_ENTRY(CPU, CX18_CPU_DE_SET_MDL_ACK, 0),
API_ENTRY(CPU, CX18_CPU_DE_SET_MDL, API_FAST),
API_ENTRY(CPU, CX18_CPU_DE_RELEASE_MDL, API_SLOW),
API_ENTRY(APU, CX18_APU_START, 0),
API_ENTRY(APU, CX18_APU_STOP, 0),
API_ENTRY(APU, CX18_APU_RESETAI, 0),
API_ENTRY(CPU, CX18_CPU_DEBUG_PEEK32, 0),
API_ENTRY(0, 0, 0),
};
static const struct cx18_api_info *find_api_info(u32 cmd)
{
int i;
for (i = 0; api_info[i].cmd; i++)
if (api_info[i].cmd == cmd)
return &api_info[i];
return NULL;
}
/* Call with buf of n*11+1 bytes */
static char *u32arr2hex(u32 data[], int n, char *buf)
{
char *p;
int i;
for (i = 0, p = buf; i < n; i++, p += 11) {
/* kernel snprintf() appends '\0' always */
snprintf(p, 12, " %#010x", data[i]);
}
*p = '\0';
return buf;
}
static void dump_mb(struct cx18 *cx, struct cx18_mailbox *mb, char *name)
{
char argstr[MAX_MB_ARGUMENTS*11+1];
if (!(cx18_debug & CX18_DBGFLG_API))
return;
CX18_DEBUG_API("%s: req %#010x ack %#010x cmd %#010x err %#010x args%s"
"\n", name, mb->request, mb->ack, mb->cmd, mb->error,
u32arr2hex(mb->args, MAX_MB_ARGUMENTS, argstr));
}
/*
* Functions that run in a work_queue work handling context
*/
static void cx18_mdl_send_to_dvb(struct cx18_stream *s, struct cx18_mdl *mdl)
{
struct cx18_buffer *buf;
if (s->dvb == NULL || !s->dvb->enabled || mdl->bytesused == 0)
return;
/* We ignore mdl and buf readpos accounting here - it doesn't matter */
/* The likely case */
if (list_is_singular(&mdl->buf_list)) {
buf = list_first_entry(&mdl->buf_list, struct cx18_buffer,
list);
if (buf->bytesused)
dvb_dmx_swfilter(&s->dvb->demux,
buf->buf, buf->bytesused);
return;
}
list_for_each_entry(buf, &mdl->buf_list, list) {
if (buf->bytesused == 0)
break;
dvb_dmx_swfilter(&s->dvb->demux, buf->buf, buf->bytesused);
}
}
static void cx18_mdl_send_to_videobuf(struct cx18_stream *s,
struct cx18_mdl *mdl)
{
struct cx18_videobuf_buffer *vb_buf;
struct cx18_buffer *buf;
u8 *p;
u32 offset = 0;
int dispatch = 0;
if (mdl->bytesused == 0)
return;
/* Acquire a videobuf buffer, clone to and and release it */
spin_lock(&s->vb_lock);
if (list_empty(&s->vb_capture))
goto out;
vb_buf = list_first_entry(&s->vb_capture, struct cx18_videobuf_buffer,
vb.queue);
p = videobuf_to_vmalloc(&vb_buf->vb);
if (!p)
goto out;
offset = vb_buf->bytes_used;
list_for_each_entry(buf, &mdl->buf_list, list) {
if (buf->bytesused == 0)
break;
if ((offset + buf->bytesused) <= vb_buf->vb.bsize) {
memcpy(p + offset, buf->buf, buf->bytesused);
offset += buf->bytesused;
vb_buf->bytes_used += buf->bytesused;
}
}
/* If we've filled the buffer as per the callers res then dispatch it */
if (vb_buf->bytes_used >= s->vb_bytes_per_frame) {
dispatch = 1;
vb_buf->bytes_used = 0;
}
if (dispatch) {
vb_buf->vb.ts = ktime_to_timeval(ktime_get());
list_del(&vb_buf->vb.queue);
vb_buf->vb.state = VIDEOBUF_DONE;
wake_up(&vb_buf->vb.done);
}
mod_timer(&s->vb_timeout, msecs_to_jiffies(2000) + jiffies);
out:
spin_unlock(&s->vb_lock);
}
static void cx18_mdl_send_to_alsa(struct cx18 *cx, struct cx18_stream *s,
struct cx18_mdl *mdl)
{
struct cx18_buffer *buf;
if (mdl->bytesused == 0)
return;
/* We ignore mdl and buf readpos accounting here - it doesn't matter */
/* The likely case */
if (list_is_singular(&mdl->buf_list)) {
buf = list_first_entry(&mdl->buf_list, struct cx18_buffer,
list);
if (buf->bytesused)
cx->pcm_announce_callback(cx->alsa, buf->buf,
buf->bytesused);
return;
}
list_for_each_entry(buf, &mdl->buf_list, list) {
if (buf->bytesused == 0)
break;
cx->pcm_announce_callback(cx->alsa, buf->buf, buf->bytesused);
}
}
static void epu_dma_done(struct cx18 *cx, struct cx18_in_work_order *order)
{
u32 handle, mdl_ack_count, id;
struct cx18_mailbox *mb;
struct cx18_mdl_ack *mdl_ack;
struct cx18_stream *s;
struct cx18_mdl *mdl;
int i;
mb = &order->mb;
handle = mb->args[0];
s = cx18_handle_to_stream(cx, handle);
if (s == NULL) {
CX18_WARN("Got DMA done notification for unknown/inactive"
" handle %d, %s mailbox seq no %d\n", handle,
(order->flags & CX18_F_EWO_MB_STALE_UPON_RECEIPT) ?
"stale" : "good", mb->request);
return;
}
mdl_ack_count = mb->args[2];
mdl_ack = order->mdl_ack;
for (i = 0; i < mdl_ack_count; i++, mdl_ack++) {
id = mdl_ack->id;
/*
* Simple integrity check for processing a stale (and possibly
* inconsistent mailbox): make sure the MDL id is in the
* valid range for the stream.
*
* We go through the trouble of dealing with stale mailboxes
* because most of the time, the mailbox data is still valid and
* unchanged (and in practice the firmware ping-pongs the
* two mdl_ack buffers so mdl_acks are not stale).
*
* There are occasions when we get a half changed mailbox,
* which this check catches for a handle & id mismatch. If the
* handle and id do correspond, the worst case is that we
* completely lost the old MDL, but pick up the new MDL
* early (but the new mdl_ack is guaranteed to be good in this
* case as the firmware wouldn't point us to a new mdl_ack until
* it's filled in).
*
* cx18_queue_get_mdl() will detect the lost MDLs
* and send them back to q_free for fw rotation eventually.
*/
if ((order->flags & CX18_F_EWO_MB_STALE_UPON_RECEIPT) &&
!(id >= s->mdl_base_idx &&
id < (s->mdl_base_idx + s->buffers))) {
CX18_WARN("Fell behind! Ignoring stale mailbox with "
" inconsistent data. Lost MDL for mailbox "
"seq no %d\n", mb->request);
break;
}
mdl = cx18_queue_get_mdl(s, id, mdl_ack->data_used);
CX18_DEBUG_HI_DMA("DMA DONE for %s (MDL %d)\n", s->name, id);
if (mdl == NULL) {
CX18_WARN("Could not find MDL %d for stream %s\n",
id, s->name);
continue;
}
CX18_DEBUG_HI_DMA("%s recv bytesused = %d\n",
s->name, mdl->bytesused);
if (s->type == CX18_ENC_STREAM_TYPE_TS) {
cx18_mdl_send_to_dvb(s, mdl);
cx18_enqueue(s, mdl, &s->q_free);
} else if (s->type == CX18_ENC_STREAM_TYPE_PCM) {
/* Pass the data to cx18-alsa */
if (cx->pcm_announce_callback != NULL) {
cx18_mdl_send_to_alsa(cx, s, mdl);
cx18_enqueue(s, mdl, &s->q_free);
} else {
cx18_enqueue(s, mdl, &s->q_full);
}
} else if (s->type == CX18_ENC_STREAM_TYPE_YUV) {
cx18_mdl_send_to_videobuf(s, mdl);
cx18_enqueue(s, mdl, &s->q_free);
} else {
cx18_enqueue(s, mdl, &s->q_full);
if (s->type == CX18_ENC_STREAM_TYPE_IDX)
cx18_stream_rotate_idx_mdls(cx);
}
}
/* Put as many MDLs as possible back into fw use */
cx18_stream_load_fw_queue(s);
wake_up(&cx->dma_waitq);
if (s->id != -1)
wake_up(&s->waitq);
}
static void epu_debug(struct cx18 *cx, struct cx18_in_work_order *order)
{
char *p;
char *str = order->str;
CX18_DEBUG_INFO("%x %s\n", order->mb.args[0], str);
p = strchr(str, '.');
if (!test_bit(CX18_F_I_LOADED_FW, &cx->i_flags) && p && p > str)
CX18_INFO("FW version: %s\n", p - 1);
}
static void epu_cmd(struct cx18 *cx, struct cx18_in_work_order *order)
{
switch (order->rpu) {
case CPU:
{
switch (order->mb.cmd) {
case CX18_EPU_DMA_DONE:
epu_dma_done(cx, order);
break;
case CX18_EPU_DEBUG:
epu_debug(cx, order);
break;
default:
CX18_WARN("Unknown CPU to EPU mailbox command %#0x\n",
order->mb.cmd);
break;
}
break;
}
case APU:
CX18_WARN("Unknown APU to EPU mailbox command %#0x\n",
order->mb.cmd);
break;
default:
break;
}
}
static
void free_in_work_order(struct cx18 *cx, struct cx18_in_work_order *order)
{
atomic_set(&order->pending, 0);
}
void cx18_in_work_handler(struct work_struct *work)
{
struct cx18_in_work_order *order =
container_of(work, struct cx18_in_work_order, work);
struct cx18 *cx = order->cx;
epu_cmd(cx, order);
free_in_work_order(cx, order);
}
/*
* Functions that run in an interrupt handling context
*/
static void mb_ack_irq(struct cx18 *cx, struct cx18_in_work_order *order)
{
struct cx18_mailbox __iomem *ack_mb;
u32 ack_irq, req;
switch (order->rpu) {
case APU:
ack_irq = IRQ_EPU_TO_APU_ACK;
ack_mb = &cx->scb->apu2epu_mb;
break;
case CPU:
ack_irq = IRQ_EPU_TO_CPU_ACK;
ack_mb = &cx->scb->cpu2epu_mb;
break;
default:
CX18_WARN("Unhandled RPU (%d) for command %x ack\n",
order->rpu, order->mb.cmd);
return;
}
req = order->mb.request;
/* Don't ack if the RPU has gotten impatient and timed us out */
if (req != cx18_readl(cx, &ack_mb->request) ||
req == cx18_readl(cx, &ack_mb->ack)) {
CX18_DEBUG_WARN("Possibly falling behind: %s self-ack'ed our "
"incoming %s to EPU mailbox (sequence no. %u) "
"while processing\n",
rpu_str[order->rpu], rpu_str[order->rpu], req);
order->flags |= CX18_F_EWO_MB_STALE_WHILE_PROC;
return;
}
cx18_writel(cx, req, &ack_mb->ack);
cx18_write_reg_expect(cx, ack_irq, SW2_INT_SET, ack_irq, ack_irq);
return;
}
static int epu_dma_done_irq(struct cx18 *cx, struct cx18_in_work_order *order)
{
u32 handle, mdl_ack_offset, mdl_ack_count;
struct cx18_mailbox *mb;
int i;
mb = &order->mb;
handle = mb->args[0];
mdl_ack_offset = mb->args[1];
mdl_ack_count = mb->args[2];
if (handle == CX18_INVALID_TASK_HANDLE ||
mdl_ack_count == 0 || mdl_ack_count > CX18_MAX_MDL_ACKS) {
if ((order->flags & CX18_F_EWO_MB_STALE) == 0)
mb_ack_irq(cx, order);
return -1;
}
for (i = 0; i < sizeof(struct cx18_mdl_ack) * mdl_ack_count; i += sizeof(u32))
((u32 *)order->mdl_ack)[i / sizeof(u32)] =
cx18_readl(cx, cx->enc_mem + mdl_ack_offset + i);
if ((order->flags & CX18_F_EWO_MB_STALE) == 0)
mb_ack_irq(cx, order);
return 1;
}
static
int epu_debug_irq(struct cx18 *cx, struct cx18_in_work_order *order)
{
u32 str_offset;
char *str = order->str;
str[0] = '\0';
str_offset = order->mb.args[1];
if (str_offset) {
cx18_setup_page(cx, str_offset);
cx18_memcpy_fromio(cx, str, cx->enc_mem + str_offset, 252);
str[252] = '\0';
cx18_setup_page(cx, SCB_OFFSET);
}
if ((order->flags & CX18_F_EWO_MB_STALE) == 0)
mb_ack_irq(cx, order);
return str_offset ? 1 : 0;
}
static inline
int epu_cmd_irq(struct cx18 *cx, struct cx18_in_work_order *order)
{
int ret = -1;
switch (order->rpu) {
case CPU:
{
switch (order->mb.cmd) {
case CX18_EPU_DMA_DONE:
ret = epu_dma_done_irq(cx, order);
break;
case CX18_EPU_DEBUG:
ret = epu_debug_irq(cx, order);
break;
default:
CX18_WARN("Unknown CPU to EPU mailbox command %#0x\n",
order->mb.cmd);
break;
}
break;
}
case APU:
CX18_WARN("Unknown APU to EPU mailbox command %#0x\n",
order->mb.cmd);
break;
default:
break;
}
return ret;
}
static inline
struct cx18_in_work_order *alloc_in_work_order_irq(struct cx18 *cx)
{
int i;
struct cx18_in_work_order *order = NULL;
for (i = 0; i < CX18_MAX_IN_WORK_ORDERS; i++) {
/*
* We only need "pending" atomic to inspect its contents,
* and need not do a check and set because:
* 1. Any work handler thread only clears "pending" and only
* on one, particular work order at a time, per handler thread.
* 2. "pending" is only set here, and we're serialized because
* we're called in an IRQ handler context.
*/
if (atomic_read(&cx->in_work_order[i].pending) == 0) {
order = &cx->in_work_order[i];
atomic_set(&order->pending, 1);
break;
}
}
return order;
}
void cx18_api_epu_cmd_irq(struct cx18 *cx, int rpu)
{
struct cx18_mailbox __iomem *mb;
struct cx18_mailbox *order_mb;
struct cx18_in_work_order *order;
int submit;
int i;
switch (rpu) {
case CPU:
mb = &cx->scb->cpu2epu_mb;
break;
case APU:
mb = &cx->scb->apu2epu_mb;
break;
default:
return;
}
order = alloc_in_work_order_irq(cx);
if (order == NULL) {
CX18_WARN("Unable to find blank work order form to schedule "
"incoming mailbox command processing\n");
return;
}
order->flags = 0;
order->rpu = rpu;
order_mb = &order->mb;
/* mb->cmd and mb->args[0] through mb->args[2] */
for (i = 0; i < 4; i++)
(&order_mb->cmd)[i] = cx18_readl(cx, &mb->cmd + i);
/* mb->request and mb->ack. N.B. we want to read mb->ack last */
for (i = 0; i < 2; i++)
(&order_mb->request)[i] = cx18_readl(cx, &mb->request + i);
if (order_mb->request == order_mb->ack) {
CX18_DEBUG_WARN("Possibly falling behind: %s self-ack'ed our "
"incoming %s to EPU mailbox (sequence no. %u)"
"\n",
rpu_str[rpu], rpu_str[rpu], order_mb->request);
if (cx18_debug & CX18_DBGFLG_WARN)
dump_mb(cx, order_mb, "incoming");
order->flags = CX18_F_EWO_MB_STALE_UPON_RECEIPT;
}
/*
* Individual EPU command processing is responsible for ack-ing
* a non-stale mailbox as soon as possible
*/
submit = epu_cmd_irq(cx, order);
if (submit > 0) {
queue_work(cx->in_work_queue, &order->work);
}
}
/*
* Functions called from a non-interrupt, non work_queue context
*/
static int cx18_api_call(struct cx18 *cx, u32 cmd, int args, u32 data[])
{
const struct cx18_api_info *info = find_api_info(cmd);
u32 irq, req, ack, err;
struct cx18_mailbox __iomem *mb;
wait_queue_head_t *waitq;
struct mutex *mb_lock;
unsigned long int t0, timeout, ret;
int i;
char argstr[MAX_MB_ARGUMENTS*11+1];
DEFINE_WAIT(w);
if (info == NULL) {
CX18_WARN("unknown cmd %x\n", cmd);
return -EINVAL;
}
if (cx18_debug & CX18_DBGFLG_API) { /* only call u32arr2hex if needed */
if (cmd == CX18_CPU_DE_SET_MDL) {
if (cx18_debug & CX18_DBGFLG_HIGHVOL)
CX18_DEBUG_HI_API("%s\tcmd %#010x args%s\n",
info->name, cmd,
u32arr2hex(data, args, argstr));
} else
CX18_DEBUG_API("%s\tcmd %#010x args%s\n",
info->name, cmd,
u32arr2hex(data, args, argstr));
}
switch (info->rpu) {
case APU:
waitq = &cx->mb_apu_waitq;
mb_lock = &cx->epu2apu_mb_lock;
irq = IRQ_EPU_TO_APU;
mb = &cx->scb->epu2apu_mb;
break;
case CPU:
waitq = &cx->mb_cpu_waitq;
mb_lock = &cx->epu2cpu_mb_lock;
irq = IRQ_EPU_TO_CPU;
mb = &cx->scb->epu2cpu_mb;
break;
default:
CX18_WARN("Unknown RPU (%d) for API call\n", info->rpu);
return -EINVAL;
}
mutex_lock(mb_lock);
/*
* Wait for an in-use mailbox to complete
*
* If the XPU is responding with Ack's, the mailbox shouldn't be in
* a busy state, since we serialize access to it on our end.
*
* If the wait for ack after sending a previous command was interrupted
* by a signal, we may get here and find a busy mailbox. After waiting,
* mark it "not busy" from our end, if the XPU hasn't ack'ed it still.
*/
req = cx18_readl(cx, &mb->request);
timeout = msecs_to_jiffies(10);
ret = wait_event_timeout(*waitq,
(ack = cx18_readl(cx, &mb->ack)) == req,
timeout);
if (req != ack) {
/* waited long enough, make the mbox "not busy" from our end */
cx18_writel(cx, req, &mb->ack);
CX18_ERR("mbox was found stuck busy when setting up for %s; "
"clearing busy and trying to proceed\n", info->name);
} else if (ret != timeout)
CX18_DEBUG_API("waited %u msecs for busy mbox to be acked\n",
jiffies_to_msecs(timeout-ret));
/* Build the outgoing mailbox */
req = ((req & 0xfffffffe) == 0xfffffffe) ? 1 : req + 1;
cx18_writel(cx, cmd, &mb->cmd);
for (i = 0; i < args; i++)
cx18_writel(cx, data[i], &mb->args[i]);
cx18_writel(cx, 0, &mb->error);
cx18_writel(cx, req, &mb->request);
cx18_writel(cx, req - 1, &mb->ack); /* ensure ack & req are distinct */
/*
* Notify the XPU and wait for it to send an Ack back
*/
timeout = msecs_to_jiffies((info->flags & API_FAST) ? 10 : 20);
CX18_DEBUG_HI_IRQ("sending interrupt SW1: %x to send %s\n",
irq, info->name);
/* So we don't miss the wakeup, prepare to wait before notifying fw */
prepare_to_wait(waitq, &w, TASK_UNINTERRUPTIBLE);
cx18_write_reg_expect(cx, irq, SW1_INT_SET, irq, irq);
t0 = jiffies;
ack = cx18_readl(cx, &mb->ack);
if (ack != req) {
schedule_timeout(timeout);
ret = jiffies - t0;
ack = cx18_readl(cx, &mb->ack);
} else {
ret = jiffies - t0;
}
finish_wait(waitq, &w);
if (req != ack) {
mutex_unlock(mb_lock);
if (ret >= timeout) {
/* Timed out */
CX18_DEBUG_WARN("sending %s timed out waiting %d msecs "
"for RPU acknowledgement\n",
info->name, jiffies_to_msecs(ret));
} else {
CX18_DEBUG_WARN("woken up before mailbox ack was ready "
"after submitting %s to RPU. only "
"waited %d msecs on req %u but awakened"
" with unmatched ack %u\n",
info->name,
jiffies_to_msecs(ret),
req, ack);
}
return -EINVAL;
}
if (ret >= timeout)
CX18_DEBUG_WARN("failed to be awakened upon RPU acknowledgment "
"sending %s; timed out waiting %d msecs\n",
info->name, jiffies_to_msecs(ret));
else
CX18_DEBUG_HI_API("waited %u msecs for %s to be acked\n",
jiffies_to_msecs(ret), info->name);
/* Collect data returned by the XPU */
for (i = 0; i < MAX_MB_ARGUMENTS; i++)
data[i] = cx18_readl(cx, &mb->args[i]);
err = cx18_readl(cx, &mb->error);
mutex_unlock(mb_lock);
/*
* Wait for XPU to perform extra actions for the caller in some cases.
* e.g. CX18_CPU_DE_RELEASE_MDL will cause the CPU to send all MDLs
* back in a burst shortly thereafter
*/
if (info->flags & API_SLOW)
cx18_msleep_timeout(300, 0);
if (err)
CX18_DEBUG_API("mailbox error %08x for command %s\n", err,
info->name);
return err ? -EIO : 0;
}
int cx18_api(struct cx18 *cx, u32 cmd, int args, u32 data[])
{
return cx18_api_call(cx, cmd, args, data);
}
static int cx18_set_filter_param(struct cx18_stream *s)
{
struct cx18 *cx = s->cx;
u32 mode;
int ret;
mode = (cx->filter_mode & 1) ? 2 : (cx->spatial_strength ? 1 : 0);
ret = cx18_vapi(cx, CX18_CPU_SET_FILTER_PARAM, 4,
s->handle, 1, mode, cx->spatial_strength);
mode = (cx->filter_mode & 2) ? 2 : (cx->temporal_strength ? 1 : 0);
ret = ret ? ret : cx18_vapi(cx, CX18_CPU_SET_FILTER_PARAM, 4,
s->handle, 0, mode, cx->temporal_strength);
ret = ret ? ret : cx18_vapi(cx, CX18_CPU_SET_FILTER_PARAM, 4,
s->handle, 2, cx->filter_mode >> 2, 0);
return ret;
}
int cx18_api_func(void *priv, u32 cmd, int in, int out,
u32 data[CX2341X_MBOX_MAX_DATA])
{
struct cx18_stream *s = priv;
struct cx18 *cx = s->cx;
switch (cmd) {
case CX2341X_ENC_SET_OUTPUT_PORT:
return 0;
case CX2341X_ENC_SET_FRAME_RATE:
return cx18_vapi(cx, CX18_CPU_SET_VIDEO_IN, 6,
s->handle, 0, 0, 0, 0, data[0]);
case CX2341X_ENC_SET_FRAME_SIZE:
return cx18_vapi(cx, CX18_CPU_SET_VIDEO_RESOLUTION, 3,
s->handle, data[1], data[0]);
case CX2341X_ENC_SET_STREAM_TYPE:
return cx18_vapi(cx, CX18_CPU_SET_STREAM_OUTPUT_TYPE, 2,
s->handle, data[0]);
case CX2341X_ENC_SET_ASPECT_RATIO:
return cx18_vapi(cx, CX18_CPU_SET_ASPECT_RATIO, 2,
s->handle, data[0]);
case CX2341X_ENC_SET_GOP_PROPERTIES:
return cx18_vapi(cx, CX18_CPU_SET_GOP_STRUCTURE, 3,
s->handle, data[0], data[1]);
case CX2341X_ENC_SET_GOP_CLOSURE:
return 0;
case CX2341X_ENC_SET_AUDIO_PROPERTIES:
return cx18_vapi(cx, CX18_CPU_SET_AUDIO_PARAMETERS, 2,
s->handle, data[0]);
case CX2341X_ENC_MUTE_AUDIO:
return cx18_vapi(cx, CX18_CPU_SET_AUDIO_MUTE, 2,
s->handle, data[0]);
case CX2341X_ENC_SET_BIT_RATE:
return cx18_vapi(cx, CX18_CPU_SET_VIDEO_RATE, 5,
s->handle, data[0], data[1], data[2], data[3]);
case CX2341X_ENC_MUTE_VIDEO:
return cx18_vapi(cx, CX18_CPU_SET_VIDEO_MUTE, 2,
s->handle, data[0]);
case CX2341X_ENC_SET_FRAME_DROP_RATE:
return cx18_vapi(cx, CX18_CPU_SET_SKIP_INPUT_FRAME, 2,
s->handle, data[0]);
case CX2341X_ENC_MISC:
return cx18_vapi(cx, CX18_CPU_SET_MISC_PARAMETERS, 4,
s->handle, data[0], data[1], data[2]);
case CX2341X_ENC_SET_DNR_FILTER_MODE:
cx->filter_mode = (data[0] & 3) | (data[1] << 2);
return cx18_set_filter_param(s);
case CX2341X_ENC_SET_DNR_FILTER_PROPS:
cx->spatial_strength = data[0];
cx->temporal_strength = data[1];
return cx18_set_filter_param(s);
case CX2341X_ENC_SET_SPATIAL_FILTER_TYPE:
return cx18_vapi(cx, CX18_CPU_SET_SPATIAL_FILTER_TYPE, 3,
s->handle, data[0], data[1]);
case CX2341X_ENC_SET_CORING_LEVELS:
return cx18_vapi(cx, CX18_CPU_SET_MEDIAN_CORING, 5,
s->handle, data[0], data[1], data[2], data[3]);
}
CX18_WARN("Unknown cmd %x\n", cmd);
return 0;
}
int cx18_vapi_result(struct cx18 *cx, u32 data[MAX_MB_ARGUMENTS],
u32 cmd, int args, ...)
{
va_list ap;
int i;
va_start(ap, args);
for (i = 0; i < args; i++)
data[i] = va_arg(ap, u32);
va_end(ap);
return cx18_api(cx, cmd, args, data);
}
int cx18_vapi(struct cx18 *cx, u32 cmd, int args, ...)
{
u32 data[MAX_MB_ARGUMENTS];
va_list ap;
int i;
if (cx == NULL) {
CX18_ERR("cx == NULL (cmd=%x)\n", cmd);
return 0;
}
if (args > MAX_MB_ARGUMENTS) {
CX18_ERR("args too big (cmd=%x)\n", cmd);
args = MAX_MB_ARGUMENTS;
}
va_start(ap, args);
for (i = 0; i < args; i++)
data[i] = va_arg(ap, u32);
va_end(ap);
return cx18_api(cx, cmd, args, data);
}

View file

@ -0,0 +1,95 @@
/*
* cx18 mailbox functions
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* 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
*/
#ifndef _CX18_MAILBOX_H_
#define _CX18_MAILBOX_H_
/* mailbox max args */
#define MAX_MB_ARGUMENTS 6
/* compatibility, should be same as the define in cx2341x.h */
#define CX2341X_MBOX_MAX_DATA 16
#define MB_RESERVED_HANDLE_0 0
#define MB_RESERVED_HANDLE_1 0xFFFFFFFF
#define APU 0
#define CPU 1
#define EPU 2
#define HPU 3
struct cx18;
/*
* This structure is used by CPU to provide completed MDL & buffers information.
* Its structure is dictated by the layout of the SCB, required by the
* firmware, but its definition needs to be here, instead of in cx18-scb.h,
* for mailbox work order scheduling
*/
struct cx18_mdl_ack {
u32 id; /* ID of a completed MDL */
u32 data_used; /* Total data filled in the MDL with 'id' */
};
/* The cx18_mailbox struct is the mailbox structure which is used for passing
messages between processors */
struct cx18_mailbox {
/* The sender sets a handle in 'request' after he fills the command. The
'request' should be different than 'ack'. The sender, also, generates
an interrupt on XPU2YPU_irq where XPU is the sender and YPU is the
receiver. */
u32 request;
/* The receiver detects a new command when 'req' is different than 'ack'.
He sets 'ack' to the same value as 'req' to clear the command. He, also,
generates an interrupt on YPU2XPU_irq where XPU is the sender and YPU
is the receiver. */
u32 ack;
u32 reserved[6];
/* 'cmd' identifies the command. The list of these commands are in
cx23418.h */
u32 cmd;
/* Each command can have up to 6 arguments */
u32 args[MAX_MB_ARGUMENTS];
/* The return code can be one of the codes in the file cx23418.h. If the
command is completed successfully, the error will be ERR_SYS_SUCCESS.
If it is pending, the code is ERR_SYS_PENDING. If it failed, the error
code would indicate the task from which the error originated and will
be one of the errors in cx23418.h. In that case, the following
applies ((error & 0xff) != 0).
If the command is pending, the return will be passed in a MB from the
receiver to the sender. 'req' will be returned in args[0] */
u32 error;
};
struct cx18_stream;
int cx18_api(struct cx18 *cx, u32 cmd, int args, u32 data[]);
int cx18_vapi_result(struct cx18 *cx, u32 data[MAX_MB_ARGUMENTS], u32 cmd,
int args, ...);
int cx18_vapi(struct cx18 *cx, u32 cmd, int args, ...);
int cx18_api_func(void *priv, u32 cmd, int in, int out,
u32 data[CX2341X_MBOX_MAX_DATA]);
void cx18_api_epu_cmd_irq(struct cx18 *cx, int rpu);
void cx18_in_work_handler(struct work_struct *work);
#endif

View file

@ -0,0 +1,443 @@
/*
* cx18 buffer queues
*
* Derived from ivtv-queue.c
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* 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 "cx18-driver.h"
#include "cx18-queue.h"
#include "cx18-streams.h"
#include "cx18-scb.h"
#include "cx18-io.h"
void cx18_buf_swap(struct cx18_buffer *buf)
{
int i;
for (i = 0; i < buf->bytesused; i += 4)
swab32s((u32 *)(buf->buf + i));
}
void _cx18_mdl_swap(struct cx18_mdl *mdl)
{
struct cx18_buffer *buf;
list_for_each_entry(buf, &mdl->buf_list, list) {
if (buf->bytesused == 0)
break;
cx18_buf_swap(buf);
}
}
void cx18_queue_init(struct cx18_queue *q)
{
INIT_LIST_HEAD(&q->list);
atomic_set(&q->depth, 0);
q->bytesused = 0;
}
struct cx18_queue *_cx18_enqueue(struct cx18_stream *s, struct cx18_mdl *mdl,
struct cx18_queue *q, int to_front)
{
/* clear the mdl if it is not to be enqueued to the full queue */
if (q != &s->q_full) {
mdl->bytesused = 0;
mdl->readpos = 0;
mdl->m_flags = 0;
mdl->skipped = 0;
mdl->curr_buf = NULL;
}
/* q_busy is restricted to a max buffer count imposed by firmware */
if (q == &s->q_busy &&
atomic_read(&q->depth) >= CX18_MAX_FW_MDLS_PER_STREAM)
q = &s->q_free;
spin_lock(&q->lock);
if (to_front)
list_add(&mdl->list, &q->list); /* LIFO */
else
list_add_tail(&mdl->list, &q->list); /* FIFO */
q->bytesused += mdl->bytesused - mdl->readpos;
atomic_inc(&q->depth);
spin_unlock(&q->lock);
return q;
}
struct cx18_mdl *cx18_dequeue(struct cx18_stream *s, struct cx18_queue *q)
{
struct cx18_mdl *mdl = NULL;
spin_lock(&q->lock);
if (!list_empty(&q->list)) {
mdl = list_first_entry(&q->list, struct cx18_mdl, list);
list_del_init(&mdl->list);
q->bytesused -= mdl->bytesused - mdl->readpos;
mdl->skipped = 0;
atomic_dec(&q->depth);
}
spin_unlock(&q->lock);
return mdl;
}
static void _cx18_mdl_update_bufs_for_cpu(struct cx18_stream *s,
struct cx18_mdl *mdl)
{
struct cx18_buffer *buf;
u32 buf_size = s->buf_size;
u32 bytesused = mdl->bytesused;
list_for_each_entry(buf, &mdl->buf_list, list) {
buf->readpos = 0;
if (bytesused >= buf_size) {
buf->bytesused = buf_size;
bytesused -= buf_size;
} else {
buf->bytesused = bytesused;
bytesused = 0;
}
cx18_buf_sync_for_cpu(s, buf);
}
}
static inline void cx18_mdl_update_bufs_for_cpu(struct cx18_stream *s,
struct cx18_mdl *mdl)
{
struct cx18_buffer *buf;
if (list_is_singular(&mdl->buf_list)) {
buf = list_first_entry(&mdl->buf_list, struct cx18_buffer,
list);
buf->bytesused = mdl->bytesused;
buf->readpos = 0;
cx18_buf_sync_for_cpu(s, buf);
} else {
_cx18_mdl_update_bufs_for_cpu(s, mdl);
}
}
struct cx18_mdl *cx18_queue_get_mdl(struct cx18_stream *s, u32 id,
u32 bytesused)
{
struct cx18 *cx = s->cx;
struct cx18_mdl *mdl;
struct cx18_mdl *tmp;
struct cx18_mdl *ret = NULL;
LIST_HEAD(sweep_up);
/*
* We don't have to acquire multiple q locks here, because we are
* serialized by the single threaded work handler.
* MDLs from the firmware will thus remain in order as
* they are moved from q_busy to q_full or to the dvb ring buffer.
*/
spin_lock(&s->q_busy.lock);
list_for_each_entry_safe(mdl, tmp, &s->q_busy.list, list) {
/*
* We should find what the firmware told us is done,
* right at the front of the queue. If we don't, we likely have
* missed an mdl done message from the firmware.
* Once we skip an mdl repeatedly, relative to the size of
* q_busy, we have high confidence we've missed it.
*/
if (mdl->id != id) {
mdl->skipped++;
if (mdl->skipped >= atomic_read(&s->q_busy.depth)-1) {
/* mdl must have fallen out of rotation */
CX18_WARN("Skipped %s, MDL %d, %d "
"times - it must have dropped out of "
"rotation\n", s->name, mdl->id,
mdl->skipped);
/* Sweep it up to put it back into rotation */
list_move_tail(&mdl->list, &sweep_up);
atomic_dec(&s->q_busy.depth);
}
continue;
}
/*
* We pull the desired mdl off of the queue here. Something
* will have to put it back on a queue later.
*/
list_del_init(&mdl->list);
atomic_dec(&s->q_busy.depth);
ret = mdl;
break;
}
spin_unlock(&s->q_busy.lock);
/*
* We found the mdl for which we were looking. Get it ready for
* the caller to put on q_full or in the dvb ring buffer.
*/
if (ret != NULL) {
ret->bytesused = bytesused;
ret->skipped = 0;
/* 0'ed readpos, m_flags & curr_buf when mdl went on q_busy */
cx18_mdl_update_bufs_for_cpu(s, ret);
if (s->type != CX18_ENC_STREAM_TYPE_TS)
set_bit(CX18_F_M_NEED_SWAP, &ret->m_flags);
}
/* Put any mdls the firmware is ignoring back into normal rotation */
list_for_each_entry_safe(mdl, tmp, &sweep_up, list) {
list_del_init(&mdl->list);
cx18_enqueue(s, mdl, &s->q_free);
}
return ret;
}
/* Move all mdls of a queue, while flushing the mdl */
static void cx18_queue_flush(struct cx18_stream *s,
struct cx18_queue *q_src, struct cx18_queue *q_dst)
{
struct cx18_mdl *mdl;
/* It only makes sense to flush to q_free or q_idle */
if (q_src == q_dst || q_dst == &s->q_full || q_dst == &s->q_busy)
return;
spin_lock(&q_src->lock);
spin_lock(&q_dst->lock);
while (!list_empty(&q_src->list)) {
mdl = list_first_entry(&q_src->list, struct cx18_mdl, list);
list_move_tail(&mdl->list, &q_dst->list);
mdl->bytesused = 0;
mdl->readpos = 0;
mdl->m_flags = 0;
mdl->skipped = 0;
mdl->curr_buf = NULL;
atomic_inc(&q_dst->depth);
}
cx18_queue_init(q_src);
spin_unlock(&q_src->lock);
spin_unlock(&q_dst->lock);
}
void cx18_flush_queues(struct cx18_stream *s)
{
cx18_queue_flush(s, &s->q_busy, &s->q_free);
cx18_queue_flush(s, &s->q_full, &s->q_free);
}
/*
* Note, s->buf_pool is not protected by a lock,
* the stream better not have *anything* going on when calling this
*/
void cx18_unload_queues(struct cx18_stream *s)
{
struct cx18_queue *q_idle = &s->q_idle;
struct cx18_mdl *mdl;
struct cx18_buffer *buf;
/* Move all MDLS to q_idle */
cx18_queue_flush(s, &s->q_busy, q_idle);
cx18_queue_flush(s, &s->q_full, q_idle);
cx18_queue_flush(s, &s->q_free, q_idle);
/* Reset MDL id's and move all buffers back to the stream's buf_pool */
spin_lock(&q_idle->lock);
list_for_each_entry(mdl, &q_idle->list, list) {
while (!list_empty(&mdl->buf_list)) {
buf = list_first_entry(&mdl->buf_list,
struct cx18_buffer, list);
list_move_tail(&buf->list, &s->buf_pool);
buf->bytesused = 0;
buf->readpos = 0;
}
mdl->id = s->mdl_base_idx; /* reset id to a "safe" value */
/* all other mdl fields were cleared by cx18_queue_flush() */
}
spin_unlock(&q_idle->lock);
}
/*
* Note, s->buf_pool is not protected by a lock,
* the stream better not have *anything* going on when calling this
*/
void cx18_load_queues(struct cx18_stream *s)
{
struct cx18 *cx = s->cx;
struct cx18_mdl *mdl;
struct cx18_buffer *buf;
int mdl_id;
int i;
u32 partial_buf_size;
/*
* Attach buffers to MDLs, give the MDLs ids, and add MDLs to q_free
* Excess MDLs are left on q_idle
* Excess buffers are left in buf_pool and/or on an MDL in q_idle
*/
mdl_id = s->mdl_base_idx;
for (mdl = cx18_dequeue(s, &s->q_idle), i = s->bufs_per_mdl;
mdl != NULL && i == s->bufs_per_mdl;
mdl = cx18_dequeue(s, &s->q_idle)) {
mdl->id = mdl_id;
for (i = 0; i < s->bufs_per_mdl; i++) {
if (list_empty(&s->buf_pool))
break;
buf = list_first_entry(&s->buf_pool, struct cx18_buffer,
list);
list_move_tail(&buf->list, &mdl->buf_list);
/* update the firmware's MDL array with this buffer */
cx18_writel(cx, buf->dma_handle,
&cx->scb->cpu_mdl[mdl_id + i].paddr);
cx18_writel(cx, s->buf_size,
&cx->scb->cpu_mdl[mdl_id + i].length);
}
if (i == s->bufs_per_mdl) {
/*
* The encoder doesn't honor s->mdl_size. So in the
* case of a non-integral number of buffers to meet
* mdl_size, we lie about the size of the last buffer
* in the MDL to get the encoder to really only send
* us mdl_size bytes per MDL transfer.
*/
partial_buf_size = s->mdl_size % s->buf_size;
if (partial_buf_size) {
cx18_writel(cx, partial_buf_size,
&cx->scb->cpu_mdl[mdl_id + i - 1].length);
}
cx18_enqueue(s, mdl, &s->q_free);
} else {
/* Not enough buffers for this MDL; we won't use it */
cx18_push(s, mdl, &s->q_idle);
}
mdl_id += i;
}
}
void _cx18_mdl_sync_for_device(struct cx18_stream *s, struct cx18_mdl *mdl)
{
int dma = s->dma;
u32 buf_size = s->buf_size;
struct pci_dev *pci_dev = s->cx->pci_dev;
struct cx18_buffer *buf;
list_for_each_entry(buf, &mdl->buf_list, list)
pci_dma_sync_single_for_device(pci_dev, buf->dma_handle,
buf_size, dma);
}
int cx18_stream_alloc(struct cx18_stream *s)
{
struct cx18 *cx = s->cx;
int i;
if (s->buffers == 0)
return 0;
CX18_DEBUG_INFO("Allocate %s stream: %d x %d buffers "
"(%d.%02d kB total)\n",
s->name, s->buffers, s->buf_size,
s->buffers * s->buf_size / 1024,
(s->buffers * s->buf_size * 100 / 1024) % 100);
if (((char __iomem *)&cx->scb->cpu_mdl[cx->free_mdl_idx + s->buffers] -
(char __iomem *)cx->scb) > SCB_RESERVED_SIZE) {
unsigned bufsz = (((char __iomem *)cx->scb) + SCB_RESERVED_SIZE -
((char __iomem *)cx->scb->cpu_mdl));
CX18_ERR("Too many buffers, cannot fit in SCB area\n");
CX18_ERR("Max buffers = %zu\n",
bufsz / sizeof(struct cx18_mdl_ent));
return -ENOMEM;
}
s->mdl_base_idx = cx->free_mdl_idx;
/* allocate stream buffers and MDLs */
for (i = 0; i < s->buffers; i++) {
struct cx18_mdl *mdl;
struct cx18_buffer *buf;
/* 1 MDL per buffer to handle the worst & also default case */
mdl = kzalloc(sizeof(struct cx18_mdl), GFP_KERNEL|__GFP_NOWARN);
if (mdl == NULL)
break;
buf = kzalloc(sizeof(struct cx18_buffer),
GFP_KERNEL|__GFP_NOWARN);
if (buf == NULL) {
kfree(mdl);
break;
}
buf->buf = kmalloc(s->buf_size, GFP_KERNEL|__GFP_NOWARN);
if (buf->buf == NULL) {
kfree(mdl);
kfree(buf);
break;
}
INIT_LIST_HEAD(&mdl->list);
INIT_LIST_HEAD(&mdl->buf_list);
mdl->id = s->mdl_base_idx; /* a somewhat safe value */
cx18_enqueue(s, mdl, &s->q_idle);
INIT_LIST_HEAD(&buf->list);
buf->dma_handle = pci_map_single(s->cx->pci_dev,
buf->buf, s->buf_size, s->dma);
cx18_buf_sync_for_cpu(s, buf);
list_add_tail(&buf->list, &s->buf_pool);
}
if (i == s->buffers) {
cx->free_mdl_idx += s->buffers;
return 0;
}
CX18_ERR("Couldn't allocate buffers for %s stream\n", s->name);
cx18_stream_free(s);
return -ENOMEM;
}
void cx18_stream_free(struct cx18_stream *s)
{
struct cx18_mdl *mdl;
struct cx18_buffer *buf;
struct cx18 *cx = s->cx;
CX18_DEBUG_INFO("Deallocating buffers for %s stream\n", s->name);
/* move all buffers to buf_pool and all MDLs to q_idle */
cx18_unload_queues(s);
/* empty q_idle */
while ((mdl = cx18_dequeue(s, &s->q_idle)))
kfree(mdl);
/* empty buf_pool */
while (!list_empty(&s->buf_pool)) {
buf = list_first_entry(&s->buf_pool, struct cx18_buffer, list);
list_del_init(&buf->list);
pci_unmap_single(s->cx->pci_dev, buf->dma_handle,
s->buf_size, s->dma);
kfree(buf->buf);
kfree(buf);
}
}

View file

@ -0,0 +1,98 @@
/*
* cx18 buffer queues
*
* Derived from ivtv-queue.h
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* 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
*/
#define CX18_DMA_UNMAPPED ((u32) -1)
/* cx18_buffer utility functions */
static inline void cx18_buf_sync_for_cpu(struct cx18_stream *s,
struct cx18_buffer *buf)
{
pci_dma_sync_single_for_cpu(s->cx->pci_dev, buf->dma_handle,
s->buf_size, s->dma);
}
static inline void cx18_buf_sync_for_device(struct cx18_stream *s,
struct cx18_buffer *buf)
{
pci_dma_sync_single_for_device(s->cx->pci_dev, buf->dma_handle,
s->buf_size, s->dma);
}
void _cx18_mdl_sync_for_device(struct cx18_stream *s, struct cx18_mdl *mdl);
static inline void cx18_mdl_sync_for_device(struct cx18_stream *s,
struct cx18_mdl *mdl)
{
if (list_is_singular(&mdl->buf_list))
cx18_buf_sync_for_device(s, list_first_entry(&mdl->buf_list,
struct cx18_buffer,
list));
else
_cx18_mdl_sync_for_device(s, mdl);
}
void cx18_buf_swap(struct cx18_buffer *buf);
void _cx18_mdl_swap(struct cx18_mdl *mdl);
static inline void cx18_mdl_swap(struct cx18_mdl *mdl)
{
if (list_is_singular(&mdl->buf_list))
cx18_buf_swap(list_first_entry(&mdl->buf_list,
struct cx18_buffer, list));
else
_cx18_mdl_swap(mdl);
}
/* cx18_queue utility functions */
struct cx18_queue *_cx18_enqueue(struct cx18_stream *s, struct cx18_mdl *mdl,
struct cx18_queue *q, int to_front);
static inline
struct cx18_queue *cx18_enqueue(struct cx18_stream *s, struct cx18_mdl *mdl,
struct cx18_queue *q)
{
return _cx18_enqueue(s, mdl, q, 0); /* FIFO */
}
static inline
struct cx18_queue *cx18_push(struct cx18_stream *s, struct cx18_mdl *mdl,
struct cx18_queue *q)
{
return _cx18_enqueue(s, mdl, q, 1); /* LIFO */
}
void cx18_queue_init(struct cx18_queue *q);
struct cx18_mdl *cx18_dequeue(struct cx18_stream *s, struct cx18_queue *q);
struct cx18_mdl *cx18_queue_get_mdl(struct cx18_stream *s, u32 id,
u32 bytesused);
void cx18_flush_queues(struct cx18_stream *s);
/* queue MDL reconfiguration helpers */
void cx18_unload_queues(struct cx18_stream *s);
void cx18_load_queues(struct cx18_stream *s);
/* cx18_stream utility functions */
int cx18_stream_alloc(struct cx18_stream *s);
void cx18_stream_free(struct cx18_stream *s);

View file

@ -0,0 +1,122 @@
/*
* cx18 System Control Block initialization
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* 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 "cx18-driver.h"
#include "cx18-io.h"
#include "cx18-scb.h"
void cx18_init_scb(struct cx18 *cx)
{
cx18_setup_page(cx, SCB_OFFSET);
cx18_memset_io(cx, cx->scb, 0, 0x10000);
cx18_writel(cx, IRQ_APU_TO_CPU, &cx->scb->apu2cpu_irq);
cx18_writel(cx, IRQ_CPU_TO_APU_ACK, &cx->scb->cpu2apu_irq_ack);
cx18_writel(cx, IRQ_HPU_TO_CPU, &cx->scb->hpu2cpu_irq);
cx18_writel(cx, IRQ_CPU_TO_HPU_ACK, &cx->scb->cpu2hpu_irq_ack);
cx18_writel(cx, IRQ_PPU_TO_CPU, &cx->scb->ppu2cpu_irq);
cx18_writel(cx, IRQ_CPU_TO_PPU_ACK, &cx->scb->cpu2ppu_irq_ack);
cx18_writel(cx, IRQ_EPU_TO_CPU, &cx->scb->epu2cpu_irq);
cx18_writel(cx, IRQ_CPU_TO_EPU_ACK, &cx->scb->cpu2epu_irq_ack);
cx18_writel(cx, IRQ_CPU_TO_APU, &cx->scb->cpu2apu_irq);
cx18_writel(cx, IRQ_APU_TO_CPU_ACK, &cx->scb->apu2cpu_irq_ack);
cx18_writel(cx, IRQ_HPU_TO_APU, &cx->scb->hpu2apu_irq);
cx18_writel(cx, IRQ_APU_TO_HPU_ACK, &cx->scb->apu2hpu_irq_ack);
cx18_writel(cx, IRQ_PPU_TO_APU, &cx->scb->ppu2apu_irq);
cx18_writel(cx, IRQ_APU_TO_PPU_ACK, &cx->scb->apu2ppu_irq_ack);
cx18_writel(cx, IRQ_EPU_TO_APU, &cx->scb->epu2apu_irq);
cx18_writel(cx, IRQ_APU_TO_EPU_ACK, &cx->scb->apu2epu_irq_ack);
cx18_writel(cx, IRQ_CPU_TO_HPU, &cx->scb->cpu2hpu_irq);
cx18_writel(cx, IRQ_HPU_TO_CPU_ACK, &cx->scb->hpu2cpu_irq_ack);
cx18_writel(cx, IRQ_APU_TO_HPU, &cx->scb->apu2hpu_irq);
cx18_writel(cx, IRQ_HPU_TO_APU_ACK, &cx->scb->hpu2apu_irq_ack);
cx18_writel(cx, IRQ_PPU_TO_HPU, &cx->scb->ppu2hpu_irq);
cx18_writel(cx, IRQ_HPU_TO_PPU_ACK, &cx->scb->hpu2ppu_irq_ack);
cx18_writel(cx, IRQ_EPU_TO_HPU, &cx->scb->epu2hpu_irq);
cx18_writel(cx, IRQ_HPU_TO_EPU_ACK, &cx->scb->hpu2epu_irq_ack);
cx18_writel(cx, IRQ_CPU_TO_PPU, &cx->scb->cpu2ppu_irq);
cx18_writel(cx, IRQ_PPU_TO_CPU_ACK, &cx->scb->ppu2cpu_irq_ack);
cx18_writel(cx, IRQ_APU_TO_PPU, &cx->scb->apu2ppu_irq);
cx18_writel(cx, IRQ_PPU_TO_APU_ACK, &cx->scb->ppu2apu_irq_ack);
cx18_writel(cx, IRQ_HPU_TO_PPU, &cx->scb->hpu2ppu_irq);
cx18_writel(cx, IRQ_PPU_TO_HPU_ACK, &cx->scb->ppu2hpu_irq_ack);
cx18_writel(cx, IRQ_EPU_TO_PPU, &cx->scb->epu2ppu_irq);
cx18_writel(cx, IRQ_PPU_TO_EPU_ACK, &cx->scb->ppu2epu_irq_ack);
cx18_writel(cx, IRQ_CPU_TO_EPU, &cx->scb->cpu2epu_irq);
cx18_writel(cx, IRQ_EPU_TO_CPU_ACK, &cx->scb->epu2cpu_irq_ack);
cx18_writel(cx, IRQ_APU_TO_EPU, &cx->scb->apu2epu_irq);
cx18_writel(cx, IRQ_EPU_TO_APU_ACK, &cx->scb->epu2apu_irq_ack);
cx18_writel(cx, IRQ_HPU_TO_EPU, &cx->scb->hpu2epu_irq);
cx18_writel(cx, IRQ_EPU_TO_HPU_ACK, &cx->scb->epu2hpu_irq_ack);
cx18_writel(cx, IRQ_PPU_TO_EPU, &cx->scb->ppu2epu_irq);
cx18_writel(cx, IRQ_EPU_TO_PPU_ACK, &cx->scb->epu2ppu_irq_ack);
cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, apu2cpu_mb),
&cx->scb->apu2cpu_mb_offset);
cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, hpu2cpu_mb),
&cx->scb->hpu2cpu_mb_offset);
cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, ppu2cpu_mb),
&cx->scb->ppu2cpu_mb_offset);
cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, epu2cpu_mb),
&cx->scb->epu2cpu_mb_offset);
cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, cpu2apu_mb),
&cx->scb->cpu2apu_mb_offset);
cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, hpu2apu_mb),
&cx->scb->hpu2apu_mb_offset);
cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, ppu2apu_mb),
&cx->scb->ppu2apu_mb_offset);
cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, epu2apu_mb),
&cx->scb->epu2apu_mb_offset);
cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, cpu2hpu_mb),
&cx->scb->cpu2hpu_mb_offset);
cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, apu2hpu_mb),
&cx->scb->apu2hpu_mb_offset);
cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, ppu2hpu_mb),
&cx->scb->ppu2hpu_mb_offset);
cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, epu2hpu_mb),
&cx->scb->epu2hpu_mb_offset);
cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, cpu2ppu_mb),
&cx->scb->cpu2ppu_mb_offset);
cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, apu2ppu_mb),
&cx->scb->apu2ppu_mb_offset);
cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, hpu2ppu_mb),
&cx->scb->hpu2ppu_mb_offset);
cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, epu2ppu_mb),
&cx->scb->epu2ppu_mb_offset);
cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, cpu2epu_mb),
&cx->scb->cpu2epu_mb_offset);
cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, apu2epu_mb),
&cx->scb->apu2epu_mb_offset);
cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, hpu2epu_mb),
&cx->scb->hpu2epu_mb_offset);
cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, ppu2epu_mb),
&cx->scb->ppu2epu_mb_offset);
cx18_writel(cx, SCB_OFFSET + offsetof(struct cx18_scb, cpu_state),
&cx->scb->ipc_offset);
cx18_writel(cx, 1, &cx->scb->epu_state);
}

View file

@ -0,0 +1,280 @@
/*
* cx18 System Control Block initialization
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* 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
*/
#ifndef CX18_SCB_H
#define CX18_SCB_H
#include "cx18-mailbox.h"
/* NOTE: All ACK interrupts are in the SW2 register. All non-ACK interrupts
are in the SW1 register. */
#define IRQ_APU_TO_CPU 0x00000001
#define IRQ_CPU_TO_APU_ACK 0x00000001
#define IRQ_HPU_TO_CPU 0x00000002
#define IRQ_CPU_TO_HPU_ACK 0x00000002
#define IRQ_PPU_TO_CPU 0x00000004
#define IRQ_CPU_TO_PPU_ACK 0x00000004
#define IRQ_EPU_TO_CPU 0x00000008
#define IRQ_CPU_TO_EPU_ACK 0x00000008
#define IRQ_CPU_TO_APU 0x00000010
#define IRQ_APU_TO_CPU_ACK 0x00000010
#define IRQ_HPU_TO_APU 0x00000020
#define IRQ_APU_TO_HPU_ACK 0x00000020
#define IRQ_PPU_TO_APU 0x00000040
#define IRQ_APU_TO_PPU_ACK 0x00000040
#define IRQ_EPU_TO_APU 0x00000080
#define IRQ_APU_TO_EPU_ACK 0x00000080
#define IRQ_CPU_TO_HPU 0x00000100
#define IRQ_HPU_TO_CPU_ACK 0x00000100
#define IRQ_APU_TO_HPU 0x00000200
#define IRQ_HPU_TO_APU_ACK 0x00000200
#define IRQ_PPU_TO_HPU 0x00000400
#define IRQ_HPU_TO_PPU_ACK 0x00000400
#define IRQ_EPU_TO_HPU 0x00000800
#define IRQ_HPU_TO_EPU_ACK 0x00000800
#define IRQ_CPU_TO_PPU 0x00001000
#define IRQ_PPU_TO_CPU_ACK 0x00001000
#define IRQ_APU_TO_PPU 0x00002000
#define IRQ_PPU_TO_APU_ACK 0x00002000
#define IRQ_HPU_TO_PPU 0x00004000
#define IRQ_PPU_TO_HPU_ACK 0x00004000
#define IRQ_EPU_TO_PPU 0x00008000
#define IRQ_PPU_TO_EPU_ACK 0x00008000
#define IRQ_CPU_TO_EPU 0x00010000
#define IRQ_EPU_TO_CPU_ACK 0x00010000
#define IRQ_APU_TO_EPU 0x00020000
#define IRQ_EPU_TO_APU_ACK 0x00020000
#define IRQ_HPU_TO_EPU 0x00040000
#define IRQ_EPU_TO_HPU_ACK 0x00040000
#define IRQ_PPU_TO_EPU 0x00080000
#define IRQ_EPU_TO_PPU_ACK 0x00080000
#define SCB_OFFSET 0xDC0000
/* If Firmware uses fixed memory map, it shall not allocate the area
between SCB_OFFSET and SCB_OFFSET+SCB_RESERVED_SIZE-1 inclusive */
#define SCB_RESERVED_SIZE 0x10000
/* This structure is used by EPU to provide memory descriptors in its memory */
struct cx18_mdl_ent {
u32 paddr; /* Physical address of a buffer segment */
u32 length; /* Length of the buffer segment */
};
struct cx18_scb {
/* These fields form the System Control Block which is used at boot time
for localizing the IPC data as well as the code positions for all
processors. The offsets are from the start of this struct. */
/* Offset where to find the Inter-Processor Communication data */
u32 ipc_offset;
u32 reserved01[7];
/* Offset where to find the start of the CPU code */
u32 cpu_code_offset;
u32 reserved02[3];
/* Offset where to find the start of the APU code */
u32 apu_code_offset;
u32 reserved03[3];
/* Offset where to find the start of the HPU code */
u32 hpu_code_offset;
u32 reserved04[3];
/* Offset where to find the start of the PPU code */
u32 ppu_code_offset;
u32 reserved05[3];
/* These fields form Inter-Processor Communication data which is used
by all processors to locate the information needed for communicating
with other processors */
/* Fields for CPU: */
/* bit 0: 1/0 processor ready/not ready. Set other bits to 0. */
u32 cpu_state;
u32 reserved1[7];
/* Offset to the mailbox used for sending commands from APU to CPU */
u32 apu2cpu_mb_offset;
/* Value to write to register SW1 register set (0xC7003100) after the
command is ready */
u32 apu2cpu_irq;
/* Value to write to register SW2 register set (0xC7003140) after the
command is cleared */
u32 cpu2apu_irq_ack;
u32 reserved2[13];
u32 hpu2cpu_mb_offset;
u32 hpu2cpu_irq;
u32 cpu2hpu_irq_ack;
u32 reserved3[13];
u32 ppu2cpu_mb_offset;
u32 ppu2cpu_irq;
u32 cpu2ppu_irq_ack;
u32 reserved4[13];
u32 epu2cpu_mb_offset;
u32 epu2cpu_irq;
u32 cpu2epu_irq_ack;
u32 reserved5[13];
u32 reserved6[8];
/* Fields for APU: */
u32 apu_state;
u32 reserved11[7];
u32 cpu2apu_mb_offset;
u32 cpu2apu_irq;
u32 apu2cpu_irq_ack;
u32 reserved12[13];
u32 hpu2apu_mb_offset;
u32 hpu2apu_irq;
u32 apu2hpu_irq_ack;
u32 reserved13[13];
u32 ppu2apu_mb_offset;
u32 ppu2apu_irq;
u32 apu2ppu_irq_ack;
u32 reserved14[13];
u32 epu2apu_mb_offset;
u32 epu2apu_irq;
u32 apu2epu_irq_ack;
u32 reserved15[13];
u32 reserved16[8];
/* Fields for HPU: */
u32 hpu_state;
u32 reserved21[7];
u32 cpu2hpu_mb_offset;
u32 cpu2hpu_irq;
u32 hpu2cpu_irq_ack;
u32 reserved22[13];
u32 apu2hpu_mb_offset;
u32 apu2hpu_irq;
u32 hpu2apu_irq_ack;
u32 reserved23[13];
u32 ppu2hpu_mb_offset;
u32 ppu2hpu_irq;
u32 hpu2ppu_irq_ack;
u32 reserved24[13];
u32 epu2hpu_mb_offset;
u32 epu2hpu_irq;
u32 hpu2epu_irq_ack;
u32 reserved25[13];
u32 reserved26[8];
/* Fields for PPU: */
u32 ppu_state;
u32 reserved31[7];
u32 cpu2ppu_mb_offset;
u32 cpu2ppu_irq;
u32 ppu2cpu_irq_ack;
u32 reserved32[13];
u32 apu2ppu_mb_offset;
u32 apu2ppu_irq;
u32 ppu2apu_irq_ack;
u32 reserved33[13];
u32 hpu2ppu_mb_offset;
u32 hpu2ppu_irq;
u32 ppu2hpu_irq_ack;
u32 reserved34[13];
u32 epu2ppu_mb_offset;
u32 epu2ppu_irq;
u32 ppu2epu_irq_ack;
u32 reserved35[13];
u32 reserved36[8];
/* Fields for EPU: */
u32 epu_state;
u32 reserved41[7];
u32 cpu2epu_mb_offset;
u32 cpu2epu_irq;
u32 epu2cpu_irq_ack;
u32 reserved42[13];
u32 apu2epu_mb_offset;
u32 apu2epu_irq;
u32 epu2apu_irq_ack;
u32 reserved43[13];
u32 hpu2epu_mb_offset;
u32 hpu2epu_irq;
u32 epu2hpu_irq_ack;
u32 reserved44[13];
u32 ppu2epu_mb_offset;
u32 ppu2epu_irq;
u32 epu2ppu_irq_ack;
u32 reserved45[13];
u32 reserved46[8];
u32 semaphores[8]; /* Semaphores */
u32 reserved50[32]; /* Reserved for future use */
struct cx18_mailbox apu2cpu_mb;
struct cx18_mailbox hpu2cpu_mb;
struct cx18_mailbox ppu2cpu_mb;
struct cx18_mailbox epu2cpu_mb;
struct cx18_mailbox cpu2apu_mb;
struct cx18_mailbox hpu2apu_mb;
struct cx18_mailbox ppu2apu_mb;
struct cx18_mailbox epu2apu_mb;
struct cx18_mailbox cpu2hpu_mb;
struct cx18_mailbox apu2hpu_mb;
struct cx18_mailbox ppu2hpu_mb;
struct cx18_mailbox epu2hpu_mb;
struct cx18_mailbox cpu2ppu_mb;
struct cx18_mailbox apu2ppu_mb;
struct cx18_mailbox hpu2ppu_mb;
struct cx18_mailbox epu2ppu_mb;
struct cx18_mailbox cpu2epu_mb;
struct cx18_mailbox apu2epu_mb;
struct cx18_mailbox hpu2epu_mb;
struct cx18_mailbox ppu2epu_mb;
struct cx18_mdl_ack cpu_mdl_ack[CX18_MAX_STREAMS][CX18_MAX_MDL_ACKS];
struct cx18_mdl_ent cpu_mdl[1];
};
void cx18_init_scb(struct cx18 *cx);
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,62 @@
/*
* cx18 init/start/stop/exit stream functions
*
* Derived from ivtv-streams.h
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
* Copyright (C) 2008 Andy Walls <awalls@md.metrocast.net>
*
* 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
*/
u32 cx18_find_handle(struct cx18 *cx);
struct cx18_stream *cx18_handle_to_stream(struct cx18 *cx, u32 handle);
int cx18_streams_setup(struct cx18 *cx);
int cx18_streams_register(struct cx18 *cx);
void cx18_streams_cleanup(struct cx18 *cx, int unregister);
#define CX18_ENC_STREAM_TYPE_IDX_FW_MDL_MIN (3)
void cx18_stream_rotate_idx_mdls(struct cx18 *cx);
static inline bool cx18_stream_enabled(struct cx18_stream *s)
{
return s->video_dev ||
(s->dvb && s->dvb->enabled) ||
(s->type == CX18_ENC_STREAM_TYPE_IDX &&
s->cx->stream_buffers[CX18_ENC_STREAM_TYPE_IDX] != 0);
}
/* Related to submission of mdls to firmware */
static inline void cx18_stream_load_fw_queue(struct cx18_stream *s)
{
schedule_work(&s->out_work_order);
}
static inline void cx18_stream_put_mdl_fw(struct cx18_stream *s,
struct cx18_mdl *mdl)
{
/* Put mdl on q_free; the out work handler will move mdl(s) to q_busy */
cx18_enqueue(s, mdl, &s->q_free);
cx18_stream_load_fw_queue(s);
}
void cx18_out_work_handler(struct work_struct *work);
/* Capture related */
int cx18_start_v4l2_encode_stream(struct cx18_stream *s);
int cx18_stop_v4l2_encode_stream(struct cx18_stream *s, int gop_end);
void cx18_stop_all_captures(struct cx18 *cx);

View file

@ -0,0 +1,277 @@
/*
* cx18 Vertical Blank Interval support functions
*
* Derived from ivtv-vbi.c
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* 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 "cx18-driver.h"
#include "cx18-vbi.h"
#include "cx18-ioctl.h"
#include "cx18-queue.h"
/*
* Raster Reference/Protection (RP) bytes, used in Start/End Active
* Video codes emitted from the digitzer in VIP 1.x mode, that flag the start
* of VBI sample or VBI ancillary data regions in the digitial ratser line.
*
* Task FieldEven VerticalBlank HorizontalBlank 0 0 0 0
*/
static const u8 raw_vbi_sav_rp[2] = { 0x20, 0x60 }; /* __V_, _FV_ */
static const u8 sliced_vbi_eav_rp[2] = { 0xb0, 0xf0 }; /* T_VH, TFVH */
static void copy_vbi_data(struct cx18 *cx, int lines, u32 pts_stamp)
{
int line = 0;
int i;
u32 linemask[2] = { 0, 0 };
unsigned short size;
static const u8 mpeg_hdr_data[] = {
/* MPEG-2 Program Pack */
0x00, 0x00, 0x01, 0xba, /* Prog Pack start code */
0x44, 0x00, 0x0c, 0x66, 0x24, 0x01, /* SCR, SCR Ext, markers */
0x01, 0xd1, 0xd3, /* Mux Rate, markers */
0xfa, 0xff, 0xff, /* Res, Suff cnt, Stuff */
/* MPEG-2 Private Stream 1 PES Packet */
0x00, 0x00, 0x01, 0xbd, /* Priv Stream 1 start */
0x00, 0x1a, /* length */
0x84, 0x80, 0x07, /* flags, hdr data len */
0x21, 0x00, 0x5d, 0x63, 0xa7, /* PTS, markers */
0xff, 0xff /* stuffing */
};
const int sd = sizeof(mpeg_hdr_data); /* start of vbi data */
int idx = cx->vbi.frame % CX18_VBI_FRAMES;
u8 *dst = &cx->vbi.sliced_mpeg_data[idx][0];
for (i = 0; i < lines; i++) {
struct v4l2_sliced_vbi_data *sdata = cx->vbi.sliced_data + i;
int f, l;
if (sdata->id == 0)
continue;
l = sdata->line - 6;
f = sdata->field;
if (f)
l += 18;
if (l < 32)
linemask[0] |= (1 << l);
else
linemask[1] |= (1 << (l - 32));
dst[sd + 12 + line * 43] = cx18_service2vbi(sdata->id);
memcpy(dst + sd + 12 + line * 43 + 1, sdata->data, 42);
line++;
}
memcpy(dst, mpeg_hdr_data, sizeof(mpeg_hdr_data));
if (line == 36) {
/* All lines are used, so there is no space for the linemask
(the max size of the VBI data is 36 * 43 + 4 bytes).
So in this case we use the magic number 'ITV0'. */
memcpy(dst + sd, "ITV0", 4);
memmove(dst + sd + 4, dst + sd + 12, line * 43);
size = 4 + ((43 * line + 3) & ~3);
} else {
memcpy(dst + sd, "itv0", 4);
cpu_to_le32s(&linemask[0]);
cpu_to_le32s(&linemask[1]);
memcpy(dst + sd + 4, &linemask[0], 8);
size = 12 + ((43 * line + 3) & ~3);
}
dst[4+16] = (size + 10) >> 8;
dst[5+16] = (size + 10) & 0xff;
dst[9+16] = 0x21 | ((pts_stamp >> 29) & 0x6);
dst[10+16] = (pts_stamp >> 22) & 0xff;
dst[11+16] = 1 | ((pts_stamp >> 14) & 0xff);
dst[12+16] = (pts_stamp >> 7) & 0xff;
dst[13+16] = 1 | ((pts_stamp & 0x7f) << 1);
cx->vbi.sliced_mpeg_size[idx] = sd + size;
}
/* Compress raw VBI format, removes leading SAV codes and surplus space
after the frame. Returns new compressed size. */
/* FIXME - this function ignores the input size. */
static u32 compress_raw_buf(struct cx18 *cx, u8 *buf, u32 size, u32 hdr_size)
{
u32 line_size = vbi_active_samples;
u32 lines = cx->vbi.count * 2;
u8 *q = buf;
u8 *p;
int i;
/* Skip the header */
buf += hdr_size;
for (i = 0; i < lines; i++) {
p = buf + i * line_size;
/* Look for SAV code */
if (p[0] != 0xff || p[1] || p[2] ||
(p[3] != raw_vbi_sav_rp[0] &&
p[3] != raw_vbi_sav_rp[1]))
break;
if (i == lines - 1) {
/* last line is hdr_size bytes short - extrapolate it */
memcpy(q, p + 4, line_size - 4 - hdr_size);
q += line_size - 4 - hdr_size;
p += line_size - hdr_size - 1;
memset(q, (int) *p, hdr_size);
} else {
memcpy(q, p + 4, line_size - 4);
q += line_size - 4;
}
}
return lines * (line_size - 4);
}
static u32 compress_sliced_buf(struct cx18 *cx, u8 *buf, u32 size,
const u32 hdr_size)
{
struct v4l2_decode_vbi_line vbi;
int i;
u32 line = 0;
u32 line_size = cx->is_60hz ? vbi_hblank_samples_60Hz
: vbi_hblank_samples_50Hz;
/* find the first valid line */
for (i = hdr_size, buf += hdr_size; i < size; i++, buf++) {
if (buf[0] == 0xff && !buf[1] && !buf[2] &&
(buf[3] == sliced_vbi_eav_rp[0] ||
buf[3] == sliced_vbi_eav_rp[1]))
break;
}
/*
* The last line is short by hdr_size bytes, but for the remaining
* checks against size, we pretend that it is not, by counting the
* header bytes we knowingly skipped
*/
size -= (i - hdr_size);
if (size < line_size)
return line;
for (i = 0; i < size / line_size; i++) {
u8 *p = buf + i * line_size;
/* Look for EAV code */
if (p[0] != 0xff || p[1] || p[2] ||
(p[3] != sliced_vbi_eav_rp[0] &&
p[3] != sliced_vbi_eav_rp[1]))
continue;
vbi.p = p + 4;
v4l2_subdev_call(cx->sd_av, vbi, decode_vbi_line, &vbi);
if (vbi.type) {
cx->vbi.sliced_data[line].id = vbi.type;
cx->vbi.sliced_data[line].field = vbi.is_second_field;
cx->vbi.sliced_data[line].line = vbi.line;
memcpy(cx->vbi.sliced_data[line].data, vbi.p, 42);
line++;
}
}
return line;
}
static void _cx18_process_vbi_data(struct cx18 *cx, struct cx18_buffer *buf)
{
/*
* The CX23418 provides a 12 byte header in its raw VBI buffers to us:
* 0x3fffffff [4 bytes of something] [4 byte presentation time stamp]
*/
struct vbi_data_hdr {
__be32 magic;
__be32 unknown;
__be32 pts;
} *hdr = (struct vbi_data_hdr *) buf->buf;
u8 *p = (u8 *) buf->buf;
u32 size = buf->bytesused;
u32 pts;
int lines;
/*
* The CX23418 sends us data that is 32 bit little-endian swapped,
* but we want the raw VBI bytes in the order they were in the raster
* line. This has a side effect of making the header big endian
*/
cx18_buf_swap(buf);
/* Raw VBI data */
if (cx18_raw_vbi(cx)) {
size = buf->bytesused =
compress_raw_buf(cx, p, size, sizeof(struct vbi_data_hdr));
/*
* Hack needed for compatibility with old VBI software.
* Write the frame # at the last 4 bytes of the frame
*/
p += size - 4;
memcpy(p, &cx->vbi.frame, 4);
cx->vbi.frame++;
return;
}
/* Sliced VBI data with data insertion */
pts = (be32_to_cpu(hdr->magic) == 0x3fffffff) ? be32_to_cpu(hdr->pts)
: 0;
lines = compress_sliced_buf(cx, p, size, sizeof(struct vbi_data_hdr));
/* always return at least one empty line */
if (lines == 0) {
cx->vbi.sliced_data[0].id = 0;
cx->vbi.sliced_data[0].line = 0;
cx->vbi.sliced_data[0].field = 0;
lines = 1;
}
buf->bytesused = size = lines * sizeof(cx->vbi.sliced_data[0]);
memcpy(p, &cx->vbi.sliced_data[0], size);
if (cx->vbi.insert_mpeg)
copy_vbi_data(cx, lines, pts);
cx->vbi.frame++;
}
void cx18_process_vbi_data(struct cx18 *cx, struct cx18_mdl *mdl,
int streamtype)
{
struct cx18_buffer *buf;
u32 orig_used;
if (streamtype != CX18_ENC_STREAM_TYPE_VBI)
return;
/*
* Big assumption here:
* Every buffer hooked to the MDL's buf_list is a complete VBI frame
* that ends at the end of the buffer.
*
* To assume anything else would make the code in this file
* more complex, or require extra memcpy()'s to make the
* buffers satisfy the above assumption. It's just simpler to set
* up the encoder buffer transfers to make the assumption true.
*/
list_for_each_entry(buf, &mdl->buf_list, list) {
orig_used = buf->bytesused;
if (orig_used == 0)
break;
_cx18_process_vbi_data(cx, buf);
mdl->bytesused -= (orig_used - buf->bytesused);
}
}

View file

@ -0,0 +1,26 @@
/*
* cx18 Vertical Blank Interval support functions
*
* Derived from ivtv-vbi.h
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* 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
*/
void cx18_process_vbi_data(struct cx18 *cx, struct cx18_mdl *mdl,
int streamtype);
int cx18_used_line(struct cx18 *cx, int line, int field);

View file

@ -0,0 +1,28 @@
/*
* cx18 driver version information
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* 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
*/
#ifndef CX18_VERSION_H
#define CX18_VERSION_H
#define CX18_DRIVER_NAME "cx18"
#define CX18_VERSION "1.5.1"
#endif

View file

@ -0,0 +1,32 @@
/*
* cx18 video interface functions
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* 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 "cx18-driver.h"
#include "cx18-video.h"
#include "cx18-cards.h"
void cx18_video_set_io(struct cx18 *cx)
{
int inp = cx->active_input;
v4l2_subdev_call(cx->sd_av, video, s_routing,
cx->card->video_inputs[inp].video_input, 0, 0);
}

View file

@ -0,0 +1,22 @@
/*
* cx18 video interface functions
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* 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
*/
void cx18_video_set_io(struct cx18 *cx);

View file

@ -0,0 +1,492 @@
/*
* cx18 header containing common defines.
*
* Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* 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
*/
#ifndef CX23418_H
#define CX23418_H
#include <media/cx2341x.h>
#define MGR_CMD_MASK 0x40000000
/* The MSB of the command code indicates that this is the completion of a
command */
#define MGR_CMD_MASK_ACK (MGR_CMD_MASK | 0x80000000)
/* Description: This command creates a new instance of a certain task
IN[0] - Task ID. This is one of the XPU_CMD_MASK_YYY where XPU is
the processor on which the task YYY will be created
OUT[0] - Task handle. This handle is passed along with commands to
dispatch to the right instance of the task
ReturnCode - One of the ERR_SYS_... */
#define CX18_CREATE_TASK (MGR_CMD_MASK | 0x0001)
/* Description: This command destroys an instance of a task
IN[0] - Task handle. Hanlde of the task to destroy
ReturnCode - One of the ERR_SYS_... */
#define CX18_DESTROY_TASK (MGR_CMD_MASK | 0x0002)
/* All commands for CPU have the following mask set */
#define CPU_CMD_MASK 0x20000000
#define CPU_CMD_MASK_DEBUG (CPU_CMD_MASK | 0x00000000)
#define CPU_CMD_MASK_ACK (CPU_CMD_MASK | 0x80000000)
#define CPU_CMD_MASK_CAPTURE (CPU_CMD_MASK | 0x00020000)
#define CPU_CMD_MASK_TS (CPU_CMD_MASK | 0x00040000)
#define EPU_CMD_MASK 0x02000000
#define EPU_CMD_MASK_DEBUG (EPU_CMD_MASK | 0x000000)
#define EPU_CMD_MASK_DE (EPU_CMD_MASK | 0x040000)
#define APU_CMD_MASK 0x10000000
#define APU_CMD_MASK_ACK (APU_CMD_MASK | 0x80000000)
#define CX18_APU_ENCODING_METHOD_MPEG (0 << 28)
#define CX18_APU_ENCODING_METHOD_AC3 (1 << 28)
/* Description: Command APU to start audio
IN[0] - audio parameters (same as CX18_CPU_SET_AUDIO_PARAMETERS?)
IN[1] - caller buffer address, or 0
ReturnCode - ??? */
#define CX18_APU_START (APU_CMD_MASK | 0x01)
/* Description: Command APU to stop audio
IN[0] - encoding method to stop
ReturnCode - ??? */
#define CX18_APU_STOP (APU_CMD_MASK | 0x02)
/* Description: Command APU to reset the AI
ReturnCode - ??? */
#define CX18_APU_RESETAI (APU_CMD_MASK | 0x05)
/* Description: This command indicates that a Memory Descriptor List has been
filled with the requested channel type
IN[0] - Task handle. Handle of the task
IN[1] - Offset of the MDL_ACK from the beginning of the local DDR.
IN[2] - Number of CNXT_MDL_ACK structures in the array pointed to by IN[1]
ReturnCode - One of the ERR_DE_... */
#define CX18_EPU_DMA_DONE (EPU_CMD_MASK_DE | 0x0001)
/* Something interesting happened
IN[0] - A value to log
IN[1] - An offset of a string in the MiniMe memory;
0/zero/NULL means "I have nothing to say" */
#define CX18_EPU_DEBUG (EPU_CMD_MASK_DEBUG | 0x0003)
/* Reads memory/registers (32-bit)
IN[0] - Address
OUT[1] - Value */
#define CX18_CPU_DEBUG_PEEK32 (CPU_CMD_MASK_DEBUG | 0x0003)
/* Description: This command starts streaming with the set channel type
IN[0] - Task handle. Handle of the task to start
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_CAPTURE_START (CPU_CMD_MASK_CAPTURE | 0x0002)
/* Description: This command stops streaming with the set channel type
IN[0] - Task handle. Handle of the task to stop
IN[1] - 0 = stop at end of GOP, 1 = stop at end of frame (MPEG only)
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_CAPTURE_STOP (CPU_CMD_MASK_CAPTURE | 0x0003)
/* Description: This command pauses streaming with the set channel type
IN[0] - Task handle. Handle of the task to pause
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_CAPTURE_PAUSE (CPU_CMD_MASK_CAPTURE | 0x0007)
/* Description: This command resumes streaming with the set channel type
IN[0] - Task handle. Handle of the task to resume
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_CAPTURE_RESUME (CPU_CMD_MASK_CAPTURE | 0x0008)
#define CAPTURE_CHANNEL_TYPE_NONE 0
#define CAPTURE_CHANNEL_TYPE_MPEG 1
#define CAPTURE_CHANNEL_TYPE_INDEX 2
#define CAPTURE_CHANNEL_TYPE_YUV 3
#define CAPTURE_CHANNEL_TYPE_PCM 4
#define CAPTURE_CHANNEL_TYPE_VBI 5
#define CAPTURE_CHANNEL_TYPE_SLICED_VBI 6
#define CAPTURE_CHANNEL_TYPE_TS 7
#define CAPTURE_CHANNEL_TYPE_MAX 15
/* Description: This command sets the channel type. This can only be done
when stopped.
IN[0] - Task handle. Handle of the task to start
IN[1] - Channel Type. See Below.
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_SET_CHANNEL_TYPE (CPU_CMD_MASK_CAPTURE + 1)
/* Description: Set stream output type
IN[0] - task handle. Handle of the task to start
IN[1] - type
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_SET_STREAM_OUTPUT_TYPE (CPU_CMD_MASK_CAPTURE | 0x0012)
/* Description: Set video input resolution and frame rate
IN[0] - task handle
IN[1] - reserved
IN[2] - reserved
IN[3] - reserved
IN[4] - reserved
IN[5] - frame rate, 0 - 29.97f/s, 1 - 25f/s
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_SET_VIDEO_IN (CPU_CMD_MASK_CAPTURE | 0x0004)
/* Description: Set video frame rate
IN[0] - task handle. Handle of the task to start
IN[1] - video bit rate mode
IN[2] - video average rate
IN[3] - video peak rate
IN[4] - system mux rate
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_SET_VIDEO_RATE (CPU_CMD_MASK_CAPTURE | 0x0005)
/* Description: Set video output resolution
IN[0] - task handle
IN[1] - horizontal size
IN[2] - vertical size
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_SET_VIDEO_RESOLUTION (CPU_CMD_MASK_CAPTURE | 0x0006)
/* Description: This command set filter parameters
IN[0] - Task handle. Handle of the task
IN[1] - type, 0 - temporal, 1 - spatial, 2 - median
IN[2] - mode, temporal/spatial: 0 - disable, 1 - static, 2 - dynamic
median: 0 = disable, 1 = horizontal, 2 = vertical,
3 = horizontal/vertical, 4 = diagonal
IN[3] - strength, temporal 0 - 31, spatial 0 - 15
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_SET_FILTER_PARAM (CPU_CMD_MASK_CAPTURE | 0x0009)
/* Description: This command set spatial filter type
IN[0] - Task handle.
IN[1] - luma type: 0 = disable, 1 = 1D horizontal only, 2 = 1D vertical only,
3 = 2D H/V separable, 4 = 2D symmetric non-separable
IN[2] - chroma type: 0 - disable, 1 = 1D horizontal
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_SET_SPATIAL_FILTER_TYPE (CPU_CMD_MASK_CAPTURE | 0x000C)
/* Description: This command set coring levels for median filter
IN[0] - Task handle.
IN[1] - luma_high
IN[2] - luma_low
IN[3] - chroma_high
IN[4] - chroma_low
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_SET_MEDIAN_CORING (CPU_CMD_MASK_CAPTURE | 0x000E)
/* Description: This command set the picture type mask for index file
IN[0] - Task handle (ignored by firmware)
IN[1] - 0 = disable index file output
1 = output I picture
2 = P picture
4 = B picture
other = illegal */
#define CX18_CPU_SET_INDEXTABLE (CPU_CMD_MASK_CAPTURE | 0x0010)
/* Description: Set audio parameters
IN[0] - task handle. Handle of the task to start
IN[1] - audio parameter
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_SET_AUDIO_PARAMETERS (CPU_CMD_MASK_CAPTURE | 0x0011)
/* Description: Set video mute
IN[0] - task handle. Handle of the task to start
IN[1] - bit31-24: muteYvalue
bit23-16: muteUvalue
bit15-8: muteVvalue
bit0: 1:mute, 0: unmute
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_SET_VIDEO_MUTE (CPU_CMD_MASK_CAPTURE | 0x0013)
/* Description: Set audio mute
IN[0] - task handle. Handle of the task to start
IN[1] - mute/unmute
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_SET_AUDIO_MUTE (CPU_CMD_MASK_CAPTURE | 0x0014)
/* Description: Set stream output type
IN[0] - task handle. Handle of the task to start
IN[1] - subType
SET_INITIAL_SCR 1
SET_QUALITY_MODE 2
SET_VIM_PROTECT_MODE 3
SET_PTS_CORRECTION 4
SET_USB_FLUSH_MODE 5
SET_MERAQPAR_ENABLE 6
SET_NAV_PACK_INSERTION 7
SET_SCENE_CHANGE_ENABLE 8
IN[2] - parameter 1
IN[3] - parameter 2
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_SET_MISC_PARAMETERS (CPU_CMD_MASK_CAPTURE | 0x0015)
/* Description: Set raw VBI parameters
IN[0] - Task handle
IN[1] - No. of input lines per field:
bit[15:0]: field 1,
bit[31:16]: field 2
IN[2] - No. of input bytes per line
IN[3] - No. of output frames per transfer
IN[4] - start code
IN[5] - stop code
ReturnCode */
#define CX18_CPU_SET_RAW_VBI_PARAM (CPU_CMD_MASK_CAPTURE | 0x0016)
/* Description: Set capture line No.
IN[0] - task handle. Handle of the task to start
IN[1] - height1
IN[2] - height2
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_SET_CAPTURE_LINE_NO (CPU_CMD_MASK_CAPTURE | 0x0017)
/* Description: Set copyright
IN[0] - task handle. Handle of the task to start
IN[1] - copyright
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_SET_COPYRIGHT (CPU_CMD_MASK_CAPTURE | 0x0018)
/* Description: Set audio PID
IN[0] - task handle. Handle of the task to start
IN[1] - PID
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_SET_AUDIO_PID (CPU_CMD_MASK_CAPTURE | 0x0019)
/* Description: Set video PID
IN[0] - task handle. Handle of the task to start
IN[1] - PID
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_SET_VIDEO_PID (CPU_CMD_MASK_CAPTURE | 0x001A)
/* Description: Set Vertical Crop Line
IN[0] - task handle. Handle of the task to start
IN[1] - Line
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_SET_VER_CROP_LINE (CPU_CMD_MASK_CAPTURE | 0x001B)
/* Description: Set COP structure
IN[0] - task handle. Handle of the task to start
IN[1] - M
IN[2] - N
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_SET_GOP_STRUCTURE (CPU_CMD_MASK_CAPTURE | 0x001C)
/* Description: Set Scene Change Detection
IN[0] - task handle. Handle of the task to start
IN[1] - scene change
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_SET_SCENE_CHANGE_DETECTION (CPU_CMD_MASK_CAPTURE | 0x001D)
/* Description: Set Aspect Ratio
IN[0] - task handle. Handle of the task to start
IN[1] - AspectRatio
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_SET_ASPECT_RATIO (CPU_CMD_MASK_CAPTURE | 0x001E)
/* Description: Set Skip Input Frame
IN[0] - task handle. Handle of the task to start
IN[1] - skip input frames
ReturnCode - One of the ERR_CAPTURE_... */
#define CX18_CPU_SET_SKIP_INPUT_FRAME (CPU_CMD_MASK_CAPTURE | 0x001F)
/* Description: Set sliced VBI parameters -
Note This API will only apply to MPEG and Sliced VBI Channels
IN[0] - Task handle
IN[1] - output type, 0 - CC, 1 - Moji, 2 - Teletext
IN[2] - start / stop line
bit[15:0] start line number
bit[31:16] stop line number
IN[3] - number of output frames per interrupt
IN[4] - VBI insertion mode
bit 0: output user data, 1 - enable
bit 1: output private stream, 1 - enable
bit 2: mux option, 0 - in GOP, 1 - in picture
bit[7:0] private stream ID
IN[5] - insertion period while mux option is in picture
ReturnCode - VBI data offset */
#define CX18_CPU_SET_SLICED_VBI_PARAM (CPU_CMD_MASK_CAPTURE | 0x0020)
/* Description: Set the user data place holder
IN[0] - type of data (0 for user)
IN[1] - Stuffing period
IN[2] - ID data size in word (less than 10)
IN[3] - Pointer to ID buffer */
#define CX18_CPU_SET_USERDATA_PLACE_HOLDER (CPU_CMD_MASK_CAPTURE | 0x0021)
/* Description:
In[0] Task Handle
return parameter:
Out[0] Reserved
Out[1] Video PTS bit[32:2] of last output video frame.
Out[2] Video PTS bit[ 1:0] of last output video frame.
Out[3] Hardware Video PTS counter bit[31:0],
these bits get incremented on every 90kHz clock tick.
Out[4] Hardware Video PTS counter bit32,
these bits get incremented on every 90kHz clock tick.
ReturnCode */
#define CX18_CPU_GET_ENC_PTS (CPU_CMD_MASK_CAPTURE | 0x0022)
/* Description: Set VFC parameters
IN[0] - task handle
IN[1] - VFC enable flag, 1 - enable, 0 - disable
*/
#define CX18_CPU_SET_VFC_PARAM (CPU_CMD_MASK_CAPTURE | 0x0023)
/* Below is the list of commands related to the data exchange */
#define CPU_CMD_MASK_DE (CPU_CMD_MASK | 0x040000)
/* Description: This command provides the physical base address of the local
DDR as viewed by EPU
IN[0] - Physical offset where EPU has the local DDR mapped
ReturnCode - One of the ERR_DE_... */
#define CPU_CMD_DE_SetBase (CPU_CMD_MASK_DE | 0x0001)
/* Description: This command provides the offsets in the device memory where
the 2 cx18_mdl_ack blocks reside
IN[0] - Task handle. Handle of the task to start
IN[1] - Offset of the first cx18_mdl_ack from the beginning of the
local DDR.
IN[2] - Offset of the second cx18_mdl_ack from the beginning of the
local DDR.
ReturnCode - One of the ERR_DE_... */
#define CX18_CPU_DE_SET_MDL_ACK (CPU_CMD_MASK_DE | 0x0002)
/* Description: This command provides the offset to a Memory Descriptor List
IN[0] - Task handle. Handle of the task to start
IN[1] - Offset of the MDL from the beginning of the local DDR.
IN[2] - Number of cx18_mdl_ent structures in the array pointed to by IN[1]
IN[3] - Buffer ID
IN[4] - Total buffer length
ReturnCode - One of the ERR_DE_... */
#define CX18_CPU_DE_SET_MDL (CPU_CMD_MASK_DE | 0x0005)
/* Description: This command requests return of all current Memory
Descriptor Lists to the driver
IN[0] - Task handle. Handle of the task to start
ReturnCode - One of the ERR_DE_... */
#define CX18_CPU_DE_RELEASE_MDL (CPU_CMD_MASK_DE | 0x0006)
/* Description: This command signals the cpu that the dat buffer has been
consumed and ready for re-use.
IN[0] - Task handle. Handle of the task
IN[1] - Offset of the data block from the beginning of the local DDR.
IN[2] - Number of bytes in the data block
ReturnCode - One of the ERR_DE_... */
/* #define CX18_CPU_DE_RELEASE_BUFFER (CPU_CMD_MASK_DE | 0x0007) */
/* No Error / Success */
#define CNXT_OK 0x000000
/* Received unknown command */
#define CXERR_UNK_CMD 0x000001
/* First parameter in the command is invalid */
#define CXERR_INVALID_PARAM1 0x000002
/* Second parameter in the command is invalid */
#define CXERR_INVALID_PARAM2 0x000003
/* Device interface is not open/found */
#define CXERR_DEV_NOT_FOUND 0x000004
/* Requested function is not implemented/available */
#define CXERR_NOTSUPPORTED 0x000005
/* Invalid pointer is provided */
#define CXERR_BADPTR 0x000006
/* Unable to allocate memory */
#define CXERR_NOMEM 0x000007
/* Object/Link not found */
#define CXERR_LINK 0x000008
/* Device busy, command cannot be executed */
#define CXERR_BUSY 0x000009
/* File/device/handle is not open. */
#define CXERR_NOT_OPEN 0x00000A
/* Value is out of range */
#define CXERR_OUTOFRANGE 0x00000B
/* Buffer overflow */
#define CXERR_OVERFLOW 0x00000C
/* Version mismatch */
#define CXERR_BADVER 0x00000D
/* Operation timed out */
#define CXERR_TIMEOUT 0x00000E
/* Operation aborted */
#define CXERR_ABORT 0x00000F
/* Specified I2C device not found for read/write */
#define CXERR_I2CDEV_NOTFOUND 0x000010
/* Error in I2C data xfer (but I2C device is present) */
#define CXERR_I2CDEV_XFERERR 0x000011
/* Chanel changing component not ready */
#define CXERR_CHANNELNOTREADY 0x000012
/* PPU (Presensation/Decoder) mail box is corrupted */
#define CXERR_PPU_MB_CORRUPT 0x000013
/* CPU (Capture/Encoder) mail box is corrupted */
#define CXERR_CPU_MB_CORRUPT 0x000014
/* APU (Audio) mail box is corrupted */
#define CXERR_APU_MB_CORRUPT 0x000015
/* Unable to open file for reading */
#define CXERR_FILE_OPEN_READ 0x000016
/* Unable to open file for writing */
#define CXERR_FILE_OPEN_WRITE 0x000017
/* Unable to find the I2C section specified */
#define CXERR_I2C_BADSECTION 0x000018
/* Error in I2C data xfer (but I2C device is present) */
#define CXERR_I2CDEV_DATALOW 0x000019
/* Error in I2C data xfer (but I2C device is present) */
#define CXERR_I2CDEV_CLOCKLOW 0x00001A
/* No Interrupt received from HW (for I2C access) */
#define CXERR_NO_HW_I2C_INTR 0x00001B
/* RPU is not ready to accept commands! */
#define CXERR_RPU_NOT_READY 0x00001C
/* RPU is not ready to accept commands! */
#define CXERR_RPU_NO_ACK 0x00001D
/* The are no buffers ready. Try again soon! */
#define CXERR_NODATA_AGAIN 0x00001E
/* The stream is stopping. Function not allowed now! */
#define CXERR_STOPPING_STATUS 0x00001F
/* Trying to access hardware when the power is turned OFF */
#define CXERR_DEVPOWER_OFF 0x000020
#endif /* CX23418_H */

View file

@ -0,0 +1,60 @@
config VIDEO_CX23885
tristate "Conexant cx23885 (2388x successor) support"
depends on DVB_CORE && VIDEO_DEV && PCI && I2C && INPUT && SND
select SND_PCM
select I2C_ALGOBIT
select VIDEO_TUNER
select VIDEO_TVEEPROM
depends on RC_CORE
select VIDEOBUF2_DVB
select VIDEOBUF2_DMA_SG
select VIDEO_CX25840
select VIDEO_CX2341X
select DVB_DIB7000P if MEDIA_SUBDRV_AUTOSELECT
select DVB_DRXK if MEDIA_SUBDRV_AUTOSELECT
select DVB_S5H1409 if MEDIA_SUBDRV_AUTOSELECT
select DVB_S5H1411 if MEDIA_SUBDRV_AUTOSELECT
select DVB_LGDT330X if MEDIA_SUBDRV_AUTOSELECT
select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT
select DVB_TDA10048 if MEDIA_SUBDRV_AUTOSELECT
select DVB_LNBP21 if MEDIA_SUBDRV_AUTOSELECT
select DVB_STV090x if MEDIA_SUBDRV_AUTOSELECT
select DVB_STB6100 if MEDIA_SUBDRV_AUTOSELECT
select DVB_STV6110 if MEDIA_SUBDRV_AUTOSELECT
select DVB_CX24116 if MEDIA_SUBDRV_AUTOSELECT
select DVB_CX24117 if MEDIA_SUBDRV_AUTOSELECT
select DVB_STV0900 if MEDIA_SUBDRV_AUTOSELECT
select DVB_DS3000 if MEDIA_SUBDRV_AUTOSELECT
select DVB_TS2020 if MEDIA_SUBDRV_AUTOSELECT
select DVB_STV0367 if MEDIA_SUBDRV_AUTOSELECT
select DVB_TDA10071 if MEDIA_SUBDRV_AUTOSELECT
select DVB_A8293 if MEDIA_SUBDRV_AUTOSELECT
select DVB_MB86A20S if MEDIA_SUBDRV_AUTOSELECT
select DVB_SI2165 if MEDIA_SUBDRV_AUTOSELECT
select DVB_SI2168 if MEDIA_SUBDRV_AUTOSELECT
select DVB_M88DS3103 if MEDIA_SUBDRV_AUTOSELECT
select MEDIA_TUNER_MT2063 if MEDIA_SUBDRV_AUTOSELECT
select MEDIA_TUNER_MT2131 if MEDIA_SUBDRV_AUTOSELECT
select MEDIA_TUNER_XC2028 if MEDIA_SUBDRV_AUTOSELECT
select MEDIA_TUNER_TDA8290 if MEDIA_SUBDRV_AUTOSELECT
select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT
select MEDIA_TUNER_XC5000 if MEDIA_SUBDRV_AUTOSELECT
select MEDIA_TUNER_SI2157 if MEDIA_SUBDRV_AUTOSELECT
select MEDIA_TUNER_M88TS2022 if MEDIA_SUBDRV_AUTOSELECT
select DVB_TUNER_DIB0070 if MEDIA_SUBDRV_AUTOSELECT
---help---
This is a video4linux driver for Conexant 23885 based
TV cards.
To compile this driver as a module, choose M here: the
module will be called cx23885
config MEDIA_ALTERA_CI
tristate "Altera FPGA based CI module"
depends on VIDEO_CX23885 && DVB_CORE
select ALTERA_STAPL
---help---
An Altera FPGA CI module for NetUP Dual DVB-T/C RF CI card.
To compile this driver as a module, choose M here: the
module will be called altera-ci

View file

@ -0,0 +1,15 @@
cx23885-objs := cx23885-cards.o cx23885-video.o cx23885-vbi.o \
cx23885-core.o cx23885-i2c.o cx23885-dvb.o cx23885-417.o \
cx23885-ioctl.o cx23885-ir.o cx23885-av.o cx23885-input.o \
cx23888-ir.o netup-init.o cimax2.o netup-eeprom.o \
cx23885-f300.o cx23885-alsa.o
obj-$(CONFIG_VIDEO_CX23885) += cx23885.o
obj-$(CONFIG_MEDIA_ALTERA_CI) += altera-ci.o
ccflags-y += -Idrivers/media/i2c
ccflags-y += -Idrivers/media/tuners
ccflags-y += -Idrivers/media/dvb-core
ccflags-y += -Idrivers/media/dvb-frontends
ccflags-y += $(extra-cflags-y) $(extra-cflags-m)

View file

@ -0,0 +1,836 @@
/*
* altera-ci.c
*
* CI driver in conjunction with NetUp Dual DVB-T/C RF CI card
*
* Copyright (C) 2010,2011 NetUP Inc.
* Copyright (C) 2010,2011 Igor M. Liplianin <liplianin@netup.ru>
*
* 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.
*/
/*
* currently cx23885 GPIO's used.
* GPIO-0 ~INT in
* GPIO-1 TMS out
* GPIO-2 ~reset chips out
* GPIO-3 to GPIO-10 data/addr for CA in/out
* GPIO-11 ~CS out
* GPIO-12 AD_RG out
* GPIO-13 ~WR out
* GPIO-14 ~RD out
* GPIO-15 ~RDY in
* GPIO-16 TCK out
* GPIO-17 TDO in
* GPIO-18 TDI out
*/
/*
* Bit definitions for MC417_RWD and MC417_OEN registers
* bits 31-16
* +-----------+
* | Reserved |
* +-----------+
* bit 15 bit 14 bit 13 bit 12 bit 11 bit 10 bit 9 bit 8
* +-------+-------+-------+-------+-------+-------+-------+-------+
* | TDI | TDO | TCK | RDY# | #RD | #WR | AD_RG | #CS |
* +-------+-------+-------+-------+-------+-------+-------+-------+
* bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0
* +-------+-------+-------+-------+-------+-------+-------+-------+
* | DATA7| DATA6| DATA5| DATA4| DATA3| DATA2| DATA1| DATA0|
* +-------+-------+-------+-------+-------+-------+-------+-------+
*/
#include <dvb_demux.h>
#include <dvb_frontend.h>
#include "altera-ci.h"
#include "dvb_ca_en50221.h"
/* FPGA regs */
#define NETUP_CI_INT_CTRL 0x00
#define NETUP_CI_BUSCTRL2 0x01
#define NETUP_CI_ADDR0 0x04
#define NETUP_CI_ADDR1 0x05
#define NETUP_CI_DATA 0x06
#define NETUP_CI_BUSCTRL 0x07
#define NETUP_CI_PID_ADDR0 0x08
#define NETUP_CI_PID_ADDR1 0x09
#define NETUP_CI_PID_DATA 0x0a
#define NETUP_CI_TSA_DIV 0x0c
#define NETUP_CI_TSB_DIV 0x0d
#define NETUP_CI_REVISION 0x0f
/* const for ci op */
#define NETUP_CI_FLG_CTL 1
#define NETUP_CI_FLG_RD 1
#define NETUP_CI_FLG_AD 1
static unsigned int ci_dbg;
module_param(ci_dbg, int, 0644);
MODULE_PARM_DESC(ci_dbg, "Enable CI debugging");
static unsigned int pid_dbg;
module_param(pid_dbg, int, 0644);
MODULE_PARM_DESC(pid_dbg, "Enable PID filtering debugging");
MODULE_DESCRIPTION("altera FPGA CI module");
MODULE_AUTHOR("Igor M. Liplianin <liplianin@netup.ru>");
MODULE_LICENSE("GPL");
#define ci_dbg_print(args...) \
do { \
if (ci_dbg) \
printk(KERN_DEBUG args); \
} while (0)
#define pid_dbg_print(args...) \
do { \
if (pid_dbg) \
printk(KERN_DEBUG args); \
} while (0)
struct altera_ci_state;
struct netup_hw_pid_filter;
struct fpga_internal {
void *dev;
struct mutex fpga_mutex;/* two CI's on the same fpga */
struct netup_hw_pid_filter *pid_filt[2];
struct altera_ci_state *state[2];
struct work_struct work;
int (*fpga_rw) (void *dev, int flag, int data, int rw);
int cis_used;
int filts_used;
int strt_wrk;
};
/* stores all private variables for communication with CI */
struct altera_ci_state {
struct fpga_internal *internal;
struct dvb_ca_en50221 ca;
int status;
int nr;
};
/* stores all private variables for hardware pid filtering */
struct netup_hw_pid_filter {
struct fpga_internal *internal;
struct dvb_demux *demux;
/* save old functions */
int (*start_feed)(struct dvb_demux_feed *feed);
int (*stop_feed)(struct dvb_demux_feed *feed);
int status;
int nr;
};
/* internal params node */
struct fpga_inode {
/* pointer for internal params, one for each pair of CI's */
struct fpga_internal *internal;
struct fpga_inode *next_inode;
};
/* first internal params */
static struct fpga_inode *fpga_first_inode;
/* find chip by dev */
static struct fpga_inode *find_inode(void *dev)
{
struct fpga_inode *temp_chip = fpga_first_inode;
if (temp_chip == NULL)
return temp_chip;
/*
Search for the last fpga CI chip or
find it by dev */
while ((temp_chip != NULL) &&
(temp_chip->internal->dev != dev))
temp_chip = temp_chip->next_inode;
return temp_chip;
}
/* check demux */
static struct fpga_internal *check_filter(struct fpga_internal *temp_int,
void *demux_dev, int filt_nr)
{
if (temp_int == NULL)
return NULL;
if ((temp_int->pid_filt[filt_nr]) == NULL)
return NULL;
if (temp_int->pid_filt[filt_nr]->demux == demux_dev)
return temp_int;
return NULL;
}
/* find chip by demux */
static struct fpga_inode *find_dinode(void *demux_dev)
{
struct fpga_inode *temp_chip = fpga_first_inode;
struct fpga_internal *temp_int;
/*
* Search of the last fpga CI chip or
* find it by demux
*/
while (temp_chip != NULL) {
if (temp_chip->internal != NULL) {
temp_int = temp_chip->internal;
if (check_filter(temp_int, demux_dev, 0))
break;
if (check_filter(temp_int, demux_dev, 1))
break;
}
temp_chip = temp_chip->next_inode;
}
return temp_chip;
}
/* deallocating chip */
static void remove_inode(struct fpga_internal *internal)
{
struct fpga_inode *prev_node = fpga_first_inode;
struct fpga_inode *del_node = find_inode(internal->dev);
if (del_node != NULL) {
if (del_node == fpga_first_inode) {
fpga_first_inode = del_node->next_inode;
} else {
while (prev_node->next_inode != del_node)
prev_node = prev_node->next_inode;
if (del_node->next_inode == NULL)
prev_node->next_inode = NULL;
else
prev_node->next_inode =
prev_node->next_inode->next_inode;
}
kfree(del_node);
}
}
/* allocating new chip */
static struct fpga_inode *append_internal(struct fpga_internal *internal)
{
struct fpga_inode *new_node = fpga_first_inode;
if (new_node == NULL) {
new_node = kmalloc(sizeof(struct fpga_inode), GFP_KERNEL);
fpga_first_inode = new_node;
} else {
while (new_node->next_inode != NULL)
new_node = new_node->next_inode;
new_node->next_inode =
kmalloc(sizeof(struct fpga_inode), GFP_KERNEL);
if (new_node->next_inode != NULL)
new_node = new_node->next_inode;
else
new_node = NULL;
}
if (new_node != NULL) {
new_node->internal = internal;
new_node->next_inode = NULL;
}
return new_node;
}
static int netup_fpga_op_rw(struct fpga_internal *inter, int addr,
u8 val, u8 read)
{
inter->fpga_rw(inter->dev, NETUP_CI_FLG_AD, addr, 0);
return inter->fpga_rw(inter->dev, 0, val, read);
}
/* flag - mem/io, read - read/write */
static int altera_ci_op_cam(struct dvb_ca_en50221 *en50221, int slot,
u8 flag, u8 read, int addr, u8 val)
{
struct altera_ci_state *state = en50221->data;
struct fpga_internal *inter = state->internal;
u8 store;
int mem = 0;
if (0 != slot)
return -EINVAL;
mutex_lock(&inter->fpga_mutex);
netup_fpga_op_rw(inter, NETUP_CI_ADDR0, ((addr << 1) & 0xfe), 0);
netup_fpga_op_rw(inter, NETUP_CI_ADDR1, ((addr >> 7) & 0x7f), 0);
store = netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL, 0, NETUP_CI_FLG_RD);
store &= 0x0f;
store |= ((state->nr << 7) | (flag << 6));
netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL, store, 0);
mem = netup_fpga_op_rw(inter, NETUP_CI_DATA, val, read);
mutex_unlock(&inter->fpga_mutex);
ci_dbg_print("%s: %s: addr=[0x%02x], %s=%x\n", __func__,
(read) ? "read" : "write", addr,
(flag == NETUP_CI_FLG_CTL) ? "ctl" : "mem",
(read) ? mem : val);
return mem;
}
static int altera_ci_read_attribute_mem(struct dvb_ca_en50221 *en50221,
int slot, int addr)
{
return altera_ci_op_cam(en50221, slot, 0, NETUP_CI_FLG_RD, addr, 0);
}
static int altera_ci_write_attribute_mem(struct dvb_ca_en50221 *en50221,
int slot, int addr, u8 data)
{
return altera_ci_op_cam(en50221, slot, 0, 0, addr, data);
}
static int altera_ci_read_cam_ctl(struct dvb_ca_en50221 *en50221,
int slot, u8 addr)
{
return altera_ci_op_cam(en50221, slot, NETUP_CI_FLG_CTL,
NETUP_CI_FLG_RD, addr, 0);
}
static int altera_ci_write_cam_ctl(struct dvb_ca_en50221 *en50221, int slot,
u8 addr, u8 data)
{
return altera_ci_op_cam(en50221, slot, NETUP_CI_FLG_CTL, 0, addr, data);
}
static int altera_ci_slot_reset(struct dvb_ca_en50221 *en50221, int slot)
{
struct altera_ci_state *state = en50221->data;
struct fpga_internal *inter = state->internal;
/* reasonable timeout for CI reset is 10 seconds */
unsigned long t_out = jiffies + msecs_to_jiffies(9999);
int ret;
ci_dbg_print("%s\n", __func__);
if (0 != slot)
return -EINVAL;
mutex_lock(&inter->fpga_mutex);
ret = netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL, 0, NETUP_CI_FLG_RD);
netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL,
(ret & 0xcf) | (1 << (5 - state->nr)), 0);
mutex_unlock(&inter->fpga_mutex);
for (;;) {
mdelay(50);
mutex_lock(&inter->fpga_mutex);
ret = netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL,
0, NETUP_CI_FLG_RD);
mutex_unlock(&inter->fpga_mutex);
if ((ret & (1 << (5 - state->nr))) == 0)
break;
if (time_after(jiffies, t_out))
break;
}
ci_dbg_print("%s: %d msecs\n", __func__,
jiffies_to_msecs(jiffies + msecs_to_jiffies(9999) - t_out));
return 0;
}
static int altera_ci_slot_shutdown(struct dvb_ca_en50221 *en50221, int slot)
{
/* not implemented */
return 0;
}
static int altera_ci_slot_ts_ctl(struct dvb_ca_en50221 *en50221, int slot)
{
struct altera_ci_state *state = en50221->data;
struct fpga_internal *inter = state->internal;
int ret;
ci_dbg_print("%s\n", __func__);
if (0 != slot)
return -EINVAL;
mutex_lock(&inter->fpga_mutex);
ret = netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL, 0, NETUP_CI_FLG_RD);
netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL,
(ret & 0x0f) | (1 << (3 - state->nr)), 0);
mutex_unlock(&inter->fpga_mutex);
return 0;
}
/* work handler */
static void netup_read_ci_status(struct work_struct *work)
{
struct fpga_internal *inter =
container_of(work, struct fpga_internal, work);
int ret;
ci_dbg_print("%s\n", __func__);
mutex_lock(&inter->fpga_mutex);
/* ack' irq */
ret = netup_fpga_op_rw(inter, NETUP_CI_INT_CTRL, 0, NETUP_CI_FLG_RD);
ret = netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL, 0, NETUP_CI_FLG_RD);
mutex_unlock(&inter->fpga_mutex);
if (inter->state[1] != NULL) {
inter->state[1]->status =
((ret & 1) == 0 ?
DVB_CA_EN50221_POLL_CAM_PRESENT |
DVB_CA_EN50221_POLL_CAM_READY : 0);
ci_dbg_print("%s: setting CI[1] status = 0x%x\n",
__func__, inter->state[1]->status);
}
if (inter->state[0] != NULL) {
inter->state[0]->status =
((ret & 2) == 0 ?
DVB_CA_EN50221_POLL_CAM_PRESENT |
DVB_CA_EN50221_POLL_CAM_READY : 0);
ci_dbg_print("%s: setting CI[0] status = 0x%x\n",
__func__, inter->state[0]->status);
}
}
/* CI irq handler */
int altera_ci_irq(void *dev)
{
struct fpga_inode *temp_int = NULL;
struct fpga_internal *inter = NULL;
ci_dbg_print("%s\n", __func__);
if (dev != NULL) {
temp_int = find_inode(dev);
if (temp_int != NULL) {
inter = temp_int->internal;
schedule_work(&inter->work);
}
}
return 1;
}
EXPORT_SYMBOL(altera_ci_irq);
static int altera_poll_ci_slot_status(struct dvb_ca_en50221 *en50221,
int slot, int open)
{
struct altera_ci_state *state = en50221->data;
if (0 != slot)
return -EINVAL;
return state->status;
}
static void altera_hw_filt_release(void *main_dev, int filt_nr)
{
struct fpga_inode *temp_int = find_inode(main_dev);
struct netup_hw_pid_filter *pid_filt = NULL;
ci_dbg_print("%s\n", __func__);
if (temp_int != NULL) {
pid_filt = temp_int->internal->pid_filt[filt_nr - 1];
/* stored old feed controls */
pid_filt->demux->start_feed = pid_filt->start_feed;
pid_filt->demux->stop_feed = pid_filt->stop_feed;
if (((--(temp_int->internal->filts_used)) <= 0) &&
((temp_int->internal->cis_used) <= 0)) {
ci_dbg_print("%s: Actually removing\n", __func__);
remove_inode(temp_int->internal);
kfree(pid_filt->internal);
}
kfree(pid_filt);
}
}
EXPORT_SYMBOL(altera_hw_filt_release);
void altera_ci_release(void *dev, int ci_nr)
{
struct fpga_inode *temp_int = find_inode(dev);
struct altera_ci_state *state = NULL;
ci_dbg_print("%s\n", __func__);
if (temp_int != NULL) {
state = temp_int->internal->state[ci_nr - 1];
altera_hw_filt_release(dev, ci_nr);
if (((temp_int->internal->filts_used) <= 0) &&
((--(temp_int->internal->cis_used)) <= 0)) {
ci_dbg_print("%s: Actually removing\n", __func__);
remove_inode(temp_int->internal);
kfree(state->internal);
}
if (state != NULL) {
if (state->ca.data != NULL)
dvb_ca_en50221_release(&state->ca);
kfree(state);
}
}
}
EXPORT_SYMBOL(altera_ci_release);
static void altera_pid_control(struct netup_hw_pid_filter *pid_filt,
u16 pid, int onoff)
{
struct fpga_internal *inter = pid_filt->internal;
u8 store = 0;
/* pid 0-0x1f always enabled, don't touch them */
if ((pid == 0x2000) || (pid < 0x20))
return;
mutex_lock(&inter->fpga_mutex);
netup_fpga_op_rw(inter, NETUP_CI_PID_ADDR0, (pid >> 3) & 0xff, 0);
netup_fpga_op_rw(inter, NETUP_CI_PID_ADDR1,
((pid >> 11) & 0x03) | (pid_filt->nr << 2), 0);
store = netup_fpga_op_rw(inter, NETUP_CI_PID_DATA, 0, NETUP_CI_FLG_RD);
if (onoff)/* 0 - on, 1 - off */
store |= (1 << (pid & 7));
else
store &= ~(1 << (pid & 7));
netup_fpga_op_rw(inter, NETUP_CI_PID_DATA, store, 0);
mutex_unlock(&inter->fpga_mutex);
pid_dbg_print("%s: (%d) set pid: %5d 0x%04x '%s'\n", __func__,
pid_filt->nr, pid, pid, onoff ? "off" : "on");
}
static void altera_toggle_fullts_streaming(struct netup_hw_pid_filter *pid_filt,
int filt_nr, int onoff)
{
struct fpga_internal *inter = pid_filt->internal;
u8 store = 0;
int i;
pid_dbg_print("%s: pid_filt->nr[%d] now %s\n", __func__, pid_filt->nr,
onoff ? "off" : "on");
if (onoff)/* 0 - on, 1 - off */
store = 0xff;/* ignore pid */
else
store = 0;/* enable pid */
mutex_lock(&inter->fpga_mutex);
for (i = 0; i < 1024; i++) {
netup_fpga_op_rw(inter, NETUP_CI_PID_ADDR0, i & 0xff, 0);
netup_fpga_op_rw(inter, NETUP_CI_PID_ADDR1,
((i >> 8) & 0x03) | (pid_filt->nr << 2), 0);
/* pid 0-0x1f always enabled */
netup_fpga_op_rw(inter, NETUP_CI_PID_DATA,
(i > 3 ? store : 0), 0);
}
mutex_unlock(&inter->fpga_mutex);
}
static int altera_pid_feed_control(void *demux_dev, int filt_nr,
struct dvb_demux_feed *feed, int onoff)
{
struct fpga_inode *temp_int = find_dinode(demux_dev);
struct fpga_internal *inter = temp_int->internal;
struct netup_hw_pid_filter *pid_filt = inter->pid_filt[filt_nr - 1];
altera_pid_control(pid_filt, feed->pid, onoff ? 0 : 1);
/* call old feed proc's */
if (onoff)
pid_filt->start_feed(feed);
else
pid_filt->stop_feed(feed);
if (feed->pid == 0x2000)
altera_toggle_fullts_streaming(pid_filt, filt_nr,
onoff ? 0 : 1);
return 0;
}
EXPORT_SYMBOL(altera_pid_feed_control);
static int altera_ci_start_feed(struct dvb_demux_feed *feed, int num)
{
altera_pid_feed_control(feed->demux, num, feed, 1);
return 0;
}
static int altera_ci_stop_feed(struct dvb_demux_feed *feed, int num)
{
altera_pid_feed_control(feed->demux, num, feed, 0);
return 0;
}
static int altera_ci_start_feed_1(struct dvb_demux_feed *feed)
{
return altera_ci_start_feed(feed, 1);
}
static int altera_ci_stop_feed_1(struct dvb_demux_feed *feed)
{
return altera_ci_stop_feed(feed, 1);
}
static int altera_ci_start_feed_2(struct dvb_demux_feed *feed)
{
return altera_ci_start_feed(feed, 2);
}
static int altera_ci_stop_feed_2(struct dvb_demux_feed *feed)
{
return altera_ci_stop_feed(feed, 2);
}
static int altera_hw_filt_init(struct altera_ci_config *config, int hw_filt_nr)
{
struct netup_hw_pid_filter *pid_filt = NULL;
struct fpga_inode *temp_int = find_inode(config->dev);
struct fpga_internal *inter = NULL;
int ret = 0;
pid_filt = kzalloc(sizeof(struct netup_hw_pid_filter), GFP_KERNEL);
ci_dbg_print("%s\n", __func__);
if (!pid_filt) {
ret = -ENOMEM;
goto err;
}
if (temp_int != NULL) {
inter = temp_int->internal;
(inter->filts_used)++;
ci_dbg_print("%s: Find Internal Structure!\n", __func__);
} else {
inter = kzalloc(sizeof(struct fpga_internal), GFP_KERNEL);
if (!inter) {
ret = -ENOMEM;
goto err;
}
temp_int = append_internal(inter);
inter->filts_used = 1;
inter->dev = config->dev;
inter->fpga_rw = config->fpga_rw;
mutex_init(&inter->fpga_mutex);
inter->strt_wrk = 1;
ci_dbg_print("%s: Create New Internal Structure!\n", __func__);
}
ci_dbg_print("%s: setting hw pid filter = %p for ci = %d\n", __func__,
pid_filt, hw_filt_nr - 1);
inter->pid_filt[hw_filt_nr - 1] = pid_filt;
pid_filt->demux = config->demux;
pid_filt->internal = inter;
pid_filt->nr = hw_filt_nr - 1;
/* store old feed controls */
pid_filt->start_feed = config->demux->start_feed;
pid_filt->stop_feed = config->demux->stop_feed;
/* replace with new feed controls */
if (hw_filt_nr == 1) {
pid_filt->demux->start_feed = altera_ci_start_feed_1;
pid_filt->demux->stop_feed = altera_ci_stop_feed_1;
} else if (hw_filt_nr == 2) {
pid_filt->demux->start_feed = altera_ci_start_feed_2;
pid_filt->demux->stop_feed = altera_ci_stop_feed_2;
}
altera_toggle_fullts_streaming(pid_filt, 0, 1);
return 0;
err:
ci_dbg_print("%s: Can't init hardware filter: Error %d\n",
__func__, ret);
kfree(pid_filt);
return ret;
}
EXPORT_SYMBOL(altera_hw_filt_init);
int altera_ci_init(struct altera_ci_config *config, int ci_nr)
{
struct altera_ci_state *state;
struct fpga_inode *temp_int = find_inode(config->dev);
struct fpga_internal *inter = NULL;
int ret = 0;
u8 store = 0;
state = kzalloc(sizeof(struct altera_ci_state), GFP_KERNEL);
ci_dbg_print("%s\n", __func__);
if (!state) {
ret = -ENOMEM;
goto err;
}
if (temp_int != NULL) {
inter = temp_int->internal;
(inter->cis_used)++;
inter->fpga_rw = config->fpga_rw;
ci_dbg_print("%s: Find Internal Structure!\n", __func__);
} else {
inter = kzalloc(sizeof(struct fpga_internal), GFP_KERNEL);
if (!inter) {
ret = -ENOMEM;
goto err;
}
temp_int = append_internal(inter);
inter->cis_used = 1;
inter->dev = config->dev;
inter->fpga_rw = config->fpga_rw;
mutex_init(&inter->fpga_mutex);
inter->strt_wrk = 1;
ci_dbg_print("%s: Create New Internal Structure!\n", __func__);
}
ci_dbg_print("%s: setting state = %p for ci = %d\n", __func__,
state, ci_nr - 1);
state->internal = inter;
state->nr = ci_nr - 1;
state->ca.owner = THIS_MODULE;
state->ca.read_attribute_mem = altera_ci_read_attribute_mem;
state->ca.write_attribute_mem = altera_ci_write_attribute_mem;
state->ca.read_cam_control = altera_ci_read_cam_ctl;
state->ca.write_cam_control = altera_ci_write_cam_ctl;
state->ca.slot_reset = altera_ci_slot_reset;
state->ca.slot_shutdown = altera_ci_slot_shutdown;
state->ca.slot_ts_enable = altera_ci_slot_ts_ctl;
state->ca.poll_slot_status = altera_poll_ci_slot_status;
state->ca.data = state;
ret = dvb_ca_en50221_init(config->adapter,
&state->ca,
/* flags */ 0,
/* n_slots */ 1);
if (0 != ret)
goto err;
inter->state[ci_nr - 1] = state;
altera_hw_filt_init(config, ci_nr);
if (inter->strt_wrk) {
INIT_WORK(&inter->work, netup_read_ci_status);
inter->strt_wrk = 0;
}
ci_dbg_print("%s: CI initialized!\n", __func__);
mutex_lock(&inter->fpga_mutex);
/* Enable div */
netup_fpga_op_rw(inter, NETUP_CI_TSA_DIV, 0x0, 0);
netup_fpga_op_rw(inter, NETUP_CI_TSB_DIV, 0x0, 0);
/* enable TS out */
store = netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL2, 0, NETUP_CI_FLG_RD);
store |= (3 << 4);
netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL2, store, 0);
ret = netup_fpga_op_rw(inter, NETUP_CI_REVISION, 0, NETUP_CI_FLG_RD);
/* enable irq */
netup_fpga_op_rw(inter, NETUP_CI_INT_CTRL, 0x44, 0);
mutex_unlock(&inter->fpga_mutex);
ci_dbg_print("%s: NetUP CI Revision = 0x%x\n", __func__, ret);
schedule_work(&inter->work);
return 0;
err:
ci_dbg_print("%s: Cannot initialize CI: Error %d.\n", __func__, ret);
kfree(state);
return ret;
}
EXPORT_SYMBOL(altera_ci_init);
int altera_ci_tuner_reset(void *dev, int ci_nr)
{
struct fpga_inode *temp_int = find_inode(dev);
struct fpga_internal *inter = NULL;
u8 store;
ci_dbg_print("%s\n", __func__);
if (temp_int == NULL)
return -1;
if (temp_int->internal == NULL)
return -1;
inter = temp_int->internal;
mutex_lock(&inter->fpga_mutex);
store = netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL2, 0, NETUP_CI_FLG_RD);
store &= ~(4 << (2 - ci_nr));
netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL2, store, 0);
msleep(100);
store |= (4 << (2 - ci_nr));
netup_fpga_op_rw(inter, NETUP_CI_BUSCTRL2, store, 0);
mutex_unlock(&inter->fpga_mutex);
return 0;
}
EXPORT_SYMBOL(altera_ci_tuner_reset);

View file

@ -0,0 +1,97 @@
/*
* altera-ci.c
*
* CI driver in conjunction with NetUp Dual DVB-T/C RF CI card
*
* Copyright (C) 2010 NetUP Inc.
* Copyright (C) 2010 Igor M. Liplianin <liplianin@netup.ru>
*
* 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.
*/
#ifndef __ALTERA_CI_H
#define __ALTERA_CI_H
#include <linux/kconfig.h>
#define ALT_DATA 0x000000ff
#define ALT_TDI 0x00008000
#define ALT_TDO 0x00004000
#define ALT_TCK 0x00002000
#define ALT_RDY 0x00001000
#define ALT_RD 0x00000800
#define ALT_WR 0x00000400
#define ALT_AD_RG 0x00000200
#define ALT_CS 0x00000100
struct altera_ci_config {
void *dev;/* main dev, for example cx23885_dev */
void *adapter;/* for CI to connect to */
struct dvb_demux *demux;/* for hardware PID filter to connect to */
int (*fpga_rw) (void *dev, int ad_rg, int val, int rw);
};
#if IS_ENABLED(CONFIG_MEDIA_ALTERA_CI)
extern int altera_ci_init(struct altera_ci_config *config, int ci_nr);
extern void altera_ci_release(void *dev, int ci_nr);
extern int altera_ci_irq(void *dev);
extern int altera_ci_tuner_reset(void *dev, int ci_nr);
#else
static inline int altera_ci_init(struct altera_ci_config *config, int ci_nr)
{
printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__);
return 0;
}
static inline void altera_ci_release(void *dev, int ci_nr)
{
printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__);
}
static inline int altera_ci_irq(void *dev)
{
printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__);
return 0;
}
static inline int altera_ci_tuner_reset(void *dev, int ci_nr)
{
printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__);
return 0;
}
#endif
#if 0
static inline int altera_hw_filt_init(struct altera_ci_config *config,
int hw_filt_nr)
{
printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__);
return 0;
}
static inline void altera_hw_filt_release(void *dev, int filt_nr)
{
printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__);
}
static inline int altera_pid_feed_control(void *dev, int filt_nr,
struct dvb_demux_feed *dvbdmxfeed, int onoff)
{
printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__);
return 0;
}
#endif /* CONFIG_MEDIA_ALTERA_CI */
#endif /* __ALTERA_CI_H */

View file

@ -0,0 +1,546 @@
/*
* cimax2.c
*
* CIMax2(R) SP2 driver in conjunction with NetUp Dual DVB-S2 CI card
*
* Copyright (C) 2009 NetUP Inc.
* Copyright (C) 2009 Igor M. Liplianin <liplianin@netup.ru>
* Copyright (C) 2009 Abylay Ospan <aospan@netup.ru>
*
* 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.
*/
#include "cx23885.h"
#include "cimax2.h"
#include "dvb_ca_en50221.h"
/* Max transfer size done by I2C transfer functions */
#define MAX_XFER_SIZE 64
/**** Bit definitions for MC417_RWD and MC417_OEN registers ***
bits 31-16
+-----------+
| Reserved |
+-----------+
bit 15 bit 14 bit 13 bit 12 bit 11 bit 10 bit 9 bit 8
+-------+-------+-------+-------+-------+-------+-------+-------+
| WR# | RD# | | ACK# | ADHI | ADLO | CS1# | CS0# |
+-------+-------+-------+-------+-------+-------+-------+-------+
bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0
+-------+-------+-------+-------+-------+-------+-------+-------+
| DATA7| DATA6| DATA5| DATA4| DATA3| DATA2| DATA1| DATA0|
+-------+-------+-------+-------+-------+-------+-------+-------+
***/
/* MC417 */
#define NETUP_DATA 0x000000ff
#define NETUP_WR 0x00008000
#define NETUP_RD 0x00004000
#define NETUP_ACK 0x00001000
#define NETUP_ADHI 0x00000800
#define NETUP_ADLO 0x00000400
#define NETUP_CS1 0x00000200
#define NETUP_CS0 0x00000100
#define NETUP_EN_ALL 0x00001000
#define NETUP_CTRL_OFF (NETUP_CS1 | NETUP_CS0 | NETUP_WR | NETUP_RD)
#define NETUP_CI_CTL 0x04
#define NETUP_CI_RD 1
#define NETUP_IRQ_DETAM 0x1
#define NETUP_IRQ_IRQAM 0x4
static unsigned int ci_dbg;
module_param(ci_dbg, int, 0644);
MODULE_PARM_DESC(ci_dbg, "Enable CI debugging");
static unsigned int ci_irq_enable;
module_param(ci_irq_enable, int, 0644);
MODULE_PARM_DESC(ci_irq_enable, "Enable IRQ from CAM");
#define ci_dbg_print(args...) \
do { \
if (ci_dbg) \
printk(KERN_DEBUG args); \
} while (0)
#define ci_irq_flags() (ci_irq_enable ? NETUP_IRQ_IRQAM : 0)
/* stores all private variables for communication with CI */
struct netup_ci_state {
struct dvb_ca_en50221 ca;
struct mutex ca_mutex;
struct i2c_adapter *i2c_adap;
u8 ci_i2c_addr;
int status;
struct work_struct work;
void *priv;
u8 current_irq_mode;
int current_ci_flag;
unsigned long next_status_checked_time;
};
static int netup_read_i2c(struct i2c_adapter *i2c_adap, u8 addr, u8 reg,
u8 *buf, int len)
{
int ret;
struct i2c_msg msg[] = {
{
.addr = addr,
.flags = 0,
.buf = &reg,
.len = 1
}, {
.addr = addr,
.flags = I2C_M_RD,
.buf = buf,
.len = len
}
};
ret = i2c_transfer(i2c_adap, msg, 2);
if (ret != 2) {
ci_dbg_print("%s: i2c read error, Reg = 0x%02x, Status = %d\n",
__func__, reg, ret);
return -1;
}
ci_dbg_print("%s: i2c read Addr=0x%04x, Reg = 0x%02x, data = %02x\n",
__func__, addr, reg, buf[0]);
return 0;
}
static int netup_write_i2c(struct i2c_adapter *i2c_adap, u8 addr, u8 reg,
u8 *buf, int len)
{
int ret;
u8 buffer[MAX_XFER_SIZE];
struct i2c_msg msg = {
.addr = addr,
.flags = 0,
.buf = &buffer[0],
.len = len + 1
};
if (1 + len > sizeof(buffer)) {
printk(KERN_WARNING
"%s: i2c wr reg=%04x: len=%d is too big!\n",
KBUILD_MODNAME, reg, len);
return -EINVAL;
}
buffer[0] = reg;
memcpy(&buffer[1], buf, len);
ret = i2c_transfer(i2c_adap, &msg, 1);
if (ret != 1) {
ci_dbg_print("%s: i2c write error, Reg=[0x%02x], Status=%d\n",
__func__, reg, ret);
return -1;
}
return 0;
}
static int netup_ci_get_mem(struct cx23885_dev *dev)
{
int mem;
unsigned long timeout = jiffies + msecs_to_jiffies(1);
for (;;) {
mem = cx_read(MC417_RWD);
if ((mem & NETUP_ACK) == 0)
break;
if (time_after(jiffies, timeout))
break;
udelay(1);
}
cx_set(MC417_RWD, NETUP_CTRL_OFF);
return mem & 0xff;
}
static int netup_ci_op_cam(struct dvb_ca_en50221 *en50221, int slot,
u8 flag, u8 read, int addr, u8 data)
{
struct netup_ci_state *state = en50221->data;
struct cx23885_tsport *port = state->priv;
struct cx23885_dev *dev = port->dev;
u8 store;
int mem;
int ret;
if (0 != slot)
return -EINVAL;
if (state->current_ci_flag != flag) {
ret = netup_read_i2c(state->i2c_adap, state->ci_i2c_addr,
0, &store, 1);
if (ret != 0)
return ret;
store &= ~0x0c;
store |= flag;
ret = netup_write_i2c(state->i2c_adap, state->ci_i2c_addr,
0, &store, 1);
if (ret != 0)
return ret;
}
state->current_ci_flag = flag;
mutex_lock(&dev->gpio_lock);
/* write addr */
cx_write(MC417_OEN, NETUP_EN_ALL);
cx_write(MC417_RWD, NETUP_CTRL_OFF |
NETUP_ADLO | (0xff & addr));
cx_clear(MC417_RWD, NETUP_ADLO);
cx_write(MC417_RWD, NETUP_CTRL_OFF |
NETUP_ADHI | (0xff & (addr >> 8)));
cx_clear(MC417_RWD, NETUP_ADHI);
if (read) { /* data in */
cx_write(MC417_OEN, NETUP_EN_ALL | NETUP_DATA);
} else /* data out */
cx_write(MC417_RWD, NETUP_CTRL_OFF | data);
/* choose chip */
cx_clear(MC417_RWD,
(state->ci_i2c_addr == 0x40) ? NETUP_CS0 : NETUP_CS1);
/* read/write */
cx_clear(MC417_RWD, (read) ? NETUP_RD : NETUP_WR);
mem = netup_ci_get_mem(dev);
mutex_unlock(&dev->gpio_lock);
if (!read)
if (mem < 0)
return -EREMOTEIO;
ci_dbg_print("%s: %s: chipaddr=[0x%x] addr=[0x%02x], %s=%x\n", __func__,
(read) ? "read" : "write", state->ci_i2c_addr, addr,
(flag == NETUP_CI_CTL) ? "ctl" : "mem",
(read) ? mem : data);
if (read)
return mem;
return 0;
}
int netup_ci_read_attribute_mem(struct dvb_ca_en50221 *en50221,
int slot, int addr)
{
return netup_ci_op_cam(en50221, slot, 0, NETUP_CI_RD, addr, 0);
}
int netup_ci_write_attribute_mem(struct dvb_ca_en50221 *en50221,
int slot, int addr, u8 data)
{
return netup_ci_op_cam(en50221, slot, 0, 0, addr, data);
}
int netup_ci_read_cam_ctl(struct dvb_ca_en50221 *en50221, int slot,
u8 addr)
{
return netup_ci_op_cam(en50221, slot, NETUP_CI_CTL,
NETUP_CI_RD, addr, 0);
}
int netup_ci_write_cam_ctl(struct dvb_ca_en50221 *en50221, int slot,
u8 addr, u8 data)
{
return netup_ci_op_cam(en50221, slot, NETUP_CI_CTL, 0, addr, data);
}
int netup_ci_slot_reset(struct dvb_ca_en50221 *en50221, int slot)
{
struct netup_ci_state *state = en50221->data;
u8 buf = 0x80;
int ret;
if (0 != slot)
return -EINVAL;
udelay(500);
ret = netup_write_i2c(state->i2c_adap, state->ci_i2c_addr,
0, &buf, 1);
if (ret != 0)
return ret;
udelay(500);
buf = 0x00;
ret = netup_write_i2c(state->i2c_adap, state->ci_i2c_addr,
0, &buf, 1);
msleep(1000);
dvb_ca_en50221_camready_irq(&state->ca, 0);
return 0;
}
int netup_ci_slot_shutdown(struct dvb_ca_en50221 *en50221, int slot)
{
/* not implemented */
return 0;
}
static int netup_ci_set_irq(struct dvb_ca_en50221 *en50221, u8 irq_mode)
{
struct netup_ci_state *state = en50221->data;
int ret;
if (irq_mode == state->current_irq_mode)
return 0;
ci_dbg_print("%s: chipaddr=[0x%x] setting ci IRQ to [0x%x] \n",
__func__, state->ci_i2c_addr, irq_mode);
ret = netup_write_i2c(state->i2c_adap, state->ci_i2c_addr,
0x1b, &irq_mode, 1);
if (ret != 0)
return ret;
state->current_irq_mode = irq_mode;
return 0;
}
int netup_ci_slot_ts_ctl(struct dvb_ca_en50221 *en50221, int slot)
{
struct netup_ci_state *state = en50221->data;
u8 buf;
if (0 != slot)
return -EINVAL;
netup_read_i2c(state->i2c_adap, state->ci_i2c_addr,
0, &buf, 1);
buf |= 0x60;
return netup_write_i2c(state->i2c_adap, state->ci_i2c_addr,
0, &buf, 1);
}
/* work handler */
static void netup_read_ci_status(struct work_struct *work)
{
struct netup_ci_state *state =
container_of(work, struct netup_ci_state, work);
u8 buf[33];
int ret;
/* CAM module IRQ processing. fast operation */
dvb_ca_en50221_frda_irq(&state->ca, 0);
/* CAM module INSERT/REMOVE processing. slow operation because of i2c
* transfers */
if (time_after(jiffies, state->next_status_checked_time)
|| !state->status) {
ret = netup_read_i2c(state->i2c_adap, state->ci_i2c_addr,
0, &buf[0], 33);
state->next_status_checked_time = jiffies
+ msecs_to_jiffies(1000);
if (ret != 0)
return;
ci_dbg_print("%s: Slot Status Addr=[0x%04x], "
"Reg=[0x%02x], data=%02x, "
"TS config = %02x\n", __func__,
state->ci_i2c_addr, 0, buf[0],
buf[0]);
if (buf[0] & 1)
state->status = DVB_CA_EN50221_POLL_CAM_PRESENT |
DVB_CA_EN50221_POLL_CAM_READY;
else
state->status = 0;
}
}
/* CI irq handler */
int netup_ci_slot_status(struct cx23885_dev *dev, u32 pci_status)
{
struct cx23885_tsport *port = NULL;
struct netup_ci_state *state = NULL;
ci_dbg_print("%s:\n", __func__);
if (0 == (pci_status & (PCI_MSK_GPIO0 | PCI_MSK_GPIO1)))
return 0;
if (pci_status & PCI_MSK_GPIO0) {
port = &dev->ts1;
state = port->port_priv;
schedule_work(&state->work);
ci_dbg_print("%s: Wakeup CI0\n", __func__);
}
if (pci_status & PCI_MSK_GPIO1) {
port = &dev->ts2;
state = port->port_priv;
schedule_work(&state->work);
ci_dbg_print("%s: Wakeup CI1\n", __func__);
}
return 1;
}
int netup_poll_ci_slot_status(struct dvb_ca_en50221 *en50221,
int slot, int open)
{
struct netup_ci_state *state = en50221->data;
if (0 != slot)
return -EINVAL;
netup_ci_set_irq(en50221, open ? (NETUP_IRQ_DETAM | ci_irq_flags())
: NETUP_IRQ_DETAM);
return state->status;
}
int netup_ci_init(struct cx23885_tsport *port)
{
struct netup_ci_state *state;
u8 cimax_init[34] = {
0x00, /* module A control*/
0x00, /* auto select mask high A */
0x00, /* auto select mask low A */
0x00, /* auto select pattern high A */
0x00, /* auto select pattern low A */
0x44, /* memory access time A */
0x00, /* invert input A */
0x00, /* RFU */
0x00, /* RFU */
0x00, /* module B control*/
0x00, /* auto select mask high B */
0x00, /* auto select mask low B */
0x00, /* auto select pattern high B */
0x00, /* auto select pattern low B */
0x44, /* memory access time B */
0x00, /* invert input B */
0x00, /* RFU */
0x00, /* RFU */
0x00, /* auto select mask high Ext */
0x00, /* auto select mask low Ext */
0x00, /* auto select pattern high Ext */
0x00, /* auto select pattern low Ext */
0x00, /* RFU */
0x02, /* destination - module A */
0x01, /* power on (use it like store place) */
0x00, /* RFU */
0x00, /* int status read only */
ci_irq_flags() | NETUP_IRQ_DETAM, /* DETAM, IRQAM unmasked */
0x05, /* EXTINT=active-high, INT=push-pull */
0x00, /* USCG1 */
0x04, /* ack active low */
0x00, /* LOCK = 0 */
0x33, /* serial mode, rising in, rising out, MSB first*/
0x31, /* synchronization */
};
int ret;
ci_dbg_print("%s\n", __func__);
state = kzalloc(sizeof(struct netup_ci_state), GFP_KERNEL);
if (!state) {
ci_dbg_print("%s: Unable create CI structure!\n", __func__);
ret = -ENOMEM;
goto err;
}
port->port_priv = state;
switch (port->nr) {
case 1:
state->ci_i2c_addr = 0x40;
break;
case 2:
state->ci_i2c_addr = 0x41;
break;
}
state->i2c_adap = &port->dev->i2c_bus[0].i2c_adap;
state->ca.owner = THIS_MODULE;
state->ca.read_attribute_mem = netup_ci_read_attribute_mem;
state->ca.write_attribute_mem = netup_ci_write_attribute_mem;
state->ca.read_cam_control = netup_ci_read_cam_ctl;
state->ca.write_cam_control = netup_ci_write_cam_ctl;
state->ca.slot_reset = netup_ci_slot_reset;
state->ca.slot_shutdown = netup_ci_slot_shutdown;
state->ca.slot_ts_enable = netup_ci_slot_ts_ctl;
state->ca.poll_slot_status = netup_poll_ci_slot_status;
state->ca.data = state;
state->priv = port;
state->current_irq_mode = ci_irq_flags() | NETUP_IRQ_DETAM;
ret = netup_write_i2c(state->i2c_adap, state->ci_i2c_addr,
0, &cimax_init[0], 34);
/* lock registers */
ret |= netup_write_i2c(state->i2c_adap, state->ci_i2c_addr,
0x1f, &cimax_init[0x18], 1);
/* power on slots */
ret |= netup_write_i2c(state->i2c_adap, state->ci_i2c_addr,
0x18, &cimax_init[0x18], 1);
if (0 != ret)
goto err;
ret = dvb_ca_en50221_init(&port->frontends.adapter,
&state->ca,
/* flags */ 0,
/* n_slots */ 1);
if (0 != ret)
goto err;
INIT_WORK(&state->work, netup_read_ci_status);
schedule_work(&state->work);
ci_dbg_print("%s: CI initialized!\n", __func__);
return 0;
err:
ci_dbg_print("%s: Cannot initialize CI: Error %d.\n", __func__, ret);
kfree(state);
return ret;
}
void netup_ci_exit(struct cx23885_tsport *port)
{
struct netup_ci_state *state;
if (NULL == port)
return;
state = (struct netup_ci_state *)port->port_priv;
if (NULL == state)
return;
if (NULL == state->ca.data)
return;
dvb_ca_en50221_release(&state->ca);
kfree(state);
}

View file

@ -0,0 +1,43 @@
/*
* cimax2.h
*
* CIMax(R) SP2 driver in conjunction with NetUp Dual DVB-S2 CI card
*
* Copyright (C) 2009 NetUP Inc.
* Copyright (C) 2009 Igor M. Liplianin <liplianin@netup.ru>
* Copyright (C) 2009 Abylay Ospan <aospan@netup.ru>
*
* 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.
*/
#ifndef CIMAX2_H
#define CIMAX2_H
#include "dvb_ca_en50221.h"
extern int netup_ci_read_attribute_mem(struct dvb_ca_en50221 *en50221,
int slot, int addr);
extern int netup_ci_write_attribute_mem(struct dvb_ca_en50221 *en50221,
int slot, int addr, u8 data);
extern int netup_ci_read_cam_ctl(struct dvb_ca_en50221 *en50221,
int slot, u8 addr);
extern int netup_ci_write_cam_ctl(struct dvb_ca_en50221 *en50221,
int slot, u8 addr, u8 data);
extern int netup_ci_slot_reset(struct dvb_ca_en50221 *en50221, int slot);
extern int netup_ci_slot_shutdown(struct dvb_ca_en50221 *en50221, int slot);
extern int netup_ci_slot_ts_ctl(struct dvb_ca_en50221 *en50221, int slot);
extern int netup_ci_slot_status(struct cx23885_dev *dev, u32 pci_status);
extern int netup_poll_ci_slot_status(struct dvb_ca_en50221 *en50221,
int slot, int open);
extern int netup_ci_init(struct cx23885_tsport *port);
extern void netup_ci_exit(struct cx23885_tsport *port);
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,607 @@
/*
*
* Support for CX23885 analog audio capture
*
* (c) 2008 Mijhail Moreyra <mijhail.moreyra@gmail.com>
* Adapted from cx88-alsa.c
* (c) 2009 Steven Toth <stoth@kernellabs.com>
*
* 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.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/vmalloc.h>
#include <linux/dma-mapping.h>
#include <linux/pci.h>
#include <asm/delay.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/control.h>
#include <sound/initval.h>
#include <sound/tlv.h>
#include "cx23885.h"
#include "cx23885-reg.h"
#define AUDIO_SRAM_CHANNEL SRAM_CH07
#define dprintk(level, fmt, arg...) do { \
if (audio_debug + 1 > level) \
printk(KERN_INFO "%s: " fmt, chip->dev->name , ## arg); \
} while(0)
#define dprintk_core(level, fmt, arg...) if (audio_debug >= level) \
printk(KERN_DEBUG "%s: " fmt, chip->dev->name , ## arg)
/****************************************************************************
Module global static vars
****************************************************************************/
static unsigned int disable_analog_audio;
module_param(disable_analog_audio, int, 0644);
MODULE_PARM_DESC(disable_analog_audio, "disable analog audio ALSA driver");
static unsigned int audio_debug;
module_param(audio_debug, int, 0644);
MODULE_PARM_DESC(audio_debug, "enable debug messages [analog audio]");
/****************************************************************************
Board specific funtions
****************************************************************************/
/* Constants taken from cx88-reg.h */
#define AUD_INT_DN_RISCI1 (1 << 0)
#define AUD_INT_UP_RISCI1 (1 << 1)
#define AUD_INT_RDS_DN_RISCI1 (1 << 2)
#define AUD_INT_DN_RISCI2 (1 << 4) /* yes, 3 is skipped */
#define AUD_INT_UP_RISCI2 (1 << 5)
#define AUD_INT_RDS_DN_RISCI2 (1 << 6)
#define AUD_INT_DN_SYNC (1 << 12)
#define AUD_INT_UP_SYNC (1 << 13)
#define AUD_INT_RDS_DN_SYNC (1 << 14)
#define AUD_INT_OPC_ERR (1 << 16)
#define AUD_INT_BER_IRQ (1 << 20)
#define AUD_INT_MCHG_IRQ (1 << 21)
#define GP_COUNT_CONTROL_RESET 0x3
static int cx23885_alsa_dma_init(struct cx23885_audio_dev *chip, int nr_pages)
{
struct cx23885_audio_buffer *buf = chip->buf;
struct page *pg;
int i;
buf->vaddr = vmalloc_32(nr_pages << PAGE_SHIFT);
if (NULL == buf->vaddr) {
dprintk(1, "vmalloc_32(%d pages) failed\n", nr_pages);
return -ENOMEM;
}
dprintk(1, "vmalloc is at addr 0x%08lx, size=%d\n",
(unsigned long)buf->vaddr,
nr_pages << PAGE_SHIFT);
memset(buf->vaddr, 0, nr_pages << PAGE_SHIFT);
buf->nr_pages = nr_pages;
buf->sglist = vzalloc(buf->nr_pages * sizeof(*buf->sglist));
if (NULL == buf->sglist)
goto vzalloc_err;
sg_init_table(buf->sglist, buf->nr_pages);
for (i = 0; i < buf->nr_pages; i++) {
pg = vmalloc_to_page(buf->vaddr + i * PAGE_SIZE);
if (NULL == pg)
goto vmalloc_to_page_err;
sg_set_page(&buf->sglist[i], pg, PAGE_SIZE, 0);
}
return 0;
vmalloc_to_page_err:
vfree(buf->sglist);
buf->sglist = NULL;
vzalloc_err:
vfree(buf->vaddr);
buf->vaddr = NULL;
return -ENOMEM;
}
static int cx23885_alsa_dma_map(struct cx23885_audio_dev *dev)
{
struct cx23885_audio_buffer *buf = dev->buf;
buf->sglen = dma_map_sg(&dev->pci->dev, buf->sglist,
buf->nr_pages, PCI_DMA_FROMDEVICE);
if (0 == buf->sglen) {
pr_warn("%s: cx23885_alsa_map_sg failed\n", __func__);
return -ENOMEM;
}
return 0;
}
static int cx23885_alsa_dma_unmap(struct cx23885_audio_dev *dev)
{
struct cx23885_audio_buffer *buf = dev->buf;
if (!buf->sglen)
return 0;
dma_unmap_sg(&dev->pci->dev, buf->sglist, buf->sglen, PCI_DMA_FROMDEVICE);
buf->sglen = 0;
return 0;
}
static int cx23885_alsa_dma_free(struct cx23885_audio_buffer *buf)
{
vfree(buf->sglist);
buf->sglist = NULL;
vfree(buf->vaddr);
buf->vaddr = NULL;
return 0;
}
/*
* BOARD Specific: Sets audio DMA
*/
static int cx23885_start_audio_dma(struct cx23885_audio_dev *chip)
{
struct cx23885_audio_buffer *buf = chip->buf;
struct cx23885_dev *dev = chip->dev;
struct sram_channel *audio_ch =
&dev->sram_channels[AUDIO_SRAM_CHANNEL];
dprintk(1, "%s()\n", __func__);
/* Make sure RISC/FIFO are off before changing FIFO/RISC settings */
cx_clear(AUD_INT_DMA_CTL, 0x11);
/* setup fifo + format - out channel */
cx23885_sram_channel_setup(chip->dev, audio_ch, buf->bpl,
buf->risc.dma);
/* sets bpl size */
cx_write(AUD_INT_A_LNGTH, buf->bpl);
/* This is required to get good audio (1 seems to be ok) */
cx_write(AUD_INT_A_MODE, 1);
/* reset counter */
cx_write(AUD_INT_A_GPCNT_CTL, GP_COUNT_CONTROL_RESET);
atomic_set(&chip->count, 0);
dprintk(1, "Start audio DMA, %d B/line, %d lines/FIFO, %d periods, %d "
"byte buffer\n", buf->bpl, cx_read(audio_ch->cmds_start+12)>>1,
chip->num_periods, buf->bpl * chip->num_periods);
/* Enables corresponding bits at AUD_INT_STAT */
cx_write(AUDIO_INT_INT_MSK, AUD_INT_OPC_ERR | AUD_INT_DN_SYNC |
AUD_INT_DN_RISCI1);
/* Clean any pending interrupt bits already set */
cx_write(AUDIO_INT_INT_STAT, ~0);
/* enable audio irqs */
cx_set(PCI_INT_MSK, chip->dev->pci_irqmask | PCI_MSK_AUD_INT);
/* start dma */
cx_set(DEV_CNTRL2, (1<<5)); /* Enables Risc Processor */
cx_set(AUD_INT_DMA_CTL, 0x11); /* audio downstream FIFO and
RISC enable */
if (audio_debug)
cx23885_sram_channel_dump(chip->dev, audio_ch);
return 0;
}
/*
* BOARD Specific: Resets audio DMA
*/
static int cx23885_stop_audio_dma(struct cx23885_audio_dev *chip)
{
struct cx23885_dev *dev = chip->dev;
dprintk(1, "Stopping audio DMA\n");
/* stop dma */
cx_clear(AUD_INT_DMA_CTL, 0x11);
/* disable irqs */
cx_clear(PCI_INT_MSK, PCI_MSK_AUD_INT);
cx_clear(AUDIO_INT_INT_MSK, AUD_INT_OPC_ERR | AUD_INT_DN_SYNC |
AUD_INT_DN_RISCI1);
if (audio_debug)
cx23885_sram_channel_dump(chip->dev,
&dev->sram_channels[AUDIO_SRAM_CHANNEL]);
return 0;
}
/*
* BOARD Specific: Handles audio IRQ
*/
int cx23885_audio_irq(struct cx23885_dev *dev, u32 status, u32 mask)
{
struct cx23885_audio_dev *chip = dev->audio_dev;
if (0 == (status & mask))
return 0;
cx_write(AUDIO_INT_INT_STAT, status);
/* risc op code error */
if (status & AUD_INT_OPC_ERR) {
printk(KERN_WARNING "%s/1: Audio risc op code error\n",
dev->name);
cx_clear(AUD_INT_DMA_CTL, 0x11);
cx23885_sram_channel_dump(dev,
&dev->sram_channels[AUDIO_SRAM_CHANNEL]);
}
if (status & AUD_INT_DN_SYNC) {
dprintk(1, "Downstream sync error\n");
cx_write(AUD_INT_A_GPCNT_CTL, GP_COUNT_CONTROL_RESET);
return 1;
}
/* risc1 downstream */
if (status & AUD_INT_DN_RISCI1) {
atomic_set(&chip->count, cx_read(AUD_INT_A_GPCNT));
snd_pcm_period_elapsed(chip->substream);
}
/* FIXME: Any other status should deserve a special handling? */
return 1;
}
static int dsp_buffer_free(struct cx23885_audio_dev *chip)
{
struct cx23885_riscmem *risc;
BUG_ON(!chip->dma_size);
dprintk(2, "Freeing buffer\n");
cx23885_alsa_dma_unmap(chip);
cx23885_alsa_dma_free(chip->buf);
risc = &chip->buf->risc;
pci_free_consistent(chip->pci, risc->size, risc->cpu, risc->dma);
kfree(chip->buf);
chip->buf = NULL;
chip->dma_size = 0;
return 0;
}
/****************************************************************************
ALSA PCM Interface
****************************************************************************/
/*
* Digital hardware definition
*/
#define DEFAULT_FIFO_SIZE 4096
static struct snd_pcm_hardware snd_cx23885_digital_hw = {
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_48000,
.rate_min = 48000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
/* Analog audio output will be full of clicks and pops if there
are not exactly four lines in the SRAM FIFO buffer. */
.period_bytes_min = DEFAULT_FIFO_SIZE/4,
.period_bytes_max = DEFAULT_FIFO_SIZE/4,
.periods_min = 1,
.periods_max = 1024,
.buffer_bytes_max = (1024*1024),
};
/*
* audio pcm capture open callback
*/
static int snd_cx23885_pcm_open(struct snd_pcm_substream *substream)
{
struct cx23885_audio_dev *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
int err;
if (!chip) {
printk(KERN_ERR "BUG: cx23885 can't find device struct."
" Can't proceed with open\n");
return -ENODEV;
}
err = snd_pcm_hw_constraint_pow2(runtime, 0,
SNDRV_PCM_HW_PARAM_PERIODS);
if (err < 0)
goto _error;
chip->substream = substream;
runtime->hw = snd_cx23885_digital_hw;
if (chip->dev->sram_channels[AUDIO_SRAM_CHANNEL].fifo_size !=
DEFAULT_FIFO_SIZE) {
unsigned int bpl = chip->dev->
sram_channels[AUDIO_SRAM_CHANNEL].fifo_size / 4;
bpl &= ~7; /* must be multiple of 8 */
runtime->hw.period_bytes_min = bpl;
runtime->hw.period_bytes_max = bpl;
}
return 0;
_error:
dprintk(1, "Error opening PCM!\n");
return err;
}
/*
* audio close callback
*/
static int snd_cx23885_close(struct snd_pcm_substream *substream)
{
return 0;
}
/*
* hw_params callback
*/
static int snd_cx23885_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct cx23885_audio_dev *chip = snd_pcm_substream_chip(substream);
struct cx23885_audio_buffer *buf;
int ret;
if (substream->runtime->dma_area) {
dsp_buffer_free(chip);
substream->runtime->dma_area = NULL;
}
chip->period_size = params_period_bytes(hw_params);
chip->num_periods = params_periods(hw_params);
chip->dma_size = chip->period_size * params_periods(hw_params);
BUG_ON(!chip->dma_size);
BUG_ON(chip->num_periods & (chip->num_periods-1));
buf = kzalloc(sizeof(*buf), GFP_KERNEL);
if (NULL == buf)
return -ENOMEM;
buf->bpl = chip->period_size;
chip->buf = buf;
ret = cx23885_alsa_dma_init(chip,
(PAGE_ALIGN(chip->dma_size) >> PAGE_SHIFT));
if (ret < 0)
goto error;
ret = cx23885_alsa_dma_map(chip);
if (ret < 0)
goto error;
ret = cx23885_risc_databuffer(chip->pci, &buf->risc, buf->sglist,
chip->period_size, chip->num_periods, 1);
if (ret < 0)
goto error;
/* Loop back to start of program */
buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP|RISC_IRQ1|RISC_CNT_INC);
buf->risc.jmp[1] = cpu_to_le32(buf->risc.dma);
buf->risc.jmp[2] = cpu_to_le32(0); /* bits 63-32 */
substream->runtime->dma_area = chip->buf->vaddr;
substream->runtime->dma_bytes = chip->dma_size;
substream->runtime->dma_addr = 0;
return 0;
error:
kfree(buf);
chip->buf = NULL;
return ret;
}
/*
* hw free callback
*/
static int snd_cx23885_hw_free(struct snd_pcm_substream *substream)
{
struct cx23885_audio_dev *chip = snd_pcm_substream_chip(substream);
if (substream->runtime->dma_area) {
dsp_buffer_free(chip);
substream->runtime->dma_area = NULL;
}
return 0;
}
/*
* prepare callback
*/
static int snd_cx23885_prepare(struct snd_pcm_substream *substream)
{
return 0;
}
/*
* trigger callback
*/
static int snd_cx23885_card_trigger(struct snd_pcm_substream *substream,
int cmd)
{
struct cx23885_audio_dev *chip = snd_pcm_substream_chip(substream);
int err;
/* Local interrupts are already disabled by ALSA */
spin_lock(&chip->lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
err = cx23885_start_audio_dma(chip);
break;
case SNDRV_PCM_TRIGGER_STOP:
err = cx23885_stop_audio_dma(chip);
break;
default:
err = -EINVAL;
break;
}
spin_unlock(&chip->lock);
return err;
}
/*
* pointer callback
*/
static snd_pcm_uframes_t snd_cx23885_pointer(
struct snd_pcm_substream *substream)
{
struct cx23885_audio_dev *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
u16 count;
count = atomic_read(&chip->count);
return runtime->period_size * (count & (runtime->periods-1));
}
/*
* page callback (needed for mmap)
*/
static struct page *snd_cx23885_page(struct snd_pcm_substream *substream,
unsigned long offset)
{
void *pageptr = substream->runtime->dma_area + offset;
return vmalloc_to_page(pageptr);
}
/*
* operators
*/
static struct snd_pcm_ops snd_cx23885_pcm_ops = {
.open = snd_cx23885_pcm_open,
.close = snd_cx23885_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_cx23885_hw_params,
.hw_free = snd_cx23885_hw_free,
.prepare = snd_cx23885_prepare,
.trigger = snd_cx23885_card_trigger,
.pointer = snd_cx23885_pointer,
.page = snd_cx23885_page,
};
/*
* create a PCM device
*/
static int snd_cx23885_pcm(struct cx23885_audio_dev *chip, int device,
char *name)
{
int err;
struct snd_pcm *pcm;
err = snd_pcm_new(chip->card, name, device, 0, 1, &pcm);
if (err < 0)
return err;
pcm->private_data = chip;
strcpy(pcm->name, name);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_cx23885_pcm_ops);
return 0;
}
/****************************************************************************
Basic Flow for Sound Devices
****************************************************************************/
/*
* Alsa Constructor - Component probe
*/
struct cx23885_audio_dev *cx23885_audio_register(struct cx23885_dev *dev)
{
struct snd_card *card;
struct cx23885_audio_dev *chip;
int err;
if (disable_analog_audio)
return NULL;
if (dev->sram_channels[AUDIO_SRAM_CHANNEL].cmds_start == 0) {
printk(KERN_WARNING "%s(): Missing SRAM channel configuration "
"for analog TV Audio\n", __func__);
return NULL;
}
err = snd_card_new(&dev->pci->dev,
SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
THIS_MODULE, sizeof(struct cx23885_audio_dev), &card);
if (err < 0)
goto error;
chip = (struct cx23885_audio_dev *) card->private_data;
chip->dev = dev;
chip->pci = dev->pci;
chip->card = card;
spin_lock_init(&chip->lock);
err = snd_cx23885_pcm(chip, 0, "CX23885 Digital");
if (err < 0)
goto error;
strcpy(card->driver, "CX23885");
sprintf(card->shortname, "Conexant CX23885");
sprintf(card->longname, "%s at %s", card->shortname, dev->name);
err = snd_card_register(card);
if (err < 0)
goto error;
dprintk(0, "registered ALSA audio device\n");
return chip;
error:
snd_card_free(card);
printk(KERN_ERR "%s(): Failed to register analog "
"audio adapter\n", __func__);
return NULL;
}
/*
* ALSA destructor
*/
void cx23885_audio_unregister(struct cx23885_dev *dev)
{
struct cx23885_audio_dev *chip = dev->audio_dev;
snd_card_free(chip->card);
}

View file

@ -0,0 +1,44 @@
/*
* Driver for the Conexant CX23885/7/8 PCIe bridge
*
* AV device support routines - non-input, non-vl42_subdev routines
*
* Copyright (C) 2010 Andy Walls <awalls@md.metrocast.net>
*
* 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.
*/
#include "cx23885.h"
#include "cx23885-av.h"
#include "cx23885-video.h"
void cx23885_av_work_handler(struct work_struct *work)
{
struct cx23885_dev *dev =
container_of(work, struct cx23885_dev, cx25840_work);
bool handled;
v4l2_subdev_call(dev->sd_cx25840, core, interrupt_service_routine,
PCI_MSK_AV_CORE, &handled);
/* Getting here with the interrupt not handled
then probbaly flatiron does have pending interrupts.
*/
if (!handled) {
/* clear left and right adc channel interrupt request flag */
cx23885_flatiron_write(dev, 0x1f,
cx23885_flatiron_read(dev, 0x1f) | 0x80);
cx23885_flatiron_write(dev, 0x23,
cx23885_flatiron_read(dev, 0x23) | 0x80);
}
cx23885_irq_enable(dev, PCI_MSK_AV_CORE);
}

View file

@ -0,0 +1,22 @@
/*
* Driver for the Conexant CX23885/7/8 PCIe bridge
*
* AV device support routines - non-input, non-vl42_subdev routines
*
* Copyright (C) 2010 Andy Walls <awalls@md.metrocast.net>
*
* 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.
*/
#ifndef _CX23885_AV_H_
#define _CX23885_AV_H_
void cx23885_av_work_handler(struct work_struct *work);
#endif

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,174 @@
/*
* Driver for Silicon Labs C8051F300 microcontroller.
*
* It is used for LNB power control in TeVii S470,
* TBS 6920 PCIe DVB-S2 cards.
*
* Microcontroller connected to cx23885 GPIO pins:
* GPIO0 - data - P0.3 F300
* GPIO1 - reset - P0.2 F300
* GPIO2 - clk - P0.1 F300
* GPIO3 - busy - P0.0 F300
*
* Copyright (C) 2009 Igor M. Liplianin <liplianin@me.by>
*
* 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.
*/
#include "cx23885.h"
#include "cx23885-f300.h"
#define F300_DATA GPIO_0
#define F300_RESET GPIO_1
#define F300_CLK GPIO_2
#define F300_BUSY GPIO_3
static void f300_set_line(struct cx23885_dev *dev, u32 line, u8 lvl)
{
cx23885_gpio_enable(dev, line, 1);
if (lvl == 1)
cx23885_gpio_set(dev, line);
else
cx23885_gpio_clear(dev, line);
}
static u8 f300_get_line(struct cx23885_dev *dev, u32 line)
{
cx23885_gpio_enable(dev, line, 0);
return cx23885_gpio_get(dev, line);
}
static void f300_send_byte(struct cx23885_dev *dev, u8 dta)
{
u8 i;
for (i = 0; i < 8; i++) {
f300_set_line(dev, F300_CLK, 0);
udelay(30);
f300_set_line(dev, F300_DATA, (dta & 0x80) >> 7);/* msb first */
udelay(30);
dta <<= 1;
f300_set_line(dev, F300_CLK, 1);
udelay(30);
}
}
static u8 f300_get_byte(struct cx23885_dev *dev)
{
u8 i, dta = 0;
for (i = 0; i < 8; i++) {
f300_set_line(dev, F300_CLK, 0);
udelay(30);
dta <<= 1;
f300_set_line(dev, F300_CLK, 1);
udelay(30);
dta |= f300_get_line(dev, F300_DATA);/* msb first */
}
return dta;
}
static u8 f300_xfer(struct dvb_frontend *fe, u8 *buf)
{
struct cx23885_tsport *port = fe->dvb->priv;
struct cx23885_dev *dev = port->dev;
u8 i, temp, ret = 0;
temp = buf[0];
for (i = 0; i < buf[0]; i++)
temp += buf[i + 1];
temp = (~temp + 1);/* get check sum */
buf[1 + buf[0]] = temp;
f300_set_line(dev, F300_RESET, 1);
f300_set_line(dev, F300_CLK, 1);
udelay(30);
f300_set_line(dev, F300_DATA, 1);
msleep(1);
/* question: */
f300_set_line(dev, F300_RESET, 0);/* begin to send data */
msleep(1);
f300_send_byte(dev, 0xe0);/* the slave address is 0xe0, write */
msleep(1);
temp = buf[0];
temp += 2;
for (i = 0; i < temp; i++)
f300_send_byte(dev, buf[i]);
f300_set_line(dev, F300_RESET, 1);/* sent data over */
f300_set_line(dev, F300_DATA, 1);
/* answer: */
temp = 0;
for (i = 0; ((i < 8) & (temp == 0)); i++) {
msleep(1);
if (f300_get_line(dev, F300_BUSY) == 0)
temp = 1;
}
if (i > 7) {
printk(KERN_ERR "%s: timeout, the slave no response\n",
__func__);
ret = 1; /* timeout, the slave no response */
} else { /* the slave not busy, prepare for getting data */
f300_set_line(dev, F300_RESET, 0);/*ready...*/
msleep(1);
f300_send_byte(dev, 0xe1);/* 0xe1 is Read */
msleep(1);
temp = f300_get_byte(dev);/*get the data length */
if (temp > 14)
temp = 14;
for (i = 0; i < (temp + 1); i++)
f300_get_byte(dev);/* get data to empty buffer */
f300_set_line(dev, F300_RESET, 1);/* received data over */
f300_set_line(dev, F300_DATA, 1);
}
return ret;
}
int f300_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage)
{
u8 buf[16];
buf[0] = 0x05;
buf[1] = 0x38;/* write port */
buf[2] = 0x01;/* A port, lnb power */
switch (voltage) {
case SEC_VOLTAGE_13:
buf[3] = 0x01;/* power on */
buf[4] = 0x02;/* B port, H/V */
buf[5] = 0x00;/*13V v*/
break;
case SEC_VOLTAGE_18:
buf[3] = 0x01;
buf[4] = 0x02;
buf[5] = 0x01;/* 18V h*/
break;
case SEC_VOLTAGE_OFF:
buf[3] = 0x00;/* power off */
buf[4] = 0x00;
buf[5] = 0x00;
break;
}
return f300_xfer(fe, buf);
}

View file

@ -0,0 +1,2 @@
extern int f300_set_voltage(struct dvb_frontend *fe,
fe_sec_voltage_t voltage);

View file

@ -0,0 +1,384 @@
/*
* Driver for the Conexant CX23885 PCIe bridge
*
* Copyright (c) 2006 Steven Toth <stoth@linuxtv.org>
*
* 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.
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/io.h>
#include "cx23885.h"
#include <media/v4l2-common.h>
static unsigned int i2c_debug;
module_param(i2c_debug, int, 0644);
MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]");
static unsigned int i2c_scan;
module_param(i2c_scan, int, 0444);
MODULE_PARM_DESC(i2c_scan, "scan i2c bus at insmod time");
#define dprintk(level, fmt, arg...)\
do { if (i2c_debug >= level)\
printk(KERN_DEBUG "%s/0: " fmt, dev->name, ## arg);\
} while (0)
#define I2C_WAIT_DELAY 32
#define I2C_WAIT_RETRY 64
#define I2C_EXTEND (1 << 3)
#define I2C_NOSTOP (1 << 4)
static inline int i2c_slave_did_ack(struct i2c_adapter *i2c_adap)
{
struct cx23885_i2c *bus = i2c_adap->algo_data;
struct cx23885_dev *dev = bus->dev;
return cx_read(bus->reg_stat) & 0x01;
}
static inline int i2c_is_busy(struct i2c_adapter *i2c_adap)
{
struct cx23885_i2c *bus = i2c_adap->algo_data;
struct cx23885_dev *dev = bus->dev;
return cx_read(bus->reg_stat) & 0x02 ? 1 : 0;
}
static int i2c_wait_done(struct i2c_adapter *i2c_adap)
{
int count;
for (count = 0; count < I2C_WAIT_RETRY; count++) {
if (!i2c_is_busy(i2c_adap))
break;
udelay(I2C_WAIT_DELAY);
}
if (I2C_WAIT_RETRY == count)
return 0;
return 1;
}
static int i2c_sendbytes(struct i2c_adapter *i2c_adap,
const struct i2c_msg *msg, int joined_rlen)
{
struct cx23885_i2c *bus = i2c_adap->algo_data;
struct cx23885_dev *dev = bus->dev;
u32 wdata, addr, ctrl;
int retval, cnt;
if (joined_rlen)
dprintk(1, "%s(msg->wlen=%d, nextmsg->rlen=%d)\n", __func__,
msg->len, joined_rlen);
else
dprintk(1, "%s(msg->len=%d)\n", __func__, msg->len);
/* Deal with i2c probe functions with zero payload */
if (msg->len == 0) {
cx_write(bus->reg_addr, msg->addr << 25);
cx_write(bus->reg_ctrl, bus->i2c_period | (1 << 2));
if (!i2c_wait_done(i2c_adap))
return -EIO;
if (!i2c_slave_did_ack(i2c_adap))
return -ENXIO;
dprintk(1, "%s() returns 0\n", __func__);
return 0;
}
/* dev, reg + first byte */
addr = (msg->addr << 25) | msg->buf[0];
wdata = msg->buf[0];
ctrl = bus->i2c_period | (1 << 12) | (1 << 2);
if (msg->len > 1)
ctrl |= I2C_NOSTOP | I2C_EXTEND;
else if (joined_rlen)
ctrl |= I2C_NOSTOP;
cx_write(bus->reg_addr, addr);
cx_write(bus->reg_wdata, wdata);
cx_write(bus->reg_ctrl, ctrl);
if (!i2c_wait_done(i2c_adap))
goto eio;
if (i2c_debug) {
printk(" <W %02x %02x", msg->addr << 1, msg->buf[0]);
if (!(ctrl & I2C_NOSTOP))
printk(" >\n");
}
for (cnt = 1; cnt < msg->len; cnt++) {
/* following bytes */
wdata = msg->buf[cnt];
ctrl = bus->i2c_period | (1 << 12) | (1 << 2);
if (cnt < msg->len - 1)
ctrl |= I2C_NOSTOP | I2C_EXTEND;
else if (joined_rlen)
ctrl |= I2C_NOSTOP;
cx_write(bus->reg_addr, addr);
cx_write(bus->reg_wdata, wdata);
cx_write(bus->reg_ctrl, ctrl);
if (!i2c_wait_done(i2c_adap))
goto eio;
if (i2c_debug) {
dprintk(1, " %02x", msg->buf[cnt]);
if (!(ctrl & I2C_NOSTOP))
dprintk(1, " >\n");
}
}
return msg->len;
eio:
retval = -EIO;
if (i2c_debug)
printk(KERN_ERR " ERR: %d\n", retval);
return retval;
}
static int i2c_readbytes(struct i2c_adapter *i2c_adap,
const struct i2c_msg *msg, int joined)
{
struct cx23885_i2c *bus = i2c_adap->algo_data;
struct cx23885_dev *dev = bus->dev;
u32 ctrl, cnt;
int retval;
if (i2c_debug && !joined)
dprintk(1, "%s(msg->len=%d)\n", __func__, msg->len);
/* Deal with i2c probe functions with zero payload */
if (msg->len == 0) {
cx_write(bus->reg_addr, msg->addr << 25);
cx_write(bus->reg_ctrl, bus->i2c_period | (1 << 2) | 1);
if (!i2c_wait_done(i2c_adap))
return -EIO;
if (!i2c_slave_did_ack(i2c_adap))
return -ENXIO;
dprintk(1, "%s() returns 0\n", __func__);
return 0;
}
if (i2c_debug) {
if (joined)
dprintk(1, " R");
else
dprintk(1, " <R %02x", (msg->addr << 1) + 1);
}
for (cnt = 0; cnt < msg->len; cnt++) {
ctrl = bus->i2c_period | (1 << 12) | (1 << 2) | 1;
if (cnt < msg->len - 1)
ctrl |= I2C_NOSTOP | I2C_EXTEND;
cx_write(bus->reg_addr, msg->addr << 25);
cx_write(bus->reg_ctrl, ctrl);
if (!i2c_wait_done(i2c_adap))
goto eio;
msg->buf[cnt] = cx_read(bus->reg_rdata) & 0xff;
if (i2c_debug) {
dprintk(1, " %02x", msg->buf[cnt]);
if (!(ctrl & I2C_NOSTOP))
dprintk(1, " >\n");
}
}
return msg->len;
eio:
retval = -EIO;
if (i2c_debug)
printk(KERN_ERR " ERR: %d\n", retval);
return retval;
}
static int i2c_xfer(struct i2c_adapter *i2c_adap,
struct i2c_msg *msgs, int num)
{
struct cx23885_i2c *bus = i2c_adap->algo_data;
struct cx23885_dev *dev = bus->dev;
int i, retval = 0;
dprintk(1, "%s(num = %d)\n", __func__, num);
for (i = 0 ; i < num; i++) {
dprintk(1, "%s(num = %d) addr = 0x%02x len = 0x%x\n",
__func__, num, msgs[i].addr, msgs[i].len);
if (msgs[i].flags & I2C_M_RD) {
/* read */
retval = i2c_readbytes(i2c_adap, &msgs[i], 0);
} else if (i + 1 < num && (msgs[i + 1].flags & I2C_M_RD) &&
msgs[i].addr == msgs[i + 1].addr) {
/* write then read from same address */
retval = i2c_sendbytes(i2c_adap, &msgs[i],
msgs[i + 1].len);
if (retval < 0)
goto err;
i++;
retval = i2c_readbytes(i2c_adap, &msgs[i], 1);
} else {
/* write */
retval = i2c_sendbytes(i2c_adap, &msgs[i], 0);
}
if (retval < 0)
goto err;
}
return num;
err:
return retval;
}
static u32 cx23885_functionality(struct i2c_adapter *adap)
{
return I2C_FUNC_SMBUS_EMUL | I2C_FUNC_I2C;
}
static struct i2c_algorithm cx23885_i2c_algo_template = {
.master_xfer = i2c_xfer,
.functionality = cx23885_functionality,
};
/* ----------------------------------------------------------------------- */
static struct i2c_adapter cx23885_i2c_adap_template = {
.name = "cx23885",
.owner = THIS_MODULE,
.algo = &cx23885_i2c_algo_template,
};
static struct i2c_client cx23885_i2c_client_template = {
.name = "cx23885 internal",
};
static char *i2c_devs[128] = {
[0x10 >> 1] = "tda10048",
[0x12 >> 1] = "dib7000pc",
[0x1c >> 1] = "lgdt3303",
[0x86 >> 1] = "tda9887",
[0x32 >> 1] = "cx24227",
[0x88 >> 1] = "cx25837",
[0x84 >> 1] = "tda8295",
[0x98 >> 1] = "flatiron",
[0xa0 >> 1] = "eeprom",
[0xc0 >> 1] = "tuner/mt2131/tda8275",
[0xc2 >> 1] = "tuner/mt2131/tda8275/xc5000/xc3028",
[0xc8 >> 1] = "tuner/xc3028L",
};
static void do_i2c_scan(char *name, struct i2c_client *c)
{
unsigned char buf;
int i, rc;
for (i = 0; i < 128; i++) {
c->addr = i;
rc = i2c_master_recv(c, &buf, 0);
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] : "???");
}
}
/* init + register i2c adapter */
int cx23885_i2c_register(struct cx23885_i2c *bus)
{
struct cx23885_dev *dev = bus->dev;
dprintk(1, "%s(bus = %d)\n", __func__, bus->nr);
bus->i2c_adap = cx23885_i2c_adap_template;
bus->i2c_client = cx23885_i2c_client_template;
bus->i2c_adap.dev.parent = &dev->pci->dev;
strlcpy(bus->i2c_adap.name, bus->dev->name,
sizeof(bus->i2c_adap.name));
bus->i2c_adap.algo_data = bus;
i2c_set_adapdata(&bus->i2c_adap, &dev->v4l2_dev);
i2c_add_adapter(&bus->i2c_adap);
bus->i2c_client.adapter = &bus->i2c_adap;
if (0 == bus->i2c_rc) {
dprintk(1, "%s: i2c bus %d registered\n", dev->name, bus->nr);
if (i2c_scan) {
printk(KERN_INFO "%s: scan bus %d:\n",
dev->name, bus->nr);
do_i2c_scan(dev->name, &bus->i2c_client);
}
} else
printk(KERN_WARNING "%s: i2c bus %d register FAILED\n",
dev->name, bus->nr);
/* Instantiate the IR receiver device, if present */
if (0 == bus->i2c_rc) {
struct i2c_board_info info;
const unsigned short addr_list[] = {
0x6b, I2C_CLIENT_END
};
memset(&info, 0, sizeof(struct i2c_board_info));
strlcpy(info.type, "ir_video", I2C_NAME_SIZE);
/* Use quick read command for probe, some IR chips don't
* support writes */
i2c_new_probed_device(&bus->i2c_adap, &info, addr_list,
i2c_probe_func_quick_read);
}
return bus->i2c_rc;
}
int cx23885_i2c_unregister(struct cx23885_i2c *bus)
{
i2c_del_adapter(&bus->i2c_adap);
return 0;
}
void cx23885_av_clk(struct cx23885_dev *dev, int enable)
{
/* write 0 to bus 2 addr 0x144 via i2x_xfer() */
char buffer[3];
struct i2c_msg msg;
dprintk(1, "%s(enabled = %d)\n", __func__, enable);
/* Register 0x144 */
buffer[0] = 0x01;
buffer[1] = 0x44;
if (enable == 1)
buffer[2] = 0x05;
else
buffer[2] = 0x00;
msg.addr = 0x44;
msg.flags = I2C_M_TEN;
msg.len = 3;
msg.buf = buffer;
i2c_xfer(&dev->i2c_bus[2].i2c_adap, &msg, 1);
}

View file

@ -0,0 +1,382 @@
/*
* Driver for the Conexant CX23885/7/8 PCIe bridge
*
* Infrared remote control input device
*
* Most of this file is
*
* Copyright (C) 2009 Andy Walls <awalls@md.metrocast.net>
*
* However, the cx23885_input_{init,fini} functions contained herein are
* derived from Linux kernel files linux/media/video/.../...-input.c marked as:
*
* Copyright (C) 2008 <srinivasa.deevi at conexant dot com>
* Copyright (C) 2005 Ludovico Cavedon <cavedon@sssup.it>
* Markus Rechberger <mrechberger@gmail.com>
* Mauro Carvalho Chehab <mchehab@infradead.org>
* Sascha Sommer <saschasommer@freenet.de>
* Copyright (C) 2004, 2005 Chris Pascoe
* Copyright (C) 2003, 2004 Gerd Knorr
* Copyright (C) 2003 Pavel Machek
*
* 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.
*/
#include <linux/slab.h>
#include <media/rc-core.h>
#include <media/v4l2-subdev.h>
#include "cx23885.h"
#include "cx23885-input.h"
#define MODULE_NAME "cx23885"
static void cx23885_input_process_measurements(struct cx23885_dev *dev,
bool overrun)
{
struct cx23885_kernel_ir *kernel_ir = dev->kernel_ir;
ssize_t num;
int count, i;
bool handle = false;
struct ir_raw_event ir_core_event[64];
do {
num = 0;
v4l2_subdev_call(dev->sd_ir, ir, rx_read, (u8 *) ir_core_event,
sizeof(ir_core_event), &num);
count = num / sizeof(struct ir_raw_event);
for (i = 0; i < count; i++) {
ir_raw_event_store(kernel_ir->rc,
&ir_core_event[i]);
handle = true;
}
} while (num != 0);
if (overrun)
ir_raw_event_reset(kernel_ir->rc);
else if (handle)
ir_raw_event_handle(kernel_ir->rc);
}
void cx23885_input_rx_work_handler(struct cx23885_dev *dev, u32 events)
{
struct v4l2_subdev_ir_parameters params;
int overrun, data_available;
if (dev->sd_ir == NULL || events == 0)
return;
switch (dev->board) {
case CX23885_BOARD_HAUPPAUGE_HVR1270:
case CX23885_BOARD_HAUPPAUGE_HVR1850:
case CX23885_BOARD_HAUPPAUGE_HVR1290:
case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL:
case CX23885_BOARD_TEVII_S470:
case CX23885_BOARD_HAUPPAUGE_HVR1250:
case CX23885_BOARD_MYGICA_X8507:
case CX23885_BOARD_TBS_6980:
case CX23885_BOARD_TBS_6981:
/*
* The only boards we handle right now. However other boards
* using the CX2388x integrated IR controller should be similar
*/
break;
default:
return;
}
overrun = events & (V4L2_SUBDEV_IR_RX_SW_FIFO_OVERRUN |
V4L2_SUBDEV_IR_RX_HW_FIFO_OVERRUN);
data_available = events & (V4L2_SUBDEV_IR_RX_END_OF_RX_DETECTED |
V4L2_SUBDEV_IR_RX_FIFO_SERVICE_REQ);
if (overrun) {
/* If there was a FIFO overrun, stop the device */
v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, &params);
params.enable = false;
/* Mitigate race with cx23885_input_ir_stop() */
params.shutdown = atomic_read(&dev->ir_input_stopping);
v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, &params);
}
if (data_available)
cx23885_input_process_measurements(dev, overrun);
if (overrun) {
/* If there was a FIFO overrun, clear & restart the device */
params.enable = true;
/* Mitigate race with cx23885_input_ir_stop() */
params.shutdown = atomic_read(&dev->ir_input_stopping);
v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, &params);
}
}
static int cx23885_input_ir_start(struct cx23885_dev *dev)
{
struct v4l2_subdev_ir_parameters params;
if (dev->sd_ir == NULL)
return -ENODEV;
atomic_set(&dev->ir_input_stopping, 0);
v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, &params);
switch (dev->board) {
case CX23885_BOARD_HAUPPAUGE_HVR1270:
case CX23885_BOARD_HAUPPAUGE_HVR1850:
case CX23885_BOARD_HAUPPAUGE_HVR1290:
case CX23885_BOARD_HAUPPAUGE_HVR1250:
case CX23885_BOARD_MYGICA_X8507:
/*
* The IR controller on this board only returns pulse widths.
* Any other mode setting will fail to set up the device.
*/
params.mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH;
params.enable = true;
params.interrupt_enable = true;
params.shutdown = false;
/* Setup for baseband compatible with both RC-5 and RC-6A */
params.modulation = false;
/* RC-5: 2,222,222 ns = 1/36 kHz * 32 cycles * 2 marks * 1.25*/
/* RC-6A: 3,333,333 ns = 1/36 kHz * 16 cycles * 6 marks * 1.25*/
params.max_pulse_width = 3333333; /* ns */
/* RC-5: 666,667 ns = 1/36 kHz * 32 cycles * 1 mark * 0.75 */
/* RC-6A: 333,333 ns = 1/36 kHz * 16 cycles * 1 mark * 0.75 */
params.noise_filter_min_width = 333333; /* ns */
/*
* This board has inverted receive sense:
* mark is received as low logic level;
* falling edges are detected as rising edges; etc.
*/
params.invert_level = true;
break;
case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL:
case CX23885_BOARD_TEVII_S470:
case CX23885_BOARD_TBS_6980:
case CX23885_BOARD_TBS_6981:
/*
* The IR controller on this board only returns pulse widths.
* Any other mode setting will fail to set up the device.
*/
params.mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH;
params.enable = true;
params.interrupt_enable = true;
params.shutdown = false;
/* Setup for a standard NEC protocol */
params.carrier_freq = 37917; /* Hz, 455 kHz/12 for NEC */
params.carrier_range_lower = 33000; /* Hz */
params.carrier_range_upper = 43000; /* Hz */
params.duty_cycle = 33; /* percent, 33 percent for NEC */
/*
* NEC max pulse width: (64/3)/(455 kHz/12) * 16 nec_units
* (64/3)/(455 kHz/12) * 16 nec_units * 1.375 = 12378022 ns
*/
params.max_pulse_width = 12378022; /* ns */
/*
* NEC noise filter min width: (64/3)/(455 kHz/12) * 1 nec_unit
* (64/3)/(455 kHz/12) * 1 nec_units * 0.625 = 351648 ns
*/
params.noise_filter_min_width = 351648; /* ns */
params.modulation = false;
params.invert_level = true;
break;
}
v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, &params);
return 0;
}
static int cx23885_input_ir_open(struct rc_dev *rc)
{
struct cx23885_kernel_ir *kernel_ir = rc->priv;
if (kernel_ir->cx == NULL)
return -ENODEV;
return cx23885_input_ir_start(kernel_ir->cx);
}
static void cx23885_input_ir_stop(struct cx23885_dev *dev)
{
struct v4l2_subdev_ir_parameters params;
if (dev->sd_ir == NULL)
return;
/*
* Stop the sd_ir subdevice from generating notifications and
* scheduling work.
* It is shutdown this way in order to mitigate a race with
* cx23885_input_rx_work_handler() in the overrun case, which could
* re-enable the subdevice.
*/
atomic_set(&dev->ir_input_stopping, 1);
v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, &params);
while (params.shutdown == false) {
params.enable = false;
params.interrupt_enable = false;
params.shutdown = true;
v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, &params);
v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, &params);
}
flush_work(&dev->cx25840_work);
flush_work(&dev->ir_rx_work);
flush_work(&dev->ir_tx_work);
}
static void cx23885_input_ir_close(struct rc_dev *rc)
{
struct cx23885_kernel_ir *kernel_ir = rc->priv;
if (kernel_ir->cx != NULL)
cx23885_input_ir_stop(kernel_ir->cx);
}
int cx23885_input_init(struct cx23885_dev *dev)
{
struct cx23885_kernel_ir *kernel_ir;
struct rc_dev *rc;
char *rc_map;
enum rc_driver_type driver_type;
unsigned long allowed_protos;
int ret;
/*
* If the IR device (hardware registers, chip, GPIO lines, etc.) isn't
* encapsulated in a v4l2_subdev, then I'm not going to deal with it.
*/
if (dev->sd_ir == NULL)
return -ENODEV;
switch (dev->board) {
case CX23885_BOARD_HAUPPAUGE_HVR1270:
case CX23885_BOARD_HAUPPAUGE_HVR1850:
case CX23885_BOARD_HAUPPAUGE_HVR1290:
case CX23885_BOARD_HAUPPAUGE_HVR1250:
/* Integrated CX2388[58] IR controller */
driver_type = RC_DRIVER_IR_RAW;
allowed_protos = RC_BIT_ALL;
/* The grey Hauppauge RC-5 remote */
rc_map = RC_MAP_HAUPPAUGE;
break;
case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL:
/* Integrated CX23885 IR controller */
driver_type = RC_DRIVER_IR_RAW;
allowed_protos = RC_BIT_NEC;
/* The grey Terratec remote with orange buttons */
rc_map = RC_MAP_NEC_TERRATEC_CINERGY_XS;
break;
case CX23885_BOARD_TEVII_S470:
/* Integrated CX23885 IR controller */
driver_type = RC_DRIVER_IR_RAW;
allowed_protos = RC_BIT_ALL;
/* A guess at the remote */
rc_map = RC_MAP_TEVII_NEC;
break;
case CX23885_BOARD_MYGICA_X8507:
/* Integrated CX23885 IR controller */
driver_type = RC_DRIVER_IR_RAW;
allowed_protos = RC_BIT_ALL;
/* A guess at the remote */
rc_map = RC_MAP_TOTAL_MEDIA_IN_HAND_02;
break;
case CX23885_BOARD_TBS_6980:
case CX23885_BOARD_TBS_6981:
/* Integrated CX23885 IR controller */
driver_type = RC_DRIVER_IR_RAW;
allowed_protos = RC_BIT_ALL;
/* A guess at the remote */
rc_map = RC_MAP_TBS_NEC;
break;
default:
return -ENODEV;
}
/* cx23885 board instance kernel IR state */
kernel_ir = kzalloc(sizeof(struct cx23885_kernel_ir), GFP_KERNEL);
if (kernel_ir == NULL)
return -ENOMEM;
kernel_ir->cx = dev;
kernel_ir->name = kasprintf(GFP_KERNEL, "cx23885 IR (%s)",
cx23885_boards[dev->board].name);
kernel_ir->phys = kasprintf(GFP_KERNEL, "pci-%s/ir0",
pci_name(dev->pci));
/* input device */
rc = rc_allocate_device();
if (!rc) {
ret = -ENOMEM;
goto err_out_free;
}
kernel_ir->rc = rc;
rc->input_name = kernel_ir->name;
rc->input_phys = kernel_ir->phys;
rc->input_id.bustype = BUS_PCI;
rc->input_id.version = 1;
if (dev->pci->subsystem_vendor) {
rc->input_id.vendor = dev->pci->subsystem_vendor;
rc->input_id.product = dev->pci->subsystem_device;
} else {
rc->input_id.vendor = dev->pci->vendor;
rc->input_id.product = dev->pci->device;
}
rc->dev.parent = &dev->pci->dev;
rc->driver_type = driver_type;
rc->allowed_protocols = allowed_protos;
rc->priv = kernel_ir;
rc->open = cx23885_input_ir_open;
rc->close = cx23885_input_ir_close;
rc->map_name = rc_map;
rc->driver_name = MODULE_NAME;
/* Go */
dev->kernel_ir = kernel_ir;
ret = rc_register_device(rc);
if (ret)
goto err_out_stop;
return 0;
err_out_stop:
cx23885_input_ir_stop(dev);
dev->kernel_ir = NULL;
rc_free_device(rc);
err_out_free:
kfree(kernel_ir->phys);
kfree(kernel_ir->name);
kfree(kernel_ir);
return ret;
}
void cx23885_input_fini(struct cx23885_dev *dev)
{
/* Always stop the IR hardware from generating interrupts */
cx23885_input_ir_stop(dev);
if (dev->kernel_ir == NULL)
return;
rc_unregister_device(dev->kernel_ir->rc);
kfree(dev->kernel_ir->phys);
kfree(dev->kernel_ir->name);
kfree(dev->kernel_ir);
dev->kernel_ir = NULL;
}

View file

@ -0,0 +1,25 @@
/*
* Driver for the Conexant CX23885/7/8 PCIe bridge
*
* Infrared remote control input device
*
* Copyright (C) 2009 Andy Walls <awalls@md.metrocast.net>
*
* 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.
*/
#ifndef _CX23885_INPUT_H_
#define _CX23885_INPUT_H_
void cx23885_input_rx_work_handler(struct cx23885_dev *dev, u32 events);
int cx23885_input_init(struct cx23885_dev *dev);
void cx23885_input_fini(struct cx23885_dev *dev);
#endif

View file

@ -0,0 +1,108 @@
/*
* Driver for the Conexant CX23885/7/8 PCIe bridge
*
* Various common ioctl() support functions
*
* Copyright (c) 2009 Andy Walls <awalls@md.metrocast.net>
*
* 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.
*/
#include "cx23885.h"
#include "cx23885-ioctl.h"
#ifdef CONFIG_VIDEO_ADV_DEBUG
int cx23885_g_chip_info(struct file *file, void *fh,
struct v4l2_dbg_chip_info *chip)
{
struct cx23885_dev *dev = video_drvdata(file);
if (chip->match.addr > 1)
return -EINVAL;
if (chip->match.addr == 1) {
if (dev->v4l_device == NULL)
return -EINVAL;
strlcpy(chip->name, "cx23417", sizeof(chip->name));
} else {
strlcpy(chip->name, dev->v4l2_dev.name, sizeof(chip->name));
}
return 0;
}
static int cx23417_g_register(struct cx23885_dev *dev,
struct v4l2_dbg_register *reg)
{
u32 value;
if (dev->v4l_device == NULL)
return -EINVAL;
if ((reg->reg & 0x3) != 0 || reg->reg >= 0x10000)
return -EINVAL;
if (mc417_register_read(dev, (u16) reg->reg, &value))
return -EINVAL; /* V4L2 spec, but -EREMOTEIO really */
reg->size = 4;
reg->val = value;
return 0;
}
int cx23885_g_register(struct file *file, void *fh,
struct v4l2_dbg_register *reg)
{
struct cx23885_dev *dev = video_drvdata(file);
if (reg->match.addr > 1)
return -EINVAL;
if (reg->match.addr)
return cx23417_g_register(dev, reg);
if ((reg->reg & 0x3) != 0 || reg->reg >= pci_resource_len(dev->pci, 0))
return -EINVAL;
reg->size = 4;
reg->val = cx_read(reg->reg);
return 0;
}
static int cx23417_s_register(struct cx23885_dev *dev,
const struct v4l2_dbg_register *reg)
{
if (dev->v4l_device == NULL)
return -EINVAL;
if ((reg->reg & 0x3) != 0 || reg->reg >= 0x10000)
return -EINVAL;
if (mc417_register_write(dev, (u16) reg->reg, (u32) reg->val))
return -EINVAL; /* V4L2 spec, but -EREMOTEIO really */
return 0;
}
int cx23885_s_register(struct file *file, void *fh,
const struct v4l2_dbg_register *reg)
{
struct cx23885_dev *dev = video_drvdata(file);
if (reg->match.addr > 1)
return -EINVAL;
if (reg->match.addr)
return cx23417_s_register(dev, reg);
if ((reg->reg & 0x3) != 0 || reg->reg >= pci_resource_len(dev->pci, 0))
return -EINVAL;
cx_write(reg->reg, reg->val);
return 0;
}
#endif

Some files were not shown because too many files have changed in this diff Show more