64221be7b9
The flexcop chip often stops generating interrupts after some hours of operation. Apparently this can be fixed by resetting register block 0x300 at each channel change (this is not detailed in the flexcop data books). This patch also restructures DMA handling and adds a bit of debug code for the irq problem in case it still happens for someone. Signed-off-by: Patrick Boettcher <pb@linuxtv.org> Signed-off-by: Johannes Stezenbach <js@linuxtv.org> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
433 lines
12 KiB
C
433 lines
12 KiB
C
/*
|
|
* This file is part of 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;
|
|
module_param(irq_chk_intv, int, 0644);
|
|
MODULE_PARM_DESC(irq_chk_intv, "set the interval for IRQ watchdog (currently just debugging).");
|
|
|
|
#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 = 0;
|
|
module_param(debug, int, 0644);
|
|
MODULE_PARM_DESC(debug, "set debug level (1=info,2=regs,4=TS,8=irqdma (|-able))." DEBSTATUS);
|
|
|
|
#define DRIVER_VERSION "0.1"
|
|
#define DRIVER_NAME "Technisat/B2C2 FlexCop II/IIb/III Digital TV PCI Driver"
|
|
#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 occured */
|
|
int count;
|
|
|
|
spinlock_t irq_lock;
|
|
|
|
unsigned long last_irq;
|
|
|
|
struct work_struct 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(void *data)
|
|
{
|
|
struct flexcop_pci *fc_pci = data;
|
|
struct flexcop_device *fc = fc_pci->fc_dev;
|
|
|
|
flexcop_ibi_value v = fc->read_ibi_reg(fc,sram_dest_reg_714);
|
|
|
|
flexcop_dump_reg(fc_pci->fc_dev,dma1_000,4);
|
|
|
|
if (v.sram_dest_reg_714.net_ovflow_error)
|
|
deb_chk("sram net_ovflow_error\n");
|
|
if (v.sram_dest_reg_714.media_ovflow_error)
|
|
deb_chk("sram media_ovflow_error\n");
|
|
if (v.sram_dest_reg_714.cai_ovflow_error)
|
|
deb_chk("sram cai_ovflow_error\n");
|
|
if (v.sram_dest_reg_714.cai_ovflow_error)
|
|
deb_chk("sram cai_ovflow_error\n");
|
|
|
|
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 pt_regs *regs)
|
|
{
|
|
struct flexcop_pci *fc_pci = dev_id;
|
|
struct flexcop_device *fc = fc_pci->fc_dev;
|
|
flexcop_ibi_value v;
|
|
irqreturn_t ret = IRQ_HANDLED;
|
|
|
|
spin_lock_irq(&fc_pci->irq_lock);
|
|
|
|
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;
|
|
} else if (v.irq_20c.DMA1_Timer_Status == 1) {
|
|
/* 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 */
|
|
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: %08x: cur_pos: %08x, last_cur_pos: %08x ",
|
|
jiffies_to_usecs(jiffies - fc_pci->last_irq),v.raw,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_irq(&fc_pci->irq_lock);
|
|
|
|
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->active_dma1_addr = 0;
|
|
// flexcop_dma_control_size_irq(fc,FC_DMA_1,1);
|
|
|
|
if (irq_chk_intv > 0)
|
|
schedule_delayed_work(&fc_pci->irq_check_work,
|
|
msecs_to_jiffies(irq_chk_intv < 100 ? 100 : irq_chk_intv));
|
|
} else {
|
|
if (irq_chk_intv > 0)
|
|
cancel_delayed_work(&fc_pci->irq_check_work);
|
|
|
|
flexcop_dma_control_timer_irq(fc,FC_DMA_1,0);
|
|
deb_irq("IRQ disabled\n");
|
|
|
|
// flexcop_dma_control_size_irq(fc,FC_DMA_1,0);
|
|
|
|
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;
|
|
if ((ret = flexcop_dma_allocate(fc_pci->pdev,&fc_pci->dma[0],FC_DEFAULT_DMA1_BUFSIZE)) != 0)
|
|
return ret;
|
|
|
|
if ((ret = flexcop_dma_allocate(fc_pci->pdev,&fc_pci->dma[1],FC_DEFAULT_DMA2_BUFSIZE)) != 0)
|
|
goto dma1_free;
|
|
|
|
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;
|
|
|
|
goto success;
|
|
dma1_free:
|
|
flexcop_dma_free(&fc_pci->dma[0]);
|
|
|
|
success:
|
|
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;
|
|
u8 card_rev;
|
|
|
|
pci_read_config_byte(fc_pci->pdev, PCI_CLASS_REVISION, &card_rev);
|
|
info("card revision %x", card_rev);
|
|
|
|
if ((ret = pci_enable_device(fc_pci->pdev)) != 0)
|
|
return ret;
|
|
|
|
pci_set_master(fc_pci->pdev);
|
|
|
|
/* enable interrupts */
|
|
// pci_write_config_dword(pdev, 0x6c, 0x8000);
|
|
|
|
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);
|
|
|
|
if ((ret = request_irq(fc_pci->pdev->irq, flexcop_pci_isr,
|
|
SA_SHIRQ, DRIVER_NAME, fc_pci)) != 0)
|
|
goto err_pci_iounmap;
|
|
|
|
spin_lock_init(&fc_pci->irq_lock);
|
|
|
|
fc_pci->init_state |= FC_PCI_INIT;
|
|
goto success;
|
|
|
|
err_pci_iounmap:
|
|
pci_iounmap(fc_pci->pdev, fc_pci->io_mem);
|
|
pci_set_drvdata(fc_pci->pdev, NULL);
|
|
err_pci_release_regions:
|
|
pci_release_regions(fc_pci->pdev);
|
|
err_pci_disable_device:
|
|
pci_disable_device(fc_pci->pdev);
|
|
|
|
success:
|
|
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_set_drvdata(fc_pci->pdev, NULL);
|
|
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_WORK(&fc_pci->irq_check_work, flexcop_pci_irq_check_work, fc_pci);
|
|
|
|
goto success;
|
|
err_fc_exit:
|
|
flexcop_device_exit(fc);
|
|
err_pci_exit:
|
|
flexcop_pci_exit(fc_pci);
|
|
err_kfree:
|
|
flexcop_device_kfree(fc);
|
|
success:
|
|
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);
|
|
|
|
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) },
|
|
/* { PCI_DEVICE(0x13d0, 0x2200) }, ? */
|
|
{ },
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(pci, flexcop_pci_tbl);
|
|
|
|
static struct pci_driver flexcop_pci_driver = {
|
|
.name = "Technisat/B2C2 FlexCop II/IIb PCI",
|
|
.id_table = flexcop_pci_tbl,
|
|
.probe = flexcop_pci_probe,
|
|
.remove = flexcop_pci_remove,
|
|
};
|
|
|
|
static int __init flexcop_pci_module_init(void)
|
|
{
|
|
return pci_register_driver(&flexcop_pci_driver);
|
|
}
|
|
|
|
static void __exit flexcop_pci_module_exit(void)
|
|
{
|
|
pci_unregister_driver(&flexcop_pci_driver);
|
|
}
|
|
|
|
module_init(flexcop_pci_module_init);
|
|
module_exit(flexcop_pci_module_exit);
|
|
|
|
MODULE_AUTHOR(DRIVER_AUTHOR);
|
|
MODULE_DESCRIPTION(DRIVER_NAME);
|
|
MODULE_LICENSE("GPL");
|