diff options
31 files changed, 6112 insertions, 120 deletions
diff --git a/linux/Documentation/video4linux/CARDLIST.cx88 b/linux/Documentation/video4linux/CARDLIST.cx88 index 7cf5685d3..8fea29754 100644 --- a/linux/Documentation/video4linux/CARDLIST.cx88 +++ b/linux/Documentation/video4linux/CARDLIST.cx88 @@ -66,3 +66,8 @@ 65 -> DViCO FusionHDTV 7 Gold [18ac:d610] 66 -> Prolink Pixelview MPEG 8000GT [1554:4935] 67 -> Kworld PlusTV HD PCI 120 (ATSC 120) [17de:08c1] + 68 -> Hauppauge WinTV-HVR4000 DVB-S/S2/T/Hybrid [0070:6900,0070:6904,0070:6902] + 69 -> Hauppauge WinTV-HVR4000(Lite) DVB-S/S2 [0070:6905,0070:6906] + 70 -> TeVii S460 DVB-S/S2 [D460:9022] + 71 -> Omicom SS4 DVB-S/S2 PCI [A044:2011] + 72 -> TBS 8920 DVB-S/S2 [8920:8888] diff --git a/linux/drivers/media/dvb/Kconfig b/linux/drivers/media/dvb/Kconfig index 8bc1445bd..14488f644 100644 --- a/linux/drivers/media/dvb/Kconfig +++ b/linux/drivers/media/dvb/Kconfig @@ -35,6 +35,10 @@ comment "Supported Pluto2 Adapters" depends on DVB_CORE && PCI && I2C source "drivers/media/dvb/pluto2/Kconfig" +comment "Supported SDMC DM1105 Adapters" + depends on DVB_CORE && PCI && I2C +source "drivers/media/dvb/dm1105/Kconfig" + comment "Supported DVB Frontends" depends on DVB_CORE source "drivers/media/dvb/frontends/Kconfig" diff --git a/linux/drivers/media/dvb/Makefile b/linux/drivers/media/dvb/Makefile index d6ba4d195..ea953a03e 100644 --- a/linux/drivers/media/dvb/Makefile +++ b/linux/drivers/media/dvb/Makefile @@ -2,4 +2,4 @@ # Makefile for the kernel multimedia device drivers. # -obj-y := dvb-core/ frontends/ ttpci/ ttusb-dec/ ttusb-budget/ b2c2/ bt8xx/ cinergyT2/ dvb-usb/ pluto2/ siano/ +obj-y := dvb-core/ frontends/ ttpci/ ttusb-dec/ ttusb-budget/ b2c2/ bt8xx/ cinergyT2/ dvb-usb/ pluto2/ siano/ dm1105/ diff --git a/linux/drivers/media/dvb/dm1105/Kconfig b/linux/drivers/media/dvb/dm1105/Kconfig new file mode 100644 index 000000000..2af5fffe1 --- /dev/null +++ b/linux/drivers/media/dvb/dm1105/Kconfig @@ -0,0 +1,16 @@ +config DVB_DM1105 + tristate "SDMC DM1105 based PCI cards" + depends on DVB_CORE && PCI && I2C + select DVB_PLL if !DVB_FE_CUSTOMISE + select DVB_STV0299 if !DVB_FE_CUSTOMISE + select DVB_CX24116 if !DVB_FE_CUSTOMISE + select DVB_SI21XX if !DVB_FE_CUSTOMISE + help + Support for cards based on the SDMC DM1105 PCI chip like + DvbWorld 2002 + + 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 or M if you own such a device and want to use it. diff --git a/linux/drivers/media/dvb/dm1105/Makefile b/linux/drivers/media/dvb/dm1105/Makefile new file mode 100644 index 000000000..8ac28b054 --- /dev/null +++ b/linux/drivers/media/dvb/dm1105/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_DVB_DM1105) += dm1105.o + +EXTRA_CFLAGS += -Idrivers/media/dvb/dvb-core/ -Idrivers/media/dvb/frontends diff --git a/linux/drivers/media/dvb/dm1105/dm1105.c b/linux/drivers/media/dvb/dm1105/dm1105.c new file mode 100644 index 000000000..07ebe639f --- /dev/null +++ b/linux/drivers/media/dvb/dm1105/dm1105.c @@ -0,0 +1,919 @@ +/* + * dm1105.c - driver for DVB cards based on SDMC DM1105 PCI chip + * + * Copyright (C) 2008 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. + * + * 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/version.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/proc_fs.h> +#include <linux/pci.h> +#include <linux/dma-mapping.h> +#include <linux/input.h> +#include <media/ir-common.h> + +#include "demux.h" +#include "dmxdev.h" +#include "dvb_demux.h" +#include "dvb_frontend.h" +#include "dvb_net.h" +#include "dvbdev.h" +#include "dvb-pll.h" + +#include "stv0299.h" +#include "stv0288.h" +#include "stb6000.h" +#include "si21xx.h" +#include "cx24116.h" +#include "z0194a.h" + +/* ----------------------------------------------- */ +/* + * PCI ID's + */ +#ifndef PCI_VENDOR_ID_TRIGEM +#define PCI_VENDOR_ID_TRIGEM 0x109f +#endif +#ifndef PCI_DEVICE_ID_DM1105 +#define PCI_DEVICE_ID_DM1105 0x036f +#endif +#ifndef PCI_DEVICE_ID_DW2002 +#define PCI_DEVICE_ID_DW2002 0x2002 +#endif +#ifndef PCI_DEVICE_ID_DW2004 +#define PCI_DEVICE_ID_DW2004 0x2004 +#endif +/* ----------------------------------------------- */ +/* sdmc dm1105 registers */ + +/* TS Control */ +#define DM1105_TSCTR 0x00 +#define DM1105_DTALENTH 0x04 + +/* GPIO Interface */ +#define DM1105_GPIOVAL 0x08 +#define DM1105_GPIOCTR 0x0c + +/* PID serial number */ +#define DM1105_PIDN 0x10 + +/* Odd-even secret key select */ +#define DM1105_CWSEL 0x14 + +/* Host Command Interface */ +#define DM1105_HOST_CTR 0x18 +#define DM1105_HOST_AD 0x1c + +/* PCI Interface */ +#define DM1105_CR 0x30 +#define DM1105_RST 0x34 +#define DM1105_STADR 0x38 +#define DM1105_RLEN 0x3c +#define DM1105_WRP 0x40 +#define DM1105_INTCNT 0x44 +#define DM1105_INTMAK 0x48 +#define DM1105_INTSTS 0x4c + +/* CW Value */ +#define DM1105_ODD 0x50 +#define DM1105_EVEN 0x58 + +/* PID Value */ +#define DM1105_PID 0x60 + +/* IR Control */ +#define DM1105_IRCTR 0x64 +#define DM1105_IRMODE 0x68 +#define DM1105_SYSTEMCODE 0x6c +#define DM1105_IRCODE 0x70 + +/* Unknown Values */ +#define DM1105_ENCRYPT 0x74 +#define DM1105_VER 0x7c + +/* I2C Interface */ +#define DM1105_I2CCTR 0x80 +#define DM1105_I2CSTS 0x81 +#define DM1105_I2CDAT 0x82 +#define DM1105_I2C_RA 0x83 +/* ----------------------------------------------- */ +/* Interrupt Mask Bits */ + +#define INTMAK_TSIRQM 0x01 +#define INTMAK_HIRQM 0x04 +#define INTMAK_IRM 0x08 +#define INTMAK_ALLMASK (INTMAK_TSIRQM | \ + INTMAK_HIRQM | \ + INTMAK_IRM) +#define INTMAK_NONEMASK 0x00 + +/* Interrupt Status Bits */ +#define INTSTS_TSIRQ 0x01 +#define INTSTS_HIRQ 0x04 +#define INTSTS_IR 0x08 + +/* IR Control Bits */ +#define DM1105_IR_EN 0x01 +#define DM1105_SYS_CHK 0x02 +#define DM1105_REP_FLG 0x08 + +/* EEPROM addr */ +#define IIC_24C01_addr 0xa0 +/* Max board count */ +#define DM1105_MAX 0x04 + +#define DRIVER_NAME "dm1105" + +#define DM1105_DMA_PACKETS 47 +#define DM1105_DMA_PACKET_LENGTH (128*4) +#define DM1105_DMA_BYTES (128 * 4 * DM1105_DMA_PACKETS) + +/* GPIO's for LNB power control */ +#define DM1105_LNB_MASK 0x00000000 +#define DM1105_LNB_13V 0x00010100 +#define DM1105_LNB_18V 0x00000100 + +static int ir_debug; +module_param(ir_debug, int, 0644); +MODULE_PARM_DESC(ir_debug, "enable debugging information for IR decoding"); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +static u16 ir_codes_dm1105_nec[128] = { + [0x0a] = KEY_Q, /*power*/ + [0x0c] = KEY_M, /*mute*/ + [0x11] = KEY_1, + [0x12] = KEY_2, + [0x13] = KEY_3, + [0x14] = KEY_4, + [0x15] = KEY_5, + [0x16] = KEY_6, + [0x17] = KEY_7, + [0x18] = KEY_8, + [0x19] = KEY_9, + [0x10] = KEY_0, + [0x1c] = KEY_PAGEUP, /*ch+*/ + [0x0f] = KEY_PAGEDOWN, /*ch-*/ + [0x1a] = KEY_O, /*vol+*/ + [0x0e] = KEY_Z, /*vol-*/ + [0x04] = KEY_R, /*rec*/ + [0x09] = KEY_D, /*fav*/ + [0x08] = KEY_BACKSPACE, /*rewind*/ + [0x07] = KEY_A, /*fast*/ + [0x0b] = KEY_P, /*pause*/ + [0x02] = KEY_ESC, /*cancel*/ + [0x03] = KEY_G, /*tab*/ + [0x00] = KEY_UP, /*up*/ + [0x1f] = KEY_ENTER, /*ok*/ + [0x01] = KEY_DOWN, /*down*/ + [0x05] = KEY_C, /*cap*/ + [0x06] = KEY_S, /*stop*/ + [0x40] = KEY_F, /*full*/ + [0x1e] = KEY_W, /*tvmode*/ + [0x1b] = KEY_B, /*recall*/ +}; + +/* infrared remote control */ +struct infrared { + u16 key_map[128]; + struct input_dev *input_dev; + char input_phys[32]; + struct tasklet_struct ir_tasklet; + u32 ir_command; +}; + +struct dm1105dvb { + /* pci */ + struct pci_dev *pdev; + u8 __iomem *io_mem; + + /* ir */ + struct infrared ir; + + /* dvb */ + 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; + unsigned int full_ts_users; + + /* i2c */ + struct i2c_adapter i2c_adap; + + /* dma */ + dma_addr_t dma_addr; + unsigned char *ts_buf; + u32 wrp; + u32 buffer_size; + unsigned int PacketErrorCount; + unsigned int dmarst; + spinlock_t lock; + +}; + +#define dm_io_mem(reg) ((unsigned long)(&dm1105dvb->io_mem[reg])) + +static struct dm1105dvb *dm1105dvb_local; + +static int dm1105_i2c_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg *msgs, int num) +{ + struct dm1105dvb *dm1105dvb ; + + int addr, rc, i, j, k, len, byte, data; + u8 status; + + dm1105dvb = i2c_adap->algo_data; + for (i = 0; i < num; i++) { + outb(0x00, dm_io_mem(DM1105_I2CCTR)); + if (msgs[i].flags & I2C_M_RD) { + /* read bytes */ + addr = msgs[i].addr << 1; + addr |= 1; + outb(addr, dm_io_mem(DM1105_I2CDAT)); + for (byte = 0; byte < msgs[i].len; byte++) + outb(0, dm_io_mem(DM1105_I2CDAT + byte + 1)); + + outb(0x81 + msgs[i].len, dm_io_mem(DM1105_I2CCTR)); + for (j = 0; j < 55; j++) { + mdelay(10); + status = inb(dm_io_mem(DM1105_I2CSTS)); + if ((status & 0xc0) == 0x40) + break; + } + if (j >= 55) + return -1; + + for (byte = 0; byte < msgs[i].len; byte++) { + rc = inb(dm_io_mem(DM1105_I2CDAT + byte + 1)); + if (rc < 0) + goto err; + msgs[i].buf[byte] = rc; + } + } else { + if ((msgs[i].buf[0] == 0xf7) && (msgs[i].addr == 0x55)) { + /* prepaired for cx24116 firmware */ + /* Write in small blocks */ + len = msgs[i].len - 1; + k = 1; + do { + outb(msgs[i].addr << 1, dm_io_mem(DM1105_I2CDAT)); + outb(0xf7, dm_io_mem(DM1105_I2CDAT + 1)); + for (byte = 0; byte < (len > 48 ? 48 : len); byte++) { + data = msgs[i].buf[k+byte]; + outb(data, dm_io_mem(DM1105_I2CDAT + byte + 2)); + } + outb(0x82 + (len > 48 ? 48 : len), dm_io_mem(DM1105_I2CCTR)); + for (j = 0; j < 25; j++) { + mdelay(10); + status = inb(dm_io_mem(DM1105_I2CSTS)); + if ((status & 0xc0) == 0x40) + break; + } + + if (j >= 25) + return -1; + + k += 48; + len -= 48; + } while (len > 0); + } else { + /* write bytes */ + outb(msgs[i].addr<<1, dm_io_mem(DM1105_I2CDAT)); + for (byte = 0; byte < msgs[i].len; byte++) { + data = msgs[i].buf[byte]; + outb(data, dm_io_mem(DM1105_I2CDAT + byte + 1)); + } + outb(0x81 + msgs[i].len, dm_io_mem(DM1105_I2CCTR)); + for (j = 0; j < 25; j++) { + mdelay(10); + status = inb(dm_io_mem(DM1105_I2CSTS)); + if ((status & 0xc0) == 0x40) + break; + } + + if (j >= 25) + return -1; + } + } + } + return num; + err: + return rc; +} + +static u32 functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C; +} + +static struct i2c_algorithm dm1105_algo = { + .master_xfer = dm1105_i2c_xfer, + .functionality = functionality, +}; + +static inline struct dm1105dvb *feed_to_dm1105dvb(struct dvb_demux_feed *feed) +{ + return container_of(feed->demux, struct dm1105dvb, demux); +} + +static inline struct dm1105dvb *frontend_to_dm1105dvb(struct dvb_frontend *fe) +{ + return container_of(fe->dvb, struct dm1105dvb, dvb_adapter); +} + +static int dm1105dvb_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage) +{ + struct dm1105dvb *dm1105dvb = frontend_to_dm1105dvb(fe); + + if (voltage == SEC_VOLTAGE_18) { + outl(DM1105_LNB_MASK, dm_io_mem(DM1105_GPIOCTR)); + outl(DM1105_LNB_18V, dm_io_mem(DM1105_GPIOVAL)); + } else { + /*LNB ON-13V by default!*/ + outl(DM1105_LNB_MASK, dm_io_mem(DM1105_GPIOCTR)); + outl(DM1105_LNB_13V, dm_io_mem(DM1105_GPIOVAL)); + } + + return 0; +} + +static void dm1105dvb_set_dma_addr(struct dm1105dvb *dm1105dvb) +{ + outl(cpu_to_le32(dm1105dvb->dma_addr), dm_io_mem(DM1105_STADR)); +} + +static int __devinit dm1105dvb_dma_map(struct dm1105dvb *dm1105dvb) +{ + dm1105dvb->ts_buf = pci_alloc_consistent(dm1105dvb->pdev, 6*DM1105_DMA_BYTES, &dm1105dvb->dma_addr); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27) + return pci_dma_mapping_error(dm1105dvb->dma_addr); +#else + return pci_dma_mapping_error(dm1105dvb->pdev, dm1105dvb->dma_addr); +#endif +} + +static void dm1105dvb_dma_unmap(struct dm1105dvb *dm1105dvb) +{ + pci_free_consistent(dm1105dvb->pdev, 6*DM1105_DMA_BYTES, dm1105dvb->ts_buf, dm1105dvb->dma_addr); +} + +static void __devinit dm1105dvb_enable_irqs(struct dm1105dvb *dm1105dvb) +{ + outb(INTMAK_ALLMASK, dm_io_mem(DM1105_INTMAK)); + outb(1, dm_io_mem(DM1105_CR)); +} + +static void dm1105dvb_disable_irqs(struct dm1105dvb *dm1105dvb) +{ + outb(INTMAK_IRM, dm_io_mem(DM1105_INTMAK)); + outb(0, dm_io_mem(DM1105_CR)); +} + +static int dm1105dvb_start_feed(struct dvb_demux_feed *f) +{ + struct dm1105dvb *dm1105dvb = feed_to_dm1105dvb(f); + + if (dm1105dvb->full_ts_users++ == 0) + dm1105dvb_enable_irqs(dm1105dvb); + + return 0; +} + +static int dm1105dvb_stop_feed(struct dvb_demux_feed *f) +{ + struct dm1105dvb *dm1105dvb = feed_to_dm1105dvb(f); + + if (--dm1105dvb->full_ts_users == 0) + dm1105dvb_disable_irqs(dm1105dvb); + + return 0; +} + +/* ir tasklet */ +static void dm1105_emit_key(unsigned long parm) +{ + struct infrared *ir = (struct infrared *) parm; + u32 ircom = ir->ir_command; + u8 data; + u16 keycode; + + data = (ircom >> 8) & 0x7f; + + input_event(ir->input_dev, EV_MSC, MSC_RAW, (0x0000f8 << 16) | data); + input_event(ir->input_dev, EV_MSC, MSC_SCAN, data); + keycode = ir->key_map[data]; + + if (!keycode) + return; + + input_event(ir->input_dev, EV_KEY, keycode, 1); + input_sync(ir->input_dev); + input_event(ir->input_dev, EV_KEY, keycode, 0); + input_sync(ir->input_dev); + +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) +static irqreturn_t dm1105dvb_irq(int irq, void *dev_id, struct pt_regs *regs) +#else +static irqreturn_t dm1105dvb_irq(int irq, void *dev_id) +#endif +{ + struct dm1105dvb *dm1105dvb = dev_id; + unsigned int piece; + unsigned int nbpackets; + u32 command; + u32 nextwrp; + u32 oldwrp; + + /* Read-Write INSTS Ack's Interrupt for DM1105 chip 16.03.2008 */ + unsigned int intsts = inb(dm_io_mem(DM1105_INTSTS)); + outb(intsts, dm_io_mem(DM1105_INTSTS)); + + switch (intsts) { + case INTSTS_TSIRQ: + case (INTSTS_TSIRQ | INTSTS_IR): + nextwrp = inl(dm_io_mem(DM1105_WRP)) - + inl(dm_io_mem(DM1105_STADR)) ; + oldwrp = dm1105dvb->wrp; + spin_lock(&dm1105dvb->lock); + if (!((dm1105dvb->ts_buf[oldwrp] == 0x47) && + (dm1105dvb->ts_buf[oldwrp + 188] == 0x47) && + (dm1105dvb->ts_buf[oldwrp + 188 * 2] == 0x47))) { + dm1105dvb->PacketErrorCount++; + /* bad packet found */ + if ((dm1105dvb->PacketErrorCount >= 2) && + (dm1105dvb->dmarst == 0)) { + outb(1, dm_io_mem(DM1105_RST)); + dm1105dvb->wrp = 0; + dm1105dvb->PacketErrorCount = 0; + dm1105dvb->dmarst = 0; + spin_unlock(&dm1105dvb->lock); + return IRQ_HANDLED; + } + } + if (nextwrp < oldwrp) { + piece = dm1105dvb->buffer_size - oldwrp; + memcpy(dm1105dvb->ts_buf + dm1105dvb->buffer_size, dm1105dvb->ts_buf, nextwrp); + nbpackets = (piece + nextwrp)/188; + } else { + nbpackets = (nextwrp - oldwrp)/188; + } + dvb_dmx_swfilter_packets(&dm1105dvb->demux, &dm1105dvb->ts_buf[oldwrp], nbpackets); + dm1105dvb->wrp = nextwrp; + spin_unlock(&dm1105dvb->lock); + break; + case INTSTS_IR: + command = inl(dm_io_mem(DM1105_IRCODE)); + if (ir_debug) + printk("dm1105: received byte 0x%04x\n", command); + + dm1105dvb->ir.ir_command = command; + tasklet_schedule(&dm1105dvb->ir.ir_tasklet); + break; + } + return IRQ_HANDLED; + + +} + +/* register with input layer */ +static void input_register_keys(struct infrared *ir) +{ + int i; + + memset(ir->input_dev->keybit, 0, sizeof(ir->input_dev->keybit)); + + for (i = 0; i < ARRAY_SIZE(ir->key_map); i++) + set_bit(ir->key_map[i], ir->input_dev->keybit); + + ir->input_dev->keycode = ir->key_map; + ir->input_dev->keycodesize = sizeof(ir->key_map[0]); + ir->input_dev->keycodemax = ARRAY_SIZE(ir->key_map); +} + +int __devinit dm1105_ir_init(struct dm1105dvb *dm1105) +{ + struct input_dev *input_dev; + int err; + + dm1105dvb_local = dm1105; + + input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + dm1105->ir.input_dev = input_dev; + snprintf(dm1105->ir.input_phys, sizeof(dm1105->ir.input_phys), + "pci-%s/ir0", pci_name(dm1105->pdev)); + + input_dev->evbit[0] = BIT(EV_KEY); + input_dev->name = "DVB on-card IR receiver"; + + input_dev->phys = dm1105->ir.input_phys; + input_dev->id.bustype = BUS_PCI; + input_dev->id.version = 2; + if (dm1105->pdev->subsystem_vendor) { + input_dev->id.vendor = dm1105->pdev->subsystem_vendor; + input_dev->id.product = dm1105->pdev->subsystem_device; + } else { + input_dev->id.vendor = dm1105->pdev->vendor; + input_dev->id.product = dm1105->pdev->device; + } + input_dev->dev.parent = &dm1105->pdev->dev; + /* initial keymap */ + memcpy(dm1105->ir.key_map, ir_codes_dm1105_nec, sizeof dm1105->ir.key_map); + input_register_keys(&dm1105->ir); + err = input_register_device(input_dev); + if (err) { + input_free_device(input_dev); + return err; + } + + tasklet_init(&dm1105->ir.ir_tasklet, dm1105_emit_key, (unsigned long) &dm1105->ir); + + return 0; +} + + +void __devexit dm1105_ir_exit(struct dm1105dvb *dm1105) +{ + tasklet_kill(&dm1105->ir.ir_tasklet); + input_unregister_device(dm1105->ir.input_dev); + +} + +static int __devinit dm1105dvb_hw_init(struct dm1105dvb *dm1105dvb) +{ + dm1105dvb_disable_irqs(dm1105dvb); + + outb(0, dm_io_mem(DM1105_HOST_CTR)); + + /*DATALEN 188,*/ + outb(188, dm_io_mem(DM1105_DTALENTH)); + /*TS_STRT TS_VALP MSBFIRST TS_MODE ALPAS TSPES*/ + outw(0xc10a, dm_io_mem(DM1105_TSCTR)); + + /* map DMA and set address */ + dm1105dvb_dma_map(dm1105dvb); + dm1105dvb_set_dma_addr(dm1105dvb); + /* big buffer */ + outl(5*DM1105_DMA_BYTES, dm_io_mem(DM1105_RLEN)); + outb(47, dm_io_mem(DM1105_INTCNT)); + + /* IR NEC mode enable */ + outb((DM1105_IR_EN | DM1105_SYS_CHK), dm_io_mem(DM1105_IRCTR)); + outb(0, dm_io_mem(DM1105_IRMODE)); + outw(0, dm_io_mem(DM1105_SYSTEMCODE)); + + return 0; +} + +static void dm1105dvb_hw_exit(struct dm1105dvb *dm1105dvb) +{ + dm1105dvb_disable_irqs(dm1105dvb); + + /* IR disable */ + outb(0, dm_io_mem(DM1105_IRCTR)); + outb(INTMAK_NONEMASK, dm_io_mem(DM1105_INTMAK)); + + dm1105dvb_dma_unmap(dm1105dvb); +} + +static struct stv0288_config earda_config = { + .demod_address = 0x68, + .min_delay_ms = 100, +}; + +static struct si21xx_config serit_config = { + .demod_address = 0x68, + .min_delay_ms = 100, + +}; + +static struct cx24116_config serit_sp2633_config = { + .demod_address = 0x55, +}; + +static int __devinit frontend_init(struct dm1105dvb *dm1105dvb) +{ + int ret; + + switch (dm1105dvb->pdev->subsystem_device) { + case PCI_DEVICE_ID_DW2002: + dm1105dvb->fe = dvb_attach( + stv0299_attach, &sharp_z0194a_config, + &dm1105dvb->i2c_adap); + + if (dm1105dvb->fe) { + dm1105dvb->fe->ops.set_voltage = + dm1105dvb_set_voltage; + dvb_attach(dvb_pll_attach, dm1105dvb->fe, 0x60, + &dm1105dvb->i2c_adap, DVB_PLL_OPERA1); + } + + if (!dm1105dvb->fe) { + dm1105dvb->fe = dvb_attach( + stv0288_attach, &earda_config, + &dm1105dvb->i2c_adap); + if (dm1105dvb->fe) { + dm1105dvb->fe->ops.set_voltage = + dm1105dvb_set_voltage; + dvb_attach(stb6000_attach, dm1105dvb->fe, 0x61, + &dm1105dvb->i2c_adap); + } + } + + if (!dm1105dvb->fe) { + dm1105dvb->fe = dvb_attach( + si21xx_attach, &serit_config, + &dm1105dvb->i2c_adap); + if (dm1105dvb->fe) + dm1105dvb->fe->ops.set_voltage = + dm1105dvb_set_voltage; + } + break; + case PCI_DEVICE_ID_DW2004: + dm1105dvb->fe = dvb_attach( + cx24116_attach, &serit_sp2633_config, + &dm1105dvb->i2c_adap); + if (dm1105dvb->fe) + dm1105dvb->fe->ops.set_voltage = dm1105dvb_set_voltage; + break; + } + + if (!dm1105dvb->fe) { + dev_err(&dm1105dvb->pdev->dev, "could not attach frontend\n"); + return -ENODEV; + } + + ret = dvb_register_frontend(&dm1105dvb->dvb_adapter, dm1105dvb->fe); + if (ret < 0) { + if (dm1105dvb->fe->ops.release) + dm1105dvb->fe->ops.release(dm1105dvb->fe); + dm1105dvb->fe = NULL; + return ret; + } + + return 0; +} + +static void __devinit dm1105dvb_read_mac(struct dm1105dvb *dm1105dvb, u8 *mac) +{ + static u8 command[1] = { 0x28 }; + + struct i2c_msg msg[] = { + { .addr = IIC_24C01_addr >> 1, .flags = 0, + .buf = command, .len = 1 }, + { .addr = IIC_24C01_addr >> 1, .flags = I2C_M_RD, + .buf = mac, .len = 6 }, + }; + + dm1105_i2c_xfer(&dm1105dvb->i2c_adap, msg , 2); + dev_info(&dm1105dvb->pdev->dev, "MAC %02x:%02x:%02x:%02x:%02x:%02x\n", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +} + +static int __devinit dm1105_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct dm1105dvb *dm1105dvb; + struct dvb_adapter *dvb_adapter; + struct dvb_demux *dvbdemux; + struct dmx_demux *dmx; + int ret = -ENOMEM; + + dm1105dvb = kzalloc(sizeof(struct dm1105dvb), GFP_KERNEL); + if (!dm1105dvb) + goto out; + + dm1105dvb->pdev = pdev; + dm1105dvb->buffer_size = 5 * DM1105_DMA_BYTES; + dm1105dvb->PacketErrorCount = 0; + dm1105dvb->dmarst = 0; + + ret = pci_enable_device(pdev); + if (ret < 0) + goto err_kfree; + + ret = pci_set_dma_mask(pdev, DMA_32BIT_MASK); + if (ret < 0) + goto err_pci_disable_device; + + pci_set_master(pdev); + + ret = pci_request_regions(pdev, DRIVER_NAME); + if (ret < 0) + goto err_pci_disable_device; + + dm1105dvb->io_mem = pci_iomap(pdev, 0, pci_resource_len(pdev, 0)); + if (!dm1105dvb->io_mem) { + ret = -EIO; + goto err_pci_release_regions; + } + + spin_lock_init(&dm1105dvb->lock); + pci_set_drvdata(pdev, dm1105dvb); + + ret = request_irq(pdev->irq, dm1105dvb_irq, IRQF_SHARED, DRIVER_NAME, dm1105dvb); + if (ret < 0) + goto err_pci_iounmap; + + ret = dm1105dvb_hw_init(dm1105dvb); + if (ret < 0) + goto err_free_irq; + + /* i2c */ + i2c_set_adapdata(&dm1105dvb->i2c_adap, dm1105dvb); + strcpy(dm1105dvb->i2c_adap.name, DRIVER_NAME); + dm1105dvb->i2c_adap.owner = THIS_MODULE; + dm1105dvb->i2c_adap.class = I2C_CLASS_TV_DIGITAL; + dm1105dvb->i2c_adap.dev.parent = &pdev->dev; + dm1105dvb->i2c_adap.algo = &dm1105_algo; + dm1105dvb->i2c_adap.algo_data = dm1105dvb; + ret = i2c_add_adapter(&dm1105dvb->i2c_adap); + + if (ret < 0) + goto err_dm1105dvb_hw_exit; + + /* dvb */ + ret = dvb_register_adapter(&dm1105dvb->dvb_adapter, DRIVER_NAME, + THIS_MODULE, &pdev->dev, adapter_nr); + if (ret < 0) + goto err_i2c_del_adapter; + + dvb_adapter = &dm1105dvb->dvb_adapter; + + dm1105dvb_read_mac(dm1105dvb, dvb_adapter->proposed_mac); + + dvbdemux = &dm1105dvb->demux; + dvbdemux->filternum = 256; + dvbdemux->feednum = 256; + dvbdemux->start_feed = dm1105dvb_start_feed; + dvbdemux->stop_feed = dm1105dvb_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; + dm1105dvb->dmxdev.filternum = 256; + dm1105dvb->dmxdev.demux = dmx; + dm1105dvb->dmxdev.capabilities = 0; + + ret = dvb_dmxdev_init(&dm1105dvb->dmxdev, dvb_adapter); + if (ret < 0) + goto err_dvb_dmx_release; + + dm1105dvb->hw_frontend.source = DMX_FRONTEND_0; + + ret = dmx->add_frontend(dmx, &dm1105dvb->hw_frontend); + if (ret < 0) + goto err_dvb_dmxdev_release; + + dm1105dvb->mem_frontend.source = DMX_MEMORY_FE; + + ret = dmx->add_frontend(dmx, &dm1105dvb->mem_frontend); + if (ret < 0) + goto err_remove_hw_frontend; + + ret = dmx->connect_frontend(dmx, &dm1105dvb->hw_frontend); + if (ret < 0) + goto err_remove_mem_frontend; + + ret = frontend_init(dm1105dvb); + if (ret < 0) + goto err_disconnect_frontend; + + dvb_net_init(dvb_adapter, &dm1105dvb->dvbnet, dmx); + dm1105_ir_init(dm1105dvb); +out: + return ret; + +err_disconnect_frontend: + dmx->disconnect_frontend(dmx); +err_remove_mem_frontend: + dmx->remove_frontend(dmx, &dm1105dvb->mem_frontend); +err_remove_hw_frontend: + dmx->remove_frontend(dmx, &dm1105dvb->hw_frontend); +err_dvb_dmxdev_release: + dvb_dmxdev_release(&dm1105dvb->dmxdev); +err_dvb_dmx_release: + dvb_dmx_release(dvbdemux); +err_dvb_unregister_adapter: + dvb_unregister_adapter(dvb_adapter); +err_i2c_del_adapter: + i2c_del_adapter(&dm1105dvb->i2c_adap); +err_dm1105dvb_hw_exit: + dm1105dvb_hw_exit(dm1105dvb); +err_free_irq: + free_irq(pdev->irq, dm1105dvb); +err_pci_iounmap: + pci_iounmap(pdev, dm1105dvb->io_mem); +err_pci_release_regions: + pci_release_regions(pdev); +err_pci_disable_device: + pci_disable_device(pdev); +err_kfree: + pci_set_drvdata(pdev, NULL); + kfree(dm1105dvb); + goto out; +} + +static void __devexit dm1105_remove(struct pci_dev *pdev) +{ + struct dm1105dvb *dm1105dvb = pci_get_drvdata(pdev); + struct dvb_adapter *dvb_adapter = &dm1105dvb->dvb_adapter; + struct dvb_demux *dvbdemux = &dm1105dvb->demux; + struct dmx_demux *dmx = &dvbdemux->dmx; + + dm1105_ir_exit(dm1105dvb); + dmx->close(dmx); + dvb_net_release(&dm1105dvb->dvbnet); + if (dm1105dvb->fe) + dvb_unregister_frontend(dm1105dvb->fe); + + dmx->disconnect_frontend(dmx); + dmx->remove_frontend(dmx, &dm1105dvb->mem_frontend); + dmx->remove_frontend(dmx, &dm1105dvb->hw_frontend); + dvb_dmxdev_release(&dm1105dvb->dmxdev); + dvb_dmx_release(dvbdemux); + dvb_unregister_adapter(dvb_adapter); + if (&dm1105dvb->i2c_adap) + i2c_del_adapter(&dm1105dvb->i2c_adap); + + dm1105dvb_hw_exit(dm1105dvb); + synchronize_irq(pdev->irq); + free_irq(pdev->irq, dm1105dvb); + pci_iounmap(pdev, dm1105dvb->io_mem); + pci_release_regions(pdev); + pci_disable_device(pdev); + pci_set_drvdata(pdev, NULL); + kfree(dm1105dvb); +} + +static struct pci_device_id dm1105_id_table[] __devinitdata = { + { + .vendor = PCI_VENDOR_ID_TRIGEM, + .device = PCI_DEVICE_ID_DM1105, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_DEVICE_ID_DW2002, + }, { + .vendor = PCI_VENDOR_ID_TRIGEM, + .device = PCI_DEVICE_ID_DM1105, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_DEVICE_ID_DW2004, + }, { + /* empty */ + }, +}; + +MODULE_DEVICE_TABLE(pci, dm1105_id_table); + +static struct pci_driver dm1105_driver = { + .name = DRIVER_NAME, + .id_table = dm1105_id_table, + .probe = dm1105_probe, + .remove = __devexit_p(dm1105_remove), +}; + +static int __init dm1105_init(void) +{ + return pci_register_driver(&dm1105_driver); +} + +static void __exit dm1105_exit(void) +{ + pci_unregister_driver(&dm1105_driver); +} + +module_init(dm1105_init); +module_exit(dm1105_exit); + +MODULE_AUTHOR("Igor M. Liplianin <liplianin@me.by>"); +MODULE_DESCRIPTION("SDMC DM1105 DVB driver"); +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/dvb/dvb-core/dvb_frontend.c b/linux/drivers/media/dvb/dvb-core/dvb_frontend.c index 60a94dc85..5c1193524 100644 --- a/linux/drivers/media/dvb/dvb-core/dvb_frontend.c +++ b/linux/drivers/media/dvb/dvb-core/dvb_frontend.c @@ -766,6 +766,547 @@ static int dvb_frontend_check_parameters(struct dvb_frontend *fe, return 0; } +struct dtv_cmds_h dtv_cmds[] = { + [DTV_TUNE] = { + .name = "DTV_TUNE", + .cmd = DTV_TUNE, + .set = 1, + }, + [DTV_CLEAR] = { + .name = "DTV_CLEAR", + .cmd = DTV_CLEAR, + .set = 1, + }, + + /* Set */ + [DTV_FREQUENCY] = { + .name = "DTV_FREQUENCY", + .cmd = DTV_FREQUENCY, + .set = 1, + }, + [DTV_BANDWIDTH_HZ] = { + .name = "DTV_BANDWIDTH_HZ", + .cmd = DTV_BANDWIDTH_HZ, + .set = 1, + }, + [DTV_MODULATION] = { + .name = "DTV_MODULATION", + .cmd = DTV_MODULATION, + .set = 1, + }, + [DTV_INVERSION] = { + .name = "DTV_INVERSION", + .cmd = DTV_INVERSION, + .set = 1, + }, + [DTV_DISEQC_MASTER] = { + .name = "DTV_DISEQC_MASTER", + .cmd = DTV_DISEQC_MASTER, + .set = 1, + .buffer = 1, + }, + [DTV_SYMBOL_RATE] = { + .name = "DTV_SYMBOL_RATE", + .cmd = DTV_SYMBOL_RATE, + .set = 1, + }, + [DTV_INNER_FEC] = { + .name = "DTV_INNER_FEC", + .cmd = DTV_INNER_FEC, + .set = 1, + }, + [DTV_VOLTAGE] = { + .name = "DTV_VOLTAGE", + .cmd = DTV_VOLTAGE, + .set = 1, + }, + [DTV_TONE] = { + .name = "DTV_TONE", + .cmd = DTV_TONE, + .set = 1, + }, + [DTV_PILOT] = { + .name = "DTV_PILOT", + .cmd = DTV_PILOT, + .set = 1, + }, + [DTV_ROLLOFF] = { + .name = "DTV_ROLLOFF", + .cmd = DTV_ROLLOFF, + .set = 1, + }, + [DTV_DELIVERY_SYSTEM] = { + .name = "DTV_DELIVERY_SYSTEM", + .cmd = DTV_DELIVERY_SYSTEM, + .set = 1, + }, +#if 0 + [DTV_ISDB_SEGMENT_IDX] = { + .name = "DTV_ISDB_SEGMENT_IDX", + .cmd = DTV_ISDB_SEGMENT_IDX, + .set = 1, + }, + [DTV_ISDB_SEGMENT_WIDTH] = { + .name = "DTV_ISDB_SEGMENT_WIDTH", + .cmd = DTV_ISDB_SEGMENT_WIDTH, + .set = 1, + }, +#endif + /* Get */ + [DTV_DISEQC_SLAVE_REPLY] = { + .name = "DTV_DISEQC_SLAVE_REPLY", + .cmd = DTV_DISEQC_SLAVE_REPLY, + .set = 0, + .buffer = 1, + }, +#if 0 + [DTV_ISDB_LAYERA_FEC] = { + .name = "DTV_ISDB_LAYERA_FEC", + .cmd = DTV_ISDB_LAYERA_FEC, + .set = 0, + }, + [DTV_ISDB_LAYERA_MODULATION] = { + .name = "DTV_ISDB_LAYERA_MODULATION", + .cmd = DTV_ISDB_LAYERA_MODULATION, + .set = 0, + }, + [DTV_ISDB_LAYERA_SEGMENT_WIDTH] = { + .name = "DTV_ISDB_LAYERA_SEGMENT_WIDTH", + .cmd = DTV_ISDB_LAYERA_SEGMENT_WIDTH, + .set = 0, + }, + [DTV_ISDB_LAYERB_FEC] = { + .name = "DTV_ISDB_LAYERB_FEC", + .cmd = DTV_ISDB_LAYERB_FEC, + .set = 0, + }, + [DTV_ISDB_LAYERB_MODULATION] = { + .name = "DTV_ISDB_LAYERB_MODULATION", + .cmd = DTV_ISDB_LAYERB_MODULATION, + .set = 0, + }, + [DTV_ISDB_LAYERB_SEGMENT_WIDTH] = { + .name = "DTV_ISDB_LAYERB_SEGMENT_WIDTH", + .cmd = DTV_ISDB_LAYERB_SEGMENT_WIDTH, + .set = 0, + }, + [DTV_ISDB_LAYERC_FEC] = { + .name = "DTV_ISDB_LAYERC_FEC", + .cmd = DTV_ISDB_LAYERC_FEC, + .set = 0, + }, + [DTV_ISDB_LAYERC_MODULATION] = { + .name = "DTV_ISDB_LAYERC_MODULATION", + .cmd = DTV_ISDB_LAYERC_MODULATION, + .set = 0, + }, + [DTV_ISDB_LAYERC_SEGMENT_WIDTH] = { + .name = "DTV_ISDB_LAYERC_SEGMENT_WIDTH", + .cmd = DTV_ISDB_LAYERC_SEGMENT_WIDTH, + .set = 0, + }, +#endif +}; + +void dtv_property_dump(struct dtv_property *tvp) +{ + int i; + + printk("%s() tvp.cmd = 0x%08x (%s)\n" + ,__FUNCTION__ + ,tvp->cmd + ,dtv_cmds[ tvp->cmd ].name); + + if(dtv_cmds[ tvp->cmd ].buffer) { + + printk("%s() tvp.u.buffer.len = 0x%02x\n" + ,__FUNCTION__ + ,tvp->u.buffer.len); + + for(i = 0; i < tvp->u.buffer.len; i++) + printk("%s() tvp.u.buffer.data[0x%02x] = 0x%02x\n" + ,__FUNCTION__ + ,i + ,tvp->u.buffer.data[i]); + + } else + printk("%s() tvp.u.data = 0x%08x\n", __FUNCTION__, tvp->u.data); +} + +int is_legacy_delivery_system(fe_delivery_system_t s) +{ + if((s == SYS_UNDEFINED) || (s == SYS_DVBC_ANNEX_AC) || + (s == SYS_DVBC_ANNEX_B) || (s == SYS_DVBT) || (s == SYS_DVBS)) + return 1; + + return 0; +} + +/* Synchronise the legacy tuning parameters into the cache, so that demodulator + * drivers can use a single set_frontend tuning function, regardless of whether + * it's being used for the legacy or new API, reducing code and complexity. + */ +void dtv_property_cache_sync(struct dvb_frontend *fe, struct dvb_frontend_parameters *p) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + + printk("%s()\n", __FUNCTION__); + + c->frequency = p->frequency; + c->inversion = p->inversion; + + switch (fe->ops.info.type) { + case FE_QPSK: + c->symbol_rate = p->u.qpsk.symbol_rate; + c->fec_inner = p->u.qpsk.fec_inner; + c->delivery_system = SYS_DVBS; + break; + case FE_QAM: + c->symbol_rate = p->u.qam.symbol_rate; + c->fec_inner = p->u.qam.fec_inner; + c->modulation = p->u.qam.modulation; + c->delivery_system = SYS_DVBC_ANNEX_AC; + break; + case FE_OFDM: + if (p->u.ofdm.bandwidth == BANDWIDTH_6_MHZ) + c->bandwidth_hz = 6000000; + else if (p->u.ofdm.bandwidth == BANDWIDTH_7_MHZ) + c->bandwidth_hz = 7000000; + else if (p->u.ofdm.bandwidth == BANDWIDTH_8_MHZ) + c->bandwidth_hz = 8000000; + else + /* Including BANDWIDTH_AUTO */ + c->bandwidth_hz = 0; + c->code_rate_HP = p->u.ofdm.code_rate_HP; + c->code_rate_LP = p->u.ofdm.code_rate_LP; + c->modulation = p->u.ofdm.constellation; + c->transmission_mode = p->u.ofdm.transmission_mode; + c->guard_interval = p->u.ofdm.guard_interval; + c->hierarchy = p->u.ofdm.hierarchy_information; + c->delivery_system = SYS_DVBT; + break; + case FE_ATSC: + c->modulation = p->u.vsb.modulation; + if ((c->modulation == VSB_8) || (c->modulation == VSB_16)) + c->delivery_system = SYS_ATSC; + else + c->delivery_system = SYS_DVBC_ANNEX_B; + break; + } +} + +/* Ensure the cached values are set correctly in the frontend + * legacy tuning structures, for the advanced tuning API. + */ +void dtv_property_legacy_params_sync(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dvb_frontend_parameters *p = &fepriv->parameters; + + printk("%s()\n", __FUNCTION__); + + p->frequency = c->frequency; + p->inversion = c->inversion; + + switch (fe->ops.info.type) { + case FE_QPSK: + printk("%s() Preparing QPSK req\n", __FUNCTION__); + p->u.qpsk.symbol_rate = c->symbol_rate; + p->u.qpsk.fec_inner = c->fec_inner; + c->delivery_system = SYS_DVBS; + break; + case FE_QAM: + printk("%s() Preparing QAM req\n", __FUNCTION__); + p->u.qam.symbol_rate = c->symbol_rate; + p->u.qam.fec_inner = c->fec_inner; + p->u.qam.modulation = c->modulation; + c->delivery_system = SYS_DVBC_ANNEX_AC; + break; + case FE_OFDM: + printk("%s() Preparing OFDM req\n", __FUNCTION__); + if (c->bandwidth_hz == 6000000) + p->u.ofdm.bandwidth = BANDWIDTH_6_MHZ; + else if (c->bandwidth_hz == 7000000) + p->u.ofdm.bandwidth = BANDWIDTH_7_MHZ; + else if (c->bandwidth_hz == 8000000) + p->u.ofdm.bandwidth = BANDWIDTH_8_MHZ; + else + p->u.ofdm.bandwidth = BANDWIDTH_AUTO; + p->u.ofdm.code_rate_HP = c->code_rate_HP; + p->u.ofdm.code_rate_LP = c->code_rate_LP; + p->u.ofdm.constellation = c->modulation; + p->u.ofdm.transmission_mode = c->transmission_mode; + p->u.ofdm.guard_interval = c->guard_interval; + p->u.ofdm.hierarchy_information = c->hierarchy; + c->delivery_system = SYS_DVBT; + break; + case FE_ATSC: + printk("%s() Preparing VSB req\n", __FUNCTION__); + p->u.vsb.modulation = c->modulation; + if ((c->modulation == VSB_8) || (c->modulation == VSB_16)) + c->delivery_system = SYS_ATSC; + else + c->delivery_system = SYS_DVBC_ANNEX_B; + break; + } +} + +/* Ensure the cached values are set correctly in the frontend + * legacy tuning structures, for the legacy tuning API. + */ +void dtv_property_adv_params_sync(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + struct dvb_frontend_parameters *p = &fepriv->parameters; + + printk("%s()\n", __FUNCTION__); + + p->frequency = c->frequency; + p->inversion = c->inversion; + + switch(c->modulation) { + case _8PSK: + case _16APSK: + case NBC_QPSK: + p->u.qpsk.symbol_rate = c->symbol_rate; + p->u.qpsk.fec_inner = c->fec_inner; + break; + default: + break; + } + + if(c->delivery_system == SYS_ISDBT) { + /* Fake out a generic DVB-T request so we pass validation in the ioctl */ + p->frequency = c->frequency; + p->inversion = INVERSION_AUTO; + p->u.ofdm.constellation = QAM_AUTO; + p->u.ofdm.code_rate_HP = FEC_AUTO; + p->u.ofdm.code_rate_LP = FEC_AUTO; + p->u.ofdm.bandwidth = BANDWIDTH_AUTO; + p->u.ofdm.transmission_mode = TRANSMISSION_MODE_AUTO; + p->u.ofdm.guard_interval = GUARD_INTERVAL_AUTO; + p->u.ofdm.hierarchy_information = HIERARCHY_AUTO; + } +} + +void dtv_property_cache_submit(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + + printk("%s()\n", __FUNCTION__); + + /* For legacy delivery systems we don't need the delivery_system to + * be specified, but we populate the older structures from the cache + * so we can call set_frontend on older drivers. + */ + if(is_legacy_delivery_system(c->delivery_system)) { + + printk("%s() legacy, modulation = %d\n", __FUNCTION__, c->modulation); + dtv_property_legacy_params_sync(fe); + + } else { + printk("%s() adv, modulation = %d\n", __FUNCTION__, c->modulation); + + /* For advanced delivery systems / modulation types ... + * we seed the lecacy dvb_frontend_parameters structure + * so that the sanity checking code later in the IOCTL processing + * can validate our basic frequency ranges, symbolrates, modulation + * etc. + */ + dtv_property_adv_params_sync(fe); + } +} + +static int dvb_frontend_ioctl_legacy(struct inode *inode, struct file *file, + unsigned int cmd, void *parg); +static int dvb_frontend_ioctl_properties(struct inode *inode, struct file *file, + unsigned int cmd, void *parg); + +int dtv_property_process_get(struct dvb_frontend *fe, struct dtv_property *tvp, + struct inode *inode, struct file *file) +{ + int r = 0; + + printk("%s()\n", __FUNCTION__); + + dtv_property_dump(tvp); + + /* Allow the frontend to validate incoming properties */ + if (fe->ops.get_property) + r = fe->ops.get_property(fe, tvp); + + if (r < 0) + return r; + + switch(tvp->cmd) { + case DTV_FREQUENCY: + tvp->u.data = fe->dtv_property_cache.frequency; + break; + case DTV_MODULATION: + tvp->u.data = fe->dtv_property_cache.modulation; + break; + case DTV_BANDWIDTH_HZ: + tvp->u.data = fe->dtv_property_cache.bandwidth_hz; + break; + case DTV_INVERSION: + tvp->u.data = fe->dtv_property_cache.inversion; + break; + case DTV_SYMBOL_RATE: + tvp->u.data = fe->dtv_property_cache.symbol_rate; + break; + case DTV_INNER_FEC: + tvp->u.data = fe->dtv_property_cache.fec_inner; + break; + case DTV_PILOT: + tvp->u.data = fe->dtv_property_cache.pilot; + break; + case DTV_ROLLOFF: + tvp->u.data = fe->dtv_property_cache.rolloff; + break; + case DTV_DELIVERY_SYSTEM: + tvp->u.data = fe->dtv_property_cache.delivery_system; + break; +#if 0 + /* ISDB-T Support here */ + case DTV_ISDB_SEGMENT_IDX: + tvp->u.data = fe->dtv_property_cache.isdb_segment_idx; + break; + case DTV_ISDB_SEGMENT_WIDTH: + tvp->u.data = fe->dtv_property_cache.isdb_segment_width; + break; + case DTV_ISDB_LAYERA_FEC: + tvp->u.data = fe->dtv_property_cache.isdb_layera_fec; + break; + case DTV_ISDB_LAYERA_MODULATION: + tvp->u.data = fe->dtv_property_cache.isdb_layera_modulation; + break; + case DTV_ISDB_LAYERA_SEGMENT_WIDTH: + tvp->u.data = fe->dtv_property_cache.isdb_layera_segment_width; + break; + case DTV_ISDB_LAYERB_FEC: + tvp->u.data = fe->dtv_property_cache.isdb_layerb_fec; + break; + case DTV_ISDB_LAYERB_MODULATION: + tvp->u.data = fe->dtv_property_cache.isdb_layerb_modulation; + break; + case DTV_ISDB_LAYERB_SEGMENT_WIDTH: + tvp->u.data = fe->dtv_property_cache.isdb_layerb_segment_width; + break; + case DTV_ISDB_LAYERC_FEC: + tvp->u.data = fe->dtv_property_cache.isdb_layerc_fec; + break; + case DTV_ISDB_LAYERC_MODULATION: + tvp->u.data = fe->dtv_property_cache.isdb_layerc_modulation; + break; + case DTV_ISDB_LAYERC_SEGMENT_WIDTH: + tvp->u.data = fe->dtv_property_cache.isdb_layerc_segment_width; + break; +#endif + case DTV_VOLTAGE: + tvp->u.data = fe->dtv_property_cache.voltage; + break; + case DTV_TONE: + tvp->u.data = fe->dtv_property_cache.sectone; + break; + default: + r = -1; + } + + return r; +} + +int dtv_property_process_set(struct dvb_frontend *fe, struct dtv_property *tvp, + struct inode *inode, struct file *file) +{ + int r = 0; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + printk("%s()\n", __FUNCTION__); + dtv_property_dump(tvp); + + /* Allow the frontend to validate incoming properties */ + if (fe->ops.set_property) + r = fe->ops.set_property(fe, tvp); + + if (r < 0) + return r; + + switch(tvp->cmd) { + case DTV_CLEAR: + /* Reset a cache of data specific to the frontend here. This does + * not effect hardware. + */ + printk("%s() Flushing property cache\n", __FUNCTION__); + memset(&fe->dtv_property_cache, 0, sizeof(struct dtv_frontend_properties)); + fe->dtv_property_cache.state = tvp->cmd; + fe->dtv_property_cache.delivery_system = SYS_UNDEFINED; + break; + case DTV_TUNE: + /* interpret the cache of data, build either a traditional frontend + * tunerequest so we can pass validation in the FE_SET_FRONTEND + * ioctl. + */ + fe->dtv_property_cache.state = tvp->cmd; + printk("%s() Finalised property cache\n", __FUNCTION__); + dtv_property_cache_submit(fe); + + r |= dvb_frontend_ioctl_legacy(inode, file, FE_SET_FRONTEND, + &fepriv->parameters); + break; + case DTV_FREQUENCY: + fe->dtv_property_cache.frequency = tvp->u.data; + break; + case DTV_MODULATION: + fe->dtv_property_cache.modulation = tvp->u.data; + break; + case DTV_BANDWIDTH_HZ: + fe->dtv_property_cache.bandwidth_hz = tvp->u.data; + break; + case DTV_INVERSION: + fe->dtv_property_cache.inversion = tvp->u.data; + break; + case DTV_SYMBOL_RATE: + fe->dtv_property_cache.symbol_rate = tvp->u.data; + break; + case DTV_INNER_FEC: + fe->dtv_property_cache.fec_inner = tvp->u.data; + break; + case DTV_PILOT: + fe->dtv_property_cache.pilot = tvp->u.data; + break; + case DTV_ROLLOFF: + fe->dtv_property_cache.rolloff = tvp->u.data; + break; + case DTV_DELIVERY_SYSTEM: + fe->dtv_property_cache.delivery_system = tvp->u.data; + break; +#if 0 + /* ISDB-T Support here */ + case DTV_ISDB_SEGMENT_IDX: + fe->dtv_property_cache.isdb_segment_idx = tvp->u.data; + break; + case DTV_ISDB_SEGMENT_WIDTH: + fe->dtv_property_cache.isdb_segment_width = tvp->u.data; + break; +#endif + case DTV_VOLTAGE: + fe->dtv_property_cache.voltage = tvp->u.data; + r = dvb_frontend_ioctl_legacy(inode, file, FE_SET_VOLTAGE, + (void *)fe->dtv_property_cache.voltage); + break; + case DTV_TONE: + fe->dtv_property_cache.sectone = tvp->u.data; + r = dvb_frontend_ioctl_legacy(inode, file, FE_SET_TONE, + (void *)fe->dtv_property_cache.sectone); + break; + default: + r = -1; + } + + return r; +} + static int dvb_frontend_ioctl(struct inode *inode, struct file *file, unsigned int cmd, void *parg) { @@ -787,6 +1328,112 @@ static int dvb_frontend_ioctl(struct inode *inode, struct file *file, if (down_interruptible (&fepriv->sem)) return -ERESTARTSYS; + if ((cmd == FE_SET_PROPERTY) || (cmd == FE_GET_PROPERTY)) + err = dvb_frontend_ioctl_properties(inode, file, cmd, parg); + else { + fe->dtv_property_cache.state = DTV_UNDEFINED; + err = dvb_frontend_ioctl_legacy(inode, file, cmd, parg); + } + + up(&fepriv->sem); + return err; +} + +static int dvb_frontend_ioctl_properties(struct inode *inode, struct file *file, + unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_frontend *fe = dvbdev->priv; + int err = 0; + + struct dtv_properties *tvps = NULL; + struct dtv_property *tvp = NULL; + int i; + + dprintk("%s\n", __func__); + + if(cmd == FE_SET_PROPERTY) { + printk("%s() FE_SET_PROPERTY\n", __FUNCTION__); + + tvps = (struct dtv_properties __user *)parg; + + printk("%s() properties.num = %d\n", __FUNCTION__, tvps->num); + printk("%s() properties.props = %p\n", __FUNCTION__, tvps->props); + + /* Put an arbitrary limit on the number of messages that can + * be sent at once */ + if (tvps->num > DTV_IOCTL_MAX_MSGS) + return -EINVAL; + + tvp = (struct dtv_property *) kmalloc(tvps->num * + sizeof(struct dtv_property), GFP_KERNEL); + if (!tvp) { + err = -ENOMEM; + goto out; + } + + if (copy_from_user(tvp, tvps->props, tvps->num * sizeof(struct dtv_property))) { + err = -EFAULT; + goto out; + } + + for (i = 0; i < tvps->num; i++) + err |= dtv_property_process_set(fe, tvp + i, inode, file); + + if(fe->dtv_property_cache.state == DTV_TUNE) { + printk("%s() Property cache is full, tuning\n", __FUNCTION__); + } + + } else + if(cmd == FE_GET_PROPERTY) { + printk("%s() FE_GET_PROPERTY\n", __FUNCTION__); + + tvps = (struct dtv_properties __user *)parg; + + printk("%s() properties.num = %d\n", __FUNCTION__, tvps->num); + printk("%s() properties.props = %p\n", __FUNCTION__, tvps->props); + + /* Put an arbitrary limit on the number of messages that can + * be sent at once */ + if (tvps->num > DTV_IOCTL_MAX_MSGS) + return -EINVAL; + + tvp = (struct dtv_property *) kmalloc(tvps->num * + sizeof(struct dtv_property), GFP_KERNEL); + if (!tvp) { + err = -ENOMEM; + goto out; + } + + if (copy_from_user(tvp, tvps->props, tvps->num * sizeof(struct dtv_property))) { + err = -EFAULT; + goto out; + } + + for (i = 0; i < tvps->num; i++) + err |= dtv_property_process_get(fe, tvp + i, inode, file); + + if (copy_to_user(tvps->props, tvp, tvps->num * sizeof(struct dtv_property))) { + err = -EFAULT; + goto out; + } + + } else + err = -EOPNOTSUPP; + +out: + kfree(tvp); + return err; +} + +static int dvb_frontend_ioctl_legacy(struct inode *inode, struct file *file, + unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_frontend *fe = dvbdev->priv; + struct dvb_frontend_private *fepriv = fe->frontend_priv; + int err = -EOPNOTSUPP; + switch (cmd) { case FE_GET_INFO: { struct dvb_frontend_info* info = parg; @@ -953,13 +1600,21 @@ static int dvb_frontend_ioctl(struct inode *inode, struct file *file, case FE_SET_FRONTEND: { struct dvb_frontend_tune_settings fetunesettings; - if (dvb_frontend_check_parameters(fe, parg) < 0) { - err = -EINVAL; - break; - } + if(fe->dtv_property_cache.state == DTV_TUNE) { + if (dvb_frontend_check_parameters(fe, &fepriv->parameters) < 0) { + err = -EINVAL; + break; + } + } else { + if (dvb_frontend_check_parameters(fe, parg) < 0) { + err = -EINVAL; + break; + } - memcpy (&fepriv->parameters, parg, - sizeof (struct dvb_frontend_parameters)); + memcpy (&fepriv->parameters, parg, + sizeof (struct dvb_frontend_parameters)); + dtv_property_cache_sync(fe, &fepriv->parameters); + } memset(&fetunesettings, 0, sizeof(struct dvb_frontend_tune_settings)); memcpy(&fetunesettings.parameters, parg, @@ -1038,10 +1693,10 @@ static int dvb_frontend_ioctl(struct inode *inode, struct file *file, break; }; - up (&fepriv->sem); return err; } + static unsigned int dvb_frontend_poll(struct file *file, struct poll_table_struct *wait) { struct dvb_device *dvbdev = file->private_data; diff --git a/linux/drivers/media/dvb/dvb-core/dvb_frontend.h b/linux/drivers/media/dvb/dvb-core/dvb_frontend.h index aa4133f0b..1207f29f2 100644 --- a/linux/drivers/media/dvb/dvb-core/dvb_frontend.h +++ b/linux/drivers/media/dvb/dvb-core/dvb_frontend.h @@ -169,6 +169,9 @@ struct dvb_frontend_ops { struct dvb_tuner_ops tuner_ops; struct analog_demod_ops analog_ops; + + int (*set_property)(struct dvb_frontend* fe, struct dtv_property* tvp); + int (*get_property)(struct dvb_frontend* fe, struct dtv_property* tvp); }; #define MAX_EVENT 8 @@ -182,6 +185,46 @@ struct dvb_fe_events { struct mutex mtx; }; +struct dtv_frontend_properties { + + /* Cache State */ + u32 state; + + u32 frequency; + fe_modulation_t modulation; + + fe_sec_voltage_t voltage; + fe_sec_tone_mode_t sectone; + fe_spectral_inversion_t inversion; + fe_code_rate_t fec_inner; + fe_transmit_mode_t transmission_mode; + u32 bandwidth_hz; /* 0 = AUTO */ + fe_guard_interval_t guard_interval; + fe_hierarchy_t hierarchy; + u32 symbol_rate; + fe_code_rate_t code_rate_HP; + fe_code_rate_t code_rate_LP; + + fe_pilot_t pilot; + fe_rolloff_t rolloff; + + fe_delivery_system_t delivery_system; +#if 0 + /* ISDB-T specifics */ + u32 isdb_segment_idx; + u32 isdb_segment_width; + fe_code_rate_t isdb_layera_fec; + fe_modulation_t isdb_layera_modulation; + u32 isdb_layera_segment_width; + fe_code_rate_t isdb_layerb_fec; + fe_modulation_t isdb_layerb_modulation; + u32 isdb_layerb_segment_width; + fe_code_rate_t isdb_layerc_fec; + fe_modulation_t isdb_layerc_modulation; + u32 isdb_layerc_segment_width; +#endif +}; + struct dvb_frontend { struct dvb_frontend_ops ops; struct dvb_adapter *dvb; @@ -190,6 +233,7 @@ struct dvb_frontend { void *frontend_priv; void *sec_priv; void *analog_demod_priv; + struct dtv_frontend_properties dtv_property_cache; }; extern int dvb_register_frontend(struct dvb_adapter *dvb, diff --git a/linux/drivers/media/dvb/dvb-usb/Kconfig b/linux/drivers/media/dvb/dvb-usb/Kconfig index b35d7f0db..aee3b5dd6 100644 --- a/linux/drivers/media/dvb/dvb-usb/Kconfig +++ b/linux/drivers/media/dvb/dvb-usb/Kconfig @@ -247,12 +247,14 @@ config DVB_USB_AF9005_REMOTE Afatech AF9005 based receiver. config DVB_USB_DW2102 - tristate "DvbWorld 2102 DVB-S USB2.0 receiver" + tristate "DvbWorld DVB-S/S2 USB2.0 support" depends on DVB_USB - select DVB_STV0299 if !DVB_FE_CUSTOMISE select DVB_PLL if !DVB_FE_CUSTOMISE + select DVB_STV0299 if !DVB_FE_CUSTOMISE + select DVB_CX24116 if !DVB_FE_CUSTOMISE help - Say Y here to support the DvbWorld 2102 DVB-S USB2.0 receiver. + Say Y here to support the DvbWorld DVB-S/S2 USB2.0 receivers + and the TeVii S650. config DVB_USB_ANYSEE tristate "Anysee DVB-T/C USB2.0 support" diff --git a/linux/drivers/media/dvb/dvb-usb/dw2102.c b/linux/drivers/media/dvb/dvb-usb/dw2102.c index a4d898b44..b5f81f761 100644 --- a/linux/drivers/media/dvb/dvb-usb/dw2102.c +++ b/linux/drivers/media/dvb/dvb-usb/dw2102.c @@ -1,4 +1,5 @@ -/* DVB USB framework compliant Linux driver for the DVBWorld DVB-S 2102 Card +/* DVB USB framework compliant Linux driver for the +* DVBWorld DVB-S 2101, 2102, DVB-S2 2104 Card * * Copyright (C) 2008 Igor M. Liplianin (liplianin@me.by) * @@ -10,62 +11,71 @@ */ #include <linux/version.h> #include "dw2102.h" +#include "si21xx.h" #include "stv0299.h" #include "z0194a.h" +#include "cx24116.h" #ifndef USB_PID_DW2102 #define USB_PID_DW2102 0x2102 #endif -#define DW2102_READ_MSG 0 -#define DW2102_WRITE_MSG 1 +#ifndef USB_PID_DW2104 +#define USB_PID_DW2104 0x2104 +#endif + +#define DW210X_READ_MSG 0 +#define DW210X_WRITE_MSG 1 #define REG_1F_SYMBOLRATE_BYTE0 0x1f #define REG_20_SYMBOLRATE_BYTE1 0x20 #define REG_21_SYMBOLRATE_BYTE2 0x21 - +/* on my own*/ #define DW2102_VOLTAGE_CTRL (0x1800) #define DW2102_RC_QUERY (0x1a00) -struct dw2102_state { +struct dw210x_state { u32 last_key_pressed; }; -struct dw2102_rc_keys { +struct dw210x_rc_keys { u32 keycode; u32 event; }; +/* debug */ +static int dvb_usb_dw2102_debug; +module_param_named(debug, dvb_usb_dw2102_debug, int, 0644); +MODULE_PARM_DESC(debug, "set debugging level (1=info 2=xfer (or-able))." DVB_USB_DEBUG_STATUS); + DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); -static int dw2102_op_rw(struct usb_device *dev, u8 request, u16 value, - u8 *data, u16 len, int flags) +static int dw210x_op_rw(struct usb_device *dev, u8 request, u16 value, + u16 index, u8 * data, u16 len, int flags) { int ret; u8 u8buf[len]; - unsigned int pipe = (flags == DW2102_READ_MSG) ? - usb_rcvctrlpipe(dev, 0) : usb_sndctrlpipe(dev, 0); - u8 request_type = (flags == DW2102_READ_MSG) ? USB_DIR_IN : USB_DIR_OUT; + unsigned int pipe = (flags == DW210X_READ_MSG) ? + usb_rcvctrlpipe(dev, 0) : usb_sndctrlpipe(dev, 0); + u8 request_type = (flags == DW210X_READ_MSG) ? USB_DIR_IN : USB_DIR_OUT; - if (flags == DW2102_WRITE_MSG) + if (flags == DW210X_WRITE_MSG) memcpy(u8buf, data, len); - ret = usb_control_msg(dev, pipe, request, - request_type | USB_TYPE_VENDOR, value, 0 , u8buf, len, 2000); + ret = usb_control_msg(dev, pipe, request, request_type | USB_TYPE_VENDOR, + value, index , u8buf, len, 2000); - if (flags == DW2102_READ_MSG) + if (flags == DW210X_READ_MSG) memcpy(data, u8buf, len); return ret; } /* I2C */ - static int dw2102_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], int num) { struct dvb_usb_device *d = i2c_get_adapdata(adap); int i = 0, ret = 0; u8 buf6[] = {0x2c, 0x05, 0xc0, 0, 0, 0, 0}; - u8 request; u16 value; if (!d) @@ -76,14 +86,12 @@ struct dvb_usb_device *d = i2c_get_adapdata(adap); switch (num) { case 2: /* read stv0299 register */ - request = 0xb5; value = msg[0].buf[0];/* register */ for (i = 0; i < msg[1].len; i++) { value = value + i; - ret = dw2102_op_rw(d->udev, 0xb5, - value, buf6, 2, DW2102_READ_MSG); + ret = dw210x_op_rw(d->udev, 0xb5, value, 0, + buf6, 2, DW210X_READ_MSG); msg[1].buf[i] = buf6[0]; - } break; case 1: @@ -93,8 +101,8 @@ struct dvb_usb_device *d = i2c_get_adapdata(adap); buf6[0] = 0x2a; buf6[1] = msg[0].buf[0]; buf6[2] = msg[0].buf[1]; - ret = dw2102_op_rw(d->udev, 0xb2, - 0, buf6, 3, DW2102_WRITE_MSG); + ret = dw210x_op_rw(d->udev, 0xb2, 0, 0, + buf6, 3, DW210X_WRITE_MSG); break; case 0x60: if (msg[0].flags == 0) { @@ -106,26 +114,26 @@ struct dvb_usb_device *d = i2c_get_adapdata(adap); buf6[4] = msg[0].buf[1]; buf6[5] = msg[0].buf[2]; buf6[6] = msg[0].buf[3]; - ret = dw2102_op_rw(d->udev, 0xb2, - 0, buf6, 7, DW2102_WRITE_MSG); + ret = dw210x_op_rw(d->udev, 0xb2, 0, 0, + buf6, 7, DW210X_WRITE_MSG); } else { - /* write to tuner pll */ - ret = dw2102_op_rw(d->udev, 0xb5, - 0, buf6, 1, DW2102_READ_MSG); + /* read from tuner */ + ret = dw210x_op_rw(d->udev, 0xb5, 0, 0, + buf6, 1, DW210X_READ_MSG); msg[0].buf[0] = buf6[0]; } break; case (DW2102_RC_QUERY): - ret = dw2102_op_rw(d->udev, 0xb8, - 0, buf6, 2, DW2102_READ_MSG); + ret = dw210x_op_rw(d->udev, 0xb8, 0, 0, + buf6, 2, DW210X_READ_MSG); msg[0].buf[0] = buf6[0]; msg[0].buf[1] = buf6[1]; break; case (DW2102_VOLTAGE_CTRL): buf6[0] = 0x30; buf6[1] = msg[0].buf[0]; - ret = dw2102_op_rw(d->udev, 0xb2, - 0, buf6, 2, DW2102_WRITE_MSG); + ret = dw210x_op_rw(d->udev, 0xb2, 0, 0, + buf6, 2, DW210X_WRITE_MSG); break; } @@ -136,17 +144,188 @@ struct dvb_usb_device *d = i2c_get_adapdata(adap); return num; } -static u32 dw2102_i2c_func(struct i2c_adapter *adapter) +static int dw2102_serit_i2c_transfer(struct i2c_adapter *adap, + struct i2c_msg msg[], int num) +{ + struct dvb_usb_device *d = i2c_get_adapdata(adap); + int ret = 0; + u8 buf6[] = {0, 0, 0, 0, 0, 0, 0}; + + if (!d) + return -ENODEV; + if (mutex_lock_interruptible(&d->i2c_mutex) < 0) + return -EAGAIN; + + switch (num) { + case 2: + /* read si2109 register by number */ + buf6[0] = 0xd0; + buf6[1] = msg[0].len; + buf6[2] = msg[0].buf[0]; + ret = dw210x_op_rw(d->udev, 0xc2, 0, 0, + buf6, msg[0].len + 2, DW210X_WRITE_MSG); + /* read si2109 register */ + ret = dw210x_op_rw(d->udev, 0xc3, 0xd0, 0, + buf6, msg[1].len + 2, DW210X_READ_MSG); + memcpy(msg[1].buf, buf6 + 2, msg[1].len); + + break; + case 1: + switch (msg[0].addr) { + case 0x68: + /* write to si2109 register */ + buf6[0] = 0xd0; + buf6[1] = msg[0].len; + memcpy(buf6 + 2, msg[0].buf, msg[0].len); + ret = dw210x_op_rw(d->udev, 0xc2, 0, 0, buf6, + msg[0].len + 2, DW210X_WRITE_MSG); + break; + case(DW2102_RC_QUERY): + ret = dw210x_op_rw(d->udev, 0xb8, 0, 0, + buf6, 2, DW210X_READ_MSG); + msg[0].buf[0] = buf6[0]; + msg[0].buf[1] = buf6[1]; + break; + case(DW2102_VOLTAGE_CTRL): + buf6[0] = 0x30; + buf6[1] = msg[0].buf[0]; + ret = dw210x_op_rw(d->udev, 0xb2, 0, 0, + buf6, 2, DW210X_WRITE_MSG); + break; + } + break; + } + + mutex_unlock(&d->i2c_mutex); + return num; +} + +static int dw2104_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], int num) +{ + struct dvb_usb_device *d = i2c_get_adapdata(adap); + int ret = 0; + int len, i; + + if (!d) + return -ENODEV; + if (mutex_lock_interruptible(&d->i2c_mutex) < 0) + return -EAGAIN; + + switch (num) { + case 2: { + /* read */ + /* first write first register number */ + u8 ibuf [msg[1].len + 2], obuf[3]; + obuf[0] = 0xaa; + obuf[1] = msg[0].len; + obuf[2] = msg[0].buf[0]; + ret = dw210x_op_rw(d->udev, 0xc2, 0, 0, + obuf, msg[0].len + 2, DW210X_WRITE_MSG); + /* second read registers */ + ret = dw210x_op_rw(d->udev, 0xc3, 0xab , 0, + ibuf, msg[1].len + 2, DW210X_READ_MSG); + memcpy(msg[1].buf, ibuf + 2, msg[1].len); + + break; + } + case 1: + switch (msg[0].addr) { + case 0x55: { + if (msg[0].buf[0] == 0xf7) { + /* firmware */ + /* Write in small blocks */ + u8 obuf[19]; + obuf[0] = 0xaa; + obuf[1] = 0x11; + obuf[2] = 0xf7; + len = msg[0].len - 1; + i = 1; + do { + memcpy(obuf + 3, msg[0].buf + i, (len > 16 ? 16 : len)); + ret = dw210x_op_rw(d->udev, 0xc2, 0, 0, + obuf, (len > 16 ? 16 : len) + 3, DW210X_WRITE_MSG); + i += 16; + len -= 16; + } while (len > 0); + } else { + /* write to register */ + u8 obuf[msg[0].len + 2]; + obuf[0] = 0xaa; + obuf[1] = msg[0].len; + memcpy(obuf + 2, msg[0].buf, msg[0].len); + ret = dw210x_op_rw(d->udev, 0xc2, 0, 0, + obuf, msg[0].len + 2, DW210X_WRITE_MSG); + } + break; + } + case(DW2102_RC_QUERY): { + u8 ibuf[2]; + ret = dw210x_op_rw(d->udev, 0xb8, 0, 0, + ibuf, 2, DW210X_READ_MSG); + memcpy(msg[0].buf, ibuf , 2); + break; + } + case(DW2102_VOLTAGE_CTRL): { + u8 obuf[2]; + obuf[0] = 0x30; + obuf[1] = msg[0].buf[0]; + ret = dw210x_op_rw(d->udev, 0xb2, 0, 0, + obuf, 2, DW210X_WRITE_MSG); + break; + } + } + + break; + } + + mutex_unlock(&d->i2c_mutex); + return num; +} + +static u32 dw210x_i2c_func(struct i2c_adapter *adapter) { return I2C_FUNC_I2C; } static struct i2c_algorithm dw2102_i2c_algo = { .master_xfer = dw2102_i2c_transfer, - .functionality = dw2102_i2c_func, + .functionality = dw210x_i2c_func, }; -static int dw2102_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage) +static struct i2c_algorithm dw2102_serit_i2c_algo = { + .master_xfer = dw2102_serit_i2c_transfer, + .functionality = dw210x_i2c_func, +}; + +static struct i2c_algorithm dw2104_i2c_algo = { + .master_xfer = dw2104_i2c_transfer, + .functionality = dw210x_i2c_func, +}; + +static int dw210x_read_mac_address(struct dvb_usb_device *d, u8 mac[6]) +{ + int i; + u8 ibuf[] = {0, 0}; + u8 eeprom[256], eepromline[16]; + + for (i = 0; i < 256; i++) { + if (dw210x_op_rw(d->udev, 0xb6, 0xa0 , i, ibuf, 2, DW210X_READ_MSG) < 0) { + err("read eeprom failed."); + return -1; + } else { + eepromline[i%16] = ibuf[0]; + eeprom[i] = ibuf[0]; + } + if ((i % 16) == 15) { + deb_xfer("%02x: ", i - 15); + debug_dump(eepromline, 16, deb_xfer); + } + } + memcpy(mac, eeprom + 8, 6); + return 0; +}; + +static int dw210x_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage) { static u8 command_13v[1] = {0x00}; static u8 command_18v[1] = {0x01}; @@ -163,18 +342,55 @@ static int dw2102_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage) return 0; } -static int dw2102_frontend_attach(struct dvb_usb_adapter *d) +static struct cx24116_config dw2104_config = { + .demod_address = 0x55, + .mpg_clk_pos_pol = 0x01, +}; + +static struct si21xx_config serit_sp1511lhb_config = { + .demod_address = 0x68, + .min_delay_ms = 100, + +}; + +static int dw2104_frontend_attach(struct dvb_usb_adapter *d) { - d->fe = dvb_attach(stv0299_attach, &sharp_z0194a_config, - &d->dev->i2c_adap); - if (d->fe != NULL) { - d->fe->ops.set_voltage = dw2102_set_voltage; - info("Attached stv0299!\n"); + if ((d->fe = dvb_attach(cx24116_attach, &dw2104_config, + &d->dev->i2c_adap)) != NULL) { + d->fe->ops.set_voltage = dw210x_set_voltage; + info("Attached cx24116!\n"); return 0; } return -EIO; } +static struct dvb_usb_device_properties dw2102_properties; + +static int dw2102_frontend_attach(struct dvb_usb_adapter *d) +{ + if (dw2102_properties.i2c_algo == &dw2102_serit_i2c_algo) { + /*dw2102_properties.adapter->tuner_attach = NULL;*/ + d->fe = dvb_attach(si21xx_attach, &serit_sp1511lhb_config, + &d->dev->i2c_adap); + if (d->fe != NULL) { + d->fe->ops.set_voltage = dw210x_set_voltage; + info("Attached si21xx!\n"); + return 0; + } + } + if (dw2102_properties.i2c_algo == &dw2102_i2c_algo) { + /*dw2102_properties.adapter->tuner_attach = dw2102_tuner_attach;*/ + d->fe = dvb_attach(stv0299_attach, &sharp_z0194a_config, + &d->dev->i2c_adap); + if (d->fe != NULL) { + d->fe->ops.set_voltage = dw210x_set_voltage; + info("Attached stv0299!\n"); + return 0; + } + } + return -EIO; +} + static int dw2102_tuner_attach(struct dvb_usb_adapter *adap) { dvb_attach(dvb_pll_attach, adap->fe, 0x60, @@ -182,7 +398,7 @@ static int dw2102_tuner_attach(struct dvb_usb_adapter *adap) return 0; } -static struct dvb_usb_rc_key dw2102_rc_keys[] = { +static struct dvb_usb_rc_key dw210x_rc_keys[] = { { 0xf8, 0x0a, KEY_Q }, /*power*/ { 0xf8, 0x0c, KEY_M }, /*mute*/ { 0xf8, 0x11, KEY_1 }, @@ -221,7 +437,7 @@ static struct dvb_usb_rc_key dw2102_rc_keys[] = { static int dw2102_rc_query(struct dvb_usb_device *d, u32 *event, int *state) { - struct dw2102_state *st = d->priv; + struct dw210x_state *st = d->priv; u8 key[2]; struct i2c_msg msg[] = { {.addr = DW2102_RC_QUERY, .flags = I2C_M_RD, .buf = key, @@ -231,12 +447,12 @@ static int dw2102_rc_query(struct dvb_usb_device *d, u32 *event, int *state) *state = REMOTE_NO_KEY_PRESSED; if (dw2102_i2c_transfer(&d->i2c_adap, msg, 1) == 1) { - for (i = 0; i < ARRAY_SIZE(dw2102_rc_keys); i++) { - if (dw2102_rc_keys[i].data == msg[0].buf[0]) { + for (i = 0; i < ARRAY_SIZE(dw210x_rc_keys); i++) { + if (dw210x_rc_keys[i].data == msg[0].buf[0]) { *state = REMOTE_KEY_PRESSED; - *event = dw2102_rc_keys[i].event; + *event = dw210x_rc_keys[i].event; st->last_key_pressed = - dw2102_rc_keys[i].event; + dw210x_rc_keys[i].event; break; } st->last_key_pressed = 0; @@ -249,6 +465,8 @@ static int dw2102_rc_query(struct dvb_usb_device *d, u32 *event, int *state) static struct usb_device_id dw2102_table[] = { {USB_DEVICE(USB_VID_CYPRESS, USB_PID_DW2102)}, {USB_DEVICE(USB_VID_CYPRESS, 0x2101)}, + {USB_DEVICE(USB_VID_CYPRESS, 0x2104)}, + {USB_DEVICE(0x9022, 0xd650)}, { } }; @@ -273,25 +491,23 @@ static int dw2102_load_firmware(struct usb_device *dev, return ret; } break; - case USB_PID_DW2102: + default: fw = frmwr; break; } - info("start downloading DW2102 firmware"); + info("start downloading DW210X firmware"); p = kmalloc(fw->size, GFP_KERNEL); reset = 1; /*stop the CPU*/ - dw2102_op_rw(dev, 0xa0, 0x7f92, &reset, 1, DW2102_WRITE_MSG); - dw2102_op_rw(dev, 0xa0, 0xe600, &reset, 1, DW2102_WRITE_MSG); + dw210x_op_rw(dev, 0xa0, 0x7f92, 0, &reset, 1, DW210X_WRITE_MSG); + dw210x_op_rw(dev, 0xa0, 0xe600, 0, &reset, 1, DW210X_WRITE_MSG); if (p != NULL) { memcpy(p, fw->data, fw->size); for (i = 0; i < fw->size; i += 0x40) { b = (u8 *) p + i; - if (dw2102_op_rw - (dev, 0xa0, i, b , 0x40, - DW2102_WRITE_MSG) != 0x40 - ) { + if (dw210x_op_rw(dev, 0xa0, i, 0, b , 0x40, + DW210X_WRITE_MSG) != 0x40) { err("error while transferring firmware"); ret = -EINVAL; break; @@ -299,43 +515,50 @@ static int dw2102_load_firmware(struct usb_device *dev, } /* restart the CPU */ reset = 0; - if (ret || dw2102_op_rw - (dev, 0xa0, 0x7f92, &reset, 1, - DW2102_WRITE_MSG) != 1) { + if (ret || dw210x_op_rw(dev, 0xa0, 0x7f92, 0, &reset, 1, + DW210X_WRITE_MSG) != 1) { err("could not restart the USB controller CPU."); ret = -EINVAL; } - if (ret || dw2102_op_rw - (dev, 0xa0, 0xe600, &reset, 1, - DW2102_WRITE_MSG) != 1) { + if (ret || dw210x_op_rw(dev, 0xa0, 0xe600, 0, &reset, 1, + DW210X_WRITE_MSG) != 1) { err("could not restart the USB controller CPU."); ret = -EINVAL; } /* init registers */ switch (dev->descriptor.idProduct) { + case USB_PID_DW2104: + case 0xd650: + reset = 1; + dw210x_op_rw(dev, 0xc4, 0x0000, 0, &reset, 1, + DW210X_WRITE_MSG); + reset = 0; + dw210x_op_rw(dev, 0xbf, 0x0040, 0, &reset, 0, + DW210X_WRITE_MSG); + break; case USB_PID_DW2102: - dw2102_op_rw - (dev, 0xbf, 0x0040, &reset, 0, - DW2102_WRITE_MSG); - dw2102_op_rw - (dev, 0xb9, 0x0000, &reset16[0], 2, - DW2102_READ_MSG); + dw210x_op_rw(dev, 0xbf, 0x0040, 0, &reset, 0, + DW210X_WRITE_MSG); + dw210x_op_rw(dev, 0xb9, 0x0000, 0, &reset16[0], 2, + DW210X_READ_MSG); + /* check STV0299 frontend */ + dw210x_op_rw(dev, 0xb5, 0, 0, &reset16[0], 2, + DW210X_READ_MSG); + if (reset16[0] == 0xa1) + dw2102_properties.i2c_algo = &dw2102_i2c_algo; break; case 0x2101: - dw2102_op_rw - (dev, 0xbc, 0x0030, &reset16[0], 2, - DW2102_READ_MSG); - dw2102_op_rw - (dev, 0xba, 0x0000, &reset16[0], 7, - DW2102_READ_MSG); - dw2102_op_rw - (dev, 0xba, 0x0000, &reset16[0], 7, - DW2102_READ_MSG); - dw2102_op_rw - (dev, 0xb9, 0x0000, &reset16[0], 2, - DW2102_READ_MSG); + dw210x_op_rw(dev, 0xbc, 0x0030, 0, &reset16[0], 2, + DW210X_READ_MSG); + dw210x_op_rw(dev, 0xba, 0x0000, 0, &reset16[0], 7, + DW210X_READ_MSG); + dw210x_op_rw(dev, 0xba, 0x0000, 0, &reset16[0], 7, + DW210X_READ_MSG); + dw210x_op_rw(dev, 0xb9, 0x0000, 0, &reset16[0], 2, + DW210X_READ_MSG); break; } + msleep(100); kfree(p); } return ret; @@ -345,12 +568,12 @@ static struct dvb_usb_device_properties dw2102_properties = { .caps = DVB_USB_IS_AN_I2C_ADAPTER, .usb_ctrl = DEVICE_SPECIFIC, .firmware = "dvb-usb-dw2102.fw", - .size_of_priv = sizeof(struct dw2102_state), + .size_of_priv = sizeof(struct dw210x_state), .no_reconnect = 1, - .i2c_algo = &dw2102_i2c_algo, - .rc_key_map = dw2102_rc_keys, - .rc_key_map_size = ARRAY_SIZE(dw2102_rc_keys), + .i2c_algo = &dw2102_serit_i2c_algo, + .rc_key_map = dw210x_rc_keys, + .rc_key_map_size = ARRAY_SIZE(dw210x_rc_keys), .rc_interval = 150, .rc_query = dw2102_rc_query, @@ -358,7 +581,8 @@ static struct dvb_usb_device_properties dw2102_properties = { /* parameter for the MPEG2-data transfer */ .num_adapters = 1, .download_firmware = dw2102_load_firmware, - .adapter = { + .read_mac_address = dw210x_read_mac_address, + .adapter = { { .frontend_attach = dw2102_frontend_attach, .streaming_ctrl = NULL, @@ -388,11 +612,64 @@ static struct dvb_usb_device_properties dw2102_properties = { } }; +static struct dvb_usb_device_properties dw2104_properties = { + .caps = DVB_USB_IS_AN_I2C_ADAPTER, + .usb_ctrl = DEVICE_SPECIFIC, + .firmware = "dvb-usb-dw2104.fw", + .size_of_priv = sizeof(struct dw210x_state), + .no_reconnect = 1, + + .i2c_algo = &dw2104_i2c_algo, + .rc_key_map = dw210x_rc_keys, + .rc_key_map_size = ARRAY_SIZE(dw210x_rc_keys), + .rc_interval = 150, + .rc_query = dw2102_rc_query, + + .generic_bulk_ctrl_endpoint = 0x81, + /* parameter for the MPEG2-data transfer */ + .num_adapters = 1, + .download_firmware = dw2102_load_firmware, + .read_mac_address = dw210x_read_mac_address, + .adapter = { + { + .frontend_attach = dw2104_frontend_attach, + .streaming_ctrl = NULL, + /*.tuner_attach = dw2104_tuner_attach,*/ + .stream = { + .type = USB_BULK, + .count = 8, + .endpoint = 0x82, + .u = { + .bulk = { + .buffersize = 4096, + } + } + }, + } + }, + .num_device_descs = 2, + .devices = { + { "DVBWorld DW2104 USB2.0", + {&dw2102_table[2], NULL}, + {NULL}, + }, + { "TeVii S650 USB2.0", + {&dw2102_table[3], NULL}, + {NULL}, + }, + } +}; + static int dw2102_probe(struct usb_interface *intf, const struct usb_device_id *id) { - return dvb_usb_device_init(intf, &dw2102_properties, - THIS_MODULE, NULL, adapter_nr); + if (0 == dvb_usb_device_init(intf, &dw2102_properties, + THIS_MODULE, NULL, adapter_nr) || + 0 == dvb_usb_device_init(intf, &dw2104_properties, + THIS_MODULE, NULL, adapter_nr)) { + return 0; + } + return -ENODEV; } static struct usb_driver dw2102_driver = { @@ -420,6 +697,6 @@ module_init(dw2102_module_init); module_exit(dw2102_module_exit); MODULE_AUTHOR("Igor M. Liplianin (c) liplianin@me.by"); -MODULE_DESCRIPTION("Driver for DVBWorld DVB-S 2101 2102 USB2.0 device"); +MODULE_DESCRIPTION("Driver for DVBWorld DVB-S 2101, 2102, DVB-S2 2104 USB2.0 device"); MODULE_VERSION("0.1"); MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/dvb/dvb-usb/dw2102.h b/linux/drivers/media/dvb/dvb-usb/dw2102.h index 7a310f906..e3370734e 100644 --- a/linux/drivers/media/dvb/dvb-usb/dw2102.h +++ b/linux/drivers/media/dvb/dvb-usb/dw2102.h @@ -4,6 +4,5 @@ #define DVB_USB_LOG_PREFIX "dw2102" #include "dvb-usb.h" -extern int dvb_usb_dw2102_debug; #define deb_xfer(args...) dprintk(dvb_usb_dw2102_debug, 0x02, args) #endif diff --git a/linux/drivers/media/dvb/frontends/Kconfig b/linux/drivers/media/dvb/frontends/Kconfig index 097d9f001..9c2f76309 100644 --- a/linux/drivers/media/dvb/frontends/Kconfig +++ b/linux/drivers/media/dvb/frontends/Kconfig @@ -43,6 +43,20 @@ config DVB_S5H1420 help A DVB-S tuner module. Say Y when you want to support this frontend. +config DVB_STV0288 + tristate "ST STV0288 based" + depends on DVB_CORE && I2C + default m if DVB_FE_CUSTOMISE + help + A DVB-S tuner module. Say Y when you want to support this frontend. + +config DVB_STB6000 + tristate "ST STB6000 silicon tuner" + depends on DVB_CORE && I2C + default m if DVB_FE_CUSTOMISE + help + A DVB-S silicon tuner module. Say Y when you want to support this tuner. + config DVB_STV0299 tristate "ST STV0299 based" depends on DVB_CORE && I2C @@ -92,6 +106,20 @@ config DVB_TUA6100 help A DVB-S PLL chip. +config DVB_CX24116 + tristate "Conexant CX24116 based" + depends on DVB_CORE && I2C + default m if DVB_FE_CUSTOMISE + help + A DVB-S/S2 tuner module. Say Y when you want to support this frontend. + +config DVB_SI21XX + tristate "Silicon Labs SI21XX based" + depends on DVB_CORE && I2C + default m if DVB_FE_CUSTOMISE + help + A DVB-S tuner module. Say Y when you want to support this frontend. + comment "DVB-T (terrestrial) frontends" depends on DVB_CORE diff --git a/linux/drivers/media/dvb/frontends/Makefile b/linux/drivers/media/dvb/frontends/Makefile index 1b5478886..491eb5c55 100644 --- a/linux/drivers/media/dvb/frontends/Makefile +++ b/linux/drivers/media/dvb/frontends/Makefile @@ -51,3 +51,7 @@ obj-$(CONFIG_DVB_S5H1411) += s5h1411.o obj-$(CONFIG_DVB_LGS8GL5) += lgs8gl5.o obj-$(CONFIG_DVB_DUMMY_FE) += dvb_dummy_fe.o obj-$(CONFIG_DVB_AF9013) += af9013.o +obj-$(CONFIG_DVB_CX24116) += cx24116.o +obj-$(CONFIG_DVB_SI21XX) += si21xx.o +obj-$(CONFIG_DVB_STV0299) += stv0288.o +obj-$(CONFIG_DVB_STB6000) += stb6000.o diff --git a/linux/drivers/media/dvb/frontends/cx24116.c b/linux/drivers/media/dvb/frontends/cx24116.c new file mode 100644 index 000000000..163190d54 --- /dev/null +++ b/linux/drivers/media/dvb/frontends/cx24116.c @@ -0,0 +1,1359 @@ +/* + Conexant cx24116/cx24118 - DVBS/S2 Satellite demod/tuner driver + + Copyright (C) 2006-2008 Steven Toth <stoth@hauppauge.com> + Copyright (C) 2006-2007 Georg Acher + Copyright (C) 2007-2008 Darron Broad + March 2007 + Fixed some bugs. + Added diseqc support. + Added corrected signal strength support. + August 2007 + Sync with legacy version. + Some clean ups. + Copyright (C) 2008 Igor Liplianin + September, 9th 2008 + Fixed locking on high symbol rates (>30000). + Implement MPEG initialization parameter. + + 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/slab.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/firmware.h> + +#include "dvb_frontend.h" +#include "cx24116.h" + +static int debug = 0; +#define dprintk(args...) \ + do { \ + if (debug) printk ("cx24116: " args); \ + } while (0) + +#define CX24116_DEFAULT_FIRMWARE "dvb-fe-cx24116.fw" +#define CX24116_SEARCH_RANGE_KHZ 5000 + +/* known registers */ +#define CX24116_REG_COMMAND (0x00) /* command args 0x00..0x1e */ +#define CX24116_REG_EXECUTE (0x1f) /* execute command */ +#define CX24116_REG_MAILBOX (0x96) /* FW or multipurpose mailbox? */ +#define CX24116_REG_RESET (0x20) /* reset status > 0 */ +#define CX24116_REG_SIGNAL (0x9e) /* signal low */ +#define CX24116_REG_SSTATUS (0x9d) /* signal high / status */ +#define CX24116_REG_QSTATUS (0xbc) +#define CX24116_REG_QUALITY (0xd5) +#define CX24116_REG_BER0 (0xc9) +#define CX24116_REG_BER8 (0xc8) +#define CX24116_REG_BER16 (0xc7) +#define CX24116_REG_BER24 (0xc6) +#define CX24116_REG_UCB0 (0xcb) +#define CX24116_REG_UCB8 (0xca) +#define CX24116_REG_CLKDIV (0xf3) +#define CX24116_REG_RATEDIV (0xf9) +#define CX24116_REG_FECSTATUS (0x9c) /* configured fec (not tuned) or actual FEC (tuned) 1=1/2 2=2/3 etc */ + +/* FECSTATUS bits */ +#define CX24116_FEC_FECMASK (0x1f) /* mask to determine configured fec (not tuned) or actual fec (tuned) */ +#define CX24116_FEC_DVBS (0x20) /* Select DVB-S demodulator, else DVB-S2 */ +#define CX24116_FEC_UNKNOWN (0x40) /* Unknown/unused */ +#define CX24116_FEC_PILOT (0x80) /* Pilot mode requested when tuning else always reset when tuned */ + +/* arg buffer size */ +#define CX24116_ARGLEN (0x1e) + +/* rolloff */ +#define CX24116_ROLLOFF_020 (0x00) +#define CX24116_ROLLOFF_025 (0x01) +#define CX24116_ROLLOFF_035 (0x02) + +/* pilot bit */ +#define CX24116_PILOT (0x40) + +/* signal status */ +#define CX24116_HAS_SIGNAL (0x01) +#define CX24116_HAS_CARRIER (0x02) +#define CX24116_HAS_VITERBI (0x04) +#define CX24116_HAS_SYNCLOCK (0x08) +#define CX24116_HAS_UNKNOWN1 (0x10) +#define CX24116_HAS_UNKNOWN2 (0x20) +#define CX24116_STATUS_MASK (0x3f) +#define CX24116_SIGNAL_MASK (0xc0) + +#define CX24116_DISEQC_TONEOFF (0) /* toneburst never sent */ +#define CX24116_DISEQC_TONECACHE (1) /* toneburst cached */ +#define CX24116_DISEQC_MESGCACHE (2) /* message cached */ + +/* arg offset for DiSEqC */ +#define CX24116_DISEQC_BURST (1) +#define CX24116_DISEQC_ARG2_2 (2) /* unknown value=2 */ +#define CX24116_DISEQC_ARG3_0 (3) /* unknown value=0 */ +#define CX24116_DISEQC_ARG4_0 (4) /* unknown value=0 */ +#define CX24116_DISEQC_MSGLEN (5) +#define CX24116_DISEQC_MSGOFS (6) + +/* DiSEqC burst */ +#define CX24116_DISEQC_MINI_A (0) +#define CX24116_DISEQC_MINI_B (1) + +/* DiSEqC tone burst */ +static int toneburst = 1; + +enum cmds +{ + CMD_SET_VCO = 0x10, + CMD_TUNEREQUEST = 0x11, + CMD_MPEGCONFIG = 0x13, + CMD_TUNERINIT = 0x14, + CMD_BANDWIDTH = 0x15, + CMD_GETAGC = 0x19, + CMD_LNBCONFIG = 0x20, + CMD_LNBSEND = 0x21, /* Formerly CMD_SEND_DISEQC */ + CMD_SET_TONEPRE = 0x22, + CMD_SET_TONE = 0x23, + CMD_UPDFWVERS = 0x35, + CMD_TUNERSLEEP = 0x36, + CMD_AGCCONTROL = 0x3b, /* Unknown */ +}; + +/* The Demod/Tuner can't easily provide these, we cache them */ +struct cx24116_tuning +{ + u32 frequency; + u32 symbol_rate; + fe_spectral_inversion_t inversion; + fe_code_rate_t fec; + + fe_modulation_t modulation; + fe_pilot_t pilot; + fe_rolloff_t rolloff; + + /* Demod values */ + u8 fec_val; + u8 fec_mask; + u8 inversion_val; + u8 rolloff_val; +}; + +/* Basic commands that are sent to the firmware */ +struct cx24116_cmd +{ + u8 len; + u8 args[CX24116_ARGLEN]; +}; + +struct cx24116_state +{ + struct i2c_adapter* i2c; + const struct cx24116_config* config; + + struct dvb_frontend frontend; + + struct cx24116_tuning dcur; + struct cx24116_tuning dnxt; + + u8 skip_fw_load; + u8 burst; + struct cx24116_cmd dsec_cmd; +}; + +static int cx24116_writereg(struct cx24116_state* state, int reg, int data) +{ + u8 buf[] = { reg, data }; + struct i2c_msg msg = { .addr = state->config->demod_address, + .flags = 0, .buf = buf, .len = 2 }; + int err; + + if (debug>1) + printk("cx24116: %s: write reg 0x%02x, value 0x%02x\n", + __func__,reg, data); + + if ((err = i2c_transfer(state->i2c, &msg, 1)) != 1) { + printk("%s: writereg error(err == %i, reg == 0x%02x," + " value == 0x%02x)\n", __func__, err, reg, data); + return -EREMOTEIO; + } + + return 0; +} + +/* Bulk byte writes to a single I2C address, for 32k firmware load */ +static int cx24116_writeregN(struct cx24116_state* state, int reg, u8 *data, u16 len) +{ + int ret = -EREMOTEIO; + struct i2c_msg msg; + u8 *buf; + + buf = kmalloc(len + 1, GFP_KERNEL); + if (buf == NULL) { + printk("Unable to kmalloc\n"); + ret = -ENOMEM; + goto error; + } + + *(buf) = reg; + memcpy(buf + 1, data, len); + + msg.addr = state->config->demod_address; + msg.flags = 0; + msg.buf = buf; + msg.len = len + 1; + + if (debug>1) + printk("cx24116: %s: write regN 0x%02x, len = %d\n", + __func__,reg, len); + + if ((ret = i2c_transfer(state->i2c, &msg, 1)) != 1) { + printk("%s: writereg error(err == %i, reg == 0x%02x\n", + __func__, ret, reg); + ret = -EREMOTEIO; + } + +error: + kfree(buf); + + return ret; +} + +static int cx24116_readreg(struct cx24116_state* state, u8 reg) +{ + int ret; + u8 b0[] = { reg }; + u8 b1[] = { 0 }; + struct i2c_msg msg[] = { + { .addr = state->config->demod_address, .flags = 0, .buf = b0, .len = 1 }, + { .addr = state->config->demod_address, .flags = I2C_M_RD, .buf = b1, .len = 1 } + }; + + ret = i2c_transfer(state->i2c, msg, 2); + + if (ret != 2) { + printk("%s: reg=0x%x (error=%d)\n", __func__, reg, ret); + return ret; + } + + if (debug>1) + printk("cx24116: read reg 0x%02x, value 0x%02x\n",reg, b1[0]); + + return b1[0]; +} + +static int cx24116_set_inversion(struct cx24116_state* state, fe_spectral_inversion_t inversion) +{ + dprintk("%s(%d)\n", __func__, inversion); + + switch (inversion) { + case INVERSION_OFF: + state->dnxt.inversion_val = 0x00; + break; + case INVERSION_ON: + state->dnxt.inversion_val = 0x04; + break; + case INVERSION_AUTO: + state->dnxt.inversion_val = 0x0C; + break; + default: + return -EINVAL; + } + + state->dnxt.inversion = inversion; + + return 0; +} + +/* + * modfec (modulation and FEC) + * =========================== + * + * MOD FEC mask/val standard + * ---- -------- ----------- -------- + * QPSK FEC_1_2 0x02 0x02+X DVB-S + * QPSK FEC_2_3 0x04 0x02+X DVB-S + * QPSK FEC_3_4 0x08 0x02+X DVB-S + * QPSK FEC_4_5 0x10 0x02+X DVB-S (?) + * QPSK FEC_5_6 0x20 0x02+X DVB-S + * QPSK FEC_6_7 0x40 0x02+X DVB-S + * QPSK FEC_7_8 0x80 0x02+X DVB-S + * QPSK FEC_8_9 0x01 0x02+X DVB-S (?) (NOT SUPPORTED?) + * QPSK AUTO 0xff 0x02+X DVB-S + * + * For DVB-S high byte probably represents FEC + * and low byte selects the modulator. The high + * byte is search range mask. Bit 5 may turn + * on DVB-S and remaining bits represent some + * kind of calibration (how/what i do not know). + * + * Eg.(2/3) szap "Zone Horror" + * + * mask/val = 0x04, 0x20 + * status 1f | signal c3c0 | snr a333 | ber 00000098 | unc 00000000 | FE_HAS_LOCK + * + * mask/val = 0x04, 0x30 + * status 1f | signal c3c0 | snr a333 | ber 00000000 | unc 00000000 | FE_HAS_LOCK + * + * After tuning FECSTATUS contains actual FEC + * in use numbered 1 through to 8 for 1/2 .. 2/3 etc + * + * NBC=NOT/NON BACKWARD COMPATIBLE WITH DVB-S (DVB-S2 only) + * + * NBC-QPSK FEC_1_2 0x00, 0x04 DVB-S2 + * NBC-QPSK FEC_3_5 0x00, 0x05 DVB-S2 + * NBC-QPSK FEC_2_3 0x00, 0x06 DVB-S2 + * NBC-QPSK FEC_3_4 0x00, 0x07 DVB-S2 + * NBC-QPSK FEC_4_5 0x00, 0x08 DVB-S2 + * NBC-QPSK FEC_5_6 0x00, 0x09 DVB-S2 + * NBC-QPSK FEC_8_9 0x00, 0x0a DVB-S2 + * NBC-QPSK FEC_9_10 0x00, 0x0b DVB-S2 + * + * NBC-8PSK FEC_3_5 0x00, 0x0c DVB-S2 + * NBC-8PSK FEC_2_3 0x00, 0x0d DVB-S2 + * NBC-8PSK FEC_3_4 0x00, 0x0e DVB-S2 + * NBC-8PSK FEC_5_6 0x00, 0x0f DVB-S2 + * NBC-8PSK FEC_8_9 0x00, 0x10 DVB-S2 + * NBC-8PSK FEC_9_10 0x00, 0x11 DVB-S2 + * + * For DVB-S2 low bytes selects both modulator + * and FEC. High byte is meaningless here. To + * set pilot, bit 6 (0x40) is set. When inspecting + * FECSTATUS bit 7 (0x80) represents the pilot + * selection whilst not tuned. When tuned, actual FEC + * in use is found in FECSTATUS as per above. Pilot + * value is reset. + */ + +/* A table of modulation, fec and configuration bytes for the demod. + * Not all S2 mmodulation schemes are support and not all rates with + * a scheme are support. Especially, no auto detect when in S2 mode. + */ +struct cx24116_modfec { + fe_modulation_t modulation; + fe_code_rate_t fec; + u8 mask; /* In DVBS mode this is used to autodetect */ + u8 val; /* Passed to the firmware to indicate mode selection */ +} CX24116_MODFEC_MODES[] = { + /* QPSK. For unknown rates we set hardware to auto detect 0xfe 0x30 */ + + /*mod fec mask val */ + { QPSK, FEC_NONE, 0xfe, 0x30 }, + { QPSK, FEC_1_2, 0x02, 0x2e }, /* 00000010 00101110 */ + { QPSK, FEC_2_3, 0x04, 0x2f }, /* 00000100 00101111 */ + { QPSK, FEC_3_4, 0x08, 0x30 }, /* 00001000 00110000 */ + { QPSK, FEC_4_5, 0xfe, 0x30 }, /* 000?0000 ? */ + { QPSK, FEC_5_6, 0x20, 0x31 }, /* 00100000 00110001 */ + { QPSK, FEC_6_7, 0xfe, 0x30 }, /* 0?000000 ? */ + { QPSK, FEC_7_8, 0x80, 0x32 }, /* 10000000 00110010 */ + { QPSK, FEC_8_9, 0xfe, 0x30 }, /* 0000000? ? */ + { QPSK, FEC_AUTO, 0xfe, 0x30 }, + /* NBC-QPSK */ + { NBC_QPSK, FEC_1_2, 0x00, 0x04 }, + { NBC_QPSK, FEC_3_5, 0x00, 0x05 }, + { NBC_QPSK, FEC_2_3, 0x00, 0x06 }, + { NBC_QPSK, FEC_3_4, 0x00, 0x07 }, + { NBC_QPSK, FEC_4_5, 0x00, 0x08 }, + { NBC_QPSK, FEC_5_6, 0x00, 0x09 }, + { NBC_QPSK, FEC_8_9, 0x00, 0x0a }, + { NBC_QPSK, FEC_9_10, 0x00, 0x0b }, + /* 8PSK */ + { _8PSK, FEC_3_5, 0x00, 0x0c }, + { _8PSK, FEC_2_3, 0x00, 0x0d }, + { _8PSK, FEC_3_4, 0x00, 0x0e }, + { _8PSK, FEC_5_6, 0x00, 0x0f }, + { _8PSK, FEC_8_9, 0x00, 0x10 }, + { _8PSK, FEC_9_10, 0x00, 0x11 }, + /* + * `val' can be found in the FECSTATUS register when tuning. + * FECSTATUS will give the actual FEC in use if tuning was successful. + */ +}; + +static int cx24116_lookup_fecmod(struct cx24116_state* state, + fe_modulation_t m, fe_code_rate_t f) +{ + int i, ret = -EOPNOTSUPP; + + dprintk("%s(0x%02x,0x%02x)\n", __func__, m, f); + + for(i=0 ; i < sizeof(CX24116_MODFEC_MODES) / sizeof(struct cx24116_modfec) ; i++) + { + if( (m == CX24116_MODFEC_MODES[i].modulation) && + (f == CX24116_MODFEC_MODES[i].fec) ) + { + ret = i; + break; + } + } + + return ret; +} + +static int cx24116_set_fec(struct cx24116_state* state, fe_modulation_t mod, fe_code_rate_t fec) +{ + int ret = 0; + + dprintk("%s(0x%02x,0x%02x)\n", __func__, mod, fec); + + ret = cx24116_lookup_fecmod(state, mod, fec); + + if(ret < 0) + return ret; + + state->dnxt.fec = fec; + state->dnxt.fec_val = CX24116_MODFEC_MODES[ret].val; + state->dnxt.fec_mask = CX24116_MODFEC_MODES[ret].mask; + dprintk("%s() mask/val = 0x%02x/0x%02x\n", __func__, + state->dnxt.fec_mask, state->dnxt.fec_val); + + return 0; +} + +static int cx24116_set_symbolrate(struct cx24116_state* state, u32 rate) +{ + dprintk("%s(%d)\n", __func__, rate); + + /* check if symbol rate is within limits */ + if ((rate > state->frontend.ops.info.symbol_rate_max) || + (rate < state->frontend.ops.info.symbol_rate_min)) { + dprintk("%s() unsupported symbol_rate = %d\n", __func__, rate); + return -EOPNOTSUPP; + } + + state->dnxt.symbol_rate = rate; + dprintk("%s() symbol_rate = %d\n", __func__, rate); + + return 0; +} + +static int cx24116_load_firmware (struct dvb_frontend* fe, const struct firmware *fw); + +static int cx24116_firmware_ondemand(struct dvb_frontend* fe) +{ + struct cx24116_state *state = fe->demodulator_priv; + const struct firmware *fw; + int ret = 0; + + dprintk("%s()\n",__func__); + + if (cx24116_readreg(state, 0x20) > 0) + { + + if (state->skip_fw_load) + return 0; + + /* Load firmware */ + /* request the firmware, this will block until someone uploads it */ + printk("%s: Waiting for firmware upload (%s)...\n", __func__, CX24116_DEFAULT_FIRMWARE); + ret = request_firmware(&fw, CX24116_DEFAULT_FIRMWARE, &state->i2c->dev); + printk("%s: Waiting for firmware upload(2)...\n", __func__); + if (ret) { + printk("%s: No firmware uploaded (timeout or file not found?)\n", __func__); + return ret; + } + + /* Make sure we don't recurse back through here during loading */ + state->skip_fw_load = 1; + + ret = cx24116_load_firmware(fe, fw); + if (ret) + printk("%s: Writing firmware to device failed\n", __func__); + + release_firmware(fw); + + printk("%s: Firmware upload %s\n", __func__, ret == 0 ? "complete" : "failed"); + + /* Ensure firmware is always loaded if required */ + state->skip_fw_load = 0; + } + + return ret; +} + +/* Take a basic firmware command structure, format it and forward it for processing */ +static int cx24116_cmd_execute(struct dvb_frontend* fe, struct cx24116_cmd *cmd) +{ + struct cx24116_state *state = fe->demodulator_priv; + int i, ret; + + dprintk("%s()\n", __func__); + + /* Load the firmware if required */ + if ( (ret = cx24116_firmware_ondemand(fe)) != 0) + { + printk("%s(): Unable initialise the firmware\n", __func__); + return ret; + } + + /* Write the command */ + for(i = 0; i < cmd->len ; i++) + { + dprintk("%s: 0x%02x == 0x%02x\n", __func__, i, cmd->args[i]); + cx24116_writereg(state, i, cmd->args[i]); + } + + /* Start execution and wait for cmd to terminate */ + cx24116_writereg(state, CX24116_REG_EXECUTE, 0x01); + while( cx24116_readreg(state, CX24116_REG_EXECUTE) ) + { + msleep(10); + if(i++ > 64) + { + /* Avoid looping forever if the firmware does no respond */ + printk("%s() Firmware not responding\n", __func__); + return -EREMOTEIO; + } + } + return 0; +} + +static int cx24116_load_firmware (struct dvb_frontend* fe, const struct firmware *fw) +{ + struct cx24116_state* state = fe->demodulator_priv; + struct cx24116_cmd cmd; + int i, ret; + unsigned char vers[4]; + + dprintk("%s\n", __func__); + dprintk("Firmware is %zu bytes (%02x %02x .. %02x %02x)\n" + ,fw->size + ,fw->data[0] + ,fw->data[1] + ,fw->data[ fw->size-2 ] + ,fw->data[ fw->size-1 ] + ); + + /* Toggle 88x SRST pin to reset demod */ + if (state->config->reset_device) + state->config->reset_device(fe); + + /* Begin the firmware load process */ + /* Prepare the demod, load the firmware, cleanup after load */ + + /* Init PLL */ + cx24116_writereg(state, 0xE5, 0x00); + cx24116_writereg(state, 0xF1, 0x08); + cx24116_writereg(state, 0xF2, 0x13); + + /* Start PLL */ + cx24116_writereg(state, 0xe0, 0x03); + cx24116_writereg(state, 0xe0, 0x00); + + /* Unknown */ + cx24116_writereg(state, CX24116_REG_CLKDIV, 0x46); + cx24116_writereg(state, CX24116_REG_RATEDIV, 0x00); + + /* Unknown */ + cx24116_writereg(state, 0xF0, 0x03); + cx24116_writereg(state, 0xF4, 0x81); + cx24116_writereg(state, 0xF5, 0x00); + cx24116_writereg(state, 0xF6, 0x00); + + /* write the entire firmware as one transaction */ + cx24116_writeregN(state, 0xF7, fw->data, fw->size); + + cx24116_writereg(state, 0xF4, 0x10); + cx24116_writereg(state, 0xF0, 0x00); + cx24116_writereg(state, 0xF8, 0x06); + + /* Firmware CMD 10: VCO config */ + cmd.args[0x00] = CMD_SET_VCO; + cmd.args[0x01] = 0x05; + cmd.args[0x02] = 0xdc; + cmd.args[0x03] = 0xda; + cmd.args[0x04] = 0xae; + cmd.args[0x05] = 0xaa; + cmd.args[0x06] = 0x04; + cmd.args[0x07] = 0x9d; + cmd.args[0x08] = 0xfc; + cmd.args[0x09] = 0x06; + cmd.len= 0x0a; + ret = cx24116_cmd_execute(fe, &cmd); + if (ret != 0) + return ret; + + cx24116_writereg(state, CX24116_REG_SSTATUS, 0x00); + + /* Firmware CMD 14: Tuner config */ + cmd.args[0x00] = CMD_TUNERINIT; + cmd.args[0x01] = 0x00; + cmd.args[0x02] = 0x00; + cmd.len= 0x03; + ret = cx24116_cmd_execute(fe, &cmd); + if (ret != 0) + return ret; + + cx24116_writereg(state, 0xe5, 0x00); + + /* Firmware CMD 13: MPEG config */ + cmd.args[0x00] = CMD_MPEGCONFIG; + cmd.args[0x01] = 0x01; + cmd.args[0x02] = 0x75; + cmd.args[0x03] = 0x00; + if (state->config->mpg_clk_pos_pol) + cmd.args[0x04] = state->config->mpg_clk_pos_pol; + else + cmd.args[0x04] = 0x02; + cmd.args[0x05] = 0x00; + cmd.len= 0x06; + ret = cx24116_cmd_execute(fe, &cmd); + if (ret != 0) + return ret; + + /* Firmware CMD 35: Get firmware version */ + cmd.args[0x00] = CMD_UPDFWVERS; + cmd.len= 0x02; + for(i=0; i<4; i++) { + cmd.args[0x01] = i; + ret = cx24116_cmd_execute(fe, &cmd); + if (ret != 0) + return ret; + vers[i]= cx24116_readreg(state, CX24116_REG_MAILBOX); + } + printk("%s: FW version %i.%i.%i.%i\n", __func__, + vers[0], vers[1], vers[2], vers[3]); + + return 0; +} + +static int cx24116_set_voltage(struct dvb_frontend* fe, fe_sec_voltage_t voltage) +{ + /* The isl6421 module will override this function in the fops. */ + dprintk("%s() This should never appear if the isl6421 module is loaded correctly\n",__func__); + + return -EOPNOTSUPP; +} + +static int cx24116_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct cx24116_state *state = fe->demodulator_priv; + + int lock = cx24116_readreg(state, CX24116_REG_SSTATUS); + + dprintk("%s: status = 0x%02x\n", __func__, lock); + + *status = 0; + + if (lock & CX24116_HAS_SIGNAL) + *status |= FE_HAS_SIGNAL; + if (lock & CX24116_HAS_CARRIER) + *status |= FE_HAS_CARRIER; + if (lock & CX24116_HAS_VITERBI) + *status |= FE_HAS_VITERBI; + if (lock & CX24116_HAS_SYNCLOCK) + *status |= FE_HAS_SYNC | FE_HAS_LOCK; + + return 0; +} + +static int cx24116_read_ber(struct dvb_frontend* fe, u32* ber) +{ + struct cx24116_state *state = fe->demodulator_priv; + + dprintk("%s()\n", __func__); + + *ber = ( cx24116_readreg(state, CX24116_REG_BER24) << 24 ) | + ( cx24116_readreg(state, CX24116_REG_BER16) << 16 ) | + ( cx24116_readreg(state, CX24116_REG_BER8 ) << 8 ) | + cx24116_readreg(state, CX24116_REG_BER0 ); + + return 0; +} + +/* TODO Determine function and scale appropriately */ +static int cx24116_read_signal_strength(struct dvb_frontend* fe, u16* signal_strength) +{ + struct cx24116_state *state = fe->demodulator_priv; + struct cx24116_cmd cmd; + int ret; + u16 sig_reading; + + dprintk("%s()\n", __func__); + + /* Firmware CMD 19: Get AGC */ + cmd.args[0x00] = CMD_GETAGC; + cmd.len= 0x01; + ret = cx24116_cmd_execute(fe, &cmd); + if (ret != 0) + return ret; + + sig_reading = ( cx24116_readreg(state, CX24116_REG_SSTATUS) & CX24116_SIGNAL_MASK ) | + ( cx24116_readreg(state, CX24116_REG_SIGNAL) << 6 ); + *signal_strength= 0 - sig_reading; + + dprintk("%s: raw / cooked = 0x%04x / 0x%04x\n", __func__, sig_reading, *signal_strength); + + return 0; +} + +/* SNR (0..100)% = (sig & 0xf0) * 10 + (sig & 0x0f) * 10 / 16 */ +static int cx24116_read_snr(struct dvb_frontend* fe, u16* snr) +{ + struct cx24116_state *state = fe->demodulator_priv; + u8 snr_reading; + static const u32 snr_tab[] = { /* 10 x Table (rounded up) */ + 0x00000,0x0199A,0x03333,0x04ccD,0x06667, + 0x08000,0x0999A,0x0b333,0x0cccD,0x0e667, + 0x10000,0x1199A,0x13333,0x14ccD,0x16667,0x18000 }; + + dprintk("%s()\n", __func__); + + snr_reading = cx24116_readreg(state, CX24116_REG_QUALITY); + + if(snr_reading >= 0xa0 /* 100% */) + *snr = 0xffff; + else + *snr = snr_tab [ ( snr_reading & 0xf0 ) >> 4 ] + + ( snr_tab [ ( snr_reading & 0x0f ) ] >> 4 ); + + dprintk("%s: raw / cooked = 0x%02x / 0x%04x\n", __func__, + snr_reading, *snr); + + return 0; +} + +static int cx24116_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + struct cx24116_state *state = fe->demodulator_priv; + + dprintk("%s()\n", __func__); + + *ucblocks = ( cx24116_readreg(state, CX24116_REG_UCB8) << 8 ) | + cx24116_readreg(state, CX24116_REG_UCB0); + + return 0; +} + +/* Overwrite the current tuning params, we are about to tune */ +static void cx24116_clone_params(struct dvb_frontend* fe) +{ + struct cx24116_state *state = fe->demodulator_priv; + memcpy(&state->dcur, &state->dnxt, sizeof(state->dcur)); +} + +/* Wait for LNB */ +static int cx24116_wait_for_lnb(struct dvb_frontend* fe) +{ + struct cx24116_state *state = fe->demodulator_priv; + int i; + + dprintk("%s() qstatus = 0x%02x\n", __func__, + cx24116_readreg(state, CX24116_REG_QSTATUS)); + + /* Wait for up to 300 ms */ + for(i = 0; i < 30 ; i++) { + if (cx24116_readreg(state, CX24116_REG_QSTATUS) & 0x20) + return 0; + msleep(10); + } + + dprintk("%s(): LNB not ready\n", __func__); + + return -ETIMEDOUT; /* -EBUSY ? */ +} + +static int cx24116_set_tone(struct dvb_frontend* fe, fe_sec_tone_mode_t tone) +{ + struct cx24116_cmd cmd; + int ret; + + dprintk("%s(%d)\n", __func__, tone); + if ( (tone != SEC_TONE_ON) && (tone != SEC_TONE_OFF) ) { + printk("%s: Invalid, tone=%d\n", __func__, tone); + return -EINVAL; + } + + /* Wait for LNB ready */ + ret = cx24116_wait_for_lnb(fe); + if(ret != 0) + return ret; + + /* Min delay time after DiSEqC send */ + msleep(15); /* XXX determine is FW does this, see send_diseqc/burst */ + + /* This is always done before the tone is set */ + cmd.args[0x00] = CMD_SET_TONEPRE; + cmd.args[0x01] = 0x00; + cmd.len= 0x02; + ret = cx24116_cmd_execute(fe, &cmd); + if (ret != 0) + return ret; + + /* Now we set the tone */ + cmd.args[0x00] = CMD_SET_TONE; + cmd.args[0x01] = 0x00; + cmd.args[0x02] = 0x00; + + switch (tone) { + case SEC_TONE_ON: + dprintk("%s: setting tone on\n", __func__); + cmd.args[0x03] = 0x01; + break; + case SEC_TONE_OFF: + dprintk("%s: setting tone off\n",__func__); + cmd.args[0x03] = 0x00; + break; + } + cmd.len= 0x04; + + /* Min delay time before DiSEqC send */ + msleep(15); /* XXX determine is FW does this, see send_diseqc/burst */ + + return cx24116_cmd_execute(fe, &cmd); +} + +/* Initialise DiSEqC */ +static int cx24116_diseqc_init(struct dvb_frontend* fe) +{ + struct cx24116_state *state = fe->demodulator_priv; + struct cx24116_cmd cmd; + int ret; + + /* Firmware CMD 20: LNB/DiSEqC config */ + cmd.args[0x00] = CMD_LNBCONFIG; + cmd.args[0x01] = 0x00; + cmd.args[0x02] = 0x10; + cmd.args[0x03] = 0x00; + cmd.args[0x04] = 0x8f; + cmd.args[0x05] = 0x28; + cmd.args[0x06] = (toneburst == CX24116_DISEQC_TONEOFF) ? 0x00 : 0x01; + cmd.args[0x07] = 0x01; + cmd.len= 0x08; + ret = cx24116_cmd_execute(fe, &cmd); + if (ret != 0) + return ret; + + /* Prepare a DiSEqC command */ + state->dsec_cmd.args[0x00] = CMD_LNBSEND; + + /* DiSEqC burst */ + state->dsec_cmd.args[CX24116_DISEQC_BURST] = CX24116_DISEQC_MINI_A; + + /* Unknown */ + state->dsec_cmd.args[CX24116_DISEQC_ARG2_2] = 0x02; + state->dsec_cmd.args[CX24116_DISEQC_ARG3_0] = 0x00; + state->dsec_cmd.args[CX24116_DISEQC_ARG4_0] = 0x00; /* Continuation flag? */ + + /* DiSEqC message length */ + state->dsec_cmd.args[CX24116_DISEQC_MSGLEN] = 0x00; + + /* Command length */ + state->dsec_cmd.len= CX24116_DISEQC_MSGOFS; + + return 0; +} + +/* Send DiSEqC message with derived burst (hack) || previous burst */ +static int cx24116_send_diseqc_msg(struct dvb_frontend* fe, struct dvb_diseqc_master_cmd *d) +{ + struct cx24116_state *state = fe->demodulator_priv; + int i, ret; + + /* Dump DiSEqC message */ + if (debug) { + printk("cx24116: %s(", __func__); + for(i = 0 ; i < d->msg_len ;) { + printk("0x%02x", d->msg[i]); + if(++i < d->msg_len) + printk(", "); + } + printk(") toneburst=%d\n", toneburst); + } + + /* Validate length */ + if(d->msg_len > (CX24116_ARGLEN - CX24116_DISEQC_MSGOFS)) + return -EINVAL; + + /* DiSEqC message */ + for (i = 0; i < d->msg_len; i++) + state->dsec_cmd.args[CX24116_DISEQC_MSGOFS + i] = d->msg[i]; + + /* DiSEqC message length */ + state->dsec_cmd.args[CX24116_DISEQC_MSGLEN] = d->msg_len; + + /* Command length */ + state->dsec_cmd.len= CX24116_DISEQC_MSGOFS + state->dsec_cmd.args[CX24116_DISEQC_MSGLEN]; + + /* DiSEqC toneburst */ + if(toneburst == CX24116_DISEQC_MESGCACHE) + /* Message is cached */ + return 0; + + else if(toneburst == CX24116_DISEQC_TONEOFF) + /* Message is sent without burst */ + state->dsec_cmd.args[CX24116_DISEQC_BURST] = 0; + + else if(toneburst == CX24116_DISEQC_TONECACHE) { + /* + * Message is sent with derived else cached burst + * + * WRITE PORT GROUP COMMAND 38 + * + * 0/A/A: E0 10 38 F0..F3 + * 1/B/B: E0 10 38 F4..F7 + * 2/C/A: E0 10 38 F8..FB + * 3/D/B: E0 10 38 FC..FF + * + * databyte[3]= 8421:8421 + * ABCD:WXYZ + * CLR :SET + * + * WX= PORT SELECT 0..3 (X=TONEBURST) + * Y = VOLTAGE (0=13V, 1=18V) + * Z = BAND (0=LOW, 1=HIGH(22K)) + */ + if(d->msg_len >= 4 && d->msg[2] == 0x38) + state->dsec_cmd.args[CX24116_DISEQC_BURST] = ((d->msg[3] & 4) >> 2); + if(debug) + dprintk("%s burst=%d\n", __func__, state->dsec_cmd.args[CX24116_DISEQC_BURST]); + } + + /* Wait for LNB ready */ + ret = cx24116_wait_for_lnb(fe); + if(ret != 0) + return ret; + + /* Wait for voltage/min repeat delay */ + msleep(100); + + /* Command */ + ret = cx24116_cmd_execute(fe, &state->dsec_cmd); + if(ret != 0) + return ret; + /* + * Wait for send + * + * Eutelsat spec: + * >15ms delay + (XXX determine if FW does this, see set_tone) + * 13.5ms per byte + + * >15ms delay + + * 12.5ms burst + + * >15ms delay (XXX determine if FW does this, see set_tone) + */ + msleep( (state->dsec_cmd.args[CX24116_DISEQC_MSGLEN] << 4) + ((toneburst == CX24116_DISEQC_TONEOFF) ? 30 : 60) ); + + return 0; +} + +/* Send DiSEqC burst */ +static int cx24116_diseqc_send_burst(struct dvb_frontend* fe, fe_sec_mini_cmd_t burst) +{ + struct cx24116_state *state = fe->demodulator_priv; + int ret; + + dprintk("%s(%d) toneburst=%d\n",__func__, burst, toneburst); + + /* DiSEqC burst */ + if (burst == SEC_MINI_A) + state->dsec_cmd.args[CX24116_DISEQC_BURST] = CX24116_DISEQC_MINI_A; + else if(burst == SEC_MINI_B) + state->dsec_cmd.args[CX24116_DISEQC_BURST] = CX24116_DISEQC_MINI_B; + else + return -EINVAL; + + /* DiSEqC toneburst */ + if(toneburst != CX24116_DISEQC_MESGCACHE) + /* Burst is cached */ + return 0; + + /* Burst is to be sent with cached message */ + + /* Wait for LNB ready */ + ret = cx24116_wait_for_lnb(fe); + if(ret != 0) + return ret; + + /* Wait for voltage/min repeat delay */ + msleep(100); + + /* Command */ + ret = cx24116_cmd_execute(fe, &state->dsec_cmd); + if(ret != 0) + return ret; + + /* + * Wait for send + * + * Eutelsat spec: + * >15ms delay + (XXX determine if FW does this, see set_tone) + * 13.5ms per byte + + * >15ms delay + + * 12.5ms burst + + * >15ms delay (XXX determine if FW does this, see set_tone) + */ + msleep( (state->dsec_cmd.args[CX24116_DISEQC_MSGLEN] << 4) + 60 ); + + return 0; +} + +static void cx24116_release(struct dvb_frontend* fe) +{ + struct cx24116_state* state = fe->demodulator_priv; + dprintk("%s\n",__func__); + kfree(state); +} + +static struct dvb_frontend_ops cx24116_ops; + +struct dvb_frontend* cx24116_attach(const struct cx24116_config* config, + struct i2c_adapter* i2c) +{ + struct cx24116_state* state = NULL; + int ret; + + dprintk("%s\n",__func__); + + /* allocate memory for the internal state */ + state = kmalloc(sizeof(struct cx24116_state), GFP_KERNEL); + if (state == NULL) { + printk("Unable to kmalloc\n"); + goto error1; + } + + /* setup the state */ + memset(state, 0, sizeof(struct cx24116_state)); + + state->config = config; + state->i2c = i2c; + + /* check if the demod is present */ + ret = (cx24116_readreg(state, 0xFF) << 8) | cx24116_readreg(state, 0xFE); + if (ret != 0x0501) { + printk("Invalid probe, probably not a CX24116 device\n"); + goto error2; + } + + /* create dvb_frontend */ + memcpy(&state->frontend.ops, &cx24116_ops, sizeof(struct dvb_frontend_ops)); + state->frontend.demodulator_priv = state; + return &state->frontend; + +error2: kfree(state); +error1: return NULL; +} +#if 0 +static int cx24116_get_params(struct dvb_frontend* fe) +{ + struct cx24116_state *state = fe->demodulator_priv; + struct dtv_frontend_properties *cache = &fe->dtv_property_cache; + + dprintk("%s()\n",__func__); + + cache->frequency = state->dcur.frequency; + cache->inversion = state->dcur.inversion; + cache->modulation = state->dcur.modulation; + cache->fec_inner = state->dcur.fec; + cache->symbol_rate = state->dcur.symbol_rate; + + return 0; +} +#endif +/* + * Initialise or wake up device + * + * Power config will reset and load initial firmware if required + */ +static int cx24116_initfe(struct dvb_frontend* fe) +{ + struct cx24116_state* state = fe->demodulator_priv; + struct cx24116_cmd cmd; + int ret; + + dprintk("%s()\n",__func__); + + /* Power on */ + cx24116_writereg(state, 0xe0, 0); + cx24116_writereg(state, 0xe1, 0); + cx24116_writereg(state, 0xea, 0); + + /* Firmware CMD 36: Power config */ + cmd.args[0x00] = CMD_TUNERSLEEP; + cmd.args[0x01] = 0; + cmd.len= 0x02; + ret = cx24116_cmd_execute(fe, &cmd); + if(ret != 0) + return ret; + + return cx24116_diseqc_init(fe); +} + +/* + * Put device to sleep + */ +static int cx24116_sleep(struct dvb_frontend* fe) +{ + struct cx24116_state* state = fe->demodulator_priv; + struct cx24116_cmd cmd; + int ret; + + dprintk("%s()\n",__func__); + + /* Firmware CMD 36: Power config */ + cmd.args[0x00] = CMD_TUNERSLEEP; + cmd.args[0x01] = 1; + cmd.len= 0x02; + ret = cx24116_cmd_execute(fe, &cmd); + if(ret != 0) + return ret; + + /* Power off (Shutdown clocks) */ + cx24116_writereg(state, 0xea, 0xff); + cx24116_writereg(state, 0xe1, 1); + cx24116_writereg(state, 0xe0, 1); + + return 0; +} + +static int cx24116_set_property(struct dvb_frontend *fe, struct dtv_property* tvp) +{ + dprintk("%s(..)\n", __func__); + return 0; +} + +static int cx24116_get_property(struct dvb_frontend *fe, struct dtv_property* tvp) +{ + dprintk("%s(..)\n", __func__); + return 0; +} + +/* dvb-core told us to tune, the tv property cache will be complete, + * it's safe for is to pull values and use them for tuning purposes. + */ +static int cx24116_set_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + struct cx24116_state *state = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct cx24116_cmd cmd; + fe_status_t tunerstat; + int i, status, ret, retune = 1; + + dprintk("%s()\n",__func__); + + state->dnxt.modulation = c->modulation; + state->dnxt.frequency = c->frequency; + + switch(c->delivery_system) { + case SYS_DVBS: + dprintk("%s: DVB-S delivery system selected\n",__func__); + state->dnxt.pilot = PILOT_OFF; + state->dnxt.rolloff_val = CX24116_ROLLOFF_035; + state->dnxt.rolloff = c->rolloff; + break; + case SYS_DVBS2: + dprintk("%s: DVB-S2 delivery system selected\n",__func__); + if(c->pilot == PILOT_AUTO) + retune++; + state->dnxt.pilot = c->pilot; + switch(c->rolloff) { + case ROLLOFF_20: + state->dnxt.rolloff_val= CX24116_ROLLOFF_020; + break; + case ROLLOFF_25: + state->dnxt.rolloff_val= CX24116_ROLLOFF_025; + break; + case ROLLOFF_35: + state->dnxt.rolloff_val= CX24116_ROLLOFF_035; + break; + case ROLLOFF_AUTO: + return -EOPNOTSUPP; + } + state->dnxt.rolloff = c->rolloff; + break; + default: + dprintk("%s: unsupported delivery system selected (%d)\n", + __func__, c->delivery_system); + return -EOPNOTSUPP; + } + + if ((ret = cx24116_set_inversion(state, c->inversion)) != 0) + return ret; + + if ((ret = cx24116_set_fec(state, c->modulation, c->fec_inner)) != 0) + return ret; + + if ((ret = cx24116_set_symbolrate(state, c->symbol_rate)) != 0) + return ret; + + /* discard the 'current' tuning parameters and prepare to tune */ + cx24116_clone_params(fe); + + dprintk("%s: retune = %d\n", __func__, retune); + dprintk("%s: rolloff = %d\n", __func__, state->dcur.rolloff); + dprintk("%s: pilot = %d\n", __func__, state->dcur.pilot); + dprintk("%s: frequency = %d\n", __func__, state->dcur.frequency); + dprintk("%s: symbol_rate = %d\n", __func__, state->dcur.symbol_rate); + dprintk("%s: FEC = %d (mask/val = 0x%02x/0x%02x)\n", __func__, + state->dcur.fec, state->dcur.fec_mask, state->dcur.fec_val); + dprintk("%s: Inversion = %d (val = 0x%02x)\n", __func__, + state->dcur.inversion, state->dcur.inversion_val); + + /* This is also done in advise/acquire on HVR4000 but not on LITE */ + if (state->config->set_ts_params) + state->config->set_ts_params(fe, 0); + + /* Set/Reset B/W */ + cmd.args[0x00] = CMD_BANDWIDTH; + cmd.args[0x01] = 0x01; + cmd.len= 0x02; + ret = cx24116_cmd_execute(fe, &cmd); + if (ret != 0) + return ret; + + /* Prepare a tune request */ + cmd.args[0x00] = CMD_TUNEREQUEST; + + /* Frequency */ + cmd.args[0x01] = (state->dcur.frequency & 0xff0000) >> 16; + cmd.args[0x02] = (state->dcur.frequency & 0x00ff00) >> 8; + cmd.args[0x03] = (state->dcur.frequency & 0x0000ff); + + /* Symbol Rate */ + cmd.args[0x04] = ((state->dcur.symbol_rate / 1000) & 0xff00) >> 8; + cmd.args[0x05] = ((state->dcur.symbol_rate / 1000) & 0x00ff); + + /* Automatic Inversion */ + cmd.args[0x06] = state->dcur.inversion_val; + + /* Modulation / FEC & Pilot Off */ + cmd.args[0x07] = state->dcur.fec_val; + + if (state->dcur.pilot == PILOT_ON) + cmd.args[0x07] |= CX24116_PILOT; + + cmd.args[0x08] = CX24116_SEARCH_RANGE_KHZ >> 8; + cmd.args[0x09] = CX24116_SEARCH_RANGE_KHZ & 0xff; + cmd.args[0x0a] = 0x00; + cmd.args[0x0b] = 0x00; + cmd.args[0x0c] = state->dcur.rolloff_val; + cmd.args[0x0d] = state->dcur.fec_mask; + + if (state->dcur.symbol_rate > 30000000) { + cmd.args[0x0e] = 0x04; + cmd.args[0x0f] = 0x00; + cmd.args[0x10] = 0x01; + cmd.args[0x11] = 0x77; + cmd.args[0x12] = 0x36; + cx24116_writereg(state, CX24116_REG_CLKDIV, 0x44); + cx24116_writereg(state, CX24116_REG_RATEDIV, 0x01); + } else { + cmd.args[0x0e] = 0x06; + cmd.args[0x0f] = 0x00; + cmd.args[0x10] = 0x00; + cmd.args[0x11] = 0xFA; + cmd.args[0x12] = 0x24; + cx24116_writereg(state, CX24116_REG_CLKDIV, 0x46); + cx24116_writereg(state, CX24116_REG_RATEDIV, 0x00); + } + + cmd.len= 0x13; + + /* We need to support pilot and non-pilot tuning in the + * driver automatically. This is a workaround for because + * the demod does not support autodetect. + */ + do { + /* Reset status register */ + status = cx24116_readreg(state, CX24116_REG_SSTATUS) & CX24116_SIGNAL_MASK; + cx24116_writereg(state, CX24116_REG_SSTATUS, status); + + /* Tune */ + ret = cx24116_cmd_execute(fe, &cmd); + if( ret != 0 ) + break; + + /* + * Wait for up to 500 ms before retrying + * + * If we are able to tune then generally it occurs within 100ms. + * If it takes longer, try a different toneburst setting. + */ + for(i = 0; i < 50 ; i++) { + cx24116_read_status(fe, &tunerstat); + status = tunerstat & (FE_HAS_SIGNAL | FE_HAS_SYNC); + if(status == (FE_HAS_SIGNAL | FE_HAS_SYNC)) { + dprintk("%s: Tuned\n",__func__); + goto tuned; + } + msleep(10); + } + + dprintk("%s: Not tuned\n",__func__); + + /* Toggle pilot bit when in auto-pilot */ + if(state->dcur.pilot == PILOT_AUTO) + cmd.args[0x07] ^= CX24116_PILOT; + } + while(--retune); + +tuned: /* Set/Reset B/W */ + cmd.args[0x00] = CMD_BANDWIDTH; + cmd.args[0x01] = 0x00; + cmd.len= 0x02; + ret = cx24116_cmd_execute(fe, &cmd); + if (ret != 0) + return ret; + + return ret; +} + +static struct dvb_frontend_ops cx24116_ops = { + + .info = { + .name = "Conexant CX24116/CX24118", + .type = FE_QPSK, + .frequency_min = 950000, + .frequency_max = 2150000, + .frequency_stepsize = 1011, /* kHz for QPSK frontends */ + .frequency_tolerance = 5000, + .symbol_rate_min = 1000000, + .symbol_rate_max = 45000000, + .caps = FE_CAN_INVERSION_AUTO | + FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | + FE_CAN_FEC_4_5 | FE_CAN_FEC_5_6 | FE_CAN_FEC_6_7 | + FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_RECOVER + }, + + .release = cx24116_release, + + .init = cx24116_initfe, + .sleep = cx24116_sleep, + .read_status = cx24116_read_status, + .read_ber = cx24116_read_ber, + .read_signal_strength = cx24116_read_signal_strength, + .read_snr = cx24116_read_snr, + .read_ucblocks = cx24116_read_ucblocks, + .set_tone = cx24116_set_tone, + .set_voltage = cx24116_set_voltage, + .diseqc_send_master_cmd = cx24116_send_diseqc_msg, + .diseqc_send_burst = cx24116_diseqc_send_burst, + + .set_property = cx24116_set_property, + .get_property = cx24116_get_property, + .set_frontend = cx24116_set_frontend, +}; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Activates frontend debugging (default:0)"); + +module_param(toneburst, int, 0644); +MODULE_PARM_DESC(toneburst, "DiSEqC toneburst 0=OFF, 1=TONE CACHE, 2=MESSAGE CACHE (default:1)"); + +MODULE_DESCRIPTION("DVB Frontend module for Conexant cx24116/cx24118 hardware"); +MODULE_AUTHOR("Steven Toth"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(cx24116_attach); diff --git a/linux/drivers/media/dvb/frontends/cx24116.h b/linux/drivers/media/dvb/frontends/cx24116.h new file mode 100644 index 000000000..8dbcec268 --- /dev/null +++ b/linux/drivers/media/dvb/frontends/cx24116.h @@ -0,0 +1,53 @@ +/* + Conexant cx24116/cx24118 - DVBS/S2 Satellite demod/tuner driver + + Copyright (C) 2006 Steven Toth <stoth@linuxtv.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 CX24116_H +#define CX24116_H + +#include <linux/dvb/frontend.h> + +struct cx24116_config +{ + /* the demodulator's i2c address */ + u8 demod_address; + + /* Need to set device param for start_dma */ + int (*set_ts_params)(struct dvb_frontend* fe, int is_punctured); + + /* Need to reset device during firmware loading */ + int (*reset_device)(struct dvb_frontend* fe); + + /* Need to set MPEG parameters */ + u8 mpg_clk_pos_pol:0x02; +}; + +#if defined(CONFIG_DVB_CX24116) || defined(CONFIG_DVB_CX24116_MODULE) +extern struct dvb_frontend* cx24116_attach(const struct cx24116_config* config, + struct i2c_adapter* i2c); +#else +static inline struct dvb_frontend* cx24116_attach(const struct cx24116_config* config, + struct i2c_adapter* i2c) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __FUNCTION__); + return NULL; +} +#endif // CONFIG_DVB_CX24116 + +#endif /* CX24116_H */ diff --git a/linux/drivers/media/dvb/frontends/si21xx.c b/linux/drivers/media/dvb/frontends/si21xx.c new file mode 100644 index 000000000..bad4ce9b8 --- /dev/null +++ b/linux/drivers/media/dvb/frontends/si21xx.c @@ -0,0 +1,1049 @@ +/* DVB compliant Linux driver for the DVB-S si2109/2110 demodulator +* +* Copyright (C) 2008 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. +* +*/ +#include <linux/version.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <asm/div64.h> + +#include "dvb_frontend.h" +#include "si21xx.h" + +#define REVISION_REG 0x00 +#define SYSTEM_MODE_REG 0x01 +#define TS_CTRL_REG_1 0x02 +#define TS_CTRL_REG_2 0x03 +#define PIN_CTRL_REG_1 0x04 +#define PIN_CTRL_REG_2 0x05 +#define LOCK_STATUS_REG_1 0x0f +#define LOCK_STATUS_REG_2 0x10 +#define ACQ_STATUS_REG 0x11 +#define ACQ_CTRL_REG_1 0x13 +#define ACQ_CTRL_REG_2 0x14 +#define PLL_DIVISOR_REG 0x15 +#define COARSE_TUNE_REG 0x16 +#define FINE_TUNE_REG_L 0x17 +#define FINE_TUNE_REG_H 0x18 + +#define ANALOG_AGC_POWER_LEVEL_REG 0x28 +#define CFO_ESTIMATOR_CTRL_REG_1 0x29 +#define CFO_ESTIMATOR_CTRL_REG_2 0x2a +#define CFO_ESTIMATOR_CTRL_REG_3 0x2b + +#define SYM_RATE_ESTIMATE_REG_L 0x31 +#define SYM_RATE_ESTIMATE_REG_M 0x32 +#define SYM_RATE_ESTIMATE_REG_H 0x33 + +#define CFO_ESTIMATOR_OFFSET_REG_L 0x36 +#define CFO_ESTIMATOR_OFFSET_REG_H 0x37 +#define CFO_ERROR_REG_L 0x38 +#define CFO_ERROR_REG_H 0x39 +#define SYM_RATE_ESTIMATOR_CTRL_REG 0x3a + +#define SYM_RATE_REG_L 0x3f +#define SYM_RATE_REG_M 0x40 +#define SYM_RATE_REG_H 0x41 +#define SYM_RATE_ESTIMATOR_MAXIMUM_REG 0x42 +#define SYM_RATE_ESTIMATOR_MINIMUM_REG 0x43 + +#define C_N_ESTIMATOR_CTRL_REG 0x7c +#define C_N_ESTIMATOR_THRSHLD_REG 0x7d +#define C_N_ESTIMATOR_LEVEL_REG_L 0x7e +#define C_N_ESTIMATOR_LEVEL_REG_H 0x7f + +#define BLIND_SCAN_CTRL_REG 0x80 + +#define LSA_CTRL_REG_1 0x8D +#define SPCTRM_TILT_CORR_THRSHLD_REG 0x8f +#define ONE_DB_BNDWDTH_THRSHLD_REG 0x90 +#define TWO_DB_BNDWDTH_THRSHLD_REG 0x91 +#define THREE_DB_BNDWDTH_THRSHLD_REG 0x92 +#define INBAND_POWER_THRSHLD_REG 0x93 +#define REF_NOISE_LVL_MRGN_THRSHLD_REG 0x94 + +#define VIT_SRCH_CTRL_REG_1 0xa0 +#define VIT_SRCH_CTRL_REG_2 0xa1 +#define VIT_SRCH_CTRL_REG_3 0xa2 +#define VIT_SRCH_STATUS_REG 0xa3 +#define VITERBI_BER_COUNT_REG_L 0xab +#define REED_SOLOMON_CTRL_REG 0xb0 +#define REED_SOLOMON_ERROR_COUNT_REG_L 0xb1 +#define PRBS_CTRL_REG 0xb5 + +#define LNB_CTRL_REG_1 0xc0 +#define LNB_CTRL_REG_2 0xc1 +#define LNB_CTRL_REG_3 0xc2 +#define LNB_CTRL_REG_4 0xc3 +#define LNB_CTRL_STATUS_REG 0xc4 +#define LNB_FIFO_REGS_0 0xc5 +#define LNB_FIFO_REGS_1 0xc6 +#define LNB_FIFO_REGS_2 0xc7 +#define LNB_FIFO_REGS_3 0xc8 +#define LNB_FIFO_REGS_4 0xc9 +#define LNB_FIFO_REGS_5 0xca +#define LNB_SUPPLY_CTRL_REG_1 0xcb +#define LNB_SUPPLY_CTRL_REG_2 0xcc +#define LNB_SUPPLY_CTRL_REG_3 0xcd +#define LNB_SUPPLY_CTRL_REG_4 0xce +#define LNB_SUPPLY_STATUS_REG 0xcf + +#define FALSE 0 +#define TRUE 1 +#define FAIL -1 +#define PASS 0 + +#define ALLOWABLE_FS_COUNT 10 +#define STATUS_BER 0 +#define STATUS_UCBLOCKS 1 + +static int debug; +#define dprintk(args...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG "si21xx: " args); \ + } while (0) + +enum { + ACTIVE_HIGH, + ACTIVE_LOW +}; +enum { + BYTE_WIDE, + BIT_WIDE +}; +enum { + CLK_GAPPED_MODE, + CLK_CONTINUOUS_MODE +}; +enum { + RISING_EDGE, + FALLING_EDGE +}; +enum { + MSB_FIRST, + LSB_FIRST +}; +enum { + SERIAL, + PARALLEL +}; + +struct si21xx_state { + struct i2c_adapter *i2c; + const struct si21xx_config *config; + struct dvb_frontend frontend; + u8 initialised:1; + int errmode; + int fs; /*Sampling rate of the ADC in MHz*/ +}; + +/* register default initialization */ +static u8 serit_sp1511lhb_inittab[] = { + 0x01, 0x28, /* set i2c_inc_disable */ + 0x20, 0x03, + 0x27, 0x20, + 0xe0, 0x45, + 0xe1, 0x08, + 0xfe, 0x01, + 0x01, 0x28, + 0x89, 0x09, + 0x04, 0x80, + 0x05, 0x01, + 0x06, 0x00, + 0x20, 0x03, + 0x24, 0x88, + 0x29, 0x09, + 0x2a, 0x0f, + 0x2c, 0x10, + 0x2d, 0x19, + 0x2e, 0x08, + 0x2f, 0x10, + 0x30, 0x19, + 0x34, 0x20, + 0x35, 0x03, + 0x45, 0x02, + 0x46, 0x45, + 0x47, 0xd0, + 0x48, 0x00, + 0x49, 0x40, + 0x4a, 0x03, + 0x4c, 0xfd, + 0x4f, 0x2e, + 0x50, 0x2e, + 0x51, 0x10, + 0x52, 0x10, + 0x56, 0x92, + 0x59, 0x00, + 0x5a, 0x2d, + 0x5b, 0x33, + 0x5c, 0x1f, + 0x5f, 0x76, + 0x62, 0xc0, + 0x63, 0xc0, + 0x64, 0xf3, + 0x65, 0xf3, + 0x79, 0x40, + 0x6a, 0x40, + 0x6b, 0x0a, + 0x6c, 0x80, + 0x6d, 0x27, + 0x71, 0x06, + 0x75, 0x60, + 0x78, 0x00, + 0x79, 0xb5, + 0x7c, 0x05, + 0x7d, 0x1a, + 0x87, 0x55, + 0x88, 0x72, + 0x8f, 0x08, + 0x90, 0xe0, + 0x94, 0x40, + 0xa0, 0x3f, + 0xa1, 0xc0, + 0xa4, 0xcc, + 0xa5, 0x66, + 0xa6, 0x66, + 0xa7, 0x7b, + 0xa8, 0x7b, + 0xa9, 0x7b, + 0xaa, 0x9a, + 0xed, 0x04, + 0xad, 0x00, + 0xae, 0x03, + 0xcc, 0xab, + 0x01, 0x08, + 0xff, 0xff +}; + +/* low level read/writes */ +static int si21_writeregs(struct si21xx_state *state, u8 reg1, + u8 *data, int len) +{ + int ret; + u8 buf[60];/* = { reg1, data };*/ + struct i2c_msg msg = { + .addr = state->config->demod_address, + .flags = 0, + .buf = buf, + .len = len + 1 + }; + + msg.buf[0] = reg1; + memcpy(msg.buf + 1, data, len); + + ret = i2c_transfer(state->i2c, &msg, 1); + + if (ret != 1) + dprintk("%s: writereg error (reg1 == 0x%02x, data == 0x%02x, " + "ret == %i)\n", __func__, reg1, data[0], ret); + + return (ret != 1) ? -EREMOTEIO : 0; +} + +static int si21_writereg(struct si21xx_state *state, u8 reg, u8 data) +{ + int ret; + u8 buf[] = { reg, data }; + struct i2c_msg msg = { + .addr = state->config->demod_address, + .flags = 0, + .buf = buf, + .len = 2 + }; + + ret = i2c_transfer(state->i2c, &msg, 1); + + if (ret != 1) + dprintk("%s: writereg error (reg == 0x%02x, data == 0x%02x, " + "ret == %i)\n", __func__, reg, data, ret); + + return (ret != 1) ? -EREMOTEIO : 0; +} + +static int si21_write(struct dvb_frontend *fe, u8 *buf, int len) +{ + struct si21xx_state *state = fe->demodulator_priv; + + if (len != 2) + return -EINVAL; + + return si21_writereg(state, buf[0], buf[1]); +} + +static u8 si21_readreg(struct si21xx_state *state, u8 reg) +{ + int ret; + u8 b0[] = { reg }; + u8 b1[] = { 0 }; + struct i2c_msg msg[] = { + { + .addr = state->config->demod_address, + .flags = 0, + .buf = b0, + .len = 1 + }, { + .addr = state->config->demod_address, + .flags = I2C_M_RD, + .buf = b1, + .len = 1 + } + }; + + ret = i2c_transfer(state->i2c, msg, 2); + + if (ret != 2) + dprintk("%s: readreg error (reg == 0x%02x, ret == %i)\n", + __func__, reg, ret); + + return b1[0]; +} + +static int si21_readregs(struct si21xx_state *state, u8 reg1, u8 *b, u8 len) +{ + int ret; + struct i2c_msg msg[] = { + { + .addr = state->config->demod_address, + .flags = 0, + .buf = ®1, + .len = 1 + }, { + .addr = state->config->demod_address, + .flags = I2C_M_RD, + .buf = b, + .len = len + } + }; + + ret = i2c_transfer(state->i2c, msg, 2); + + if (ret != 2) + dprintk("%s: readreg error (ret == %i)\n", __func__, ret); + + return ret == 2 ? 0 : -1; +} +#if 0 +static int si21xx_wait_diseqc_fifo(struct si21xx_state *state, int timeout) +{ + unsigned long start = jiffies; + + dprintk("%s\n", __func__); + + while (((si21_readreg(state, 0xc4) >> 7) & 1) == 0) { + if (jiffies - start > timeout) { + dprintk("%s: timeout!!\n", __func__); + return -ETIMEDOUT; + } + msleep(10); + }; + + return 0; +} +#endif + +static int si21xx_wait_diseqc_idle(struct si21xx_state *state, int timeout) +{ + unsigned long start = jiffies; + + dprintk("%s\n", __func__); + + while ((si21_readreg(state, LNB_CTRL_REG_1) & 0x8) == 8) { + if (jiffies - start > timeout) { + dprintk("%s: timeout!!\n", __func__); + return -ETIMEDOUT; + } + msleep(10); + }; + + return 0; +} + +static int si21xx_set_symbolrate(struct dvb_frontend *fe, u32 srate) +{ + struct si21xx_state *state = fe->demodulator_priv; + u32 sym_rate, data_rate; + int i; + u8 sym_rate_bytes[3]; + + dprintk("%s : srate = %i\n", __func__ , srate); + + if ((srate < 1000000) || (srate > 45000000)) + return -EINVAL; + + data_rate = srate; + sym_rate = 0; + + for (i = 0; i < 4; ++i) { + sym_rate /= 100; + sym_rate = sym_rate + ((data_rate % 100) * 0x800000) / + state->fs; + data_rate /= 100; + } + for (i = 0; i < 3; ++i) + sym_rate_bytes[i] = (u8)((sym_rate >> (i * 8)) & 0xff); + + si21_writeregs(state, SYM_RATE_REG_L, sym_rate_bytes, 0x03); + + return 0; +} + +static int si21xx_send_diseqc_msg(struct dvb_frontend *fe, + struct dvb_diseqc_master_cmd *m) +{ + struct si21xx_state *state = fe->demodulator_priv; +#if 0 + u8 val; + int i; + + if (si21xx_wait_diseqc_idle(state, 100) < 0) + return -ETIMEDOUT; + + val = si21_readreg(state, 0x08); + /* DiSEqC mode */ + if (si21_writereg(state, 0x08, (val & ~0x7) | 0x6)) + return -EREMOTEIO; + + for (i = 0; i < m->msg_len; i++) { + if (si21xx_wait_diseqc_fifo(state, 100) < 0) + return -ETIMEDOUT; + + if (si21_writereg(state, 0x09, m->msg[i])) + return -EREMOTEIO; + } + + if (si21xx_wait_diseqc_idle(state, 100) < 0) + return -ETIMEDOUT; + + return 0; + } + +int si21xx_set_lnb_msg(state, lnb_cmd) +{ +#endif + u8 lnb_status; + u8 LNB_CTRL_1; + int status; + + dprintk("%s\n", __func__); + + status = PASS; + LNB_CTRL_1 = 0; + + status |= si21_readregs(state, LNB_CTRL_STATUS_REG, &lnb_status, 0x01); + status |= si21_readregs(state, LNB_CTRL_REG_1, &lnb_status, 0x01); + + /*fill the FIFO*/ + status |= si21_writeregs(state, LNB_FIFO_REGS_0, m->msg, m->msg_len); + + LNB_CTRL_1 = (lnb_status & 0x70); +#if 0 + LNB_CTRL_1 |= voltage << 6; /*voltage select*/ + LNB_CTRL_1 |= tone << 5; /*continuous tone selection*/ + LNB_CTRL_1 |= burst << 4; /*tone burst selection*/ + LNB_CTRL_1 |= mmsg << 3; /*more messages indicator*/ +#endif + LNB_CTRL_1 |= m->msg_len; + + LNB_CTRL_1 |= 0x80; /* begin LNB signaling */ + + status |= si21_writeregs(state, LNB_CTRL_REG_1, &LNB_CTRL_1, 0x01); + + return status; +} + +static int si21xx_send_diseqc_burst(struct dvb_frontend *fe, + fe_sec_mini_cmd_t burst) +{ + struct si21xx_state *state = fe->demodulator_priv; + u8 val; + + dprintk("%s\n", __func__); + + if (si21xx_wait_diseqc_idle(state, 100) < 0) + return -ETIMEDOUT; + + val = (0x80 | si21_readreg(state, 0xc1)); +#if 0 + /* burst mode */ + if (si21_writereg(state, LNB_CTRL_REG_1, (val & ~0x10)) + return -EREMOTEIO; +#endif + if (si21_writereg(state, LNB_CTRL_REG_1, + burst == SEC_MINI_A ? (val & ~0x10) : (val | 0x10))) + return -EREMOTEIO; + + if (si21xx_wait_diseqc_idle(state, 100) < 0) + return -ETIMEDOUT; + + if (si21_writereg(state, LNB_CTRL_REG_1, val)) + return -EREMOTEIO; + + return 0; +} +/* 30.06.2008 */ +static int si21xx_set_tone(struct dvb_frontend *fe, fe_sec_tone_mode_t tone) +{ + struct si21xx_state *state = fe->demodulator_priv; + u8 val; + + dprintk("%s\n", __func__); +#if 0 + if (si21xx_wait_diseqc_idle(state, 100) < 0) + return -ETIMEDOUT; +#endif + val = (0x80 | si21_readreg(state, LNB_CTRL_REG_1)); + + switch (tone) { + case SEC_TONE_ON: + return si21_writereg(state, LNB_CTRL_REG_1, val | 0x20); + + case SEC_TONE_OFF: + return si21_writereg(state, LNB_CTRL_REG_1, (val & ~0x20)); + + default: + return -EINVAL; + } +} + +static int si21xx_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t volt) +{ + struct si21xx_state *state = fe->demodulator_priv; + + u8 val; + dprintk("%s: %s\n", __func__, + volt == SEC_VOLTAGE_13 ? "SEC_VOLTAGE_13" : + volt == SEC_VOLTAGE_18 ? "SEC_VOLTAGE_18" : "??"); + + + val = (0x80 | si21_readreg(state, LNB_CTRL_REG_1)); + + switch (volt) { + case SEC_VOLTAGE_18: + return si21_writereg(state, LNB_CTRL_REG_1, val | 0x40); + break; + case SEC_VOLTAGE_13: + return si21_writereg(state, LNB_CTRL_REG_1, (val & ~0x40)); + break; + default: + return -EINVAL; + }; +} + +static int si21xx_init(struct dvb_frontend *fe) +{ + struct si21xx_state *state = fe->demodulator_priv; + int i; + int status = 0; + u8 reg1; + u8 val; + u8 reg2[2]; + + dprintk("%s\n", __func__); + + for (i = 0; ; i += 2) { + reg1 = serit_sp1511lhb_inittab[i]; + val = serit_sp1511lhb_inittab[i+1]; + if (reg1 == 0xff && val == 0xff) + break; + si21_writeregs(state, reg1, &val, 1); + } + + /*DVB QPSK SYSTEM MODE REG*/ + reg1 = 0x08; + si21_writeregs(state, SYSTEM_MODE_REG, ®1, 0x01); + + /*transport stream config*/ + /* + mode = PARALLEL; + sdata_form = LSB_FIRST; + clk_edge = FALLING_EDGE; + clk_mode = CLK_GAPPED_MODE; + strt_len = BYTE_WIDE; + sync_pol = ACTIVE_HIGH; + val_pol = ACTIVE_HIGH; + err_pol = ACTIVE_HIGH; + sclk_rate = 0x00; + parity = 0x00 ; + data_delay = 0x00; + clk_delay = 0x00; + pclk_smooth = 0x00; + */ + reg2[0] = + PARALLEL + (LSB_FIRST << 1) + + (FALLING_EDGE << 2) + (CLK_GAPPED_MODE << 3) + + (BYTE_WIDE << 4) + (ACTIVE_HIGH << 5) + + (ACTIVE_HIGH << 6) + (ACTIVE_HIGH << 7); + + reg2[1] = 0; + /* sclk_rate + (parity << 2) + + (data_delay << 3) + (clk_delay << 4) + + (pclk_smooth << 5); + */ + status |= si21_writeregs(state, TS_CTRL_REG_1, reg2, 0x02); + if (status != 0) + dprintk(" %s : TS Set Error\n", __func__); + +#if 0 + lnb_cmd.tone = ON; /* 22khz continuous */ + lnb_cmd.mmsg = OFF; /* diseqc more message */ + /* diseqc command */ + lnb_cmd.msg[6] = { "0xE0", "0x10", "0x38", "0xF0" }; + lnb_cmd.msg_len = OFF; /* diseqc command length */ + lnb_cmd.burst = OFF; /* tone burst a,b */ + lnb_cmd.volt = OFF; /* 13v 18v select */ + + status |= si21xx_set_lnb_msg(state, lnb_cmd); + if (status != PASS) + dprintk("%s LNB Set Error\n", __func__); +#endif + return 0; + +} + +static int si21_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct si21xx_state *state = fe->demodulator_priv; + u8 regs_read[2]; + u8 reg_read; + u8 i; + u8 lock; + u8 signal = si21_readreg(state, ANALOG_AGC_POWER_LEVEL_REG); + + si21_readregs(state, LOCK_STATUS_REG_1, regs_read, 0x02); + reg_read = 0; + + for (i = 0; i < 7; ++i) + reg_read |= ((regs_read[0] >> i) & 0x01) << (6 - i); + + lock = ((reg_read & 0x7f) | (regs_read[1] & 0x80)); + + dprintk("%s : FE_READ_STATUS : VSTATUS: 0x%02x\n", __func__, lock); + *status = 0; + + if (signal > 10) + *status |= FE_HAS_SIGNAL; + + if (lock & 0x2) + *status |= FE_HAS_CARRIER; + + if (lock & 0x20) + *status |= FE_HAS_VITERBI; + + if (lock & 0x40) + *status |= FE_HAS_SYNC; + + if ((lock & 0x7b) == 0x7b) + *status |= FE_HAS_LOCK; + + return 0; +} + +static int si21_read_signal_strength(struct dvb_frontend *fe, u16 *strength) +{ + struct si21xx_state *state = fe->demodulator_priv; + + /*status = si21_readreg(state, ANALOG_AGC_POWER_LEVEL_REG, + (u8*)agclevel, 0x01);*/ + + u16 signal = (3 * si21_readreg(state, 0x27) * + si21_readreg(state, 0x28)); + + dprintk("%s : AGCPWR: 0x%02x%02x, signal=0x%04x\n", __func__, + si21_readreg(state, 0x27), + si21_readreg(state, 0x28), (int) signal); + + signal <<= 4; + *strength = signal; + + return 0; +} + +static int si21_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct si21xx_state *state = fe->demodulator_priv; + + dprintk("%s\n", __func__); + + if (state->errmode != STATUS_BER) + return 0; + + *ber = (si21_readreg(state, 0x1d) << 8) | + si21_readreg(state, 0x1e); + + return 0; +} + +static int si21_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct si21xx_state *state = fe->demodulator_priv; + + s32 xsnr = 0xffff - ((si21_readreg(state, 0x24) << 8) | + si21_readreg(state, 0x25)); + xsnr = 3 * (xsnr - 0xa100); + *snr = (xsnr > 0xffff) ? 0xffff : (xsnr < 0) ? 0 : xsnr; + + dprintk("%s\n", __func__); + + return 0; +} + +static int si21_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + struct si21xx_state *state = fe->demodulator_priv; + + dprintk("%s\n", __func__); + + if (state->errmode != STATUS_UCBLOCKS) + *ucblocks = 0; + else + *ucblocks = (si21_readreg(state, 0x1d) << 8) | + si21_readreg(state, 0x1e); + + return 0; +} + +/* initiates a channel acquisition sequence + using the specified symbol rate and code rate */ +static int si21xx_setacquire(struct dvb_frontend *fe, int symbrate, + fe_code_rate_t crate) +{ + + struct si21xx_state *state = fe->demodulator_priv; + u8 coderates[] = { + 0x0, 0x01, 0x02, 0x04, 0x00, + 0x8, 0x10, 0x20, 0x00, 0x3f + }; + + u8 coderate_ptr; + int status; + u8 start_acq = 0x80; + u8 reg, regs[3]; + + dprintk("%s\n", __func__); + + status = PASS; + coderate_ptr = coderates[crate]; + + si21xx_set_symbolrate(fe, symbrate); + + /* write code rates to use in the Viterbi search */ + status |= si21_writeregs(state, + VIT_SRCH_CTRL_REG_1, + &coderate_ptr, 0x01); + + /* clear acq_start bit */ + status |= si21_readregs(state, ACQ_CTRL_REG_2, ®, 0x01); + reg &= ~start_acq; + status |= si21_writeregs(state, ACQ_CTRL_REG_2, ®, 0x01); + + /* use new Carrier Frequency Offset Estimator (QuickLock) */ + regs[0] = 0xCB; + regs[1] = 0x40; + regs[2] = 0xCB; + + status |= si21_writeregs(state, + TWO_DB_BNDWDTH_THRSHLD_REG, + ®s[0], 0x03); + reg = 0x56; + status |= si21_writeregs(state, + LSA_CTRL_REG_1, ®, 1); + reg = 0x05; + status |= si21_writeregs(state, + BLIND_SCAN_CTRL_REG, ®, 1); + /* start automatic acq */ + status |= si21_writeregs(state, + ACQ_CTRL_REG_2, &start_acq, 0x01); + + return status; +} + +static int si21xx_set_property(struct dvb_frontend *fe, struct dtv_property *p) +{ + dprintk("%s(..)\n", __func__); + return 0; +} + +static int si21xx_get_property(struct dvb_frontend *fe, struct dtv_property *p) +{ + dprintk("%s(..)\n", __func__); + return 0; +} + +static int si21xx_set_frontend(struct dvb_frontend *fe, + struct dvb_frontend_parameters *dfp) +{ + struct si21xx_state *state = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + + /* freq Channel carrier frequency in KHz (i.e. 1550000 KHz) + datarate Channel symbol rate in Sps (i.e. 22500000 Sps)*/ + + /* in MHz */ + unsigned char coarse_tune_freq; + int fine_tune_freq; + unsigned char sample_rate = 0; + /* boolean */ + unsigned int inband_interferer_ind; + + /* INTERMEDIATE VALUES */ + int icoarse_tune_freq; /* MHz */ + int ifine_tune_freq; /* MHz */ + unsigned int band_high; + unsigned int band_low; + unsigned int x1; + unsigned int x2; + int i; + unsigned int inband_interferer_div2[ALLOWABLE_FS_COUNT] = { + FALSE, FALSE, FALSE, FALSE, FALSE, + FALSE, FALSE, FALSE, FALSE, FALSE + }; + unsigned int inband_interferer_div4[ALLOWABLE_FS_COUNT] = { + FALSE, FALSE, FALSE, FALSE, FALSE, + FALSE, FALSE, FALSE, FALSE, FALSE + }; + + int status; + + /* allowable sample rates for ADC in MHz */ + int afs[ALLOWABLE_FS_COUNT] = { 200, 192, 193, 194, 195, + 196, 204, 205, 206, 207 + }; + /* in MHz */ + int if_limit_high; + int if_limit_low; + int lnb_lo; + int lnb_uncertanity; + + int rf_freq; + int data_rate; + unsigned char regs[4]; + + dprintk("%s : FE_SET_FRONTEND\n", __func__); + + if (c->delivery_system != SYS_DVBS) { + dprintk("%s: unsupported delivery system selected (%d)\n", + __func__, c->delivery_system); + return -EOPNOTSUPP; + } + + for (i = 0; i < ALLOWABLE_FS_COUNT; ++i) + inband_interferer_div2[i] = inband_interferer_div4[i] = FALSE; + + if_limit_high = -700000; + if_limit_low = -100000; + /* in MHz */ + lnb_lo = 0; + lnb_uncertanity = 0; + + rf_freq = 10 * c->frequency ; + data_rate = c->symbol_rate / 100; + + status = PASS; + + band_low = (rf_freq - lnb_lo) - ((lnb_uncertanity * 200) + + (data_rate * 135)) / 200; + + band_high = (rf_freq - lnb_lo) + ((lnb_uncertanity * 200) + + (data_rate * 135)) / 200; + + + icoarse_tune_freq = 100000 * + (((rf_freq - lnb_lo) - + (if_limit_low + if_limit_high) / 2) + / 100000); + + ifine_tune_freq = (rf_freq - lnb_lo) - icoarse_tune_freq ; + + for (i = 0; i < ALLOWABLE_FS_COUNT; ++i) { + x1 = ((rf_freq - lnb_lo) / (afs[i] * 2500)) * + (afs[i] * 2500) + afs[i] * 2500; + + x2 = ((rf_freq - lnb_lo) / (afs[i] * 2500)) * + (afs[i] * 2500); + + if (((band_low < x1) && (x1 < band_high)) || + ((band_low < x2) && (x2 < band_high))) + inband_interferer_div4[i] = TRUE; + + } + + for (i = 0; i < ALLOWABLE_FS_COUNT; ++i) { + x1 = ((rf_freq - lnb_lo) / (afs[i] * 5000)) * + (afs[i] * 5000) + afs[i] * 5000; + + x2 = ((rf_freq - lnb_lo) / (afs[i] * 5000)) * + (afs[i] * 5000); + + if (((band_low < x1) && (x1 < band_high)) || + ((band_low < x2) && (x2 < band_high))) + inband_interferer_div2[i] = TRUE; + } + + inband_interferer_ind = TRUE; + for (i = 0; i < ALLOWABLE_FS_COUNT; ++i) + inband_interferer_ind &= inband_interferer_div2[i] | + inband_interferer_div4[i]; + + if (inband_interferer_ind) { + for (i = 0; i < ALLOWABLE_FS_COUNT; ++i) { + if (inband_interferer_div2[i] == FALSE) { + sample_rate = (u8) afs[i]; + break; + } + } + } else { + for (i = 0; i < ALLOWABLE_FS_COUNT; ++i) { + if ((inband_interferer_div2[i] | + inband_interferer_div4[i]) == FALSE) { + sample_rate = (u8) afs[i]; + break; + } + } + + } + + if (sample_rate > 207 || sample_rate < 192) + sample_rate = 200; + + fine_tune_freq = ((0x4000 * (ifine_tune_freq / 10)) / + ((sample_rate) * 1000)); + + coarse_tune_freq = (u8)(icoarse_tune_freq / 100000); + + regs[0] = sample_rate; + regs[1] = coarse_tune_freq; + regs[2] = fine_tune_freq & 0xFF; + regs[3] = fine_tune_freq >> 8 & 0xFF; + + status |= si21_writeregs(state, PLL_DIVISOR_REG, ®s[0], 0x04); + + state->fs = sample_rate;/*ADC MHz*/ + si21xx_setacquire(fe, c->symbol_rate, c->fec_inner); + + return 0; +} + +static int si21xx_sleep(struct dvb_frontend *fe) +{ + struct si21xx_state *state = fe->demodulator_priv; + u8 regdata; + + dprintk("%s\n", __func__); + + si21_readregs(state, SYSTEM_MODE_REG, ®data, 0x01); + regdata |= 1 << 6; + si21_writeregs(state, SYSTEM_MODE_REG, ®data, 0x01); + state->initialised = 0; + + return 0; +} + +static void si21xx_release(struct dvb_frontend *fe) +{ + struct si21xx_state *state = fe->demodulator_priv; + + dprintk("%s\n", __func__); + + kfree(state); +} + +static struct dvb_frontend_ops si21xx_ops = { + + .info = { + .name = "SL SI21XX DVB-S", + .type = FE_QPSK, + .frequency_min = 950000, + .frequency_max = 2150000, + .frequency_stepsize = 125, /* kHz for QPSK frontends */ + .frequency_tolerance = 0, + .symbol_rate_min = 1000000, + .symbol_rate_max = 45000000, + .symbol_rate_tolerance = 500, /* ppm */ + .caps = FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | + FE_CAN_QPSK | + FE_CAN_FEC_AUTO + }, + + .release = si21xx_release, + .init = si21xx_init, + .sleep = si21xx_sleep, + .write = si21_write, + .read_status = si21_read_status, + .read_ber = si21_read_ber, + .read_signal_strength = si21_read_signal_strength, + .read_snr = si21_read_snr, + .read_ucblocks = si21_read_ucblocks, + .diseqc_send_master_cmd = si21xx_send_diseqc_msg, + .diseqc_send_burst = si21xx_send_diseqc_burst, + .set_tone = si21xx_set_tone, + .set_voltage = si21xx_set_voltage, + + .set_property = si21xx_set_property, + .get_property = si21xx_get_property, + .set_frontend = si21xx_set_frontend, +}; + +struct dvb_frontend *si21xx_attach(const struct si21xx_config *config, + struct i2c_adapter *i2c) +{ + struct si21xx_state *state = NULL; + int id; + + dprintk("%s\n", __func__); + + /* allocate memory for the internal state */ + state = kmalloc(sizeof(struct si21xx_state), GFP_KERNEL); + if (state == NULL) + goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + state->initialised = 0; + state->errmode = STATUS_BER; + + /* check if the demod is there */ + id = si21_readreg(state, SYSTEM_MODE_REG); + si21_writereg(state, SYSTEM_MODE_REG, id | 0x40); /* standby off */ + msleep(200); + id = si21_readreg(state, 0x00); + + /* register 0x00 contains: + 0x34 for SI2107 + 0x24 for SI2108 + 0x14 for SI2109 + 0x04 for SI2110 + */ + if (id != 0x04 && id != 0x14) + goto error; + + /* create dvb_frontend */ + memcpy(&state->frontend.ops, &si21xx_ops, + sizeof(struct dvb_frontend_ops)); + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} +EXPORT_SYMBOL(si21xx_attach); + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("SL SI21XX DVB Demodulator driver"); +MODULE_AUTHOR("Igor M. Liplianin"); +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/dvb/frontends/si21xx.h b/linux/drivers/media/dvb/frontends/si21xx.h new file mode 100644 index 000000000..141b5b8a5 --- /dev/null +++ b/linux/drivers/media/dvb/frontends/si21xx.h @@ -0,0 +1,37 @@ +#ifndef SI21XX_H +#define SI21XX_H + +#include <linux/dvb/frontend.h> +#include "dvb_frontend.h" + +struct si21xx_config { + /* the demodulator's i2c address */ + u8 demod_address; + + /* minimum delay before retuning */ + int min_delay_ms; +}; + +#if defined(CONFIG_DVB_SI21XX) || \ + (defined(CONFIG_DVB_SI21XX_MODULE) && defined(MODULE)) +extern struct dvb_frontend *si21xx_attach(const struct si21xx_config *config, + struct i2c_adapter *i2c); +#else +static inline struct dvb_frontend *si21xx_attach( + const struct si21xx_config *config, struct i2c_adapter *i2c) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif + +static inline int si21xx_writeregister(struct dvb_frontend *fe, u8 reg, u8 val) +{ + int r = 0; + u8 buf[] = {reg, val}; + if (fe->ops.write) + r = fe->ops.write(fe, buf, 2); + return r; +} + +#endif diff --git a/linux/drivers/media/dvb/frontends/stb6000.c b/linux/drivers/media/dvb/frontends/stb6000.c new file mode 100644 index 000000000..a0ece4278 --- /dev/null +++ b/linux/drivers/media/dvb/frontends/stb6000.c @@ -0,0 +1,255 @@ + /* + Driver for ST STB6000 DVBS Silicon tuner + + Copyright (C) 2008 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. + + 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 "compat.h" +#include <linux/dvb/frontend.h> +#include <asm/types.h> + +#include "stb6000.h" + +static int debug; +#define dprintk(args...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG "stb6000: " args); \ + } while (0) + +struct stb6000_priv { + /* i2c details */ + int i2c_address; + struct i2c_adapter *i2c; + u32 frequency; +}; + +static int stb6000_release(struct dvb_frontend *fe) +{ + kfree(fe->tuner_priv); + fe->tuner_priv = NULL; + return 0; +} + +static int stb6000_sleep(struct dvb_frontend *fe) +{ + struct stb6000_priv *priv = fe->tuner_priv; + int ret; + u8 buf[] = { 10, 0 }; + struct i2c_msg msg = { + .addr = priv->i2c_address, + .flags = 0, + .buf = buf, + .len = 2 + }; + + dprintk("%s:\n", __func__); + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + + ret = i2c_transfer(priv->i2c, &msg, 1); + if (ret != 1) + dprintk("%s: i2c error\n", __func__); + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + + return (ret == 1) ? 0 : ret; +} + +static int stb6000_set_params(struct dvb_frontend *fe, + struct dvb_frontend_parameters *params) +{ + struct stb6000_priv *priv = fe->tuner_priv; + unsigned int n, m; + int ret; + u32 freq_mhz; + int bandwidth; + u8 buf[12]; + struct i2c_msg msg = { + .addr = priv->i2c_address, + .flags = 0, + .buf = buf, + .len = 12 + }; + + dprintk("%s:\n", __func__); + + freq_mhz = params->frequency / 1000; + bandwidth = params->u.qpsk.symbol_rate / 1000000; + + if (bandwidth > 31) + bandwidth = 31; + + if ((freq_mhz > 949) && (freq_mhz < 2151)) { + buf[0] = 0x01; + buf[1] = 0xac; + if (freq_mhz < 1950) + buf[1] = 0xaa; + if (freq_mhz < 1800) + buf[1] = 0xa8; + if (freq_mhz < 1650) + buf[1] = 0xa6; + if (freq_mhz < 1530) + buf[1] = 0xa5; + if (freq_mhz < 1470) + buf[1] = 0xa4; + if (freq_mhz < 1370) + buf[1] = 0xa2; + if (freq_mhz < 1300) + buf[1] = 0xa1; + if (freq_mhz < 1200) + buf[1] = 0xa0; + if (freq_mhz < 1075) + buf[1] = 0xbc; + if (freq_mhz < 1000) + buf[1] = 0xba; + if (freq_mhz < 1075) { + n = freq_mhz / 8; /* vco=lo*4 */ + m = 2; + } else { + n = freq_mhz / 16; /* vco=lo*2 */ + m = 1; + } + buf[2] = n >> 1; + buf[3] = (unsigned char)(((n & 1) << 7) | + (m * freq_mhz - n * 16) | 0x60); + buf[4] = 0x04; + buf[5] = 0x0e; + + buf[6] = (unsigned char)(bandwidth); + + buf[7] = 0xd8; + buf[8] = 0xd0; + buf[9] = 0x50; + buf[10] = 0xeb; + buf[11] = 0x4f; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + + ret = i2c_transfer(priv->i2c, &msg, 1); + if (ret != 1) + dprintk("%s: i2c error\n", __func__); + + udelay(10); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + + buf[0] = 0x07; + buf[1] = 0xdf; + buf[2] = 0xd0; + buf[3] = 0x50; + buf[4] = 0xfb; + msg.len = 5; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + + ret = i2c_transfer(priv->i2c, &msg, 1); + if (ret != 1) + dprintk("%s: i2c error\n", __func__); + + udelay(10); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + + priv->frequency = freq_mhz * 1000; + + return (ret == 1) ? 0 : ret; + } + return -1; +} + +static int stb6000_get_frequency(struct dvb_frontend *fe, u32 *frequency) +{ + struct stb6000_priv *priv = fe->tuner_priv; + *frequency = priv->frequency; + return 0; +} + +static struct dvb_tuner_ops stb6000_tuner_ops = { + .info = { + .name = "ST STB6000", + .frequency_min = 950000, + .frequency_max = 2150000 + }, + .release = stb6000_release, + .sleep = stb6000_sleep, + .set_params = stb6000_set_params, + .get_frequency = stb6000_get_frequency, +}; + +struct dvb_frontend *stb6000_attach(struct dvb_frontend *fe, int addr, + struct i2c_adapter *i2c) +{ + struct stb6000_priv *priv = NULL; + u8 b1[] = { 0, 0 }; + struct i2c_msg msg[2] = { + { + .addr = addr, + .flags = 0, + .buf = NULL, + .len = 0 + }, { + .addr = addr, + .flags = I2C_M_RD, + .buf = b1, + .len = 2 + } + }; + int ret; + + dprintk("%s:\n", __func__); + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + + /* is some i2c device here ? */ + ret = i2c_transfer(i2c, msg, 2); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + + if (ret != 2) + return NULL; + + priv = kzalloc(sizeof(struct stb6000_priv), GFP_KERNEL); + if (priv == NULL) + return NULL; + + priv->i2c_address = addr; + priv->i2c = i2c; + + memcpy(&fe->ops.tuner_ops, &stb6000_tuner_ops, + sizeof(struct dvb_tuner_ops)); + + fe->tuner_priv = priv; + + return fe; +} +EXPORT_SYMBOL(stb6000_attach); + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("DVB STB6000 driver"); +MODULE_AUTHOR("Igor M. Liplianin <liplianin@me.by>"); +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/dvb/frontends/stb6000.h b/linux/drivers/media/dvb/frontends/stb6000.h new file mode 100644 index 000000000..7be479c22 --- /dev/null +++ b/linux/drivers/media/dvb/frontends/stb6000.h @@ -0,0 +1,51 @@ + /* + Driver for ST stb6000 DVBS Silicon tuner + + Copyright (C) 2008 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. + + 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_STB6000_H__ +#define __DVB_STB6000_H__ + +#include <linux/i2c.h> +#include "dvb_frontend.h" + +/** + * Attach a stb6000 tuner to the supplied frontend structure. + * + * @param fe Frontend to attach to. + * @param addr i2c address of the tuner. + * @param i2c i2c adapter to use. + * @return FE pointer on success, NULL on failure. + */ +#if defined(CONFIG_DVB_STB6000) || (defined(CONFIG_DVB_STB6000_MODULE) \ + && defined(MODULE)) +extern struct dvb_frontend *stb6000_attach(struct dvb_frontend *fe, int addr, + struct i2c_adapter *i2c); +#else +static inline struct dvb_frontend *stb6000_attach(struct dvb_frontend *fe, + int addr, + struct i2c_adapter *i2c) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif /* CONFIG_DVB_STB6000 */ + +#endif /* __DVB_STB6000_H__ */ diff --git a/linux/drivers/media/dvb/frontends/stv0288.c b/linux/drivers/media/dvb/frontends/stv0288.c new file mode 100644 index 000000000..90e72e771 --- /dev/null +++ b/linux/drivers/media/dvb/frontends/stv0288.c @@ -0,0 +1,606 @@ +/* + Driver for ST STV0288 demodulator + Copyright (C) 2006 Georg Acher, BayCom GmbH, acher (at) baycom (dot) de + for Reel Multimedia + Copyright (C) 2008 TurboSight.com, Bob Liu <bob@turbosight.com> + Copyright (C) 2008 Igor M. Liplianin <liplianin@me.by> + Removed stb6000 specific tuner code and revised some + procedures. + + 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/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <asm/div64.h> + +#include "dvb_frontend.h" +#include "stv0288.h" + +struct stv0288_state { + struct i2c_adapter *i2c; + const struct stv0288_config *config; + struct dvb_frontend frontend; + + u8 initialised:1; + u32 tuner_frequency; + u32 symbol_rate; + fe_code_rate_t fec_inner; + int errmode; +}; + +#define STATUS_BER 0 +#define STATUS_UCBLOCKS 1 + +static int debug; +static int debug_legacy_dish_switch; +#define dprintk(args...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG "stv0288: " args); \ + } while (0) + + +static int stv0288_writeregI(struct stv0288_state *state, u8 reg, u8 data) +{ + int ret; + u8 buf[] = { reg, data }; + struct i2c_msg msg = { + .addr = state->config->demod_address, + .flags = 0, + .buf = buf, + .len = 2 + }; + + ret = i2c_transfer(state->i2c, &msg, 1); + + if (ret != 1) + dprintk("%s: writereg error (reg == 0x%02x, val == 0x%02x, " + "ret == %i)\n", __func__, reg, data, ret); + + return (ret != 1) ? -EREMOTEIO : 0; +} + +static int stv0288_write(struct dvb_frontend *fe, u8 *buf, int len) +{ + struct stv0288_state *state = fe->demodulator_priv; + + if (len != 2) + return -EINVAL; + + return stv0288_writeregI(state, buf[0], buf[1]); +} + +static u8 stv0288_readreg(struct stv0288_state *state, u8 reg) +{ + int ret; + u8 b0[] = { reg }; + u8 b1[] = { 0 }; + struct i2c_msg msg[] = { + { + .addr = state->config->demod_address, + .flags = 0, + .buf = b0, + .len = 1 + }, { + .addr = state->config->demod_address, + .flags = I2C_M_RD, + .buf = b1, + .len = 1 + } + }; + + ret = i2c_transfer(state->i2c, msg, 2); + + if (ret != 2) + dprintk("%s: readreg error (reg == 0x%02x, ret == %i)\n", + __func__, reg, ret); + + return b1[0]; +} + +static int stv0288_set_symbolrate(struct dvb_frontend *fe, u32 srate) +{ + struct stv0288_state *state = fe->demodulator_priv; + unsigned int temp; + unsigned char b[3]; + + if ((srate < 1000000) || (srate > 45000000)) + return -EINVAL; + + temp = (unsigned int)srate / 1000; + + temp = temp * 32768; + temp = temp / 25; + temp = temp / 125; + b[0] = (unsigned char)((temp >> 12) & 0xff); + b[1] = (unsigned char)((temp >> 4) & 0xff); + b[2] = (unsigned char)((temp << 4) & 0xf0); + stv0288_writeregI(state, 0x28, 0x80); /* SFRH */ + stv0288_writeregI(state, 0x29, 0); /* SFRM */ + stv0288_writeregI(state, 0x2a, 0); /* SFRL */ + + stv0288_writeregI(state, 0x28, b[0]); + stv0288_writeregI(state, 0x29, b[1]); + stv0288_writeregI(state, 0x2a, b[2]); + dprintk("stv0288: stv0288_set_symbolrate\n"); + + return 0; +} + +static int stv0288_send_diseqc_msg(struct dvb_frontend *fe, + struct dvb_diseqc_master_cmd *m) +{ + struct stv0288_state *state = fe->demodulator_priv; + + int i; + + dprintk("%s\n", __func__); + + stv0288_writeregI(state, 0x09, 0); + msleep(30); + stv0288_writeregI(state, 0x05, 0x16); + + for (i = 0; i < m->msg_len; i++) { + if (stv0288_writeregI(state, 0x06, m->msg[i])) + return -EREMOTEIO; + msleep(12); + } + + return 0; +} + +static int stv0288_send_diseqc_burst(struct dvb_frontend *fe, + fe_sec_mini_cmd_t burst) +{ + struct stv0288_state *state = fe->demodulator_priv; + + dprintk("%s\n", __func__); + + if (stv0288_writeregI(state, 0x05, 0x16))/* burst mode */ + return -EREMOTEIO; + + if (stv0288_writeregI(state, 0x06, burst == SEC_MINI_A ? 0x00 : 0xff)) + return -EREMOTEIO; + + if (stv0288_writeregI(state, 0x06, 0x12)) + return -EREMOTEIO; + + return 0; +} + +static int stv0288_set_tone(struct dvb_frontend *fe, fe_sec_tone_mode_t tone) +{ + struct stv0288_state *state = fe->demodulator_priv; + + switch (tone) { + case SEC_TONE_ON: + if (stv0288_writeregI(state, 0x05, 0x10))/* burst mode */ + return -EREMOTEIO; + return stv0288_writeregI(state, 0x06, 0xff); + + case SEC_TONE_OFF: + if (stv0288_writeregI(state, 0x05, 0x13))/* burst mode */ + return -EREMOTEIO; + return stv0288_writeregI(state, 0x06, 0x00); + + default: + return -EINVAL; + } +} + +static u8 stv0288_inittab[] = { + 0x01, 0x15, + 0x02, 0x20, + 0x09, 0x0, + 0x0a, 0x4, + 0x0b, 0x0, + 0x0c, 0x0, + 0x0d, 0x0, + 0x0e, 0xd4, + 0x0f, 0x30, + 0x11, 0x80, + 0x12, 0x03, + 0x13, 0x48, + 0x14, 0x84, + 0x15, 0x45, + 0x16, 0xb7, + 0x17, 0x9c, + 0x18, 0x0, + 0x19, 0xa6, + 0x1a, 0x88, + 0x1b, 0x8f, + 0x1c, 0xf0, + 0x20, 0x0b, + 0x21, 0x54, + 0x22, 0x0, + 0x23, 0x0, + 0x2b, 0xff, + 0x2c, 0xf7, + 0x30, 0x0, + 0x31, 0x1e, + 0x32, 0x14, + 0x33, 0x0f, + 0x34, 0x09, + 0x35, 0x0c, + 0x36, 0x05, + 0x37, 0x2f, + 0x38, 0x16, + 0x39, 0xbe, + 0x3a, 0x0, + 0x3b, 0x13, + 0x3c, 0x11, + 0x3d, 0x30, + 0x40, 0x63, + 0x41, 0x04, + 0x42, 0x60, + 0x43, 0x00, + 0x44, 0x00, + 0x45, 0x00, + 0x46, 0x00, + 0x47, 0x00, + 0x4a, 0x00, + 0x50, 0x10, + 0x51, 0x38, + 0x52, 0x21, + 0x58, 0x54, + 0x59, 0x86, + 0x5a, 0x0, + 0x5b, 0x9b, + 0x5c, 0x08, + 0x5d, 0x7f, + 0x5e, 0x0, + 0x5f, 0xff, + 0x70, 0x0, + 0x71, 0x0, + 0x72, 0x0, + 0x74, 0x0, + 0x75, 0x0, + 0x76, 0x0, + 0x81, 0x0, + 0x82, 0x3f, + 0x83, 0x3f, + 0x84, 0x0, + 0x85, 0x0, + 0x88, 0x0, + 0x89, 0x0, + 0x8a, 0x0, + 0x8b, 0x0, + 0x8c, 0x0, + 0x90, 0x0, + 0x91, 0x0, + 0x92, 0x0, + 0x93, 0x0, + 0x94, 0x1c, + 0x97, 0x0, + 0xa0, 0x48, + 0xa1, 0x0, + 0xb0, 0xb8, + 0xb1, 0x3a, + 0xb2, 0x10, + 0xb3, 0x82, + 0xb4, 0x80, + 0xb5, 0x82, + 0xb6, 0x82, + 0xb7, 0x82, + 0xb8, 0x20, + 0xb9, 0x0, + 0xf0, 0x0, + 0xf1, 0x0, + 0xf2, 0xc0, + 0x51, 0x36, + 0x52, 0x09, + 0x53, 0x94, + 0x54, 0x62, + 0x55, 0x29, + 0x56, 0x64, + 0x57, 0x2b, + 0xff, 0xff, +}; + +static int stv0288_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t volt) +{ + dprintk("%s: %s\n", __func__, + volt == SEC_VOLTAGE_13 ? "SEC_VOLTAGE_13" : + volt == SEC_VOLTAGE_18 ? "SEC_VOLTAGE_18" : "??"); + + return 0; +} + +static int stv0288_init(struct dvb_frontend *fe) +{ + struct stv0288_state *state = fe->demodulator_priv; + int i; + + dprintk("stv0288: init chip\n"); + stv0288_writeregI(state, 0x41, 0x04); + msleep(50); + + for (i = 0; !(stv0288_inittab[i] == 0xff && + stv0288_inittab[i + 1] == 0xff); i += 2) + stv0288_writeregI(state, stv0288_inittab[i], + stv0288_inittab[i + 1]); + + return 0; +} + +static int stv0288_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct stv0288_state *state = fe->demodulator_priv; + + u8 sync = stv0288_readreg(state, 0x24); + if (sync == 255) + sync = 0; + + dprintk("%s : FE_READ_STATUS : VSTATUS: 0x%02x\n", __func__, sync); + + *status = 0; + + if ((sync & 0x08) == 0x08) { + *status |= FE_HAS_LOCK; + dprintk("stv0288 has locked\n"); + } + + return 0; +} + +static int stv0288_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct stv0288_state *state = fe->demodulator_priv; + + if (state->errmode != STATUS_BER) + return 0; + *ber = (stv0288_readreg(state, 0x26) << 8) | + stv0288_readreg(state, 0x27); + dprintk("stv0288_read_ber %d\n", *ber); + + return 0; +} + + +static int stv0288_read_signal_strength(struct dvb_frontend *fe, u16 *strength) +{ + struct stv0288_state *state = fe->demodulator_priv; + + s32 signal = 0xffff - ((stv0288_readreg(state, 0x10) << 8)); + + + signal = signal * 5 / 4; + *strength = (signal > 0xffff) ? 0xffff : (signal < 0) ? 0 : signal; + dprintk("stv0288_read_signal_strength %d\n", *strength); + + return 0; +} +static int stv0288_sleep(struct dvb_frontend *fe) +{ + struct stv0288_state *state = fe->demodulator_priv; + + stv0288_writeregI(state, 0x41, 0x84); + state->initialised = 0; + + return 0; +} +static int stv0288_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct stv0288_state *state = fe->demodulator_priv; + + s32 xsnr = 0xffff - ((stv0288_readreg(state, 0x2d) << 8) + | stv0288_readreg(state, 0x2e)); + xsnr = 3 * (xsnr - 0xa100); + *snr = (xsnr > 0xffff) ? 0xffff : (xsnr < 0) ? 0 : xsnr; + dprintk("stv0288_read_snr %d\n", *snr); + + return 0; +} + +static int stv0288_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + struct stv0288_state *state = fe->demodulator_priv; + + if (state->errmode != STATUS_BER) + return 0; + *ucblocks = (stv0288_readreg(state, 0x26) << 8) | + stv0288_readreg(state, 0x27); + dprintk("stv0288_read_ber %d\n", *ucblocks); + + return 0; +} + +static int stv0288_set_property(struct dvb_frontend *fe, struct dtv_property *p) +{ + dprintk("%s(..)\n", __func__); + return 0; +} + +static int stv0288_get_property(struct dvb_frontend *fe, struct dtv_property *p) +{ + dprintk("%s(..)\n", __func__); + return 0; +} + +static int stv0288_set_frontend(struct dvb_frontend *fe, + struct dvb_frontend_parameters *dfp) +{ + struct stv0288_state *state = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + + char tm; + unsigned char tda[3]; + + dprintk("%s : FE_SET_FRONTEND\n", __func__); + + if (c->delivery_system != SYS_DVBS) { + dprintk("%s: unsupported delivery " + "system selected (%d)\n", + __func__, c->delivery_system); + return -EOPNOTSUPP; + } + + if (state->config->set_ts_params) + state->config->set_ts_params(fe, 0); + + /* only frequency & symbol_rate are used for tuner*/ + dfp->frequency = c->frequency; + dfp->u.qpsk.symbol_rate = c->symbol_rate; + if (fe->ops.tuner_ops.set_params) { + fe->ops.tuner_ops.set_params(fe, dfp); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + } + + udelay(10); + stv0288_set_symbolrate(fe, c->symbol_rate); + /* Carrier lock control register */ + stv0288_writeregI(state, 0x15, 0xc5); + + tda[0] = 0x2b; /* CFRM */ + tda[2] = 0x0; /* CFRL */ + for (tm = -6; tm < 7;) { + /* Viterbi status */ + if (stv0288_readreg(state, 0x24) & 0x80) + break; + + tda[2] += 40; + if (tda[2] < 40) + tm++; + tda[1] = (unsigned char)tm; + stv0288_writeregI(state, 0x2b, tda[1]); + stv0288_writeregI(state, 0x2c, tda[2]); + udelay(30); + } + + state->tuner_frequency = c->frequency; + state->fec_inner = FEC_AUTO; + state->symbol_rate = c->symbol_rate; + + return 0; +} + +static int stv0288_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + struct stv0288_state *state = fe->demodulator_priv; + + if (enable) + stv0288_writeregI(state, 0x01, 0xb5); + else + stv0288_writeregI(state, 0x01, 0x35); + + udelay(1); + + return 0; +} + +static void stv0288_release(struct dvb_frontend *fe) +{ + struct stv0288_state *state = fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops stv0288_ops = { + + .info = { + .name = "ST STV0288 DVB-S", + .type = FE_QPSK, + .frequency_min = 950000, + .frequency_max = 2150000, + .frequency_stepsize = 1000, /* kHz for QPSK frontends */ + .frequency_tolerance = 0, + .symbol_rate_min = 1000000, + .symbol_rate_max = 45000000, + .symbol_rate_tolerance = 500, /* ppm */ + .caps = FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | + FE_CAN_QPSK | + FE_CAN_FEC_AUTO + }, + + .release = stv0288_release, + .init = stv0288_init, + .sleep = stv0288_sleep, + .write = stv0288_write, + .i2c_gate_ctrl = stv0288_i2c_gate_ctrl, + .read_status = stv0288_read_status, + .read_ber = stv0288_read_ber, + .read_signal_strength = stv0288_read_signal_strength, + .read_snr = stv0288_read_snr, + .read_ucblocks = stv0288_read_ucblocks, + .diseqc_send_master_cmd = stv0288_send_diseqc_msg, + .diseqc_send_burst = stv0288_send_diseqc_burst, + .set_tone = stv0288_set_tone, + .set_voltage = stv0288_set_voltage, + + .set_property = stv0288_set_property, + .get_property = stv0288_get_property, + .set_frontend = stv0288_set_frontend, +}; + +struct dvb_frontend *stv0288_attach(const struct stv0288_config *config, + struct i2c_adapter *i2c) +{ + struct stv0288_state *state = NULL; + int id; + + /* allocate memory for the internal state */ + state = kmalloc(sizeof(struct stv0288_state), GFP_KERNEL); + if (state == NULL) + goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + state->initialised = 0; + state->tuner_frequency = 0; + state->symbol_rate = 0; + state->fec_inner = 0; + state->errmode = STATUS_BER; + + stv0288_writeregI(state, 0x41, 0x04); + msleep(200); + id = stv0288_readreg(state, 0x00); + dprintk("stv0288 id %x\n", id); + + /* register 0x00 contains 0x11 for STV0288 */ + if (id != 0x11) + goto error; + + /* create dvb_frontend */ + memcpy(&state->frontend.ops, &stv0288_ops, + sizeof(struct dvb_frontend_ops)); + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + + return NULL; +} +EXPORT_SYMBOL(stv0288_attach); + +module_param(debug_legacy_dish_switch, int, 0444); +MODULE_PARM_DESC(debug_legacy_dish_switch, + "Enable timing analysis for Dish Network legacy switches"); + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("ST STV0288 DVB Demodulator driver"); +MODULE_AUTHOR("Georg Acher, Bob Liu, Igor liplianin"); +MODULE_LICENSE("GPL"); + diff --git a/linux/drivers/media/dvb/frontends/stv0288.h b/linux/drivers/media/dvb/frontends/stv0288.h new file mode 100644 index 000000000..aa0cdd273 --- /dev/null +++ b/linux/drivers/media/dvb/frontends/stv0288.h @@ -0,0 +1,65 @@ +/* + Driver for ST STV0288 demodulator + + Copyright (C) 2006 Georg Acher, BayCom GmbH, acher (at) baycom (dot) de + for Reel Multimedia + Copyright (C) 2008 TurboSight.com, <bob@turbosight.com> + Copyright (C) 2008 Igor M. Liplianin <liplianin@me.by> + Removed stb6000 specific tuner code and revised some + procedures. + + 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 STV0288_H +#define STV0288_H + +#include <linux/dvb/frontend.h> +#include "dvb_frontend.h" + +struct stv0288_config { + /* the demodulator's i2c address */ + u8 demod_address; + + /* minimum delay before retuning */ + int min_delay_ms; + + int (*set_ts_params)(struct dvb_frontend *fe, int is_punctured); +}; + +#if defined(CONFIG_DVB_STV0288) || (defined(CONFIG_DVB_STV0288_MODULE) && \ + defined(MODULE)) +extern struct dvb_frontend *stv0288_attach(const struct stv0288_config *config, + struct i2c_adapter *i2c); +#else +static inline struct dvb_frontend *stv0288_attach(const struct stv0288_config *config, + struct i2c_adapter *i2c) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif /* CONFIG_DVB_STV0288 */ + +static inline int stv0288_writereg(struct dvb_frontend *fe, u8 reg, u8 val) +{ + int r = 0; + u8 buf[] = { reg, val }; + if (fe->ops.write) + r = fe->ops.write(fe, buf, 2); + return r; +} + +#endif /* STV0288_H */ diff --git a/linux/drivers/media/dvb/frontends/stv0299.c b/linux/drivers/media/dvb/frontends/stv0299.c index 35435bef8..6c1cb1973 100644 --- a/linux/drivers/media/dvb/frontends/stv0299.c +++ b/linux/drivers/media/dvb/frontends/stv0299.c @@ -559,6 +559,8 @@ static int stv0299_set_frontend(struct dvb_frontend* fe, struct dvb_frontend_par int invval = 0; dprintk ("%s : FE_SET_FRONTEND\n", __func__); + if (state->config->set_ts_params) + state->config->set_ts_params(fe, 0); // set the inversion if (p->inversion == INVERSION_OFF) invval = 0; diff --git a/linux/drivers/media/dvb/frontends/stv0299.h b/linux/drivers/media/dvb/frontends/stv0299.h index 3282f4302..0fd96e22b 100644 --- a/linux/drivers/media/dvb/frontends/stv0299.h +++ b/linux/drivers/media/dvb/frontends/stv0299.h @@ -89,15 +89,18 @@ struct stv0299_config int min_delay_ms; /* Set the symbol rate */ - int (*set_symbol_rate)(struct dvb_frontend* fe, u32 srate, u32 ratio); + int (*set_symbol_rate)(struct dvb_frontend *fe, u32 srate, u32 ratio); + + /* Set device param to start dma */ + int (*set_ts_params)(struct dvb_frontend *fe, int is_punctured); }; #if defined(CONFIG_DVB_STV0299) || (defined(CONFIG_DVB_STV0299_MODULE) && defined(MODULE)) -extern struct dvb_frontend* stv0299_attach(const struct stv0299_config* config, - struct i2c_adapter* i2c); +extern struct dvb_frontend *stv0299_attach(const struct stv0299_config *config, + struct i2c_adapter *i2c); #else -static inline struct dvb_frontend* stv0299_attach(const struct stv0299_config* config, - struct i2c_adapter* i2c) +static inline struct dvb_frontend *stv0299_attach(const struct stv0299_config *config, + struct i2c_adapter *i2c) { printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); return NULL; diff --git a/linux/drivers/media/video/cx88/Kconfig b/linux/drivers/media/video/cx88/Kconfig index 9dd7bdf65..0b9e5fac6 100644 --- a/linux/drivers/media/video/cx88/Kconfig +++ b/linux/drivers/media/video/cx88/Kconfig @@ -58,6 +58,10 @@ config VIDEO_CX88_DVB select DVB_ISL6421 if !DVB_FE_CUSTOMISE select MEDIA_TUNER_SIMPLE if !DVB_FE_CUSTOMISE select DVB_S5H1411 if !DVB_FE_CUSTOMISE + select DVB_CX24116 if !DVB_FE_CUSTOMISE + select DVB_STV0299 if !DVB_FE_CUSTOMISE + select DVB_STV0288 if !DVB_FE_CUSTOMISE + select DVB_STB6000 if !DVB_FE_CUSTOMISE ---help--- This adds support for DVB/ATSC cards based on the Conexant 2388x chip. diff --git a/linux/drivers/media/video/cx88/cx88-cards.c b/linux/drivers/media/video/cx88/cx88-cards.c index 871ba1992..95b2ff5d9 100644 --- a/linux/drivers/media/video/cx88/cx88-cards.c +++ b/linux/drivers/media/video/cx88/cx88-cards.c @@ -1382,27 +1382,30 @@ static const struct cx88_board cx88_boards[] = { .radio_addr = ADDR_UNSET, .tda9887_conf = TDA9887_PRESENT, .audio_chip = V4L2_IDENT_WM8775, + /* + * gpio0 as reported by Mike Crash <mike AT mikecrash.com> + */ .input = {{ .type = CX88_VMUX_TELEVISION, .vmux = 0, - .gpio0 = 0xe780, + .gpio0 = 0xef88, .audioroute = 1, },{ .type = CX88_VMUX_COMPOSITE1, .vmux = 1, - .gpio0 = 0xe780, + .gpio0 = 0xef88, .audioroute = 2, },{ .type = CX88_VMUX_SVIDEO, .vmux = 2, - .gpio0 = 0xe780, + .gpio0 = 0xef88, .audioroute = 2, }}, /* fixme: Add radio support */ .mpeg = CX88_MPEG_DVB | CX88_MPEG_BLACKBIRD, .radio = { .type = CX88_RADIO, - .gpio0 = 0xe780, + .gpio0 = 0xef88, }, }, [CX88_BOARD_ADSTECH_PTV_390] = { @@ -1701,6 +1704,119 @@ static const struct cx88_board cx88_boards[] = { }, .mpeg = CX88_MPEG_DVB, }, + [CX88_BOARD_HAUPPAUGE_HVR4000] = { + .name = "Hauppauge WinTV-HVR4000 DVB-S/S2/T/Hybrid", + .tuner_type = TUNER_PHILIPS_FMD1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tda9887_conf = TDA9887_PRESENT, + /* + * GPIO0 (WINTV2000) + * + * Analogue SAT DVB-T + * Antenna 0xc4bf 0xc4bb + * Composite 0xc4bf 0xc4bb + * S-Video 0xc4bf 0xc4bb + * Composite1 0xc4ff 0xc4fb + * S-Video1 0xc4ff 0xc4fb + * + * BIT VALUE FUNCTION GP{x}_IO + * 0 1 I:? + * 1 1 I:? + * 2 1 O:DVB-T DEMOD ENABLE LOW/ANALOG DEMOD ENABLE HIGH + * 3 1 I:? + * 4 1 I:? + * 5 1 I:? + * 6 0 O:INPUT SELECTOR 0=INTERNAL 1=EXPANSION + * 7 1 O:DVB-T DEMOD RESET LOW + * + * BIT VALUE FUNCTION GP{x}_OE + * 8 0 I + * 9 0 I + * a 1 O + * b 0 I + * c 0 I + * d 0 I + * e 1 O + * f 1 O + */ + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0xc4bf, + }, { + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0xc4bf, + }, { + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0xc4bf, + } }, + /* fixme: Add radio support */ + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_HAUPPAUGE_HVR4000LITE] = { + .name = "Hauppauge WinTV-HVR4000(Lite) DVB-S/S2", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_TEVII_S420] = { + .name = "TeVii S420 DVB-S", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_TEVII_S460] = { + .name = "TeVii S460 DVB-S/S2", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_OMICOM_SS4_PCI] = { + .name = "Omicom SS4 DVB-S/S2 PCI", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, + [CX88_BOARD_TBS_8920] = { + .name = "TBS 8920 DVB-S/S2", + .tuner_type = TUNER_ABSENT, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 1, + } }, + .mpeg = CX88_MPEG_DVB, + }, }; /* ------------------------------------------------------------------ */ @@ -2050,6 +2166,42 @@ static const struct cx88_subid cx88_subids[] = { .subvendor = 0x17de, .subdevice = 0x08c1, .card = CX88_BOARD_KWORLD_ATSC_120, + }, { + .subvendor = 0x0070, + .subdevice = 0x6900, + .card = CX88_BOARD_HAUPPAUGE_HVR4000, + }, { + .subvendor = 0x0070, + .subdevice = 0x6904, + .card = CX88_BOARD_HAUPPAUGE_HVR4000, + }, { + .subvendor = 0x0070, + .subdevice = 0x6902, + .card = CX88_BOARD_HAUPPAUGE_HVR4000, + }, { + .subvendor = 0x0070, + .subdevice = 0x6905, + .card = CX88_BOARD_HAUPPAUGE_HVR4000LITE, + }, { + .subvendor = 0x0070, + .subdevice = 0x6906, + .card = CX88_BOARD_HAUPPAUGE_HVR4000LITE, + }, { + .subvendor = 0xd420, + .subdevice = 0x9022, + .card = CX88_BOARD_TEVII_S420, + }, { + .subvendor = 0xd460, + .subdevice = 0x9022, + .card = CX88_BOARD_TEVII_S460, + }, { + .subvendor = 0xA044, + .subdevice = 0x2011, + .card = CX88_BOARD_OMICOM_SS4_PCI, + }, { + .subvendor = 0x8920, + .subdevice = 0x8888, + .card = CX88_BOARD_TBS_8920, }, }; @@ -2102,6 +2254,13 @@ static void hauppauge_eeprom(struct cx88_core *core, u8 *eeprom_data) case 14669: /* WinTV-HVR3000 (OEM, no IR, no b/panel video - Low profile) */ case 28552: /* WinTV-PVR 'Roslyn' (No IR) */ case 34519: /* WinTV-PCI-FM */ + case 69009: + /* WinTV-HVR4000 (DVBS/S2/T, Video and IR, back panel inputs) */ + case 69100: /* WinTV-HVR4000LITE (DVBS/S2, IR) */ + case 69500: /* WinTV-HVR4000LITE (DVBS/S2, No IR) */ + case 69559: + /* WinTV-HVR4000 (DVBS/S2/T, Video no IR, back panel inputs) */ + case 69569: /* WinTV-HVR4000 (DVBS/S2/T, Video no IR) */ case 90002: /* Nova-T-PCI (9002) */ case 92001: /* Nova-S-Plus (Video and IR) */ case 92002: /* Nova-S-Plus (Video and IR) */ @@ -2429,13 +2588,18 @@ static void cx88_card_setup_pre_i2c(struct cx88_core *core) { switch (core->boardnr) { case CX88_BOARD_HAUPPAUGE_HVR1300: - /* Bring the 702 demod up before i2c scanning/attach or devices are hidden */ - /* We leave here with the 702 on the bus */ - cx_write(MO_GP0_IO, 0x0000e780); + /* + * Bring the 702 demod up before i2c scanning/attach or devices are hidden + * We leave here with the 702 on the bus + * + * "reset the IR receiver on GPIO[3]" + * Reported by Mike Crash <mike AT mikecrash.com> + */ + cx_write(MO_GP0_IO, 0x0000ef88); udelay(1000); - cx_clear(MO_GP0_IO, 0x00000080); + cx_clear(MO_GP0_IO, 0x00000088); udelay(50); - cx_set(MO_GP0_IO, 0x00000080); /* 702 out of reset */ + cx_set(MO_GP0_IO, 0x00000088); /* 702 out of reset */ udelay(1000); break; @@ -2448,10 +2612,18 @@ static void cx88_card_setup_pre_i2c(struct cx88_core *core) msleep(10); break; - case CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD: + case CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD: /* Enable the xc5000 tuner */ cx_set(MO_GP0_IO, 0x00001010); break; + + case CX88_BOARD_HAUPPAUGE_HVR3000: + case CX88_BOARD_HAUPPAUGE_HVR4000: + case CX88_BOARD_HAUPPAUGE_HVR4000LITE: + /* Init GPIO */ + cx_write(MO_GP0_IO, core->board.input[0].gpio0); + udelay(1000); + break; } } @@ -2526,6 +2698,8 @@ static void cx88_card_setup(struct cx88_core *core) case CX88_BOARD_HAUPPAUGE_HVR1100LP: case CX88_BOARD_HAUPPAUGE_HVR3000: case CX88_BOARD_HAUPPAUGE_HVR1300: + case CX88_BOARD_HAUPPAUGE_HVR4000: + case CX88_BOARD_HAUPPAUGE_HVR4000LITE: if (0 == core->i2c_rc) hauppauge_eeprom(core, eeprom); break; @@ -2607,7 +2781,27 @@ static void cx88_card_setup(struct cx88_core *core) tea5767_cfg.priv = &ctl; cx88_call_i2c_clients(core, TUNER_SET_CONFIG, &tea5767_cfg); + break; } + case CX88_BOARD_TEVII_S420: + case CX88_BOARD_TEVII_S460: + cx_write(MO_SRST_IO, 0); + msleep(100); + cx_write(MO_SRST_IO, 1); + msleep(100); + break; + case CX88_BOARD_OMICOM_SS4_PCI: + cx_write(MO_SRST_IO, 0); + msleep(100); + cx_write(MO_SRST_IO, 1); + msleep(100); + break; + case CX88_BOARD_TBS_8920: + cx_write(MO_SRST_IO, 0); + msleep(100); + cx_write(MO_SRST_IO, 1); + msleep(100); + break; } /*end switch() */ diff --git a/linux/drivers/media/video/cx88/cx88-dvb.c b/linux/drivers/media/video/cx88/cx88-dvb.c index f5fef4983..64c799500 100644 --- a/linux/drivers/media/video/cx88/cx88-dvb.c +++ b/linux/drivers/media/video/cx88/cx88-dvb.c @@ -49,6 +49,11 @@ #include "tuner-simple.h" #include "tda9887.h" #include "s5h1411.h" +#include "stv0299.h" +#include "z0194a.h" +#include "stv0288.h" +#include "stb6000.h" +#include "cx24116.h" MODULE_DESCRIPTION("driver for cx2388x based DVB cards"); MODULE_AUTHOR("Chris Pascoe <c.pascoe@itee.uq.edu.au>"); @@ -376,6 +381,31 @@ static int geniatech_dvbs_set_voltage(struct dvb_frontend *fe, return 0; } +static int tevii_dvbs_set_voltage(struct dvb_frontend *fe, + fe_sec_voltage_t voltage) +{ + struct cx8802_dev *dev= fe->dvb->priv; + struct cx88_core *core = dev->core; + + switch (voltage) { + case SEC_VOLTAGE_13: + printk("LNB Voltage SEC_VOLTAGE_13\n"); + cx_write(MO_GP0_IO, 0x00006040); + break; + case SEC_VOLTAGE_18: + printk("LNB Voltage SEC_VOLTAGE_18\n"); + cx_write(MO_GP0_IO, 0x00006060); + break; + case SEC_VOLTAGE_OFF: + printk("LNB Voltage SEC_VOLTAGE_off\n"); + break; + } + + if (core->prev_set_voltage) + return core->prev_set_voltage(fe, voltage); + return 0; +} + static int cx88_pci_nano_callback(void *ptr, int command, int arg) { struct cx88_core *core = ptr; @@ -525,6 +555,60 @@ static int attach_xc3028(u8 addr, struct cx8802_dev *dev) return 0; } +static int cx24116_set_ts_param(struct dvb_frontend *fe, + int is_punctured) +{ + struct cx8802_dev *dev = fe->dvb->priv; + dev->ts_gen_cntrl = 0x2; + + return 0; +} + +static int cx24116_reset_device(struct dvb_frontend *fe) +{ + struct cx8802_dev *dev = fe->dvb->priv; + struct cx88_core *core = dev->core; + + /* Reset the part */ + cx_write(MO_SRST_IO, 0); + msleep(10); + cx_write(MO_SRST_IO, 1); + msleep(10); + + return 0; +} + +static struct cx24116_config hauppauge_hvr4000_config = { + .demod_address = 0x05, + .set_ts_params = cx24116_set_ts_param, + .reset_device = cx24116_reset_device, +}; + +static struct cx24116_config tevii_s460_config = { + .demod_address = 0x55, + .set_ts_params = cx24116_set_ts_param, + .reset_device = cx24116_reset_device, +}; + +static struct stv0299_config tevii_tuner_sharp_config = { + .demod_address = 0x68, + .inittab = sharp_z0194a__inittab, + .mclk = 88000000UL, + .invert = 1, + .skip_reinit = 0, + .lock_output = 1, + .volt13_op0_op1 = STV0299_VOLT13_OP1, + .min_delay_ms = 100, + .set_symbol_rate = sharp_z0194a__set_symbol_rate, + .set_ts_params = cx24116_set_ts_param, +}; + +static struct stv0288_config tevii_tuner_earda_config = { + .demod_address = 0x68, + .min_delay_ms = 100, + .set_ts_params = cx24116_set_ts_param, +}; + static int dvb_register(struct cx8802_dev *dev) { struct cx88_core *core = dev->core; @@ -793,7 +877,7 @@ static int dvb_register(struct cx8802_dev *dev) &core->i2c_adap); if (dev->dvb.frontend) { if (!dvb_attach(isl6421_attach, dev->dvb.frontend, - &core->i2c_adap, 0x08, 0x00, 0x00)) + &core->i2c_adap, 0x08, ISL6421_DCL, 0x00)) goto frontend_detach; } break; @@ -883,6 +967,70 @@ static int dvb_register(struct cx8802_dev *dev) goto frontend_detach; } break; + case CX88_BOARD_HAUPPAUGE_HVR4000: + case CX88_BOARD_HAUPPAUGE_HVR4000LITE: + /* Support for DVB-S only, not DVB-T support */ + dev->dvb.frontend = dvb_attach(cx24116_attach, + &hauppauge_hvr4000_config, + &dev->core->i2c_adap); + if (dev->dvb.frontend) { + dvb_attach(isl6421_attach, dev->dvb.frontend, + &dev->core->i2c_adap, + 0x08, ISL6421_DCL, 0x00); + } + break; + case CX88_BOARD_TEVII_S420: + dev->dvb.frontend = dvb_attach(stv0299_attach, + &tevii_tuner_sharp_config, + &core->i2c_adap); + if (dev->dvb.frontend != NULL) { + if (!dvb_attach(dvb_pll_attach, dev->dvb.frontend, 0x60, + &core->i2c_adap, DVB_PLL_OPERA1)) + goto frontend_detach; + core->prev_set_voltage = dev->dvb.frontend->ops.set_voltage; + dev->dvb.frontend->ops.set_voltage = tevii_dvbs_set_voltage; + + } else { + dev->dvb.frontend = dvb_attach(stv0288_attach, + &tevii_tuner_earda_config, + &core->i2c_adap); + if (dev->dvb.frontend != NULL) { + if (!dvb_attach(stb6000_attach, dev->dvb.frontend, 0x61, + &core->i2c_adap)) + goto frontend_detach; + core->prev_set_voltage = dev->dvb.frontend->ops.set_voltage; + dev->dvb.frontend->ops.set_voltage = tevii_dvbs_set_voltage; + + } + } + break; + case CX88_BOARD_TEVII_S460: + dev->dvb.frontend = dvb_attach(cx24116_attach, + &tevii_s460_config, + &core->i2c_adap); + if (dev->dvb.frontend != NULL) { + core->prev_set_voltage = dev->dvb.frontend->ops.set_voltage; + dev->dvb.frontend->ops.set_voltage = tevii_dvbs_set_voltage; + } + break; + case CX88_BOARD_OMICOM_SS4_PCI: + dev->dvb.frontend = dvb_attach(cx24116_attach, + &hauppauge_hvr4000_config, + &core->i2c_adap); + if (dev->dvb.frontend != NULL) { + core->prev_set_voltage = dev->dvb.frontend->ops.set_voltage; + dev->dvb.frontend->ops.set_voltage = tevii_dvbs_set_voltage; + } + break; + case CX88_BOARD_TBS_8920: + dev->dvb.frontend = dvb_attach(cx24116_attach, + &hauppauge_hvr4000_config, + &core->i2c_adap); + if (dev->dvb.frontend != NULL) { + core->prev_set_voltage = dev->dvb.frontend->ops.set_voltage; + dev->dvb.frontend->ops.set_voltage = tevii_dvbs_set_voltage; + } + break; default: printk(KERN_ERR "%s/2: The frontend of your DVB/ATSC card isn't supported yet\n", core->name); diff --git a/linux/drivers/media/video/cx88/cx88-i2c.c b/linux/drivers/media/video/cx88/cx88-i2c.c index d7406a994..8e74d64fd 100644 --- a/linux/drivers/media/video/cx88/cx88-i2c.c +++ b/linux/drivers/media/video/cx88/cx88-i2c.c @@ -201,7 +201,23 @@ int cx88_i2c_init(struct cx88_core *core, struct pci_dev *pci) core->i2c_rc = i2c_bit_add_bus(&core->i2c_adap); if (0 == core->i2c_rc) { + static u8 tuner_data[] = + { 0x0b, 0xdc, 0x86, 0x52 }; + static struct i2c_msg tuner_msg = + { .flags = 0, .addr = 0xc2 >> 1, .buf = tuner_data, .len = 4 }; + dprintk(1, "i2c register ok\n"); + switch( core->boardnr ) { + case CX88_BOARD_HAUPPAUGE_HVR1300: + case CX88_BOARD_HAUPPAUGE_HVR3000: + case CX88_BOARD_HAUPPAUGE_HVR4000: + printk("%s: i2c init: enabling analog demod on HVR1300/3000/4000 tuner\n", + core->name); + i2c_transfer(core->i2c_client.adapter, &tuner_msg, 1); + break; + default: + break; + } if (i2c_scan) do_i2c_scan(core->name,&core->i2c_client); } else diff --git a/linux/drivers/media/video/cx88/cx88-input.c b/linux/drivers/media/video/cx88/cx88-input.c index 7d174382e..3cdc473de 100644 --- a/linux/drivers/media/video/cx88/cx88-input.c +++ b/linux/drivers/media/video/cx88/cx88-input.c @@ -237,6 +237,8 @@ int cx88_ir_init(struct cx88_core *core, struct pci_dev *pci) case CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1: case CX88_BOARD_HAUPPAUGE_HVR1100: case CX88_BOARD_HAUPPAUGE_HVR3000: + case CX88_BOARD_HAUPPAUGE_HVR4000: + case CX88_BOARD_HAUPPAUGE_HVR4000LITE: ir_codes = ir_codes_hauppauge_new; ir_type = IR_TYPE_RC5; ir->sampling = 1; @@ -409,7 +411,7 @@ void cx88_ir_irq(struct cx88_core *core) { struct cx88_IR *ir = core->ir; u32 samples, ircode; - int i; + int i, start, range, toggle, dev, code; if (NULL == ir) return; @@ -478,6 +480,34 @@ void cx88_ir_irq(struct cx88_core *core) case CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1: case CX88_BOARD_HAUPPAUGE_HVR1100: case CX88_BOARD_HAUPPAUGE_HVR3000: + case CX88_BOARD_HAUPPAUGE_HVR4000: + case CX88_BOARD_HAUPPAUGE_HVR4000LITE: + ircode = ir_decode_biphase(ir->samples, ir->scount, 5, 7); + ir_dprintk("biphase decoded: %x\n", ircode); + /* + * RC5 has an extension bit which adds a new range + * of available codes, this is detected here. Also + * hauppauge remotes (black/silver) always use + * specific device ids. If we do not filter the + * device ids then messages destined for devices + * such as TVs (id=0) will get through to the + * device causing mis-fired events. + */ + /* split rc5 data block ... */ + start = (ircode & 0x2000) >> 13; + range = (ircode & 0x1000) >> 12; + toggle= (ircode & 0x0800) >> 11; + dev = (ircode & 0x07c0) >> 6; + code = (ircode & 0x003f) | ((range << 6) ^ 0x0040); + if( start != 1) + /* no key pressed */ + break; + if ( dev != 0x1e && dev != 0x1f ) + /* not a hauppauge remote */ + break; + ir_input_keydown(ir->input, &ir->ir, code, ircode); + ir->release = jiffies + msecs_to_jiffies(120); + break; case CX88_BOARD_PINNACLE_PCTV_HD_800i: ircode = ir_decode_biphase(ir->samples, ir->scount, 5, 7); ir_dprintk("biphase decoded: %x\n", ircode); diff --git a/linux/drivers/media/video/cx88/cx88.h b/linux/drivers/media/video/cx88/cx88.h index 751176e7a..238370681 100644 --- a/linux/drivers/media/video/cx88/cx88.h +++ b/linux/drivers/media/video/cx88/cx88.h @@ -222,6 +222,12 @@ extern struct sram_channel cx88_sram_channels[]; #define CX88_BOARD_DVICO_FUSIONHDTV_7_GOLD 65 #define CX88_BOARD_PROLINK_PV_8000GT 66 #define CX88_BOARD_KWORLD_ATSC_120 67 +#define CX88_BOARD_HAUPPAUGE_HVR4000 68 +#define CX88_BOARD_HAUPPAUGE_HVR4000LITE 69 +#define CX88_BOARD_TEVII_S460 70 +#define CX88_BOARD_OMICOM_SS4_PCI 71 +#define CX88_BOARD_TBS_8920 72 +#define CX88_BOARD_TEVII_S420 73 enum cx88_itype { CX88_VMUX_COMPOSITE1 = 1, diff --git a/linux/drivers/media/video/ir-kbd-i2c.c b/linux/drivers/media/video/ir-kbd-i2c.c index 73fcfddce..038a5c04b 100644 --- a/linux/drivers/media/video/ir-kbd-i2c.c +++ b/linux/drivers/media/video/ir-kbd-i2c.c @@ -66,7 +66,7 @@ static int get_key_haup_common(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw, int size, int offset) { unsigned char buf[6]; - int start, range, toggle, dev, code; + int start, range, toggle, dev, code, ircode; /* poll IR chip */ if (size != i2c_master_recv(&ir->c,buf,size)) @@ -86,6 +86,24 @@ static int get_key_haup_common(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw, if (!start) /* no key pressed */ return 0; + /* + * Hauppauge remotes (black/silver) always use + * specific device ids. If we do not filter the + * device ids then messages destined for devices + * such as TVs (id=0) will get through causing + * mis-fired events. + * + * We also filter out invalid key presses which + * produce annoying debug log entries. + */ + ircode= (start << 12) | (toggle << 11) | (dev << 6) | code; + if ((ircode & 0x1fff)==0x1fff) + /* invalid key press */ + return 0; + + if (dev!=0x1e && dev!=0x1f) + /* not a hauppauge remote */ + return 0; if (!range) code += 64; @@ -95,7 +113,7 @@ static int get_key_haup_common(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw, /* return key */ *ir_key = code; - *ir_raw = (start << 12) | (toggle << 11) | (dev << 6) | code; + *ir_raw = ircode; return 1; } diff --git a/linux/include/linux/dvb/frontend.h b/linux/include/linux/dvb/frontend.h index c8cbd90ba..6793deada 100644 --- a/linux/include/linux/dvb/frontend.h +++ b/linux/include/linux/dvb/frontend.h @@ -62,6 +62,7 @@ typedef enum fe_caps { FE_CAN_HIERARCHY_AUTO = 0x100000, FE_CAN_8VSB = 0x200000, FE_CAN_16VSB = 0x400000, + FE_HAS_EXTENDED_CAPS = 0x800000, // We need more bitspace for newer APIs, indicate this. FE_NEEDS_BENDING = 0x20000000, // not supported anymore, don't use (frontend requires frequency bending) FE_CAN_RECOVER = 0x40000000, // frontend can recover from a cable unplug automatically FE_CAN_MUTE_TS = 0x80000000 // frontend can stop spurious TS data output @@ -147,7 +148,9 @@ typedef enum fe_code_rate { FEC_6_7, FEC_7_8, FEC_8_9, - FEC_AUTO + FEC_AUTO, + FEC_3_5, + FEC_9_10, } fe_code_rate_t; @@ -160,7 +163,11 @@ typedef enum fe_modulation { QAM_256, QAM_AUTO, VSB_8, - VSB_16 + VSB_16, + _8PSK, + _16APSK, + NBC_QPSK, + DQPSK, } fe_modulation_t; typedef enum fe_transmit_mode { @@ -239,6 +246,139 @@ struct dvb_frontend_event { struct dvb_frontend_parameters parameters; }; +/* TODO: Turn this into a series of defines, so future maintainers + * don't insert random new commands and break backwards + * binary compatability. + */ +typedef enum dtv_cmd_types { + DTV_UNDEFINED, + DTV_TUNE, + DTV_CLEAR, + + DTV_FREQUENCY, + DTV_MODULATION, + + /* XXX PB: I would like to have field which describes the + * bandwidth of a channel in Hz or kHz - maybe we can remove the + * DTV_BANDWIDTH now and put a compat layer */ + DTV_BANDWIDTH_HZ, + + DTV_INVERSION, + DTV_DISEQC_MASTER, + DTV_SYMBOL_RATE, + DTV_INNER_FEC, + DTV_VOLTAGE, + DTV_TONE, + DTV_PILOT, + DTV_ROLLOFF, + + DTV_DISEQC_SLAVE_REPLY, + + /* Basic enumeration set for querying unlimited capabilities */ + DTV_FE_CAPABILITY_COUNT, + DTV_FE_CAPABILITY, + + /* New commands are always appended */ + DTV_DELIVERY_SYSTEM, +#if 0 + /* ISDB */ + /* maybe a dup of DTV_ISDB_SOUND_BROADCASTING_SUBCHANNEL_ID ??? */ + DTV_ISDB_SEGMENT_IDX, + DTV_ISDB_SEGMENT_WIDTH, /* 1, 3 or 13 ??? */ + + /* the central segment can be received independently or 1/3 seg in SB-mode */ + DTV_ISDB_PARTIAL_RECEPTION, + /* sound broadcasting is used 0 = 13segment, 1 = 1 or 3 see DTV_ISDB_PARTIAL_RECEPTION */ + DTV_ISDB_SOUND_BROADCASTING, + + /* only used in SB */ + /* determines the initial PRBS of the segment (to match with 13seg channel) */ + DTV_ISDB_SOUND_BROADCASTING_SUBCHANNEL_ID, + + DTV_ISDB_LAYERA_FEC, + DTV_ISDB_LAYERA_MODULATION, + DTV_ISDB_LAYERA_SEGMENT_WIDTH, + DTV_ISDB_LAYERA_TIME_INTERLEAVER, + + DTV_ISDB_LAYERB_FEC, + DTV_ISDB_LAYERB_MODULATION, + DTV_ISDB_LAYERB_SEGMENT_WIDTH, + DTV_ISDB_LAYERB_TIME_INTERLEAVING, + + DTV_ISDB_LAYERC_FEC, + DTV_ISDB_LAYERC_MODULATION, + DTV_ISDB_LAYERC_SEGMENT_WIDTH, + DTV_ISDB_LAYERC_TIME_INTERLEAVING, +#endif +} dtv_cmd_types_t; + +typedef enum fe_pilot { + PILOT_ON, + PILOT_OFF, + PILOT_AUTO, +} fe_pilot_t; + +typedef enum fe_rolloff { + ROLLOFF_20, + ROLLOFF_25, + ROLLOFF_35, + ROLLOFF_AUTO, +} fe_rolloff_t; + +typedef enum fe_delivery_system { + SYS_UNDEFINED, + SYS_DVBC_ANNEX_AC, + SYS_DVBC_ANNEX_B, + SYS_DVBT, + SYS_DVBS, + SYS_DVBS2, + SYS_DVBH, + SYS_ISDBT, + SYS_ISDBS, + SYS_ISDBC, + SYS_ATSC, + SYS_ATSCMH, + SYS_DMBTH, + SYS_CMMB, + SYS_DAB, +} fe_delivery_system_t; + +struct dtv_cmds_h { + char *name; /* A display name for debugging purposes */ + + __u32 cmd; /* A unique ID */ + + /* Flags */ + __u32 set:1; /* Either a set or get property */ + __u32 buffer:1; /* Does this property use the buffer? */ + __u32 reserved:30; /* Align */ +}; + +struct dtv_property { + __u32 cmd; + __u32 reserved[3]; + union { + __u32 data; + struct { + __u8 data[32]; + __u32 len; + __u32 reserved1[3]; + void *reserved2; + } buffer; + } u; +} __attribute__ ((packed)); + +/* No more than 16 properties during any given ioctl */ +struct dtv_properties { + __u32 num; + struct dtv_property *props; +}; + +#define DTV_IOCTL_MAX_MSGS 64 + +#define FE_SET_PROPERTY _IOW('o', 82, struct dtv_properties) +#define FE_GET_PROPERTY _IOR('o', 83, struct dtv_properties) + /** * When set, this flag will disable any zigzagging or other "normal" tuning |