diff options
Diffstat (limited to 'linux/drivers/media/dvb/ttpci-budget/budget.c')
-rw-r--r-- | linux/drivers/media/dvb/ttpci-budget/budget.c | 739 |
1 files changed, 739 insertions, 0 deletions
diff --git a/linux/drivers/media/dvb/ttpci-budget/budget.c b/linux/drivers/media/dvb/ttpci-budget/budget.c new file mode 100644 index 000000000..b50b1d4ad --- /dev/null +++ b/linux/drivers/media/dvb/ttpci-budget/budget.c @@ -0,0 +1,739 @@ +/* + * budget.c: driver for the SAA7146 based Nova/Budget DVB cards + * + * Copyright (C) 1999-2002 Ralph Metzler + * & Marcus Metzler for convergence integrated media GmbH + * + * 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 + * + * + * the project's page is at http://www.linuxtv.org/dvb/ + */ + +#include "dvb_i2c.h" +#include "dvb_frontend.h" +#include "dvbdev.h" +#include "demux.h" +#include "dvb_demux.h" +#include "dmxdev.h" +#include "dvb_filter.h" +#include "dvb_net.h" + +#include "saa7146.h" + +static int budget_debug = 0; +#if 1 +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) + #define KBUILD_MODNAME budget +#endif +#define DEBUG_PROLOG printk("%s: %s(): ",__stringify(KBUILD_MODNAME),__FUNCTION__) +#define DEB_S(x) if (0!=(budget_debug&0x01)) { DEBUG_PROLOG; printk x; } /* simple debug messages */ +#define DEB_D(x) if (0!=(budget_debug&0x02)) { DEBUG_PROLOG; printk x; } /* more detailed debug messages */ +#define DEB_EE(x) if (0!=(budget_debug&0x04)) { DEBUG_PROLOG; printk x; } /* print enter and exit of functions */ +#else +#define DEB_S(x) +#define DEB_D(x) +#define DEB_EE(x) +#endif + +int budget_num = 0; + +/* place to store all the necessary device information */ +typedef struct budget_s { + + /* devices */ + struct dvb_device dvb_dev; + dvb_net_t dvb_net; + + struct saa7146_dev *dev; + + struct dvb_i2c_bus *i2c_bus; + struct card_info *card_type; + + unsigned char *grabbing; + struct saa7146_pgtable pt; + + struct tasklet_struct fidb_tasklet; + + dmxdev_t dmxdev; + struct dvb_demux demux; + char demux_id[16]; + + dmx_frontend_t hw_frontend; + dmx_frontend_t mem_frontend; + + int fe_synced; + struct semaphore pid_mutex; + + int tsf; + u32 ttbp; + int feeding; + + int registered; + + struct dvb_adapter *dvb_adapter; +} budget_t; + +/**************************************************************************** + * General helper functions + ****************************************************************************/ + +/* this is videobuf_vmalloc_to_sg() from video-buf.c */ +struct scatterlist* +vmalloc_to_sg(unsigned char *virt, int nr_pages) +{ + struct scatterlist *sglist; + struct page *pg; + int i; + + sglist = kmalloc(sizeof(struct scatterlist)*nr_pages, GFP_KERNEL); + if (NULL == sglist) + return NULL; + memset(sglist,0,sizeof(struct scatterlist)*nr_pages); + for (i = 0; i < nr_pages; i++, virt += PAGE_SIZE) { + pg = vmalloc_to_page(virt); + if (NULL == pg) + goto err; + if (PageHighMem(pg)) + BUG(); + sglist[i].page = pg; + sglist[i].length = PAGE_SIZE; + } + return sglist; + + err: + kfree(sglist); + return NULL; +} + + +static inline void ddelay(int i) +{ + current->state=TASK_INTERRUPTIBLE; + schedule_timeout((HZ*i)/100); +} + +/**************************************************************************** + * TT budget / WinTV Nova + ****************************************************************************/ + +static int +TTBStop(budget_t *budget) +{ + DEB_EE(("budget: %p\n",budget)); + + if (--budget->feeding) + return budget->feeding; + + saa7146_write(budget->dev, MC1, MASK_20); // DMA3 off + IER_DISABLE(budget->dev, MASK_07); + return 0; +} + +#define TS_WIDTH (4*188) +#define TS_HEIGHT (1024/4) +#define TS_BUFLEN (TS_WIDTH*TS_HEIGHT) + +static int +TTBStart(budget_t *budget) +{ + struct saa7146_dev *dev=budget->dev; + + DEB_EE(("budget: %p\n",budget)); + + if (budget->feeding) + return ++budget->feeding; + + saa7146_write(dev, MC1, MASK_20); // DMA3 off + + memset(budget->grabbing, 0x00, TS_HEIGHT*TS_WIDTH); + + saa7146_write(dev, PCI_BT_V1, 0x001c0000); + + budget->tsf=0; + budget->ttbp=0; + saa7146_write(dev, DD1_INIT, 0x02000680); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + + saa7146_write(dev, BRS_CTRL, 0x60000000); + saa7146_write(dev, MC2, (MASK_08 | MASK_24)); + mdelay(10); + + saa7146_write(dev, BASE_ODD3, 0); + saa7146_write(dev, BASE_EVEN3, TS_WIDTH*TS_HEIGHT/2); + saa7146_write(dev, PROT_ADDR3, TS_WIDTH*TS_HEIGHT); + saa7146_write(dev, BASE_PAGE3, budget->pt.dma |ME1|0xb0); + saa7146_write(dev, PITCH3, TS_WIDTH); + + saa7146_write(dev, NUM_LINE_BYTE3, ((TS_HEIGHT/2)<<16)|TS_WIDTH); + saa7146_write(dev, MC2, (MASK_04 | MASK_20)); + + saa7146_write(dev, MC1, (MASK_04 | MASK_20)); // DMA3 on + + // FIDB + IER_ENABLE(budget->dev, MASK_07); + + return ++budget->feeding; +} + +static +void fidbirq (unsigned long data) +{ + struct budget_s *budget = (struct budget_s*) data; + u8 *mem=(u8 *)(budget->grabbing); + int num; + u32 dmapos; + + DEB_EE(("budget: %p\n",budget)); + + dmapos=saa7146_read(budget->dev, PCI_VDP3); + dmapos-=(dmapos%188); + + if (dmapos>=TS_BUFLEN) { + DEB_S(("bogus dmapos value ignored, budget: %p\n",budget)); + return; + } + + if (budget->tsf) { + mem+=budget->ttbp; + if (dmapos<0x20000) { + num=1024-budget->ttbp/188; + budget->ttbp=0; + } else { + num=(dmapos - budget->ttbp)/188; + budget->ttbp=dmapos; + } + } else { + if (budget->ttbp>1000*188 && budget->ttbp<1024*188) { + if (budget->feeding) + dvb_dmx_swfilter_packets(&budget->demux, + mem+budget->ttbp, + 1024- budget->ttbp / 188); + } + num=dmapos/188; + budget->ttbp=dmapos; + } + + budget->tsf^=1; + saa7146_write(budget->dev, DD1_INIT, 0x02000600|(budget->tsf ? 0x40:0x80)); + saa7146_write(budget->dev, MC2, + (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + + // FIXME: use bottom half or tasklet + if (budget->feeding && mem[0]==0x47) + dvb_dmx_swfilter_packets(&budget->demux, mem, num); +} + +inline static void +Set22K(budget_t *budget, int state) +{ + struct saa7146_dev *dev=budget->dev; + DEB_EE(("budget: %p\n",budget)); + saa7146_setgpio(dev, 3, (state ? SAA7146_GPIO_OUTHI : SAA7146_GPIO_OUTLO)); +} + + +/* Diseqc functions only for TT Budget card */ +/* taken from the Skyvision DVB driver by + Ralph Metzler <rjkm@metzlerbros.de> */ + +inline static void +DiseqcSendBit(budget_t *budget, int data) +{ + struct saa7146_dev *dev=budget->dev; + DEB_EE(("budget: %p\n",budget)); + + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTHI); + udelay(data ? 500 : 1000); + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); + udelay(data ? 1000 : 500); +} + +static void +DiseqcSendByte(budget_t *budget, int data) +{ + struct saa7146_dev *dev=budget->dev; + int i, par=1, d; + + DEB_EE(("budget: %p\n",budget)); + + for (i=7; i>=0; i--) + { + d=(data>>i)&1; + par^=d; + DiseqcSendBit(budget, d); + } + DiseqcSendBit(budget, par); +} + +inline static int +SendDiSEqCMsg(budget_t *budget, int len, u8 *msg, int burst) +{ + struct saa7146_dev *dev=budget->dev; + int i; + + DEB_EE(("budget: %p\n",budget)); + + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); + mdelay(16); + + for (i=0; i<len; i++) + DiseqcSendByte(budget, msg[i]); + + mdelay(16); + + if (burst!=-1) { + if (burst) + DiseqcSendByte(budget, 0xff); + else { + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTHI); + udelay(12500); + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); + } + ddelay(2); + } + + return 0; +} + +/**************************************************************************** + * DVB API SECTION + ****************************************************************************/ + +static int +budget_start_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + budget_t *budget = (budget_t *) demux->priv; + + DEB_EE(("budget: %p\n",budget)); + + if (!demux->dmx.frontend) + return -EINVAL; + + return TTBStart(budget); +} + +static int +budget_stop_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + budget_t *budget = (budget_t *) demux->priv; + + DEB_EE(("budget: %p\n",budget)); + + return TTBStop(budget); +} + +/****************************************************************************** + * SEC device file operations + ******************************************************************************/ + +static +int budget_diseqc_ioctl (struct dvb_frontend *fe, unsigned int cmd, void *arg) +{ + budget_t *budget = fe->before_after_data; + + DEB_EE(("budget: %p\n",budget)); + + switch (cmd) { + case FE_SET_TONE: + switch ((fe_sec_tone_mode_t) arg) { + case SEC_TONE_ON: + Set22K (budget, 1); + break; + case SEC_TONE_OFF: + Set22K (budget, 0); + break; + default: + return -EINVAL; + }; + break; + + case FE_DISEQC_SEND_MASTER_CMD: + { + struct dvb_diseqc_master_cmd *cmd = arg; + + SendDiSEqCMsg (budget, cmd->msg_len, cmd->msg, 0); + break; + } + + case FE_DISEQC_SEND_BURST: + SendDiSEqCMsg (budget, 0, NULL, (int) arg); + break; + + default: + return -EOPNOTSUPP; + }; + + return 0; +} + +static +int budget_register(budget_t *budget) +{ + int ret; + dmx_frontend_t *dvbfront=&budget->hw_frontend; + struct dvb_demux *dvbdemux=&budget->demux; + + DEB_EE(("budget: %p\n",budget)); + + if (budget->registered) + return -1; + + budget->registered=1; + + /* init DiSEqC stuff */ + dvb_add_frontend_ioctls (budget->dvb_adapter, budget_diseqc_ioctl, NULL, budget); + + memcpy(budget->demux_id, "demux0_0", 9); + budget->demux_id[7]=budget->dvb_adapter->num+0x30; + dvbdemux->priv=(void *) budget; + + dvbdemux->filternum=256; + dvbdemux->feednum=256; + dvbdemux->start_feed=budget_start_feed; + dvbdemux->stop_feed=budget_stop_feed; + dvbdemux->write_to_decoder=NULL; + + dvbdemux->dmx.vendor="CIM"; + dvbdemux->dmx.model="sw"; + dvbdemux->dmx.id=budget->demux_id; + dvbdemux->dmx.capabilities=(DMX_TS_FILTERING| + DMX_SECTION_FILTERING| + DMX_MEMORY_BASED_FILTERING); + + dvb_dmx_init(&budget->demux); + + dvbfront->id="hw_frontend"; + dvbfront->vendor="VLSI"; + dvbfront->model="DVB Frontend"; + dvbfront->source=DMX_FRONTEND_0; + + budget->dmxdev.filternum=256; + budget->dmxdev.demux=&dvbdemux->dmx; + budget->dmxdev.capabilities=0; + + dvb_dmxdev_init(&budget->dmxdev, budget->dvb_adapter); + + ret=dvbdemux->dmx.add_frontend(&dvbdemux->dmx, + &budget->hw_frontend); + if (ret<0) + return ret; + + budget->mem_frontend.id="mem_frontend"; + budget->mem_frontend.vendor="memory"; + budget->mem_frontend.model="sw"; + budget->mem_frontend.source=DMX_MEMORY_FE; + ret=dvbdemux->dmx.add_frontend(&dvbdemux->dmx, + &budget->mem_frontend); + if (ret<0) + return ret; + + ret=dvbdemux->dmx.connect_frontend(&dvbdemux->dmx, + &budget->hw_frontend); + if (ret<0) + return ret; + + budget->dvb_net.card_num=budget->dvb_adapter->num; + dvb_net_init(budget->dvb_adapter, &budget->dvb_net, &dvbdemux->dmx); + + return 0; +} + + +static void +dvb_unregister(budget_t *budget) +{ + struct dvb_demux *dvbdemux=&budget->demux; + + DEB_EE(("budget: %p\n",budget)); + + if (!budget->registered) + return; + + dvb_net_release(&budget->dvb_net); + + dvbdemux->dmx.close(&dvbdemux->dmx); + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, &budget->hw_frontend); + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, &budget->mem_frontend); + + dvb_dmxdev_release(&budget->dmxdev); + dvb_dmx_release(&budget->demux); + dvb_remove_frontend_ioctls (budget->dvb_adapter, budget_diseqc_ioctl, NULL); +} + +static +int master_xfer (struct dvb_i2c_bus *i2c, const struct i2c_msg msgs[], int num) +{ + struct saa7146_dev *dev = i2c->data; + return saa7146_i2c_transfer(dev, msgs, num, 6); +} + +/**************************************************************************** + * INITIALIZATION + ****************************************************************************/ + +int budget_preinit(struct saa7146_dev* dev) +{ + DEB_EE(("dev: %p\n",dev)); + return 0; +} + +struct card_info { + int type; + char *name; +}; + +#define DVB_CARD_TT_BUDGET 0 +#define DVB_CARD_TT_BUDGET_CI 1 +#define DVB_CARD_KNC1 2 + +static struct card_info ttbs = { DVB_CARD_TT_BUDGET, "TT-Budget/WinTV-NOVA-S PCI" }; +static struct card_info ttbc = { DVB_CARD_TT_BUDGET, "TT-Budget/WinTV-NOVA-C PCI" }; +static struct card_info ttbt = { DVB_CARD_TT_BUDGET, "TT-Budget/WinTV-NOVA-T PCI" }; +static struct card_info ttbci = { DVB_CARD_TT_BUDGET_CI, "TT-Budget/WinTV-NOVA-CI PCI" }; +static struct card_info satel = { DVB_CARD_TT_BUDGET, "SATELCO Multimedia PCI"}; +static struct card_info knc1 = { DVB_CARD_KNC1, "KNC1 DVB-S" }; + +static struct saa7146_sub_info sub_data[] = { + { 0x1131, 0x4f56 }, + { 0x13c2, 0x1003 }, + { 0x13c2, 0x1004 }, + { 0x13c2, 0x1005 }, + { 0x13c2, 0x100c }, + { 0x13c2, 0x1013 }, + { 0xffff, 0xffff }, +}; + +struct card_match { + struct saa7146_sub_info *sub; /* Subsystem IDs or PCI_ANY_ID */ + struct card_info *card; +}; + +static struct card_match match_data[] = { + { &sub_data[ 0], &knc1 }, + { &sub_data[ 1], &ttbs }, + { &sub_data[ 2], &ttbc }, + { &sub_data[ 3], &ttbt }, + { &sub_data[ 4], &ttbci }, + { &sub_data[ 5], &satel }, + { &sub_data[ 6], NULL }, +}; + + +int budget_probe(struct saa7146_dev* dev, unsigned int subvendor, unsigned int subdevice) +{ + budget_t *budget; + int i = 0; + + DEB_EE(("dev: %p\n",dev)); + + for(i = 0;;i++) { + if( 0xffff == match_data[i].sub->subvendor ) { + printk(KERN_ERR "dvb: device subvendor:0x%04x, subdevice:0x%04x is not a known dvb card.\n",subvendor,subdevice); + return -ENODEV; + } + if( subvendor == match_data[i].sub->subvendor && subdevice == match_data[i].sub->subdevice ) { + break; + } + } + + if (!(budget = kmalloc (sizeof (struct budget_s), GFP_KERNEL))) { + printk ("%s: out of memory!\n", __FUNCTION__); + return -ENOMEM; + } + memset(budget, 0, sizeof(budget_t)); + budget->card_type = match_data[i].card; + + (budget_t*)dev->ext_priv = budget; + + return 0; +} + +static +int budget_attach (struct saa7146_dev* dev) +{ + budget_t *budget = (budget_t*)dev->ext_priv; + struct scatterlist *slist = NULL; + int slen = 0; + int length = TS_WIDTH*TS_HEIGHT; + int pages = (length+PAGE_SIZE-1)/PAGE_SIZE; + int ret = 0; + + DEB_EE(("dev: %p, budget: %p\n",dev,budget)); + + budget->dev=(struct saa7146_dev *)dev; + dvb_register_adapter(&budget->dvb_adapter, budget->card_type->name); + + saa7146_i2c_adapter_prepare(dev, NULL, SAA7146_I2C_BUS_BIT_RATE_3200); + budget->i2c_bus = dvb_register_i2c_bus (master_xfer, dev, budget->dvb_adapter, 0); + + if (!budget->i2c_bus) { + dvb_unregister_adapter (budget->dvb_adapter); + return -ENOMEM; + } + + budget->grabbing = vmalloc(length); + if (!budget->grabbing) { + printk(KERN_ERR "dvb: vmalloc() failed.\n"); + ret = -ENOMEM; + goto err; + } + + if (!(slist = vmalloc_to_sg(budget->grabbing, pages))) { + printk(KERN_ERR "dvb: vmalloc_to_sg() failed.\n"); + ret = -ENOMEM; + goto err; + } + + if (saa7146_pgtable_alloc(dev->pci, &budget->pt)) { + printk(KERN_ERR "dvb: saa7146_pgtable_alloc() failed.\n"); + ret = -ENOMEM; + goto err; + } + + slen = pci_map_sg(dev->pci,slist,pages,PCI_DMA_FROMDEVICE); + saa7146_pgtable_build_single(dev->pci, &budget->pt, slist, slen); + + saa7146_write(dev, PCI_BT_V1, 0x1c00101f); + /* set dd1 stream a & b */ + saa7146_write(dev, DD1_STREAM_B, 0x00000000); + saa7146_write(dev, DD1_INIT, 0x02000000); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + /* upload all */ + saa7146_write(dev, MC2, 0x077c077c); + saa7146_write(dev, GPIO_CTRL, 0x000000); + + tasklet_init (&budget->fidb_tasklet, fidbirq, (unsigned long) budget); + + saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTHI); /* frontend power on */ + + budget_register(budget); + + printk(KERN_INFO "budget: found budget-%d.\n",budget_num); + budget_num++; + return 0; + +err: + if( NULL != budget->grabbing ) { + vfree(budget->grabbing); + } + if( NULL != slist ) { + kfree(slist); + } + dvb_unregister_i2c_bus (master_xfer,budget->i2c_bus->adapter, budget->i2c_bus->id); + dvb_unregister_adapter (budget->dvb_adapter); + return ret; +} + +static +int budget_detach (struct saa7146_dev* saa) +{ + budget_t *budget = (budget_t*)saa->ext_priv; + DEB_EE(("budget: %p\n",budget)); + + dvb_unregister(budget); + dvb_unregister_i2c_bus (master_xfer,budget->i2c_bus->adapter, budget->i2c_bus->id); + dvb_unregister_adapter (budget->dvb_adapter); + + saa7146_pgtable_free(saa->pci, &budget->pt); + vfree(budget->grabbing); + kfree (budget); + + saa->ext_priv = NULL; + budget_num--; + + return 0; +} + + +static +void budget_irq(struct saa7146_dev* dev, u32 *isr) +{ + budget_t *budget = (budget_t*)dev->ext_priv; + + DEB_EE(("dev: %p, budget: %p\n",dev,budget)); + + if (*isr & MASK_07) + tasklet_schedule (&budget->fidb_tasklet); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) +static +void budget_inc_use(struct saa7146_dev* adap) +{ + MOD_INC_USE_COUNT; +} + +static +void budget_dec_use(struct saa7146_dev* adap) +{ + MOD_DEC_USE_COUNT; +} +#endif + +static +struct saa7146_extension budget_extension = { + .name = "budget dvb\0", + .devices = &sub_data[0], + .module = THIS_MODULE, +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) + .inc_use = budget_inc_use, + .dec_use = budget_dec_use, +#endif + .preinit = budget_preinit, + .probe = budget_probe, + .attach = budget_attach, + .detach = budget_detach, + + .irq_mask = MASK_07, + .irq_func = budget_irq, +}; + + +static +int __init budget_init(void) +{ + DEB_EE((".\n")); + + if (saa7146_register_extension(&budget_extension)) + return -ENODEV; + + return 0; +} + + +static +void __exit budget_exit(void) +{ + DEB_EE((".\n")); + + if (saa7146_unregister_extension(&budget_extension)) + printk(KERN_ERR "dvb: extension deregistration failed.\n"); +} + +module_init(budget_init); +module_exit(budget_exit); + +MODULE_DESCRIPTION("driver for the SAA7146 based so-called budget PCI DVB cards by " + "Siemens, Technotrend, Hauppauge"); +MODULE_AUTHOR("Ralph Metzler, Marcus Metzler, others"); +MODULE_LICENSE("GPL"); + +MODULE_PARM(budget_debug,"i"); |