diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-17 02:20:36 +0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-17 02:20:36 +0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/media/dvb | |
download | linux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.xz |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/media/dvb')
138 files changed, 55188 insertions, 0 deletions
diff --git a/drivers/media/dvb/Kconfig b/drivers/media/dvb/Kconfig new file mode 100644 index 000000000000..883ec08490f4 --- /dev/null +++ b/drivers/media/dvb/Kconfig @@ -0,0 +1,47 @@ +# +# Multimedia device configuration +# + +menu "Digital Video Broadcasting Devices" + +config DVB + bool "DVB For Linux" + depends on NET && INET + ---help--- + Support Digital Video Broadcasting hardware. Enable this if you + own a DVB adapter and want to use it or if you compile Linux for + a digital SetTopBox. + + API specs and user tools are available from <http://www.linuxtv.org/>. + + Please report problems regarding this driver to the LinuxDVB + mailing list. + + If unsure say N. + +source "drivers/media/dvb/dvb-core/Kconfig" + +comment "Supported SAA7146 based PCI Adapters" + depends on DVB_CORE && PCI +source "drivers/media/dvb/ttpci/Kconfig" + +comment "Supported USB Adapters" + depends on DVB_CORE && USB +source "drivers/media/dvb/ttusb-budget/Kconfig" +source "drivers/media/dvb/ttusb-dec/Kconfig" +source "drivers/media/dvb/dibusb/Kconfig" +source "drivers/media/dvb/cinergyT2/Kconfig" + +comment "Supported FlexCopII (B2C2) Adapters" + depends on DVB_CORE && PCI +source "drivers/media/dvb/b2c2/Kconfig" + +comment "Supported BT878 Adapters" + depends on DVB_CORE && PCI +source "drivers/media/dvb/bt8xx/Kconfig" + +comment "Supported DVB Frontends" + depends on DVB_CORE +source "drivers/media/dvb/frontends/Kconfig" + +endmenu diff --git a/drivers/media/dvb/Makefile b/drivers/media/dvb/Makefile new file mode 100644 index 000000000000..520fc3902819 --- /dev/null +++ b/drivers/media/dvb/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the kernel multimedia device drivers. +# + +obj-y := dvb-core/ frontends/ ttpci/ ttusb-dec/ ttusb-budget/ b2c2/ bt8xx/ dibusb/ cinergyT2/ diff --git a/drivers/media/dvb/b2c2/Kconfig b/drivers/media/dvb/b2c2/Kconfig new file mode 100644 index 000000000000..52596907a0be --- /dev/null +++ b/drivers/media/dvb/b2c2/Kconfig @@ -0,0 +1,26 @@ +config DVB_B2C2_SKYSTAR + tristate "B2C2/Technisat Air/Sky/CableStar 2 PCI" + depends on DVB_CORE && PCI + select DVB_STV0299 + select DVB_MT352 + select DVB_MT312 + select DVB_NXT2002 + help + Support for the Skystar2 PCI DVB card by Technisat, which + is equipped with the FlexCopII chipset by B2C2, and + for the B2C2/BBTI Air2PC-ATSC card. + + Say Y if you own such a device and want to use it. + +config DVB_B2C2_USB + tristate "B2C2/Technisat Air/Sky/Cable2PC USB" + depends on DVB_CORE && USB && EXPERIMENTAL + select DVB_STV0299 + select DVB_MT352 + help + Support for the Air/Sky/Cable2PC USB DVB device by B2C2. Currently + the does nothing, but providing basic function for the used usb + protocol. + + Say Y if you own such a device and want to use it. + diff --git a/drivers/media/dvb/b2c2/Makefile b/drivers/media/dvb/b2c2/Makefile new file mode 100644 index 000000000000..9fb1247bfab8 --- /dev/null +++ b/drivers/media/dvb/b2c2/Makefile @@ -0,0 +1,6 @@ +obj-b2c2-usb = b2c2-usb-core.o b2c2-common.o + +obj-$(CONFIG_DVB_B2C2_SKYSTAR) += skystar2.o +obj-$(CONFIG_DVB_B2C2_USB) + = b2c2-usb.o + +EXTRA_CFLAGS = -Idrivers/media/dvb/dvb-core/ -Idrivers/media/dvb/frontends/ diff --git a/drivers/media/dvb/b2c2/b2c2-common.c b/drivers/media/dvb/b2c2/b2c2-common.c new file mode 100644 index 000000000000..000d60c405e3 --- /dev/null +++ b/drivers/media/dvb/b2c2/b2c2-common.c @@ -0,0 +1,214 @@ +/* + * b2c2-common.c - common methods for the B2C2/Technisat SkyStar2 PCI DVB card and + * for the B2C2/Technisat Sky/Cable/AirStar USB devices + * based on the FlexCopII/FlexCopIII by B2C2, Inc. + * + * Copyright (C) 2003 Vadim Catana, skystar@moldova.cc + * + * FIX: DISEQC Tone Burst in flexcop_diseqc_ioctl() + * FIX: FULL soft DiSEqC for skystar2 (FlexCopII rev 130) VP310 equipped + * Vincenzo Di Massa, hawk.it at tiscalinet.it + * + * Converted to Linux coding style + * Misc reorganization, polishing, restyling + * Roberto Ragusa, r.ragusa at libero.it + * + * Added hardware filtering support, + * Niklas Peinecke, peinecke at gdv.uni-hannover.de + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * 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 Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +#include "stv0299.h" +#include "mt352.h" +#include "mt312.h" + +static int samsung_tbmu24112_set_symbol_rate(struct dvb_frontend* fe, u32 srate, u32 ratio) +{ + u8 aclk = 0; + u8 bclk = 0; + + if (srate < 1500000) { aclk = 0xb7; bclk = 0x47; } + else if (srate < 3000000) { aclk = 0xb7; bclk = 0x4b; } + else if (srate < 7000000) { aclk = 0xb7; bclk = 0x4f; } + else if (srate < 14000000) { aclk = 0xb7; bclk = 0x53; } + else if (srate < 30000000) { aclk = 0xb6; bclk = 0x53; } + else if (srate < 45000000) { aclk = 0xb4; bclk = 0x51; } + + stv0299_writereg (fe, 0x13, aclk); + stv0299_writereg (fe, 0x14, bclk); + stv0299_writereg (fe, 0x1f, (ratio >> 16) & 0xff); + stv0299_writereg (fe, 0x20, (ratio >> 8) & 0xff); + stv0299_writereg (fe, 0x21, (ratio ) & 0xf0); + + return 0; +} + +static int samsung_tbmu24112_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + u8 buf[4]; + u32 div; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = buf, .len = sizeof(buf) }; +// struct adapter* adapter = (struct adapter*) fe->dvb->priv; + + div = params->frequency / 125; + + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = 0x84; // 0xC4 + buf[3] = 0x08; + + if (params->frequency < 1500000) buf[3] |= 0x10; + +// if (i2c_transfer (&adapter->i2c_adap, &msg, 1) != 1) return -EIO; + return 0; +} + +static u8 samsung_tbmu24112_inittab[] = { + 0x01, 0x15, + 0x02, 0x30, + 0x03, 0x00, + 0x04, 0x7D, + 0x05, 0x35, + 0x06, 0x02, + 0x07, 0x00, + 0x08, 0xC3, + 0x0C, 0x00, + 0x0D, 0x81, + 0x0E, 0x23, + 0x0F, 0x12, + 0x10, 0x7E, + 0x11, 0x84, + 0x12, 0xB9, + 0x13, 0x88, + 0x14, 0x89, + 0x15, 0xC9, + 0x16, 0x00, + 0x17, 0x5C, + 0x18, 0x00, + 0x19, 0x00, + 0x1A, 0x00, + 0x1C, 0x00, + 0x1D, 0x00, + 0x1E, 0x00, + 0x1F, 0x3A, + 0x20, 0x2E, + 0x21, 0x80, + 0x22, 0xFF, + 0x23, 0xC1, + 0x28, 0x00, + 0x29, 0x1E, + 0x2A, 0x14, + 0x2B, 0x0F, + 0x2C, 0x09, + 0x2D, 0x05, + 0x31, 0x1F, + 0x32, 0x19, + 0x33, 0xFE, + 0x34, 0x93, + 0xff, 0xff, +}; + +static struct stv0299_config samsung_tbmu24112_config = { + .demod_address = 0x68, + .inittab = samsung_tbmu24112_inittab, + .mclk = 88000000UL, + .invert = 0, + .enhanced_tuning = 0, + .skip_reinit = 0, + .lock_output = STV0229_LOCKOUTPUT_LK, + .volt13_op0_op1 = STV0299_VOLT13_OP1, + .min_delay_ms = 100, + .set_symbol_rate = samsung_tbmu24112_set_symbol_rate, + .pll_set = samsung_tbmu24112_pll_set, +}; + + + + + +static int samsung_tdtc9251dh0_demod_init(struct dvb_frontend* fe) +{ + static u8 mt352_clock_config [] = { 0x89, 0x10, 0x2d }; + static u8 mt352_reset [] = { 0x50, 0x80 }; + static u8 mt352_adc_ctl_1_cfg [] = { 0x8E, 0x40 }; + static u8 mt352_agc_cfg [] = { 0x67, 0x28, 0xa1 }; + static u8 mt352_capt_range_cfg[] = { 0x75, 0x32 }; + + mt352_write(fe, mt352_clock_config, sizeof(mt352_clock_config)); + udelay(2000); + mt352_write(fe, mt352_reset, sizeof(mt352_reset)); + mt352_write(fe, mt352_adc_ctl_1_cfg, sizeof(mt352_adc_ctl_1_cfg)); + + mt352_write(fe, mt352_agc_cfg, sizeof(mt352_agc_cfg)); + mt352_write(fe, mt352_capt_range_cfg, sizeof(mt352_capt_range_cfg)); + + return 0; +} + +static int samsung_tdtc9251dh0_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params, u8* pllbuf) +{ + u32 div; + unsigned char bs = 0; + + #define IF_FREQUENCYx6 217 /* 6 * 36.16666666667MHz */ + div = (((params->frequency + 83333) * 3) / 500000) + IF_FREQUENCYx6; + + if (params->frequency >= 48000000 && params->frequency <= 154000000) bs = 0x09; + if (params->frequency >= 161000000 && params->frequency <= 439000000) bs = 0x0a; + if (params->frequency >= 447000000 && params->frequency <= 863000000) bs = 0x08; + + pllbuf[0] = 0xc2; // Note: non-linux standard PLL i2c address + pllbuf[1] = div >> 8; + pllbuf[2] = div & 0xff; + pllbuf[3] = 0xcc; + pllbuf[4] = bs; + + return 0; +} + +static struct mt352_config samsung_tdtc9251dh0_config = { + + .demod_address = 0x0f, + .demod_init = samsung_tdtc9251dh0_demod_init, + .pll_set = samsung_tdtc9251dh0_pll_set, +}; + +static int skystar23_samsung_tbdu18132_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + u8 buf[4]; + u32 div; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = buf, .len = sizeof(buf) }; +// struct adapter* adapter = (struct adapter*) fe->dvb->priv; + + div = (params->frequency + (125/2)) / 125; + + buf[0] = (div >> 8) & 0x7f; + buf[1] = (div >> 0) & 0xff; + buf[2] = 0x84 | ((div >> 10) & 0x60); + buf[3] = 0x80; + + if (params->frequency < 1550000) + buf[3] |= 0x02; + + //if (i2c_transfer (&adapter->i2c_adap, &msg, 1) != 1) return -EIO; + return 0; +} + +static struct mt312_config skystar23_samsung_tbdu18132_config = { + + .demod_address = 0x0e, + .pll_set = skystar23_samsung_tbdu18132_pll_set, +}; diff --git a/drivers/media/dvb/b2c2/b2c2-usb-core.c b/drivers/media/dvb/b2c2/b2c2-usb-core.c new file mode 100644 index 000000000000..9306da046c91 --- /dev/null +++ b/drivers/media/dvb/b2c2/b2c2-usb-core.c @@ -0,0 +1,549 @@ +/* + * Copyright (C) 2004 Patrick Boettcher <patrick.boettcher@desy.de>, + * Luca Bertagnolio <>, + * + * based on information provided by John Jurrius from BBTI, Inc. + * + * 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, version 2. + * + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/usb.h> +#include <linux/moduleparam.h> +#include <linux/pci.h> +#include <linux/version.h> + +#include "dmxdev.h" +#include "dvb_demux.h" +#include "dvb_filter.h" +#include "dvb_net.h" +#include "dvb_frontend.h" + +/* debug */ +#define dprintk(level,args...) \ + do { if ((debug & level)) { printk(args); } } while (0) +#define debug_dump(b,l) if (debug) {\ + int i; deb_xfer("%s: %d > ",__FUNCTION__,l); \ + for (i = 0; i < l; i++) deb_xfer("%02x ", b[i]); \ + deb_xfer("\n");\ +} + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "set debugging level (1=info,ts=2,ctrl=4 (or-able))."); + +#define deb_info(args...) dprintk(0x01,args) +#define deb_ts(args...) dprintk(0x02,args) +#define deb_ctrl(args...) dprintk(0x04,args) + +/* Version information */ +#define DRIVER_VERSION "0.0" +#define DRIVER_DESC "Driver for B2C2/Technisat Air/Cable/Sky-2-PC USB devices" +#define DRIVER_AUTHOR "Patrick Boettcher, patrick.boettcher@desy.de" + +/* transfer parameters */ +#define B2C2_USB_FRAMES_PER_ISO 4 +#define B2C2_USB_NUM_ISO_URB 4 /* TODO check out a good value */ + +#define B2C2_USB_CTRL_PIPE_IN usb_rcvctrlpipe(b2c2->udev,0) +#define B2C2_USB_CTRL_PIPE_OUT usb_sndctrlpipe(b2c2->udev,0) +#define B2C2_USB_DATA_PIPE usb_rcvisocpipe(b2c2->udev,0x81) + +struct usb_b2c2_usb { + struct usb_device *udev; + struct usb_interface *uintf; + + u8 *iso_buffer; + int buffer_size; + dma_addr_t iso_dma_handle; + struct urb *iso_urb[B2C2_USB_NUM_ISO_URB]; +}; + + +/* + * USB + * 10 90 34 12 78 56 04 00 + * usb_control_msg(udev, usb_sndctrlpipe(udev,0), + * 0x90, + * 0x10, + * 0x1234, + * 0x5678, + * buf, + * 4, + * 5*HZ); + * + * extern int usb_control_msg(struct usb_device *dev, unsigned int pipe, + * __u8 request, + * __u8 requesttype, + * __u16 value, + * __u16 index, + * void *data, + * __u16 size, + * int timeout); + * + */ + +/* request types */ +typedef enum { + +/* something is wrong with this part + RTYPE_READ_DW = (1 << 6), + RTYPE_WRITE_DW_1 = (3 << 6), + RTYPE_READ_V8_MEMORY = (6 << 6), + RTYPE_WRITE_V8_MEMORY = (7 << 6), + RTYPE_WRITE_V8_FLASH = (8 << 6), + RTYPE_GENERIC = (9 << 6), +*/ + RTYPE_READ_DW = (3 << 6), + RTYPE_WRITE_DW_1 = (1 << 6), + + RTYPE_READ_V8_MEMORY = (6 << 6), + RTYPE_WRITE_V8_MEMORY = (7 << 6), + RTYPE_WRITE_V8_FLASH = (8 << 6), + RTYPE_GENERIC = (9 << 6), +} b2c2_usb_request_type_t; + +/* request */ +typedef enum { + B2C2_USB_WRITE_V8_MEM = 0x04, + B2C2_USB_READ_V8_MEM = 0x05, + B2C2_USB_READ_REG = 0x08, + B2C2_USB_WRITE_REG = 0x0A, +/* B2C2_USB_WRITEREGLO = 0x0A, */ + B2C2_USB_WRITEREGHI = 0x0B, + B2C2_USB_FLASH_BLOCK = 0x10, + B2C2_USB_I2C_REQUEST = 0x11, + B2C2_USB_UTILITY = 0x12, +} b2c2_usb_request_t; + +/* function definition for I2C_REQUEST */ +typedef enum { + USB_FUNC_I2C_WRITE = 0x01, + USB_FUNC_I2C_MULTIWRITE = 0x02, + USB_FUNC_I2C_READ = 0x03, + USB_FUNC_I2C_REPEATWRITE = 0x04, + USB_FUNC_GET_DESCRIPTOR = 0x05, + USB_FUNC_I2C_REPEATREAD = 0x06, +/* DKT 020208 - add this to support special case of DiSEqC */ + USB_FUNC_I2C_CHECKWRITE = 0x07, + USB_FUNC_I2C_CHECKRESULT = 0x08, +} b2c2_usb_i2c_function_t; + +/* + * function definition for UTILITY request 0x12 + * DKT 020304 - new utility function + */ +typedef enum { + UTILITY_SET_FILTER = 0x01, + UTILITY_DATA_ENABLE = 0x02, + UTILITY_FLEX_MULTIWRITE = 0x03, + UTILITY_SET_BUFFER_SIZE = 0x04, + UTILITY_FLEX_OPERATOR = 0x05, + UTILITY_FLEX_RESET300_START = 0x06, + UTILITY_FLEX_RESET300_STOP = 0x07, + UTILITY_FLEX_RESET300 = 0x08, + UTILITY_SET_ISO_SIZE = 0x09, + UTILITY_DATA_RESET = 0x0A, + UTILITY_GET_DATA_STATUS = 0x10, + UTILITY_GET_V8_REG = 0x11, +/* DKT 020326 - add function for v1.14 */ + UTILITY_SRAM_WRITE = 0x12, + UTILITY_SRAM_READ = 0x13, + UTILITY_SRAM_TESTFILL = 0x14, + UTILITY_SRAM_TESTSET = 0x15, + UTILITY_SRAM_TESTVERIFY = 0x16, +} b2c2_usb_utility_function_t; + +#define B2C2_WAIT_FOR_OPERATION_RW 1 // 1 s +#define B2C2_WAIT_FOR_OPERATION_RDW 3 // 3 s +#define B2C2_WAIT_FOR_OPERATION_WDW 1 // 1 s + +#define B2C2_WAIT_FOR_OPERATION_V8READ 3 // 3 s +#define B2C2_WAIT_FOR_OPERATION_V8WRITE 3 // 3 s +#define B2C2_WAIT_FOR_OPERATION_V8FLASH 3 // 3 s + +/* JLP 111700: we will include the 1 bit gap between the upper and lower 3 bits + * in the IBI address, to make the V8 code simpler. + * PCI ADDRESS FORMAT: 0x71C -> 0000 0111 0001 1100 (these are the six bits used) + * in general: 0000 0HHH 000L LL00 + * IBI ADDRESS FORMAT: RHHH BLLL + * + * where R is the read(1)/write(0) bit, B is the busy bit + * and HHH and LLL are the two sets of three bits from the PCI address. + */ +#define B2C2_FLEX_PCIOFFSET_TO_INTERNALADDR(usPCI) (u8) (((usPCI >> 2) & 0x07) + ((usPCI >> 4) & 0x70)) +#define B2C2_FLEX_INTERNALADDR_TO_PCIOFFSET(ucAddr) (u16) (((ucAddr & 0x07) << 2) + ((ucAddr & 0x70) << 4)) + +/* + * DKT 020228 - forget about this VENDOR_BUFFER_SIZE, read and write register + * deal with DWORD or 4 bytes, that should be should from now on + */ +static u32 b2c2_usb_read_dw(struct usb_b2c2_usb *b2c2, u16 wRegOffsPCI) +{ + u32 val; + u16 wAddress = B2C2_FLEX_PCIOFFSET_TO_INTERNALADDR(wRegOffsPCI) | 0x0080; + int len = usb_control_msg(b2c2->udev, + B2C2_USB_CTRL_PIPE_IN, + B2C2_USB_READ_REG, + RTYPE_READ_DW, + wAddress, + 0, + &val, + sizeof(u32), + B2C2_WAIT_FOR_OPERATION_RDW * 1000); + + if (len != sizeof(u32)) { + err("error while reading dword from %d (%d).",wAddress,wRegOffsPCI); + return -EIO; + } else + return val; +} + +/* + * DKT 020228 - from now on, we don't support anything older than firm 1.00 + * I eliminated the write register as a 2 trip of writing hi word and lo word + * and force this to write only 4 bytes at a time. + * NOTE: this should work with all the firmware from 1.00 and newer + */ +static int b2c2_usb_write_dw(struct usb_b2c2_usb *b2c2, u16 wRegOffsPCI, u32 val) +{ + u16 wAddress = B2C2_FLEX_PCIOFFSET_TO_INTERNALADDR(wRegOffsPCI); + int len = usb_control_msg(b2c2->udev, + B2C2_USB_CTRL_PIPE_OUT, + B2C2_USB_WRITE_REG, + RTYPE_WRITE_DW_1, + wAddress, + 0, + &val, + sizeof(u32), + B2C2_WAIT_FOR_OPERATION_RDW * 1000); + + if (len != sizeof(u32)) { + err("error while reading dword from %d (%d).",wAddress,wRegOffsPCI); + return -EIO; + } else + return 0; +} + +/* + * DKT 010817 - add support for V8 memory read/write and flash update + */ +static int b2c2_usb_v8_memory_req(struct usb_b2c2_usb *b2c2, + b2c2_usb_request_t req, u8 page, u16 wAddress, + u16 buflen, u8 *pbBuffer) +{ + u8 dwRequestType; + u16 wIndex; + int nWaitTime,pipe,len; + + wIndex = page << 8; + + switch (req) { + case B2C2_USB_READ_V8_MEM: + nWaitTime = B2C2_WAIT_FOR_OPERATION_V8READ; + dwRequestType = (u8) RTYPE_READ_V8_MEMORY; + pipe = B2C2_USB_CTRL_PIPE_IN; + break; + case B2C2_USB_WRITE_V8_MEM: + wIndex |= pbBuffer[0]; + nWaitTime = B2C2_WAIT_FOR_OPERATION_V8WRITE; + dwRequestType = (u8) RTYPE_WRITE_V8_MEMORY; + pipe = B2C2_USB_CTRL_PIPE_OUT; + break; + case B2C2_USB_FLASH_BLOCK: + nWaitTime = B2C2_WAIT_FOR_OPERATION_V8FLASH; + dwRequestType = (u8) RTYPE_WRITE_V8_FLASH; + pipe = B2C2_USB_CTRL_PIPE_OUT; + break; + default: + deb_info("unsupported request for v8_mem_req %x.\n",req); + return -EINVAL; + } + len = usb_control_msg(b2c2->udev,pipe, + req, + dwRequestType, + wAddress, + wIndex, + pbBuffer, + buflen, + nWaitTime * 1000); + return len == buflen ? 0 : -EIO; +} + +static int b2c2_usb_i2c_req(struct usb_b2c2_usb *b2c2, + b2c2_usb_request_t req, b2c2_usb_i2c_function_t func, + u8 port, u8 chipaddr, u8 addr, u8 buflen, u8 *buf) +{ + u16 wValue, wIndex; + int nWaitTime,pipe,len; + u8 dwRequestType; + + switch (func) { + case USB_FUNC_I2C_WRITE: + case USB_FUNC_I2C_MULTIWRITE: + case USB_FUNC_I2C_REPEATWRITE: + /* DKT 020208 - add this to support special case of DiSEqC */ + case USB_FUNC_I2C_CHECKWRITE: + pipe = B2C2_USB_CTRL_PIPE_OUT; + nWaitTime = 2; + dwRequestType = (u8) RTYPE_GENERIC; + break; + case USB_FUNC_I2C_READ: + case USB_FUNC_I2C_REPEATREAD: + pipe = B2C2_USB_CTRL_PIPE_IN; + nWaitTime = 2; + dwRequestType = (u8) RTYPE_GENERIC; + break; + default: + deb_info("unsupported function for i2c_req %x\n",func); + return -EINVAL; + } + wValue = (func << 8 ) | port; + wIndex = (chipaddr << 8 ) | addr; + + len = usb_control_msg(b2c2->udev,pipe, + req, + dwRequestType, + addr, + wIndex, + buf, + buflen, + nWaitTime * 1000); + return len == buflen ? 0 : -EIO; +} + +int static b2c2_usb_utility_req(struct usb_b2c2_usb *b2c2, int set, + b2c2_usb_utility_function_t func, u8 extra, u16 wIndex, + u16 buflen, u8 *pvBuffer) +{ + u16 wValue; + int nWaitTime = 2, + pipe = set ? B2C2_USB_CTRL_PIPE_OUT : B2C2_USB_CTRL_PIPE_IN, + len; + + wValue = (func << 8) | extra; + + len = usb_control_msg(b2c2->udev,pipe, + B2C2_USB_UTILITY, + (u8) RTYPE_GENERIC, + wValue, + wIndex, + pvBuffer, + buflen, + nWaitTime * 1000); + return len == buflen ? 0 : -EIO; +} + + + +static void b2c2_dumpfourreg(struct usb_b2c2_usb *b2c2, u16 offs) +{ + u32 r0,r1,r2,r3; + r0 = r1 = r2 = r3 = 0; + r0 = b2c2_usb_read_dw(b2c2,offs); + r1 = b2c2_usb_read_dw(b2c2,offs + 0x04); + r2 = b2c2_usb_read_dw(b2c2,offs + 0x08); + r3 = b2c2_usb_read_dw(b2c2,offs + 0x0c); + deb_ctrl("dump: offset: %03x, %08x, %08x, %08x, %08x\n",offs,r0,r1,r2,r3); +} + +static void b2c2_urb_complete(struct urb *urb, struct pt_regs *ptregs) +{ + struct usb_b2c2_usb *b2c2 = urb->context; + deb_ts("urb completed, bufsize: %d\n",urb->transfer_buffer_length); + +// urb_submit_urb(urb,GFP_ATOMIC); enable for real action +} + +static void b2c2_exit_usb(struct usb_b2c2_usb *b2c2) +{ + int i; + for (i = 0; i < B2C2_USB_NUM_ISO_URB; i++) + if (b2c2->iso_urb[i] != NULL) { /* not sure about unlink_urb and iso-urbs TODO */ + deb_info("unlinking/killing urb no. %d\n",i); +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,7) + usb_unlink_urb(b2c2->iso_urb[i]); +#else + usb_kill_urb(b2c2->iso_urb[i]); +#endif + usb_free_urb(b2c2->iso_urb[i]); + } + + if (b2c2->iso_buffer != NULL) + pci_free_consistent(NULL,b2c2->buffer_size, b2c2->iso_buffer, b2c2->iso_dma_handle); + +} + +static int b2c2_init_usb(struct usb_b2c2_usb *b2c2) +{ + u16 frame_size = le16_to_cpu(b2c2->uintf->cur_altsetting->endpoint[0].desc.wMaxPacketSize); + int bufsize = B2C2_USB_NUM_ISO_URB * B2C2_USB_FRAMES_PER_ISO * frame_size,i,j,ret; + int buffer_offset = 0; + + deb_info("creating %d iso-urbs with %d frames each of %d bytes size = %d.\n", + B2C2_USB_NUM_ISO_URB, B2C2_USB_FRAMES_PER_ISO, frame_size,bufsize); + + b2c2->iso_buffer = pci_alloc_consistent(NULL,bufsize,&b2c2->iso_dma_handle); + if (b2c2->iso_buffer == NULL) + return -ENOMEM; + memset(b2c2->iso_buffer, 0, bufsize); + b2c2->buffer_size = bufsize; + + /* creating iso urbs */ + for (i = 0; i < B2C2_USB_NUM_ISO_URB; i++) + if (!(b2c2->iso_urb[i] = usb_alloc_urb(B2C2_USB_FRAMES_PER_ISO,GFP_ATOMIC))) { + ret = -ENOMEM; + goto urb_error; + } + /* initialising and submitting iso urbs */ + for (i = 0; i < B2C2_USB_NUM_ISO_URB; i++) { + int frame_offset = 0; + struct urb *urb = b2c2->iso_urb[i]; + deb_info("initializing and submitting urb no. %d (buf_offset: %d).\n",i,buffer_offset); + + urb->dev = b2c2->udev; + urb->context = b2c2; + urb->complete = b2c2_urb_complete; + urb->pipe = B2C2_USB_DATA_PIPE; + urb->transfer_flags = URB_ISO_ASAP; + urb->interval = 1; + urb->number_of_packets = B2C2_USB_FRAMES_PER_ISO; + urb->transfer_buffer_length = frame_size * B2C2_USB_FRAMES_PER_ISO; + urb->transfer_buffer = b2c2->iso_buffer + buffer_offset; + + buffer_offset += frame_size * B2C2_USB_FRAMES_PER_ISO; + for (j = 0; j < B2C2_USB_FRAMES_PER_ISO; j++) { + deb_info("urb no: %d, frame: %d, frame_offset: %d\n",i,j,frame_offset); + urb->iso_frame_desc[j].offset = frame_offset; + urb->iso_frame_desc[j].length = frame_size; + frame_offset += frame_size; + } + + if ((ret = usb_submit_urb(b2c2->iso_urb[i],GFP_ATOMIC))) { + err("submitting urb %d failed with %d.",i,ret); + goto urb_error; + } + deb_info("submitted urb no. %d.\n",i); + } + + ret = 0; + goto success; +urb_error: + b2c2_exit_usb(b2c2); +success: + return ret; +} + +static int b2c2_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_b2c2_usb *b2c2 = NULL; + int ret; + + b2c2 = kmalloc(sizeof(struct usb_b2c2_usb),GFP_KERNEL); + if (b2c2 == NULL) { + err("no memory"); + return -ENOMEM; + } + b2c2->udev = udev; + b2c2->uintf = intf; + + /* use the alternate setting with the larges buffer */ + usb_set_interface(udev,0,1); + + if ((ret = b2c2_init_usb(b2c2))) + goto usb_init_error; + + usb_set_intfdata(intf,b2c2); + + switch (udev->speed) { + case USB_SPEED_LOW: + err("cannot handle USB speed because it is to sLOW."); + break; + case USB_SPEED_FULL: + info("running at FULL speed."); + break; + case USB_SPEED_HIGH: + info("running at HIGH speed."); + break; + case USB_SPEED_UNKNOWN: /* fall through */ + default: + err("cannot handle USB speed because it is unkown."); + break; + } + + b2c2_dumpfourreg(b2c2,0x200); + b2c2_dumpfourreg(b2c2,0x300); + b2c2_dumpfourreg(b2c2,0x400); + b2c2_dumpfourreg(b2c2,0x700); + + + if (ret == 0) + info("%s successfully initialized and connected.",DRIVER_DESC); + else + info("%s error while loading driver (%d)",DRIVER_DESC,ret); + + ret = 0; + goto success; + +usb_init_error: + kfree(b2c2); +success: + return ret; +} + +static void b2c2_usb_disconnect(struct usb_interface *intf) +{ + struct usb_b2c2_usb *b2c2 = usb_get_intfdata(intf); + usb_set_intfdata(intf,NULL); + if (b2c2 != NULL) { + b2c2_exit_usb(b2c2); + kfree(b2c2); + } + info("%s successfully deinitialized and disconnected.",DRIVER_DESC); + +} + +static struct usb_device_id b2c2_usb_table [] = { + { USB_DEVICE(0x0af7, 0x0101) } +}; + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver b2c2_usb_driver = { + .owner = THIS_MODULE, + .name = "dvb_b2c2_usb", + .probe = b2c2_usb_probe, + .disconnect = b2c2_usb_disconnect, + .id_table = b2c2_usb_table, +}; + +/* module stuff */ +static int __init b2c2_usb_init(void) +{ + int result; + if ((result = usb_register(&b2c2_usb_driver))) { + err("usb_register failed. Error number %d",result); + return result; + } + + return 0; +} + +static void __exit b2c2_usb_exit(void) +{ + /* deregister this driver from the USB subsystem */ + usb_deregister(&b2c2_usb_driver); +} + +module_init (b2c2_usb_init); +module_exit (b2c2_usb_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(usb, b2c2_usb_table); diff --git a/drivers/media/dvb/b2c2/skystar2.c b/drivers/media/dvb/b2c2/skystar2.c new file mode 100644 index 000000000000..336c178fcd5f --- /dev/null +++ b/drivers/media/dvb/b2c2/skystar2.c @@ -0,0 +1,2644 @@ +/* + * skystar2.c - driver for the Technisat SkyStar2 PCI DVB card + * based on the FlexCopII by B2C2,Inc. + * + * Copyright (C) 2003 Vadim Catana, skystar@moldova.cc + * + * FIX: DISEQC Tone Burst in flexcop_diseqc_ioctl() + * FIX: FULL soft DiSEqC for skystar2 (FlexCopII rev 130) VP310 equipped + * Vincenzo Di Massa, hawk.it at tiscalinet.it + * + * Converted to Linux coding style + * Misc reorganization, polishing, restyling + * Roberto Ragusa, skystar2-c5b8 at robertoragusa dot it + * + * Added hardware filtering support, + * Niklas Peinecke, peinecke at gdv.uni-hannover.de + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * 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 Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/version.h> + +#include <asm/io.h> + +#include "dvb_frontend.h" + +#include <linux/dvb/frontend.h> +#include <linux/dvb/dmx.h> +#include "dvb_demux.h" +#include "dmxdev.h" +#include "dvb_filter.h" +#include "dvbdev.h" +#include "demux.h" +#include "dvb_net.h" +#include "stv0299.h" +#include "mt352.h" +#include "mt312.h" +#include "nxt2002.h" + +static int debug; +static int enable_hw_filters = 2; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Set debugging level (0 = default, 1 = most messages, 2 = all messages)."); +module_param(enable_hw_filters, int, 0444); +MODULE_PARM_DESC(enable_hw_filters, "enable hardware filters: supported values: 0 (none), 1, 2"); + +#define dprintk(x...) do { if (debug>=1) printk(x); } while (0) +#define ddprintk(x...) do { if (debug>=2) printk(x); } while (0) + +#define SIZE_OF_BUF_DMA1 0x3ac00 +#define SIZE_OF_BUF_DMA2 0x758 + +#define MAX_N_HW_FILTERS (6+32) +#define N_PID_SLOTS 256 + +struct dmaq { + u32 bus_addr; + u32 head; + u32 tail; + u32 buffer_size; + u8 *buffer; +}; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,9) +#define __iomem +#endif + +struct adapter { + struct pci_dev *pdev; + + u8 card_revision; + u32 b2c2_revision; + u32 pid_filter_max; + u32 mac_filter_max; + u32 irq; + void __iomem *io_mem; + unsigned long io_port; + u8 mac_addr[8]; + u32 dw_sram_type; + + struct dvb_adapter *dvb_adapter; + struct dvb_demux demux; + struct dmxdev dmxdev; + struct dmx_frontend hw_frontend; + struct dmx_frontend mem_frontend; + struct i2c_adapter i2c_adap; + struct dvb_net dvbnet; + + struct semaphore i2c_sem; + + struct dmaq dmaq1; + struct dmaq dmaq2; + + u32 dma_ctrl; + u32 dma_status; + + int capturing; + + spinlock_t lock; + + int useable_hw_filters; + u16 hw_pids[MAX_N_HW_FILTERS]; + u16 pid_list[N_PID_SLOTS]; + int pid_rc[N_PID_SLOTS]; // ref counters for the pids + int pid_count; + int whole_bandwidth_count; + u32 mac_filter; + + struct dvb_frontend* fe; + int (*fe_sleep)(struct dvb_frontend* fe); +}; + +#define write_reg_dw(adapter,reg,value) writel(value, adapter->io_mem + reg) +#define read_reg_dw(adapter,reg) readl(adapter->io_mem + reg) + +static void write_reg_bitfield(struct adapter *adapter, u32 reg, u32 zeromask, u32 orvalue) +{ + u32 tmp; + + tmp = read_reg_dw(adapter, reg); + tmp = (tmp & ~zeromask) | orvalue; + write_reg_dw(adapter, reg, tmp); +} + +/* i2c functions */ +static int i2c_main_write_for_flex2(struct adapter *adapter, u32 command, u8 *buf, int retries) +{ + int i; + u32 value; + + write_reg_dw(adapter, 0x100, 0); + write_reg_dw(adapter, 0x100, command); + + for (i = 0; i < retries; i++) { + value = read_reg_dw(adapter, 0x100); + + if ((value & 0x40000000) == 0) { + if ((value & 0x81000000) == 0x80000000) { + if (buf != 0) + *buf = (value >> 0x10) & 0xff; + + return 1; + } + } else { + write_reg_dw(adapter, 0x100, 0); + write_reg_dw(adapter, 0x100, command); + } + } + + return 0; +} + +/* device = 0x10000000 for tuner, 0x20000000 for eeprom */ +static void i2c_main_setup(u32 device, u32 chip_addr, u8 op, u8 addr, u32 value, u32 len, u32 *command) +{ + *command = device | ((len - 1) << 26) | (value << 16) | (addr << 8) | chip_addr; + + if (op != 0) + *command = *command | 0x03000000; + else + *command = *command | 0x01000000; +} + +static int flex_i2c_read4(struct adapter *adapter, u32 device, u32 chip_addr, u16 addr, u8 *buf, u8 len) +{ + u32 command; + u32 value; + + int result, i; + + i2c_main_setup(device, chip_addr, 1, addr, 0, len, &command); + + result = i2c_main_write_for_flex2(adapter, command, buf, 100000); + + if ((result & 0xff) != 0) { + if (len > 1) { + value = read_reg_dw(adapter, 0x104); + + for (i = 1; i < len; i++) { + buf[i] = value & 0xff; + value = value >> 8; + } + } + } + + return result; +} + +static int flex_i2c_write4(struct adapter *adapter, u32 device, u32 chip_addr, u32 addr, u8 *buf, u8 len) +{ + u32 command; + u32 value; + int i; + + if (len > 1) { + value = 0; + + for (i = len; i > 1; i--) { + value = value << 8; + value = value | buf[i - 1]; + } + + write_reg_dw(adapter, 0x104, value); + } + + i2c_main_setup(device, chip_addr, 0, addr, buf[0], len, &command); + + return i2c_main_write_for_flex2(adapter, command, NULL, 100000); +} + +static void fixchipaddr(u32 device, u32 bus, u32 addr, u32 *ret) +{ + if (device == 0x20000000) + *ret = bus | ((addr >> 8) & 3); + else + *ret = bus; +} + +static u32 flex_i2c_read(struct adapter *adapter, u32 device, u32 bus, u32 addr, u8 *buf, u32 len) +{ + u32 chipaddr; + u32 bytes_to_transfer; + u8 *start; + + ddprintk("%s:\n", __FUNCTION__); + + start = buf; + + while (len != 0) { + bytes_to_transfer = len; + + if (bytes_to_transfer > 4) + bytes_to_transfer = 4; + + fixchipaddr(device, bus, addr, &chipaddr); + + if (flex_i2c_read4(adapter, device, chipaddr, addr, buf, bytes_to_transfer) == 0) + return buf - start; + + buf = buf + bytes_to_transfer; + addr = addr + bytes_to_transfer; + len = len - bytes_to_transfer; + }; + + return buf - start; +} + +static u32 flex_i2c_write(struct adapter *adapter, u32 device, u32 bus, u32 addr, u8 *buf, u32 len) +{ + u32 chipaddr; + u32 bytes_to_transfer; + u8 *start; + + ddprintk("%s:\n", __FUNCTION__); + + start = buf; + + while (len != 0) { + bytes_to_transfer = len; + + if (bytes_to_transfer > 4) + bytes_to_transfer = 4; + + fixchipaddr(device, bus, addr, &chipaddr); + + if (flex_i2c_write4(adapter, device, chipaddr, addr, buf, bytes_to_transfer) == 0) + return buf - start; + + buf = buf + bytes_to_transfer; + addr = addr + bytes_to_transfer; + len = len - bytes_to_transfer; + } + + return buf - start; +} + +static int master_xfer(struct i2c_adapter* adapter, struct i2c_msg *msgs, int num) +{ + struct adapter *tmp = i2c_get_adapdata(adapter); + int i, ret = 0; + + if (down_interruptible(&tmp->i2c_sem)) + return -ERESTARTSYS; + + ddprintk("%s: %d messages to transfer\n", __FUNCTION__, num); + + for (i = 0; i < num; i++) { + ddprintk("message %d: flags=0x%x, addr=0x%x, buf=0x%x, len=%d \n", i, + msgs[i].flags, msgs[i].addr, msgs[i].buf[0], msgs[i].len); + } + + // read command + if ((num == 2) && (msgs[0].flags == 0) && (msgs[1].flags == I2C_M_RD) && (msgs[0].buf != NULL) && (msgs[1].buf != NULL)) { + + ret = flex_i2c_read(tmp, 0x10000000, msgs[0].addr, msgs[0].buf[0], msgs[1].buf, msgs[1].len); + + up(&tmp->i2c_sem); + + if (ret != msgs[1].len) { + dprintk("%s: read error !\n", __FUNCTION__); + + for (i = 0; i < 2; i++) { + dprintk("message %d: flags=0x%x, addr=0x%x, buf=0x%x, len=%d \n", i, + msgs[i].flags, msgs[i].addr, msgs[i].buf[0], msgs[i].len); + } + + return -EREMOTEIO; + } + + return num; + } + // write command + for (i = 0; i < num; i++) { + + if ((msgs[i].flags != 0) || (msgs[i].buf == NULL) || (msgs[i].len < 2)) + return -EINVAL; + + ret = flex_i2c_write(tmp, 0x10000000, msgs[i].addr, msgs[i].buf[0], &msgs[i].buf[1], msgs[i].len - 1); + + up(&tmp->i2c_sem); + + if (ret != msgs[0].len - 1) { + dprintk("%s: write error %i !\n", __FUNCTION__, ret); + + dprintk("message %d: flags=0x%x, addr=0x%x, buf[0]=0x%x, len=%d \n", i, + msgs[i].flags, msgs[i].addr, msgs[i].buf[0], msgs[i].len); + + return -EREMOTEIO; + } + + return num; + } + + printk("%s: unknown command format !\n", __FUNCTION__); + + return -EINVAL; +} + +/* SRAM (Skystar2 rev2.3 has one "ISSI IS61LV256" chip on board, + but it seems that FlexCopII can work with more than one chip) */ +static void sram_set_net_dest(struct adapter *adapter, u8 dest) +{ + u32 tmp; + + udelay(1000); + + tmp = (read_reg_dw(adapter, 0x714) & 0xfffffffc) | (dest & 3); + + udelay(1000); + + write_reg_dw(adapter, 0x714, tmp); + write_reg_dw(adapter, 0x714, tmp); + + udelay(1000); + + /* return value is never used? */ +/* return tmp; */ +} + +static void sram_set_cai_dest(struct adapter *adapter, u8 dest) +{ + u32 tmp; + + udelay(1000); + + tmp = (read_reg_dw(adapter, 0x714) & 0xfffffff3) | ((dest & 3) << 2); + + udelay(1000); + udelay(1000); + + write_reg_dw(adapter, 0x714, tmp); + write_reg_dw(adapter, 0x714, tmp); + + udelay(1000); + + /* return value is never used? */ +/* return tmp; */ +} + +static void sram_set_cao_dest(struct adapter *adapter, u8 dest) +{ + u32 tmp; + + udelay(1000); + + tmp = (read_reg_dw(adapter, 0x714) & 0xffffffcf) | ((dest & 3) << 4); + + udelay(1000); + udelay(1000); + + write_reg_dw(adapter, 0x714, tmp); + write_reg_dw(adapter, 0x714, tmp); + + udelay(1000); + + /* return value is never used? */ +/* return tmp; */ +} + +static void sram_set_media_dest(struct adapter *adapter, u8 dest) +{ + u32 tmp; + + udelay(1000); + + tmp = (read_reg_dw(adapter, 0x714) & 0xffffff3f) | ((dest & 3) << 6); + + udelay(1000); + udelay(1000); + + write_reg_dw(adapter, 0x714, tmp); + write_reg_dw(adapter, 0x714, tmp); + + udelay(1000); + + /* return value is never used? */ +/* return tmp; */ +} + +/* SRAM memory is accessed through a buffer register in the FlexCop + chip (0x700). This register has the following structure: + bits 0-14 : address + bit 15 : read/write flag + bits 16-23 : 8-bit word to write + bits 24-27 : = 4 + bits 28-29 : memory bank selector + bit 31 : busy flag +*/ +static void flex_sram_write(struct adapter *adapter, u32 bank, u32 addr, u8 *buf, u32 len) +{ + int i, retries; + u32 command; + + for (i = 0; i < len; i++) { + command = bank | addr | 0x04000000 | (*buf << 0x10); + + retries = 2; + + while (((read_reg_dw(adapter, 0x700) & 0x80000000) != 0) && (retries > 0)) { + mdelay(1); + retries--; + }; + + if (retries == 0) + printk("%s: SRAM timeout\n", __FUNCTION__); + + write_reg_dw(adapter, 0x700, command); + + buf++; + addr++; + } +} + +static void flex_sram_read(struct adapter *adapter, u32 bank, u32 addr, u8 *buf, u32 len) +{ + int i, retries; + u32 command, value; + + for (i = 0; i < len; i++) { + command = bank | addr | 0x04008000; + + retries = 10000; + + while (((read_reg_dw(adapter, 0x700) & 0x80000000) != 0) && (retries > 0)) { + mdelay(1); + retries--; + }; + + if (retries == 0) + printk("%s: SRAM timeout\n", __FUNCTION__); + + write_reg_dw(adapter, 0x700, command); + + retries = 10000; + + while (((read_reg_dw(adapter, 0x700) & 0x80000000) != 0) && (retries > 0)) { + mdelay(1); + retries--; + }; + + if (retries == 0) + printk("%s: SRAM timeout\n", __FUNCTION__); + + value = read_reg_dw(adapter, 0x700) >> 0x10; + + *buf = (value & 0xff); + + addr++; + buf++; + } +} + +static void sram_write_chunk(struct adapter *adapter, u32 addr, u8 *buf, u16 len) +{ + u32 bank; + + bank = 0; + + if (adapter->dw_sram_type == 0x20000) { + bank = (addr & 0x18000) << 0x0d; + } + + if (adapter->dw_sram_type == 0x00000) { + if ((addr >> 0x0f) == 0) + bank = 0x20000000; + else + bank = 0x10000000; + } + + flex_sram_write(adapter, bank, addr & 0x7fff, buf, len); +} + +static void sram_read_chunk(struct adapter *adapter, u32 addr, u8 *buf, u16 len) +{ + u32 bank; + + bank = 0; + + if (adapter->dw_sram_type == 0x20000) { + bank = (addr & 0x18000) << 0x0d; + } + + if (adapter->dw_sram_type == 0x00000) { + if ((addr >> 0x0f) == 0) + bank = 0x20000000; + else + bank = 0x10000000; + } + + flex_sram_read(adapter, bank, addr & 0x7fff, buf, len); +} + +static void sram_read(struct adapter *adapter, u32 addr, u8 *buf, u32 len) +{ + u32 length; + + while (len != 0) { + length = len; + + // check if the address range belongs to the same + // 32K memory chip. If not, the data is read from + // one chip at a time. + if ((addr >> 0x0f) != ((addr + len - 1) >> 0x0f)) { + length = (((addr >> 0x0f) + 1) << 0x0f) - addr; + } + + sram_read_chunk(adapter, addr, buf, length); + + addr = addr + length; + buf = buf + length; + len = len - length; + } +} + +static void sram_write(struct adapter *adapter, u32 addr, u8 *buf, u32 len) +{ + u32 length; + + while (len != 0) { + length = len; + + // check if the address range belongs to the same + // 32K memory chip. If not, the data is written to + // one chip at a time. + if ((addr >> 0x0f) != ((addr + len - 1) >> 0x0f)) { + length = (((addr >> 0x0f) + 1) << 0x0f) - addr; + } + + sram_write_chunk(adapter, addr, buf, length); + + addr = addr + length; + buf = buf + length; + len = len - length; + } +} + +static void sram_set_size(struct adapter *adapter, u32 mask) +{ + write_reg_dw(adapter, 0x71c, (mask | (~0x30000 & read_reg_dw(adapter, 0x71c)))); +} + +static void sram_init(struct adapter *adapter) +{ + u32 tmp; + + tmp = read_reg_dw(adapter, 0x71c); + + write_reg_dw(adapter, 0x71c, 1); + + if (read_reg_dw(adapter, 0x71c) != 0) { + write_reg_dw(adapter, 0x71c, tmp); + + adapter->dw_sram_type = tmp & 0x30000; + + ddprintk("%s: dw_sram_type = %x\n", __FUNCTION__, adapter->dw_sram_type); + + } else { + + adapter->dw_sram_type = 0x10000; + + ddprintk("%s: dw_sram_type = %x\n", __FUNCTION__, adapter->dw_sram_type); + } + + /* return value is never used? */ +/* return adapter->dw_sram_type; */ +} + +static int sram_test_location(struct adapter *adapter, u32 mask, u32 addr) +{ + u8 tmp1, tmp2; + + dprintk("%s: mask = %x, addr = %x\n", __FUNCTION__, mask, addr); + + sram_set_size(adapter, mask); + sram_init(adapter); + + tmp2 = 0xa5; + tmp1 = 0x4f; + + sram_write(adapter, addr, &tmp2, 1); + sram_write(adapter, addr + 4, &tmp1, 1); + + tmp2 = 0; + + mdelay(20); + + sram_read(adapter, addr, &tmp2, 1); + sram_read(adapter, addr, &tmp2, 1); + + dprintk("%s: wrote 0xa5, read 0x%2x\n", __FUNCTION__, tmp2); + + if (tmp2 != 0xa5) + return 0; + + tmp2 = 0x5a; + tmp1 = 0xf4; + + sram_write(adapter, addr, &tmp2, 1); + sram_write(adapter, addr + 4, &tmp1, 1); + + tmp2 = 0; + + mdelay(20); + + sram_read(adapter, addr, &tmp2, 1); + sram_read(adapter, addr, &tmp2, 1); + + dprintk("%s: wrote 0x5a, read 0x%2x\n", __FUNCTION__, tmp2); + + if (tmp2 != 0x5a) + return 0; + + return 1; +} + +static u32 sram_length(struct adapter *adapter) +{ + if (adapter->dw_sram_type == 0x10000) + return 32768; // 32K + if (adapter->dw_sram_type == 0x00000) + return 65536; // 64K + if (adapter->dw_sram_type == 0x20000) + return 131072; // 128K + + return 32768; // 32K +} + +/* FlexcopII can work with 32K, 64K or 128K of external SRAM memory. + - for 128K there are 4x32K chips at bank 0,1,2,3. + - for 64K there are 2x32K chips at bank 1,2. + - for 32K there is one 32K chip at bank 0. + + FlexCop works only with one bank at a time. The bank is selected + by bits 28-29 of the 0x700 register. + + bank 0 covers addresses 0x00000-0x07fff + bank 1 covers addresses 0x08000-0x0ffff + bank 2 covers addresses 0x10000-0x17fff + bank 3 covers addresses 0x18000-0x1ffff +*/ +static int sram_detect_for_flex2(struct adapter *adapter) +{ + u32 tmp, tmp2, tmp3; + + dprintk("%s:\n", __FUNCTION__); + + tmp = read_reg_dw(adapter, 0x208); + write_reg_dw(adapter, 0x208, 0); + + tmp2 = read_reg_dw(adapter, 0x71c); + + dprintk("%s: tmp2 = %x\n", __FUNCTION__, tmp2); + + write_reg_dw(adapter, 0x71c, 1); + + tmp3 = read_reg_dw(adapter, 0x71c); + + dprintk("%s: tmp3 = %x\n", __FUNCTION__, tmp3); + + write_reg_dw(adapter, 0x71c, tmp2); + + // check for internal SRAM ??? + tmp3--; + if (tmp3 != 0) { + sram_set_size(adapter, 0x10000); + sram_init(adapter); + write_reg_dw(adapter, 0x208, tmp); + + dprintk("%s: sram size = 32K\n", __FUNCTION__); + + return 32; + } + + if (sram_test_location(adapter, 0x20000, 0x18000) != 0) { + sram_set_size(adapter, 0x20000); + sram_init(adapter); + write_reg_dw(adapter, 0x208, tmp); + + dprintk("%s: sram size = 128K\n", __FUNCTION__); + + return 128; + } + + if (sram_test_location(adapter, 0x00000, 0x10000) != 0) { + sram_set_size(adapter, 0x00000); + sram_init(adapter); + write_reg_dw(adapter, 0x208, tmp); + + dprintk("%s: sram size = 64K\n", __FUNCTION__); + + return 64; + } + + if (sram_test_location(adapter, 0x10000, 0x00000) != 0) { + sram_set_size(adapter, 0x10000); + sram_init(adapter); + write_reg_dw(adapter, 0x208, tmp); + + dprintk("%s: sram size = 32K\n", __FUNCTION__); + + return 32; + } + + sram_set_size(adapter, 0x10000); + sram_init(adapter); + write_reg_dw(adapter, 0x208, tmp); + + dprintk("%s: SRAM detection failed. Set to 32K \n", __FUNCTION__); + + return 0; +} + +static void sll_detect_sram_size(struct adapter *adapter) +{ + sram_detect_for_flex2(adapter); +} + +/* EEPROM (Skystar2 has one "24LC08B" chip on board) */ +/* +static int eeprom_write(struct adapter *adapter, u16 addr, u8 *buf, u16 len) +{ + return flex_i2c_write(adapter, 0x20000000, 0x50, addr, buf, len); +} +*/ + +static int eeprom_read(struct adapter *adapter, u16 addr, u8 *buf, u16 len) +{ + return flex_i2c_read(adapter, 0x20000000, 0x50, addr, buf, len); +} + +static u8 calc_lrc(u8 *buf, int len) +{ + int i; + u8 sum; + + sum = 0; + + for (i = 0; i < len; i++) + sum = sum ^ buf[i]; + + return sum; +} + +static int eeprom_lrc_read(struct adapter *adapter, u32 addr, u32 len, u8 *buf, int retries) +{ + int i; + + for (i = 0; i < retries; i++) { + if (eeprom_read(adapter, addr, buf, len) == len) { + if (calc_lrc(buf, len - 1) == buf[len - 1]) + return 1; + } + } + + return 0; +} + +/* +static int eeprom_lrc_write(struct adapter *adapter, u32 addr, u32 len, u8 *wbuf, u8 *rbuf, int retries) +{ + int i; + + for (i = 0; i < retries; i++) { + if (eeprom_write(adapter, addr, wbuf, len) == len) { + if (eeprom_lrc_read(adapter, addr, len, rbuf, retries) == 1) + return 1; + } + } + + return 0; +} +*/ + + +/* These functions could be used to unlock SkyStar2 cards. */ + +/* +static int eeprom_writeKey(struct adapter *adapter, u8 *key, u32 len) +{ + u8 rbuf[20]; + u8 wbuf[20]; + + if (len != 16) + return 0; + + memcpy(wbuf, key, len); + + wbuf[16] = 0; + wbuf[17] = 0; + wbuf[18] = 0; + wbuf[19] = calc_lrc(wbuf, 19); + + return eeprom_lrc_write(adapter, 0x3e4, 20, wbuf, rbuf, 4); +} + +static int eeprom_readKey(struct adapter *adapter, u8 *key, u32 len) +{ + u8 buf[20]; + + if (len != 16) + return 0; + + if (eeprom_lrc_read(adapter, 0x3e4, 20, buf, 4) == 0) + return 0; + + memcpy(key, buf, len); + + return 1; +} +*/ + +static int eeprom_get_mac_addr(struct adapter *adapter, char type, u8 *mac) +{ + u8 tmp[8]; + + if (eeprom_lrc_read(adapter, 0x3f8, 8, tmp, 4) != 0) { + if (type != 0) { + mac[0] = tmp[0]; + mac[1] = tmp[1]; + mac[2] = tmp[2]; + mac[3] = 0xfe; + mac[4] = 0xff; + mac[5] = tmp[3]; + mac[6] = tmp[4]; + mac[7] = tmp[5]; + + } else { + + mac[0] = tmp[0]; + mac[1] = tmp[1]; + mac[2] = tmp[2]; + mac[3] = tmp[3]; + mac[4] = tmp[4]; + mac[5] = tmp[5]; + } + + return 1; + + } else { + + if (type == 0) { + memset(mac, 0, 6); + + } else { + + memset(mac, 0, 8); + } + + return 0; + } +} + +/* +static char eeprom_set_mac_addr(struct adapter *adapter, char type, u8 *mac) +{ + u8 tmp[8]; + + if (type != 0) { + tmp[0] = mac[0]; + tmp[1] = mac[1]; + tmp[2] = mac[2]; + tmp[3] = mac[5]; + tmp[4] = mac[6]; + tmp[5] = mac[7]; + + } else { + + tmp[0] = mac[0]; + tmp[1] = mac[1]; + tmp[2] = mac[2]; + tmp[3] = mac[3]; + tmp[4] = mac[4]; + tmp[5] = mac[5]; + } + + tmp[6] = 0; + tmp[7] = calc_lrc(tmp, 7); + + if (eeprom_write(adapter, 0x3f8, tmp, 8) == 8) + return 1; + + return 0; +} +*/ + +/* PID filter */ + +/* every flexcop has 6 "lower" hw PID filters */ +/* these are enabled by setting bits 0-5 of 0x208 */ +/* for the 32 additional filters we have to select one */ +/* of them through 0x310 and modify through 0x314 */ +/* op: 0=disable, 1=enable */ +static void filter_enable_hw_filter(struct adapter *adapter, int id, u8 op) +{ + dprintk("%s: id=%d op=%d\n", __FUNCTION__, id, op); + if (id <= 5) { + u32 mask = (0x00000001 << id); + write_reg_bitfield(adapter, 0x208, mask, op ? mask : 0); + } else { + /* select */ + write_reg_bitfield(adapter, 0x310, 0x1f, (id - 6) & 0x1f); + /* modify */ + write_reg_bitfield(adapter, 0x314, 0x00006000, op ? 0x00004000 : 0); + } +} + +/* this sets the PID that should pass the specified filter */ +static void pid_set_hw_pid(struct adapter *adapter, int id, u16 pid) +{ + dprintk("%s: id=%d pid=%d\n", __FUNCTION__, id, pid); + if (id <= 5) { + u32 adr = 0x300 + ((id & 6) << 1); + int shift = (id & 1) ? 16 : 0; + dprintk("%s: id=%d addr=%x %c pid=%d\n", __FUNCTION__, id, adr, (id & 1) ? 'h' : 'l', pid); + write_reg_bitfield(adapter, adr, (0x7fff) << shift, (pid & 0x1fff) << shift); + } else { + /* select */ + write_reg_bitfield(adapter, 0x310, 0x1f, (id - 6) & 0x1f); + /* modify */ + write_reg_bitfield(adapter, 0x314, 0x1fff, pid & 0x1fff); + } +} + + +/* +static void filter_enable_null_filter(struct adapter *adapter, u32 op) +{ + dprintk("%s: op=%x\n", __FUNCTION__, op); + + write_reg_bitfield(adapter, 0x208, 0x00000040, op?0x00000040:0); +} +*/ + +static void filter_enable_mask_filter(struct adapter *adapter, u32 op) +{ + dprintk("%s: op=%x\n", __FUNCTION__, op); + + write_reg_bitfield(adapter, 0x208, 0x00000080, op ? 0x00000080 : 0); +} + + +static void ctrl_enable_mac(struct adapter *adapter, u32 op) +{ + write_reg_bitfield(adapter, 0x208, 0x00004000, op ? 0x00004000 : 0); +} + +static int ca_set_mac_dst_addr_filter(struct adapter *adapter, u8 *mac) +{ + u32 tmp1, tmp2; + + tmp1 = (mac[3] << 0x18) | (mac[2] << 0x10) | (mac[1] << 0x08) | mac[0]; + tmp2 = (mac[5] << 0x08) | mac[4]; + + write_reg_dw(adapter, 0x418, tmp1); + write_reg_dw(adapter, 0x41c, tmp2); + + return 0; +} + +/* +static void set_ignore_mac_filter(struct adapter *adapter, u8 op) +{ + if (op != 0) { + write_reg_bitfield(adapter, 0x208, 0x00004000, 0); + adapter->mac_filter = 1; + } else { + if (adapter->mac_filter != 0) { + adapter->mac_filter = 0; + write_reg_bitfield(adapter, 0x208, 0x00004000, 0x00004000); + } + } +} +*/ + +/* +static void check_null_filter_enable(struct adapter *adapter) +{ + filter_enable_null_filter(adapter, 1); + filter_enable_mask_filter(adapter, 1); +} +*/ + +static void pid_set_group_pid(struct adapter *adapter, u16 pid) +{ + u32 value; + + dprintk("%s: pid=%x\n", __FUNCTION__, pid); + value = (pid & 0x3fff) | (read_reg_dw(adapter, 0x30c) & 0xffff0000); + write_reg_dw(adapter, 0x30c, value); +} + +static void pid_set_group_mask(struct adapter *adapter, u16 pid) +{ + u32 value; + + dprintk("%s: pid=%x\n", __FUNCTION__, pid); + value = ((pid & 0x3fff) << 0x10) | (read_reg_dw(adapter, 0x30c) & 0xffff); + write_reg_dw(adapter, 0x30c, value); +} + +/* +static int pid_get_group_pid(struct adapter *adapter) +{ + return read_reg_dw(adapter, 0x30c) & 0x00001fff; +} + +static int pid_get_group_mask(struct adapter *adapter) +{ + return (read_reg_dw(adapter, 0x30c) >> 0x10)& 0x00001fff; +} +*/ + +/* +static void reset_hardware_pid_filter(struct adapter *adapter) +{ + pid_set_stream1_pid(adapter, 0x1fff); + + pid_set_stream2_pid(adapter, 0x1fff); + filter_enable_stream2_filter(adapter, 0); + + pid_set_pcr_pid(adapter, 0x1fff); + filter_enable_pcr_filter(adapter, 0); + + pid_set_pmt_pid(adapter, 0x1fff); + filter_enable_pmt_filter(adapter, 0); + + pid_set_ecm_pid(adapter, 0x1fff); + filter_enable_ecm_filter(adapter, 0); + + pid_set_emm_pid(adapter, 0x1fff); + filter_enable_emm_filter(adapter, 0); +} +*/ + +static void init_pids(struct adapter *adapter) +{ + int i; + + adapter->pid_count = 0; + adapter->whole_bandwidth_count = 0; + for (i = 0; i < adapter->useable_hw_filters; i++) { + dprintk("%s: setting filter %d to 0x1fff\n", __FUNCTION__, i); + adapter->hw_pids[i] = 0x1fff; + pid_set_hw_pid(adapter, i, 0x1fff); +} + + pid_set_group_pid(adapter, 0); + pid_set_group_mask(adapter, 0x1fe0); +} + +static void open_whole_bandwidth(struct adapter *adapter) +{ + dprintk("%s:\n", __FUNCTION__); + pid_set_group_pid(adapter, 0); + pid_set_group_mask(adapter, 0); +/* + filter_enable_mask_filter(adapter, 1); +*/ +} + +static void close_whole_bandwidth(struct adapter *adapter) +{ + dprintk("%s:\n", __FUNCTION__); + pid_set_group_pid(adapter, 0); + pid_set_group_mask(adapter, 0x1fe0); +/* + filter_enable_mask_filter(adapter, 1); +*/ +} + +static void whole_bandwidth_inc(struct adapter *adapter) +{ + if (adapter->whole_bandwidth_count++ == 0) + open_whole_bandwidth(adapter); +} + +static void whole_bandwidth_dec(struct adapter *adapter) +{ + if (--adapter->whole_bandwidth_count <= 0) + close_whole_bandwidth(adapter); +} + +/* The specified PID has to be let through the + hw filters. + We try to allocate an hardware filter and open whole + bandwidth when allocation is impossible. + All pids<=0x1f pass through the group filter. + Returns 1 on success, -1 on error */ +static int add_hw_pid(struct adapter *adapter, u16 pid) +{ + int i; + + dprintk("%s: pid=%d\n", __FUNCTION__, pid); + + if (pid <= 0x1f) + return 1; + + /* we can't use a filter for 0x2000, so no search */ + if (pid != 0x2000) { + /* find an unused hardware filter */ + for (i = 0; i < adapter->useable_hw_filters; i++) { + dprintk("%s: pid=%d searching slot=%d\n", __FUNCTION__, pid, i); + if (adapter->hw_pids[i] == 0x1fff) { + dprintk("%s: pid=%d slot=%d\n", __FUNCTION__, pid, i); + adapter->hw_pids[i] = pid; + pid_set_hw_pid(adapter, i, pid); + filter_enable_hw_filter(adapter, i, 1); + return 1; + } + } + } + /* if we have not used a filter, this pid depends on whole bandwidth */ + dprintk("%s: pid=%d whole_bandwidth\n", __FUNCTION__, pid); + whole_bandwidth_inc(adapter); + return 1; + } + +/* returns -1 if the pid was not present in the filters */ +static int remove_hw_pid(struct adapter *adapter, u16 pid) +{ + int i; + + dprintk("%s: pid=%d\n", __FUNCTION__, pid); + + if (pid <= 0x1f) + return 1; + + /* we can't use a filter for 0x2000, so no search */ + if (pid != 0x2000) { + for (i = 0; i < adapter->useable_hw_filters; i++) { + dprintk("%s: pid=%d searching slot=%d\n", __FUNCTION__, pid, i); + if (adapter->hw_pids[i] == pid) { // find the pid slot + dprintk("%s: pid=%d slot=%d\n", __FUNCTION__, pid, i); + adapter->hw_pids[i] = 0x1fff; + pid_set_hw_pid(adapter, i, 0x1fff); + filter_enable_hw_filter(adapter, i, 0); + return 1; + } + } + } + /* if we have not used a filter, this pid depended on whole bandwith */ + dprintk("%s: pid=%d whole_bandwidth\n", __FUNCTION__, pid); + whole_bandwidth_dec(adapter); + return 1; + } + +/* Adds a PID to the filters. + Adding a pid more than once is possible, we keep reference counts. + Whole stream available through pid==0x2000. + Returns 1 on success, -1 on error */ +static int add_pid(struct adapter *adapter, u16 pid) +{ + int i; + + dprintk("%s: pid=%d\n", __FUNCTION__, pid); + + if (pid > 0x1ffe && pid != 0x2000) + return -1; + + // check if the pid is already present + for (i = 0; i < adapter->pid_count; i++) + if (adapter->pid_list[i] == pid) { + adapter->pid_rc[i]++; // increment ref counter + return 1; + } + + if (adapter->pid_count == N_PID_SLOTS) + return -1; // no more pids can be added + adapter->pid_list[adapter->pid_count] = pid; // register pid + adapter->pid_rc[adapter->pid_count] = 1; + adapter->pid_count++; + // hardware setting + add_hw_pid(adapter, pid); + + return 1; + } + +/* Removes a PID from the filters. */ +static int remove_pid(struct adapter *adapter, u16 pid) +{ + int i; + + dprintk("%s: pid=%d\n", __FUNCTION__, pid); + + if (pid > 0x1ffe && pid != 0x2000) + return -1; + + // check if the pid is present (it must be!) + for (i = 0; i < adapter->pid_count; i++) { + if (adapter->pid_list[i] == pid) { + adapter->pid_rc[i]--; + if (adapter->pid_rc[i] <= 0) { + // remove from the list + adapter->pid_count--; + adapter->pid_list[i]=adapter->pid_list[adapter->pid_count]; + adapter->pid_rc[i] = adapter->pid_rc[adapter->pid_count]; + // hardware setting + remove_hw_pid(adapter, pid); + } + return 1; + } + } + + return -1; +} + + +/* dma & irq */ +static void ctrl_enable_smc(struct adapter *adapter, u32 op) +{ + write_reg_bitfield(adapter, 0x208, 0x00000800, op ? 0x00000800 : 0); +} + +static void dma_enable_disable_irq(struct adapter *adapter, u32 flag1, u32 flag2, u32 flag3) +{ + adapter->dma_ctrl = adapter->dma_ctrl & 0x000f0000; + + if (flag1 == 0) { + if (flag2 == 0) + adapter->dma_ctrl = adapter->dma_ctrl & ~0x00010000; + else + adapter->dma_ctrl = adapter->dma_ctrl | 0x00010000; + + if (flag3 == 0) + adapter->dma_ctrl = adapter->dma_ctrl & ~0x00020000; + else + adapter->dma_ctrl = adapter->dma_ctrl | 0x00020000; + + } else { + + if (flag2 == 0) + adapter->dma_ctrl = adapter->dma_ctrl & ~0x00040000; + else + adapter->dma_ctrl = adapter->dma_ctrl | 0x00040000; + + if (flag3 == 0) + adapter->dma_ctrl = adapter->dma_ctrl & ~0x00080000; + else + adapter->dma_ctrl = adapter->dma_ctrl | 0x00080000; + } +} + +static void irq_dma_enable_disable_irq(struct adapter *adapter, u32 op) +{ + u32 value; + + value = read_reg_dw(adapter, 0x208) & 0xfff0ffff; + + if (op != 0) + value = value | (adapter->dma_ctrl & 0x000f0000); + + write_reg_dw(adapter, 0x208, value); +} + +/* FlexCopII has 2 dma channels. DMA1 is used to transfer TS data to + system memory. + + The DMA1 buffer is divided in 2 subbuffers of equal size. + FlexCopII will transfer TS data to one subbuffer, signal an interrupt + when the subbuffer is full and continue fillig the second subbuffer. + + For DMA1: + subbuffer size in 32-bit words is stored in the first 24 bits of + register 0x004. The last 8 bits of register 0x004 contain the number + of subbuffers. + + the first 30 bits of register 0x000 contain the address of the first + subbuffer. The last 2 bits contain 0, when dma1 is disabled and 1, + when dma1 is enabled. + + the first 30 bits of register 0x00c contain the address of the second + subbuffer. the last 2 bits contain 1. + + register 0x008 will contain the address of the subbuffer that was filled + with TS data, when FlexCopII will generate an interrupt. + + For DMA2: + subbuffer size in 32-bit words is stored in the first 24 bits of + register 0x014. The last 8 bits of register 0x014 contain the number + of subbuffers. + + the first 30 bits of register 0x010 contain the address of the first + subbuffer. The last 2 bits contain 0, when dma1 is disabled and 1, + when dma1 is enabled. + + the first 30 bits of register 0x01c contain the address of the second + subbuffer. the last 2 bits contain 1. + + register 0x018 contains the address of the subbuffer that was filled + with TS data, when FlexCopII generates an interrupt. +*/ +static int dma_init_dma(struct adapter *adapter, u32 dma_channel) +{ + u32 subbuffers, subbufsize, subbuf0, subbuf1; + + if (dma_channel == 0) { + dprintk("%s: Initializing DMA1 channel\n", __FUNCTION__); + + subbuffers = 2; + + subbufsize = (((adapter->dmaq1.buffer_size / 2) / 4) << 8) | subbuffers; + + subbuf0 = adapter->dmaq1.bus_addr & 0xfffffffc; + + subbuf1 = ((adapter->dmaq1.bus_addr + adapter->dmaq1.buffer_size / 2) & 0xfffffffc) | 1; + + dprintk("%s: first subbuffer address = 0x%x\n", __FUNCTION__, subbuf0); + udelay(1000); + write_reg_dw(adapter, 0x000, subbuf0); + + dprintk("%s: subbuffer size = 0x%x\n", __FUNCTION__, (subbufsize >> 8) * 4); + udelay(1000); + write_reg_dw(adapter, 0x004, subbufsize); + + dprintk("%s: second subbuffer address = 0x%x\n", __FUNCTION__, subbuf1); + udelay(1000); + write_reg_dw(adapter, 0x00c, subbuf1); + + dprintk("%s: counter = 0x%x\n", __FUNCTION__, adapter->dmaq1.bus_addr & 0xfffffffc); + write_reg_dw(adapter, 0x008, adapter->dmaq1.bus_addr & 0xfffffffc); + udelay(1000); + + dma_enable_disable_irq(adapter, 0, 1, subbuffers ? 1 : 0); + + irq_dma_enable_disable_irq(adapter, 1); + + sram_set_media_dest(adapter, 1); + sram_set_net_dest(adapter, 1); + sram_set_cai_dest(adapter, 2); + sram_set_cao_dest(adapter, 2); + } + + if (dma_channel == 1) { + dprintk("%s: Initializing DMA2 channel\n", __FUNCTION__); + + subbuffers = 2; + + subbufsize = (((adapter->dmaq2.buffer_size / 2) / 4) << 8) | subbuffers; + + subbuf0 = adapter->dmaq2.bus_addr & 0xfffffffc; + + subbuf1 = ((adapter->dmaq2.bus_addr + adapter->dmaq2.buffer_size / 2) & 0xfffffffc) | 1; + + dprintk("%s: first subbuffer address = 0x%x\n", __FUNCTION__, subbuf0); + udelay(1000); + write_reg_dw(adapter, 0x010, subbuf0); + + dprintk("%s: subbuffer size = 0x%x\n", __FUNCTION__, (subbufsize >> 8) * 4); + udelay(1000); + write_reg_dw(adapter, 0x014, subbufsize); + + dprintk("%s: second buffer address = 0x%x\n", __FUNCTION__, subbuf1); + udelay(1000); + write_reg_dw(adapter, 0x01c, subbuf1); + + sram_set_cai_dest(adapter, 2); + } + + return 0; +} + +static void ctrl_enable_receive_data(struct adapter *adapter, u32 op) +{ + if (op == 0) { + write_reg_bitfield(adapter, 0x208, 0x00008000, 0); + adapter->dma_status = adapter->dma_status & ~0x00000004; + } else { + write_reg_bitfield(adapter, 0x208, 0x00008000, 0x00008000); + adapter->dma_status = adapter->dma_status | 0x00000004; + } +} + +/* bit 0 of dma_mask is set to 1 if dma1 channel has to be enabled/disabled + bit 1 of dma_mask is set to 1 if dma2 channel has to be enabled/disabled +*/ +static void dma_start_stop(struct adapter *adapter, u32 dma_mask, int start_stop) +{ + u32 dma_enable, dma1_enable, dma2_enable; + + dprintk("%s: dma_mask=%x\n", __FUNCTION__, dma_mask); + + if (start_stop == 1) { + dprintk("%s: starting dma\n", __FUNCTION__); + + dma1_enable = 0; + dma2_enable = 0; + + if (((dma_mask & 1) != 0) && ((adapter->dma_status & 1) == 0) && (adapter->dmaq1.bus_addr != 0)) { + adapter->dma_status = adapter->dma_status | 1; + dma1_enable = 1; + } + + if (((dma_mask & 2) != 0) && ((adapter->dma_status & 2) == 0) && (adapter->dmaq2.bus_addr != 0)) { + adapter->dma_status = adapter->dma_status | 2; + dma2_enable = 1; + } + // enable dma1 and dma2 + if ((dma1_enable == 1) && (dma2_enable == 1)) { + write_reg_dw(adapter, 0x000, adapter->dmaq1.bus_addr | 1); + write_reg_dw(adapter, 0x00c, (adapter->dmaq1.bus_addr + adapter->dmaq1.buffer_size / 2) | 1); + write_reg_dw(adapter, 0x010, adapter->dmaq2.bus_addr | 1); + + ctrl_enable_receive_data(adapter, 1); + + return; + } + // enable dma1 + if ((dma1_enable == 1) && (dma2_enable == 0)) { + write_reg_dw(adapter, 0x000, adapter->dmaq1.bus_addr | 1); + write_reg_dw(adapter, 0x00c, (adapter->dmaq1.bus_addr + adapter->dmaq1.buffer_size / 2) | 1); + + ctrl_enable_receive_data(adapter, 1); + + return; + } + // enable dma2 + if ((dma1_enable == 0) && (dma2_enable == 1)) { + write_reg_dw(adapter, 0x010, adapter->dmaq2.bus_addr | 1); + + ctrl_enable_receive_data(adapter, 1); + + return; + } + // start dma + if ((dma1_enable == 0) && (dma2_enable == 0)) { + ctrl_enable_receive_data(adapter, 1); + + return; + } + + } else { + + dprintk("%s: stopping dma\n", __FUNCTION__); + + dma_enable = adapter->dma_status & 0x00000003; + + if (((dma_mask & 1) != 0) && ((adapter->dma_status & 1) != 0)) { + dma_enable = dma_enable & 0xfffffffe; + } + + if (((dma_mask & 2) != 0) && ((adapter->dma_status & 2) != 0)) { + dma_enable = dma_enable & 0xfffffffd; + } + //stop dma + if ((dma_enable == 0) && ((adapter->dma_status & 4) != 0)) { + ctrl_enable_receive_data(adapter, 0); + + udelay(3000); + } + //disable dma1 + if (((dma_mask & 1) != 0) && ((adapter->dma_status & 1) != 0) && (adapter->dmaq1.bus_addr != 0)) { + write_reg_dw(adapter, 0x000, adapter->dmaq1.bus_addr); + write_reg_dw(adapter, 0x00c, (adapter->dmaq1.bus_addr + adapter->dmaq1.buffer_size / 2) | 1); + + adapter->dma_status = adapter->dma_status & ~0x00000001; + } + //disable dma2 + if (((dma_mask & 2) != 0) && ((adapter->dma_status & 2) != 0) && (adapter->dmaq2.bus_addr != 0)) { + write_reg_dw(adapter, 0x010, adapter->dmaq2.bus_addr); + + adapter->dma_status = adapter->dma_status & ~0x00000002; + } + } +} + +static void open_stream(struct adapter *adapter, u16 pid) +{ + u32 dma_mask; + + ++adapter->capturing; + + filter_enable_mask_filter(adapter, 1); + + add_pid(adapter, pid); + + dprintk("%s: adapter->dma_status=%x\n", __FUNCTION__, adapter->dma_status); + + if ((adapter->dma_status & 7) != 7) { + dma_mask = 0; + + if (((adapter->dma_status & 0x10000000) != 0) && ((adapter->dma_status & 1) == 0)) { + dma_mask = dma_mask | 1; + + adapter->dmaq1.head = 0; + adapter->dmaq1.tail = 0; + + memset(adapter->dmaq1.buffer, 0, adapter->dmaq1.buffer_size); + } + + if (((adapter->dma_status & 0x20000000) != 0) && ((adapter->dma_status & 2) == 0)) { + dma_mask = dma_mask | 2; + + adapter->dmaq2.head = 0; + adapter->dmaq2.tail = 0; + } + + if (dma_mask != 0) { + irq_dma_enable_disable_irq(adapter, 1); + + dma_start_stop(adapter, dma_mask, 1); + } + } +} + +static void close_stream(struct adapter *adapter, u16 pid) +{ + if (adapter->capturing > 0) + --adapter->capturing; + + dprintk("%s: dma_status=%x\n", __FUNCTION__, adapter->dma_status); + + if (adapter->capturing == 0) { + u32 dma_mask = 0; + + if ((adapter->dma_status & 1) != 0) + dma_mask = dma_mask | 0x00000001; + if ((adapter->dma_status & 2) != 0) + dma_mask = dma_mask | 0x00000002; + + if (dma_mask != 0) { + dma_start_stop(adapter, dma_mask, 0); + } + } + remove_pid(adapter, pid); +} + +static void interrupt_service_dma1(struct adapter *adapter) +{ + struct dvb_demux *dvbdmx = &adapter->demux; + + int n_cur_dma_counter; + u32 n_num_bytes_parsed; + u32 n_num_new_bytes_transferred; + u32 dw_default_packet_size = 188; + u8 gb_tmp_buffer[188]; + u8 *pb_dma_buf_cur_pos; + + n_cur_dma_counter = readl(adapter->io_mem + 0x008) - adapter->dmaq1.bus_addr; + n_cur_dma_counter = (n_cur_dma_counter / dw_default_packet_size) * dw_default_packet_size; + + if ((n_cur_dma_counter < 0) || (n_cur_dma_counter > adapter->dmaq1.buffer_size)) { + dprintk("%s: dma counter outside dma buffer\n", __FUNCTION__); + return; + } + + adapter->dmaq1.head = n_cur_dma_counter; + + if (adapter->dmaq1.tail <= n_cur_dma_counter) { + n_num_new_bytes_transferred = n_cur_dma_counter - adapter->dmaq1.tail; + + } else { + + n_num_new_bytes_transferred = (adapter->dmaq1.buffer_size - adapter->dmaq1.tail) + n_cur_dma_counter; + } + + ddprintk("%s: n_cur_dma_counter = %d\n", __FUNCTION__, n_cur_dma_counter); + ddprintk("%s: dmaq1.tail = %d\n", __FUNCTION__, adapter->dmaq1.tail); + ddprintk("%s: bytes_transferred = %d\n", __FUNCTION__, n_num_new_bytes_transferred); + + if (n_num_new_bytes_transferred < dw_default_packet_size) + return; + + n_num_bytes_parsed = 0; + + while (n_num_bytes_parsed < n_num_new_bytes_transferred) { + pb_dma_buf_cur_pos = adapter->dmaq1.buffer + adapter->dmaq1.tail; + + if (adapter->dmaq1.buffer + adapter->dmaq1.buffer_size < adapter->dmaq1.buffer + adapter->dmaq1.tail + 188) { + memcpy(gb_tmp_buffer, adapter->dmaq1.buffer + adapter->dmaq1.tail, + adapter->dmaq1.buffer_size - adapter->dmaq1.tail); + memcpy(gb_tmp_buffer + (adapter->dmaq1.buffer_size - adapter->dmaq1.tail), adapter->dmaq1.buffer, + (188 - (adapter->dmaq1.buffer_size - adapter->dmaq1.tail))); + + pb_dma_buf_cur_pos = gb_tmp_buffer; + } + + if (adapter->capturing != 0) { + dvb_dmx_swfilter_packets(dvbdmx, pb_dma_buf_cur_pos, dw_default_packet_size / 188); + } + + n_num_bytes_parsed = n_num_bytes_parsed + dw_default_packet_size; + + adapter->dmaq1.tail = adapter->dmaq1.tail + dw_default_packet_size; + + if (adapter->dmaq1.tail >= adapter->dmaq1.buffer_size) + adapter->dmaq1.tail = adapter->dmaq1.tail - adapter->dmaq1.buffer_size; + }; +} + +static void interrupt_service_dma2(struct adapter *adapter) +{ + printk("%s:\n", __FUNCTION__); +} + +static irqreturn_t isr(int irq, void *dev_id, struct pt_regs *regs) +{ + struct adapter *tmp = dev_id; + + u32 value; + + ddprintk("%s:\n", __FUNCTION__); + + spin_lock_irq(&tmp->lock); + + if (0 == ((value = read_reg_dw(tmp, 0x20c)) & 0x0f)) { + spin_unlock_irq(&tmp->lock); + return IRQ_NONE; + } + + while (value != 0) { + if ((value & 0x03) != 0) + interrupt_service_dma1(tmp); + if ((value & 0x0c) != 0) + interrupt_service_dma2(tmp); + value = read_reg_dw(tmp, 0x20c) & 0x0f; + } + + spin_unlock_irq(&tmp->lock); + return IRQ_HANDLED; +} + +static int init_dma_queue_one(struct adapter *adapter, struct dmaq *dmaq, + int size, int dmaq_offset) +{ + struct pci_dev *pdev = adapter->pdev; + dma_addr_t dma_addr; + + dmaq->head = 0; + dmaq->tail = 0; + + dmaq->buffer = pci_alloc_consistent(pdev, size + 0x80, &dma_addr); + if (!dmaq->buffer) + return -ENOMEM; + + dmaq->bus_addr = dma_addr; + dmaq->buffer_size = size; + + dma_init_dma(adapter, dmaq_offset); + + ddprintk("%s: allocated dma buffer at 0x%p, length=%d\n", + __FUNCTION__, dmaq->buffer, size); + + return 0; + } + +static int init_dma_queue(struct adapter *adapter) +{ + struct { + struct dmaq *dmaq; + u32 dma_status; + int size; + } dmaq_desc[] = { + { &adapter->dmaq1, 0x10000000, SIZE_OF_BUF_DMA1 }, + { &adapter->dmaq2, 0x20000000, SIZE_OF_BUF_DMA2 } + }, *p = dmaq_desc; + int i; + + for (i = 0; i < 2; i++, p++) { + if (init_dma_queue_one(adapter, p->dmaq, p->size, i) < 0) + adapter->dma_status &= ~p->dma_status; + else + adapter->dma_status |= p->dma_status; + } + return (adapter->dma_status & 0x30000000) ? 0 : -ENOMEM; +} + +static void free_dma_queue_one(struct adapter *adapter, struct dmaq *dmaq) +{ + if (dmaq->buffer) { + pci_free_consistent(adapter->pdev, dmaq->buffer_size + 0x80, + dmaq->buffer, dmaq->bus_addr); + memset(dmaq, 0, sizeof(*dmaq)); + } +} + +static void free_dma_queue(struct adapter *adapter) +{ + struct dmaq *dmaq[] = { + &adapter->dmaq1, + &adapter->dmaq2, + NULL + }, **p; + + for (p = dmaq; *p; p++) + free_dma_queue_one(adapter, *p); + } + +static void release_adapter(struct adapter *adapter) +{ + struct pci_dev *pdev = adapter->pdev; + + iounmap(adapter->io_mem); + pci_disable_device(pdev); + pci_release_region(pdev, 0); + pci_release_region(pdev, 1); +} + +static void free_adapter_object(struct adapter *adapter) +{ + dprintk("%s:\n", __FUNCTION__); + + close_stream(adapter, 0); + free_irq(adapter->irq, adapter); + free_dma_queue(adapter); + release_adapter(adapter); + kfree(adapter); +} + +static struct pci_driver skystar2_pci_driver; + +static int claim_adapter(struct adapter *adapter) +{ + struct pci_dev *pdev = adapter->pdev; + u16 var; + int ret; + + ret = pci_request_region(pdev, 1, skystar2_pci_driver.name); + if (ret < 0) + goto out; + + ret = pci_request_region(pdev, 0, skystar2_pci_driver.name); + if (ret < 0) + goto err_pci_release_1; + + pci_read_config_byte(pdev, PCI_CLASS_REVISION, &adapter->card_revision); + + dprintk("%s: card revision %x \n", __FUNCTION__, adapter->card_revision); + + ret = pci_enable_device(pdev); + if (ret < 0) + goto err_pci_release_0; + + pci_read_config_word(pdev, 4, &var); + + if ((var & 4) == 0) + pci_set_master(pdev); + + adapter->io_port = pdev->resource[1].start; + + adapter->io_mem = ioremap(pdev->resource[0].start, 0x800); + + if (!adapter->io_mem) { + dprintk("%s: can not map io memory\n", __FUNCTION__); + ret = -EIO; + goto err_pci_disable; + } + + dprintk("%s: io memory maped at %p\n", __FUNCTION__, adapter->io_mem); + + ret = 1; +out: + return ret; + +err_pci_disable: + pci_disable_device(pdev); +err_pci_release_0: + pci_release_region(pdev, 0); +err_pci_release_1: + pci_release_region(pdev, 1); + goto out; +} + +/* +static int sll_reset_flexcop(struct adapter *adapter) +{ + write_reg_dw(adapter, 0x208, 0); + write_reg_dw(adapter, 0x210, 0xb2ff); + + return 0; +} +*/ + +static void decide_how_many_hw_filters(struct adapter *adapter) +{ + int hw_filters; + int mod_option_hw_filters; + + // FlexCop IIb & III have 6+32 hw filters + // FlexCop II has 6 hw filters, every other should have at least 6 + switch (adapter->b2c2_revision) { + case 0x82: /* II */ + hw_filters = 6; + break; + case 0xc3: /* IIB */ + hw_filters = 6 + 32; + break; + case 0xc0: /* III */ + hw_filters = 6 + 32; + break; + default: + hw_filters = 6; + break; + } + printk("%s: the chip has %i hardware filters", __FILE__, hw_filters); + + mod_option_hw_filters = 0; + if (enable_hw_filters >= 1) + mod_option_hw_filters += 6; + if (enable_hw_filters >= 2) + mod_option_hw_filters += 32; + + if (mod_option_hw_filters >= hw_filters) { + adapter->useable_hw_filters = hw_filters; + } else { + adapter->useable_hw_filters = mod_option_hw_filters; + printk(", but only %d will be used because of module option", mod_option_hw_filters); + } + printk("\n"); + dprintk("%s: useable_hardware_filters set to %i\n", __FILE__, adapter->useable_hw_filters); +} + +static int driver_initialize(struct pci_dev *pdev) +{ + struct adapter *adapter; + u32 tmp; + int ret = -ENOMEM; + + adapter = kmalloc(sizeof(struct adapter), GFP_KERNEL); + if (!adapter) { + dprintk("%s: out of memory!\n", __FUNCTION__); + goto out; + } + + memset(adapter, 0, sizeof(struct adapter)); + + pci_set_drvdata(pdev,adapter); + + adapter->pdev = pdev; + adapter->irq = pdev->irq; + + ret = claim_adapter(adapter); + if (ret < 0) + goto err_kfree; + + irq_dma_enable_disable_irq(adapter, 0); + + ret = request_irq(pdev->irq, isr, 0x4000000, "Skystar2", adapter); + if (ret < 0) { + dprintk("%s: unable to allocate irq=%d !\n", __FUNCTION__, pdev->irq); + goto err_release_adapter; + } + + read_reg_dw(adapter, 0x208); + write_reg_dw(adapter, 0x208, 0); + write_reg_dw(adapter, 0x210, 0xb2ff); + write_reg_dw(adapter, 0x208, 0x40); + + ret = init_dma_queue(adapter); + if (ret < 0) + goto err_free_irq; + + adapter->b2c2_revision = (read_reg_dw(adapter, 0x204) >> 0x18); + + switch (adapter->b2c2_revision) { + case 0x82: + printk("%s: FlexCopII(rev.130) chip found\n", __FILE__); + break; + case 0xc3: + printk("%s: FlexCopIIB(rev.195) chip found\n", __FILE__); + break; + case 0xc0: + printk("%s: FlexCopIII(rev.192) chip found\n", __FILE__); + break; + default: + printk("%s: The revision of the FlexCop chip on your card is %d\n", __FILE__, adapter->b2c2_revision); + printk("%s: This driver works only with FlexCopII(rev.130), FlexCopIIB(rev.195) and FlexCopIII(rev.192).\n", __FILE__); + ret = -ENODEV; + goto err_free_dma_queue; + } + + decide_how_many_hw_filters(adapter); + + init_pids(adapter); + + tmp = read_reg_dw(adapter, 0x204); + + write_reg_dw(adapter, 0x204, 0); + mdelay(20); + + write_reg_dw(adapter, 0x204, tmp); + mdelay(10); + + tmp = read_reg_dw(adapter, 0x308); + write_reg_dw(adapter, 0x308, 0x4000 | tmp); + + adapter->dw_sram_type = 0x10000; + + sll_detect_sram_size(adapter); + + dprintk("%s sram length = %d, sram type= %x\n", __FUNCTION__, sram_length(adapter), adapter->dw_sram_type); + + sram_set_media_dest(adapter, 1); + sram_set_net_dest(adapter, 1); + + ctrl_enable_smc(adapter, 0); + + sram_set_cai_dest(adapter, 2); + sram_set_cao_dest(adapter, 2); + + dma_enable_disable_irq(adapter, 1, 0, 0); + + if (eeprom_get_mac_addr(adapter, 0, adapter->mac_addr) != 0) { + printk("%s MAC address = %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x \n", __FUNCTION__, adapter->mac_addr[0], + adapter->mac_addr[1], adapter->mac_addr[2], adapter->mac_addr[3], adapter->mac_addr[4], adapter->mac_addr[5], + adapter->mac_addr[6], adapter->mac_addr[7] + ); + + ca_set_mac_dst_addr_filter(adapter, adapter->mac_addr); + ctrl_enable_mac(adapter, 1); + } + + spin_lock_init(&adapter->lock); + +out: + return ret; + +err_free_dma_queue: + free_dma_queue(adapter); +err_free_irq: + free_irq(pdev->irq, adapter); +err_release_adapter: + release_adapter(adapter); +err_kfree: + pci_set_drvdata(pdev, NULL); + kfree(adapter); + goto out; +} + +static void driver_halt(struct pci_dev *pdev) +{ + struct adapter *adapter = pci_get_drvdata(pdev); + + irq_dma_enable_disable_irq(adapter, 0); + + ctrl_enable_receive_data(adapter, 0); + + free_adapter_object(adapter); + + pci_set_drvdata(pdev, NULL); +} + +static int dvb_start_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + struct adapter *adapter = (struct adapter *) dvbdmx->priv; + + dprintk("%s: PID=%d, type=%d\n", __FUNCTION__, dvbdmxfeed->pid, dvbdmxfeed->type); + + open_stream(adapter, dvbdmxfeed->pid); + + return 0; +} + +static int dvb_stop_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + struct adapter *adapter = (struct adapter *) dvbdmx->priv; + + dprintk("%s: PID=%d, type=%d\n", __FUNCTION__, dvbdmxfeed->pid, dvbdmxfeed->type); + + close_stream(adapter, dvbdmxfeed->pid); + + return 0; +} + +/* lnb control */ +static void set_tuner_tone(struct adapter *adapter, u8 tone) +{ + u16 wz_half_period_for_45_mhz[] = { 0x01ff, 0x0154, 0x00ff, 0x00cc }; + u16 ax; + + dprintk("%s: %u\n", __FUNCTION__, tone); + + switch (tone) { + case 1: + ax = wz_half_period_for_45_mhz[0]; + break; + case 2: + ax = wz_half_period_for_45_mhz[1]; + break; + case 3: + ax = wz_half_period_for_45_mhz[2]; + break; + case 4: + ax = wz_half_period_for_45_mhz[3]; + break; + + default: + ax = 0; + } + + if (ax != 0) { + write_reg_dw(adapter, 0x200, ((ax << 0x0f) + (ax & 0x7fff)) | 0x40000000); + + } else { + + write_reg_dw(adapter, 0x200, 0x40ff8000); + } +} + +static void set_tuner_polarity(struct adapter *adapter, u8 polarity) +{ + u32 var; + + dprintk("%s : polarity = %u \n", __FUNCTION__, polarity); + + var = read_reg_dw(adapter, 0x204); + + if (polarity == 0) { + dprintk("%s: LNB power off\n", __FUNCTION__); + var = var | 1; + }; + + if (polarity == 1) { + var = var & ~1; + var = var & ~4; + }; + + if (polarity == 2) { + var = var & ~1; + var = var | 4; + } + + write_reg_dw(adapter, 0x204, var); +} + +static void diseqc_send_bit(struct adapter *adapter, int data) +{ + set_tuner_tone(adapter, 1); + udelay(data ? 500 : 1000); + set_tuner_tone(adapter, 0); + udelay(data ? 1000 : 500); +} + + +static void diseqc_send_byte(struct adapter *adapter, int data) + { + int i, par = 1, d; + + for (i = 7; i >= 0; i--) { + d = (data >> i) & 1; + par ^= d; + diseqc_send_bit(adapter, d); + } + + diseqc_send_bit(adapter, par); + } + + +static int send_diseqc_msg(struct adapter *adapter, int len, u8 *msg, unsigned long burst) +{ + int i; + + set_tuner_tone(adapter, 0); + mdelay(16); + + for (i = 0; i < len; i++) + diseqc_send_byte(adapter, msg[i]); + + mdelay(16); + + if (burst != -1) { + if (burst) + diseqc_send_byte(adapter, 0xff); + else { + set_tuner_tone(adapter, 1); + udelay(12500); + set_tuner_tone(adapter, 0); + } + msleep(20); + } + + return 0; +} + +static int flexcop_set_tone(struct dvb_frontend* fe, fe_sec_tone_mode_t tone) +{ + struct adapter* adapter = (struct adapter*) fe->dvb->priv; + + switch(tone) { + case SEC_TONE_ON: + set_tuner_tone(adapter, 1); + break; + case SEC_TONE_OFF: + set_tuner_tone(adapter, 0); + break; + default: + return -EINVAL; + }; + + return 0; +} + +static int flexcop_diseqc_send_master_cmd(struct dvb_frontend* fe, struct dvb_diseqc_master_cmd* cmd) + { + struct adapter* adapter = (struct adapter*) fe->dvb->priv; + + send_diseqc_msg(adapter, cmd->msg_len, cmd->msg, 0); + + return 0; + } + +static int flexcop_diseqc_send_burst(struct dvb_frontend* fe, fe_sec_mini_cmd_t minicmd) +{ + struct adapter* adapter = (struct adapter*) fe->dvb->priv; + + send_diseqc_msg(adapter, 0, NULL, minicmd); + + return 0; +} + +static int flexcop_set_voltage(struct dvb_frontend* fe, fe_sec_voltage_t voltage) + { + struct adapter* adapter = (struct adapter*) fe->dvb->priv; + + dprintk("%s: FE_SET_VOLTAGE\n", __FUNCTION__); + + switch (voltage) { + case SEC_VOLTAGE_13: + dprintk("%s: SEC_VOLTAGE_13, %x\n", __FUNCTION__, SEC_VOLTAGE_13); + set_tuner_polarity(adapter, 1); + return 0; + + case SEC_VOLTAGE_18: + dprintk("%s: SEC_VOLTAGE_18, %x\n", __FUNCTION__, SEC_VOLTAGE_18); + set_tuner_polarity(adapter, 2); + return 0; + + default: + return -EINVAL; + } + } + +static int flexcop_sleep(struct dvb_frontend* fe) + { + struct adapter* adapter = (struct adapter*) fe->dvb->priv; + + dprintk("%s: FE_SLEEP\n", __FUNCTION__); + set_tuner_polarity(adapter, 0); + + if (adapter->fe_sleep) return adapter->fe_sleep(fe); + return 0; + } + +static u32 flexcop_i2c_func(struct i2c_adapter *adapter) + { + printk("flexcop_i2c_func\n"); + + return I2C_FUNC_I2C; +} + +static struct i2c_algorithm flexcop_algo = { + .name = "flexcop i2c algorithm", + .id = I2C_ALGO_BIT, + .master_xfer = master_xfer, + .functionality = flexcop_i2c_func, +}; + + + + +static int samsung_tbmu24112_set_symbol_rate(struct dvb_frontend* fe, u32 srate, u32 ratio) +{ + u8 aclk = 0; + u8 bclk = 0; + + if (srate < 1500000) { aclk = 0xb7; bclk = 0x47; } + else if (srate < 3000000) { aclk = 0xb7; bclk = 0x4b; } + else if (srate < 7000000) { aclk = 0xb7; bclk = 0x4f; } + else if (srate < 14000000) { aclk = 0xb7; bclk = 0x53; } + else if (srate < 30000000) { aclk = 0xb6; bclk = 0x53; } + else if (srate < 45000000) { aclk = 0xb4; bclk = 0x51; } + + stv0299_writereg (fe, 0x13, aclk); + stv0299_writereg (fe, 0x14, bclk); + stv0299_writereg (fe, 0x1f, (ratio >> 16) & 0xff); + stv0299_writereg (fe, 0x20, (ratio >> 8) & 0xff); + stv0299_writereg (fe, 0x21, (ratio ) & 0xf0); + + return 0; +} + +static int samsung_tbmu24112_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + u8 buf[4]; + u32 div; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = buf, .len = sizeof(buf) }; + struct adapter* adapter = (struct adapter*) fe->dvb->priv; + + div = params->frequency / 125; + + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = 0x84; // 0xC4 + buf[3] = 0x08; + + if (params->frequency < 1500000) buf[3] |= 0x10; + + if (i2c_transfer (&adapter->i2c_adap, &msg, 1) != 1) return -EIO; + return 0; +} + +static u8 samsung_tbmu24112_inittab[] = { + 0x01, 0x15, + 0x02, 0x30, + 0x03, 0x00, + 0x04, 0x7D, + 0x05, 0x35, + 0x06, 0x02, + 0x07, 0x00, + 0x08, 0xC3, + 0x0C, 0x00, + 0x0D, 0x81, + 0x0E, 0x23, + 0x0F, 0x12, + 0x10, 0x7E, + 0x11, 0x84, + 0x12, 0xB9, + 0x13, 0x88, + 0x14, 0x89, + 0x15, 0xC9, + 0x16, 0x00, + 0x17, 0x5C, + 0x18, 0x00, + 0x19, 0x00, + 0x1A, 0x00, + 0x1C, 0x00, + 0x1D, 0x00, + 0x1E, 0x00, + 0x1F, 0x3A, + 0x20, 0x2E, + 0x21, 0x80, + 0x22, 0xFF, + 0x23, 0xC1, + 0x28, 0x00, + 0x29, 0x1E, + 0x2A, 0x14, + 0x2B, 0x0F, + 0x2C, 0x09, + 0x2D, 0x05, + 0x31, 0x1F, + 0x32, 0x19, + 0x33, 0xFE, + 0x34, 0x93, + 0xff, 0xff, + }; + +static struct stv0299_config samsung_tbmu24112_config = { + .demod_address = 0x68, + .inittab = samsung_tbmu24112_inittab, + .mclk = 88000000UL, + .invert = 0, + .enhanced_tuning = 0, + .skip_reinit = 0, + .lock_output = STV0229_LOCKOUTPUT_LK, + .volt13_op0_op1 = STV0299_VOLT13_OP1, + .min_delay_ms = 100, + .set_symbol_rate = samsung_tbmu24112_set_symbol_rate, + .pll_set = samsung_tbmu24112_pll_set, +}; + + + +static int nxt2002_request_firmware(struct dvb_frontend* fe, const struct firmware **fw, char* name) +{ + struct adapter* adapter = (struct adapter*) fe->dvb->priv; + + return request_firmware(fw, name, &adapter->pdev->dev); +} + + +static struct nxt2002_config samsung_tbmv_config = { + .demod_address = 0x0A, + .request_firmware = nxt2002_request_firmware, +}; + +static int samsung_tdtc9251dh0_demod_init(struct dvb_frontend* fe) +{ + static u8 mt352_clock_config [] = { 0x89, 0x18, 0x2d }; + static u8 mt352_reset [] = { 0x50, 0x80 }; + static u8 mt352_adc_ctl_1_cfg [] = { 0x8E, 0x40 }; + static u8 mt352_agc_cfg [] = { 0x67, 0x28, 0xa1 }; + static u8 mt352_capt_range_cfg[] = { 0x75, 0x32 }; + + mt352_write(fe, mt352_clock_config, sizeof(mt352_clock_config)); + udelay(2000); + mt352_write(fe, mt352_reset, sizeof(mt352_reset)); + mt352_write(fe, mt352_adc_ctl_1_cfg, sizeof(mt352_adc_ctl_1_cfg)); + + mt352_write(fe, mt352_agc_cfg, sizeof(mt352_agc_cfg)); + mt352_write(fe, mt352_capt_range_cfg, sizeof(mt352_capt_range_cfg)); + + return 0; +} + +static int samsung_tdtc9251dh0_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params, u8* pllbuf) +{ + u32 div; + unsigned char bs = 0; + + #define IF_FREQUENCYx6 217 /* 6 * 36.16666666667MHz */ + div = (((params->frequency + 83333) * 3) / 500000) + IF_FREQUENCYx6; + + if (params->frequency >= 48000000 && params->frequency <= 154000000) bs = 0x09; + if (params->frequency >= 161000000 && params->frequency <= 439000000) bs = 0x0a; + if (params->frequency >= 447000000 && params->frequency <= 863000000) bs = 0x08; + + pllbuf[0] = 0xc2; // Note: non-linux standard PLL i2c address + pllbuf[1] = div >> 8; + pllbuf[2] = div & 0xff; + pllbuf[3] = 0xcc; + pllbuf[4] = bs; + + return 0; +} + +static struct mt352_config samsung_tdtc9251dh0_config = { + + .demod_address = 0x0f, + .demod_init = samsung_tdtc9251dh0_demod_init, + .pll_set = samsung_tdtc9251dh0_pll_set, +}; + +static int skystar23_samsung_tbdu18132_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + u8 buf[4]; + u32 div; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = buf, .len = sizeof(buf) }; + struct adapter* adapter = (struct adapter*) fe->dvb->priv; + + div = (params->frequency + (125/2)) / 125; + + buf[0] = (div >> 8) & 0x7f; + buf[1] = (div >> 0) & 0xff; + buf[2] = 0x84 | ((div >> 10) & 0x60); + buf[3] = 0x80; + + if (params->frequency < 1550000) + buf[3] |= 0x02; + + if (i2c_transfer (&adapter->i2c_adap, &msg, 1) != 1) return -EIO; + return 0; +} + +static struct mt312_config skystar23_samsung_tbdu18132_config = { + + .demod_address = 0x0e, + .pll_set = skystar23_samsung_tbdu18132_pll_set, +}; + + + + +static void frontend_init(struct adapter *skystar2) +{ + switch(skystar2->pdev->device) { + case 0x2103: // Technisat Skystar2 OR Technisat Airstar2 (DVB-T or ATSC) + + // Attempt to load the Nextwave nxt2002 for ATSC support + skystar2->fe = nxt2002_attach(&samsung_tbmv_config, &skystar2->i2c_adap); + if (skystar2->fe != NULL) { + skystar2->fe_sleep = skystar2->fe->ops->sleep; + skystar2->fe->ops->sleep = flexcop_sleep; + break; + } + + // try the skystar2 v2.6 first (stv0299/Samsung tbmu24112(sl1935)) + skystar2->fe = stv0299_attach(&samsung_tbmu24112_config, &skystar2->i2c_adap); + if (skystar2->fe != NULL) { + skystar2->fe->ops->set_voltage = flexcop_set_voltage; + skystar2->fe_sleep = skystar2->fe->ops->sleep; + skystar2->fe->ops->sleep = flexcop_sleep; + break; +} + + // try the airstar2 (mt352/Samsung tdtc9251dh0(??)) + skystar2->fe = mt352_attach(&samsung_tdtc9251dh0_config, &skystar2->i2c_adap); + if (skystar2->fe != NULL) { + skystar2->fe->ops->info.frequency_min = 474000000; + skystar2->fe->ops->info.frequency_max = 858000000; + break; + } + + // try the skystar2 v2.3 (vp310/Samsung tbdu18132(tsa5059)) + skystar2->fe = vp310_attach(&skystar23_samsung_tbdu18132_config, &skystar2->i2c_adap); + if (skystar2->fe != NULL) { + skystar2->fe->ops->diseqc_send_master_cmd = flexcop_diseqc_send_master_cmd; + skystar2->fe->ops->diseqc_send_burst = flexcop_diseqc_send_burst; + skystar2->fe->ops->set_tone = flexcop_set_tone; + skystar2->fe->ops->set_voltage = flexcop_set_voltage; + skystar2->fe_sleep = skystar2->fe->ops->sleep; + skystar2->fe->ops->sleep = flexcop_sleep; + break; + } + break; + } + + if (skystar2->fe == NULL) { + printk("skystar2: A frontend driver was not found for device %04x/%04x subsystem %04x/%04x\n", + skystar2->pdev->vendor, + skystar2->pdev->device, + skystar2->pdev->subsystem_vendor, + skystar2->pdev->subsystem_device); + } else { + if (dvb_register_frontend(skystar2->dvb_adapter, skystar2->fe)) { + printk("skystar2: Frontend registration failed!\n"); + if (skystar2->fe->ops->release) + skystar2->fe->ops->release(skystar2->fe); + skystar2->fe = NULL; + } + } +} + + +static int skystar2_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct adapter *adapter; + struct dvb_adapter *dvb_adapter; + struct dvb_demux *dvbdemux; + struct dmx_demux *dmx; + int ret = -ENODEV; + + if (!pdev) + goto out; + + ret = driver_initialize(pdev); + if (ret < 0) + goto out; + + ret = dvb_register_adapter(&dvb_adapter, skystar2_pci_driver.name, + THIS_MODULE); + if (ret < 0) { + printk("%s: Error registering DVB adapter\n", __FUNCTION__); + goto err_halt; + } + + adapter = pci_get_drvdata(pdev); + + dvb_adapter->priv = adapter; + adapter->dvb_adapter = dvb_adapter; + + + init_MUTEX(&adapter->i2c_sem); + + + memset(&adapter->i2c_adap, 0, sizeof(struct i2c_adapter)); + strcpy(adapter->i2c_adap.name, "SkyStar2"); + + i2c_set_adapdata(&adapter->i2c_adap, adapter); + +#ifdef I2C_ADAP_CLASS_TV_DIGITAL + adapter->i2c_adap.class = I2C_ADAP_CLASS_TV_DIGITAL; +#else + adapter->i2c_adap.class = I2C_CLASS_TV_DIGITAL; +#endif + adapter->i2c_adap.algo = &flexcop_algo; + adapter->i2c_adap.algo_data = NULL; + adapter->i2c_adap.id = I2C_ALGO_BIT; + + ret = i2c_add_adapter(&adapter->i2c_adap); + if (ret < 0) + goto err_dvb_unregister; + + dvbdemux = &adapter->demux; + + dvbdemux->priv = adapter; + dvbdemux->filternum = N_PID_SLOTS; + dvbdemux->feednum = N_PID_SLOTS; + dvbdemux->start_feed = dvb_start_feed; + dvbdemux->stop_feed = dvb_stop_feed; + dvbdemux->write_to_decoder = NULL; + dvbdemux->dmx.capabilities = (DMX_TS_FILTERING | DMX_SECTION_FILTERING | DMX_MEMORY_BASED_FILTERING); + + ret = dvb_dmx_init(&adapter->demux); + if (ret < 0) + goto err_i2c_del; + + dmx = &dvbdemux->dmx; + + adapter->hw_frontend.source = DMX_FRONTEND_0; + adapter->dmxdev.filternum = N_PID_SLOTS; + adapter->dmxdev.demux = dmx; + adapter->dmxdev.capabilities = 0; + + ret = dvb_dmxdev_init(&adapter->dmxdev, adapter->dvb_adapter); + if (ret < 0) + goto err_dmx_release; + + ret = dmx->add_frontend(dmx, &adapter->hw_frontend); + if (ret < 0) + goto err_dmxdev_release; + + adapter->mem_frontend.source = DMX_MEMORY_FE; + + ret = dmx->add_frontend(dmx, &adapter->mem_frontend); + if (ret < 0) + goto err_remove_hw_frontend; + + ret = dmx->connect_frontend(dmx, &adapter->hw_frontend); + if (ret < 0) + goto err_remove_mem_frontend; + + dvb_net_init(adapter->dvb_adapter, &adapter->dvbnet, &dvbdemux->dmx); + + frontend_init(adapter); +out: + return ret; + +err_remove_mem_frontend: + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, &adapter->mem_frontend); +err_remove_hw_frontend: + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, &adapter->hw_frontend); +err_dmxdev_release: + dvb_dmxdev_release(&adapter->dmxdev); +err_dmx_release: + dvb_dmx_release(&adapter->demux); +err_i2c_del: + i2c_del_adapter(&adapter->i2c_adap); +err_dvb_unregister: + dvb_unregister_adapter(adapter->dvb_adapter); +err_halt: + driver_halt(pdev); + goto out; +} + +static void skystar2_remove(struct pci_dev *pdev) +{ + struct adapter *adapter = pci_get_drvdata(pdev); + struct dvb_demux *dvbdemux; + struct dmx_demux *dmx; + + if (!adapter) + return; + + dvb_net_release(&adapter->dvbnet); + dvbdemux = &adapter->demux; + dmx = &dvbdemux->dmx; + + dmx->close(dmx); + dmx->remove_frontend(dmx, &adapter->hw_frontend); + dmx->remove_frontend(dmx, &adapter->mem_frontend); + + dvb_dmxdev_release(&adapter->dmxdev); + dvb_dmx_release(dvbdemux); + + if (adapter->fe != NULL) + dvb_unregister_frontend(adapter->fe); + + dvb_unregister_adapter(adapter->dvb_adapter); + + i2c_del_adapter(&adapter->i2c_adap); + + driver_halt(pdev); + } + +static struct pci_device_id skystar2_pci_tbl[] = { + {0x000013d0, 0x00002103, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000}, +/* {0x000013d0, 0x00002200, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000}, UNDEFINED HARDWARE - mail linuxtv.org list */ //FCIII + {0,}, +}; + +MODULE_DEVICE_TABLE(pci, skystar2_pci_tbl); + +static struct pci_driver skystar2_pci_driver = { + .name = "SkyStar2", + .id_table = skystar2_pci_tbl, + .probe = skystar2_probe, + .remove = skystar2_remove, +}; + +static int skystar2_init(void) +{ + return pci_register_driver(&skystar2_pci_driver); +} + +static void skystar2_cleanup(void) +{ + pci_unregister_driver(&skystar2_pci_driver); +} + +module_init(skystar2_init); +module_exit(skystar2_cleanup); + +MODULE_DESCRIPTION("Technisat SkyStar2 DVB PCI Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/bt8xx/Kconfig b/drivers/media/dvb/bt8xx/Kconfig new file mode 100644 index 000000000000..e7d11e0667a8 --- /dev/null +++ b/drivers/media/dvb/bt8xx/Kconfig @@ -0,0 +1,19 @@ +config DVB_BT8XX + tristate "Nebula/Pinnacle PCTV/Twinhan PCI cards" + depends on DVB_CORE && PCI && VIDEO_BT848 + select DVB_MT352 + select DVB_SP887X + select DVB_NXT6000 + select DVB_CX24110 + select DVB_OR51211 + help + Support for PCI cards based on the Bt8xx PCI bridge. Examples are + the Nebula cards, the Pinnacle PCTV cards, the Twinhan DST cards and + pcHDTV HD2000 cards. + + Since these cards have no MPEG decoder onboard, they transmit + only compressed MPEG data over the PCI bus, so you need + an external software decoder to watch TV on your computer. + + Say Y if you own such a device and want to use it. + diff --git a/drivers/media/dvb/bt8xx/Makefile b/drivers/media/dvb/bt8xx/Makefile new file mode 100644 index 000000000000..9da8604b9e18 --- /dev/null +++ b/drivers/media/dvb/bt8xx/Makefile @@ -0,0 +1,5 @@ + +obj-$(CONFIG_DVB_BT8XX) += bt878.o dvb-bt8xx.o dst.o + +EXTRA_CFLAGS = -Idrivers/media/dvb/dvb-core/ -Idrivers/media/video -Idrivers/media/dvb/frontends + diff --git a/drivers/media/dvb/bt8xx/bt878.c b/drivers/media/dvb/bt8xx/bt878.c new file mode 100644 index 000000000000..213ff7902024 --- /dev/null +++ b/drivers/media/dvb/bt8xx/bt878.c @@ -0,0 +1,588 @@ +/* + * bt878.c: part of the driver for the Pinnacle PCTV Sat DVB PCI card + * + * Copyright (C) 2002 Peter Hettkamp <peter.hettkamp@t-online.de> + * + * large parts based on the bttv driver + * Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + * & Marcus Metzler (mocm@thp.uni-koeln.de) + * (c) 1999,2000 Gerd Knorr <kraxel@goldbach.in-berlin.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <asm/io.h> +#include <linux/ioport.h> +#include <asm/pgtable.h> +#include <asm/page.h> +#include <linux/types.h> +#include <linux/interrupt.h> +#include <linux/kmod.h> +#include <linux/vmalloc.h> +#include <linux/init.h> + +#include "dmxdev.h" +#include "dvbdev.h" +#include "bt878.h" +#include "dst_priv.h" + + +/**************************************/ +/* Miscellaneous utility definitions */ +/**************************************/ + +static unsigned int bt878_verbose = 1; +static unsigned int bt878_debug; + +module_param_named(verbose, bt878_verbose, int, 0444); +MODULE_PARM_DESC(verbose, + "verbose startup messages, default is 1 (yes)"); +module_param_named(debug, bt878_debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off)."); + +int bt878_num; +struct bt878 bt878[BT878_MAX]; + +EXPORT_SYMBOL(bt878_debug); +EXPORT_SYMBOL(bt878_verbose); +EXPORT_SYMBOL(bt878_num); +EXPORT_SYMBOL(bt878); + +#define btwrite(dat,adr) bmtwrite((dat), (bt->bt878_mem+(adr))) +#define btread(adr) bmtread(bt->bt878_mem+(adr)) + +#define btand(dat,adr) btwrite((dat) & btread(adr), adr) +#define btor(dat,adr) btwrite((dat) | btread(adr), adr) +#define btaor(dat,mask,adr) btwrite((dat) | ((mask) & btread(adr)), adr) + +#if defined(dprintk) +#undef dprintk +#endif +#define dprintk if(bt878_debug) printk + +static void bt878_mem_free(struct bt878 *bt) +{ + if (bt->buf_cpu) { + pci_free_consistent(bt->dev, bt->buf_size, bt->buf_cpu, + bt->buf_dma); + bt->buf_cpu = NULL; + } + + if (bt->risc_cpu) { + pci_free_consistent(bt->dev, bt->risc_size, bt->risc_cpu, + bt->risc_dma); + bt->risc_cpu = NULL; + } +} + +static int bt878_mem_alloc(struct bt878 *bt) +{ + if (!bt->buf_cpu) { + bt->buf_size = 128 * 1024; + + bt->buf_cpu = + pci_alloc_consistent(bt->dev, bt->buf_size, + &bt->buf_dma); + + if (!bt->buf_cpu) + return -ENOMEM; + + memset(bt->buf_cpu, 0, bt->buf_size); + } + + if (!bt->risc_cpu) { + bt->risc_size = PAGE_SIZE; + bt->risc_cpu = + pci_alloc_consistent(bt->dev, bt->risc_size, + &bt->risc_dma); + + if (!bt->risc_cpu) { + bt878_mem_free(bt); + return -ENOMEM; + } + + memset(bt->risc_cpu, 0, bt->risc_size); + } + + return 0; +} + +/* RISC instructions */ +#define RISC_WRITE (0x01 << 28) +#define RISC_JUMP (0x07 << 28) +#define RISC_SYNC (0x08 << 28) + +/* RISC bits */ +#define RISC_WR_SOL (1 << 27) +#define RISC_WR_EOL (1 << 26) +#define RISC_IRQ (1 << 24) +#define RISC_STATUS(status) ((((~status) & 0x0F) << 20) | ((status & 0x0F) << 16)) +#define RISC_SYNC_RESYNC (1 << 15) +#define RISC_SYNC_FM1 0x06 +#define RISC_SYNC_VRO 0x0C + +#define RISC_FLUSH() bt->risc_pos = 0 +#define RISC_INSTR(instr) bt->risc_cpu[bt->risc_pos++] = cpu_to_le32(instr) + +static int bt878_make_risc(struct bt878 *bt) +{ + bt->block_bytes = bt->buf_size >> 4; + bt->block_count = 1 << 4; + bt->line_bytes = bt->block_bytes; + bt->line_count = bt->block_count; + + while (bt->line_bytes > 4095) { + bt->line_bytes >>= 1; + bt->line_count <<= 1; + } + + if (bt->line_count > 255) { + printk("bt878: buffer size error!\n"); + return -EINVAL; + } + return 0; +} + + +static void bt878_risc_program(struct bt878 *bt, u32 op_sync_orin) +{ + u32 buf_pos = 0; + u32 line; + + RISC_FLUSH(); + RISC_INSTR(RISC_SYNC | RISC_SYNC_FM1 | op_sync_orin); + RISC_INSTR(0); + + dprintk("bt878: risc len lines %u, bytes per line %u\n", + bt->line_count, bt->line_bytes); + for (line = 0; line < bt->line_count; line++) { + // At the beginning of every block we issue an IRQ with previous (finished) block number set + if (!(buf_pos % bt->block_bytes)) + RISC_INSTR(RISC_WRITE | RISC_WR_SOL | RISC_WR_EOL | + RISC_IRQ | + RISC_STATUS(((buf_pos / + bt->block_bytes) + + (bt->block_count - + 1)) % + bt->block_count) | bt-> + line_bytes); + else + RISC_INSTR(RISC_WRITE | RISC_WR_SOL | RISC_WR_EOL | + bt->line_bytes); + RISC_INSTR(bt->buf_dma + buf_pos); + buf_pos += bt->line_bytes; + } + + RISC_INSTR(RISC_SYNC | op_sync_orin | RISC_SYNC_VRO); + RISC_INSTR(0); + + RISC_INSTR(RISC_JUMP); + RISC_INSTR(bt->risc_dma); + + btwrite((bt->line_count << 16) | bt->line_bytes, BT878_APACK_LEN); +} + +/*****************************/ +/* Start/Stop grabbing funcs */ +/*****************************/ + +void bt878_start(struct bt878 *bt, u32 controlreg, u32 op_sync_orin, + u32 irq_err_ignore) +{ + u32 int_mask; + + dprintk("bt878 debug: bt878_start (ctl=%8.8x)\n", controlreg); + /* complete the writing of the risc dma program now we have + * the card specifics + */ + bt878_risc_program(bt, op_sync_orin); + controlreg &= ~0x1f; + controlreg |= 0x1b; + + btwrite(cpu_to_le32(bt->risc_dma), BT878_ARISC_START); + + /* original int mask had : + * 6 2 8 4 0 + * 1111 1111 1000 0000 0000 + * SCERR|OCERR|PABORT|RIPERR|FDSR|FTRGT|FBUS|RISCI + * Hacked for DST to: + * SCERR | OCERR | FDSR | FTRGT | FBUS | RISCI + */ + int_mask = BT878_ASCERR | BT878_AOCERR | BT878_APABORT | + BT878_ARIPERR | BT878_APPERR | BT878_AFDSR | BT878_AFTRGT | + BT878_AFBUS | BT878_ARISCI; + + + /* ignore pesky bits */ + int_mask &= ~irq_err_ignore; + + btwrite(int_mask, BT878_AINT_MASK); + btwrite(controlreg, BT878_AGPIO_DMA_CTL); +} + +void bt878_stop(struct bt878 *bt) +{ + u32 stat; + int i = 0; + + dprintk("bt878 debug: bt878_stop\n"); + + btwrite(0, BT878_AINT_MASK); + btand(~0x13, BT878_AGPIO_DMA_CTL); + + do { + stat = btread(BT878_AINT_STAT); + if (!(stat & BT878_ARISC_EN)) + break; + i++; + } while (i < 500); + + dprintk("bt878(%d) debug: bt878_stop, i=%d, stat=0x%8.8x\n", + bt->nr, i, stat); +} + +EXPORT_SYMBOL(bt878_start); +EXPORT_SYMBOL(bt878_stop); + +/*****************************/ +/* Interrupt service routine */ +/*****************************/ + +static irqreturn_t bt878_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + u32 stat, astat, mask; + int count; + struct bt878 *bt; + + bt = (struct bt878 *) dev_id; + + count = 0; + while (1) { + stat = btread(BT878_AINT_STAT); + mask = btread(BT878_AINT_MASK); + if (!(astat = (stat & mask))) + return IRQ_NONE; /* this interrupt is not for me */ +/* dprintk("bt878(%d) debug: irq count %d, stat 0x%8.8x, mask 0x%8.8x\n",bt->nr,count,stat,mask); */ + btwrite(astat, BT878_AINT_STAT); /* try to clear interupt condition */ + + + if (astat & (BT878_ASCERR | BT878_AOCERR)) { + if (bt878_verbose) { + printk("bt878(%d): irq%s%s risc_pc=%08x\n", + bt->nr, + (astat & BT878_ASCERR) ? " SCERR" : + "", + (astat & BT878_AOCERR) ? " OCERR" : + "", btread(BT878_ARISC_PC)); + } + } + if (astat & (BT878_APABORT | BT878_ARIPERR | BT878_APPERR)) { + if (bt878_verbose) { + printk + ("bt878(%d): irq%s%s%s risc_pc=%08x\n", + bt->nr, + (astat & BT878_APABORT) ? " PABORT" : + "", + (astat & BT878_ARIPERR) ? " RIPERR" : + "", + (astat & BT878_APPERR) ? " PPERR" : + "", btread(BT878_ARISC_PC)); + } + } + if (astat & (BT878_AFDSR | BT878_AFTRGT | BT878_AFBUS)) { + if (bt878_verbose) { + printk + ("bt878(%d): irq%s%s%s risc_pc=%08x\n", + bt->nr, + (astat & BT878_AFDSR) ? " FDSR" : "", + (astat & BT878_AFTRGT) ? " FTRGT" : + "", + (astat & BT878_AFBUS) ? " FBUS" : "", + btread(BT878_ARISC_PC)); + } + } + if (astat & BT878_ARISCI) { + bt->finished_block = (stat & BT878_ARISCS) >> 28; + tasklet_schedule(&bt->tasklet); + break; + } + count++; + if (count > 20) { + btwrite(0, BT878_AINT_MASK); + printk(KERN_ERR + "bt878(%d): IRQ lockup, cleared int mask\n", + bt->nr); + break; + } + } + return IRQ_HANDLED; +} + +int +bt878_device_control(struct bt878 *bt, unsigned int cmd, union dst_gpio_packet *mp) +{ + int retval; + + retval = 0; + if (down_interruptible (&bt->gpio_lock)) + return -ERESTARTSYS; + /* special gpio signal */ + switch (cmd) { + case DST_IG_ENABLE: + // dprintk("dvb_bt8xx: dst enable mask 0x%02x enb 0x%02x \n", mp->dstg.enb.mask, mp->dstg.enb.enable); + retval = bttv_gpio_enable(bt->bttv_nr, + mp->enb.mask, + mp->enb.enable); + break; + case DST_IG_WRITE: + // dprintk("dvb_bt8xx: dst write gpio mask 0x%02x out 0x%02x\n", mp->dstg.outp.mask, mp->dstg.outp.highvals); + retval = bttv_write_gpio(bt->bttv_nr, + mp->outp.mask, + mp->outp.highvals); + + break; + case DST_IG_READ: + /* read */ + retval = bttv_read_gpio(bt->bttv_nr, &mp->rd.value); + // dprintk("dvb_bt8xx: dst read gpio 0x%02x\n", (unsigned)mp->dstg.rd.value); + break; + case DST_IG_TS: + /* Set packet size */ + bt->TS_Size = mp->psize; + break; + + default: + retval = -EINVAL; + break; + } + up(&bt->gpio_lock); + return retval; +} + +EXPORT_SYMBOL(bt878_device_control); + +/***********************/ +/* PCI device handling */ +/***********************/ + +static int __devinit bt878_probe(struct pci_dev *dev, + const struct pci_device_id *pci_id) +{ + int result; + unsigned char lat; + struct bt878 *bt; +#if defined(__powerpc__) + unsigned int cmd; +#endif + + printk(KERN_INFO "bt878: Bt878 AUDIO function found (%d).\n", + bt878_num); + if (pci_enable_device(dev)) + return -EIO; + + bt = &bt878[bt878_num]; + bt->dev = dev; + bt->nr = bt878_num; + bt->shutdown = 0; + + bt->id = dev->device; + bt->irq = dev->irq; + bt->bt878_adr = pci_resource_start(dev, 0); + if (!request_mem_region(pci_resource_start(dev, 0), + pci_resource_len(dev, 0), "bt878")) { + result = -EBUSY; + goto fail0; + } + + pci_read_config_byte(dev, PCI_CLASS_REVISION, &bt->revision); + pci_read_config_byte(dev, PCI_LATENCY_TIMER, &lat); + printk(KERN_INFO "bt878(%d): Bt%x (rev %d) at %02x:%02x.%x, ", + bt878_num, bt->id, bt->revision, dev->bus->number, + PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn)); + printk("irq: %d, latency: %d, memory: 0x%lx\n", + bt->irq, lat, bt->bt878_adr); + + +#if defined(__powerpc__) + /* on OpenFirmware machines (PowerMac at least), PCI memory cycle */ + /* response on cards with no firmware is not enabled by OF */ + pci_read_config_dword(dev, PCI_COMMAND, &cmd); + cmd = (cmd | PCI_COMMAND_MEMORY); + pci_write_config_dword(dev, PCI_COMMAND, cmd); +#endif + +#ifdef __sparc__ + bt->bt878_mem = (unsigned char *) bt->bt878_adr; +#else + bt->bt878_mem = ioremap(bt->bt878_adr, 0x1000); +#endif + + /* clear interrupt mask */ + btwrite(0, BT848_INT_MASK); + + result = request_irq(bt->irq, bt878_irq, + SA_SHIRQ | SA_INTERRUPT, "bt878", + (void *) bt); + if (result == -EINVAL) { + printk(KERN_ERR "bt878(%d): Bad irq number or handler\n", + bt878_num); + goto fail1; + } + if (result == -EBUSY) { + printk(KERN_ERR + "bt878(%d): IRQ %d busy, change your PnP config in BIOS\n", + bt878_num, bt->irq); + goto fail1; + } + if (result < 0) + goto fail1; + + pci_set_master(dev); + pci_set_drvdata(dev, bt); + +/* if(init_bt878(btv) < 0) { + bt878_remove(dev); + return -EIO; + } +*/ + + if ((result = bt878_mem_alloc(bt))) { + printk("bt878: failed to allocate memory!\n"); + goto fail2; + } + + bt878_make_risc(bt); + btwrite(0, BT878_AINT_MASK); + bt878_num++; + + return 0; + + fail2: + free_irq(bt->irq, bt); + fail1: + release_mem_region(pci_resource_start(bt->dev, 0), + pci_resource_len(bt->dev, 0)); + fail0: + pci_disable_device(dev); + return result; +} + +static void __devexit bt878_remove(struct pci_dev *pci_dev) +{ + u8 command; + struct bt878 *bt = pci_get_drvdata(pci_dev); + + if (bt878_verbose) + printk("bt878(%d): unloading\n", bt->nr); + + /* turn off all capturing, DMA and IRQs */ + btand(~0x13, BT878_AGPIO_DMA_CTL); + + /* first disable interrupts before unmapping the memory! */ + btwrite(0, BT878_AINT_MASK); + btwrite(~0U, BT878_AINT_STAT); + + /* disable PCI bus-mastering */ + pci_read_config_byte(bt->dev, PCI_COMMAND, &command); + /* Should this be &=~ ?? */ + command &= ~PCI_COMMAND_MASTER; + pci_write_config_byte(bt->dev, PCI_COMMAND, command); + + free_irq(bt->irq, bt); + printk(KERN_DEBUG "bt878_mem: 0x%p.\n", bt->bt878_mem); + if (bt->bt878_mem) + iounmap(bt->bt878_mem); + + release_mem_region(pci_resource_start(bt->dev, 0), + pci_resource_len(bt->dev, 0)); + /* wake up any waiting processes + because shutdown flag is set, no new processes (in this queue) + are expected + */ + bt->shutdown = 1; + bt878_mem_free(bt); + + pci_set_drvdata(pci_dev, NULL); + pci_disable_device(pci_dev); + return; +} + +static struct pci_device_id bt878_pci_tbl[] __devinitdata = { + {PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BROOKTREE_878, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {0,} +}; + +MODULE_DEVICE_TABLE(pci, bt878_pci_tbl); + +static struct pci_driver bt878_pci_driver = { + .name = "bt878", + .id_table = bt878_pci_tbl, + .probe = bt878_probe, + .remove = bt878_remove, +}; + +static int bt878_pci_driver_registered = 0; + +/*******************************/ +/* Module management functions */ +/*******************************/ + +static int bt878_init_module(void) +{ + bt878_num = 0; + bt878_pci_driver_registered = 0; + + printk(KERN_INFO "bt878: AUDIO driver version %d.%d.%d loaded\n", + (BT878_VERSION_CODE >> 16) & 0xff, + (BT878_VERSION_CODE >> 8) & 0xff, + BT878_VERSION_CODE & 0xff); +/* + bt878_check_chipset(); +*/ + /* later we register inside of bt878_find_audio_dma() + * because we may want to ignore certain cards */ + bt878_pci_driver_registered = 1; + return pci_register_driver(&bt878_pci_driver); +} + +static void bt878_cleanup_module(void) +{ + if (bt878_pci_driver_registered) { + bt878_pci_driver_registered = 0; + pci_unregister_driver(&bt878_pci_driver); + } + return; +} + +module_init(bt878_init_module); +module_exit(bt878_cleanup_module); + +//MODULE_AUTHOR("XXX"); +MODULE_LICENSE("GPL"); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/dvb/bt8xx/bt878.h b/drivers/media/dvb/bt8xx/bt878.h new file mode 100644 index 000000000000..e1b9809d1b08 --- /dev/null +++ b/drivers/media/dvb/bt8xx/bt878.h @@ -0,0 +1,147 @@ +/* + bt878.h - Bt878 audio module (register offsets) + + Copyright (C) 2002 Peter Hettkamp <peter.hettkamp@t-online.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef _BT878_H_ +#define _BT878_H_ + +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include "bt848.h" +#include "bttv.h" + +#define BT878_VERSION_CODE 0x000000 + +#define BT878_AINT_STAT 0x100 +#define BT878_ARISCS (0xf<<28) +#define BT878_ARISC_EN (1<<27) +#define BT878_ASCERR (1<<19) +#define BT878_AOCERR (1<<18) +#define BT878_APABORT (1<<17) +#define BT878_ARIPERR (1<<16) +#define BT878_APPERR (1<<15) +#define BT878_AFDSR (1<<14) +#define BT878_AFTRGT (1<<13) +#define BT878_AFBUS (1<<12) +#define BT878_ARISCI (1<<11) +#define BT878_AOFLOW (1<<3) + +#define BT878_AINT_MASK 0x104 + +#define BT878_AGPIO_DMA_CTL 0x10c +#define BT878_A_GAIN (0xf<<28) +#define BT878_A_G2X (1<<27) +#define BT878_A_PWRDN (1<<26) +#define BT878_A_SEL (3<<24) +#define BT878_DA_SCE (1<<23) +#define BT878_DA_LRI (1<<22) +#define BT878_DA_MLB (1<<21) +#define BT878_DA_LRD (0x1f<<16) +#define BT878_DA_DPM (1<<15) +#define BT878_DA_SBR (1<<14) +#define BT878_DA_ES2 (1<<13) +#define BT878_DA_LMT (1<<12) +#define BT878_DA_SDR (0xf<<8) +#define BT878_DA_IOM (3<<6) +#define BT878_DA_APP (1<<5) +#define BT878_ACAP_EN (1<<4) +#define BT878_PKTP (3<<2) +#define BT878_RISC_EN (1<<1) +#define BT878_FIFO_EN 1 + +#define BT878_APACK_LEN 0x110 +#define BT878_AFP_LEN (0xff<<16) +#define BT878_ALP_LEN 0xfff + +#define BT878_ARISC_START 0x114 + +#define BT878_ARISC_PC 0x120 + +/* BT878 FUNCTION 0 REGISTERS */ +#define BT878_GPIO_DMA_CTL 0x10c + +/* Interrupt register */ +#define BT878_INT_STAT 0x100 +#define BT878_INT_MASK 0x104 +#define BT878_I2CRACK (1<<25) +#define BT878_I2CDONE (1<<8) + +#define BT878_MAX 4 + +#define BT878_RISC_SYNC_MASK (1 << 15) + +extern int bt878_num; + +struct bt878 { + struct semaphore gpio_lock; + unsigned int nr; + unsigned int bttv_nr; + struct i2c_adapter *adapter; + struct pci_dev *dev; + unsigned int id; + unsigned int TS_Size; + unsigned char revision; + unsigned int irq; + unsigned long bt878_adr; + volatile void __iomem *bt878_mem; /* function 1 */ + + volatile u32 finished_block; + volatile u32 last_block; + u32 block_count; + u32 block_bytes; + u32 line_bytes; + u32 line_count; + + u32 buf_size; + u8 *buf_cpu; + dma_addr_t buf_dma; + + u32 risc_size; + u32 *risc_cpu; + dma_addr_t risc_dma; + u32 risc_pos; + + struct tasklet_struct tasklet; + int shutdown; +}; + +extern struct bt878 bt878[BT878_MAX]; + +void bt878_start(struct bt878 *bt, u32 controlreg, u32 op_sync_orin, + u32 irq_err_ignore); +void bt878_stop(struct bt878 *bt); + +#if defined(__powerpc__) /* big-endian */ +extern __inline__ void io_st_le32(volatile unsigned __iomem *addr, unsigned val) +{ + __asm__ __volatile__("stwbrx %1,0,%2":"=m"(*addr):"r"(val), + "r"(addr)); + __asm__ __volatile__("eieio":::"memory"); +} + +#define bmtwrite(dat,adr) io_st_le32((adr),(dat)) +#define bmtread(adr) ld_le32((adr)) +#else +#define bmtwrite(dat,adr) writel((dat), (adr)) +#define bmtread(adr) readl(adr) +#endif + +#endif diff --git a/drivers/media/dvb/bt8xx/dst.c b/drivers/media/dvb/bt8xx/dst.c new file mode 100644 index 000000000000..eac83768dfd0 --- /dev/null +++ b/drivers/media/dvb/bt8xx/dst.c @@ -0,0 +1,1089 @@ +/* + Frontend-driver for TwinHan DST Frontend + + Copyright (C) 2003 Jamie Honan + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/delay.h> +#include <asm/div64.h> + +#include "dvb_frontend.h" +#include "dst_priv.h" +#include "dst.h" + +struct dst_state { + + struct i2c_adapter* i2c; + + struct bt878* bt; + + struct dvb_frontend_ops ops; + + /* configuration settings */ + const struct dst_config* config; + + struct dvb_frontend frontend; + + /* private demodulator data */ + u8 tx_tuna[10]; + u8 rx_tuna[10]; + u8 rxbuffer[10]; + u8 diseq_flags; + u8 dst_type; + u32 type_flags; + u32 frequency; /* intermediate frequency in kHz for QPSK */ + fe_spectral_inversion_t inversion; + u32 symbol_rate; /* symbol rate in Symbols per second */ + fe_code_rate_t fec; + fe_sec_voltage_t voltage; + fe_sec_tone_mode_t tone; + u32 decode_freq; + u8 decode_lock; + u16 decode_strength; + u16 decode_snr; + unsigned long cur_jiff; + u8 k22; + fe_bandwidth_t bandwidth; +}; + +static unsigned int dst_verbose = 0; +module_param(dst_verbose, int, 0644); +MODULE_PARM_DESC(dst_verbose, "verbose startup messages, default is 1 (yes)"); +static unsigned int dst_debug = 0; +module_param(dst_debug, int, 0644); +MODULE_PARM_DESC(dst_debug, "debug messages, default is 0 (no)"); + +#define dprintk if (dst_debug) printk + +#define DST_TYPE_IS_SAT 0 +#define DST_TYPE_IS_TERR 1 +#define DST_TYPE_IS_CABLE 2 + +#define DST_TYPE_HAS_NEWTUNE 1 +#define DST_TYPE_HAS_TS204 2 +#define DST_TYPE_HAS_SYMDIV 4 + +#define HAS_LOCK 1 +#define ATTEMPT_TUNE 2 +#define HAS_POWER 4 + +static void dst_packsize(struct dst_state* state, int psize) +{ + union dst_gpio_packet bits; + + bits.psize = psize; + bt878_device_control(state->bt, DST_IG_TS, &bits); +} + +static int dst_gpio_outb(struct dst_state* state, u32 mask, u32 enbb, u32 outhigh) +{ + union dst_gpio_packet enb; + union dst_gpio_packet bits; + int err; + + enb.enb.mask = mask; + enb.enb.enable = enbb; + if ((err = bt878_device_control(state->bt, DST_IG_ENABLE, &enb)) < 0) { + dprintk("%s: dst_gpio_enb error (err == %i, mask == 0x%02x, enb == 0x%02x)\n", __FUNCTION__, err, mask, enbb); + return -EREMOTEIO; + } + + /* because complete disabling means no output, no need to do output packet */ + if (enbb == 0) + return 0; + + bits.outp.mask = enbb; + bits.outp.highvals = outhigh; + + if ((err = bt878_device_control(state->bt, DST_IG_WRITE, &bits)) < 0) { + dprintk("%s: dst_gpio_outb error (err == %i, enbb == 0x%02x, outhigh == 0x%02x)\n", __FUNCTION__, err, enbb, outhigh); + return -EREMOTEIO; + } + return 0; +} + +static int dst_gpio_inb(struct dst_state *state, u8 * result) +{ + union dst_gpio_packet rd_packet; + int err; + + *result = 0; + + if ((err = bt878_device_control(state->bt, DST_IG_READ, &rd_packet)) < 0) { + dprintk("%s: dst_gpio_inb error (err == %i)\n", __FUNCTION__, err); + return -EREMOTEIO; + } + + *result = (u8) rd_packet.rd.value; + return 0; +} + +#define DST_I2C_ENABLE 1 +#define DST_8820 2 + +static int dst_reset8820(struct dst_state *state) +{ + int retval; + /* pull 8820 gpio pin low, wait, high, wait, then low */ + // dprintk ("%s: reset 8820\n", __FUNCTION__); + retval = dst_gpio_outb(state, DST_8820, DST_8820, 0); + if (retval < 0) + return retval; + msleep(10); + retval = dst_gpio_outb(state, DST_8820, DST_8820, DST_8820); + if (retval < 0) + return retval; + /* wait for more feedback on what works here * + msleep(10); + retval = dst_gpio_outb(dst, DST_8820, DST_8820, 0); + if (retval < 0) + return retval; + */ + return 0; +} + +static int dst_i2c_enable(struct dst_state *state) +{ + int retval; + /* pull I2C enable gpio pin low, wait */ + // dprintk ("%s: i2c enable\n", __FUNCTION__); + retval = dst_gpio_outb(state, ~0, DST_I2C_ENABLE, 0); + if (retval < 0) + return retval; + // dprintk ("%s: i2c enable delay\n", __FUNCTION__); + msleep(33); + return 0; +} + +static int dst_i2c_disable(struct dst_state *state) +{ + int retval; + /* release I2C enable gpio pin, wait */ + // dprintk ("%s: i2c disable\n", __FUNCTION__); + retval = dst_gpio_outb(state, ~0, 0, 0); + if (retval < 0) + return retval; + // dprintk ("%s: i2c disable delay\n", __FUNCTION__); + msleep(33); + return 0; +} + +static int dst_wait_dst_ready(struct dst_state *state) +{ + u8 reply; + int retval; + int i; + for (i = 0; i < 200; i++) { + retval = dst_gpio_inb(state, &reply); + if (retval < 0) + return retval; + if ((reply & DST_I2C_ENABLE) == 0) { + dprintk("%s: dst wait ready after %d\n", __FUNCTION__, i); + return 1; + } + msleep(10); + } + dprintk("%s: dst wait NOT ready after %d\n", __FUNCTION__, i); + return 0; +} + +static int write_dst(struct dst_state *state, u8 * data, u8 len) +{ + struct i2c_msg msg = { + .addr = state->config->demod_address,.flags = 0,.buf = data,.len = len + }; + int err; + int cnt; + + if (dst_debug && dst_verbose) { + u8 i; + dprintk("%s writing", __FUNCTION__); + for (i = 0; i < len; i++) { + dprintk(" 0x%02x", data[i]); + } + dprintk("\n"); + } + msleep(30); + for (cnt = 0; cnt < 4; cnt++) { + if ((err = i2c_transfer(state->i2c, &msg, 1)) < 0) { + dprintk("%s: write_dst error (err == %i, len == 0x%02x, b0 == 0x%02x)\n", __FUNCTION__, err, len, data[0]); + dst_i2c_disable(state); + msleep(500); + dst_i2c_enable(state); + msleep(500); + continue; + } else + break; + } + if (cnt >= 4) + return -EREMOTEIO; + return 0; +} + +static int read_dst(struct dst_state *state, u8 * ret, u8 len) +{ + struct i2c_msg msg = {.addr = state->config->demod_address,.flags = I2C_M_RD,.buf = ret,.len = len }; + int err; + int cnt; + + for (cnt = 0; cnt < 4; cnt++) { + if ((err = i2c_transfer(state->i2c, &msg, 1)) < 0) { + dprintk("%s: read_dst error (err == %i, len == 0x%02x, b0 == 0x%02x)\n", __FUNCTION__, err, len, ret[0]); + dst_i2c_disable(state); + dst_i2c_enable(state); + continue; + } else + break; + } + if (cnt >= 4) + return -EREMOTEIO; + dprintk("%s reply is 0x%x\n", __FUNCTION__, ret[0]); + if (dst_debug && dst_verbose) { + for (err = 1; err < len; err++) + dprintk(" 0x%x", ret[err]); + if (err > 1) + dprintk("\n"); + } + return 0; +} + +static int dst_set_freq(struct dst_state *state, u32 freq) +{ + u8 *val; + + state->frequency = freq; + + // dprintk("%s: set frequency %u\n", __FUNCTION__, freq); + if (state->dst_type == DST_TYPE_IS_SAT) { + freq = freq / 1000; + if (freq < 950 || freq > 2150) + return -EINVAL; + val = &state->tx_tuna[0]; + val[2] = (freq >> 8) & 0x7f; + val[3] = (u8) freq; + val[4] = 1; + val[8] &= ~4; + if (freq < 1531) + val[8] |= 4; + } else if (state->dst_type == DST_TYPE_IS_TERR) { + freq = freq / 1000; + if (freq < 137000 || freq > 858000) + return -EINVAL; + val = &state->tx_tuna[0]; + val[2] = (freq >> 16) & 0xff; + val[3] = (freq >> 8) & 0xff; + val[4] = (u8) freq; + val[5] = 0; + switch (state->bandwidth) { + case BANDWIDTH_6_MHZ: + val[6] = 6; + break; + + case BANDWIDTH_7_MHZ: + case BANDWIDTH_AUTO: + val[6] = 7; + break; + + case BANDWIDTH_8_MHZ: + val[6] = 8; + break; + } + + val[7] = 0; + val[8] = 0; + } else if (state->dst_type == DST_TYPE_IS_CABLE) { + /* guess till will get one */ + freq = freq / 1000; + val = &state->tx_tuna[0]; + val[2] = (freq >> 16) & 0xff; + val[3] = (freq >> 8) & 0xff; + val[4] = (u8) freq; + } else + return -EINVAL; + return 0; +} + +static int dst_set_bandwidth(struct dst_state* state, fe_bandwidth_t bandwidth) +{ + u8 *val; + + state->bandwidth = bandwidth; + + if (state->dst_type != DST_TYPE_IS_TERR) + return 0; + + val = &state->tx_tuna[0]; + switch (bandwidth) { + case BANDWIDTH_6_MHZ: + val[6] = 6; + break; + + case BANDWIDTH_7_MHZ: + val[6] = 7; + break; + + case BANDWIDTH_8_MHZ: + val[6] = 8; + break; + + default: + return -EINVAL; + } + return 0; +} + +static int dst_set_inversion(struct dst_state* state, fe_spectral_inversion_t inversion) +{ + u8 *val; + + state->inversion = inversion; + + val = &state->tx_tuna[0]; + + val[8] &= ~0x80; + + switch (inversion) { + case INVERSION_OFF: + break; + case INVERSION_ON: + val[8] |= 0x80; + break; + default: + return -EINVAL; + } + return 0; +} + +static int dst_set_fec(struct dst_state* state, fe_code_rate_t fec) +{ + state->fec = fec; + return 0; +} + +static fe_code_rate_t dst_get_fec(struct dst_state* state) +{ + return state->fec; +} + +static int dst_set_symbolrate(struct dst_state* state, u32 srate) +{ + u8 *val; + u32 symcalc; + u64 sval; + + state->symbol_rate = srate; + + if (state->dst_type == DST_TYPE_IS_TERR) { + return 0; + } + // dprintk("%s: set srate %u\n", __FUNCTION__, srate); + srate /= 1000; + val = &state->tx_tuna[0]; + + if (state->type_flags & DST_TYPE_HAS_SYMDIV) { + sval = srate; + sval <<= 20; + do_div(sval, 88000); + symcalc = (u32) sval; + // dprintk("%s: set symcalc %u\n", __FUNCTION__, symcalc); + val[5] = (u8) (symcalc >> 12); + val[6] = (u8) (symcalc >> 4); + val[7] = (u8) (symcalc << 4); + } else { + val[5] = (u8) (srate >> 16) & 0x7f; + val[6] = (u8) (srate >> 8); + val[7] = (u8) srate; + } + val[8] &= ~0x20; + if (srate > 8000) + val[8] |= 0x20; + return 0; +} + +static u8 dst_check_sum(u8 * buf, u32 len) +{ + u32 i; + u8 val = 0; + if (!len) + return 0; + for (i = 0; i < len; i++) { + val += buf[i]; + } + return ((~val) + 1); +} + +struct dst_types { + char *mstr; + int offs; + u8 dst_type; + u32 type_flags; +}; + +static struct dst_types dst_tlist[] = { + {"DST-020", 0, DST_TYPE_IS_SAT, DST_TYPE_HAS_SYMDIV}, + {"DST-030", 0, DST_TYPE_IS_SAT, DST_TYPE_HAS_TS204 | DST_TYPE_HAS_NEWTUNE}, + {"DST-03T", 0, DST_TYPE_IS_SAT, DST_TYPE_HAS_SYMDIV | DST_TYPE_HAS_TS204}, + {"DST-MOT", 0, DST_TYPE_IS_SAT, DST_TYPE_HAS_SYMDIV}, + {"DST-CI", 1, DST_TYPE_IS_SAT, DST_TYPE_HAS_TS204 | DST_TYPE_HAS_NEWTUNE}, + {"DSTMCI", 1, DST_TYPE_IS_SAT, DST_TYPE_HAS_NEWTUNE}, + {"DSTFCI", 1, DST_TYPE_IS_SAT, DST_TYPE_HAS_NEWTUNE}, + {"DCTNEW", 1, DST_TYPE_IS_CABLE, DST_TYPE_HAS_NEWTUNE}, + {"DCT-CI", 1, DST_TYPE_IS_CABLE, DST_TYPE_HAS_NEWTUNE | DST_TYPE_HAS_TS204}, + {"DTTDIG", 1, DST_TYPE_IS_TERR, 0} +}; + +/* DCTNEW and DCT-CI are guesses */ + +static void dst_type_flags_print(u32 type_flags) +{ + printk("DST type flags :"); + if (type_flags & DST_TYPE_HAS_NEWTUNE) + printk(" 0x%x newtuner", DST_TYPE_HAS_NEWTUNE); + if (type_flags & DST_TYPE_HAS_TS204) + printk(" 0x%x ts204", DST_TYPE_HAS_TS204); + if (type_flags & DST_TYPE_HAS_SYMDIV) + printk(" 0x%x symdiv", DST_TYPE_HAS_SYMDIV); + printk("\n"); +} + +static int dst_type_print(u8 type) +{ + char *otype; + switch (type) { + case DST_TYPE_IS_SAT: + otype = "satellite"; + break; + case DST_TYPE_IS_TERR: + otype = "terrestrial"; + break; + case DST_TYPE_IS_CABLE: + otype = "cable"; + break; + default: + printk("%s: invalid dst type %d\n", __FUNCTION__, type); + return -EINVAL; + } + printk("DST type : %s\n", otype); + return 0; +} + +static int dst_check_ci(struct dst_state *state) +{ + u8 txbuf[8]; + u8 rxbuf[8]; + int retval; + int i; + struct dst_types *dsp; + u8 use_dst_type; + u32 use_type_flags; + + memset(txbuf, 0, sizeof(txbuf)); + txbuf[1] = 6; + txbuf[7] = dst_check_sum(txbuf, 7); + + dst_i2c_enable(state); + dst_reset8820(state); + retval = write_dst(state, txbuf, 8); + if (retval < 0) { + dst_i2c_disable(state); + dprintk("%s: write not successful, maybe no card?\n", __FUNCTION__); + return retval; + } + msleep(3); + retval = read_dst(state, rxbuf, 1); + dst_i2c_disable(state); + if (retval < 0) { + dprintk("%s: read not successful, maybe no card?\n", __FUNCTION__); + return retval; + } + if (rxbuf[0] != 0xff) { + dprintk("%s: write reply not 0xff, not ci (%02x)\n", __FUNCTION__, rxbuf[0]); + return retval; + } + if (!dst_wait_dst_ready(state)) + return 0; + // dst_i2c_enable(i2c); Dimitri + retval = read_dst(state, rxbuf, 8); + dst_i2c_disable(state); + if (retval < 0) { + dprintk("%s: read not successful\n", __FUNCTION__); + return retval; + } + if (rxbuf[7] != dst_check_sum(rxbuf, 7)) { + dprintk("%s: checksum failure\n", __FUNCTION__); + return retval; + } + rxbuf[7] = '\0'; + for (i = 0, dsp = &dst_tlist[0]; i < sizeof(dst_tlist) / sizeof(dst_tlist[0]); i++, dsp++) { + if (!strncmp(&rxbuf[dsp->offs], dsp->mstr, strlen(dsp->mstr))) { + use_type_flags = dsp->type_flags; + use_dst_type = dsp->dst_type; + printk("%s: recognize %s\n", __FUNCTION__, dsp->mstr); + break; + } + } + if (i >= sizeof(dst_tlist) / sizeof(dst_tlist[0])) { + printk("%s: unable to recognize %s or %s\n", __FUNCTION__, &rxbuf[0], &rxbuf[1]); + printk("%s please email linux-dvb@linuxtv.org with this type in\n", __FUNCTION__); + use_dst_type = DST_TYPE_IS_SAT; + use_type_flags = DST_TYPE_HAS_SYMDIV; + } + dst_type_print(use_dst_type); + + state->type_flags = use_type_flags; + state->dst_type = use_dst_type; + dst_type_flags_print(state->type_flags); + + if (state->type_flags & DST_TYPE_HAS_TS204) { + dst_packsize(state, 204); + } + return 0; +} + +static int dst_command(struct dst_state* state, u8 * data, u8 len) +{ + int retval; + u8 reply; + + dst_i2c_enable(state); + dst_reset8820(state); + retval = write_dst(state, data, len); + if (retval < 0) { + dst_i2c_disable(state); + dprintk("%s: write not successful\n", __FUNCTION__); + return retval; + } + msleep(33); + retval = read_dst(state, &reply, 1); + dst_i2c_disable(state); + if (retval < 0) { + dprintk("%s: read verify not successful\n", __FUNCTION__); + return retval; + } + if (reply != 0xff) { + dprintk("%s: write reply not 0xff 0x%02x \n", __FUNCTION__, reply); + return 0; + } + if (len >= 2 && data[0] == 0 && (data[1] == 1 || data[1] == 3)) + return 0; + if (!dst_wait_dst_ready(state)) + return 0; + // dst_i2c_enable(i2c); Per dimitri + retval = read_dst(state, state->rxbuffer, 8); + dst_i2c_disable(state); + if (retval < 0) { + dprintk("%s: read not successful\n", __FUNCTION__); + return 0; + } + if (state->rxbuffer[7] != dst_check_sum(state->rxbuffer, 7)) { + dprintk("%s: checksum failure\n", __FUNCTION__); + return 0; + } + return 0; +} + +static int dst_get_signal(struct dst_state* state) +{ + int retval; + u8 get_signal[] = { 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb }; + + if ((state->diseq_flags & ATTEMPT_TUNE) == 0) { + state->decode_lock = state->decode_strength = state->decode_snr = 0; + return 0; + } + if (0 == (state->diseq_flags & HAS_LOCK)) { + state->decode_lock = state->decode_strength = state->decode_snr = 0; + return 0; + } + if (time_after_eq(jiffies, state->cur_jiff + (HZ / 5))) { + retval = dst_command(state, get_signal, 8); + if (retval < 0) + return retval; + if (state->dst_type == DST_TYPE_IS_SAT) { + state->decode_lock = ((state->rxbuffer[6] & 0x10) == 0) ? 1 : 0; + state->decode_strength = state->rxbuffer[5] << 8; + state->decode_snr = state->rxbuffer[2] << 8 | state->rxbuffer[3]; + } else if ((state->dst_type == DST_TYPE_IS_TERR) || (state->dst_type == DST_TYPE_IS_CABLE)) { + state->decode_lock = (state->rxbuffer[1]) ? 1 : 0; + state->decode_strength = state->rxbuffer[4] << 8; + state->decode_snr = state->rxbuffer[3] << 8; + } + state->cur_jiff = jiffies; + } + return 0; +} + +static int dst_tone_power_cmd(struct dst_state* state) +{ + u8 paket[8] = { 0x00, 0x09, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00 }; + + if (state->dst_type == DST_TYPE_IS_TERR) + return 0; + + if (state->voltage == SEC_VOLTAGE_OFF) + paket[4] = 0; + else + paket[4] = 1; + if (state->tone == SEC_TONE_ON) + paket[2] = state->k22; + else + paket[2] = 0; + paket[7] = dst_check_sum(&paket[0], 7); + dst_command(state, paket, 8); + return 0; +} + +static int dst_get_tuna(struct dst_state* state) +{ + int retval; + if ((state->diseq_flags & ATTEMPT_TUNE) == 0) + return 0; + state->diseq_flags &= ~(HAS_LOCK); + if (!dst_wait_dst_ready(state)) + return 0; + if (state->type_flags & DST_TYPE_HAS_NEWTUNE) { + /* how to get variable length reply ???? */ + retval = read_dst(state, state->rx_tuna, 10); + } else { + retval = read_dst(state, &state->rx_tuna[2], 8); + } + if (retval < 0) { + dprintk("%s: read not successful\n", __FUNCTION__); + return 0; + } + if (state->type_flags & DST_TYPE_HAS_NEWTUNE) { + if (state->rx_tuna[9] != dst_check_sum(&state->rx_tuna[0], 9)) { + dprintk("%s: checksum failure?\n", __FUNCTION__); + return 0; + } + } else { + if (state->rx_tuna[9] != dst_check_sum(&state->rx_tuna[2], 7)) { + dprintk("%s: checksum failure?\n", __FUNCTION__); + return 0; + } + } + if (state->rx_tuna[2] == 0 && state->rx_tuna[3] == 0) + return 0; + state->decode_freq = ((state->rx_tuna[2] & 0x7f) << 8) + state->rx_tuna[3]; + + state->decode_lock = 1; + /* + dst->decode_n1 = (dst->rx_tuna[4] << 8) + + (dst->rx_tuna[5]); + + dst->decode_n2 = (dst->rx_tuna[8] << 8) + + (dst->rx_tuna[7]); + */ + state->diseq_flags |= HAS_LOCK; + /* dst->cur_jiff = jiffies; */ + return 1; +} + +static int dst_set_voltage(struct dvb_frontend* fe, fe_sec_voltage_t voltage); + +static int dst_write_tuna(struct dvb_frontend* fe) +{ + struct dst_state* state = (struct dst_state*) fe->demodulator_priv; + int retval; + u8 reply; + + dprintk("%s: type_flags 0x%x \n", __FUNCTION__, state->type_flags); + state->decode_freq = 0; + state->decode_lock = state->decode_strength = state->decode_snr = 0; + if (state->dst_type == DST_TYPE_IS_SAT) { + if (!(state->diseq_flags & HAS_POWER)) + dst_set_voltage(fe, SEC_VOLTAGE_13); + } + state->diseq_flags &= ~(HAS_LOCK | ATTEMPT_TUNE); + dst_i2c_enable(state); + if (state->type_flags & DST_TYPE_HAS_NEWTUNE) { + dst_reset8820(state); + state->tx_tuna[9] = dst_check_sum(&state->tx_tuna[0], 9); + retval = write_dst(state, &state->tx_tuna[0], 10); + } else { + state->tx_tuna[9] = dst_check_sum(&state->tx_tuna[2], 7); + retval = write_dst(state, &state->tx_tuna[2], 8); + } + if (retval < 0) { + dst_i2c_disable(state); + dprintk("%s: write not successful\n", __FUNCTION__); + return retval; + } + msleep(3); + retval = read_dst(state, &reply, 1); + dst_i2c_disable(state); + if (retval < 0) { + dprintk("%s: read verify not successful\n", __FUNCTION__); + return retval; + } + if (reply != 0xff) { + dprintk("%s: write reply not 0xff 0x%02x \n", __FUNCTION__, reply); + return 0; + } + state->diseq_flags |= ATTEMPT_TUNE; + return dst_get_tuna(state); +} + +/* + * line22k0 0x00, 0x09, 0x00, 0xff, 0x01, 0x00, 0x00, 0x00 + * line22k1 0x00, 0x09, 0x01, 0xff, 0x01, 0x00, 0x00, 0x00 + * line22k2 0x00, 0x09, 0x02, 0xff, 0x01, 0x00, 0x00, 0x00 + * tone 0x00, 0x09, 0xff, 0x00, 0x01, 0x00, 0x00, 0x00 + * data 0x00, 0x09, 0xff, 0x01, 0x01, 0x00, 0x00, 0x00 + * power_off 0x00, 0x09, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 + * power_on 0x00, 0x09, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00 + * Diseqc 1 0x00, 0x08, 0x04, 0xe0, 0x10, 0x38, 0xf0, 0xec + * Diseqc 2 0x00, 0x08, 0x04, 0xe0, 0x10, 0x38, 0xf4, 0xe8 + * Diseqc 3 0x00, 0x08, 0x04, 0xe0, 0x10, 0x38, 0xf8, 0xe4 + * Diseqc 4 0x00, 0x08, 0x04, 0xe0, 0x10, 0x38, 0xfc, 0xe0 + */ + +static int dst_set_diseqc(struct dvb_frontend* fe, struct dvb_diseqc_master_cmd* cmd) +{ + struct dst_state* state = (struct dst_state*) fe->demodulator_priv; + u8 paket[8] = { 0x00, 0x08, 0x04, 0xe0, 0x10, 0x38, 0xf0, 0xec }; + + if (state->dst_type == DST_TYPE_IS_TERR) + return 0; + + if (cmd->msg_len == 0 || cmd->msg_len > 4) + return -EINVAL; + memcpy(&paket[3], cmd->msg, cmd->msg_len); + paket[7] = dst_check_sum(&paket[0], 7); + dst_command(state, paket, 8); + return 0; +} + +static int dst_set_voltage(struct dvb_frontend* fe, fe_sec_voltage_t voltage) +{ + u8 *val; + int need_cmd; + struct dst_state* state = (struct dst_state*) fe->demodulator_priv; + + state->voltage = voltage; + + if (state->dst_type == DST_TYPE_IS_TERR) + return 0; + + need_cmd = 0; + val = &state->tx_tuna[0]; + val[8] &= ~0x40; + switch (voltage) { + case SEC_VOLTAGE_13: + if ((state->diseq_flags & HAS_POWER) == 0) + need_cmd = 1; + state->diseq_flags |= HAS_POWER; + break; + case SEC_VOLTAGE_18: + if ((state->diseq_flags & HAS_POWER) == 0) + need_cmd = 1; + state->diseq_flags |= HAS_POWER; + val[8] |= 0x40; + break; + case SEC_VOLTAGE_OFF: + need_cmd = 1; + state->diseq_flags &= ~(HAS_POWER | HAS_LOCK | ATTEMPT_TUNE); + break; + default: + return -EINVAL; + } + if (need_cmd) { + dst_tone_power_cmd(state); + } + return 0; +} + +static int dst_set_tone(struct dvb_frontend* fe, fe_sec_tone_mode_t tone) +{ + u8 *val; + struct dst_state* state = (struct dst_state*) fe->demodulator_priv; + + state->tone = tone; + + if (state->dst_type == DST_TYPE_IS_TERR) + return 0; + + val = &state->tx_tuna[0]; + + val[8] &= ~0x1; + + switch (tone) { + case SEC_TONE_OFF: + break; + case SEC_TONE_ON: + val[8] |= 1; + break; + default: + return -EINVAL; + } + dst_tone_power_cmd(state); + return 0; +} + +static int dst_init(struct dvb_frontend* fe) +{ + struct dst_state* state = (struct dst_state*) fe->demodulator_priv; + static u8 ini_satci_tuna[] = { 9, 0, 3, 0xb6, 1, 0, 0x73, 0x21, 0, 0 }; + static u8 ini_satfta_tuna[] = { 0, 0, 3, 0xb6, 1, 0x55, 0xbd, 0x50, 0, 0 }; + static u8 ini_tvfta_tuna[] = { 0, 0, 3, 0xb6, 1, 7, 0x0, 0x0, 0, 0 }; + static u8 ini_tvci_tuna[] = { 9, 0, 3, 0xb6, 1, 7, 0x0, 0x0, 0, 0 }; + static u8 ini_cabfta_tuna[] = { 0, 0, 3, 0xb6, 1, 7, 0x0, 0x0, 0, 0 }; + static u8 ini_cabci_tuna[] = { 9, 0, 3, 0xb6, 1, 7, 0x0, 0x0, 0, 0 }; + state->inversion = INVERSION_ON; + state->voltage = SEC_VOLTAGE_13; + state->tone = SEC_TONE_OFF; + state->symbol_rate = 29473000; + state->fec = FEC_AUTO; + state->diseq_flags = 0; + state->k22 = 0x02; + state->bandwidth = BANDWIDTH_7_MHZ; + state->cur_jiff = jiffies; + if (state->dst_type == DST_TYPE_IS_SAT) { + state->frequency = 950000; + memcpy(state->tx_tuna, ((state->type_flags & DST_TYPE_HAS_NEWTUNE) ? ini_satci_tuna : ini_satfta_tuna), sizeof(ini_satfta_tuna)); + } else if (state->dst_type == DST_TYPE_IS_TERR) { + state->frequency = 137000000; + memcpy(state->tx_tuna, ((state->type_flags & DST_TYPE_HAS_NEWTUNE) ? ini_tvci_tuna : ini_tvfta_tuna), sizeof(ini_tvfta_tuna)); + } else if (state->dst_type == DST_TYPE_IS_CABLE) { + state->frequency = 51000000; + memcpy(state->tx_tuna, ((state->type_flags & DST_TYPE_HAS_NEWTUNE) ? ini_cabci_tuna : ini_cabfta_tuna), sizeof(ini_cabfta_tuna)); + } + + return 0; +} + +static int dst_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct dst_state* state = (struct dst_state*) fe->demodulator_priv; + + *status = 0; + if (state->diseq_flags & HAS_LOCK) { + dst_get_signal(state); + if (state->decode_lock) + *status |= FE_HAS_LOCK | FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_SYNC | FE_HAS_VITERBI; + } + + return 0; +} + +static int dst_read_signal_strength(struct dvb_frontend* fe, u16* strength) +{ + struct dst_state* state = (struct dst_state*) fe->demodulator_priv; + + dst_get_signal(state); + *strength = state->decode_strength; + + return 0; +} + +static int dst_read_snr(struct dvb_frontend* fe, u16* snr) +{ + struct dst_state* state = (struct dst_state*) fe->demodulator_priv; + + dst_get_signal(state); + *snr = state->decode_snr; + + return 0; +} + +static int dst_set_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + struct dst_state* state = (struct dst_state*) fe->demodulator_priv; + + dst_set_freq(state, p->frequency); + dst_set_inversion(state, p->inversion); + if (state->dst_type == DST_TYPE_IS_SAT) { + dst_set_fec(state, p->u.qpsk.fec_inner); + dst_set_symbolrate(state, p->u.qpsk.symbol_rate); + } else if (state->dst_type == DST_TYPE_IS_TERR) { + dst_set_bandwidth(state, p->u.ofdm.bandwidth); + } else if (state->dst_type == DST_TYPE_IS_CABLE) { + dst_set_fec(state, p->u.qam.fec_inner); + dst_set_symbolrate(state, p->u.qam.symbol_rate); + } + dst_write_tuna(fe); + + return 0; +} + +static int dst_get_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + struct dst_state* state = (struct dst_state*) fe->demodulator_priv; + + p->frequency = state->decode_freq; + p->inversion = state->inversion; + if (state->dst_type == DST_TYPE_IS_SAT) { + p->u.qpsk.symbol_rate = state->symbol_rate; + p->u.qpsk.fec_inner = dst_get_fec(state); + } else if (state->dst_type == DST_TYPE_IS_TERR) { + p->u.ofdm.bandwidth = state->bandwidth; + } else if (state->dst_type == DST_TYPE_IS_CABLE) { + p->u.qam.symbol_rate = state->symbol_rate; + p->u.qam.fec_inner = dst_get_fec(state); + p->u.qam.modulation = QAM_AUTO; + } + + return 0; +} + +static void dst_release(struct dvb_frontend* fe) +{ + struct dst_state* state = (struct dst_state*) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops dst_dvbt_ops; +static struct dvb_frontend_ops dst_dvbs_ops; +static struct dvb_frontend_ops dst_dvbc_ops; + +struct dvb_frontend* dst_attach(const struct dst_config* config, + struct i2c_adapter* i2c, + struct bt878 *bt) +{ + struct dst_state* state = NULL; + + /* allocate memory for the internal state */ + state = (struct dst_state*) kmalloc(sizeof(struct dst_state), GFP_KERNEL); + if (state == NULL) goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + state->bt = bt; + + /* check if the demod is there */ + if (dst_check_ci(state) < 0) goto error; + + /* determine settings based on type */ + switch (state->dst_type) { + case DST_TYPE_IS_TERR: + memcpy(&state->ops, &dst_dvbt_ops, sizeof(struct dvb_frontend_ops)); + break; + case DST_TYPE_IS_CABLE: + memcpy(&state->ops, &dst_dvbc_ops, sizeof(struct dvb_frontend_ops)); + break; + case DST_TYPE_IS_SAT: + memcpy(&state->ops, &dst_dvbs_ops, sizeof(struct dvb_frontend_ops)); + break; + default: + printk("dst: unknown frontend type. please report to the LinuxTV.org DVB mailinglist.\n"); + goto error; + } + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops dst_dvbt_ops = { + + .info = { + .name = "DST DVB-T", + .type = FE_OFDM, + .frequency_min = 137000000, + .frequency_max = 858000000, + .frequency_stepsize = 166667, + .caps = FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO | FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO + }, + + .release = dst_release, + + .init = dst_init, + + .set_frontend = dst_set_frontend, + .get_frontend = dst_get_frontend, + + .read_status = dst_read_status, + .read_signal_strength = dst_read_signal_strength, + .read_snr = dst_read_snr, +}; + +static struct dvb_frontend_ops dst_dvbs_ops = { + + .info = { + .name = "DST DVB-S", + .type = FE_QPSK, + .frequency_min = 950000, + .frequency_max = 2150000, + .frequency_stepsize = 1000, /* kHz for QPSK frontends */ + .frequency_tolerance = 29500, + .symbol_rate_min = 1000000, + .symbol_rate_max = 45000000, + /* . symbol_rate_tolerance = ???,*/ + .caps = FE_CAN_FEC_AUTO | FE_CAN_QPSK + }, + + .release = dst_release, + + .init = dst_init, + + .set_frontend = dst_set_frontend, + .get_frontend = dst_get_frontend, + + .read_status = dst_read_status, + .read_signal_strength = dst_read_signal_strength, + .read_snr = dst_read_snr, + + .diseqc_send_master_cmd = dst_set_diseqc, + .set_voltage = dst_set_voltage, + .set_tone = dst_set_tone, +}; + +static struct dvb_frontend_ops dst_dvbc_ops = { + + .info = { + .name = "DST DVB-C", + .type = FE_QAM, + .frequency_stepsize = 62500, + .frequency_min = 51000000, + .frequency_max = 858000000, + .symbol_rate_min = 1000000, + .symbol_rate_max = 45000000, + /* . symbol_rate_tolerance = ???,*/ + .caps = FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO + }, + + .release = dst_release, + + .init = dst_init, + + .set_frontend = dst_set_frontend, + .get_frontend = dst_get_frontend, + + .read_status = dst_read_status, + .read_signal_strength = dst_read_signal_strength, + .read_snr = dst_read_snr, +}; + +MODULE_DESCRIPTION("DST DVB-S/T/C Combo Frontend driver"); +MODULE_AUTHOR("Jamie Honan"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(dst_attach); diff --git a/drivers/media/dvb/bt8xx/dst.h b/drivers/media/dvb/bt8xx/dst.h new file mode 100644 index 000000000000..bcb418c5c121 --- /dev/null +++ b/drivers/media/dvb/bt8xx/dst.h @@ -0,0 +1,40 @@ +/* + Frontend-driver for TwinHan DST Frontend + + Copyright (C) 2003 Jamie Honan + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef DST_H +#define DST_H + +#include <linux/dvb/frontend.h> +#include <linux/device.h> +#include "bt878.h" + +struct dst_config +{ + /* the demodulator's i2c address */ + u8 demod_address; +}; + +extern struct dvb_frontend* dst_attach(const struct dst_config* config, + struct i2c_adapter* i2c, + struct bt878 *bt); + +#endif // DST_H diff --git a/drivers/media/dvb/bt8xx/dst_priv.h b/drivers/media/dvb/bt8xx/dst_priv.h new file mode 100644 index 000000000000..80488aa628b4 --- /dev/null +++ b/drivers/media/dvb/bt8xx/dst_priv.h @@ -0,0 +1,36 @@ +/* + * dst-bt878.h: part of the DST driver for the TwinHan DST Frontend + * + * Copyright (C) 2003 Jamie Honan + */ + +struct dst_gpio_enable { + u32 mask; + u32 enable; +}; + +struct dst_gpio_output { + u32 mask; + u32 highvals; +}; + +struct dst_gpio_read { + unsigned long value; +}; + +union dst_gpio_packet { + struct dst_gpio_enable enb; + struct dst_gpio_output outp; + struct dst_gpio_read rd; + int psize; +}; + +#define DST_IG_ENABLE 0 +#define DST_IG_WRITE 1 +#define DST_IG_READ 2 +#define DST_IG_TS 3 + +struct bt878; + +int bt878_device_control(struct bt878 *bt, unsigned int cmd, union dst_gpio_packet *mp); + diff --git a/drivers/media/dvb/bt8xx/dvb-bt8xx.c b/drivers/media/dvb/bt8xx/dvb-bt8xx.c new file mode 100644 index 000000000000..b735397f59aa --- /dev/null +++ b/drivers/media/dvb/bt8xx/dvb-bt8xx.c @@ -0,0 +1,797 @@ +/* + * Bt8xx based DVB adapter driver + * + * Copyright (C) 2002,2003 Florian Schirmer <jolt@tuxbox.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/bitops.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/i2c.h> + +#include "dmxdev.h" +#include "dvbdev.h" +#include "dvb_demux.h" +#include "dvb_frontend.h" + +#include "dvb-bt8xx.h" + +#include "bt878.h" + +static int debug; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off)."); + +#define dprintk( args... ) \ + do { \ + if (debug) printk(KERN_DEBUG args); \ + } while (0) + +static void dvb_bt8xx_task(unsigned long data) +{ + struct dvb_bt8xx_card *card = (struct dvb_bt8xx_card *)data; + + //printk("%d ", card->bt->finished_block); + + while (card->bt->last_block != card->bt->finished_block) { + (card->bt->TS_Size ? dvb_dmx_swfilter_204 : dvb_dmx_swfilter) + (&card->demux, + &card->bt->buf_cpu[card->bt->last_block * + card->bt->block_bytes], + card->bt->block_bytes); + card->bt->last_block = (card->bt->last_block + 1) % + card->bt->block_count; + } +} + +static int dvb_bt8xx_start_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + struct dvb_bt8xx_card *card = dvbdmx->priv; + int rc; + + dprintk("dvb_bt8xx: start_feed\n"); + + if (!dvbdmx->dmx.frontend) + return -EINVAL; + + down(&card->lock); + card->nfeeds++; + rc = card->nfeeds; + if (card->nfeeds == 1) + bt878_start(card->bt, card->gpio_mode, + card->op_sync_orin, card->irq_err_ignore); + up(&card->lock); + return rc; +} + +static int dvb_bt8xx_stop_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + struct dvb_bt8xx_card *card = dvbdmx->priv; + + dprintk("dvb_bt8xx: stop_feed\n"); + + if (!dvbdmx->dmx.frontend) + return -EINVAL; + + down(&card->lock); + card->nfeeds--; + if (card->nfeeds == 0) + bt878_stop(card->bt); + up(&card->lock); + + return 0; +} + +static int is_pci_slot_eq(struct pci_dev* adev, struct pci_dev* bdev) +{ + if ((adev->subsystem_vendor == bdev->subsystem_vendor) && + (adev->subsystem_device == bdev->subsystem_device) && + (adev->bus->number == bdev->bus->number) && + (PCI_SLOT(adev->devfn) == PCI_SLOT(bdev->devfn))) + return 1; + return 0; +} + +static struct bt878 __init *dvb_bt8xx_878_match(unsigned int bttv_nr, struct pci_dev* bttv_pci_dev) +{ + unsigned int card_nr; + + /* Hmm, n squared. Hope n is small */ + for (card_nr = 0; card_nr < bt878_num; card_nr++) { + if (is_pci_slot_eq(bt878[card_nr].dev, bttv_pci_dev)) + return &bt878[card_nr]; + } + return NULL; +} + + +static int thomson_dtt7579_demod_init(struct dvb_frontend* fe) +{ + static u8 mt352_clock_config [] = { 0x89, 0x38, 0x38 }; + static u8 mt352_reset [] = { 0x50, 0x80 }; + static u8 mt352_adc_ctl_1_cfg [] = { 0x8E, 0x40 }; + static u8 mt352_agc_cfg [] = { 0x67, 0x28, 0x20 }; + static u8 mt352_gpp_ctl_cfg [] = { 0x8C, 0x33 }; + static u8 mt352_capt_range_cfg[] = { 0x75, 0x32 }; + + mt352_write(fe, mt352_clock_config, sizeof(mt352_clock_config)); + udelay(2000); + mt352_write(fe, mt352_reset, sizeof(mt352_reset)); + mt352_write(fe, mt352_adc_ctl_1_cfg, sizeof(mt352_adc_ctl_1_cfg)); + + mt352_write(fe, mt352_agc_cfg, sizeof(mt352_agc_cfg)); + mt352_write(fe, mt352_gpp_ctl_cfg, sizeof(mt352_gpp_ctl_cfg)); + mt352_write(fe, mt352_capt_range_cfg, sizeof(mt352_capt_range_cfg)); + + return 0; +} + +static int thomson_dtt7579_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params, u8* pllbuf) +{ + u32 div; + unsigned char bs = 0; + unsigned char cp = 0; + + #define IF_FREQUENCYx6 217 /* 6 * 36.16666666667MHz */ + div = (((params->frequency + 83333) * 3) / 500000) + IF_FREQUENCYx6; + + if (params->frequency < 542000000) cp = 0xb4; + else if (params->frequency < 771000000) cp = 0xbc; + else cp = 0xf4; + + if (params->frequency == 0) bs = 0x03; + else if (params->frequency < 443250000) bs = 0x02; + else bs = 0x08; + + pllbuf[0] = 0xc0; // Note: non-linux standard PLL i2c address + pllbuf[1] = div >> 8; + pllbuf[2] = div & 0xff; + pllbuf[3] = cp; + pllbuf[4] = bs; + + return 0; +} + +static struct mt352_config thomson_dtt7579_config = { + + .demod_address = 0x0f, + .demod_init = thomson_dtt7579_demod_init, + .pll_set = thomson_dtt7579_pll_set, +}; + +static int cx24108_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + u32 freq = params->frequency; + + int i, a, n, pump; + u32 band, pll; + + + u32 osci[]={950000,1019000,1075000,1178000,1296000,1432000, + 1576000,1718000,1856000,2036000,2150000}; + u32 bandsel[]={0,0x00020000,0x00040000,0x00100800,0x00101000, + 0x00102000,0x00104000,0x00108000,0x00110000, + 0x00120000,0x00140000}; + +#define XTAL 1011100 /* Hz, really 1.0111 MHz and a /10 prescaler */ + printk("cx24108 debug: entering SetTunerFreq, freq=%d\n",freq); + + /* This is really the bit driving the tuner chip cx24108 */ + + if(freq<950000) freq=950000; /* kHz */ + if(freq>2150000) freq=2150000; /* satellite IF is 950..2150MHz */ + + /* decide which VCO to use for the input frequency */ + for(i=1;(i<sizeof(osci)/sizeof(osci[0]))&&(osci[i]<freq);i++); + printk("cx24108 debug: select vco #%d (f=%d)\n",i,freq); + band=bandsel[i]; + /* the gain values must be set by SetSymbolrate */ + /* compute the pll divider needed, from Conexant data sheet, + resolved for (n*32+a), remember f(vco) is f(receive) *2 or *4, + depending on the divider bit. It is set to /4 on the 2 lowest + bands */ + n=((i<=2?2:1)*freq*10L)/(XTAL/100); + a=n%32; n/=32; if(a==0) n--; + pump=(freq<(osci[i-1]+osci[i])/2); + pll=0xf8000000| + ((pump?1:2)<<(14+11))| + ((n&0x1ff)<<(5+11))| + ((a&0x1f)<<11); + /* everything is shifted left 11 bits to left-align the bits in the + 32bit word. Output to the tuner goes MSB-aligned, after all */ + printk("cx24108 debug: pump=%d, n=%d, a=%d\n",pump,n,a); + cx24110_pll_write(fe,band); + /* set vga and vca to their widest-band settings, as a precaution. + SetSymbolrate might not be called to set this up */ + cx24110_pll_write(fe,0x500c0000); + cx24110_pll_write(fe,0x83f1f800); + cx24110_pll_write(fe,pll); +/* writereg(client,0x56,0x7f);*/ + + return 0; +} + +static int pinnsat_pll_init(struct dvb_frontend* fe) +{ + return 0; +} + + +static struct cx24110_config pctvsat_config = { + + .demod_address = 0x55, + .pll_init = pinnsat_pll_init, + .pll_set = cx24108_pll_set, +}; + + +static int microtune_mt7202dtf_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + struct dvb_bt8xx_card *card = (struct dvb_bt8xx_card *) fe->dvb->priv; + u8 cfg, cpump, band_select; + u8 data[4]; + u32 div; + struct i2c_msg msg = { .addr = 0x60, .flags = 0, .buf = data, .len = sizeof(data) }; + + div = (36000000 + params->frequency + 83333) / 166666; + cfg = 0x88; + + if (params->frequency < 175000000) cpump = 2; + else if (params->frequency < 390000000) cpump = 1; + else if (params->frequency < 470000000) cpump = 2; + else if (params->frequency < 750000000) cpump = 2; + else cpump = 3; + + if (params->frequency < 175000000) band_select = 0x0e; + else if (params->frequency < 470000000) band_select = 0x05; + else band_select = 0x03; + + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = ((div >> 10) & 0x60) | cfg; + data[3] = cpump | band_select; + + i2c_transfer(card->i2c_adapter, &msg, 1); + return (div * 166666 - 36000000); +} + +static int microtune_mt7202dtf_request_firmware(struct dvb_frontend* fe, const struct firmware **fw, char* name) +{ + struct dvb_bt8xx_card* bt = (struct dvb_bt8xx_card*) fe->dvb->priv; + + return request_firmware(fw, name, &bt->bt->dev->dev); +} + +static struct sp887x_config microtune_mt7202dtf_config = { + + .demod_address = 0x70, + .pll_set = microtune_mt7202dtf_pll_set, + .request_firmware = microtune_mt7202dtf_request_firmware, +}; + + + +static int advbt771_samsung_tdtc9251dh0_demod_init(struct dvb_frontend* fe) +{ + static u8 mt352_clock_config [] = { 0x89, 0x38, 0x2d }; + static u8 mt352_reset [] = { 0x50, 0x80 }; + static u8 mt352_adc_ctl_1_cfg [] = { 0x8E, 0x40 }; + static u8 mt352_agc_cfg [] = { 0x67, 0x10, 0x23, 0x00, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0x40, 0x40 }; + static u8 mt352_av771_extra[] = { 0xB5, 0x7A }; + static u8 mt352_capt_range_cfg[] = { 0x75, 0x32 }; + + + mt352_write(fe, mt352_clock_config, sizeof(mt352_clock_config)); + udelay(2000); + mt352_write(fe, mt352_reset, sizeof(mt352_reset)); + mt352_write(fe, mt352_adc_ctl_1_cfg, sizeof(mt352_adc_ctl_1_cfg)); + + mt352_write(fe, mt352_agc_cfg,sizeof(mt352_agc_cfg)); + udelay(2000); + mt352_write(fe, mt352_av771_extra,sizeof(mt352_av771_extra)); + mt352_write(fe, mt352_capt_range_cfg, sizeof(mt352_capt_range_cfg)); + + return 0; +} + +static int advbt771_samsung_tdtc9251dh0_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params, u8* pllbuf) +{ + u32 div; + unsigned char bs = 0; + unsigned char cp = 0; + + #define IF_FREQUENCYx6 217 /* 6 * 36.16666666667MHz */ + div = (((params->frequency + 83333) * 3) / 500000) + IF_FREQUENCYx6; + + if (params->frequency < 150000000) cp = 0xB4; + else if (params->frequency < 173000000) cp = 0xBC; + else if (params->frequency < 250000000) cp = 0xB4; + else if (params->frequency < 400000000) cp = 0xBC; + else if (params->frequency < 420000000) cp = 0xF4; + else if (params->frequency < 470000000) cp = 0xFC; + else if (params->frequency < 600000000) cp = 0xBC; + else if (params->frequency < 730000000) cp = 0xF4; + else cp = 0xFC; + + if (params->frequency < 150000000) bs = 0x01; + else if (params->frequency < 173000000) bs = 0x01; + else if (params->frequency < 250000000) bs = 0x02; + else if (params->frequency < 400000000) bs = 0x02; + else if (params->frequency < 420000000) bs = 0x02; + else if (params->frequency < 470000000) bs = 0x02; + else if (params->frequency < 600000000) bs = 0x08; + else if (params->frequency < 730000000) bs = 0x08; + else bs = 0x08; + + pllbuf[0] = 0xc2; // Note: non-linux standard PLL i2c address + pllbuf[1] = div >> 8; + pllbuf[2] = div & 0xff; + pllbuf[3] = cp; + pllbuf[4] = bs; + + return 0; +} + +static struct mt352_config advbt771_samsung_tdtc9251dh0_config = { + + .demod_address = 0x0f, + .demod_init = advbt771_samsung_tdtc9251dh0_demod_init, + .pll_set = advbt771_samsung_tdtc9251dh0_pll_set, +}; + + +static struct dst_config dst_config = { + + .demod_address = 0x55, +}; + + +static int or51211_request_firmware(struct dvb_frontend* fe, const struct firmware **fw, char* name) +{ + struct dvb_bt8xx_card* bt = (struct dvb_bt8xx_card*) fe->dvb->priv; + + return request_firmware(fw, name, &bt->bt->dev->dev); +} + +static void or51211_setmode(struct dvb_frontend * fe, int mode) +{ + struct dvb_bt8xx_card *bt = fe->dvb->priv; + bttv_write_gpio(bt->bttv_nr, 0x0002, mode); /* Reset */ + msleep(20); +} + +static void or51211_reset(struct dvb_frontend * fe) +{ + struct dvb_bt8xx_card *bt = fe->dvb->priv; + + /* RESET DEVICE + * reset is controled by GPIO-0 + * when set to 0 causes reset and when to 1 for normal op + * must remain reset for 128 clock cycles on a 50Mhz clock + * also PRM1 PRM2 & PRM4 are controled by GPIO-1,GPIO-2 & GPIO-4 + * We assume that the reset has be held low long enough or we + * have been reset by a power on. When the driver is unloaded + * reset set to 0 so if reloaded we have been reset. + */ + /* reset & PRM1,2&4 are outputs */ + int ret = bttv_gpio_enable(bt->bttv_nr, 0x001F, 0x001F); + if (ret != 0) { + printk(KERN_WARNING "or51211: Init Error - Can't Reset DVR " + "(%i)\n", ret); + } + bttv_write_gpio(bt->bttv_nr, 0x001F, 0x0000); /* Reset */ + msleep(20); + /* Now set for normal operation */ + bttv_write_gpio(bt->bttv_nr, 0x0001F, 0x0001); + /* wait for operation to begin */ + msleep(500); +} + +static void or51211_sleep(struct dvb_frontend * fe) +{ + struct dvb_bt8xx_card *bt = fe->dvb->priv; + bttv_write_gpio(bt->bttv_nr, 0x0001, 0x0000); +} + +static struct or51211_config or51211_config = { + + .demod_address = 0x15, + .request_firmware = or51211_request_firmware, + .setmode = or51211_setmode, + .reset = or51211_reset, + .sleep = or51211_sleep, +}; + + +static int vp3021_alps_tded4_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + struct dvb_bt8xx_card *card = (struct dvb_bt8xx_card *) fe->dvb->priv; + u8 buf[4]; + u32 div; + struct i2c_msg msg = { .addr = 0x60, .flags = 0, .buf = buf, .len = sizeof(buf) }; + + div = (params->frequency + 36166667) / 166667; + + buf[0] = (div >> 8) & 0x7F; + buf[1] = div & 0xFF; + buf[2] = 0x85; + if ((params->frequency >= 47000000) && (params->frequency < 153000000)) + buf[3] = 0x01; + else if ((params->frequency >= 153000000) && (params->frequency < 430000000)) + buf[3] = 0x02; + else if ((params->frequency >= 430000000) && (params->frequency < 824000000)) + buf[3] = 0x0C; + else if ((params->frequency >= 824000000) && (params->frequency < 863000000)) + buf[3] = 0x8C; + else + return -EINVAL; + + i2c_transfer(card->i2c_adapter, &msg, 1); + return 0; +} + +static struct nxt6000_config vp3021_alps_tded4_config = { + + .demod_address = 0x0a, + .clock_inversion = 1, + .pll_set = vp3021_alps_tded4_pll_set, +}; + + +static void frontend_init(struct dvb_bt8xx_card *card, u32 type) +{ + switch(type) { +#ifdef BTTV_DVICO_DVBT_LITE + case BTTV_DVICO_DVBT_LITE: + card->fe = mt352_attach(&thomson_dtt7579_config, card->i2c_adapter); + if (card->fe != NULL) { + card->fe->ops->info.frequency_min = 174000000; + card->fe->ops->info.frequency_max = 862000000; + break; + } + break; +#endif + +#ifdef BTTV_TWINHAN_VP3021 + case BTTV_TWINHAN_VP3021: +#else + case BTTV_NEBULA_DIGITV: +#endif + card->fe = nxt6000_attach(&vp3021_alps_tded4_config, card->i2c_adapter); + if (card->fe != NULL) { + break; + } + break; + + case BTTV_AVDVBT_761: + card->fe = sp887x_attach(µtune_mt7202dtf_config, card->i2c_adapter); + if (card->fe != NULL) { + break; + } + break; + + case BTTV_AVDVBT_771: + card->fe = mt352_attach(&advbt771_samsung_tdtc9251dh0_config, card->i2c_adapter); + if (card->fe != NULL) { + card->fe->ops->info.frequency_min = 174000000; + card->fe->ops->info.frequency_max = 862000000; + break; + } + break; + + case BTTV_TWINHAN_DST: + card->fe = dst_attach(&dst_config, card->i2c_adapter, card->bt); + if (card->fe != NULL) { + break; + } + break; + + case BTTV_PINNACLESAT: + card->fe = cx24110_attach(&pctvsat_config, card->i2c_adapter); + if (card->fe != NULL) { + break; + } + break; + + case BTTV_PC_HDTV: + card->fe = or51211_attach(&or51211_config, card->i2c_adapter); + if (card->fe != NULL) { + break; + } + break; + } + + if (card->fe == NULL) { + printk("dvb-bt8xx: A frontend driver was not found for device %04x/%04x subsystem %04x/%04x\n", + card->bt->dev->vendor, + card->bt->dev->device, + card->bt->dev->subsystem_vendor, + card->bt->dev->subsystem_device); + } else { + if (dvb_register_frontend(card->dvb_adapter, card->fe)) { + printk("dvb-bt8xx: Frontend registration failed!\n"); + if (card->fe->ops->release) + card->fe->ops->release(card->fe); + card->fe = NULL; + } + } +} + +static int __init dvb_bt8xx_load_card(struct dvb_bt8xx_card *card, u32 type) +{ + int result; + + if ((result = dvb_register_adapter(&card->dvb_adapter, card->card_name, + THIS_MODULE)) < 0) { + printk("dvb_bt8xx: dvb_register_adapter failed (errno = %d)\n", result); + return result; + + } + card->dvb_adapter->priv = card; + + card->bt->adapter = card->i2c_adapter; + + memset(&card->demux, 0, sizeof(struct dvb_demux)); + + card->demux.dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING | DMX_MEMORY_BASED_FILTERING; + + card->demux.priv = card; + card->demux.filternum = 256; + card->demux.feednum = 256; + card->demux.start_feed = dvb_bt8xx_start_feed; + card->demux.stop_feed = dvb_bt8xx_stop_feed; + card->demux.write_to_decoder = NULL; + + if ((result = dvb_dmx_init(&card->demux)) < 0) { + printk("dvb_bt8xx: dvb_dmx_init failed (errno = %d)\n", result); + + dvb_unregister_adapter(card->dvb_adapter); + return result; + } + + card->dmxdev.filternum = 256; + card->dmxdev.demux = &card->demux.dmx; + card->dmxdev.capabilities = 0; + + if ((result = dvb_dmxdev_init(&card->dmxdev, card->dvb_adapter)) < 0) { + printk("dvb_bt8xx: dvb_dmxdev_init failed (errno = %d)\n", result); + + dvb_dmx_release(&card->demux); + dvb_unregister_adapter(card->dvb_adapter); + return result; + } + + card->fe_hw.source = DMX_FRONTEND_0; + + if ((result = card->demux.dmx.add_frontend(&card->demux.dmx, &card->fe_hw)) < 0) { + printk("dvb_bt8xx: dvb_dmx_init failed (errno = %d)\n", result); + + dvb_dmxdev_release(&card->dmxdev); + dvb_dmx_release(&card->demux); + dvb_unregister_adapter(card->dvb_adapter); + return result; + } + + card->fe_mem.source = DMX_MEMORY_FE; + + if ((result = card->demux.dmx.add_frontend(&card->demux.dmx, &card->fe_mem)) < 0) { + printk("dvb_bt8xx: dvb_dmx_init failed (errno = %d)\n", result); + + card->demux.dmx.remove_frontend(&card->demux.dmx, &card->fe_hw); + dvb_dmxdev_release(&card->dmxdev); + dvb_dmx_release(&card->demux); + dvb_unregister_adapter(card->dvb_adapter); + return result; + } + + if ((result = card->demux.dmx.connect_frontend(&card->demux.dmx, &card->fe_hw)) < 0) { + printk("dvb_bt8xx: dvb_dmx_init failed (errno = %d)\n", result); + + card->demux.dmx.remove_frontend(&card->demux.dmx, &card->fe_mem); + card->demux.dmx.remove_frontend(&card->demux.dmx, &card->fe_hw); + dvb_dmxdev_release(&card->dmxdev); + dvb_dmx_release(&card->demux); + dvb_unregister_adapter(card->dvb_adapter); + return result; + } + + dvb_net_init(card->dvb_adapter, &card->dvbnet, &card->demux.dmx); + + tasklet_init(&card->bt->tasklet, dvb_bt8xx_task, (unsigned long) card); + + frontend_init(card, type); + + return 0; +} + +static int dvb_bt8xx_probe(struct device *dev) +{ + struct bttv_sub_device *sub = to_bttv_sub_dev(dev); + struct dvb_bt8xx_card *card; + struct pci_dev* bttv_pci_dev; + int ret; + + if (!(card = kmalloc(sizeof(struct dvb_bt8xx_card), GFP_KERNEL))) + return -ENOMEM; + + memset(card, 0, sizeof(*card)); + init_MUTEX(&card->lock); + card->bttv_nr = sub->core->nr; + strncpy(card->card_name, sub->core->name, sizeof(sub->core->name)); + card->i2c_adapter = &sub->core->i2c_adap; + + switch(sub->core->type) + { + case BTTV_PINNACLESAT: + card->gpio_mode = 0x0400c060; + /* should be: BT878_A_GAIN=0,BT878_A_PWRDN,BT878_DA_DPM,BT878_DA_SBR, + BT878_DA_IOM=1,BT878_DA_APP to enable serial highspeed mode. */ + card->op_sync_orin = 0; + card->irq_err_ignore = 0; + break; + +#ifdef BTTV_DVICO_DVBT_LITE + case BTTV_DVICO_DVBT_LITE: +#endif + card->gpio_mode = 0x0400C060; + card->op_sync_orin = 0; + card->irq_err_ignore = 0; + /* 26, 15, 14, 6, 5 + * A_PWRDN DA_DPM DA_SBR DA_IOM_DA + * DA_APP(parallel) */ + break; + +#ifdef BTTV_TWINHAN_VP3021 + case BTTV_TWINHAN_VP3021: +#else + case BTTV_NEBULA_DIGITV: +#endif + case BTTV_AVDVBT_761: + card->gpio_mode = (1 << 26) | (1 << 14) | (1 << 5); + card->op_sync_orin = 0; + card->irq_err_ignore = 0; + /* A_PWRDN DA_SBR DA_APP (high speed serial) */ + break; + + case BTTV_AVDVBT_771: //case 0x07711461: + card->gpio_mode = 0x0400402B; + card->op_sync_orin = BT878_RISC_SYNC_MASK; + card->irq_err_ignore = 0; + /* A_PWRDN DA_SBR DA_APP[0] PKTP=10 RISC_ENABLE FIFO_ENABLE*/ + break; + + case BTTV_TWINHAN_DST: + card->gpio_mode = 0x2204f2c; + card->op_sync_orin = BT878_RISC_SYNC_MASK; + card->irq_err_ignore = BT878_APABORT | BT878_ARIPERR | + BT878_APPERR | BT878_AFBUS; + /* 25,21,14,11,10,9,8,3,2 then + * 0x33 = 5,4,1,0 + * A_SEL=SML, DA_MLB, DA_SBR, + * DA_SDR=f, fifo trigger = 32 DWORDS + * IOM = 0 == audio A/D + * DPM = 0 == digital audio mode + * == async data parallel port + * then 0x33 (13 is set by start_capture) + * DA_APP = async data parallel port, + * ACAP_EN = 1, + * RISC+FIFO ENABLE */ + break; + + case BTTV_PC_HDTV: + card->gpio_mode = 0x0100EC7B; + card->op_sync_orin = 0; + card->irq_err_ignore = 0; + break; + + default: + printk(KERN_WARNING "dvb_bt8xx: Unknown bttv card type: %d.\n", + sub->core->type); + kfree(card); + return -ENODEV; + } + + dprintk("dvb_bt8xx: identified card%d as %s\n", card->bttv_nr, card->card_name); + + if (!(bttv_pci_dev = bttv_get_pcidev(card->bttv_nr))) { + printk("dvb_bt8xx: no pci device for card %d\n", card->bttv_nr); + kfree(card); + return -EFAULT; + } + + if (!(card->bt = dvb_bt8xx_878_match(card->bttv_nr, bttv_pci_dev))) { + printk("dvb_bt8xx: unable to determine DMA core of card %d,\n", + card->bttv_nr); + printk("dvb_bt8xx: if you have the ALSA bt87x audio driver " + "installed, try removing it.\n"); + + kfree(card); + return -EFAULT; + + } + + init_MUTEX(&card->bt->gpio_lock); + card->bt->bttv_nr = sub->core->nr; + + if ( (ret = dvb_bt8xx_load_card(card, sub->core->type)) ) { + kfree(card); + return ret; + } + + dev_set_drvdata(dev, card); + return 0; +} + +static int dvb_bt8xx_remove(struct device *dev) +{ + struct dvb_bt8xx_card *card = dev_get_drvdata(dev); + + dprintk("dvb_bt8xx: unloading card%d\n", card->bttv_nr); + + bt878_stop(card->bt); + tasklet_kill(&card->bt->tasklet); + dvb_net_release(&card->dvbnet); + card->demux.dmx.remove_frontend(&card->demux.dmx, &card->fe_mem); + card->demux.dmx.remove_frontend(&card->demux.dmx, &card->fe_hw); + dvb_dmxdev_release(&card->dmxdev); + dvb_dmx_release(&card->demux); + if (card->fe) dvb_unregister_frontend(card->fe); + dvb_unregister_adapter(card->dvb_adapter); + + kfree(card); + + return 0; +} + +static struct bttv_sub_driver driver = { + .drv = { + .name = "dvb-bt8xx", + .probe = dvb_bt8xx_probe, + .remove = dvb_bt8xx_remove, + /* FIXME: + * .shutdown = dvb_bt8xx_shutdown, + * .suspend = dvb_bt8xx_suspend, + * .resume = dvb_bt8xx_resume, + */ + }, +}; + +static int __init dvb_bt8xx_init(void) +{ + return bttv_sub_register(&driver, "dvb"); +} + +static void __exit dvb_bt8xx_exit(void) +{ + bttv_sub_unregister(&driver); +} + +module_init(dvb_bt8xx_init); +module_exit(dvb_bt8xx_exit); + +MODULE_DESCRIPTION("Bt8xx based DVB adapter driver"); +MODULE_AUTHOR("Florian Schirmer <jolt@tuxbox.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/bt8xx/dvb-bt8xx.h b/drivers/media/dvb/bt8xx/dvb-bt8xx.h new file mode 100644 index 000000000000..80ef189f930f --- /dev/null +++ b/drivers/media/dvb/bt8xx/dvb-bt8xx.h @@ -0,0 +1,59 @@ +/* + * Bt8xx based DVB adapter driver + * + * Copyright (C) 2002,2003 Florian Schirmer <jolt@tuxbox.org> + * Copyright (C) 2002 Peter Hettkamp <peter.hettkamp@t-online.de> + * Copyright (C) 1999-2001 Ralph Metzler & Marcus Metzler for convergence integrated media GmbH + * Copyright (C) 1998,1999 Christian Theiss <mistert@rz.fh-augsburg.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef DVB_BT8XX_H +#define DVB_BT8XX_H + +#include <linux/i2c.h> +#include "dvbdev.h" +#include "dvb_net.h" +#include "bttv.h" +#include "mt352.h" +#include "sp887x.h" +#include "dst.h" +#include "nxt6000.h" +#include "cx24110.h" +#include "or51211.h" + +struct dvb_bt8xx_card { + struct semaphore lock; + int nfeeds; + char card_name[32]; + struct dvb_adapter *dvb_adapter; + struct bt878 *bt; + unsigned int bttv_nr; + struct dvb_demux demux; + struct dmxdev dmxdev; + struct dmx_frontend fe_hw; + struct dmx_frontend fe_mem; + u32 gpio_mode; + u32 op_sync_orin; + u32 irq_err_ignore; + struct i2c_adapter *i2c_adapter; + struct dvb_net dvbnet; + + struct dvb_frontend* fe; +}; + +#endif /* DVB_BT8XX_H */ diff --git a/drivers/media/dvb/cinergyT2/Kconfig b/drivers/media/dvb/cinergyT2/Kconfig new file mode 100644 index 000000000000..226714085f58 --- /dev/null +++ b/drivers/media/dvb/cinergyT2/Kconfig @@ -0,0 +1,85 @@ +config DVB_CINERGYT2 + tristate "Terratec CinergyT2/qanu USB2 DVB-T receiver" + depends on DVB_CORE && USB + help + Support for "TerraTec CinergyT2" USB2.0 Highspeed DVB Receivers + + Say Y if you own such a device and want to use it. + + +config DVB_CINERGYT2_TUNING + bool "sophisticated fine-tuning for CinergyT2 cards" + depends on DVB_CINERGYT2 + help + Here you can fine-tune some parameters of the CinergyT2 driver. + + Normally you don't need to touch this, but in exotic setups you + may fine-tune your setup and adjust e.g. DMA buffer sizes for + a particular application. + + +config DVB_CINERGYT2_STREAM_URB_COUNT + int "Number of queued USB Request Blocks for Highspeed Stream Transfers" + depends on DVB_CINERGYT2_TUNING + default "32" + help + USB Request Blocks for Highspeed Stream transfers are scheduled in + a queue for the Host Controller. + + Usually the default value is a safe choice. + + You may increase this number if you are using this device in a + Server Environment with many high-traffic USB Highspeed devices + sharing the same USB bus. + + +config DVB_CINERGYT2_STREAM_BUF_SIZE + int "Size of URB Stream Buffers for Highspeed Transfers" + depends on DVB_CINERGYT2_TUNING + default "512" + help + Should be a multiple of native buffer size of 512 bytes. + Default value is a safe choice. + + You may increase this number if you are using this device in a + Server Environment with many high-traffic USB Highspeed devices + sharing the same USB bus. + + +config DVB_CINERGYT2_QUERY_INTERVAL + int "Status update interval [milliseconds]" + depends on DVB_CINERGYT2_TUNING + default "250" + help + This is the interval for status readouts from the demodulator. + You may try lower values if you need more responsive signal quality + measurements. + + Please keep in mind that these updates cause traffic on the tuner + control bus and thus may or may not affect receiption sensitivity. + + The default value should be a safe choice for common applications. + + +config DVB_CINERGYT2_ENABLE_RC_INPUT_DEVICE + bool "Register the onboard IR Remote Control Receiver as Input Device" + depends on DVB_CINERGYT2_TUNING + default "yes" + help + Enable this option if you want to use the onboard Infrared Remote + Control Receiver as Linux-Input device. + + Right now only the keycode table for the default Remote Control + delivered with the device is supported, please see the driver + source code to find out how to add support for other controls. + + +config DVB_CINERGYT2_RC_QUERY_INTERVAL + int "Infrared Remote Controller update interval [milliseconds]" + depends on DVB_CINERGYT2_TUNING && DVB_CINERGYT2_ENABLE_RC_INPUT_DEVICE + default "100" + help + If you have a very fast-repeating remote control you can try lower + values, for normal consumer receivers the default value should be + a safe choice. + diff --git a/drivers/media/dvb/cinergyT2/Makefile b/drivers/media/dvb/cinergyT2/Makefile new file mode 100644 index 000000000000..c51aece20f9f --- /dev/null +++ b/drivers/media/dvb/cinergyT2/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_DVB_CINERGYT2) += cinergyT2.o + +EXTRA_CFLAGS = -Idrivers/media/dvb/dvb-core/ diff --git a/drivers/media/dvb/cinergyT2/cinergyT2.c b/drivers/media/dvb/cinergyT2/cinergyT2.c new file mode 100644 index 000000000000..f1f539761371 --- /dev/null +++ b/drivers/media/dvb/cinergyT2/cinergyT2.c @@ -0,0 +1,965 @@ +/* + * TerraTec Cinergy T²/qanu USB2 DVB-T adapter. + * + * Copyright (C) 2004 Daniel Mack <daniel@qanu.de> and + * Holger Waechtler <holger@qanu.de> + * + * Protocol Spec published on http://qanu.de/specs/terratec_cinergyT2.pdf + * + * 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/config.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/version.h> +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/pci.h> +#include <linux/input.h> +#include <linux/dvb/frontend.h> + +#include "dmxdev.h" +#include "dvb_demux.h" +#include "dvb_net.h" + + +#ifdef CONFIG_DVB_CINERGYT2_TUNING + #define STREAM_URB_COUNT (CONFIG_DVB_CINERGYT2_STREAM_URB_COUNT) + #define STREAM_BUF_SIZE (CONFIG_DVB_CINERGYT2_STREAM_BUF_SIZE) + #define QUERY_INTERVAL (CONFIG_DVB_CINERGYT2_QUERY_INTERVAL) + #ifdef CONFIG_DVB_CINERGYT2_ENABLE_RC_INPUT_DEVICE + #define RC_QUERY_INTERVAL (CONFIG_DVB_CINERGYT2_RC_QUERY_INTERVAL) + #define ENABLE_RC (1) + #endif +#else + #define STREAM_URB_COUNT (32) + #define STREAM_BUF_SIZE (512) /* bytes */ + #define ENABLE_RC (1) + #define RC_QUERY_INTERVAL (100) /* milliseconds */ + #define QUERY_INTERVAL (333) /* milliseconds */ +#endif + +#define DRIVER_NAME "TerraTec/qanu USB2.0 Highspeed DVB-T Receiver" + +static int debug; +module_param_named(debug, debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off)."); + +#define dprintk(level, args...) \ +do { \ + if ((debug & level)) { \ + printk("%s: %s(): ", __stringify(KBUILD_MODNAME), \ + __FUNCTION__); \ + printk(args); } \ +} while (0) + +enum cinergyt2_ep1_cmd { + CINERGYT2_EP1_PID_TABLE_RESET = 0x01, + CINERGYT2_EP1_PID_SETUP = 0x02, + CINERGYT2_EP1_CONTROL_STREAM_TRANSFER = 0x03, + CINERGYT2_EP1_SET_TUNER_PARAMETERS = 0x04, + CINERGYT2_EP1_GET_TUNER_STATUS = 0x05, + CINERGYT2_EP1_START_SCAN = 0x06, + CINERGYT2_EP1_CONTINUE_SCAN = 0x07, + CINERGYT2_EP1_GET_RC_EVENTS = 0x08, + CINERGYT2_EP1_SLEEP_MODE = 0x09 +}; + +struct dvbt_set_parameters_msg { + uint8_t cmd; + uint32_t freq; + uint8_t bandwidth; + uint16_t tps; + uint8_t flags; +} __attribute__((packed)); + +struct dvbt_get_status_msg { + uint32_t freq; + uint8_t bandwidth; + uint16_t tps; + uint8_t flags; + uint16_t gain; + uint8_t snr; + uint32_t viterbi_error_rate; + uint32_t rs_error_rate; + uint32_t uncorrected_block_count; + uint8_t lock_bits; + uint8_t prev_lock_bits; +} __attribute__((packed)); + +static struct dvb_frontend_info cinergyt2_fe_info = { + .name = DRIVER_NAME, + .type = FE_OFDM, + .frequency_min = 174000000, + .frequency_max = 862000000, + .frequency_stepsize = 166667, + .caps = FE_CAN_INVERSION_AUTO | 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_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_HIERARCHY_AUTO | FE_CAN_RECOVER | FE_CAN_MUTE_TS +}; + +struct cinergyt2 { + struct dvb_demux demux; + struct usb_device *udev; + struct semaphore sem; + struct dvb_adapter *adapter; + struct dvb_device *fedev; + struct dmxdev dmxdev; + struct dvb_net dvbnet; + + int streaming; + int sleeping; + + struct dvbt_set_parameters_msg param; + struct dvbt_get_status_msg status; + struct work_struct query_work; + + wait_queue_head_t poll_wq; + int pending_fe_events; + + void *streambuf; + dma_addr_t streambuf_dmahandle; + struct urb *stream_urb [STREAM_URB_COUNT]; + +#ifdef ENABLE_RC + struct input_dev rc_input_dev; + struct work_struct rc_query_work; + int rc_input_event; +#endif +}; + +enum { + CINERGYT2_RC_EVENT_TYPE_NONE = 0x00, + CINERGYT2_RC_EVENT_TYPE_NEC = 0x01, + CINERGYT2_RC_EVENT_TYPE_RC5 = 0x02 +}; + +struct cinergyt2_rc_event { + char type; + uint32_t value; +} __attribute__((packed)); + +static const uint32_t rc_keys [] = { + CINERGYT2_RC_EVENT_TYPE_NEC, 0xfe01eb04, KEY_POWER, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xfd02eb04, KEY_1, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xfc03eb04, KEY_2, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xfb04eb04, KEY_3, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xfa05eb04, KEY_4, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xf906eb04, KEY_5, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xf807eb04, KEY_6, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xf708eb04, KEY_7, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xf609eb04, KEY_8, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xf50aeb04, KEY_9, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xf30ceb04, KEY_0, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xf40beb04, KEY_VIDEO, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xf20deb04, KEY_REFRESH, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xf10eeb04, KEY_SELECT, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xf00feb04, KEY_EPG, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xef10eb04, KEY_UP, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xeb14eb04, KEY_DOWN, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xee11eb04, KEY_LEFT, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xec13eb04, KEY_RIGHT, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xed12eb04, KEY_OK, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xea15eb04, KEY_TEXT, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xe916eb04, KEY_INFO, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xe817eb04, KEY_RED, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xe718eb04, KEY_GREEN, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xe619eb04, KEY_YELLOW, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xe51aeb04, KEY_BLUE, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xe31ceb04, KEY_VOLUMEUP, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xe11eeb04, KEY_VOLUMEDOWN, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xe21deb04, KEY_MUTE, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xe41beb04, KEY_CHANNELUP, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xe01feb04, KEY_CHANNELDOWN, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xbf40eb04, KEY_PAUSE, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xb34ceb04, KEY_PLAY, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xa758eb04, KEY_RECORD, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xab54eb04, KEY_PREVIOUS, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xb748eb04, KEY_STOP, + CINERGYT2_RC_EVENT_TYPE_NEC, 0xa35ceb04, KEY_NEXT +}; + +static int cinergyt2_command (struct cinergyt2 *cinergyt2, + char *send_buf, int send_buf_len, + char *recv_buf, int recv_buf_len) +{ + int actual_len; + char dummy; + int ret; + + ret = usb_bulk_msg(cinergyt2->udev, usb_sndbulkpipe(cinergyt2->udev, 1), + send_buf, send_buf_len, &actual_len, 1000); + + if (ret) + dprintk(1, "usb_bulk_msg (send) failed, err %i\n", ret); + + if (!recv_buf) + recv_buf = &dummy; + + ret = usb_bulk_msg(cinergyt2->udev, usb_rcvbulkpipe(cinergyt2->udev, 1), + recv_buf, recv_buf_len, &actual_len, 1000); + + if (ret) + dprintk(1, "usb_bulk_msg (read) failed, err %i\n", ret); + + return ret ? ret : actual_len; +} + +static void cinergyt2_control_stream_transfer (struct cinergyt2 *cinergyt2, int enable) +{ + char buf [] = { CINERGYT2_EP1_CONTROL_STREAM_TRANSFER, enable ? 1 : 0 }; + cinergyt2_command(cinergyt2, buf, sizeof(buf), NULL, 0); +} + +static void cinergyt2_sleep (struct cinergyt2 *cinergyt2, int sleep) +{ + char buf [] = { CINERGYT2_EP1_SLEEP_MODE, sleep ? 1 : 0 }; + cinergyt2_command(cinergyt2, buf, sizeof(buf), NULL, 0); + cinergyt2->sleeping = sleep; +} + +static void cinergyt2_stream_irq (struct urb *urb, struct pt_regs *regs); + +static int cinergyt2_submit_stream_urb (struct cinergyt2 *cinergyt2, struct urb *urb) +{ + int err; + + usb_fill_bulk_urb(urb, + cinergyt2->udev, + usb_rcvbulkpipe(cinergyt2->udev, 0x2), + urb->transfer_buffer, + STREAM_BUF_SIZE, + cinergyt2_stream_irq, + cinergyt2); + + if ((err = usb_submit_urb(urb, GFP_ATOMIC))) + dprintk(1, "urb submission failed (err = %i)!\n", err); + + return err; +} + +static void cinergyt2_stream_irq (struct urb *urb, struct pt_regs *regs) +{ + struct cinergyt2 *cinergyt2 = urb->context; + + if (urb->actual_length > 0) + dvb_dmx_swfilter(&cinergyt2->demux, + urb->transfer_buffer, urb->actual_length); + + if (cinergyt2->streaming) + cinergyt2_submit_stream_urb(cinergyt2, urb); +} + +static void cinergyt2_free_stream_urbs (struct cinergyt2 *cinergyt2) +{ + int i; + + for (i=0; i<STREAM_URB_COUNT; i++) + if (cinergyt2->stream_urb[i]) + usb_free_urb(cinergyt2->stream_urb[i]); + + pci_free_consistent(NULL, STREAM_URB_COUNT*STREAM_BUF_SIZE, + cinergyt2->streambuf, cinergyt2->streambuf_dmahandle); +} + +static int cinergyt2_alloc_stream_urbs (struct cinergyt2 *cinergyt2) +{ + int i; + + cinergyt2->streambuf = pci_alloc_consistent(NULL, + STREAM_URB_COUNT*STREAM_BUF_SIZE, + &cinergyt2->streambuf_dmahandle); + if (!cinergyt2->streambuf) { + dprintk(1, "failed to alloc consistent stream memory area, bailing out!\n"); + return -ENOMEM; + } + + memset(cinergyt2->streambuf, 0, STREAM_URB_COUNT*STREAM_BUF_SIZE); + + for (i=0; i<STREAM_URB_COUNT; i++) { + struct urb *urb; + + if (!(urb = usb_alloc_urb(0, GFP_ATOMIC))) { + dprintk(1, "failed to alloc consistent stream urbs, bailing out!\n"); + cinergyt2_free_stream_urbs(cinergyt2); + return -ENOMEM; + } + + urb->transfer_buffer = cinergyt2->streambuf + i * STREAM_BUF_SIZE; + urb->transfer_buffer_length = STREAM_BUF_SIZE; + + cinergyt2->stream_urb[i] = urb; + } + + return 0; +} + +static void cinergyt2_stop_stream_xfer (struct cinergyt2 *cinergyt2) +{ + int i; + + cinergyt2_control_stream_transfer(cinergyt2, 0); + + for (i=0; i<STREAM_URB_COUNT; i++) + if (cinergyt2->stream_urb[i]) + usb_kill_urb(cinergyt2->stream_urb[i]); +} + +static int cinergyt2_start_stream_xfer (struct cinergyt2 *cinergyt2) +{ + int i, err; + + for (i=0; i<STREAM_URB_COUNT; i++) { + if ((err = cinergyt2_submit_stream_urb(cinergyt2, cinergyt2->stream_urb[i]))) { + cinergyt2_stop_stream_xfer(cinergyt2); + dprintk(1, "failed urb submission (%i: err = %i)!\n", i, err); + return err; + } + } + + cinergyt2_control_stream_transfer(cinergyt2, 1); + return 0; +} + +static int cinergyt2_start_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *demux = dvbdmxfeed->demux; + struct cinergyt2 *cinergyt2 = demux->priv; + + if (down_interruptible(&cinergyt2->sem)) + return -ERESTARTSYS; + + if (cinergyt2->streaming == 0) + cinergyt2_start_stream_xfer(cinergyt2); + + cinergyt2->streaming++; + up(&cinergyt2->sem); + return 0; +} + +static int cinergyt2_stop_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *demux = dvbdmxfeed->demux; + struct cinergyt2 *cinergyt2 = demux->priv; + + if (down_interruptible(&cinergyt2->sem)) + return -ERESTARTSYS; + + if (--cinergyt2->streaming == 0) + cinergyt2_stop_stream_xfer(cinergyt2); + + up(&cinergyt2->sem); + return 0; +} + +/** + * convert linux-dvb frontend parameter set into TPS. + * See ETSI ETS-300744, section 4.6.2, table 9 for details. + * + * This function is probably reusable and may better get placed in a support + * library. + * + * We replace errornous fields by default TPS fields (the ones with value 0). + */ +static uint16_t compute_tps (struct dvb_frontend_parameters *p) +{ + struct dvb_ofdm_parameters *op = &p->u.ofdm; + uint16_t tps = 0; + + switch (op->code_rate_HP) { + case FEC_2_3: + tps |= (1 << 7); + break; + case FEC_3_4: + tps |= (2 << 7); + break; + case FEC_5_6: + tps |= (3 << 7); + break; + case FEC_7_8: + tps |= (4 << 7); + break; + case FEC_1_2: + case FEC_AUTO: + default: + /* tps |= (0 << 7) */; + } + + switch (op->code_rate_LP) { + case FEC_2_3: + tps |= (1 << 4); + break; + case FEC_3_4: + tps |= (2 << 4); + break; + case FEC_5_6: + tps |= (3 << 4); + break; + case FEC_7_8: + tps |= (4 << 4); + break; + case FEC_1_2: + case FEC_AUTO: + default: + /* tps |= (0 << 4) */; + } + + switch (op->constellation) { + case QAM_16: + tps |= (1 << 13); + break; + case QAM_64: + tps |= (2 << 13); + break; + case QPSK: + default: + /* tps |= (0 << 13) */; + } + + switch (op->transmission_mode) { + case TRANSMISSION_MODE_8K: + tps |= (1 << 0); + break; + case TRANSMISSION_MODE_2K: + default: + /* tps |= (0 << 0) */; + } + + switch (op->guard_interval) { + case GUARD_INTERVAL_1_16: + tps |= (1 << 2); + break; + case GUARD_INTERVAL_1_8: + tps |= (2 << 2); + break; + case GUARD_INTERVAL_1_4: + tps |= (3 << 2); + break; + case GUARD_INTERVAL_1_32: + default: + /* tps |= (0 << 2) */; + } + + switch (op->hierarchy_information) { + case HIERARCHY_1: + tps |= (1 << 10); + break; + case HIERARCHY_2: + tps |= (2 << 10); + break; + case HIERARCHY_4: + tps |= (3 << 10); + break; + case HIERARCHY_NONE: + default: + /* tps |= (0 << 10) */; + } + + return tps; +} + +static int cinergyt2_open (struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + struct cinergyt2 *cinergyt2 = dvbdev->priv; + int err; + + if ((err = dvb_generic_open(inode, file))) + return err; + + if (down_interruptible(&cinergyt2->sem)) + return -ERESTARTSYS; + + if ((file->f_flags & O_ACCMODE) != O_RDONLY) { + cinergyt2_sleep(cinergyt2, 0); + schedule_delayed_work(&cinergyt2->query_work, HZ/2); + } + + up(&cinergyt2->sem); + return 0; +} + +static int cinergyt2_release (struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + struct cinergyt2 *cinergyt2 = dvbdev->priv; + + if (down_interruptible(&cinergyt2->sem)) + return -ERESTARTSYS; + + if ((file->f_flags & O_ACCMODE) != O_RDONLY) { + cancel_delayed_work(&cinergyt2->query_work); + flush_scheduled_work(); + cinergyt2_sleep(cinergyt2, 1); + } + + up(&cinergyt2->sem); + + return dvb_generic_release(inode, file); +} + +static unsigned int cinergyt2_poll (struct file *file, struct poll_table_struct *wait) +{ + struct dvb_device *dvbdev = file->private_data; + struct cinergyt2 *cinergyt2 = dvbdev->priv; + poll_wait(file, &cinergyt2->poll_wq, wait); + return (POLLIN | POLLRDNORM | POLLPRI); +} + + +static int cinergyt2_ioctl (struct inode *inode, struct file *file, + unsigned cmd, unsigned long arg) +{ + struct dvb_device *dvbdev = file->private_data; + struct cinergyt2 *cinergyt2 = dvbdev->priv; + struct dvbt_get_status_msg *stat = &cinergyt2->status; + fe_status_t status = 0; + + switch (cmd) { + case FE_GET_INFO: + return copy_to_user((void __user*) arg, &cinergyt2_fe_info, + sizeof(struct dvb_frontend_info)); + + case FE_READ_STATUS: + if (0xffff - le16_to_cpu(stat->gain) > 30) + status |= FE_HAS_SIGNAL; + if (stat->lock_bits & (1 << 6)) + status |= FE_HAS_LOCK; + if (stat->lock_bits & (1 << 5)) + status |= FE_HAS_SYNC; + if (stat->lock_bits & (1 << 4)) + status |= FE_HAS_CARRIER; + if (stat->lock_bits & (1 << 1)) + status |= FE_HAS_VITERBI; + + return copy_to_user((void __user*) arg, &status, sizeof(status)); + + case FE_READ_BER: + return put_user(le32_to_cpu(stat->viterbi_error_rate), + (__u32 __user *) arg); + + case FE_READ_SIGNAL_STRENGTH: + return put_user(0xffff - le16_to_cpu(stat->gain), + (__u16 __user *) arg); + + case FE_READ_SNR: + return put_user((stat->snr << 8) | stat->snr, + (__u16 __user *) arg); + + case FE_READ_UNCORRECTED_BLOCKS: + /* UNC are already converted to host byte order... */ + return put_user(stat->uncorrected_block_count, + (__u32 __user *) arg); + + case FE_SET_FRONTEND: + { + struct dvbt_set_parameters_msg *param = &cinergyt2->param; + struct dvb_frontend_parameters p; + int err; + + if ((file->f_flags & O_ACCMODE) == O_RDONLY) + return -EPERM; + + if (copy_from_user(&p, (void __user*) arg, sizeof(p))) + return -EFAULT; + + if (down_interruptible(&cinergyt2->sem)) + return -ERESTARTSYS; + + param->cmd = CINERGYT2_EP1_SET_TUNER_PARAMETERS; + param->tps = cpu_to_le16(compute_tps(&p)); + param->freq = cpu_to_le32(p.frequency / 1000); + param->bandwidth = 8 - p.u.ofdm.bandwidth - BANDWIDTH_8_MHZ; + + stat->lock_bits = 0; + cinergyt2->pending_fe_events++; + wake_up_interruptible(&cinergyt2->poll_wq); + + err = cinergyt2_command(cinergyt2, + (char *) param, sizeof(*param), + NULL, 0); + + up(&cinergyt2->sem); + + return (err < 0) ? err : 0; + } + + case FE_GET_FRONTEND: + /** + * trivial to implement (see struct dvbt_get_status_msg). + * equivalent to FE_READ ioctls, but needs + * TPS -> linux-dvb parameter set conversion. Feel free + * to implement this and send us a patch if you need this + * functionality. + */ + break; + + case FE_GET_EVENT: + { + /** + * for now we only fill the status field. the parameters + * are trivial to fill as soon FE_GET_FRONTEND is done. + */ + struct dvb_frontend_event __user *e = (void __user *) arg; + if (cinergyt2->pending_fe_events == 0) { + if (file->f_flags & O_NONBLOCK) + return -EWOULDBLOCK; + wait_event_interruptible(cinergyt2->poll_wq, + cinergyt2->pending_fe_events > 0); + } + cinergyt2->pending_fe_events = 0; + return cinergyt2_ioctl(inode, file, FE_READ_STATUS, + (unsigned long) &e->status); + } + + default: + ; + } + + return -EINVAL; +} + +static int cinergyt2_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct dvb_device *dvbdev = file->private_data; + struct cinergyt2 *cinergyt2 = dvbdev->priv; + int ret = 0; + + lock_kernel(); + + if (vma->vm_flags & (VM_WRITE | VM_EXEC)) { + ret = -EPERM; + goto bailout; + } + + if (vma->vm_end > vma->vm_start + STREAM_URB_COUNT * STREAM_BUF_SIZE) { + ret = -EINVAL; + goto bailout; + } + + vma->vm_flags |= (VM_IO | VM_DONTCOPY); + vma->vm_file = file; + + ret = remap_pfn_range(vma, vma->vm_start, + virt_to_phys(cinergyt2->streambuf) >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, + vma->vm_page_prot) ? -EAGAIN : 0; +bailout: + unlock_kernel(); + return ret; +} + +static struct file_operations cinergyt2_fops = { + .owner = THIS_MODULE, + .ioctl = cinergyt2_ioctl, + .poll = cinergyt2_poll, + .open = cinergyt2_open, + .release = cinergyt2_release, + .mmap = cinergyt2_mmap +}; + +static struct dvb_device cinergyt2_fe_template = { + .users = ~0, + .writers = 1, + .readers = (~0)-1, + .fops = &cinergyt2_fops +}; + +#ifdef ENABLE_RC +static void cinergyt2_query_rc (void *data) +{ + struct cinergyt2 *cinergyt2 = (struct cinergyt2 *) data; + char buf [1] = { CINERGYT2_EP1_GET_RC_EVENTS }; + struct cinergyt2_rc_event rc_events[12]; + int n, len; + + if (down_interruptible(&cinergyt2->sem)) + return; + + len = cinergyt2_command(cinergyt2, buf, sizeof(buf), + (char *) rc_events, sizeof(rc_events)); + + for (n=0; len>0 && n<(len/sizeof(rc_events[0])); n++) { + int i; + + if (rc_events[n].type == CINERGYT2_RC_EVENT_TYPE_NEC && + rc_events[n].value == ~0) + { + /** + * keyrepeat bit. If we would handle this properly + * we would need to emit down events as long the + * keyrepeat goes, a up event if no further + * repeat bits occur. Would need a timer to implement + * and no other driver does this, so we simply + * emit the last key up/down sequence again. + */ + } else { + cinergyt2->rc_input_event = KEY_MAX; + for (i=0; i<sizeof(rc_keys)/sizeof(rc_keys[0]); i+=3) { + if (rc_keys[i+0] == rc_events[n].type && + rc_keys[i+1] == rc_events[n].value) + { + cinergyt2->rc_input_event = rc_keys[i+2]; + break; + } + } + } + + if (cinergyt2->rc_input_event != KEY_MAX) { + input_report_key(&cinergyt2->rc_input_dev, cinergyt2->rc_input_event, 1); + input_report_key(&cinergyt2->rc_input_dev, cinergyt2->rc_input_event, 0); + input_sync(&cinergyt2->rc_input_dev); + } + } + + schedule_delayed_work(&cinergyt2->rc_query_work, + msecs_to_jiffies(RC_QUERY_INTERVAL)); + + up(&cinergyt2->sem); +} +#endif + +static void cinergyt2_query (void *data) +{ + struct cinergyt2 *cinergyt2 = (struct cinergyt2 *) data; + char cmd [] = { CINERGYT2_EP1_GET_TUNER_STATUS }; + struct dvbt_get_status_msg *s = &cinergyt2->status; + uint8_t lock_bits; + uint32_t unc; + + if (down_interruptible(&cinergyt2->sem)) + return; + + unc = s->uncorrected_block_count; + lock_bits = s->lock_bits; + + cinergyt2_command(cinergyt2, cmd, sizeof(cmd), (char *) s, sizeof(*s)); + + unc += le32_to_cpu(s->uncorrected_block_count); + s->uncorrected_block_count = unc; + + if (lock_bits != s->lock_bits) { + wake_up_interruptible(&cinergyt2->poll_wq); + cinergyt2->pending_fe_events++; + } + + schedule_delayed_work(&cinergyt2->query_work, + msecs_to_jiffies(QUERY_INTERVAL)); + + up(&cinergyt2->sem); +} + +static int cinergyt2_probe (struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct cinergyt2 *cinergyt2; + int i, err; + + if (!(cinergyt2 = kmalloc (sizeof(struct cinergyt2), GFP_KERNEL))) { + dprintk(1, "out of memory?!?\n"); + return -ENOMEM; + } + + memset (cinergyt2, 0, sizeof (struct cinergyt2)); + usb_set_intfdata (intf, (void *) cinergyt2); + + init_MUTEX(&cinergyt2->sem); + init_waitqueue_head (&cinergyt2->poll_wq); + INIT_WORK(&cinergyt2->query_work, cinergyt2_query, cinergyt2); + + cinergyt2->udev = interface_to_usbdev(intf); + cinergyt2->param.cmd = CINERGYT2_EP1_SET_TUNER_PARAMETERS; + + if (cinergyt2_alloc_stream_urbs (cinergyt2) < 0) { + dprintk(1, "unable to allocate stream urbs\n"); + kfree(cinergyt2); + return -ENOMEM; + } + + dvb_register_adapter(&cinergyt2->adapter, DRIVER_NAME, THIS_MODULE); + + cinergyt2->demux.priv = cinergyt2; + cinergyt2->demux.filternum = 256; + cinergyt2->demux.feednum = 256; + cinergyt2->demux.start_feed = cinergyt2_start_feed; + cinergyt2->demux.stop_feed = cinergyt2_stop_feed; + cinergyt2->demux.dmx.capabilities = DMX_TS_FILTERING | + DMX_SECTION_FILTERING | + DMX_MEMORY_BASED_FILTERING; + + if ((err = dvb_dmx_init(&cinergyt2->demux)) < 0) { + dprintk(1, "dvb_dmx_init() failed (err = %d)\n", err); + goto bailout; + } + + cinergyt2->dmxdev.filternum = cinergyt2->demux.filternum; + cinergyt2->dmxdev.demux = &cinergyt2->demux.dmx; + cinergyt2->dmxdev.capabilities = 0; + + if ((err = dvb_dmxdev_init(&cinergyt2->dmxdev, cinergyt2->adapter)) < 0) { + dprintk(1, "dvb_dmxdev_init() failed (err = %d)\n", err); + goto bailout; + } + + if (dvb_net_init(cinergyt2->adapter, &cinergyt2->dvbnet, &cinergyt2->demux.dmx)) + dprintk(1, "dvb_net_init() failed!\n"); + + dvb_register_device(cinergyt2->adapter, &cinergyt2->fedev, + &cinergyt2_fe_template, cinergyt2, + DVB_DEVICE_FRONTEND); + +#ifdef ENABLE_RC + init_input_dev(&cinergyt2->rc_input_dev); + + cinergyt2->rc_input_dev.evbit[0] = BIT(EV_KEY); + cinergyt2->rc_input_dev.keycodesize = sizeof(unsigned char); + cinergyt2->rc_input_dev.keycodemax = KEY_MAX; + cinergyt2->rc_input_dev.name = DRIVER_NAME " remote control"; + + for (i=0; i<sizeof(rc_keys)/sizeof(rc_keys[0]); i+=3) + set_bit(rc_keys[i+2], cinergyt2->rc_input_dev.keybit); + + input_register_device(&cinergyt2->rc_input_dev); + + cinergyt2->rc_input_event = KEY_MAX; + + INIT_WORK(&cinergyt2->rc_query_work, cinergyt2_query_rc, cinergyt2); + schedule_delayed_work(&cinergyt2->rc_query_work, HZ/2); +#endif + return 0; + +bailout: + dvb_dmxdev_release(&cinergyt2->dmxdev); + dvb_dmx_release(&cinergyt2->demux); + dvb_unregister_adapter (cinergyt2->adapter); + cinergyt2_free_stream_urbs (cinergyt2); + kfree(cinergyt2); + return -ENOMEM; +} + +static void cinergyt2_disconnect (struct usb_interface *intf) +{ + struct cinergyt2 *cinergyt2 = usb_get_intfdata (intf); + + if (down_interruptible(&cinergyt2->sem)) + return; + +#ifdef ENABLE_RC + cancel_delayed_work(&cinergyt2->rc_query_work); + flush_scheduled_work(); + input_unregister_device(&cinergyt2->rc_input_dev); +#endif + + cinergyt2->demux.dmx.close(&cinergyt2->demux.dmx); + dvb_net_release(&cinergyt2->dvbnet); + dvb_dmxdev_release(&cinergyt2->dmxdev); + dvb_dmx_release(&cinergyt2->demux); + dvb_unregister_device(cinergyt2->fedev); + dvb_unregister_adapter(cinergyt2->adapter); + + cinergyt2_free_stream_urbs(cinergyt2); + up(&cinergyt2->sem); + kfree(cinergyt2); +} + +static int cinergyt2_suspend (struct usb_interface *intf, u32 state) +{ + struct cinergyt2 *cinergyt2 = usb_get_intfdata (intf); + + if (down_interruptible(&cinergyt2->sem)) + return -ERESTARTSYS; + + if (state > 0) { /* state 0 seems to mean DEVICE_PM_ON */ + struct cinergyt2 *cinergyt2 = usb_get_intfdata (intf); +#ifdef ENABLE_RC + cancel_delayed_work(&cinergyt2->rc_query_work); +#endif + cancel_delayed_work(&cinergyt2->query_work); + if (cinergyt2->streaming) + cinergyt2_stop_stream_xfer(cinergyt2); + flush_scheduled_work(); + cinergyt2_sleep(cinergyt2, 1); + } + + up(&cinergyt2->sem); + return 0; +} + +static int cinergyt2_resume (struct usb_interface *intf) +{ + struct cinergyt2 *cinergyt2 = usb_get_intfdata (intf); + struct dvbt_set_parameters_msg *param = &cinergyt2->param; + + if (down_interruptible(&cinergyt2->sem)) + return -ERESTARTSYS; + + if (!cinergyt2->sleeping) { + cinergyt2_sleep(cinergyt2, 0); + cinergyt2_command(cinergyt2, (char *) param, sizeof(*param), NULL, 0); + if (cinergyt2->streaming) + cinergyt2_start_stream_xfer(cinergyt2); + schedule_delayed_work(&cinergyt2->query_work, HZ/2); + } + +#ifdef ENABLE_RC + schedule_delayed_work(&cinergyt2->rc_query_work, HZ/2); +#endif + up(&cinergyt2->sem); + return 0; +} + +static const struct usb_device_id cinergyt2_table [] __devinitdata = { + { USB_DEVICE(0x0ccd, 0x0038) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(usb, cinergyt2_table); + +static struct usb_driver cinergyt2_driver = { + .owner = THIS_MODULE, + .name = "cinergyT2", + .probe = cinergyt2_probe, + .disconnect = cinergyt2_disconnect, + .suspend = cinergyt2_suspend, + .resume = cinergyt2_resume, + .id_table = cinergyt2_table +}; + +static int __init cinergyt2_init (void) +{ + int err; + + if ((err = usb_register(&cinergyt2_driver)) < 0) + dprintk(1, "usb_register() failed! (err %i)\n", err); + + return err; +} + +static void __exit cinergyt2_exit (void) +{ + usb_deregister(&cinergyt2_driver); +} + +module_init (cinergyt2_init); +module_exit (cinergyt2_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Holger Waechtler, Daniel Mack"); + diff --git a/drivers/media/dvb/dibusb/Kconfig b/drivers/media/dvb/dibusb/Kconfig new file mode 100644 index 000000000000..74dfc73ae5b0 --- /dev/null +++ b/drivers/media/dvb/dibusb/Kconfig @@ -0,0 +1,62 @@ +config DVB_DIBUSB + tristate "DiBcom USB DVB-T devices (see help for a complete device list)" + depends on DVB_CORE && USB + select FW_LOADER + select DVB_DIB3000MB + select DVB_DIB3000MC + select DVB_MT352 + help + Support for USB 1.1 and 2.0 DVB-T devices based on reference designs made by + DiBcom (http://www.dibcom.fr) and C&E. + + Devices supported by this driver: + + TwinhanDTV USB-Ter (VP7041) + TwinhanDTV Magic Box (VP7041e) + KWorld/JetWay/ADSTech V-Stream XPERT DTV - DVB-T USB1.1 and USB2.0 + Hama DVB-T USB-Box + DiBcom reference devices (non-public) + Ultima Electronic/Artec T1 USB TVBOX + Compro Videomate DVB-U2000 - DVB-T USB + Grandtec DVB-T USB + Avermedia AverTV DVBT USB + Artec T1 USB1.1 and USB2.0 boxes + Yakumo/Typhoon DVB-T USB2.0 + Hanftek UMT-010 USB2.0 + Hauppauge WinTV NOVA-T USB2 + + The VP7041 seems to be identical to "CTS Portable" (Chinese + Television System). + + These devices can be understood as budget ones, they "only" deliver + (a part of) the MPEG2 transport stream. + + A firmware is needed to get the device working. See Documentation/dvb/README.dibusb + details. + + Say Y if you own such a device and want to use it. You should build it as + a module. + +config DVB_DIBUSB_MISDESIGNED_DEVICES + bool "Enable support for some misdesigned (see help) devices, which identify with wrong IDs" + depends on DVB_DIBUSB + help + Somehow Artec/Ultima Electronic forgot to program the eeprom of some of their + USB1.1/USB2.0 devices. + So comes that they identify with the default Vendor and Product ID of the Cypress + CY7C64613 (AN2235) or Cypress FX2. + + Affected device IDs: + 0x0574:0x2235 (Artec T1 USB1.1, cold) + 0x04b4:0x8613 (Artec T1 USB2.0, cold) + 0x0574:0x1002 (Artec T1 USB2.0, warm) + 0x0574:0x2131 (aged DiBcom USB1.1 test device) + + Say Y if your device has one of the mentioned IDs. + +config DVB_DIBCOM_DEBUG + bool "Enable extended debug support for DiBcom USB device" + depends on DVB_DIBUSB + help + Say Y if you want to enable debuging. See modinfo dvb-dibusb for + debug levels. diff --git a/drivers/media/dvb/dibusb/Makefile b/drivers/media/dvb/dibusb/Makefile new file mode 100644 index 000000000000..e941c508624e --- /dev/null +++ b/drivers/media/dvb/dibusb/Makefile @@ -0,0 +1,11 @@ +dvb-dibusb-objs = dvb-dibusb-core.o \ + dvb-dibusb-dvb.o \ + dvb-dibusb-fe-i2c.o \ + dvb-dibusb-firmware.o \ + dvb-dibusb-remote.o \ + dvb-dibusb-usb.o \ + dvb-fe-dtt200u.o + +obj-$(CONFIG_DVB_DIBUSB) += dvb-dibusb.o + +EXTRA_CFLAGS = -Idrivers/media/dvb/dvb-core/ -Idrivers/media/dvb/frontends/ diff --git a/drivers/media/dvb/dibusb/dvb-dibusb-core.c b/drivers/media/dvb/dibusb/dvb-dibusb-core.c new file mode 100644 index 000000000000..26235f9247e4 --- /dev/null +++ b/drivers/media/dvb/dibusb/dvb-dibusb-core.c @@ -0,0 +1,558 @@ +/* + * Driver for mobile USB Budget DVB-T devices based on reference + * design made by DiBcom (http://www.dibcom.fr/) + * + * dvb-dibusb-core.c + * + * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@desy.de) + * + * based on GPL code from DiBcom, which has + * Copyright (C) 2004 Amaury Demol for DiBcom (ademol@dibcom.fr) + * + * Remote control code added by David Matthews (dm@prolingua.co.uk) + * + * 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, version 2. + * + * Acknowledgements + * + * Amaury Demol (ademol@dibcom.fr) from DiBcom for providing specs and driver + * sources, on which this driver (and the dib3000mb/mc/p frontends) are based. + * + * see Documentation/dvb/README.dibusb for more information + */ +#include "dvb-dibusb.h" + +#include <linux/moduleparam.h> + +/* debug */ +int dvb_dibusb_debug; +module_param_named(debug, dvb_dibusb_debug, int, 0644); + +#ifdef CONFIG_DVB_DIBCOM_DEBUG +#define DBSTATUS "" +#else +#define DBSTATUS " (debugging is not enabled)" +#endif +MODULE_PARM_DESC(debug, "set debugging level (1=info,2=xfer,4=alotmore,8=ts,16=err,32=rc (|-able))." DBSTATUS); +#undef DBSTATUS + +static int pid_parse; +module_param(pid_parse, int, 0644); +MODULE_PARM_DESC(pid_parse, "enable pid parsing (filtering) when running at USB2.0"); + +static int rc_query_interval = 100; +module_param(rc_query_interval, int, 0644); +MODULE_PARM_DESC(rc_query_interval, "interval in msecs for remote control query (default: 100; min: 40)"); + +static int rc_key_repeat_count = 2; +module_param(rc_key_repeat_count, int, 0644); +MODULE_PARM_DESC(rc_key_repeat_count, "how many key repeats will be dropped before passing the key event again (default: 2)"); + +/* Vendor IDs */ +#define USB_VID_ADSTECH 0x06e1 +#define USB_VID_ANCHOR 0x0547 +#define USB_VID_AVERMEDIA 0x14aa +#define USB_VID_COMPRO 0x185b +#define USB_VID_COMPRO_UNK 0x145f +#define USB_VID_CYPRESS 0x04b4 +#define USB_VID_DIBCOM 0x10b8 +#define USB_VID_EMPIA 0xeb1a +#define USB_VID_GRANDTEC 0x5032 +#define USB_VID_HANFTEK 0x15f4 +#define USB_VID_HAUPPAUGE 0x2040 +#define USB_VID_HYPER_PALTEK 0x1025 +#define USB_VID_IMC_NETWORKS 0x13d3 +#define USB_VID_TWINHAN 0x1822 +#define USB_VID_ULTIMA_ELECTRONIC 0x05d8 + +/* Product IDs */ +#define USB_PID_ADSTECH_USB2_COLD 0xa333 +#define USB_PID_ADSTECH_USB2_WARM 0xa334 +#define USB_PID_AVERMEDIA_DVBT_USB_COLD 0x0001 +#define USB_PID_AVERMEDIA_DVBT_USB_WARM 0x0002 +#define USB_PID_COMPRO_DVBU2000_COLD 0xd000 +#define USB_PID_COMPRO_DVBU2000_WARM 0xd001 +#define USB_PID_COMPRO_DVBU2000_UNK_COLD 0x010c +#define USB_PID_COMPRO_DVBU2000_UNK_WARM 0x010d +#define USB_PID_DIBCOM_MOD3000_COLD 0x0bb8 +#define USB_PID_DIBCOM_MOD3000_WARM 0x0bb9 +#define USB_PID_DIBCOM_MOD3001_COLD 0x0bc6 +#define USB_PID_DIBCOM_MOD3001_WARM 0x0bc7 +#define USB_PID_DIBCOM_ANCHOR_2135_COLD 0x2131 +#define USB_PID_GRANDTEC_DVBT_USB_COLD 0x0fa0 +#define USB_PID_GRANDTEC_DVBT_USB_WARM 0x0fa1 +#define USB_PID_KWORLD_VSTREAM_COLD 0x17de +#define USB_PID_KWORLD_VSTREAM_WARM 0x17df +#define USB_PID_TWINHAN_VP7041_COLD 0x3201 +#define USB_PID_TWINHAN_VP7041_WARM 0x3202 +#define USB_PID_ULTIMA_TVBOX_COLD 0x8105 +#define USB_PID_ULTIMA_TVBOX_WARM 0x8106 +#define USB_PID_ULTIMA_TVBOX_AN2235_COLD 0x8107 +#define USB_PID_ULTIMA_TVBOX_AN2235_WARM 0x8108 +#define USB_PID_ULTIMA_TVBOX_ANCHOR_COLD 0x2235 +#define USB_PID_ULTIMA_TVBOX_USB2_COLD 0x8109 +#define USB_PID_ULTIMA_TVBOX_USB2_FX_COLD 0x8613 +#define USB_PID_ULTIMA_TVBOX_USB2_FX_WARM 0x1002 +#define USB_PID_UNK_HYPER_PALTEK_COLD 0x005e +#define USB_PID_UNK_HYPER_PALTEK_WARM 0x005f +#define USB_PID_HANFTEK_UMT_010_COLD 0x0001 +#define USB_PID_HANFTEK_UMT_010_WARM 0x0015 +#define USB_PID_YAKUMO_DTT200U_COLD 0x0201 +#define USB_PID_YAKUMO_DTT200U_WARM 0x0301 +#define USB_PID_WINTV_NOVA_T_USB2_COLD 0x9300 +#define USB_PID_WINTV_NOVA_T_USB2_WARM 0x9301 + +/* USB Driver stuff + * table of devices that this driver is working with + * + * ATTENTION: Never ever change the order of this table, the particular + * devices depend on this order + * + * Each entry is used as a reference in the device_struct. Currently this is + * the only non-redundant way of assigning USB ids to actual devices I'm aware + * of, because there is only one place in the code where the assignment of + * vendor and product id is done, here. + */ +static struct usb_device_id dib_table [] = { +/* 00 */ { USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_DVBT_USB_COLD)}, +/* 01 */ { USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_DVBT_USB_WARM)}, +/* 02 */ { USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_YAKUMO_DTT200U_COLD) }, +/* 03 */ { USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_YAKUMO_DTT200U_WARM) }, + +/* 04 */ { USB_DEVICE(USB_VID_COMPRO, USB_PID_COMPRO_DVBU2000_COLD) }, +/* 05 */ { USB_DEVICE(USB_VID_COMPRO, USB_PID_COMPRO_DVBU2000_WARM) }, +/* 06 */ { USB_DEVICE(USB_VID_COMPRO_UNK, USB_PID_COMPRO_DVBU2000_UNK_COLD) }, +/* 07 */ { USB_DEVICE(USB_VID_DIBCOM, USB_PID_DIBCOM_MOD3000_COLD) }, +/* 08 */ { USB_DEVICE(USB_VID_DIBCOM, USB_PID_DIBCOM_MOD3000_WARM) }, +/* 09 */ { USB_DEVICE(USB_VID_DIBCOM, USB_PID_DIBCOM_MOD3001_COLD) }, +/* 10 */ { USB_DEVICE(USB_VID_DIBCOM, USB_PID_DIBCOM_MOD3001_WARM) }, +/* 11 */ { USB_DEVICE(USB_VID_EMPIA, USB_PID_KWORLD_VSTREAM_COLD) }, +/* 12 */ { USB_DEVICE(USB_VID_EMPIA, USB_PID_KWORLD_VSTREAM_WARM) }, +/* 13 */ { USB_DEVICE(USB_VID_GRANDTEC, USB_PID_GRANDTEC_DVBT_USB_COLD) }, +/* 14 */ { USB_DEVICE(USB_VID_GRANDTEC, USB_PID_GRANDTEC_DVBT_USB_WARM) }, +/* 15 */ { USB_DEVICE(USB_VID_GRANDTEC, USB_PID_DIBCOM_MOD3000_COLD) }, +/* 16 */ { USB_DEVICE(USB_VID_GRANDTEC, USB_PID_DIBCOM_MOD3000_WARM) }, +/* 17 */ { USB_DEVICE(USB_VID_HYPER_PALTEK, USB_PID_UNK_HYPER_PALTEK_COLD) }, +/* 18 */ { USB_DEVICE(USB_VID_HYPER_PALTEK, USB_PID_UNK_HYPER_PALTEK_WARM) }, +/* 19 */ { USB_DEVICE(USB_VID_IMC_NETWORKS, USB_PID_TWINHAN_VP7041_COLD) }, +/* 20 */ { USB_DEVICE(USB_VID_IMC_NETWORKS, USB_PID_TWINHAN_VP7041_WARM) }, +/* 21 */ { USB_DEVICE(USB_VID_TWINHAN, USB_PID_TWINHAN_VP7041_COLD) }, +/* 22 */ { USB_DEVICE(USB_VID_TWINHAN, USB_PID_TWINHAN_VP7041_WARM) }, +/* 23 */ { USB_DEVICE(USB_VID_ULTIMA_ELECTRONIC, USB_PID_ULTIMA_TVBOX_COLD) }, +/* 24 */ { USB_DEVICE(USB_VID_ULTIMA_ELECTRONIC, USB_PID_ULTIMA_TVBOX_WARM) }, +/* 25 */ { USB_DEVICE(USB_VID_ULTIMA_ELECTRONIC, USB_PID_ULTIMA_TVBOX_AN2235_COLD) }, +/* 26 */ { USB_DEVICE(USB_VID_ULTIMA_ELECTRONIC, USB_PID_ULTIMA_TVBOX_AN2235_WARM) }, +/* 27 */ { USB_DEVICE(USB_VID_ULTIMA_ELECTRONIC, USB_PID_ULTIMA_TVBOX_USB2_COLD) }, + +/* 28 */ { USB_DEVICE(USB_VID_HANFTEK, USB_PID_HANFTEK_UMT_010_COLD) }, +/* 29 */ { USB_DEVICE(USB_VID_HANFTEK, USB_PID_HANFTEK_UMT_010_WARM) }, + +/* 30 */ { USB_DEVICE(USB_VID_HAUPPAUGE, USB_PID_WINTV_NOVA_T_USB2_COLD) }, +/* 31 */ { USB_DEVICE(USB_VID_HAUPPAUGE, USB_PID_WINTV_NOVA_T_USB2_WARM) }, +/* 32 */ { USB_DEVICE(USB_VID_ADSTECH, USB_PID_ADSTECH_USB2_COLD) }, +/* 33 */ { USB_DEVICE(USB_VID_ADSTECH, USB_PID_ADSTECH_USB2_WARM) }, +/* + * activate the following define when you have one of the devices and want to + * build it from build-2.6 in dvb-kernel + */ +// #define CONFIG_DVB_DIBUSB_MISDESIGNED_DEVICES +#ifdef CONFIG_DVB_DIBUSB_MISDESIGNED_DEVICES +/* 34 */ { USB_DEVICE(USB_VID_ANCHOR, USB_PID_ULTIMA_TVBOX_ANCHOR_COLD) }, +/* 35 */ { USB_DEVICE(USB_VID_CYPRESS, USB_PID_ULTIMA_TVBOX_USB2_FX_COLD) }, +/* 36 */ { USB_DEVICE(USB_VID_ANCHOR, USB_PID_ULTIMA_TVBOX_USB2_FX_WARM) }, +/* 37 */ { USB_DEVICE(USB_VID_ANCHOR, USB_PID_DIBCOM_ANCHOR_2135_COLD) }, +#endif + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, dib_table); + +static struct dibusb_usb_controller dibusb_usb_ctrl[] = { + { .name = "Cypress AN2135", .cpu_cs_register = 0x7f92 }, + { .name = "Cypress AN2235", .cpu_cs_register = 0x7f92 }, + { .name = "Cypress FX2", .cpu_cs_register = 0xe600 }, +}; + +struct dibusb_tuner dibusb_tuner[] = { + { DIBUSB_TUNER_CABLE_THOMSON, + 0x61 + }, + { DIBUSB_TUNER_COFDM_PANASONIC_ENV57H1XD5, + 0x60 + }, + { DIBUSB_TUNER_CABLE_LG_TDTP_E102P, + 0x61 + }, + { DIBUSB_TUNER_COFDM_PANASONIC_ENV77H11D5, + 0x60 + }, +}; + +static struct dibusb_demod dibusb_demod[] = { + { DIBUSB_DIB3000MB, + 16, + { 0x8, 0 }, + }, + { DIBUSB_DIB3000MC, + 32, + { 0x9, 0xa, 0xb, 0xc }, + }, + { DIBUSB_MT352, + 254, + { 0xf, 0 }, + }, + { DTT200U_FE, + 8, + { 0xff,0 }, /* there is no i2c bus in this device */ + } +}; + +static struct dibusb_device_class dibusb_device_classes[] = { + { .id = DIBUSB1_1, .usb_ctrl = &dibusb_usb_ctrl[0], + .firmware = "dvb-dibusb-5.0.0.11.fw", + .pipe_cmd = 0x01, .pipe_data = 0x02, + .urb_count = 7, .urb_buffer_size = 4096, + DIBUSB_RC_NEC_PROTOCOL, + &dibusb_demod[DIBUSB_DIB3000MB], + &dibusb_tuner[DIBUSB_TUNER_CABLE_THOMSON], + }, + { DIBUSB1_1_AN2235, &dibusb_usb_ctrl[1], + "dvb-dibusb-an2235-1.fw", + 0x01, 0x02, + 7, 4096, + DIBUSB_RC_NEC_PROTOCOL, + &dibusb_demod[DIBUSB_DIB3000MB], + &dibusb_tuner[DIBUSB_TUNER_CABLE_THOMSON], + }, + { DIBUSB2_0,&dibusb_usb_ctrl[2], + "dvb-dibusb-6.0.0.5.fw", + 0x01, 0x06, + 7, 4096, + DIBUSB_RC_NEC_PROTOCOL, + &dibusb_demod[DIBUSB_DIB3000MC], + &dibusb_tuner[DIBUSB_TUNER_COFDM_PANASONIC_ENV57H1XD5], + }, + { UMT2_0, &dibusb_usb_ctrl[2], + "dvb-dibusb-umt-2.fw", + 0x01, 0x06, + 20, 512, + DIBUSB_RC_NO, + &dibusb_demod[DIBUSB_MT352], + &dibusb_tuner[DIBUSB_TUNER_CABLE_LG_TDTP_E102P], + }, + { DIBUSB2_0B,&dibusb_usb_ctrl[2], + "dvb-dibusb-adstech-usb2-1.fw", + 0x01, 0x06, + 7, 4096, + DIBUSB_RC_NEC_PROTOCOL, + &dibusb_demod[DIBUSB_DIB3000MB], + &dibusb_tuner[DIBUSB_TUNER_CABLE_THOMSON], + }, + { NOVAT_USB2,&dibusb_usb_ctrl[2], + "dvb-dibusb-nova-t-1.fw", + 0x01, 0x06, + 7, 4096, + DIBUSB_RC_HAUPPAUGE_PROTO, + &dibusb_demod[DIBUSB_DIB3000MC], + &dibusb_tuner[DIBUSB_TUNER_COFDM_PANASONIC_ENV57H1XD5], + }, + { DTT200U,&dibusb_usb_ctrl[2], + "dvb-dtt200u-1.fw", + 0x01, 0x02, + 7, 4096, + DIBUSB_RC_NO, + &dibusb_demod[DTT200U_FE], + NULL, /* no explicit tuner/pll-programming necessary (it has the ENV57H1XD5) */ + }, +}; + +static struct dibusb_usb_device dibusb_devices[] = { + { "TwinhanDTV USB1.1 / Magic Box / HAMA USB1.1 DVB-T device", + &dibusb_device_classes[DIBUSB1_1], + { &dib_table[19], &dib_table[21], NULL}, + { &dib_table[20], &dib_table[22], NULL}, + }, + { "KWorld V-Stream XPERT DTV - DVB-T USB1.1", + &dibusb_device_classes[DIBUSB1_1], + { &dib_table[11], NULL }, + { &dib_table[12], NULL }, + }, + { "Grandtec USB1.1 DVB-T", + &dibusb_device_classes[DIBUSB1_1], + { &dib_table[13], &dib_table[15], NULL }, + { &dib_table[14], &dib_table[16], NULL }, + }, + { "DiBcom USB1.1 DVB-T reference design (MOD3000)", + &dibusb_device_classes[DIBUSB1_1], + { &dib_table[7], NULL }, + { &dib_table[8], NULL }, + }, + { "Artec T1 USB1.1 TVBOX with AN2135", + &dibusb_device_classes[DIBUSB1_1], + { &dib_table[23], NULL }, + { &dib_table[24], NULL }, + }, + { "Artec T1 USB1.1 TVBOX with AN2235", + &dibusb_device_classes[DIBUSB1_1_AN2235], + { &dib_table[25], NULL }, + { &dib_table[26], NULL }, + }, + { "Avermedia AverTV DVBT USB1.1", + &dibusb_device_classes[DIBUSB1_1], + { &dib_table[0], NULL }, + { &dib_table[1], NULL }, + }, + { "Compro Videomate DVB-U2000 - DVB-T USB1.1 (please confirm to linux-dvb)", + &dibusb_device_classes[DIBUSB1_1], + { &dib_table[4], &dib_table[6], NULL}, + { &dib_table[5], NULL }, + }, + { "Unkown USB1.1 DVB-T device ???? please report the name to the author", + &dibusb_device_classes[DIBUSB1_1], + { &dib_table[17], NULL }, + { &dib_table[18], NULL }, + }, + { "DiBcom USB2.0 DVB-T reference design (MOD3000P)", + &dibusb_device_classes[DIBUSB2_0], + { &dib_table[9], NULL }, + { &dib_table[10], NULL }, + }, + { "Artec T1 USB2.0 TVBOX (please report the warm ID)", + &dibusb_device_classes[DIBUSB2_0], + { &dib_table[27], NULL }, + { NULL }, + }, + { "Hauppauge WinTV NOVA-T USB2", + &dibusb_device_classes[NOVAT_USB2], + { &dib_table[30], NULL }, + { &dib_table[31], NULL }, + }, + { "DTT200U (Yakumo/Hama/Typhoon) DVB-T USB2.0", + &dibusb_device_classes[DTT200U], + { &dib_table[2], NULL }, + { &dib_table[3], NULL }, + }, + { "Hanftek UMT-010 DVB-T USB2.0", + &dibusb_device_classes[UMT2_0], + { &dib_table[28], NULL }, + { &dib_table[29], NULL }, + }, + { "KWorld/ADSTech Instant DVB-T USB 2.0", + &dibusb_device_classes[DIBUSB2_0B], + { &dib_table[32], NULL }, + { &dib_table[33], NULL }, /* device ID with default DIBUSB2_0-firmware */ + }, +#ifdef CONFIG_DVB_DIBUSB_MISDESIGNED_DEVICES + { "Artec T1 USB1.1 TVBOX with AN2235 (misdesigned)", + &dibusb_device_classes[DIBUSB1_1_AN2235], + { &dib_table[34], NULL }, + { NULL }, + }, + { "Artec T1 USB2.0 TVBOX with FX2 IDs (misdesigned, please report the warm ID)", + &dibusb_device_classes[DTT200U], + { &dib_table[35], NULL }, + { &dib_table[36], NULL }, /* undefined, it could be that the device will get another USB ID in warm state */ + }, + { "DiBcom USB1.1 DVB-T reference design (MOD3000) with AN2135 default IDs", + &dibusb_device_classes[DIBUSB1_1], + { &dib_table[37], NULL }, + { NULL }, + }, +#endif +}; + +static int dibusb_exit(struct usb_dibusb *dib) +{ + deb_info("init_state before exiting everything: %x\n",dib->init_state); + dibusb_remote_exit(dib); + dibusb_fe_exit(dib); + dibusb_i2c_exit(dib); + dibusb_dvb_exit(dib); + dibusb_urb_exit(dib); + deb_info("init_state should be zero now: %x\n",dib->init_state); + dib->init_state = DIBUSB_STATE_INIT; + kfree(dib); + return 0; +} + +static int dibusb_init(struct usb_dibusb *dib) +{ + int ret = 0; + sema_init(&dib->usb_sem, 1); + sema_init(&dib->i2c_sem, 1); + + dib->init_state = DIBUSB_STATE_INIT; + + if ((ret = dibusb_urb_init(dib)) || + (ret = dibusb_dvb_init(dib)) || + (ret = dibusb_i2c_init(dib))) { + dibusb_exit(dib); + return ret; + } + + if ((ret = dibusb_fe_init(dib))) + err("could not initialize a frontend."); + + if ((ret = dibusb_remote_init(dib))) + err("could not initialize remote control."); + + return 0; +} + +static struct dibusb_usb_device * dibusb_device_class_quirk(struct usb_device *udev, struct dibusb_usb_device *dev) +{ + int i; + + /* Quirk for the Kworld/ADSTech Instant USB2.0 device. It has the same USB + * IDs like the USB1.1 KWorld after loading the firmware. Which is a bad + * idea and make this quirk necessary. + */ + if (dev->dev_cl->id == DIBUSB1_1 && udev->speed == USB_SPEED_HIGH) { + info("this seems to be the Kworld/ADSTech Instant USB2.0 device or equal."); + for (i = 0; i < sizeof(dibusb_devices)/sizeof(struct dibusb_usb_device); i++) { + if (dibusb_devices[i].dev_cl->id == DIBUSB2_0B) { + dev = &dibusb_devices[i]; + break; + } + } + } + + return dev; +} + +static struct dibusb_usb_device * dibusb_find_device (struct usb_device *udev,int *cold) +{ + int i,j; + struct dibusb_usb_device *dev = NULL; + *cold = -1; + + for (i = 0; i < sizeof(dibusb_devices)/sizeof(struct dibusb_usb_device); i++) { + for (j = 0; j < DIBUSB_ID_MAX_NUM && dibusb_devices[i].cold_ids[j] != NULL; j++) { + deb_info("check for cold %x %x\n",dibusb_devices[i].cold_ids[j]->idVendor, dibusb_devices[i].cold_ids[j]->idProduct); + if (dibusb_devices[i].cold_ids[j]->idVendor == le16_to_cpu(udev->descriptor.idVendor) && + dibusb_devices[i].cold_ids[j]->idProduct == le16_to_cpu(udev->descriptor.idProduct)) { + *cold = 1; + dev = &dibusb_devices[i]; + break; + } + } + + if (dev != NULL) + break; + + for (j = 0; j < DIBUSB_ID_MAX_NUM && dibusb_devices[i].warm_ids[j] != NULL; j++) { + deb_info("check for warm %x %x\n",dibusb_devices[i].warm_ids[j]->idVendor, dibusb_devices[i].warm_ids[j]->idProduct); + if (dibusb_devices[i].warm_ids[j]->idVendor == le16_to_cpu(udev->descriptor.idVendor) && + dibusb_devices[i].warm_ids[j]->idProduct == le16_to_cpu(udev->descriptor.idProduct)) { + *cold = 0; + dev = &dibusb_devices[i]; + break; + } + } + } + + if (dev != NULL) + dev = dibusb_device_class_quirk(udev,dev); + + return dev; +} + +/* + * USB + */ +static int dibusb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_dibusb *dib = NULL; + struct dibusb_usb_device *dibdev = NULL; + + int ret = -ENOMEM,cold=0; + + if ((dibdev = dibusb_find_device(udev,&cold)) == NULL) { + err("something went very wrong, " + "unknown product ID: %.4x",le16_to_cpu(udev->descriptor.idProduct)); + return -ENODEV; + } + + if (cold == 1) { + info("found a '%s' in cold state, will try to load a firmware",dibdev->name); + ret = dibusb_loadfirmware(udev,dibdev); + } else { + info("found a '%s' in warm state.",dibdev->name); + dib = kmalloc(sizeof(struct usb_dibusb),GFP_KERNEL); + if (dib == NULL) { + err("no memory"); + return ret; + } + memset(dib,0,sizeof(struct usb_dibusb)); + + dib->udev = udev; + dib->dibdev = dibdev; + + /* store parameters to structures */ + dib->rc_query_interval = rc_query_interval; + dib->pid_parse = pid_parse; + dib->rc_key_repeat_count = rc_key_repeat_count; + + usb_set_intfdata(intf, dib); + + ret = dibusb_init(dib); + } + + if (ret == 0) + info("%s successfully initialized and connected.",dibdev->name); + else + info("%s error while loading driver (%d)",dibdev->name,ret); + return ret; +} + +static void dibusb_disconnect(struct usb_interface *intf) +{ + struct usb_dibusb *dib = usb_get_intfdata(intf); + const char *name = DRIVER_DESC; + + usb_set_intfdata(intf,NULL); + if (dib != NULL && dib->dibdev != NULL) { + name = dib->dibdev->name; + dibusb_exit(dib); + } + info("%s successfully deinitialized and disconnected.",name); + +} + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver dibusb_driver = { + .owner = THIS_MODULE, + .name = DRIVER_DESC, + .probe = dibusb_probe, + .disconnect = dibusb_disconnect, + .id_table = dib_table, +}; + +/* module stuff */ +static int __init usb_dibusb_init(void) +{ + int result; + if ((result = usb_register(&dibusb_driver))) { + err("usb_register failed. Error number %d",result); + return result; + } + + return 0; +} + +static void __exit usb_dibusb_exit(void) +{ + /* deregister this driver from the USB subsystem */ + usb_deregister(&dibusb_driver); +} + +module_init (usb_dibusb_init); +module_exit (usb_dibusb_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/dibusb/dvb-dibusb-dvb.c b/drivers/media/dvb/dibusb/dvb-dibusb-dvb.c new file mode 100644 index 000000000000..04e54ec093f0 --- /dev/null +++ b/drivers/media/dvb/dibusb/dvb-dibusb-dvb.c @@ -0,0 +1,185 @@ +/* + * dvb-dibusb-dvb.c is part of the driver for mobile USB Budget DVB-T devices + * based on reference design made by DiBcom (http://www.dibcom.fr/) + * + * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@desy.de) + * + * see dvb-dibusb-core.c for more copyright details. + * + * This file contains functions for initializing and handling the + * linux-dvb API. + */ +#include "dvb-dibusb.h" + +#include <linux/usb.h> +#include <linux/version.h> + +static u32 urb_compl_count; + +/* + * MPEG2 TS DVB stuff + */ +void dibusb_urb_complete(struct urb *urb, struct pt_regs *ptregs) +{ + struct usb_dibusb *dib = urb->context; + + deb_ts("urb complete feedcount: %d, status: %d, length: %d\n",dib->feedcount,urb->status, + urb->actual_length); + + urb_compl_count++; + if (urb_compl_count % 1000 == 0) + deb_info("%d urbs completed so far.\n",urb_compl_count); + + switch (urb->status) { + case 0: /* success */ + case -ETIMEDOUT: /* NAK */ + break; + case -ECONNRESET: /* kill */ + case -ENOENT: + case -ESHUTDOWN: + return; + default: /* error */ + deb_ts("urb completition error %d.", urb->status); + break; + } + + if (dib->feedcount > 0 && urb->actual_length > 0) { + if (dib->init_state & DIBUSB_STATE_DVB) + dvb_dmx_swfilter(&dib->demux, (u8*) urb->transfer_buffer,urb->actual_length); + } else + deb_ts("URB dropped because of feedcount.\n"); + + usb_submit_urb(urb,GFP_ATOMIC); +} + +static int dibusb_ctrl_feed(struct dvb_demux_feed *dvbdmxfeed, int onoff) +{ + struct usb_dibusb *dib = dvbdmxfeed->demux->priv; + int newfeedcount; + + if (dib == NULL) + return -ENODEV; + + newfeedcount = dib->feedcount + (onoff ? 1 : -1); + + /* + * stop feed before setting a new pid if there will be no pid anymore + */ + if (newfeedcount == 0) { + deb_ts("stop feeding\n"); + if (dib->xfer_ops.fifo_ctrl != NULL) { + if (dib->xfer_ops.fifo_ctrl(dib->fe,0)) { + err("error while inhibiting fifo."); + return -ENODEV; + } + } + dibusb_streaming(dib,0); + } + + dib->feedcount = newfeedcount; + + /* activate the pid on the device specific pid_filter */ + deb_ts("setting pid: %5d %04x at index %d '%s'\n",dvbdmxfeed->pid,dvbdmxfeed->pid,dvbdmxfeed->index,onoff ? "on" : "off"); + if (dib->pid_parse && dib->xfer_ops.pid_ctrl != NULL) + dib->xfer_ops.pid_ctrl(dib->fe,dvbdmxfeed->index,dvbdmxfeed->pid,onoff); + + /* + * start the feed if this was the first pid to set and there is still a pid + * for reception. + */ + if (dib->feedcount == onoff && dib->feedcount > 0) { + + deb_ts("controlling pid parser\n"); + if (dib->xfer_ops.pid_parse != NULL) { + if (dib->xfer_ops.pid_parse(dib->fe,dib->pid_parse) < 0) { + err("could not handle pid_parser"); + } + } + + deb_ts("start feeding\n"); + if (dib->xfer_ops.fifo_ctrl != NULL) { + if (dib->xfer_ops.fifo_ctrl(dib->fe,1)) { + err("error while enabling fifo."); + return -ENODEV; + } + } + dibusb_streaming(dib,1); + } + return 0; +} + +static int dibusb_start_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + deb_ts("start pid: 0x%04x, feedtype: %d\n", dvbdmxfeed->pid,dvbdmxfeed->type); + return dibusb_ctrl_feed(dvbdmxfeed,1); +} + +static int dibusb_stop_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + deb_ts("stop pid: 0x%04x, feedtype: %d\n", dvbdmxfeed->pid, dvbdmxfeed->type); + return dibusb_ctrl_feed(dvbdmxfeed,0); +} + +int dibusb_dvb_init(struct usb_dibusb *dib) +{ + int ret; + + urb_compl_count = 0; + + if ((ret = dvb_register_adapter(&dib->adapter, DRIVER_DESC, + THIS_MODULE)) < 0) { + deb_info("dvb_register_adapter failed: error %d", ret); + goto err; + } + dib->adapter->priv = dib; + +/* i2c is done in dibusb_i2c_init */ + + dib->demux.dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING; + + dib->demux.priv = (void *)dib; + /* get pidcount from demod */ + dib->demux.feednum = dib->demux.filternum = 255; + dib->demux.start_feed = dibusb_start_feed; + dib->demux.stop_feed = dibusb_stop_feed; + dib->demux.write_to_decoder = NULL; + if ((ret = dvb_dmx_init(&dib->demux)) < 0) { + err("dvb_dmx_init failed: error %d",ret); + goto err_dmx; + } + + dib->dmxdev.filternum = dib->demux.filternum; + dib->dmxdev.demux = &dib->demux.dmx; + dib->dmxdev.capabilities = 0; + if ((ret = dvb_dmxdev_init(&dib->dmxdev, dib->adapter)) < 0) { + err("dvb_dmxdev_init failed: error %d",ret); + goto err_dmx_dev; + } + + dvb_net_init(dib->adapter, &dib->dvb_net, &dib->demux.dmx); + + goto success; +err_dmx_dev: + dvb_dmx_release(&dib->demux); +err_dmx: + dvb_unregister_adapter(dib->adapter); +err: + return ret; +success: + dib->init_state |= DIBUSB_STATE_DVB; + return 0; +} + +int dibusb_dvb_exit(struct usb_dibusb *dib) +{ + if (dib->init_state & DIBUSB_STATE_DVB) { + dib->init_state &= ~DIBUSB_STATE_DVB; + deb_info("unregistering DVB part\n"); + dvb_net_release(&dib->dvb_net); + dib->demux.dmx.close(&dib->demux.dmx); + dvb_dmxdev_release(&dib->dmxdev); + dvb_dmx_release(&dib->demux); + dvb_unregister_adapter(dib->adapter); + } + return 0; +} diff --git a/drivers/media/dvb/dibusb/dvb-dibusb-fe-i2c.c b/drivers/media/dvb/dibusb/dvb-dibusb-fe-i2c.c new file mode 100644 index 000000000000..2ed89488c7c4 --- /dev/null +++ b/drivers/media/dvb/dibusb/dvb-dibusb-fe-i2c.c @@ -0,0 +1,582 @@ +/* + * dvb-dibusb-fe-i2c.c is part of the driver for mobile USB Budget DVB-T devices + * based on reference design made by DiBcom (http://www.dibcom.fr/) + * + * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@desy.de) + * + * see dvb-dibusb-core.c for more copyright details. + * + * This file contains functions for attaching, initializing of an appropriate + * demodulator/frontend. I2C-stuff is also located here. + * + */ +#include "dvb-dibusb.h" + +#include <linux/usb.h> + +static int dibusb_i2c_msg(struct usb_dibusb *dib, u8 addr, + u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen) +{ + u8 sndbuf[wlen+4]; /* lead(1) devaddr,direction(1) addr(2) data(wlen) (len(2) (when reading)) */ + /* write only ? */ + int wo = (rbuf == NULL || rlen == 0), + len = 2 + wlen + (wo ? 0 : 2); + + sndbuf[0] = wo ? DIBUSB_REQ_I2C_WRITE : DIBUSB_REQ_I2C_READ; + sndbuf[1] = (addr << 1) | (wo ? 0 : 1); + + memcpy(&sndbuf[2],wbuf,wlen); + + if (!wo) { + sndbuf[wlen+2] = (rlen >> 8) & 0xff; + sndbuf[wlen+3] = rlen & 0xff; + } + + return dibusb_readwrite_usb(dib,sndbuf,len,rbuf,rlen); +} + +/* + * I2C master xfer function + */ +static int dibusb_i2c_xfer(struct i2c_adapter *adap,struct i2c_msg *msg,int num) +{ + struct usb_dibusb *dib = i2c_get_adapdata(adap); + int i; + + if (down_interruptible(&dib->i2c_sem) < 0) + return -EAGAIN; + + if (num > 2) + warn("more than 2 i2c messages at a time is not handled yet. TODO."); + + for (i = 0; i < num; i++) { + /* write/read request */ + if (i+1 < num && (msg[i+1].flags & I2C_M_RD)) { + if (dibusb_i2c_msg(dib, msg[i].addr, msg[i].buf,msg[i].len, + msg[i+1].buf,msg[i+1].len) < 0) + break; + i++; + } else + if (dibusb_i2c_msg(dib, msg[i].addr, msg[i].buf,msg[i].len,NULL,0) < 0) + break; + } + + up(&dib->i2c_sem); + return i; +} + +static u32 dibusb_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +static struct i2c_algorithm dibusb_algo = { + .name = "DiBcom USB i2c algorithm", + .id = I2C_ALGO_BIT, + .master_xfer = dibusb_i2c_xfer, + .functionality = dibusb_i2c_func, +}; + +static int dibusb_general_demod_init(struct dvb_frontend *fe); +static u8 dibusb_general_pll_addr(struct dvb_frontend *fe); +static int dibusb_general_pll_init(struct dvb_frontend *fe, u8 pll_buf[5]); +static int dibusb_general_pll_set(struct dvb_frontend *fe, + struct dvb_frontend_parameters* params, u8 pll_buf[5]); + +static struct mt352_config mt352_hanftek_umt_010_config = { + .demod_address = 0x1e, + .demod_init = dibusb_general_demod_init, + .pll_set = dibusb_general_pll_set, +}; + +static int dibusb_tuner_quirk(struct usb_dibusb *dib) +{ + switch (dib->dibdev->dev_cl->id) { + case DIBUSB1_1: /* some these device have the ENV77H11D5 and some the THOMSON CABLE */ + case DIBUSB1_1_AN2235: { /* actually its this device, but in warm state they are indistinguishable */ + struct dibusb_tuner *t; + u8 b[2] = { 0,0 } ,b2[1]; + struct i2c_msg msg[2] = { + { .flags = 0, .buf = b, .len = 2 }, + { .flags = I2C_M_RD, .buf = b2, .len = 1}, + }; + + t = &dibusb_tuner[DIBUSB_TUNER_COFDM_PANASONIC_ENV77H11D5]; + + msg[0].addr = msg[1].addr = t->pll_addr; + + if (dib->xfer_ops.tuner_pass_ctrl != NULL) + dib->xfer_ops.tuner_pass_ctrl(dib->fe,1,t->pll_addr); + dibusb_i2c_xfer(&dib->i2c_adap,msg,2); + if (dib->xfer_ops.tuner_pass_ctrl != NULL) + dib->xfer_ops.tuner_pass_ctrl(dib->fe,0,t->pll_addr); + + if (b2[0] == 0xfe) + info("this device has the Thomson Cable onboard. Which is default."); + else { + dib->tuner = t; + info("this device has the Panasonic ENV77H11D5 onboard."); + } + break; + } + default: + break; + } + return 0; +} + +int dibusb_fe_init(struct usb_dibusb* dib) +{ + struct dib3000_config demod_cfg; + int i; + + if (dib->init_state & DIBUSB_STATE_I2C) { + for (i = 0; i < sizeof(dib->dibdev->dev_cl->demod->i2c_addrs) / sizeof(unsigned char) && + dib->dibdev->dev_cl->demod->i2c_addrs[i] != 0; i++) { + + demod_cfg.demod_address = dib->dibdev->dev_cl->demod->i2c_addrs[i]; + demod_cfg.pll_addr = dibusb_general_pll_addr; + demod_cfg.pll_set = dibusb_general_pll_set; + demod_cfg.pll_init = dibusb_general_pll_init; + + deb_info("demod id: %d %d\n",dib->dibdev->dev_cl->demod->id,DTT200U_FE); + + switch (dib->dibdev->dev_cl->demod->id) { + case DIBUSB_DIB3000MB: + dib->fe = dib3000mb_attach(&demod_cfg,&dib->i2c_adap,&dib->xfer_ops); + break; + case DIBUSB_DIB3000MC: + dib->fe = dib3000mc_attach(&demod_cfg,&dib->i2c_adap,&dib->xfer_ops); + break; + case DIBUSB_MT352: + mt352_hanftek_umt_010_config.demod_address = dib->dibdev->dev_cl->demod->i2c_addrs[i]; + dib->fe = mt352_attach(&mt352_hanftek_umt_010_config, &dib->i2c_adap); + break; + case DTT200U_FE: + dib->fe = dtt200u_fe_attach(dib,&dib->xfer_ops); + break; + } + if (dib->fe != NULL) { + info("found demodulator at i2c address 0x%x",dib->dibdev->dev_cl->demod->i2c_addrs[i]); + break; + } + } + /* if a frontend was found */ + if (dib->fe != NULL) { + if (dib->fe->ops->sleep != NULL) + dib->fe_sleep = dib->fe->ops->sleep; + dib->fe->ops->sleep = dibusb_hw_sleep; + + if (dib->fe->ops->init != NULL ) + dib->fe_init = dib->fe->ops->init; + dib->fe->ops->init = dibusb_hw_wakeup; + + /* setting the default tuner */ + dib->tuner = dib->dibdev->dev_cl->tuner; + + /* check which tuner is mounted on this device, in case this is unsure */ + dibusb_tuner_quirk(dib); + } + } + if (dib->fe == NULL) { + err("A frontend driver was not found for device '%s'.", + dib->dibdev->name); + return -ENODEV; + } else { + if (dvb_register_frontend(dib->adapter, dib->fe)) { + err("Frontend registration failed."); + if (dib->fe->ops->release) + dib->fe->ops->release(dib->fe); + dib->fe = NULL; + return -ENODEV; + } + } + + return 0; +} + +int dibusb_fe_exit(struct usb_dibusb *dib) +{ + if (dib->fe != NULL) + dvb_unregister_frontend(dib->fe); + return 0; +} + +int dibusb_i2c_init(struct usb_dibusb *dib) +{ + int ret = 0; + + dib->adapter->priv = dib; + + strncpy(dib->i2c_adap.name,dib->dibdev->name,I2C_NAME_SIZE); +#ifdef I2C_ADAP_CLASS_TV_DIGITAL + dib->i2c_adap.class = I2C_ADAP_CLASS_TV_DIGITAL, +#else + dib->i2c_adap.class = I2C_CLASS_TV_DIGITAL, +#endif + dib->i2c_adap.algo = &dibusb_algo; + dib->i2c_adap.algo_data = NULL; + dib->i2c_adap.id = I2C_ALGO_BIT; + + i2c_set_adapdata(&dib->i2c_adap, dib); + + if ((ret = i2c_add_adapter(&dib->i2c_adap)) < 0) + err("could not add i2c adapter"); + + dib->init_state |= DIBUSB_STATE_I2C; + + return ret; +} + +int dibusb_i2c_exit(struct usb_dibusb *dib) +{ + if (dib->init_state & DIBUSB_STATE_I2C) + i2c_del_adapter(&dib->i2c_adap); + dib->init_state &= ~DIBUSB_STATE_I2C; + return 0; +} + + +/* pll stuff, maybe removed soon (thx to Gerd/Andrew in advance) */ +static int thomson_cable_eu_pll_set(struct dvb_frontend_parameters *fep, u8 pllbuf[4]) +{ + u32 tfreq = (fep->frequency + 36125000) / 62500; + int vu,p0,p1,p2; + + if (fep->frequency > 403250000) + vu = 1, p2 = 1, p1 = 0, p0 = 1; + else if (fep->frequency > 115750000) + vu = 0, p2 = 1, p1 = 1, p0 = 0; + else if (fep->frequency > 44250000) + vu = 0, p2 = 0, p1 = 1, p0 = 1; + else + return -EINVAL; + + pllbuf[0] = (tfreq >> 8) & 0x7f; + pllbuf[1] = tfreq & 0xff; + pllbuf[2] = 0x8e; + pllbuf[3] = (vu << 7) | (p2 << 2) | (p1 << 1) | p0; + return 0; +} + +static int panasonic_cofdm_env57h1xd5_pll_set(struct dvb_frontend_parameters *fep, u8 pllbuf[4]) +{ + u32 freq_khz = fep->frequency / 1000; + u32 tfreq = ((freq_khz + 36125)*6 + 500) / 1000; + u8 TA, T210, R210, ctrl1, cp210, p4321; + if (freq_khz > 858000) { + err("frequency cannot be larger than 858 MHz."); + return -EINVAL; + } + + // contol data 1 : 1 | T/A=1 | T2,T1,T0 = 0,0,0 | R2,R1,R0 = 0,1,0 + TA = 1; + T210 = 0; + R210 = 0x2; + ctrl1 = (1 << 7) | (TA << 6) | (T210 << 3) | R210; + +// ******** CHARGE PUMP CONFIG vs RF FREQUENCIES ***************** + if (freq_khz < 470000) + cp210 = 2; // VHF Low and High band ch E12 to E4 to E12 + else if (freq_khz < 526000) + cp210 = 4; // UHF band Ch E21 to E27 + else // if (freq < 862000000) + cp210 = 5; // UHF band ch E28 to E69 + +//********************* BW select ******************************* + if (freq_khz < 153000) + p4321 = 1; // BW selected for VHF low + else if (freq_khz < 470000) + p4321 = 2; // BW selected for VHF high E5 to E12 + else // if (freq < 862000000) + p4321 = 4; // BW selection for UHF E21 to E69 + + pllbuf[0] = (tfreq >> 8) & 0xff; + pllbuf[1] = (tfreq >> 0) & 0xff; + pllbuf[2] = 0xff & ctrl1; + pllbuf[3] = (cp210 << 5) | (p4321); + + return 0; +} + +/* + * 7 6 5 4 3 2 1 0 + * Address Byte 1 1 0 0 0 MA1 MA0 R/~W=0 + * + * Program divider byte 1 0 n14 n13 n12 n11 n10 n9 n8 + * Program divider byte 2 n7 n6 n5 n4 n3 n2 n1 n0 + * + * Control byte 1 1 T/A=1 T2 T1 T0 R2 R1 R0 + * 1 T/A=0 0 0 ATC AL2 AL1 AL0 + * + * Control byte 2 CP2 CP1 CP0 BS5 BS4 BS3 BS2 BS1 + * + * MA0/1 = programmable address bits + * R/~W = read/write bit (0 for writing) + * N14-0 = programmable LO frequency + * + * T/A = test AGC bit (0 = next 6 bits AGC setting, + * 1 = next 6 bits test and reference divider ratio settings) + * T2-0 = test bits + * R2-0 = reference divider ratio and programmable frequency step + * ATC = AGC current setting and time constant + * ATC = 0: AGC current = 220nA, AGC time constant = 2s + * ATC = 1: AGC current = 9uA, AGC time constant = 50ms + * AL2-0 = AGC take-over point bits + * CP2-0 = charge pump current + * BS5-1 = PMOS ports control bits; + * BSn = 0 corresponding port is off, high-impedance state (at power-on) + * BSn = 1 corresponding port is on + */ +static int panasonic_cofdm_env77h11d5_tda6650_init(struct dvb_frontend *fe, u8 pllbuf[4]) +{ + pllbuf[0] = 0x0b; + pllbuf[1] = 0xf5; + pllbuf[2] = 0x85; + pllbuf[3] = 0xab; + return 0; +} + +static int panasonic_cofdm_env77h11d5_tda6650_set (struct dvb_frontend_parameters *fep,u8 pllbuf[4]) +{ + int tuner_frequency = 0; + u8 band, cp, filter; + + // determine charge pump + tuner_frequency = fep->frequency + 36166000; + if (tuner_frequency < 87000000) + return -EINVAL; + else if (tuner_frequency < 130000000) + cp = 3; + else if (tuner_frequency < 160000000) + cp = 5; + else if (tuner_frequency < 200000000) + cp = 6; + else if (tuner_frequency < 290000000) + cp = 3; + else if (tuner_frequency < 420000000) + cp = 5; + else if (tuner_frequency < 480000000) + cp = 6; + else if (tuner_frequency < 620000000) + cp = 3; + else if (tuner_frequency < 830000000) + cp = 5; + else if (tuner_frequency < 895000000) + cp = 7; + else + return -EINVAL; + + // determine band + if (fep->frequency < 49000000) + return -EINVAL; + else if (fep->frequency < 161000000) + band = 1; + else if (fep->frequency < 444000000) + band = 2; + else if (fep->frequency < 861000000) + band = 4; + else + return -EINVAL; + + // setup PLL filter + switch (fep->u.ofdm.bandwidth) { + case BANDWIDTH_6_MHZ: + case BANDWIDTH_7_MHZ: + filter = 0; + break; + case BANDWIDTH_8_MHZ: + filter = 1; + break; + default: + return -EINVAL; + } + + // calculate divisor + // ((36166000+((1000000/6)/2)) + Finput)/(1000000/6) + tuner_frequency = (((fep->frequency / 1000) * 6) + 217496) / 1000; + + // setup tuner buffer + pllbuf[0] = (tuner_frequency >> 8) & 0x7f; + pllbuf[1] = tuner_frequency & 0xff; + pllbuf[2] = 0xca; + pllbuf[3] = (cp << 5) | (filter << 3) | band; + return 0; +} + +/* + * 7 6 5 4 3 2 1 0 + * Address Byte 1 1 0 0 0 MA1 MA0 R/~W=0 + * + * Program divider byte 1 0 n14 n13 n12 n11 n10 n9 n8 + * Program divider byte 2 n7 n6 n5 n4 n3 n2 n1 n0 + * + * Control byte 1 CP T2 T1 T0 RSA RSB OS + * + * Band Switch byte X X X P4 P3 P2 P1 P0 + * + * Auxiliary byte ATC AL2 AL1 AL0 0 0 0 0 + * + * Address: MA1 MA0 Address + * 0 0 c0 + * 0 1 c2 (always valid) + * 1 0 c4 + * 1 1 c6 + */ +static int lg_tdtp_e102p_tua6034(struct dvb_frontend_parameters* fep, u8 pllbuf[4]) +{ + u32 div; + u8 p210, p3; + +#define TUNER_MUL 62500 + + div = (fep->frequency + 36125000 + TUNER_MUL / 2) / TUNER_MUL; +// div = ((fep->frequency/1000 + 36166) * 6) / 1000; + + if (fep->frequency < 174500000) + p210 = 1; // not supported by the tdtp_e102p + else if (fep->frequency < 230000000) // VHF + p210 = 2; + else + p210 = 4; + + if (fep->u.ofdm.bandwidth == BANDWIDTH_7_MHZ) + p3 = 0; + else + p3 = 1; + + pllbuf[0] = (div >> 8) & 0x7f; + pllbuf[1] = div & 0xff; + pllbuf[2] = 0xce; +// pllbuf[2] = 0xcc; + pllbuf[3] = (p3 << 3) | p210; + + return 0; +} + +static int lg_tdtp_e102p_mt352_demod_init(struct dvb_frontend *fe) +{ + static u8 mt352_clock_config[] = { 0x89, 0xb8, 0x2d }; + static u8 mt352_reset[] = { 0x50, 0x80 }; + static u8 mt352_mclk_ratio[] = { 0x8b, 0x00 }; + static u8 mt352_adc_ctl_1_cfg[] = { 0x8E, 0x40 }; + static u8 mt352_agc_cfg[] = { 0x67, 0x10, 0xa0 }; + + static u8 mt352_sec_agc_cfg1[] = { 0x6a, 0xff }; + static u8 mt352_sec_agc_cfg2[] = { 0x6d, 0xff }; + static u8 mt352_sec_agc_cfg3[] = { 0x70, 0x40 }; + static u8 mt352_sec_agc_cfg4[] = { 0x7b, 0x03 }; + static u8 mt352_sec_agc_cfg5[] = { 0x7d, 0x0f }; + + static u8 mt352_acq_ctl[] = { 0x53, 0x50 }; + static u8 mt352_input_freq_1[] = { 0x56, 0x31, 0x06 }; + + mt352_write(fe, mt352_clock_config, sizeof(mt352_clock_config)); + udelay(2000); + mt352_write(fe, mt352_reset, sizeof(mt352_reset)); + mt352_write(fe, mt352_mclk_ratio, sizeof(mt352_mclk_ratio)); + + mt352_write(fe, mt352_adc_ctl_1_cfg, sizeof(mt352_adc_ctl_1_cfg)); + mt352_write(fe, mt352_agc_cfg, sizeof(mt352_agc_cfg)); + + mt352_write(fe, mt352_sec_agc_cfg1, sizeof(mt352_sec_agc_cfg1)); + mt352_write(fe, mt352_sec_agc_cfg2, sizeof(mt352_sec_agc_cfg2)); + mt352_write(fe, mt352_sec_agc_cfg3, sizeof(mt352_sec_agc_cfg3)); + mt352_write(fe, mt352_sec_agc_cfg4, sizeof(mt352_sec_agc_cfg4)); + mt352_write(fe, mt352_sec_agc_cfg5, sizeof(mt352_sec_agc_cfg5)); + + mt352_write(fe, mt352_acq_ctl, sizeof(mt352_acq_ctl)); + mt352_write(fe, mt352_input_freq_1, sizeof(mt352_input_freq_1)); + + return 0; +} + +static int dibusb_general_demod_init(struct dvb_frontend *fe) +{ + struct usb_dibusb* dib = (struct usb_dibusb*) fe->dvb->priv; + switch (dib->dibdev->dev_cl->id) { + case UMT2_0: + return lg_tdtp_e102p_mt352_demod_init(fe); + default: /* other device classes do not have device specific demod inits */ + break; + } + return 0; +} + +static u8 dibusb_general_pll_addr(struct dvb_frontend *fe) +{ + struct usb_dibusb* dib = (struct usb_dibusb*) fe->dvb->priv; + return dib->tuner->pll_addr; +} + +static int dibusb_pll_i2c_helper(struct usb_dibusb *dib, u8 pll_buf[5], u8 buf[4]) +{ + if (pll_buf == NULL) { + struct i2c_msg msg = { + .addr = dib->tuner->pll_addr, + .flags = 0, + .buf = buf, + .len = sizeof(buf) + }; + if (i2c_transfer (&dib->i2c_adap, &msg, 1) != 1) + return -EIO; + msleep(1); + } else { + pll_buf[0] = dib->tuner->pll_addr << 1; + memcpy(&pll_buf[1],buf,4); + } + + return 0; +} + +static int dibusb_general_pll_init(struct dvb_frontend *fe, + u8 pll_buf[5]) +{ + struct usb_dibusb* dib = (struct usb_dibusb*) fe->dvb->priv; + u8 buf[4]; + int ret=0; + switch (dib->tuner->id) { + case DIBUSB_TUNER_COFDM_PANASONIC_ENV77H11D5: + ret = panasonic_cofdm_env77h11d5_tda6650_init(fe,buf); + break; + default: + break; + } + + if (ret) + return ret; + + return dibusb_pll_i2c_helper(dib,pll_buf,buf); +} + +static int dibusb_general_pll_set(struct dvb_frontend *fe, + struct dvb_frontend_parameters *fep, u8 pll_buf[5]) +{ + struct usb_dibusb* dib = (struct usb_dibusb*) fe->dvb->priv; + u8 buf[4]; + int ret=0; + + switch (dib->tuner->id) { + case DIBUSB_TUNER_CABLE_THOMSON: + ret = thomson_cable_eu_pll_set(fep, buf); + break; + case DIBUSB_TUNER_COFDM_PANASONIC_ENV57H1XD5: + ret = panasonic_cofdm_env57h1xd5_pll_set(fep, buf); + break; + case DIBUSB_TUNER_CABLE_LG_TDTP_E102P: + ret = lg_tdtp_e102p_tua6034(fep, buf); + break; + case DIBUSB_TUNER_COFDM_PANASONIC_ENV77H11D5: + ret = panasonic_cofdm_env77h11d5_tda6650_set(fep,buf); + break; + default: + warn("no pll programming routine found for tuner %d.\n",dib->tuner->id); + ret = -ENODEV; + break; + } + + if (ret) + return ret; + + return dibusb_pll_i2c_helper(dib,pll_buf,buf); +} diff --git a/drivers/media/dvb/dibusb/dvb-dibusb-firmware.c b/drivers/media/dvb/dibusb/dvb-dibusb-firmware.c new file mode 100644 index 000000000000..504ba47afdf3 --- /dev/null +++ b/drivers/media/dvb/dibusb/dvb-dibusb-firmware.c @@ -0,0 +1,87 @@ +/* + * dvb-dibusb-firmware.c is part of the driver for mobile USB Budget DVB-T devices + * based on reference design made by DiBcom (http://www.dibcom.fr/) + * + * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@desy.de) + * + * see dvb-dibusb-core.c for more copyright details. + * + * This file contains functions for downloading the firmware to the device. + */ +#include "dvb-dibusb.h" + +#include <linux/firmware.h> +#include <linux/usb.h> + +/* + * load a firmware packet to the device + */ +static int dibusb_writemem(struct usb_device *udev,u16 addr,u8 *data, u8 len) +{ + return usb_control_msg(udev, usb_sndctrlpipe(udev,0), + 0xa0, USB_TYPE_VENDOR, addr, 0x00, data, len, 5000); +} + +int dibusb_loadfirmware(struct usb_device *udev, struct dibusb_usb_device *dibdev) +{ + const struct firmware *fw = NULL; + u16 addr; + u8 *b,*p; + int ret = 0,i; + + if ((ret = request_firmware(&fw, dibdev->dev_cl->firmware, &udev->dev)) != 0) { + err("did not find the firmware file. (%s) " + "Please see linux/Documentation/dvb/ for more details on firmware-problems.", + dibdev->dev_cl->firmware); + return ret; + } + + info("downloading firmware from file '%s'.",dibdev->dev_cl->firmware); + + p = kmalloc(fw->size,GFP_KERNEL); + if (p != NULL) { + u8 reset; + /* + * you cannot use the fw->data as buffer for + * usb_control_msg, a new buffer has to be + * created + */ + memcpy(p,fw->data,fw->size); + + /* stop the CPU */ + reset = 1; + if ((ret = dibusb_writemem(udev,dibdev->dev_cl->usb_ctrl->cpu_cs_register,&reset,1)) != 1) + err("could not stop the USB controller CPU."); + for(i = 0; p[i+3] == 0 && i < fw->size; ) { + b = (u8 *) &p[i]; + addr = *((u16 *) &b[1]); + + ret = dibusb_writemem(udev,addr,&b[4],b[0]); + + if (ret != b[0]) { + err("error while transferring firmware " + "(transferred size: %d, block size: %d)", + ret,b[0]); + ret = -EINVAL; + break; + } + i += 5 + b[0]; + } + /* length in ret */ + if (ret > 0) + ret = 0; + /* restart the CPU */ + reset = 0; + if (ret || dibusb_writemem(udev,dibdev->dev_cl->usb_ctrl->cpu_cs_register,&reset,1) != 1) { + err("could not restart the USB controller CPU."); + ret = -EINVAL; + } + + kfree(p); + } else { + ret = -ENOMEM; + } + release_firmware(fw); + + return ret; +} diff --git a/drivers/media/dvb/dibusb/dvb-dibusb-remote.c b/drivers/media/dvb/dibusb/dvb-dibusb-remote.c new file mode 100644 index 000000000000..9dc8b15517b7 --- /dev/null +++ b/drivers/media/dvb/dibusb/dvb-dibusb-remote.c @@ -0,0 +1,316 @@ +/* + * dvb-dibusb-remote.c is part of the driver for mobile USB Budget DVB-T devices + * based on reference design made by DiBcom (http://www.dibcom.fr/) + * + * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@desy.de) + * + * see dvb-dibusb-core.c for more copyright details. + * + * This file contains functions for handling the event device on the software + * side and the remote control on the hardware side. + */ +#include "dvb-dibusb.h" + +/* Table to map raw key codes to key events. This should not be hard-wired + into the kernel. */ +static const struct { u8 c0, c1, c2; uint32_t key; } nec_rc_keys [] = +{ + /* Key codes for the little Artec T1/Twinhan/HAMA/ remote. */ + { 0x00, 0xff, 0x16, KEY_POWER }, + { 0x00, 0xff, 0x10, KEY_MUTE }, + { 0x00, 0xff, 0x03, KEY_1 }, + { 0x00, 0xff, 0x01, KEY_2 }, + { 0x00, 0xff, 0x06, KEY_3 }, + { 0x00, 0xff, 0x09, KEY_4 }, + { 0x00, 0xff, 0x1d, KEY_5 }, + { 0x00, 0xff, 0x1f, KEY_6 }, + { 0x00, 0xff, 0x0d, KEY_7 }, + { 0x00, 0xff, 0x19, KEY_8 }, + { 0x00, 0xff, 0x1b, KEY_9 }, + { 0x00, 0xff, 0x15, KEY_0 }, + { 0x00, 0xff, 0x05, KEY_CHANNELUP }, + { 0x00, 0xff, 0x02, KEY_CHANNELDOWN }, + { 0x00, 0xff, 0x1e, KEY_VOLUMEUP }, + { 0x00, 0xff, 0x0a, KEY_VOLUMEDOWN }, + { 0x00, 0xff, 0x11, KEY_RECORD }, + { 0x00, 0xff, 0x17, KEY_FAVORITES }, /* Heart symbol - Channel list. */ + { 0x00, 0xff, 0x14, KEY_PLAY }, + { 0x00, 0xff, 0x1a, KEY_STOP }, + { 0x00, 0xff, 0x40, KEY_REWIND }, + { 0x00, 0xff, 0x12, KEY_FASTFORWARD }, + { 0x00, 0xff, 0x0e, KEY_PREVIOUS }, /* Recall - Previous channel. */ + { 0x00, 0xff, 0x4c, KEY_PAUSE }, + { 0x00, 0xff, 0x4d, KEY_SCREEN }, /* Full screen mode. */ + { 0x00, 0xff, 0x54, KEY_AUDIO }, /* MTS - Switch to secondary audio. */ + /* additional keys TwinHan VisionPlus, the Artec seemingly not have */ + { 0x00, 0xff, 0x0c, KEY_CANCEL }, /* Cancel */ + { 0x00, 0xff, 0x1c, KEY_EPG }, /* EPG */ + { 0x00, 0xff, 0x00, KEY_TAB }, /* Tab */ + { 0x00, 0xff, 0x48, KEY_INFO }, /* Preview */ + { 0x00, 0xff, 0x04, KEY_LIST }, /* RecordList */ + { 0x00, 0xff, 0x0f, KEY_TEXT }, /* Teletext */ + /* Key codes for the KWorld/ADSTech/JetWay remote. */ + { 0x86, 0x6b, 0x12, KEY_POWER }, + { 0x86, 0x6b, 0x0f, KEY_SELECT }, /* source */ + { 0x86, 0x6b, 0x0c, KEY_UNKNOWN }, /* scan */ + { 0x86, 0x6b, 0x0b, KEY_EPG }, + { 0x86, 0x6b, 0x10, KEY_MUTE }, + { 0x86, 0x6b, 0x01, KEY_1 }, + { 0x86, 0x6b, 0x02, KEY_2 }, + { 0x86, 0x6b, 0x03, KEY_3 }, + { 0x86, 0x6b, 0x04, KEY_4 }, + { 0x86, 0x6b, 0x05, KEY_5 }, + { 0x86, 0x6b, 0x06, KEY_6 }, + { 0x86, 0x6b, 0x07, KEY_7 }, + { 0x86, 0x6b, 0x08, KEY_8 }, + { 0x86, 0x6b, 0x09, KEY_9 }, + { 0x86, 0x6b, 0x0a, KEY_0 }, + { 0x86, 0x6b, 0x18, KEY_ZOOM }, + { 0x86, 0x6b, 0x1c, KEY_UNKNOWN }, /* preview */ + { 0x86, 0x6b, 0x13, KEY_UNKNOWN }, /* snap */ + { 0x86, 0x6b, 0x00, KEY_UNDO }, + { 0x86, 0x6b, 0x1d, KEY_RECORD }, + { 0x86, 0x6b, 0x0d, KEY_STOP }, + { 0x86, 0x6b, 0x0e, KEY_PAUSE }, + { 0x86, 0x6b, 0x16, KEY_PLAY }, + { 0x86, 0x6b, 0x11, KEY_BACK }, + { 0x86, 0x6b, 0x19, KEY_FORWARD }, + { 0x86, 0x6b, 0x14, KEY_UNKNOWN }, /* pip */ + { 0x86, 0x6b, 0x15, KEY_ESC }, + { 0x86, 0x6b, 0x1a, KEY_UP }, + { 0x86, 0x6b, 0x1e, KEY_DOWN }, + { 0x86, 0x6b, 0x1f, KEY_LEFT }, + { 0x86, 0x6b, 0x1b, KEY_RIGHT }, +}; + +/* Hauppauge NOVA-T USB2 keys */ +static const struct { u16 raw; uint32_t key; } haupp_rc_keys [] = { + { 0xddf, KEY_GOTO }, + { 0xdef, KEY_POWER }, + { 0xce7, KEY_TV }, + { 0xcc7, KEY_VIDEO }, + { 0xccf, KEY_AUDIO }, + { 0xcd7, KEY_MEDIA }, + { 0xcdf, KEY_EPG }, + { 0xca7, KEY_UP }, + { 0xc67, KEY_RADIO }, + { 0xcb7, KEY_LEFT }, + { 0xd2f, KEY_OK }, + { 0xcbf, KEY_RIGHT }, + { 0xcff, KEY_BACK }, + { 0xcaf, KEY_DOWN }, + { 0xc6f, KEY_MENU }, + { 0xc87, KEY_VOLUMEUP }, + { 0xc8f, KEY_VOLUMEDOWN }, + { 0xc97, KEY_CHANNEL }, + { 0xc7f, KEY_MUTE }, + { 0xd07, KEY_CHANNELUP }, + { 0xd0f, KEY_CHANNELDOWN }, + { 0xdbf, KEY_RECORD }, + { 0xdb7, KEY_STOP }, + { 0xd97, KEY_REWIND }, + { 0xdaf, KEY_PLAY }, + { 0xda7, KEY_FASTFORWARD }, + { 0xd27, KEY_LAST }, /* Skip backwards */ + { 0xd87, KEY_PAUSE }, + { 0xcf7, KEY_NEXT }, + { 0xc07, KEY_0 }, + { 0xc0f, KEY_1 }, + { 0xc17, KEY_2 }, + { 0xc1f, KEY_3 }, + { 0xc27, KEY_4 }, + { 0xc2f, KEY_5 }, + { 0xc37, KEY_6 }, + { 0xc3f, KEY_7 }, + { 0xc47, KEY_8 }, + { 0xc4f, KEY_9 }, + { 0xc57, KEY_KPASTERISK }, + { 0xc77, KEY_GRAVE }, /* # */ + { 0xc5f, KEY_RED }, + { 0xd77, KEY_GREEN }, + { 0xdc7, KEY_YELLOW }, + { 0xd4f, KEY_BLUE}, +}; + +static int dibusb_key2event_nec(struct usb_dibusb *dib,u8 rb[5]) +{ + int i; + switch (rb[0]) { + case DIBUSB_RC_NEC_KEY_PRESSED: + /* rb[1-3] is the actual key, rb[4] is a checksum */ + deb_rc("raw key code 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", + rb[1], rb[2], rb[3], rb[4]); + + if ((0xff - rb[3]) != rb[4]) { + deb_rc("remote control checksum failed.\n"); + break; + } + + /* See if we can match the raw key code. */ + for (i = 0; i < sizeof(nec_rc_keys)/sizeof(nec_rc_keys[0]); i++) { + if (nec_rc_keys[i].c0 == rb[1] && + nec_rc_keys[i].c1 == rb[2] && + nec_rc_keys[i].c2 == rb[3]) { + + dib->last_event = nec_rc_keys[i].key; + return 1; + } + } + break; + case DIBUSB_RC_NEC_KEY_REPEATED: + /* rb[1]..rb[4] are always zero.*/ + /* Repeats often seem to occur so for the moment just ignore this. */ + return 0; + case DIBUSB_RC_NEC_EMPTY: /* No (more) remote control keys. */ + default: + break; + } + return -1; +} + +static int dibusb_key2event_hauppauge(struct usb_dibusb *dib,u8 rb[4]) +{ + u16 raw; + int i,state; + switch (rb[0]) { + case DIBUSB_RC_HAUPPAUGE_KEY_PRESSED: + raw = ((rb[1] & 0x0f) << 8) | rb[2]; + + state = !!(rb[1] & 0x40); + + deb_rc("raw key code 0x%02x, 0x%02x, 0x%02x to %04x state: %d\n",rb[1],rb[2],rb[3],raw,state); + for (i = 0; i < sizeof(haupp_rc_keys)/sizeof(haupp_rc_keys[0]); i++) { + if (haupp_rc_keys[i].raw == raw) { + if (dib->last_event == haupp_rc_keys[i].key && + dib->last_state == state) { + deb_rc("key repeat\n"); + return 0; + } else { + dib->last_event = haupp_rc_keys[i].key; + dib->last_state = state; + return 1; + } + } + } + + break; + case DIBUSB_RC_HAUPPAUGE_KEY_EMPTY: + default: + break; + } + return -1; +} + +/* + * Read the remote control and feed the appropriate event. + * NEC protocol is used for remote controls + */ +static int dibusb_read_remote_control(struct usb_dibusb *dib) +{ + u8 b[1] = { DIBUSB_REQ_POLL_REMOTE }, rb[5]; + int ret,event = 0; + + if ((ret = dibusb_readwrite_usb(dib,b,1,rb,5))) + return ret; + + switch (dib->dibdev->dev_cl->remote_type) { + case DIBUSB_RC_NEC_PROTOCOL: + event = dibusb_key2event_nec(dib,rb); + break; + case DIBUSB_RC_HAUPPAUGE_PROTO: + event = dibusb_key2event_hauppauge(dib,rb); + default: + break; + } + + /* key repeat */ + if (event == 0) + if (++dib->repeat_key_count < dib->rc_key_repeat_count) { + deb_rc("key repeat dropped. (%d)\n",dib->repeat_key_count); + event = -1; /* skip this key repeat */ + } + + if (event == 1 || event == 0) { + deb_rc("Translated key 0x%04x\n",event); + + /* Signal down and up events for this key. */ + input_report_key(&dib->rc_input_dev, dib->last_event, 1); + input_report_key(&dib->rc_input_dev, dib->last_event, 0); + input_sync(&dib->rc_input_dev); + + if (event == 1) + dib->repeat_key_count = 0; + } + return 0; +} + +/* Remote-control poll function - called every dib->rc_query_interval ms to see + whether the remote control has received anything. */ +static void dibusb_remote_query(void *data) +{ + struct usb_dibusb *dib = (struct usb_dibusb *) data; + /* TODO: need a lock here. We can simply skip checking for the remote control + if we're busy. */ + dibusb_read_remote_control(dib); + schedule_delayed_work(&dib->rc_query_work, + msecs_to_jiffies(dib->rc_query_interval)); +} + +int dibusb_remote_init(struct usb_dibusb *dib) +{ + int i; + + if (dib->dibdev->dev_cl->remote_type == DIBUSB_RC_NO) + return 0; + + /* Initialise the remote-control structures.*/ + init_input_dev(&dib->rc_input_dev); + + dib->rc_input_dev.evbit[0] = BIT(EV_KEY); + dib->rc_input_dev.keycodesize = sizeof(unsigned char); + dib->rc_input_dev.keycodemax = KEY_MAX; + dib->rc_input_dev.name = DRIVER_DESC " remote control"; + + switch (dib->dibdev->dev_cl->remote_type) { + case DIBUSB_RC_NEC_PROTOCOL: + for (i=0; i<sizeof(nec_rc_keys)/sizeof(nec_rc_keys[0]); i++) + set_bit(nec_rc_keys[i].key, dib->rc_input_dev.keybit); + break; + case DIBUSB_RC_HAUPPAUGE_PROTO: + for (i=0; i<sizeof(haupp_rc_keys)/sizeof(haupp_rc_keys[0]); i++) + set_bit(haupp_rc_keys[i].key, dib->rc_input_dev.keybit); + break; + default: + break; + } + + + input_register_device(&dib->rc_input_dev); + + INIT_WORK(&dib->rc_query_work, dibusb_remote_query, dib); + + /* Start the remote-control polling. */ + if (dib->rc_query_interval < 40) + dib->rc_query_interval = 100; /* default */ + + info("schedule remote query interval to %d msecs.",dib->rc_query_interval); + schedule_delayed_work(&dib->rc_query_work,msecs_to_jiffies(dib->rc_query_interval)); + + dib->init_state |= DIBUSB_STATE_REMOTE; + + return 0; +} + +int dibusb_remote_exit(struct usb_dibusb *dib) +{ + if (dib->dibdev->dev_cl->remote_type == DIBUSB_RC_NO) + return 0; + + if (dib->init_state & DIBUSB_STATE_REMOTE) { + cancel_delayed_work(&dib->rc_query_work); + flush_scheduled_work(); + input_unregister_device(&dib->rc_input_dev); + } + dib->init_state &= ~DIBUSB_STATE_REMOTE; + return 0; +} diff --git a/drivers/media/dvb/dibusb/dvb-dibusb-usb.c b/drivers/media/dvb/dibusb/dvb-dibusb-usb.c new file mode 100644 index 000000000000..642f0596a5ba --- /dev/null +++ b/drivers/media/dvb/dibusb/dvb-dibusb-usb.c @@ -0,0 +1,303 @@ +/* + * dvb-dibusb-usb.c is part of the driver for mobile USB Budget DVB-T devices + * based on reference design made by DiBcom (http://www.dibcom.fr/) + * + * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@desy.de) + * + * see dvb-dibusb-core.c for more copyright details. + * + * This file contains functions for initializing and handling the + * usb specific stuff. + */ +#include "dvb-dibusb.h" + +#include <linux/version.h> +#include <linux/pci.h> + +int dibusb_readwrite_usb(struct usb_dibusb *dib, u8 *wbuf, u16 wlen, u8 *rbuf, + u16 rlen) +{ + int actlen,ret = -ENOMEM; + + if (wbuf == NULL || wlen == 0) + return -EINVAL; + + if ((ret = down_interruptible(&dib->usb_sem))) + return ret; + + debug_dump(wbuf,wlen); + + ret = usb_bulk_msg(dib->udev,usb_sndbulkpipe(dib->udev, + dib->dibdev->dev_cl->pipe_cmd), wbuf,wlen,&actlen, + DIBUSB_I2C_TIMEOUT); + + if (ret) + err("bulk message failed: %d (%d/%d)",ret,wlen,actlen); + else + ret = actlen != wlen ? -1 : 0; + + /* an answer is expected, and no error before */ + if (!ret && rbuf && rlen) { + ret = usb_bulk_msg(dib->udev,usb_rcvbulkpipe(dib->udev, + dib->dibdev->dev_cl->pipe_cmd),rbuf,rlen,&actlen, + DIBUSB_I2C_TIMEOUT); + + if (ret) + err("recv bulk message failed: %d",ret); + else { + deb_alot("rlen: %d\n",rlen); + debug_dump(rbuf,actlen); + } + } + + up(&dib->usb_sem); + return ret; +} + +/* + * Cypress controls + */ +int dibusb_write_usb(struct usb_dibusb *dib, u8 *buf, u16 len) +{ + return dibusb_readwrite_usb(dib,buf,len,NULL,0); +} + +#if 0 +/* + * #if 0'ing the following functions as they are not in use _now_, + * but probably will be sometime. + */ +/* + * do not use this, just a workaround for a bug, + * which will hopefully never occur :). + */ +int dibusb_interrupt_read_loop(struct usb_dibusb *dib) +{ + u8 b[1] = { DIBUSB_REQ_INTR_READ }; + return dibusb_write_usb(dib,b,1); +} +#endif + +/* + * ioctl for the firmware + */ +static int dibusb_ioctl_cmd(struct usb_dibusb *dib, u8 cmd, u8 *param, int plen) +{ + u8 b[34]; + int size = plen > 32 ? 32 : plen; + memset(b,0,34); + b[0] = DIBUSB_REQ_SET_IOCTL; + b[1] = cmd; + + if (size > 0) + memcpy(&b[2],param,size); + + return dibusb_write_usb(dib,b,34); //2+size); +} + +/* + * ioctl for power control + */ +int dibusb_hw_wakeup(struct dvb_frontend *fe) +{ + struct usb_dibusb *dib = (struct usb_dibusb *) fe->dvb->priv; + u8 b[1] = { DIBUSB_IOCTL_POWER_WAKEUP }; + deb_info("dibusb-device is getting up.\n"); + + switch (dib->dibdev->dev_cl->id) { + case DTT200U: + break; + default: + dibusb_ioctl_cmd(dib,DIBUSB_IOCTL_CMD_POWER_MODE, b,1); + break; + } + + if (dib->fe_init) + return dib->fe_init(fe); + + return 0; +} + +int dibusb_hw_sleep(struct dvb_frontend *fe) +{ + struct usb_dibusb *dib = (struct usb_dibusb *) fe->dvb->priv; + u8 b[1] = { DIBUSB_IOCTL_POWER_SLEEP }; + deb_info("dibusb-device is going to bed.\n"); + /* workaround, something is wrong, when dibusb 1.1 device are going to bed too late */ + switch (dib->dibdev->dev_cl->id) { + case DIBUSB1_1: + case NOVAT_USB2: + case DTT200U: + break; + default: + dibusb_ioctl_cmd(dib,DIBUSB_IOCTL_CMD_POWER_MODE, b,1); + break; + } + if (dib->fe_sleep) + return dib->fe_sleep(fe); + + return 0; +} + +int dibusb_set_streaming_mode(struct usb_dibusb *dib,u8 mode) +{ + u8 b[2] = { DIBUSB_REQ_SET_STREAMING_MODE, mode }; + return dibusb_readwrite_usb(dib,b,2,NULL,0); +} + +static int dibusb_urb_kill(struct usb_dibusb *dib) +{ + int i; +deb_info("trying to kill urbs\n"); + if (dib->init_state & DIBUSB_STATE_URB_SUBMIT) { + for (i = 0; i < dib->dibdev->dev_cl->urb_count; i++) { + deb_info("killing URB no. %d.\n",i); + + /* stop the URB */ + usb_kill_urb(dib->urb_list[i]); + } + } else + deb_info(" URBs not killed.\n"); + dib->init_state &= ~DIBUSB_STATE_URB_SUBMIT; + return 0; +} + +static int dibusb_urb_submit(struct usb_dibusb *dib) +{ + int i,ret; + if (dib->init_state & DIBUSB_STATE_URB_INIT) { + for (i = 0; i < dib->dibdev->dev_cl->urb_count; i++) { + deb_info("submitting URB no. %d\n",i); + if ((ret = usb_submit_urb(dib->urb_list[i],GFP_ATOMIC))) { + err("could not submit buffer urb no. %d - get them all back\n",i); + dibusb_urb_kill(dib); + return ret; + } + dib->init_state |= DIBUSB_STATE_URB_SUBMIT; + } + } + return 0; +} + +int dibusb_streaming(struct usb_dibusb *dib,int onoff) +{ + if (onoff) + dibusb_urb_submit(dib); + else + dibusb_urb_kill(dib); + + switch (dib->dibdev->dev_cl->id) { + case DIBUSB2_0: + case DIBUSB2_0B: + case NOVAT_USB2: + case UMT2_0: + if (onoff) + return dibusb_ioctl_cmd(dib,DIBUSB_IOCTL_CMD_ENABLE_STREAM,NULL,0); + else + return dibusb_ioctl_cmd(dib,DIBUSB_IOCTL_CMD_DISABLE_STREAM,NULL,0); + break; + default: + break; + } + return 0; +} + +int dibusb_urb_init(struct usb_dibusb *dib) +{ + int i,bufsize,def_pid_parse = 1; + + /* + * when reloading the driver w/o replugging the device + * a timeout occures, this helps + */ + usb_clear_halt(dib->udev,usb_sndbulkpipe(dib->udev,dib->dibdev->dev_cl->pipe_cmd)); + usb_clear_halt(dib->udev,usb_rcvbulkpipe(dib->udev,dib->dibdev->dev_cl->pipe_cmd)); + usb_clear_halt(dib->udev,usb_rcvbulkpipe(dib->udev,dib->dibdev->dev_cl->pipe_data)); + + /* allocate the array for the data transfer URBs */ + dib->urb_list = kmalloc(dib->dibdev->dev_cl->urb_count*sizeof(struct urb *),GFP_KERNEL); + if (dib->urb_list == NULL) + return -ENOMEM; + memset(dib->urb_list,0,dib->dibdev->dev_cl->urb_count*sizeof(struct urb *)); + + dib->init_state |= DIBUSB_STATE_URB_LIST; + + bufsize = dib->dibdev->dev_cl->urb_count*dib->dibdev->dev_cl->urb_buffer_size; + deb_info("allocate %d bytes as buffersize for all URBs\n",bufsize); + /* allocate the actual buffer for the URBs */ + if ((dib->buffer = pci_alloc_consistent(NULL,bufsize,&dib->dma_handle)) == NULL) { + deb_info("not enough memory.\n"); + return -ENOMEM; + } + deb_info("allocation complete\n"); + memset(dib->buffer,0,bufsize); + + dib->init_state |= DIBUSB_STATE_URB_BUF; + + /* allocate and submit the URBs */ + for (i = 0; i < dib->dibdev->dev_cl->urb_count; i++) { + if (!(dib->urb_list[i] = usb_alloc_urb(0,GFP_ATOMIC))) { + return -ENOMEM; + } + + usb_fill_bulk_urb( dib->urb_list[i], dib->udev, + usb_rcvbulkpipe(dib->udev,dib->dibdev->dev_cl->pipe_data), + &dib->buffer[i*dib->dibdev->dev_cl->urb_buffer_size], + dib->dibdev->dev_cl->urb_buffer_size, + dibusb_urb_complete, dib); + + dib->urb_list[i]->transfer_flags = 0; + + dib->init_state |= DIBUSB_STATE_URB_INIT; + } + + /* dib->pid_parse here contains the value of the module parameter */ + /* decide if pid parsing can be deactivated: + * is possible (by device type) and wanted (by user) + */ + switch (dib->dibdev->dev_cl->id) { + case DIBUSB2_0: + case DIBUSB2_0B: + if (dib->udev->speed == USB_SPEED_HIGH && !dib->pid_parse) { + def_pid_parse = 0; + info("running at HIGH speed, will deliver the complete TS."); + } else + info("will use pid_parsing."); + break; + default: + break; + } + /* from here on it contains the device and user decision */ + dib->pid_parse = def_pid_parse; + + return 0; +} + +int dibusb_urb_exit(struct usb_dibusb *dib) +{ + int i; + + dibusb_urb_kill(dib); + + if (dib->init_state & DIBUSB_STATE_URB_LIST) { + for (i = 0; i < dib->dibdev->dev_cl->urb_count; i++) { + if (dib->urb_list[i] != NULL) { + deb_info("freeing URB no. %d.\n",i); + /* free the URBs */ + usb_free_urb(dib->urb_list[i]); + } + } + /* free the urb array */ + kfree(dib->urb_list); + dib->init_state &= ~DIBUSB_STATE_URB_LIST; + } + + if (dib->init_state & DIBUSB_STATE_URB_BUF) + pci_free_consistent(NULL, + dib->dibdev->dev_cl->urb_buffer_size*dib->dibdev->dev_cl->urb_count, + dib->buffer,dib->dma_handle); + + dib->init_state &= ~DIBUSB_STATE_URB_BUF; + dib->init_state &= ~DIBUSB_STATE_URB_INIT; + return 0; +} diff --git a/drivers/media/dvb/dibusb/dvb-dibusb.h b/drivers/media/dvb/dibusb/dvb-dibusb.h new file mode 100644 index 000000000000..52cd35dd9d83 --- /dev/null +++ b/drivers/media/dvb/dibusb/dvb-dibusb.h @@ -0,0 +1,327 @@ +/* + * dvb-dibusb.h + * + * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@desy.de) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + * for more information see dvb-dibusb-core.c . + */ +#ifndef __DVB_DIBUSB_H__ +#define __DVB_DIBUSB_H__ + +#include <linux/input.h> +#include <linux/config.h> +#include <linux/usb.h> + +#include "dvb_frontend.h" +#include "dvb_demux.h" +#include "dvb_net.h" +#include "dmxdev.h" + +#include "dib3000.h" +#include "mt352.h" + +/* debug */ +#ifdef CONFIG_DVB_DIBCOM_DEBUG +#define dprintk(level,args...) \ + do { if ((dvb_dibusb_debug & level)) { printk(args); } } while (0) + +#define debug_dump(b,l) {\ + int i; \ + for (i = 0; i < l; i++) deb_xfer("%02x ", b[i]); \ + deb_xfer("\n");\ +} + +#else +#define dprintk(args...) +#define debug_dump(b,l) +#endif + +extern int dvb_dibusb_debug; + +/* Version information */ +#define DRIVER_VERSION "0.3" +#define DRIVER_DESC "DiBcom based USB Budget DVB-T device" +#define DRIVER_AUTHOR "Patrick Boettcher, patrick.boettcher@desy.de" + +#define deb_info(args...) dprintk(0x01,args) +#define deb_xfer(args...) dprintk(0x02,args) +#define deb_alot(args...) dprintk(0x04,args) +#define deb_ts(args...) dprintk(0x08,args) +#define deb_err(args...) dprintk(0x10,args) +#define deb_rc(args...) dprintk(0x20,args) + +/* generic log methods - taken from usb.h */ +#undef err +#define err(format, arg...) printk(KERN_ERR "dvb-dibusb: " format "\n" , ## arg) +#undef info +#define info(format, arg...) printk(KERN_INFO "dvb-dibusb: " format "\n" , ## arg) +#undef warn +#define warn(format, arg...) printk(KERN_WARNING "dvb-dibusb: " format "\n" , ## arg) + +struct dibusb_usb_controller { + const char *name; /* name of the usb controller */ + u16 cpu_cs_register; /* needs to be restarted, when the firmware has been downloaded. */ +}; + +typedef enum { + DIBUSB1_1 = 0, + DIBUSB1_1_AN2235, + DIBUSB2_0, + UMT2_0, + DIBUSB2_0B, + NOVAT_USB2, + DTT200U, +} dibusb_class_t; + +typedef enum { + DIBUSB_TUNER_CABLE_THOMSON = 0, + DIBUSB_TUNER_COFDM_PANASONIC_ENV57H1XD5, + DIBUSB_TUNER_CABLE_LG_TDTP_E102P, + DIBUSB_TUNER_COFDM_PANASONIC_ENV77H11D5, +} dibusb_tuner_t; + +typedef enum { + DIBUSB_DIB3000MB = 0, + DIBUSB_DIB3000MC, + DIBUSB_MT352, + DTT200U_FE, +} dibusb_demodulator_t; + +typedef enum { + DIBUSB_RC_NO = 0, + DIBUSB_RC_NEC_PROTOCOL, + DIBUSB_RC_HAUPPAUGE_PROTO, +} dibusb_remote_t; + +struct dibusb_tuner { + dibusb_tuner_t id; + + u8 pll_addr; /* tuner i2c address */ +}; +extern struct dibusb_tuner dibusb_tuner[]; + +#define DIBUSB_POSSIBLE_I2C_ADDR_NUM 4 +struct dibusb_demod { + dibusb_demodulator_t id; + + int pid_filter_count; /* counter of the internal pid_filter */ + u8 i2c_addrs[DIBUSB_POSSIBLE_I2C_ADDR_NUM]; /* list of possible i2c addresses of the demod */ +}; + +#define DIBUSB_MAX_TUNER_NUM 2 +struct dibusb_device_class { + dibusb_class_t id; + + const struct dibusb_usb_controller *usb_ctrl; /* usb controller */ + const char *firmware; /* valid firmware filenames */ + + int pipe_cmd; /* command pipe (read/write) */ + int pipe_data; /* data pipe */ + + int urb_count; /* number of data URBs to be submitted */ + int urb_buffer_size; /* the size of the buffer for each URB */ + + dibusb_remote_t remote_type; /* does this device have a ir-receiver */ + + struct dibusb_demod *demod; /* which demodulator is mount */ + struct dibusb_tuner *tuner; /* which tuner can be found here */ +}; + +#define DIBUSB_ID_MAX_NUM 15 +struct dibusb_usb_device { + const char *name; /* real name of the box */ + struct dibusb_device_class *dev_cl; /* which dibusb_device_class is this device part of */ + + struct usb_device_id *cold_ids[DIBUSB_ID_MAX_NUM]; /* list of USB ids when this device is at pre firmware state */ + struct usb_device_id *warm_ids[DIBUSB_ID_MAX_NUM]; /* list of USB ids when this device is at post firmware state */ +}; + +/* a PID for the pid_filter list, when in use */ +struct dibusb_pid +{ + int index; + u16 pid; + int active; +}; + +struct usb_dibusb { + /* usb */ + struct usb_device * udev; + + struct dibusb_usb_device * dibdev; + +#define DIBUSB_STATE_INIT 0x000 +#define DIBUSB_STATE_URB_LIST 0x001 +#define DIBUSB_STATE_URB_BUF 0x002 +#define DIBUSB_STATE_URB_INIT 0x004 +#define DIBUSB_STATE_DVB 0x008 +#define DIBUSB_STATE_I2C 0x010 +#define DIBUSB_STATE_REMOTE 0x020 +#define DIBUSB_STATE_URB_SUBMIT 0x040 + int init_state; + + int feedcount; + struct dib_fe_xfer_ops xfer_ops; + + struct dibusb_tuner *tuner; + + struct urb **urb_list; + u8 *buffer; + dma_addr_t dma_handle; + + /* I2C */ + struct i2c_adapter i2c_adap; + + /* locking */ + struct semaphore usb_sem; + struct semaphore i2c_sem; + + /* dvb */ + struct dvb_adapter *adapter; + struct dmxdev dmxdev; + struct dvb_demux demux; + struct dvb_net dvb_net; + struct dvb_frontend* fe; + + int (*fe_sleep) (struct dvb_frontend *); + int (*fe_init) (struct dvb_frontend *); + + /* remote control */ + struct input_dev rc_input_dev; + struct work_struct rc_query_work; + int last_event; + int last_state; /* for Hauppauge RC protocol */ + int repeat_key_count; + int rc_key_repeat_count; /* module parameter */ + + /* module parameters */ + int pid_parse; + int rc_query_interval; +}; + +/* commonly used functions in the separated files */ + +/* dvb-dibusb-firmware.c */ +int dibusb_loadfirmware(struct usb_device *udev, struct dibusb_usb_device *dibdev); + +/* dvb-dibusb-remote.c */ +int dibusb_remote_exit(struct usb_dibusb *dib); +int dibusb_remote_init(struct usb_dibusb *dib); + +/* dvb-dibusb-fe-i2c.c */ +int dibusb_fe_init(struct usb_dibusb* dib); +int dibusb_fe_exit(struct usb_dibusb *dib); +int dibusb_i2c_init(struct usb_dibusb *dib); +int dibusb_i2c_exit(struct usb_dibusb *dib); + +/* dvb-dibusb-dvb.c */ +void dibusb_urb_complete(struct urb *urb, struct pt_regs *ptregs); +int dibusb_dvb_init(struct usb_dibusb *dib); +int dibusb_dvb_exit(struct usb_dibusb *dib); + +/* dvb-dibusb-usb.c */ +int dibusb_readwrite_usb(struct usb_dibusb *dib, u8 *wbuf, u16 wlen, u8 *rbuf, + u16 rlen); +int dibusb_write_usb(struct usb_dibusb *dib, u8 *buf, u16 len); + +int dibusb_hw_wakeup(struct dvb_frontend *); +int dibusb_hw_sleep(struct dvb_frontend *); +int dibusb_set_streaming_mode(struct usb_dibusb *,u8); +int dibusb_streaming(struct usb_dibusb *,int); + +int dibusb_urb_init(struct usb_dibusb *); +int dibusb_urb_exit(struct usb_dibusb *); + +/* dvb-fe-dtt200u.c */ +struct dvb_frontend* dtt200u_fe_attach(struct usb_dibusb *,struct dib_fe_xfer_ops *); + +/* i2c and transfer stuff */ +#define DIBUSB_I2C_TIMEOUT 5000 + +/* + * protocol of all dibusb related devices + */ + +/* + * bulk msg to/from endpoint 0x01 + * + * general structure: + * request_byte parameter_bytes + */ + +#define DIBUSB_REQ_START_READ 0x00 +#define DIBUSB_REQ_START_DEMOD 0x01 + +/* + * i2c read + * bulk write: 0x02 ((7bit i2c_addr << 1) & 0x01) register_bytes length_word + * bulk read: byte_buffer (length_word bytes) + */ +#define DIBUSB_REQ_I2C_READ 0x02 + +/* + * i2c write + * bulk write: 0x03 (7bit i2c_addr << 1) register_bytes value_bytes + */ +#define DIBUSB_REQ_I2C_WRITE 0x03 + +/* + * polling the value of the remote control + * bulk write: 0x04 + * bulk read: byte_buffer (5 bytes) + * + * first byte of byte_buffer shows the status (0x00, 0x01, 0x02) + */ +#define DIBUSB_REQ_POLL_REMOTE 0x04 + +#define DIBUSB_RC_NEC_EMPTY 0x00 +#define DIBUSB_RC_NEC_KEY_PRESSED 0x01 +#define DIBUSB_RC_NEC_KEY_REPEATED 0x02 + +/* additional status values for Hauppauge Remote Control Protocol */ +#define DIBUSB_RC_HAUPPAUGE_KEY_PRESSED 0x01 +#define DIBUSB_RC_HAUPPAUGE_KEY_EMPTY 0x03 + +/* streaming mode: + * bulk write: 0x05 mode_byte + * + * mode_byte is mostly 0x00 + */ +#define DIBUSB_REQ_SET_STREAMING_MODE 0x05 + +/* interrupt the internal read loop, when blocking */ +#define DIBUSB_REQ_INTR_READ 0x06 + +/* io control + * 0x07 cmd_byte param_bytes + * + * param_bytes can be up to 32 bytes + * + * cmd_byte function parameter name + * 0x00 power mode + * 0x00 sleep + * 0x01 wakeup + * + * 0x01 enable streaming + * 0x02 disable streaming + * + * + */ +#define DIBUSB_REQ_SET_IOCTL 0x07 + +/* IOCTL commands */ + +/* change the power mode in firmware */ +#define DIBUSB_IOCTL_CMD_POWER_MODE 0x00 +#define DIBUSB_IOCTL_POWER_SLEEP 0x00 +#define DIBUSB_IOCTL_POWER_WAKEUP 0x01 + +/* modify streaming of the FX2 */ +#define DIBUSB_IOCTL_CMD_ENABLE_STREAM 0x01 +#define DIBUSB_IOCTL_CMD_DISABLE_STREAM 0x02 + +#endif diff --git a/drivers/media/dvb/dibusb/dvb-fe-dtt200u.c b/drivers/media/dvb/dibusb/dvb-fe-dtt200u.c new file mode 100644 index 000000000000..1872aa6d200a --- /dev/null +++ b/drivers/media/dvb/dibusb/dvb-fe-dtt200u.c @@ -0,0 +1,263 @@ +/* + * dvb-dtt200u-fe.c is a driver which implements the frontend-part of the + * Yakumo/Typhoon/Hama USB2.0 boxes. It is hard-wired to the dibusb-driver as + * it uses the usb-transfer functions directly (maybe creating a + * generic-dvb-usb-lib for all usb-drivers will be reduce some more code.) + * + * Copyright (C) 2005 Patrick Boettcher <patrick.boettcher@desy.de> + * + * see dvb-dibusb-core.c for copyright details. + */ + +/* guessed protocol description (reverse engineered): + * read + * 00 - USB type 0x02 for usb2.0, 0x01 for usb1.1 + * 81 - <TS_LOCK> <current frequency divided by 250000> + * 82 - crash - do not touch + * 83 - crash - do not touch + * 84 - remote control + * 85 - crash - do not touch (OK, stop testing here) + * 88 - locking 2 bytes (0x80 0x40 == no signal, 0x89 0x20 == nice signal) + * 89 - noise-to-signal + * 8a - unkown 1 byte - signal_strength + * 8c - ber ??? + * 8d - ber + * 8e - unc + * + * write + * 02 - bandwidth + * 03 - frequency (divided by 250000) + * 04 - pid table (index pid(7:0) pid(12:8)) + * 05 - reset the pid table + * 08 - demod transfer enabled or not (FX2 transfer is enabled by default) + */ + +#include "dvb-dibusb.h" +#include "dvb_frontend.h" + +struct dtt200u_fe_state { + struct usb_dibusb *dib; + + struct dvb_frontend_parameters fep; + struct dvb_frontend frontend; +}; + +#define moan(which,what) info("unexpected value in '%s' for cmd '%02x' - please report to linux-dvb@linuxtv.org",which,what) + +static int dtt200u_fe_read_status(struct dvb_frontend* fe, fe_status_t *stat) +{ + struct dtt200u_fe_state *state = fe->demodulator_priv; + u8 bw[1] = { 0x81 }; + u8 br[3] = { 0 }; +// u8 bdeb[5] = { 0 }; + + dibusb_readwrite_usb(state->dib,bw,1,br,3); + switch (br[0]) { + case 0x01: + *stat = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; + break; + case 0x00: + *stat = 0; + break; + default: + moan("br[0]",0x81); + break; + } + +// bw[0] = 0x88; +// dibusb_readwrite_usb(state->dib,bw,1,bdeb,5); + +// deb_info("%02x: %02x %02x %02x %02x %02x\n",bw[0],bdeb[0],bdeb[1],bdeb[2],bdeb[3],bdeb[4]); + + return 0; +} +static int dtt200u_fe_read_ber(struct dvb_frontend* fe, u32 *ber) +{ + struct dtt200u_fe_state *state = fe->demodulator_priv; + u8 bw[1] = { 0x8d }; + *ber = 0; + dibusb_readwrite_usb(state->dib,bw,1,(u8*) ber, 3); + return 0; +} + +static int dtt200u_fe_read_unc_blocks(struct dvb_frontend* fe, u32 *unc) +{ + struct dtt200u_fe_state *state = fe->demodulator_priv; + u8 bw[1] = { 0x8c }; + *unc = 0; + dibusb_readwrite_usb(state->dib,bw,1,(u8*) unc, 3); + return 0; +} + +static int dtt200u_fe_read_signal_strength(struct dvb_frontend* fe, u16 *strength) +{ + struct dtt200u_fe_state *state = fe->demodulator_priv; + u8 bw[1] = { 0x8a }; + u8 b; + dibusb_readwrite_usb(state->dib,bw,1,&b, 1); + *strength = (b << 8) | b; + return 0; +} + +static int dtt200u_fe_read_snr(struct dvb_frontend* fe, u16 *snr) +{ + struct dtt200u_fe_state *state = fe->demodulator_priv; + u8 bw[1] = { 0x89 }; + u8 br[1] = { 0 }; + dibusb_readwrite_usb(state->dib,bw,1,br,1); + *snr = ((0xff - br[0]) << 8) | (0xff - br[0]); + return 0; +} + +static int dtt200u_fe_init(struct dvb_frontend* fe) +{ + struct dtt200u_fe_state *state = fe->demodulator_priv; + u8 b[] = { 0x01 }; + return dibusb_write_usb(state->dib,b,1); +} + +static int dtt200u_fe_sleep(struct dvb_frontend* fe) +{ + return dtt200u_fe_init(fe); +} + +static int dtt200u_fe_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings *tune) +{ + tune->min_delay_ms = 1500; + tune->step_size = 166667; + tune->max_drift = 166667 * 2; + return 0; +} + +static int dtt200u_fe_set_frontend(struct dvb_frontend* fe, + struct dvb_frontend_parameters *fep) +{ + struct dtt200u_fe_state *state = fe->demodulator_priv; + u16 freq = fep->frequency / 250000; + u8 bw,bwbuf[2] = { 0x03, 0 }, freqbuf[3] = { 0x02, 0, 0 }; + + switch (fep->u.ofdm.bandwidth) { + case BANDWIDTH_8_MHZ: bw = 8; break; + case BANDWIDTH_7_MHZ: bw = 7; break; + case BANDWIDTH_6_MHZ: bw = 6; break; + case BANDWIDTH_AUTO: return -EOPNOTSUPP; + default: + return -EINVAL; + } + deb_info("set_frontend\n"); + + bwbuf[1] = bw; + dibusb_write_usb(state->dib,bwbuf,2); + + freqbuf[1] = freq & 0xff; + freqbuf[2] = (freq >> 8) & 0xff; + dibusb_write_usb(state->dib,freqbuf,3); + + memcpy(&state->fep,fep,sizeof(struct dvb_frontend_parameters)); + + return 0; +} + +static int dtt200u_fe_get_frontend(struct dvb_frontend* fe, + struct dvb_frontend_parameters *fep) +{ + struct dtt200u_fe_state *state = fe->demodulator_priv; + memcpy(fep,&state->fep,sizeof(struct dvb_frontend_parameters)); + return 0; +} + +static void dtt200u_fe_release(struct dvb_frontend* fe) +{ + struct dtt200u_fe_state *state = (struct dtt200u_fe_state*) fe->demodulator_priv; + kfree(state); +} + +static int dtt200u_pid_control(struct dvb_frontend *fe,int index, int pid,int onoff) +{ + struct dtt200u_fe_state *state = (struct dtt200u_fe_state*) fe->demodulator_priv; + u8 b_pid[4]; + pid = onoff ? pid : 0; + + b_pid[0] = 0x04; + b_pid[1] = index; + b_pid[2] = pid & 0xff; + b_pid[3] = (pid >> 8) & 0xff; + + dibusb_write_usb(state->dib,b_pid,4); + return 0; +} + +static int dtt200u_fifo_control(struct dvb_frontend *fe, int onoff) +{ + struct dtt200u_fe_state *state = (struct dtt200u_fe_state*) fe->demodulator_priv; + u8 b_streaming[2] = { 0x08, onoff }; + u8 b_rst_pid[1] = { 0x05 }; + + dibusb_write_usb(state->dib,b_streaming,2); + + if (!onoff) + dibusb_write_usb(state->dib,b_rst_pid,1); + return 0; +} + +static struct dvb_frontend_ops dtt200u_fe_ops; + +struct dvb_frontend* dtt200u_fe_attach(struct usb_dibusb *dib, struct dib_fe_xfer_ops *xfer_ops) +{ + struct dtt200u_fe_state* state = NULL; + + /* allocate memory for the internal state */ + state = (struct dtt200u_fe_state*) kmalloc(sizeof(struct dtt200u_fe_state), GFP_KERNEL); + if (state == NULL) + goto error; + memset(state,0,sizeof(struct dtt200u_fe_state)); + + deb_info("attaching frontend dtt200u\n"); + + state->dib = dib; + + state->frontend.ops = &dtt200u_fe_ops; + state->frontend.demodulator_priv = state; + + xfer_ops->fifo_ctrl = dtt200u_fifo_control; + xfer_ops->pid_ctrl = dtt200u_pid_control; + + goto success; +error: + return NULL; +success: + return &state->frontend; +} + +static struct dvb_frontend_ops dtt200u_fe_ops = { + .info = { + .name = "DTT200U (Yakumo/Typhoon/Hama) DVB-T", + .type = FE_OFDM, + .frequency_min = 44250000, + .frequency_max = 867250000, + .frequency_stepsize = 250000, + .caps = FE_CAN_INVERSION_AUTO | + 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_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_RECOVER | + FE_CAN_HIERARCHY_AUTO, + }, + + .release = dtt200u_fe_release, + + .init = dtt200u_fe_init, + .sleep = dtt200u_fe_sleep, + + .set_frontend = dtt200u_fe_set_frontend, + .get_frontend = dtt200u_fe_get_frontend, + .get_tune_settings = dtt200u_fe_get_tune_settings, + + .read_status = dtt200u_fe_read_status, + .read_ber = dtt200u_fe_read_ber, + .read_signal_strength = dtt200u_fe_read_signal_strength, + .read_snr = dtt200u_fe_read_snr, + .read_ucblocks = dtt200u_fe_read_unc_blocks, +}; diff --git a/drivers/media/dvb/dvb-core/Kconfig b/drivers/media/dvb/dvb-core/Kconfig new file mode 100644 index 000000000000..a9a7b3421048 --- /dev/null +++ b/drivers/media/dvb/dvb-core/Kconfig @@ -0,0 +1,11 @@ +config DVB_CORE + tristate "DVB Core Support" + depends on DVB + select CRC32 + help + DVB core utility functions for device handling, software fallbacks etc. + Say Y when you have a DVB card and want to use it. Say Y if your want + to build your drivers outside the kernel, but need the DVB core. All + in-kernel drivers will select this automatically if needed. + If unsure say N. + diff --git a/drivers/media/dvb/dvb-core/Makefile b/drivers/media/dvb/dvb-core/Makefile new file mode 100644 index 000000000000..c6baac20f529 --- /dev/null +++ b/drivers/media/dvb/dvb-core/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for the kernel DVB device drivers. +# + +dvb-core-objs = dvbdev.o dmxdev.o dvb_demux.o dvb_filter.o \ + dvb_ca_en50221.o dvb_frontend.o \ + dvb_net.o dvb_ringbuffer.o + +obj-$(CONFIG_DVB_CORE) += dvb-core.o diff --git a/drivers/media/dvb/dvb-core/demux.h b/drivers/media/dvb/dvb-core/demux.h new file mode 100644 index 000000000000..fb55eaa5c8e7 --- /dev/null +++ b/drivers/media/dvb/dvb-core/demux.h @@ -0,0 +1,301 @@ +/* + * demux.h + * + * Copyright (c) 2002 Convergence GmbH + * + * based on code: + * Copyright (c) 2000 Nokia Research Center + * Tampere, FINLAND + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * 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 Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __DEMUX_H +#define __DEMUX_H + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/time.h> + +/*--------------------------------------------------------------------------*/ +/* Common definitions */ +/*--------------------------------------------------------------------------*/ + +/* + * DMX_MAX_FILTER_SIZE: Maximum length (in bytes) of a section/PES filter. + */ + +#ifndef DMX_MAX_FILTER_SIZE +#define DMX_MAX_FILTER_SIZE 18 +#endif + +/* + * DMX_MAX_SECFEED_SIZE: Maximum length (in bytes) of a private section feed filter. + */ + +#ifndef DMX_MAX_SECFEED_SIZE +#define DMX_MAX_SECFEED_SIZE 4096 +#endif + + +/* + * enum dmx_success: Success codes for the Demux Callback API. + */ + +enum dmx_success { + DMX_OK = 0, /* Received Ok */ + DMX_LENGTH_ERROR, /* Incorrect length */ + DMX_OVERRUN_ERROR, /* Receiver ring buffer overrun */ + DMX_CRC_ERROR, /* Incorrect CRC */ + DMX_FRAME_ERROR, /* Frame alignment error */ + DMX_FIFO_ERROR, /* Receiver FIFO overrun */ + DMX_MISSED_ERROR /* Receiver missed packet */ +} ; + +/*--------------------------------------------------------------------------*/ +/* TS packet reception */ +/*--------------------------------------------------------------------------*/ + +/* TS filter type for set() */ + +#define TS_PACKET 1 /* send TS packets (188 bytes) to callback (default) */ +#define TS_PAYLOAD_ONLY 2 /* in case TS_PACKET is set, only send the TS + payload (<=184 bytes per packet) to callback */ +#define TS_DECODER 4 /* send stream to built-in decoder (if present) */ + +/* PES type for filters which write to built-in decoder */ +/* these should be kept identical to the types in dmx.h */ + +enum dmx_ts_pes +{ /* also send packets to decoder (if it exists) */ + DMX_TS_PES_AUDIO0, + DMX_TS_PES_VIDEO0, + DMX_TS_PES_TELETEXT0, + DMX_TS_PES_SUBTITLE0, + DMX_TS_PES_PCR0, + + DMX_TS_PES_AUDIO1, + DMX_TS_PES_VIDEO1, + DMX_TS_PES_TELETEXT1, + DMX_TS_PES_SUBTITLE1, + DMX_TS_PES_PCR1, + + DMX_TS_PES_AUDIO2, + DMX_TS_PES_VIDEO2, + DMX_TS_PES_TELETEXT2, + DMX_TS_PES_SUBTITLE2, + DMX_TS_PES_PCR2, + + DMX_TS_PES_AUDIO3, + DMX_TS_PES_VIDEO3, + DMX_TS_PES_TELETEXT3, + DMX_TS_PES_SUBTITLE3, + DMX_TS_PES_PCR3, + + DMX_TS_PES_OTHER +}; + +#define DMX_TS_PES_AUDIO DMX_TS_PES_AUDIO0 +#define DMX_TS_PES_VIDEO DMX_TS_PES_VIDEO0 +#define DMX_TS_PES_TELETEXT DMX_TS_PES_TELETEXT0 +#define DMX_TS_PES_SUBTITLE DMX_TS_PES_SUBTITLE0 +#define DMX_TS_PES_PCR DMX_TS_PES_PCR0 + + +struct dmx_ts_feed { + int is_filtering; /* Set to non-zero when filtering in progress */ + struct dmx_demux *parent; /* Back-pointer */ + void *priv; /* Pointer to private data of the API client */ + int (*set) (struct dmx_ts_feed *feed, + u16 pid, + int type, + enum dmx_ts_pes pes_type, + size_t callback_length, + size_t circular_buffer_size, + int descramble, + struct timespec timeout); + int (*start_filtering) (struct dmx_ts_feed* feed); + int (*stop_filtering) (struct dmx_ts_feed* feed); +}; + +/*--------------------------------------------------------------------------*/ +/* Section reception */ +/*--------------------------------------------------------------------------*/ + +struct dmx_section_filter { + u8 filter_value [DMX_MAX_FILTER_SIZE]; + u8 filter_mask [DMX_MAX_FILTER_SIZE]; + u8 filter_mode [DMX_MAX_FILTER_SIZE]; + struct dmx_section_feed* parent; /* Back-pointer */ + void* priv; /* Pointer to private data of the API client */ +}; + +struct dmx_section_feed { + int is_filtering; /* Set to non-zero when filtering in progress */ + struct dmx_demux* parent; /* Back-pointer */ + void* priv; /* Pointer to private data of the API client */ + + int check_crc; + u32 crc_val; + + u8 *secbuf; + u8 secbuf_base[DMX_MAX_SECFEED_SIZE]; + u16 secbufp, seclen, tsfeedp; + + int (*set) (struct dmx_section_feed* feed, + u16 pid, + size_t circular_buffer_size, + int descramble, + int check_crc); + int (*allocate_filter) (struct dmx_section_feed* feed, + struct dmx_section_filter** filter); + int (*release_filter) (struct dmx_section_feed* feed, + struct dmx_section_filter* filter); + int (*start_filtering) (struct dmx_section_feed* feed); + int (*stop_filtering) (struct dmx_section_feed* feed); +}; + +/*--------------------------------------------------------------------------*/ +/* Callback functions */ +/*--------------------------------------------------------------------------*/ + +typedef int (*dmx_ts_cb) ( const u8 * buffer1, + size_t buffer1_length, + const u8 * buffer2, + size_t buffer2_length, + struct dmx_ts_feed* source, + enum dmx_success success); + +typedef int (*dmx_section_cb) ( const u8 * buffer1, + size_t buffer1_len, + const u8 * buffer2, + size_t buffer2_len, + struct dmx_section_filter * source, + enum dmx_success success); + +/*--------------------------------------------------------------------------*/ +/* DVB Front-End */ +/*--------------------------------------------------------------------------*/ + +enum dmx_frontend_source { + DMX_MEMORY_FE, + DMX_FRONTEND_0, + DMX_FRONTEND_1, + DMX_FRONTEND_2, + DMX_FRONTEND_3, + DMX_STREAM_0, /* external stream input, e.g. LVDS */ + DMX_STREAM_1, + DMX_STREAM_2, + DMX_STREAM_3 +}; + +struct dmx_frontend { + struct list_head connectivity_list; /* List of front-ends that can + be connected to a particular + demux */ + void* priv; /* Pointer to private data of the API client */ + enum dmx_frontend_source source; +}; + +/*--------------------------------------------------------------------------*/ +/* MPEG-2 TS Demux */ +/*--------------------------------------------------------------------------*/ + +/* + * Flags OR'ed in the capabilites field of struct dmx_demux. + */ + +#define DMX_TS_FILTERING 1 +#define DMX_PES_FILTERING 2 +#define DMX_SECTION_FILTERING 4 +#define DMX_MEMORY_BASED_FILTERING 8 /* write() available */ +#define DMX_CRC_CHECKING 16 +#define DMX_TS_DESCRAMBLING 32 +#define DMX_SECTION_PAYLOAD_DESCRAMBLING 64 +#define DMX_MAC_ADDRESS_DESCRAMBLING 128 + +/* + * Demux resource type identifier. +*/ + +/* + * DMX_FE_ENTRY(): Casts elements in the list of registered + * front-ends from the generic type struct list_head + * to the type * struct dmx_frontend + *. +*/ + +#define DMX_FE_ENTRY(list) list_entry(list, struct dmx_frontend, connectivity_list) + +struct dmx_demux { + u32 capabilities; /* Bitfield of capability flags */ + struct dmx_frontend* frontend; /* Front-end connected to the demux */ + struct list_head reg_list; /* List of registered demuxes */ + void* priv; /* Pointer to private data of the API client */ + int users; /* Number of users */ + int (*open) (struct dmx_demux* demux); + int (*close) (struct dmx_demux* demux); + int (*write) (struct dmx_demux* demux, const char* buf, size_t count); + int (*allocate_ts_feed) (struct dmx_demux* demux, + struct dmx_ts_feed** feed, + dmx_ts_cb callback); + int (*release_ts_feed) (struct dmx_demux* demux, + struct dmx_ts_feed* feed); + int (*allocate_section_feed) (struct dmx_demux* demux, + struct dmx_section_feed** feed, + dmx_section_cb callback); + int (*release_section_feed) (struct dmx_demux* demux, + struct dmx_section_feed* feed); + int (*descramble_mac_address) (struct dmx_demux* demux, + u8* buffer1, + size_t buffer1_length, + u8* buffer2, + size_t buffer2_length, + u16 pid); + int (*descramble_section_payload) (struct dmx_demux* demux, + u8* buffer1, + size_t buffer1_length, + u8* buffer2, size_t buffer2_length, + u16 pid); + int (*add_frontend) (struct dmx_demux* demux, + struct dmx_frontend* frontend); + int (*remove_frontend) (struct dmx_demux* demux, + struct dmx_frontend* frontend); + struct list_head* (*get_frontends) (struct dmx_demux* demux); + int (*connect_frontend) (struct dmx_demux* demux, + struct dmx_frontend* frontend); + int (*disconnect_frontend) (struct dmx_demux* demux); + + int (*get_pes_pids) (struct dmx_demux* demux, u16 *pids); + + int (*get_stc) (struct dmx_demux* demux, unsigned int num, + u64 *stc, unsigned int *base); +}; + +/*--------------------------------------------------------------------------*/ +/* Demux directory */ +/*--------------------------------------------------------------------------*/ + +/* + * DMX_DIR_ENTRY(): Casts elements in the list of registered + * demuxes from the generic type struct list_head* to the type struct dmx_demux + *. + */ + +#define DMX_DIR_ENTRY(list) list_entry(list, struct dmx_demux, reg_list) + +#endif /* #ifndef __DEMUX_H */ diff --git a/drivers/media/dvb/dvb-core/dmxdev.c b/drivers/media/dvb/dvb-core/dmxdev.c new file mode 100644 index 000000000000..1863f1dfb00c --- /dev/null +++ b/drivers/media/dvb/dvb-core/dmxdev.c @@ -0,0 +1,1137 @@ +/* + * dmxdev.c - DVB demultiplexer device + * + * Copyright (C) 2000 Ralph Metzler <ralph@convergence.de> + * & Marcus Metzler <marcus@convergence.de> + for convergence integrated media GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * 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 Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/sched.h> +#include <linux/poll.h> +#include <linux/ioctl.h> +#include <linux/wait.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +#include "dmxdev.h" + +static int debug; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off)."); + +#define dprintk if (debug) printk + +static inline struct dmxdev_filter * +dvb_dmxdev_file_to_filter(struct file *file) +{ + return (struct dmxdev_filter *) file->private_data; +} + +static inline void dvb_dmxdev_buffer_init(struct dmxdev_buffer *buffer) +{ + buffer->data=NULL; + buffer->size=8192; + buffer->pread=0; + buffer->pwrite=0; + buffer->error=0; + init_waitqueue_head(&buffer->queue); +} + +static inline int dvb_dmxdev_buffer_write(struct dmxdev_buffer *buf, const u8 *src, int len) +{ + int split; + int free; + int todo; + + if (!len) + return 0; + if (!buf->data) + return 0; + + free=buf->pread-buf->pwrite; + split=0; + if (free<=0) { + free+=buf->size; + split=buf->size-buf->pwrite; + } + if (len>=free) { + dprintk("dmxdev: buffer overflow\n"); + return -1; + } + if (split>=len) + split=0; + todo=len; + if (split) { + memcpy(buf->data + buf->pwrite, src, split); + todo-=split; + buf->pwrite=0; + } + memcpy(buf->data + buf->pwrite, src+split, todo); + buf->pwrite=(buf->pwrite+todo)%buf->size; + return len; +} + +static ssize_t dvb_dmxdev_buffer_read(struct dmxdev_buffer *src, + int non_blocking, char __user *buf, size_t count, loff_t *ppos) +{ + unsigned long todo=count; + int split, avail, error; + + if (!src->data) + return 0; + + if ((error=src->error)) { + src->pwrite=src->pread; + src->error=0; + return error; + } + + if (non_blocking && (src->pwrite==src->pread)) + return -EWOULDBLOCK; + + while (todo>0) { + if (non_blocking && (src->pwrite==src->pread)) + return (count-todo) ? (count-todo) : -EWOULDBLOCK; + + if (wait_event_interruptible(src->queue, + (src->pread!=src->pwrite) || + (src->error))<0) + return count-todo; + + if ((error=src->error)) { + src->pwrite=src->pread; + src->error=0; + return error; + } + + split=src->size; + avail=src->pwrite - src->pread; + if (avail<0) { + avail+=src->size; + split=src->size - src->pread; + } + if (avail>todo) + avail=todo; + if (split<avail) { + if (copy_to_user(buf, src->data+src->pread, split)) + return -EFAULT; + buf+=split; + src->pread=0; + todo-=split; + avail-=split; + } + if (avail) { + if (copy_to_user(buf, src->data+src->pread, avail)) + return -EFAULT; + src->pread = (src->pread + avail) % src->size; + todo-=avail; + buf+=avail; + } + } + return count; +} + +static struct dmx_frontend * get_fe(struct dmx_demux *demux, int type) +{ + struct list_head *head, *pos; + + head=demux->get_frontends(demux); + if (!head) + return NULL; + list_for_each(pos, head) + if (DMX_FE_ENTRY(pos)->source==type) + return DMX_FE_ENTRY(pos); + + return NULL; +} + +static inline void dvb_dmxdev_dvr_state_set(struct dmxdev_dvr *dmxdevdvr, int state) +{ + spin_lock_irq(&dmxdevdvr->dev->lock); + dmxdevdvr->state=state; + spin_unlock_irq(&dmxdevdvr->dev->lock); +} + +static int dvb_dvr_open(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev=(struct dvb_device *) file->private_data; + struct dmxdev *dmxdev=(struct dmxdev *) dvbdev->priv; + struct dmx_frontend *front; + + dprintk ("function : %s\n", __FUNCTION__); + + if (down_interruptible (&dmxdev->mutex)) + return -ERESTARTSYS; + + if ((file->f_flags&O_ACCMODE)==O_RDWR) { + if (!(dmxdev->capabilities&DMXDEV_CAP_DUPLEX)) { + up(&dmxdev->mutex); + return -EOPNOTSUPP; + } + } + + if ((file->f_flags&O_ACCMODE)==O_RDONLY) { + dvb_dmxdev_buffer_init(&dmxdev->dvr_buffer); + dmxdev->dvr_buffer.size=DVR_BUFFER_SIZE; + dmxdev->dvr_buffer.data=vmalloc(DVR_BUFFER_SIZE); + if (!dmxdev->dvr_buffer.data) { + up(&dmxdev->mutex); + return -ENOMEM; + } + } + + if ((file->f_flags&O_ACCMODE)==O_WRONLY) { + dmxdev->dvr_orig_fe=dmxdev->demux->frontend; + + if (!dmxdev->demux->write) { + up(&dmxdev->mutex); + return -EOPNOTSUPP; + } + + front=get_fe(dmxdev->demux, DMX_MEMORY_FE); + + if (!front) { + up(&dmxdev->mutex); + return -EINVAL; + } + dmxdev->demux->disconnect_frontend(dmxdev->demux); + dmxdev->demux->connect_frontend(dmxdev->demux, front); + } + up(&dmxdev->mutex); + return 0; +} + +static int dvb_dvr_release(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev=(struct dvb_device *) file->private_data; + struct dmxdev *dmxdev=(struct dmxdev *) dvbdev->priv; + + if (down_interruptible (&dmxdev->mutex)) + return -ERESTARTSYS; + + if ((file->f_flags&O_ACCMODE)==O_WRONLY) { + dmxdev->demux->disconnect_frontend(dmxdev->demux); + dmxdev->demux->connect_frontend(dmxdev->demux, + dmxdev->dvr_orig_fe); + } + if ((file->f_flags&O_ACCMODE)==O_RDONLY) { + if (dmxdev->dvr_buffer.data) { + void *mem=dmxdev->dvr_buffer.data; + mb(); + spin_lock_irq(&dmxdev->lock); + dmxdev->dvr_buffer.data=NULL; + spin_unlock_irq(&dmxdev->lock); + vfree(mem); + } + } + up(&dmxdev->mutex); + return 0; +} + +static ssize_t dvb_dvr_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct dvb_device *dvbdev=(struct dvb_device *) file->private_data; + struct dmxdev *dmxdev=(struct dmxdev *) dvbdev->priv; + int ret; + + if (!dmxdev->demux->write) + return -EOPNOTSUPP; + if ((file->f_flags&O_ACCMODE)!=O_WRONLY) + return -EINVAL; + if (down_interruptible (&dmxdev->mutex)) + return -ERESTARTSYS; + ret=dmxdev->demux->write(dmxdev->demux, buf, count); + up(&dmxdev->mutex); + return ret; +} + +static ssize_t dvb_dvr_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + struct dvb_device *dvbdev=(struct dvb_device *) file->private_data; + struct dmxdev *dmxdev=(struct dmxdev *) dvbdev->priv; + int ret; + + //down(&dmxdev->mutex); + ret= dvb_dmxdev_buffer_read(&dmxdev->dvr_buffer, + file->f_flags&O_NONBLOCK, + buf, count, ppos); + //up(&dmxdev->mutex); + return ret; +} + +static inline void dvb_dmxdev_filter_state_set(struct dmxdev_filter *dmxdevfilter, int state) +{ + spin_lock_irq(&dmxdevfilter->dev->lock); + dmxdevfilter->state=state; + spin_unlock_irq(&dmxdevfilter->dev->lock); +} + +static int dvb_dmxdev_set_buffer_size(struct dmxdev_filter *dmxdevfilter, unsigned long size) +{ + struct dmxdev_buffer *buf=&dmxdevfilter->buffer; + void *mem; + + if (buf->size==size) + return 0; + if (dmxdevfilter->state>=DMXDEV_STATE_GO) + return -EBUSY; + spin_lock_irq(&dmxdevfilter->dev->lock); + mem=buf->data; + buf->data=NULL; + buf->size=size; + buf->pwrite=buf->pread=0; + spin_unlock_irq(&dmxdevfilter->dev->lock); + vfree(mem); + + if (buf->size) { + mem=vmalloc(dmxdevfilter->buffer.size); + if (!mem) + return -ENOMEM; + spin_lock_irq(&dmxdevfilter->dev->lock); + buf->data=mem; + spin_unlock_irq(&dmxdevfilter->dev->lock); + } + return 0; +} + +static void dvb_dmxdev_filter_timeout(unsigned long data) +{ + struct dmxdev_filter *dmxdevfilter=(struct dmxdev_filter *)data; + + dmxdevfilter->buffer.error=-ETIMEDOUT; + spin_lock_irq(&dmxdevfilter->dev->lock); + dmxdevfilter->state=DMXDEV_STATE_TIMEDOUT; + spin_unlock_irq(&dmxdevfilter->dev->lock); + wake_up(&dmxdevfilter->buffer.queue); +} + +static void dvb_dmxdev_filter_timer(struct dmxdev_filter *dmxdevfilter) +{ + struct dmx_sct_filter_params *para=&dmxdevfilter->params.sec; + + del_timer(&dmxdevfilter->timer); + if (para->timeout) { + dmxdevfilter->timer.function=dvb_dmxdev_filter_timeout; + dmxdevfilter->timer.data=(unsigned long) dmxdevfilter; + dmxdevfilter->timer.expires=jiffies+1+(HZ/2+HZ*para->timeout)/1000; + add_timer(&dmxdevfilter->timer); + } +} + +static int dvb_dmxdev_section_callback(const u8 *buffer1, size_t buffer1_len, + const u8 *buffer2, size_t buffer2_len, + struct dmx_section_filter *filter, enum dmx_success success) +{ + struct dmxdev_filter *dmxdevfilter=(struct dmxdev_filter *) filter->priv; + int ret; + + if (dmxdevfilter->buffer.error) { + wake_up(&dmxdevfilter->buffer.queue); + return 0; + } + spin_lock(&dmxdevfilter->dev->lock); + if (dmxdevfilter->state!=DMXDEV_STATE_GO) { + spin_unlock(&dmxdevfilter->dev->lock); + return 0; + } + del_timer(&dmxdevfilter->timer); + dprintk("dmxdev: section callback %02x %02x %02x %02x %02x %02x\n", + buffer1[0], buffer1[1], + buffer1[2], buffer1[3], + buffer1[4], buffer1[5]); + ret=dvb_dmxdev_buffer_write(&dmxdevfilter->buffer, buffer1, buffer1_len); + if (ret==buffer1_len) { + ret=dvb_dmxdev_buffer_write(&dmxdevfilter->buffer, buffer2, buffer2_len); + } + if (ret<0) { + dmxdevfilter->buffer.pwrite=dmxdevfilter->buffer.pread; + dmxdevfilter->buffer.error=-EOVERFLOW; + } + if (dmxdevfilter->params.sec.flags&DMX_ONESHOT) + dmxdevfilter->state=DMXDEV_STATE_DONE; + spin_unlock(&dmxdevfilter->dev->lock); + wake_up(&dmxdevfilter->buffer.queue); + return 0; +} + +static int dvb_dmxdev_ts_callback(const u8 *buffer1, size_t buffer1_len, + const u8 *buffer2, size_t buffer2_len, + struct dmx_ts_feed *feed, enum dmx_success success) +{ + struct dmxdev_filter *dmxdevfilter=(struct dmxdev_filter *) feed->priv; + struct dmxdev_buffer *buffer; + int ret; + + spin_lock(&dmxdevfilter->dev->lock); + if (dmxdevfilter->params.pes.output==DMX_OUT_DECODER) { + spin_unlock(&dmxdevfilter->dev->lock); + return 0; + } + + if (dmxdevfilter->params.pes.output==DMX_OUT_TAP) + buffer=&dmxdevfilter->buffer; + else + buffer=&dmxdevfilter->dev->dvr_buffer; + if (buffer->error) { + spin_unlock(&dmxdevfilter->dev->lock); + wake_up(&buffer->queue); + return 0; + } + ret=dvb_dmxdev_buffer_write(buffer, buffer1, buffer1_len); + if (ret==buffer1_len) + ret=dvb_dmxdev_buffer_write(buffer, buffer2, buffer2_len); + if (ret<0) { + buffer->pwrite=buffer->pread; + buffer->error=-EOVERFLOW; + } + spin_unlock(&dmxdevfilter->dev->lock); + wake_up(&buffer->queue); + return 0; +} + + +/* stop feed but only mark the specified filter as stopped (state set) */ + +static int dvb_dmxdev_feed_stop(struct dmxdev_filter *dmxdevfilter) +{ + dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_SET); + + switch (dmxdevfilter->type) { + case DMXDEV_TYPE_SEC: + del_timer(&dmxdevfilter->timer); + dmxdevfilter->feed.sec->stop_filtering(dmxdevfilter->feed.sec); + break; + case DMXDEV_TYPE_PES: + dmxdevfilter->feed.ts->stop_filtering(dmxdevfilter->feed.ts); + break; + default: + return -EINVAL; + } + return 0; +} + + +/* start feed associated with the specified filter */ + +static int dvb_dmxdev_feed_start(struct dmxdev_filter *filter) +{ + dvb_dmxdev_filter_state_set (filter, DMXDEV_STATE_GO); + + switch (filter->type) { + case DMXDEV_TYPE_SEC: + return filter->feed.sec->start_filtering(filter->feed.sec); + break; + case DMXDEV_TYPE_PES: + return filter->feed.ts->start_filtering(filter->feed.ts); + break; + default: + return -EINVAL; + } + + return 0; +} + + +/* restart section feed if it has filters left associated with it, + otherwise release the feed */ + +static int dvb_dmxdev_feed_restart(struct dmxdev_filter *filter) +{ + int i; + struct dmxdev *dmxdev = filter->dev; + u16 pid = filter->params.sec.pid; + + for (i=0; i<dmxdev->filternum; i++) + if (dmxdev->filter[i].state>=DMXDEV_STATE_GO && + dmxdev->filter[i].type==DMXDEV_TYPE_SEC && + dmxdev->filter[i].pid==pid) { + dvb_dmxdev_feed_start(&dmxdev->filter[i]); + return 0; + } + + filter->dev->demux->release_section_feed(dmxdev->demux, filter->feed.sec); + + return 0; +} + +static int dvb_dmxdev_filter_stop(struct dmxdev_filter *dmxdevfilter) +{ + if (dmxdevfilter->state<DMXDEV_STATE_GO) + return 0; + + switch (dmxdevfilter->type) { + case DMXDEV_TYPE_SEC: + if (!dmxdevfilter->feed.sec) + break; + dvb_dmxdev_feed_stop(dmxdevfilter); + if (dmxdevfilter->filter.sec) + dmxdevfilter->feed.sec-> + release_filter(dmxdevfilter->feed.sec, + dmxdevfilter->filter.sec); + dvb_dmxdev_feed_restart(dmxdevfilter); + dmxdevfilter->feed.sec=NULL; + break; + case DMXDEV_TYPE_PES: + if (!dmxdevfilter->feed.ts) + break; + dvb_dmxdev_feed_stop(dmxdevfilter); + dmxdevfilter->dev->demux-> + release_ts_feed(dmxdevfilter->dev->demux, + dmxdevfilter->feed.ts); + dmxdevfilter->feed.ts=NULL; + break; + default: + if (dmxdevfilter->state==DMXDEV_STATE_ALLOCATED) + return 0; + return -EINVAL; + } + dmxdevfilter->buffer.pwrite=dmxdevfilter->buffer.pread=0; + return 0; +} + +static inline int dvb_dmxdev_filter_reset(struct dmxdev_filter *dmxdevfilter) +{ + if (dmxdevfilter->state<DMXDEV_STATE_SET) + return 0; + + dmxdevfilter->type=DMXDEV_TYPE_NONE; + dmxdevfilter->pid=0xffff; + dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_ALLOCATED); + return 0; +} + +static int dvb_dmxdev_filter_start(struct dmxdev_filter *filter) +{ + struct dmxdev *dmxdev = filter->dev; + void *mem; + int ret, i; + + if (filter->state < DMXDEV_STATE_SET) + return -EINVAL; + + if (filter->state >= DMXDEV_STATE_GO) + dvb_dmxdev_filter_stop(filter); + + if (!(mem = filter->buffer.data)) { + mem = vmalloc(filter->buffer.size); + spin_lock_irq(&filter->dev->lock); + filter->buffer.data=mem; + spin_unlock_irq(&filter->dev->lock); + if (!filter->buffer.data) + return -ENOMEM; + } + + filter->buffer.pwrite = filter->buffer.pread = 0; + + switch (filter->type) { + case DMXDEV_TYPE_SEC: + { + struct dmx_sct_filter_params *para=&filter->params.sec; + struct dmx_section_filter **secfilter=&filter->filter.sec; + struct dmx_section_feed **secfeed=&filter->feed.sec; + + *secfilter=NULL; + *secfeed=NULL; + + /* find active filter/feed with same PID */ + for (i=0; i<dmxdev->filternum; i++) { + if (dmxdev->filter[i].state >= DMXDEV_STATE_GO && + dmxdev->filter[i].pid == para->pid && + dmxdev->filter[i].type == DMXDEV_TYPE_SEC) { + *secfeed = dmxdev->filter[i].feed.sec; + break; + } + } + + /* if no feed found, try to allocate new one */ + if (!*secfeed) { + ret=dmxdev->demux->allocate_section_feed(dmxdev->demux, + secfeed, + dvb_dmxdev_section_callback); + if (ret<0) { + printk ("DVB (%s): could not alloc feed\n", + __FUNCTION__); + return ret; + } + + ret=(*secfeed)->set(*secfeed, para->pid, 32768, 0, + (para->flags & DMX_CHECK_CRC) ? 1 : 0); + + if (ret<0) { + printk ("DVB (%s): could not set feed\n", + __FUNCTION__); + dvb_dmxdev_feed_restart(filter); + return ret; + } + } else { + dvb_dmxdev_feed_stop(filter); + } + + ret=(*secfeed)->allocate_filter(*secfeed, secfilter); + + if (ret < 0) { + dvb_dmxdev_feed_restart(filter); + filter->feed.sec->start_filtering(*secfeed); + dprintk ("could not get filter\n"); + return ret; + } + + (*secfilter)->priv = filter; + + memcpy(&((*secfilter)->filter_value[3]), + &(para->filter.filter[1]), DMX_FILTER_SIZE-1); + memcpy(&(*secfilter)->filter_mask[3], + ¶->filter.mask[1], DMX_FILTER_SIZE-1); + memcpy(&(*secfilter)->filter_mode[3], + ¶->filter.mode[1], DMX_FILTER_SIZE-1); + + (*secfilter)->filter_value[0]=para->filter.filter[0]; + (*secfilter)->filter_mask[0]=para->filter.mask[0]; + (*secfilter)->filter_mode[0]=para->filter.mode[0]; + (*secfilter)->filter_mask[1]=0; + (*secfilter)->filter_mask[2]=0; + + filter->todo = 0; + + ret = filter->feed.sec->start_filtering (filter->feed.sec); + + if (ret < 0) + return ret; + + dvb_dmxdev_filter_timer(filter); + break; + } + + case DMXDEV_TYPE_PES: + { + struct timespec timeout = { 0 }; + struct dmx_pes_filter_params *para = &filter->params.pes; + dmx_output_t otype; + int ret; + int ts_type; + enum dmx_ts_pes ts_pes; + struct dmx_ts_feed **tsfeed = &filter->feed.ts; + + filter->feed.ts = NULL; + otype=para->output; + + ts_pes=(enum dmx_ts_pes) para->pes_type; + + if (ts_pes<DMX_PES_OTHER) + ts_type=TS_DECODER; + else + ts_type=0; + + if (otype == DMX_OUT_TS_TAP) + ts_type |= TS_PACKET; + + if (otype == DMX_OUT_TAP) + ts_type |= TS_PAYLOAD_ONLY|TS_PACKET; + + ret=dmxdev->demux->allocate_ts_feed(dmxdev->demux, + tsfeed, + dvb_dmxdev_ts_callback); + if (ret<0) + return ret; + + (*tsfeed)->priv = (void *) filter; + + ret = (*tsfeed)->set(*tsfeed, para->pid, ts_type, ts_pes, + 188, 32768, 0, timeout); + + if (ret < 0) { + dmxdev->demux->release_ts_feed(dmxdev->demux, *tsfeed); + return ret; + } + + ret = filter->feed.ts->start_filtering(filter->feed.ts); + + if (ret < 0) + return ret; + + break; + } + default: + return -EINVAL; + } + + dvb_dmxdev_filter_state_set(filter, DMXDEV_STATE_GO); + return 0; +} + +static int dvb_demux_open(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev=(struct dvb_device *) file->private_data; + struct dmxdev *dmxdev=(struct dmxdev *) dvbdev->priv; + int i; + struct dmxdev_filter *dmxdevfilter; + + if (!dmxdev->filter) + return -EINVAL; + + if (down_interruptible(&dmxdev->mutex)) + return -ERESTARTSYS; + + for (i=0; i<dmxdev->filternum; i++) + if (dmxdev->filter[i].state==DMXDEV_STATE_FREE) + break; + + if (i==dmxdev->filternum) { + up(&dmxdev->mutex); + return -EMFILE; + } + + dmxdevfilter=&dmxdev->filter[i]; + sema_init(&dmxdevfilter->mutex, 1); + dmxdevfilter->dvbdev=dmxdev->dvbdev; + file->private_data=dmxdevfilter; + + dvb_dmxdev_buffer_init(&dmxdevfilter->buffer); + dmxdevfilter->type=DMXDEV_TYPE_NONE; + dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_ALLOCATED); + dmxdevfilter->feed.ts=NULL; + init_timer(&dmxdevfilter->timer); + + up(&dmxdev->mutex); + return 0; +} + + +static int dvb_dmxdev_filter_free(struct dmxdev *dmxdev, struct dmxdev_filter *dmxdevfilter) +{ + if (down_interruptible(&dmxdev->mutex)) + return -ERESTARTSYS; + + if (down_interruptible(&dmxdevfilter->mutex)) { + up(&dmxdev->mutex); + return -ERESTARTSYS; + } + + dvb_dmxdev_filter_stop(dmxdevfilter); + dvb_dmxdev_filter_reset(dmxdevfilter); + + if (dmxdevfilter->buffer.data) { + void *mem=dmxdevfilter->buffer.data; + + spin_lock_irq(&dmxdev->lock); + dmxdevfilter->buffer.data=NULL; + spin_unlock_irq(&dmxdev->lock); + vfree(mem); + } + + dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_FREE); + wake_up(&dmxdevfilter->buffer.queue); + up(&dmxdevfilter->mutex); + up(&dmxdev->mutex); + return 0; +} + +static inline void invert_mode(dmx_filter_t *filter) +{ + int i; + + for (i=0; i<DMX_FILTER_SIZE; i++) + filter->mode[i]^=0xff; +} + + +static int dvb_dmxdev_filter_set(struct dmxdev *dmxdev, + struct dmxdev_filter *dmxdevfilter, + struct dmx_sct_filter_params *params) +{ + dprintk ("function : %s\n", __FUNCTION__); + + dvb_dmxdev_filter_stop(dmxdevfilter); + + dmxdevfilter->type=DMXDEV_TYPE_SEC; + dmxdevfilter->pid=params->pid; + memcpy(&dmxdevfilter->params.sec, + params, sizeof(struct dmx_sct_filter_params)); + invert_mode(&dmxdevfilter->params.sec.filter); + dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_SET); + + if (params->flags&DMX_IMMEDIATE_START) + return dvb_dmxdev_filter_start(dmxdevfilter); + + return 0; +} + +static int dvb_dmxdev_pes_filter_set(struct dmxdev *dmxdev, + struct dmxdev_filter *dmxdevfilter, + struct dmx_pes_filter_params *params) +{ + dvb_dmxdev_filter_stop(dmxdevfilter); + + if (params->pes_type>DMX_PES_OTHER || params->pes_type<0) + return -EINVAL; + + dmxdevfilter->type=DMXDEV_TYPE_PES; + dmxdevfilter->pid=params->pid; + memcpy(&dmxdevfilter->params, params, sizeof(struct dmx_pes_filter_params)); + + dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_SET); + + if (params->flags&DMX_IMMEDIATE_START) + return dvb_dmxdev_filter_start(dmxdevfilter); + + return 0; +} + +static ssize_t dvb_dmxdev_read_sec(struct dmxdev_filter *dfil, + struct file *file, char __user *buf, size_t count, loff_t *ppos) +{ + int result, hcount; + int done=0; + + if (dfil->todo<=0) { + hcount=3+dfil->todo; + if (hcount>count) + hcount=count; + result=dvb_dmxdev_buffer_read(&dfil->buffer, file->f_flags&O_NONBLOCK, + buf, hcount, ppos); + if (result<0) { + dfil->todo=0; + return result; + } + if (copy_from_user(dfil->secheader-dfil->todo, buf, result)) + return -EFAULT; + buf+=result; + done=result; + count-=result; + dfil->todo-=result; + if (dfil->todo>-3) + return done; + dfil->todo=((dfil->secheader[1]<<8)|dfil->secheader[2])&0xfff; + if (!count) + return done; + } + if (count>dfil->todo) + count=dfil->todo; + result=dvb_dmxdev_buffer_read(&dfil->buffer, file->f_flags&O_NONBLOCK, + buf, count, ppos); + if (result<0) + return result; + dfil->todo-=result; + return (result+done); +} + + +static ssize_t +dvb_demux_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) +{ + struct dmxdev_filter *dmxdevfilter=dvb_dmxdev_file_to_filter(file); + int ret=0; + + if (down_interruptible(&dmxdevfilter->mutex)) + return -ERESTARTSYS; + + if (dmxdevfilter->type==DMXDEV_TYPE_SEC) + ret=dvb_dmxdev_read_sec(dmxdevfilter, file, buf, count, ppos); + else + ret=dvb_dmxdev_buffer_read(&dmxdevfilter->buffer, + file->f_flags&O_NONBLOCK, + buf, count, ppos); + + up(&dmxdevfilter->mutex); + return ret; +} + + +static int dvb_demux_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *parg) +{ + struct dmxdev_filter *dmxdevfilter=dvb_dmxdev_file_to_filter(file); + struct dmxdev *dmxdev=dmxdevfilter->dev; + unsigned long arg=(unsigned long) parg; + int ret=0; + + if (down_interruptible (&dmxdev->mutex)) + return -ERESTARTSYS; + + switch (cmd) { + case DMX_START: + if (down_interruptible(&dmxdevfilter->mutex)) { + up(&dmxdev->mutex); + return -ERESTARTSYS; + } + if (dmxdevfilter->state<DMXDEV_STATE_SET) + ret = -EINVAL; + else + ret = dvb_dmxdev_filter_start(dmxdevfilter); + up(&dmxdevfilter->mutex); + break; + + case DMX_STOP: + if (down_interruptible(&dmxdevfilter->mutex)) { + up(&dmxdev->mutex); + return -ERESTARTSYS; + } + ret=dvb_dmxdev_filter_stop(dmxdevfilter); + up(&dmxdevfilter->mutex); + break; + + case DMX_SET_FILTER: + if (down_interruptible(&dmxdevfilter->mutex)) { + up(&dmxdev->mutex); + return -ERESTARTSYS; + } + ret = dvb_dmxdev_filter_set(dmxdev, dmxdevfilter, + (struct dmx_sct_filter_params *)parg); + up(&dmxdevfilter->mutex); + break; + + case DMX_SET_PES_FILTER: + if (down_interruptible(&dmxdevfilter->mutex)) { + up(&dmxdev->mutex); + return -ERESTARTSYS; + } + ret=dvb_dmxdev_pes_filter_set(dmxdev, dmxdevfilter, + (struct dmx_pes_filter_params *)parg); + up(&dmxdevfilter->mutex); + break; + + case DMX_SET_BUFFER_SIZE: + if (down_interruptible(&dmxdevfilter->mutex)) { + up(&dmxdev->mutex); + return -ERESTARTSYS; + } + ret=dvb_dmxdev_set_buffer_size(dmxdevfilter, arg); + up(&dmxdevfilter->mutex); + break; + + case DMX_GET_EVENT: + break; + + case DMX_GET_PES_PIDS: + if (!dmxdev->demux->get_pes_pids) { + ret=-EINVAL; + break; + } + dmxdev->demux->get_pes_pids(dmxdev->demux, (u16 *)parg); + break; + + case DMX_GET_STC: + if (!dmxdev->demux->get_stc) { + ret=-EINVAL; + break; + } + ret = dmxdev->demux->get_stc(dmxdev->demux, + ((struct dmx_stc *)parg)->num, + &((struct dmx_stc *)parg)->stc, + &((struct dmx_stc *)parg)->base); + break; + + default: + ret=-EINVAL; + } + up(&dmxdev->mutex); + return ret; +} + +static int dvb_demux_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return dvb_usercopy(inode, file, cmd, arg, dvb_demux_do_ioctl); +} + + +static unsigned int dvb_demux_poll (struct file *file, poll_table *wait) +{ + struct dmxdev_filter *dmxdevfilter = dvb_dmxdev_file_to_filter(file); + unsigned int mask = 0; + + if (!dmxdevfilter) + return -EINVAL; + + poll_wait(file, &dmxdevfilter->buffer.queue, wait); + + if (dmxdevfilter->state != DMXDEV_STATE_GO && + dmxdevfilter->state != DMXDEV_STATE_DONE && + dmxdevfilter->state != DMXDEV_STATE_TIMEDOUT) + return 0; + + if (dmxdevfilter->buffer.error) + mask |= (POLLIN | POLLRDNORM | POLLPRI | POLLERR); + + if (dmxdevfilter->buffer.pread != dmxdevfilter->buffer.pwrite) + mask |= (POLLIN | POLLRDNORM | POLLPRI); + + return mask; +} + + +static int dvb_demux_release(struct inode *inode, struct file *file) +{ + struct dmxdev_filter *dmxdevfilter = dvb_dmxdev_file_to_filter(file); + struct dmxdev *dmxdev = dmxdevfilter->dev; + + return dvb_dmxdev_filter_free(dmxdev, dmxdevfilter); +} + + +static struct file_operations dvb_demux_fops = { + .owner = THIS_MODULE, + .read = dvb_demux_read, + .ioctl = dvb_demux_ioctl, + .open = dvb_demux_open, + .release = dvb_demux_release, + .poll = dvb_demux_poll, +}; + + +static struct dvb_device dvbdev_demux = { + .priv = NULL, + .users = 1, + .writers = 1, + .fops = &dvb_demux_fops +}; + + +static int dvb_dvr_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev=(struct dvb_device *) file->private_data; + struct dmxdev *dmxdev=(struct dmxdev *) dvbdev->priv; + + int ret=0; + + if (down_interruptible (&dmxdev->mutex)) + return -ERESTARTSYS; + + switch (cmd) { + case DMX_SET_BUFFER_SIZE: + // FIXME: implement + ret=0; + break; + + default: + ret=-EINVAL; + } + up(&dmxdev->mutex); + return ret; +} + + +static int dvb_dvr_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return dvb_usercopy(inode, file, cmd, arg, dvb_dvr_do_ioctl); +} + + +static unsigned int dvb_dvr_poll (struct file *file, poll_table *wait) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct dmxdev *dmxdev = (struct dmxdev *) dvbdev->priv; + unsigned int mask = 0; + + dprintk ("function : %s\n", __FUNCTION__); + + poll_wait(file, &dmxdev->dvr_buffer.queue, wait); + + if ((file->f_flags&O_ACCMODE) == O_RDONLY) { + if (dmxdev->dvr_buffer.error) + mask |= (POLLIN | POLLRDNORM | POLLPRI | POLLERR); + + if (dmxdev->dvr_buffer.pread!=dmxdev->dvr_buffer.pwrite) + mask |= (POLLIN | POLLRDNORM | POLLPRI); + } else + mask |= (POLLOUT | POLLWRNORM | POLLPRI); + + return mask; +} + + +static struct file_operations dvb_dvr_fops = { + .owner = THIS_MODULE, + .read = dvb_dvr_read, + .write = dvb_dvr_write, + .ioctl = dvb_dvr_ioctl, + .open = dvb_dvr_open, + .release = dvb_dvr_release, + .poll = dvb_dvr_poll, +}; + +static struct dvb_device dvbdev_dvr = { + .priv = NULL, + .users = 1, + .writers = 1, + .fops = &dvb_dvr_fops +}; + +int +dvb_dmxdev_init(struct dmxdev *dmxdev, struct dvb_adapter *dvb_adapter) +{ + int i; + + if (dmxdev->demux->open(dmxdev->demux) < 0) + return -EUSERS; + + dmxdev->filter = vmalloc(dmxdev->filternum*sizeof(struct dmxdev_filter)); + if (!dmxdev->filter) + return -ENOMEM; + + dmxdev->dvr = vmalloc(dmxdev->filternum*sizeof(struct dmxdev_dvr)); + if (!dmxdev->dvr) { + vfree(dmxdev->filter); + dmxdev->filter = NULL; + return -ENOMEM; + } + + sema_init(&dmxdev->mutex, 1); + spin_lock_init(&dmxdev->lock); + for (i=0; i<dmxdev->filternum; i++) { + dmxdev->filter[i].dev=dmxdev; + dmxdev->filter[i].buffer.data=NULL; + dvb_dmxdev_filter_state_set(&dmxdev->filter[i], DMXDEV_STATE_FREE); + dmxdev->dvr[i].dev=dmxdev; + dmxdev->dvr[i].buffer.data=NULL; + dvb_dmxdev_filter_state_set(&dmxdev->filter[i], DMXDEV_STATE_FREE); + dvb_dmxdev_dvr_state_set(&dmxdev->dvr[i], DMXDEV_STATE_FREE); + } + + dvb_register_device(dvb_adapter, &dmxdev->dvbdev, &dvbdev_demux, dmxdev, DVB_DEVICE_DEMUX); + dvb_register_device(dvb_adapter, &dmxdev->dvr_dvbdev, &dvbdev_dvr, dmxdev, DVB_DEVICE_DVR); + + dvb_dmxdev_buffer_init(&dmxdev->dvr_buffer); + + return 0; +} +EXPORT_SYMBOL(dvb_dmxdev_init); + +void +dvb_dmxdev_release(struct dmxdev *dmxdev) +{ + dvb_unregister_device(dmxdev->dvbdev); + dvb_unregister_device(dmxdev->dvr_dvbdev); + + vfree(dmxdev->filter); + dmxdev->filter=NULL; + vfree(dmxdev->dvr); + dmxdev->dvr=NULL; + dmxdev->demux->close(dmxdev->demux); +} +EXPORT_SYMBOL(dvb_dmxdev_release); diff --git a/drivers/media/dvb/dvb-core/dmxdev.h b/drivers/media/dvb/dvb-core/dmxdev.h new file mode 100644 index 000000000000..395a9cd75012 --- /dev/null +++ b/drivers/media/dvb/dvb-core/dmxdev.h @@ -0,0 +1,128 @@ +/* + * dmxdev.h + * + * Copyright (C) 2000 Ralph Metzler & Marcus Metzler + * for convergence integrated media GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * 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 Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef _DMXDEV_H_ +#define _DMXDEV_H_ + +#include <linux/types.h> +#include <linux/spinlock.h> +#include <linux/kernel.h> +#include <linux/timer.h> +#include <linux/wait.h> +#include <linux/fs.h> +#include <linux/string.h> +#include <asm/semaphore.h> + +#include <linux/dvb/dmx.h> + +#include "dvbdev.h" +#include "demux.h" + +enum dmxdevype { + DMXDEV_TYPE_NONE, + DMXDEV_TYPE_SEC, + DMXDEV_TYPE_PES, +}; + +enum dmxdev_state { + DMXDEV_STATE_FREE, + DMXDEV_STATE_ALLOCATED, + DMXDEV_STATE_SET, + DMXDEV_STATE_GO, + DMXDEV_STATE_DONE, + DMXDEV_STATE_TIMEDOUT +}; + +struct dmxdev_buffer { + u8 *data; + int size; + int pread; + int pwrite; + wait_queue_head_t queue; + int error; +}; + +struct dmxdev_filter { + struct dvb_device *dvbdev; + + union { + struct dmx_section_filter *sec; + } filter; + + union { + struct dmx_ts_feed *ts; + struct dmx_section_feed *sec; + } feed; + + union { + struct dmx_sct_filter_params sec; + struct dmx_pes_filter_params pes; + } params; + + int type; + enum dmxdev_state state; + struct dmxdev *dev; + struct dmxdev_buffer buffer; + + struct semaphore mutex; + + /* only for sections */ + struct timer_list timer; + int todo; + u8 secheader[3]; + + u16 pid; +}; + + +struct dmxdev_dvr { + int state; + struct dmxdev *dev; + struct dmxdev_buffer buffer; +}; + + +struct dmxdev { + struct dvb_device *dvbdev; + struct dvb_device *dvr_dvbdev; + + struct dmxdev_filter *filter; + struct dmxdev_dvr *dvr; + struct dmx_demux *demux; + + int filternum; + int capabilities; +#define DMXDEV_CAP_DUPLEX 1 + struct dmx_frontend *dvr_orig_fe; + + struct dmxdev_buffer dvr_buffer; +#define DVR_BUFFER_SIZE (10*188*1024) + + struct semaphore mutex; + spinlock_t lock; +}; + + +int dvb_dmxdev_init(struct dmxdev *dmxdev, struct dvb_adapter *); +void dvb_dmxdev_release(struct dmxdev *dmxdev); + +#endif /* _DMXDEV_H_ */ diff --git a/drivers/media/dvb/dvb-core/dvb_ca_en50221.c b/drivers/media/dvb/dvb-core/dvb_ca_en50221.c new file mode 100644 index 000000000000..c1ea89f2880c --- /dev/null +++ b/drivers/media/dvb/dvb-core/dvb_ca_en50221.c @@ -0,0 +1,1778 @@ +/* + * dvb_ca.c: generic DVB functions for EN50221 CAM interfaces + * + * Copyright (C) 2004 Andrew de Quincey + * + * Parts of this file were based on sources as follows: + * + * Copyright (C) 2003 Ralph Metzler <rjkm@metzlerbros.de> + * + * based on code: + * + * Copyright (C) 1999-2002 Ralph Metzler + * & Marcus Metzler for convergence integrated media GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/vmalloc.h> +#include <linux/delay.h> +#include <linux/rwsem.h> + +#include "dvb_ca_en50221.h" +#include "dvb_ringbuffer.h" + +static int dvb_ca_en50221_debug; + +module_param_named(cam_debug, dvb_ca_en50221_debug, int, 0644); +MODULE_PARM_DESC(cam_debug, "enable verbose debug messages"); + +#define dprintk if (dvb_ca_en50221_debug) printk + +#define INIT_TIMEOUT_SECS 5 + +#define HOST_LINK_BUF_SIZE 0x200 + +#define RX_BUFFER_SIZE 65535 + +#define MAX_RX_PACKETS_PER_ITERATION 10 + +#define CTRLIF_DATA 0 +#define CTRLIF_COMMAND 1 +#define CTRLIF_STATUS 1 +#define CTRLIF_SIZE_LOW 2 +#define CTRLIF_SIZE_HIGH 3 + +#define CMDREG_HC 1 /* Host control */ +#define CMDREG_SW 2 /* Size write */ +#define CMDREG_SR 4 /* Size read */ +#define CMDREG_RS 8 /* Reset interface */ +#define CMDREG_FRIE 0x40 /* Enable FR interrupt */ +#define CMDREG_DAIE 0x80 /* Enable DA interrupt */ +#define IRQEN (CMDREG_DAIE) + +#define STATUSREG_RE 1 /* read error */ +#define STATUSREG_WE 2 /* write error */ +#define STATUSREG_FR 0x40 /* module free */ +#define STATUSREG_DA 0x80 /* data available */ +#define STATUSREG_TXERR (STATUSREG_RE|STATUSREG_WE) /* general transfer error */ + + +#define DVB_CA_SLOTSTATE_NONE 0 +#define DVB_CA_SLOTSTATE_UNINITIALISED 1 +#define DVB_CA_SLOTSTATE_RUNNING 2 +#define DVB_CA_SLOTSTATE_INVALID 3 +#define DVB_CA_SLOTSTATE_WAITREADY 4 +#define DVB_CA_SLOTSTATE_VALIDATE 5 +#define DVB_CA_SLOTSTATE_WAITFR 6 +#define DVB_CA_SLOTSTATE_LINKINIT 7 + + +/* Information on a CA slot */ +struct dvb_ca_slot { + + /* current state of the CAM */ + int slot_state; + + /* Number of CAMCHANGES that have occurred since last processing */ + atomic_t camchange_count; + + /* Type of last CAMCHANGE */ + int camchange_type; + + /* base address of CAM config */ + u32 config_base; + + /* value to write into Config Control register */ + u8 config_option; + + /* if 1, the CAM supports DA IRQs */ + u8 da_irq_supported:1; + + /* size of the buffer to use when talking to the CAM */ + int link_buf_size; + + /* semaphore for syncing access to slot structure */ + struct rw_semaphore sem; + + /* buffer for incoming packets */ + struct dvb_ringbuffer rx_buffer; + + /* timer used during various states of the slot */ + unsigned long timeout; +}; + +/* Private CA-interface information */ +struct dvb_ca_private { + + /* pointer back to the public data structure */ + struct dvb_ca_en50221 *pub; + + /* the DVB device */ + struct dvb_device *dvbdev; + + /* Flags describing the interface (DVB_CA_FLAG_*) */ + u32 flags; + + /* number of slots supported by this CA interface */ + unsigned int slot_count; + + /* information on each slot */ + struct dvb_ca_slot *slot_info; + + /* wait queues for read() and write() operations */ + wait_queue_head_t wait_queue; + + /* PID of the monitoring thread */ + pid_t thread_pid; + + /* Wait queue used when shutting thread down */ + wait_queue_head_t thread_queue; + + /* Flag indicating when thread should exit */ + unsigned int exit:1; + + /* Flag indicating if the CA device is open */ + unsigned int open:1; + + /* Flag indicating the thread should wake up now */ + unsigned int wakeup:1; + + /* Delay the main thread should use */ + unsigned long delay; + + /* Slot to start looking for data to read from in the next user-space read operation */ + int next_read_slot; +}; + +static void dvb_ca_en50221_thread_wakeup(struct dvb_ca_private *ca); +static int dvb_ca_en50221_read_data(struct dvb_ca_private *ca, int slot, u8 * ebuf, int ecount); +static int dvb_ca_en50221_write_data(struct dvb_ca_private *ca, int slot, u8 * ebuf, int ecount); + + +/** + * Safely find needle in haystack. + * + * @param haystack Buffer to look in. + * @param hlen Number of bytes in haystack. + * @param needle Buffer to find. + * @param nlen Number of bytes in needle. + * @return Pointer into haystack needle was found at, or NULL if not found. + */ +static u8 *findstr(u8 * haystack, int hlen, u8 * needle, int nlen) +{ + int i; + + if (hlen < nlen) + return NULL; + + for (i = 0; i <= hlen - nlen; i++) { + if (!strncmp(haystack + i, needle, nlen)) + return haystack + i; + } + + return NULL; +} + + + +/* ******************************************************************************** */ +/* EN50221 physical interface functions */ + + +/** + * Check CAM status. + */ +static int dvb_ca_en50221_check_camstatus(struct dvb_ca_private *ca, int slot) +{ + int slot_status; + int cam_present_now; + int cam_changed; + + /* IRQ mode */ + if (ca->flags & DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE) { + return (atomic_read(&ca->slot_info[slot].camchange_count) != 0); + } + + /* poll mode */ + slot_status = ca->pub->poll_slot_status(ca->pub, slot, ca->open); + + cam_present_now = (slot_status & DVB_CA_EN50221_POLL_CAM_PRESENT) ? 1 : 0; + cam_changed = (slot_status & DVB_CA_EN50221_POLL_CAM_CHANGED) ? 1 : 0; + if (!cam_changed) { + int cam_present_old = (ca->slot_info[slot].slot_state != DVB_CA_SLOTSTATE_NONE); + cam_changed = (cam_present_now != cam_present_old); + } + + if (cam_changed) { + if (!cam_present_now) { + ca->slot_info[slot].camchange_type = DVB_CA_EN50221_CAMCHANGE_REMOVED; + } else { + ca->slot_info[slot].camchange_type = DVB_CA_EN50221_CAMCHANGE_INSERTED; + } + atomic_set(&ca->slot_info[slot].camchange_count, 1); + } else { + if ((ca->slot_info[slot].slot_state == DVB_CA_SLOTSTATE_WAITREADY) && + (slot_status & DVB_CA_EN50221_POLL_CAM_READY)) { + // move to validate state if reset is completed + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_VALIDATE; + } + } + + return cam_changed; +} + + +/** + * Wait for flags to become set on the STATUS register on a CAM interface, + * checking for errors and timeout. + * + * @param ca CA instance. + * @param slot Slot on interface. + * @param waitfor Flags to wait for. + * @param timeout_ms Timeout in milliseconds. + * + * @return 0 on success, nonzero on error. + */ +static int dvb_ca_en50221_wait_if_status(struct dvb_ca_private *ca, int slot, + u8 waitfor, int timeout_hz) +{ + unsigned long timeout; + unsigned long start; + + dprintk("%s\n", __FUNCTION__); + + /* loop until timeout elapsed */ + start = jiffies; + timeout = jiffies + timeout_hz; + while (1) { + /* read the status and check for error */ + int res = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS); + if (res < 0) + return -EIO; + + /* if we got the flags, it was successful! */ + if (res & waitfor) { + dprintk("%s succeeded timeout:%lu\n", __FUNCTION__, jiffies - start); + return 0; + } + + /* check for timeout */ + if (time_after(jiffies, timeout)) { + break; + } + + /* wait for a bit */ + msleep(1); + } + + dprintk("%s failed timeout:%lu\n", __FUNCTION__, jiffies - start); + + /* if we get here, we've timed out */ + return -ETIMEDOUT; +} + + +/** + * Initialise the link layer connection to a CAM. + * + * @param ca CA instance. + * @param slot Slot id. + * + * @return 0 on success, nonzero on failure. + */ +static int dvb_ca_en50221_link_init(struct dvb_ca_private *ca, int slot) +{ + int ret; + int buf_size; + u8 buf[2]; + + dprintk("%s\n", __FUNCTION__); + + /* we'll be determining these during this function */ + ca->slot_info[slot].da_irq_supported = 0; + + /* set the host link buffer size temporarily. it will be overwritten with the + * real negotiated size later. */ + ca->slot_info[slot].link_buf_size = 2; + + /* read the buffer size from the CAM */ + if ((ret = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_COMMAND, IRQEN | CMDREG_SR)) != 0) + return ret; + if ((ret = dvb_ca_en50221_wait_if_status(ca, slot, STATUSREG_DA, HZ / 10)) != 0) + return ret; + if ((ret = dvb_ca_en50221_read_data(ca, slot, buf, 2)) != 2) + return -EIO; + if ((ret = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_COMMAND, IRQEN)) != 0) + return ret; + + /* store it, and choose the minimum of our buffer and the CAM's buffer size */ + buf_size = (buf[0] << 8) | buf[1]; + if (buf_size > HOST_LINK_BUF_SIZE) + buf_size = HOST_LINK_BUF_SIZE; + ca->slot_info[slot].link_buf_size = buf_size; + buf[0] = buf_size >> 8; + buf[1] = buf_size & 0xff; + dprintk("Chosen link buffer size of %i\n", buf_size); + + /* write the buffer size to the CAM */ + if ((ret = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_COMMAND, IRQEN | CMDREG_SW)) != 0) + return ret; + if ((ret = dvb_ca_en50221_wait_if_status(ca, slot, STATUSREG_FR, HZ / 10)) != 0) + return ret; + if ((ret = dvb_ca_en50221_write_data(ca, slot, buf, 2)) != 2) + return -EIO; + if ((ret = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_COMMAND, IRQEN)) != 0) + return ret; + + /* success */ + return 0; +} + +/** + * Read a tuple from attribute memory. + * + * @param ca CA instance. + * @param slot Slot id. + * @param address Address to read from. Updated. + * @param tupleType Tuple id byte. Updated. + * @param tupleLength Tuple length. Updated. + * @param tuple Dest buffer for tuple (must be 256 bytes). Updated. + * + * @return 0 on success, nonzero on error. + */ +static int dvb_ca_en50221_read_tuple(struct dvb_ca_private *ca, int slot, + int *address, int *tupleType, int *tupleLength, u8 * tuple) +{ + int i; + int _tupleType; + int _tupleLength; + int _address = *address; + + /* grab the next tuple length and type */ + if ((_tupleType = ca->pub->read_attribute_mem(ca->pub, slot, _address)) < 0) + return _tupleType; + if (_tupleType == 0xff) { + dprintk("END OF CHAIN TUPLE type:0x%x\n", _tupleType); + *address += 2; + *tupleType = _tupleType; + *tupleLength = 0; + return 0; + } + if ((_tupleLength = ca->pub->read_attribute_mem(ca->pub, slot, _address + 2)) < 0) + return _tupleLength; + _address += 4; + + dprintk("TUPLE type:0x%x length:%i\n", _tupleType, _tupleLength); + + /* read in the whole tuple */ + for (i = 0; i < _tupleLength; i++) { + tuple[i] = ca->pub->read_attribute_mem(ca->pub, slot, _address + (i * 2)); + dprintk(" 0x%02x: 0x%02x %c\n", + i, tuple[i] & 0xff, + ((tuple[i] > 31) && (tuple[i] < 127)) ? tuple[i] : '.'); + } + _address += (_tupleLength * 2); + + // success + *tupleType = _tupleType; + *tupleLength = _tupleLength; + *address = _address; + return 0; +} + + +/** + * Parse attribute memory of a CAM module, extracting Config register, and checking + * it is a DVB CAM module. + * + * @param ca CA instance. + * @param slot Slot id. + * + * @return 0 on success, <0 on failure. + */ +static int dvb_ca_en50221_parse_attributes(struct dvb_ca_private *ca, int slot) +{ + int address = 0; + int tupleLength; + int tupleType; + u8 tuple[257]; + char *dvb_str; + int rasz; + int status; + int got_cftableentry = 0; + int end_chain = 0; + int i; + u16 manfid = 0; + u16 devid = 0; + + + // CISTPL_DEVICE_0A + if ((status = + dvb_ca_en50221_read_tuple(ca, slot, &address, &tupleType, &tupleLength, tuple)) < 0) + return status; + if (tupleType != 0x1D) + return -EINVAL; + + + + // CISTPL_DEVICE_0C + if ((status = + dvb_ca_en50221_read_tuple(ca, slot, &address, &tupleType, &tupleLength, tuple)) < 0) + return status; + if (tupleType != 0x1C) + return -EINVAL; + + + + // CISTPL_VERS_1 + if ((status = + dvb_ca_en50221_read_tuple(ca, slot, &address, &tupleType, &tupleLength, tuple)) < 0) + return status; + if (tupleType != 0x15) + return -EINVAL; + + + + // CISTPL_MANFID + if ((status = dvb_ca_en50221_read_tuple(ca, slot, &address, &tupleType, + &tupleLength, tuple)) < 0) + return status; + if (tupleType != 0x20) + return -EINVAL; + if (tupleLength != 4) + return -EINVAL; + manfid = (tuple[1] << 8) | tuple[0]; + devid = (tuple[3] << 8) | tuple[2]; + + + + // CISTPL_CONFIG + if ((status = dvb_ca_en50221_read_tuple(ca, slot, &address, &tupleType, + &tupleLength, tuple)) < 0) + return status; + if (tupleType != 0x1A) + return -EINVAL; + if (tupleLength < 3) + return -EINVAL; + + /* extract the configbase */ + rasz = tuple[0] & 3; + if (tupleLength < (3 + rasz + 14)) + return -EINVAL; + ca->slot_info[slot].config_base = 0; + for (i = 0; i < rasz + 1; i++) { + ca->slot_info[slot].config_base |= (tuple[2 + i] << (8 * i)); + } + + /* check it contains the correct DVB string */ + dvb_str = findstr(tuple, tupleLength, "DVB_CI_V", 8); + if (dvb_str == NULL) + return -EINVAL; + if (tupleLength < ((dvb_str - (char *) tuple) + 12)) + return -EINVAL; + + /* is it a version we support? */ + if (strncmp(dvb_str + 8, "1.00", 4)) { + printk("dvb_ca adapter %d: Unsupported DVB CAM module version %c%c%c%c\n", + ca->dvbdev->adapter->num, dvb_str[8], dvb_str[9], dvb_str[10], dvb_str[11]); + return -EINVAL; + } + + /* process the CFTABLE_ENTRY tuples, and any after those */ + while ((!end_chain) && (address < 0x1000)) { + if ((status = dvb_ca_en50221_read_tuple(ca, slot, &address, &tupleType, + &tupleLength, tuple)) < 0) + return status; + switch (tupleType) { + case 0x1B: // CISTPL_CFTABLE_ENTRY + if (tupleLength < (2 + 11 + 17)) + break; + + /* if we've already parsed one, just use it */ + if (got_cftableentry) + break; + + /* get the config option */ + ca->slot_info[slot].config_option = tuple[0] & 0x3f; + + /* OK, check it contains the correct strings */ + if ((findstr(tuple, tupleLength, "DVB_HOST", 8) == NULL) || + (findstr(tuple, tupleLength, "DVB_CI_MODULE", 13) == NULL)) + break; + + got_cftableentry = 1; + break; + + case 0x14: // CISTPL_NO_LINK + break; + + case 0xFF: // CISTPL_END + end_chain = 1; + break; + + default: /* Unknown tuple type - just skip this tuple and move to the next one */ + dprintk("dvb_ca: Skipping unknown tuple type:0x%x length:0x%x\n", tupleType, + tupleLength); + break; + } + } + + if ((address > 0x1000) || (!got_cftableentry)) + return -EINVAL; + + dprintk("Valid DVB CAM detected MANID:%x DEVID:%x CONFIGBASE:0x%x CONFIGOPTION:0x%x\n", + manfid, devid, ca->slot_info[slot].config_base, ca->slot_info[slot].config_option); + + // success! + return 0; +} + + +/** + * Set CAM's configoption correctly. + * + * @param ca CA instance. + * @param slot Slot containing the CAM. + */ +static int dvb_ca_en50221_set_configoption(struct dvb_ca_private *ca, int slot) +{ + int configoption; + + dprintk("%s\n", __FUNCTION__); + + /* set the config option */ + ca->pub->write_attribute_mem(ca->pub, slot, + ca->slot_info[slot].config_base, + ca->slot_info[slot].config_option); + + /* check it */ + configoption = ca->pub->read_attribute_mem(ca->pub, slot, ca->slot_info[slot].config_base); + dprintk("Set configoption 0x%x, read configoption 0x%x\n", + ca->slot_info[slot].config_option, configoption & 0x3f); + + /* fine! */ + return 0; + +} + + +/** + * This function talks to an EN50221 CAM control interface. It reads a buffer of + * data from the CAM. The data can either be stored in a supplied buffer, or + * automatically be added to the slot's rx_buffer. + * + * @param ca CA instance. + * @param slot Slot to read from. + * @param ebuf If non-NULL, the data will be written to this buffer. If NULL, + * the data will be added into the buffering system as a normal fragment. + * @param ecount Size of ebuf. Ignored if ebuf is NULL. + * + * @return Number of bytes read, or < 0 on error + */ +static int dvb_ca_en50221_read_data(struct dvb_ca_private *ca, int slot, u8 * ebuf, int ecount) +{ + int bytes_read; + int status; + u8 buf[HOST_LINK_BUF_SIZE]; + int i; + + dprintk("%s\n", __FUNCTION__); + + /* check if we have space for a link buf in the rx_buffer */ + if (ebuf == NULL) { + int buf_free; + + down_read(&ca->slot_info[slot].sem); + if (ca->slot_info[slot].rx_buffer.data == NULL) { + up_read(&ca->slot_info[slot].sem); + status = -EIO; + goto exit; + } + buf_free = dvb_ringbuffer_free(&ca->slot_info[slot].rx_buffer); + up_read(&ca->slot_info[slot].sem); + + if (buf_free < (ca->slot_info[slot].link_buf_size + DVB_RINGBUFFER_PKTHDRSIZE)) { + status = -EAGAIN; + goto exit; + } + } + + /* check if there is data available */ + if ((status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS)) < 0) + goto exit; + if (!(status & STATUSREG_DA)) { + /* no data */ + status = 0; + goto exit; + } + + /* read the amount of data */ + if ((status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_SIZE_HIGH)) < 0) + goto exit; + bytes_read = status << 8; + if ((status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_SIZE_LOW)) < 0) + goto exit; + bytes_read |= status; + + /* check it will fit */ + if (ebuf == NULL) { + if (bytes_read > ca->slot_info[slot].link_buf_size) { + printk("dvb_ca adapter %d: CAM tried to send a buffer larger than the link buffer size (%i > %i)!\n", + ca->dvbdev->adapter->num, bytes_read, ca->slot_info[slot].link_buf_size); + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_LINKINIT; + status = -EIO; + goto exit; + } + if (bytes_read < 2) { + printk("dvb_ca adapter %d: CAM sent a buffer that was less than 2 bytes!\n", + ca->dvbdev->adapter->num); + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_LINKINIT; + status = -EIO; + goto exit; + } + } else { + if (bytes_read > ecount) { + printk("dvb_ca adapter %d: CAM tried to send a buffer larger than the ecount size!\n", + ca->dvbdev->adapter->num); + status = -EIO; + goto exit; + } + } + + /* fill the buffer */ + for (i = 0; i < bytes_read; i++) { + /* read byte and check */ + if ((status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_DATA)) < 0) + goto exit; + + /* OK, store it in the buffer */ + buf[i] = status; + } + + /* check for read error (RE should now be 0) */ + if ((status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS)) < 0) + goto exit; + if (status & STATUSREG_RE) { + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_LINKINIT; + status = -EIO; + goto exit; + } + + /* OK, add it to the receive buffer, or copy into external buffer if supplied */ + if (ebuf == NULL) { + down_read(&ca->slot_info[slot].sem); + if (ca->slot_info[slot].rx_buffer.data == NULL) { + up_read(&ca->slot_info[slot].sem); + status = -EIO; + goto exit; + } + dvb_ringbuffer_pkt_write(&ca->slot_info[slot].rx_buffer, buf, bytes_read); + up_read(&ca->slot_info[slot].sem); + } else { + memcpy(ebuf, buf, bytes_read); + } + + dprintk("Received CA packet for slot %i connection id 0x%x last_frag:%i size:0x%x\n", slot, + buf[0], (buf[1] & 0x80) == 0, bytes_read); + + /* wake up readers when a last_fragment is received */ + if ((buf[1] & 0x80) == 0x00) { + wake_up_interruptible(&ca->wait_queue); + } + status = bytes_read; + +exit: + return status; +} + + +/** + * This function talks to an EN50221 CAM control interface. It writes a buffer of data + * to a CAM. + * + * @param ca CA instance. + * @param slot Slot to write to. + * @param ebuf The data in this buffer is treated as a complete link-level packet to + * be written. + * @param count Size of ebuf. + * + * @return Number of bytes written, or < 0 on error. + */ +static int dvb_ca_en50221_write_data(struct dvb_ca_private *ca, int slot, u8 * buf, int bytes_write) +{ + int status; + int i; + + dprintk("%s\n", __FUNCTION__); + + + // sanity check + if (bytes_write > ca->slot_info[slot].link_buf_size) + return -EINVAL; + + /* check if interface is actually waiting for us to read from it, or if a read is in progress */ + if ((status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS)) < 0) + goto exitnowrite; + if (status & (STATUSREG_DA | STATUSREG_RE)) { + status = -EAGAIN; + goto exitnowrite; + } + + /* OK, set HC bit */ + if ((status = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_COMMAND, + IRQEN | CMDREG_HC)) != 0) + goto exit; + + /* check if interface is still free */ + if ((status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS)) < 0) + goto exit; + if (!(status & STATUSREG_FR)) { + /* it wasn't free => try again later */ + status = -EAGAIN; + goto exit; + } + + /* send the amount of data */ + if ((status = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_SIZE_HIGH, bytes_write >> 8)) != 0) + goto exit; + if ((status = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_SIZE_LOW, + bytes_write & 0xff)) != 0) + goto exit; + + /* send the buffer */ + for (i = 0; i < bytes_write; i++) { + if ((status = ca->pub->write_cam_control(ca->pub, slot, CTRLIF_DATA, buf[i])) != 0) + goto exit; + } + + /* check for write error (WE should now be 0) */ + if ((status = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS)) < 0) + goto exit; + if (status & STATUSREG_WE) { + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_LINKINIT; + status = -EIO; + goto exit; + } + status = bytes_write; + + dprintk("Wrote CA packet for slot %i, connection id 0x%x last_frag:%i size:0x%x\n", slot, + buf[0], (buf[1] & 0x80) == 0, bytes_write); + +exit: + ca->pub->write_cam_control(ca->pub, slot, CTRLIF_COMMAND, IRQEN); + +exitnowrite: + return status; +} +EXPORT_SYMBOL(dvb_ca_en50221_camchange_irq); + + + +/* ******************************************************************************** */ +/* EN50221 higher level functions */ + + +/** + * A CAM has been removed => shut it down. + * + * @param ca CA instance. + * @param slot Slot to shut down. + */ +static int dvb_ca_en50221_slot_shutdown(struct dvb_ca_private *ca, int slot) +{ + dprintk("%s\n", __FUNCTION__); + + down_write(&ca->slot_info[slot].sem); + ca->pub->slot_shutdown(ca->pub, slot); + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_NONE; + vfree(ca->slot_info[slot].rx_buffer.data); + ca->slot_info[slot].rx_buffer.data = NULL; + up_write(&ca->slot_info[slot].sem); + + /* need to wake up all processes to check if they're now + trying to write to a defunct CAM */ + wake_up_interruptible(&ca->wait_queue); + + dprintk("Slot %i shutdown\n", slot); + + /* success */ + return 0; +} +EXPORT_SYMBOL(dvb_ca_en50221_camready_irq); + + +/** + * A CAMCHANGE IRQ has occurred. + * + * @param ca CA instance. + * @param slot Slot concerned. + * @param change_type One of the DVB_CA_CAMCHANGE_* values. + */ +void dvb_ca_en50221_camchange_irq(struct dvb_ca_en50221 *pubca, int slot, int change_type) +{ + struct dvb_ca_private *ca = (struct dvb_ca_private *) pubca->private; + + dprintk("CAMCHANGE IRQ slot:%i change_type:%i\n", slot, change_type); + + switch (change_type) { + case DVB_CA_EN50221_CAMCHANGE_REMOVED: + case DVB_CA_EN50221_CAMCHANGE_INSERTED: + break; + + default: + return; + } + + ca->slot_info[slot].camchange_type = change_type; + atomic_inc(&ca->slot_info[slot].camchange_count); + dvb_ca_en50221_thread_wakeup(ca); +} +EXPORT_SYMBOL(dvb_ca_en50221_frda_irq); + + +/** + * A CAMREADY IRQ has occurred. + * + * @param ca CA instance. + * @param slot Slot concerned. + */ +void dvb_ca_en50221_camready_irq(struct dvb_ca_en50221 *pubca, int slot) +{ + struct dvb_ca_private *ca = (struct dvb_ca_private *) pubca->private; + + dprintk("CAMREADY IRQ slot:%i\n", slot); + + if (ca->slot_info[slot].slot_state == DVB_CA_SLOTSTATE_WAITREADY) { + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_VALIDATE; + dvb_ca_en50221_thread_wakeup(ca); + } +} + + +/** + * An FR or DA IRQ has occurred. + * + * @param ca CA instance. + * @param slot Slot concerned. + */ +void dvb_ca_en50221_frda_irq(struct dvb_ca_en50221 *pubca, int slot) +{ + struct dvb_ca_private *ca = (struct dvb_ca_private *) pubca->private; + int flags; + + dprintk("FR/DA IRQ slot:%i\n", slot); + + switch (ca->slot_info[slot].slot_state) { + case DVB_CA_SLOTSTATE_LINKINIT: + flags = ca->pub->read_cam_control(pubca, slot, CTRLIF_STATUS); + if (flags & STATUSREG_DA) { + dprintk("CAM supports DA IRQ\n"); + ca->slot_info[slot].da_irq_supported = 1; + } + break; + + case DVB_CA_SLOTSTATE_RUNNING: + if (ca->open) + dvb_ca_en50221_read_data(ca, slot, NULL, 0); + break; + } +} + + + +/* ******************************************************************************** */ +/* EN50221 thread functions */ + +/** + * Wake up the DVB CA thread + * + * @param ca CA instance. + */ +static void dvb_ca_en50221_thread_wakeup(struct dvb_ca_private *ca) +{ + + dprintk("%s\n", __FUNCTION__); + + ca->wakeup = 1; + mb(); + wake_up_interruptible(&ca->thread_queue); +} + +/** + * Used by the CA thread to determine if an early wakeup is necessary + * + * @param ca CA instance. + */ +static int dvb_ca_en50221_thread_should_wakeup(struct dvb_ca_private *ca) +{ + if (ca->wakeup) { + ca->wakeup = 0; + return 1; + } + if (ca->exit) + return 1; + + return 0; +} + + +/** + * Update the delay used by the thread. + * + * @param ca CA instance. + */ +static void dvb_ca_en50221_thread_update_delay(struct dvb_ca_private *ca) +{ + int delay; + int curdelay = 100000000; + int slot; + + for (slot = 0; slot < ca->slot_count; slot++) { + switch (ca->slot_info[slot].slot_state) { + default: + case DVB_CA_SLOTSTATE_NONE: + case DVB_CA_SLOTSTATE_INVALID: + delay = HZ * 60; + if (!(ca->flags & DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE)) { + delay = HZ / 10; + } + break; + + case DVB_CA_SLOTSTATE_UNINITIALISED: + case DVB_CA_SLOTSTATE_WAITREADY: + case DVB_CA_SLOTSTATE_VALIDATE: + case DVB_CA_SLOTSTATE_WAITFR: + case DVB_CA_SLOTSTATE_LINKINIT: + delay = HZ / 10; + break; + + case DVB_CA_SLOTSTATE_RUNNING: + delay = HZ * 60; + if (!(ca->flags & DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE)) { + delay = HZ / 10; + } + if (ca->open) { + if ((!ca->slot_info[slot].da_irq_supported) || + (!(ca->flags & DVB_CA_EN50221_FLAG_IRQ_DA))) { + delay = HZ / 10; + } + } + break; + } + + if (delay < curdelay) + curdelay = delay; + } + + ca->delay = curdelay; +} + + + +/** + * Kernel thread which monitors CA slots for CAM changes, and performs data transfers. + */ +static int dvb_ca_en50221_thread(void *data) +{ + struct dvb_ca_private *ca = (struct dvb_ca_private *) data; + char name[15]; + int slot; + int flags; + int status; + int pktcount; + void *rxbuf; + + dprintk("%s\n", __FUNCTION__); + + /* setup kernel thread */ + snprintf(name, sizeof(name), "kdvb-ca-%i:%i", ca->dvbdev->adapter->num, ca->dvbdev->id); + + lock_kernel(); + daemonize(name); + sigfillset(¤t->blocked); + unlock_kernel(); + + /* choose the correct initial delay */ + dvb_ca_en50221_thread_update_delay(ca); + + /* main loop */ + while (!ca->exit) { + /* sleep for a bit */ + if (!ca->wakeup) { + flags = wait_event_interruptible_timeout(ca->thread_queue, + dvb_ca_en50221_thread_should_wakeup(ca), + ca->delay); + if ((flags == -ERESTARTSYS) || ca->exit) { + /* got signal or quitting */ + break; + } + } + ca->wakeup = 0; + + /* go through all the slots processing them */ + for (slot = 0; slot < ca->slot_count; slot++) { + + // check the cam status + deal with CAMCHANGEs + while (dvb_ca_en50221_check_camstatus(ca, slot)) { + /* clear down an old CI slot if necessary */ + if (ca->slot_info[slot].slot_state != DVB_CA_SLOTSTATE_NONE) + dvb_ca_en50221_slot_shutdown(ca, slot); + + /* if a CAM is NOW present, initialise it */ + if (ca->slot_info[slot].camchange_type == DVB_CA_EN50221_CAMCHANGE_INSERTED) { + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_UNINITIALISED; + } + + /* we've handled one CAMCHANGE */ + dvb_ca_en50221_thread_update_delay(ca); + atomic_dec(&ca->slot_info[slot].camchange_count); + } + + // CAM state machine + switch (ca->slot_info[slot].slot_state) { + case DVB_CA_SLOTSTATE_NONE: + case DVB_CA_SLOTSTATE_INVALID: + // no action needed + break; + + case DVB_CA_SLOTSTATE_UNINITIALISED: + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_WAITREADY; + ca->pub->slot_reset(ca->pub, slot); + ca->slot_info[slot].timeout = jiffies + (INIT_TIMEOUT_SECS * HZ); + break; + + case DVB_CA_SLOTSTATE_WAITREADY: + if (time_after(jiffies, ca->slot_info[slot].timeout)) { + printk("dvb_ca adaptor %d: PC card did not respond :(\n", + ca->dvbdev->adapter->num); + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_INVALID; + dvb_ca_en50221_thread_update_delay(ca); + break; + } + // no other action needed; will automatically change state when ready + break; + + case DVB_CA_SLOTSTATE_VALIDATE: + if (dvb_ca_en50221_parse_attributes(ca, slot) + != 0) { + printk("dvb_ca adapter %d: Invalid PC card inserted :(\n", + ca->dvbdev->adapter->num); + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_INVALID; + dvb_ca_en50221_thread_update_delay(ca); + break; + } + if (dvb_ca_en50221_set_configoption(ca, slot) != 0) { + printk("dvb_ca adapter %d: Unable to initialise CAM :(\n", + ca->dvbdev->adapter->num); + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_INVALID; + dvb_ca_en50221_thread_update_delay(ca); + break; + } + if (ca->pub->write_cam_control(ca->pub, slot, + CTRLIF_COMMAND, CMDREG_RS) != 0) { + printk("dvb_ca adapter %d: Unable to reset CAM IF\n", + ca->dvbdev->adapter->num); + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_INVALID; + dvb_ca_en50221_thread_update_delay(ca); + break; + } + dprintk("DVB CAM validated successfully\n"); + + ca->slot_info[slot].timeout = jiffies + (INIT_TIMEOUT_SECS * HZ); + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_WAITFR; + ca->wakeup = 1; + break; + + case DVB_CA_SLOTSTATE_WAITFR: + if (time_after(jiffies, ca->slot_info[slot].timeout)) { + printk("dvb_ca adapter %d: DVB CAM did not respond :(\n", + ca->dvbdev->adapter->num); + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_INVALID; + dvb_ca_en50221_thread_update_delay(ca); + break; + } + + flags = ca->pub->read_cam_control(ca->pub, slot, CTRLIF_STATUS); + if (flags & STATUSREG_FR) { + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_LINKINIT; + ca->wakeup = 1; + } + break; + + case DVB_CA_SLOTSTATE_LINKINIT: + if (dvb_ca_en50221_link_init(ca, slot) != 0) { + printk("dvb_ca adapter %d: DVB CAM link initialisation failed :(\n", ca->dvbdev->adapter->num); + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_INVALID; + dvb_ca_en50221_thread_update_delay(ca); + break; + } + + rxbuf = vmalloc(RX_BUFFER_SIZE); + if (rxbuf == NULL) { + printk("dvb_ca adapter %d: Unable to allocate CAM rx buffer :(\n", ca->dvbdev->adapter->num); + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_INVALID; + dvb_ca_en50221_thread_update_delay(ca); + break; + } + down_write(&ca->slot_info[slot].sem); + dvb_ringbuffer_init(&ca->slot_info[slot].rx_buffer, rxbuf, RX_BUFFER_SIZE); + up_write(&ca->slot_info[slot].sem); + + ca->pub->slot_ts_enable(ca->pub, slot); + ca->slot_info[slot].slot_state = DVB_CA_SLOTSTATE_RUNNING; + dvb_ca_en50221_thread_update_delay(ca); + printk("dvb_ca adapter %d: DVB CAM detected and initialised successfully\n", ca->dvbdev->adapter->num); + break; + + case DVB_CA_SLOTSTATE_RUNNING: + if (!ca->open) + continue; + + // no need to poll if the CAM supports IRQs + if (ca->slot_info[slot].da_irq_supported) + break; + + // poll mode + pktcount = 0; + while ((status = dvb_ca_en50221_read_data(ca, slot, NULL, 0)) > 0) { + if (!ca->open) + break; + + /* if a CAMCHANGE occurred at some point, do not do any more processing of this slot */ + if (dvb_ca_en50221_check_camstatus(ca, slot)) { + // we dont want to sleep on the next iteration so we can handle the cam change + ca->wakeup = 1; + break; + } + + /* check if we've hit our limit this time */ + if (++pktcount >= MAX_RX_PACKETS_PER_ITERATION) { + // dont sleep; there is likely to be more data to read + ca->wakeup = 1; + break; + } + } + break; + } + } + } + + /* completed */ + ca->thread_pid = 0; + mb(); + wake_up_interruptible(&ca->thread_queue); + return 0; +} + + + +/* ******************************************************************************** */ +/* EN50221 IO interface functions */ + +/** + * Real ioctl implementation. + * NOTE: CA_SEND_MSG/CA_GET_MSG ioctls have userspace buffers passed to them. + * + * @param inode Inode concerned. + * @param file File concerned. + * @param cmd IOCTL command. + * @param arg Associated argument. + * + * @return 0 on success, <0 on error. + */ +static int dvb_ca_en50221_io_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct dvb_ca_private *ca = (struct dvb_ca_private *) dvbdev->priv; + int err = 0; + int slot; + + dprintk("%s\n", __FUNCTION__); + + switch (cmd) { + case CA_RESET: + for (slot = 0; slot < ca->slot_count; slot++) { + if (ca->slot_info[slot].slot_state != DVB_CA_SLOTSTATE_NONE) { + dvb_ca_en50221_slot_shutdown(ca, slot); + if (ca->flags & DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE) + dvb_ca_en50221_camchange_irq(ca->pub, + slot, + DVB_CA_EN50221_CAMCHANGE_INSERTED); + } + } + ca->next_read_slot = 0; + dvb_ca_en50221_thread_wakeup(ca); + break; + + case CA_GET_CAP: { + struct ca_caps *caps = (struct ca_caps *) parg; + + caps->slot_num = ca->slot_count; + caps->slot_type = CA_CI_LINK; + caps->descr_num = 0; + caps->descr_type = 0; + break; + } + + case CA_GET_SLOT_INFO: { + struct ca_slot_info *info = (struct ca_slot_info *) parg; + + if ((info->num > ca->slot_count) || (info->num < 0)) + return -EINVAL; + + info->type = CA_CI_LINK; + info->flags = 0; + if ((ca->slot_info[info->num].slot_state != DVB_CA_SLOTSTATE_NONE) + && (ca->slot_info[info->num].slot_state != DVB_CA_SLOTSTATE_INVALID)) { + info->flags = CA_CI_MODULE_PRESENT; + } + if (ca->slot_info[info->num].slot_state == DVB_CA_SLOTSTATE_RUNNING) { + info->flags |= CA_CI_MODULE_READY; + } + break; + } + + default: + err = -EINVAL; + break; + } + + return err; +} + + +/** + * Wrapper for ioctl implementation. + * + * @param inode Inode concerned. + * @param file File concerned. + * @param cmd IOCTL command. + * @param arg Associated argument. + * + * @return 0 on success, <0 on error. + */ +static int dvb_ca_en50221_io_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return dvb_usercopy(inode, file, cmd, arg, dvb_ca_en50221_io_do_ioctl); +} + + +/** + * Implementation of write() syscall. + * + * @param file File structure. + * @param buf Source buffer. + * @param count Size of source buffer. + * @param ppos Position in file (ignored). + * + * @return Number of bytes read, or <0 on error. + */ +static ssize_t dvb_ca_en50221_io_write(struct file *file, + const char __user * buf, size_t count, loff_t * ppos) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct dvb_ca_private *ca = (struct dvb_ca_private *) dvbdev->priv; + u8 slot, connection_id; + int status; + char fragbuf[HOST_LINK_BUF_SIZE]; + int fragpos = 0; + int fraglen; + unsigned long timeout; + int written; + + dprintk("%s\n", __FUNCTION__); + + /* Incoming packet has a 2 byte header. hdr[0] = slot_id, hdr[1] = connection_id */ + if (count < 2) + return -EINVAL; + + /* extract slot & connection id */ + if (copy_from_user(&slot, buf, 1)) + return -EFAULT; + if (copy_from_user(&connection_id, buf + 1, 1)) + return -EFAULT; + buf += 2; + count -= 2; + + /* check if the slot is actually running */ + if (ca->slot_info[slot].slot_state != DVB_CA_SLOTSTATE_RUNNING) + return -EINVAL; + + /* fragment the packets & store in the buffer */ + while (fragpos < count) { + fraglen = ca->slot_info[slot].link_buf_size - 2; + if ((count - fragpos) < fraglen) + fraglen = count - fragpos; + + fragbuf[0] = connection_id; + fragbuf[1] = ((fragpos + fraglen) < count) ? 0x80 : 0x00; + if ((status = copy_from_user(fragbuf + 2, buf + fragpos, fraglen)) != 0) + goto exit; + + timeout = jiffies + HZ / 2; + written = 0; + while (!time_after(jiffies, timeout)) { + /* check the CAM hasn't been removed/reset in the meantime */ + if (ca->slot_info[slot].slot_state != DVB_CA_SLOTSTATE_RUNNING) { + status = -EIO; + goto exit; + } + + status = dvb_ca_en50221_write_data(ca, slot, fragbuf, fraglen + 2); + if (status == (fraglen + 2)) { + written = 1; + break; + } + if (status != -EAGAIN) + goto exit; + + msleep(1); + } + if (!written) { + status = -EIO; + goto exit; + } + + fragpos += fraglen; + } + status = count + 2; + +exit: + return status; +} + + +/** + * Condition for waking up in dvb_ca_en50221_io_read_condition + */ +static int dvb_ca_en50221_io_read_condition(struct dvb_ca_private *ca, int *result, int *_slot) +{ + int slot; + int slot_count = 0; + int idx; + int fraglen; + int connection_id = -1; + int found = 0; + u8 hdr[2]; + + slot = ca->next_read_slot; + while ((slot_count < ca->slot_count) && (!found)) { + if (ca->slot_info[slot].slot_state != DVB_CA_SLOTSTATE_RUNNING) + goto nextslot; + + down_read(&ca->slot_info[slot].sem); + + if (ca->slot_info[slot].rx_buffer.data == NULL) { + up_read(&ca->slot_info[slot].sem); + return 0; + } + + idx = dvb_ringbuffer_pkt_next(&ca->slot_info[slot].rx_buffer, -1, &fraglen); + while (idx != -1) { + dvb_ringbuffer_pkt_read(&ca->slot_info[slot].rx_buffer, idx, 0, hdr, 2, 0); + if (connection_id == -1) + connection_id = hdr[0]; + if ((hdr[0] == connection_id) && ((hdr[1] & 0x80) == 0)) { + *_slot = slot; + found = 1; + break; + } + + idx = dvb_ringbuffer_pkt_next(&ca->slot_info[slot].rx_buffer, idx, &fraglen); + } + + if (!found) + up_read(&ca->slot_info[slot].sem); + + nextslot: + slot = (slot + 1) % ca->slot_count; + slot_count++; + } + + ca->next_read_slot = slot; + return found; +} + + +/** + * Implementation of read() syscall. + * + * @param file File structure. + * @param buf Destination buffer. + * @param count Size of destination buffer. + * @param ppos Position in file (ignored). + * + * @return Number of bytes read, or <0 on error. + */ +static ssize_t dvb_ca_en50221_io_read(struct file *file, char __user * buf, + size_t count, loff_t * ppos) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct dvb_ca_private *ca = (struct dvb_ca_private *) dvbdev->priv; + int status; + int result = 0; + u8 hdr[2]; + int slot; + int connection_id = -1; + size_t idx, idx2; + int last_fragment = 0; + size_t fraglen; + int pktlen; + int dispose = 0; + + dprintk("%s\n", __FUNCTION__); + + /* Outgoing packet has a 2 byte header. hdr[0] = slot_id, hdr[1] = connection_id */ + if (count < 2) + return -EINVAL; + + /* wait for some data */ + if ((status = dvb_ca_en50221_io_read_condition(ca, &result, &slot)) == 0) { + + /* if we're in nonblocking mode, exit immediately */ + if (file->f_flags & O_NONBLOCK) + return -EWOULDBLOCK; + + /* wait for some data */ + status = wait_event_interruptible(ca->wait_queue, + dvb_ca_en50221_io_read_condition + (ca, &result, &slot)); + } + if ((status < 0) || (result < 0)) { + if (result) + return result; + return status; + } + + idx = dvb_ringbuffer_pkt_next(&ca->slot_info[slot].rx_buffer, -1, &fraglen); + pktlen = 2; + do { + if (idx == -1) { + printk("dvb_ca adapter %d: BUG: read packet ended before last_fragment encountered\n", ca->dvbdev->adapter->num); + status = -EIO; + goto exit; + } + + dvb_ringbuffer_pkt_read(&ca->slot_info[slot].rx_buffer, idx, 0, hdr, 2, 0); + if (connection_id == -1) + connection_id = hdr[0]; + if (hdr[0] == connection_id) { + if (pktlen < count) { + if ((pktlen + fraglen - 2) > count) { + fraglen = count - pktlen; + } else { + fraglen -= 2; + } + + if ((status = dvb_ringbuffer_pkt_read(&ca->slot_info[slot].rx_buffer, idx, 2, + buf + pktlen, fraglen, 1)) < 0) { + goto exit; + } + pktlen += fraglen; + } + + if ((hdr[1] & 0x80) == 0) + last_fragment = 1; + dispose = 1; + } + + idx2 = dvb_ringbuffer_pkt_next(&ca->slot_info[slot].rx_buffer, idx, &fraglen); + if (dispose) + dvb_ringbuffer_pkt_dispose(&ca->slot_info[slot].rx_buffer, idx); + idx = idx2; + dispose = 0; + } while (!last_fragment); + + hdr[0] = slot; + hdr[1] = connection_id; + if ((status = copy_to_user(buf, hdr, 2)) != 0) + goto exit; + status = pktlen; + + exit: + up_read(&ca->slot_info[slot].sem); + return status; +} + + +/** + * Implementation of file open syscall. + * + * @param inode Inode concerned. + * @param file File concerned. + * + * @return 0 on success, <0 on failure. + */ +static int dvb_ca_en50221_io_open(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct dvb_ca_private *ca = (struct dvb_ca_private *) dvbdev->priv; + int err; + int i; + + dprintk("%s\n", __FUNCTION__); + + if (!try_module_get(ca->pub->owner)) + return -EIO; + + err = dvb_generic_open(inode, file); + if (err < 0) + return err; + + for (i = 0; i < ca->slot_count; i++) { + + if (ca->slot_info[i].slot_state == DVB_CA_SLOTSTATE_RUNNING) { + down_write(&ca->slot_info[i].sem); + if (ca->slot_info[i].rx_buffer.data != NULL) { + dvb_ringbuffer_flush(&ca->slot_info[i].rx_buffer); + } + up_write(&ca->slot_info[i].sem); + } + } + + ca->open = 1; + dvb_ca_en50221_thread_update_delay(ca); + dvb_ca_en50221_thread_wakeup(ca); + + return 0; +} + + +/** + * Implementation of file close syscall. + * + * @param inode Inode concerned. + * @param file File concerned. + * + * @return 0 on success, <0 on failure. + */ +static int dvb_ca_en50221_io_release(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct dvb_ca_private *ca = (struct dvb_ca_private *) dvbdev->priv; + int err = 0; + + dprintk("%s\n", __FUNCTION__); + + /* mark the CA device as closed */ + ca->open = 0; + dvb_ca_en50221_thread_update_delay(ca); + + err = dvb_generic_release(inode, file); + + module_put(ca->pub->owner); + + return 0; +} + + +/** + * Implementation of poll() syscall. + * + * @param file File concerned. + * @param wait poll wait table. + * + * @return Standard poll mask. + */ +static unsigned int dvb_ca_en50221_io_poll(struct file *file, poll_table * wait) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct dvb_ca_private *ca = (struct dvb_ca_private *) dvbdev->priv; + unsigned int mask = 0; + int slot; + int result = 0; + + dprintk("%s\n", __FUNCTION__); + + if (dvb_ca_en50221_io_read_condition(ca, &result, &slot) == 1) { + up_read(&ca->slot_info[slot].sem); + mask |= POLLIN; + } + + /* if there is something, return now */ + if (mask) + return mask; + + /* wait for something to happen */ + poll_wait(file, &ca->wait_queue, wait); + + if (dvb_ca_en50221_io_read_condition(ca, &result, &slot) == 1) { + up_read(&ca->slot_info[slot].sem); + mask |= POLLIN; + } + + return mask; +} +EXPORT_SYMBOL(dvb_ca_en50221_init); + + +static struct file_operations dvb_ca_fops = { + .owner = THIS_MODULE, + .read = dvb_ca_en50221_io_read, + .write = dvb_ca_en50221_io_write, + .ioctl = dvb_ca_en50221_io_ioctl, + .open = dvb_ca_en50221_io_open, + .release = dvb_ca_en50221_io_release, + .poll = dvb_ca_en50221_io_poll, +}; + +static struct dvb_device dvbdev_ca = { + .priv = NULL, + .users = 1, + .readers = 1, + .writers = 1, + .fops = &dvb_ca_fops, +}; + + +/* ******************************************************************************** */ +/* Initialisation/shutdown functions */ + + +/** + * Initialise a new DVB CA EN50221 interface device. + * + * @param dvb_adapter DVB adapter to attach the new CA device to. + * @param ca The dvb_ca instance. + * @param flags Flags describing the CA device (DVB_CA_FLAG_*). + * @param slot_count Number of slots supported. + * + * @return 0 on success, nonzero on failure + */ +int dvb_ca_en50221_init(struct dvb_adapter *dvb_adapter, + struct dvb_ca_en50221 *pubca, int flags, int slot_count) +{ + int ret; + struct dvb_ca_private *ca = NULL; + int i; + + dprintk("%s\n", __FUNCTION__); + + if (slot_count < 1) + return -EINVAL; + + /* initialise the system data */ + if ((ca = + (struct dvb_ca_private *) kmalloc(sizeof(struct dvb_ca_private), + GFP_KERNEL)) == NULL) { + ret = -ENOMEM; + goto error; + } + memset(ca, 0, sizeof(struct dvb_ca_private)); + ca->pub = pubca; + ca->flags = flags; + ca->slot_count = slot_count; + if ((ca->slot_info = kmalloc(sizeof(struct dvb_ca_slot) * slot_count, GFP_KERNEL)) == NULL) { + ret = -ENOMEM; + goto error; + } + memset(ca->slot_info, 0, sizeof(struct dvb_ca_slot) * slot_count); + init_waitqueue_head(&ca->wait_queue); + ca->thread_pid = 0; + init_waitqueue_head(&ca->thread_queue); + ca->exit = 0; + ca->open = 0; + ca->wakeup = 0; + ca->next_read_slot = 0; + pubca->private = ca; + + /* register the DVB device */ + ret = dvb_register_device(dvb_adapter, &ca->dvbdev, &dvbdev_ca, ca, DVB_DEVICE_CA); + if (ret) + goto error; + + /* now initialise each slot */ + for (i = 0; i < slot_count; i++) { + memset(&ca->slot_info[i], 0, sizeof(struct dvb_ca_slot)); + ca->slot_info[i].slot_state = DVB_CA_SLOTSTATE_NONE; + atomic_set(&ca->slot_info[i].camchange_count, 0); + ca->slot_info[i].camchange_type = DVB_CA_EN50221_CAMCHANGE_REMOVED; + init_rwsem(&ca->slot_info[i].sem); + } + + if (signal_pending(current)) { + ret = -EINTR; + goto error; + } + mb(); + + /* create a kthread for monitoring this CA device */ + + ret = kernel_thread(dvb_ca_en50221_thread, ca, 0); + + if (ret < 0) { + printk("dvb_ca_init: failed to start kernel_thread (%d)\n", ret); + goto error; + } + ca->thread_pid = ret; + return 0; + + error: + if (ca != NULL) { + if (ca->dvbdev != NULL) + dvb_unregister_device(ca->dvbdev); + kfree(ca->slot_info); + kfree(ca); + } + pubca->private = NULL; + return ret; +} +EXPORT_SYMBOL(dvb_ca_en50221_release); + + + +/** + * Release a DVB CA EN50221 interface device. + * + * @param ca_dev The dvb_device_t instance for the CA device. + * @param ca The associated dvb_ca instance. + */ +void dvb_ca_en50221_release(struct dvb_ca_en50221 *pubca) +{ + struct dvb_ca_private *ca = (struct dvb_ca_private *) pubca->private; + int i; + + dprintk("%s\n", __FUNCTION__); + + /* shutdown the thread if there was one */ + if (ca->thread_pid) { + if (kill_proc(ca->thread_pid, 0, 1) == -ESRCH) { + printk("dvb_ca_release adapter %d: thread PID %d already died\n", + ca->dvbdev->adapter->num, ca->thread_pid); + } else { + ca->exit = 1; + mb(); + dvb_ca_en50221_thread_wakeup(ca); + wait_event_interruptible(ca->thread_queue, ca->thread_pid == 0); + } + } + + for (i = 0; i < ca->slot_count; i++) { + dvb_ca_en50221_slot_shutdown(ca, i); + } + kfree(ca->slot_info); + dvb_unregister_device(ca->dvbdev); + kfree(ca); + pubca->private = NULL; +} diff --git a/drivers/media/dvb/dvb-core/dvb_ca_en50221.h b/drivers/media/dvb/dvb-core/dvb_ca_en50221.h new file mode 100644 index 000000000000..8467e63ddc0d --- /dev/null +++ b/drivers/media/dvb/dvb-core/dvb_ca_en50221.h @@ -0,0 +1,134 @@ +/* + * dvb_ca.h: generic DVB functions for EN50221 CA interfaces + * + * Copyright (C) 2004 Andrew de Quincey + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * 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 Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _DVB_CA_EN50221_H_ +#define _DVB_CA_EN50221_H_ + +#include <linux/list.h> +#include <linux/dvb/ca.h> + +#include "dvbdev.h" + +#define DVB_CA_EN50221_POLL_CAM_PRESENT 1 +#define DVB_CA_EN50221_POLL_CAM_CHANGED 2 +#define DVB_CA_EN50221_POLL_CAM_READY 4 + +#define DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE 1 +#define DVB_CA_EN50221_FLAG_IRQ_FR 2 +#define DVB_CA_EN50221_FLAG_IRQ_DA 4 + +#define DVB_CA_EN50221_CAMCHANGE_REMOVED 0 +#define DVB_CA_EN50221_CAMCHANGE_INSERTED 1 + + + +/* Structure describing a CA interface */ +struct dvb_ca_en50221 { + + /* the module owning this structure */ + struct module* owner; + + /* NOTE: the read_*, write_* and poll_slot_status functions must use locks as + * they may be called from several threads at once */ + + /* functions for accessing attribute memory on the CAM */ + int (*read_attribute_mem)(struct dvb_ca_en50221* ca, int slot, int address); + int (*write_attribute_mem)(struct dvb_ca_en50221* ca, int slot, int address, u8 value); + + /* functions for accessing the control interface on the CAM */ + int (*read_cam_control)(struct dvb_ca_en50221* ca, int slot, u8 address); + int (*write_cam_control)(struct dvb_ca_en50221* ca, int slot, u8 address, u8 value); + + /* Functions for controlling slots */ + int (*slot_reset)(struct dvb_ca_en50221* ca, int slot); + int (*slot_shutdown)(struct dvb_ca_en50221* ca, int slot); + int (*slot_ts_enable)(struct dvb_ca_en50221* ca, int slot); + + /* + * Poll slot status. + * Only necessary if DVB_CA_FLAG_EN50221_IRQ_CAMCHANGE is not set + */ + int (*poll_slot_status)(struct dvb_ca_en50221* ca, int slot, int open); + + /* private data, used by caller */ + void* data; + + /* Opaque data used by the dvb_ca core. Do not modify! */ + void* private; +}; + + + + +/* ******************************************************************************** */ +/* Functions for reporting IRQ events */ + +/** + * A CAMCHANGE IRQ has occurred. + * + * @param ca CA instance. + * @param slot Slot concerned. + * @param change_type One of the DVB_CA_CAMCHANGE_* values + */ +void dvb_ca_en50221_camchange_irq(struct dvb_ca_en50221* pubca, int slot, int change_type); + +/** + * A CAMREADY IRQ has occurred. + * + * @param ca CA instance. + * @param slot Slot concerned. + */ +void dvb_ca_en50221_camready_irq(struct dvb_ca_en50221* pubca, int slot); + +/** + * An FR or a DA IRQ has occurred. + * + * @param ca CA instance. + * @param slot Slot concerned. + */ +void dvb_ca_en50221_frda_irq(struct dvb_ca_en50221* ca, int slot); + + + +/* ******************************************************************************** */ +/* Initialisation/shutdown functions */ + +/** + * Initialise a new DVB CA device. + * + * @param dvb_adapter DVB adapter to attach the new CA device to. + * @param ca The dvb_ca instance. + * @param flags Flags describing the CA device (DVB_CA_EN50221_FLAG_*). + * @param slot_count Number of slots supported. + * + * @return 0 on success, nonzero on failure + */ +extern int dvb_ca_en50221_init(struct dvb_adapter *dvb_adapter, struct dvb_ca_en50221* ca, int flags, int slot_count); + +/** + * Release a DVB CA device. + * + * @param ca The associated dvb_ca instance. + */ +extern void dvb_ca_en50221_release(struct dvb_ca_en50221* ca); + + + +#endif diff --git a/drivers/media/dvb/dvb-core/dvb_demux.c b/drivers/media/dvb/dvb-core/dvb_demux.c new file mode 100644 index 000000000000..ac9889d22288 --- /dev/null +++ b/drivers/media/dvb/dvb-core/dvb_demux.c @@ -0,0 +1,1294 @@ +/* + * dvb_demux.c - DVB kernel demux API + * + * Copyright (C) 2000-2001 Ralph Metzler <ralph@convergence.de> + * & Marcus Metzler <marcus@convergence.de> + * for convergence integrated media GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * 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 Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/module.h> +#include <linux/poll.h> +#include <linux/string.h> +#include <linux/crc32.h> +#include <asm/uaccess.h> + +#include "dvb_demux.h" + +#define NOBUFS +/* +** #define DVB_DEMUX_SECTION_LOSS_LOG to monitor payload loss in the syslog +*/ +// #define DVB_DEMUX_SECTION_LOSS_LOG + + +static LIST_HEAD(dmx_muxs); + + +static int dmx_register_demux(struct dmx_demux *demux) +{ + demux->users = 0; + list_add(&demux->reg_list, &dmx_muxs); + return 0; +} + +static int dmx_unregister_demux(struct dmx_demux* demux) +{ + struct list_head *pos, *n, *head=&dmx_muxs; + + list_for_each_safe (pos, n, head) { + if (DMX_DIR_ENTRY(pos) == demux) { + if (demux->users>0) + return -EINVAL; + list_del(pos); + return 0; + } + } + + return -ENODEV; +} + + +/****************************************************************************** + * static inlined helper functions + ******************************************************************************/ + + +static inline u16 section_length(const u8 *buf) +{ + return 3+((buf[1]&0x0f)<<8)+buf[2]; +} + + +static inline u16 ts_pid(const u8 *buf) +{ + return ((buf[1]&0x1f)<<8)+buf[2]; +} + + +static inline u8 payload(const u8 *tsp) +{ + if (!(tsp[3] & 0x10)) // no payload? + return 0; + if (tsp[3] & 0x20) { // adaptation field? + if (tsp[4] > 183) // corrupted data? + return 0; + else + return 184-1-tsp[4]; + } + return 184; +} + + +static u32 dvb_dmx_crc32 (struct dvb_demux_feed *f, const u8 *src, size_t len) +{ + return (f->feed.sec.crc_val = crc32_be (f->feed.sec.crc_val, src, len)); +} + + +static void dvb_dmx_memcopy (struct dvb_demux_feed *f, u8 *d, const u8 *s, size_t len) +{ + memcpy (d, s, len); +} + + +/****************************************************************************** + * Software filter functions + ******************************************************************************/ + +static inline int dvb_dmx_swfilter_payload (struct dvb_demux_feed *feed, const u8 *buf) +{ + int count = payload(buf); + int p; + //int ccok; + //u8 cc; + + if (count == 0) + return -1; + + p = 188-count; + + /* + cc=buf[3]&0x0f; + ccok=((dvbdmxfeed->cc+1)&0x0f)==cc ? 1 : 0; + dvbdmxfeed->cc=cc; + if (!ccok) + printk("missed packet!\n"); + */ + + if (buf[1] & 0x40) // PUSI ? + feed->peslen = 0xfffa; + + feed->peslen += count; + + return feed->cb.ts (&buf[p], count, NULL, 0, &feed->feed.ts, DMX_OK); +} + + +static int dvb_dmx_swfilter_sectionfilter (struct dvb_demux_feed *feed, + struct dvb_demux_filter *f) +{ + u8 neq = 0; + int i; + + for (i=0; i<DVB_DEMUX_MASK_MAX; i++) { + u8 xor = f->filter.filter_value[i] ^ feed->feed.sec.secbuf[i]; + + if (f->maskandmode[i] & xor) + return 0; + + neq |= f->maskandnotmode[i] & xor; + } + + if (f->doneq && !neq) + return 0; + + return feed->cb.sec (feed->feed.sec.secbuf, feed->feed.sec.seclen, + NULL, 0, &f->filter, DMX_OK); +} + + +static inline int dvb_dmx_swfilter_section_feed (struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct dvb_demux_filter *f = feed->filter; + struct dmx_section_feed *sec = &feed->feed.sec; + int section_syntax_indicator; + + if (!sec->is_filtering) + return 0; + + if (!f) + return 0; + + if (sec->check_crc) { + section_syntax_indicator = ((sec->secbuf[1] & 0x80) != 0); + if (section_syntax_indicator && + demux->check_crc32(feed, sec->secbuf, sec->seclen)) + return -1; + } + + do { + if (dvb_dmx_swfilter_sectionfilter(feed, f) < 0) + return -1; + } while ((f = f->next) && sec->is_filtering); + + sec->seclen = 0; + + return 0; +} + + +static void dvb_dmx_swfilter_section_new(struct dvb_demux_feed *feed) +{ + struct dmx_section_feed *sec = &feed->feed.sec; + +#ifdef DVB_DEMUX_SECTION_LOSS_LOG + if(sec->secbufp < sec->tsfeedp) + { + int i, n = sec->tsfeedp - sec->secbufp; + + /* section padding is done with 0xff bytes entirely. + ** due to speed reasons, we won't check all of them + ** but just first and last + */ + if(sec->secbuf[0] != 0xff || sec->secbuf[n-1] != 0xff) + { + printk("dvb_demux.c section ts padding loss: %d/%d\n", + n, sec->tsfeedp); + printk("dvb_demux.c pad data:"); + for(i = 0; i < n; i++) + printk(" %02x", sec->secbuf[i]); + printk("\n"); + } + } +#endif + + sec->tsfeedp = sec->secbufp = sec->seclen = 0; + sec->secbuf = sec->secbuf_base; +} + +/* +** Losless Section Demux 1.4.1 by Emard +** Valsecchi Patrick: +** - middle of section A (no PUSI) +** - end of section A and start of section B +** (with PUSI pointing to the start of the second section) +** +** In this case, without feed->pusi_seen you'll receive a garbage section +** consisting of the end of section A. Basically because tsfeedp +** is incemented and the use=0 condition is not raised +** when the second packet arrives. +** +** Fix: +** when demux is started, let feed->pusi_seen = 0 to +** prevent initial feeding of garbage from the end of +** previous section. When you for the first time see PUSI=1 +** then set feed->pusi_seen = 1 +*/ +static int dvb_dmx_swfilter_section_copy_dump(struct dvb_demux_feed *feed, const u8 *buf, u8 len) +{ + struct dvb_demux *demux = feed->demux; + struct dmx_section_feed *sec = &feed->feed.sec; + u16 limit, seclen, n; + + if(sec->tsfeedp >= DMX_MAX_SECFEED_SIZE) + return 0; + + if(sec->tsfeedp + len > DMX_MAX_SECFEED_SIZE) + { +#ifdef DVB_DEMUX_SECTION_LOSS_LOG + printk("dvb_demux.c section buffer full loss: %d/%d\n", + sec->tsfeedp + len - DMX_MAX_SECFEED_SIZE, DMX_MAX_SECFEED_SIZE); +#endif + len = DMX_MAX_SECFEED_SIZE - sec->tsfeedp; + } + + if(len <= 0) + return 0; + + demux->memcopy(feed, sec->secbuf_base + sec->tsfeedp, buf, len); + sec->tsfeedp += len; + + /* ----------------------------------------------------- + ** Dump all the sections we can find in the data (Emard) + */ + + limit = sec->tsfeedp; + if(limit > DMX_MAX_SECFEED_SIZE) + return -1; /* internal error should never happen */ + + /* to be sure always set secbuf */ + sec->secbuf = sec->secbuf_base + sec->secbufp; + + for(n = 0; sec->secbufp + 2 < limit; n++) + { + seclen = section_length(sec->secbuf); + if(seclen <= 0 || seclen > DMX_MAX_SECFEED_SIZE + || seclen + sec->secbufp > limit) + return 0; + sec->seclen = seclen; + sec->crc_val = ~0; + /* dump [secbuf .. secbuf+seclen) */ + if(feed->pusi_seen) + dvb_dmx_swfilter_section_feed(feed); +#ifdef DVB_DEMUX_SECTION_LOSS_LOG + else + printk("dvb_demux.c pusi not seen, discarding section data\n"); +#endif + sec->secbufp += seclen; /* secbufp and secbuf moving together is */ + sec->secbuf += seclen; /* redundand but saves pointer arithmetic */ + } + + return 0; +} + + +static int dvb_dmx_swfilter_section_packet(struct dvb_demux_feed *feed, const u8 *buf) +{ + u8 p, count; + int ccok, dc_i = 0; + u8 cc; + + count = payload(buf); + + if (count == 0) /* count == 0 if no payload or out of range */ + return -1; + + p = 188 - count; /* payload start */ + + cc = buf[3] & 0x0f; + ccok = ((feed->cc + 1) & 0x0f) == cc; + feed->cc = cc; + + if (buf[3] & 0x20) { + /* adaption field present, check for discontinuity_indicator */ + if ((buf[4] > 0) && (buf[5] & 0x80)) + dc_i = 1; + } + + if (!ccok || dc_i) { +#ifdef DVB_DEMUX_SECTION_LOSS_LOG + printk("dvb_demux.c discontinuity detected %d bytes lost\n", count); + /* those bytes under sume circumstances will again be reported + ** in the following dvb_dmx_swfilter_section_new + */ +#endif + /* Discontinuity detected. Reset pusi_seen = 0 to + ** stop feeding of suspicious data until next PUSI=1 arrives + */ + feed->pusi_seen = 0; + dvb_dmx_swfilter_section_new(feed); + return 0; + } + + if (buf[1] & 0x40) { + // PUSI=1 (is set), section boundary is here + if (count > 1 && buf[p] < count) { + const u8 *before = buf+p+1; + u8 before_len = buf[p]; + const u8 *after = before+before_len; + u8 after_len = count-1-before_len; + + dvb_dmx_swfilter_section_copy_dump(feed, before, before_len); + /* before start of new section, set pusi_seen = 1 */ + feed->pusi_seen = 1; + dvb_dmx_swfilter_section_new(feed); + dvb_dmx_swfilter_section_copy_dump(feed, after, after_len); + } +#ifdef DVB_DEMUX_SECTION_LOSS_LOG + else + if (count > 0) + printk("dvb_demux.c PUSI=1 but %d bytes lost\n", count); +#endif + } else { + // PUSI=0 (is not set), no section boundary + const u8 *entire = buf+p; + u8 entire_len = count; + + dvb_dmx_swfilter_section_copy_dump(feed, entire, entire_len); + } + return 0; +} + + +static inline void dvb_dmx_swfilter_packet_type(struct dvb_demux_feed *feed, const u8 *buf) +{ + switch(feed->type) { + case DMX_TYPE_TS: + if (!feed->feed.ts.is_filtering) + break; + if (feed->ts_type & TS_PACKET) { + if (feed->ts_type & TS_PAYLOAD_ONLY) + dvb_dmx_swfilter_payload(feed, buf); + else + feed->cb.ts(buf, 188, NULL, 0, &feed->feed.ts, DMX_OK); + } + if (feed->ts_type & TS_DECODER) + if (feed->demux->write_to_decoder) + feed->demux->write_to_decoder(feed, buf, 188); + break; + + case DMX_TYPE_SEC: + if (!feed->feed.sec.is_filtering) + break; + if (dvb_dmx_swfilter_section_packet(feed, buf) < 0) + feed->feed.sec.seclen = feed->feed.sec.secbufp=0; + break; + + default: + break; + } +} + +#define DVR_FEED(f) \ + (((f)->type == DMX_TYPE_TS) && \ + ((f)->feed.ts.is_filtering) && \ + (((f)->ts_type & (TS_PACKET|TS_PAYLOAD_ONLY)) == TS_PACKET)) + +static void dvb_dmx_swfilter_packet(struct dvb_demux *demux, const u8 *buf) +{ + struct dvb_demux_feed *feed; + struct list_head *pos, *head=&demux->feed_list; + u16 pid = ts_pid(buf); + int dvr_done = 0; + + list_for_each(pos, head) { + feed = list_entry(pos, struct dvb_demux_feed, list_head); + + if ((feed->pid != pid) && (feed->pid != 0x2000)) + continue; + + /* copy each packet only once to the dvr device, even + * if a PID is in multiple filters (e.g. video + PCR) */ + if ((DVR_FEED(feed)) && (dvr_done++)) + continue; + + if (feed->pid == pid) { + dvb_dmx_swfilter_packet_type(feed, buf); + if (DVR_FEED(feed)) + continue; + } + + if (feed->pid == 0x2000) + feed->cb.ts(buf, 188, NULL, 0, &feed->feed.ts, DMX_OK); + } +} + +void dvb_dmx_swfilter_packets(struct dvb_demux *demux, const u8 *buf, size_t count) +{ + spin_lock(&demux->lock); + + while (count--) { + if(buf[0] == 0x47) { + dvb_dmx_swfilter_packet(demux, buf); + } + buf += 188; + } + + spin_unlock(&demux->lock); +} +EXPORT_SYMBOL(dvb_dmx_swfilter_packets); + + +void dvb_dmx_swfilter(struct dvb_demux *demux, const u8 *buf, size_t count) +{ + int p = 0, i, j; + + spin_lock(&demux->lock); + + if ((i = demux->tsbufp)) { + if (count < (j=188-i)) { + memcpy(&demux->tsbuf[i], buf, count); + demux->tsbufp += count; + goto bailout; + } + memcpy(&demux->tsbuf[i], buf, j); + if (demux->tsbuf[0] == 0x47) + dvb_dmx_swfilter_packet(demux, demux->tsbuf); + demux->tsbufp = 0; + p += j; + } + + while (p < count) { + if (buf[p] == 0x47) { + if (count-p >= 188) { + dvb_dmx_swfilter_packet(demux, buf+p); + p += 188; + } else { + i = count-p; + memcpy(demux->tsbuf, buf+p, i); + demux->tsbufp=i; + goto bailout; + } + } else + p++; + } + +bailout: + spin_unlock(&demux->lock); +} +EXPORT_SYMBOL(dvb_dmx_swfilter); + +void dvb_dmx_swfilter_204(struct dvb_demux *demux, const u8 *buf, size_t count) +{ + int p = 0,i, j; + u8 tmppack[188]; + spin_lock(&demux->lock); + + if ((i = demux->tsbufp)) { + if (count < (j=204-i)) { + memcpy(&demux->tsbuf[i], buf, count); + demux->tsbufp += count; + goto bailout; + } + memcpy(&demux->tsbuf[i], buf, j); + if ((demux->tsbuf[0] == 0x47)|(demux->tsbuf[0]==0xB8)) { + memcpy(tmppack, demux->tsbuf, 188); + if (tmppack[0] == 0xB8) tmppack[0] = 0x47; + dvb_dmx_swfilter_packet(demux, tmppack); + } + demux->tsbufp = 0; + p += j; + } + + while (p < count) { + if ((buf[p] == 0x47)|(buf[p] == 0xB8)) { + if (count-p >= 204) { + memcpy(tmppack, buf+p, 188); + if (tmppack[0] == 0xB8) tmppack[0] = 0x47; + dvb_dmx_swfilter_packet(demux, tmppack); + p += 204; + } else { + i = count-p; + memcpy(demux->tsbuf, buf+p, i); + demux->tsbufp=i; + goto bailout; + } + } else { + p++; + } + } + +bailout: + spin_unlock(&demux->lock); +} +EXPORT_SYMBOL(dvb_dmx_swfilter_204); + + +static struct dvb_demux_filter * dvb_dmx_filter_alloc(struct dvb_demux *demux) +{ + int i; + + for (i=0; i<demux->filternum; i++) + if (demux->filter[i].state == DMX_STATE_FREE) + break; + + if (i == demux->filternum) + return NULL; + + demux->filter[i].state = DMX_STATE_ALLOCATED; + + return &demux->filter[i]; +} + +static struct dvb_demux_feed * dvb_dmx_feed_alloc(struct dvb_demux *demux) +{ + int i; + + for (i=0; i<demux->feednum; i++) + if (demux->feed[i].state == DMX_STATE_FREE) + break; + + if (i == demux->feednum) + return NULL; + + demux->feed[i].state = DMX_STATE_ALLOCATED; + + return &demux->feed[i]; +} + +static int dvb_demux_feed_find(struct dvb_demux_feed *feed) +{ + struct dvb_demux_feed *entry; + + list_for_each_entry(entry, &feed->demux->feed_list, list_head) + if (entry == feed) + return 1; + + return 0; +} + +static void dvb_demux_feed_add(struct dvb_demux_feed *feed) +{ + spin_lock_irq(&feed->demux->lock); + if (dvb_demux_feed_find(feed)) { + printk(KERN_ERR "%s: feed already in list (type=%x state=%x pid=%x)\n", + __FUNCTION__, feed->type, feed->state, feed->pid); + goto out; + } + + list_add(&feed->list_head, &feed->demux->feed_list); +out: + spin_unlock_irq(&feed->demux->lock); +} + +static void dvb_demux_feed_del(struct dvb_demux_feed *feed) +{ + spin_lock_irq(&feed->demux->lock); + if (!(dvb_demux_feed_find(feed))) { + printk(KERN_ERR "%s: feed not in list (type=%x state=%x pid=%x)\n", + __FUNCTION__, feed->type, feed->state, feed->pid); + goto out; + } + + list_del(&feed->list_head); +out: + spin_unlock_irq(&feed->demux->lock); +} + +static int dmx_ts_feed_set (struct dmx_ts_feed* ts_feed, u16 pid, int ts_type, + enum dmx_ts_pes pes_type, size_t callback_length, + size_t circular_buffer_size, int descramble, + struct timespec timeout) +{ + struct dvb_demux_feed *feed = (struct dvb_demux_feed *) ts_feed; + struct dvb_demux *demux = feed->demux; + + if (pid > DMX_MAX_PID) + return -EINVAL; + + if (down_interruptible (&demux->mutex)) + return -ERESTARTSYS; + + if (ts_type & TS_DECODER) { + if (pes_type >= DMX_TS_PES_OTHER) { + up(&demux->mutex); + return -EINVAL; + } + + if (demux->pesfilter[pes_type] && + demux->pesfilter[pes_type] != feed) { + up(&demux->mutex); + return -EINVAL; + } + + demux->pesfilter[pes_type] = feed; + demux->pids[pes_type] = pid; + } + + dvb_demux_feed_add(feed); + + feed->pid = pid; + feed->buffer_size = circular_buffer_size; + feed->descramble = descramble; + feed->timeout = timeout; + feed->cb_length = callback_length; + feed->ts_type = ts_type; + feed->pes_type = pes_type; + + if (feed->descramble) { + up(&demux->mutex); + return -ENOSYS; + } + + if (feed->buffer_size) { +#ifdef NOBUFS + feed->buffer=NULL; +#else + feed->buffer = vmalloc(feed->buffer_size); + if (!feed->buffer) { + up(&demux->mutex); + return -ENOMEM; + } +#endif + } + + feed->state = DMX_STATE_READY; + up(&demux->mutex); + + return 0; +} + + +static int dmx_ts_feed_start_filtering(struct dmx_ts_feed* ts_feed) +{ + struct dvb_demux_feed *feed = (struct dvb_demux_feed *) ts_feed; + struct dvb_demux *demux = feed->demux; + int ret; + + if (down_interruptible (&demux->mutex)) + return -ERESTARTSYS; + + if (feed->state != DMX_STATE_READY || feed->type != DMX_TYPE_TS) { + up(&demux->mutex); + return -EINVAL; + } + + if (!demux->start_feed) { + up(&demux->mutex); + return -ENODEV; + } + + if ((ret = demux->start_feed(feed)) < 0) { + up(&demux->mutex); + return ret; + } + + spin_lock_irq(&demux->lock); + ts_feed->is_filtering = 1; + feed->state = DMX_STATE_GO; + spin_unlock_irq(&demux->lock); + up(&demux->mutex); + + return 0; +} + +static int dmx_ts_feed_stop_filtering(struct dmx_ts_feed* ts_feed) +{ + struct dvb_demux_feed *feed = (struct dvb_demux_feed *) ts_feed; + struct dvb_demux *demux = feed->demux; + int ret; + + if (down_interruptible (&demux->mutex)) + return -ERESTARTSYS; + + if (feed->state < DMX_STATE_GO) { + up(&demux->mutex); + return -EINVAL; + } + + if (!demux->stop_feed) { + up(&demux->mutex); + return -ENODEV; + } + + ret = demux->stop_feed(feed); + + spin_lock_irq(&demux->lock); + ts_feed->is_filtering = 0; + feed->state = DMX_STATE_ALLOCATED; + spin_unlock_irq(&demux->lock); + up(&demux->mutex); + + return ret; +} + +static int dvbdmx_allocate_ts_feed (struct dmx_demux *dmx, struct dmx_ts_feed **ts_feed, + dmx_ts_cb callback) +{ + struct dvb_demux *demux = (struct dvb_demux *) dmx; + struct dvb_demux_feed *feed; + + if (down_interruptible (&demux->mutex)) + return -ERESTARTSYS; + + if (!(feed = dvb_dmx_feed_alloc(demux))) { + up(&demux->mutex); + return -EBUSY; + } + + feed->type = DMX_TYPE_TS; + feed->cb.ts = callback; + feed->demux = demux; + feed->pid = 0xffff; + feed->peslen = 0xfffa; + feed->buffer = NULL; + + (*ts_feed) = &feed->feed.ts; + (*ts_feed)->parent = dmx; + (*ts_feed)->priv = NULL; + (*ts_feed)->is_filtering = 0; + (*ts_feed)->start_filtering = dmx_ts_feed_start_filtering; + (*ts_feed)->stop_filtering = dmx_ts_feed_stop_filtering; + (*ts_feed)->set = dmx_ts_feed_set; + + + if (!(feed->filter = dvb_dmx_filter_alloc(demux))) { + feed->state = DMX_STATE_FREE; + up(&demux->mutex); + return -EBUSY; + } + + feed->filter->type = DMX_TYPE_TS; + feed->filter->feed = feed; + feed->filter->state = DMX_STATE_READY; + + up(&demux->mutex); + + return 0; +} + +static int dvbdmx_release_ts_feed(struct dmx_demux *dmx, struct dmx_ts_feed *ts_feed) +{ + struct dvb_demux *demux = (struct dvb_demux *) dmx; + struct dvb_demux_feed *feed = (struct dvb_demux_feed *) ts_feed; + + if (down_interruptible (&demux->mutex)) + return -ERESTARTSYS; + + if (feed->state == DMX_STATE_FREE) { + up(&demux->mutex); + return -EINVAL; + } + +#ifndef NOBUFS + vfree(feed->buffer); + feed->buffer=0; +#endif + + feed->state = DMX_STATE_FREE; + feed->filter->state = DMX_STATE_FREE; + + dvb_demux_feed_del(feed); + + feed->pid = 0xffff; + + if (feed->ts_type & TS_DECODER && feed->pes_type < DMX_TS_PES_OTHER) + demux->pesfilter[feed->pes_type] = NULL; + + up(&demux->mutex); + return 0; +} + + +/****************************************************************************** + * dmx_section_feed API calls + ******************************************************************************/ + +static int dmx_section_feed_allocate_filter(struct dmx_section_feed* feed, + struct dmx_section_filter** filter) +{ + struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *) feed; + struct dvb_demux *dvbdemux = dvbdmxfeed->demux; + struct dvb_demux_filter *dvbdmxfilter; + + if (down_interruptible (&dvbdemux->mutex)) + return -ERESTARTSYS; + + dvbdmxfilter = dvb_dmx_filter_alloc(dvbdemux); + if (!dvbdmxfilter) { + up(&dvbdemux->mutex); + return -EBUSY; + } + + spin_lock_irq(&dvbdemux->lock); + *filter = &dvbdmxfilter->filter; + (*filter)->parent = feed; + (*filter)->priv = NULL; + dvbdmxfilter->feed = dvbdmxfeed; + dvbdmxfilter->type = DMX_TYPE_SEC; + dvbdmxfilter->state = DMX_STATE_READY; + dvbdmxfilter->next = dvbdmxfeed->filter; + dvbdmxfeed->filter = dvbdmxfilter; + spin_unlock_irq(&dvbdemux->lock); + + up(&dvbdemux->mutex); + return 0; +} + + +static int dmx_section_feed_set(struct dmx_section_feed* feed, + u16 pid, size_t circular_buffer_size, + int descramble, int check_crc) +{ + struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *) feed; + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + + if (pid > 0x1fff) + return -EINVAL; + + if (down_interruptible (&dvbdmx->mutex)) + return -ERESTARTSYS; + + dvb_demux_feed_add(dvbdmxfeed); + + dvbdmxfeed->pid = pid; + dvbdmxfeed->buffer_size = circular_buffer_size; + dvbdmxfeed->descramble = descramble; + if (dvbdmxfeed->descramble) { + up(&dvbdmx->mutex); + return -ENOSYS; + } + + dvbdmxfeed->feed.sec.check_crc = check_crc; + +#ifdef NOBUFS + dvbdmxfeed->buffer = NULL; +#else + dvbdmxfeed->buffer=vmalloc(dvbdmxfeed->buffer_size); + if (!dvbdmxfeed->buffer) { + up(&dvbdmx->mutex); + return -ENOMEM; + } +#endif + + dvbdmxfeed->state = DMX_STATE_READY; + up(&dvbdmx->mutex); + return 0; +} + + +static void prepare_secfilters(struct dvb_demux_feed *dvbdmxfeed) +{ + int i; + struct dvb_demux_filter *f; + struct dmx_section_filter *sf; + u8 mask, mode, doneq; + + if (!(f=dvbdmxfeed->filter)) + return; + do { + sf = &f->filter; + doneq = 0; + for (i=0; i<DVB_DEMUX_MASK_MAX; i++) { + mode = sf->filter_mode[i]; + mask = sf->filter_mask[i]; + f->maskandmode[i] = mask & mode; + doneq |= f->maskandnotmode[i] = mask & ~mode; + } + f->doneq = doneq ? 1 : 0; + } while ((f = f->next)); +} + + +static int dmx_section_feed_start_filtering(struct dmx_section_feed *feed) +{ + struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *) feed; + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + int ret; + + if (down_interruptible (&dvbdmx->mutex)) + return -ERESTARTSYS; + + if (feed->is_filtering) { + up(&dvbdmx->mutex); + return -EBUSY; + } + + if (!dvbdmxfeed->filter) { + up(&dvbdmx->mutex); + return -EINVAL; + } + + dvbdmxfeed->feed.sec.tsfeedp = 0; + dvbdmxfeed->feed.sec.secbuf = dvbdmxfeed->feed.sec.secbuf_base; + dvbdmxfeed->feed.sec.secbufp = 0; + dvbdmxfeed->feed.sec.seclen = 0; + + if (!dvbdmx->start_feed) { + up(&dvbdmx->mutex); + return -ENODEV; + } + + prepare_secfilters(dvbdmxfeed); + + if ((ret = dvbdmx->start_feed(dvbdmxfeed)) < 0) { + up(&dvbdmx->mutex); + return ret; + } + + spin_lock_irq(&dvbdmx->lock); + feed->is_filtering = 1; + dvbdmxfeed->state = DMX_STATE_GO; + spin_unlock_irq(&dvbdmx->lock); + + up(&dvbdmx->mutex); + return 0; +} + + +static int dmx_section_feed_stop_filtering(struct dmx_section_feed* feed) +{ + struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *) feed; + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + int ret; + + if (down_interruptible (&dvbdmx->mutex)) + return -ERESTARTSYS; + + if (!dvbdmx->stop_feed) { + up(&dvbdmx->mutex); + return -ENODEV; + } + + ret = dvbdmx->stop_feed(dvbdmxfeed); + + spin_lock_irq(&dvbdmx->lock); + dvbdmxfeed->state = DMX_STATE_READY; + feed->is_filtering = 0; + spin_unlock_irq(&dvbdmx->lock); + + up(&dvbdmx->mutex); + return ret; +} + + +static int dmx_section_feed_release_filter(struct dmx_section_feed *feed, + struct dmx_section_filter* filter) +{ + struct dvb_demux_filter *dvbdmxfilter = (struct dvb_demux_filter *) filter, *f; + struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *) feed; + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + + if (down_interruptible (&dvbdmx->mutex)) + return -ERESTARTSYS; + + if (dvbdmxfilter->feed != dvbdmxfeed) { + up(&dvbdmx->mutex); + return -EINVAL; + } + + if (feed->is_filtering) + feed->stop_filtering(feed); + + spin_lock_irq(&dvbdmx->lock); + f = dvbdmxfeed->filter; + + if (f == dvbdmxfilter) { + dvbdmxfeed->filter = dvbdmxfilter->next; + } else { + while(f->next != dvbdmxfilter) + f = f->next; + f->next = f->next->next; + } + + dvbdmxfilter->state = DMX_STATE_FREE; + spin_unlock_irq(&dvbdmx->lock); + up(&dvbdmx->mutex); + return 0; +} + +static int dvbdmx_allocate_section_feed(struct dmx_demux *demux, + struct dmx_section_feed **feed, + dmx_section_cb callback) +{ + struct dvb_demux *dvbdmx = (struct dvb_demux *) demux; + struct dvb_demux_feed *dvbdmxfeed; + + if (down_interruptible (&dvbdmx->mutex)) + return -ERESTARTSYS; + + if (!(dvbdmxfeed = dvb_dmx_feed_alloc(dvbdmx))) { + up(&dvbdmx->mutex); + return -EBUSY; + } + + dvbdmxfeed->type = DMX_TYPE_SEC; + dvbdmxfeed->cb.sec = callback; + dvbdmxfeed->demux = dvbdmx; + dvbdmxfeed->pid = 0xffff; + dvbdmxfeed->feed.sec.secbuf = dvbdmxfeed->feed.sec.secbuf_base; + dvbdmxfeed->feed.sec.secbufp = dvbdmxfeed->feed.sec.seclen = 0; + dvbdmxfeed->feed.sec.tsfeedp = 0; + dvbdmxfeed->filter = NULL; + dvbdmxfeed->buffer = NULL; + + (*feed)=&dvbdmxfeed->feed.sec; + (*feed)->is_filtering = 0; + (*feed)->parent = demux; + (*feed)->priv = NULL; + + (*feed)->set = dmx_section_feed_set; + (*feed)->allocate_filter = dmx_section_feed_allocate_filter; + (*feed)->start_filtering = dmx_section_feed_start_filtering; + (*feed)->stop_filtering = dmx_section_feed_stop_filtering; + (*feed)->release_filter = dmx_section_feed_release_filter; + + up(&dvbdmx->mutex); + return 0; +} + +static int dvbdmx_release_section_feed(struct dmx_demux *demux, + struct dmx_section_feed *feed) +{ + struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *) feed; + struct dvb_demux *dvbdmx = (struct dvb_demux *) demux; + + if (down_interruptible (&dvbdmx->mutex)) + return -ERESTARTSYS; + + if (dvbdmxfeed->state==DMX_STATE_FREE) { + up(&dvbdmx->mutex); + return -EINVAL; + } +#ifndef NOBUFS + vfree(dvbdmxfeed->buffer); + dvbdmxfeed->buffer=0; +#endif + dvbdmxfeed->state=DMX_STATE_FREE; + + dvb_demux_feed_del(dvbdmxfeed); + + dvbdmxfeed->pid = 0xffff; + + up(&dvbdmx->mutex); + return 0; +} + + +/****************************************************************************** + * dvb_demux kernel data API calls + ******************************************************************************/ + +static int dvbdmx_open(struct dmx_demux *demux) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *) demux; + + if (dvbdemux->users >= MAX_DVB_DEMUX_USERS) + return -EUSERS; + + dvbdemux->users++; + return 0; +} + + +static int dvbdmx_close(struct dmx_demux *demux) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *) demux; + + if (dvbdemux->users == 0) + return -ENODEV; + + dvbdemux->users--; + //FIXME: release any unneeded resources if users==0 + return 0; +} + + +static int dvbdmx_write(struct dmx_demux *demux, const char *buf, size_t count) +{ + struct dvb_demux *dvbdemux=(struct dvb_demux *) demux; + + if ((!demux->frontend) || (demux->frontend->source != DMX_MEMORY_FE)) + return -EINVAL; + + if (down_interruptible (&dvbdemux->mutex)) + return -ERESTARTSYS; + dvb_dmx_swfilter(dvbdemux, buf, count); + up(&dvbdemux->mutex); + + if (signal_pending(current)) + return -EINTR; + return count; +} + + +static int dvbdmx_add_frontend(struct dmx_demux *demux, struct dmx_frontend *frontend) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *) demux; + struct list_head *head = &dvbdemux->frontend_list; + + list_add(&(frontend->connectivity_list), head); + + return 0; +} + + +static int dvbdmx_remove_frontend(struct dmx_demux *demux, struct dmx_frontend *frontend) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *) demux; + struct list_head *pos, *n, *head = &dvbdemux->frontend_list; + + list_for_each_safe (pos, n, head) { + if (DMX_FE_ENTRY(pos) == frontend) { + list_del(pos); + return 0; + } + } + + return -ENODEV; +} + + +static struct list_head * dvbdmx_get_frontends(struct dmx_demux *demux) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *) demux; + + if (list_empty(&dvbdemux->frontend_list)) + return NULL; + return &dvbdemux->frontend_list; +} + + +static int dvbdmx_connect_frontend(struct dmx_demux *demux, struct dmx_frontend *frontend) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *) demux; + + if (demux->frontend) + return -EINVAL; + + if (down_interruptible (&dvbdemux->mutex)) + return -ERESTARTSYS; + + demux->frontend = frontend; + up(&dvbdemux->mutex); + return 0; +} + + +static int dvbdmx_disconnect_frontend(struct dmx_demux *demux) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *) demux; + + if (down_interruptible (&dvbdemux->mutex)) + return -ERESTARTSYS; + + demux->frontend = NULL; + up(&dvbdemux->mutex); + return 0; +} + + +static int dvbdmx_get_pes_pids(struct dmx_demux *demux, u16 *pids) +{ + struct dvb_demux *dvbdemux = (struct dvb_demux *) demux; + + memcpy(pids, dvbdemux->pids, 5*sizeof(u16)); + return 0; +} + + +int dvb_dmx_init(struct dvb_demux *dvbdemux) +{ + int i, err; + struct dmx_demux *dmx = &dvbdemux->dmx; + + dvbdemux->users = 0; + dvbdemux->filter = vmalloc(dvbdemux->filternum*sizeof(struct dvb_demux_filter)); + + if (!dvbdemux->filter) + return -ENOMEM; + + dvbdemux->feed = vmalloc(dvbdemux->feednum*sizeof(struct dvb_demux_feed)); + if (!dvbdemux->feed) { + vfree(dvbdemux->filter); + return -ENOMEM; + } + for (i=0; i<dvbdemux->filternum; i++) { + dvbdemux->filter[i].state = DMX_STATE_FREE; + dvbdemux->filter[i].index = i; + } + for (i=0; i<dvbdemux->feednum; i++) { + dvbdemux->feed[i].state = DMX_STATE_FREE; + dvbdemux->feed[i].index = i; + } + dvbdemux->frontend_list.next= + dvbdemux->frontend_list.prev= + &dvbdemux->frontend_list; + for (i=0; i<DMX_TS_PES_OTHER; i++) { + dvbdemux->pesfilter[i] = NULL; + dvbdemux->pids[i] = 0xffff; + } + + INIT_LIST_HEAD(&dvbdemux->feed_list); + + dvbdemux->playing = 0; + dvbdemux->recording = 0; + dvbdemux->tsbufp = 0; + + if (!dvbdemux->check_crc32) + dvbdemux->check_crc32 = dvb_dmx_crc32; + + if (!dvbdemux->memcopy) + dvbdemux->memcopy = dvb_dmx_memcopy; + + dmx->frontend = NULL; + dmx->reg_list.prev = dmx->reg_list.next = &dmx->reg_list; + dmx->priv = (void *) dvbdemux; + dmx->open = dvbdmx_open; + dmx->close = dvbdmx_close; + dmx->write = dvbdmx_write; + dmx->allocate_ts_feed = dvbdmx_allocate_ts_feed; + dmx->release_ts_feed = dvbdmx_release_ts_feed; + dmx->allocate_section_feed = dvbdmx_allocate_section_feed; + dmx->release_section_feed = dvbdmx_release_section_feed; + + dmx->descramble_mac_address = NULL; + dmx->descramble_section_payload = NULL; + + dmx->add_frontend = dvbdmx_add_frontend; + dmx->remove_frontend = dvbdmx_remove_frontend; + dmx->get_frontends = dvbdmx_get_frontends; + dmx->connect_frontend = dvbdmx_connect_frontend; + dmx->disconnect_frontend = dvbdmx_disconnect_frontend; + dmx->get_pes_pids = dvbdmx_get_pes_pids; + + sema_init(&dvbdemux->mutex, 1); + spin_lock_init(&dvbdemux->lock); + + if ((err = dmx_register_demux(dmx)) < 0) + return err; + + return 0; +} +EXPORT_SYMBOL(dvb_dmx_init); + + +int dvb_dmx_release(struct dvb_demux *dvbdemux) +{ + struct dmx_demux *dmx = &dvbdemux->dmx; + + dmx_unregister_demux(dmx); + vfree(dvbdemux->filter); + vfree(dvbdemux->feed); + return 0; +} +EXPORT_SYMBOL(dvb_dmx_release); diff --git a/drivers/media/dvb/dvb-core/dvb_demux.h b/drivers/media/dvb/dvb-core/dvb_demux.h new file mode 100644 index 000000000000..c09beb391622 --- /dev/null +++ b/drivers/media/dvb/dvb-core/dvb_demux.h @@ -0,0 +1,146 @@ +/* + * dvb_demux.h: DVB kernel demux API + * + * Copyright (C) 2000-2001 Marcus Metzler & Ralph Metzler + * for convergence integrated media GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * 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 Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + + +#ifndef _DVB_DEMUX_H_ +#define _DVB_DEMUX_H_ + +#include <linux/time.h> +#include <linux/timer.h> +#include <linux/spinlock.h> +#include <asm/semaphore.h> + +#include "demux.h" + +#define DMX_TYPE_TS 0 +#define DMX_TYPE_SEC 1 +#define DMX_TYPE_PES 2 + +#define DMX_STATE_FREE 0 +#define DMX_STATE_ALLOCATED 1 +#define DMX_STATE_SET 2 +#define DMX_STATE_READY 3 +#define DMX_STATE_GO 4 + +#define DVB_DEMUX_MASK_MAX 18 + +struct dvb_demux_filter { + struct dmx_section_filter filter; + u8 maskandmode [DMX_MAX_FILTER_SIZE]; + u8 maskandnotmode [DMX_MAX_FILTER_SIZE]; + int doneq; + + struct dvb_demux_filter *next; + struct dvb_demux_feed *feed; + int index; + int state; + int type; + int pesto; + + u16 handle; + u16 hw_handle; + struct timer_list timer; + int ts_state; +}; + + +#define DMX_FEED_ENTRY(pos) list_entry(pos, struct dvb_demux_feed, list_head) + +struct dvb_demux_feed { + union { + struct dmx_ts_feed ts; + struct dmx_section_feed sec; + } feed; + + union { + dmx_ts_cb ts; + dmx_section_cb sec; + } cb; + + struct dvb_demux *demux; + void *priv; + int type; + int state; + u16 pid; + u8 *buffer; + int buffer_size; + int descramble; + + struct timespec timeout; + struct dvb_demux_filter *filter; + int cb_length; + + int ts_type; + enum dmx_ts_pes pes_type; + + int cc; + int pusi_seen; /* prevents feeding of garbage from previous section */ + + u16 peslen; + + struct list_head list_head; + int index; /* a unique index for each feed (can be used as hardware pid filter index) */ +}; + +struct dvb_demux { + struct dmx_demux dmx; + void *priv; + int filternum; + int feednum; + int (*start_feed) (struct dvb_demux_feed *feed); + int (*stop_feed) (struct dvb_demux_feed *feed); + int (*write_to_decoder) (struct dvb_demux_feed *feed, + const u8 *buf, size_t len); + u32 (*check_crc32) (struct dvb_demux_feed *feed, + const u8 *buf, size_t len); + void (*memcopy) (struct dvb_demux_feed *feed, u8 *dst, + const u8 *src, size_t len); + + int users; +#define MAX_DVB_DEMUX_USERS 10 + struct dvb_demux_filter *filter; + struct dvb_demux_feed *feed; + + struct list_head frontend_list; + + struct dvb_demux_feed *pesfilter[DMX_TS_PES_OTHER]; + u16 pids[DMX_TS_PES_OTHER]; + int playing; + int recording; + +#define DMX_MAX_PID 0x2000 + struct list_head feed_list; + u8 tsbuf[204]; + int tsbufp; + + struct semaphore mutex; + spinlock_t lock; +}; + + +int dvb_dmx_init(struct dvb_demux *dvbdemux); +int dvb_dmx_release(struct dvb_demux *dvbdemux); +void dvb_dmx_swfilter_packets(struct dvb_demux *dvbdmx, const u8 *buf, size_t count); +void dvb_dmx_swfilter(struct dvb_demux *demux, const u8 *buf, size_t count); +void dvb_dmx_swfilter_204(struct dvb_demux *demux, const u8 *buf, size_t count); + +#endif /* _DVB_DEMUX_H_ */ diff --git a/drivers/media/dvb/dvb-core/dvb_filter.c b/drivers/media/dvb/dvb-core/dvb_filter.c new file mode 100644 index 000000000000..bd5143906084 --- /dev/null +++ b/drivers/media/dvb/dvb-core/dvb_filter.c @@ -0,0 +1,603 @@ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include "dvb_filter.h" + +#if 0 +static unsigned int bitrates[3][16] = +{{0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0}, + {0,32,48,56,64,80,96,112,128,160,192,224,256,320,384,0}, + {0,32,40,48,56,64,80,96,112,128,160,192,224,256,320,0}}; +#endif + +static u32 freq[4] = {480, 441, 320, 0}; + +static unsigned int ac3_bitrates[32] = + {32,40,48,56,64,80,96,112,128,160,192,224,256,320,384,448,512,576,640, + 0,0,0,0,0,0,0,0,0,0,0,0,0}; + +static u32 ac3_frames[3][32] = + {{64,80,96,112,128,160,192,224,256,320,384,448,512,640,768,896,1024, + 1152,1280,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {69,87,104,121,139,174,208,243,278,348,417,487,557,696,835,975,1114, + 1253,1393,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {96,120,144,168,192,240,288,336,384,480,576,672,768,960,1152,1344, + 1536,1728,1920,0,0,0,0,0,0,0,0,0,0,0,0,0}}; + + + +#if 0 +static void setup_ts2pes(ipack *pa, ipack *pv, u16 *pida, u16 *pidv, + void (*pes_write)(u8 *buf, int count, void *data), + void *priv) +{ + dvb_filter_ipack_init(pa, IPACKS, pes_write); + dvb_filter_ipack_init(pv, IPACKS, pes_write); + pa->pid = pida; + pv->pid = pidv; + pa->data = priv; + pv->data = priv; +} +#endif + +#if 0 +static void ts_to_pes(ipack *p, u8 *buf) // don't need count (=188) +{ + u8 off = 0; + + if (!buf || !p ){ + printk("NULL POINTER IDIOT\n"); + return; + } + if (buf[1]&PAY_START) { + if (p->plength == MMAX_PLENGTH-6 && p->found>6){ + p->plength = p->found-6; + p->found = 0; + send_ipack(p); + dvb_filter_ipack_reset(p); + } + } + if (buf[3] & ADAPT_FIELD) { // adaptation field? + off = buf[4] + 1; + if (off+4 > 187) return; + } + dvb_filter_instant_repack(buf+4+off, TS_SIZE-4-off, p); +} +#endif + +#if 0 +/* needs 5 byte input, returns picture coding type*/ +static int read_picture_header(u8 *headr, struct mpg_picture *pic, int field, int pr) +{ + u8 pct; + + if (pr) printk( "Pic header: "); + pic->temporal_reference[field] = (( headr[0] << 2 ) | + (headr[1] & 0x03) )& 0x03ff; + if (pr) printk( " temp ref: 0x%04x", pic->temporal_reference[field]); + + pct = ( headr[1] >> 2 ) & 0x07; + pic->picture_coding_type[field] = pct; + if (pr) { + switch(pct){ + case I_FRAME: + printk( " I-FRAME"); + break; + case B_FRAME: + printk( " B-FRAME"); + break; + case P_FRAME: + printk( " P-FRAME"); + break; + } + } + + + pic->vinfo.vbv_delay = (( headr[1] >> 5 ) | ( headr[2] << 3) | + ( (headr[3] & 0x1F) << 11) ) & 0xffff; + + if (pr) printk( " vbv delay: 0x%04x", pic->vinfo.vbv_delay); + + pic->picture_header_parameter = ( headr[3] & 0xe0 ) | + ((headr[4] & 0x80) >> 3); + + if ( pct == B_FRAME ){ + pic->picture_header_parameter |= ( headr[4] >> 3 ) & 0x0f; + } + if (pr) printk( " pic head param: 0x%x", + pic->picture_header_parameter); + + return pct; +} +#endif + +#if 0 +/* needs 4 byte input */ +static int read_gop_header(u8 *headr, struct mpg_picture *pic, int pr) +{ + if (pr) printk("GOP header: "); + + pic->time_code = (( headr[0] << 17 ) | ( headr[1] << 9) | + ( headr[2] << 1 ) | (headr[3] &0x01)) & 0x1ffffff; + + if (pr) printk(" time: %d:%d.%d ", (headr[0]>>2)& 0x1F, + ((headr[0]<<4)& 0x30)| ((headr[1]>>4)& 0x0F), + ((headr[1]<<3)& 0x38)| ((headr[2]>>5)& 0x0F)); + + if ( ( headr[3] & 0x40 ) != 0 ){ + pic->closed_gop = 1; + } else { + pic->closed_gop = 0; + } + if (pr) printk("closed: %d", pic->closed_gop); + + if ( ( headr[3] & 0x20 ) != 0 ){ + pic->broken_link = 1; + } else { + pic->broken_link = 0; + } + if (pr) printk(" broken: %d\n", pic->broken_link); + + return 0; +} +#endif + +#if 0 +/* needs 8 byte input */ +static int read_sequence_header(u8 *headr, struct dvb_video_info *vi, int pr) +{ + int sw; + int form = -1; + + if (pr) printk("Reading sequence header\n"); + + vi->horizontal_size = ((headr[1] &0xF0) >> 4) | (headr[0] << 4); + vi->vertical_size = ((headr[1] &0x0F) << 8) | (headr[2]); + + sw = (int)((headr[3]&0xF0) >> 4) ; + + switch( sw ){ + case 1: + if (pr) + printk("Videostream: ASPECT: 1:1"); + vi->aspect_ratio = 100; + break; + case 2: + if (pr) + printk("Videostream: ASPECT: 4:3"); + vi->aspect_ratio = 133; + break; + case 3: + if (pr) + printk("Videostream: ASPECT: 16:9"); + vi->aspect_ratio = 177; + break; + case 4: + if (pr) + printk("Videostream: ASPECT: 2.21:1"); + vi->aspect_ratio = 221; + break; + + case 5 ... 15: + if (pr) + printk("Videostream: ASPECT: reserved"); + vi->aspect_ratio = 0; + break; + + default: + vi->aspect_ratio = 0; + return -1; + } + + if (pr) + printk(" Size = %dx%d",vi->horizontal_size,vi->vertical_size); + + sw = (int)(headr[3]&0x0F); + + switch ( sw ) { + case 1: + if (pr) + printk(" FRate: 23.976 fps"); + vi->framerate = 23976; + form = -1; + break; + case 2: + if (pr) + printk(" FRate: 24 fps"); + vi->framerate = 24000; + form = -1; + break; + case 3: + if (pr) + printk(" FRate: 25 fps"); + vi->framerate = 25000; + form = VIDEO_MODE_PAL; + break; + case 4: + if (pr) + printk(" FRate: 29.97 fps"); + vi->framerate = 29970; + form = VIDEO_MODE_NTSC; + break; + case 5: + if (pr) + printk(" FRate: 30 fps"); + vi->framerate = 30000; + form = VIDEO_MODE_NTSC; + break; + case 6: + if (pr) + printk(" FRate: 50 fps"); + vi->framerate = 50000; + form = VIDEO_MODE_PAL; + break; + case 7: + if (pr) + printk(" FRate: 60 fps"); + vi->framerate = 60000; + form = VIDEO_MODE_NTSC; + break; + } + + vi->bit_rate = (headr[4] << 10) | (headr[5] << 2) | (headr[6] & 0x03); + + vi->vbv_buffer_size + = (( headr[6] & 0xF8) >> 3 ) | (( headr[7] & 0x1F )<< 5); + + if (pr){ + printk(" BRate: %d Mbit/s",4*(vi->bit_rate)/10000); + printk(" vbvbuffer %d",16*1024*(vi->vbv_buffer_size)); + printk("\n"); + } + + vi->video_format = form; + + return 0; +} +#endif + + +#if 0 +static int get_vinfo(u8 *mbuf, int count, struct dvb_video_info *vi, int pr) +{ + u8 *headr; + int found = 0; + int c = 0; + + while (found < 4 && c+4 < count){ + u8 *b; + + b = mbuf+c; + if ( b[0] == 0x00 && b[1] == 0x00 && b[2] == 0x01 + && b[3] == 0xb3) found = 4; + else { + c++; + } + } + + if (! found) return -1; + c += 4; + if (c+12 >= count) return -1; + headr = mbuf+c; + if (read_sequence_header(headr, vi, pr) < 0) return -1; + vi->off = c-4; + return 0; +} +#endif + + +#if 0 +static int get_ainfo(u8 *mbuf, int count, struct dvb_audio_info *ai, int pr) +{ + u8 *headr; + int found = 0; + int c = 0; + int fr = 0; + + while (found < 2 && c < count){ + u8 b[2]; + memcpy( b, mbuf+c, 2); + + if ( b[0] == 0xff && (b[1] & 0xf8) == 0xf8) + found = 2; + else { + c++; + } + } + + if (!found) return -1; + + if (c+3 >= count) return -1; + headr = mbuf+c; + + ai->layer = (headr[1] & 0x06) >> 1; + + if (pr) + printk("Audiostream: Layer: %d", 4-ai->layer); + + + ai->bit_rate = bitrates[(3-ai->layer)][(headr[2] >> 4 )]*1000; + + if (pr){ + if (ai->bit_rate == 0) + printk(" Bit rate: free"); + else if (ai->bit_rate == 0xf) + printk(" BRate: reserved"); + else + printk(" BRate: %d kb/s", ai->bit_rate/1000); + } + + fr = (headr[2] & 0x0c ) >> 2; + ai->frequency = freq[fr]*100; + if (pr){ + if (ai->frequency == 3) + printk(" Freq: reserved\n"); + else + printk(" Freq: %d kHz\n",ai->frequency); + + } + ai->off = c; + return 0; +} +#endif + + +int dvb_filter_get_ac3info(u8 *mbuf, int count, struct dvb_audio_info *ai, int pr) +{ + u8 *headr; + int found = 0; + int c = 0; + u8 frame = 0; + int fr = 0; + + while ( !found && c < count){ + u8 *b = mbuf+c; + + if ( b[0] == 0x0b && b[1] == 0x77 ) + found = 1; + else { + c++; + } + } + + if (!found) return -1; + if (pr) + printk("Audiostream: AC3"); + + ai->off = c; + if (c+5 >= count) return -1; + + ai->layer = 0; // 0 for AC3 + headr = mbuf+c+2; + + frame = (headr[2]&0x3f); + ai->bit_rate = ac3_bitrates[frame >> 1]*1000; + + if (pr) + printk(" BRate: %d kb/s", (int) ai->bit_rate/1000); + + ai->frequency = (headr[2] & 0xc0 ) >> 6; + fr = (headr[2] & 0xc0 ) >> 6; + ai->frequency = freq[fr]*100; + if (pr) printk (" Freq: %d Hz\n", (int) ai->frequency); + + + ai->framesize = ac3_frames[fr][frame >> 1]; + if ((frame & 1) && (fr == 1)) ai->framesize++; + ai->framesize = ai->framesize << 1; + if (pr) printk (" Framesize %d\n",(int) ai->framesize); + + + return 0; +} +EXPORT_SYMBOL(dvb_filter_get_ac3info); + + +#if 0 +static u8 *skip_pes_header(u8 **bufp) +{ + u8 *inbuf = *bufp; + u8 *buf = inbuf; + u8 *pts = NULL; + int skip = 0; + + static const int mpeg1_skip_table[16] = { + 1, 0xffff, 5, 10, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff + }; + + + if ((inbuf[6] & 0xc0) == 0x80){ /* mpeg2 */ + if (buf[7] & PTS_ONLY) + pts = buf+9; + else pts = NULL; + buf = inbuf + 9 + inbuf[8]; + } else { /* mpeg1 */ + for (buf = inbuf + 6; *buf == 0xff; buf++) + if (buf == inbuf + 6 + 16) { + break; + } + if ((*buf & 0xc0) == 0x40) + buf += 2; + skip = mpeg1_skip_table [*buf >> 4]; + if (skip == 5 || skip == 10) pts = buf; + else pts = NULL; + + buf += mpeg1_skip_table [*buf >> 4]; + } + + *bufp = buf; + return pts; +} +#endif + +#if 0 +static void initialize_quant_matrix( u32 *matrix ) +{ + int i; + + matrix[0] = 0x08101013; + matrix[1] = 0x10131616; + matrix[2] = 0x16161616; + matrix[3] = 0x1a181a1b; + matrix[4] = 0x1b1b1a1a; + matrix[5] = 0x1a1a1b1b; + matrix[6] = 0x1b1d1d1d; + matrix[7] = 0x2222221d; + matrix[8] = 0x1d1d1b1b; + matrix[9] = 0x1d1d2020; + matrix[10] = 0x22222526; + matrix[11] = 0x25232322; + matrix[12] = 0x23262628; + matrix[13] = 0x28283030; + matrix[14] = 0x2e2e3838; + matrix[15] = 0x3a454553; + + for ( i = 16 ; i < 32 ; i++ ) + matrix[i] = 0x10101010; +} +#endif + +#if 0 +static void initialize_mpg_picture(struct mpg_picture *pic) +{ + int i; + + /* set MPEG1 */ + pic->mpeg1_flag = 1; + pic->profile_and_level = 0x4A ; /* MP@LL */ + pic->progressive_sequence = 1; + pic->low_delay = 0; + + pic->sequence_display_extension_flag = 0; + for ( i = 0 ; i < 4 ; i++ ){ + pic->frame_centre_horizontal_offset[i] = 0; + pic->frame_centre_vertical_offset[i] = 0; + } + pic->last_frame_centre_horizontal_offset = 0; + pic->last_frame_centre_vertical_offset = 0; + + pic->picture_display_extension_flag[0] = 0; + pic->picture_display_extension_flag[1] = 0; + pic->sequence_header_flag = 0; + pic->gop_flag = 0; + pic->sequence_end_flag = 0; +} +#endif + +#if 0 +static void mpg_set_picture_parameter( int32_t field_type, struct mpg_picture *pic ) +{ + int16_t last_h_offset; + int16_t last_v_offset; + + int16_t *p_h_offset; + int16_t *p_v_offset; + + if ( pic->mpeg1_flag ){ + pic->picture_structure[field_type] = VIDEO_FRAME_PICTURE; + pic->top_field_first = 0; + pic->repeat_first_field = 0; + pic->progressive_frame = 1; + pic->picture_coding_parameter = 0x000010; + } + + /* Reset flag */ + pic->picture_display_extension_flag[field_type] = 0; + + last_h_offset = pic->last_frame_centre_horizontal_offset; + last_v_offset = pic->last_frame_centre_vertical_offset; + if ( field_type == FIRST_FIELD ){ + p_h_offset = pic->frame_centre_horizontal_offset; + p_v_offset = pic->frame_centre_vertical_offset; + *p_h_offset = last_h_offset; + *(p_h_offset + 1) = last_h_offset; + *(p_h_offset + 2) = last_h_offset; + *p_v_offset = last_v_offset; + *(p_v_offset + 1) = last_v_offset; + *(p_v_offset + 2) = last_v_offset; + } else { + pic->frame_centre_horizontal_offset[3] = last_h_offset; + pic->frame_centre_vertical_offset[3] = last_v_offset; + } +} +#endif + +#if 0 +static void init_mpg_picture( struct mpg_picture *pic, int chan, int32_t field_type) +{ + pic->picture_header = 0; + pic->sequence_header_data + = ( INIT_HORIZONTAL_SIZE << 20 ) + | ( INIT_VERTICAL_SIZE << 8 ) + | ( INIT_ASPECT_RATIO << 4 ) + | ( INIT_FRAME_RATE ); + pic->mpeg1_flag = 0; + pic->vinfo.horizontal_size + = INIT_DISP_HORIZONTAL_SIZE; + pic->vinfo.vertical_size + = INIT_DISP_VERTICAL_SIZE; + pic->picture_display_extension_flag[field_type] + = 0; + pic->pts_flag[field_type] = 0; + + pic->sequence_gop_header = 0; + pic->picture_header = 0; + pic->sequence_header_flag = 0; + pic->gop_flag = 0; + pic->sequence_end_flag = 0; + pic->sequence_display_extension_flag = 0; + pic->last_frame_centre_horizontal_offset = 0; + pic->last_frame_centre_vertical_offset = 0; + pic->channel = chan; +} +#endif + +void dvb_filter_pes2ts_init(struct dvb_filter_pes2ts *p2ts, unsigned short pid, + dvb_filter_pes2ts_cb_t *cb, void *priv) +{ + unsigned char *buf=p2ts->buf; + + buf[0]=0x47; + buf[1]=(pid>>8); + buf[2]=pid&0xff; + p2ts->cc=0; + p2ts->cb=cb; + p2ts->priv=priv; +} +EXPORT_SYMBOL(dvb_filter_pes2ts_init); + +int dvb_filter_pes2ts(struct dvb_filter_pes2ts *p2ts, unsigned char *pes, + int len, int payload_start) +{ + unsigned char *buf=p2ts->buf; + int ret=0, rest; + + //len=6+((pes[4]<<8)|pes[5]); + + if (payload_start) + buf[1]|=0x40; + else + buf[1]&=~0x40; + while (len>=184) { + buf[3]=0x10|((p2ts->cc++)&0x0f); + memcpy(buf+4, pes, 184); + if ((ret=p2ts->cb(p2ts->priv, buf))) + return ret; + len-=184; pes+=184; + buf[1]&=~0x40; + } + if (!len) + return 0; + buf[3]=0x30|((p2ts->cc++)&0x0f); + rest=183-len; + if (rest) { + buf[5]=0x00; + if (rest-1) + memset(buf+6, 0xff, rest-1); + } + buf[4]=rest; + memcpy(buf+5+rest, pes, len); + return p2ts->cb(p2ts->priv, buf); +} +EXPORT_SYMBOL(dvb_filter_pes2ts); diff --git a/drivers/media/dvb/dvb-core/dvb_filter.h b/drivers/media/dvb/dvb-core/dvb_filter.h new file mode 100644 index 000000000000..b0848f7836b7 --- /dev/null +++ b/drivers/media/dvb/dvb-core/dvb_filter.h @@ -0,0 +1,246 @@ +/* + * dvb_filter.h + * + * Copyright (C) 2003 Convergence GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * 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 Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _DVB_FILTER_H_ +#define _DVB_FILTER_H_ + +#include <linux/slab.h> + +#include "demux.h" + +typedef int (dvb_filter_pes2ts_cb_t) (void *, unsigned char *); + +struct dvb_filter_pes2ts { + unsigned char buf[188]; + unsigned char cc; + dvb_filter_pes2ts_cb_t *cb; + void *priv; +}; + +void dvb_filter_pes2ts_init(struct dvb_filter_pes2ts *p2ts, unsigned short pid, + dvb_filter_pes2ts_cb_t *cb, void *priv); + +int dvb_filter_pes2ts(struct dvb_filter_pes2ts *p2ts, unsigned char *pes, + int len, int payload_start); + + +#define PROG_STREAM_MAP 0xBC +#define PRIVATE_STREAM1 0xBD +#define PADDING_STREAM 0xBE +#define PRIVATE_STREAM2 0xBF +#define AUDIO_STREAM_S 0xC0 +#define AUDIO_STREAM_E 0xDF +#define VIDEO_STREAM_S 0xE0 +#define VIDEO_STREAM_E 0xEF +#define ECM_STREAM 0xF0 +#define EMM_STREAM 0xF1 +#define DSM_CC_STREAM 0xF2 +#define ISO13522_STREAM 0xF3 +#define PROG_STREAM_DIR 0xFF + +#define DVB_PICTURE_START 0x00 +#define DVB_USER_START 0xb2 +#define DVB_SEQUENCE_HEADER 0xb3 +#define DVB_SEQUENCE_ERROR 0xb4 +#define DVB_EXTENSION_START 0xb5 +#define DVB_SEQUENCE_END 0xb7 +#define DVB_GOP_START 0xb8 +#define DVB_EXCEPT_SLICE 0xb0 + +#define SEQUENCE_EXTENSION 0x01 +#define SEQUENCE_DISPLAY_EXTENSION 0x02 +#define PICTURE_CODING_EXTENSION 0x08 +#define QUANT_MATRIX_EXTENSION 0x03 +#define PICTURE_DISPLAY_EXTENSION 0x07 + +#define I_FRAME 0x01 +#define B_FRAME 0x02 +#define P_FRAME 0x03 + +/* Initialize sequence_data */ +#define INIT_HORIZONTAL_SIZE 720 +#define INIT_VERTICAL_SIZE 576 +#define INIT_ASPECT_RATIO 0x02 +#define INIT_FRAME_RATE 0x03 +#define INIT_DISP_HORIZONTAL_SIZE 540 +#define INIT_DISP_VERTICAL_SIZE 576 + + +//flags2 +#define PTS_DTS_FLAGS 0xC0 +#define ESCR_FLAG 0x20 +#define ES_RATE_FLAG 0x10 +#define DSM_TRICK_FLAG 0x08 +#define ADD_CPY_FLAG 0x04 +#define PES_CRC_FLAG 0x02 +#define PES_EXT_FLAG 0x01 + +//pts_dts flags +#define PTS_ONLY 0x80 +#define PTS_DTS 0xC0 + +#define TS_SIZE 188 +#define TRANS_ERROR 0x80 +#define PAY_START 0x40 +#define TRANS_PRIO 0x20 +#define PID_MASK_HI 0x1F +//flags +#define TRANS_SCRMBL1 0x80 +#define TRANS_SCRMBL2 0x40 +#define ADAPT_FIELD 0x20 +#define PAYLOAD 0x10 +#define COUNT_MASK 0x0F + +// adaptation flags +#define DISCON_IND 0x80 +#define RAND_ACC_IND 0x40 +#define ES_PRI_IND 0x20 +#define PCR_FLAG 0x10 +#define OPCR_FLAG 0x08 +#define SPLICE_FLAG 0x04 +#define TRANS_PRIV 0x02 +#define ADAP_EXT_FLAG 0x01 + +// adaptation extension flags +#define LTW_FLAG 0x80 +#define PIECE_RATE 0x40 +#define SEAM_SPLICE 0x20 + + +#define MAX_PLENGTH 0xFFFF +#define MMAX_PLENGTH (256*MAX_PLENGTH) + +#ifndef IPACKS +#define IPACKS 2048 +#endif + +struct ipack { + int size; + int found; + u8 *buf; + u8 cid; + u32 plength; + u8 plen[2]; + u8 flag1; + u8 flag2; + u8 hlength; + u8 pts[5]; + u16 *pid; + int mpeg; + u8 check; + int which; + int done; + void *data; + void (*func)(u8 *buf, int size, void *priv); + int count; + int repack_subids; +}; + +struct dvb_video_info { + u32 horizontal_size; + u32 vertical_size; + u32 aspect_ratio; + u32 framerate; + u32 video_format; + u32 bit_rate; + u32 comp_bit_rate; + u32 vbv_buffer_size; + s16 vbv_delay; + u32 CSPF; + u32 off; +}; + +#define OFF_SIZE 4 +#define FIRST_FIELD 0 +#define SECOND_FIELD 1 +#define VIDEO_FRAME_PICTURE 0x03 + +struct mpg_picture { + int channel; + struct dvb_video_info vinfo; + u32 *sequence_gop_header; + u32 *picture_header; + s32 time_code; + int low_delay; + int closed_gop; + int broken_link; + int sequence_header_flag; + int gop_flag; + int sequence_end_flag; + + u8 profile_and_level; + s32 picture_coding_parameter; + u32 matrix[32]; + s8 matrix_change_flag; + + u8 picture_header_parameter; + /* bit 0 - 2: bwd f code + bit 3 : fpb vector + bit 4 - 6: fwd f code + bit 7 : fpf vector */ + + int mpeg1_flag; + int progressive_sequence; + int sequence_display_extension_flag; + u32 sequence_header_data; + s16 last_frame_centre_horizontal_offset; + s16 last_frame_centre_vertical_offset; + + u32 pts[2]; /* [0] 1st field, [1] 2nd field */ + int top_field_first; + int repeat_first_field; + int progressive_frame; + int bank; + int forward_bank; + int backward_bank; + int compress; + s16 frame_centre_horizontal_offset[OFF_SIZE]; + /* [0-2] 1st field, [3] 2nd field */ + s16 frame_centre_vertical_offset[OFF_SIZE]; + /* [0-2] 1st field, [3] 2nd field */ + s16 temporal_reference[2]; + /* [0] 1st field, [1] 2nd field */ + + s8 picture_coding_type[2]; + /* [0] 1st field, [1] 2nd field */ + s8 picture_structure[2]; + /* [0] 1st field, [1] 2nd field */ + s8 picture_display_extension_flag[2]; + /* [0] 1st field, [1] 2nd field */ + /* picture_display_extenion() 0:no 1:exit*/ + s8 pts_flag[2]; + /* [0] 1st field, [1] 2nd field */ +}; + +struct dvb_audio_info { + int layer; + u32 bit_rate; + u32 frequency; + u32 mode; + u32 mode_extension ; + u32 emphasis; + u32 framesize; + u32 off; +}; + +int dvb_filter_get_ac3info(u8 *mbuf, int count, struct dvb_audio_info *ai, int pr); + + +#endif diff --git a/drivers/media/dvb/dvb-core/dvb_frontend.c b/drivers/media/dvb/dvb-core/dvb_frontend.c new file mode 100644 index 000000000000..59a9adfae1eb --- /dev/null +++ b/drivers/media/dvb/dvb-core/dvb_frontend.c @@ -0,0 +1,915 @@ +/* + * dvb_frontend.c: DVB frontend tuning interface/thread + * + * + * Copyright (C) 1999-2001 Ralph Metzler + * Marcus Metzler + * Holger Waechtler + * for convergence integrated media GmbH + * + * Copyright (C) 2004 Andrew de Quincey (tuning thread cleanup) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/list.h> +#include <linux/suspend.h> +#include <asm/processor.h> +#include <asm/semaphore.h> + +#include "dvb_frontend.h" +#include "dvbdev.h" + +static int dvb_frontend_debug; +static int dvb_shutdown_timeout = 5; +static int dvb_force_auto_inversion; +static int dvb_override_tune_delay; +static int dvb_powerdown_on_sleep = 1; + +module_param_named(frontend_debug, dvb_frontend_debug, int, 0644); +MODULE_PARM_DESC(dvb_frontend_debug, "Turn on/off frontend core debugging (default:off)."); +module_param(dvb_shutdown_timeout, int, 0444); +MODULE_PARM_DESC(dvb_shutdown_timeout, "wait <shutdown_timeout> seconds after close() before suspending hardware"); +module_param(dvb_force_auto_inversion, int, 0444); +MODULE_PARM_DESC(dvb_force_auto_inversion, "0: normal (default), 1: INVERSION_AUTO forced always"); +module_param(dvb_override_tune_delay, int, 0444); +MODULE_PARM_DESC(dvb_override_tune_delay, "0: normal (default), >0 => delay in milliseconds to wait for lock after a tune attempt"); +module_param(dvb_powerdown_on_sleep, int, 0444); +MODULE_PARM_DESC(dvb_powerdown_on_sleep, "0: do not power down, 1: turn LNB volatage off on sleep (default)"); + +#define dprintk if (dvb_frontend_debug) printk + +#define FESTATE_IDLE 1 +#define FESTATE_RETUNE 2 +#define FESTATE_TUNING_FAST 4 +#define FESTATE_TUNING_SLOW 8 +#define FESTATE_TUNED 16 +#define FESTATE_ZIGZAG_FAST 32 +#define FESTATE_ZIGZAG_SLOW 64 +#define FESTATE_DISEQC 128 +#define FESTATE_WAITFORLOCK (FESTATE_TUNING_FAST | FESTATE_TUNING_SLOW | FESTATE_ZIGZAG_FAST | FESTATE_ZIGZAG_SLOW | FESTATE_DISEQC) +#define FESTATE_SEARCHING_FAST (FESTATE_TUNING_FAST | FESTATE_ZIGZAG_FAST) +#define FESTATE_SEARCHING_SLOW (FESTATE_TUNING_SLOW | FESTATE_ZIGZAG_SLOW) +#define FESTATE_LOSTLOCK (FESTATE_ZIGZAG_FAST | FESTATE_ZIGZAG_SLOW) +/* + * FESTATE_IDLE. No tuning parameters have been supplied and the loop is idling. + * FESTATE_RETUNE. Parameters have been supplied, but we have not yet performed the first tune. + * FESTATE_TUNING_FAST. Tuning parameters have been supplied and fast zigzag scan is in progress. + * FESTATE_TUNING_SLOW. Tuning parameters have been supplied. Fast zigzag failed, so we're trying again, but slower. + * FESTATE_TUNED. The frontend has successfully locked on. + * FESTATE_ZIGZAG_FAST. The lock has been lost, and a fast zigzag has been initiated to try and regain it. + * FESTATE_ZIGZAG_SLOW. The lock has been lost. Fast zigzag has been failed, so we're trying again, but slower. + * FESTATE_DISEQC. A DISEQC command has just been issued. + * FESTATE_WAITFORLOCK. When we're waiting for a lock. + * FESTATE_SEARCHING_FAST. When we're searching for a signal using a fast zigzag scan. + * FESTATE_SEARCHING_SLOW. When we're searching for a signal using a slow zigzag scan. + * FESTATE_LOSTLOCK. When the lock has been lost, and we're searching it again. + */ + +static DECLARE_MUTEX(frontend_mutex); + +struct dvb_frontend_private { + + struct dvb_device *dvbdev; + struct dvb_frontend_parameters parameters; + struct dvb_fe_events events; + struct semaphore sem; + struct list_head list_head; + wait_queue_head_t wait_queue; + pid_t thread_pid; + unsigned long release_jiffies; + int state; + int bending; + int lnb_drift; + int inversion; + int auto_step; + int auto_sub_step; + int started_auto_step; + int min_delay; + int max_drift; + int step_size; + int exit; + int wakeup; + fe_status_t status; +}; + + +static void dvb_frontend_add_event(struct dvb_frontend *fe, fe_status_t status) +{ + struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv; + struct dvb_fe_events *events = &fepriv->events; + struct dvb_frontend_event *e; + int wp; + + dprintk ("%s\n", __FUNCTION__); + + if (down_interruptible (&events->sem)) + return; + + wp = (events->eventw + 1) % MAX_EVENT; + + if (wp == events->eventr) { + events->overflow = 1; + events->eventr = (events->eventr + 1) % MAX_EVENT; + } + + e = &events->events[events->eventw]; + + memcpy (&e->parameters, &fepriv->parameters, + sizeof (struct dvb_frontend_parameters)); + + if (status & FE_HAS_LOCK) + if (fe->ops->get_frontend) + fe->ops->get_frontend(fe, &e->parameters); + + events->eventw = wp; + + up (&events->sem); + + e->status = status; + + wake_up_interruptible (&events->wait_queue); +} + +static int dvb_frontend_get_event(struct dvb_frontend *fe, + struct dvb_frontend_event *event, int flags) +{ + struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv; + struct dvb_fe_events *events = &fepriv->events; + + dprintk ("%s\n", __FUNCTION__); + + if (events->overflow) { + events->overflow = 0; + return -EOVERFLOW; + } + + if (events->eventw == events->eventr) { + int ret; + + if (flags & O_NONBLOCK) + return -EWOULDBLOCK; + + up(&fepriv->sem); + + ret = wait_event_interruptible (events->wait_queue, + events->eventw != events->eventr); + + if (down_interruptible (&fepriv->sem)) + return -ERESTARTSYS; + + if (ret < 0) + return ret; + } + + if (down_interruptible (&events->sem)) + return -ERESTARTSYS; + + memcpy (event, &events->events[events->eventr], + sizeof(struct dvb_frontend_event)); + + events->eventr = (events->eventr + 1) % MAX_EVENT; + + up (&events->sem); + + return 0; +} + +static void dvb_frontend_init(struct dvb_frontend *fe) +{ + dprintk ("DVB: initialising frontend %i (%s)...\n", + fe->dvb->num, + fe->ops->info.name); + + if (fe->ops->init) + fe->ops->init(fe); +} + +static void update_delay(int *quality, int *delay, int min_delay, int locked) +{ + int q2; + + dprintk ("%s\n", __FUNCTION__); + + if (locked) + (*quality) = (*quality * 220 + 36*256) / 256; + else + (*quality) = (*quality * 220 + 0) / 256; + + q2 = *quality - 128; + q2 *= q2; + + *delay = min_delay + q2 * HZ / (128*128); +} + +/** + * Performs automatic twiddling of frontend parameters. + * + * @param fe The frontend concerned. + * @param check_wrapped Checks if an iteration has completed. DO NOT SET ON THE FIRST ATTEMPT + * @returns Number of complete iterations that have been performed. + */ +static int dvb_frontend_autotune(struct dvb_frontend *fe, int check_wrapped) +{ + int autoinversion; + int ready = 0; + struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv; + int original_inversion = fepriv->parameters.inversion; + u32 original_frequency = fepriv->parameters.frequency; + + /* are we using autoinversion? */ + autoinversion = ((!(fe->ops->info.caps & FE_CAN_INVERSION_AUTO)) && + (fepriv->parameters.inversion == INVERSION_AUTO)); + + /* setup parameters correctly */ + while(!ready) { + /* calculate the lnb_drift */ + fepriv->lnb_drift = fepriv->auto_step * fepriv->step_size; + + /* wrap the auto_step if we've exceeded the maximum drift */ + if (fepriv->lnb_drift > fepriv->max_drift) { + fepriv->auto_step = 0; + fepriv->auto_sub_step = 0; + fepriv->lnb_drift = 0; + } + + /* perform inversion and +/- zigzag */ + switch(fepriv->auto_sub_step) { + case 0: + /* try with the current inversion and current drift setting */ + ready = 1; + break; + + case 1: + if (!autoinversion) break; + + fepriv->inversion = (fepriv->inversion == INVERSION_OFF) ? INVERSION_ON : INVERSION_OFF; + ready = 1; + break; + + case 2: + if (fepriv->lnb_drift == 0) break; + + fepriv->lnb_drift = -fepriv->lnb_drift; + ready = 1; + break; + + case 3: + if (fepriv->lnb_drift == 0) break; + if (!autoinversion) break; + + fepriv->inversion = (fepriv->inversion == INVERSION_OFF) ? INVERSION_ON : INVERSION_OFF; + fepriv->lnb_drift = -fepriv->lnb_drift; + ready = 1; + break; + + default: + fepriv->auto_step++; + fepriv->auto_sub_step = -1; /* it'll be incremented to 0 in a moment */ + break; + } + + if (!ready) fepriv->auto_sub_step++; + } + + /* if this attempt would hit where we started, indicate a complete + * iteration has occurred */ + if ((fepriv->auto_step == fepriv->started_auto_step) && + (fepriv->auto_sub_step == 0) && check_wrapped) { + return 1; + } + + dprintk("%s: drift:%i inversion:%i auto_step:%i " + "auto_sub_step:%i started_auto_step:%i\n", + __FUNCTION__, fepriv->lnb_drift, fepriv->inversion, + fepriv->auto_step, fepriv->auto_sub_step, fepriv->started_auto_step); + + /* set the frontend itself */ + fepriv->parameters.frequency += fepriv->lnb_drift; + if (autoinversion) + fepriv->parameters.inversion = fepriv->inversion; + if (fe->ops->set_frontend) + fe->ops->set_frontend(fe, &fepriv->parameters); + + fepriv->parameters.frequency = original_frequency; + fepriv->parameters.inversion = original_inversion; + + fepriv->auto_sub_step++; + return 0; +} + +static int dvb_frontend_is_exiting(struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv; + + if (fepriv->exit) + return 1; + + if (fepriv->dvbdev->writers == 1) + if (jiffies - fepriv->release_jiffies > dvb_shutdown_timeout * HZ) + return 1; + + return 0; +} + +static int dvb_frontend_should_wakeup(struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv; + + if (fepriv->wakeup) { + fepriv->wakeup = 0; + return 1; + } + return dvb_frontend_is_exiting(fe); +} + +static void dvb_frontend_wakeup(struct dvb_frontend *fe) +{ + struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv; + + fepriv->wakeup = 1; + wake_up_interruptible(&fepriv->wait_queue); +} + +/* + * FIXME: use linux/kthread.h + */ +static int dvb_frontend_thread(void *data) +{ + struct dvb_frontend *fe = (struct dvb_frontend *) data; + struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv; + unsigned long timeout; + char name [15]; + int quality = 0, delay = 3*HZ; + fe_status_t s; + int check_wrapped = 0; + + dprintk("%s\n", __FUNCTION__); + + snprintf (name, sizeof(name), "kdvb-fe-%i", fe->dvb->num); + + lock_kernel(); + daemonize(name); + sigfillset(¤t->blocked); + unlock_kernel(); + + fepriv->status = 0; + dvb_frontend_init(fe); + fepriv->wakeup = 0; + + while (1) { + up(&fepriv->sem); /* is locked when we enter the thread... */ + + timeout = wait_event_interruptible_timeout(fepriv->wait_queue, + dvb_frontend_should_wakeup(fe), + delay); + if (0 != dvb_frontend_is_exiting(fe)) { + /* got signal or quitting */ + break; + } + + if (current->flags & PF_FREEZE) + refrigerator(PF_FREEZE); + + if (down_interruptible(&fepriv->sem)) + break; + + /* if we've got no parameters, just keep idling */ + if (fepriv->state & FESTATE_IDLE) { + delay = 3*HZ; + quality = 0; + continue; + } + + /* get the frontend status */ + if (fepriv->state & FESTATE_RETUNE) { + s = 0; + } else { + if (fe->ops->read_status) + fe->ops->read_status(fe, &s); + if (s != fepriv->status) { + dvb_frontend_add_event(fe, s); + fepriv->status = s; + } + } + /* if we're not tuned, and we have a lock, move to the TUNED state */ + if ((fepriv->state & FESTATE_WAITFORLOCK) && (s & FE_HAS_LOCK)) { + update_delay(&quality, &delay, fepriv->min_delay, s & FE_HAS_LOCK); + fepriv->state = FESTATE_TUNED; + + /* if we're tuned, then we have determined the correct inversion */ + if ((!(fe->ops->info.caps & FE_CAN_INVERSION_AUTO)) && + (fepriv->parameters.inversion == INVERSION_AUTO)) { + fepriv->parameters.inversion = fepriv->inversion; + } + continue; + } + + /* if we are tuned already, check we're still locked */ + if (fepriv->state & FESTATE_TUNED) { + update_delay(&quality, &delay, fepriv->min_delay, s & FE_HAS_LOCK); + + /* we're tuned, and the lock is still good... */ + if (s & FE_HAS_LOCK) + continue; + else { + /* if we _WERE_ tuned, but now don't have a lock, + * need to zigzag */ + fepriv->state = FESTATE_ZIGZAG_FAST; + fepriv->started_auto_step = fepriv->auto_step; + check_wrapped = 0; + } + } + + /* don't actually do anything if we're in the LOSTLOCK state, + * the frontend is set to FE_CAN_RECOVER, and the max_drift is 0 */ + if ((fepriv->state & FESTATE_LOSTLOCK) && + (fe->ops->info.caps & FE_CAN_RECOVER) && (fepriv->max_drift == 0)) { + update_delay(&quality, &delay, fepriv->min_delay, s & FE_HAS_LOCK); + continue; + } + + /* don't do anything if we're in the DISEQC state, since this + * might be someone with a motorized dish controlled by DISEQC. + * If its actually a re-tune, there will be a SET_FRONTEND soon enough. */ + if (fepriv->state & FESTATE_DISEQC) { + update_delay(&quality, &delay, fepriv->min_delay, s & FE_HAS_LOCK); + continue; + } + + /* if we're in the RETUNE state, set everything up for a brand + * new scan, keeping the current inversion setting, as the next + * tune is _very_ likely to require the same */ + if (fepriv->state & FESTATE_RETUNE) { + fepriv->lnb_drift = 0; + fepriv->auto_step = 0; + fepriv->auto_sub_step = 0; + fepriv->started_auto_step = 0; + check_wrapped = 0; + } + + /* fast zigzag. */ + if ((fepriv->state & FESTATE_SEARCHING_FAST) || (fepriv->state & FESTATE_RETUNE)) { + delay = fepriv->min_delay; + + /* peform a tune */ + if (dvb_frontend_autotune(fe, check_wrapped)) { + /* OK, if we've run out of trials at the fast speed. + * Drop back to slow for the _next_ attempt */ + fepriv->state = FESTATE_SEARCHING_SLOW; + fepriv->started_auto_step = fepriv->auto_step; + continue; + } + check_wrapped = 1; + + /* if we've just retuned, enter the ZIGZAG_FAST state. + * This ensures we cannot return from an + * FE_SET_FRONTEND ioctl before the first frontend tune + * occurs */ + if (fepriv->state & FESTATE_RETUNE) { + fepriv->state = FESTATE_TUNING_FAST; + } + } + + /* slow zigzag */ + if (fepriv->state & FESTATE_SEARCHING_SLOW) { + update_delay(&quality, &delay, fepriv->min_delay, s & FE_HAS_LOCK); + + /* Note: don't bother checking for wrapping; we stay in this + * state until we get a lock */ + dvb_frontend_autotune(fe, 0); + } + } + + if (dvb_shutdown_timeout) { + if (dvb_powerdown_on_sleep) + if (fe->ops->set_voltage) + fe->ops->set_voltage(fe, SEC_VOLTAGE_OFF); + if (fe->ops->sleep) + fe->ops->sleep(fe); + } + + fepriv->thread_pid = 0; + mb(); + + dvb_frontend_wakeup(fe); + return 0; +} + +static void dvb_frontend_stop(struct dvb_frontend *fe) +{ + unsigned long ret; + struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv; + + dprintk ("%s\n", __FUNCTION__); + + fepriv->exit = 1; + mb(); + + if (!fepriv->thread_pid) + return; + + /* check if the thread is really alive */ + if (kill_proc(fepriv->thread_pid, 0, 1) == -ESRCH) { + printk("dvb_frontend_stop: thread PID %d already died\n", + fepriv->thread_pid); + /* make sure the mutex was not held by the thread */ + init_MUTEX (&fepriv->sem); + return; + } + + /* wake up the frontend thread, so it notices that fe->exit == 1 */ + dvb_frontend_wakeup(fe); + + /* wait until the frontend thread has exited */ + ret = wait_event_interruptible(fepriv->wait_queue,0 == fepriv->thread_pid); + if (-ERESTARTSYS != ret) { + fepriv->state = FESTATE_IDLE; + return; + } + fepriv->state = FESTATE_IDLE; + + /* paranoia check in case a signal arrived */ + if (fepriv->thread_pid) + printk("dvb_frontend_stop: warning: thread PID %d won't exit\n", + fepriv->thread_pid); +} + +static int dvb_frontend_start(struct dvb_frontend *fe) +{ + int ret; + struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv; + + dprintk ("%s\n", __FUNCTION__); + + if (fepriv->thread_pid) { + if (!fepriv->exit) + return 0; + else + dvb_frontend_stop (fe); + } + + if (signal_pending(current)) + return -EINTR; + if (down_interruptible (&fepriv->sem)) + return -EINTR; + + fepriv->state = FESTATE_IDLE; + fepriv->exit = 0; + fepriv->thread_pid = 0; + mb(); + + ret = kernel_thread (dvb_frontend_thread, fe, 0); + + if (ret < 0) { + printk("dvb_frontend_start: failed to start kernel_thread (%d)\n", ret); + up(&fepriv->sem); + return ret; + } + fepriv->thread_pid = ret; + + return 0; +} + +static int dvb_frontend_ioctl(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 = (struct dvb_frontend_private*) fe->frontend_priv; + int err = -EOPNOTSUPP; + + dprintk ("%s\n", __FUNCTION__); + + if (!fe || fepriv->exit) + return -ENODEV; + + if ((file->f_flags & O_ACCMODE) == O_RDONLY && + (_IOC_DIR(cmd) != _IOC_READ || cmd == FE_GET_EVENT || + cmd == FE_DISEQC_RECV_SLAVE_REPLY)) + return -EPERM; + + if (down_interruptible (&fepriv->sem)) + return -ERESTARTSYS; + + switch (cmd) { + case FE_GET_INFO: { + struct dvb_frontend_info* info = (struct dvb_frontend_info*) parg; + memcpy(info, &fe->ops->info, sizeof(struct dvb_frontend_info)); + + /* Force the CAN_INVERSION_AUTO bit on. If the frontend doesn't + * do it, it is done for it. */ + info->caps |= FE_CAN_INVERSION_AUTO; + err = 0; + break; + } + + case FE_READ_STATUS: + if (fe->ops->read_status) + err = fe->ops->read_status(fe, (fe_status_t*) parg); + break; + + case FE_READ_BER: + if (fe->ops->read_ber) + err = fe->ops->read_ber(fe, (__u32*) parg); + break; + + case FE_READ_SIGNAL_STRENGTH: + if (fe->ops->read_signal_strength) + err = fe->ops->read_signal_strength(fe, (__u16*) parg); + break; + + case FE_READ_SNR: + if (fe->ops->read_snr) + err = fe->ops->read_snr(fe, (__u16*) parg); + break; + + case FE_READ_UNCORRECTED_BLOCKS: + if (fe->ops->read_ucblocks) + err = fe->ops->read_ucblocks(fe, (__u32*) parg); + break; + + + case FE_DISEQC_RESET_OVERLOAD: + if (fe->ops->diseqc_reset_overload) { + err = fe->ops->diseqc_reset_overload(fe); + fepriv->state = FESTATE_DISEQC; + fepriv->status = 0; + } + break; + + case FE_DISEQC_SEND_MASTER_CMD: + if (fe->ops->diseqc_send_master_cmd) { + err = fe->ops->diseqc_send_master_cmd(fe, (struct dvb_diseqc_master_cmd*) parg); + fepriv->state = FESTATE_DISEQC; + fepriv->status = 0; + } + break; + + case FE_DISEQC_SEND_BURST: + if (fe->ops->diseqc_send_burst) { + err = fe->ops->diseqc_send_burst(fe, (fe_sec_mini_cmd_t) parg); + fepriv->state = FESTATE_DISEQC; + fepriv->status = 0; + } + break; + + case FE_SET_TONE: + if (fe->ops->set_tone) { + err = fe->ops->set_tone(fe, (fe_sec_tone_mode_t) parg); + fepriv->state = FESTATE_DISEQC; + fepriv->status = 0; + } + break; + + case FE_SET_VOLTAGE: + if (fe->ops->set_voltage) { + err = fe->ops->set_voltage(fe, (fe_sec_voltage_t) parg); + fepriv->state = FESTATE_DISEQC; + fepriv->status = 0; + } + break; + + case FE_DISHNETWORK_SEND_LEGACY_CMD: + if (fe->ops->dishnetwork_send_legacy_command) { + err = fe->ops->dishnetwork_send_legacy_command(fe, (unsigned int) parg); + fepriv->state = FESTATE_DISEQC; + fepriv->status = 0; + } + break; + + case FE_DISEQC_RECV_SLAVE_REPLY: + if (fe->ops->diseqc_recv_slave_reply) + err = fe->ops->diseqc_recv_slave_reply(fe, (struct dvb_diseqc_slave_reply*) parg); + break; + + case FE_ENABLE_HIGH_LNB_VOLTAGE: + if (fe->ops->enable_high_lnb_voltage) + err = fe->ops->enable_high_lnb_voltage(fe, (int) parg); + break; + + case FE_SET_FRONTEND: { + struct dvb_frontend_tune_settings fetunesettings; + + memcpy (&fepriv->parameters, parg, + sizeof (struct dvb_frontend_parameters)); + + memset(&fetunesettings, 0, sizeof(struct dvb_frontend_tune_settings)); + memcpy(&fetunesettings.parameters, parg, + sizeof (struct dvb_frontend_parameters)); + + /* force auto frequency inversion if requested */ + if (dvb_force_auto_inversion) { + fepriv->parameters.inversion = INVERSION_AUTO; + fetunesettings.parameters.inversion = INVERSION_AUTO; + } + if (fe->ops->info.type == FE_OFDM) { + /* without hierachical coding code_rate_LP is irrelevant, + * so we tolerate the otherwise invalid FEC_NONE setting */ + if (fepriv->parameters.u.ofdm.hierarchy_information == HIERARCHY_NONE && + fepriv->parameters.u.ofdm.code_rate_LP == FEC_NONE) + fepriv->parameters.u.ofdm.code_rate_LP = FEC_AUTO; + } + + /* get frontend-specific tuning settings */ + if (fe->ops->get_tune_settings && (fe->ops->get_tune_settings(fe, &fetunesettings) == 0)) { + fepriv->min_delay = (fetunesettings.min_delay_ms * HZ) / 1000; + fepriv->max_drift = fetunesettings.max_drift; + fepriv->step_size = fetunesettings.step_size; + } else { + /* default values */ + switch(fe->ops->info.type) { + case FE_QPSK: + fepriv->min_delay = HZ/20; + fepriv->step_size = fepriv->parameters.u.qpsk.symbol_rate / 16000; + fepriv->max_drift = fepriv->parameters.u.qpsk.symbol_rate / 2000; + break; + + case FE_QAM: + fepriv->min_delay = HZ/20; + fepriv->step_size = 0; /* no zigzag */ + fepriv->max_drift = 0; + break; + + case FE_OFDM: + fepriv->min_delay = HZ/20; + fepriv->step_size = fe->ops->info.frequency_stepsize * 2; + fepriv->max_drift = (fe->ops->info.frequency_stepsize * 2) + 1; + break; + case FE_ATSC: + printk("dvb-core: FE_ATSC not handled yet.\n"); + break; + } + } + if (dvb_override_tune_delay > 0) + fepriv->min_delay = (dvb_override_tune_delay * HZ) / 1000; + + fepriv->state = FESTATE_RETUNE; + dvb_frontend_wakeup(fe); + dvb_frontend_add_event(fe, 0); + fepriv->status = 0; + err = 0; + break; + } + + case FE_GET_EVENT: + err = dvb_frontend_get_event (fe, parg, file->f_flags); + break; + + case FE_GET_FRONTEND: + if (fe->ops->get_frontend) { + memcpy (parg, &fepriv->parameters, sizeof (struct dvb_frontend_parameters)); + err = fe->ops->get_frontend(fe, (struct dvb_frontend_parameters*) parg); + } + 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; + struct dvb_frontend *fe = dvbdev->priv; + struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv; + + dprintk ("%s\n", __FUNCTION__); + + poll_wait (file, &fepriv->events.wait_queue, wait); + + if (fepriv->events.eventw != fepriv->events.eventr) + return (POLLIN | POLLRDNORM | POLLPRI); + + return 0; +} + +static int dvb_frontend_open(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_frontend *fe = dvbdev->priv; + struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv; + int ret; + + dprintk ("%s\n", __FUNCTION__); + + if ((ret = dvb_generic_open (inode, file)) < 0) + return ret; + + if ((file->f_flags & O_ACCMODE) != O_RDONLY) { + ret = dvb_frontend_start (fe); + if (ret) + dvb_generic_release (inode, file); + + /* empty event queue */ + fepriv->events.eventr = fepriv->events.eventw = 0; + } + + return ret; +} + +static int dvb_frontend_release(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + struct dvb_frontend *fe = dvbdev->priv; + struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv; + + dprintk ("%s\n", __FUNCTION__); + + if ((file->f_flags & O_ACCMODE) != O_RDONLY) + fepriv->release_jiffies = jiffies; + + return dvb_generic_release (inode, file); +} + +static struct file_operations dvb_frontend_fops = { + .owner = THIS_MODULE, + .ioctl = dvb_generic_ioctl, + .poll = dvb_frontend_poll, + .open = dvb_frontend_open, + .release = dvb_frontend_release +}; + +int dvb_register_frontend(struct dvb_adapter* dvb, + struct dvb_frontend* fe) +{ + struct dvb_frontend_private *fepriv; + static const struct dvb_device dvbdev_template = { + .users = ~0, + .writers = 1, + .readers = (~0)-1, + .fops = &dvb_frontend_fops, + .kernel_ioctl = dvb_frontend_ioctl + }; + + dprintk ("%s\n", __FUNCTION__); + + if (down_interruptible (&frontend_mutex)) + return -ERESTARTSYS; + + fe->frontend_priv = kmalloc(sizeof(struct dvb_frontend_private), GFP_KERNEL); + if (fe->frontend_priv == NULL) { + up(&frontend_mutex); + return -ENOMEM; + } + fepriv = (struct dvb_frontend_private*) fe->frontend_priv; + memset(fe->frontend_priv, 0, sizeof(struct dvb_frontend_private)); + + init_MUTEX (&fepriv->sem); + init_waitqueue_head (&fepriv->wait_queue); + init_waitqueue_head (&fepriv->events.wait_queue); + init_MUTEX (&fepriv->events.sem); + fe->dvb = dvb; + fepriv->inversion = INVERSION_OFF; + + printk ("DVB: registering frontend %i (%s)...\n", + fe->dvb->num, + fe->ops->info.name); + + dvb_register_device (fe->dvb, &fepriv->dvbdev, &dvbdev_template, + fe, DVB_DEVICE_FRONTEND); + + up (&frontend_mutex); + return 0; +} +EXPORT_SYMBOL(dvb_register_frontend); + +int dvb_unregister_frontend(struct dvb_frontend* fe) +{ + struct dvb_frontend_private *fepriv = (struct dvb_frontend_private*) fe->frontend_priv; + dprintk ("%s\n", __FUNCTION__); + + down (&frontend_mutex); + dvb_unregister_device (fepriv->dvbdev); + dvb_frontend_stop (fe); + if (fe->ops->release) + fe->ops->release(fe); + else + printk("dvb_frontend: Demodulator (%s) does not have a release callback!\n", fe->ops->info.name); + /* fe is invalid now */ + kfree(fepriv); + up (&frontend_mutex); + return 0; +} +EXPORT_SYMBOL(dvb_unregister_frontend); diff --git a/drivers/media/dvb/dvb-core/dvb_frontend.h b/drivers/media/dvb/dvb-core/dvb_frontend.h new file mode 100644 index 000000000000..d2b021792791 --- /dev/null +++ b/drivers/media/dvb/dvb-core/dvb_frontend.h @@ -0,0 +1,126 @@ +/* + * dvb_frontend.h + * + * Copyright (C) 2001 convergence integrated media GmbH + * Copyright (C) 2004 convergence GmbH + * + * Written by Ralph Metzler + * Overhauled by Holger Waechtler + * Kernel I2C stuff by Michael Hunold <hunold@convergence.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * 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 Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef _DVB_FRONTEND_H_ +#define _DVB_FRONTEND_H_ + +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/ioctl.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/delay.h> + +#include <linux/dvb/frontend.h> + +#include "dvbdev.h" + +/* FIXME: Move to i2c-id.h */ +#define I2C_DRIVERID_DVBFE_SP8870 I2C_DRIVERID_EXP2 +#define I2C_DRIVERID_DVBFE_CX22700 I2C_DRIVERID_EXP2 +#define I2C_DRIVERID_DVBFE_AT76C651 I2C_DRIVERID_EXP2 +#define I2C_DRIVERID_DVBFE_CX24110 I2C_DRIVERID_EXP2 +#define I2C_DRIVERID_DVBFE_CX22702 I2C_DRIVERID_EXP2 +#define I2C_DRIVERID_DVBFE_DIB3000MB I2C_DRIVERID_EXP2 +#define I2C_DRIVERID_DVBFE_DST I2C_DRIVERID_EXP2 +#define I2C_DRIVERID_DVBFE_DUMMY I2C_DRIVERID_EXP2 +#define I2C_DRIVERID_DVBFE_L64781 I2C_DRIVERID_EXP2 +#define I2C_DRIVERID_DVBFE_MT312 I2C_DRIVERID_EXP2 +#define I2C_DRIVERID_DVBFE_MT352 I2C_DRIVERID_EXP2 +#define I2C_DRIVERID_DVBFE_NXT6000 I2C_DRIVERID_EXP2 +#define I2C_DRIVERID_DVBFE_SP887X I2C_DRIVERID_EXP2 +#define I2C_DRIVERID_DVBFE_STV0299 I2C_DRIVERID_EXP2 +#define I2C_DRIVERID_DVBFE_TDA1004X I2C_DRIVERID_EXP2 +#define I2C_DRIVERID_DVBFE_TDA8083 I2C_DRIVERID_EXP2 +#define I2C_DRIVERID_DVBFE_VES1820 I2C_DRIVERID_EXP2 +#define I2C_DRIVERID_DVBFE_VES1X93 I2C_DRIVERID_EXP2 +#define I2C_DRIVERID_DVBFE_TDA80XX I2C_DRIVERID_EXP2 + + +struct dvb_frontend_tune_settings { + int min_delay_ms; + int step_size; + int max_drift; + struct dvb_frontend_parameters parameters; +}; + +struct dvb_frontend; + +struct dvb_frontend_ops { + + struct dvb_frontend_info info; + + void (*release)(struct dvb_frontend* fe); + + int (*init)(struct dvb_frontend* fe); + int (*sleep)(struct dvb_frontend* fe); + + int (*set_frontend)(struct dvb_frontend* fe, struct dvb_frontend_parameters* params); + int (*get_frontend)(struct dvb_frontend* fe, struct dvb_frontend_parameters* params); + int (*get_tune_settings)(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* settings); + + int (*read_status)(struct dvb_frontend* fe, fe_status_t* status); + int (*read_ber)(struct dvb_frontend* fe, u32* ber); + int (*read_signal_strength)(struct dvb_frontend* fe, u16* strength); + int (*read_snr)(struct dvb_frontend* fe, u16* snr); + int (*read_ucblocks)(struct dvb_frontend* fe, u32* ucblocks); + + int (*diseqc_reset_overload)(struct dvb_frontend* fe); + int (*diseqc_send_master_cmd)(struct dvb_frontend* fe, struct dvb_diseqc_master_cmd* cmd); + int (*diseqc_recv_slave_reply)(struct dvb_frontend* fe, struct dvb_diseqc_slave_reply* reply); + int (*diseqc_send_burst)(struct dvb_frontend* fe, fe_sec_mini_cmd_t minicmd); + int (*set_tone)(struct dvb_frontend* fe, fe_sec_tone_mode_t tone); + int (*set_voltage)(struct dvb_frontend* fe, fe_sec_voltage_t voltage); + int (*enable_high_lnb_voltage)(struct dvb_frontend* fe, int arg); + int (*dishnetwork_send_legacy_command)(struct dvb_frontend* fe, unsigned int cmd); +}; + +#define MAX_EVENT 8 + +struct dvb_fe_events { + struct dvb_frontend_event events[MAX_EVENT]; + int eventw; + int eventr; + int overflow; + wait_queue_head_t wait_queue; + struct semaphore sem; +}; + +struct dvb_frontend { + struct dvb_frontend_ops* ops; + struct dvb_adapter *dvb; + void* demodulator_priv; + void* frontend_priv; +}; + +extern int dvb_register_frontend(struct dvb_adapter* dvb, + struct dvb_frontend* fe); + +extern int dvb_unregister_frontend(struct dvb_frontend* fe); + +#endif diff --git a/drivers/media/dvb/dvb-core/dvb_net.c b/drivers/media/dvb/dvb-core/dvb_net.c new file mode 100644 index 000000000000..44892e7abd3d --- /dev/null +++ b/drivers/media/dvb/dvb-core/dvb_net.c @@ -0,0 +1,1381 @@ +/* + * dvb_net.c + * + * Copyright (C) 2001 Convergence integrated media GmbH + * Ralph Metzler <ralph@convergence.de> + * Copyright (C) 2002 Ralph Metzler <rjkm@metzlerbros.de> + * + * ULE Decapsulation code: + * Copyright (C) 2003, 2004 gcs - Global Communication & Services GmbH. + * and Department of Scientific Computing + * Paris Lodron University of Salzburg. + * Hilmar Linder <hlinder@cosy.sbg.ac.at> + * and Wolfram Stering <wstering@cosy.sbg.ac.at> + * + * ULE Decaps according to draft-ietf-ipdvb-ule-03.txt. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + */ + +/* + * ULE ChangeLog: + * Feb 2004: hl/ws v1: Implementing draft-fair-ipdvb-ule-01.txt + * + * Dec 2004: hl/ws v2: Implementing draft-ietf-ipdvb-ule-03.txt: + * ULE Extension header handling. + * Bugreports by Moritz Vieth and Hanno Tersteegen, + * Fraunhofer Institute for Open Communication Systems + * Competence Center for Advanced Satellite Communications. + * Bugfixes and robustness improvements. + * Filtering on dest MAC addresses, if present (D-Bit = 0) + * ULE_DEBUG compile-time option. + */ + +/* + * FIXME / TODO (dvb_net.c): + * + * Unloading does not work for 2.6.9 kernels: a refcount doesn't go to zero. + * + * TS_FEED callback is called once for every single TS cell although it is + * registered (in dvb_net_feed_start()) for 100 TS cells (used for dvb_net_ule()). + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/dvb/net.h> +#include <linux/uio.h> +#include <asm/uaccess.h> +#include <linux/crc32.h> +#include <linux/version.h> + +#include "dvb_demux.h" +#include "dvb_net.h" + +static int dvb_net_debug; +module_param(dvb_net_debug, int, 0444); +MODULE_PARM_DESC(dvb_net_debug, "enable debug messages"); + +#define dprintk(x...) do { if (dvb_net_debug) printk(x); } while (0) + + +static inline __u32 iov_crc32( __u32 c, struct kvec *iov, unsigned int cnt ) +{ + unsigned int j; + for (j = 0; j < cnt; j++) + c = crc32_be( c, iov[j].iov_base, iov[j].iov_len ); + return c; +} + + +#define DVB_NET_MULTICAST_MAX 10 + +#undef ULE_DEBUG + +#ifdef ULE_DEBUG + +#define isprint(c) ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) + +static void hexdump( const unsigned char *buf, unsigned short len ) +{ + char str[80], octet[10]; + int ofs, i, l; + + for (ofs = 0; ofs < len; ofs += 16) { + sprintf( str, "%03d: ", ofs ); + + for (i = 0; i < 16; i++) { + if ((i + ofs) < len) + sprintf( octet, "%02x ", buf[ofs + i] ); + else + strcpy( octet, " " ); + + strcat( str, octet ); + } + strcat( str, " " ); + l = strlen( str ); + + for (i = 0; (i < 16) && ((i + ofs) < len); i++) + str[l++] = isprint( buf[ofs + i] ) ? buf[ofs + i] : '.'; + + str[l] = '\0'; + printk( KERN_WARNING "%s\n", str ); + } +} + +#endif + +struct dvb_net_priv { + int in_use; + struct net_device_stats stats; + u16 pid; + struct dvb_net *host; + struct dmx_demux *demux; + struct dmx_section_feed *secfeed; + struct dmx_section_filter *secfilter; + struct dmx_ts_feed *tsfeed; + int multi_num; + struct dmx_section_filter *multi_secfilter[DVB_NET_MULTICAST_MAX]; + unsigned char multi_macs[DVB_NET_MULTICAST_MAX][6]; + int rx_mode; +#define RX_MODE_UNI 0 +#define RX_MODE_MULTI 1 +#define RX_MODE_ALL_MULTI 2 +#define RX_MODE_PROMISC 3 + struct work_struct set_multicast_list_wq; + struct work_struct restart_net_feed_wq; + unsigned char feedtype; /* Either FEED_TYPE_ or FEED_TYPE_ULE */ + int need_pusi; /* Set to 1, if synchronization on PUSI required. */ + unsigned char tscc; /* TS continuity counter after sync on PUSI. */ + struct sk_buff *ule_skb; /* ULE SNDU decodes into this buffer. */ + unsigned char *ule_next_hdr; /* Pointer into skb to next ULE extension header. */ + unsigned short ule_sndu_len; /* ULE SNDU length in bytes, w/o D-Bit. */ + unsigned short ule_sndu_type; /* ULE SNDU type field, complete. */ + unsigned char ule_sndu_type_1; /* ULE SNDU type field, if split across 2 TS cells. */ + unsigned char ule_dbit; /* Whether the DestMAC address present + * or not (bit is set). */ + unsigned char ule_bridged; /* Whether the ULE_BRIDGED extension header was found. */ + int ule_sndu_remain; /* Nr. of bytes still required for current ULE SNDU. */ + unsigned long ts_count; /* Current ts cell counter. */ +}; + + +/** + * Determine the packet's protocol ID. The rule here is that we + * assume 802.3 if the type field is short enough to be a length. + * This is normal practice and works for any 'now in use' protocol. + * + * stolen from eth.c out of the linux kernel, hacked for dvb-device + * by Michael Holzt <kju@debian.org> + */ +static unsigned short dvb_net_eth_type_trans(struct sk_buff *skb, + struct net_device *dev) +{ + struct ethhdr *eth; + unsigned char *rawp; + + skb->mac.raw=skb->data; + skb_pull(skb,dev->hard_header_len); +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,8) + eth = skb->mac.ethernet; +#else + eth = eth_hdr(skb); +#endif + + if (*eth->h_dest & 1) { + if(memcmp(eth->h_dest,dev->broadcast, ETH_ALEN)==0) + skb->pkt_type=PACKET_BROADCAST; + else + skb->pkt_type=PACKET_MULTICAST; + } + + if (ntohs(eth->h_proto) >= 1536) + return eth->h_proto; + + rawp = skb->data; + + /** + * This is a magic hack to spot IPX packets. Older Novell breaks + * the protocol design and runs IPX over 802.3 without an 802.2 LLC + * layer. We look for FFFF which isn't a used 802.2 SSAP/DSAP. This + * won't work for fault tolerant netware but does for the rest. + */ + if (*(unsigned short *)rawp == 0xFFFF) + return htons(ETH_P_802_3); + + /** + * Real 802.2 LLC + */ + return htons(ETH_P_802_2); +} + +#define TS_SZ 188 +#define TS_SYNC 0x47 +#define TS_TEI 0x80 +#define TS_SC 0xC0 +#define TS_PUSI 0x40 +#define TS_AF_A 0x20 +#define TS_AF_D 0x10 + +/* ULE Extension Header handlers. */ + +#define ULE_TEST 0 +#define ULE_BRIDGED 1 + +static int ule_test_sndu( struct dvb_net_priv *p ) +{ + return -1; +} + +static int ule_bridged_sndu( struct dvb_net_priv *p ) +{ + /* BRIDGE SNDU handling sucks in draft-ietf-ipdvb-ule-03.txt. + * This has to be the last extension header, otherwise it won't work. + * Blame the authors! + */ + p->ule_bridged = 1; + return 0; +} + + +/** Handle ULE extension headers. + * Function is called after a successful CRC32 verification of an ULE SNDU to complete its decoding. + * Returns: >= 0: nr. of bytes consumed by next extension header + * -1: Mandatory extension header that is not recognized or TEST SNDU; discard. + */ +static int handle_one_ule_extension( struct dvb_net_priv *p ) +{ + /* Table of mandatory extension header handlers. The header type is the index. */ + static int (*ule_mandatory_ext_handlers[255])( struct dvb_net_priv *p ) = + { [0] = ule_test_sndu, [1] = ule_bridged_sndu, [2] = NULL, }; + + /* Table of optional extension header handlers. The header type is the index. */ + static int (*ule_optional_ext_handlers[255])( struct dvb_net_priv *p ) = { NULL, }; + + int ext_len = 0; + unsigned char hlen = (p->ule_sndu_type & 0x0700) >> 8; + unsigned char htype = p->ule_sndu_type & 0x00FF; + + /* Discriminate mandatory and optional extension headers. */ + if (hlen == 0) { + /* Mandatory extension header */ + if (ule_mandatory_ext_handlers[htype]) { + ext_len = ule_mandatory_ext_handlers[htype]( p ); + p->ule_next_hdr += ext_len; + if (! p->ule_bridged) { + p->ule_sndu_type = ntohs( *(unsigned short *)p->ule_next_hdr ); + p->ule_next_hdr += 2; + } else { + p->ule_sndu_type = ntohs( *(unsigned short *)(p->ule_next_hdr + ((p->ule_dbit ? 2 : 3) * ETH_ALEN)) ); + /* This assures the extension handling loop will terminate. */ + } + } else + ext_len = -1; /* SNDU has to be discarded. */ + } else { + /* Optional extension header. Calculate the length. */ + ext_len = hlen << 2; + /* Process the optional extension header according to its type. */ + if (ule_optional_ext_handlers[htype]) + (void)ule_optional_ext_handlers[htype]( p ); + p->ule_next_hdr += ext_len; + p->ule_sndu_type = ntohs( *(unsigned short *)p->ule_next_hdr ); + p->ule_next_hdr += 2; + } + + return ext_len; +} + +static int handle_ule_extensions( struct dvb_net_priv *p ) +{ + int total_ext_len = 0, l; + + p->ule_next_hdr = p->ule_skb->data; + do { + l = handle_one_ule_extension( p ); + if (l == -1) return -1; /* Stop extension header processing and discard SNDU. */ + total_ext_len += l; + + } while (p->ule_sndu_type < 1536); + + return total_ext_len; +} + + +/** Prepare for a new ULE SNDU: reset the decoder state. */ +static inline void reset_ule( struct dvb_net_priv *p ) +{ + p->ule_skb = NULL; + p->ule_next_hdr = NULL; + p->ule_sndu_len = 0; + p->ule_sndu_type = 0; + p->ule_sndu_type_1 = 0; + p->ule_sndu_remain = 0; + p->ule_dbit = 0xFF; + p->ule_bridged = 0; +} + +/** + * Decode ULE SNDUs according to draft-ietf-ipdvb-ule-03.txt from a sequence of + * TS cells of a single PID. + */ +static void dvb_net_ule( struct net_device *dev, const u8 *buf, size_t buf_len ) +{ + struct dvb_net_priv *priv = (struct dvb_net_priv *)dev->priv; + unsigned long skipped = 0L; + u8 *ts, *ts_end, *from_where = NULL, ts_remain = 0, how_much = 0, new_ts = 1; + struct ethhdr *ethh = NULL; + +#ifdef ULE_DEBUG + /* The code inside ULE_DEBUG keeps a history of the last 100 TS cells processed. */ + static unsigned char ule_hist[100*TS_SZ]; + static unsigned char *ule_where = ule_hist, ule_dump = 0; +#endif + + if (dev == NULL) { + printk( KERN_ERR "NO netdev struct!\n" ); + return; + } + + /* For all TS cells in current buffer. + * Appearently, we are called for every single TS cell. + */ + for (ts = (char *)buf, ts_end = (char *)buf + buf_len; ts < ts_end; /* no default incr. */ ) { + + if (new_ts) { + /* We are about to process a new TS cell. */ + +#ifdef ULE_DEBUG + if (ule_where >= &ule_hist[100*TS_SZ]) ule_where = ule_hist; + memcpy( ule_where, ts, TS_SZ ); + if (ule_dump) { + hexdump( ule_where, TS_SZ ); + ule_dump = 0; + } + ule_where += TS_SZ; +#endif + + /* Check TS error conditions: sync_byte, transport_error_indicator, scrambling_control . */ + if ((ts[0] != TS_SYNC) || (ts[1] & TS_TEI) || ((ts[3] & TS_SC) != 0)) { + printk(KERN_WARNING "%lu: Invalid TS cell: SYNC %#x, TEI %u, SC %#x.\n", + priv->ts_count, ts[0], ts[1] & TS_TEI >> 7, ts[3] & 0xC0 >> 6); + + /* Drop partly decoded SNDU, reset state, resync on PUSI. */ + if (priv->ule_skb) { + dev_kfree_skb( priv->ule_skb ); + /* Prepare for next SNDU. */ + ((struct dvb_net_priv *) dev->priv)->stats.rx_errors++; + ((struct dvb_net_priv *) dev->priv)->stats.rx_frame_errors++; + } + reset_ule(priv); + priv->need_pusi = 1; + + /* Continue with next TS cell. */ + ts += TS_SZ; + priv->ts_count++; + continue; + } + + ts_remain = 184; + from_where = ts + 4; + } + /* Synchronize on PUSI, if required. */ + if (priv->need_pusi) { + if (ts[1] & TS_PUSI) { + /* Find beginning of first ULE SNDU in current TS cell. */ + /* Synchronize continuity counter. */ + priv->tscc = ts[3] & 0x0F; + /* There is a pointer field here. */ + if (ts[4] > ts_remain) { + printk(KERN_ERR "%lu: Invalid ULE packet " + "(pointer field %d)\n", priv->ts_count, ts[4]); + ts += TS_SZ; + priv->ts_count++; + continue; + } + /* Skip to destination of pointer field. */ + from_where = &ts[5] + ts[4]; + ts_remain -= 1 + ts[4]; + skipped = 0; + } else { + skipped++; + ts += TS_SZ; + priv->ts_count++; + continue; + } + } + + /* Check continuity counter. */ + if (new_ts) { + if ((ts[3] & 0x0F) == priv->tscc) + priv->tscc = (priv->tscc + 1) & 0x0F; + else { + /* TS discontinuity handling: */ + printk(KERN_WARNING "%lu: TS discontinuity: got %#x, " + "exptected %#x.\n", priv->ts_count, ts[3] & 0x0F, priv->tscc); + /* Drop partly decoded SNDU, reset state, resync on PUSI. */ + if (priv->ule_skb) { + dev_kfree_skb( priv->ule_skb ); + /* Prepare for next SNDU. */ + // reset_ule(priv); moved to below. + ((struct dvb_net_priv *) dev->priv)->stats.rx_errors++; + ((struct dvb_net_priv *) dev->priv)->stats.rx_frame_errors++; + } + reset_ule(priv); + /* skip to next PUSI. */ + priv->need_pusi = 1; + ts += TS_SZ; + priv->ts_count++; + continue; + } + /* If we still have an incomplete payload, but PUSI is + * set; some TS cells are missing. + * This is only possible here, if we missed exactly 16 TS + * cells (continuity counter wrap). */ + if (ts[1] & TS_PUSI) { + if (! priv->need_pusi) { + if (*from_where > 181) { + /* Pointer field is invalid. Drop this TS cell and any started ULE SNDU. */ + printk(KERN_WARNING "%lu: Invalid pointer " + "field: %u.\n", priv->ts_count, *from_where); + + /* Drop partly decoded SNDU, reset state, resync on PUSI. */ + if (priv->ule_skb) { + dev_kfree_skb( priv->ule_skb ); + ((struct dvb_net_priv *) dev->priv)->stats.rx_errors++; + ((struct dvb_net_priv *) dev->priv)->stats.rx_frame_errors++; + } + reset_ule(priv); + priv->need_pusi = 1; + ts += TS_SZ; + priv->ts_count++; + continue; + } + /* Skip pointer field (we're processing a + * packed payload). */ + from_where += 1; + ts_remain -= 1; + } else + priv->need_pusi = 0; + + if (priv->ule_sndu_remain > 183) { + /* Current SNDU lacks more data than there could be available in the + * current TS cell. */ + ((struct dvb_net_priv *) dev->priv)->stats.rx_errors++; + ((struct dvb_net_priv *) dev->priv)->stats.rx_length_errors++; + printk(KERN_WARNING "%lu: Expected %d more SNDU bytes, but " + "got PUSI (pf %d, ts_remain %d). Flushing incomplete payload.\n", + priv->ts_count, priv->ule_sndu_remain, ts[4], ts_remain); + dev_kfree_skb(priv->ule_skb); + /* Prepare for next SNDU. */ + reset_ule(priv); + /* Resync: go to where pointer field points to: start of next ULE SNDU. */ + from_where += ts[4]; + ts_remain -= ts[4]; + } + } + } + + /* Check if new payload needs to be started. */ + if (priv->ule_skb == NULL) { + /* Start a new payload with skb. + * Find ULE header. It is only guaranteed that the + * length field (2 bytes) is contained in the current + * TS. + * Check ts_remain has to be >= 2 here. */ + if (ts_remain < 2) { + printk(KERN_WARNING "Invalid payload packing: only %d " + "bytes left in TS. Resyncing.\n", ts_remain); + priv->ule_sndu_len = 0; + priv->need_pusi = 1; + continue; + } + + if (! priv->ule_sndu_len) { + /* Got at least two bytes, thus extrace the SNDU length. */ + priv->ule_sndu_len = from_where[0] << 8 | from_where[1]; + if (priv->ule_sndu_len & 0x8000) { + /* D-Bit is set: no dest mac present. */ + priv->ule_sndu_len &= 0x7FFF; + priv->ule_dbit = 1; + } else + priv->ule_dbit = 0; + + if (priv->ule_sndu_len > 32763) { + printk(KERN_WARNING "%lu: Invalid ULE SNDU length %u. " + "Resyncing.\n", priv->ts_count, priv->ule_sndu_len); + priv->ule_sndu_len = 0; + priv->need_pusi = 1; + new_ts = 1; + ts += TS_SZ; + priv->ts_count++; + continue; + } + ts_remain -= 2; /* consume the 2 bytes SNDU length. */ + from_where += 2; + } + + /* + * State of current TS: + * ts_remain (remaining bytes in the current TS cell) + * 0 ule_type is not available now, we need the next TS cell + * 1 the first byte of the ule_type is present + * >=2 full ULE header present, maybe some payload data as well. + */ + switch (ts_remain) { + case 1: + priv->ule_sndu_type = from_where[0] << 8; + priv->ule_sndu_type_1 = 1; /* first byte of ule_type is set. */ + ts_remain -= 1; from_where += 1; + /* Continue w/ next TS. */ + case 0: + new_ts = 1; + ts += TS_SZ; + priv->ts_count++; + continue; + + default: /* complete ULE header is present in current TS. */ + /* Extract ULE type field. */ + if (priv->ule_sndu_type_1) { + priv->ule_sndu_type |= from_where[0]; + from_where += 1; /* points to payload start. */ + ts_remain -= 1; + } else { + /* Complete type is present in new TS. */ + priv->ule_sndu_type = from_where[0] << 8 | from_where[1]; + from_where += 2; /* points to payload start. */ + ts_remain -= 2; + } + break; + } + + /* Allocate the skb (decoder target buffer) with the correct size, as follows: + * prepare for the largest case: bridged SNDU with MAC address (dbit = 0). */ + priv->ule_skb = dev_alloc_skb( priv->ule_sndu_len + ETH_HLEN + ETH_ALEN ); + if (priv->ule_skb == NULL) { + printk(KERN_NOTICE "%s: Memory squeeze, dropping packet.\n", + dev->name); + ((struct dvb_net_priv *)dev->priv)->stats.rx_dropped++; + return; + } + + /* This includes the CRC32 _and_ dest mac, if !dbit. */ + priv->ule_sndu_remain = priv->ule_sndu_len; + priv->ule_skb->dev = dev; + /* Leave space for Ethernet or bridged SNDU header (eth hdr plus one MAC addr). */ + skb_reserve( priv->ule_skb, ETH_HLEN + ETH_ALEN ); + } + + /* Copy data into our current skb. */ + how_much = min(priv->ule_sndu_remain, (int)ts_remain); + memcpy(skb_put(priv->ule_skb, how_much), from_where, how_much); + priv->ule_sndu_remain -= how_much; + ts_remain -= how_much; + from_where += how_much; + + /* Check for complete payload. */ + if (priv->ule_sndu_remain <= 0) { + /* Check CRC32, we've got it in our skb already. */ + unsigned short ulen = htons(priv->ule_sndu_len); + unsigned short utype = htons(priv->ule_sndu_type); + struct kvec iov[3] = { + { &ulen, sizeof ulen }, + { &utype, sizeof utype }, + { priv->ule_skb->data, priv->ule_skb->len - 4 } + }; + unsigned long ule_crc = ~0L, expected_crc; + if (priv->ule_dbit) { + /* Set D-bit for CRC32 verification, + * if it was set originally. */ + ulen |= 0x0080; + } + + ule_crc = iov_crc32(ule_crc, iov, 3); + expected_crc = *((u8 *)priv->ule_skb->tail - 4) << 24 | + *((u8 *)priv->ule_skb->tail - 3) << 16 | + *((u8 *)priv->ule_skb->tail - 2) << 8 | + *((u8 *)priv->ule_skb->tail - 1); + if (ule_crc != expected_crc) { + printk(KERN_WARNING "%lu: CRC32 check FAILED: %#lx / %#lx, SNDU len %d type %#x, ts_remain %d, next 2: %x.\n", + priv->ts_count, ule_crc, expected_crc, priv->ule_sndu_len, priv->ule_sndu_type, ts_remain, ts_remain > 2 ? *(unsigned short *)from_where : 0); + +#ifdef ULE_DEBUG + hexdump( iov[0].iov_base, iov[0].iov_len ); + hexdump( iov[1].iov_base, iov[1].iov_len ); + hexdump( iov[2].iov_base, iov[2].iov_len ); + + if (ule_where == ule_hist) { + hexdump( &ule_hist[98*TS_SZ], TS_SZ ); + hexdump( &ule_hist[99*TS_SZ], TS_SZ ); + } else if (ule_where == &ule_hist[TS_SZ]) { + hexdump( &ule_hist[99*TS_SZ], TS_SZ ); + hexdump( ule_hist, TS_SZ ); + } else { + hexdump( ule_where - TS_SZ - TS_SZ, TS_SZ ); + hexdump( ule_where - TS_SZ, TS_SZ ); + } + ule_dump = 1; +#endif + + ((struct dvb_net_priv *) dev->priv)->stats.rx_errors++; + ((struct dvb_net_priv *) dev->priv)->stats.rx_crc_errors++; + dev_kfree_skb(priv->ule_skb); + } else { + /* CRC32 verified OK. */ + /* Handle ULE Extension Headers. */ + if (priv->ule_sndu_type < 1536) { + /* There is an extension header. Handle it accordingly. */ + int l = handle_ule_extensions( priv ); + if (l < 0) { + /* Mandatory extension header unknown or TEST SNDU. Drop it. */ + // printk( KERN_WARNING "Dropping SNDU, extension headers.\n" ); + dev_kfree_skb( priv->ule_skb ); + goto sndu_done; + } + skb_pull( priv->ule_skb, l ); + } + + /* CRC32 was OK. Remove it from skb. */ + priv->ule_skb->tail -= 4; + priv->ule_skb->len -= 4; + + /* Filter on receiver's destination MAC address, if present. */ + if (!priv->ule_dbit) { + /* The destination MAC address is the next data in the skb. */ + if (memcmp( priv->ule_skb->data, dev->dev_addr, ETH_ALEN )) { + /* MAC addresses don't match. Drop SNDU. */ + // printk( KERN_WARNING "Dropping SNDU, MAC address.\n" ); + dev_kfree_skb( priv->ule_skb ); + goto sndu_done; + } + if (! priv->ule_bridged) { + skb_push( priv->ule_skb, ETH_ALEN + 2 ); + ethh = (struct ethhdr *)priv->ule_skb->data; + memcpy( ethh->h_dest, ethh->h_source, ETH_ALEN ); + memset( ethh->h_source, 0, ETH_ALEN ); + ethh->h_proto = htons( priv->ule_sndu_type ); + } else { + /* Skip the Receiver destination MAC address. */ + skb_pull( priv->ule_skb, ETH_ALEN ); + } + } else { + if (! priv->ule_bridged) { + skb_push( priv->ule_skb, ETH_HLEN ); + ethh = (struct ethhdr *)priv->ule_skb->data; + memcpy( ethh->h_dest, dev->dev_addr, ETH_ALEN ); + memset( ethh->h_source, 0, ETH_ALEN ); + ethh->h_proto = htons( priv->ule_sndu_type ); + } else { + /* skb is in correct state; nothing to do. */ + } + } + priv->ule_bridged = 0; + + /* Stuff into kernel's protocol stack. */ + priv->ule_skb->protocol = dvb_net_eth_type_trans(priv->ule_skb, dev); + /* If D-bit is set (i.e. destination MAC address not present), + * receive the packet anyhow. */ + /* if (priv->ule_dbit && skb->pkt_type == PACKET_OTHERHOST) + priv->ule_skb->pkt_type = PACKET_HOST; */ + ((struct dvb_net_priv *) dev->priv)->stats.rx_packets++; + ((struct dvb_net_priv *) dev->priv)->stats.rx_bytes += priv->ule_skb->len; + netif_rx(priv->ule_skb); + } + sndu_done: + /* Prepare for next SNDU. */ + reset_ule(priv); + } + + /* More data in current TS (look at the bytes following the CRC32)? */ + if (ts_remain >= 2 && *((unsigned short *)from_where) != 0xFFFF) { + /* Next ULE SNDU starts right there. */ + new_ts = 0; + priv->ule_skb = NULL; + priv->ule_sndu_type_1 = 0; + priv->ule_sndu_len = 0; + // printk(KERN_WARNING "More data in current TS: [%#x %#x %#x %#x]\n", + // *(from_where + 0), *(from_where + 1), + // *(from_where + 2), *(from_where + 3)); + // printk(KERN_WARNING "ts @ %p, stopped @ %p:\n", ts, from_where + 0); + // hexdump(ts, 188); + } else { + new_ts = 1; + ts += TS_SZ; + priv->ts_count++; + if (priv->ule_skb == NULL) { + priv->need_pusi = 1; + priv->ule_sndu_type_1 = 0; + priv->ule_sndu_len = 0; + } + } + } /* for all available TS cells */ +} + +static int dvb_net_ts_callback(const u8 *buffer1, size_t buffer1_len, + const u8 *buffer2, size_t buffer2_len, + struct dmx_ts_feed *feed, enum dmx_success success) +{ + struct net_device *dev = (struct net_device *)feed->priv; + + if (buffer2 != 0) + printk(KERN_WARNING "buffer2 not 0: %p.\n", buffer2); + if (buffer1_len > 32768) + printk(KERN_WARNING "length > 32k: %zu.\n", buffer1_len); + /* printk("TS callback: %u bytes, %u TS cells @ %p.\n", + buffer1_len, buffer1_len / TS_SZ, buffer1); */ + dvb_net_ule(dev, buffer1, buffer1_len); + return 0; +} + + +static void dvb_net_sec(struct net_device *dev, u8 *pkt, int pkt_len) +{ + u8 *eth; + struct sk_buff *skb; + struct net_device_stats *stats = &(((struct dvb_net_priv *) dev->priv)->stats); + + /* note: pkt_len includes a 32bit checksum */ + if (pkt_len < 16) { + printk("%s: IP/MPE packet length = %d too small.\n", + dev->name, pkt_len); + stats->rx_errors++; + stats->rx_length_errors++; + return; + } +/* it seems some ISPs manage to screw up here, so we have to + * relax the error checks... */ +#if 0 + if ((pkt[5] & 0xfd) != 0xc1) { + /* drop scrambled or broken packets */ +#else + if ((pkt[5] & 0x3c) != 0x00) { + /* drop scrambled */ +#endif + stats->rx_errors++; + stats->rx_crc_errors++; + return; + } + if (pkt[5] & 0x02) { + //FIXME: handle LLC/SNAP + stats->rx_dropped++; + return; + } + if (pkt[7]) { + /* FIXME: assemble datagram from multiple sections */ + stats->rx_errors++; + stats->rx_frame_errors++; + return; + } + + /* we have 14 byte ethernet header (ip header follows); + * 12 byte MPE header; 4 byte checksum; + 2 byte alignment + */ + if (!(skb = dev_alloc_skb(pkt_len - 4 - 12 + 14 + 2))) { + //printk(KERN_NOTICE "%s: Memory squeeze, dropping packet.\n", dev->name); + stats->rx_dropped++; + return; + } + skb_reserve(skb, 2); /* longword align L3 header */ + skb->dev = dev; + + /* copy L3 payload */ + eth = (u8 *) skb_put(skb, pkt_len - 12 - 4 + 14); + memcpy(eth + 14, pkt + 12, pkt_len - 12 - 4); + + /* create ethernet header: */ + eth[0]=pkt[0x0b]; + eth[1]=pkt[0x0a]; + eth[2]=pkt[0x09]; + eth[3]=pkt[0x08]; + eth[4]=pkt[0x04]; + eth[5]=pkt[0x03]; + + eth[6]=eth[7]=eth[8]=eth[9]=eth[10]=eth[11]=0; + + eth[12] = 0x08; /* ETH_P_IP */ + eth[13] = 0x00; + + skb->protocol = dvb_net_eth_type_trans(skb, dev); + + stats->rx_packets++; + stats->rx_bytes+=skb->len; + netif_rx(skb); +} + +static int dvb_net_sec_callback(const u8 *buffer1, size_t buffer1_len, + const u8 *buffer2, size_t buffer2_len, + struct dmx_section_filter *filter, + enum dmx_success success) +{ + struct net_device *dev=(struct net_device *) filter->priv; + + /** + * we rely on the DVB API definition where exactly one complete + * section is delivered in buffer1 + */ + dvb_net_sec (dev, (u8*) buffer1, buffer1_len); + return 0; +} + +static int dvb_net_tx(struct sk_buff *skb, struct net_device *dev) +{ + dev_kfree_skb(skb); + return 0; +} + +static u8 mask_normal[6]={0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; +static u8 mask_allmulti[6]={0xff, 0xff, 0xff, 0x00, 0x00, 0x00}; +static u8 mac_allmulti[6]={0x01, 0x00, 0x5e, 0x00, 0x00, 0x00}; +static u8 mask_promisc[6]={0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static int dvb_net_filter_sec_set(struct net_device *dev, + struct dmx_section_filter **secfilter, + u8 *mac, u8 *mac_mask) +{ + struct dvb_net_priv *priv = (struct dvb_net_priv*) dev->priv; + int ret; + + *secfilter=NULL; + ret = priv->secfeed->allocate_filter(priv->secfeed, secfilter); + if (ret<0) { + printk("%s: could not get filter\n", dev->name); + return ret; + } + + (*secfilter)->priv=(void *) dev; + + memset((*secfilter)->filter_value, 0x00, DMX_MAX_FILTER_SIZE); + memset((*secfilter)->filter_mask, 0x00, DMX_MAX_FILTER_SIZE); + memset((*secfilter)->filter_mode, 0xff, DMX_MAX_FILTER_SIZE); + + (*secfilter)->filter_value[0]=0x3e; + (*secfilter)->filter_value[3]=mac[5]; + (*secfilter)->filter_value[4]=mac[4]; + (*secfilter)->filter_value[8]=mac[3]; + (*secfilter)->filter_value[9]=mac[2]; + (*secfilter)->filter_value[10]=mac[1]; + (*secfilter)->filter_value[11]=mac[0]; + + (*secfilter)->filter_mask[0] = 0xff; + (*secfilter)->filter_mask[3] = mac_mask[5]; + (*secfilter)->filter_mask[4] = mac_mask[4]; + (*secfilter)->filter_mask[8] = mac_mask[3]; + (*secfilter)->filter_mask[9] = mac_mask[2]; + (*secfilter)->filter_mask[10] = mac_mask[1]; + (*secfilter)->filter_mask[11]=mac_mask[0]; + + dprintk("%s: filter mac=%02x %02x %02x %02x %02x %02x\n", + dev->name, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + dprintk("%s: filter mask=%02x %02x %02x %02x %02x %02x\n", + dev->name, mac_mask[0], mac_mask[1], mac_mask[2], + mac_mask[3], mac_mask[4], mac_mask[5]); + + return 0; +} + +static int dvb_net_feed_start(struct net_device *dev) +{ + int ret, i; + struct dvb_net_priv *priv = (struct dvb_net_priv*) dev->priv; + struct dmx_demux *demux = priv->demux; + unsigned char *mac = (unsigned char *) dev->dev_addr; + + dprintk("%s: rx_mode %i\n", __FUNCTION__, priv->rx_mode); + if (priv->tsfeed || priv->secfeed || priv->secfilter || priv->multi_secfilter[0]) + printk("%s: BUG %d\n", __FUNCTION__, __LINE__); + + priv->secfeed=NULL; + priv->secfilter=NULL; + priv->tsfeed = NULL; + + if (priv->feedtype == DVB_NET_FEEDTYPE_MPE) { + dprintk("%s: alloc secfeed\n", __FUNCTION__); + ret=demux->allocate_section_feed(demux, &priv->secfeed, + dvb_net_sec_callback); + if (ret<0) { + printk("%s: could not allocate section feed\n", dev->name); + return ret; + } + + ret = priv->secfeed->set(priv->secfeed, priv->pid, 32768, 0, 1); + + if (ret<0) { + printk("%s: could not set section feed\n", dev->name); + priv->demux->release_section_feed(priv->demux, priv->secfeed); + priv->secfeed=NULL; + return ret; + } + + if (priv->rx_mode != RX_MODE_PROMISC) { + dprintk("%s: set secfilter\n", __FUNCTION__); + dvb_net_filter_sec_set(dev, &priv->secfilter, mac, mask_normal); + } + + switch (priv->rx_mode) { + case RX_MODE_MULTI: + for (i = 0; i < priv->multi_num; i++) { + dprintk("%s: set multi_secfilter[%d]\n", __FUNCTION__, i); + dvb_net_filter_sec_set(dev, &priv->multi_secfilter[i], + priv->multi_macs[i], mask_normal); + } + break; + case RX_MODE_ALL_MULTI: + priv->multi_num=1; + dprintk("%s: set multi_secfilter[0]\n", __FUNCTION__); + dvb_net_filter_sec_set(dev, &priv->multi_secfilter[0], + mac_allmulti, mask_allmulti); + break; + case RX_MODE_PROMISC: + priv->multi_num=0; + dprintk("%s: set secfilter\n", __FUNCTION__); + dvb_net_filter_sec_set(dev, &priv->secfilter, mac, mask_promisc); + break; + } + + dprintk("%s: start filtering\n", __FUNCTION__); + priv->secfeed->start_filtering(priv->secfeed); + } else if (priv->feedtype == DVB_NET_FEEDTYPE_ULE) { + struct timespec timeout = { 0, 30000000 }; // 30 msec + + /* we have payloads encapsulated in TS */ + dprintk("%s: alloc tsfeed\n", __FUNCTION__); + ret = demux->allocate_ts_feed(demux, &priv->tsfeed, dvb_net_ts_callback); + if (ret < 0) { + printk("%s: could not allocate ts feed\n", dev->name); + return ret; + } + + /* Set netdevice pointer for ts decaps callback. */ + priv->tsfeed->priv = (void *)dev; + ret = priv->tsfeed->set(priv->tsfeed, priv->pid, + TS_PACKET, DMX_TS_PES_OTHER, + 188 * 100, /* nr. of bytes delivered per callback */ + 32768, /* circular buffer size */ + 0, /* descramble */ + timeout); + + if (ret < 0) { + printk("%s: could not set ts feed\n", dev->name); + priv->demux->release_ts_feed(priv->demux, priv->tsfeed); + priv->tsfeed = NULL; + return ret; + } + + dprintk("%s: start filtering\n", __FUNCTION__); + priv->tsfeed->start_filtering(priv->tsfeed); + } else + return -EINVAL; + + return 0; +} + +static int dvb_net_feed_stop(struct net_device *dev) +{ + struct dvb_net_priv *priv = (struct dvb_net_priv*) dev->priv; + int i; + + dprintk("%s\n", __FUNCTION__); + if (priv->feedtype == DVB_NET_FEEDTYPE_MPE) { + if (priv->secfeed) { + if (priv->secfeed->is_filtering) { + dprintk("%s: stop secfeed\n", __FUNCTION__); + priv->secfeed->stop_filtering(priv->secfeed); + } + + if (priv->secfilter) { + dprintk("%s: release secfilter\n", __FUNCTION__); + priv->secfeed->release_filter(priv->secfeed, + priv->secfilter); + priv->secfilter=NULL; + } + + for (i=0; i<priv->multi_num; i++) { + if (priv->multi_secfilter[i]) { + dprintk("%s: release multi_filter[%d]\n", + __FUNCTION__, i); + priv->secfeed->release_filter(priv->secfeed, + priv->multi_secfilter[i]); + priv->multi_secfilter[i] = NULL; + } + } + + priv->demux->release_section_feed(priv->demux, priv->secfeed); + priv->secfeed = NULL; + } else + printk("%s: no feed to stop\n", dev->name); + } else if (priv->feedtype == DVB_NET_FEEDTYPE_ULE) { + if (priv->tsfeed) { + if (priv->tsfeed->is_filtering) { + dprintk("%s: stop tsfeed\n", __FUNCTION__); + priv->tsfeed->stop_filtering(priv->tsfeed); + } + priv->demux->release_ts_feed(priv->demux, priv->tsfeed); + priv->tsfeed = NULL; + } + else + printk("%s: no ts feed to stop\n", dev->name); + } else + return -EINVAL; + return 0; +} + + +static int dvb_set_mc_filter (struct net_device *dev, struct dev_mc_list *mc) +{ + struct dvb_net_priv *priv = (struct dvb_net_priv*) dev->priv; + + if (priv->multi_num == DVB_NET_MULTICAST_MAX) + return -ENOMEM; + + memcpy(priv->multi_macs[priv->multi_num], mc->dmi_addr, 6); + + priv->multi_num++; + return 0; +} + + +static void wq_set_multicast_list (void *data) +{ + struct net_device *dev = data; + struct dvb_net_priv *priv = (struct dvb_net_priv*) dev->priv; + + dvb_net_feed_stop(dev); + + priv->rx_mode = RX_MODE_UNI; + + if (dev->flags & IFF_PROMISC) { + dprintk("%s: promiscuous mode\n", dev->name); + priv->rx_mode = RX_MODE_PROMISC; + } else if ((dev->flags & IFF_ALLMULTI)) { + dprintk("%s: allmulti mode\n", dev->name); + priv->rx_mode = RX_MODE_ALL_MULTI; + } else if (dev->mc_count) { + int mci; + struct dev_mc_list *mc; + + dprintk("%s: set_mc_list, %d entries\n", + dev->name, dev->mc_count); + + priv->rx_mode = RX_MODE_MULTI; + priv->multi_num = 0; + + for (mci = 0, mc=dev->mc_list; + mci < dev->mc_count; + mc = mc->next, mci++) { + dvb_set_mc_filter(dev, mc); + } + } + + dvb_net_feed_start(dev); +} + + +static void dvb_net_set_multicast_list (struct net_device *dev) +{ + struct dvb_net_priv *priv = (struct dvb_net_priv*) dev->priv; + schedule_work(&priv->set_multicast_list_wq); +} + + +static void wq_restart_net_feed (void *data) +{ + struct net_device *dev = data; + + if (netif_running(dev)) { + dvb_net_feed_stop(dev); + dvb_net_feed_start(dev); + } +} + + +static int dvb_net_set_mac (struct net_device *dev, void *p) +{ + struct dvb_net_priv *priv = (struct dvb_net_priv*) dev->priv; + struct sockaddr *addr=p; + + memcpy(dev->dev_addr, addr->sa_data, dev->addr_len); + + if (netif_running(dev)) + schedule_work(&priv->restart_net_feed_wq); + + return 0; +} + + +static int dvb_net_open(struct net_device *dev) +{ + struct dvb_net_priv *priv = (struct dvb_net_priv*) dev->priv; + + priv->in_use++; + dvb_net_feed_start(dev); + return 0; +} + + +static int dvb_net_stop(struct net_device *dev) +{ + struct dvb_net_priv *priv = (struct dvb_net_priv*) dev->priv; + + priv->in_use--; + return dvb_net_feed_stop(dev); +} + +static struct net_device_stats * dvb_net_get_stats(struct net_device *dev) +{ + return &((struct dvb_net_priv*) dev->priv)->stats; +} + +static void dvb_net_setup(struct net_device *dev) +{ + ether_setup(dev); + + dev->open = dvb_net_open; + dev->stop = dvb_net_stop; + dev->hard_start_xmit = dvb_net_tx; + dev->get_stats = dvb_net_get_stats; + dev->set_multicast_list = dvb_net_set_multicast_list; + dev->set_mac_address = dvb_net_set_mac; + dev->mtu = 4096; + dev->mc_count = 0; + dev->hard_header_cache = NULL; + dev->flags |= IFF_NOARP; +} + +static int get_if(struct dvb_net *dvbnet) +{ + int i; + + for (i=0; i<DVB_NET_DEVICES_MAX; i++) + if (!dvbnet->state[i]) + break; + + if (i == DVB_NET_DEVICES_MAX) + return -1; + + dvbnet->state[i]=1; + return i; +} + +static int dvb_net_add_if(struct dvb_net *dvbnet, u16 pid, u8 feedtype) +{ + struct net_device *net; + struct dvb_net_priv *priv; + int result; + int if_num; + + if (feedtype != DVB_NET_FEEDTYPE_MPE && feedtype != DVB_NET_FEEDTYPE_ULE) + return -EINVAL; + if ((if_num = get_if(dvbnet)) < 0) + return -EINVAL; + + net = alloc_netdev(sizeof(struct dvb_net_priv), "dvb", dvb_net_setup); + if (!net) + return -ENOMEM; + + if (dvbnet->dvbdev->id) + snprintf(net->name, IFNAMSIZ, "dvb%d%u%d", + dvbnet->dvbdev->adapter->num, dvbnet->dvbdev->id, if_num); + else + /* compatibility fix to keep dvb0_0 format */ + snprintf(net->name, IFNAMSIZ, "dvb%d_%d", + dvbnet->dvbdev->adapter->num, if_num); + + net->addr_len = 6; + memcpy(net->dev_addr, dvbnet->dvbdev->adapter->proposed_mac, 6); + + dvbnet->device[if_num] = net; + + priv = net->priv; + priv->demux = dvbnet->demux; + priv->pid = pid; + priv->rx_mode = RX_MODE_UNI; + priv->need_pusi = 1; + priv->tscc = 0; + priv->feedtype = feedtype; + reset_ule(priv); + + INIT_WORK(&priv->set_multicast_list_wq, wq_set_multicast_list, net); + INIT_WORK(&priv->restart_net_feed_wq, wq_restart_net_feed, net); + + net->base_addr = pid; + + if ((result = register_netdev(net)) < 0) { + dvbnet->device[if_num] = NULL; + free_netdev(net); + return result; + } + printk("dvb_net: created network interface %s\n", net->name); + + return if_num; +} + +static int dvb_net_remove_if(struct dvb_net *dvbnet, unsigned int num) +{ + struct net_device *net = dvbnet->device[num]; + struct dvb_net_priv *priv; + + if (!dvbnet->state[num]) + return -EINVAL; + priv = net->priv; + if (priv->in_use) + return -EBUSY; + + dvb_net_stop(net); + flush_scheduled_work(); + printk("dvb_net: removed network interface %s\n", net->name); + unregister_netdev(net); + dvbnet->state[num]=0; + dvbnet->device[num] = NULL; + free_netdev(net); + + return 0; +} + +static int dvb_net_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct dvb_net *dvbnet = (struct dvb_net *) dvbdev->priv; + + if (((file->f_flags&O_ACCMODE)==O_RDONLY)) + return -EPERM; + + switch (cmd) { + case NET_ADD_IF: + { + struct dvb_net_if *dvbnetif=(struct dvb_net_if *)parg; + int result; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (!try_module_get(dvbdev->adapter->module)) + return -EPERM; + + result=dvb_net_add_if(dvbnet, dvbnetif->pid, dvbnetif->feedtype); + if (result<0) { + module_put(dvbdev->adapter->module); + return result; + } + dvbnetif->if_num=result; + break; + } + case NET_GET_IF: + { + struct net_device *netdev; + struct dvb_net_priv *priv_data; + struct dvb_net_if *dvbnetif=(struct dvb_net_if *)parg; + + if (dvbnetif->if_num >= DVB_NET_DEVICES_MAX || + !dvbnet->state[dvbnetif->if_num]) + return -EINVAL; + + netdev = dvbnet->device[dvbnetif->if_num]; + + priv_data=(struct dvb_net_priv*)netdev->priv; + dvbnetif->pid=priv_data->pid; + dvbnetif->feedtype=priv_data->feedtype; + break; + } + case NET_REMOVE_IF: + { + int ret; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if ((unsigned int) parg >= DVB_NET_DEVICES_MAX) + return -EINVAL; + ret = dvb_net_remove_if(dvbnet, (unsigned int) parg); + if (!ret) + module_put(dvbdev->adapter->module); + return ret; + } + + /* binary compatiblity cruft */ + case __NET_ADD_IF_OLD: + { + struct __dvb_net_if_old *dvbnetif=(struct __dvb_net_if_old *)parg; + int result; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (!try_module_get(dvbdev->adapter->module)) + return -EPERM; + + result=dvb_net_add_if(dvbnet, dvbnetif->pid, DVB_NET_FEEDTYPE_MPE); + if (result<0) { + module_put(dvbdev->adapter->module); + return result; + } + dvbnetif->if_num=result; + break; + } + case __NET_GET_IF_OLD: + { + struct net_device *netdev; + struct dvb_net_priv *priv_data; + struct __dvb_net_if_old *dvbnetif=(struct __dvb_net_if_old *)parg; + + if (dvbnetif->if_num >= DVB_NET_DEVICES_MAX || + !dvbnet->state[dvbnetif->if_num]) + return -EINVAL; + + netdev = dvbnet->device[dvbnetif->if_num]; + + priv_data=(struct dvb_net_priv*)netdev->priv; + dvbnetif->pid=priv_data->pid; + break; + } + default: + return -ENOTTY; + } + return 0; +} + +static int dvb_net_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return dvb_usercopy(inode, file, cmd, arg, dvb_net_do_ioctl); +} + +static struct file_operations dvb_net_fops = { + .owner = THIS_MODULE, + .ioctl = dvb_net_ioctl, + .open = dvb_generic_open, + .release = dvb_generic_release, +}; + +static struct dvb_device dvbdev_net = { + .priv = NULL, + .users = 1, + .writers = 1, + .fops = &dvb_net_fops, +}; + + +void dvb_net_release (struct dvb_net *dvbnet) +{ + int i; + + dvb_unregister_device(dvbnet->dvbdev); + + for (i=0; i<DVB_NET_DEVICES_MAX; i++) { + if (!dvbnet->state[i]) + continue; + dvb_net_remove_if(dvbnet, i); + } +} +EXPORT_SYMBOL(dvb_net_release); + + +int dvb_net_init (struct dvb_adapter *adap, struct dvb_net *dvbnet, + struct dmx_demux *dmx) +{ + int i; + + dvbnet->demux = dmx; + + for (i=0; i<DVB_NET_DEVICES_MAX; i++) + dvbnet->state[i] = 0; + + dvb_register_device (adap, &dvbnet->dvbdev, &dvbdev_net, + dvbnet, DVB_DEVICE_NET); + + return 0; +} +EXPORT_SYMBOL(dvb_net_init); diff --git a/drivers/media/dvb/dvb-core/dvb_net.h b/drivers/media/dvb/dvb-core/dvb_net.h new file mode 100644 index 000000000000..f14e4ca38570 --- /dev/null +++ b/drivers/media/dvb/dvb-core/dvb_net.h @@ -0,0 +1,46 @@ +/* + * dvb_net.h + * + * Copyright (C) 2001 Ralph Metzler for convergence integrated media GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * 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 Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef _DVB_NET_H_ +#define _DVB_NET_H_ + +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/inetdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#include "dvbdev.h" + +#define DVB_NET_DEVICES_MAX 10 + +struct dvb_net { + struct dvb_device *dvbdev; + struct net_device *device[DVB_NET_DEVICES_MAX]; + int state[DVB_NET_DEVICES_MAX]; + struct dmx_demux *demux; +}; + + +void dvb_net_release(struct dvb_net *); +int dvb_net_init(struct dvb_adapter *, struct dvb_net *, struct dmx_demux *); + +#endif diff --git a/drivers/media/dvb/dvb-core/dvb_ringbuffer.c b/drivers/media/dvb/dvb-core/dvb_ringbuffer.c new file mode 100644 index 000000000000..fb6d94a69d71 --- /dev/null +++ b/drivers/media/dvb/dvb-core/dvb_ringbuffer.c @@ -0,0 +1,270 @@ +/* + * + * dvb_ringbuffer.c: ring buffer implementation for the dvb driver + * + * Copyright (C) 2003 Oliver Endriss + * Copyright (C) 2004 Andrew de Quincey + * + * based on code originally found in av7110.c & dvb_ci.c: + * Copyright (C) 1999-2003 Ralph Metzler + * & Marcus Metzler for convergence integrated media GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + + +#define __KERNEL_SYSCALLS__ +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <asm/uaccess.h> + +#include "dvb_ringbuffer.h" + +#define PKT_READY 0 +#define PKT_DISPOSED 1 + + +void dvb_ringbuffer_init(struct dvb_ringbuffer *rbuf, void *data, size_t len) +{ + rbuf->pread=rbuf->pwrite=0; + rbuf->data=data; + rbuf->size=len; + + init_waitqueue_head(&rbuf->queue); + + spin_lock_init(&(rbuf->lock)); +} + + + +int dvb_ringbuffer_empty(struct dvb_ringbuffer *rbuf) +{ + return (rbuf->pread==rbuf->pwrite); +} + + + +ssize_t dvb_ringbuffer_free(struct dvb_ringbuffer *rbuf) +{ + ssize_t free; + + free = rbuf->pread - rbuf->pwrite; + if (free <= 0) + free += rbuf->size; + return free-1; +} + + + +ssize_t dvb_ringbuffer_avail(struct dvb_ringbuffer *rbuf) +{ + ssize_t avail; + + avail = rbuf->pwrite - rbuf->pread; + if (avail < 0) + avail += rbuf->size; + return avail; +} + + + +void dvb_ringbuffer_flush(struct dvb_ringbuffer *rbuf) +{ + rbuf->pread = rbuf->pwrite; +} + + + +void dvb_ringbuffer_flush_spinlock_wakeup(struct dvb_ringbuffer *rbuf) +{ + unsigned long flags; + + spin_lock_irqsave(&rbuf->lock, flags); + dvb_ringbuffer_flush(rbuf); + spin_unlock_irqrestore(&rbuf->lock, flags); + + wake_up(&rbuf->queue); +} + + + +ssize_t dvb_ringbuffer_read(struct dvb_ringbuffer *rbuf, u8 *buf, size_t len, int usermem) +{ + size_t todo = len; + size_t split; + + split = (rbuf->pread + len > rbuf->size) ? rbuf->size - rbuf->pread : 0; + if (split > 0) { + if (!usermem) + memcpy(buf, rbuf->data+rbuf->pread, split); + else + if (copy_to_user(buf, rbuf->data+rbuf->pread, split)) + return -EFAULT; + buf += split; + todo -= split; + rbuf->pread = 0; + } + if (!usermem) + memcpy(buf, rbuf->data+rbuf->pread, todo); + else + if (copy_to_user(buf, rbuf->data+rbuf->pread, todo)) + return -EFAULT; + + rbuf->pread = (rbuf->pread + todo) % rbuf->size; + + return len; +} + + + +ssize_t dvb_ringbuffer_write(struct dvb_ringbuffer *rbuf, const u8 *buf, size_t len) +{ + size_t todo = len; + size_t split; + + split = (rbuf->pwrite + len > rbuf->size) ? rbuf->size - rbuf->pwrite : 0; + + if (split > 0) { + memcpy(rbuf->data+rbuf->pwrite, buf, split); + buf += split; + todo -= split; + rbuf->pwrite = 0; + } + memcpy(rbuf->data+rbuf->pwrite, buf, todo); + rbuf->pwrite = (rbuf->pwrite + todo) % rbuf->size; + + return len; +} + +ssize_t dvb_ringbuffer_pkt_write(struct dvb_ringbuffer *rbuf, u8* buf, size_t len) +{ + int status; + ssize_t oldpwrite = rbuf->pwrite; + + DVB_RINGBUFFER_WRITE_BYTE(rbuf, len >> 8); + DVB_RINGBUFFER_WRITE_BYTE(rbuf, len & 0xff); + DVB_RINGBUFFER_WRITE_BYTE(rbuf, PKT_READY); + status = dvb_ringbuffer_write(rbuf, buf, len); + + if (status < 0) rbuf->pwrite = oldpwrite; + return status; +} + +ssize_t dvb_ringbuffer_pkt_read(struct dvb_ringbuffer *rbuf, size_t idx, + int offset, u8* buf, size_t len, int usermem) +{ + size_t todo; + size_t split; + size_t pktlen; + + pktlen = rbuf->data[idx] << 8; + pktlen |= rbuf->data[(idx + 1) % rbuf->size]; + if (offset > pktlen) return -EINVAL; + if ((offset + len) > pktlen) len = pktlen - offset; + + idx = (idx + DVB_RINGBUFFER_PKTHDRSIZE + offset) % rbuf->size; + todo = len; + split = ((idx + len) > rbuf->size) ? rbuf->size - idx : 0; + if (split > 0) { + if (!usermem) + memcpy(buf, rbuf->data+idx, split); + else + if (copy_to_user(buf, rbuf->data+idx, split)) + return -EFAULT; + buf += split; + todo -= split; + idx = 0; + } + if (!usermem) + memcpy(buf, rbuf->data+idx, todo); + else + if (copy_to_user(buf, rbuf->data+idx, todo)) + return -EFAULT; + + return len; +} + +void dvb_ringbuffer_pkt_dispose(struct dvb_ringbuffer *rbuf, size_t idx) +{ + size_t pktlen; + + rbuf->data[(idx + 2) % rbuf->size] = PKT_DISPOSED; + + // clean up disposed packets + while(dvb_ringbuffer_avail(rbuf) > DVB_RINGBUFFER_PKTHDRSIZE) { + if (DVB_RINGBUFFER_PEEK(rbuf, 2) == PKT_DISPOSED) { + pktlen = DVB_RINGBUFFER_PEEK(rbuf, 0) << 8; + pktlen |= DVB_RINGBUFFER_PEEK(rbuf, 1); + DVB_RINGBUFFER_SKIP(rbuf, pktlen + DVB_RINGBUFFER_PKTHDRSIZE); + } else { + // first packet is not disposed, so we stop cleaning now + break; + } + } +} + +ssize_t dvb_ringbuffer_pkt_next(struct dvb_ringbuffer *rbuf, size_t idx, size_t* pktlen) +{ + int consumed; + int curpktlen; + int curpktstatus; + + if (idx == -1) { + idx = rbuf->pread; + } else { + curpktlen = rbuf->data[idx] << 8; + curpktlen |= rbuf->data[(idx + 1) % rbuf->size]; + idx = (idx + curpktlen + DVB_RINGBUFFER_PKTHDRSIZE) % rbuf->size; + } + + consumed = (idx - rbuf->pread) % rbuf->size; + + while((dvb_ringbuffer_avail(rbuf) - consumed) > DVB_RINGBUFFER_PKTHDRSIZE) { + + curpktlen = rbuf->data[idx] << 8; + curpktlen |= rbuf->data[(idx + 1) % rbuf->size]; + curpktstatus = rbuf->data[(idx + 2) % rbuf->size]; + + if (curpktstatus == PKT_READY) { + *pktlen = curpktlen; + return idx; + } + + consumed += curpktlen + DVB_RINGBUFFER_PKTHDRSIZE; + idx = (idx + curpktlen + DVB_RINGBUFFER_PKTHDRSIZE) % rbuf->size; + } + + // no packets available + return -1; +} + + + +EXPORT_SYMBOL(dvb_ringbuffer_init); +EXPORT_SYMBOL(dvb_ringbuffer_empty); +EXPORT_SYMBOL(dvb_ringbuffer_free); +EXPORT_SYMBOL(dvb_ringbuffer_avail); +EXPORT_SYMBOL(dvb_ringbuffer_flush); +EXPORT_SYMBOL(dvb_ringbuffer_flush_spinlock_wakeup); +EXPORT_SYMBOL(dvb_ringbuffer_read); +EXPORT_SYMBOL(dvb_ringbuffer_write); +EXPORT_SYMBOL(dvb_ringbuffer_pkt_write); +EXPORT_SYMBOL(dvb_ringbuffer_pkt_read); +EXPORT_SYMBOL(dvb_ringbuffer_pkt_dispose); +EXPORT_SYMBOL(dvb_ringbuffer_pkt_next); diff --git a/drivers/media/dvb/dvb-core/dvb_ringbuffer.h b/drivers/media/dvb/dvb-core/dvb_ringbuffer.h new file mode 100644 index 000000000000..d18e9c4ba9ea --- /dev/null +++ b/drivers/media/dvb/dvb-core/dvb_ringbuffer.h @@ -0,0 +1,173 @@ +/* + * + * dvb_ringbuffer.h: ring buffer implementation for the dvb driver + * + * Copyright (C) 2003 Oliver Endriss + * Copyright (C) 2004 Andrew de Quincey + * + * based on code originally found in av7110.c & dvb_ci.c: + * Copyright (C) 1999-2003 Ralph Metzler & Marcus Metzler + * for convergence integrated media GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _DVB_RINGBUFFER_H_ +#define _DVB_RINGBUFFER_H_ + +#include <linux/spinlock.h> +#include <linux/wait.h> + +struct dvb_ringbuffer { + u8 *data; + ssize_t size; + ssize_t pread; + ssize_t pwrite; + + wait_queue_head_t queue; + spinlock_t lock; +}; + +#define DVB_RINGBUFFER_PKTHDRSIZE 3 + + +/* +** Notes: +** ------ +** (1) For performance reasons read and write routines don't check buffer sizes +** and/or number of bytes free/available. This has to be done before these +** routines are called. For example: +** +** *** write <buflen> bytes *** +** free = dvb_ringbuffer_free(rbuf); +** if (free >= buflen) +** count = dvb_ringbuffer_write(rbuf, buffer, buflen); +** else +** ... +** +** *** read min. 1000, max. <bufsize> bytes *** +** avail = dvb_ringbuffer_avail(rbuf); +** if (avail >= 1000) +** count = dvb_ringbuffer_read(rbuf, buffer, min(avail, bufsize), 0); +** else +** ... +** +** (2) If there is exactly one reader and one writer, there is no need +** to lock read or write operations. +** Two or more readers must be locked against each other. +** Flushing the buffer counts as a read operation. +** Two or more writers must be locked against each other. +*/ + +/* initialize ring buffer, lock and queue */ +extern void dvb_ringbuffer_init(struct dvb_ringbuffer *rbuf, void *data, size_t len); + +/* test whether buffer is empty */ +extern int dvb_ringbuffer_empty(struct dvb_ringbuffer *rbuf); + +/* return the number of free bytes in the buffer */ +extern ssize_t dvb_ringbuffer_free(struct dvb_ringbuffer *rbuf); + +/* return the number of bytes waiting in the buffer */ +extern ssize_t dvb_ringbuffer_avail(struct dvb_ringbuffer *rbuf); + + +/* read routines & macros */ +/* ---------------------- */ +/* flush buffer */ +extern void dvb_ringbuffer_flush(struct dvb_ringbuffer *rbuf); + +/* flush buffer protected by spinlock and wake-up waiting task(s) */ +extern void dvb_ringbuffer_flush_spinlock_wakeup(struct dvb_ringbuffer *rbuf); + +/* peek at byte <offs> in the buffer */ +#define DVB_RINGBUFFER_PEEK(rbuf,offs) \ + (rbuf)->data[((rbuf)->pread+(offs))%(rbuf)->size] + +/* advance read ptr by <num> bytes */ +#define DVB_RINGBUFFER_SKIP(rbuf,num) \ + (rbuf)->pread=((rbuf)->pread+(num))%(rbuf)->size + +/* +** read <len> bytes from ring buffer into <buf> +** <usermem> specifies whether <buf> resides in user space +** returns number of bytes transferred or -EFAULT +*/ +extern ssize_t dvb_ringbuffer_read(struct dvb_ringbuffer *rbuf, u8 *buf, + size_t len, int usermem); + + +/* write routines & macros */ +/* ----------------------- */ +/* write single byte to ring buffer */ +#define DVB_RINGBUFFER_WRITE_BYTE(rbuf,byte) \ + { (rbuf)->data[(rbuf)->pwrite]=(byte); \ + (rbuf)->pwrite=((rbuf)->pwrite+1)%(rbuf)->size; } +/* +** write <len> bytes to ring buffer +** <usermem> specifies whether <buf> resides in user space +** returns number of bytes transferred or -EFAULT +*/ +extern ssize_t dvb_ringbuffer_write(struct dvb_ringbuffer *rbuf, const u8 *buf, + size_t len); + + +/** + * Write a packet into the ringbuffer. + * + * <rbuf> Ringbuffer to write to. + * <buf> Buffer to write. + * <len> Length of buffer (currently limited to 65535 bytes max). + * returns Number of bytes written, or -EFAULT, -ENOMEM, -EVINAL. + */ +extern ssize_t dvb_ringbuffer_pkt_write(struct dvb_ringbuffer *rbuf, u8* buf, + size_t len); + +/** + * Read from a packet in the ringbuffer. Note: unlike dvb_ringbuffer_read(), this + * does NOT update the read pointer in the ringbuffer. You must use + * dvb_ringbuffer_pkt_dispose() to mark a packet as no longer required. + * + * <rbuf> Ringbuffer concerned. + * <idx> Packet index as returned by dvb_ringbuffer_pkt_next(). + * <offset> Offset into packet to read from. + * <buf> Destination buffer for data. + * <len> Size of destination buffer. + * <usermem> Set to 1 if <buf> is in userspace. + * returns Number of bytes read, or -EFAULT. + */ +extern ssize_t dvb_ringbuffer_pkt_read(struct dvb_ringbuffer *rbuf, size_t idx, + int offset, u8* buf, size_t len, int usermem); + +/** + * Dispose of a packet in the ring buffer. + * + * <rbuf> Ring buffer concerned. + * <idx> Packet index as returned by dvb_ringbuffer_pkt_next(). + */ +extern void dvb_ringbuffer_pkt_dispose(struct dvb_ringbuffer *rbuf, size_t idx); + +/** + * Get the index of the next packet in a ringbuffer. + * + * <rbuf> Ringbuffer concerned. + * <idx> Previous packet index, or -1 to return the first packet index. + * <pktlen> On success, will be updated to contain the length of the packet in bytes. + * returns Packet index (if >=0), or -1 if no packets available. + */ +extern ssize_t dvb_ringbuffer_pkt_next(struct dvb_ringbuffer *rbuf, size_t idx, size_t* pktlen); + + +#endif /* _DVB_RINGBUFFER_H_ */ diff --git a/drivers/media/dvb/dvb-core/dvbdev.c b/drivers/media/dvb/dvb-core/dvbdev.c new file mode 100644 index 000000000000..cf4ffe38fda3 --- /dev/null +++ b/drivers/media/dvb/dvb-core/dvbdev.c @@ -0,0 +1,449 @@ +/* + * dvbdev.c + * + * Copyright (C) 2000 Ralph Metzler <ralph@convergence.de> + * & Marcus Metzler <marcus@convergence.de> + * for convergence integrated media GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * 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 Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/cdev.h> + +#include "dvbdev.h" + +static int dvbdev_debug; + +module_param(dvbdev_debug, int, 0644); +MODULE_PARM_DESC(dvbdev_debug, "Turn on/off device debugging (default:off)."); + +#define dprintk if (dvbdev_debug) printk + +static LIST_HEAD(dvb_adapter_list); +static DECLARE_MUTEX(dvbdev_register_lock); + +static const char * const dnames[] = { + "video", "audio", "sec", "frontend", "demux", "dvr", "ca", + "net", "osd" +}; + +#define DVB_MAX_ADAPTERS 8 +#define DVB_MAX_IDS 4 +#define nums2minor(num,type,id) ((num << 6) | (id << 4) | type) +#define MAX_DVB_MINORS (DVB_MAX_ADAPTERS*64) + +struct class_simple *dvb_class; +EXPORT_SYMBOL(dvb_class); + +static struct dvb_device* dvbdev_find_device (int minor) +{ + struct list_head *entry; + + list_for_each (entry, &dvb_adapter_list) { + struct list_head *entry0; + struct dvb_adapter *adap; + adap = list_entry (entry, struct dvb_adapter, list_head); + list_for_each (entry0, &adap->device_list) { + struct dvb_device *dev; + dev = list_entry (entry0, struct dvb_device, list_head); + if (nums2minor(adap->num, dev->type, dev->id) == minor) + return dev; + } + } + + return NULL; +} + + +static int dvb_device_open(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev; + + dvbdev = dvbdev_find_device (iminor(inode)); + + if (dvbdev && dvbdev->fops) { + int err = 0; + struct file_operations *old_fops; + + file->private_data = dvbdev; + old_fops = file->f_op; + file->f_op = fops_get(dvbdev->fops); + if(file->f_op->open) + err = file->f_op->open(inode,file); + if (err) { + fops_put(file->f_op); + file->f_op = fops_get(old_fops); + } + fops_put(old_fops); + return err; + } + return -ENODEV; +} + + +static struct file_operations dvb_device_fops = +{ + .owner = THIS_MODULE, + .open = dvb_device_open, +}; + +static struct cdev dvb_device_cdev = { + .kobj = {.name = "dvb", }, + .owner = THIS_MODULE, +}; + +int dvb_generic_open(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + + if (!dvbdev) + return -ENODEV; + + if (!dvbdev->users) + return -EBUSY; + + if ((file->f_flags & O_ACCMODE) == O_RDONLY) { + if (!dvbdev->readers) + return -EBUSY; + dvbdev->readers--; + } else { + if (!dvbdev->writers) + return -EBUSY; + dvbdev->writers--; + } + + dvbdev->users--; + return 0; +} +EXPORT_SYMBOL(dvb_generic_open); + + +int dvb_generic_release(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = file->private_data; + + if (!dvbdev) + return -ENODEV; + + if ((file->f_flags & O_ACCMODE) == O_RDONLY) { + dvbdev->readers++; + } else { + dvbdev->writers++; + } + + dvbdev->users++; + return 0; +} +EXPORT_SYMBOL(dvb_generic_release); + + +int dvb_generic_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct dvb_device *dvbdev = file->private_data; + + if (!dvbdev) + return -ENODEV; + + if (!dvbdev->kernel_ioctl) + return -EINVAL; + + return dvb_usercopy (inode, file, cmd, arg, dvbdev->kernel_ioctl); +} +EXPORT_SYMBOL(dvb_generic_ioctl); + + +static int dvbdev_get_free_id (struct dvb_adapter *adap, int type) +{ + u32 id = 0; + + while (id < DVB_MAX_IDS) { + struct list_head *entry; + list_for_each (entry, &adap->device_list) { + struct dvb_device *dev; + dev = list_entry (entry, struct dvb_device, list_head); + if (dev->type == type && dev->id == id) + goto skip; + } + return id; +skip: + id++; + } + return -ENFILE; +} + + +int dvb_register_device(struct dvb_adapter *adap, struct dvb_device **pdvbdev, + const struct dvb_device *template, void *priv, int type) +{ + struct dvb_device *dvbdev; + int id; + + if (down_interruptible (&dvbdev_register_lock)) + return -ERESTARTSYS; + + if ((id = dvbdev_get_free_id (adap, type)) < 0) { + up (&dvbdev_register_lock); + *pdvbdev = NULL; + printk ("%s: could get find free device id...\n", __FUNCTION__); + return -ENFILE; + } + + *pdvbdev = dvbdev = kmalloc(sizeof(struct dvb_device), GFP_KERNEL); + + if (!dvbdev) { + up(&dvbdev_register_lock); + return -ENOMEM; + } + + up (&dvbdev_register_lock); + + memcpy(dvbdev, template, sizeof(struct dvb_device)); + dvbdev->type = type; + dvbdev->id = id; + dvbdev->adapter = adap; + dvbdev->priv = priv; + + dvbdev->fops->owner = adap->module; + + list_add_tail (&dvbdev->list_head, &adap->device_list); + + devfs_mk_cdev(MKDEV(DVB_MAJOR, nums2minor(adap->num, type, id)), + S_IFCHR | S_IRUSR | S_IWUSR, + "dvb/adapter%d/%s%d", adap->num, dnames[type], id); + + class_simple_device_add(dvb_class, MKDEV(DVB_MAJOR, nums2minor(adap->num, type, id)), + NULL, "dvb%d.%s%d", adap->num, dnames[type], id); + + dprintk("DVB: register adapter%d/%s%d @ minor: %i (0x%02x)\n", + adap->num, dnames[type], id, nums2minor(adap->num, type, id), + nums2minor(adap->num, type, id)); + + return 0; +} +EXPORT_SYMBOL(dvb_register_device); + + +void dvb_unregister_device(struct dvb_device *dvbdev) +{ + if (!dvbdev) + return; + + devfs_remove("dvb/adapter%d/%s%d", dvbdev->adapter->num, + dnames[dvbdev->type], dvbdev->id); + + class_simple_device_remove(MKDEV(DVB_MAJOR, nums2minor(dvbdev->adapter->num, + dvbdev->type, dvbdev->id))); + + list_del (&dvbdev->list_head); + kfree (dvbdev); +} +EXPORT_SYMBOL(dvb_unregister_device); + + +static int dvbdev_get_free_adapter_num (void) +{ + int num = 0; + + while (num < DVB_MAX_ADAPTERS) { + struct list_head *entry; + list_for_each (entry, &dvb_adapter_list) { + struct dvb_adapter *adap; + adap = list_entry (entry, struct dvb_adapter, list_head); + if (adap->num == num) + goto skip; + } + return num; +skip: + num++; + } + + return -ENFILE; +} + + +int dvb_register_adapter(struct dvb_adapter **padap, const char *name, struct module *module) +{ + struct dvb_adapter *adap; + int num; + + if (down_interruptible (&dvbdev_register_lock)) + return -ERESTARTSYS; + + if ((num = dvbdev_get_free_adapter_num ()) < 0) { + up (&dvbdev_register_lock); + return -ENFILE; + } + + if (!(*padap = adap = kmalloc(sizeof(struct dvb_adapter), GFP_KERNEL))) { + up(&dvbdev_register_lock); + return -ENOMEM; + } + + memset (adap, 0, sizeof(struct dvb_adapter)); + INIT_LIST_HEAD (&adap->device_list); + + printk ("DVB: registering new adapter (%s).\n", name); + + devfs_mk_dir("dvb/adapter%d", num); + adap->num = num; + adap->name = name; + adap->module = module; + + list_add_tail (&adap->list_head, &dvb_adapter_list); + + up (&dvbdev_register_lock); + + return num; +} +EXPORT_SYMBOL(dvb_register_adapter); + + +int dvb_unregister_adapter(struct dvb_adapter *adap) +{ + devfs_remove("dvb/adapter%d", adap->num); + + if (down_interruptible (&dvbdev_register_lock)) + return -ERESTARTSYS; + list_del (&adap->list_head); + up (&dvbdev_register_lock); + kfree (adap); + return 0; +} +EXPORT_SYMBOL(dvb_unregister_adapter); + +/* if the miracle happens and "generic_usercopy()" is included into + the kernel, then this can vanish. please don't make the mistake and + define this as video_usercopy(). this will introduce a dependecy + to the v4l "videodev.o" module, which is unnecessary for some + cards (ie. the budget dvb-cards don't need the v4l module...) */ +int dvb_usercopy(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg, + int (*func)(struct inode *inode, struct file *file, + unsigned int cmd, void *arg)) +{ + char sbuf[128]; + void *mbuf = NULL; + void *parg = NULL; + int err = -EINVAL; + + /* Copy arguments into temp kernel buffer */ + switch (_IOC_DIR(cmd)) { + case _IOC_NONE: + /* + * For this command, the pointer is actually an integer + * argument. + */ + parg = (void *) arg; + break; + case _IOC_READ: /* some v4l ioctls are marked wrong ... */ + case _IOC_WRITE: + case (_IOC_WRITE | _IOC_READ): + if (_IOC_SIZE(cmd) <= sizeof(sbuf)) { + parg = sbuf; + } else { + /* too big to allocate from stack */ + mbuf = kmalloc(_IOC_SIZE(cmd),GFP_KERNEL); + if (NULL == mbuf) + return -ENOMEM; + parg = mbuf; + } + + err = -EFAULT; + if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd))) + goto out; + break; + } + + /* call driver */ + if ((err = func(inode, file, cmd, parg)) == -ENOIOCTLCMD) + err = -EINVAL; + + if (err < 0) + goto out; + + /* Copy results into user buffer */ + switch (_IOC_DIR(cmd)) + { + case _IOC_READ: + case (_IOC_WRITE | _IOC_READ): + if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd))) + err = -EFAULT; + break; + } + +out: + kfree(mbuf); + return err; +} + +static int __init init_dvbdev(void) +{ + int retval; + dev_t dev = MKDEV(DVB_MAJOR, 0); + + if ((retval = register_chrdev_region(dev, MAX_DVB_MINORS, "DVB")) != 0) { + printk("dvb-core: unable to get major %d\n", DVB_MAJOR); + return retval; + } + + cdev_init(&dvb_device_cdev, &dvb_device_fops); + if ((retval = cdev_add(&dvb_device_cdev, dev, MAX_DVB_MINORS)) != 0) { + printk("dvb-core: unable to get major %d\n", DVB_MAJOR); + goto error; + } + + devfs_mk_dir("dvb"); + + dvb_class = class_simple_create(THIS_MODULE, "dvb"); + if (IS_ERR(dvb_class)) { + retval = PTR_ERR(dvb_class); + goto error; + } + return 0; + +error: + cdev_del(&dvb_device_cdev); + unregister_chrdev_region(dev, MAX_DVB_MINORS); + return retval; +} + + +static void __exit exit_dvbdev(void) +{ + devfs_remove("dvb"); + class_simple_destroy(dvb_class); + cdev_del(&dvb_device_cdev); + unregister_chrdev_region(MKDEV(DVB_MAJOR, 0), MAX_DVB_MINORS); +} + +module_init(init_dvbdev); +module_exit(exit_dvbdev); + +MODULE_DESCRIPTION("DVB Core Driver"); +MODULE_AUTHOR("Marcus Metzler, Ralph Metzler, Holger Waechtler"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/dvb-core/dvbdev.h b/drivers/media/dvb/dvb-core/dvbdev.h new file mode 100644 index 000000000000..184edba3caa7 --- /dev/null +++ b/drivers/media/dvb/dvb-core/dvbdev.h @@ -0,0 +1,104 @@ +/* + * dvbdev.h + * + * Copyright (C) 2000 Ralph Metzler & Marcus Metzler + * for convergence integrated media GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Lesser Public License + * as published by the Free Software Foundation; either version 2.1 + * 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 Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef _DVBDEV_H_ +#define _DVBDEV_H_ + +#include <linux/types.h> +#include <linux/poll.h> +#include <linux/fs.h> +#include <linux/list.h> +#include <linux/devfs_fs_kernel.h> +#include <linux/smp_lock.h> + +#define DVB_MAJOR 212 + +#define DVB_DEVICE_VIDEO 0 +#define DVB_DEVICE_AUDIO 1 +#define DVB_DEVICE_SEC 2 +#define DVB_DEVICE_FRONTEND 3 +#define DVB_DEVICE_DEMUX 4 +#define DVB_DEVICE_DVR 5 +#define DVB_DEVICE_CA 6 +#define DVB_DEVICE_NET 7 +#define DVB_DEVICE_OSD 8 + + +struct dvb_adapter { + int num; + struct list_head list_head; + struct list_head device_list; + const char *name; + u8 proposed_mac [6]; + void* priv; + + struct module *module; +}; + + +struct dvb_device { + struct list_head list_head; + struct file_operations *fops; + struct dvb_adapter *adapter; + int type; + u32 id; + + /* in theory, 'users' can vanish now, + but I don't want to change too much now... */ + int readers; + int writers; + int users; + + /* don't really need those !? -- FIXME: use video_usercopy */ + int (*kernel_ioctl)(struct inode *inode, struct file *file, + unsigned int cmd, void *arg); + + void *priv; +}; + + +extern int dvb_register_adapter (struct dvb_adapter **padap, const char *name, struct module *module); +extern int dvb_unregister_adapter (struct dvb_adapter *adap); + +extern int dvb_register_device (struct dvb_adapter *adap, + struct dvb_device **pdvbdev, + const struct dvb_device *template, + void *priv, + int type); + +extern void dvb_unregister_device (struct dvb_device *dvbdev); + +extern int dvb_generic_open (struct inode *inode, struct file *file); +extern int dvb_generic_release (struct inode *inode, struct file *file); +extern int dvb_generic_ioctl (struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg); + +/* we don't mess with video_usercopy() any more, +we simply define out own dvb_usercopy(), which will hopefully become +generic_usercopy() someday... */ + +extern int dvb_usercopy(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg, + int (*func)(struct inode *inode, struct file *file, + unsigned int cmd, void *arg)); + +#endif /* #ifndef _DVBDEV_H_ */ diff --git a/drivers/media/dvb/frontends/Kconfig b/drivers/media/dvb/frontends/Kconfig new file mode 100644 index 000000000000..0bfd4df17d08 --- /dev/null +++ b/drivers/media/dvb/frontends/Kconfig @@ -0,0 +1,172 @@ +menu "Customise DVB Frontends" + depends on DVB_CORE + +comment "DVB-S (satellite) frontends" + depends on DVB_CORE + +config DVB_STV0299 + tristate "ST STV0299 based" + depends on DVB_CORE + help + A DVB-S tuner module. Say Y when you want to support this frontend. + +config DVB_CX24110 + tristate "Conexant CX24110 based" + depends on DVB_CORE + help + A DVB-S tuner module. Say Y when you want to support this frontend. + +config DVB_TDA8083 + tristate "Philips TDA8083 based" + depends on DVB_CORE + help + A DVB-S tuner module. Say Y when you want to support this frontend. + +config DVB_TDA80XX + tristate "Philips TDA8044 or TDA8083 based" + depends on DVB_CORE + help + A DVB-S tuner module. Say Y when you want to support this frontend. + +config DVB_MT312 + tristate "Zarlink MT312 based" + depends on DVB_CORE + help + A DVB-S tuner module. Say Y when you want to support this frontend. + +config DVB_VES1X93 + tristate "VLSI VES1893 or VES1993 based" + depends on DVB_CORE + help + A DVB-S tuner module. Say Y when you want to support this frontend. + +comment "DVB-T (terrestrial) frontends" + depends on DVB_CORE + +config DVB_SP8870 + tristate "Spase sp8870 based" + depends on DVB_CORE + select FW_LOADER + help + A DVB-T tuner module. Say Y when you want to support this frontend. + + This driver needs external firmware. Please use the command + "<kerneldir>/Documentation/dvb/get_dvb_firmware sp8870" to + download/extract it, and then copy it to /usr/lib/hotplug/firmware. + +config DVB_SP887X + tristate "Spase sp887x based" + depends on DVB_CORE + select FW_LOADER + help + A DVB-T tuner module. Say Y when you want to support this frontend. + + This driver needs external firmware. Please use the command + "<kerneldir>/Documentation/dvb/get_dvb_firmware sp887x" to + download/extract it, and then copy it to /usr/lib/hotplug/firmware. + +config DVB_CX22700 + tristate "Conexant CX22700 based" + depends on DVB_CORE + help + A DVB-T tuner module. Say Y when you want to support this frontend. + +config DVB_CX22702 + tristate "Conexant cx22702 demodulator (OFDM)" + depends on DVB_CORE + help + A DVB-T tuner module. Say Y when you want to support this frontend. + +config DVB_L64781 + tristate "LSI L64781" + depends on DVB_CORE + help + A DVB-T tuner module. Say Y when you want to support this frontend. + +config DVB_TDA1004X + tristate "Philips TDA10045H/TDA10046H based" + depends on DVB_CORE + select FW_LOADER + help + A DVB-T tuner module. Say Y when you want to support this frontend. + + This driver needs external firmware. Please use the commands + "<kerneldir>/Documentation/dvb/get_dvb_firmware tda10045", + "<kerneldir>/Documentation/dvb/get_dvb_firmware tda10046" to + download/extract them, and then copy them to /usr/lib/hotplug/firmware. + +config DVB_NXT6000 + tristate "NxtWave Communications NXT6000 based" + depends on DVB_CORE + help + A DVB-T tuner module. Say Y when you want to support this frontend. + +config DVB_MT352 + tristate "Zarlink MT352 based" + depends on DVB_CORE + help + A DVB-T tuner module. Say Y when you want to support this frontend. + +config DVB_DIB3000MB + tristate "DiBcom 3000M-B" + depends on DVB_CORE + help + A DVB-T tuner module. Designed for mobile usage. Say Y when you want + to support this frontend. + +config DVB_DIB3000MC + tristate "DiBcom 3000P/M-C" + depends on DVB_CORE + help + A DVB-T tuner module. Designed for mobile usage. Say Y when you want + to support this frontend. + +comment "DVB-C (cable) frontends" + depends on DVB_CORE + +config DVB_ATMEL_AT76C651 + tristate "Atmel AT76C651 based" + depends on DVB_CORE + help + A DVB-C tuner module. Say Y when you want to support this frontend. + +config DVB_VES1820 + tristate "VLSI VES1820 based" + depends on DVB_CORE + help + A DVB-C tuner module. Say Y when you want to support this frontend. + +config DVB_TDA10021 + tristate "Philips TDA10021 based" + depends on DVB_CORE + help + A DVB-C tuner module. Say Y when you want to support this frontend. + +config DVB_STV0297 + tristate "ST STV0297 based" + depends on DVB_CORE + help + A DVB-C tuner module. Say Y when you want to support this frontend. + +comment "ATSC (North American/Korean Terresterial DTV) frontends" + depends on DVB_CORE + +config DVB_NXT2002 + tristate "Nxt2002 based" + depends on DVB_CORE + select FW_LOADER + help + An ATSC 8VSB tuner module. Say Y when you want to support this frontend. + +config DVB_OR51132 + tristate "OR51132 based (pcHDTV)" + depends on DVB_CORE + +config DVB_OR51211 + tristate "or51211 based (pcHDTV HD2000 card)" + depends on DVB_CORE + select FW_LOADER + help + An ATSC 8VSB tuner module. Say Y when you want to support this frontend. + +endmenu diff --git a/drivers/media/dvb/frontends/Makefile b/drivers/media/dvb/frontends/Makefile new file mode 100644 index 000000000000..7f8784870eab --- /dev/null +++ b/drivers/media/dvb/frontends/Makefile @@ -0,0 +1,30 @@ +# +# Makefile for the kernel DVB frontend device drivers. +# + +EXTRA_CFLAGS = -Idrivers/media/dvb/dvb-core/ + +obj-$(CONFIG_DVB_CORE) += dvb-pll.o +obj-$(CONFIG_DVB_STV0299) += stv0299.o +obj-$(CONFIG_DVB_SP8870) += sp8870.o +obj-$(CONFIG_DVB_CX22700) += cx22700.o +obj-$(CONFIG_DVB_ATMEL_AT76C651) += at76c651.o +obj-$(CONFIG_DVB_CX24110) += cx24110.o +obj-$(CONFIG_DVB_TDA8083) += tda8083.o +obj-$(CONFIG_DVB_L64781) += l64781.o +obj-$(CONFIG_DVB_DIB3000MB) += dib3000mb.o dib3000-common.o +obj-$(CONFIG_DVB_DIB3000MC) += dib3000mc.o dib3000-common.o +obj-$(CONFIG_DVB_MT312) += mt312.o +obj-$(CONFIG_DVB_VES1820) += ves1820.o +obj-$(CONFIG_DVB_VES1X93) += ves1x93.o +obj-$(CONFIG_DVB_TDA1004X) += tda1004x.o +obj-$(CONFIG_DVB_SP887X) += sp887x.o +obj-$(CONFIG_DVB_NXT6000) += nxt6000.o +obj-$(CONFIG_DVB_MT352) += mt352.o +obj-$(CONFIG_DVB_CX22702) += cx22702.o +obj-$(CONFIG_DVB_TDA80XX) += tda80xx.o +obj-$(CONFIG_DVB_TDA10021) += tda10021.o +obj-$(CONFIG_DVB_STV0297) += stv0297.o +obj-$(CONFIG_DVB_NXT2002) += nxt2002.o +obj-$(CONFIG_DVB_OR51211) += or51211.o +obj-$(CONFIG_DVB_OR51132) += or51132.o diff --git a/drivers/media/dvb/frontends/at76c651.c b/drivers/media/dvb/frontends/at76c651.c new file mode 100644 index 000000000000..ce2eaa1640e8 --- /dev/null +++ b/drivers/media/dvb/frontends/at76c651.c @@ -0,0 +1,450 @@ +/* + * at76c651.c + * + * Atmel DVB-C Frontend Driver (at76c651/tua6010xs) + * + * Copyright (C) 2001 fnbrd <fnbrd@gmx.de> + * & 2002-2004 Andreas Oberritter <obi@linuxtv.org> + * & 2003 Wolfram Joost <dbox2@frokaschwei.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * AT76C651 + * http://www.nalanda.nitc.ac.in/industry/datasheets/atmel/acrobat/doc1293.pdf + * http://www.atmel.com/atmel/acrobat/doc1320.pdf + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/bitops.h> +#include "dvb_frontend.h" +#include "at76c651.h" + + +struct at76c651_state { + + struct i2c_adapter* i2c; + + struct dvb_frontend_ops ops; + + const struct at76c651_config* config; + + struct dvb_frontend frontend; + + /* revision of the chip */ + u8 revision; + + /* last QAM value set */ + u8 qam; +}; + +static int debug; +#define dprintk(args...) \ + do { \ + if (debug) printk(KERN_DEBUG "at76c651: " args); \ + } while (0) + + +#if ! defined(__powerpc__) +static __inline__ int __ilog2(unsigned long x) +{ + int i; + + if (x == 0) + return -1; + + for (i = 0; x != 0; i++) + x >>= 1; + + return i - 1; +} +#endif + +static int at76c651_writereg(struct at76c651_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", + __FUNCTION__, reg, data, ret); + + msleep(10); + + return (ret != 1) ? -EREMOTEIO : 0; +} + +static u8 at76c651_readreg(struct at76c651_state* state, u8 reg) +{ + int ret; + u8 val; + struct i2c_msg msg[] = { + { .addr = state->config->demod_address, .flags = 0, .buf = ®, .len = 1 }, + { .addr = state->config->demod_address, .flags = I2C_M_RD, .buf = &val, .len = 1 } + }; + + ret = i2c_transfer(state->i2c, msg, 2); + + if (ret != 2) + dprintk("%s: readreg error (ret == %i)\n", __FUNCTION__, ret); + + return val; +} + +static int at76c651_reset(struct at76c651_state* state) +{ + return at76c651_writereg(state, 0x07, 0x01); +} + +static void at76c651_disable_interrupts(struct at76c651_state* state) +{ + at76c651_writereg(state, 0x0b, 0x00); +} + +static int at76c651_set_auto_config(struct at76c651_state *state) +{ + /* + * Autoconfig + */ + + at76c651_writereg(state, 0x06, 0x01); + + /* + * Performance optimizations, should be done after autoconfig + */ + + at76c651_writereg(state, 0x10, 0x06); + at76c651_writereg(state, 0x11, ((state->qam == 5) || (state->qam == 7)) ? 0x12 : 0x10); + at76c651_writereg(state, 0x15, 0x28); + at76c651_writereg(state, 0x20, 0x09); + at76c651_writereg(state, 0x24, ((state->qam == 5) || (state->qam == 7)) ? 0xC0 : 0x90); + at76c651_writereg(state, 0x30, 0x90); + if (state->qam == 5) + at76c651_writereg(state, 0x35, 0x2A); + + /* + * Initialize A/D-converter + */ + + if (state->revision == 0x11) { + at76c651_writereg(state, 0x2E, 0x38); + at76c651_writereg(state, 0x2F, 0x13); + } + + at76c651_disable_interrupts(state); + + /* + * Restart operation + */ + + at76c651_reset(state); + + return 0; +} + +static void at76c651_set_bbfreq(struct at76c651_state* state) +{ + at76c651_writereg(state, 0x04, 0x3f); + at76c651_writereg(state, 0x05, 0xee); +} + +static int at76c651_set_symbol_rate(struct at76c651_state* state, u32 symbol_rate) +{ + u8 exponent; + u32 mantissa; + + if (symbol_rate > 9360000) + return -EINVAL; + + /* + * FREF = 57800 kHz + * exponent = 10 + floor (log2(symbol_rate / FREF)) + * mantissa = (symbol_rate / FREF) * (1 << (30 - exponent)) + */ + + exponent = __ilog2((symbol_rate << 4) / 903125); + mantissa = ((symbol_rate / 3125) * (1 << (24 - exponent))) / 289; + + at76c651_writereg(state, 0x00, mantissa >> 13); + at76c651_writereg(state, 0x01, mantissa >> 5); + at76c651_writereg(state, 0x02, (mantissa << 3) | exponent); + + return 0; +} + +static int at76c651_set_qam(struct at76c651_state *state, fe_modulation_t qam) +{ + switch (qam) { + case QPSK: + state->qam = 0x02; + break; + case QAM_16: + state->qam = 0x04; + break; + case QAM_32: + state->qam = 0x05; + break; + case QAM_64: + state->qam = 0x06; + break; + case QAM_128: + state->qam = 0x07; + break; + case QAM_256: + state->qam = 0x08; + break; +#if 0 + case QAM_512: + state->qam = 0x09; + break; + case QAM_1024: + state->qam = 0x0A; + break; +#endif + default: + return -EINVAL; + + } + + return at76c651_writereg(state, 0x03, state->qam); +} + +static int at76c651_set_inversion(struct at76c651_state* state, fe_spectral_inversion_t inversion) +{ + u8 feciqinv = at76c651_readreg(state, 0x60); + + switch (inversion) { + case INVERSION_OFF: + feciqinv |= 0x02; + feciqinv &= 0xFE; + break; + + case INVERSION_ON: + feciqinv |= 0x03; + break; + + case INVERSION_AUTO: + feciqinv &= 0xFC; + break; + + default: + return -EINVAL; + } + + return at76c651_writereg(state, 0x60, feciqinv); +} + +static int at76c651_set_parameters(struct dvb_frontend* fe, + struct dvb_frontend_parameters *p) +{ + int ret; + struct at76c651_state* state = (struct at76c651_state*) fe->demodulator_priv; + + at76c651_writereg(state, 0x0c, 0xc3); + state->config->pll_set(fe, p); + at76c651_writereg(state, 0x0c, 0xc2); + + if ((ret = at76c651_set_symbol_rate(state, p->u.qam.symbol_rate))) + return ret; + + if ((ret = at76c651_set_inversion(state, p->inversion))) + return ret; + + return at76c651_set_auto_config(state); +} + +static int at76c651_set_defaults(struct dvb_frontend* fe) +{ + struct at76c651_state* state = (struct at76c651_state*) fe->demodulator_priv; + + at76c651_set_symbol_rate(state, 6900000); + at76c651_set_qam(state, QAM_64); + at76c651_set_bbfreq(state); + at76c651_set_auto_config(state); + + if (state->config->pll_init) { + at76c651_writereg(state, 0x0c, 0xc3); + state->config->pll_init(fe); + at76c651_writereg(state, 0x0c, 0xc2); + } + + return 0; +} + +static int at76c651_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct at76c651_state* state = (struct at76c651_state*) fe->demodulator_priv; + u8 sync; + + /* + * Bits: FEC, CAR, EQU, TIM, AGC2, AGC1, ADC, PLL (PLL=0) + */ + sync = at76c651_readreg(state, 0x80); + *status = 0; + + if (sync & (0x04 | 0x10)) /* AGC1 || TIM */ + *status |= FE_HAS_SIGNAL; + if (sync & 0x10) /* TIM */ + *status |= FE_HAS_CARRIER; + if (sync & 0x80) /* FEC */ + *status |= FE_HAS_VITERBI; + if (sync & 0x40) /* CAR */ + *status |= FE_HAS_SYNC; + if ((sync & 0xF0) == 0xF0) /* TIM && EQU && CAR && FEC */ + *status |= FE_HAS_LOCK; + + return 0; +} + +static int at76c651_read_ber(struct dvb_frontend* fe, u32* ber) +{ + struct at76c651_state* state = (struct at76c651_state*) fe->demodulator_priv; + + *ber = (at76c651_readreg(state, 0x81) & 0x0F) << 16; + *ber |= at76c651_readreg(state, 0x82) << 8; + *ber |= at76c651_readreg(state, 0x83); + *ber *= 10; + + return 0; +} + +static int at76c651_read_signal_strength(struct dvb_frontend* fe, u16* strength) +{ + struct at76c651_state* state = (struct at76c651_state*) fe->demodulator_priv; + + u8 gain = ~at76c651_readreg(state, 0x91); + *strength = (gain << 8) | gain; + + return 0; +} + +static int at76c651_read_snr(struct dvb_frontend* fe, u16* snr) +{ + struct at76c651_state* state = (struct at76c651_state*) fe->demodulator_priv; + + *snr = 0xFFFF - + ((at76c651_readreg(state, 0x8F) << 8) | + at76c651_readreg(state, 0x90)); + + return 0; +} + +static int at76c651_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + struct at76c651_state* state = (struct at76c651_state*) fe->demodulator_priv; + + *ucblocks = at76c651_readreg(state, 0x82); + + return 0; +} + +static int at76c651_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings *fesettings) +{ + fesettings->min_delay_ms = 50; + fesettings->step_size = 0; + fesettings->max_drift = 0; + return 0; +} + +static void at76c651_release(struct dvb_frontend* fe) +{ + struct at76c651_state* state = (struct at76c651_state*) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops at76c651_ops; + +struct dvb_frontend* at76c651_attach(const struct at76c651_config* config, + struct i2c_adapter* i2c) +{ + struct at76c651_state* state = NULL; + + /* allocate memory for the internal state */ + state = (struct at76c651_state*) kmalloc(sizeof(struct at76c651_state), GFP_KERNEL); + if (state == NULL) goto error; + + /* setup the state */ + state->config = config; + state->qam = 0; + + /* check if the demod is there */ + if (at76c651_readreg(state, 0x0e) != 0x65) goto error; + + /* finalise state setup */ + state->i2c = i2c; + state->revision = at76c651_readreg(state, 0x0f) & 0xfe; + memcpy(&state->ops, &at76c651_ops, sizeof(struct dvb_frontend_ops)); + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops at76c651_ops = { + + .info = { + .name = "Atmel AT76C651B DVB-C", + .type = FE_QAM, + .frequency_min = 48250000, + .frequency_max = 863250000, + .frequency_stepsize = 62500, + /*.frequency_tolerance = */ /* FIXME: 12% of SR */ + .symbol_rate_min = 0, /* FIXME */ + .symbol_rate_max = 9360000, /* FIXME */ + .symbol_rate_tolerance = 4000, + .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_8_9 | FE_CAN_FEC_AUTO | + FE_CAN_QAM_16 | FE_CAN_QAM_32 | FE_CAN_QAM_64 | FE_CAN_QAM_128 | + FE_CAN_MUTE_TS | FE_CAN_QAM_256 | FE_CAN_RECOVER + }, + + .release = at76c651_release, + + .init = at76c651_set_defaults, + + .set_frontend = at76c651_set_parameters, + .get_tune_settings = at76c651_get_tune_settings, + + .read_status = at76c651_read_status, + .read_ber = at76c651_read_ber, + .read_signal_strength = at76c651_read_signal_strength, + .read_snr = at76c651_read_snr, + .read_ucblocks = at76c651_read_ucblocks, +}; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("Atmel AT76C651 DVB-C Demodulator Driver"); +MODULE_AUTHOR("Andreas Oberritter <obi@linuxtv.org>"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(at76c651_attach); diff --git a/drivers/media/dvb/frontends/at76c651.h b/drivers/media/dvb/frontends/at76c651.h new file mode 100644 index 000000000000..34054df93608 --- /dev/null +++ b/drivers/media/dvb/frontends/at76c651.h @@ -0,0 +1,47 @@ +/* + * at76c651.c + * + * Atmel DVB-C Frontend Driver (at76c651) + * + * Copyright (C) 2001 fnbrd <fnbrd@gmx.de> + * & 2002-2004 Andreas Oberritter <obi@linuxtv.org> + * & 2003 Wolfram Joost <dbox2@frokaschwei.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * AT76C651 + * http://www.nalanda.nitc.ac.in/industry/datasheets/atmel/acrobat/doc1293.pdf + * http://www.atmel.com/atmel/acrobat/doc1320.pdf + */ + +#ifndef AT76C651_H +#define AT76C651_H + +#include <linux/dvb/frontend.h> + +struct at76c651_config +{ + /* the demodulator's i2c address */ + u8 demod_address; + + /* PLL maintenance */ + int (*pll_init)(struct dvb_frontend* fe); + int (*pll_set)(struct dvb_frontend* fe, struct dvb_frontend_parameters* params); +}; + +extern struct dvb_frontend* at76c651_attach(const struct at76c651_config* config, + struct i2c_adapter* i2c); + +#endif // AT76C651_H diff --git a/drivers/media/dvb/frontends/cx22700.c b/drivers/media/dvb/frontends/cx22700.c new file mode 100644 index 000000000000..a212279042b8 --- /dev/null +++ b/drivers/media/dvb/frontends/cx22700.c @@ -0,0 +1,435 @@ +/* + Conexant cx22700 DVB OFDM demodulator driver + + Copyright (C) 2001-2002 Convergence Integrated Media GmbH + Holger Waechtler <holger@convergence.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/string.h> +#include <linux/slab.h> +#include "dvb_frontend.h" +#include "cx22700.h" + + +struct cx22700_state { + + struct i2c_adapter* i2c; + + struct dvb_frontend_ops ops; + + const struct cx22700_config* config; + + struct dvb_frontend frontend; +}; + + +static int debug; +#define dprintk(args...) \ + do { \ + if (debug) printk(KERN_DEBUG "cx22700: " args); \ + } while (0) + +static u8 init_tab [] = { + 0x04, 0x10, + 0x05, 0x09, + 0x06, 0x00, + 0x08, 0x04, + 0x09, 0x00, + 0x0a, 0x01, + 0x15, 0x40, + 0x16, 0x10, + 0x17, 0x87, + 0x18, 0x17, + 0x1a, 0x10, + 0x25, 0x04, + 0x2e, 0x00, + 0x39, 0x00, + 0x3a, 0x04, + 0x45, 0x08, + 0x46, 0x02, + 0x47, 0x05, +}; + + +static int cx22700_writereg (struct cx22700_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 }; + + dprintk ("%s\n", __FUNCTION__); + + ret = i2c_transfer (state->i2c, &msg, 1); + + if (ret != 1) + printk("%s: writereg error (reg == 0x%02x, val == 0x%02x, ret == %i)\n", + __FUNCTION__, reg, data, ret); + + return (ret != 1) ? -1 : 0; +} + +static int cx22700_readreg (struct cx22700_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 } }; + + dprintk ("%s\n", __FUNCTION__); + + ret = i2c_transfer (state->i2c, msg, 2); + + if (ret != 2) return -EIO; + + return b1[0]; +} + +static int cx22700_set_inversion (struct cx22700_state* state, int inversion) +{ + u8 val; + + dprintk ("%s\n", __FUNCTION__); + + switch (inversion) { + case INVERSION_AUTO: + return -EOPNOTSUPP; + case INVERSION_ON: + val = cx22700_readreg (state, 0x09); + return cx22700_writereg (state, 0x09, val | 0x01); + case INVERSION_OFF: + val = cx22700_readreg (state, 0x09); + return cx22700_writereg (state, 0x09, val & 0xfe); + default: + return -EINVAL; + } +} + +static int cx22700_set_tps (struct cx22700_state *state, struct dvb_ofdm_parameters *p) +{ + static const u8 qam_tab [4] = { 0, 1, 0, 2 }; + static const u8 fec_tab [6] = { 0, 1, 2, 0, 3, 4 }; + u8 val; + + dprintk ("%s\n", __FUNCTION__); + + if (p->code_rate_HP < FEC_1_2 || p->code_rate_HP > FEC_7_8) + return -EINVAL; + + if (p->code_rate_LP < FEC_1_2 || p->code_rate_LP > FEC_7_8) + + if (p->code_rate_HP == FEC_4_5 || p->code_rate_LP == FEC_4_5) + return -EINVAL; + + if (p->guard_interval < GUARD_INTERVAL_1_32 || + p->guard_interval > GUARD_INTERVAL_1_4) + return -EINVAL; + + if (p->transmission_mode != TRANSMISSION_MODE_2K && + p->transmission_mode != TRANSMISSION_MODE_8K) + return -EINVAL; + + if (p->constellation != QPSK && + p->constellation != QAM_16 && + p->constellation != QAM_64) + return -EINVAL; + + if (p->hierarchy_information < HIERARCHY_NONE || + p->hierarchy_information > HIERARCHY_4) + return -EINVAL; + + if (p->bandwidth < BANDWIDTH_8_MHZ && p->bandwidth > BANDWIDTH_6_MHZ) + return -EINVAL; + + if (p->bandwidth == BANDWIDTH_7_MHZ) + cx22700_writereg (state, 0x09, cx22700_readreg (state, 0x09 | 0x10)); + else + cx22700_writereg (state, 0x09, cx22700_readreg (state, 0x09 & ~0x10)); + + val = qam_tab[p->constellation - QPSK]; + val |= p->hierarchy_information - HIERARCHY_NONE; + + cx22700_writereg (state, 0x04, val); + + val = fec_tab[p->code_rate_HP - FEC_1_2] << 3; + val |= fec_tab[p->code_rate_LP - FEC_1_2]; + + cx22700_writereg (state, 0x05, val); + + val = (p->guard_interval - GUARD_INTERVAL_1_32) << 2; + val |= p->transmission_mode - TRANSMISSION_MODE_2K; + + cx22700_writereg (state, 0x06, val); + + cx22700_writereg (state, 0x08, 0x04 | 0x02); /* use user tps parameters */ + cx22700_writereg (state, 0x08, 0x04); /* restart aquisition */ + + return 0; +} + +static int cx22700_get_tps (struct cx22700_state* state, struct dvb_ofdm_parameters *p) +{ + static const fe_modulation_t qam_tab [3] = { QPSK, QAM_16, QAM_64 }; + static const fe_code_rate_t fec_tab [5] = { FEC_1_2, FEC_2_3, FEC_3_4, + FEC_5_6, FEC_7_8 }; + u8 val; + + dprintk ("%s\n", __FUNCTION__); + + if (!(cx22700_readreg(state, 0x07) & 0x20)) /* tps valid? */ + return -EAGAIN; + + val = cx22700_readreg (state, 0x01); + + if ((val & 0x7) > 4) + p->hierarchy_information = HIERARCHY_AUTO; + else + p->hierarchy_information = HIERARCHY_NONE + (val & 0x7); + + if (((val >> 3) & 0x3) > 2) + p->constellation = QAM_AUTO; + else + p->constellation = qam_tab[(val >> 3) & 0x3]; + + val = cx22700_readreg (state, 0x02); + + if (((val >> 3) & 0x07) > 4) + p->code_rate_HP = FEC_AUTO; + else + p->code_rate_HP = fec_tab[(val >> 3) & 0x07]; + + if ((val & 0x07) > 4) + p->code_rate_LP = FEC_AUTO; + else + p->code_rate_LP = fec_tab[val & 0x07]; + + val = cx22700_readreg (state, 0x03); + + p->guard_interval = GUARD_INTERVAL_1_32 + ((val >> 6) & 0x3); + p->transmission_mode = TRANSMISSION_MODE_2K + ((val >> 5) & 0x1); + + return 0; +} + +static int cx22700_init (struct dvb_frontend* fe) + +{ struct cx22700_state* state = (struct cx22700_state*) fe->demodulator_priv; + int i; + + dprintk("cx22700_init: init chip\n"); + + cx22700_writereg (state, 0x00, 0x02); /* soft reset */ + cx22700_writereg (state, 0x00, 0x00); + + msleep(10); + + for (i=0; i<sizeof(init_tab); i+=2) + cx22700_writereg (state, init_tab[i], init_tab[i+1]); + + cx22700_writereg (state, 0x00, 0x01); + + if (state->config->pll_init) { + cx22700_writereg (state, 0x0a, 0x00); /* open i2c bus switch */ + state->config->pll_init(fe); + cx22700_writereg (state, 0x0a, 0x01); /* close i2c bus switch */ + } + + return 0; +} + +static int cx22700_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct cx22700_state* state = (struct cx22700_state*) fe->demodulator_priv; + + u16 rs_ber = (cx22700_readreg (state, 0x0d) << 9) + | (cx22700_readreg (state, 0x0e) << 1); + u8 sync = cx22700_readreg (state, 0x07); + + *status = 0; + + if (rs_ber < 0xff00) + *status |= FE_HAS_SIGNAL; + + if (sync & 0x20) + *status |= FE_HAS_CARRIER; + + if (sync & 0x10) + *status |= FE_HAS_VITERBI; + + if (sync & 0x10) + *status |= FE_HAS_SYNC; + + if (*status == 0x0f) + *status |= FE_HAS_LOCK; + + return 0; +} + +static int cx22700_read_ber(struct dvb_frontend* fe, u32* ber) +{ + struct cx22700_state* state = (struct cx22700_state*) fe->demodulator_priv; + + *ber = cx22700_readreg (state, 0x0c) & 0x7f; + cx22700_writereg (state, 0x0c, 0x00); + + return 0; +} + +static int cx22700_read_signal_strength(struct dvb_frontend* fe, u16* signal_strength) +{ + struct cx22700_state* state = (struct cx22700_state*) fe->demodulator_priv; + + u16 rs_ber = (cx22700_readreg (state, 0x0d) << 9) + | (cx22700_readreg (state, 0x0e) << 1); + *signal_strength = ~rs_ber; + + return 0; +} + +static int cx22700_read_snr(struct dvb_frontend* fe, u16* snr) +{ + struct cx22700_state* state = (struct cx22700_state*) fe->demodulator_priv; + + u16 rs_ber = (cx22700_readreg (state, 0x0d) << 9) + | (cx22700_readreg (state, 0x0e) << 1); + *snr = ~rs_ber; + + return 0; +} + +static int cx22700_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + struct cx22700_state* state = (struct cx22700_state*) fe->demodulator_priv; + + *ucblocks = cx22700_readreg (state, 0x0f); + cx22700_writereg (state, 0x0f, 0x00); + + return 0; +} + +static int cx22700_set_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + struct cx22700_state* state = (struct cx22700_state*) fe->demodulator_priv; + + cx22700_writereg (state, 0x00, 0x02); /* XXX CHECKME: soft reset*/ + cx22700_writereg (state, 0x00, 0x00); + + cx22700_writereg (state, 0x0a, 0x00); /* open i2c bus switch */ + state->config->pll_set(fe, p); + cx22700_writereg (state, 0x0a, 0x01); /* close i2c bus switch */ + cx22700_set_inversion (state, p->inversion); + cx22700_set_tps (state, &p->u.ofdm); + cx22700_writereg (state, 0x37, 0x01); /* PAL loop filter off */ + cx22700_writereg (state, 0x00, 0x01); /* restart acquire */ + + return 0; +} + +static int cx22700_get_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + struct cx22700_state* state = (struct cx22700_state*) fe->demodulator_priv; + u8 reg09 = cx22700_readreg (state, 0x09); + + p->inversion = reg09 & 0x1 ? INVERSION_ON : INVERSION_OFF; + return cx22700_get_tps (state, &p->u.ofdm); +} + +static int cx22700_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* fesettings) +{ + fesettings->min_delay_ms = 150; + fesettings->step_size = 166667; + fesettings->max_drift = 166667*2; + return 0; +} + +static void cx22700_release(struct dvb_frontend* fe) +{ + struct cx22700_state* state = (struct cx22700_state*) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops cx22700_ops; + +struct dvb_frontend* cx22700_attach(const struct cx22700_config* config, + struct i2c_adapter* i2c) +{ + struct cx22700_state* state = NULL; + + /* allocate memory for the internal state */ + state = (struct cx22700_state*) kmalloc(sizeof(struct cx22700_state), GFP_KERNEL); + if (state == NULL) goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + memcpy(&state->ops, &cx22700_ops, sizeof(struct dvb_frontend_ops)); + + /* check if the demod is there */ + if (cx22700_readreg(state, 0x07) < 0) goto error; + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops cx22700_ops = { + + .info = { + .name = "Conexant CX22700 DVB-T", + .type = FE_OFDM, + .frequency_min = 470000000, + .frequency_max = 860000000, + .frequency_stepsize = 166667, + .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_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | + FE_CAN_RECOVER + }, + + .release = cx22700_release, + + .init = cx22700_init, + + .set_frontend = cx22700_set_frontend, + .get_frontend = cx22700_get_frontend, + .get_tune_settings = cx22700_get_tune_settings, + + .read_status = cx22700_read_status, + .read_ber = cx22700_read_ber, + .read_signal_strength = cx22700_read_signal_strength, + .read_snr = cx22700_read_snr, + .read_ucblocks = cx22700_read_ucblocks, +}; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("Conexant CX22700 DVB-T Demodulator driver"); +MODULE_AUTHOR("Holger Waechtler"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(cx22700_attach); diff --git a/drivers/media/dvb/frontends/cx22700.h b/drivers/media/dvb/frontends/cx22700.h new file mode 100644 index 000000000000..c9145b45874b --- /dev/null +++ b/drivers/media/dvb/frontends/cx22700.h @@ -0,0 +1,41 @@ +/* + Conexant CX22700 DVB OFDM demodulator driver + + Copyright (C) 2001-2002 Convergence Integrated Media GmbH + Holger Waechtler <holger@convergence.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef CX22700_H +#define CX22700_H + +#include <linux/dvb/frontend.h> + +struct cx22700_config +{ + /* the demodulator's i2c address */ + u8 demod_address; + + /* PLL maintenance */ + int (*pll_init)(struct dvb_frontend* fe); + int (*pll_set)(struct dvb_frontend* fe, struct dvb_frontend_parameters* params); +}; + +extern struct dvb_frontend* cx22700_attach(const struct cx22700_config* config, + struct i2c_adapter* i2c); + +#endif // CX22700_H diff --git a/drivers/media/dvb/frontends/cx22702.c b/drivers/media/dvb/frontends/cx22702.c new file mode 100644 index 000000000000..1930b513eefa --- /dev/null +++ b/drivers/media/dvb/frontends/cx22702.c @@ -0,0 +1,519 @@ +/* + Conexant 22702 DVB OFDM demodulator driver + + based on: + Alps TDMB7 DVB OFDM demodulator driver + + Copyright (C) 2001-2002 Convergence Integrated Media GmbH + Holger Waechtler <holger@convergence.de> + + Copyright (C) 2004 Steven Toth <steve@toth.demon.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "dvb_frontend.h" +#include "cx22702.h" + + +struct cx22702_state { + + struct i2c_adapter* i2c; + + struct dvb_frontend_ops ops; + + /* configuration settings */ + const struct cx22702_config* config; + + struct dvb_frontend frontend; + + /* previous uncorrected block counter */ + u8 prevUCBlocks; +}; + +static int debug = 0; +#define dprintk if (debug) printk + +/* Register values to initialise the demod */ +static u8 init_tab [] = { + 0x00, 0x00, /* Stop aquisition */ + 0x0B, 0x06, + 0x09, 0x01, + 0x0D, 0x41, + 0x16, 0x32, + 0x20, 0x0A, + 0x21, 0x17, + 0x24, 0x3e, + 0x26, 0xff, + 0x27, 0x10, + 0x28, 0x00, + 0x29, 0x00, + 0x2a, 0x10, + 0x2b, 0x00, + 0x2c, 0x10, + 0x2d, 0x00, + 0x48, 0xd4, + 0x49, 0x56, + 0x6b, 0x1e, + 0xc8, 0x02, + 0xf8, 0x02, + 0xf9, 0x00, + 0xfa, 0x00, + 0xfb, 0x00, + 0xfc, 0x00, + 0xfd, 0x00, +}; + +static int cx22702_writereg (struct cx22702_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) + printk("%s: writereg error (reg == 0x%02x, val == 0x%02x, ret == %i)\n", + __FUNCTION__, reg, data, ret); + + return (ret != 1) ? -1 : 0; +} + +static u8 cx22702_readreg (struct cx22702_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: readreg error (ret == %i)\n", __FUNCTION__, ret); + + return b1[0]; +} + +static int cx22702_set_inversion (struct cx22702_state *state, int inversion) +{ + u8 val; + + switch (inversion) { + + case INVERSION_AUTO: + return -EOPNOTSUPP; + + case INVERSION_ON: + val = cx22702_readreg (state, 0x0C); + return cx22702_writereg (state, 0x0C, val | 0x01); + + case INVERSION_OFF: + val = cx22702_readreg (state, 0x0C); + return cx22702_writereg (state, 0x0C, val & 0xfe); + + default: + return -EINVAL; + + } + +} + +/* Retrieve the demod settings */ +static int cx22702_get_tps (struct cx22702_state *state, struct dvb_ofdm_parameters *p) +{ + u8 val; + + /* Make sure the TPS regs are valid */ + if (!(cx22702_readreg(state, 0x0A) & 0x20)) + return -EAGAIN; + + val = cx22702_readreg (state, 0x01); + switch( (val&0x18)>>3) { + case 0: p->constellation = QPSK; break; + case 1: p->constellation = QAM_16; break; + case 2: p->constellation = QAM_64; break; + } + switch( val&0x07 ) { + case 0: p->hierarchy_information = HIERARCHY_NONE; break; + case 1: p->hierarchy_information = HIERARCHY_1; break; + case 2: p->hierarchy_information = HIERARCHY_2; break; + case 3: p->hierarchy_information = HIERARCHY_4; break; + } + + + val = cx22702_readreg (state, 0x02); + switch( (val&0x38)>>3 ) { + case 0: p->code_rate_HP = FEC_1_2; break; + case 1: p->code_rate_HP = FEC_2_3; break; + case 2: p->code_rate_HP = FEC_3_4; break; + case 3: p->code_rate_HP = FEC_5_6; break; + case 4: p->code_rate_HP = FEC_7_8; break; + } + switch( val&0x07 ) { + case 0: p->code_rate_LP = FEC_1_2; break; + case 1: p->code_rate_LP = FEC_2_3; break; + case 2: p->code_rate_LP = FEC_3_4; break; + case 3: p->code_rate_LP = FEC_5_6; break; + case 4: p->code_rate_LP = FEC_7_8; break; + } + + + val = cx22702_readreg (state, 0x03); + switch( (val&0x0c)>>2 ) { + case 0: p->guard_interval = GUARD_INTERVAL_1_32; break; + case 1: p->guard_interval = GUARD_INTERVAL_1_16; break; + case 2: p->guard_interval = GUARD_INTERVAL_1_8; break; + case 3: p->guard_interval = GUARD_INTERVAL_1_4; break; + } + switch( val&0x03 ) { + case 0: p->transmission_mode = TRANSMISSION_MODE_2K; break; + case 1: p->transmission_mode = TRANSMISSION_MODE_8K; break; + } + + return 0; +} + +/* Talk to the demod, set the FEC, GUARD, QAM settings etc */ +static int cx22702_set_tps (struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + u8 val; + struct cx22702_state* state = (struct cx22702_state*) fe->demodulator_priv; + + /* set PLL */ + cx22702_writereg (state, 0x0D, cx22702_readreg(state,0x0D) &0xfe); + state->config->pll_set(fe, p); + cx22702_writereg (state, 0x0D, cx22702_readreg(state,0x0D) | 1); + + /* set inversion */ + cx22702_set_inversion (state, p->inversion); + + /* set bandwidth */ + switch(p->u.ofdm.bandwidth) { + case BANDWIDTH_6_MHZ: + cx22702_writereg(state, 0x0C, (cx22702_readreg(state, 0x0C) & 0xcf) | 0x20 ); + break; + case BANDWIDTH_7_MHZ: + cx22702_writereg(state, 0x0C, (cx22702_readreg(state, 0x0C) & 0xcf) | 0x10 ); + break; + case BANDWIDTH_8_MHZ: + cx22702_writereg(state, 0x0C, cx22702_readreg(state, 0x0C) &0xcf ); + break; + default: + dprintk ("%s: invalid bandwidth\n",__FUNCTION__); + return -EINVAL; + } + + + p->u.ofdm.code_rate_LP = FEC_AUTO; //temp hack as manual not working + + /* use auto configuration? */ + if((p->u.ofdm.hierarchy_information==HIERARCHY_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.guard_interval==GUARD_INTERVAL_AUTO) || + (p->u.ofdm.transmission_mode==TRANSMISSION_MODE_AUTO) ) { + + /* TPS Source - use hardware driven values */ + cx22702_writereg(state, 0x06, 0x10); + cx22702_writereg(state, 0x07, 0x9); + cx22702_writereg(state, 0x08, 0xC1); + cx22702_writereg(state, 0x0B, cx22702_readreg(state, 0x0B) & 0xfc ); + cx22702_writereg(state, 0x0C, (cx22702_readreg(state, 0x0C) & 0xBF) | 0x40 ); + cx22702_writereg(state, 0x00, 0x01); /* Begin aquisition */ + printk("%s: Autodetecting\n",__FUNCTION__); + return 0; + } + + /* manually programmed values */ + val=0; + switch(p->u.ofdm.constellation) { + case QPSK: val = (val&0xe7); break; + case QAM_16: val = (val&0xe7)|0x08; break; + case QAM_64: val = (val&0xe7)|0x10; break; + default: + dprintk ("%s: invalid constellation\n",__FUNCTION__); + return -EINVAL; + } + switch(p->u.ofdm.hierarchy_information) { + case HIERARCHY_NONE: val = (val&0xf8); break; + case HIERARCHY_1: val = (val&0xf8)|1; break; + case HIERARCHY_2: val = (val&0xf8)|2; break; + case HIERARCHY_4: val = (val&0xf8)|3; break; + default: + dprintk ("%s: invalid hierarchy\n",__FUNCTION__); + return -EINVAL; + } + cx22702_writereg (state, 0x06, val); + + val=0; + switch(p->u.ofdm.code_rate_HP) { + case FEC_NONE: + case FEC_1_2: val = (val&0xc7); break; + case FEC_2_3: val = (val&0xc7)|0x08; break; + case FEC_3_4: val = (val&0xc7)|0x10; break; + case FEC_5_6: val = (val&0xc7)|0x18; break; + case FEC_7_8: val = (val&0xc7)|0x20; break; + default: + dprintk ("%s: invalid code_rate_HP\n",__FUNCTION__); + return -EINVAL; + } + switch(p->u.ofdm.code_rate_LP) { + case FEC_NONE: + case FEC_1_2: val = (val&0xf8); break; + case FEC_2_3: val = (val&0xf8)|1; break; + case FEC_3_4: val = (val&0xf8)|2; break; + case FEC_5_6: val = (val&0xf8)|3; break; + case FEC_7_8: val = (val&0xf8)|4; break; + default: + dprintk ("%s: invalid code_rate_LP\n",__FUNCTION__); + return -EINVAL; + } + cx22702_writereg (state, 0x07, val); + + val=0; + switch(p->u.ofdm.guard_interval) { + case GUARD_INTERVAL_1_32: val = (val&0xf3); break; + case GUARD_INTERVAL_1_16: val = (val&0xf3)|0x04; break; + case GUARD_INTERVAL_1_8: val = (val&0xf3)|0x08; break; + case GUARD_INTERVAL_1_4: val = (val&0xf3)|0x0c; break; + default: + dprintk ("%s: invalid guard_interval\n",__FUNCTION__); + return -EINVAL; + } + switch(p->u.ofdm.transmission_mode) { + case TRANSMISSION_MODE_2K: val = (val&0xfc); break; + case TRANSMISSION_MODE_8K: val = (val&0xfc)|1; break; + default: + dprintk ("%s: invalid transmission_mode\n",__FUNCTION__); + return -EINVAL; + } + cx22702_writereg(state, 0x08, val); + cx22702_writereg(state, 0x0B, (cx22702_readreg(state, 0x0B) & 0xfc) | 0x02 ); + cx22702_writereg(state, 0x0C, (cx22702_readreg(state, 0x0C) & 0xBF) | 0x40 ); + + /* Begin channel aquisition */ + cx22702_writereg(state, 0x00, 0x01); + + return 0; +} + +/* Reset the demod hardware and reset all of the configuration registers + to a default state. */ +static int cx22702_init (struct dvb_frontend* fe) +{ + int i; + struct cx22702_state* state = (struct cx22702_state*) fe->demodulator_priv; + + cx22702_writereg (state, 0x00, 0x02); + + msleep(10); + + for (i=0; i<sizeof(init_tab); i+=2) + cx22702_writereg (state, init_tab[i], init_tab[i+1]); + + + /* init PLL */ + if (state->config->pll_init) { + cx22702_writereg (state, 0x0D, cx22702_readreg(state,0x0D) &0xfe); + state->config->pll_init(fe); + cx22702_writereg (state, 0x0D, cx22702_readreg(state,0x0D) | 1); + } + + return 0; +} + +static int cx22702_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct cx22702_state* state = (struct cx22702_state*) fe->demodulator_priv; + u8 reg0A; + u8 reg23; + + *status = 0; + + reg0A = cx22702_readreg (state, 0x0A); + reg23 = cx22702_readreg (state, 0x23); + + dprintk ("%s: status demod=0x%02x agc=0x%02x\n" + ,__FUNCTION__,reg0A,reg23); + + if(reg0A & 0x10) { + *status |= FE_HAS_LOCK; + *status |= FE_HAS_VITERBI; + *status |= FE_HAS_SYNC; + } + + if(reg0A & 0x20) + *status |= FE_HAS_CARRIER; + + if(reg23 < 0xf0) + *status |= FE_HAS_SIGNAL; + + return 0; +} + +static int cx22702_read_ber(struct dvb_frontend* fe, u32* ber) +{ + struct cx22702_state* state = (struct cx22702_state*) fe->demodulator_priv; + + if(cx22702_readreg (state, 0xE4) & 0x02) { + /* Realtime statistics */ + *ber = (cx22702_readreg (state, 0xDE) & 0x7F) << 7 + | (cx22702_readreg (state, 0xDF)&0x7F); + } else { + /* Averagtine statistics */ + *ber = (cx22702_readreg (state, 0xDE) & 0x7F) << 7 + | cx22702_readreg (state, 0xDF); + } + + return 0; +} + +static int cx22702_read_signal_strength(struct dvb_frontend* fe, u16* signal_strength) +{ + struct cx22702_state* state = (struct cx22702_state*) fe->demodulator_priv; + + *signal_strength = cx22702_readreg (state, 0x23); + + return 0; +} + +static int cx22702_read_snr(struct dvb_frontend* fe, u16* snr) +{ + struct cx22702_state* state = (struct cx22702_state*) fe->demodulator_priv; + + u16 rs_ber=0; + if(cx22702_readreg (state, 0xE4) & 0x02) { + /* Realtime statistics */ + rs_ber = (cx22702_readreg (state, 0xDE) & 0x7F) << 7 + | (cx22702_readreg (state, 0xDF)& 0x7F); + } else { + /* Averagine statistics */ + rs_ber = (cx22702_readreg (state, 0xDE) & 0x7F) << 8 + | cx22702_readreg (state, 0xDF); + } + *snr = ~rs_ber; + + return 0; +} + +static int cx22702_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + struct cx22702_state* state = (struct cx22702_state*) fe->demodulator_priv; + + u8 _ucblocks; + + /* RS Uncorrectable Packet Count then reset */ + _ucblocks = cx22702_readreg (state, 0xE3); + if (state->prevUCBlocks < _ucblocks) *ucblocks = (_ucblocks - state->prevUCBlocks); + else *ucblocks = state->prevUCBlocks - _ucblocks; + state->prevUCBlocks = _ucblocks; + + return 0; +} + +static int cx22702_get_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + struct cx22702_state* state = (struct cx22702_state*) fe->demodulator_priv; + + u8 reg0C = cx22702_readreg (state, 0x0C); + + p->inversion = reg0C & 0x1 ? INVERSION_ON : INVERSION_OFF; + return cx22702_get_tps (state, &p->u.ofdm); +} + +static void cx22702_release(struct dvb_frontend* fe) +{ + struct cx22702_state* state = (struct cx22702_state*) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops cx22702_ops; + +struct dvb_frontend* cx22702_attach(const struct cx22702_config* config, + struct i2c_adapter* i2c) +{ + struct cx22702_state* state = NULL; + + /* allocate memory for the internal state */ + state = (struct cx22702_state*) kmalloc(sizeof(struct cx22702_state), GFP_KERNEL); + if (state == NULL) goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + memcpy(&state->ops, &cx22702_ops, sizeof(struct dvb_frontend_ops)); + state->prevUCBlocks = 0; + + /* check if the demod is there */ + if (cx22702_readreg(state, 0x1f) != 0x3) goto error; + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops cx22702_ops = { + + .info = { + .name = "Conexant CX22702 DVB-T", + .type = FE_OFDM, + .frequency_min = 177000000, + .frequency_max = 858000000, + .frequency_stepsize = 166666, + .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_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | + FE_CAN_HIERARCHY_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_RECOVER + }, + + .release = cx22702_release, + + .init = cx22702_init, + + .set_frontend = cx22702_set_tps, + .get_frontend = cx22702_get_frontend, + + .read_status = cx22702_read_status, + .read_ber = cx22702_read_ber, + .read_signal_strength = cx22702_read_signal_strength, + .read_snr = cx22702_read_snr, + .read_ucblocks = cx22702_read_ucblocks, +}; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Enable verbose debug messages"); + +MODULE_DESCRIPTION("Conexant CX22702 DVB-T Demodulator driver"); +MODULE_AUTHOR("Steven Toth"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(cx22702_attach); diff --git a/drivers/media/dvb/frontends/cx22702.h b/drivers/media/dvb/frontends/cx22702.h new file mode 100644 index 000000000000..6e34f997aba2 --- /dev/null +++ b/drivers/media/dvb/frontends/cx22702.h @@ -0,0 +1,46 @@ +/* + Conexant 22702 DVB OFDM demodulator driver + + based on: + Alps TDMB7 DVB OFDM demodulator driver + + Copyright (C) 2001-2002 Convergence Integrated Media GmbH + Holger Waechtler <holger@convergence.de> + + Copyright (C) 2004 Steven Toth <steve@toth.demon.co.uk> + + 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 CX22702_H +#define CX22702_H + +#include <linux/dvb/frontend.h> + +struct cx22702_config +{ + /* the demodulator's i2c address */ + u8 demod_address; + + /* PLL maintenance */ + int (*pll_init)(struct dvb_frontend* fe); + int (*pll_set)(struct dvb_frontend* fe, struct dvb_frontend_parameters* params); +}; + +extern struct dvb_frontend* cx22702_attach(const struct cx22702_config* config, + struct i2c_adapter* i2c); + +#endif // CX22702_H diff --git a/drivers/media/dvb/frontends/cx24110.c b/drivers/media/dvb/frontends/cx24110.c new file mode 100644 index 000000000000..ae16112a0653 --- /dev/null +++ b/drivers/media/dvb/frontends/cx24110.c @@ -0,0 +1,657 @@ +/* + cx24110 - Single Chip Satellite Channel Receiver driver module + + Copyright (C) 2002 Peter Hettkamp <peter.hettkamp@t-online.de> based on + work + Copyright (C) 1999 Convergence Integrated Media GmbH <ralph@convergence.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> + +#include "dvb_frontend.h" +#include "cx24110.h" + + +struct cx24110_state { + + struct i2c_adapter* i2c; + + struct dvb_frontend_ops ops; + + const struct cx24110_config* config; + + struct dvb_frontend frontend; + + u32 lastber; + u32 lastbler; + u32 lastesn0; +}; + +static int debug; +#define dprintk(args...) \ + do { \ + if (debug) printk(KERN_DEBUG "cx24110: " args); \ + } while (0) + +static struct {u8 reg; u8 data;} cx24110_regdata[]= + /* Comments beginning with @ denote this value should + be the default */ + {{0x09,0x01}, /* SoftResetAll */ + {0x09,0x00}, /* release reset */ + {0x01,0xe8}, /* MSB of code rate 27.5MS/s */ + {0x02,0x17}, /* middle byte " */ + {0x03,0x29}, /* LSB " */ + {0x05,0x03}, /* @ DVB mode, standard code rate 3/4 */ + {0x06,0xa5}, /* @ PLL 60MHz */ + {0x07,0x01}, /* @ Fclk, i.e. sampling clock, 60MHz */ + {0x0a,0x00}, /* @ partial chip disables, do not set */ + {0x0b,0x01}, /* set output clock in gapped mode, start signal low + active for first byte */ + {0x0c,0x11}, /* no parity bytes, large hold time, serial data out */ + {0x0d,0x6f}, /* @ RS Sync/Unsync thresholds */ + {0x10,0x40}, /* chip doc is misleading here: write bit 6 as 1 + to avoid starting the BER counter. Reset the + CRC test bit. Finite counting selected */ + {0x15,0xff}, /* @ size of the limited time window for RS BER + estimation. It is <value>*256 RS blocks, this + gives approx. 2.6 sec at 27.5MS/s, rate 3/4 */ + {0x16,0x00}, /* @ enable all RS output ports */ + {0x17,0x04}, /* @ time window allowed for the RS to sync */ + {0x18,0xae}, /* @ allow all standard DVB code rates to be scanned + for automatically */ + /* leave the current code rate and normalization + registers as they are after reset... */ + {0x21,0x10}, /* @ during AutoAcq, search each viterbi setting + only once */ + {0x23,0x18}, /* @ size of the limited time window for Viterbi BER + estimation. It is <value>*65536 channel bits, i.e. + approx. 38ms at 27.5MS/s, rate 3/4 */ + {0x24,0x24}, /* do not trigger Viterbi CRC test. Finite count window */ + /* leave front-end AGC parameters at default values */ + /* leave decimation AGC parameters at default values */ + {0x35,0x40}, /* disable all interrupts. They are not connected anyway */ + {0x36,0xff}, /* clear all interrupt pending flags */ + {0x37,0x00}, /* @ fully enable AutoAcqq state machine */ + {0x38,0x07}, /* @ enable fade recovery, but not autostart AutoAcq */ + /* leave the equalizer parameters on their default values */ + /* leave the final AGC parameters on their default values */ + {0x41,0x00}, /* @ MSB of front-end derotator frequency */ + {0x42,0x00}, /* @ middle bytes " */ + {0x43,0x00}, /* @ LSB " */ + /* leave the carrier tracking loop parameters on default */ + /* leave the bit timing loop parameters at gefault */ + {0x56,0x4d}, /* set the filtune voltage to 2.7V, as recommended by */ + /* the cx24108 data sheet for symbol rates above 15MS/s */ + {0x57,0x00}, /* @ Filter sigma delta enabled, positive */ + {0x61,0x95}, /* GPIO pins 1-4 have special function */ + {0x62,0x05}, /* GPIO pin 5 has special function, pin 6 is GPIO */ + {0x63,0x00}, /* All GPIO pins use CMOS output characteristics */ + {0x64,0x20}, /* GPIO 6 is input, all others are outputs */ + {0x6d,0x30}, /* tuner auto mode clock freq 62kHz */ + {0x70,0x15}, /* use auto mode, tuner word is 21 bits long */ + {0x73,0x00}, /* @ disable several demod bypasses */ + {0x74,0x00}, /* @ " */ + {0x75,0x00} /* @ " */ + /* the remaining registers are for SEC */ + }; + + +static int cx24110_writereg (struct cx24110_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 ((err = i2c_transfer(state->i2c, &msg, 1)) != 1) { + dprintk ("%s: writereg error (err == %i, reg == 0x%02x," + " data == 0x%02x)\n", __FUNCTION__, err, reg, data); + return -EREMOTEIO; + } + + return 0; +} + +static int cx24110_readreg (struct cx24110_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) return ret; + + return b1[0]; +} + +static int cx24110_set_inversion (struct cx24110_state* state, fe_spectral_inversion_t inversion) +{ +/* fixme (low): error handling */ + + switch (inversion) { + case INVERSION_OFF: + cx24110_writereg(state,0x37,cx24110_readreg(state,0x37)|0x1); + /* AcqSpectrInvDis on. No idea why someone should want this */ + cx24110_writereg(state,0x5,cx24110_readreg(state,0x5)&0xf7); + /* Initial value 0 at start of acq */ + cx24110_writereg(state,0x22,cx24110_readreg(state,0x22)&0xef); + /* current value 0 */ + /* The cx24110 manual tells us this reg is read-only. + But what the heck... set it ayways */ + break; + case INVERSION_ON: + cx24110_writereg(state,0x37,cx24110_readreg(state,0x37)|0x1); + /* AcqSpectrInvDis on. No idea why someone should want this */ + cx24110_writereg(state,0x5,cx24110_readreg(state,0x5)|0x08); + /* Initial value 1 at start of acq */ + cx24110_writereg(state,0x22,cx24110_readreg(state,0x22)|0x10); + /* current value 1 */ + break; + case INVERSION_AUTO: + cx24110_writereg(state,0x37,cx24110_readreg(state,0x37)&0xfe); + /* AcqSpectrInvDis off. Leave initial & current states as is */ + break; + default: + return -EINVAL; + } + + return 0; +} + +static int cx24110_set_fec (struct cx24110_state* state, fe_code_rate_t fec) +{ +/* fixme (low): error handling */ + + static const int rate[]={-1,1,2,3,5,7,-1}; + static const int g1[]={-1,0x01,0x02,0x05,0x15,0x45,-1}; + static const int g2[]={-1,0x01,0x03,0x06,0x1a,0x7a,-1}; + + /* Well, the AutoAcq engine of the cx24106 and 24110 automatically + searches all enabled viterbi rates, and can handle non-standard + rates as well. */ + + if (fec>FEC_AUTO) + fec=FEC_AUTO; + + if (fec==FEC_AUTO) { /* (re-)establish AutoAcq behaviour */ + cx24110_writereg(state,0x37,cx24110_readreg(state,0x37)&0xdf); + /* clear AcqVitDis bit */ + cx24110_writereg(state,0x18,0xae); + /* allow all DVB standard code rates */ + cx24110_writereg(state,0x05,(cx24110_readreg(state,0x05)&0xf0)|0x3); + /* set nominal Viterbi rate 3/4 */ + cx24110_writereg(state,0x22,(cx24110_readreg(state,0x22)&0xf0)|0x3); + /* set current Viterbi rate 3/4 */ + cx24110_writereg(state,0x1a,0x05); cx24110_writereg(state,0x1b,0x06); + /* set the puncture registers for code rate 3/4 */ + return 0; + } else { + cx24110_writereg(state,0x37,cx24110_readreg(state,0x37)|0x20); + /* set AcqVitDis bit */ + if(rate[fec]>0) { + cx24110_writereg(state,0x05,(cx24110_readreg(state,0x05)&0xf0)|rate[fec]); + /* set nominal Viterbi rate */ + cx24110_writereg(state,0x22,(cx24110_readreg(state,0x22)&0xf0)|rate[fec]); + /* set current Viterbi rate */ + cx24110_writereg(state,0x1a,g1[fec]); + cx24110_writereg(state,0x1b,g2[fec]); + /* not sure if this is the right way: I always used AutoAcq mode */ + } else + return -EOPNOTSUPP; +/* fixme (low): which is the correct return code? */ + }; + return 0; +} + +static fe_code_rate_t cx24110_get_fec (struct cx24110_state* state) +{ + int i; + + i=cx24110_readreg(state,0x22)&0x0f; + if(!(i&0x08)) { + return FEC_1_2 + i - 1; + } else { +/* fixme (low): a special code rate has been selected. In theory, we need to + return a denominator value, a numerator value, and a pair of puncture + maps to correctly describe this mode. But this should never happen in + practice, because it cannot be set by cx24110_get_fec. */ + return FEC_NONE; + } +} + +static int cx24110_set_symbolrate (struct cx24110_state* state, u32 srate) +{ +/* fixme (low): add error handling */ + u32 ratio; + u32 tmp, fclk, BDRI; + + static const u32 bands[]={5000000UL,15000000UL,90999000UL/2}; + int i; + +dprintk("cx24110 debug: entering %s(%d)\n",__FUNCTION__,srate); + if (srate>90999000UL/2) + srate=90999000UL/2; + if (srate<500000) + srate=500000; + + for(i=0;(i<sizeof(bands)/sizeof(bands[0]))&&(srate>bands[i]);i++) + ; + /* first, check which sample rate is appropriate: 45, 60 80 or 90 MHz, + and set the PLL accordingly (R07[1:0] Fclk, R06[7:4] PLLmult, + R06[3:0] PLLphaseDetGain */ + tmp=cx24110_readreg(state,0x07)&0xfc; + if(srate<90999000UL/4) { /* sample rate 45MHz*/ + cx24110_writereg(state,0x07,tmp); + cx24110_writereg(state,0x06,0x78); + fclk=90999000UL/2; + } else if(srate<60666000UL/2) { /* sample rate 60MHz */ + cx24110_writereg(state,0x07,tmp|0x1); + cx24110_writereg(state,0x06,0xa5); + fclk=60666000UL; + } else if(srate<80888000UL/2) { /* sample rate 80MHz */ + cx24110_writereg(state,0x07,tmp|0x2); + cx24110_writereg(state,0x06,0x87); + fclk=80888000UL; + } else { /* sample rate 90MHz */ + cx24110_writereg(state,0x07,tmp|0x3); + cx24110_writereg(state,0x06,0x78); + fclk=90999000UL; + }; + dprintk("cx24110 debug: fclk %d Hz\n",fclk); + /* we need to divide two integers with approx. 27 bits in 32 bit + arithmetic giving a 25 bit result */ + /* the maximum dividend is 90999000/2, 0x02b6446c, this number is + also the most complex divisor. Hence, the dividend has, + assuming 32bit unsigned arithmetic, 6 clear bits on top, the + divisor 2 unused bits at the bottom. Also, the quotient is + always less than 1/2. Borrowed from VES1893.c, of course */ + + tmp=srate<<6; + BDRI=fclk>>2; + ratio=(tmp/BDRI); + + tmp=(tmp%BDRI)<<8; + ratio=(ratio<<8)+(tmp/BDRI); + + tmp=(tmp%BDRI)<<8; + ratio=(ratio<<8)+(tmp/BDRI); + + tmp=(tmp%BDRI)<<1; + ratio=(ratio<<1)+(tmp/BDRI); + + dprintk("srate= %d (range %d, up to %d)\n", srate,i,bands[i]); + dprintk("fclk = %d\n", fclk); + dprintk("ratio= %08x\n", ratio); + + cx24110_writereg(state, 0x1, (ratio>>16)&0xff); + cx24110_writereg(state, 0x2, (ratio>>8)&0xff); + cx24110_writereg(state, 0x3, (ratio)&0xff); + + return 0; + +} + +int cx24110_pll_write (struct dvb_frontend* fe, u32 data) +{ + struct cx24110_state *state = (struct cx24110_state*) fe->demodulator_priv; + +/* tuner data is 21 bits long, must be left-aligned in data */ +/* tuner cx24108 is written through a dedicated 3wire interface on the demod chip */ +/* FIXME (low): add error handling, avoid infinite loops if HW fails... */ + + dprintk("cx24110 debug: cx24108_write(%8.8x)\n",data); + + cx24110_writereg(state,0x6d,0x30); /* auto mode at 62kHz */ + cx24110_writereg(state,0x70,0x15); /* auto mode 21 bits */ + + /* if the auto tuner writer is still busy, clear it out */ + while (cx24110_readreg(state,0x6d)&0x80) + cx24110_writereg(state,0x72,0); + + /* write the topmost 8 bits */ + cx24110_writereg(state,0x72,(data>>24)&0xff); + + /* wait for the send to be completed */ + while ((cx24110_readreg(state,0x6d)&0xc0)==0x80) + ; + + /* send another 8 bytes */ + cx24110_writereg(state,0x72,(data>>16)&0xff); + while ((cx24110_readreg(state,0x6d)&0xc0)==0x80) + ; + + /* and the topmost 5 bits of this byte */ + cx24110_writereg(state,0x72,(data>>8)&0xff); + while ((cx24110_readreg(state,0x6d)&0xc0)==0x80) + ; + + /* now strobe the enable line once */ + cx24110_writereg(state,0x6d,0x32); + cx24110_writereg(state,0x6d,0x30); + + return 0; +} + +static int cx24110_initfe(struct dvb_frontend* fe) +{ + struct cx24110_state *state = (struct cx24110_state*) fe->demodulator_priv; +/* fixme (low): error handling */ + int i; + + dprintk("%s: init chip\n", __FUNCTION__); + + for(i=0;i<sizeof(cx24110_regdata)/sizeof(cx24110_regdata[0]);i++) { + cx24110_writereg(state, cx24110_regdata[i].reg, cx24110_regdata[i].data); + }; + + if (state->config->pll_init) state->config->pll_init(fe); + + return 0; +} + +static int cx24110_set_voltage (struct dvb_frontend* fe, fe_sec_voltage_t voltage) +{ + struct cx24110_state *state = (struct cx24110_state*) fe->demodulator_priv; + + switch (voltage) { + case SEC_VOLTAGE_13: + return cx24110_writereg(state,0x76,(cx24110_readreg(state,0x76)&0x3b)|0xc0); + case SEC_VOLTAGE_18: + return cx24110_writereg(state,0x76,(cx24110_readreg(state,0x76)&0x3b)|0x40); + default: + return -EINVAL; + }; +} + +static int cx24110_diseqc_send_burst(struct dvb_frontend* fe, + fe_sec_mini_cmd_t burst) +{ + int rv, bit, i; + struct cx24110_state *state = fe->demodulator_priv; + + if (burst == SEC_MINI_A) + bit = 0x00; + else if (burst == SEC_MINI_B) + bit = 0x08; + else + return -EINVAL; + + rv = cx24110_readreg(state, 0x77); + cx24110_writereg(state, 0x77, rv|0x04); + + rv = cx24110_readreg(state, 0x76); + cx24110_writereg(state, 0x76, ((rv & 0x90) | 0x40 | bit)); + for (i = 500; i-- > 0 && !(cx24110_readreg(state,0x76)&0x40) ; ) + ; /* wait for LNB ready */ + + return 0; +} + +static int cx24110_send_diseqc_msg(struct dvb_frontend* fe, + struct dvb_diseqc_master_cmd *cmd) +{ + int i, rv; + struct cx24110_state *state = (struct cx24110_state*) fe->demodulator_priv; + + for (i = 0; i < cmd->msg_len; i++) + cx24110_writereg(state, 0x79 + i, cmd->msg[i]); + + rv = cx24110_readreg(state, 0x77); + cx24110_writereg(state, 0x77, rv|0x04); + + rv = cx24110_readreg(state, 0x76); + + cx24110_writereg(state, 0x76, ((rv & 0x90) | 0x40) | ((cmd->msg_len-3) & 3)); + for (i=500; i-- > 0 && !(cx24110_readreg(state,0x76)&0x40);) + ; /* wait for LNB ready */ + + return 0; +} + +static int cx24110_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct cx24110_state *state = (struct cx24110_state*) fe->demodulator_priv; + + int sync = cx24110_readreg (state, 0x55); + + *status = 0; + + if (sync & 0x10) + *status |= FE_HAS_SIGNAL; + + if (sync & 0x08) + *status |= FE_HAS_CARRIER; + + sync = cx24110_readreg (state, 0x08); + + if (sync & 0x40) + *status |= FE_HAS_VITERBI; + + if (sync & 0x20) + *status |= FE_HAS_SYNC; + + if ((sync & 0x60) == 0x60) + *status |= FE_HAS_LOCK; + + return 0; +} + +static int cx24110_read_ber(struct dvb_frontend* fe, u32* ber) +{ + struct cx24110_state *state = (struct cx24110_state*) fe->demodulator_priv; + + /* fixme (maybe): value range is 16 bit. Scale? */ + if(cx24110_readreg(state,0x24)&0x10) { + /* the Viterbi error counter has finished one counting window */ + cx24110_writereg(state,0x24,0x04); /* select the ber reg */ + state->lastber=cx24110_readreg(state,0x25)| + (cx24110_readreg(state,0x26)<<8); + cx24110_writereg(state,0x24,0x04); /* start new count window */ + cx24110_writereg(state,0x24,0x14); + } + *ber = state->lastber; + + return 0; +} + +static int cx24110_read_signal_strength(struct dvb_frontend* fe, u16* signal_strength) +{ + struct cx24110_state *state = (struct cx24110_state*) fe->demodulator_priv; + +/* no provision in hardware. Read the frontend AGC accumulator. No idea how to scale this, but I know it is 2s complement */ + u8 signal = cx24110_readreg (state, 0x27)+128; + *signal_strength = (signal << 8) | signal; + + return 0; +} + +static int cx24110_read_snr(struct dvb_frontend* fe, u16* snr) +{ + struct cx24110_state *state = (struct cx24110_state*) fe->demodulator_priv; + + /* no provision in hardware. Can be computed from the Es/N0 estimator, but I don't know how. */ + if(cx24110_readreg(state,0x6a)&0x80) { + /* the Es/N0 error counter has finished one counting window */ + state->lastesn0=cx24110_readreg(state,0x69)| + (cx24110_readreg(state,0x68)<<8); + cx24110_writereg(state,0x6a,0x84); /* start new count window */ + } + *snr = state->lastesn0; + + return 0; +} + +static int cx24110_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + struct cx24110_state *state = (struct cx24110_state*) fe->demodulator_priv; + u32 lastbyer; + + if(cx24110_readreg(state,0x10)&0x40) { + /* the RS error counter has finished one counting window */ + cx24110_writereg(state,0x10,0x60); /* select the byer reg */ + lastbyer=cx24110_readreg(state,0x12)| + (cx24110_readreg(state,0x13)<<8)| + (cx24110_readreg(state,0x14)<<16); + cx24110_writereg(state,0x10,0x70); /* select the bler reg */ + state->lastbler=cx24110_readreg(state,0x12)| + (cx24110_readreg(state,0x13)<<8)| + (cx24110_readreg(state,0x14)<<16); + cx24110_writereg(state,0x10,0x20); /* start new count window */ + } + *ucblocks = state->lastbler; + + return 0; +} + +static int cx24110_set_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + struct cx24110_state *state = (struct cx24110_state*) fe->demodulator_priv; + + state->config->pll_set(fe, p); + cx24110_set_inversion (state, p->inversion); + cx24110_set_fec (state, p->u.qpsk.fec_inner); + cx24110_set_symbolrate (state, p->u.qpsk.symbol_rate); + cx24110_writereg(state,0x04,0x05); /* start aquisition */ + + return 0; +} + +static int cx24110_get_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + struct cx24110_state *state = (struct cx24110_state*) fe->demodulator_priv; + s32 afc; unsigned sclk; + +/* cannot read back tuner settings (freq). Need to have some private storage */ + + sclk = cx24110_readreg (state, 0x07) & 0x03; +/* ok, real AFC (FEDR) freq. is afc/2^24*fsamp, fsamp=45/60/80/90MHz. + * Need 64 bit arithmetic. Is thiss possible in the kernel? */ + if (sclk==0) sclk=90999000L/2L; + else if (sclk==1) sclk=60666000L; + else if (sclk==2) sclk=80888000L; + else sclk=90999000L; + sclk>>=8; + afc = sclk*(cx24110_readreg (state, 0x44)&0x1f)+ + ((sclk*cx24110_readreg (state, 0x45))>>8)+ + ((sclk*cx24110_readreg (state, 0x46))>>16); + + p->frequency += afc; + p->inversion = (cx24110_readreg (state, 0x22) & 0x10) ? + INVERSION_ON : INVERSION_OFF; + p->u.qpsk.fec_inner = cx24110_get_fec (state); + + return 0; +} + +static int cx24110_set_tone(struct dvb_frontend* fe, fe_sec_tone_mode_t tone) +{ + struct cx24110_state *state = (struct cx24110_state*) fe->demodulator_priv; + + return cx24110_writereg(state,0x76,(cx24110_readreg(state,0x76)&~0x10)|(((tone==SEC_TONE_ON))?0x10:0)); +} + +static void cx24110_release(struct dvb_frontend* fe) +{ + struct cx24110_state* state = (struct cx24110_state*) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops cx24110_ops; + +struct dvb_frontend* cx24110_attach(const struct cx24110_config* config, + struct i2c_adapter* i2c) +{ + struct cx24110_state* state = NULL; + int ret; + + /* allocate memory for the internal state */ + state = (struct cx24110_state*) kmalloc(sizeof(struct cx24110_state), GFP_KERNEL); + if (state == NULL) goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + memcpy(&state->ops, &cx24110_ops, sizeof(struct dvb_frontend_ops)); + state->lastber = 0; + state->lastbler = 0; + state->lastesn0 = 0; + + /* check if the demod is there */ + ret = cx24110_readreg(state, 0x00); + if ((ret != 0x5a) && (ret != 0x69)) goto error; + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops cx24110_ops = { + + .info = { + .name = "Conexant CX24110 DVB-S", + .type = FE_QPSK, + .frequency_min = 950000, + .frequency_max = 2150000, + .frequency_stepsize = 1011, /* kHz for QPSK frontends */ + .frequency_tolerance = 29500, + .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_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_RECOVER + }, + + .release = cx24110_release, + + .init = cx24110_initfe, + .set_frontend = cx24110_set_frontend, + .get_frontend = cx24110_get_frontend, + .read_status = cx24110_read_status, + .read_ber = cx24110_read_ber, + .read_signal_strength = cx24110_read_signal_strength, + .read_snr = cx24110_read_snr, + .read_ucblocks = cx24110_read_ucblocks, + + .diseqc_send_master_cmd = cx24110_send_diseqc_msg, + .set_tone = cx24110_set_tone, + .set_voltage = cx24110_set_voltage, + .diseqc_send_burst = cx24110_diseqc_send_burst, +}; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("Conexant CX24110 DVB-S Demodulator driver"); +MODULE_AUTHOR("Peter Hettkamp"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(cx24110_attach); +EXPORT_SYMBOL(cx24110_pll_write); diff --git a/drivers/media/dvb/frontends/cx24110.h b/drivers/media/dvb/frontends/cx24110.h new file mode 100644 index 000000000000..6b663f4744e0 --- /dev/null +++ b/drivers/media/dvb/frontends/cx24110.h @@ -0,0 +1,45 @@ +/* + cx24110 - Single Chip Satellite Channel Receiver driver module + + Copyright (C) 2002 Peter Hettkamp <peter.hettkamp@t-online.de> based on + work + Copyright (C) 1999 Convergence Integrated Media GmbH <ralph@convergence.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef CX24110_H +#define CX24110_H + +#include <linux/dvb/frontend.h> + +struct cx24110_config +{ + /* the demodulator's i2c address */ + u8 demod_address; + + /* PLL maintenance */ + int (*pll_init)(struct dvb_frontend* fe); + int (*pll_set)(struct dvb_frontend* fe, struct dvb_frontend_parameters* params); +}; + +extern struct dvb_frontend* cx24110_attach(const struct cx24110_config* config, + struct i2c_adapter* i2c); + +extern int cx24110_pll_write(struct dvb_frontend* fe, u32 data); + +#endif // CX24110_H diff --git a/drivers/media/dvb/frontends/dib3000-common.c b/drivers/media/dvb/frontends/dib3000-common.c new file mode 100644 index 000000000000..47ab02e133d1 --- /dev/null +++ b/drivers/media/dvb/frontends/dib3000-common.c @@ -0,0 +1,83 @@ +#include "dib3000-common.h" + +#ifdef CONFIG_DVB_DIBCOM_DEBUG +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "set debugging level (1=info,2=i2c,4=srch (|-able))."); +#endif +#define deb_info(args...) dprintk(0x01,args) +#define deb_i2c(args...) dprintk(0x02,args) +#define deb_srch(args...) dprintk(0x04,args) + + +int dib3000_read_reg(struct dib3000_state *state, u16 reg) +{ + u8 wb[] = { ((reg >> 8) | 0x80) & 0xff, reg & 0xff }; + u8 rb[2]; + struct i2c_msg msg[] = { + { .addr = state->config.demod_address, .flags = 0, .buf = wb, .len = 2 }, + { .addr = state->config.demod_address, .flags = I2C_M_RD, .buf = rb, .len = 2 }, + }; + + if (i2c_transfer(state->i2c, msg, 2) != 2) + deb_i2c("i2c read error\n"); + + deb_i2c("reading i2c bus (reg: %5d 0x%04x, val: %5d 0x%04x)\n",reg,reg, + (rb[0] << 8) | rb[1],(rb[0] << 8) | rb[1]); + + return (rb[0] << 8) | rb[1]; +} + +int dib3000_write_reg(struct dib3000_state *state, u16 reg, u16 val) +{ + u8 b[] = { + (reg >> 8) & 0xff, reg & 0xff, + (val >> 8) & 0xff, val & 0xff, + }; + struct i2c_msg msg[] = { + { .addr = state->config.demod_address, .flags = 0, .buf = b, .len = 4 } + }; + deb_i2c("writing i2c bus (reg: %5d 0x%04x, val: %5d 0x%04x)\n",reg,reg,val,val); + + return i2c_transfer(state->i2c,msg, 1) != 1 ? -EREMOTEIO : 0; +} + +int dib3000_search_status(u16 irq,u16 lock) +{ + if (irq & 0x02) { + if (lock & 0x01) { + deb_srch("auto search succeeded\n"); + return 1; // auto search succeeded + } else { + deb_srch("auto search not successful\n"); + return 0; // auto search failed + } + } else if (irq & 0x01) { + deb_srch("auto search failed\n"); + return 0; // auto search failed + } + return -1; // try again +} + +/* for auto search */ +u16 dib3000_seq[2][2][2] = /* fft,gua, inv */ + { /* fft */ + { /* gua */ + { 0, 1 }, /* 0 0 { 0,1 } */ + { 3, 9 }, /* 0 1 { 0,1 } */ + }, + { + { 2, 5 }, /* 1 0 { 0,1 } */ + { 6, 11 }, /* 1 1 { 0,1 } */ + } + }; + +MODULE_AUTHOR("Patrick Boettcher <patrick.boettcher@desy.de"); +MODULE_DESCRIPTION("Common functions for the dib3000mb/dib3000mc dvb-frontend drivers"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(dib3000_seq); + +EXPORT_SYMBOL(dib3000_read_reg); +EXPORT_SYMBOL(dib3000_write_reg); +EXPORT_SYMBOL(dib3000_search_status); diff --git a/drivers/media/dvb/frontends/dib3000-common.h b/drivers/media/dvb/frontends/dib3000-common.h new file mode 100644 index 000000000000..c31d6df15472 --- /dev/null +++ b/drivers/media/dvb/frontends/dib3000-common.h @@ -0,0 +1,137 @@ +/* + * .h-files for the common use of the frontend drivers made by DiBcom + * DiBcom 3000M-B/C, 3000P + * + * DiBcom (http://www.dibcom.fr/) + * + * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@desy.de) + * + * based on GPL code from DibCom, which has + * + * Copyright (C) 2004 Amaury Demol for DiBcom (ademol@dibcom.fr) + * + * 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, version 2. + * + * Acknowledgements + * + * Amaury Demol (ademol@dibcom.fr) from DiBcom for providing specs and driver + * sources, on which this driver (and the dvb-dibusb) are based. + * + * see Documentation/dvb/README.dibusb for more information + * + */ + +#ifndef DIB3000_COMMON_H +#define DIB3000_COMMON_H + +#include "dvb_frontend.h" +#include "dib3000.h" + +/* info and err, taken from usb.h, if there is anything available like by default. */ +#define err(format, arg...) printk(KERN_ERR "dib3000: " format "\n" , ## arg) +#define info(format, arg...) printk(KERN_INFO "dib3000: " format "\n" , ## arg) +#define warn(format, arg...) printk(KERN_WARNING "dib3000: " format "\n" , ## arg) + +/* frontend state */ +struct dib3000_state { + struct i2c_adapter* i2c; + + struct dvb_frontend_ops ops; + +/* configuration settings */ + struct dib3000_config config; + + struct dvb_frontend frontend; + int timing_offset; + int timing_offset_comp_done; + + fe_bandwidth_t last_tuned_bw; + u32 last_tuned_freq; +}; + +/* commonly used methods by the dib3000mb/mc/p frontend */ +extern int dib3000_read_reg(struct dib3000_state *state, u16 reg); +extern int dib3000_write_reg(struct dib3000_state *state, u16 reg, u16 val); + +extern int dib3000_search_status(u16 irq,u16 lock); + +/* handy shortcuts */ +#define rd(reg) dib3000_read_reg(state,reg) + +#define wr(reg,val) if (dib3000_write_reg(state,reg,val)) \ + { err("while sending 0x%04x to 0x%04x.",val,reg); return -EREMOTEIO; } + +#define wr_foreach(a,v) { int i; \ + if (sizeof(a) != sizeof(v)) \ + err("sizeof: %zu %zu is different",sizeof(a),sizeof(v));\ + for (i=0; i < sizeof(a)/sizeof(u16); i++) \ + wr(a[i],v[i]); \ + } + +#define set_or(reg,val) wr(reg,rd(reg) | val) + +#define set_and(reg,val) wr(reg,rd(reg) & val) + + +/* debug */ + +#ifdef CONFIG_DVB_DIBCOM_DEBUG +#define dprintk(level,args...) \ + do { if ((debug & level)) { printk(args); } } while (0) +#else +#define dprintk(args...) do { } while (0) +#endif + +/* mask for enabling a specific pid for the pid_filter */ +#define DIB3000_ACTIVATE_PID_FILTERING (0x2000) + +/* common values for tuning */ +#define DIB3000_ALPHA_0 ( 0) +#define DIB3000_ALPHA_1 ( 1) +#define DIB3000_ALPHA_2 ( 2) +#define DIB3000_ALPHA_4 ( 4) + +#define DIB3000_CONSTELLATION_QPSK ( 0) +#define DIB3000_CONSTELLATION_16QAM ( 1) +#define DIB3000_CONSTELLATION_64QAM ( 2) + +#define DIB3000_GUARD_TIME_1_32 ( 0) +#define DIB3000_GUARD_TIME_1_16 ( 1) +#define DIB3000_GUARD_TIME_1_8 ( 2) +#define DIB3000_GUARD_TIME_1_4 ( 3) + +#define DIB3000_TRANSMISSION_MODE_2K ( 0) +#define DIB3000_TRANSMISSION_MODE_8K ( 1) + +#define DIB3000_SELECT_LP ( 0) +#define DIB3000_SELECT_HP ( 1) + +#define DIB3000_FEC_1_2 ( 1) +#define DIB3000_FEC_2_3 ( 2) +#define DIB3000_FEC_3_4 ( 3) +#define DIB3000_FEC_5_6 ( 5) +#define DIB3000_FEC_7_8 ( 7) + +#define DIB3000_HRCH_OFF ( 0) +#define DIB3000_HRCH_ON ( 1) + +#define DIB3000_DDS_INVERSION_OFF ( 0) +#define DIB3000_DDS_INVERSION_ON ( 1) + +#define DIB3000_TUNER_WRITE_ENABLE(a) (0xffff & (a << 8)) +#define DIB3000_TUNER_WRITE_DISABLE(a) (0xffff & ((a << 8) | (1 << 7))) + +/* for auto search */ +extern u16 dib3000_seq[2][2][2]; + +#define DIB3000_REG_MANUFACTOR_ID ( 1025) +#define DIB3000_I2C_ID_DIBCOM (0x01b3) + +#define DIB3000_REG_DEVICE_ID ( 1026) +#define DIB3000MB_DEVICE_ID (0x3000) +#define DIB3000MC_DEVICE_ID (0x3001) +#define DIB3000P_DEVICE_ID (0x3002) + +#endif // DIB3000_COMMON_H diff --git a/drivers/media/dvb/frontends/dib3000.h b/drivers/media/dvb/frontends/dib3000.h new file mode 100644 index 000000000000..80687c130836 --- /dev/null +++ b/drivers/media/dvb/frontends/dib3000.h @@ -0,0 +1,54 @@ +/* + * public header file of the frontend drivers for mobile DVB-T demodulators + * DiBcom 3000M-B and DiBcom 3000P/M-C (http://www.dibcom.fr/) + * + * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@desy.de) + * + * based on GPL code from DibCom, which has + * + * Copyright (C) 2004 Amaury Demol for DiBcom (ademol@dibcom.fr) + * + * 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, version 2. + * + * Acknowledgements + * + * Amaury Demol (ademol@dibcom.fr) from DiBcom for providing specs and driver + * sources, on which this driver (and the dvb-dibusb) are based. + * + * see Documentation/dvb/README.dibusb for more information + * + */ + +#ifndef DIB3000_H +#define DIB3000_H + +#include <linux/dvb/frontend.h> + +struct dib3000_config +{ + /* the demodulator's i2c address */ + u8 demod_address; + + /* PLL maintenance and the i2c address of the PLL */ + u8 (*pll_addr)(struct dvb_frontend *fe); + int (*pll_init)(struct dvb_frontend *fe, u8 pll_buf[5]); + int (*pll_set)(struct dvb_frontend *fe, struct dvb_frontend_parameters* params, u8 pll_buf[5]); +}; + +struct dib_fe_xfer_ops +{ + /* pid and transfer handling is done in the demodulator */ + int (*pid_parse)(struct dvb_frontend *fe, int onoff); + int (*fifo_ctrl)(struct dvb_frontend *fe, int onoff); + int (*pid_ctrl)(struct dvb_frontend *fe, int index, int pid, int onoff); + int (*tuner_pass_ctrl)(struct dvb_frontend *fe, int onoff, u8 pll_ctrl); +}; + +extern struct dvb_frontend* dib3000mb_attach(const struct dib3000_config* config, + struct i2c_adapter* i2c, struct dib_fe_xfer_ops *xfer_ops); + +extern struct dvb_frontend* dib3000mc_attach(const struct dib3000_config* config, + struct i2c_adapter* i2c, struct dib_fe_xfer_ops *xfer_ops); +#endif // DIB3000_H diff --git a/drivers/media/dvb/frontends/dib3000mb.c b/drivers/media/dvb/frontends/dib3000mb.c new file mode 100644 index 000000000000..a853d12a26f1 --- /dev/null +++ b/drivers/media/dvb/frontends/dib3000mb.c @@ -0,0 +1,784 @@ +/* + * Frontend driver for mobile DVB-T demodulator DiBcom 3000M-B + * DiBcom (http://www.dibcom.fr/) + * + * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@desy.de) + * + * based on GPL code from DibCom, which has + * + * Copyright (C) 2004 Amaury Demol for DiBcom (ademol@dibcom.fr) + * + * 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, version 2. + * + * Acknowledgements + * + * Amaury Demol (ademol@dibcom.fr) from DiBcom for providing specs and driver + * sources, on which this driver (and the dvb-dibusb) are based. + * + * see Documentation/dvb/README.dibusb for more information + * + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/version.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> + +#include "dib3000-common.h" +#include "dib3000mb_priv.h" +#include "dib3000.h" + +/* Version information */ +#define DRIVER_VERSION "0.1" +#define DRIVER_DESC "DiBcom 3000M-B DVB-T demodulator" +#define DRIVER_AUTHOR "Patrick Boettcher, patrick.boettcher@desy.de" + +#ifdef CONFIG_DVB_DIBCOM_DEBUG +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "set debugging level (1=info,2=xfer,4=setfe,8=getfe (|-able))."); +#endif +#define deb_info(args...) dprintk(0x01,args) +#define deb_xfer(args...) dprintk(0x02,args) +#define deb_setf(args...) dprintk(0x04,args) +#define deb_getf(args...) dprintk(0x08,args) + +static int dib3000mb_tuner_pass_ctrl(struct dvb_frontend *fe, int onoff, u8 pll_addr); + +static int dib3000mb_get_frontend(struct dvb_frontend* fe, + struct dvb_frontend_parameters *fep); + +static int dib3000mb_set_frontend(struct dvb_frontend* fe, + struct dvb_frontend_parameters *fep, int tuner) +{ + struct dib3000_state* state = (struct dib3000_state*) fe->demodulator_priv; + struct dvb_ofdm_parameters *ofdm = &fep->u.ofdm; + fe_code_rate_t fe_cr = FEC_NONE; + int search_state, seq; + + if (tuner) { + dib3000mb_tuner_pass_ctrl(fe,1,state->config.pll_addr(fe)); + state->config.pll_set(fe, fep, NULL); + dib3000mb_tuner_pass_ctrl(fe,0,state->config.pll_addr(fe)); + + deb_setf("bandwidth: "); + switch (ofdm->bandwidth) { + case BANDWIDTH_8_MHZ: + deb_setf("8 MHz\n"); + wr_foreach(dib3000mb_reg_timing_freq, dib3000mb_timing_freq[2]); + wr_foreach(dib3000mb_reg_bandwidth, dib3000mb_bandwidth_8mhz); + break; + case BANDWIDTH_7_MHZ: + deb_setf("7 MHz\n"); + wr_foreach(dib3000mb_reg_timing_freq, dib3000mb_timing_freq[1]); + wr_foreach(dib3000mb_reg_bandwidth, dib3000mb_bandwidth_7mhz); + break; + case BANDWIDTH_6_MHZ: + deb_setf("6 MHz\n"); + wr_foreach(dib3000mb_reg_timing_freq, dib3000mb_timing_freq[0]); + wr_foreach(dib3000mb_reg_bandwidth, dib3000mb_bandwidth_6mhz); + break; + case BANDWIDTH_AUTO: + return -EOPNOTSUPP; + default: + err("unkown bandwidth value."); + return -EINVAL; + } + } + wr(DIB3000MB_REG_LOCK1_MASK, DIB3000MB_LOCK1_SEARCH_4); + + deb_setf("transmission mode: "); + switch (ofdm->transmission_mode) { + case TRANSMISSION_MODE_2K: + deb_setf("2k\n"); + wr(DIB3000MB_REG_FFT, DIB3000_TRANSMISSION_MODE_2K); + break; + case TRANSMISSION_MODE_8K: + deb_setf("8k\n"); + wr(DIB3000MB_REG_FFT, DIB3000_TRANSMISSION_MODE_8K); + break; + case TRANSMISSION_MODE_AUTO: + deb_setf("auto\n"); + break; + default: + return -EINVAL; + } + + deb_setf("guard: "); + switch (ofdm->guard_interval) { + case GUARD_INTERVAL_1_32: + deb_setf("1_32\n"); + wr(DIB3000MB_REG_GUARD_TIME, DIB3000_GUARD_TIME_1_32); + break; + case GUARD_INTERVAL_1_16: + deb_setf("1_16\n"); + wr(DIB3000MB_REG_GUARD_TIME, DIB3000_GUARD_TIME_1_16); + break; + case GUARD_INTERVAL_1_8: + deb_setf("1_8\n"); + wr(DIB3000MB_REG_GUARD_TIME, DIB3000_GUARD_TIME_1_8); + break; + case GUARD_INTERVAL_1_4: + deb_setf("1_4\n"); + wr(DIB3000MB_REG_GUARD_TIME, DIB3000_GUARD_TIME_1_4); + break; + case GUARD_INTERVAL_AUTO: + deb_setf("auto\n"); + break; + default: + return -EINVAL; + } + + deb_setf("inversion: "); + switch (fep->inversion) { + case INVERSION_OFF: + deb_setf("off\n"); + wr(DIB3000MB_REG_DDS_INV, DIB3000_DDS_INVERSION_OFF); + break; + case INVERSION_AUTO: + deb_setf("auto "); + break; + case INVERSION_ON: + deb_setf("on\n"); + wr(DIB3000MB_REG_DDS_INV, DIB3000_DDS_INVERSION_ON); + break; + default: + return -EINVAL; + } + + deb_setf("constellation: "); + switch (ofdm->constellation) { + case QPSK: + deb_setf("qpsk\n"); + wr(DIB3000MB_REG_QAM, DIB3000_CONSTELLATION_QPSK); + break; + case QAM_16: + deb_setf("qam16\n"); + wr(DIB3000MB_REG_QAM, DIB3000_CONSTELLATION_16QAM); + break; + case QAM_64: + deb_setf("qam64\n"); + wr(DIB3000MB_REG_QAM, DIB3000_CONSTELLATION_64QAM); + break; + case QAM_AUTO: + break; + default: + return -EINVAL; + } + deb_setf("hierachy: "); + switch (ofdm->hierarchy_information) { + case HIERARCHY_NONE: + deb_setf("none "); + /* fall through */ + case HIERARCHY_1: + deb_setf("alpha=1\n"); + wr(DIB3000MB_REG_VIT_ALPHA, DIB3000_ALPHA_1); + break; + case HIERARCHY_2: + deb_setf("alpha=2\n"); + wr(DIB3000MB_REG_VIT_ALPHA, DIB3000_ALPHA_2); + break; + case HIERARCHY_4: + deb_setf("alpha=4\n"); + wr(DIB3000MB_REG_VIT_ALPHA, DIB3000_ALPHA_4); + break; + case HIERARCHY_AUTO: + deb_setf("alpha=auto\n"); + break; + default: + return -EINVAL; + } + + deb_setf("hierarchy: "); + if (ofdm->hierarchy_information == HIERARCHY_NONE) { + deb_setf("none\n"); + wr(DIB3000MB_REG_VIT_HRCH, DIB3000_HRCH_OFF); + wr(DIB3000MB_REG_VIT_HP, DIB3000_SELECT_HP); + fe_cr = ofdm->code_rate_HP; + } else if (ofdm->hierarchy_information != HIERARCHY_AUTO) { + deb_setf("on\n"); + wr(DIB3000MB_REG_VIT_HRCH, DIB3000_HRCH_ON); + wr(DIB3000MB_REG_VIT_HP, DIB3000_SELECT_LP); + fe_cr = ofdm->code_rate_LP; + } + deb_setf("fec: "); + switch (fe_cr) { + case FEC_1_2: + deb_setf("1_2\n"); + wr(DIB3000MB_REG_VIT_CODE_RATE, DIB3000_FEC_1_2); + break; + case FEC_2_3: + deb_setf("2_3\n"); + wr(DIB3000MB_REG_VIT_CODE_RATE, DIB3000_FEC_2_3); + break; + case FEC_3_4: + deb_setf("3_4\n"); + wr(DIB3000MB_REG_VIT_CODE_RATE, DIB3000_FEC_3_4); + break; + case FEC_5_6: + deb_setf("5_6\n"); + wr(DIB3000MB_REG_VIT_CODE_RATE, DIB3000_FEC_5_6); + break; + case FEC_7_8: + deb_setf("7_8\n"); + wr(DIB3000MB_REG_VIT_CODE_RATE, DIB3000_FEC_7_8); + break; + case FEC_NONE: + deb_setf("none "); + break; + case FEC_AUTO: + deb_setf("auto\n"); + break; + default: + return -EINVAL; + } + + seq = dib3000_seq + [ofdm->transmission_mode == TRANSMISSION_MODE_AUTO] + [ofdm->guard_interval == GUARD_INTERVAL_AUTO] + [fep->inversion == INVERSION_AUTO]; + + deb_setf("seq? %d\n", seq); + + wr(DIB3000MB_REG_SEQ, seq); + + wr(DIB3000MB_REG_ISI, seq ? DIB3000MB_ISI_INHIBIT : DIB3000MB_ISI_ACTIVATE); + + if (ofdm->transmission_mode == TRANSMISSION_MODE_2K) { + if (ofdm->guard_interval == GUARD_INTERVAL_1_8) { + wr(DIB3000MB_REG_SYNC_IMPROVEMENT, DIB3000MB_SYNC_IMPROVE_2K_1_8); + } else { + wr(DIB3000MB_REG_SYNC_IMPROVEMENT, DIB3000MB_SYNC_IMPROVE_DEFAULT); + } + + wr(DIB3000MB_REG_UNK_121, DIB3000MB_UNK_121_2K); + } else { + wr(DIB3000MB_REG_UNK_121, DIB3000MB_UNK_121_DEFAULT); + } + + wr(DIB3000MB_REG_MOBILE_ALGO, DIB3000MB_MOBILE_ALGO_OFF); + wr(DIB3000MB_REG_MOBILE_MODE_QAM, DIB3000MB_MOBILE_MODE_QAM_OFF); + wr(DIB3000MB_REG_MOBILE_MODE, DIB3000MB_MOBILE_MODE_OFF); + + wr_foreach(dib3000mb_reg_agc_bandwidth, dib3000mb_agc_bandwidth_high); + + wr(DIB3000MB_REG_ISI, DIB3000MB_ISI_ACTIVATE); + + wr(DIB3000MB_REG_RESTART, DIB3000MB_RESTART_AGC + DIB3000MB_RESTART_CTRL); + wr(DIB3000MB_REG_RESTART, DIB3000MB_RESTART_OFF); + + /* wait for AGC lock */ + msleep(70); + + wr_foreach(dib3000mb_reg_agc_bandwidth, dib3000mb_agc_bandwidth_low); + + /* something has to be auto searched */ + if (ofdm->constellation == QAM_AUTO || + ofdm->hierarchy_information == HIERARCHY_AUTO || + fe_cr == FEC_AUTO || + fep->inversion == INVERSION_AUTO) { + int as_count=0; + + deb_setf("autosearch enabled.\n"); + + wr(DIB3000MB_REG_ISI, DIB3000MB_ISI_INHIBIT); + + wr(DIB3000MB_REG_RESTART, DIB3000MB_RESTART_AUTO_SEARCH); + wr(DIB3000MB_REG_RESTART, DIB3000MB_RESTART_OFF); + + while ((search_state = + dib3000_search_status( + rd(DIB3000MB_REG_AS_IRQ_PENDING), + rd(DIB3000MB_REG_LOCK2_VALUE))) < 0 && as_count++ < 100) + msleep(1); + + deb_setf("search_state after autosearch %d after %d checks\n",search_state,as_count); + + if (search_state == 1) { + struct dvb_frontend_parameters feps; + if (dib3000mb_get_frontend(fe, &feps) == 0) { + deb_setf("reading tuning data from frontend succeeded.\n"); + return dib3000mb_set_frontend(fe, &feps, 0); + } + } + + } else { + wr(DIB3000MB_REG_RESTART, DIB3000MB_RESTART_CTRL); + wr(DIB3000MB_REG_RESTART, DIB3000MB_RESTART_OFF); + } + + return 0; +} + +static int dib3000mb_fe_init(struct dvb_frontend* fe, int mobile_mode) +{ + struct dib3000_state* state = (struct dib3000_state*) fe->demodulator_priv; + + deb_info("dib3000mb is getting up.\n"); + wr(DIB3000MB_REG_POWER_CONTROL, DIB3000MB_POWER_UP); + + wr(DIB3000MB_REG_RESTART, DIB3000MB_RESTART_AGC); + + wr(DIB3000MB_REG_RESET_DEVICE, DIB3000MB_RESET_DEVICE); + wr(DIB3000MB_REG_RESET_DEVICE, DIB3000MB_RESET_DEVICE_RST); + + wr(DIB3000MB_REG_CLOCK, DIB3000MB_CLOCK_DEFAULT); + + wr(DIB3000MB_REG_ELECT_OUT_MODE, DIB3000MB_ELECT_OUT_MODE_ON); + + wr(DIB3000MB_REG_DDS_FREQ_MSB, DIB3000MB_DDS_FREQ_MSB); + wr(DIB3000MB_REG_DDS_FREQ_LSB, DIB3000MB_DDS_FREQ_LSB); + + wr_foreach(dib3000mb_reg_timing_freq, dib3000mb_timing_freq[2]); + + wr_foreach(dib3000mb_reg_impulse_noise, + dib3000mb_impulse_noise_values[DIB3000MB_IMPNOISE_OFF]); + + wr_foreach(dib3000mb_reg_agc_gain, dib3000mb_default_agc_gain); + + wr(DIB3000MB_REG_PHASE_NOISE, DIB3000MB_PHASE_NOISE_DEFAULT); + + wr_foreach(dib3000mb_reg_phase_noise, dib3000mb_default_noise_phase); + + wr_foreach(dib3000mb_reg_lock_duration, dib3000mb_default_lock_duration); + + wr_foreach(dib3000mb_reg_agc_bandwidth, dib3000mb_agc_bandwidth_low); + + wr(DIB3000MB_REG_LOCK0_MASK, DIB3000MB_LOCK0_DEFAULT); + wr(DIB3000MB_REG_LOCK1_MASK, DIB3000MB_LOCK1_SEARCH_4); + wr(DIB3000MB_REG_LOCK2_MASK, DIB3000MB_LOCK2_DEFAULT); + wr(DIB3000MB_REG_SEQ, dib3000_seq[1][1][1]); + + wr_foreach(dib3000mb_reg_bandwidth, dib3000mb_bandwidth_8mhz); + + wr(DIB3000MB_REG_UNK_68, DIB3000MB_UNK_68); + wr(DIB3000MB_REG_UNK_69, DIB3000MB_UNK_69); + wr(DIB3000MB_REG_UNK_71, DIB3000MB_UNK_71); + wr(DIB3000MB_REG_UNK_77, DIB3000MB_UNK_77); + wr(DIB3000MB_REG_UNK_78, DIB3000MB_UNK_78); + wr(DIB3000MB_REG_ISI, DIB3000MB_ISI_INHIBIT); + wr(DIB3000MB_REG_UNK_92, DIB3000MB_UNK_92); + wr(DIB3000MB_REG_UNK_96, DIB3000MB_UNK_96); + wr(DIB3000MB_REG_UNK_97, DIB3000MB_UNK_97); + wr(DIB3000MB_REG_UNK_106, DIB3000MB_UNK_106); + wr(DIB3000MB_REG_UNK_107, DIB3000MB_UNK_107); + wr(DIB3000MB_REG_UNK_108, DIB3000MB_UNK_108); + wr(DIB3000MB_REG_UNK_122, DIB3000MB_UNK_122); + wr(DIB3000MB_REG_MOBILE_MODE_QAM, DIB3000MB_MOBILE_MODE_QAM_OFF); + wr(DIB3000MB_REG_BERLEN, DIB3000MB_BERLEN_DEFAULT); + + wr_foreach(dib3000mb_reg_filter_coeffs, dib3000mb_filter_coeffs); + + wr(DIB3000MB_REG_MOBILE_ALGO, DIB3000MB_MOBILE_ALGO_ON); + wr(DIB3000MB_REG_MULTI_DEMOD_MSB, DIB3000MB_MULTI_DEMOD_MSB); + wr(DIB3000MB_REG_MULTI_DEMOD_LSB, DIB3000MB_MULTI_DEMOD_LSB); + + wr(DIB3000MB_REG_OUTPUT_MODE, DIB3000MB_OUTPUT_MODE_SLAVE); + + wr(DIB3000MB_REG_FIFO_142, DIB3000MB_FIFO_142); + wr(DIB3000MB_REG_MPEG2_OUT_MODE, DIB3000MB_MPEG2_OUT_MODE_188); + wr(DIB3000MB_REG_PID_PARSE, DIB3000MB_PID_PARSE_ACTIVATE); + wr(DIB3000MB_REG_FIFO, DIB3000MB_FIFO_INHIBIT); + wr(DIB3000MB_REG_FIFO_146, DIB3000MB_FIFO_146); + wr(DIB3000MB_REG_FIFO_147, DIB3000MB_FIFO_147); + + wr(DIB3000MB_REG_DATA_IN_DIVERSITY, DIB3000MB_DATA_DIVERSITY_IN_OFF); + + if (state->config.pll_init) { + dib3000mb_tuner_pass_ctrl(fe,1,state->config.pll_addr(fe)); + state->config.pll_init(fe,NULL); + dib3000mb_tuner_pass_ctrl(fe,0,state->config.pll_addr(fe)); + } + + return 0; +} + +static int dib3000mb_get_frontend(struct dvb_frontend* fe, + struct dvb_frontend_parameters *fep) +{ + struct dib3000_state* state = (struct dib3000_state*) fe->demodulator_priv; + struct dvb_ofdm_parameters *ofdm = &fep->u.ofdm; + fe_code_rate_t *cr; + u16 tps_val; + int inv_test1,inv_test2; + u32 dds_val, threshold = 0x800000; + + if (!rd(DIB3000MB_REG_TPS_LOCK)) + return 0; + + dds_val = ((rd(DIB3000MB_REG_DDS_VALUE_MSB) & 0xff) << 16) + rd(DIB3000MB_REG_DDS_VALUE_LSB); + deb_getf("DDS_VAL: %x %x %x",dds_val, rd(DIB3000MB_REG_DDS_VALUE_MSB), rd(DIB3000MB_REG_DDS_VALUE_LSB)); + if (dds_val < threshold) + inv_test1 = 0; + else if (dds_val == threshold) + inv_test1 = 1; + else + inv_test1 = 2; + + dds_val = ((rd(DIB3000MB_REG_DDS_FREQ_MSB) & 0xff) << 16) + rd(DIB3000MB_REG_DDS_FREQ_LSB); + deb_getf("DDS_FREQ: %x %x %x",dds_val, rd(DIB3000MB_REG_DDS_FREQ_MSB), rd(DIB3000MB_REG_DDS_FREQ_LSB)); + if (dds_val < threshold) + inv_test2 = 0; + else if (dds_val == threshold) + inv_test2 = 1; + else + inv_test2 = 2; + + fep->inversion = + ((inv_test2 == 2) && (inv_test1==1 || inv_test1==0)) || + ((inv_test2 == 0) && (inv_test1==1 || inv_test1==2)) ? + INVERSION_ON : INVERSION_OFF; + + deb_getf("inversion %d %d, %d\n", inv_test2, inv_test1, fep->inversion); + + switch ((tps_val = rd(DIB3000MB_REG_TPS_QAM))) { + case DIB3000_CONSTELLATION_QPSK: + deb_getf("QPSK "); + ofdm->constellation = QPSK; + break; + case DIB3000_CONSTELLATION_16QAM: + deb_getf("QAM16 "); + ofdm->constellation = QAM_16; + break; + case DIB3000_CONSTELLATION_64QAM: + deb_getf("QAM64 "); + ofdm->constellation = QAM_64; + break; + default: + err("Unexpected constellation returned by TPS (%d)", tps_val); + break; + } + deb_getf("TPS: %d\n", tps_val); + + if (rd(DIB3000MB_REG_TPS_HRCH)) { + deb_getf("HRCH ON\n"); + cr = &ofdm->code_rate_LP; + ofdm->code_rate_HP = FEC_NONE; + switch ((tps_val = rd(DIB3000MB_REG_TPS_VIT_ALPHA))) { + case DIB3000_ALPHA_0: + deb_getf("HIERARCHY_NONE "); + ofdm->hierarchy_information = HIERARCHY_NONE; + break; + case DIB3000_ALPHA_1: + deb_getf("HIERARCHY_1 "); + ofdm->hierarchy_information = HIERARCHY_1; + break; + case DIB3000_ALPHA_2: + deb_getf("HIERARCHY_2 "); + ofdm->hierarchy_information = HIERARCHY_2; + break; + case DIB3000_ALPHA_4: + deb_getf("HIERARCHY_4 "); + ofdm->hierarchy_information = HIERARCHY_4; + break; + default: + err("Unexpected ALPHA value returned by TPS (%d)", tps_val); + break; + } + deb_getf("TPS: %d\n", tps_val); + + tps_val = rd(DIB3000MB_REG_TPS_CODE_RATE_LP); + } else { + deb_getf("HRCH OFF\n"); + cr = &ofdm->code_rate_HP; + ofdm->code_rate_LP = FEC_NONE; + ofdm->hierarchy_information = HIERARCHY_NONE; + + tps_val = rd(DIB3000MB_REG_TPS_CODE_RATE_HP); + } + + switch (tps_val) { + case DIB3000_FEC_1_2: + deb_getf("FEC_1_2 "); + *cr = FEC_1_2; + break; + case DIB3000_FEC_2_3: + deb_getf("FEC_2_3 "); + *cr = FEC_2_3; + break; + case DIB3000_FEC_3_4: + deb_getf("FEC_3_4 "); + *cr = FEC_3_4; + break; + case DIB3000_FEC_5_6: + deb_getf("FEC_5_6 "); + *cr = FEC_4_5; + break; + case DIB3000_FEC_7_8: + deb_getf("FEC_7_8 "); + *cr = FEC_7_8; + break; + default: + err("Unexpected FEC returned by TPS (%d)", tps_val); + break; + } + deb_getf("TPS: %d\n",tps_val); + + switch ((tps_val = rd(DIB3000MB_REG_TPS_GUARD_TIME))) { + case DIB3000_GUARD_TIME_1_32: + deb_getf("GUARD_INTERVAL_1_32 "); + ofdm->guard_interval = GUARD_INTERVAL_1_32; + break; + case DIB3000_GUARD_TIME_1_16: + deb_getf("GUARD_INTERVAL_1_16 "); + ofdm->guard_interval = GUARD_INTERVAL_1_16; + break; + case DIB3000_GUARD_TIME_1_8: + deb_getf("GUARD_INTERVAL_1_8 "); + ofdm->guard_interval = GUARD_INTERVAL_1_8; + break; + case DIB3000_GUARD_TIME_1_4: + deb_getf("GUARD_INTERVAL_1_4 "); + ofdm->guard_interval = GUARD_INTERVAL_1_4; + break; + default: + err("Unexpected Guard Time returned by TPS (%d)", tps_val); + break; + } + deb_getf("TPS: %d\n", tps_val); + + switch ((tps_val = rd(DIB3000MB_REG_TPS_FFT))) { + case DIB3000_TRANSMISSION_MODE_2K: + deb_getf("TRANSMISSION_MODE_2K "); + ofdm->transmission_mode = TRANSMISSION_MODE_2K; + break; + case DIB3000_TRANSMISSION_MODE_8K: + deb_getf("TRANSMISSION_MODE_8K "); + ofdm->transmission_mode = TRANSMISSION_MODE_8K; + break; + default: + err("unexpected transmission mode return by TPS (%d)", tps_val); + break; + } + deb_getf("TPS: %d\n", tps_val); + + return 0; +} + +static int dib3000mb_read_status(struct dvb_frontend* fe, fe_status_t *stat) +{ + struct dib3000_state* state = (struct dib3000_state*) fe->demodulator_priv; + + *stat = 0; + + if (rd(DIB3000MB_REG_AGC_LOCK)) + *stat |= FE_HAS_SIGNAL; + if (rd(DIB3000MB_REG_CARRIER_LOCK)) + *stat |= FE_HAS_CARRIER; + if (rd(DIB3000MB_REG_VIT_LCK)) + *stat |= FE_HAS_VITERBI; + if (rd(DIB3000MB_REG_TS_SYNC_LOCK)) + *stat |= (FE_HAS_SYNC | FE_HAS_LOCK); + + deb_getf("actual status is %2x\n",*stat); + + deb_getf("autoval: tps: %d, qam: %d, hrch: %d, alpha: %d, hp: %d, lp: %d, guard: %d, fft: %d cell: %d\n", + rd(DIB3000MB_REG_TPS_LOCK), + rd(DIB3000MB_REG_TPS_QAM), + rd(DIB3000MB_REG_TPS_HRCH), + rd(DIB3000MB_REG_TPS_VIT_ALPHA), + rd(DIB3000MB_REG_TPS_CODE_RATE_HP), + rd(DIB3000MB_REG_TPS_CODE_RATE_LP), + rd(DIB3000MB_REG_TPS_GUARD_TIME), + rd(DIB3000MB_REG_TPS_FFT), + rd(DIB3000MB_REG_TPS_CELL_ID)); + + //*stat = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; + return 0; +} + +static int dib3000mb_read_ber(struct dvb_frontend* fe, u32 *ber) +{ + struct dib3000_state* state = (struct dib3000_state*) fe->demodulator_priv; + + *ber = ((rd(DIB3000MB_REG_BER_MSB) << 16) | rd(DIB3000MB_REG_BER_LSB)); + return 0; +} + +/* see dib3000-watch dvb-apps for exact calcuations of signal_strength and snr */ +static int dib3000mb_read_signal_strength(struct dvb_frontend* fe, u16 *strength) +{ + struct dib3000_state* state = (struct dib3000_state*) fe->demodulator_priv; + + *strength = rd(DIB3000MB_REG_SIGNAL_POWER) * 0xffff / 0x170; + return 0; +} + +static int dib3000mb_read_snr(struct dvb_frontend* fe, u16 *snr) +{ + struct dib3000_state* state = (struct dib3000_state*) fe->demodulator_priv; + short sigpow = rd(DIB3000MB_REG_SIGNAL_POWER); + int icipow = ((rd(DIB3000MB_REG_NOISE_POWER_MSB) & 0xff) << 16) | + rd(DIB3000MB_REG_NOISE_POWER_LSB); + *snr = (sigpow << 8) / ((icipow > 0) ? icipow : 1); + return 0; +} + +static int dib3000mb_read_unc_blocks(struct dvb_frontend* fe, u32 *unc) +{ + struct dib3000_state* state = (struct dib3000_state*) fe->demodulator_priv; + + *unc = rd(DIB3000MB_REG_UNC); + return 0; +} + +static int dib3000mb_sleep(struct dvb_frontend* fe) +{ + struct dib3000_state* state = (struct dib3000_state*) fe->demodulator_priv; + deb_info("dib3000mb is going to bed.\n"); + wr(DIB3000MB_REG_POWER_CONTROL, DIB3000MB_POWER_DOWN); + return 0; +} + +static int dib3000mb_fe_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings *tune) +{ + tune->min_delay_ms = 800; + tune->step_size = 166667; + tune->max_drift = 166667 * 2; + + return 0; +} + +static int dib3000mb_fe_init_nonmobile(struct dvb_frontend* fe) +{ + return dib3000mb_fe_init(fe, 0); +} + +static int dib3000mb_set_frontend_and_tuner(struct dvb_frontend* fe, struct dvb_frontend_parameters *fep) +{ + return dib3000mb_set_frontend(fe, fep, 1); +} + +static void dib3000mb_release(struct dvb_frontend* fe) +{ + struct dib3000_state *state = (struct dib3000_state*) fe->demodulator_priv; + kfree(state); +} + +/* pid filter and transfer stuff */ +static int dib3000mb_pid_control(struct dvb_frontend *fe,int index, int pid,int onoff) +{ + struct dib3000_state *state = fe->demodulator_priv; + pid = (onoff ? pid | DIB3000_ACTIVATE_PID_FILTERING : 0); + wr(index+DIB3000MB_REG_FIRST_PID,pid); + return 0; +} + +static int dib3000mb_fifo_control(struct dvb_frontend *fe, int onoff) +{ + struct dib3000_state *state = (struct dib3000_state*) fe->demodulator_priv; + + deb_xfer("%s fifo\n",onoff ? "enabling" : "disabling"); + if (onoff) { + wr(DIB3000MB_REG_FIFO, DIB3000MB_FIFO_ACTIVATE); + } else { + wr(DIB3000MB_REG_FIFO, DIB3000MB_FIFO_INHIBIT); + } + return 0; +} + +static int dib3000mb_pid_parse(struct dvb_frontend *fe, int onoff) +{ + struct dib3000_state *state = fe->demodulator_priv; + deb_xfer("%s pid parsing\n",onoff ? "enabling" : "disabling"); + wr(DIB3000MB_REG_PID_PARSE,onoff); + return 0; +} + +static int dib3000mb_tuner_pass_ctrl(struct dvb_frontend *fe, int onoff, u8 pll_addr) +{ + struct dib3000_state *state = (struct dib3000_state*) fe->demodulator_priv; + if (onoff) { + wr(DIB3000MB_REG_TUNER, DIB3000_TUNER_WRITE_ENABLE(pll_addr)); + } else { + wr(DIB3000MB_REG_TUNER, DIB3000_TUNER_WRITE_DISABLE(pll_addr)); + } + return 0; +} + +static struct dvb_frontend_ops dib3000mb_ops; + +struct dvb_frontend* dib3000mb_attach(const struct dib3000_config* config, + struct i2c_adapter* i2c, struct dib_fe_xfer_ops *xfer_ops) +{ + struct dib3000_state* state = NULL; + + /* allocate memory for the internal state */ + state = (struct dib3000_state*) kmalloc(sizeof(struct dib3000_state), GFP_KERNEL); + if (state == NULL) + goto error; + memset(state,0,sizeof(struct dib3000_state)); + + /* setup the state */ + state->i2c = i2c; + memcpy(&state->config,config,sizeof(struct dib3000_config)); + memcpy(&state->ops, &dib3000mb_ops, sizeof(struct dvb_frontend_ops)); + + /* check for the correct demod */ + if (rd(DIB3000_REG_MANUFACTOR_ID) != DIB3000_I2C_ID_DIBCOM) + goto error; + + if (rd(DIB3000_REG_DEVICE_ID) != DIB3000MB_DEVICE_ID) + goto error; + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + + /* set the xfer operations */ + xfer_ops->pid_parse = dib3000mb_pid_parse; + xfer_ops->fifo_ctrl = dib3000mb_fifo_control; + xfer_ops->pid_ctrl = dib3000mb_pid_control; + xfer_ops->tuner_pass_ctrl = dib3000mb_tuner_pass_ctrl; + + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops dib3000mb_ops = { + + .info = { + .name = "DiBcom 3000M-B DVB-T", + .type = FE_OFDM, + .frequency_min = 44250000, + .frequency_max = 867250000, + .frequency_stepsize = 62500, + .caps = FE_CAN_INVERSION_AUTO | + 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_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_RECOVER | + FE_CAN_HIERARCHY_AUTO, + }, + + .release = dib3000mb_release, + + .init = dib3000mb_fe_init_nonmobile, + .sleep = dib3000mb_sleep, + + .set_frontend = dib3000mb_set_frontend_and_tuner, + .get_frontend = dib3000mb_get_frontend, + .get_tune_settings = dib3000mb_fe_get_tune_settings, + + .read_status = dib3000mb_read_status, + .read_ber = dib3000mb_read_ber, + .read_signal_strength = dib3000mb_read_signal_strength, + .read_snr = dib3000mb_read_snr, + .read_ucblocks = dib3000mb_read_unc_blocks, +}; + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(dib3000mb_attach); diff --git a/drivers/media/dvb/frontends/dib3000mb_priv.h b/drivers/media/dvb/frontends/dib3000mb_priv.h new file mode 100644 index 000000000000..57e61aa5b07b --- /dev/null +++ b/drivers/media/dvb/frontends/dib3000mb_priv.h @@ -0,0 +1,467 @@ +/* + * dib3000mb_priv.h + * + * Copyright (C) 2004 Patrick Boettcher (patrick.boettcher@desy.de) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + * for more information see dib3000mb.c . + */ + +#ifndef __DIB3000MB_PRIV_H_INCLUDED__ +#define __DIB3000MB_PRIV_H_INCLUDED__ + +/* register addresses and some of their default values */ + +/* restart subsystems */ +#define DIB3000MB_REG_RESTART ( 0) + +#define DIB3000MB_RESTART_OFF ( 0) +#define DIB3000MB_RESTART_AUTO_SEARCH (1 << 1) +#define DIB3000MB_RESTART_CTRL (1 << 2) +#define DIB3000MB_RESTART_AGC (1 << 3) + +/* FFT size */ +#define DIB3000MB_REG_FFT ( 1) + +/* Guard time */ +#define DIB3000MB_REG_GUARD_TIME ( 2) + +/* QAM */ +#define DIB3000MB_REG_QAM ( 3) + +/* Alpha coefficient high priority Viterbi algorithm */ +#define DIB3000MB_REG_VIT_ALPHA ( 4) + +/* spectrum inversion */ +#define DIB3000MB_REG_DDS_INV ( 5) + +/* DDS frequency value (IF position) ad ? values don't match reg_3000mb.txt */ +#define DIB3000MB_REG_DDS_FREQ_MSB ( 6) +#define DIB3000MB_REG_DDS_FREQ_LSB ( 7) +#define DIB3000MB_DDS_FREQ_MSB ( 178) +#define DIB3000MB_DDS_FREQ_LSB ( 8990) + +/* timing frequency (carrier spacing) */ +static u16 dib3000mb_reg_timing_freq[] = { 8,9 }; +static u16 dib3000mb_timing_freq[][2] = { + { 126 , 48873 }, /* 6 MHz */ + { 147 , 57019 }, /* 7 MHz */ + { 168 , 65164 }, /* 8 MHz */ +}; + +/* impulse noise parameter */ +/* 36 ??? */ + +static u16 dib3000mb_reg_impulse_noise[] = { 10,11,12,15,36 }; + +enum dib3000mb_impulse_noise_type { + DIB3000MB_IMPNOISE_OFF, + DIB3000MB_IMPNOISE_MOBILE, + DIB3000MB_IMPNOISE_FIXED, + DIB3000MB_IMPNOISE_DEFAULT +}; + +static u16 dib3000mb_impulse_noise_values[][5] = { + { 0x0000, 0x0004, 0x0014, 0x01ff, 0x0399 }, /* off */ + { 0x0001, 0x0004, 0x0014, 0x01ff, 0x037b }, /* mobile */ + { 0x0001, 0x0004, 0x0020, 0x01bd, 0x0399 }, /* fixed */ + { 0x0000, 0x0002, 0x000a, 0x01ff, 0x0399 }, /* default */ +}; + +/* + * Dual Automatic-Gain-Control + * - gains RF in tuner (AGC1) + * - gains IF after filtering (AGC2) + */ + +/* also from 16 to 18 */ +static u16 dib3000mb_reg_agc_gain[] = { + 19,20,21,22,23,24,25,26,27,28,29,30,31,32 +}; + +static u16 dib3000mb_default_agc_gain[] = + { 0x0001, 52429, 623, 128, 166, 195, 61, /* RF ??? */ + 0x0001, 53766, 38011, 0, 90, 33, 23 }; /* IF ??? */ + +/* phase noise */ +/* 36 is set when setting the impulse noise */ +static u16 dib3000mb_reg_phase_noise[] = { 33,34,35,37,38 }; + +static u16 dib3000mb_default_noise_phase[] = { 2, 544, 0, 5, 4 }; + +/* lock duration */ +static u16 dib3000mb_reg_lock_duration[] = { 39,40 }; +static u16 dib3000mb_default_lock_duration[] = { 135, 135 }; + +/* AGC loop bandwidth */ +static u16 dib3000mb_reg_agc_bandwidth[] = { 43,44,45,46,47,48,49,50 }; + +static u16 dib3000mb_agc_bandwidth_low[] = + { 2088, 10, 2088, 10, 3448, 5, 3448, 5 }; +static u16 dib3000mb_agc_bandwidth_high[] = + { 2349, 5, 2349, 5, 2586, 2, 2586, 2 }; + +/* + * lock0 definition (coff_lock) + */ +#define DIB3000MB_REG_LOCK0_MASK ( 51) +#define DIB3000MB_LOCK0_DEFAULT ( 4) + +/* + * lock1 definition (cpil_lock) + * for auto search + * which values hide behind the lock masks + */ +#define DIB3000MB_REG_LOCK1_MASK ( 52) +#define DIB3000MB_LOCK1_SEARCH_4 (0x0004) +#define DIB3000MB_LOCK1_SEARCH_2048 (0x0800) +#define DIB3000MB_LOCK1_DEFAULT (0x0001) + +/* + * lock2 definition (fec_lock) */ +#define DIB3000MB_REG_LOCK2_MASK ( 53) +#define DIB3000MB_LOCK2_DEFAULT (0x0080) + +/* + * SEQ ? what was that again ... :) + * changes when, inversion, guard time and fft is + * either automatically detected or not + */ +#define DIB3000MB_REG_SEQ ( 54) + +/* bandwidth */ +static u16 dib3000mb_reg_bandwidth[] = { 55,56,57,58,59,60,61,62,63,64,65,66,67 }; +static u16 dib3000mb_bandwidth_6mhz[] = + { 0, 33, 53312, 112, 46635, 563, 36565, 0, 1000, 0, 1010, 1, 45264 }; + +static u16 dib3000mb_bandwidth_7mhz[] = + { 0, 28, 64421, 96, 39973, 483, 3255, 0, 1000, 0, 1010, 1, 45264 }; + +static u16 dib3000mb_bandwidth_8mhz[] = + { 0, 25, 23600, 84, 34976, 422, 43808, 0, 1000, 0, 1010, 1, 45264 }; + +#define DIB3000MB_REG_UNK_68 ( 68) +#define DIB3000MB_UNK_68 ( 0) + +#define DIB3000MB_REG_UNK_69 ( 69) +#define DIB3000MB_UNK_69 ( 0) + +#define DIB3000MB_REG_UNK_71 ( 71) +#define DIB3000MB_UNK_71 ( 0) + +#define DIB3000MB_REG_UNK_77 ( 77) +#define DIB3000MB_UNK_77 ( 6) + +#define DIB3000MB_REG_UNK_78 ( 78) +#define DIB3000MB_UNK_78 (0x0080) + +/* isi */ +#define DIB3000MB_REG_ISI ( 79) +#define DIB3000MB_ISI_ACTIVATE ( 0) +#define DIB3000MB_ISI_INHIBIT ( 1) + +/* sync impovement */ +#define DIB3000MB_REG_SYNC_IMPROVEMENT ( 84) +#define DIB3000MB_SYNC_IMPROVE_2K_1_8 ( 3) +#define DIB3000MB_SYNC_IMPROVE_DEFAULT ( 0) + +/* phase noise compensation inhibition */ +#define DIB3000MB_REG_PHASE_NOISE ( 87) +#define DIB3000MB_PHASE_NOISE_DEFAULT ( 0) + +#define DIB3000MB_REG_UNK_92 ( 92) +#define DIB3000MB_UNK_92 (0x0080) + +#define DIB3000MB_REG_UNK_96 ( 96) +#define DIB3000MB_UNK_96 (0x0010) + +#define DIB3000MB_REG_UNK_97 ( 97) +#define DIB3000MB_UNK_97 (0x0009) + +/* mobile mode ??? */ +#define DIB3000MB_REG_MOBILE_MODE ( 101) +#define DIB3000MB_MOBILE_MODE_ON ( 1) +#define DIB3000MB_MOBILE_MODE_OFF ( 0) + +#define DIB3000MB_REG_UNK_106 ( 106) +#define DIB3000MB_UNK_106 (0x0080) + +#define DIB3000MB_REG_UNK_107 ( 107) +#define DIB3000MB_UNK_107 (0x0080) + +#define DIB3000MB_REG_UNK_108 ( 108) +#define DIB3000MB_UNK_108 (0x0080) + +/* fft */ +#define DIB3000MB_REG_UNK_121 ( 121) +#define DIB3000MB_UNK_121_2K ( 7) +#define DIB3000MB_UNK_121_DEFAULT ( 5) + +#define DIB3000MB_REG_UNK_122 ( 122) +#define DIB3000MB_UNK_122 ( 2867) + +/* QAM for mobile mode */ +#define DIB3000MB_REG_MOBILE_MODE_QAM ( 126) +#define DIB3000MB_MOBILE_MODE_QAM_64 ( 3) +#define DIB3000MB_MOBILE_MODE_QAM_QPSK_16 ( 1) +#define DIB3000MB_MOBILE_MODE_QAM_OFF ( 0) + +/* + * data diversity when having more than one chip on-board + * see also DIB3000MB_OUTPUT_MODE_DATA_DIVERSITY + */ +#define DIB3000MB_REG_DATA_IN_DIVERSITY ( 127) +#define DIB3000MB_DATA_DIVERSITY_IN_OFF ( 0) +#define DIB3000MB_DATA_DIVERSITY_IN_ON ( 2) + +/* vit hrch */ +#define DIB3000MB_REG_VIT_HRCH ( 128) + +/* vit code rate */ +#define DIB3000MB_REG_VIT_CODE_RATE ( 129) + +/* vit select hp */ +#define DIB3000MB_REG_VIT_HP ( 130) + +/* time frame for Bit-Error-Rate calculation */ +#define DIB3000MB_REG_BERLEN ( 135) +#define DIB3000MB_BERLEN_LONG ( 0) +#define DIB3000MB_BERLEN_DEFAULT ( 1) +#define DIB3000MB_BERLEN_MEDIUM ( 2) +#define DIB3000MB_BERLEN_SHORT ( 3) + +/* 142 - 152 FIFO parameters + * which is what ? + */ + +#define DIB3000MB_REG_FIFO_142 ( 142) +#define DIB3000MB_FIFO_142 ( 0) + +/* MPEG2 TS output mode */ +#define DIB3000MB_REG_MPEG2_OUT_MODE ( 143) +#define DIB3000MB_MPEG2_OUT_MODE_204 ( 0) +#define DIB3000MB_MPEG2_OUT_MODE_188 ( 1) + +#define DIB3000MB_REG_PID_PARSE ( 144) +#define DIB3000MB_PID_PARSE_INHIBIT ( 0) +#define DIB3000MB_PID_PARSE_ACTIVATE ( 1) + +#define DIB3000MB_REG_FIFO ( 145) +#define DIB3000MB_FIFO_INHIBIT ( 1) +#define DIB3000MB_FIFO_ACTIVATE ( 0) + +#define DIB3000MB_REG_FIFO_146 ( 146) +#define DIB3000MB_FIFO_146 ( 3) + +#define DIB3000MB_REG_FIFO_147 ( 147) +#define DIB3000MB_FIFO_147 (0x0100) + +/* + * pidfilter + * it is not a hardware pidfilter but a filter which drops all pids + * except the ones set. Necessary because of the limited USB1.1 bandwidth. + * regs 153-168 + */ + +#define DIB3000MB_REG_FIRST_PID ( 153) +#define DIB3000MB_NUM_PIDS ( 16) + +/* + * output mode + * USB devices have to use 'slave'-mode + * see also DIB3000MB_REG_ELECT_OUT_MODE + */ +#define DIB3000MB_REG_OUTPUT_MODE ( 169) +#define DIB3000MB_OUTPUT_MODE_GATED_CLK ( 0) +#define DIB3000MB_OUTPUT_MODE_CONT_CLK ( 1) +#define DIB3000MB_OUTPUT_MODE_SERIAL ( 2) +#define DIB3000MB_OUTPUT_MODE_DATA_DIVERSITY ( 5) +#define DIB3000MB_OUTPUT_MODE_SLAVE ( 6) + +/* irq event mask */ +#define DIB3000MB_REG_IRQ_EVENT_MASK ( 170) +#define DIB3000MB_IRQ_EVENT_MASK ( 0) + +/* filter coefficients */ +static u16 dib3000mb_reg_filter_coeffs[] = { + 171, 172, 173, 174, 175, 176, 177, 178, + 179, 180, 181, 182, 183, 184, 185, 186, + 188, 189, 190, 191, 192, 194 +}; + +static u16 dib3000mb_filter_coeffs[] = { + 226, 160, 29, + 979, 998, 19, + 22, 1019, 1006, + 1022, 12, 6, + 1017, 1017, 3, + 6, 1019, + 1021, 2, 3, + 1, 0, +}; + +/* + * mobile algorithm (when you are moving with your device) + * but not faster than 90 km/h + */ +#define DIB3000MB_REG_MOBILE_ALGO ( 195) +#define DIB3000MB_MOBILE_ALGO_ON ( 0) +#define DIB3000MB_MOBILE_ALGO_OFF ( 1) + +/* multiple demodulators algorithm */ +#define DIB3000MB_REG_MULTI_DEMOD_MSB ( 206) +#define DIB3000MB_REG_MULTI_DEMOD_LSB ( 207) + +/* terminator, no more demods */ +#define DIB3000MB_MULTI_DEMOD_MSB ( 32767) +#define DIB3000MB_MULTI_DEMOD_LSB ( 4095) + +/* bring the device into a known */ +#define DIB3000MB_REG_RESET_DEVICE ( 1024) +#define DIB3000MB_RESET_DEVICE (0x812c) +#define DIB3000MB_RESET_DEVICE_RST ( 0) + +/* hardware clock configuration */ +#define DIB3000MB_REG_CLOCK ( 1027) +#define DIB3000MB_CLOCK_DEFAULT (0x9000) +#define DIB3000MB_CLOCK_DIVERSITY (0x92b0) + +/* power down config */ +#define DIB3000MB_REG_POWER_CONTROL ( 1028) +#define DIB3000MB_POWER_DOWN ( 1) +#define DIB3000MB_POWER_UP ( 0) + +/* electrical output mode */ +#define DIB3000MB_REG_ELECT_OUT_MODE ( 1029) +#define DIB3000MB_ELECT_OUT_MODE_OFF ( 0) +#define DIB3000MB_ELECT_OUT_MODE_ON ( 1) + +/* set the tuner i2c address */ +#define DIB3000MB_REG_TUNER ( 1089) + +/* monitoring registers (read only) */ + +/* agc loop locked (size: 1) */ +#define DIB3000MB_REG_AGC_LOCK ( 324) + +/* agc power (size: 16) */ +#define DIB3000MB_REG_AGC_POWER ( 325) + +/* agc1 value (16) */ +#define DIB3000MB_REG_AGC1_VALUE ( 326) + +/* agc2 value (16) */ +#define DIB3000MB_REG_AGC2_VALUE ( 327) + +/* total RF power (16), can be used for signal strength */ +#define DIB3000MB_REG_RF_POWER ( 328) + +/* dds_frequency with offset (24) */ +#define DIB3000MB_REG_DDS_VALUE_MSB ( 339) +#define DIB3000MB_REG_DDS_VALUE_LSB ( 340) + +/* timing offset signed (24) */ +#define DIB3000MB_REG_TIMING_OFFSET_MSB ( 341) +#define DIB3000MB_REG_TIMING_OFFSET_LSB ( 342) + +/* fft start position (13) */ +#define DIB3000MB_REG_FFT_WINDOW_POS ( 353) + +/* carriers locked (1) */ +#define DIB3000MB_REG_CARRIER_LOCK ( 355) + +/* noise power (24) */ +#define DIB3000MB_REG_NOISE_POWER_MSB ( 372) +#define DIB3000MB_REG_NOISE_POWER_LSB ( 373) + +#define DIB3000MB_REG_MOBILE_NOISE_MSB ( 374) +#define DIB3000MB_REG_MOBILE_NOISE_LSB ( 375) + +/* + * signal power (16), this and the above can be + * used to calculate the signal/noise - ratio + */ +#define DIB3000MB_REG_SIGNAL_POWER ( 380) + +/* mer (24) */ +#define DIB3000MB_REG_MER_MSB ( 381) +#define DIB3000MB_REG_MER_LSB ( 382) + +/* + * Transmission Parameter Signalling (TPS) + * the following registers can be used to get TPS-information. + * The values are according to the DVB-T standard. + */ + +/* TPS locked (1) */ +#define DIB3000MB_REG_TPS_LOCK ( 394) + +/* QAM from TPS (2) (values according to DIB3000MB_REG_QAM) */ +#define DIB3000MB_REG_TPS_QAM ( 398) + +/* hierarchy from TPS (1) */ +#define DIB3000MB_REG_TPS_HRCH ( 399) + +/* alpha from TPS (3) (values according to DIB3000MB_REG_VIT_ALPHA) */ +#define DIB3000MB_REG_TPS_VIT_ALPHA ( 400) + +/* code rate high priority from TPS (3) (values according to DIB3000MB_FEC_*) */ +#define DIB3000MB_REG_TPS_CODE_RATE_HP ( 401) + +/* code rate low priority from TPS (3) if DIB3000MB_REG_TPS_VIT_ALPHA */ +#define DIB3000MB_REG_TPS_CODE_RATE_LP ( 402) + +/* guard time from TPS (2) (values according to DIB3000MB_REG_GUARD_TIME */ +#define DIB3000MB_REG_TPS_GUARD_TIME ( 403) + +/* fft size from TPS (2) (values according to DIB3000MB_REG_FFT) */ +#define DIB3000MB_REG_TPS_FFT ( 404) + +/* cell id from TPS (16) */ +#define DIB3000MB_REG_TPS_CELL_ID ( 406) + +/* TPS (68) */ +#define DIB3000MB_REG_TPS_1 ( 408) +#define DIB3000MB_REG_TPS_2 ( 409) +#define DIB3000MB_REG_TPS_3 ( 410) +#define DIB3000MB_REG_TPS_4 ( 411) +#define DIB3000MB_REG_TPS_5 ( 412) + +/* bit error rate (before RS correction) (21) */ +#define DIB3000MB_REG_BER_MSB ( 414) +#define DIB3000MB_REG_BER_LSB ( 415) + +/* packet error rate (uncorrected TS packets) (16) */ +#define DIB3000MB_REG_PACKET_ERROR_RATE ( 417) + +/* uncorrected packet count (16) */ +#define DIB3000MB_REG_UNC ( 420) + +/* viterbi locked (1) */ +#define DIB3000MB_REG_VIT_LCK ( 421) + +/* viterbi inidcator (16) */ +#define DIB3000MB_REG_VIT_INDICATOR ( 422) + +/* transport stream sync lock (1) */ +#define DIB3000MB_REG_TS_SYNC_LOCK ( 423) + +/* transport stream RS lock (1) */ +#define DIB3000MB_REG_TS_RS_LOCK ( 424) + +/* lock mask 0 value (1) */ +#define DIB3000MB_REG_LOCK0_VALUE ( 425) + +/* lock mask 1 value (1) */ +#define DIB3000MB_REG_LOCK1_VALUE ( 426) + +/* lock mask 2 value (1) */ +#define DIB3000MB_REG_LOCK2_VALUE ( 427) + +/* interrupt pending for auto search */ +#define DIB3000MB_REG_AS_IRQ_PENDING ( 434) + +#endif diff --git a/drivers/media/dvb/frontends/dib3000mc.c b/drivers/media/dvb/frontends/dib3000mc.c new file mode 100644 index 000000000000..4a31c05eaecd --- /dev/null +++ b/drivers/media/dvb/frontends/dib3000mc.c @@ -0,0 +1,931 @@ +/* + * Frontend driver for mobile DVB-T demodulator DiBcom 3000P/M-C + * DiBcom (http://www.dibcom.fr/) + * + * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@desy.de) + * + * based on GPL code from DiBCom, which has + * + * Copyright (C) 2004 Amaury Demol for DiBcom (ademol@dibcom.fr) + * + * 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, version 2. + * + * Acknowledgements + * + * Amaury Demol (ademol@dibcom.fr) from DiBcom for providing specs and driver + * sources, on which this driver (and the dvb-dibusb) are based. + * + * see Documentation/dvb/README.dibusb for more information + * + */ +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/version.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> + +#include "dib3000-common.h" +#include "dib3000mc_priv.h" +#include "dib3000.h" + +/* Version information */ +#define DRIVER_VERSION "0.1" +#define DRIVER_DESC "DiBcom 3000M-C DVB-T demodulator" +#define DRIVER_AUTHOR "Patrick Boettcher, patrick.boettcher@desy.de" + +#ifdef CONFIG_DVB_DIBCOM_DEBUG +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "set debugging level (1=info,2=xfer,4=setfe,8=getfe,16=stat (|-able))."); +#endif +#define deb_info(args...) dprintk(0x01,args) +#define deb_xfer(args...) dprintk(0x02,args) +#define deb_setf(args...) dprintk(0x04,args) +#define deb_getf(args...) dprintk(0x08,args) +#define deb_stat(args...) dprintk(0x10,args) + +static int dib3000mc_tuner_pass_ctrl(struct dvb_frontend *fe, int onoff, u8 pll_addr); + +static int dib3000mc_set_impulse_noise(struct dib3000_state * state, int mode, + fe_transmit_mode_t transmission_mode, fe_bandwidth_t bandwidth) +{ + switch (transmission_mode) { + case TRANSMISSION_MODE_2K: + wr_foreach(dib3000mc_reg_fft,dib3000mc_fft_modes[0]); + break; + case TRANSMISSION_MODE_8K: + wr_foreach(dib3000mc_reg_fft,dib3000mc_fft_modes[1]); + break; + default: + break; + } + + switch (bandwidth) { +/* case BANDWIDTH_5_MHZ: + wr_foreach(dib3000mc_reg_impulse_noise,dib3000mc_impluse_noise[0]); + break; */ + case BANDWIDTH_6_MHZ: + wr_foreach(dib3000mc_reg_impulse_noise,dib3000mc_impluse_noise[1]); + break; + case BANDWIDTH_7_MHZ: + wr_foreach(dib3000mc_reg_impulse_noise,dib3000mc_impluse_noise[2]); + break; + case BANDWIDTH_8_MHZ: + wr_foreach(dib3000mc_reg_impulse_noise,dib3000mc_impluse_noise[3]); + break; + default: + break; + } + + switch (mode) { + case 0: /* no impulse */ /* fall through */ + wr_foreach(dib3000mc_reg_imp_noise_ctl,dib3000mc_imp_noise_ctl[0]); + break; + case 1: /* new algo */ + wr_foreach(dib3000mc_reg_imp_noise_ctl,dib3000mc_imp_noise_ctl[1]); + set_or(DIB3000MC_REG_IMP_NOISE_55,DIB3000MC_IMP_NEW_ALGO(0)); /* gives 1<<10 */ + break; + default: /* old algo */ + wr_foreach(dib3000mc_reg_imp_noise_ctl,dib3000mc_imp_noise_ctl[3]); + break; + } + return 0; +} + +static int dib3000mc_set_timing(struct dib3000_state *state, int upd_offset, + fe_transmit_mode_t fft, fe_bandwidth_t bw) +{ + u16 timf_msb,timf_lsb; + s32 tim_offset,tim_sgn; + u64 comp1,comp2,comp=0; + + switch (bw) { + case BANDWIDTH_8_MHZ: comp = DIB3000MC_CLOCK_REF*8; break; + case BANDWIDTH_7_MHZ: comp = DIB3000MC_CLOCK_REF*7; break; + case BANDWIDTH_6_MHZ: comp = DIB3000MC_CLOCK_REF*6; break; + default: err("unknown bandwidth (%d)",bw); break; + } + timf_msb = (comp >> 16) & 0xff; + timf_lsb = (comp & 0xffff); + + // Update the timing offset ; + if (upd_offset > 0) { + if (!state->timing_offset_comp_done) { + msleep(200); + state->timing_offset_comp_done = 1; + } + tim_offset = rd(DIB3000MC_REG_TIMING_OFFS_MSB); + if ((tim_offset & 0x2000) == 0x2000) + tim_offset |= 0xC000; + if (fft == TRANSMISSION_MODE_2K) + tim_offset <<= 2; + state->timing_offset += tim_offset; + } + + tim_offset = state->timing_offset; + if (tim_offset < 0) { + tim_sgn = 1; + tim_offset = -tim_offset; + } else + tim_sgn = 0; + + comp1 = (u32)tim_offset * (u32)timf_lsb ; + comp2 = (u32)tim_offset * (u32)timf_msb ; + comp = ((comp1 >> 16) + comp2) >> 7; + + if (tim_sgn == 0) + comp = (u32)(timf_msb << 16) + (u32) timf_lsb + comp; + else + comp = (u32)(timf_msb << 16) + (u32) timf_lsb - comp ; + + timf_msb = (comp >> 16) & 0xff; + timf_lsb = comp & 0xffff; + + wr(DIB3000MC_REG_TIMING_FREQ_MSB,timf_msb); + wr(DIB3000MC_REG_TIMING_FREQ_LSB,timf_lsb); + return 0; +} + +static int dib3000mc_init_auto_scan(struct dib3000_state *state, fe_bandwidth_t bw, int boost) +{ + if (boost) { + wr(DIB3000MC_REG_SCAN_BOOST,DIB3000MC_SCAN_BOOST_ON); + } else { + wr(DIB3000MC_REG_SCAN_BOOST,DIB3000MC_SCAN_BOOST_OFF); + } + switch (bw) { + case BANDWIDTH_8_MHZ: + wr_foreach(dib3000mc_reg_bandwidth,dib3000mc_bandwidth_8mhz); + break; + case BANDWIDTH_7_MHZ: + wr_foreach(dib3000mc_reg_bandwidth,dib3000mc_bandwidth_7mhz); + break; + case BANDWIDTH_6_MHZ: + wr_foreach(dib3000mc_reg_bandwidth,dib3000mc_bandwidth_6mhz); + break; +/* case BANDWIDTH_5_MHZ: + wr_foreach(dib3000mc_reg_bandwidth,dib3000mc_bandwidth_5mhz); + break;*/ + case BANDWIDTH_AUTO: + return -EOPNOTSUPP; + default: + err("unknown bandwidth value (%d).",bw); + return -EINVAL; + } + if (boost) { + u32 timeout = (rd(DIB3000MC_REG_BW_TIMOUT_MSB) << 16) + + rd(DIB3000MC_REG_BW_TIMOUT_LSB); + timeout *= 85; timeout >>= 7; + wr(DIB3000MC_REG_BW_TIMOUT_MSB,(timeout >> 16) & 0xffff); + wr(DIB3000MC_REG_BW_TIMOUT_LSB,timeout & 0xffff); + } + return 0; +} + +static int dib3000mc_set_adp_cfg(struct dib3000_state *state, fe_modulation_t con) +{ + switch (con) { + case QAM_64: + wr_foreach(dib3000mc_reg_adp_cfg,dib3000mc_adp_cfg[2]); + break; + case QAM_16: + wr_foreach(dib3000mc_reg_adp_cfg,dib3000mc_adp_cfg[1]); + break; + case QPSK: + wr_foreach(dib3000mc_reg_adp_cfg,dib3000mc_adp_cfg[0]); + break; + case QAM_AUTO: + break; + default: + warn("unkown constellation."); + break; + } + return 0; +} + +static int dib3000mc_set_general_cfg(struct dib3000_state *state, struct dvb_frontend_parameters *fep, int *auto_val) +{ + struct dvb_ofdm_parameters *ofdm = &fep->u.ofdm; + fe_code_rate_t fe_cr = FEC_NONE; + u8 fft=0, guard=0, qam=0, alpha=0, sel_hp=0, cr=0, hrch=0; + int seq; + + switch (ofdm->transmission_mode) { + case TRANSMISSION_MODE_2K: fft = DIB3000_TRANSMISSION_MODE_2K; break; + case TRANSMISSION_MODE_8K: fft = DIB3000_TRANSMISSION_MODE_8K; break; + case TRANSMISSION_MODE_AUTO: break; + default: return -EINVAL; + } + switch (ofdm->guard_interval) { + case GUARD_INTERVAL_1_32: guard = DIB3000_GUARD_TIME_1_32; break; + case GUARD_INTERVAL_1_16: guard = DIB3000_GUARD_TIME_1_16; break; + case GUARD_INTERVAL_1_8: guard = DIB3000_GUARD_TIME_1_8; break; + case GUARD_INTERVAL_1_4: guard = DIB3000_GUARD_TIME_1_4; break; + case GUARD_INTERVAL_AUTO: break; + default: return -EINVAL; + } + switch (ofdm->constellation) { + case QPSK: qam = DIB3000_CONSTELLATION_QPSK; break; + case QAM_16: qam = DIB3000_CONSTELLATION_16QAM; break; + case QAM_64: qam = DIB3000_CONSTELLATION_64QAM; break; + case QAM_AUTO: break; + default: return -EINVAL; + } + switch (ofdm->hierarchy_information) { + case HIERARCHY_NONE: /* fall through */ + case HIERARCHY_1: alpha = DIB3000_ALPHA_1; break; + case HIERARCHY_2: alpha = DIB3000_ALPHA_2; break; + case HIERARCHY_4: alpha = DIB3000_ALPHA_4; break; + case HIERARCHY_AUTO: break; + default: return -EINVAL; + } + if (ofdm->hierarchy_information == HIERARCHY_NONE) { + hrch = DIB3000_HRCH_OFF; + sel_hp = DIB3000_SELECT_HP; + fe_cr = ofdm->code_rate_HP; + } else if (ofdm->hierarchy_information != HIERARCHY_AUTO) { + hrch = DIB3000_HRCH_ON; + sel_hp = DIB3000_SELECT_LP; + fe_cr = ofdm->code_rate_LP; + } + switch (fe_cr) { + case FEC_1_2: cr = DIB3000_FEC_1_2; break; + case FEC_2_3: cr = DIB3000_FEC_2_3; break; + case FEC_3_4: cr = DIB3000_FEC_3_4; break; + case FEC_5_6: cr = DIB3000_FEC_5_6; break; + case FEC_7_8: cr = DIB3000_FEC_7_8; break; + case FEC_NONE: break; + case FEC_AUTO: break; + default: return -EINVAL; + } + + wr(DIB3000MC_REG_DEMOD_PARM,DIB3000MC_DEMOD_PARM(alpha,qam,guard,fft)); + wr(DIB3000MC_REG_HRCH_PARM,DIB3000MC_HRCH_PARM(sel_hp,cr,hrch)); + + switch (fep->inversion) { + case INVERSION_OFF: + wr(DIB3000MC_REG_SET_DDS_FREQ_MSB,DIB3000MC_DDS_FREQ_MSB_INV_OFF); + break; + case INVERSION_AUTO: /* fall through */ + case INVERSION_ON: + wr(DIB3000MC_REG_SET_DDS_FREQ_MSB,DIB3000MC_DDS_FREQ_MSB_INV_ON); + break; + default: + return -EINVAL; + } + + seq = dib3000_seq + [ofdm->transmission_mode == TRANSMISSION_MODE_AUTO] + [ofdm->guard_interval == GUARD_INTERVAL_AUTO] + [fep->inversion == INVERSION_AUTO]; + + deb_setf("seq? %d\n", seq); + wr(DIB3000MC_REG_SEQ_TPS,DIB3000MC_SEQ_TPS(seq,1)); + *auto_val = ofdm->constellation == QAM_AUTO || + ofdm->hierarchy_information == HIERARCHY_AUTO || + ofdm->guard_interval == GUARD_INTERVAL_AUTO || + ofdm->transmission_mode == TRANSMISSION_MODE_AUTO || + fe_cr == FEC_AUTO || + fep->inversion == INVERSION_AUTO; + return 0; +} + +static int dib3000mc_get_frontend(struct dvb_frontend* fe, + struct dvb_frontend_parameters *fep) +{ + struct dib3000_state* state = (struct dib3000_state*) fe->demodulator_priv; + struct dvb_ofdm_parameters *ofdm = &fep->u.ofdm; + fe_code_rate_t *cr; + u16 tps_val,cr_val; + int inv_test1,inv_test2; + u32 dds_val, threshold = 0x1000000; + + if (!(rd(DIB3000MC_REG_LOCK_507) & DIB3000MC_LOCK_507)) + return 0; + + dds_val = (rd(DIB3000MC_REG_DDS_FREQ_MSB) << 16) + rd(DIB3000MC_REG_DDS_FREQ_LSB); + deb_getf("DDS_FREQ: %6x\n",dds_val); + if (dds_val < threshold) + inv_test1 = 0; + else if (dds_val == threshold) + inv_test1 = 1; + else + inv_test1 = 2; + + dds_val = (rd(DIB3000MC_REG_SET_DDS_FREQ_MSB) << 16) + rd(DIB3000MC_REG_SET_DDS_FREQ_LSB); + deb_getf("DDS_SET_FREQ: %6x\n",dds_val); + if (dds_val < threshold) + inv_test2 = 0; + else if (dds_val == threshold) + inv_test2 = 1; + else + inv_test2 = 2; + + fep->inversion = + ((inv_test2 == 2) && (inv_test1==1 || inv_test1==0)) || + ((inv_test2 == 0) && (inv_test1==1 || inv_test1==2)) ? + INVERSION_ON : INVERSION_OFF; + + deb_getf("inversion %d %d, %d\n", inv_test2, inv_test1, fep->inversion); + + fep->frequency = state->last_tuned_freq; + fep->u.ofdm.bandwidth= state->last_tuned_bw; + + tps_val = rd(DIB3000MC_REG_TUNING_PARM); + + switch (DIB3000MC_TP_QAM(tps_val)) { + case DIB3000_CONSTELLATION_QPSK: + deb_getf("QPSK "); + ofdm->constellation = QPSK; + break; + case DIB3000_CONSTELLATION_16QAM: + deb_getf("QAM16 "); + ofdm->constellation = QAM_16; + break; + case DIB3000_CONSTELLATION_64QAM: + deb_getf("QAM64 "); + ofdm->constellation = QAM_64; + break; + default: + err("Unexpected constellation returned by TPS (%d)", tps_val); + break; + } + + if (DIB3000MC_TP_HRCH(tps_val)) { + deb_getf("HRCH ON "); + cr = &ofdm->code_rate_LP; + ofdm->code_rate_HP = FEC_NONE; + switch (DIB3000MC_TP_ALPHA(tps_val)) { + case DIB3000_ALPHA_0: + deb_getf("HIERARCHY_NONE "); + ofdm->hierarchy_information = HIERARCHY_NONE; + break; + case DIB3000_ALPHA_1: + deb_getf("HIERARCHY_1 "); + ofdm->hierarchy_information = HIERARCHY_1; + break; + case DIB3000_ALPHA_2: + deb_getf("HIERARCHY_2 "); + ofdm->hierarchy_information = HIERARCHY_2; + break; + case DIB3000_ALPHA_4: + deb_getf("HIERARCHY_4 "); + ofdm->hierarchy_information = HIERARCHY_4; + break; + default: + err("Unexpected ALPHA value returned by TPS (%d)", tps_val); + break; + } + cr_val = DIB3000MC_TP_FEC_CR_LP(tps_val); + } else { + deb_getf("HRCH OFF "); + cr = &ofdm->code_rate_HP; + ofdm->code_rate_LP = FEC_NONE; + ofdm->hierarchy_information = HIERARCHY_NONE; + cr_val = DIB3000MC_TP_FEC_CR_HP(tps_val); + } + + switch (cr_val) { + case DIB3000_FEC_1_2: + deb_getf("FEC_1_2 "); + *cr = FEC_1_2; + break; + case DIB3000_FEC_2_3: + deb_getf("FEC_2_3 "); + *cr = FEC_2_3; + break; + case DIB3000_FEC_3_4: + deb_getf("FEC_3_4 "); + *cr = FEC_3_4; + break; + case DIB3000_FEC_5_6: + deb_getf("FEC_5_6 "); + *cr = FEC_4_5; + break; + case DIB3000_FEC_7_8: + deb_getf("FEC_7_8 "); + *cr = FEC_7_8; + break; + default: + err("Unexpected FEC returned by TPS (%d)", tps_val); + break; + } + + switch (DIB3000MC_TP_GUARD(tps_val)) { + case DIB3000_GUARD_TIME_1_32: + deb_getf("GUARD_INTERVAL_1_32 "); + ofdm->guard_interval = GUARD_INTERVAL_1_32; + break; + case DIB3000_GUARD_TIME_1_16: + deb_getf("GUARD_INTERVAL_1_16 "); + ofdm->guard_interval = GUARD_INTERVAL_1_16; + break; + case DIB3000_GUARD_TIME_1_8: + deb_getf("GUARD_INTERVAL_1_8 "); + ofdm->guard_interval = GUARD_INTERVAL_1_8; + break; + case DIB3000_GUARD_TIME_1_4: + deb_getf("GUARD_INTERVAL_1_4 "); + ofdm->guard_interval = GUARD_INTERVAL_1_4; + break; + default: + err("Unexpected Guard Time returned by TPS (%d)", tps_val); + break; + } + + switch (DIB3000MC_TP_FFT(tps_val)) { + case DIB3000_TRANSMISSION_MODE_2K: + deb_getf("TRANSMISSION_MODE_2K "); + ofdm->transmission_mode = TRANSMISSION_MODE_2K; + break; + case DIB3000_TRANSMISSION_MODE_8K: + deb_getf("TRANSMISSION_MODE_8K "); + ofdm->transmission_mode = TRANSMISSION_MODE_8K; + break; + default: + err("unexpected transmission mode return by TPS (%d)", tps_val); + break; + } + deb_getf("\n"); + + return 0; +} + +static int dib3000mc_set_frontend(struct dvb_frontend* fe, + struct dvb_frontend_parameters *fep, int tuner) +{ + struct dib3000_state* state = (struct dib3000_state*) fe->demodulator_priv; + struct dvb_ofdm_parameters *ofdm = &fep->u.ofdm; + int search_state,auto_val; + u16 val; + + if (tuner) { /* initial call from dvb */ + dib3000mc_tuner_pass_ctrl(fe,1,state->config.pll_addr(fe)); + state->config.pll_set(fe,fep,NULL); + dib3000mc_tuner_pass_ctrl(fe,0,state->config.pll_addr(fe)); + + state->last_tuned_freq = fep->frequency; + // if (!scanboost) { + dib3000mc_set_timing(state,0,ofdm->transmission_mode,ofdm->bandwidth); + dib3000mc_init_auto_scan(state, ofdm->bandwidth, 0); + state->last_tuned_bw = ofdm->bandwidth; + + wr_foreach(dib3000mc_reg_agc_bandwidth,dib3000mc_agc_bandwidth); + wr(DIB3000MC_REG_RESTART,DIB3000MC_RESTART_AGC); + wr(DIB3000MC_REG_RESTART,DIB3000MC_RESTART_OFF); + + /* Default cfg isi offset adp */ + wr_foreach(dib3000mc_reg_offset,dib3000mc_offset[0]); + + wr(DIB3000MC_REG_ISI,DIB3000MC_ISI_DEFAULT | DIB3000MC_ISI_INHIBIT); + dib3000mc_set_adp_cfg(state,ofdm->constellation); + wr(DIB3000MC_REG_UNK_133,DIB3000MC_UNK_133); + + wr_foreach(dib3000mc_reg_bandwidth_general,dib3000mc_bandwidth_general); + /* power smoothing */ + if (ofdm->bandwidth != BANDWIDTH_8_MHZ) { + wr_foreach(dib3000mc_reg_bw,dib3000mc_bw[0]); + } else { + wr_foreach(dib3000mc_reg_bw,dib3000mc_bw[3]); + } + auto_val = 0; + dib3000mc_set_general_cfg(state,fep,&auto_val); + dib3000mc_set_impulse_noise(state,0,ofdm->constellation,ofdm->bandwidth); + + val = rd(DIB3000MC_REG_DEMOD_PARM); + wr(DIB3000MC_REG_DEMOD_PARM,val|DIB3000MC_DEMOD_RST_DEMOD_ON); + wr(DIB3000MC_REG_DEMOD_PARM,val); + // } + msleep(70); + + /* something has to be auto searched */ + if (auto_val) { + int as_count=0; + + deb_setf("autosearch enabled.\n"); + + val = rd(DIB3000MC_REG_DEMOD_PARM); + wr(DIB3000MC_REG_DEMOD_PARM,val | DIB3000MC_DEMOD_RST_AUTO_SRCH_ON); + wr(DIB3000MC_REG_DEMOD_PARM,val); + + while ((search_state = dib3000_search_status( + rd(DIB3000MC_REG_AS_IRQ),1)) < 0 && as_count++ < 100) + msleep(10); + + deb_info("search_state after autosearch %d after %d checks\n",search_state,as_count); + + if (search_state == 1) { + struct dvb_frontend_parameters feps; + if (dib3000mc_get_frontend(fe, &feps) == 0) { + deb_setf("reading tuning data from frontend succeeded.\n"); + return dib3000mc_set_frontend(fe, &feps, 0); + } + } + } else { + dib3000mc_set_impulse_noise(state,0,ofdm->transmission_mode,ofdm->bandwidth); + wr(DIB3000MC_REG_ISI,DIB3000MC_ISI_DEFAULT|DIB3000MC_ISI_ACTIVATE); + dib3000mc_set_adp_cfg(state,ofdm->constellation); + + /* set_offset_cfg */ + wr_foreach(dib3000mc_reg_offset, + dib3000mc_offset[(ofdm->transmission_mode == TRANSMISSION_MODE_8K)+1]); + } + } else { /* second call, after autosearch (fka: set_WithKnownParams) */ +// dib3000mc_set_timing(state,1,ofdm->transmission_mode,ofdm->bandwidth); + + auto_val = 0; + dib3000mc_set_general_cfg(state,fep,&auto_val); + if (auto_val) + deb_info("auto_val is true, even though an auto search was already performed.\n"); + + dib3000mc_set_impulse_noise(state,0,ofdm->constellation,ofdm->bandwidth); + + val = rd(DIB3000MC_REG_DEMOD_PARM); + wr(DIB3000MC_REG_DEMOD_PARM,val | DIB3000MC_DEMOD_RST_AUTO_SRCH_ON); + wr(DIB3000MC_REG_DEMOD_PARM,val); + + msleep(30); + + wr(DIB3000MC_REG_ISI,DIB3000MC_ISI_DEFAULT|DIB3000MC_ISI_ACTIVATE); + dib3000mc_set_adp_cfg(state,ofdm->constellation); + wr_foreach(dib3000mc_reg_offset, + dib3000mc_offset[(ofdm->transmission_mode == TRANSMISSION_MODE_8K)+1]); + + + } + return 0; +} + +static int dib3000mc_fe_init(struct dvb_frontend* fe, int mobile_mode) +{ + struct dib3000_state *state; + + deb_info("init start\n"); + + state = fe->demodulator_priv; + state->timing_offset = 0; + state->timing_offset_comp_done = 0; + + wr(DIB3000MC_REG_RESTART,DIB3000MC_RESTART_CONFIG); + wr(DIB3000MC_REG_RESTART,DIB3000MC_RESTART_OFF); + wr(DIB3000MC_REG_CLK_CFG_1,DIB3000MC_CLK_CFG_1_POWER_UP); + wr(DIB3000MC_REG_CLK_CFG_2,DIB3000MC_CLK_CFG_2_PUP_MOBILE); + wr(DIB3000MC_REG_CLK_CFG_3,DIB3000MC_CLK_CFG_3_POWER_UP); + wr(DIB3000MC_REG_CLK_CFG_7,DIB3000MC_CLK_CFG_7_INIT); + + wr(DIB3000MC_REG_RST_UNC,DIB3000MC_RST_UNC_OFF); + wr(DIB3000MC_REG_UNK_19,DIB3000MC_UNK_19); + + wr(33,5); + wr(36,81); + wr(DIB3000MC_REG_UNK_88,DIB3000MC_UNK_88); + + wr(DIB3000MC_REG_UNK_99,DIB3000MC_UNK_99); + wr(DIB3000MC_REG_UNK_111,DIB3000MC_UNK_111_PH_N_MODE_0); /* phase noise algo off */ + + /* mobile mode - portable reception */ + wr_foreach(dib3000mc_reg_mobile_mode,dib3000mc_mobile_mode[1]); + +/* TUNER_PANASONIC_ENV57H12D5: */ + wr_foreach(dib3000mc_reg_agc_bandwidth,dib3000mc_agc_bandwidth); + wr_foreach(dib3000mc_reg_agc_bandwidth_general,dib3000mc_agc_bandwidth_general); + wr_foreach(dib3000mc_reg_agc,dib3000mc_agc_tuner[1]); + + wr(DIB3000MC_REG_UNK_110,DIB3000MC_UNK_110); + wr(26,0x6680); + wr(DIB3000MC_REG_UNK_1,DIB3000MC_UNK_1); + wr(DIB3000MC_REG_UNK_2,DIB3000MC_UNK_2); + wr(DIB3000MC_REG_UNK_3,DIB3000MC_UNK_3); + wr(DIB3000MC_REG_SEQ_TPS,DIB3000MC_SEQ_TPS_DEFAULT); + + wr_foreach(dib3000mc_reg_bandwidth,dib3000mc_bandwidth_8mhz); + wr_foreach(dib3000mc_reg_bandwidth_general,dib3000mc_bandwidth_general); + + wr(DIB3000MC_REG_UNK_4,DIB3000MC_UNK_4); + + wr(DIB3000MC_REG_SET_DDS_FREQ_MSB,DIB3000MC_DDS_FREQ_MSB_INV_OFF); + wr(DIB3000MC_REG_SET_DDS_FREQ_LSB,DIB3000MC_DDS_FREQ_LSB); + + dib3000mc_set_timing(state,0,TRANSMISSION_MODE_8K,BANDWIDTH_8_MHZ); +// wr_foreach(dib3000mc_reg_timing_freq,dib3000mc_timing_freq[3]); + + wr(DIB3000MC_REG_UNK_120,DIB3000MC_UNK_120); + wr(DIB3000MC_REG_UNK_134,DIB3000MC_UNK_134); + wr(DIB3000MC_REG_FEC_CFG,DIB3000MC_FEC_CFG); + + wr(DIB3000MC_REG_DIVERSITY3,DIB3000MC_DIVERSITY3_IN_OFF); + + dib3000mc_set_impulse_noise(state,0,TRANSMISSION_MODE_8K,BANDWIDTH_8_MHZ); + +/* output mode control, just the MPEG2_SLAVE */ +// set_or(DIB3000MC_REG_OUTMODE,DIB3000MC_OM_SLAVE); + wr(DIB3000MC_REG_OUTMODE,DIB3000MC_OM_SLAVE); + wr(DIB3000MC_REG_SMO_MODE,DIB3000MC_SMO_MODE_SLAVE); + wr(DIB3000MC_REG_FIFO_THRESHOLD,DIB3000MC_FIFO_THRESHOLD_SLAVE); + wr(DIB3000MC_REG_ELEC_OUT,DIB3000MC_ELEC_OUT_SLAVE); + +/* MPEG2_PARALLEL_CONTINUOUS_CLOCK + wr(DIB3000MC_REG_OUTMODE, + DIB3000MC_SET_OUTMODE(DIB3000MC_OM_PAR_CONT_CLK, + rd(DIB3000MC_REG_OUTMODE))); + + wr(DIB3000MC_REG_SMO_MODE, + DIB3000MC_SMO_MODE_DEFAULT | + DIB3000MC_SMO_MODE_188); + + wr(DIB3000MC_REG_FIFO_THRESHOLD,DIB3000MC_FIFO_THRESHOLD_DEFAULT); + wr(DIB3000MC_REG_ELEC_OUT,DIB3000MC_ELEC_OUT_DIV_OUT_ON); +*/ + +/* diversity */ + wr(DIB3000MC_REG_DIVERSITY1,DIB3000MC_DIVERSITY1_DEFAULT); + wr(DIB3000MC_REG_DIVERSITY2,DIB3000MC_DIVERSITY2_DEFAULT); + + set_and(DIB3000MC_REG_DIVERSITY3,DIB3000MC_DIVERSITY3_IN_OFF); + + set_or(DIB3000MC_REG_CLK_CFG_7,DIB3000MC_CLK_CFG_7_DIV_IN_OFF); + +/* if (state->config->pll_init) { + dib3000mc_tuner_pass_ctrl(fe,1,state->config.pll_addr(fe)); + state->config->pll_init(fe,NULL); + dib3000mc_tuner_pass_ctrl(fe,0,state->config.pll_addr(fe)); + }*/ + deb_info("init end\n"); + return 0; +} +static int dib3000mc_read_status(struct dvb_frontend* fe, fe_status_t *stat) +{ + struct dib3000_state* state = (struct dib3000_state*) fe->demodulator_priv; + u16 lock = rd(DIB3000MC_REG_LOCKING); + + *stat = 0; + if (DIB3000MC_AGC_LOCK(lock)) + *stat |= FE_HAS_SIGNAL; + if (DIB3000MC_CARRIER_LOCK(lock)) + *stat |= FE_HAS_CARRIER; + if (DIB3000MC_TPS_LOCK(lock)) + *stat |= FE_HAS_VITERBI; + if (DIB3000MC_MPEG_SYNC_LOCK(lock)) + *stat |= (FE_HAS_SYNC | FE_HAS_LOCK); + + deb_stat("actual status is %2x fifo_level: %x,244: %x, 206: %x, 207: %x, 1040: %x\n",*stat,rd(510),rd(244),rd(206),rd(207),rd(1040)); + + return 0; +} + +static int dib3000mc_read_ber(struct dvb_frontend* fe, u32 *ber) +{ + struct dib3000_state* state = (struct dib3000_state*) fe->demodulator_priv; + *ber = ((rd(DIB3000MC_REG_BER_MSB) << 16) | rd(DIB3000MC_REG_BER_LSB)); + return 0; +} + +static int dib3000mc_read_unc_blocks(struct dvb_frontend* fe, u32 *unc) +{ + struct dib3000_state* state = (struct dib3000_state*) fe->demodulator_priv; + + *unc = rd(DIB3000MC_REG_PACKET_ERROR_COUNT); + return 0; +} + +/* see dib3000mb.c for calculation comments */ +static int dib3000mc_read_signal_strength(struct dvb_frontend* fe, u16 *strength) +{ + struct dib3000_state* state = (struct dib3000_state*) fe->demodulator_priv; + u16 val = rd(DIB3000MC_REG_SIGNAL_NOISE_LSB); + *strength = (((val >> 6) & 0xff) << 8) + (val & 0x3f); + + deb_stat("signal: mantisse = %d, exponent = %d\n",(*strength >> 8) & 0xff, *strength & 0xff); + return 0; +} + +/* see dib3000mb.c for calculation comments */ +static int dib3000mc_read_snr(struct dvb_frontend* fe, u16 *snr) +{ + struct dib3000_state* state = (struct dib3000_state*) fe->demodulator_priv; + u16 val = rd(DIB3000MC_REG_SIGNAL_NOISE_LSB), + val2 = rd(DIB3000MC_REG_SIGNAL_NOISE_MSB); + u16 sig,noise; + + sig = (((val >> 6) & 0xff) << 8) + (val & 0x3f); + noise = (((val >> 4) & 0xff) << 8) + ((val & 0xf) << 2) + ((val2 >> 14) & 0x3); + if (noise == 0) + *snr = 0xffff; + else + *snr = (u16) sig/noise; + + deb_stat("signal: mantisse = %d, exponent = %d\n",(sig >> 8) & 0xff, sig & 0xff); + deb_stat("noise: mantisse = %d, exponent = %d\n",(noise >> 8) & 0xff, noise & 0xff); + deb_stat("snr: %d\n",*snr); + return 0; +} + +static int dib3000mc_sleep(struct dvb_frontend* fe) +{ + struct dib3000_state* state = (struct dib3000_state*) fe->demodulator_priv; + + set_or(DIB3000MC_REG_CLK_CFG_7,DIB3000MC_CLK_CFG_7_PWR_DOWN); + wr(DIB3000MC_REG_CLK_CFG_1,DIB3000MC_CLK_CFG_1_POWER_DOWN); + wr(DIB3000MC_REG_CLK_CFG_2,DIB3000MC_CLK_CFG_2_POWER_DOWN); + wr(DIB3000MC_REG_CLK_CFG_3,DIB3000MC_CLK_CFG_3_POWER_DOWN); + return 0; +} + +static int dib3000mc_fe_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings *tune) +{ + tune->min_delay_ms = 2000; + tune->step_size = 166667; + tune->max_drift = 166667 * 2; + + return 0; +} + +static int dib3000mc_fe_init_nonmobile(struct dvb_frontend* fe) +{ + return dib3000mc_fe_init(fe, 0); +} + +static int dib3000mc_set_frontend_and_tuner(struct dvb_frontend* fe, struct dvb_frontend_parameters *fep) +{ + return dib3000mc_set_frontend(fe, fep, 1); +} + +static void dib3000mc_release(struct dvb_frontend* fe) +{ + struct dib3000_state *state = (struct dib3000_state *) fe->demodulator_priv; + kfree(state); +} + +/* pid filter and transfer stuff */ +static int dib3000mc_pid_control(struct dvb_frontend *fe,int index, int pid,int onoff) +{ + struct dib3000_state *state = fe->demodulator_priv; + pid = (onoff ? pid | DIB3000_ACTIVATE_PID_FILTERING : 0); + wr(index+DIB3000MC_REG_FIRST_PID,pid); + return 0; +} + +static int dib3000mc_fifo_control(struct dvb_frontend *fe, int onoff) +{ + struct dib3000_state *state = (struct dib3000_state*) fe->demodulator_priv; + u16 tmp = rd(DIB3000MC_REG_SMO_MODE); + + deb_xfer("%s fifo\n",onoff ? "enabling" : "disabling"); + + if (onoff) { + deb_xfer("%d %x\n",tmp & DIB3000MC_SMO_MODE_FIFO_UNFLUSH,tmp & DIB3000MC_SMO_MODE_FIFO_UNFLUSH); + wr(DIB3000MC_REG_SMO_MODE,tmp & DIB3000MC_SMO_MODE_FIFO_UNFLUSH); + } else { + deb_xfer("%d %x\n",tmp | DIB3000MC_SMO_MODE_FIFO_FLUSH,tmp | DIB3000MC_SMO_MODE_FIFO_FLUSH); + wr(DIB3000MC_REG_SMO_MODE,tmp | DIB3000MC_SMO_MODE_FIFO_FLUSH); + } + return 0; +} + +static int dib3000mc_pid_parse(struct dvb_frontend *fe, int onoff) +{ + struct dib3000_state *state = fe->demodulator_priv; + u16 tmp = rd(DIB3000MC_REG_SMO_MODE); + + deb_xfer("%s pid parsing\n",onoff ? "enabling" : "disabling"); + + if (onoff) { + wr(DIB3000MC_REG_SMO_MODE,tmp | DIB3000MC_SMO_MODE_PID_PARSE); + } else { + wr(DIB3000MC_REG_SMO_MODE,tmp & DIB3000MC_SMO_MODE_NO_PID_PARSE); + } + return 0; +} + +static int dib3000mc_tuner_pass_ctrl(struct dvb_frontend *fe, int onoff, u8 pll_addr) +{ + struct dib3000_state *state = (struct dib3000_state*) fe->demodulator_priv; + if (onoff) { + wr(DIB3000MC_REG_TUNER, DIB3000_TUNER_WRITE_ENABLE(pll_addr)); + } else { + wr(DIB3000MC_REG_TUNER, DIB3000_TUNER_WRITE_DISABLE(pll_addr)); + } + return 0; +} + +static int dib3000mc_demod_init(struct dib3000_state *state) +{ + u16 default_addr = 0x0a; + /* first init */ + if (state->config.demod_address != default_addr) { + deb_info("initializing the demod the first time. Setting demod addr to 0x%x\n",default_addr); + wr(DIB3000MC_REG_ELEC_OUT,DIB3000MC_ELEC_OUT_DIV_OUT_ON); + wr(DIB3000MC_REG_OUTMODE,DIB3000MC_OM_PAR_CONT_CLK); + + wr(DIB3000MC_REG_RST_I2C_ADDR, + DIB3000MC_DEMOD_ADDR(default_addr) | + DIB3000MC_DEMOD_ADDR_ON); + + state->config.demod_address = default_addr; + + wr(DIB3000MC_REG_RST_I2C_ADDR, + DIB3000MC_DEMOD_ADDR(default_addr)); + } else + deb_info("demod is already initialized. Demod addr: 0x%x\n",state->config.demod_address); + return 0; +} + + +static struct dvb_frontend_ops dib3000mc_ops; + +struct dvb_frontend* dib3000mc_attach(const struct dib3000_config* config, + struct i2c_adapter* i2c, struct dib_fe_xfer_ops *xfer_ops) +{ + struct dib3000_state* state = NULL; + u16 devid; + + /* allocate memory for the internal state */ + state = (struct dib3000_state*) kmalloc(sizeof(struct dib3000_state), GFP_KERNEL); + if (state == NULL) + goto error; + memset(state,0,sizeof(struct dib3000_state)); + + /* setup the state */ + state->i2c = i2c; + memcpy(&state->config,config,sizeof(struct dib3000_config)); + memcpy(&state->ops, &dib3000mc_ops, sizeof(struct dvb_frontend_ops)); + + /* check for the correct demod */ + if (rd(DIB3000_REG_MANUFACTOR_ID) != DIB3000_I2C_ID_DIBCOM) + goto error; + + devid = rd(DIB3000_REG_DEVICE_ID); + if (devid != DIB3000MC_DEVICE_ID && devid != DIB3000P_DEVICE_ID) + goto error; + + switch (devid) { + case DIB3000MC_DEVICE_ID: + info("Found a DiBcom 3000M-C, interesting..."); + break; + case DIB3000P_DEVICE_ID: + info("Found a DiBcom 3000P."); + break; + } + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + + /* set the xfer operations */ + xfer_ops->pid_parse = dib3000mc_pid_parse; + xfer_ops->fifo_ctrl = dib3000mc_fifo_control; + xfer_ops->pid_ctrl = dib3000mc_pid_control; + xfer_ops->tuner_pass_ctrl = dib3000mc_tuner_pass_ctrl; + + dib3000mc_demod_init(state); + + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops dib3000mc_ops = { + + .info = { + .name = "DiBcom 3000P/M-C DVB-T", + .type = FE_OFDM, + .frequency_min = 44250000, + .frequency_max = 867250000, + .frequency_stepsize = 62500, + .caps = FE_CAN_INVERSION_AUTO | + 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_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_RECOVER | + FE_CAN_HIERARCHY_AUTO, + }, + + .release = dib3000mc_release, + + .init = dib3000mc_fe_init_nonmobile, + .sleep = dib3000mc_sleep, + + .set_frontend = dib3000mc_set_frontend_and_tuner, + .get_frontend = dib3000mc_get_frontend, + .get_tune_settings = dib3000mc_fe_get_tune_settings, + + .read_status = dib3000mc_read_status, + .read_ber = dib3000mc_read_ber, + .read_signal_strength = dib3000mc_read_signal_strength, + .read_snr = dib3000mc_read_snr, + .read_ucblocks = dib3000mc_read_unc_blocks, +}; + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(dib3000mc_attach); diff --git a/drivers/media/dvb/frontends/dib3000mc_priv.h b/drivers/media/dvb/frontends/dib3000mc_priv.h new file mode 100644 index 000000000000..2930aac7591b --- /dev/null +++ b/drivers/media/dvb/frontends/dib3000mc_priv.h @@ -0,0 +1,428 @@ +/* + * dib3000mc_priv.h + * + * Copyright (C) 2004 Patrick Boettcher (patrick.boettcher@desy.de) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + * for more information see dib3000mc.c . + */ + +#ifndef __DIB3000MC_PRIV_H__ +#define __DIB3000MC_PRIV_H__ + +/* + * Demodulator parameters + * reg: 0 1 1 1 11 11 111 + * | | | | | | + * | | | | | +-- alpha (000=0, 001=1, 010=2, 100=4) + * | | | | +----- constellation (00=QPSK, 01=16QAM, 10=64QAM) + * | | | +-------- guard (00=1/32, 01=1/16, 10=1/8, 11=1/4) + * | | +----------- transmission mode (0=2k, 1=8k) + * | | + * | +-------------- restart autosearch for parameters + * +---------------- restart the demodulator + * reg: 181 1 111 1 + * | | | + * | | +- FEC applies for HP or LP (0=LP, 1=HP) + * | +---- FEC rate (001=1/2, 010=2/3, 011=3/4, 101=5/6, 111=7/8) + * +------- hierarchy on (0=no, 1=yes) + */ + +/* demodulator tuning parameter and restart options */ +#define DIB3000MC_REG_DEMOD_PARM ( 0) +#define DIB3000MC_DEMOD_PARM(a,c,g,t) ( \ + (0x7 & a) | \ + ((0x3 & c) << 3) | \ + ((0x3 & g) << 5) | \ + ((0x1 & t) << 7) ) +#define DIB3000MC_DEMOD_RST_AUTO_SRCH_ON (1 << 8) +#define DIB3000MC_DEMOD_RST_AUTO_SRCH_OFF (0 << 8) +#define DIB3000MC_DEMOD_RST_DEMOD_ON (1 << 9) +#define DIB3000MC_DEMOD_RST_DEMOD_OFF (0 << 9) + +/* register for hierarchy parameters */ +#define DIB3000MC_REG_HRCH_PARM ( 181) +#define DIB3000MC_HRCH_PARM(s,f,h) ( \ + (0x1 & s) | \ + ((0x7 & f) << 1) | \ + ((0x1 & h) << 4) ) + +/* timeout ??? */ +#define DIB3000MC_REG_UNK_1 ( 1) +#define DIB3000MC_UNK_1 ( 0x04) + +/* timeout ??? */ +#define DIB3000MC_REG_UNK_2 ( 2) +#define DIB3000MC_UNK_2 ( 0x04) + +/* timeout ??? */ +#define DIB3000MC_REG_UNK_3 ( 3) +#define DIB3000MC_UNK_3 (0x1000) + +#define DIB3000MC_REG_UNK_4 ( 4) +#define DIB3000MC_UNK_4 (0x0814) + +/* timeout ??? */ +#define DIB3000MC_REG_SEQ_TPS ( 5) +#define DIB3000MC_SEQ_TPS_DEFAULT ( 1) +#define DIB3000MC_SEQ_TPS(s,t) ( \ + ((s & 0x0f) << 4) | \ + ((t & 0x01) << 8) ) +#define DIB3000MC_IS_TPS(v) ((v << 8) & 0x1) +#define DIB3000MC_IS_AS(v) ((v >> 4) & 0xf) + +/* parameters for the bandwidth */ +#define DIB3000MC_REG_BW_TIMOUT_MSB ( 6) +#define DIB3000MC_REG_BW_TIMOUT_LSB ( 7) + +static u16 dib3000mc_reg_bandwidth[] = { 6,7,8,9,10,11,16,17 }; + +/*static u16 dib3000mc_bandwidth_5mhz[] = + { 0x28, 0x9380, 0x87, 0x4100, 0x2a4, 0x4500, 0x1, 0xb0d0 };*/ + +static u16 dib3000mc_bandwidth_6mhz[] = + { 0x21, 0xd040, 0x70, 0xb62b, 0x233, 0x8ed5, 0x1, 0xb0d0 }; + +static u16 dib3000mc_bandwidth_7mhz[] = + { 0x1c, 0xfba5, 0x60, 0x9c25, 0x1e3, 0x0cb7, 0x1, 0xb0d0 }; + +static u16 dib3000mc_bandwidth_8mhz[] = + { 0x19, 0x5c30, 0x54, 0x88a0, 0x1a6, 0xab20, 0x1, 0xb0d0 }; + +static u16 dib3000mc_reg_bandwidth_general[] = { 12,13,14,15 }; +static u16 dib3000mc_bandwidth_general[] = { 0x0000, 0x03e8, 0x0000, 0x03f2 }; + +/* lock mask */ +#define DIB3000MC_REG_LOCK_MASK ( 15) +#define DIB3000MC_ACTIVATE_LOCK_MASK (0x0800) + +/* reset the uncorrected packet count (??? do it 5 times) */ +#define DIB3000MC_REG_RST_UNC ( 18) +#define DIB3000MC_RST_UNC_ON ( 1) +#define DIB3000MC_RST_UNC_OFF ( 0) + +#define DIB3000MC_REG_UNK_19 ( 19) +#define DIB3000MC_UNK_19 ( 0) + +/* DDS frequency value (IF position) and inversion bit */ +#define DIB3000MC_REG_INVERSION ( 21) +#define DIB3000MC_REG_SET_DDS_FREQ_MSB ( 21) +#define DIB3000MC_DDS_FREQ_MSB_INV_OFF (0x0164) +#define DIB3000MC_DDS_FREQ_MSB_INV_ON (0x0364) + +#define DIB3000MC_REG_SET_DDS_FREQ_LSB ( 22) +#define DIB3000MC_DDS_FREQ_LSB (0x463d) + +/* timing frequencies setting */ +#define DIB3000MC_REG_TIMING_FREQ_MSB ( 23) +#define DIB3000MC_REG_TIMING_FREQ_LSB ( 24) +#define DIB3000MC_CLOCK_REF (0x151fd1) + +//static u16 dib3000mc_reg_timing_freq[] = { 23,24 }; + +//static u16 dib3000mc_timing_freq[][2] = { +// { 0x69, 0x9f18 }, /* 5 MHz */ +// { 0x7e ,0xbee9 }, /* 6 MHz */ +// { 0x93 ,0xdebb }, /* 7 MHz */ +// { 0xa8 ,0xfe8c }, /* 8 MHz */ +//}; + +/* timeout ??? */ +static u16 dib3000mc_reg_offset[] = { 26,33 }; + +static u16 dib3000mc_offset[][2] = { + { 26240, 5 }, /* default */ + { 30336, 6 }, /* 8K */ + { 38528, 8 }, /* 2K */ +}; + +#define DIB3000MC_REG_ISI ( 29) +#define DIB3000MC_ISI_DEFAULT (0x1073) +#define DIB3000MC_ISI_ACTIVATE (0x0000) +#define DIB3000MC_ISI_INHIBIT (0x0200) + +/* impulse noise control */ +static u16 dib3000mc_reg_imp_noise_ctl[] = { 34,35 }; + +static u16 dib3000mc_imp_noise_ctl[][2] = { + { 0x1294, 0x1ff8 }, /* mode 0 */ + { 0x1294, 0x1ff8 }, /* mode 1 */ + { 0x1294, 0x1ff8 }, /* mode 2 */ + { 0x1294, 0x1ff8 }, /* mode 3 */ + { 0x1294, 0x1ff8 }, /* mode 4 */ +}; + +/* AGC registers */ +static u16 dib3000mc_reg_agc[] = { + 36,37,38,39,42,43,44,45,46,47,48,49 +}; + +static u16 dib3000mc_agc_tuner[][12] = { + { 0x0051, 0x301d, 0x0000, 0x1cc7, 0xcf5c, 0x6666, + 0xbae1, 0xa148, 0x3b5e, 0x3c1c, 0x001a, 0x2019 + }, /* TUNER_PANASONIC_ENV77H04D5, */ + + { 0x0051, 0x301d, 0x0000, 0x1cc7, 0xdc29, 0x570a, + 0xbae1, 0x8ccd, 0x3b6d, 0x551d, 0x000a, 0x951e + }, /* TUNER_PANASONIC_ENV57H13D5, TUNER_PANASONIC_ENV57H12D5 */ + + { 0x0051, 0x301d, 0x0000, 0x1cc7, 0xffff, 0xffff, + 0xffff, 0x0000, 0xfdfd, 0x4040, 0x00fd, 0x4040 + }, /* TUNER_SAMSUNG_DTOS333IH102, TUNER_RFAGCIN_UNKNOWN */ + + { 0x0196, 0x301d, 0x0000, 0x1cc7, 0xbd71, 0x5c29, + 0xb5c3, 0x6148, 0x6569, 0x5127, 0x0033, 0x3537 + }, /* TUNER_PROVIDER_X */ + /* TODO TUNER_PANASONIC_ENV57H10D8, TUNER_PANASONIC_ENV57H11D8 */ +}; + +/* AGC loop bandwidth */ +static u16 dib3000mc_reg_agc_bandwidth[] = { 40,41 }; +static u16 dib3000mc_agc_bandwidth[] = { 0x119,0x330 }; + +static u16 dib3000mc_reg_agc_bandwidth_general[] = { 50,51,52,53,54 }; +static u16 dib3000mc_agc_bandwidth_general[] = + { 0x8000, 0x91ca, 0x01ba, 0x0087, 0x0087 }; + +#define DIB3000MC_REG_IMP_NOISE_55 ( 55) +#define DIB3000MC_IMP_NEW_ALGO(w) (w | (1<<10)) + +/* Impulse noise params */ +static u16 dib3000mc_reg_impulse_noise[] = { 55,56,57 }; +static u16 dib3000mc_impluse_noise[][3] = { + { 0x489, 0x89, 0x72 }, /* 5 MHz */ + { 0x4a5, 0xa5, 0x89 }, /* 6 MHz */ + { 0x4c0, 0xc0, 0xa0 }, /* 7 MHz */ + { 0x4db, 0xdb, 0xb7 }, /* 8 Mhz */ +}; + +static u16 dib3000mc_reg_fft[] = { + 58,59,60,61,62,63,64,65,66,67,68,69, + 70,71,72,73,74,75,76,77,78,79,80,81, + 82,83,84,85,86 +}; + +static u16 dib3000mc_fft_modes[][29] = { + { 0x38, 0x6d9, 0x3f28, 0x7a7, 0x3a74, 0x196, 0x32a, 0x48c, + 0x3ffe, 0x7f3, 0x2d94, 0x76, 0x53d, + 0x3ff8, 0x7e3, 0x3320, 0x76, 0x5b3, + 0x3feb, 0x7d2, 0x365e, 0x76, 0x48c, + 0x3ffe, 0x5b3, 0x3feb, 0x76, 0x0, 0xd + }, /* fft mode 0 */ + { 0x3b, 0x6d9, 0x3f28, 0x7a7, 0x3a74, 0x196, 0x32a, 0x48c, + 0x3ffe, 0x7f3, 0x2d94, 0x76, 0x53d, + 0x3ff8, 0x7e3, 0x3320, 0x76, 0x5b3, + 0x3feb, 0x7d2, 0x365e, 0x76, 0x48c, + 0x3ffe, 0x5b3, 0x3feb, 0x0, 0x8200, 0xd + }, /* fft mode 1 */ +}; + +#define DIB3000MC_REG_UNK_88 ( 88) +#define DIB3000MC_UNK_88 (0x0410) + +static u16 dib3000mc_reg_bw[] = { 93,94,95,96,97,98 }; +static u16 dib3000mc_bw[][6] = { + { 0,0,0,0,0,0 }, /* 5 MHz */ + { 0,0,0,0,0,0 }, /* 6 MHz */ + { 0,0,0,0,0,0 }, /* 7 MHz */ + { 0x20, 0x21, 0x20, 0x23, 0x20, 0x27 }, /* 8 MHz */ +}; + + +/* phase noise control */ +#define DIB3000MC_REG_UNK_99 ( 99) +#define DIB3000MC_UNK_99 (0x0220) + +#define DIB3000MC_REG_SCAN_BOOST ( 100) +#define DIB3000MC_SCAN_BOOST_ON ((11 << 6) + 6) +#define DIB3000MC_SCAN_BOOST_OFF ((16 << 6) + 9) + +/* timeout ??? */ +#define DIB3000MC_REG_UNK_110 ( 110) +#define DIB3000MC_UNK_110 ( 3277) + +#define DIB3000MC_REG_UNK_111 ( 111) +#define DIB3000MC_UNK_111_PH_N_MODE_0 ( 0) +#define DIB3000MC_UNK_111_PH_N_MODE_1 (1 << 1) + +/* superious rm config */ +#define DIB3000MC_REG_UNK_120 ( 120) +#define DIB3000MC_UNK_120 ( 8207) + +#define DIB3000MC_REG_UNK_133 ( 133) +#define DIB3000MC_UNK_133 ( 15564) + +#define DIB3000MC_REG_UNK_134 ( 134) +#define DIB3000MC_UNK_134 ( 0) + +/* adapter config for constellation */ +static u16 dib3000mc_reg_adp_cfg[] = { 129, 130, 131, 132 }; + +static u16 dib3000mc_adp_cfg[][4] = { + { 0x99a, 0x7fae, 0x333, 0x7ff0 }, /* QPSK */ + { 0x23d, 0x7fdf, 0x0a4, 0x7ff0 }, /* 16-QAM */ + { 0x148, 0x7ff0, 0x0a4, 0x7ff8 }, /* 64-QAM */ +}; + +static u16 dib3000mc_reg_mobile_mode[] = { 139, 140, 141, 175, 1032 }; + +static u16 dib3000mc_mobile_mode[][5] = { + { 0x01, 0x0, 0x0, 0x00, 0x12c }, /* fixed */ + { 0x01, 0x0, 0x0, 0x00, 0x12c }, /* portable */ + { 0x00, 0x0, 0x0, 0x02, 0x000 }, /* mobile */ + { 0x00, 0x0, 0x0, 0x02, 0x000 }, /* auto */ +}; + +#define DIB3000MC_REG_DIVERSITY1 ( 177) +#define DIB3000MC_DIVERSITY1_DEFAULT ( 1) + +#define DIB3000MC_REG_DIVERSITY2 ( 178) +#define DIB3000MC_DIVERSITY2_DEFAULT ( 1) + +#define DIB3000MC_REG_DIVERSITY3 ( 180) +#define DIB3000MC_DIVERSITY3_IN_OFF (0xfff0) +#define DIB3000MC_DIVERSITY3_IN_ON (0xfff6) + +#define DIB3000MC_REG_FEC_CFG ( 195) +#define DIB3000MC_FEC_CFG ( 0x10) + +/* + * reg 206, output mode + * 1111 1111 + * |||| |||| + * |||| |||+- unk + * |||| ||+-- unk + * |||| |+--- unk (on by default) + * |||| +---- fifo_ctrl (1 = inhibit (flushed), 0 = active (unflushed)) + * |||+------ pid_parse (1 = enabled, 0 = disabled) + * ||+------- outp_188 (1 = TS packet size 188, 0 = packet size 204) + * |+-------- unk + * +--------- unk + */ + +#define DIB3000MC_REG_SMO_MODE ( 206) +#define DIB3000MC_SMO_MODE_DEFAULT (1 << 2) +#define DIB3000MC_SMO_MODE_FIFO_FLUSH (1 << 3) +#define DIB3000MC_SMO_MODE_FIFO_UNFLUSH (0xfff7) +#define DIB3000MC_SMO_MODE_PID_PARSE (1 << 4) +#define DIB3000MC_SMO_MODE_NO_PID_PARSE (0xffef) +#define DIB3000MC_SMO_MODE_188 (1 << 5) +#define DIB3000MC_SMO_MODE_SLAVE (DIB3000MC_SMO_MODE_DEFAULT | \ + DIB3000MC_SMO_MODE_188 | DIB3000MC_SMO_MODE_PID_PARSE | (1<<1)) + +#define DIB3000MC_REG_FIFO_THRESHOLD ( 207) +#define DIB3000MC_FIFO_THRESHOLD_DEFAULT ( 1792) +#define DIB3000MC_FIFO_THRESHOLD_SLAVE ( 512) +/* + * pidfilter + * it is not a hardware pidfilter but a filter which drops all pids + * except the ones set. When connected to USB1.1 bandwidth this is important. + * DiB3000P/M-C can filter up to 32 PIDs + */ +#define DIB3000MC_REG_FIRST_PID ( 212) +#define DIB3000MC_NUM_PIDS ( 32) + +#define DIB3000MC_REG_OUTMODE ( 244) +#define DIB3000MC_OM_PARALLEL_GATED_CLK ( 0) +#define DIB3000MC_OM_PAR_CONT_CLK (1 << 11) +#define DIB3000MC_OM_SERIAL (2 << 11) +#define DIB3000MC_OM_DIVOUT_ON (4 << 11) +#define DIB3000MC_OM_SLAVE (DIB3000MC_OM_DIVOUT_ON | DIB3000MC_OM_PAR_CONT_CLK) + +#define DIB3000MC_REG_RF_POWER ( 392) + +#define DIB3000MC_REG_FFT_POSITION ( 407) + +#define DIB3000MC_REG_DDS_FREQ_MSB ( 414) +#define DIB3000MC_REG_DDS_FREQ_LSB ( 415) + +#define DIB3000MC_REG_TIMING_OFFS_MSB ( 416) +#define DIB3000MC_REG_TIMING_OFFS_LSB ( 417) + +#define DIB3000MC_REG_TUNING_PARM ( 458) +#define DIB3000MC_TP_QAM(v) ((v >> 13) & 0x03) +#define DIB3000MC_TP_HRCH(v) ((v >> 12) & 0x01) +#define DIB3000MC_TP_ALPHA(v) ((v >> 9) & 0x07) +#define DIB3000MC_TP_FFT(v) ((v >> 8) & 0x01) +#define DIB3000MC_TP_FEC_CR_HP(v) ((v >> 5) & 0x07) +#define DIB3000MC_TP_FEC_CR_LP(v) ((v >> 2) & 0x07) +#define DIB3000MC_TP_GUARD(v) (v & 0x03) + +#define DIB3000MC_REG_SIGNAL_NOISE_MSB ( 483) +#define DIB3000MC_REG_SIGNAL_NOISE_LSB ( 484) + +#define DIB3000MC_REG_MER ( 485) + +#define DIB3000MC_REG_BER_MSB ( 500) +#define DIB3000MC_REG_BER_LSB ( 501) + +#define DIB3000MC_REG_PACKET_ERRORS ( 503) + +#define DIB3000MC_REG_PACKET_ERROR_COUNT ( 506) + +#define DIB3000MC_REG_LOCK_507 ( 507) +#define DIB3000MC_LOCK_507 (0x0002) // ? name correct ? + +#define DIB3000MC_REG_LOCKING ( 509) +#define DIB3000MC_AGC_LOCK(v) (v & 0x8000) +#define DIB3000MC_CARRIER_LOCK(v) (v & 0x2000) +#define DIB3000MC_MPEG_SYNC_LOCK(v) (v & 0x0080) +#define DIB3000MC_MPEG_DATA_LOCK(v) (v & 0x0040) +#define DIB3000MC_TPS_LOCK(v) (v & 0x0004) + +#define DIB3000MC_REG_AS_IRQ ( 511) +#define DIB3000MC_AS_IRQ_SUCCESS (1 << 1) +#define DIB3000MC_AS_IRQ_FAIL ( 1) + +#define DIB3000MC_REG_TUNER ( 769) + +#define DIB3000MC_REG_RST_I2C_ADDR ( 1024) +#define DIB3000MC_DEMOD_ADDR_ON ( 1) +#define DIB3000MC_DEMOD_ADDR(a) ((a << 4) & 0x03F0) + +#define DIB3000MC_REG_RESTART ( 1027) +#define DIB3000MC_RESTART_OFF (0x0000) +#define DIB3000MC_RESTART_AGC (0x0800) +#define DIB3000MC_RESTART_CONFIG (0x8000) + +#define DIB3000MC_REG_RESTART_VIT ( 1028) +#define DIB3000MC_RESTART_VIT_OFF ( 0) +#define DIB3000MC_RESTART_VIT_ON ( 1) + +#define DIB3000MC_REG_CLK_CFG_1 ( 1031) +#define DIB3000MC_CLK_CFG_1_POWER_UP ( 0) +#define DIB3000MC_CLK_CFG_1_POWER_DOWN (0xffff) + +#define DIB3000MC_REG_CLK_CFG_2 ( 1032) +#define DIB3000MC_CLK_CFG_2_PUP_FIXED (0x012c) +#define DIB3000MC_CLK_CFG_2_PUP_PORT (0x0104) +#define DIB3000MC_CLK_CFG_2_PUP_MOBILE (0x0000) +#define DIB3000MC_CLK_CFG_2_POWER_DOWN (0xffff) + +#define DIB3000MC_REG_CLK_CFG_3 ( 1033) +#define DIB3000MC_CLK_CFG_3_POWER_UP ( 0) +#define DIB3000MC_CLK_CFG_3_POWER_DOWN (0xfff5) + +#define DIB3000MC_REG_CLK_CFG_7 ( 1037) +#define DIB3000MC_CLK_CFG_7_INIT ( 12592) +#define DIB3000MC_CLK_CFG_7_POWER_UP (~0x0003) +#define DIB3000MC_CLK_CFG_7_PWR_DOWN (0x0003) +#define DIB3000MC_CLK_CFG_7_DIV_IN_OFF (1 << 8) + +/* was commented out ??? */ +#define DIB3000MC_REG_CLK_CFG_8 ( 1038) +#define DIB3000MC_CLK_CFG_8_POWER_UP (0x160c) + +#define DIB3000MC_REG_CLK_CFG_9 ( 1039) +#define DIB3000MC_CLK_CFG_9_POWER_UP ( 0) + +/* also clock ??? */ +#define DIB3000MC_REG_ELEC_OUT ( 1040) +#define DIB3000MC_ELEC_OUT_HIGH_Z ( 0) +#define DIB3000MC_ELEC_OUT_DIV_OUT_ON ( 1) +#define DIB3000MC_ELEC_OUT_SLAVE ( 3) + +#endif diff --git a/drivers/media/dvb/frontends/dvb-pll.c b/drivers/media/dvb/frontends/dvb-pll.c new file mode 100644 index 000000000000..2a3c2ce7b2aa --- /dev/null +++ b/drivers/media/dvb/frontends/dvb-pll.c @@ -0,0 +1,168 @@ +/* + * $Id: dvb-pll.c,v 1.7 2005/02/10 11:52:02 kraxel Exp $ + * + * descriptions + helper functions for simple dvb plls. + * + * (c) 2004 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/dvb/frontend.h> +#include <asm/types.h> + +#include "dvb-pll.h" + +/* ----------------------------------------------------------- */ +/* descriptions */ + +struct dvb_pll_desc dvb_pll_thomson_dtt7579 = { + .name = "Thomson dtt7579", + .min = 177000000, + .max = 858000000, + .count = 5, + .entries = { + { 0, 36166667, 166666, 0xb4, 0x03 }, /* go sleep */ + { 443250000, 36166667, 166666, 0xb4, 0x02 }, + { 542000000, 36166667, 166666, 0xb4, 0x08 }, + { 771000000, 36166667, 166666, 0xbc, 0x08 }, + { 999999999, 36166667, 166666, 0xf4, 0x08 }, + }, +}; +EXPORT_SYMBOL(dvb_pll_thomson_dtt7579); + +struct dvb_pll_desc dvb_pll_thomson_dtt7610 = { + .name = "Thomson dtt7610", + .min = 44000000, + .max = 958000000, + .count = 3, + .entries = { + { 157250000, 44000000, 62500, 0x8e, 0x39 }, + { 454000000, 44000000, 62500, 0x8e, 0x3a }, + { 999999999, 44000000, 62500, 0x8e, 0x3c }, + }, +}; +EXPORT_SYMBOL(dvb_pll_thomson_dtt7610); + +static void thomson_dtt759x_bw(u8 *buf, int bandwidth) +{ + if (BANDWIDTH_7_MHZ == bandwidth) + buf[3] |= 0x10; +} + +struct dvb_pll_desc dvb_pll_thomson_dtt759x = { + .name = "Thomson dtt759x", + .min = 177000000, + .max = 896000000, + .setbw = thomson_dtt759x_bw, + .count = 6, + .entries = { + { 0, 36166667, 166666, 0x84, 0x03 }, + { 264000000, 36166667, 166666, 0xb4, 0x02 }, + { 470000000, 36166667, 166666, 0xbc, 0x02 }, + { 735000000, 36166667, 166666, 0xbc, 0x08 }, + { 835000000, 36166667, 166666, 0xf4, 0x08 }, + { 999999999, 36166667, 166666, 0xfc, 0x08 }, + }, +}; +EXPORT_SYMBOL(dvb_pll_thomson_dtt759x); + +struct dvb_pll_desc dvb_pll_lg_z201 = { + .name = "LG z201", + .min = 174000000, + .max = 862000000, + .count = 5, + .entries = { + { 0, 36166667, 166666, 0xbc, 0x03 }, + { 443250000, 36166667, 166666, 0xbc, 0x01 }, + { 542000000, 36166667, 166666, 0xbc, 0x02 }, + { 830000000, 36166667, 166666, 0xf4, 0x02 }, + { 999999999, 36166667, 166666, 0xfc, 0x02 }, + }, +}; +EXPORT_SYMBOL(dvb_pll_lg_z201); + +struct dvb_pll_desc dvb_pll_unknown_1 = { + .name = "unknown 1", /* used by dntv live dvb-t */ + .min = 174000000, + .max = 862000000, + .count = 9, + .entries = { + { 150000000, 36166667, 166666, 0xb4, 0x01 }, + { 173000000, 36166667, 166666, 0xbc, 0x01 }, + { 250000000, 36166667, 166666, 0xb4, 0x02 }, + { 400000000, 36166667, 166666, 0xbc, 0x02 }, + { 420000000, 36166667, 166666, 0xf4, 0x02 }, + { 470000000, 36166667, 166666, 0xfc, 0x02 }, + { 600000000, 36166667, 166666, 0xbc, 0x08 }, + { 730000000, 36166667, 166666, 0xf4, 0x08 }, + { 999999999, 36166667, 166666, 0xfc, 0x08 }, + }, +}; +EXPORT_SYMBOL(dvb_pll_unknown_1); + +/* ----------------------------------------------------------- */ +/* code */ + +static int debug = 0; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "enable verbose debug messages"); + +int dvb_pll_configure(struct dvb_pll_desc *desc, u8 *buf, + u32 freq, int bandwidth) +{ + u32 div; + int i; + + if (freq != 0 && (freq < desc->min || freq > desc->max)) + return -EINVAL; + + for (i = 0; i < desc->count; i++) { + if (freq > desc->entries[i].limit) + continue; + break; + } + if (debug) + printk("pll: %s: freq=%d bw=%d | i=%d/%d\n", + desc->name, freq, bandwidth, i, desc->count); + BUG_ON(i == desc->count); + + div = (freq + desc->entries[i].offset) / desc->entries[i].stepsize; + buf[0] = div >> 8; + buf[1] = div & 0xff; + buf[2] = desc->entries[i].cb1; + buf[3] = desc->entries[i].cb2; + + if (desc->setbw) + desc->setbw(buf, bandwidth); + + if (debug) + printk("pll: %s: div=%d | buf=0x%02x,0x%02x,0x%02x,0x%02x\n", + desc->name, div, buf[0], buf[1], buf[2], buf[3]); + + return 0; +} +EXPORT_SYMBOL(dvb_pll_configure); + +MODULE_DESCRIPTION("dvb pll library"); +MODULE_AUTHOR("Gerd Knorr"); +MODULE_LICENSE("GPL"); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/dvb/frontends/dvb-pll.h b/drivers/media/dvb/frontends/dvb-pll.h new file mode 100644 index 000000000000..016c794a5677 --- /dev/null +++ b/drivers/media/dvb/frontends/dvb-pll.h @@ -0,0 +1,34 @@ +/* + * $Id: dvb-pll.h,v 1.2 2005/02/10 11:43:41 kraxel Exp $ + */ + +struct dvb_pll_desc { + char *name; + u32 min; + u32 max; + void (*setbw)(u8 *buf, int bandwidth); + int count; + struct { + u32 limit; + u32 offset; + u32 stepsize; + u8 cb1; + u8 cb2; + } entries[9]; +}; + +extern struct dvb_pll_desc dvb_pll_thomson_dtt7579; +extern struct dvb_pll_desc dvb_pll_thomson_dtt759x; +extern struct dvb_pll_desc dvb_pll_thomson_dtt7610; +extern struct dvb_pll_desc dvb_pll_lg_z201; +extern struct dvb_pll_desc dvb_pll_unknown_1; + +int dvb_pll_configure(struct dvb_pll_desc *desc, u8 *buf, + u32 freq, int bandwidth); + +/* + * Local variables: + * c-basic-offset: 8 + * compile-command: "make DVB=1" + * End: + */ diff --git a/drivers/media/dvb/frontends/dvb_dummy_fe.c b/drivers/media/dvb/frontends/dvb_dummy_fe.c new file mode 100644 index 000000000000..c05a9b05600c --- /dev/null +++ b/drivers/media/dvb/frontends/dvb_dummy_fe.c @@ -0,0 +1,279 @@ +/* + * Driver for Dummy Frontend + * + * Written by Emard <emard@softhome.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.= + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> + +#include "dvb_frontend.h" +#include "dvb_dummy_fe.h" + + +struct dvb_dummy_fe_state { + struct dvb_frontend_ops ops; + struct dvb_frontend frontend; +}; + + +static int dvb_dummy_fe_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + *status = FE_HAS_SIGNAL + | FE_HAS_CARRIER + | FE_HAS_VITERBI + | FE_HAS_SYNC + | FE_HAS_LOCK; + + return 0; +} + +static int dvb_dummy_fe_read_ber(struct dvb_frontend* fe, u32* ber) +{ + *ber = 0; + return 0; +} + +static int dvb_dummy_fe_read_signal_strength(struct dvb_frontend* fe, u16* strength) +{ + *strength = 0; + return 0; +} + +static int dvb_dummy_fe_read_snr(struct dvb_frontend* fe, u16* snr) +{ + *snr = 0; + return 0; +} + +static int dvb_dummy_fe_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + *ucblocks = 0; + return 0; +} + +static int dvb_dummy_fe_get_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + return 0; +} + +static int dvb_dummy_fe_set_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + return 0; +} + +static int dvb_dummy_fe_sleep(struct dvb_frontend* fe) +{ + return 0; +} + +static int dvb_dummy_fe_init(struct dvb_frontend* fe) +{ + return 0; +} + +static int dvb_dummy_fe_set_tone(struct dvb_frontend* fe, fe_sec_tone_mode_t tone) +{ + return 0; +} + +static int dvb_dummy_fe_set_voltage(struct dvb_frontend* fe, fe_sec_voltage_t voltage) +{ + return 0; +} + +static void dvb_dummy_fe_release(struct dvb_frontend* fe) +{ + struct dvb_dummy_fe_state* state = (struct dvb_dummy_fe_state*) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops dvb_dummy_fe_ofdm_ops; + +struct dvb_frontend* dvb_dummy_fe_ofdm_attach(void) +{ + struct dvb_dummy_fe_state* state = NULL; + + /* allocate memory for the internal state */ + state = (struct dvb_dummy_fe_state*) kmalloc(sizeof(struct dvb_dummy_fe_state), GFP_KERNEL); + if (state == NULL) goto error; + + /* setup the state */ + memcpy(&state->ops, &dvb_dummy_fe_ofdm_ops, sizeof(struct dvb_frontend_ops)); + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops dvb_dummy_fe_qpsk_ops; + +struct dvb_frontend* dvb_dummy_fe_qpsk_attach() +{ + struct dvb_dummy_fe_state* state = NULL; + + /* allocate memory for the internal state */ + state = (struct dvb_dummy_fe_state*) kmalloc(sizeof(struct dvb_dummy_fe_state), GFP_KERNEL); + if (state == NULL) goto error; + + /* setup the state */ + memcpy(&state->ops, &dvb_dummy_fe_qpsk_ops, sizeof(struct dvb_frontend_ops)); + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + if (state) kfree(state); + return NULL; +} + +static struct dvb_frontend_ops dvb_dummy_fe_qam_ops; + +struct dvb_frontend* dvb_dummy_fe_qam_attach() +{ + struct dvb_dummy_fe_state* state = NULL; + + /* allocate memory for the internal state */ + state = (struct dvb_dummy_fe_state*) kmalloc(sizeof(struct dvb_dummy_fe_state), GFP_KERNEL); + if (state == NULL) goto error; + + /* setup the state */ + memcpy(&state->ops, &dvb_dummy_fe_qam_ops, sizeof(struct dvb_frontend_ops)); + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + if (state) kfree(state); + return NULL; +} + +static struct dvb_frontend_ops dvb_dummy_fe_ofdm_ops = { + + .info = { + .name = "Dummy DVB-T", + .type = FE_OFDM, + .frequency_min = 0, + .frequency_max = 863250000, + .frequency_stepsize = 62500, + .caps = 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_8_9 | FE_CAN_FEC_AUTO | + FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_HIERARCHY_AUTO, + }, + + .release = dvb_dummy_fe_release, + + .init = dvb_dummy_fe_init, + .sleep = dvb_dummy_fe_sleep, + + .set_frontend = dvb_dummy_fe_set_frontend, + .get_frontend = dvb_dummy_fe_get_frontend, + + .read_status = dvb_dummy_fe_read_status, + .read_ber = dvb_dummy_fe_read_ber, + .read_signal_strength = dvb_dummy_fe_read_signal_strength, + .read_snr = dvb_dummy_fe_read_snr, + .read_ucblocks = dvb_dummy_fe_read_ucblocks, +}; + +static struct dvb_frontend_ops dvb_dummy_fe_qam_ops = { + + .info = { + .name = "Dummy DVB-C", + .type = FE_QAM, + .frequency_stepsize = 62500, + .frequency_min = 51000000, + .frequency_max = 858000000, + .symbol_rate_min = (57840000/2)/64, /* SACLK/64 == (XIN/2)/64 */ + .symbol_rate_max = (57840000/2)/4, /* SACLK/4 */ + .caps = FE_CAN_QAM_16 | FE_CAN_QAM_32 | FE_CAN_QAM_64 | + FE_CAN_QAM_128 | FE_CAN_QAM_256 | + FE_CAN_FEC_AUTO | FE_CAN_INVERSION_AUTO + }, + + .release = dvb_dummy_fe_release, + + .init = dvb_dummy_fe_init, + .sleep = dvb_dummy_fe_sleep, + + .set_frontend = dvb_dummy_fe_set_frontend, + .get_frontend = dvb_dummy_fe_get_frontend, + + .read_status = dvb_dummy_fe_read_status, + .read_ber = dvb_dummy_fe_read_ber, + .read_signal_strength = dvb_dummy_fe_read_signal_strength, + .read_snr = dvb_dummy_fe_read_snr, + .read_ucblocks = dvb_dummy_fe_read_ucblocks, +}; + +static struct dvb_frontend_ops dvb_dummy_fe_qpsk_ops = { + + .info = { + .name = "Dummy DVB-S", + .type = FE_QPSK, + .frequency_min = 950000, + .frequency_max = 2150000, + .frequency_stepsize = 250, /* kHz for QPSK frontends */ + .frequency_tolerance = 29500, + .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_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | + FE_CAN_QPSK + }, + + .release = dvb_dummy_fe_release, + + .init = dvb_dummy_fe_init, + .sleep = dvb_dummy_fe_sleep, + + .set_frontend = dvb_dummy_fe_set_frontend, + .get_frontend = dvb_dummy_fe_get_frontend, + + .read_status = dvb_dummy_fe_read_status, + .read_ber = dvb_dummy_fe_read_ber, + .read_signal_strength = dvb_dummy_fe_read_signal_strength, + .read_snr = dvb_dummy_fe_read_snr, + .read_ucblocks = dvb_dummy_fe_read_ucblocks, + + .set_voltage = dvb_dummy_fe_set_voltage, + .set_tone = dvb_dummy_fe_set_tone, +}; + +MODULE_DESCRIPTION("DVB DUMMY Frontend"); +MODULE_AUTHOR("Emard"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(dvb_dummy_fe_ofdm_attach); +EXPORT_SYMBOL(dvb_dummy_fe_qam_attach); +EXPORT_SYMBOL(dvb_dummy_fe_qpsk_attach); diff --git a/drivers/media/dvb/frontends/dvb_dummy_fe.h b/drivers/media/dvb/frontends/dvb_dummy_fe.h new file mode 100644 index 000000000000..8210f19d56ce --- /dev/null +++ b/drivers/media/dvb/frontends/dvb_dummy_fe.h @@ -0,0 +1,32 @@ +/* + * Driver for Dummy Frontend + * + * Written by Emard <emard@softhome.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.= + */ + +#ifndef DVB_DUMMY_FE_H +#define DVB_DUMMY_FE_H + +#include <linux/dvb/frontend.h> +#include "dvb_frontend.h" + +extern struct dvb_frontend* dvb_dummy_fe_ofdm_attach(void); +extern struct dvb_frontend* dvb_dummy_fe_qpsk_attach(void); +extern struct dvb_frontend* dvb_dummy_fe_qam_attach(void); + +#endif // DVB_DUMMY_FE_H diff --git a/drivers/media/dvb/frontends/l64781.c b/drivers/media/dvb/frontends/l64781.c new file mode 100644 index 000000000000..9ac95de9834d --- /dev/null +++ b/drivers/media/dvb/frontends/l64781.c @@ -0,0 +1,602 @@ +/* + driver for LSI L64781 COFDM demodulator + + Copyright (C) 2001 Holger Waechtler for Convergence Integrated Media GmbH + Marko Kohtala <marko.kohtala@luukku.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/string.h> +#include <linux/slab.h> +#include "dvb_frontend.h" +#include "l64781.h" + + +struct l64781_state { + struct i2c_adapter* i2c; + struct dvb_frontend_ops ops; + const struct l64781_config* config; + struct dvb_frontend frontend; + + /* private demodulator data */ + int first:1; +}; + +#define dprintk(args...) \ + do { \ + if (debug) printk(KERN_DEBUG "l64781: " args); \ + } while (0) + +static int debug; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + + +static int l64781_writereg (struct l64781_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 }; + + if ((ret = i2c_transfer(state->i2c, &msg, 1)) != 1) + dprintk ("%s: write_reg error (reg == %02x) = %02x!\n", + __FUNCTION__, reg, ret); + + return (ret != 1) ? -1 : 0; +} + +static int l64781_readreg (struct l64781_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) return ret; + + return b1[0]; +} + +static void apply_tps (struct l64781_state* state) +{ + l64781_writereg (state, 0x2a, 0x00); + l64781_writereg (state, 0x2a, 0x01); + + /* This here is a little bit questionable because it enables + the automatic update of TPS registers. I think we'd need to + handle the IRQ from FE to update some other registers as + well, or at least implement some magic to tuning to correct + to the TPS received from transmission. */ + l64781_writereg (state, 0x2a, 0x02); +} + + +static void reset_afc (struct l64781_state* state) +{ + /* Set AFC stall for the AFC_INIT_FRQ setting, TIM_STALL for + timing offset */ + l64781_writereg (state, 0x07, 0x9e); /* stall AFC */ + l64781_writereg (state, 0x08, 0); /* AFC INIT FREQ */ + l64781_writereg (state, 0x09, 0); + l64781_writereg (state, 0x0a, 0); + l64781_writereg (state, 0x07, 0x8e); + l64781_writereg (state, 0x0e, 0); /* AGC gain to zero in beginning */ + l64781_writereg (state, 0x11, 0x80); /* stall TIM */ + l64781_writereg (state, 0x10, 0); /* TIM_OFFSET_LSB */ + l64781_writereg (state, 0x12, 0); + l64781_writereg (state, 0x13, 0); + l64781_writereg (state, 0x11, 0x00); +} + +static int reset_and_configure (struct l64781_state* state) +{ + u8 buf [] = { 0x06 }; + struct i2c_msg msg = { .addr = 0x00, .flags = 0, .buf = buf, .len = 1 }; + // NOTE: this is correct in writing to address 0x00 + + return (i2c_transfer(state->i2c, &msg, 1) == 1) ? 0 : -ENODEV; +} + +static int apply_frontend_param (struct dvb_frontend* fe, struct dvb_frontend_parameters *param) +{ + struct l64781_state* state = (struct l64781_state*) fe->demodulator_priv; + /* The coderates for FEC_NONE, FEC_4_5 and FEC_FEC_6_7 are arbitrary */ + static const u8 fec_tab[] = { 7, 0, 1, 2, 9, 3, 10, 4 }; + /* QPSK, QAM_16, QAM_64 */ + static const u8 qam_tab [] = { 2, 4, 0, 6 }; + static const u8 bw_tab [] = { 8, 7, 6 }; /* 8Mhz, 7MHz, 6MHz */ + static const u8 guard_tab [] = { 1, 2, 4, 8 }; + /* The Grundig 29504-401.04 Tuner comes with 18.432MHz crystal. */ + static const u32 ppm = 8000; + struct dvb_ofdm_parameters *p = ¶m->u.ofdm; + u32 ddfs_offset_fixed; +/* u32 ddfs_offset_variable = 0x6000-((1000000UL+ppm)/ */ +/* bw_tab[p->bandWidth]<<10)/15625; */ + u32 init_freq; + u32 spi_bias; + u8 val0x04; + u8 val0x05; + u8 val0x06; + int bw = p->bandwidth - BANDWIDTH_8_MHZ; + + state->config->pll_set(fe, param); + + if (param->inversion != INVERSION_ON && + param->inversion != INVERSION_OFF) + return -EINVAL; + + if (bw < 0 || bw > 2) + return -EINVAL; + + if (p->code_rate_HP != FEC_1_2 && p->code_rate_HP != FEC_2_3 && + p->code_rate_HP != FEC_3_4 && p->code_rate_HP != FEC_5_6 && + p->code_rate_HP != FEC_7_8) + return -EINVAL; + + if (p->hierarchy_information != HIERARCHY_NONE && + (p->code_rate_LP != FEC_1_2 && p->code_rate_LP != FEC_2_3 && + p->code_rate_LP != FEC_3_4 && p->code_rate_LP != FEC_5_6 && + p->code_rate_LP != FEC_7_8)) + return -EINVAL; + + if (p->constellation != QPSK && p->constellation != QAM_16 && + p->constellation != QAM_64) + return -EINVAL; + + if (p->transmission_mode != TRANSMISSION_MODE_2K && + p->transmission_mode != TRANSMISSION_MODE_8K) + return -EINVAL; + + if (p->guard_interval < GUARD_INTERVAL_1_32 || + p->guard_interval > GUARD_INTERVAL_1_4) + return -EINVAL; + + if (p->hierarchy_information < HIERARCHY_NONE || + p->hierarchy_information > HIERARCHY_4) + return -EINVAL; + + ddfs_offset_fixed = 0x4000-(ppm<<16)/bw_tab[p->bandwidth]/1000000; + + /* This works up to 20000 ppm, it overflows if too large ppm! */ + init_freq = (((8UL<<25) + (8UL<<19) / 25*ppm / (15625/25)) / + bw_tab[p->bandwidth] & 0xFFFFFF); + + /* SPI bias calculation is slightly modified to fit in 32bit */ + /* will work for high ppm only... */ + spi_bias = 378 * (1 << 10); + spi_bias *= 16; + spi_bias *= bw_tab[p->bandwidth]; + spi_bias *= qam_tab[p->constellation]; + spi_bias /= p->code_rate_HP + 1; + spi_bias /= (guard_tab[p->guard_interval] + 32); + spi_bias *= 1000ULL; + spi_bias /= 1000ULL + ppm/1000; + spi_bias *= p->code_rate_HP; + + val0x04 = (p->transmission_mode << 2) | p->guard_interval; + val0x05 = fec_tab[p->code_rate_HP]; + + if (p->hierarchy_information != HIERARCHY_NONE) + val0x05 |= (p->code_rate_LP - FEC_1_2) << 3; + + val0x06 = (p->hierarchy_information << 2) | p->constellation; + + l64781_writereg (state, 0x04, val0x04); + l64781_writereg (state, 0x05, val0x05); + l64781_writereg (state, 0x06, val0x06); + + reset_afc (state); + + /* Technical manual section 2.6.1, TIM_IIR_GAIN optimal values */ + l64781_writereg (state, 0x15, + p->transmission_mode == TRANSMISSION_MODE_2K ? 1 : 3); + l64781_writereg (state, 0x16, init_freq & 0xff); + l64781_writereg (state, 0x17, (init_freq >> 8) & 0xff); + l64781_writereg (state, 0x18, (init_freq >> 16) & 0xff); + + l64781_writereg (state, 0x1b, spi_bias & 0xff); + l64781_writereg (state, 0x1c, (spi_bias >> 8) & 0xff); + l64781_writereg (state, 0x1d, ((spi_bias >> 16) & 0x7f) | + (param->inversion == INVERSION_ON ? 0x80 : 0x00)); + + l64781_writereg (state, 0x22, ddfs_offset_fixed & 0xff); + l64781_writereg (state, 0x23, (ddfs_offset_fixed >> 8) & 0x3f); + + l64781_readreg (state, 0x00); /* clear interrupt registers... */ + l64781_readreg (state, 0x01); /* dto. */ + + apply_tps (state); + + return 0; +} + +static int get_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters* param) +{ + struct l64781_state* state = (struct l64781_state*) fe->demodulator_priv; + int tmp; + + + tmp = l64781_readreg(state, 0x04); + switch(tmp & 3) { + case 0: + param->u.ofdm.guard_interval = GUARD_INTERVAL_1_32; + break; + case 1: + param->u.ofdm.guard_interval = GUARD_INTERVAL_1_16; + break; + case 2: + param->u.ofdm.guard_interval = GUARD_INTERVAL_1_8; + break; + case 3: + param->u.ofdm.guard_interval = GUARD_INTERVAL_1_4; + break; + } + switch((tmp >> 2) & 3) { + case 0: + param->u.ofdm.transmission_mode = TRANSMISSION_MODE_2K; + break; + case 1: + param->u.ofdm.transmission_mode = TRANSMISSION_MODE_8K; + break; + default: + printk("Unexpected value for transmission_mode\n"); + } + + + + tmp = l64781_readreg(state, 0x05); + switch(tmp & 7) { + case 0: + param->u.ofdm.code_rate_HP = FEC_1_2; + break; + case 1: + param->u.ofdm.code_rate_HP = FEC_2_3; + break; + case 2: + param->u.ofdm.code_rate_HP = FEC_3_4; + break; + case 3: + param->u.ofdm.code_rate_HP = FEC_5_6; + break; + case 4: + param->u.ofdm.code_rate_HP = FEC_7_8; + break; + default: + printk("Unexpected value for code_rate_HP\n"); + } + switch((tmp >> 3) & 7) { + case 0: + param->u.ofdm.code_rate_LP = FEC_1_2; + break; + case 1: + param->u.ofdm.code_rate_LP = FEC_2_3; + break; + case 2: + param->u.ofdm.code_rate_LP = FEC_3_4; + break; + case 3: + param->u.ofdm.code_rate_LP = FEC_5_6; + break; + case 4: + param->u.ofdm.code_rate_LP = FEC_7_8; + break; + default: + printk("Unexpected value for code_rate_LP\n"); + } + + + tmp = l64781_readreg(state, 0x06); + switch(tmp & 3) { + case 0: + param->u.ofdm.constellation = QPSK; + break; + case 1: + param->u.ofdm.constellation = QAM_16; + break; + case 2: + param->u.ofdm.constellation = QAM_64; + break; + default: + printk("Unexpected value for constellation\n"); + } + switch((tmp >> 2) & 7) { + case 0: + param->u.ofdm.hierarchy_information = HIERARCHY_NONE; + break; + case 1: + param->u.ofdm.hierarchy_information = HIERARCHY_1; + break; + case 2: + param->u.ofdm.hierarchy_information = HIERARCHY_2; + break; + case 3: + param->u.ofdm.hierarchy_information = HIERARCHY_4; + break; + default: + printk("Unexpected value for hierarchy\n"); + } + + + tmp = l64781_readreg (state, 0x1d); + param->inversion = (tmp & 0x80) ? INVERSION_ON : INVERSION_OFF; + + tmp = (int) (l64781_readreg (state, 0x08) | + (l64781_readreg (state, 0x09) << 8) | + (l64781_readreg (state, 0x0a) << 16)); + param->frequency += tmp; + + return 0; +} + +static int l64781_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct l64781_state* state = (struct l64781_state*) fe->demodulator_priv; + int sync = l64781_readreg (state, 0x32); + int gain = l64781_readreg (state, 0x0e); + + l64781_readreg (state, 0x00); /* clear interrupt registers... */ + l64781_readreg (state, 0x01); /* dto. */ + + *status = 0; + + if (gain > 5) + *status |= FE_HAS_SIGNAL; + + if (sync & 0x02) /* VCXO locked, this criteria should be ok */ + *status |= FE_HAS_CARRIER; + + if (sync & 0x20) + *status |= FE_HAS_VITERBI; + + if (sync & 0x40) + *status |= FE_HAS_SYNC; + + if (sync == 0x7f) + *status |= FE_HAS_LOCK; + + return 0; +} + +static int l64781_read_ber(struct dvb_frontend* fe, u32* ber) +{ + struct l64781_state* state = (struct l64781_state*) fe->demodulator_priv; + + /* XXX FIXME: set up counting period (reg 0x26...0x28) + */ + *ber = l64781_readreg (state, 0x39) + | (l64781_readreg (state, 0x3a) << 8); + + return 0; +} + +static int l64781_read_signal_strength(struct dvb_frontend* fe, u16* signal_strength) +{ + struct l64781_state* state = (struct l64781_state*) fe->demodulator_priv; + + u8 gain = l64781_readreg (state, 0x0e); + *signal_strength = (gain << 8) | gain; + + return 0; +} + +static int l64781_read_snr(struct dvb_frontend* fe, u16* snr) +{ + struct l64781_state* state = (struct l64781_state*) fe->demodulator_priv; + + u8 avg_quality = 0xff - l64781_readreg (state, 0x33); + *snr = (avg_quality << 8) | avg_quality; /* not exact, but...*/ + + return 0; +} + +static int l64781_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + struct l64781_state* state = (struct l64781_state*) fe->demodulator_priv; + + *ucblocks = l64781_readreg (state, 0x37) + | (l64781_readreg (state, 0x38) << 8); + + return 0; +} + +static int l64781_sleep(struct dvb_frontend* fe) +{ + struct l64781_state* state = (struct l64781_state*) fe->demodulator_priv; + + /* Power down */ + return l64781_writereg (state, 0x3e, 0x5a); +} + +static int l64781_init(struct dvb_frontend* fe) +{ + struct l64781_state* state = (struct l64781_state*) fe->demodulator_priv; + + reset_and_configure (state); + + /* Power up */ + l64781_writereg (state, 0x3e, 0xa5); + + /* Reset hard */ + l64781_writereg (state, 0x2a, 0x04); + l64781_writereg (state, 0x2a, 0x00); + + /* Set tuner specific things */ + /* AFC_POL, set also in reset_afc */ + l64781_writereg (state, 0x07, 0x8e); + + /* Use internal ADC */ + l64781_writereg (state, 0x0b, 0x81); + + /* AGC loop gain, and polarity is positive */ + l64781_writereg (state, 0x0c, 0x84); + + /* Internal ADC outputs two's complement */ + l64781_writereg (state, 0x0d, 0x8c); + + /* With ppm=8000, it seems the DTR_SENSITIVITY will result in + value of 2 with all possible bandwidths and guard + intervals, which is the initial value anyway. */ + /*l64781_writereg (state, 0x19, 0x92);*/ + + /* Everything is two's complement, soft bit and CSI_OUT too */ + l64781_writereg (state, 0x1e, 0x09); + + if (state->config->pll_init) state->config->pll_init(fe); + + /* delay a bit after first init attempt */ + if (state->first) { + state->first = 0; + msleep(200); + } + + return 0; +} + +static int l64781_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* fesettings) +{ + fesettings->min_delay_ms = 200; + fesettings->step_size = 166667; + fesettings->max_drift = 166667*2; + return 0; +} + +static void l64781_release(struct dvb_frontend* fe) +{ + struct l64781_state* state = (struct l64781_state*) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops l64781_ops; + +struct dvb_frontend* l64781_attach(const struct l64781_config* config, + struct i2c_adapter* i2c) +{ + struct l64781_state* state = NULL; + int reg0x3e = -1; + u8 b0 [] = { 0x1a }; + u8 b1 [] = { 0x00 }; + struct i2c_msg msg [] = { { .addr = config->demod_address, .flags = 0, .buf = b0, .len = 1 }, + { .addr = config->demod_address, .flags = I2C_M_RD, .buf = b1, .len = 1 } }; + + /* allocate memory for the internal state */ + state = (struct l64781_state*) kmalloc(sizeof(struct l64781_state), GFP_KERNEL); + if (state == NULL) goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + memcpy(&state->ops, &l64781_ops, sizeof(struct dvb_frontend_ops)); + state->first = 1; + + /** + * the L64781 won't show up before we send the reset_and_configure() + * broadcast. If nothing responds there is no L64781 on the bus... + */ + if (reset_and_configure(state) < 0) { + dprintk("No response to reset and configure broadcast...\n"); + goto error; + } + + /* The chip always responds to reads */ + if (i2c_transfer(state->i2c, msg, 2) != 2) { + dprintk("No response to read on I2C bus\n"); + goto error; + } + + /* Save current register contents for bailout */ + reg0x3e = l64781_readreg(state, 0x3e); + + /* Reading the POWER_DOWN register always returns 0 */ + if (reg0x3e != 0) { + dprintk("Device doesn't look like L64781\n"); + goto error; + } + + /* Turn the chip off */ + l64781_writereg (state, 0x3e, 0x5a); + + /* Responds to all reads with 0 */ + if (l64781_readreg(state, 0x1a) != 0) { + dprintk("Read 1 returned unexpcted value\n"); + goto error; + } + + /* Turn the chip on */ + l64781_writereg (state, 0x3e, 0xa5); + + /* Responds with register default value */ + if (l64781_readreg(state, 0x1a) != 0xa1) { + dprintk("Read 2 returned unexpcted value\n"); + goto error; + } + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + if (reg0x3e >= 0) l64781_writereg (state, 0x3e, reg0x3e); /* restore reg 0x3e */ + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops l64781_ops = { + + .info = { + .name = "LSI L64781 DVB-T", + .type = FE_OFDM, + /* .frequency_min = ???,*/ + /* .frequency_max = ???,*/ + .frequency_stepsize = 166666, + /* .frequency_tolerance = ???,*/ + /* .symbol_rate_tolerance = ???,*/ + .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_QAM_16 | FE_CAN_QAM_64 | + FE_CAN_MUTE_TS + }, + + .release = l64781_release, + + .init = l64781_init, + .sleep = l64781_sleep, + + .set_frontend = apply_frontend_param, + .get_frontend = get_frontend, + .get_tune_settings = l64781_get_tune_settings, + + .read_status = l64781_read_status, + .read_ber = l64781_read_ber, + .read_signal_strength = l64781_read_signal_strength, + .read_snr = l64781_read_snr, + .read_ucblocks = l64781_read_ucblocks, +}; + +MODULE_DESCRIPTION("LSI L64781 DVB-T Demodulator driver"); +MODULE_AUTHOR("Holger Waechtler, Marko Kohtala"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(l64781_attach); diff --git a/drivers/media/dvb/frontends/l64781.h b/drivers/media/dvb/frontends/l64781.h new file mode 100644 index 000000000000..7e30fb0fdfa7 --- /dev/null +++ b/drivers/media/dvb/frontends/l64781.h @@ -0,0 +1,42 @@ +/* + driver for LSI L64781 COFDM demodulator + + Copyright (C) 2001 Holger Waechtler for Convergence Integrated Media GmbH + Marko Kohtala <marko.kohtala@luukku.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 L64781_H +#define L64781_H + +#include <linux/dvb/frontend.h> + +struct l64781_config +{ + /* the demodulator's i2c address */ + u8 demod_address; + + /* PLL maintenance */ + int (*pll_init)(struct dvb_frontend* fe); + int (*pll_set)(struct dvb_frontend* fe, struct dvb_frontend_parameters* params); +}; + + +extern struct dvb_frontend* l64781_attach(const struct l64781_config* config, + struct i2c_adapter* i2c); + +#endif // L64781_H diff --git a/drivers/media/dvb/frontends/mt312.c b/drivers/media/dvb/frontends/mt312.c new file mode 100644 index 000000000000..176a22e3441b --- /dev/null +++ b/drivers/media/dvb/frontends/mt312.c @@ -0,0 +1,729 @@ +/* + Driver for Zarlink VP310/MT312 Satellite Channel Decoder + + Copyright (C) 2003 Andreas Oberritter <obi@linuxtv.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + References: + http://products.zarlink.com/product_profiles/MT312.htm + http://products.zarlink.com/product_profiles/SL1935.htm +*/ + +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> + +#include "dvb_frontend.h" +#include "mt312_priv.h" +#include "mt312.h" + + +struct mt312_state { + struct i2c_adapter* i2c; + struct dvb_frontend_ops ops; + /* configuration settings */ + const struct mt312_config* config; + struct dvb_frontend frontend; + + u8 id; + u8 frequency; +}; + +static int debug; +#define dprintk(args...) \ + do { \ + if (debug) printk(KERN_DEBUG "mt312: " args); \ + } while (0) + +#define MT312_SYS_CLK 90000000UL /* 90 MHz */ +#define MT312_LPOWER_SYS_CLK 60000000UL /* 60 MHz */ +#define MT312_PLL_CLK 10000000UL /* 10 MHz */ + +static int mt312_read(struct mt312_state* state, const enum mt312_reg_addr reg, + void *buf, const size_t count) +{ + int ret; + struct i2c_msg msg[2]; + u8 regbuf[1] = { reg }; + + msg[0].addr = state->config->demod_address; + msg[0].flags = 0; + msg[0].buf = regbuf; + msg[0].len = 1; + msg[1].addr = state->config->demod_address; + msg[1].flags = I2C_M_RD; + msg[1].buf = buf; + msg[1].len = count; + + ret = i2c_transfer(state->i2c, msg, 2); + + if (ret != 2) { + printk(KERN_ERR "%s: ret == %d\n", __FUNCTION__, ret); + return -EREMOTEIO; + } + + if(debug) { + int i; + dprintk("R(%d):", reg & 0x7f); + for (i = 0; i < count; i++) + printk(" %02x", ((const u8 *) buf)[i]); + printk("\n"); + } + + return 0; +} + +static int mt312_write(struct mt312_state* state, const enum mt312_reg_addr reg, + const void *src, const size_t count) +{ + int ret; + u8 buf[count + 1]; + struct i2c_msg msg; + + if(debug) { + int i; + dprintk("W(%d):", reg & 0x7f); + for (i = 0; i < count; i++) + printk(" %02x", ((const u8 *) src)[i]); + printk("\n"); + } + + buf[0] = reg; + memcpy(&buf[1], src, count); + + msg.addr = state->config->demod_address; + msg.flags = 0; + msg.buf = buf; + msg.len = count + 1; + + ret = i2c_transfer(state->i2c, &msg, 1); + + if (ret != 1) { + dprintk("%s: ret == %d\n", __FUNCTION__, ret); + return -EREMOTEIO; + } + + return 0; +} + +static inline int mt312_readreg(struct mt312_state* state, + const enum mt312_reg_addr reg, u8 *val) +{ + return mt312_read(state, reg, val, 1); +} + +static inline int mt312_writereg(struct mt312_state* state, + const enum mt312_reg_addr reg, const u8 val) +{ + return mt312_write(state, reg, &val, 1); +} + +static inline u32 mt312_div(u32 a, u32 b) +{ + return (a + (b / 2)) / b; +} + +static int mt312_reset(struct mt312_state* state, const u8 full) +{ + return mt312_writereg(state, RESET, full ? 0x80 : 0x40); +} + +static int mt312_get_inversion(struct mt312_state* state, + fe_spectral_inversion_t *i) +{ + int ret; + u8 vit_mode; + + if ((ret = mt312_readreg(state, VIT_MODE, &vit_mode)) < 0) + return ret; + + if (vit_mode & 0x80) /* auto inversion was used */ + *i = (vit_mode & 0x40) ? INVERSION_ON : INVERSION_OFF; + + return 0; +} + +static int mt312_get_symbol_rate(struct mt312_state* state, u32 *sr) +{ + int ret; + u8 sym_rate_h; + u8 dec_ratio; + u16 sym_rat_op; + u16 monitor; + u8 buf[2]; + + if ((ret = mt312_readreg(state, SYM_RATE_H, &sym_rate_h)) < 0) + return ret; + + if (sym_rate_h & 0x80) { /* symbol rate search was used */ + if ((ret = mt312_writereg(state, MON_CTRL, 0x03)) < 0) + return ret; + + if ((ret = mt312_read(state, MONITOR_H, buf, sizeof(buf))) < 0) + return ret; + + monitor = (buf[0] << 8) | buf[1]; + + dprintk(KERN_DEBUG "sr(auto) = %u\n", + mt312_div(monitor * 15625, 4)); + } else { + if ((ret = mt312_writereg(state, MON_CTRL, 0x05)) < 0) + return ret; + + if ((ret = mt312_read(state, MONITOR_H, buf, sizeof(buf))) < 0) + return ret; + + dec_ratio = ((buf[0] >> 5) & 0x07) * 32; + + if ((ret = mt312_read(state, SYM_RAT_OP_H, buf, sizeof(buf))) < 0) + return ret; + + sym_rat_op = (buf[0] << 8) | buf[1]; + + dprintk(KERN_DEBUG "sym_rat_op=%d dec_ratio=%d\n", + sym_rat_op, dec_ratio); + dprintk(KERN_DEBUG "*sr(manual) = %lu\n", + (((MT312_PLL_CLK * 8192) / (sym_rat_op + 8192)) * + 2) - dec_ratio); + } + + return 0; +} + +static int mt312_get_code_rate(struct mt312_state* state, fe_code_rate_t *cr) +{ + const fe_code_rate_t fec_tab[8] = + { FEC_1_2, FEC_2_3, FEC_3_4, FEC_5_6, FEC_6_7, FEC_7_8, + FEC_AUTO, FEC_AUTO }; + + int ret; + u8 fec_status; + + if ((ret = mt312_readreg(state, FEC_STATUS, &fec_status)) < 0) + return ret; + + *cr = fec_tab[(fec_status >> 4) & 0x07]; + + return 0; +} + +static int mt312_initfe(struct dvb_frontend* fe) +{ + struct mt312_state *state = (struct mt312_state*) fe->demodulator_priv; + int ret; + u8 buf[2]; + + /* wake up */ + if ((ret = mt312_writereg(state, CONFIG, (state->frequency == 60 ? 0x88 : 0x8c))) < 0) + return ret; + + /* wait at least 150 usec */ + udelay(150); + + /* full reset */ + if ((ret = mt312_reset(state, 1)) < 0) + return ret; + +// Per datasheet, write correct values. 09/28/03 ACCJr. +// If we don't do this, we won't get FE_HAS_VITERBI in the VP310. + { + u8 buf_def[8]={0x14, 0x12, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00}; + + if ((ret = mt312_write(state, VIT_SETUP, buf_def, sizeof(buf_def))) < 0) + return ret; + } + + /* SYS_CLK */ + buf[0] = mt312_div((state->frequency == 60 ? MT312_LPOWER_SYS_CLK : MT312_SYS_CLK) * 2, 1000000); + + /* DISEQC_RATIO */ + buf[1] = mt312_div(MT312_PLL_CLK, 15000 * 4); + + if ((ret = mt312_write(state, SYS_CLK, buf, sizeof(buf))) < 0) + return ret; + + if ((ret = mt312_writereg(state, SNR_THS_HIGH, 0x32)) < 0) + return ret; + + if ((ret = mt312_writereg(state, OP_CTRL, 0x53)) < 0) + return ret; + + /* TS_SW_LIM */ + buf[0] = 0x8c; + buf[1] = 0x98; + + if ((ret = mt312_write(state, TS_SW_LIM_L, buf, sizeof(buf))) < 0) + return ret; + + if ((ret = mt312_writereg(state, CS_SW_LIM, 0x69)) < 0) + return ret; + + if (state->config->pll_init) { + mt312_writereg(state, GPP_CTRL, 0x40); + state->config->pll_init(fe); + mt312_writereg(state, GPP_CTRL, 0x00); + } + + return 0; +} + +static int mt312_send_master_cmd(struct dvb_frontend* fe, + struct dvb_diseqc_master_cmd *c) +{ + struct mt312_state *state = (struct mt312_state*) fe->demodulator_priv; + int ret; + u8 diseqc_mode; + + if ((c->msg_len == 0) || (c->msg_len > sizeof(c->msg))) + return -EINVAL; + + if ((ret = mt312_readreg(state, DISEQC_MODE, &diseqc_mode)) < 0) + return ret; + + if ((ret = + mt312_write(state, (0x80 | DISEQC_INSTR), c->msg, c->msg_len)) < 0) + return ret; + + if ((ret = + mt312_writereg(state, DISEQC_MODE, + (diseqc_mode & 0x40) | ((c->msg_len - 1) << 3) + | 0x04)) < 0) + return ret; + + /* set DISEQC_MODE[2:0] to zero if a return message is expected */ + if (c->msg[0] & 0x02) + if ((ret = + mt312_writereg(state, DISEQC_MODE, (diseqc_mode & 0x40))) < 0) + return ret; + + return 0; +} + +static int mt312_send_burst(struct dvb_frontend* fe, const fe_sec_mini_cmd_t c) +{ + struct mt312_state *state = (struct mt312_state*) fe->demodulator_priv; + const u8 mini_tab[2] = { 0x02, 0x03 }; + + int ret; + u8 diseqc_mode; + + if (c > SEC_MINI_B) + return -EINVAL; + + if ((ret = mt312_readreg(state, DISEQC_MODE, &diseqc_mode)) < 0) + return ret; + + if ((ret = + mt312_writereg(state, DISEQC_MODE, + (diseqc_mode & 0x40) | mini_tab[c])) < 0) + return ret; + + return 0; +} + +static int mt312_set_tone(struct dvb_frontend* fe, const fe_sec_tone_mode_t t) +{ + struct mt312_state *state = (struct mt312_state*) fe->demodulator_priv; + const u8 tone_tab[2] = { 0x01, 0x00 }; + + int ret; + u8 diseqc_mode; + + if (t > SEC_TONE_OFF) + return -EINVAL; + + if ((ret = mt312_readreg(state, DISEQC_MODE, &diseqc_mode)) < 0) + return ret; + + if ((ret = + mt312_writereg(state, DISEQC_MODE, + (diseqc_mode & 0x40) | tone_tab[t])) < 0) + return ret; + + return 0; +} + +static int mt312_set_voltage(struct dvb_frontend* fe, const fe_sec_voltage_t v) +{ + struct mt312_state *state = (struct mt312_state*) fe->demodulator_priv; + const u8 volt_tab[3] = { 0x00, 0x40, 0x00 }; + + if (v > SEC_VOLTAGE_OFF) + return -EINVAL; + + return mt312_writereg(state, DISEQC_MODE, volt_tab[v]); +} + +static int mt312_read_status(struct dvb_frontend* fe, fe_status_t *s) +{ + struct mt312_state *state = (struct mt312_state*) fe->demodulator_priv; + int ret; + u8 status[3]; + + *s = 0; + + if ((ret = mt312_read(state, QPSK_STAT_H, status, sizeof(status))) < 0) + return ret; + + dprintk(KERN_DEBUG "QPSK_STAT_H: 0x%02x, QPSK_STAT_L: 0x%02x, FEC_STATUS: 0x%02x\n", status[0], status[1], status[2]); + + if (status[0] & 0xc0) + *s |= FE_HAS_SIGNAL; /* signal noise ratio */ + if (status[0] & 0x04) + *s |= FE_HAS_CARRIER; /* qpsk carrier lock */ + if (status[2] & 0x02) + *s |= FE_HAS_VITERBI; /* viterbi lock */ + if (status[2] & 0x04) + *s |= FE_HAS_SYNC; /* byte align lock */ + if (status[0] & 0x01) + *s |= FE_HAS_LOCK; /* qpsk lock */ + + return 0; +} + +static int mt312_read_ber(struct dvb_frontend* fe, u32 *ber) +{ + struct mt312_state *state = (struct mt312_state*) fe->demodulator_priv; + int ret; + u8 buf[3]; + + if ((ret = mt312_read(state, RS_BERCNT_H, buf, 3)) < 0) + return ret; + + *ber = ((buf[0] << 16) | (buf[1] << 8) | buf[2]) * 64; + + return 0; +} + +static int mt312_read_signal_strength(struct dvb_frontend* fe, u16 *signal_strength) +{ + struct mt312_state *state = (struct mt312_state*) fe->demodulator_priv; + int ret; + u8 buf[3]; + u16 agc; + s16 err_db; + + if ((ret = mt312_read(state, AGC_H, buf, sizeof(buf))) < 0) + return ret; + + agc = (buf[0] << 6) | (buf[1] >> 2); + err_db = (s16) (((buf[1] & 0x03) << 14) | buf[2] << 6) >> 6; + + *signal_strength = agc; + + dprintk(KERN_DEBUG "agc=%08x err_db=%hd\n", agc, err_db); + + return 0; +} + +static int mt312_read_snr(struct dvb_frontend* fe, u16 *snr) +{ + struct mt312_state *state = (struct mt312_state*) fe->demodulator_priv; + int ret; + u8 buf[2]; + + if ((ret = mt312_read(state, M_SNR_H, &buf, sizeof(buf))) < 0) + return ret; + + *snr = 0xFFFF - ((((buf[0] & 0x7f) << 8) | buf[1]) << 1); + + return 0; +} + +static int mt312_read_ucblocks(struct dvb_frontend* fe, u32 *ubc) +{ + struct mt312_state *state = (struct mt312_state*) fe->demodulator_priv; + int ret; + u8 buf[2]; + + if ((ret = mt312_read(state, RS_UBC_H, &buf, sizeof(buf))) < 0) + return ret; + + *ubc = (buf[0] << 8) | buf[1]; + + return 0; +} + +static int mt312_set_frontend(struct dvb_frontend* fe, + struct dvb_frontend_parameters *p) +{ + struct mt312_state *state = (struct mt312_state*) fe->demodulator_priv; + int ret; + u8 buf[5], config_val; + u16 sr; + + const u8 fec_tab[10] = + { 0x00, 0x01, 0x02, 0x04, 0x3f, 0x08, 0x10, 0x20, 0x3f, 0x3f }; + const u8 inv_tab[3] = { 0x00, 0x40, 0x80 }; + + dprintk("%s: Freq %d\n", __FUNCTION__, p->frequency); + + if ((p->frequency < fe->ops->info.frequency_min) + || (p->frequency > fe->ops->info.frequency_max)) + return -EINVAL; + + if ((p->inversion < INVERSION_OFF) + || (p->inversion > INVERSION_ON)) + return -EINVAL; + + if ((p->u.qpsk.symbol_rate < fe->ops->info.symbol_rate_min) + || (p->u.qpsk.symbol_rate > fe->ops->info.symbol_rate_max)) + return -EINVAL; + + if ((p->u.qpsk.fec_inner < FEC_NONE) + || (p->u.qpsk.fec_inner > FEC_AUTO)) + return -EINVAL; + + if ((p->u.qpsk.fec_inner == FEC_4_5) + || (p->u.qpsk.fec_inner == FEC_8_9)) + return -EINVAL; + + switch (state->id) { + case ID_VP310: + // For now we will do this only for the VP310. + // It should be better for the mt312 as well, but tunning will be slower. ACCJr 09/29/03 + if ((ret = mt312_readreg(state, CONFIG, &config_val) < 0)) + return ret; + if (p->u.qpsk.symbol_rate >= 30000000) //Note that 30MS/s should use 90MHz + { + if ((config_val & 0x0c) == 0x08) { //We are running 60MHz + state->frequency = 90; + if ((ret = mt312_initfe(fe)) < 0) + return ret; + } + } + else + { + if ((config_val & 0x0c) == 0x0C) { //We are running 90MHz + state->frequency = 60; + if ((ret = mt312_initfe(fe)) < 0) + return ret; + } + } + break; + + case ID_MT312: + break; + + default: + return -EINVAL; + } + + mt312_writereg(state, GPP_CTRL, 0x40); + state->config->pll_set(fe, p); + mt312_writereg(state, GPP_CTRL, 0x00); + + /* sr = (u16)(sr * 256.0 / 1000000.0) */ + sr = mt312_div(p->u.qpsk.symbol_rate * 4, 15625); + + /* SYM_RATE */ + buf[0] = (sr >> 8) & 0x3f; + buf[1] = (sr >> 0) & 0xff; + + /* VIT_MODE */ + buf[2] = inv_tab[p->inversion] | fec_tab[p->u.qpsk.fec_inner]; + + /* QPSK_CTRL */ + buf[3] = 0x40; /* swap I and Q before QPSK demodulation */ + + if (p->u.qpsk.symbol_rate < 10000000) + buf[3] |= 0x04; /* use afc mode */ + + /* GO */ + buf[4] = 0x01; + + if ((ret = mt312_write(state, SYM_RATE_H, buf, sizeof(buf))) < 0) + return ret; + + mt312_reset(state, 0); + + return 0; +} + +static int mt312_get_frontend(struct dvb_frontend* fe, + struct dvb_frontend_parameters *p) +{ + struct mt312_state *state = (struct mt312_state*) fe->demodulator_priv; + int ret; + + if ((ret = mt312_get_inversion(state, &p->inversion)) < 0) + return ret; + + if ((ret = mt312_get_symbol_rate(state, &p->u.qpsk.symbol_rate)) < 0) + return ret; + + if ((ret = mt312_get_code_rate(state, &p->u.qpsk.fec_inner)) < 0) + return ret; + + return 0; +} + +static int mt312_sleep(struct dvb_frontend* fe) +{ + struct mt312_state *state = (struct mt312_state*) fe->demodulator_priv; + int ret; + u8 config; + + /* reset all registers to defaults */ + if ((ret = mt312_reset(state, 1)) < 0) + return ret; + + if ((ret = mt312_readreg(state, CONFIG, &config)) < 0) + return ret; + + /* enter standby */ + if ((ret = mt312_writereg(state, CONFIG, config & 0x7f)) < 0) + return ret; + + return 0; +} + +static int mt312_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* fesettings) +{ + fesettings->min_delay_ms = 50; + fesettings->step_size = 0; + fesettings->max_drift = 0; + return 0; +} + +static void mt312_release(struct dvb_frontend* fe) +{ + struct mt312_state* state = (struct mt312_state*) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops vp310_mt312_ops; + +struct dvb_frontend* vp310_attach(const struct mt312_config* config, + struct i2c_adapter* i2c) +{ + struct mt312_state* state = NULL; + + /* allocate memory for the internal state */ + state = (struct mt312_state*) kmalloc(sizeof(struct mt312_state), GFP_KERNEL); + if (state == NULL) + goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + memcpy(&state->ops, &vp310_mt312_ops, sizeof(struct dvb_frontend_ops)); + strcpy(state->ops.info.name, "Zarlink VP310 DVB-S"); + + /* check if the demod is there */ + if (mt312_readreg(state, ID, &state->id) < 0) + goto error; + if (state->id != ID_VP310) { + goto error; + } + + /* create dvb_frontend */ + state->frequency = 90; + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +struct dvb_frontend* mt312_attach(const struct mt312_config* config, + struct i2c_adapter* i2c) +{ + struct mt312_state* state = NULL; + + /* allocate memory for the internal state */ + state = (struct mt312_state*) kmalloc(sizeof(struct mt312_state), GFP_KERNEL); + if (state == NULL) + goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + memcpy(&state->ops, &vp310_mt312_ops, sizeof(struct dvb_frontend_ops)); + strcpy(state->ops.info.name, "Zarlink MT312 DVB-S"); + + /* check if the demod is there */ + if (mt312_readreg(state, ID, &state->id) < 0) + goto error; + if (state->id != ID_MT312) { + goto error; + } + + /* create dvb_frontend */ + state->frequency = 60; + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + if (state) + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops vp310_mt312_ops = { + + .info = { + .name = "Zarlink ???? DVB-S", + .type = FE_QPSK, + .frequency_min = 950000, + .frequency_max = 2150000, + .frequency_stepsize = (MT312_PLL_CLK / 1000) / 128, + .symbol_rate_min = MT312_SYS_CLK / 128, + .symbol_rate_max = MT312_SYS_CLK / 2, + .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_FEC_AUTO | FE_CAN_QPSK | FE_CAN_MUTE_TS | + FE_CAN_RECOVER + }, + + .release = mt312_release, + + .init = mt312_initfe, + .sleep = mt312_sleep, + + .set_frontend = mt312_set_frontend, + .get_frontend = mt312_get_frontend, + .get_tune_settings = mt312_get_tune_settings, + + .read_status = mt312_read_status, + .read_ber = mt312_read_ber, + .read_signal_strength = mt312_read_signal_strength, + .read_snr = mt312_read_snr, + .read_ucblocks = mt312_read_ucblocks, + + .diseqc_send_master_cmd = mt312_send_master_cmd, + .diseqc_send_burst = mt312_send_burst, + .set_tone = mt312_set_tone, + .set_voltage = mt312_set_voltage, +}; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("Zarlink VP310/MT312 DVB-S Demodulator driver"); +MODULE_AUTHOR("Andreas Oberritter <obi@linuxtv.org>"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(mt312_attach); +EXPORT_SYMBOL(vp310_attach); diff --git a/drivers/media/dvb/frontends/mt312.h b/drivers/media/dvb/frontends/mt312.h new file mode 100644 index 000000000000..b3a53a73a117 --- /dev/null +++ b/drivers/media/dvb/frontends/mt312.h @@ -0,0 +1,47 @@ +/* + Driver for Zarlink MT312 Satellite Channel Decoder + + Copyright (C) 2003 Andreas Oberritter <obi@linuxtv.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + References: + http://products.zarlink.com/product_profiles/MT312.htm + http://products.zarlink.com/product_profiles/SL1935.htm +*/ + +#ifndef MT312_H +#define MT312_H + +#include <linux/dvb/frontend.h> + +struct mt312_config +{ + /* the demodulator's i2c address */ + u8 demod_address; + + /* PLL maintenance */ + int (*pll_init)(struct dvb_frontend* fe); + int (*pll_set)(struct dvb_frontend* fe, struct dvb_frontend_parameters* params); +}; + +extern struct dvb_frontend* mt312_attach(const struct mt312_config* config, + struct i2c_adapter* i2c); + +extern struct dvb_frontend* vp310_attach(const struct mt312_config* config, + struct i2c_adapter* i2c); + +#endif // MT312_H diff --git a/drivers/media/dvb/frontends/mt312_priv.h b/drivers/media/dvb/frontends/mt312_priv.h new file mode 100644 index 000000000000..5e0b95b5337b --- /dev/null +++ b/drivers/media/dvb/frontends/mt312_priv.h @@ -0,0 +1,162 @@ +/* + Driver for Zarlink MT312 QPSK Frontend + + Copyright (C) 2003 Andreas Oberritter <obi@linuxtv.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _DVB_FRONTENDS_MT312_PRIV +#define _DVB_FRONTENDS_MT312_PRIV + +enum mt312_reg_addr { + QPSK_INT_H = 0, + QPSK_INT_M = 1, + QPSK_INT_L = 2, + FEC_INT = 3, + QPSK_STAT_H = 4, + QPSK_STAT_L = 5, + FEC_STATUS = 6, + LNB_FREQ_H = 7, + LNB_FREQ_L = 8, + M_SNR_H = 9, + M_SNR_L = 10, + VIT_ERRCNT_H = 11, + VIT_ERRCNT_M = 12, + VIT_ERRCNT_L = 13, + RS_BERCNT_H = 14, + RS_BERCNT_M = 15, + RS_BERCNT_L = 16, + RS_UBC_H = 17, + RS_UBC_L = 18, + SIG_LEVEL = 19, + GPP_CTRL = 20, + RESET = 21, + DISEQC_MODE = 22, + SYM_RATE_H = 23, + SYM_RATE_L = 24, + VIT_MODE = 25, + QPSK_CTRL = 26, + GO = 27, + IE_QPSK_H = 28, + IE_QPSK_M = 29, + IE_QPSK_L = 30, + IE_FEC = 31, + QPSK_STAT_EN = 32, + FEC_STAT_EN = 33, + SYS_CLK = 34, + DISEQC_RATIO = 35, + DISEQC_INSTR = 36, + FR_LIM = 37, + FR_OFF = 38, + AGC_CTRL = 39, + AGC_INIT = 40, + AGC_REF = 41, + AGC_MAX = 42, + AGC_MIN = 43, + AGC_LK_TH = 44, + TS_AGC_LK_TH = 45, + AGC_PWR_SET = 46, + QPSK_MISC = 47, + SNR_THS_LOW = 48, + SNR_THS_HIGH = 49, + TS_SW_RATE = 50, + TS_SW_LIM_L = 51, + TS_SW_LIM_H = 52, + CS_SW_RATE_1 = 53, + CS_SW_RATE_2 = 54, + CS_SW_RATE_3 = 55, + CS_SW_RATE_4 = 56, + CS_SW_LIM = 57, + TS_LPK = 58, + TS_LPK_M = 59, + TS_LPK_L = 60, + CS_KPROP_H = 61, + CS_KPROP_L = 62, + CS_KINT_H = 63, + CS_KINT_L = 64, + QPSK_SCALE = 65, + TLD_OUTCLK_TH = 66, + TLD_INCLK_TH = 67, + FLD_TH = 68, + PLD_OUTLK3 = 69, + PLD_OUTLK2 = 70, + PLD_OUTLK1 = 71, + PLD_OUTLK0 = 72, + PLD_INLK3 = 73, + PLD_INLK2 = 74, + PLD_INLK1 = 75, + PLD_INLK0 = 76, + PLD_ACC_TIME = 77, + SWEEP_PAR = 78, + STARTUP_TIME = 79, + LOSSLOCK_TH = 80, + FEC_LOCK_TM = 81, + LOSSLOCK_TM = 82, + VIT_ERRPER_H = 83, + VIT_ERRPER_M = 84, + VIT_ERRPER_L = 85, + VIT_SETUP = 86, + VIT_REF0 = 87, + VIT_REF1 = 88, + VIT_REF2 = 89, + VIT_REF3 = 90, + VIT_REF4 = 91, + VIT_REF5 = 92, + VIT_REF6 = 93, + VIT_MAXERR = 94, + BA_SETUPT = 95, + OP_CTRL = 96, + FEC_SETUP = 97, + PROG_SYNC = 98, + AFC_SEAR_TH = 99, + CSACC_DIF_TH = 100, + QPSK_LK_CT = 101, + QPSK_ST_CT = 102, + MON_CTRL = 103, + QPSK_RESET = 104, + QPSK_TST_CT = 105, + QPSK_TST_ST = 106, + TEST_R = 107, + AGC_H = 108, + AGC_M = 109, + AGC_L = 110, + FREQ_ERR1_H = 111, + FREQ_ERR1_M = 112, + FREQ_ERR1_L = 113, + FREQ_ERR2_H = 114, + FREQ_ERR2_L = 115, + SYM_RAT_OP_H = 116, + SYM_RAT_OP_L = 117, + DESEQC2_INT = 118, + DISEQC2_STAT = 119, + DISEQC2_FIFO = 120, + DISEQC2_CTRL1 = 121, + DISEQC2_CTRL2 = 122, + MONITOR_H = 123, + MONITOR_L = 124, + TEST_MODE = 125, + ID = 126, + CONFIG = 127 +}; + +enum mt312_model_id { + ID_VP310 = 1, + ID_MT312 = 3 +}; + +#endif /* DVB_FRONTENDS_MT312_PRIV */ diff --git a/drivers/media/dvb/frontends/mt352.c b/drivers/media/dvb/frontends/mt352.c new file mode 100644 index 000000000000..50326c7248fa --- /dev/null +++ b/drivers/media/dvb/frontends/mt352.c @@ -0,0 +1,610 @@ +/* + * Driver for Zarlink DVB-T MT352 demodulator + * + * Written by Holger Waechtler <holger@qanu.de> + * and Daniel Mack <daniel@qanu.de> + * + * AVerMedia AVerTV DVB-T 771 support by + * Wolfram Joost <dbox2@frokaschwei.de> + * + * Support for Samsung TDTC9251DH01C(M) tuner + * Copyright (C) 2004 Antonio Mancuso <antonio.mancuso@digitaltelevision.it> + * Amauri Celani <acelani@essegi.net> + * + * DVICO FusionHDTV DVB-T1 and DVICO FusionHDTV DVB-T Lite support by + * Christopher Pascoe <c.pascoe@itee.uq.edu.au> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.= + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> + +#include "dvb_frontend.h" +#include "mt352_priv.h" +#include "mt352.h" + +struct mt352_state { + struct i2c_adapter* i2c; + struct dvb_frontend frontend; + struct dvb_frontend_ops ops; + + /* configuration settings */ + const struct mt352_config* config; +}; + +static int debug; +#define dprintk(args...) \ + do { \ + if (debug) printk(KERN_DEBUG "mt352: " args); \ + } while (0) + +static int mt352_single_write(struct dvb_frontend *fe, u8 reg, u8 val) +{ + struct mt352_state* state = fe->demodulator_priv; + u8 buf[2] = { reg, val }; + struct i2c_msg msg = { .addr = state->config->demod_address, .flags = 0, + .buf = buf, .len = 2 }; + int err = i2c_transfer(state->i2c, &msg, 1); + if (err != 1) { + printk("mt352_write() to reg %x failed (err = %d)!\n", reg, err); + return err; + } + return 0; +} + +int mt352_write(struct dvb_frontend* fe, u8* ibuf, int ilen) +{ + int err,i; + for (i=0; i < ilen-1; i++) + if ((err = mt352_single_write(fe,ibuf[0]+i,ibuf[i+1]))) + return err; + + return 0; +} + +static int mt352_read_register(struct mt352_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: readreg error (reg=%d, ret==%i)\n", + __FUNCTION__, reg, ret); + return ret; + } + + return b1[0]; +} + +int mt352_read(struct dvb_frontend *fe, u8 reg) +{ + return mt352_read_register(fe->demodulator_priv,reg); +} + +static int mt352_sleep(struct dvb_frontend* fe) +{ + static u8 mt352_softdown[] = { CLOCK_CTL, 0x20, 0x08 }; + + mt352_write(fe, mt352_softdown, sizeof(mt352_softdown)); + return 0; +} + +static void mt352_calc_nominal_rate(struct mt352_state* state, + enum fe_bandwidth bandwidth, + unsigned char *buf) +{ + u32 adc_clock = 20480; /* 20.340 MHz */ + u32 bw,value; + + switch (bandwidth) { + case BANDWIDTH_6_MHZ: + bw = 6; + break; + case BANDWIDTH_7_MHZ: + bw = 7; + break; + case BANDWIDTH_8_MHZ: + default: + bw = 8; + break; + } + if (state->config->adc_clock) + adc_clock = state->config->adc_clock; + + value = 64 * bw * (1<<16) / (7 * 8); + value = value * 1000 / adc_clock; + dprintk("%s: bw %d, adc_clock %d => 0x%x\n", + __FUNCTION__, bw, adc_clock, value); + buf[0] = msb(value); + buf[1] = lsb(value); +} + +static void mt352_calc_input_freq(struct mt352_state* state, + unsigned char *buf) +{ + int adc_clock = 20480; /* 20.480000 MHz */ + int if2 = 36167; /* 36.166667 MHz */ + int ife,value; + + if (state->config->adc_clock) + adc_clock = state->config->adc_clock; + if (state->config->if2) + if2 = state->config->if2; + + ife = (2*adc_clock - if2); + value = -16374 * ife / adc_clock; + dprintk("%s: if2 %d, ife %d, adc_clock %d => %d / 0x%x\n", + __FUNCTION__, if2, ife, adc_clock, value, value & 0x3fff); + buf[0] = msb(value); + buf[1] = lsb(value); +} + +static int mt352_set_parameters(struct dvb_frontend* fe, + struct dvb_frontend_parameters *param) +{ + struct mt352_state* state = fe->demodulator_priv; + unsigned char buf[13]; + static unsigned char tuner_go[] = { 0x5d, 0x01 }; + static unsigned char fsm_go[] = { 0x5e, 0x01 }; + unsigned int tps = 0; + struct dvb_ofdm_parameters *op = ¶m->u.ofdm; + + switch (op->code_rate_HP) { + case FEC_2_3: + tps |= (1 << 7); + break; + case FEC_3_4: + tps |= (2 << 7); + break; + case FEC_5_6: + tps |= (3 << 7); + break; + case FEC_7_8: + tps |= (4 << 7); + break; + case FEC_1_2: + case FEC_AUTO: + break; + default: + return -EINVAL; + } + + switch (op->code_rate_LP) { + case FEC_2_3: + tps |= (1 << 4); + break; + case FEC_3_4: + tps |= (2 << 4); + break; + case FEC_5_6: + tps |= (3 << 4); + break; + case FEC_7_8: + tps |= (4 << 4); + break; + case FEC_1_2: + case FEC_AUTO: + break; + case FEC_NONE: + if (op->hierarchy_information == HIERARCHY_AUTO || + op->hierarchy_information == HIERARCHY_NONE) + break; + default: + return -EINVAL; + } + + switch (op->constellation) { + case QPSK: + break; + case QAM_AUTO: + case QAM_16: + tps |= (1 << 13); + break; + case QAM_64: + tps |= (2 << 13); + break; + default: + return -EINVAL; + } + + switch (op->transmission_mode) { + case TRANSMISSION_MODE_2K: + case TRANSMISSION_MODE_AUTO: + break; + case TRANSMISSION_MODE_8K: + tps |= (1 << 0); + break; + default: + return -EINVAL; + } + + switch (op->guard_interval) { + case GUARD_INTERVAL_1_32: + case GUARD_INTERVAL_AUTO: + break; + case GUARD_INTERVAL_1_16: + tps |= (1 << 2); + break; + case GUARD_INTERVAL_1_8: + tps |= (2 << 2); + break; + case GUARD_INTERVAL_1_4: + tps |= (3 << 2); + break; + default: + return -EINVAL; + } + + switch (op->hierarchy_information) { + case HIERARCHY_AUTO: + case HIERARCHY_NONE: + break; + case HIERARCHY_1: + tps |= (1 << 10); + break; + case HIERARCHY_2: + tps |= (2 << 10); + break; + case HIERARCHY_4: + tps |= (3 << 10); + break; + default: + return -EINVAL; + } + + + buf[0] = TPS_GIVEN_1; /* TPS_GIVEN_1 and following registers */ + + buf[1] = msb(tps); /* TPS_GIVEN_(1|0) */ + buf[2] = lsb(tps); + + buf[3] = 0x50; // old +// buf[3] = 0xf4; // pinnacle + + mt352_calc_nominal_rate(state, op->bandwidth, buf+4); + mt352_calc_input_freq(state, buf+6); + state->config->pll_set(fe, param, buf+8); + + mt352_write(fe, buf, sizeof(buf)); + if (state->config->no_tuner) { + /* start decoding */ + mt352_write(fe, fsm_go, 2); + } else { + /* start tuning */ + mt352_write(fe, tuner_go, 2); + } + return 0; +} + +static int mt352_get_parameters(struct dvb_frontend* fe, + struct dvb_frontend_parameters *param) +{ + struct mt352_state* state = fe->demodulator_priv; + u16 tps; + u16 div; + u8 trl; + struct dvb_ofdm_parameters *op = ¶m->u.ofdm; + static const u8 tps_fec_to_api[8] = + { + FEC_1_2, + FEC_2_3, + FEC_3_4, + FEC_5_6, + FEC_7_8, + FEC_AUTO, + FEC_AUTO, + FEC_AUTO + }; + + if ( (mt352_read_register(state,0x00) & 0xC0) != 0xC0 ) + return -EINVAL; + + /* Use TPS_RECEIVED-registers, not the TPS_CURRENT-registers because + * the mt352 sometimes works with the wrong parameters + */ + tps = (mt352_read_register(state, TPS_RECEIVED_1) << 8) | mt352_read_register(state, TPS_RECEIVED_0); + div = (mt352_read_register(state, CHAN_START_1) << 8) | mt352_read_register(state, CHAN_START_0); + trl = mt352_read_register(state, TRL_NOMINAL_RATE_1); + + op->code_rate_HP = tps_fec_to_api[(tps >> 7) & 7]; + op->code_rate_LP = tps_fec_to_api[(tps >> 4) & 7]; + + switch ( (tps >> 13) & 3) + { + case 0: + op->constellation = QPSK; + break; + case 1: + op->constellation = QAM_16; + break; + case 2: + op->constellation = QAM_64; + break; + default: + op->constellation = QAM_AUTO; + break; + } + + op->transmission_mode = (tps & 0x01) ? TRANSMISSION_MODE_8K : TRANSMISSION_MODE_2K; + + switch ( (tps >> 2) & 3) + { + case 0: + op->guard_interval = GUARD_INTERVAL_1_32; + break; + case 1: + op->guard_interval = GUARD_INTERVAL_1_16; + break; + case 2: + op->guard_interval = GUARD_INTERVAL_1_8; + break; + case 3: + op->guard_interval = GUARD_INTERVAL_1_4; + break; + default: + op->guard_interval = GUARD_INTERVAL_AUTO; + break; + } + + switch ( (tps >> 10) & 7) + { + case 0: + op->hierarchy_information = HIERARCHY_NONE; + break; + case 1: + op->hierarchy_information = HIERARCHY_1; + break; + case 2: + op->hierarchy_information = HIERARCHY_2; + break; + case 3: + op->hierarchy_information = HIERARCHY_4; + break; + default: + op->hierarchy_information = HIERARCHY_AUTO; + break; + } + + param->frequency = ( 500 * (div - IF_FREQUENCYx6) ) / 3 * 1000; + + if (trl == 0x72) + op->bandwidth = BANDWIDTH_8_MHZ; + else if (trl == 0x64) + op->bandwidth = BANDWIDTH_7_MHZ; + else + op->bandwidth = BANDWIDTH_6_MHZ; + + + if (mt352_read_register(state, STATUS_2) & 0x02) + param->inversion = INVERSION_OFF; + else + param->inversion = INVERSION_ON; + + return 0; +} + +static int mt352_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct mt352_state* state = fe->demodulator_priv; + int s0, s1, s3; + + /* FIXME: + * + * The MT352 design manual from Zarlink states (page 46-47): + * + * Notes about the TUNER_GO register: + * + * If the Read_Tuner_Byte (bit-1) is activated, then the tuner status + * byte is copied from the tuner to the STATUS_3 register and + * completion of the read operation is indicated by bit-5 of the + * INTERRUPT_3 register. + */ + + if ((s0 = mt352_read_register(state, STATUS_0)) < 0) + return -EREMOTEIO; + if ((s1 = mt352_read_register(state, STATUS_1)) < 0) + return -EREMOTEIO; + if ((s3 = mt352_read_register(state, STATUS_3)) < 0) + return -EREMOTEIO; + + *status = 0; + if (s0 & (1 << 4)) + *status |= FE_HAS_CARRIER; + if (s0 & (1 << 1)) + *status |= FE_HAS_VITERBI; + if (s0 & (1 << 5)) + *status |= FE_HAS_LOCK; + if (s1 & (1 << 1)) + *status |= FE_HAS_SYNC; + if (s3 & (1 << 6)) + *status |= FE_HAS_SIGNAL; + + if ((*status & (FE_HAS_CARRIER | FE_HAS_VITERBI | FE_HAS_SYNC)) != + (FE_HAS_CARRIER | FE_HAS_VITERBI | FE_HAS_SYNC)) + *status &= ~FE_HAS_LOCK; + + return 0; +} + +static int mt352_read_ber(struct dvb_frontend* fe, u32* ber) +{ + struct mt352_state* state = fe->demodulator_priv; + + *ber = (mt352_read_register (state, RS_ERR_CNT_2) << 16) | + (mt352_read_register (state, RS_ERR_CNT_1) << 8) | + (mt352_read_register (state, RS_ERR_CNT_0)); + + return 0; +} + +static int mt352_read_signal_strength(struct dvb_frontend* fe, u16* strength) +{ + struct mt352_state* state = fe->demodulator_priv; + + u16 signal = ((mt352_read_register(state, AGC_GAIN_1) << 8) & 0x0f) | + (mt352_read_register(state, AGC_GAIN_0)); + + *strength = ~signal; + return 0; +} + +static int mt352_read_snr(struct dvb_frontend* fe, u16* snr) +{ + struct mt352_state* state = fe->demodulator_priv; + + u8 _snr = mt352_read_register (state, SNR); + *snr = (_snr << 8) | _snr; + + return 0; +} + +static int mt352_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + struct mt352_state* state = fe->demodulator_priv; + + *ucblocks = (mt352_read_register (state, RS_UBC_1) << 8) | + (mt352_read_register (state, RS_UBC_0)); + + return 0; +} + +static int mt352_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* fe_tune_settings) +{ + fe_tune_settings->min_delay_ms = 800; + fe_tune_settings->step_size = 0; + fe_tune_settings->max_drift = 0; + + return 0; +} + +static int mt352_init(struct dvb_frontend* fe) +{ + struct mt352_state* state = fe->demodulator_priv; + + static u8 mt352_reset_attach [] = { RESET, 0xC0 }; + + dprintk("%s: hello\n",__FUNCTION__); + + if ((mt352_read_register(state, CLOCK_CTL) & 0x10) == 0 || + (mt352_read_register(state, CONFIG) & 0x20) == 0) { + + /* Do a "hard" reset */ + mt352_write(fe, mt352_reset_attach, sizeof(mt352_reset_attach)); + return state->config->demod_init(fe); + } + + return 0; +} + +static void mt352_release(struct dvb_frontend* fe) +{ + struct mt352_state* state = fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops mt352_ops; + +struct dvb_frontend* mt352_attach(const struct mt352_config* config, + struct i2c_adapter* i2c) +{ + struct mt352_state* state = NULL; + + /* allocate memory for the internal state */ + state = kmalloc(sizeof(struct mt352_state), GFP_KERNEL); + if (state == NULL) goto error; + memset(state,0,sizeof(*state)); + + /* setup the state */ + state->config = config; + state->i2c = i2c; + memcpy(&state->ops, &mt352_ops, sizeof(struct dvb_frontend_ops)); + + /* check if the demod is there */ + if (mt352_read_register(state, CHIP_ID) != ID_MT352) goto error; + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops mt352_ops = { + + .info = { + .name = "Zarlink MT352 DVB-T", + .type = FE_OFDM, + .frequency_min = 174000000, + .frequency_max = 862000000, + .frequency_stepsize = 166667, + .frequency_tolerance = 0, + .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_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_HIERARCHY_AUTO | FE_CAN_RECOVER | + FE_CAN_MUTE_TS + }, + + .release = mt352_release, + + .init = mt352_init, + .sleep = mt352_sleep, + + .set_frontend = mt352_set_parameters, + .get_frontend = mt352_get_parameters, + .get_tune_settings = mt352_get_tune_settings, + + .read_status = mt352_read_status, + .read_ber = mt352_read_ber, + .read_signal_strength = mt352_read_signal_strength, + .read_snr = mt352_read_snr, + .read_ucblocks = mt352_read_ucblocks, +}; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("Zarlink MT352 DVB-T Demodulator driver"); +MODULE_AUTHOR("Holger Waechtler, Daniel Mack, Antonio Mancuso"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(mt352_attach); +EXPORT_SYMBOL(mt352_write); +EXPORT_SYMBOL(mt352_read); +/* + * Local variables: + * c-basic-offset: 8 + * compile-command: "make DVB=1" + * End: + */ diff --git a/drivers/media/dvb/frontends/mt352.h b/drivers/media/dvb/frontends/mt352.h new file mode 100644 index 000000000000..f5d8a5aed8a9 --- /dev/null +++ b/drivers/media/dvb/frontends/mt352.h @@ -0,0 +1,72 @@ +/* + * Driver for Zarlink DVB-T MT352 demodulator + * + * Written by Holger Waechtler <holger@qanu.de> + * and Daniel Mack <daniel@qanu.de> + * + * AVerMedia AVerTV DVB-T 771 support by + * Wolfram Joost <dbox2@frokaschwei.de> + * + * Support for Samsung TDTC9251DH01C(M) tuner + * Copyright (C) 2004 Antonio Mancuso <antonio.mancuso@digitaltelevision.it> + * Amauri Celani <acelani@essegi.net> + * + * DVICO FusionHDTV DVB-T1 and DVICO FusionHDTV DVB-T Lite support by + * Christopher Pascoe <c.pascoe@itee.uq.edu.au> + * + * 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 MT352_H +#define MT352_H + +#include <linux/dvb/frontend.h> + +struct mt352_config +{ + /* the demodulator's i2c address */ + u8 demod_address; + + /* frequencies in kHz */ + int adc_clock; // default: 20480 + int if2; // default: 36166 + + /* set if no pll is connected to the secondary i2c bus */ + int no_tuner; + + /* Initialise the demodulator and PLL. Cannot be NULL */ + int (*demod_init)(struct dvb_frontend* fe); + + /* PLL setup - fill out the supplied 5 byte buffer with your PLL settings. + * byte0: Set to pll i2c address (nonlinux; left shifted by 1) + * byte1-4: PLL configuration. + */ + int (*pll_set)(struct dvb_frontend* fe, struct dvb_frontend_parameters* params, u8* pllbuf); +}; + +extern struct dvb_frontend* mt352_attach(const struct mt352_config* config, + struct i2c_adapter* i2c); + +extern int mt352_write(struct dvb_frontend* fe, u8* ibuf, int ilen); +extern int mt352_read(struct dvb_frontend *fe, u8 reg); + +#endif // MT352_H + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/dvb/frontends/mt352_priv.h b/drivers/media/dvb/frontends/mt352_priv.h new file mode 100644 index 000000000000..44ad0d4c8f12 --- /dev/null +++ b/drivers/media/dvb/frontends/mt352_priv.h @@ -0,0 +1,127 @@ +/* + * Driver for Zarlink DVB-T MT352 demodulator + * + * Written by Holger Waechtler <holger@qanu.de> + * and Daniel Mack <daniel@qanu.de> + * + * AVerMedia AVerTV DVB-T 771 support by + * Wolfram Joost <dbox2@frokaschwei.de> + * + * Support for Samsung TDTC9251DH01C(M) tuner + * Copyright (C) 2004 Antonio Mancuso <antonio.mancuso@digitaltelevision.it> + * Amauri Celani <acelani@essegi.net> + * + * DVICO FusionHDTV DVB-T1 and DVICO FusionHDTV DVB-T Lite support by + * Christopher Pascoe <c.pascoe@itee.uq.edu.au> + * + * 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 _MT352_PRIV_ +#define _MT352_PRIV_ + +#define ID_MT352 0x13 + +#define msb(x) (((x) >> 8) & 0xff) +#define lsb(x) ((x) & 0xff) + +enum mt352_reg_addr { + STATUS_0 = 0x00, + STATUS_1 = 0x01, + STATUS_2 = 0x02, + STATUS_3 = 0x03, + STATUS_4 = 0x04, + INTERRUPT_0 = 0x05, + INTERRUPT_1 = 0x06, + INTERRUPT_2 = 0x07, + INTERRUPT_3 = 0x08, + SNR = 0x09, + VIT_ERR_CNT_2 = 0x0A, + VIT_ERR_CNT_1 = 0x0B, + VIT_ERR_CNT_0 = 0x0C, + RS_ERR_CNT_2 = 0x0D, + RS_ERR_CNT_1 = 0x0E, + RS_ERR_CNT_0 = 0x0F, + RS_UBC_1 = 0x10, + RS_UBC_0 = 0x11, + AGC_GAIN_3 = 0x12, + AGC_GAIN_2 = 0x13, + AGC_GAIN_1 = 0x14, + AGC_GAIN_0 = 0x15, + FREQ_OFFSET_2 = 0x17, + FREQ_OFFSET_1 = 0x18, + FREQ_OFFSET_0 = 0x19, + TIMING_OFFSET_1 = 0x1A, + TIMING_OFFSET_0 = 0x1B, + CHAN_FREQ_1 = 0x1C, + CHAN_FREQ_0 = 0x1D, + TPS_RECEIVED_1 = 0x1E, + TPS_RECEIVED_0 = 0x1F, + TPS_CURRENT_1 = 0x20, + TPS_CURRENT_0 = 0x21, + TPS_CELL_ID_1 = 0x22, + TPS_CELL_ID_0 = 0x23, + TPS_MISC_DATA_2 = 0x24, + TPS_MISC_DATA_1 = 0x25, + TPS_MISC_DATA_0 = 0x26, + RESET = 0x50, + TPS_GIVEN_1 = 0x51, + TPS_GIVEN_0 = 0x52, + ACQ_CTL = 0x53, + TRL_NOMINAL_RATE_1 = 0x54, + TRL_NOMINAL_RATE_0 = 0x55, + INPUT_FREQ_1 = 0x56, + INPUT_FREQ_0 = 0x57, + TUNER_ADDR = 0x58, + CHAN_START_1 = 0x59, + CHAN_START_0 = 0x5A, + CONT_1 = 0x5B, + CONT_0 = 0x5C, + TUNER_GO = 0x5D, + STATUS_EN_0 = 0x5F, + STATUS_EN_1 = 0x60, + INTERRUPT_EN_0 = 0x61, + INTERRUPT_EN_1 = 0x62, + INTERRUPT_EN_2 = 0x63, + INTERRUPT_EN_3 = 0x64, + AGC_TARGET = 0x67, + AGC_CTL = 0x68, + CAPT_RANGE = 0x75, + SNR_SELECT_1 = 0x79, + SNR_SELECT_0 = 0x7A, + RS_ERR_PER_1 = 0x7C, + RS_ERR_PER_0 = 0x7D, + CHIP_ID = 0x7F, + CHAN_STOP_1 = 0x80, + CHAN_STOP_0 = 0x81, + CHAN_STEP_1 = 0x82, + CHAN_STEP_0 = 0x83, + FEC_LOCK_TIME = 0x85, + OFDM_LOCK_TIME = 0x86, + ACQ_DELAY = 0x87, + SCAN_CTL = 0x88, + CLOCK_CTL = 0x89, + CONFIG = 0x8A, + MCLK_RATIO = 0x8B, + GPP_CTL = 0x8C, + ADC_CTL_1 = 0x8E, + ADC_CTL_0 = 0x8F +}; + +/* here we assume 1/6MHz == 166.66kHz stepsize */ +#define IF_FREQUENCYx6 217 /* 6 * 36.16666666667MHz */ + +#endif /* _MT352_PRIV_ */ diff --git a/drivers/media/dvb/frontends/nxt2002.c b/drivers/media/dvb/frontends/nxt2002.c new file mode 100644 index 000000000000..4743aa17406e --- /dev/null +++ b/drivers/media/dvb/frontends/nxt2002.c @@ -0,0 +1,705 @@ +/* + Support for B2C2/BBTI Technisat Air2PC - ATSC + + Copyright (C) 2004 Taylor Jacob <rtjacob@earthlink.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/* + * This driver needs external firmware. Please use the command + * "<kerneldir>/Documentation/dvb/get_dvb_firmware nxt2002" to + * download/extract it, and then copy it to /usr/lib/hotplug/firmware. + */ +#define NXT2002_DEFAULT_FIRMWARE "dvb-fe-nxt2002.fw" +#define CRC_CCIT_MASK 0x1021 + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/firmware.h> + +#include "dvb_frontend.h" +#include "nxt2002.h" + +struct nxt2002_state { + + struct i2c_adapter* i2c; + struct dvb_frontend_ops ops; + const struct nxt2002_config* config; + struct dvb_frontend frontend; + + /* demodulator private data */ + u8 initialised:1; +}; + +static int debug; +#define dprintk(args...) \ + do { \ + if (debug) printk(KERN_DEBUG "nxt2002: " args); \ + } while (0) + +static int i2c_writebytes (struct nxt2002_state* state, u8 reg, u8 *buf, u8 len) +{ + /* probbably a much better way or doing this */ + u8 buf2 [256],x; + int err; + struct i2c_msg msg = { .addr = state->config->demod_address, .flags = 0, .buf = buf2, .len = len + 1 }; + + buf2[0] = reg; + for (x = 0 ; x < len ; x++) + buf2[x+1] = buf[x]; + + if ((err = i2c_transfer (state->i2c, &msg, 1)) != 1) { + printk ("%s: i2c write error (addr %02x, err == %i)\n", + __FUNCTION__, state->config->demod_address, err); + return -EREMOTEIO; + } + + return 0; +} + +static u8 i2c_readbytes (struct nxt2002_state* state, u8 reg, u8* buf, u8 len) +{ + u8 reg2 [] = { reg }; + + struct i2c_msg msg [] = { { .addr = state->config->demod_address, .flags = 0, .buf = reg2, .len = 1 }, + { .addr = state->config->demod_address, .flags = I2C_M_RD, .buf = buf, .len = len } }; + + int err; + + if ((err = i2c_transfer (state->i2c, msg, 2)) != 2) { + printk ("%s: i2c read error (addr %02x, err == %i)\n", + __FUNCTION__, state->config->demod_address, err); + return -EREMOTEIO; + } + + return 0; +} + +static u16 nxt2002_crc(u16 crc, u8 c) +{ + + u8 i; + u16 input = (u16) c & 0xFF; + + input<<=8; + for(i=0 ;i<8 ;i++) { + if((crc ^ input) & 0x8000) + crc=(crc<<1)^CRC_CCIT_MASK; + else + crc<<=1; + input<<=1; + } + return crc; +} + +static int nxt2002_writereg_multibyte (struct nxt2002_state* state, u8 reg, u8* data, u8 len) +{ + u8 buf; + dprintk("%s\n", __FUNCTION__); + + /* set multi register length */ + i2c_writebytes(state,0x34,&len,1); + + /* set mutli register register */ + i2c_writebytes(state,0x35,®,1); + + /* send the actual data */ + i2c_writebytes(state,0x36,data,len); + + /* toggle the multireg write bit*/ + buf = 0x02; + i2c_writebytes(state,0x21,&buf,1); + + i2c_readbytes(state,0x21,&buf,1); + + if ((buf & 0x02) == 0) + return 0; + + dprintk("Error writing multireg register %02X\n",reg); + + return 0; +} + +static int nxt2002_readreg_multibyte (struct nxt2002_state* state, u8 reg, u8* data, u8 len) +{ + u8 len2; + dprintk("%s\n", __FUNCTION__); + + /* set multi register length */ + len2 = len & 0x80; + i2c_writebytes(state,0x34,&len2,1); + + /* set mutli register register */ + i2c_writebytes(state,0x35,®,1); + + /* send the actual data */ + i2c_readbytes(state,reg,data,len); + + return 0; +} + +static void nxt2002_microcontroller_stop (struct nxt2002_state* state) +{ + u8 buf[2],counter = 0; + dprintk("%s\n", __FUNCTION__); + + buf[0] = 0x80; + i2c_writebytes(state,0x22,buf,1); + + while (counter < 20) { + i2c_readbytes(state,0x31,buf,1); + if (buf[0] & 0x40) + return; + msleep(10); + counter++; + } + + dprintk("Timeout waiting for micro to stop.. This is ok after firmware upload\n"); + return; +} + +static void nxt2002_microcontroller_start (struct nxt2002_state* state) +{ + u8 buf; + dprintk("%s\n", __FUNCTION__); + + buf = 0x00; + i2c_writebytes(state,0x22,&buf,1); +} + +static int nxt2002_writetuner (struct nxt2002_state* state, u8* data) +{ + u8 buf,count = 0; + + dprintk("Tuner Bytes: %02X %02X %02X %02X\n",data[0],data[1],data[2],data[3]); + + dprintk("%s\n", __FUNCTION__); + /* stop the micro first */ + nxt2002_microcontroller_stop(state); + + /* set the i2c transfer speed to the tuner */ + buf = 0x03; + i2c_writebytes(state,0x20,&buf,1); + + /* setup to transfer 4 bytes via i2c */ + buf = 0x04; + i2c_writebytes(state,0x34,&buf,1); + + /* write actual tuner bytes */ + i2c_writebytes(state,0x36,data,4); + + /* set tuner i2c address */ + buf = 0xC2; + i2c_writebytes(state,0x35,&buf,1); + + /* write UC Opmode to begin transfer */ + buf = 0x80; + i2c_writebytes(state,0x21,&buf,1); + + while (count < 20) { + i2c_readbytes(state,0x21,&buf,1); + if ((buf & 0x80)== 0x00) + return 0; + msleep(100); + count++; + } + + printk("nxt2002: timeout error writing tuner\n"); + return 0; +} + +static void nxt2002_agc_reset(struct nxt2002_state* state) +{ + u8 buf; + dprintk("%s\n", __FUNCTION__); + + buf = 0x08; + i2c_writebytes(state,0x08,&buf,1); + + buf = 0x00; + i2c_writebytes(state,0x08,&buf,1); + + return; +} + +static int nxt2002_load_firmware (struct dvb_frontend* fe, const struct firmware *fw) +{ + + struct nxt2002_state* state = (struct nxt2002_state*) fe->demodulator_priv; + u8 buf[256],written = 0,chunkpos = 0; + u16 rambase,position,crc = 0; + + dprintk("%s\n", __FUNCTION__); + dprintk("Firmware is %zu bytes\n",fw->size); + + /* Get the RAM base for this nxt2002 */ + i2c_readbytes(state,0x10,buf,1); + + if (buf[0] & 0x10) + rambase = 0x1000; + else + rambase = 0x0000; + + dprintk("rambase on this nxt2002 is %04X\n",rambase); + + /* Hold the micro in reset while loading firmware */ + buf[0] = 0x80; + i2c_writebytes(state,0x2B,buf,1); + + for (position = 0; position < fw->size ; position++) { + if (written == 0) { + crc = 0; + chunkpos = 0x28; + buf[0] = ((rambase + position) >> 8); + buf[1] = (rambase + position) & 0xFF; + buf[2] = 0x81; + /* write starting address */ + i2c_writebytes(state,0x29,buf,3); + } + written++; + chunkpos++; + + if ((written % 4) == 0) + i2c_writebytes(state,chunkpos,&fw->data[position-3],4); + + crc = nxt2002_crc(crc,fw->data[position]); + + if ((written == 255) || (position+1 == fw->size)) { + /* write remaining bytes of firmware */ + i2c_writebytes(state, chunkpos+4-(written %4), + &fw->data[position-(written %4) + 1], + written %4); + buf[0] = crc << 8; + buf[1] = crc & 0xFF; + + /* write crc */ + i2c_writebytes(state,0x2C,buf,2); + + /* do a read to stop things */ + i2c_readbytes(state,0x2A,buf,1); + + /* set transfer mode to complete */ + buf[0] = 0x80; + i2c_writebytes(state,0x2B,buf,1); + + written = 0; + } + } + + printk ("done.\n"); + return 0; +}; + +static int nxt2002_setup_frontend_parameters (struct dvb_frontend* fe, + struct dvb_frontend_parameters *p) +{ + struct nxt2002_state* state = (struct nxt2002_state*) fe->demodulator_priv; + u32 freq = 0; + u16 tunerfreq = 0; + u8 buf[4]; + + freq = 44000 + ( p->frequency / 1000 ); + + dprintk("freq = %d p->frequency = %d\n",freq,p->frequency); + + tunerfreq = freq * 24/4000; + + buf[0] = (tunerfreq >> 8) & 0x7F; + buf[1] = (tunerfreq & 0xFF); + + if (p->frequency <= 214000000) { + buf[2] = 0x84 + (0x06 << 3); + buf[3] = (p->frequency <= 172000000) ? 0x01 : 0x02; + } else if (p->frequency <= 721000000) { + buf[2] = 0x84 + (0x07 << 3); + buf[3] = (p->frequency <= 467000000) ? 0x02 : 0x08; + } else if (p->frequency <= 841000000) { + buf[2] = 0x84 + (0x0E << 3); + buf[3] = 0x08; + } else { + buf[2] = 0x84 + (0x0F << 3); + buf[3] = 0x02; + } + + /* write frequency information */ + nxt2002_writetuner(state,buf); + + /* reset the agc now that tuning has been completed */ + nxt2002_agc_reset(state); + + + + /* set target power level */ + switch (p->u.vsb.modulation) { + case QAM_64: + case QAM_256: + buf[0] = 0x74; + break; + case VSB_8: + buf[0] = 0x70; + break; + default: + return -EINVAL; + break; + } + i2c_writebytes(state,0x42,buf,1); + + /* configure sdm */ + buf[0] = 0x87; + i2c_writebytes(state,0x57,buf,1); + + /* write sdm1 input */ + buf[0] = 0x10; + buf[1] = 0x00; + nxt2002_writereg_multibyte(state,0x58,buf,2); + + /* write sdmx input */ + switch (p->u.vsb.modulation) { + case QAM_64: + buf[0] = 0x68; + break; + case QAM_256: + buf[0] = 0x64; + break; + case VSB_8: + buf[0] = 0x60; + break; + default: + return -EINVAL; + break; + } + buf[1] = 0x00; + nxt2002_writereg_multibyte(state,0x5C,buf,2); + + /* write adc power lpf fc */ + buf[0] = 0x05; + i2c_writebytes(state,0x43,buf,1); + + /* write adc power lpf fc */ + buf[0] = 0x05; + i2c_writebytes(state,0x43,buf,1); + + /* write accumulator2 input */ + buf[0] = 0x80; + buf[1] = 0x00; + nxt2002_writereg_multibyte(state,0x4B,buf,2); + + /* write kg1 */ + buf[0] = 0x00; + i2c_writebytes(state,0x4D,buf,1); + + /* write sdm12 lpf fc */ + buf[0] = 0x44; + i2c_writebytes(state,0x55,buf,1); + + /* write agc control reg */ + buf[0] = 0x04; + i2c_writebytes(state,0x41,buf,1); + + /* write agc ucgp0 */ + switch (p->u.vsb.modulation) { + case QAM_64: + buf[0] = 0x02; + break; + case QAM_256: + buf[0] = 0x03; + break; + case VSB_8: + buf[0] = 0x00; + break; + default: + return -EINVAL; + break; + } + i2c_writebytes(state,0x30,buf,1); + + /* write agc control reg */ + buf[0] = 0x00; + i2c_writebytes(state,0x41,buf,1); + + /* write accumulator2 input */ + buf[0] = 0x80; + buf[1] = 0x00; + nxt2002_writereg_multibyte(state,0x49,buf,2); + nxt2002_writereg_multibyte(state,0x4B,buf,2); + + /* write agc control reg */ + buf[0] = 0x04; + i2c_writebytes(state,0x41,buf,1); + + nxt2002_microcontroller_start(state); + + /* adjacent channel detection should be done here, but I don't + have any stations with this need so I cannot test it */ + + return 0; +} + +static int nxt2002_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct nxt2002_state* state = (struct nxt2002_state*) fe->demodulator_priv; + u8 lock; + i2c_readbytes(state,0x31,&lock,1); + + *status = 0; + if (lock & 0x20) { + *status |= FE_HAS_SIGNAL; + *status |= FE_HAS_CARRIER; + *status |= FE_HAS_VITERBI; + *status |= FE_HAS_SYNC; + *status |= FE_HAS_LOCK; + } + return 0; +} + +static int nxt2002_read_ber(struct dvb_frontend* fe, u32* ber) +{ + struct nxt2002_state* state = (struct nxt2002_state*) fe->demodulator_priv; + u8 b[3]; + + nxt2002_readreg_multibyte(state,0xE6,b,3); + + *ber = ((b[0] << 8) + b[1]) * 8; + + return 0; +} + +static int nxt2002_read_signal_strength(struct dvb_frontend* fe, u16* strength) +{ + struct nxt2002_state* state = (struct nxt2002_state*) fe->demodulator_priv; + u8 b[2]; + u16 temp = 0; + + /* setup to read cluster variance */ + b[0] = 0x00; + i2c_writebytes(state,0xA1,b,1); + + /* get multreg val */ + nxt2002_readreg_multibyte(state,0xA6,b,2); + + temp = (b[0] << 8) | b[1]; + *strength = ((0x7FFF - temp) & 0x0FFF) * 16; + + return 0; +} + +static int nxt2002_read_snr(struct dvb_frontend* fe, u16* snr) +{ + + struct nxt2002_state* state = (struct nxt2002_state*) fe->demodulator_priv; + u8 b[2]; + u16 temp = 0, temp2; + u32 snrdb = 0; + + /* setup to read cluster variance */ + b[0] = 0x00; + i2c_writebytes(state,0xA1,b,1); + + /* get multreg val from 0xA6 */ + nxt2002_readreg_multibyte(state,0xA6,b,2); + + temp = (b[0] << 8) | b[1]; + temp2 = 0x7FFF - temp; + + /* snr will be in db */ + if (temp2 > 0x7F00) + snrdb = 1000*24 + ( 1000*(30-24) * ( temp2 - 0x7F00 ) / ( 0x7FFF - 0x7F00 ) ); + else if (temp2 > 0x7EC0) + snrdb = 1000*18 + ( 1000*(24-18) * ( temp2 - 0x7EC0 ) / ( 0x7F00 - 0x7EC0 ) ); + else if (temp2 > 0x7C00) + snrdb = 1000*12 + ( 1000*(18-12) * ( temp2 - 0x7C00 ) / ( 0x7EC0 - 0x7C00 ) ); + else + snrdb = 1000*0 + ( 1000*(12-0) * ( temp2 - 0 ) / ( 0x7C00 - 0 ) ); + + /* the value reported back from the frontend will be FFFF=32db 0000=0db */ + + *snr = snrdb * (0xFFFF/32000); + + return 0; +} + +static int nxt2002_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + struct nxt2002_state* state = (struct nxt2002_state*) fe->demodulator_priv; + u8 b[3]; + + nxt2002_readreg_multibyte(state,0xE6,b,3); + *ucblocks = b[2]; + + return 0; +} + +static int nxt2002_sleep(struct dvb_frontend* fe) +{ + return 0; +} + +static int nxt2002_init(struct dvb_frontend* fe) +{ + struct nxt2002_state* state = (struct nxt2002_state*) fe->demodulator_priv; + const struct firmware *fw; + int ret; + u8 buf[2]; + + if (!state->initialised) { + /* request the firmware, this will block until someone uploads it */ + printk("nxt2002: Waiting for firmware upload (%s)...\n", NXT2002_DEFAULT_FIRMWARE); + ret = state->config->request_firmware(fe, &fw, NXT2002_DEFAULT_FIRMWARE); + printk("nxt2002: Waiting for firmware upload(2)...\n"); + if (ret) { + printk("nxt2002: no firmware upload (timeout or file not found?)\n"); + return ret; + } + + ret = nxt2002_load_firmware(fe, fw); + if (ret) { + printk("nxt2002: writing firmware to device failed\n"); + release_firmware(fw); + return ret; + } + printk("nxt2002: firmware upload complete\n"); + + /* Put the micro into reset */ + nxt2002_microcontroller_stop(state); + + /* ensure transfer is complete */ + buf[0]=0; + i2c_writebytes(state,0x2B,buf,1); + + /* Put the micro into reset for real this time */ + nxt2002_microcontroller_stop(state); + + /* soft reset everything (agc,frontend,eq,fec)*/ + buf[0] = 0x0F; + i2c_writebytes(state,0x08,buf,1); + buf[0] = 0x00; + i2c_writebytes(state,0x08,buf,1); + + /* write agc sdm configure */ + buf[0] = 0xF1; + i2c_writebytes(state,0x57,buf,1); + + /* write mod output format */ + buf[0] = 0x20; + i2c_writebytes(state,0x09,buf,1); + + /* write fec mpeg mode */ + buf[0] = 0x7E; + buf[1] = 0x00; + i2c_writebytes(state,0xE9,buf,2); + + /* write mux selection */ + buf[0] = 0x00; + i2c_writebytes(state,0xCC,buf,1); + + state->initialised = 1; + } + + return 0; +} + +static int nxt2002_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* fesettings) +{ + fesettings->min_delay_ms = 500; + fesettings->step_size = 0; + fesettings->max_drift = 0; + return 0; +} + +static void nxt2002_release(struct dvb_frontend* fe) +{ + struct nxt2002_state* state = (struct nxt2002_state*) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops nxt2002_ops; + +struct dvb_frontend* nxt2002_attach(const struct nxt2002_config* config, + struct i2c_adapter* i2c) +{ + struct nxt2002_state* state = NULL; + u8 buf [] = {0,0,0,0,0}; + + /* allocate memory for the internal state */ + state = (struct nxt2002_state*) kmalloc(sizeof(struct nxt2002_state), GFP_KERNEL); + if (state == NULL) goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + memcpy(&state->ops, &nxt2002_ops, sizeof(struct dvb_frontend_ops)); + state->initialised = 0; + + /* Check the first 5 registers to ensure this a revision we can handle */ + + i2c_readbytes(state, 0x00, buf, 5); + if (buf[0] != 0x04) goto error; /* device id */ + if (buf[1] != 0x02) goto error; /* fab id */ + if (buf[2] != 0x11) goto error; /* month */ + if (buf[3] != 0x20) goto error; /* year msb */ + if (buf[4] != 0x00) goto error; /* year lsb */ + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops nxt2002_ops = { + + .info = { + .name = "Nextwave nxt2002 VSB/QAM frontend", + .type = FE_ATSC, + .frequency_min = 54000000, + .frequency_max = 860000000, + /* stepsize is just a guess */ + .frequency_stepsize = 166666, + .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_FEC_AUTO | + FE_CAN_8VSB | FE_CAN_QAM_64 | FE_CAN_QAM_256 + }, + + .release = nxt2002_release, + + .init = nxt2002_init, + .sleep = nxt2002_sleep, + + .set_frontend = nxt2002_setup_frontend_parameters, + .get_tune_settings = nxt2002_get_tune_settings, + + .read_status = nxt2002_read_status, + .read_ber = nxt2002_read_ber, + .read_signal_strength = nxt2002_read_signal_strength, + .read_snr = nxt2002_read_snr, + .read_ucblocks = nxt2002_read_ucblocks, + +}; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("NXT2002 ATSC (8VSB & ITU J83 AnnexB FEC QAM64/256) demodulator driver"); +MODULE_AUTHOR("Taylor Jacob"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(nxt2002_attach); diff --git a/drivers/media/dvb/frontends/nxt2002.h b/drivers/media/dvb/frontends/nxt2002.h new file mode 100644 index 000000000000..462301f577ee --- /dev/null +++ b/drivers/media/dvb/frontends/nxt2002.h @@ -0,0 +1,23 @@ +/* + Driver for the Nxt2002 demodulator +*/ + +#ifndef NXT2002_H +#define NXT2002_H + +#include <linux/dvb/frontend.h> +#include <linux/firmware.h> + +struct nxt2002_config +{ + /* the demodulator's i2c address */ + u8 demod_address; + + /* request firmware for device */ + int (*request_firmware)(struct dvb_frontend* fe, const struct firmware **fw, char* name); +}; + +extern struct dvb_frontend* nxt2002_attach(const struct nxt2002_config* config, + struct i2c_adapter* i2c); + +#endif // NXT2002_H diff --git a/drivers/media/dvb/frontends/nxt6000.c b/drivers/media/dvb/frontends/nxt6000.c new file mode 100644 index 000000000000..a41f7da8b842 --- /dev/null +++ b/drivers/media/dvb/frontends/nxt6000.c @@ -0,0 +1,554 @@ +/* + NxtWave Communications - NXT6000 demodulator driver + + Copyright (C) 2002-2003 Florian Schirmer <jolt@tuxbox.org> + Copyright (C) 2003 Paul Andreassen <paul@andreassen.com.au> + + 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 "dvb_frontend.h" +#include "nxt6000_priv.h" +#include "nxt6000.h" + + + +struct nxt6000_state { + struct i2c_adapter* i2c; + struct dvb_frontend_ops ops; + /* configuration settings */ + const struct nxt6000_config* config; + struct dvb_frontend frontend; +}; + +static int debug = 0; +#define dprintk if (debug) printk + +static int nxt6000_writereg(struct nxt6000_state* state, u8 reg, u8 data) +{ + u8 buf[] = { reg, data }; + struct i2c_msg msg = {.addr = state->config->demod_address,.flags = 0,.buf = buf,.len = 2 }; + int ret; + + if ((ret = i2c_transfer(state->i2c, &msg, 1)) != 1) + dprintk("nxt6000: nxt6000_write error (reg: 0x%02X, data: 0x%02X, ret: %d)\n", reg, data, ret); + + return (ret != 1) ? -EFAULT : 0; +} + +static u8 nxt6000_readreg(struct nxt6000_state* state, u8 reg) +{ + int ret; + u8 b0[] = { reg }; + u8 b1[] = { 0 }; + struct i2c_msg msgs[] = { + {.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, msgs, 2); + + if (ret != 2) + dprintk("nxt6000: nxt6000_read error (reg: 0x%02X, ret: %d)\n", reg, ret); + + return b1[0]; +} + +static void nxt6000_reset(struct nxt6000_state* state) +{ + u8 val; + + val = nxt6000_readreg(state, OFDM_COR_CTL); + + nxt6000_writereg(state, OFDM_COR_CTL, val & ~COREACT); + nxt6000_writereg(state, OFDM_COR_CTL, val | COREACT); +} + +static int nxt6000_set_bandwidth(struct nxt6000_state* state, fe_bandwidth_t bandwidth) +{ + u16 nominal_rate; + int result; + + switch (bandwidth) { + + case BANDWIDTH_6_MHZ: + nominal_rate = 0x55B7; + break; + + case BANDWIDTH_7_MHZ: + nominal_rate = 0x6400; + break; + + case BANDWIDTH_8_MHZ: + nominal_rate = 0x7249; + break; + + default: + return -EINVAL; + } + + if ((result = nxt6000_writereg(state, OFDM_TRL_NOMINALRATE_1, nominal_rate & 0xFF)) < 0) + return result; + + return nxt6000_writereg(state, OFDM_TRL_NOMINALRATE_2, (nominal_rate >> 8) & 0xFF); +} + +static int nxt6000_set_guard_interval(struct nxt6000_state* state, fe_guard_interval_t guard_interval) +{ + switch (guard_interval) { + + case GUARD_INTERVAL_1_32: + return nxt6000_writereg(state, OFDM_COR_MODEGUARD, 0x00 | (nxt6000_readreg(state, OFDM_COR_MODEGUARD) & ~0x03)); + + case GUARD_INTERVAL_1_16: + return nxt6000_writereg(state, OFDM_COR_MODEGUARD, 0x01 | (nxt6000_readreg(state, OFDM_COR_MODEGUARD) & ~0x03)); + + case GUARD_INTERVAL_AUTO: + case GUARD_INTERVAL_1_8: + return nxt6000_writereg(state, OFDM_COR_MODEGUARD, 0x02 | (nxt6000_readreg(state, OFDM_COR_MODEGUARD) & ~0x03)); + + case GUARD_INTERVAL_1_4: + return nxt6000_writereg(state, OFDM_COR_MODEGUARD, 0x03 | (nxt6000_readreg(state, OFDM_COR_MODEGUARD) & ~0x03)); + + default: + return -EINVAL; + } +} + +static int nxt6000_set_inversion(struct nxt6000_state* state, fe_spectral_inversion_t inversion) +{ + switch (inversion) { + + case INVERSION_OFF: + return nxt6000_writereg(state, OFDM_ITB_CTL, 0x00); + + case INVERSION_ON: + return nxt6000_writereg(state, OFDM_ITB_CTL, ITBINV); + + default: + return -EINVAL; + + } +} + +static int nxt6000_set_transmission_mode(struct nxt6000_state* state, fe_transmit_mode_t transmission_mode) +{ + int result; + + switch (transmission_mode) { + + case TRANSMISSION_MODE_2K: + if ((result = nxt6000_writereg(state, EN_DMD_RACQ, 0x00 | (nxt6000_readreg(state, EN_DMD_RACQ) & ~0x03))) < 0) + return result; + + return nxt6000_writereg(state, OFDM_COR_MODEGUARD, (0x00 << 2) | (nxt6000_readreg(state, OFDM_COR_MODEGUARD) & ~0x04)); + + case TRANSMISSION_MODE_8K: + case TRANSMISSION_MODE_AUTO: + if ((result = nxt6000_writereg(state, EN_DMD_RACQ, 0x02 | (nxt6000_readreg(state, EN_DMD_RACQ) & ~0x03))) < 0) + return result; + + return nxt6000_writereg(state, OFDM_COR_MODEGUARD, (0x01 << 2) | (nxt6000_readreg(state, OFDM_COR_MODEGUARD) & ~0x04)); + + default: + return -EINVAL; + + } +} + +static void nxt6000_setup(struct dvb_frontend* fe) +{ + struct nxt6000_state* state = (struct nxt6000_state*) fe->demodulator_priv; + + nxt6000_writereg(state, RS_COR_SYNC_PARAM, SYNC_PARAM); + nxt6000_writereg(state, BER_CTRL, /*(1 << 2) | */ (0x01 << 1) | 0x01); + nxt6000_writereg(state, VIT_COR_CTL, VIT_COR_RESYNC); + nxt6000_writereg(state, OFDM_COR_CTL, (0x01 << 5) | (nxt6000_readreg(state, OFDM_COR_CTL) & 0x0F)); + nxt6000_writereg(state, OFDM_COR_MODEGUARD, FORCEMODE8K | 0x02); + nxt6000_writereg(state, OFDM_AGC_CTL, AGCLAST | INITIAL_AGC_BW); + nxt6000_writereg(state, OFDM_ITB_FREQ_1, 0x06); + nxt6000_writereg(state, OFDM_ITB_FREQ_2, 0x31); + nxt6000_writereg(state, OFDM_CAS_CTL, (0x01 << 7) | (0x02 << 3) | 0x04); + nxt6000_writereg(state, CAS_FREQ, 0xBB); /* CHECKME */ + nxt6000_writereg(state, OFDM_SYR_CTL, 1 << 2); + nxt6000_writereg(state, OFDM_PPM_CTL_1, PPM256); + nxt6000_writereg(state, OFDM_TRL_NOMINALRATE_1, 0x49); + nxt6000_writereg(state, OFDM_TRL_NOMINALRATE_2, 0x72); + nxt6000_writereg(state, ANALOG_CONTROL_0, 1 << 5); + nxt6000_writereg(state, EN_DMD_RACQ, (1 << 7) | (3 << 4) | 2); + nxt6000_writereg(state, DIAG_CONFIG, TB_SET); + + if (state->config->clock_inversion) + nxt6000_writereg(state, SUB_DIAG_MODE_SEL, CLKINVERSION); + else + nxt6000_writereg(state, SUB_DIAG_MODE_SEL, 0); + + nxt6000_writereg(state, TS_FORMAT, 0); + + if (state->config->pll_init) { + nxt6000_writereg(state, ENABLE_TUNER_IIC, 0x01); /* open i2c bus switch */ + state->config->pll_init(fe); + nxt6000_writereg(state, ENABLE_TUNER_IIC, 0x00); /* close i2c bus switch */ + } +} + +static void nxt6000_dump_status(struct nxt6000_state *state) +{ + u8 val; + +/* + printk("RS_COR_STAT: 0x%02X\n", nxt6000_readreg(fe, RS_COR_STAT)); + printk("VIT_SYNC_STATUS: 0x%02X\n", nxt6000_readreg(fe, VIT_SYNC_STATUS)); + printk("OFDM_COR_STAT: 0x%02X\n", nxt6000_readreg(fe, OFDM_COR_STAT)); + printk("OFDM_SYR_STAT: 0x%02X\n", nxt6000_readreg(fe, OFDM_SYR_STAT)); + printk("OFDM_TPS_RCVD_1: 0x%02X\n", nxt6000_readreg(fe, OFDM_TPS_RCVD_1)); + printk("OFDM_TPS_RCVD_2: 0x%02X\n", nxt6000_readreg(fe, OFDM_TPS_RCVD_2)); + printk("OFDM_TPS_RCVD_3: 0x%02X\n", nxt6000_readreg(fe, OFDM_TPS_RCVD_3)); + printk("OFDM_TPS_RCVD_4: 0x%02X\n", nxt6000_readreg(fe, OFDM_TPS_RCVD_4)); + printk("OFDM_TPS_RESERVED_1: 0x%02X\n", nxt6000_readreg(fe, OFDM_TPS_RESERVED_1)); + printk("OFDM_TPS_RESERVED_2: 0x%02X\n", nxt6000_readreg(fe, OFDM_TPS_RESERVED_2)); +*/ + printk("NXT6000 status:"); + + val = nxt6000_readreg(state, RS_COR_STAT); + + printk(" DATA DESCR LOCK: %d,", val & 0x01); + printk(" DATA SYNC LOCK: %d,", (val >> 1) & 0x01); + + val = nxt6000_readreg(state, VIT_SYNC_STATUS); + + printk(" VITERBI LOCK: %d,", (val >> 7) & 0x01); + + switch ((val >> 4) & 0x07) { + + case 0x00: + printk(" VITERBI CODERATE: 1/2,"); + break; + + case 0x01: + printk(" VITERBI CODERATE: 2/3,"); + break; + + case 0x02: + printk(" VITERBI CODERATE: 3/4,"); + break; + + case 0x03: + printk(" VITERBI CODERATE: 5/6,"); + break; + + case 0x04: + printk(" VITERBI CODERATE: 7/8,"); + break; + + default: + printk(" VITERBI CODERATE: Reserved,"); + + } + + val = nxt6000_readreg(state, OFDM_COR_STAT); + + printk(" CHCTrack: %d,", (val >> 7) & 0x01); + printk(" TPSLock: %d,", (val >> 6) & 0x01); + printk(" SYRLock: %d,", (val >> 5) & 0x01); + printk(" AGCLock: %d,", (val >> 4) & 0x01); + + switch (val & 0x0F) { + + case 0x00: + printk(" CoreState: IDLE,"); + break; + + case 0x02: + printk(" CoreState: WAIT_AGC,"); + break; + + case 0x03: + printk(" CoreState: WAIT_SYR,"); + break; + + case 0x04: + printk(" CoreState: WAIT_PPM,"); + break; + + case 0x01: + printk(" CoreState: WAIT_TRL,"); + break; + + case 0x05: + printk(" CoreState: WAIT_TPS,"); + break; + + case 0x06: + printk(" CoreState: MONITOR_TPS,"); + break; + + default: + printk(" CoreState: Reserved,"); + + } + + val = nxt6000_readreg(state, OFDM_SYR_STAT); + + printk(" SYRLock: %d,", (val >> 4) & 0x01); + printk(" SYRMode: %s,", (val >> 2) & 0x01 ? "8K" : "2K"); + + switch ((val >> 4) & 0x03) { + + case 0x00: + printk(" SYRGuard: 1/32,"); + break; + + case 0x01: + printk(" SYRGuard: 1/16,"); + break; + + case 0x02: + printk(" SYRGuard: 1/8,"); + break; + + case 0x03: + printk(" SYRGuard: 1/4,"); + break; + } + + val = nxt6000_readreg(state, OFDM_TPS_RCVD_3); + + switch ((val >> 4) & 0x07) { + + case 0x00: + printk(" TPSLP: 1/2,"); + break; + + case 0x01: + printk(" TPSLP: 2/3,"); + break; + + case 0x02: + printk(" TPSLP: 3/4,"); + break; + + case 0x03: + printk(" TPSLP: 5/6,"); + break; + + case 0x04: + printk(" TPSLP: 7/8,"); + break; + + default: + printk(" TPSLP: Reserved,"); + + } + + switch (val & 0x07) { + + case 0x00: + printk(" TPSHP: 1/2,"); + break; + + case 0x01: + printk(" TPSHP: 2/3,"); + break; + + case 0x02: + printk(" TPSHP: 3/4,"); + break; + + case 0x03: + printk(" TPSHP: 5/6,"); + break; + + case 0x04: + printk(" TPSHP: 7/8,"); + break; + + default: + printk(" TPSHP: Reserved,"); + + } + + val = nxt6000_readreg(state, OFDM_TPS_RCVD_4); + + printk(" TPSMode: %s,", val & 0x01 ? "8K" : "2K"); + + switch ((val >> 4) & 0x03) { + + case 0x00: + printk(" TPSGuard: 1/32,"); + break; + + case 0x01: + printk(" TPSGuard: 1/16,"); + break; + + case 0x02: + printk(" TPSGuard: 1/8,"); + break; + + case 0x03: + printk(" TPSGuard: 1/4,"); + break; + + } + + /* Strange magic required to gain access to RF_AGC_STATUS */ + nxt6000_readreg(state, RF_AGC_VAL_1); + val = nxt6000_readreg(state, RF_AGC_STATUS); + val = nxt6000_readreg(state, RF_AGC_STATUS); + + printk(" RF AGC LOCK: %d,", (val >> 4) & 0x01); + printk("\n"); +} + +static int nxt6000_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + u8 core_status; + struct nxt6000_state* state = (struct nxt6000_state*) fe->demodulator_priv; + + *status = 0; + + core_status = nxt6000_readreg(state, OFDM_COR_STAT); + + if (core_status & AGCLOCKED) + *status |= FE_HAS_SIGNAL; + + if (nxt6000_readreg(state, OFDM_SYR_STAT) & GI14_SYR_LOCK) + *status |= FE_HAS_CARRIER; + + if (nxt6000_readreg(state, VIT_SYNC_STATUS) & VITINSYNC) + *status |= FE_HAS_VITERBI; + + if (nxt6000_readreg(state, RS_COR_STAT) & RSCORESTATUS) + *status |= FE_HAS_SYNC; + + if ((core_status & TPSLOCKED) && (*status == (FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_VITERBI | FE_HAS_SYNC))) + *status |= FE_HAS_LOCK; + + if (debug) + nxt6000_dump_status(state); + + return 0; +} + +static int nxt6000_init(struct dvb_frontend* fe) +{ + struct nxt6000_state* state = (struct nxt6000_state*) fe->demodulator_priv; + + nxt6000_reset(state); + nxt6000_setup(fe); + + return 0; +} + +static int nxt6000_set_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *param) +{ + struct nxt6000_state* state = (struct nxt6000_state*) fe->demodulator_priv; + int result; + + nxt6000_writereg(state, ENABLE_TUNER_IIC, 0x01); /* open i2c bus switch */ + state->config->pll_set(fe, param); + nxt6000_writereg(state, ENABLE_TUNER_IIC, 0x00); /* close i2c bus switch */ + + if ((result = nxt6000_set_bandwidth(state, param->u.ofdm.bandwidth)) < 0) + return result; + if ((result = nxt6000_set_guard_interval(state, param->u.ofdm.guard_interval)) < 0) + return result; + if ((result = nxt6000_set_transmission_mode(state, param->u.ofdm.transmission_mode)) < 0) + return result; + if ((result = nxt6000_set_inversion(state, param->inversion)) < 0) + return result; + + return 0; +} + +static void nxt6000_release(struct dvb_frontend* fe) +{ + struct nxt6000_state* state = (struct nxt6000_state*) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops nxt6000_ops; + +struct dvb_frontend* nxt6000_attach(const struct nxt6000_config* config, + struct i2c_adapter* i2c) +{ + struct nxt6000_state* state = NULL; + + /* allocate memory for the internal state */ + state = (struct nxt6000_state*) kmalloc(sizeof(struct nxt6000_state), GFP_KERNEL); + if (state == NULL) goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + memcpy(&state->ops, &nxt6000_ops, sizeof(struct dvb_frontend_ops)); + + /* check if the demod is there */ + if (nxt6000_readreg(state, OFDM_MSC_REV) != NXT6000ASICDEVICE) goto error; + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops nxt6000_ops = { + + .info = { + .name = "NxtWave NXT6000 DVB-T", + .type = FE_OFDM, + .frequency_min = 0, + .frequency_max = 863250000, + .frequency_stepsize = 62500, + /*.frequency_tolerance = *//* FIXME: 12% of SR */ + .symbol_rate_min = 0, /* FIXME */ + .symbol_rate_max = 9360000, /* FIXME */ + .symbol_rate_tolerance = 4000, + .caps = 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_8_9 | FE_CAN_FEC_AUTO | + FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_HIERARCHY_AUTO, + }, + + .release = nxt6000_release, + + .init = nxt6000_init, + + .set_frontend = nxt6000_set_frontend, + + .read_status = nxt6000_read_status, +}; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("NxtWave NXT6000 DVB-T demodulator driver"); +MODULE_AUTHOR("Florian Schirmer"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(nxt6000_attach); diff --git a/drivers/media/dvb/frontends/nxt6000.h b/drivers/media/dvb/frontends/nxt6000.h new file mode 100644 index 000000000000..b7d9bead3002 --- /dev/null +++ b/drivers/media/dvb/frontends/nxt6000.h @@ -0,0 +1,43 @@ +/* + NxtWave Communications - NXT6000 demodulator driver + + Copyright (C) 2002-2003 Florian Schirmer <jolt@tuxbox.org> + Copyright (C) 2003 Paul Andreassen <paul@andreassen.com.au> + + 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 NXT6000_H +#define NXT6000_H + +#include <linux/dvb/frontend.h> + +struct nxt6000_config +{ + /* the demodulator's i2c address */ + u8 demod_address; + + /* should clock inversion be used? */ + u8 clock_inversion:1; + + /* PLL maintenance */ + int (*pll_init)(struct dvb_frontend* fe); + int (*pll_set)(struct dvb_frontend* fe, struct dvb_frontend_parameters* params); +}; + +extern struct dvb_frontend* nxt6000_attach(const struct nxt6000_config* config, + struct i2c_adapter* i2c); + +#endif // NXT6000_H diff --git a/drivers/media/dvb/frontends/nxt6000_priv.h b/drivers/media/dvb/frontends/nxt6000_priv.h new file mode 100644 index 000000000000..64b1a89b2a22 --- /dev/null +++ b/drivers/media/dvb/frontends/nxt6000_priv.h @@ -0,0 +1,265 @@ +/* + * Public Include File for DRV6000 users + * (ie. NxtWave Communications - NXT6000 demodulator driver) + * + * Copyright (C) 2001 NxtWave Communications, Inc. + * + */ + +/* Nxt6000 Register Addresses and Bit Masks */ + +/* Maximum Register Number */ +#define MAXNXT6000REG (0x9A) + +/* 0x1B A_VIT_BER_0 aka 0x3A */ +#define A_VIT_BER_0 (0x1B) + +/* 0x1D A_VIT_BER_TIMER_0 aka 0x38 */ +#define A_VIT_BER_TIMER_0 (0x1D) + +/* 0x21 RS_COR_STAT */ +#define RS_COR_STAT (0x21) +#define RSCORESTATUS (0x03) + +/* 0x22 RS_COR_INTEN */ +#define RS_COR_INTEN (0x22) + +/* 0x23 RS_COR_INSTAT */ +#define RS_COR_INSTAT (0x23) +#define INSTAT_ERROR (0x04) +#define LOCK_LOSS_BITS (0x03) + +/* 0x24 RS_COR_SYNC_PARAM */ +#define RS_COR_SYNC_PARAM (0x24) +#define SYNC_PARAM (0x03) + +/* 0x25 BER_CTRL */ +#define BER_CTRL (0x25) +#define BER_ENABLE (0x02) +#define BER_RESET (0x01) + +/* 0x26 BER_PAY */ +#define BER_PAY (0x26) + +/* 0x27 BER_PKT_L */ +#define BER_PKT_L (0x27) +#define BER_PKTOVERFLOW (0x80) + +/* 0x30 VIT_COR_CTL */ +#define VIT_COR_CTL (0x30) +#define BER_CONTROL (0x02) +#define VIT_COR_MASK (0x82) +#define VIT_COR_RESYNC (0x80) + + +/* 0x32 VIT_SYNC_STATUS */ +#define VIT_SYNC_STATUS (0x32) +#define VITINSYNC (0x80) + +/* 0x33 VIT_COR_INTEN */ +#define VIT_COR_INTEN (0x33) +#define GLOBAL_ENABLE (0x80) + +/* 0x34 VIT_COR_INTSTAT */ +#define VIT_COR_INTSTAT (0x34) +#define BER_DONE (0x08) +#define BER_OVERFLOW (0x10) + + /* 0x38 OFDM_BERTimer *//* Use the alias registers */ +#define A_VIT_BER_TIMER_0 (0x1D) + + /* 0x3A VIT_BER_TIMER_0 *//* Use the alias registers */ +#define A_VIT_BER_0 (0x1B) + +/* 0x40 OFDM_COR_CTL */ +#define OFDM_COR_CTL (0x40) +#define COREACT (0x20) +#define HOLDSM (0x10) +#define WAIT_AGC (0x02) +#define WAIT_SYR (0x03) + +/* 0x41 OFDM_COR_STAT */ +#define OFDM_COR_STAT (0x41) +#define COR_STATUS (0x0F) +#define MONITOR_TPS (0x06) +#define TPSLOCKED (0x40) +#define AGCLOCKED (0x10) + +/* 0x42 OFDM_COR_INTEN */ +#define OFDM_COR_INTEN (0x42) +#define TPSRCVBAD (0x04) +#define TPSRCVCHANGED (0x02) +#define TPSRCVUPDATE (0x01) + +/* 0x43 OFDM_COR_INSTAT */ +#define OFDM_COR_INSTAT (0x43) + +/* 0x44 OFDM_COR_MODEGUARD */ +#define OFDM_COR_MODEGUARD (0x44) +#define FORCEMODE (0x08) +#define FORCEMODE8K (0x04) + +/* 0x45 OFDM_AGC_CTL */ +#define OFDM_AGC_CTL (0x45) +#define INITIAL_AGC_BW (0x08) +#define AGCNEG (0x02) +#define AGCLAST (0x10) + +/* 0x48 OFDM_AGC_TARGET */ +#define OFDM_AGC_TARGET (0x48) +#define OFDM_AGC_TARGET_DEFAULT (0x28) +#define OFDM_AGC_TARGET_IMPULSE (0x38) + +/* 0x49 OFDM_AGC_GAIN_1 */ +#define OFDM_AGC_GAIN_1 (0x49) + +/* 0x4B OFDM_ITB_CTL */ +#define OFDM_ITB_CTL (0x4B) +#define ITBINV (0x01) + +/* 0x4C OFDM_ITB_FREQ_1 */ +#define OFDM_ITB_FREQ_1 (0x4C) + +/* 0x4D OFDM_ITB_FREQ_2 */ +#define OFDM_ITB_FREQ_2 (0x4D) + +/* 0x4E OFDM_CAS_CTL */ +#define OFDM_CAS_CTL (0x4E) +#define ACSDIS (0x40) +#define CCSEN (0x80) + +/* 0x4F CAS_FREQ */ +#define CAS_FREQ (0x4F) + +/* 0x51 OFDM_SYR_CTL */ +#define OFDM_SYR_CTL (0x51) +#define SIXTH_ENABLE (0x80) +#define SYR_TRACKING_DISABLE (0x01) + +/* 0x52 OFDM_SYR_STAT */ +#define OFDM_SYR_STAT (0x52) +#define GI14_2K_SYR_LOCK (0x13) +#define GI14_8K_SYR_LOCK (0x17) +#define GI14_SYR_LOCK (0x10) + +/* 0x55 OFDM_SYR_OFFSET_1 */ +#define OFDM_SYR_OFFSET_1 (0x55) + +/* 0x56 OFDM_SYR_OFFSET_2 */ +#define OFDM_SYR_OFFSET_2 (0x56) + +/* 0x58 OFDM_SCR_CTL */ +#define OFDM_SCR_CTL (0x58) +#define SYR_ADJ_DECAY_MASK (0x70) +#define SYR_ADJ_DECAY (0x30) + +/* 0x59 OFDM_PPM_CTL_1 */ +#define OFDM_PPM_CTL_1 (0x59) +#define PPMMAX_MASK (0x30) +#define PPM256 (0x30) + +/* 0x5B OFDM_TRL_NOMINALRATE_1 */ +#define OFDM_TRL_NOMINALRATE_1 (0x5B) + +/* 0x5C OFDM_TRL_NOMINALRATE_2 */ +#define OFDM_TRL_NOMINALRATE_2 (0x5C) + +/* 0x5D OFDM_TRL_TIME_1 */ +#define OFDM_TRL_TIME_1 (0x5D) + +/* 0x60 OFDM_CRL_FREQ_1 */ +#define OFDM_CRL_FREQ_1 (0x60) + +/* 0x63 OFDM_CHC_CTL_1 */ +#define OFDM_CHC_CTL_1 (0x63) +#define MANMEAN1 (0xF0); +#define CHCFIR (0x01) + +/* 0x64 OFDM_CHC_SNR */ +#define OFDM_CHC_SNR (0x64) + +/* 0x65 OFDM_BDI_CTL */ +#define OFDM_BDI_CTL (0x65) +#define LP_SELECT (0x02) + +/* 0x67 OFDM_TPS_RCVD_1 */ +#define OFDM_TPS_RCVD_1 (0x67) +#define TPSFRAME (0x03) + +/* 0x68 OFDM_TPS_RCVD_2 */ +#define OFDM_TPS_RCVD_2 (0x68) + +/* 0x69 OFDM_TPS_RCVD_3 */ +#define OFDM_TPS_RCVD_3 (0x69) + +/* 0x6A OFDM_TPS_RCVD_4 */ +#define OFDM_TPS_RCVD_4 (0x6A) + +/* 0x6B OFDM_TPS_RESERVED_1 */ +#define OFDM_TPS_RESERVED_1 (0x6B) + +/* 0x6C OFDM_TPS_RESERVED_2 */ +#define OFDM_TPS_RESERVED_2 (0x6C) + +/* 0x73 OFDM_MSC_REV */ +#define OFDM_MSC_REV (0x73) + +/* 0x76 OFDM_SNR_CARRIER_2 */ +#define OFDM_SNR_CARRIER_2 (0x76) +#define MEAN_MASK (0x80) +#define MEANBIT (0x80) + +/* 0x80 ANALOG_CONTROL_0 */ +#define ANALOG_CONTROL_0 (0x80) +#define POWER_DOWN_ADC (0x40) + +/* 0x81 ENABLE_TUNER_IIC */ +#define ENABLE_TUNER_IIC (0x81) +#define ENABLE_TUNER_BIT (0x01) + +/* 0x82 EN_DMD_RACQ */ +#define EN_DMD_RACQ (0x82) +#define EN_DMD_RACQ_REG_VAL (0x81) +#define EN_DMD_RACQ_REG_VAL_14 (0x01) + +/* 0x84 SNR_COMMAND */ +#define SNR_COMMAND (0x84) +#define SNRStat (0x80) + +/* 0x85 SNRCARRIERNUMBER_LSB */ +#define SNRCARRIERNUMBER_LSB (0x85) + +/* 0x87 SNRMINTHRESHOLD_LSB */ +#define SNRMINTHRESHOLD_LSB (0x87) + +/* 0x89 SNR_PER_CARRIER_LSB */ +#define SNR_PER_CARRIER_LSB (0x89) + +/* 0x8B SNRBELOWTHRESHOLD_LSB */ +#define SNRBELOWTHRESHOLD_LSB (0x8B) + +/* 0x91 RF_AGC_VAL_1 */ +#define RF_AGC_VAL_1 (0x91) + +/* 0x92 RF_AGC_STATUS */ +#define RF_AGC_STATUS (0x92) + +/* 0x98 DIAG_CONFIG */ +#define DIAG_CONFIG (0x98) +#define DIAG_MASK (0x70) +#define TB_SET (0x10) +#define TRAN_SELECT (0x07) +#define SERIAL_SELECT (0x01) + +/* 0x99 SUB_DIAG_MODE_SEL */ +#define SUB_DIAG_MODE_SEL (0x99) +#define CLKINVERSION (0x01) + +/* 0x9A TS_FORMAT */ +#define TS_FORMAT (0x9A) +#define ERROR_SENSE (0x08) +#define VALID_SENSE (0x04) +#define SYNC_SENSE (0x02) +#define GATED_CLOCK (0x01) + +#define NXT6000ASICDEVICE (0x0b) diff --git a/drivers/media/dvb/frontends/or51132.c b/drivers/media/dvb/frontends/or51132.c new file mode 100644 index 000000000000..df5dee7760a3 --- /dev/null +++ b/drivers/media/dvb/frontends/or51132.c @@ -0,0 +1,628 @@ +/* + * Support for OR51132 (pcHDTV HD-3000) - VSB/QAM + * + * Copyright (C) 2005 Kirk Lapray <kirk_lapray@bigfoot.com> + * + * Based on code from Jack Kelliher (kelliher@xmission.com) + * Copyright (C) 2002 & pcHDTV, inc. + * + * 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. + * +*/ + +/* + * This driver needs two external firmware files. Please copy + * "dvb-fe-or51132-vsb.fw" and "dvb-fe-or51132-qam.fw" to + * /usr/lib/hotplug/firmware/ or /lib/firmware/ + * (depending on configuration of firmware hotplug). + */ +#define OR51132_VSB_FIRMWARE "dvb-fe-or51132-vsb.fw" +#define OR51132_QAM_FIRMWARE "dvb-fe-or51132-qam.fw" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <asm/byteorder.h> + +#include "dvb_frontend.h" +#include "dvb-pll.h" +#include "or51132.h" + +static int debug; +#define dprintk(args...) \ + do { \ + if (debug) printk(KERN_DEBUG "or51132: " args); \ + } while (0) + + +struct or51132_state +{ + struct i2c_adapter* i2c; + struct dvb_frontend_ops ops; + + /* Configuration settings */ + const struct or51132_config* config; + + struct dvb_frontend frontend; + + /* Demodulator private data */ + fe_modulation_t current_modulation; + + /* Tuner private data */ + u32 current_frequency; +}; + +static int i2c_writebytes (struct or51132_state* state, u8 reg, u8 *buf, int len) +{ + int err; + struct i2c_msg msg; + msg.addr = reg; + msg.flags = 0; + msg.len = len; + msg.buf = buf; + + if ((err = i2c_transfer(state->i2c, &msg, 1)) != 1) { + printk(KERN_WARNING "or51132: i2c_writebytes error (addr %02x, err == %i)\n", reg, err); + return -EREMOTEIO; + } + + return 0; +} + +static u8 i2c_readbytes (struct or51132_state* state, u8 reg, u8* buf, int len) +{ + int err; + struct i2c_msg msg; + msg.addr = reg; + msg.flags = I2C_M_RD; + msg.len = len; + msg.buf = buf; + + if ((err = i2c_transfer(state->i2c, &msg, 1)) != 1) { + printk(KERN_WARNING "or51132: i2c_readbytes error (addr %02x, err == %i)\n", reg, err); + return -EREMOTEIO; + } + + return 0; +} + +static int or51132_load_firmware (struct dvb_frontend* fe, const struct firmware *fw) +{ + struct or51132_state* state = (struct or51132_state*) fe->demodulator_priv; + static u8 run_buf[] = {0x7F,0x01}; + static u8 get_ver_buf[] = {0x04,0x00,0x30,0x00,0x00}; + u8 rec_buf[14]; + u8 cmd_buf[14]; + u32 firmwareAsize, firmwareBsize; + int i,ret; + + dprintk("Firmware is %Zd bytes\n",fw->size); + + /* Get size of firmware A and B */ + firmwareAsize = le32_to_cpu(*((u32*)fw->data)); + dprintk("FirmwareA is %i bytes\n",firmwareAsize); + firmwareBsize = le32_to_cpu(*((u32*)(fw->data+4))); + dprintk("FirmwareB is %i bytes\n",firmwareBsize); + + /* Upload firmware */ + if ((ret = i2c_writebytes(state,state->config->demod_address, + &fw->data[8],firmwareAsize))) { + printk(KERN_WARNING "or51132: load_firmware error 1\n"); + return ret; + } + msleep(1); /* 1ms */ + if ((ret = i2c_writebytes(state,state->config->demod_address, + &fw->data[8+firmwareAsize],firmwareBsize))) { + printk(KERN_WARNING "or51132: load_firmware error 2\n"); + return ret; + } + msleep(1); /* 1ms */ + + if ((ret = i2c_writebytes(state,state->config->demod_address, + run_buf,2))) { + printk(KERN_WARNING "or51132: load_firmware error 3\n"); + return ret; + } + + /* Wait at least 5 msec */ + msleep(20); /* 10ms */ + + if ((ret = i2c_writebytes(state,state->config->demod_address, + run_buf,2))) { + printk(KERN_WARNING "or51132: load_firmware error 4\n"); + return ret; + } + + /* 50ms for operation to begin */ + msleep(50); + + /* Read back ucode version to besure we loaded correctly and are really up and running */ + /* Get uCode version */ + cmd_buf[0] = 0x10; + cmd_buf[1] = 0x10; + cmd_buf[2] = 0x00; + cmd_buf[3] = 0x00; + msleep(20); /* 20ms */ + if ((ret = i2c_writebytes(state,state->config->demod_address, + cmd_buf,3))) { + printk(KERN_WARNING "or51132: load_firmware error a\n"); + return ret; + } + + cmd_buf[0] = 0x04; + cmd_buf[1] = 0x17; + cmd_buf[2] = 0x00; + cmd_buf[3] = 0x00; + msleep(20); /* 20ms */ + if ((ret = i2c_writebytes(state,state->config->demod_address, + cmd_buf,2))) { + printk(KERN_WARNING "or51132: load_firmware error b\n"); + return ret; + } + + cmd_buf[0] = 0x00; + cmd_buf[1] = 0x00; + cmd_buf[2] = 0x00; + cmd_buf[3] = 0x00; + msleep(20); /* 20ms */ + if ((ret = i2c_writebytes(state,state->config->demod_address, + cmd_buf,2))) { + printk(KERN_WARNING "or51132: load_firmware error c\n"); + return ret; + } + + for(i=0;i<4;i++) { + msleep(20); /* 20ms */ + get_ver_buf[4] = i+1; + if ((ret = i2c_readbytes(state,state->config->demod_address, + &rec_buf[i*2],2))) { + printk(KERN_WARNING + "or51132: load_firmware error d - %d\n",i); + return ret; + } + } + + printk(KERN_WARNING + "or51132: Version: %02X%02X%02X%02X-%02X%02X%02X%02X (%02X%01X-%01X-%02X%01X-%01X)\n", + rec_buf[1],rec_buf[0],rec_buf[3],rec_buf[2], + rec_buf[5],rec_buf[4],rec_buf[7],rec_buf[6], + rec_buf[3],rec_buf[2]>>4,rec_buf[2]&0x0f, + rec_buf[5],rec_buf[4]>>4,rec_buf[4]&0x0f); + + cmd_buf[0] = 0x10; + cmd_buf[1] = 0x00; + cmd_buf[2] = 0x00; + cmd_buf[3] = 0x00; + msleep(20); /* 20ms */ + if ((ret = i2c_writebytes(state,state->config->demod_address, + cmd_buf,3))) { + printk(KERN_WARNING "or51132: load_firmware error e\n"); + return ret; + } + return 0; +}; + +static int or51132_init(struct dvb_frontend* fe) +{ + return 0; +} + +static int or51132_read_ber(struct dvb_frontend* fe, u32* ber) +{ + *ber = 0; + return 0; +} + +static int or51132_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + *ucblocks = 0; + return 0; +} + +static int or51132_sleep(struct dvb_frontend* fe) +{ + return 0; +} + +static int or51132_setmode(struct dvb_frontend* fe) +{ + struct or51132_state* state = (struct or51132_state*) fe->demodulator_priv; + unsigned char cmd_buf[4]; + + dprintk("setmode %d\n",(int)state->current_modulation); + /* set operation mode in Receiver 1 register; */ + cmd_buf[0] = 0x04; + cmd_buf[1] = 0x01; + switch (state->current_modulation) { + case QAM_256: + case QAM_64: + case QAM_AUTO: + /* Auto-deinterleave; MPEG ser, MPEG2tr, phase noise-high*/ + cmd_buf[2] = 0x5F; + break; + case VSB_8: + /* Auto CH, Auto NTSC rej, MPEGser, MPEG2tr, phase noise-high*/ + cmd_buf[2] = 0x50; + break; + default: + printk("setmode:Modulation set to unsupported value\n"); + }; + cmd_buf[3] = 0x00; + if (i2c_writebytes(state,state->config->demod_address, + cmd_buf,3)) { + printk(KERN_WARNING "or51132: set_mode error 1\n"); + return -1; + } + dprintk("or51132: set #1 to %02x\n", cmd_buf[2]); + + /* Set operation mode in Receiver 6 register */ + cmd_buf[0] = 0x1C; + switch (state->current_modulation) { + case QAM_AUTO: + /* REC MODE Normal Carrier Lock */ + cmd_buf[1] = 0x00; + /* Channel MODE Auto QAM64/256 */ + cmd_buf[2] = 0x4f; + break; + case QAM_256: + /* REC MODE Normal Carrier Lock */ + cmd_buf[1] = 0x00; + /* Channel MODE QAM256 */ + cmd_buf[2] = 0x45; + break; + case QAM_64: + /* REC MODE Normal Carrier Lock */ + cmd_buf[1] = 0x00; + /* Channel MODE QAM64 */ + cmd_buf[2] = 0x43; + break; + case VSB_8: + /* REC MODE inv IF spectrum, Normal */ + cmd_buf[1] = 0x03; + /* Channel MODE ATSC/VSB8 */ + cmd_buf[2] = 0x06; + break; + default: + printk("setmode: Modulation set to unsupported value\n"); + }; + cmd_buf[3] = 0x00; + msleep(20); /* 20ms */ + if (i2c_writebytes(state,state->config->demod_address, + cmd_buf,3)) { + printk(KERN_WARNING "or51132: set_mode error 2\n"); + return -1; + } + dprintk("or51132: set #6 to 0x%02x%02x\n", cmd_buf[1], cmd_buf[2]); + + return 0; +} + +static int or51132_set_parameters(struct dvb_frontend* fe, + struct dvb_frontend_parameters *param) +{ + int ret; + u8 buf[4]; + struct or51132_state* state = (struct or51132_state*) fe->demodulator_priv; + const struct firmware *fw; + + /* Change only if we are actually changing the modulation */ + if (state->current_modulation != param->u.vsb.modulation) { + switch(param->u.vsb.modulation) { + case VSB_8: + dprintk("set_parameters VSB MODE\n"); + printk("or51132: Waiting for firmware upload(%s)...\n", + OR51132_VSB_FIRMWARE); + ret = request_firmware(&fw, OR51132_VSB_FIRMWARE, + &state->i2c->dev); + if (ret){ + printk(KERN_WARNING "or51132: No firmware up" + "loaded(timeout or file not found?)\n"); + return ret; + } + /* Set non-punctured clock for VSB */ + state->config->set_ts_params(fe, 0); + break; + case QAM_AUTO: + case QAM_64: + case QAM_256: + dprintk("set_parameters QAM MODE\n"); + printk("or51132: Waiting for firmware upload(%s)...\n", + OR51132_QAM_FIRMWARE); + ret = request_firmware(&fw, OR51132_QAM_FIRMWARE, + &state->i2c->dev); + if (ret){ + printk(KERN_WARNING "or51132: No firmware up" + "loaded(timeout or file not found?)\n"); + return ret; + } + /* Set punctured clock for QAM */ + state->config->set_ts_params(fe, 1); + break; + default: + printk("or51132:Modulation type(%d) UNSUPPORTED\n", + param->u.vsb.modulation); + return -1; + }; + ret = or51132_load_firmware(fe, fw); + release_firmware(fw); + if (ret) { + printk(KERN_WARNING "or51132: Writing firmware to " + "device failed!\n"); + return ret; + } + printk("or51132: Firmware upload complete.\n"); + + state->current_modulation = param->u.vsb.modulation; + or51132_setmode(fe); + } + + /* Change only if we are actually changing the channel */ + if (state->current_frequency != param->frequency) { + dvb_pll_configure(state->config->pll_desc, buf, + param->frequency, 0); + dprintk("set_parameters tuner bytes: 0x%02x 0x%02x " + "0x%02x 0x%02x\n",buf[0],buf[1],buf[2],buf[3]); + if (i2c_writebytes(state, state->config->pll_address ,buf, 4)) + printk(KERN_WARNING "or51132: set_parameters error " + "writing to tuner\n"); + + /* Set to current mode */ + or51132_setmode(fe); + + /* Update current frequency */ + state->current_frequency = param->frequency; + } + return 0; +} + +static int or51132_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct or51132_state* state = (struct or51132_state*) fe->demodulator_priv; + unsigned char rec_buf[2]; + unsigned char snd_buf[2]; + *status = 0; + + /* Receiver Status */ + snd_buf[0]=0x04; + snd_buf[1]=0x00; + msleep(30); /* 30ms */ + if (i2c_writebytes(state,state->config->demod_address,snd_buf,2)) { + printk(KERN_WARNING "or51132: read_status write error\n"); + return -1; + } + msleep(30); /* 30ms */ + if (i2c_readbytes(state,state->config->demod_address,rec_buf,2)) { + printk(KERN_WARNING "or51132: read_status read error\n"); + return -1; + } + dprintk("read_status %x %x\n",rec_buf[0],rec_buf[1]); + + if (rec_buf[1] & 0x01) { /* Receiver Lock */ + *status |= FE_HAS_SIGNAL; + *status |= FE_HAS_CARRIER; + *status |= FE_HAS_VITERBI; + *status |= FE_HAS_SYNC; + *status |= FE_HAS_LOCK; + } + return 0; +} + +/* log10-1 table at .5 increments from 1 to 100.5 */ +static unsigned int i100x20log10[] = { + 0, 352, 602, 795, 954, 1088, 1204, 1306, 1397, 1480, + 1556, 1625, 1690, 1750, 1806, 1858, 1908, 1955, 2000, 2042, + 2082, 2121, 2158, 2193, 2227, 2260, 2292, 2322, 2352, 2380, + 2408, 2434, 2460, 2486, 2510, 2534, 2557, 2580, 2602, 2623, + 2644, 2664, 2684, 2704, 2723, 2742, 2760, 2778, 2795, 2813, + 2829, 2846, 2862, 2878, 2894, 2909, 2924, 2939, 2954, 2968, + 2982, 2996, 3010, 3023, 3037, 3050, 3062, 3075, 3088, 3100, + 3112, 3124, 3136, 3148, 3159, 3170, 3182, 3193, 3204, 3214, + 3225, 3236, 3246, 3256, 3266, 3276, 3286, 3296, 3306, 3316, + 3325, 3334, 3344, 3353, 3362, 3371, 3380, 3389, 3397, 3406, + 3415, 3423, 3432, 3440, 3448, 3456, 3464, 3472, 3480, 3488, + 3496, 3504, 3511, 3519, 3526, 3534, 3541, 3549, 3556, 3563, + 3570, 3577, 3584, 3591, 3598, 3605, 3612, 3619, 3625, 3632, + 3639, 3645, 3652, 3658, 3665, 3671, 3677, 3683, 3690, 3696, + 3702, 3708, 3714, 3720, 3726, 3732, 3738, 3744, 3750, 3755, + 3761, 3767, 3772, 3778, 3784, 3789, 3795, 3800, 3806, 3811, + 3816, 3822, 3827, 3832, 3838, 3843, 3848, 3853, 3858, 3863, + 3868, 3874, 3879, 3884, 3888, 3893, 3898, 3903, 3908, 3913, + 3918, 3922, 3927, 3932, 3936, 3941, 3946, 3950, 3955, 3960, + 3964, 3969, 3973, 3978, 3982, 3986, 3991, 3995, 4000, 4004, +}; + +static unsigned int denom[] = {1,1,100,1000,10000,100000,1000000,10000000,100000000}; + +static unsigned int i20Log10(unsigned short val) +{ + unsigned int rntval = 100; + unsigned int tmp = val; + unsigned int exp = 1; + + while(tmp > 100) {tmp /= 100; exp++;} + + val = (2 * val)/denom[exp]; + if (exp > 1) rntval = 2000*exp; + + rntval += i100x20log10[val]; + return rntval; +} + +static int or51132_read_signal_strength(struct dvb_frontend* fe, u16* strength) +{ + struct or51132_state* state = (struct or51132_state*) fe->demodulator_priv; + unsigned char rec_buf[2]; + unsigned char snd_buf[2]; + u8 rcvr_stat; + u16 snr_equ; + int usK; + + snd_buf[0]=0x04; + snd_buf[1]=0x02; /* SNR after Equalizer */ + msleep(30); /* 30ms */ + if (i2c_writebytes(state,state->config->demod_address,snd_buf,2)) { + printk(KERN_WARNING "or51132: read_status write error\n"); + return -1; + } + msleep(30); /* 30ms */ + if (i2c_readbytes(state,state->config->demod_address,rec_buf,2)) { + printk(KERN_WARNING "or51132: read_status read error\n"); + return -1; + } + snr_equ = rec_buf[0] | (rec_buf[1] << 8); + dprintk("read_signal_strength snr_equ %x %x (%i)\n",rec_buf[0],rec_buf[1],snr_equ); + + /* Receiver Status */ + snd_buf[0]=0x04; + snd_buf[1]=0x00; + msleep(30); /* 30ms */ + if (i2c_writebytes(state,state->config->demod_address,snd_buf,2)) { + printk(KERN_WARNING "or51132: read_signal_strength read_status write error\n"); + return -1; + } + msleep(30); /* 30ms */ + if (i2c_readbytes(state,state->config->demod_address,rec_buf,2)) { + printk(KERN_WARNING "or51132: read_signal_strength read_status read error\n"); + return -1; + } + dprintk("read_signal_strength read_status %x %x\n",rec_buf[0],rec_buf[1]); + rcvr_stat = rec_buf[1]; + usK = (rcvr_stat & 0x10) ? 3 : 0; + + /* The value reported back from the frontend will be FFFF=100% 0000=0% */ + *strength = (((8952 - i20Log10(snr_equ) - usK*100)/3+5)*65535)/1000; + dprintk("read_signal_strength %i\n",*strength); + + return 0; +} + +static int or51132_read_snr(struct dvb_frontend* fe, u16* snr) +{ + struct or51132_state* state = (struct or51132_state*) fe->demodulator_priv; + unsigned char rec_buf[2]; + unsigned char snd_buf[2]; + u16 snr_equ; + + snd_buf[0]=0x04; + snd_buf[1]=0x02; /* SNR after Equalizer */ + msleep(30); /* 30ms */ + if (i2c_writebytes(state,state->config->demod_address,snd_buf,2)) { + printk(KERN_WARNING "or51132: read_snr write error\n"); + return -1; + } + msleep(30); /* 30ms */ + if (i2c_readbytes(state,state->config->demod_address,rec_buf,2)) { + printk(KERN_WARNING "or51132: read_snr dvr read error\n"); + return -1; + } + snr_equ = rec_buf[0] | (rec_buf[1] << 8); + dprintk("read_snr snr_equ %x %x (%i)\n",rec_buf[0],rec_buf[1],snr_equ); + + *snr = 0xFFFF - snr_equ; + dprintk("read_snr %i\n",*snr); + + return 0; +} + +static int or51132_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* fe_tune_settings) +{ + fe_tune_settings->min_delay_ms = 500; + fe_tune_settings->step_size = 0; + fe_tune_settings->max_drift = 0; + + return 0; +} + +static void or51132_release(struct dvb_frontend* fe) +{ + struct or51132_state* state = (struct or51132_state*) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops or51132_ops; + +struct dvb_frontend* or51132_attach(const struct or51132_config* config, + struct i2c_adapter* i2c) +{ + struct or51132_state* state = NULL; + + /* Allocate memory for the internal state */ + state = kmalloc(sizeof(struct or51132_state), GFP_KERNEL); + if (state == NULL) + goto error; + + /* Setup the state */ + state->config = config; + state->i2c = i2c; + memcpy(&state->ops, &or51132_ops, sizeof(struct dvb_frontend_ops)); + state->current_frequency = -1; + state->current_modulation = -1; + + /* Create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + if (state) + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops or51132_ops = { + + .info = { + .name = "Oren OR51132 VSB/QAM Frontend", + .type = FE_ATSC, + .frequency_min = 44000000, + .frequency_max = 958000000, + .frequency_stepsize = 166666, + .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_FEC_AUTO | + FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_QAM_AUTO | + FE_CAN_8VSB + }, + + .release = or51132_release, + + .init = or51132_init, + .sleep = or51132_sleep, + + .set_frontend = or51132_set_parameters, + .get_tune_settings = or51132_get_tune_settings, + + .read_status = or51132_read_status, + .read_ber = or51132_read_ber, + .read_signal_strength = or51132_read_signal_strength, + .read_snr = or51132_read_snr, + .read_ucblocks = or51132_read_ucblocks, +}; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("OR51132 ATSC [pcHDTV HD-3000] (8VSB & ITU J83 AnnexB FEC QAM64/256) Demodulator Driver"); +MODULE_AUTHOR("Kirk Lapray"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(or51132_attach); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/dvb/frontends/or51132.h b/drivers/media/dvb/frontends/or51132.h new file mode 100644 index 000000000000..622cdd18381b --- /dev/null +++ b/drivers/media/dvb/frontends/or51132.h @@ -0,0 +1,48 @@ +/* + * Support for OR51132 (pcHDTV HD-3000) - VSB/QAM + * + * Copyright (C) 2005 Kirk Lapray <kirk_lapray@bigfoot.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 OR51132_H +#define OR51132_H + +#include <linux/firmware.h> +#include <linux/dvb/frontend.h> + +struct or51132_config +{ + /* The demodulator's i2c address */ + u8 demod_address; + u8 pll_address; + struct dvb_pll_desc *pll_desc; + + /* Need to set device param for start_dma */ + int (*set_ts_params)(struct dvb_frontend* fe, int is_punctured); +}; + +extern struct dvb_frontend* or51132_attach(const struct or51132_config* config, + struct i2c_adapter* i2c); + +#endif // OR51132_H + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/dvb/frontends/or51211.c b/drivers/media/dvb/frontends/or51211.c new file mode 100644 index 000000000000..ad56a9958404 --- /dev/null +++ b/drivers/media/dvb/frontends/or51211.c @@ -0,0 +1,631 @@ +/* + * Support for OR51211 (pcHDTV HD-2000) - VSB + * + * Copyright (C) 2005 Kirk Lapray <kirk_lapray@bigfoot.com> + * + * Based on code from Jack Kelliher (kelliher@xmission.com) + * Copyright (C) 2002 & pcHDTV, inc. + * + * 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. + * +*/ + +/* + * This driver needs external firmware. Please use the command + * "<kerneldir>/Documentation/dvb/get_dvb_firmware or51211" to + * download/extract it, and then copy it to /usr/lib/hotplug/firmware. + */ +#define OR51211_DEFAULT_FIRMWARE "dvb-fe-or51211.fw" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <asm/byteorder.h> + +#include "dvb_frontend.h" +#include "or51211.h" + +static int debug; +#define dprintk(args...) \ + do { \ + if (debug) printk(KERN_DEBUG "or51211: " args); \ + } while (0) + +static u8 run_buf[] = {0x7f,0x01}; +static u8 cmd_buf[] = {0x04,0x01,0x50,0x80,0x06}; // ATSC + +struct or51211_state { + + struct i2c_adapter* i2c; + struct dvb_frontend_ops ops; + + /* Configuration settings */ + const struct or51211_config* config; + + struct dvb_frontend frontend; + struct bt878* bt; + + /* Demodulator private data */ + u8 initialized:1; + + /* Tuner private data */ + u32 current_frequency; +}; + +static int i2c_writebytes (struct or51211_state* state, u8 reg, u8 *buf, + int len) +{ + int err; + struct i2c_msg msg; + msg.addr = reg; + msg.flags = 0; + msg.len = len; + msg.buf = buf; + + if ((err = i2c_transfer (state->i2c, &msg, 1)) != 1) { + printk(KERN_WARNING "or51211: i2c_writebytes error " + "(addr %02x, err == %i)\n", reg, err); + return -EREMOTEIO; + } + + return 0; +} + +static u8 i2c_readbytes (struct or51211_state* state, u8 reg, u8* buf, int len) +{ + int err; + struct i2c_msg msg; + msg.addr = reg; + msg.flags = I2C_M_RD; + msg.len = len; + msg.buf = buf; + + if ((err = i2c_transfer (state->i2c, &msg, 1)) != 1) { + printk(KERN_WARNING "or51211: i2c_readbytes error " + "(addr %02x, err == %i)\n", reg, err); + return -EREMOTEIO; + } + + return 0; +} + +static int or51211_load_firmware (struct dvb_frontend* fe, + const struct firmware *fw) +{ + struct or51211_state* state = fe->demodulator_priv; + u8 tudata[585]; + int i; + + dprintk("Firmware is %d bytes\n",fw->size); + + /* Get eprom data */ + tudata[0] = 17; + if (i2c_writebytes(state,0x50,tudata,1)) { + printk(KERN_WARNING "or51211:load_firmware error eprom addr\n"); + return -1; + } + if (i2c_readbytes(state,0x50,&tudata[145],192)) { + printk(KERN_WARNING "or51211: load_firmware error eprom\n"); + return -1; + } + + /* Create firmware buffer */ + for (i = 0; i < 145; i++) + tudata[i] = fw->data[i]; + + for (i = 0; i < 248; i++) + tudata[i+337] = fw->data[145+i]; + + state->config->reset(fe); + + if (i2c_writebytes(state,state->config->demod_address,tudata,585)) { + printk(KERN_WARNING "or51211: load_firmware error 1\n"); + return -1; + } + msleep(1); + + if (i2c_writebytes(state,state->config->demod_address, + &fw->data[393],8125)) { + printk(KERN_WARNING "or51211: load_firmware error 2\n"); + return -1; + } + msleep(1); + + if (i2c_writebytes(state,state->config->demod_address,run_buf,2)) { + printk(KERN_WARNING "or51211: load_firmware error 3\n"); + return -1; + } + + /* Wait at least 5 msec */ + msleep(10); + if (i2c_writebytes(state,state->config->demod_address,run_buf,2)) { + printk(KERN_WARNING "or51211: load_firmware error 4\n"); + return -1; + } + msleep(10); + + printk("or51211: Done.\n"); + return 0; +}; + +static int or51211_setmode(struct dvb_frontend* fe, int mode) +{ + struct or51211_state* state = fe->demodulator_priv; + u8 rec_buf[14]; + + state->config->setmode(fe, mode); + + if (i2c_writebytes(state,state->config->demod_address,run_buf,2)) { + printk(KERN_WARNING "or51211: setmode error 1\n"); + return -1; + } + + /* Wait at least 5 msec */ + msleep(10); + if (i2c_writebytes(state,state->config->demod_address,run_buf,2)) { + printk(KERN_WARNING "or51211: setmode error 2\n"); + return -1; + } + + msleep(10); + + /* Set operation mode in Receiver 1 register; + * type 1: + * data 0x50h Automatic sets receiver channel conditions + * Automatic NTSC rejection filter + * Enable MPEG serial data output + * MPEG2tr + * High tuner phase noise + * normal +/-150kHz Carrier acquisition range + */ + if (i2c_writebytes(state,state->config->demod_address,cmd_buf,3)) { + printk(KERN_WARNING "or51211: setmode error 3\n"); + return -1; + } + + rec_buf[0] = 0x04; + rec_buf[1] = 0x00; + rec_buf[2] = 0x03; + rec_buf[3] = 0x00; + msleep(20); + if (i2c_writebytes(state,state->config->demod_address,rec_buf,3)) { + printk(KERN_WARNING "or51211: setmode error 5\n"); + } + msleep(3); + if (i2c_readbytes(state,state->config->demod_address,&rec_buf[10],2)) { + printk(KERN_WARNING "or51211: setmode error 6"); + return -1; + } + dprintk("setmode rec status %02x %02x\n",rec_buf[10],rec_buf[11]); + + return 0; +} + +static int or51211_set_parameters(struct dvb_frontend* fe, + struct dvb_frontend_parameters *param) +{ + struct or51211_state* state = fe->demodulator_priv; + u32 freq = 0; + u16 tunerfreq = 0; + u8 buf[4]; + + /* Change only if we are actually changing the channel */ + if (state->current_frequency != param->frequency) { + freq = 44000 + (param->frequency/1000); + tunerfreq = freq * 16/1000; + + dprintk("set_parameters frequency = %d (tunerfreq = %d)\n", + param->frequency,tunerfreq); + + buf[0] = (tunerfreq >> 8) & 0x7F; + buf[1] = (tunerfreq & 0xFF); + buf[2] = 0x8E; + + if (param->frequency < 157250000) { + buf[3] = 0xA0; + dprintk("set_parameters VHF low range\n"); + } else if (param->frequency < 454000000) { + buf[3] = 0x90; + dprintk("set_parameters VHF high range\n"); + } else { + buf[3] = 0x30; + dprintk("set_parameters UHF range\n"); + } + dprintk("set_parameters tuner bytes: 0x%02x 0x%02x " + "0x%02x 0x%02x\n",buf[0],buf[1],buf[2],buf[3]); + + if (i2c_writebytes(state,0xC2>>1,buf,4)) + printk(KERN_WARNING "or51211:set_parameters error " + "writing to tuner\n"); + + /* Set to ATSC mode */ + or51211_setmode(fe,0); + + /* Update current frequency */ + state->current_frequency = param->frequency; + } + return 0; +} + +static int or51211_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct or51211_state* state = fe->demodulator_priv; + unsigned char rec_buf[2]; + unsigned char snd_buf[] = {0x04,0x00,0x03,0x00}; + *status = 0; + + /* Receiver Status */ + if (i2c_writebytes(state,state->config->demod_address,snd_buf,3)) { + printk(KERN_WARNING "or51132: read_status write error\n"); + return -1; + } + msleep(3); + if (i2c_readbytes(state,state->config->demod_address,rec_buf,2)) { + printk(KERN_WARNING "or51132: read_status read error\n"); + return -1; + } + dprintk("read_status %x %x\n",rec_buf[0],rec_buf[1]); + + if (rec_buf[0] & 0x01) { /* Receiver Lock */ + *status |= FE_HAS_SIGNAL; + *status |= FE_HAS_CARRIER; + *status |= FE_HAS_VITERBI; + *status |= FE_HAS_SYNC; + *status |= FE_HAS_LOCK; + } + return 0; +} + +/* log10-1 table at .5 increments from 1 to 100.5 */ +static unsigned int i100x20log10[] = { + 0, 352, 602, 795, 954, 1088, 1204, 1306, 1397, 1480, + 1556, 1625, 1690, 1750, 1806, 1858, 1908, 1955, 2000, 2042, + 2082, 2121, 2158, 2193, 2227, 2260, 2292, 2322, 2352, 2380, + 2408, 2434, 2460, 2486, 2510, 2534, 2557, 2580, 2602, 2623, + 2644, 2664, 2684, 2704, 2723, 2742, 2760, 2778, 2795, 2813, + 2829, 2846, 2862, 2878, 2894, 2909, 2924, 2939, 2954, 2968, + 2982, 2996, 3010, 3023, 3037, 3050, 3062, 3075, 3088, 3100, + 3112, 3124, 3136, 3148, 3159, 3170, 3182, 3193, 3204, 3214, + 3225, 3236, 3246, 3256, 3266, 3276, 3286, 3296, 3306, 3316, + 3325, 3334, 3344, 3353, 3362, 3371, 3380, 3389, 3397, 3406, + 3415, 3423, 3432, 3440, 3448, 3456, 3464, 3472, 3480, 3488, + 3496, 3504, 3511, 3519, 3526, 3534, 3541, 3549, 3556, 3563, + 3570, 3577, 3584, 3591, 3598, 3605, 3612, 3619, 3625, 3632, + 3639, 3645, 3652, 3658, 3665, 3671, 3677, 3683, 3690, 3696, + 3702, 3708, 3714, 3720, 3726, 3732, 3738, 3744, 3750, 3755, + 3761, 3767, 3772, 3778, 3784, 3789, 3795, 3800, 3806, 3811, + 3816, 3822, 3827, 3832, 3838, 3843, 3848, 3853, 3858, 3863, + 3868, 3874, 3879, 3884, 3888, 3893, 3898, 3903, 3908, 3913, + 3918, 3922, 3927, 3932, 3936, 3941, 3946, 3950, 3955, 3960, + 3964, 3969, 3973, 3978, 3982, 3986, 3991, 3995, 4000, 4004, +}; + +static unsigned int denom[] = {1,1,100,1000,10000,100000,1000000,10000000,100000000}; + +static unsigned int i20Log10(unsigned short val) +{ + unsigned int rntval = 100; + unsigned int tmp = val; + unsigned int exp = 1; + + while(tmp > 100) {tmp /= 100; exp++;} + + val = (2 * val)/denom[exp]; + if (exp > 1) rntval = 2000*exp; + + rntval += i100x20log10[val]; + return rntval; +} + +static int or51211_read_signal_strength(struct dvb_frontend* fe, u16* strength) +{ + struct or51211_state* state = fe->demodulator_priv; + u8 rec_buf[2]; + u8 snd_buf[4]; + u8 snr_equ; + + /* SNR after Equalizer */ + snd_buf[0] = 0x04; + snd_buf[1] = 0x00; + snd_buf[2] = 0x04; + snd_buf[3] = 0x00; + + if (i2c_writebytes(state,state->config->demod_address,snd_buf,3)) { + printk(KERN_WARNING "or51211: read_status write error\n"); + return -1; + } + msleep(3); + if (i2c_readbytes(state,state->config->demod_address,rec_buf,2)) { + printk(KERN_WARNING "or51211: read_status read error\n"); + return -1; + } + snr_equ = rec_buf[0] & 0xff; + + /* The value reported back from the frontend will be FFFF=100% 0000=0% */ + *strength = (((5334 - i20Log10(snr_equ))/3+5)*65535)/1000; + + dprintk("read_signal_strength %i\n",*strength); + + return 0; +} + +static int or51211_read_snr(struct dvb_frontend* fe, u16* snr) +{ + struct or51211_state* state = fe->demodulator_priv; + u8 rec_buf[2]; + u8 snd_buf[4]; + + /* SNR after Equalizer */ + snd_buf[0] = 0x04; + snd_buf[1] = 0x00; + snd_buf[2] = 0x04; + snd_buf[3] = 0x00; + + if (i2c_writebytes(state,state->config->demod_address,snd_buf,3)) { + printk(KERN_WARNING "or51211: read_status write error\n"); + return -1; + } + msleep(3); + if (i2c_readbytes(state,state->config->demod_address,rec_buf,2)) { + printk(KERN_WARNING "or51211: read_status read error\n"); + return -1; + } + *snr = rec_buf[0] & 0xff; + + dprintk("read_snr %i\n",*snr); + + return 0; +} + +static int or51211_read_ber(struct dvb_frontend* fe, u32* ber) +{ + *ber = -ENOSYS; + return 0; +} + +static int or51211_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + *ucblocks = -ENOSYS; + return 0; +} + +static int or51211_sleep(struct dvb_frontend* fe) +{ + return 0; +} + +static int or51211_init(struct dvb_frontend* fe) +{ + struct or51211_state* state = fe->demodulator_priv; + const struct or51211_config* config = state->config; + const struct firmware* fw; + unsigned char get_ver_buf[] = {0x04,0x00,0x30,0x00,0x00}; + unsigned char rec_buf[14]; + int ret,i; + + if (!state->initialized) { + /* Request the firmware, this will block until it uploads */ + printk(KERN_INFO "or51211: Waiting for firmware upload " + "(%s)...\n", OR51211_DEFAULT_FIRMWARE); + ret = config->request_firmware(fe, &fw, + OR51211_DEFAULT_FIRMWARE); + printk(KERN_INFO "or51211:Got Hotplug firmware\n"); + if (ret) { + printk(KERN_WARNING "or51211: No firmware uploaded " + "(timeout or file not found?)\n"); + return ret; + } + + ret = or51211_load_firmware(fe, fw); + if (ret) { + printk(KERN_WARNING "or51211: Writing firmware to " + "device failed!\n"); + release_firmware(fw); + return ret; + } + printk(KERN_INFO "or51211: Firmware upload complete.\n"); + + /* Set operation mode in Receiver 1 register; + * type 1: + * data 0x50h Automatic sets receiver channel conditions + * Automatic NTSC rejection filter + * Enable MPEG serial data output + * MPEG2tr + * High tuner phase noise + * normal +/-150kHz Carrier acquisition range + */ + if (i2c_writebytes(state,state->config->demod_address, + cmd_buf,3)) { + printk(KERN_WARNING "or51211: Load DVR Error 5\n"); + return -1; + } + + /* Read back ucode version to besure we loaded correctly */ + /* and are really up and running */ + rec_buf[0] = 0x04; + rec_buf[1] = 0x00; + rec_buf[2] = 0x03; + rec_buf[3] = 0x00; + msleep(30); + if (i2c_writebytes(state,state->config->demod_address, + rec_buf,3)) { + printk(KERN_WARNING "or51211: Load DVR Error A\n"); + return -1; + } + msleep(3); + if (i2c_readbytes(state,state->config->demod_address, + &rec_buf[10],2)) { + printk(KERN_WARNING "or51211: Load DVR Error B\n"); + return -1; + } + + rec_buf[0] = 0x04; + rec_buf[1] = 0x00; + rec_buf[2] = 0x01; + rec_buf[3] = 0x00; + msleep(20); + if (i2c_writebytes(state,state->config->demod_address, + rec_buf,3)) { + printk(KERN_WARNING "or51211: Load DVR Error C\n"); + return -1; + } + msleep(3); + if (i2c_readbytes(state,state->config->demod_address, + &rec_buf[12],2)) { + printk(KERN_WARNING "or51211: Load DVR Error D\n"); + return -1; + } + + for (i = 0; i < 8; i++) + rec_buf[i]=0xed; + + for (i = 0; i < 5; i++) { + msleep(30); + get_ver_buf[4] = i+1; + if (i2c_writebytes(state,state->config->demod_address, + get_ver_buf,5)) { + printk(KERN_WARNING "or51211:Load DVR Error 6" + " - %d\n",i); + return -1; + } + msleep(3); + + if (i2c_readbytes(state,state->config->demod_address, + &rec_buf[i*2],2)) { + printk(KERN_WARNING "or51211:Load DVR Error 7" + " - %d\n",i); + return -1; + } + /* If we didn't receive the right index, try again */ + if ((int)rec_buf[i*2+1]!=i+1){ + i--; + } + } + dprintk("read_fwbits %x %x %x %x %x %x %x %x %x %x\n", + rec_buf[0], rec_buf[1], rec_buf[2], rec_buf[3], + rec_buf[4], rec_buf[5], rec_buf[6], rec_buf[7], + rec_buf[8], rec_buf[9]); + + printk(KERN_INFO "or51211: ver TU%02x%02x%02x VSB mode %02x" + " Status %02x\n", + rec_buf[2], rec_buf[4],rec_buf[6], + rec_buf[12],rec_buf[10]); + + rec_buf[0] = 0x04; + rec_buf[1] = 0x00; + rec_buf[2] = 0x03; + rec_buf[3] = 0x00; + msleep(20); + if (i2c_writebytes(state,state->config->demod_address, + rec_buf,3)) { + printk(KERN_WARNING "or51211: Load DVR Error 8\n"); + return -1; + } + msleep(20); + if (i2c_readbytes(state,state->config->demod_address, + &rec_buf[8],2)) { + printk(KERN_WARNING "or51211: Load DVR Error 9\n"); + return -1; + } + state->initialized = 1; + } + + return 0; +} + +static int or51211_get_tune_settings(struct dvb_frontend* fe, + struct dvb_frontend_tune_settings* fesettings) +{ + fesettings->min_delay_ms = 500; + fesettings->step_size = 0; + fesettings->max_drift = 0; + return 0; +} + +static void or51211_release(struct dvb_frontend* fe) +{ + struct or51211_state* state = fe->demodulator_priv; + state->config->sleep(fe); + kfree(state); +} + +static struct dvb_frontend_ops or51211_ops; + +struct dvb_frontend* or51211_attach(const struct or51211_config* config, + struct i2c_adapter* i2c) +{ + struct or51211_state* state = NULL; + + /* Allocate memory for the internal state */ + state = kmalloc(sizeof(struct or51211_state), GFP_KERNEL); + if (state == NULL) + goto error; + + /* Setup the state */ + state->config = config; + state->i2c = i2c; + memcpy(&state->ops, &or51211_ops, sizeof(struct dvb_frontend_ops)); + state->initialized = 0; + state->current_frequency = 0; + + /* Create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops or51211_ops = { + + .info = { + .name = "Oren OR51211 VSB Frontend", + .type = FE_ATSC, + .frequency_min = 44000000, + .frequency_max = 958000000, + .frequency_stepsize = 166666, + .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_FEC_AUTO | + FE_CAN_8VSB + }, + + .release = or51211_release, + + .init = or51211_init, + .sleep = or51211_sleep, + + .set_frontend = or51211_set_parameters, + .get_tune_settings = or51211_get_tune_settings, + + .read_status = or51211_read_status, + .read_ber = or51211_read_ber, + .read_signal_strength = or51211_read_signal_strength, + .read_snr = or51211_read_snr, + .read_ucblocks = or51211_read_ucblocks, +}; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("Oren OR51211 VSB [pcHDTV HD-2000] Demodulator Driver"); +MODULE_AUTHOR("Kirk Lapray"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(or51211_attach); + diff --git a/drivers/media/dvb/frontends/or51211.h b/drivers/media/dvb/frontends/or51211.h new file mode 100644 index 000000000000..13a5a3afbf8b --- /dev/null +++ b/drivers/media/dvb/frontends/or51211.h @@ -0,0 +1,44 @@ +/* + * Support for OR51211 (pcHDTV HD-2000) - VSB + * + * Copyright (C) 2005 Kirk Lapray <kirk_lapray@bigfoot.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 OR51211_H +#define OR51211_H + +#include <linux/dvb/frontend.h> +#include <linux/firmware.h> + +struct or51211_config +{ + /* The demodulator's i2c address */ + u8 demod_address; + + /* Request firmware for device */ + int (*request_firmware)(struct dvb_frontend* fe, const struct firmware **fw, char* name); + void (*setmode)(struct dvb_frontend * fe, int mode); + void (*reset)(struct dvb_frontend * fe); + void (*sleep)(struct dvb_frontend * fe); +}; + +extern struct dvb_frontend* or51211_attach(const struct or51211_config* config, + struct i2c_adapter* i2c); + +#endif // OR51211_H + diff --git a/drivers/media/dvb/frontends/sp8870.c b/drivers/media/dvb/frontends/sp8870.c new file mode 100644 index 000000000000..58ad34ef0a00 --- /dev/null +++ b/drivers/media/dvb/frontends/sp8870.c @@ -0,0 +1,614 @@ +/* + Driver for Spase SP8870 demodulator + + Copyright (C) 1999 Juergen Peitz + + 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. + +*/ +/* + * This driver needs external firmware. Please use the command + * "<kerneldir>/Documentation/dvb/get_dvb_firmware alps_tdlb7" to + * download/extract it, and then copy it to /usr/lib/hotplug/firmware. + */ +#define SP8870_DEFAULT_FIRMWARE "dvb-fe-sp8870.fw" + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/delay.h> + +#include "dvb_frontend.h" +#include "sp8870.h" + + +struct sp8870_state { + + struct i2c_adapter* i2c; + + struct dvb_frontend_ops ops; + + const struct sp8870_config* config; + + struct dvb_frontend frontend; + + /* demodulator private data */ + u8 initialised:1; +}; + +static int debug; +#define dprintk(args...) \ + do { \ + if (debug) printk(KERN_DEBUG "sp8870: " args); \ + } while (0) + +/* firmware size for sp8870 */ +#define SP8870_FIRMWARE_SIZE 16382 + +/* starting point for firmware in file 'Sc_main.mc' */ +#define SP8870_FIRMWARE_OFFSET 0x0A + +static int sp8870_writereg (struct sp8870_state* state, u16 reg, u16 data) +{ + u8 buf [] = { reg >> 8, reg & 0xff, data >> 8, data & 0xff }; + struct i2c_msg msg = { .addr = state->config->demod_address, .flags = 0, .buf = buf, .len = 4 }; + int err; + + if ((err = i2c_transfer (state->i2c, &msg, 1)) != 1) { + dprintk ("%s: writereg error (err == %i, reg == 0x%02x, data == 0x%02x)\n", __FUNCTION__, err, reg, data); + return -EREMOTEIO; + } + + return 0; +} + +static int sp8870_readreg (struct sp8870_state* state, u16 reg) +{ + int ret; + u8 b0 [] = { reg >> 8 , reg & 0xff }; + u8 b1 [] = { 0, 0 }; + struct i2c_msg msg [] = { { .addr = state->config->demod_address, .flags = 0, .buf = b0, .len = 2 }, + { .addr = state->config->demod_address, .flags = I2C_M_RD, .buf = b1, .len = 2 } }; + + ret = i2c_transfer (state->i2c, msg, 2); + + if (ret != 2) { + dprintk("%s: readreg error (ret == %i)\n", __FUNCTION__, ret); + return -1; + } + + return (b1[0] << 8 | b1[1]); +} + +static int sp8870_firmware_upload (struct sp8870_state* state, const struct firmware *fw) +{ + struct i2c_msg msg; + char *fw_buf = fw->data; + int fw_pos; + u8 tx_buf[255]; + int tx_len; + int err = 0; + + dprintk ("%s: ...\n", __FUNCTION__); + + if (fw->size < SP8870_FIRMWARE_SIZE + SP8870_FIRMWARE_OFFSET) + return -EINVAL; + + // system controller stop + sp8870_writereg(state, 0x0F00, 0x0000); + + // instruction RAM register hiword + sp8870_writereg(state, 0x8F08, ((SP8870_FIRMWARE_SIZE / 2) & 0xFFFF)); + + // instruction RAM MWR + sp8870_writereg(state, 0x8F0A, ((SP8870_FIRMWARE_SIZE / 2) >> 16)); + + // do firmware upload + fw_pos = SP8870_FIRMWARE_OFFSET; + while (fw_pos < SP8870_FIRMWARE_SIZE + SP8870_FIRMWARE_OFFSET){ + tx_len = (fw_pos <= SP8870_FIRMWARE_SIZE + SP8870_FIRMWARE_OFFSET - 252) ? 252 : SP8870_FIRMWARE_SIZE + SP8870_FIRMWARE_OFFSET - fw_pos; + // write register 0xCF0A + tx_buf[0] = 0xCF; + tx_buf[1] = 0x0A; + memcpy(&tx_buf[2], fw_buf + fw_pos, tx_len); + msg.addr = state->config->demod_address; + msg.flags = 0; + msg.buf = tx_buf; + msg.len = tx_len + 2; + if ((err = i2c_transfer (state->i2c, &msg, 1)) != 1) { + printk("%s: firmware upload failed!\n", __FUNCTION__); + printk ("%s: i2c error (err == %i)\n", __FUNCTION__, err); + return err; + } + fw_pos += tx_len; + } + + dprintk ("%s: done!\n", __FUNCTION__); + return 0; +}; + +static void sp8870_microcontroller_stop (struct sp8870_state* state) +{ + sp8870_writereg(state, 0x0F08, 0x000); + sp8870_writereg(state, 0x0F09, 0x000); + + // microcontroller STOP + sp8870_writereg(state, 0x0F00, 0x000); +} + +static void sp8870_microcontroller_start (struct sp8870_state* state) +{ + sp8870_writereg(state, 0x0F08, 0x000); + sp8870_writereg(state, 0x0F09, 0x000); + + // microcontroller START + sp8870_writereg(state, 0x0F00, 0x001); + // not documented but if we don't read 0x0D01 out here + // we don't get a correct data valid signal + sp8870_readreg(state, 0x0D01); +} + +static int sp8870_read_data_valid_signal(struct sp8870_state* state) +{ + return (sp8870_readreg(state, 0x0D02) > 0); +} + +static int configure_reg0xc05 (struct dvb_frontend_parameters *p, u16 *reg0xc05) +{ + int known_parameters = 1; + + *reg0xc05 = 0x000; + + switch (p->u.ofdm.constellation) { + case QPSK: + break; + case QAM_16: + *reg0xc05 |= (1 << 10); + break; + case QAM_64: + *reg0xc05 |= (2 << 10); + break; + case QAM_AUTO: + known_parameters = 0; + break; + default: + return -EINVAL; + }; + + switch (p->u.ofdm.hierarchy_information) { + case HIERARCHY_NONE: + break; + case HIERARCHY_1: + *reg0xc05 |= (1 << 7); + break; + case HIERARCHY_2: + *reg0xc05 |= (2 << 7); + break; + case HIERARCHY_4: + *reg0xc05 |= (3 << 7); + break; + case HIERARCHY_AUTO: + known_parameters = 0; + break; + default: + return -EINVAL; + }; + + switch (p->u.ofdm.code_rate_HP) { + case FEC_1_2: + break; + case FEC_2_3: + *reg0xc05 |= (1 << 3); + break; + case FEC_3_4: + *reg0xc05 |= (2 << 3); + break; + case FEC_5_6: + *reg0xc05 |= (3 << 3); + break; + case FEC_7_8: + *reg0xc05 |= (4 << 3); + break; + case FEC_AUTO: + known_parameters = 0; + break; + default: + return -EINVAL; + }; + + if (known_parameters) + *reg0xc05 |= (2 << 1); /* use specified parameters */ + else + *reg0xc05 |= (1 << 1); /* enable autoprobing */ + + return 0; +} + +static int sp8870_wake_up(struct sp8870_state* state) +{ + // enable TS output and interface pins + return sp8870_writereg(state, 0xC18, 0x00D); +} + +static int sp8870_set_frontend_parameters (struct dvb_frontend* fe, + struct dvb_frontend_parameters *p) +{ + struct sp8870_state* state = (struct sp8870_state*) fe->demodulator_priv; + int err; + u16 reg0xc05; + + if ((err = configure_reg0xc05(p, ®0xc05))) + return err; + + // system controller stop + sp8870_microcontroller_stop(state); + + // set tuner parameters + sp8870_writereg(state, 0x206, 0x001); + state->config->pll_set(fe, p); + sp8870_writereg(state, 0x206, 0x000); + + // sample rate correction bit [23..17] + sp8870_writereg(state, 0x0319, 0x000A); + + // sample rate correction bit [16..0] + sp8870_writereg(state, 0x031A, 0x0AAB); + + // integer carrier offset + sp8870_writereg(state, 0x0309, 0x0400); + + // fractional carrier offset + sp8870_writereg(state, 0x030A, 0x0000); + + // filter for 6/7/8 Mhz channel + if (p->u.ofdm.bandwidth == BANDWIDTH_6_MHZ) + sp8870_writereg(state, 0x0311, 0x0002); + else if (p->u.ofdm.bandwidth == BANDWIDTH_7_MHZ) + sp8870_writereg(state, 0x0311, 0x0001); + else + sp8870_writereg(state, 0x0311, 0x0000); + + // scan order: 2k first = 0x0000, 8k first = 0x0001 + if (p->u.ofdm.transmission_mode == TRANSMISSION_MODE_2K) + sp8870_writereg(state, 0x0338, 0x0000); + else + sp8870_writereg(state, 0x0338, 0x0001); + + sp8870_writereg(state, 0xc05, reg0xc05); + + // read status reg in order to clear pending irqs + sp8870_readreg(state, 0x200); + + // system controller start + sp8870_microcontroller_start(state); + + return 0; +} + +static int sp8870_init (struct dvb_frontend* fe) +{ + struct sp8870_state* state = (struct sp8870_state*) fe->demodulator_priv; + const struct firmware *fw = NULL; + + sp8870_wake_up(state); + if (state->initialised) return 0; + state->initialised = 1; + + dprintk ("%s\n", __FUNCTION__); + + + /* request the firmware, this will block until someone uploads it */ + printk("sp8870: waiting for firmware upload (%s)...\n", SP8870_DEFAULT_FIRMWARE); + if (state->config->request_firmware(fe, &fw, SP8870_DEFAULT_FIRMWARE)) { + printk("sp8870: no firmware upload (timeout or file not found?)\n"); + release_firmware(fw); + return -EIO; + } + + if (sp8870_firmware_upload(state, fw)) { + printk("sp8870: writing firmware to device failed\n"); + release_firmware(fw); + return -EIO; + } + printk("sp8870: firmware upload complete\n"); + + /* enable TS output and interface pins */ + sp8870_writereg(state, 0xc18, 0x00d); + + // system controller stop + sp8870_microcontroller_stop(state); + + // ADC mode + sp8870_writereg(state, 0x0301, 0x0003); + + // Reed Solomon parity bytes passed to output + sp8870_writereg(state, 0x0C13, 0x0001); + + // MPEG clock is suppressed if no valid data + sp8870_writereg(state, 0x0C14, 0x0001); + + /* bit 0x010: enable data valid signal */ + sp8870_writereg(state, 0x0D00, 0x010); + sp8870_writereg(state, 0x0D01, 0x000); + + /* setup PLL */ + if (state->config->pll_init) { + sp8870_writereg(state, 0x206, 0x001); + state->config->pll_init(fe); + sp8870_writereg(state, 0x206, 0x000); + } + + return 0; +} + +static int sp8870_read_status (struct dvb_frontend* fe, fe_status_t * fe_status) +{ + struct sp8870_state* state = (struct sp8870_state*) fe->demodulator_priv; + int status; + int signal; + + *fe_status = 0; + + status = sp8870_readreg (state, 0x0200); + if (status < 0) + return -EIO; + + signal = sp8870_readreg (state, 0x0303); + if (signal < 0) + return -EIO; + + if (signal > 0x0F) + *fe_status |= FE_HAS_SIGNAL; + if (status & 0x08) + *fe_status |= FE_HAS_SYNC; + if (status & 0x04) + *fe_status |= FE_HAS_LOCK | FE_HAS_CARRIER | FE_HAS_VITERBI; + + return 0; +} + +static int sp8870_read_ber (struct dvb_frontend* fe, u32 * ber) +{ + struct sp8870_state* state = (struct sp8870_state*) fe->demodulator_priv; + int ret; + u32 tmp; + + *ber = 0; + + ret = sp8870_readreg(state, 0xC08); + if (ret < 0) + return -EIO; + + tmp = ret & 0x3F; + + ret = sp8870_readreg(state, 0xC07); + if (ret < 0) + return -EIO; + + tmp = ret << 6; + + if (tmp >= 0x3FFF0) + tmp = ~0; + + *ber = tmp; + + return 0; +} + +static int sp8870_read_signal_strength(struct dvb_frontend* fe, u16 * signal) +{ + struct sp8870_state* state = (struct sp8870_state*) fe->demodulator_priv; + int ret; + u16 tmp; + + *signal = 0; + + ret = sp8870_readreg (state, 0x306); + if (ret < 0) + return -EIO; + + tmp = ret << 8; + + ret = sp8870_readreg (state, 0x303); + if (ret < 0) + return -EIO; + + tmp |= ret; + + if (tmp) + *signal = 0xFFFF - tmp; + + return 0; +} + +static int sp8870_read_uncorrected_blocks (struct dvb_frontend* fe, u32* ublocks) +{ + struct sp8870_state* state = (struct sp8870_state*) fe->demodulator_priv; + int ret; + + *ublocks = 0; + + ret = sp8870_readreg(state, 0xC0C); + if (ret < 0) + return -EIO; + + if (ret == 0xFFFF) + ret = ~0; + + *ublocks = ret; + + return 0; +} + +// number of trials to recover from lockup +#define MAXTRIALS 5 +// maximum checks for data valid signal +#define MAXCHECKS 100 + +// only for debugging: counter for detected lockups +static int lockups = 0; +// only for debugging: counter for channel switches +static int switches = 0; + +static int sp8870_set_frontend (struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + struct sp8870_state* state = (struct sp8870_state*) fe->demodulator_priv; + + /* + The firmware of the sp8870 sometimes locks up after setting frontend parameters. + We try to detect this by checking the data valid signal. + If it is not set after MAXCHECKS we try to recover the lockup by setting + the frontend parameters again. + */ + + int err = 0; + int valid = 0; + int trials = 0; + int check_count = 0; + + dprintk("%s: frequency = %i\n", __FUNCTION__, p->frequency); + + for (trials = 1; trials <= MAXTRIALS; trials++) { + + if ((err = sp8870_set_frontend_parameters(fe, p))) + return err; + + for (check_count = 0; check_count < MAXCHECKS; check_count++) { +// valid = ((sp8870_readreg(i2c, 0x0200) & 4) == 0); + valid = sp8870_read_data_valid_signal(state); + if (valid) { + dprintk("%s: delay = %i usec\n", + __FUNCTION__, check_count * 10); + break; + } + udelay(10); + } + if (valid) + break; + } + + if (!valid) { + printk("%s: firmware crash!!!!!!\n", __FUNCTION__); + return -EIO; + } + + if (debug) { + if (valid) { + if (trials > 1) { + printk("%s: firmware lockup!!!\n", __FUNCTION__); + printk("%s: recovered after %i trial(s))\n", __FUNCTION__, trials - 1); + lockups++; + } + } + switches++; + printk("%s: switches = %i lockups = %i\n", __FUNCTION__, switches, lockups); + } + + return 0; +} + +static int sp8870_sleep(struct dvb_frontend* fe) +{ + struct sp8870_state* state = (struct sp8870_state*) fe->demodulator_priv; + + // tristate TS output and disable interface pins + return sp8870_writereg(state, 0xC18, 0x000); +} + +static int sp8870_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* fesettings) +{ + fesettings->min_delay_ms = 350; + fesettings->step_size = 0; + fesettings->max_drift = 0; + return 0; +} + +static void sp8870_release(struct dvb_frontend* fe) +{ + struct sp8870_state* state = (struct sp8870_state*) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops sp8870_ops; + +struct dvb_frontend* sp8870_attach(const struct sp8870_config* config, + struct i2c_adapter* i2c) +{ + struct sp8870_state* state = NULL; + + /* allocate memory for the internal state */ + state = (struct sp8870_state*) kmalloc(sizeof(struct sp8870_state), GFP_KERNEL); + if (state == NULL) goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + memcpy(&state->ops, &sp8870_ops, sizeof(struct dvb_frontend_ops)); + state->initialised = 0; + + /* check if the demod is there */ + if (sp8870_readreg(state, 0x0200) < 0) goto error; + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops sp8870_ops = { + + .info = { + .name = "Spase SP8870 DVB-T", + .type = FE_OFDM, + .frequency_min = 470000000, + .frequency_max = 860000000, + .frequency_stepsize = 166666, + .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_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_QAM_16 | + FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | + FE_CAN_HIERARCHY_AUTO | FE_CAN_RECOVER + }, + + .release = sp8870_release, + + .init = sp8870_init, + .sleep = sp8870_sleep, + + .set_frontend = sp8870_set_frontend, + .get_tune_settings = sp8870_get_tune_settings, + + .read_status = sp8870_read_status, + .read_ber = sp8870_read_ber, + .read_signal_strength = sp8870_read_signal_strength, + .read_ucblocks = sp8870_read_uncorrected_blocks, +}; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("Spase SP8870 DVB-T Demodulator driver"); +MODULE_AUTHOR("Juergen Peitz"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(sp8870_attach); diff --git a/drivers/media/dvb/frontends/sp8870.h b/drivers/media/dvb/frontends/sp8870.h new file mode 100644 index 000000000000..f3b555dbc960 --- /dev/null +++ b/drivers/media/dvb/frontends/sp8870.h @@ -0,0 +1,45 @@ +/* + Driver for Spase SP8870 demodulator + + Copyright (C) 1999 Juergen Peitz + + 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 SP8870_H +#define SP8870_H + +#include <linux/dvb/frontend.h> +#include <linux/firmware.h> + +struct sp8870_config +{ + /* the demodulator's i2c address */ + u8 demod_address; + + /* PLL maintenance */ + int (*pll_init)(struct dvb_frontend* fe); + int (*pll_set)(struct dvb_frontend* fe, struct dvb_frontend_parameters* params); + + /* request firmware for device */ + int (*request_firmware)(struct dvb_frontend* fe, const struct firmware **fw, char* name); +}; + +extern struct dvb_frontend* sp8870_attach(const struct sp8870_config* config, + struct i2c_adapter* i2c); + +#endif // SP8870_H diff --git a/drivers/media/dvb/frontends/sp887x.c b/drivers/media/dvb/frontends/sp887x.c new file mode 100644 index 000000000000..7eae833ece49 --- /dev/null +++ b/drivers/media/dvb/frontends/sp887x.c @@ -0,0 +1,606 @@ +/* + Driver for the Spase sp887x demodulator +*/ + +/* + * This driver needs external firmware. Please use the command + * "<kerneldir>/Documentation/dvb/get_dvb_firmware sp887x" to + * download/extract it, and then copy it to /usr/lib/hotplug/firmware. + */ +#define SP887X_DEFAULT_FIRMWARE "dvb-fe-sp887x.fw" + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/firmware.h> + +#include "dvb_frontend.h" +#include "sp887x.h" + + +struct sp887x_state { + struct i2c_adapter* i2c; + struct dvb_frontend_ops ops; + const struct sp887x_config* config; + struct dvb_frontend frontend; + + /* demodulator private data */ + u8 initialised:1; +}; + +static int debug; +#define dprintk(args...) \ + do { \ + if (debug) printk(KERN_DEBUG "sp887x: " args); \ + } while (0) + +static int i2c_writebytes (struct sp887x_state* state, u8 *buf, u8 len) +{ + struct i2c_msg msg = { .addr = state->config->demod_address, .flags = 0, .buf = buf, .len = len }; + int err; + + if ((err = i2c_transfer (state->i2c, &msg, 1)) != 1) { + printk ("%s: i2c write error (addr %02x, err == %i)\n", + __FUNCTION__, state->config->demod_address, err); + return -EREMOTEIO; + } + + return 0; +} + +static int sp887x_writereg (struct sp887x_state* state, u16 reg, u16 data) +{ + u8 b0 [] = { reg >> 8 , reg & 0xff, data >> 8, data & 0xff }; + struct i2c_msg msg = { .addr = state->config->demod_address, .flags = 0, .buf = b0, .len = 4 }; + int ret; + + if ((ret = i2c_transfer(state->i2c, &msg, 1)) != 1) { + /** + * in case of soft reset we ignore ACK errors... + */ + if (!(reg == 0xf1a && data == 0x000 && + (ret == -EREMOTEIO || ret == -EFAULT))) + { + printk("%s: writereg error " + "(reg %03x, data %03x, ret == %i)\n", + __FUNCTION__, reg & 0xffff, data & 0xffff, ret); + return ret; + } + } + + return 0; +} + +static int sp887x_readreg (struct sp887x_state* state, u16 reg) +{ + u8 b0 [] = { reg >> 8 , reg & 0xff }; + u8 b1 [2]; + int ret; + struct i2c_msg msg[] = {{ .addr = state->config->demod_address, .flags = 0, .buf = b0, .len = 2 }, + { .addr = state->config->demod_address, .flags = I2C_M_RD, .buf = b1, .len = 2 }}; + + if ((ret = i2c_transfer(state->i2c, msg, 2)) != 2) { + printk("%s: readreg error (ret == %i)\n", __FUNCTION__, ret); + return -1; + } + + return (((b1[0] << 8) | b1[1]) & 0xfff); +} + +static void sp887x_microcontroller_stop (struct sp887x_state* state) +{ + dprintk("%s\n", __FUNCTION__); + sp887x_writereg(state, 0xf08, 0x000); + sp887x_writereg(state, 0xf09, 0x000); + + /* microcontroller STOP */ + sp887x_writereg(state, 0xf00, 0x000); +} + +static void sp887x_microcontroller_start (struct sp887x_state* state) +{ + dprintk("%s\n", __FUNCTION__); + sp887x_writereg(state, 0xf08, 0x000); + sp887x_writereg(state, 0xf09, 0x000); + + /* microcontroller START */ + sp887x_writereg(state, 0xf00, 0x001); +} + +static void sp887x_setup_agc (struct sp887x_state* state) +{ + /* setup AGC parameters */ + dprintk("%s\n", __FUNCTION__); + sp887x_writereg(state, 0x33c, 0x054); + sp887x_writereg(state, 0x33b, 0x04c); + sp887x_writereg(state, 0x328, 0x000); + sp887x_writereg(state, 0x327, 0x005); + sp887x_writereg(state, 0x326, 0x001); + sp887x_writereg(state, 0x325, 0x001); + sp887x_writereg(state, 0x324, 0x001); + sp887x_writereg(state, 0x318, 0x050); + sp887x_writereg(state, 0x317, 0x3fe); + sp887x_writereg(state, 0x316, 0x001); + sp887x_writereg(state, 0x313, 0x005); + sp887x_writereg(state, 0x312, 0x002); + sp887x_writereg(state, 0x306, 0x000); + sp887x_writereg(state, 0x303, 0x000); +} + +#define BLOCKSIZE 30 +#define FW_SIZE 0x4000 +/** + * load firmware and setup MPEG interface... + */ +static int sp887x_initial_setup (struct dvb_frontend* fe, const struct firmware *fw) +{ + struct sp887x_state* state = (struct sp887x_state*) fe->demodulator_priv; + u8 buf [BLOCKSIZE+2]; + int i; + int fw_size = fw->size; + unsigned char *mem = fw->data; + + dprintk("%s\n", __FUNCTION__); + + /* ignore the first 10 bytes, then we expect 0x4000 bytes of firmware */ + if (fw_size < FW_SIZE+10) + return -ENODEV; + + mem = fw->data + 10; + + /* soft reset */ + sp887x_writereg(state, 0xf1a, 0x000); + + sp887x_microcontroller_stop (state); + + printk ("%s: firmware upload... ", __FUNCTION__); + + /* setup write pointer to -1 (end of memory) */ + /* bit 0x8000 in address is set to enable 13bit mode */ + sp887x_writereg(state, 0x8f08, 0x1fff); + + /* dummy write (wrap around to start of memory) */ + sp887x_writereg(state, 0x8f0a, 0x0000); + + for (i = 0; i < FW_SIZE; i += BLOCKSIZE) { + int c = BLOCKSIZE; + int err; + + if (i+c > FW_SIZE) + c = FW_SIZE - i; + + /* bit 0x8000 in address is set to enable 13bit mode */ + /* bit 0x4000 enables multibyte read/write transfers */ + /* write register is 0xf0a */ + buf[0] = 0xcf; + buf[1] = 0x0a; + + memcpy(&buf[2], mem + i, c); + + if ((err = i2c_writebytes (state, buf, c+2)) < 0) { + printk ("failed.\n"); + printk ("%s: i2c error (err == %i)\n", __FUNCTION__, err); + return err; + } + } + + /* don't write RS bytes between packets */ + sp887x_writereg(state, 0xc13, 0x001); + + /* suppress clock if (!data_valid) */ + sp887x_writereg(state, 0xc14, 0x000); + + /* setup MPEG interface... */ + sp887x_writereg(state, 0xc1a, 0x872); + sp887x_writereg(state, 0xc1b, 0x001); + sp887x_writereg(state, 0xc1c, 0x000); /* parallel mode (serial mode == 1) */ + sp887x_writereg(state, 0xc1a, 0x871); + + /* ADC mode, 2 for MT8872, 3 for SP8870/SP8871 */ + sp887x_writereg(state, 0x301, 0x002); + + sp887x_setup_agc(state); + + /* bit 0x010: enable data valid signal */ + sp887x_writereg(state, 0xd00, 0x010); + sp887x_writereg(state, 0x0d1, 0x000); + + /* setup the PLL */ + if (state->config->pll_init) { + sp887x_writereg(state, 0x206, 0x001); + state->config->pll_init(fe); + sp887x_writereg(state, 0x206, 0x000); + } + + printk ("done.\n"); + return 0; +}; + +static int configure_reg0xc05 (struct dvb_frontend_parameters *p, u16 *reg0xc05) +{ + int known_parameters = 1; + + *reg0xc05 = 0x000; + + switch (p->u.ofdm.constellation) { + case QPSK: + break; + case QAM_16: + *reg0xc05 |= (1 << 10); + break; + case QAM_64: + *reg0xc05 |= (2 << 10); + break; + case QAM_AUTO: + known_parameters = 0; + break; + default: + return -EINVAL; + }; + + switch (p->u.ofdm.hierarchy_information) { + case HIERARCHY_NONE: + break; + case HIERARCHY_1: + *reg0xc05 |= (1 << 7); + break; + case HIERARCHY_2: + *reg0xc05 |= (2 << 7); + break; + case HIERARCHY_4: + *reg0xc05 |= (3 << 7); + break; + case HIERARCHY_AUTO: + known_parameters = 0; + break; + default: + return -EINVAL; + }; + + switch (p->u.ofdm.code_rate_HP) { + case FEC_1_2: + break; + case FEC_2_3: + *reg0xc05 |= (1 << 3); + break; + case FEC_3_4: + *reg0xc05 |= (2 << 3); + break; + case FEC_5_6: + *reg0xc05 |= (3 << 3); + break; + case FEC_7_8: + *reg0xc05 |= (4 << 3); + break; + case FEC_AUTO: + known_parameters = 0; + break; + default: + return -EINVAL; + }; + + if (known_parameters) + *reg0xc05 |= (2 << 1); /* use specified parameters */ + else + *reg0xc05 |= (1 << 1); /* enable autoprobing */ + + return 0; +} + +/** + * estimates division of two 24bit numbers, + * derived from the ves1820/stv0299 driver code + */ +static void divide (int n, int d, int *quotient_i, int *quotient_f) +{ + unsigned int q, r; + + r = (n % d) << 8; + q = (r / d); + + if (quotient_i) + *quotient_i = q; + + if (quotient_f) { + r = (r % d) << 8; + q = (q << 8) | (r / d); + r = (r % d) << 8; + *quotient_f = (q << 8) | (r / d); + } +} + +static void sp887x_correct_offsets (struct sp887x_state* state, + struct dvb_frontend_parameters *p, + int actual_freq) +{ + static const u32 srate_correction [] = { 1879617, 4544878, 8098561 }; + int bw_index = p->u.ofdm.bandwidth - BANDWIDTH_8_MHZ; + int freq_offset = actual_freq - p->frequency; + int sysclock = 61003; //[kHz] + int ifreq = 36000000; + int freq; + int frequency_shift; + + if (p->inversion == INVERSION_ON) + freq = ifreq - freq_offset; + else + freq = ifreq + freq_offset; + + divide(freq / 333, sysclock, NULL, &frequency_shift); + + if (p->inversion == INVERSION_ON) + frequency_shift = -frequency_shift; + + /* sample rate correction */ + sp887x_writereg(state, 0x319, srate_correction[bw_index] >> 12); + sp887x_writereg(state, 0x31a, srate_correction[bw_index] & 0xfff); + + /* carrier offset correction */ + sp887x_writereg(state, 0x309, frequency_shift >> 12); + sp887x_writereg(state, 0x30a, frequency_shift & 0xfff); +} + +static int sp887x_setup_frontend_parameters (struct dvb_frontend* fe, + struct dvb_frontend_parameters *p) +{ + struct sp887x_state* state = (struct sp887x_state*) fe->demodulator_priv; + int actual_freq, err; + u16 val, reg0xc05; + + if (p->u.ofdm.bandwidth != BANDWIDTH_8_MHZ && + p->u.ofdm.bandwidth != BANDWIDTH_7_MHZ && + p->u.ofdm.bandwidth != BANDWIDTH_6_MHZ) + return -EINVAL; + + if ((err = configure_reg0xc05(p, ®0xc05))) + return err; + + sp887x_microcontroller_stop(state); + + /* setup the PLL */ + sp887x_writereg(state, 0x206, 0x001); + actual_freq = state->config->pll_set(fe, p); + sp887x_writereg(state, 0x206, 0x000); + + /* read status reg in order to clear <pending irqs */ + sp887x_readreg(state, 0x200); + + sp887x_correct_offsets(state, p, actual_freq); + + /* filter for 6/7/8 Mhz channel */ + if (p->u.ofdm.bandwidth == BANDWIDTH_6_MHZ) + val = 2; + else if (p->u.ofdm.bandwidth == BANDWIDTH_7_MHZ) + val = 1; + else + val = 0; + + sp887x_writereg(state, 0x311, val); + + /* scan order: 2k first = 0, 8k first = 1 */ + if (p->u.ofdm.transmission_mode == TRANSMISSION_MODE_2K) + sp887x_writereg(state, 0x338, 0x000); + else + sp887x_writereg(state, 0x338, 0x001); + + sp887x_writereg(state, 0xc05, reg0xc05); + + if (p->u.ofdm.bandwidth == BANDWIDTH_6_MHZ) + val = 2 << 3; + else if (p->u.ofdm.bandwidth == BANDWIDTH_7_MHZ) + val = 3 << 3; + else + val = 0 << 3; + + /* enable OFDM and SAW bits as lock indicators in sync register 0xf17, + * optimize algorithm for given bandwidth... + */ + sp887x_writereg(state, 0xf14, 0x160 | val); + sp887x_writereg(state, 0xf15, 0x000); + + sp887x_microcontroller_start(state); + return 0; +} + +static int sp887x_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct sp887x_state* state = (struct sp887x_state*) fe->demodulator_priv; + u16 snr12 = sp887x_readreg(state, 0xf16); + u16 sync0x200 = sp887x_readreg(state, 0x200); + u16 sync0xf17 = sp887x_readreg(state, 0xf17); + + *status = 0; + + if (snr12 > 0x00f) + *status |= FE_HAS_SIGNAL; + + //if (sync0x200 & 0x004) + // *status |= FE_HAS_SYNC | FE_HAS_CARRIER; + + //if (sync0x200 & 0x008) + // *status |= FE_HAS_VITERBI; + + if ((sync0xf17 & 0x00f) == 0x002) { + *status |= FE_HAS_LOCK; + *status |= FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_CARRIER; + } + + if (sync0x200 & 0x001) { /* tuner adjustment requested...*/ + int steps = (sync0x200 >> 4) & 0x00f; + if (steps & 0x008) + steps = -steps; + dprintk("sp887x: implement tuner adjustment (%+i steps)!!\n", + steps); + } + + return 0; +} + +static int sp887x_read_ber(struct dvb_frontend* fe, u32* ber) +{ + struct sp887x_state* state = (struct sp887x_state*) fe->demodulator_priv; + + *ber = (sp887x_readreg(state, 0xc08) & 0x3f) | + (sp887x_readreg(state, 0xc07) << 6); + sp887x_writereg(state, 0xc08, 0x000); + sp887x_writereg(state, 0xc07, 0x000); + if (*ber >= 0x3fff0) + *ber = ~0; + + return 0; +} + +static int sp887x_read_signal_strength(struct dvb_frontend* fe, u16* strength) +{ + struct sp887x_state* state = (struct sp887x_state*) fe->demodulator_priv; + + u16 snr12 = sp887x_readreg(state, 0xf16); + u32 signal = 3 * (snr12 << 4); + *strength = (signal < 0xffff) ? signal : 0xffff; + + return 0; +} + +static int sp887x_read_snr(struct dvb_frontend* fe, u16* snr) +{ + struct sp887x_state* state = (struct sp887x_state*) fe->demodulator_priv; + + u16 snr12 = sp887x_readreg(state, 0xf16); + *snr = (snr12 << 4) | (snr12 >> 8); + + return 0; +} + +static int sp887x_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + struct sp887x_state* state = (struct sp887x_state*) fe->demodulator_priv; + + *ucblocks = sp887x_readreg(state, 0xc0c); + if (*ucblocks == 0xfff) + *ucblocks = ~0; + + return 0; +} + +static int sp887x_sleep(struct dvb_frontend* fe) +{ + struct sp887x_state* state = (struct sp887x_state*) fe->demodulator_priv; + + /* tristate TS output and disable interface pins */ + sp887x_writereg(state, 0xc18, 0x000); + + return 0; +} + +static int sp887x_init(struct dvb_frontend* fe) +{ + struct sp887x_state* state = (struct sp887x_state*) fe->demodulator_priv; + const struct firmware *fw = NULL; + int ret; + + if (!state->initialised) { + /* request the firmware, this will block until someone uploads it */ + printk("sp887x: waiting for firmware upload (%s)...\n", SP887X_DEFAULT_FIRMWARE); + ret = state->config->request_firmware(fe, &fw, SP887X_DEFAULT_FIRMWARE); + if (ret) { + printk("sp887x: no firmware upload (timeout or file not found?)\n"); + return ret; + } + + ret = sp887x_initial_setup(fe, fw); + if (ret) { + printk("sp887x: writing firmware to device failed\n"); + release_firmware(fw); + return ret; + } + printk("sp887x: firmware upload complete\n"); + state->initialised = 1; + } + + /* enable TS output and interface pins */ + sp887x_writereg(state, 0xc18, 0x00d); + + return 0; +} + +static int sp887x_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* fesettings) +{ + fesettings->min_delay_ms = 350; + fesettings->step_size = 166666*2; + fesettings->max_drift = (166666*2)+1; + return 0; +} + +static void sp887x_release(struct dvb_frontend* fe) +{ + struct sp887x_state* state = (struct sp887x_state*) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops sp887x_ops; + +struct dvb_frontend* sp887x_attach(const struct sp887x_config* config, + struct i2c_adapter* i2c) +{ + struct sp887x_state* state = NULL; + + /* allocate memory for the internal state */ + state = (struct sp887x_state*) kmalloc(sizeof(struct sp887x_state), GFP_KERNEL); + if (state == NULL) goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + memcpy(&state->ops, &sp887x_ops, sizeof(struct dvb_frontend_ops)); + state->initialised = 0; + + /* check if the demod is there */ + if (sp887x_readreg(state, 0x0200) < 0) goto error; + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops sp887x_ops = { + + .info = { + .name = "Spase SP887x DVB-T", + .type = FE_OFDM, + .frequency_min = 50500000, + .frequency_max = 858000000, + .frequency_stepsize = 166666, + .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_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | + FE_CAN_RECOVER + }, + + .release = sp887x_release, + + .init = sp887x_init, + .sleep = sp887x_sleep, + + .set_frontend = sp887x_setup_frontend_parameters, + .get_tune_settings = sp887x_get_tune_settings, + + .read_status = sp887x_read_status, + .read_ber = sp887x_read_ber, + .read_signal_strength = sp887x_read_signal_strength, + .read_snr = sp887x_read_snr, + .read_ucblocks = sp887x_read_ucblocks, +}; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("Spase sp887x DVB-T demodulator driver"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(sp887x_attach); diff --git a/drivers/media/dvb/frontends/sp887x.h b/drivers/media/dvb/frontends/sp887x.h new file mode 100644 index 000000000000..6a05d8f8e8cc --- /dev/null +++ b/drivers/media/dvb/frontends/sp887x.h @@ -0,0 +1,29 @@ +/* + Driver for the Spase sp887x demodulator +*/ + +#ifndef SP887X_H +#define SP887X_H + +#include <linux/dvb/frontend.h> +#include <linux/firmware.h> + +struct sp887x_config +{ + /* the demodulator's i2c address */ + u8 demod_address; + + /* PLL maintenance */ + int (*pll_init)(struct dvb_frontend* fe); + + /* this should return the actual frequency tuned to */ + int (*pll_set)(struct dvb_frontend* fe, struct dvb_frontend_parameters* params); + + /* request firmware for device */ + int (*request_firmware)(struct dvb_frontend* fe, const struct firmware **fw, char* name); +}; + +extern struct dvb_frontend* sp887x_attach(const struct sp887x_config* config, + struct i2c_adapter* i2c); + +#endif // SP887X_H diff --git a/drivers/media/dvb/frontends/stv0297.c b/drivers/media/dvb/frontends/stv0297.c new file mode 100644 index 000000000000..502c6403dfc6 --- /dev/null +++ b/drivers/media/dvb/frontends/stv0297.c @@ -0,0 +1,798 @@ +/* + Driver for STV0297 demodulator + + Copyright (C) 2004 Andrew de Quincey <adq_dvb@lidskialf.net> + Copyright (C) 2003-2004 Dennis Noermann <dennis.noermann@noernet.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/delay.h> + +#include "dvb_frontend.h" +#include "stv0297.h" + +struct stv0297_state { + struct i2c_adapter *i2c; + struct dvb_frontend_ops ops; + const struct stv0297_config *config; + struct dvb_frontend frontend; + + unsigned long base_freq; + u8 pwm; +}; + +#if 1 +#define dprintk(x...) printk(x) +#else +#define dprintk(x...) +#endif + +#define STV0297_CLOCK_KHZ 28900 + +static u8 init_tab[] = { + 0x00, 0x09, + 0x01, 0x69, + 0x03, 0x00, + 0x04, 0x00, + 0x07, 0x00, + 0x08, 0x00, + 0x20, 0x00, + 0x21, 0x40, + 0x22, 0x00, + 0x23, 0x00, + 0x24, 0x40, + 0x25, 0x88, + 0x30, 0xff, + 0x31, 0x00, + 0x32, 0xff, + 0x33, 0x00, + 0x34, 0x50, + 0x35, 0x7f, + 0x36, 0x00, + 0x37, 0x20, + 0x38, 0x00, + 0x40, 0x1c, + 0x41, 0xff, + 0x42, 0x29, + 0x43, 0x00, + 0x44, 0xff, + 0x45, 0x00, + 0x46, 0x00, + 0x49, 0x04, + 0x4a, 0xff, + 0x4b, 0x7f, + 0x52, 0x30, + 0x55, 0xae, + 0x56, 0x47, + 0x57, 0xe1, + 0x58, 0x3a, + 0x5a, 0x1e, + 0x5b, 0x34, + 0x60, 0x00, + 0x63, 0x00, + 0x64, 0x00, + 0x65, 0x00, + 0x66, 0x00, + 0x67, 0x00, + 0x68, 0x00, + 0x69, 0x00, + 0x6a, 0x02, + 0x6b, 0x00, + 0x70, 0xff, + 0x71, 0x00, + 0x72, 0x00, + 0x73, 0x00, + 0x74, 0x0c, + 0x80, 0x00, + 0x81, 0x00, + 0x82, 0x00, + 0x83, 0x00, + 0x84, 0x04, + 0x85, 0x80, + 0x86, 0x24, + 0x87, 0x78, + 0x88, 0x00, + 0x89, 0x00, + 0x90, 0x01, + 0x91, 0x01, + 0xa0, 0x00, + 0xa1, 0x00, + 0xa2, 0x00, + 0xb0, 0x91, + 0xb1, 0x0b, + 0xc0, 0x53, + 0xc1, 0x70, + 0xc2, 0x12, + 0xd0, 0x00, + 0xd1, 0x00, + 0xd2, 0x00, + 0xd3, 0x00, + 0xd4, 0x00, + 0xd5, 0x00, + 0xde, 0x00, + 0xdf, 0x00, + 0x61, 0x49, + 0x62, 0x0b, + 0x53, 0x08, + 0x59, 0x08, +}; + + +static int stv0297_writereg(struct stv0297_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", __FUNCTION__, reg, data, ret); + + return (ret != 1) ? -1 : 0; +} + +static int stv0297_readreg(struct stv0297_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} + }; + + // this device needs a STOP between the register and data + if ((ret = i2c_transfer(state->i2c, &msg[0], 1)) != 1) { + dprintk("%s: readreg error (reg == 0x%02x, ret == %i)\n", __FUNCTION__, reg, ret); + return -1; + } + if ((ret = i2c_transfer(state->i2c, &msg[1], 1)) != 1) { + dprintk("%s: readreg error (reg == 0x%02x, ret == %i)\n", __FUNCTION__, reg, ret); + return -1; + } + + return b1[0]; +} + +static int stv0297_writereg_mask(struct stv0297_state *state, u8 reg, u8 mask, u8 data) +{ + int val; + + val = stv0297_readreg(state, reg); + val &= ~mask; + val |= (data & mask); + stv0297_writereg(state, reg, val); + + return 0; +} + +static int stv0297_readregs(struct stv0297_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} + }; + + // this device needs a STOP between the register and data + if ((ret = i2c_transfer(state->i2c, &msg[0], 1)) != 1) { + dprintk("%s: readreg error (reg == 0x%02x, ret == %i)\n", __FUNCTION__, reg1, ret); + return -1; + } + if ((ret = i2c_transfer(state->i2c, &msg[1], 1)) != 1) { + dprintk("%s: readreg error (reg == 0x%02x, ret == %i)\n", __FUNCTION__, reg1, ret); + return -1; + } + + return 0; +} + +static u32 stv0297_get_symbolrate(struct stv0297_state *state) +{ + u64 tmp; + + tmp = stv0297_readreg(state, 0x55); + tmp |= stv0297_readreg(state, 0x56) << 8; + tmp |= stv0297_readreg(state, 0x57) << 16; + tmp |= stv0297_readreg(state, 0x58) << 24; + + tmp *= STV0297_CLOCK_KHZ; + tmp >>= 32; + + return (u32) tmp; +} + +static void stv0297_set_symbolrate(struct stv0297_state *state, u32 srate) +{ + long tmp; + + tmp = 131072L * srate; /* 131072 = 2^17 */ + tmp = tmp / (STV0297_CLOCK_KHZ / 4); /* 1/4 = 2^-2 */ + tmp = tmp * 8192L; /* 8192 = 2^13 */ + + stv0297_writereg(state, 0x55, (unsigned char) (tmp & 0xFF)); + stv0297_writereg(state, 0x56, (unsigned char) (tmp >> 8)); + stv0297_writereg(state, 0x57, (unsigned char) (tmp >> 16)); + stv0297_writereg(state, 0x58, (unsigned char) (tmp >> 24)); +} + +static void stv0297_set_sweeprate(struct stv0297_state *state, short fshift, long symrate) +{ + long tmp; + + tmp = (long) fshift *262144L; /* 262144 = 2*18 */ + tmp /= symrate; + tmp *= 1024; /* 1024 = 2*10 */ + + // adjust + if (tmp >= 0) { + tmp += 500000; + } else { + tmp -= 500000; + } + tmp /= 1000000; + + stv0297_writereg(state, 0x60, tmp & 0xFF); + stv0297_writereg_mask(state, 0x69, 0xF0, (tmp >> 4) & 0xf0); +} + +static void stv0297_set_carrieroffset(struct stv0297_state *state, long offset) +{ + long tmp; + + /* symrate is hardcoded to 10000 */ + tmp = offset * 26844L; /* (2**28)/10000 */ + if (tmp < 0) + tmp += 0x10000000; + tmp &= 0x0FFFFFFF; + + stv0297_writereg(state, 0x66, (unsigned char) (tmp & 0xFF)); + stv0297_writereg(state, 0x67, (unsigned char) (tmp >> 8)); + stv0297_writereg(state, 0x68, (unsigned char) (tmp >> 16)); + stv0297_writereg_mask(state, 0x69, 0x0F, (tmp >> 24) & 0x0f); +} + +/* +static long stv0297_get_carrieroffset(struct stv0297_state *state) +{ + s64 tmp; + + stv0297_writereg(state, 0x6B, 0x00); + + tmp = stv0297_readreg(state, 0x66); + tmp |= (stv0297_readreg(state, 0x67) << 8); + tmp |= (stv0297_readreg(state, 0x68) << 16); + tmp |= (stv0297_readreg(state, 0x69) & 0x0F) << 24; + + tmp *= stv0297_get_symbolrate(state); + tmp >>= 28; + + return (s32) tmp; +} +*/ + +static void stv0297_set_initialdemodfreq(struct stv0297_state *state, long freq) +{ + s32 tmp; + + if (freq > 10000) + freq -= STV0297_CLOCK_KHZ; + + tmp = (STV0297_CLOCK_KHZ * 1000) / (1 << 16); + tmp = (freq * 1000) / tmp; + if (tmp > 0xffff) + tmp = 0xffff; + + stv0297_writereg_mask(state, 0x25, 0x80, 0x80); + stv0297_writereg(state, 0x21, tmp >> 8); + stv0297_writereg(state, 0x20, tmp); +} + +static int stv0297_set_qam(struct stv0297_state *state, fe_modulation_t modulation) +{ + int val = 0; + + switch (modulation) { + case QAM_16: + val = 0; + break; + + case QAM_32: + val = 1; + break; + + case QAM_64: + val = 4; + break; + + case QAM_128: + val = 2; + break; + + case QAM_256: + val = 3; + break; + + default: + return -EINVAL; + } + + stv0297_writereg_mask(state, 0x00, 0x70, val << 4); + + return 0; +} + +static int stv0297_set_inversion(struct stv0297_state *state, fe_spectral_inversion_t inversion) +{ + int val = 0; + + switch (inversion) { + case INVERSION_OFF: + val = 0; + break; + + case INVERSION_ON: + val = 1; + break; + + default: + return -EINVAL; + } + + stv0297_writereg_mask(state, 0x83, 0x08, val << 3); + + return 0; +} + +int stv0297_enable_plli2c(struct dvb_frontend *fe) +{ + struct stv0297_state *state = (struct stv0297_state *) fe->demodulator_priv; + + stv0297_writereg(state, 0x87, 0x78); + stv0297_writereg(state, 0x86, 0xc8); + + return 0; +} + +static int stv0297_init(struct dvb_frontend *fe) +{ + struct stv0297_state *state = (struct stv0297_state *) fe->demodulator_priv; + int i; + + /* soft reset */ + stv0297_writereg_mask(state, 0x80, 1, 1); + stv0297_writereg_mask(state, 0x80, 1, 0); + + /* reset deinterleaver */ + stv0297_writereg_mask(state, 0x81, 1, 1); + stv0297_writereg_mask(state, 0x81, 1, 0); + + /* load init table */ + for (i = 0; i < sizeof(init_tab); i += 2) { + stv0297_writereg(state, init_tab[i], init_tab[i + 1]); + } + + /* set a dummy symbol rate */ + stv0297_set_symbolrate(state, 6900); + + /* invert AGC1 polarity */ + stv0297_writereg_mask(state, 0x88, 0x10, 0x10); + + /* setup bit error counting */ + stv0297_writereg_mask(state, 0xA0, 0x80, 0x00); + stv0297_writereg_mask(state, 0xA0, 0x10, 0x00); + stv0297_writereg_mask(state, 0xA0, 0x08, 0x00); + stv0297_writereg_mask(state, 0xA0, 0x07, 0x04); + + /* min + max PWM */ + stv0297_writereg(state, 0x4a, 0x00); + stv0297_writereg(state, 0x4b, state->pwm); + msleep(200); + + if (state->config->pll_init) + state->config->pll_init(fe); + + return 0; +} + +static int stv0297_sleep(struct dvb_frontend *fe) +{ + struct stv0297_state *state = (struct stv0297_state *) fe->demodulator_priv; + + stv0297_writereg_mask(state, 0x80, 1, 1); + + return 0; +} + +static int stv0297_read_status(struct dvb_frontend *fe, fe_status_t * status) +{ + struct stv0297_state *state = (struct stv0297_state *) fe->demodulator_priv; + + u8 sync = stv0297_readreg(state, 0xDF); + + *status = 0; + if (sync & 0x80) + *status |= + FE_HAS_SYNC | FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_VITERBI | FE_HAS_LOCK; + return 0; +} + +static int stv0297_read_ber(struct dvb_frontend *fe, u32 * ber) +{ + struct stv0297_state *state = (struct stv0297_state *) fe->demodulator_priv; + u8 BER[3]; + + stv0297_writereg(state, 0xA0, 0x80); // Start Counting bit errors for 4096 Bytes + mdelay(25); // Hopefully got 4096 Bytes + stv0297_readregs(state, 0xA0, BER, 3); + mdelay(25); + *ber = (BER[2] << 8 | BER[1]) / (8 * 4096); + + return 0; +} + + +static int stv0297_read_signal_strength(struct dvb_frontend *fe, u16 * strength) +{ + struct stv0297_state *state = (struct stv0297_state *) fe->demodulator_priv; + u8 STRENGTH[2]; + + stv0297_readregs(state, 0x41, STRENGTH, 2); + *strength = (STRENGTH[1] & 0x03) << 8 | STRENGTH[0]; + + return 0; +} + +static int stv0297_read_snr(struct dvb_frontend *fe, u16 * snr) +{ + struct stv0297_state *state = (struct stv0297_state *) fe->demodulator_priv; + u8 SNR[2]; + + stv0297_readregs(state, 0x07, SNR, 2); + *snr = SNR[1] << 8 | SNR[0]; + + return 0; +} + +static int stv0297_read_ucblocks(struct dvb_frontend *fe, u32 * ucblocks) +{ + struct stv0297_state *state = (struct stv0297_state *) fe->demodulator_priv; + + *ucblocks = (stv0297_readreg(state, 0xD5) << 8) + | stv0297_readreg(state, 0xD4); + + return 0; +} + +static int stv0297_set_frontend(struct dvb_frontend *fe, struct dvb_frontend_parameters *p) +{ + struct stv0297_state *state = (struct stv0297_state *) fe->demodulator_priv; + int u_threshold; + int initial_u; + int blind_u; + int delay; + int sweeprate; + int carrieroffset; + unsigned long starttime; + unsigned long timeout; + fe_spectral_inversion_t inversion; + + switch (p->u.qam.modulation) { + case QAM_16: + case QAM_32: + case QAM_64: + delay = 100; + sweeprate = 1500; + break; + + case QAM_128: + delay = 150; + sweeprate = 1000; + break; + + case QAM_256: + delay = 200; + sweeprate = 500; + break; + + default: + return -EINVAL; + } + + // determine inversion dependant parameters + inversion = p->inversion; + if (state->config->invert) + inversion = (inversion == INVERSION_ON) ? INVERSION_OFF : INVERSION_ON; + carrieroffset = -330; + switch (inversion) { + case INVERSION_OFF: + break; + + case INVERSION_ON: + sweeprate = -sweeprate; + carrieroffset = -carrieroffset; + break; + + default: + return -EINVAL; + } + + stv0297_init(fe); + state->config->pll_set(fe, p); + + /* clear software interrupts */ + stv0297_writereg(state, 0x82, 0x0); + + /* set initial demodulation frequency */ + stv0297_set_initialdemodfreq(state, 7250); + + /* setup AGC */ + stv0297_writereg_mask(state, 0x43, 0x10, 0x00); + stv0297_writereg(state, 0x41, 0x00); + stv0297_writereg_mask(state, 0x42, 0x03, 0x01); + stv0297_writereg_mask(state, 0x36, 0x60, 0x00); + stv0297_writereg_mask(state, 0x36, 0x18, 0x00); + stv0297_writereg_mask(state, 0x71, 0x80, 0x80); + stv0297_writereg(state, 0x72, 0x00); + stv0297_writereg(state, 0x73, 0x00); + stv0297_writereg_mask(state, 0x74, 0x0F, 0x00); + stv0297_writereg_mask(state, 0x43, 0x08, 0x00); + stv0297_writereg_mask(state, 0x71, 0x80, 0x00); + + /* setup STL */ + stv0297_writereg_mask(state, 0x5a, 0x20, 0x20); + stv0297_writereg_mask(state, 0x5b, 0x02, 0x02); + stv0297_writereg_mask(state, 0x5b, 0x02, 0x00); + stv0297_writereg_mask(state, 0x5b, 0x01, 0x00); + stv0297_writereg_mask(state, 0x5a, 0x40, 0x40); + + /* disable frequency sweep */ + stv0297_writereg_mask(state, 0x6a, 0x01, 0x00); + + /* reset deinterleaver */ + stv0297_writereg_mask(state, 0x81, 0x01, 0x01); + stv0297_writereg_mask(state, 0x81, 0x01, 0x00); + + /* ??? */ + stv0297_writereg_mask(state, 0x83, 0x20, 0x20); + stv0297_writereg_mask(state, 0x83, 0x20, 0x00); + + /* reset equaliser */ + u_threshold = stv0297_readreg(state, 0x00) & 0xf; + initial_u = stv0297_readreg(state, 0x01) >> 4; + blind_u = stv0297_readreg(state, 0x01) & 0xf; + stv0297_writereg_mask(state, 0x84, 0x01, 0x01); + stv0297_writereg_mask(state, 0x84, 0x01, 0x00); + stv0297_writereg_mask(state, 0x00, 0x0f, u_threshold); + stv0297_writereg_mask(state, 0x01, 0xf0, initial_u << 4); + stv0297_writereg_mask(state, 0x01, 0x0f, blind_u); + + /* data comes from internal A/D */ + stv0297_writereg_mask(state, 0x87, 0x80, 0x00); + + /* clear phase registers */ + stv0297_writereg(state, 0x63, 0x00); + stv0297_writereg(state, 0x64, 0x00); + stv0297_writereg(state, 0x65, 0x00); + stv0297_writereg(state, 0x66, 0x00); + stv0297_writereg(state, 0x67, 0x00); + stv0297_writereg(state, 0x68, 0x00); + stv0297_writereg_mask(state, 0x69, 0x0f, 0x00); + + /* set parameters */ + stv0297_set_qam(state, p->u.qam.modulation); + stv0297_set_symbolrate(state, p->u.qam.symbol_rate / 1000); + stv0297_set_sweeprate(state, sweeprate, p->u.qam.symbol_rate / 1000); + stv0297_set_carrieroffset(state, carrieroffset); + stv0297_set_inversion(state, inversion); + + /* kick off lock */ + stv0297_writereg_mask(state, 0x88, 0x08, 0x08); + stv0297_writereg_mask(state, 0x5a, 0x20, 0x00); + stv0297_writereg_mask(state, 0x6a, 0x01, 0x01); + stv0297_writereg_mask(state, 0x43, 0x40, 0x40); + stv0297_writereg_mask(state, 0x5b, 0x30, 0x00); + stv0297_writereg_mask(state, 0x03, 0x0c, 0x0c); + stv0297_writereg_mask(state, 0x03, 0x03, 0x03); + stv0297_writereg_mask(state, 0x43, 0x10, 0x10); + + /* wait for WGAGC lock */ + starttime = jiffies; + timeout = jiffies + (200 * HZ) / 1000; + while (time_before(jiffies, timeout)) { + msleep(10); + if (stv0297_readreg(state, 0x43) & 0x08) + break; + } + if (time_after(jiffies, timeout)) { + goto timeout; + } + msleep(20); + + /* wait for equaliser partial convergence */ + timeout = jiffies + (50 * HZ) / 1000; + while (time_before(jiffies, timeout)) { + msleep(10); + + if (stv0297_readreg(state, 0x82) & 0x04) { + break; + } + } + if (time_after(jiffies, timeout)) { + goto timeout; + } + + /* wait for equaliser full convergence */ + timeout = jiffies + (delay * HZ) / 1000; + while (time_before(jiffies, timeout)) { + msleep(10); + + if (stv0297_readreg(state, 0x82) & 0x08) { + break; + } + } + if (time_after(jiffies, timeout)) { + goto timeout; + } + + /* disable sweep */ + stv0297_writereg_mask(state, 0x6a, 1, 0); + stv0297_writereg_mask(state, 0x88, 8, 0); + + /* wait for main lock */ + timeout = jiffies + (20 * HZ) / 1000; + while (time_before(jiffies, timeout)) { + msleep(10); + + if (stv0297_readreg(state, 0xDF) & 0x80) { + break; + } + } + if (time_after(jiffies, timeout)) { + goto timeout; + } + msleep(100); + + /* is it still locked after that delay? */ + if (!(stv0297_readreg(state, 0xDF) & 0x80)) { + goto timeout; + } + + /* success!! */ + stv0297_writereg_mask(state, 0x5a, 0x40, 0x00); + state->base_freq = p->frequency; + return 0; + +timeout: + stv0297_writereg_mask(state, 0x6a, 0x01, 0x00); + return 0; +} + +static int stv0297_get_frontend(struct dvb_frontend *fe, struct dvb_frontend_parameters *p) +{ + struct stv0297_state *state = (struct stv0297_state *) fe->demodulator_priv; + int reg_00, reg_83; + + reg_00 = stv0297_readreg(state, 0x00); + reg_83 = stv0297_readreg(state, 0x83); + + p->frequency = state->base_freq; + p->inversion = (reg_83 & 0x08) ? INVERSION_ON : INVERSION_OFF; + if (state->config->invert) + p->inversion = (p->inversion == INVERSION_ON) ? INVERSION_OFF : INVERSION_ON; + p->u.qam.symbol_rate = stv0297_get_symbolrate(state) * 1000; + p->u.qam.fec_inner = FEC_NONE; + + switch ((reg_00 >> 4) & 0x7) { + case 0: + p->u.qam.modulation = QAM_16; + break; + case 1: + p->u.qam.modulation = QAM_32; + break; + case 2: + p->u.qam.modulation = QAM_128; + break; + case 3: + p->u.qam.modulation = QAM_256; + break; + case 4: + p->u.qam.modulation = QAM_64; + break; + } + + return 0; +} + +static void stv0297_release(struct dvb_frontend *fe) +{ + struct stv0297_state *state = (struct stv0297_state *) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops stv0297_ops; + +struct dvb_frontend *stv0297_attach(const struct stv0297_config *config, + struct i2c_adapter *i2c, int pwm) +{ + struct stv0297_state *state = NULL; + + /* allocate memory for the internal state */ + state = (struct stv0297_state *) kmalloc(sizeof(struct stv0297_state), GFP_KERNEL); + if (state == NULL) + goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + memcpy(&state->ops, &stv0297_ops, sizeof(struct dvb_frontend_ops)); + state->base_freq = 0; + state->pwm = pwm; + + /* check if the demod is there */ + if ((stv0297_readreg(state, 0x80) & 0x70) != 0x20) + goto error; + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops stv0297_ops = { + + .info = { + .name = "ST STV0297 DVB-C", + .type = FE_QAM, + .frequency_min = 64000000, + .frequency_max = 1300000000, + .frequency_stepsize = 62500, + .symbol_rate_min = 870000, + .symbol_rate_max = 11700000, + .caps = FE_CAN_QAM_16 | FE_CAN_QAM_32 | FE_CAN_QAM_64 | + FE_CAN_QAM_128 | FE_CAN_QAM_256 | FE_CAN_FEC_AUTO}, + + .release = stv0297_release, + + .init = stv0297_init, + .sleep = stv0297_sleep, + + .set_frontend = stv0297_set_frontend, + .get_frontend = stv0297_get_frontend, + + .read_status = stv0297_read_status, + .read_ber = stv0297_read_ber, + .read_signal_strength = stv0297_read_signal_strength, + .read_snr = stv0297_read_snr, + .read_ucblocks = stv0297_read_ucblocks, +}; + +MODULE_DESCRIPTION("ST STV0297 DVB-C Demodulator driver"); +MODULE_AUTHOR("Dennis Noermann and Andrew de Quincey"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(stv0297_attach); +EXPORT_SYMBOL(stv0297_enable_plli2c); diff --git a/drivers/media/dvb/frontends/stv0297.h b/drivers/media/dvb/frontends/stv0297.h new file mode 100644 index 000000000000..3be535989302 --- /dev/null +++ b/drivers/media/dvb/frontends/stv0297.h @@ -0,0 +1,44 @@ +/* + Driver for STV0297 demodulator + + Copyright (C) 2003-2004 Dennis Noermann <dennis.noermann@noernet.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef STV0297_H +#define STV0297_H + +#include <linux/dvb/frontend.h> +#include "dvb_frontend.h" + +struct stv0297_config +{ + /* the demodulator's i2c address */ + u8 demod_address; + + /* does the "inversion" need inverted? */ + u8 invert:1; + + /* PLL maintenance */ + int (*pll_init)(struct dvb_frontend* fe); + int (*pll_set)(struct dvb_frontend* fe, struct dvb_frontend_parameters* params); +}; + +extern struct dvb_frontend* stv0297_attach(const struct stv0297_config* config, + struct i2c_adapter* i2c, int pwm); +extern int stv0297_enable_plli2c(struct dvb_frontend* fe); + +#endif // STV0297_H diff --git a/drivers/media/dvb/frontends/stv0299.c b/drivers/media/dvb/frontends/stv0299.c new file mode 100644 index 000000000000..15b40541b62d --- /dev/null +++ b/drivers/media/dvb/frontends/stv0299.c @@ -0,0 +1,731 @@ +/* + Driver for ST STV0299 demodulator + + Copyright (C) 2001-2002 Convergence Integrated Media GmbH + <ralph@convergence.de>, + <holger@convergence.de>, + <js@convergence.de> + + + Philips SU1278/SH + + Copyright (C) 2002 by Peter Schildmann <peter.schildmann@web.de> + + + LG TDQF-S001F + + Copyright (C) 2002 Felix Domke <tmbinc@elitedvb.net> + & Andreas Oberritter <obi@linuxtv.org> + + + Support for Samsung TBMU24112IMB used on Technisat SkyStar2 rev. 2.6B + + Copyright (C) 2003 Vadim Catana <skystar@moldova.cc>: + + Support for Philips SU1278 on Technotrend hardware + + Copyright (C) 2004 Andrew de Quincey <adq_dvb@lidskialf.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <asm/div64.h> + +#include "dvb_frontend.h" +#include "stv0299.h" + +struct stv0299_state { + struct i2c_adapter* i2c; + struct dvb_frontend_ops ops; + const struct stv0299_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; +#define dprintk(args...) \ + do { \ + if (debug) printk(KERN_DEBUG "stv0299: " args); \ + } while (0) + + +static int stv0299_writeregI (struct stv0299_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", __FUNCTION__, reg, data, ret); + + return (ret != 1) ? -EREMOTEIO : 0; +} + +int stv0299_writereg (struct dvb_frontend* fe, u8 reg, u8 data) +{ + struct stv0299_state* state = (struct stv0299_state*) fe->demodulator_priv; + + return stv0299_writeregI(state, reg, data); +} + +static u8 stv0299_readreg (struct stv0299_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", + __FUNCTION__, reg, ret); + + return b1[0]; +} + +static int stv0299_readregs (struct stv0299_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", __FUNCTION__, ret); + + return ret == 2 ? 0 : ret; +} + +static int stv0299_set_FEC (struct stv0299_state* state, fe_code_rate_t fec) +{ + dprintk ("%s\n", __FUNCTION__); + + switch (fec) { + case FEC_AUTO: + { + return stv0299_writeregI (state, 0x31, 0x1f); + } + case FEC_1_2: + { + return stv0299_writeregI (state, 0x31, 0x01); + } + case FEC_2_3: + { + return stv0299_writeregI (state, 0x31, 0x02); + } + case FEC_3_4: + { + return stv0299_writeregI (state, 0x31, 0x04); + } + case FEC_5_6: + { + return stv0299_writeregI (state, 0x31, 0x08); + } + case FEC_7_8: + { + return stv0299_writeregI (state, 0x31, 0x10); + } + default: + { + return -EINVAL; + } + } +} + +static fe_code_rate_t stv0299_get_fec (struct stv0299_state* state) +{ + static fe_code_rate_t fec_tab [] = { FEC_2_3, FEC_3_4, FEC_5_6, + FEC_7_8, FEC_1_2 }; + u8 index; + + dprintk ("%s\n", __FUNCTION__); + + index = stv0299_readreg (state, 0x1b); + index &= 0x7; + + if (index > 4) + return FEC_AUTO; + + return fec_tab [index]; +} + +static int stv0299_wait_diseqc_fifo (struct stv0299_state* state, int timeout) +{ + unsigned long start = jiffies; + + dprintk ("%s\n", __FUNCTION__); + + while (stv0299_readreg(state, 0x0a) & 1) { + if (jiffies - start > timeout) { + dprintk ("%s: timeout!!\n", __FUNCTION__); + return -ETIMEDOUT; + } + msleep(10); + }; + + return 0; +} + +static int stv0299_wait_diseqc_idle (struct stv0299_state* state, int timeout) +{ + unsigned long start = jiffies; + + dprintk ("%s\n", __FUNCTION__); + + while ((stv0299_readreg(state, 0x0a) & 3) != 2 ) { + if (jiffies - start > timeout) { + dprintk ("%s: timeout!!\n", __FUNCTION__); + return -ETIMEDOUT; + } + msleep(10); + }; + + return 0; +} + +static int stv0299_set_symbolrate (struct dvb_frontend* fe, u32 srate) +{ + struct stv0299_state* state = (struct stv0299_state*) fe->demodulator_priv; + u64 big = srate; + u32 ratio; + + // check rate is within limits + if ((srate < 1000000) || (srate > 45000000)) return -EINVAL; + + // calculate value to program + big = big << 20; + big += (state->config->mclk-1); // round correctly + do_div(big, state->config->mclk); + ratio = big << 4; + + return state->config->set_symbol_rate(fe, srate, ratio); +} + +static int stv0299_get_symbolrate (struct stv0299_state* state) +{ + u32 Mclk = state->config->mclk / 4096L; + u32 srate; + s32 offset; + u8 sfr[3]; + s8 rtf; + + dprintk ("%s\n", __FUNCTION__); + + stv0299_readregs (state, 0x1f, sfr, 3); + stv0299_readregs (state, 0x1a, &rtf, 1); + + srate = (sfr[0] << 8) | sfr[1]; + srate *= Mclk; + srate /= 16; + srate += (sfr[2] >> 4) * Mclk / 256; + offset = (s32) rtf * (srate / 4096L); + offset /= 128; + + dprintk ("%s : srate = %i\n", __FUNCTION__, srate); + dprintk ("%s : ofset = %i\n", __FUNCTION__, offset); + + srate += offset; + + srate += 1000; + srate /= 2000; + srate *= 2000; + + return srate; +} + +static int stv0299_send_diseqc_msg (struct dvb_frontend* fe, + struct dvb_diseqc_master_cmd *m) +{ + struct stv0299_state* state = (struct stv0299_state*) fe->demodulator_priv; + u8 val; + int i; + + dprintk ("%s\n", __FUNCTION__); + + if (stv0299_wait_diseqc_idle (state, 100) < 0) + return -ETIMEDOUT; + + val = stv0299_readreg (state, 0x08); + + if (stv0299_writeregI (state, 0x08, (val & ~0x7) | 0x6)) /* DiSEqC mode */ + return -EREMOTEIO; + + for (i=0; i<m->msg_len; i++) { + if (stv0299_wait_diseqc_fifo (state, 100) < 0) + return -ETIMEDOUT; + + if (stv0299_writeregI (state, 0x09, m->msg[i])) + return -EREMOTEIO; + } + + if (stv0299_wait_diseqc_idle (state, 100) < 0) + return -ETIMEDOUT; + + return 0; +} + +static int stv0299_send_diseqc_burst (struct dvb_frontend* fe, fe_sec_mini_cmd_t burst) +{ + struct stv0299_state* state = (struct stv0299_state*) fe->demodulator_priv; + u8 val; + + dprintk ("%s\n", __FUNCTION__); + + if (stv0299_wait_diseqc_idle (state, 100) < 0) + return -ETIMEDOUT; + + val = stv0299_readreg (state, 0x08); + + if (stv0299_writeregI (state, 0x08, (val & ~0x7) | 0x2)) /* burst mode */ + return -EREMOTEIO; + + if (stv0299_writeregI (state, 0x09, burst == SEC_MINI_A ? 0x00 : 0xff)) + return -EREMOTEIO; + + if (stv0299_wait_diseqc_idle (state, 100) < 0) + return -ETIMEDOUT; + + if (stv0299_writeregI (state, 0x08, val)) + return -EREMOTEIO; + + return 0; +} + +static int stv0299_set_tone (struct dvb_frontend* fe, fe_sec_tone_mode_t tone) +{ + struct stv0299_state* state = (struct stv0299_state*) fe->demodulator_priv; + u8 val; + + if (stv0299_wait_diseqc_idle (state, 100) < 0) + return -ETIMEDOUT; + + val = stv0299_readreg (state, 0x08); + + switch (tone) { + case SEC_TONE_ON: + return stv0299_writeregI (state, 0x08, val | 0x3); + + case SEC_TONE_OFF: + return stv0299_writeregI (state, 0x08, (val & ~0x3) | 0x02); + + default: + return -EINVAL; + } +} + +static int stv0299_set_voltage (struct dvb_frontend* fe, fe_sec_voltage_t voltage) +{ + struct stv0299_state* state = (struct stv0299_state*) fe->demodulator_priv; + u8 reg0x08; + u8 reg0x0c; + + dprintk("%s: %s\n", __FUNCTION__, + voltage == SEC_VOLTAGE_13 ? "SEC_VOLTAGE_13" : + voltage == SEC_VOLTAGE_18 ? "SEC_VOLTAGE_18" : "??"); + + reg0x08 = stv0299_readreg (state, 0x08); + reg0x0c = stv0299_readreg (state, 0x0c); + + /** + * H/V switching over OP0, OP1 and OP2 are LNB power enable bits + */ + reg0x0c &= 0x0f; + + if (voltage == SEC_VOLTAGE_OFF) { + stv0299_writeregI (state, 0x0c, 0x00); /* LNB power off! */ + return stv0299_writeregI (state, 0x08, 0x00); /* LNB power off! */ + } + + stv0299_writeregI (state, 0x08, (reg0x08 & 0x3f) | (state->config->lock_output << 6)); + + switch (voltage) { + case SEC_VOLTAGE_13: + if (state->config->volt13_op0_op1 == STV0299_VOLT13_OP0) reg0x0c |= 0x10; + else reg0x0c |= 0x40; + + return stv0299_writeregI(state, 0x0c, reg0x0c); + + case SEC_VOLTAGE_18: + return stv0299_writeregI(state, 0x0c, reg0x0c | 0x50); + default: + return -EINVAL; + }; +} + +static int stv0299_send_legacy_dish_cmd(struct dvb_frontend* fe, u32 cmd) +{ + u8 last = 1; + int i; + + /* reset voltage at the end + if((0x50 & stv0299_readreg (i2c, 0x0c)) == 0x50) + cmd |= 0x80; + else + cmd &= 0x7F; + */ + + cmd = cmd << 1; + dprintk("%s switch command: 0x%04x\n",__FUNCTION__, cmd); + + stv0299_set_voltage(fe,SEC_VOLTAGE_18); + msleep(32); + + for (i=0; i<9; i++) { + if((cmd & 0x01) != last) { + stv0299_set_voltage(fe, last ? SEC_VOLTAGE_13 : SEC_VOLTAGE_18); + last = (last) ? 0 : 1; + } + + cmd = cmd >> 1; + + if (i != 8) + msleep(8); + } + + return 0; +} + +static int stv0299_init (struct dvb_frontend* fe) +{ + struct stv0299_state* state = (struct stv0299_state*) fe->demodulator_priv; + int i; + + dprintk("stv0299: init chip\n"); + + for (i=0; !(state->config->inittab[i] == 0xff && state->config->inittab[i+1] == 0xff); i+=2) + stv0299_writeregI(state, state->config->inittab[i], state->config->inittab[i+1]); + + if (state->config->pll_init) { + stv0299_writeregI(state, 0x05, 0xb5); /* enable i2c repeater on stv0299 */ + state->config->pll_init(fe); + stv0299_writeregI(state, 0x05, 0x35); /* disable i2c repeater on stv0299 */ + } + + return 0; +} + +static int stv0299_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct stv0299_state* state = (struct stv0299_state*) fe->demodulator_priv; + + u8 signal = 0xff - stv0299_readreg (state, 0x18); + u8 sync = stv0299_readreg (state, 0x1b); + + dprintk ("%s : FE_READ_STATUS : VSTATUS: 0x%02x\n", __FUNCTION__, sync); + *status = 0; + + if (signal > 10) + *status |= FE_HAS_SIGNAL; + + if (sync & 0x80) + *status |= FE_HAS_CARRIER; + + if (sync & 0x10) + *status |= FE_HAS_VITERBI; + + if (sync & 0x08) + *status |= FE_HAS_SYNC; + + if ((sync & 0x98) == 0x98) + *status |= FE_HAS_LOCK; + + return 0; +} + +static int stv0299_read_ber(struct dvb_frontend* fe, u32* ber) +{ + struct stv0299_state* state = (struct stv0299_state*) fe->demodulator_priv; + + if (state->errmode != STATUS_BER) return 0; + *ber = (stv0299_readreg (state, 0x1d) << 8) | stv0299_readreg (state, 0x1e); + + return 0; +} + +static int stv0299_read_signal_strength(struct dvb_frontend* fe, u16* strength) +{ + struct stv0299_state* state = (struct stv0299_state*) fe->demodulator_priv; + + s32 signal = 0xffff - ((stv0299_readreg (state, 0x18) << 8) + | stv0299_readreg (state, 0x19)); + + dprintk ("%s : FE_READ_SIGNAL_STRENGTH : AGC2I: 0x%02x%02x, signal=0x%04x\n", __FUNCTION__, + stv0299_readreg (state, 0x18), + stv0299_readreg (state, 0x19), (int) signal); + + signal = signal * 5 / 4; + *strength = (signal > 0xffff) ? 0xffff : (signal < 0) ? 0 : signal; + + return 0; +} + +static int stv0299_read_snr(struct dvb_frontend* fe, u16* snr) +{ + struct stv0299_state* state = (struct stv0299_state*) fe->demodulator_priv; + + s32 xsnr = 0xffff - ((stv0299_readreg (state, 0x24) << 8) + | stv0299_readreg (state, 0x25)); + xsnr = 3 * (xsnr - 0xa100); + *snr = (xsnr > 0xffff) ? 0xffff : (xsnr < 0) ? 0 : xsnr; + + return 0; +} + +static int stv0299_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + struct stv0299_state* state = (struct stv0299_state*) fe->demodulator_priv; + + if (state->errmode != STATUS_UCBLOCKS) *ucblocks = 0; + else *ucblocks = (stv0299_readreg (state, 0x1d) << 8) | stv0299_readreg (state, 0x1e); + + return 0; +} + +static int stv0299_set_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters * p) +{ + struct stv0299_state* state = (struct stv0299_state*) fe->demodulator_priv; + int invval = 0; + + dprintk ("%s : FE_SET_FRONTEND\n", __FUNCTION__); + + // set the inversion + if (p->inversion == INVERSION_OFF) invval = 0; + else if (p->inversion == INVERSION_ON) invval = 1; + else { + printk("stv0299 does not support auto-inversion\n"); + return -EINVAL; + } + if (state->config->invert) invval = (~invval) & 1; + stv0299_writeregI(state, 0x0c, (stv0299_readreg(state, 0x0c) & 0xfe) | invval); + + if (state->config->enhanced_tuning) { + /* check if we should do a finetune */ + int frequency_delta = p->frequency - state->tuner_frequency; + int minmax = p->u.qpsk.symbol_rate / 2000; + if (minmax < 5000) minmax = 5000; + + if ((frequency_delta > -minmax) && (frequency_delta < minmax) && (frequency_delta != 0) && + (state->fec_inner == p->u.qpsk.fec_inner) && + (state->symbol_rate == p->u.qpsk.symbol_rate)) { + int Drot_freq = (frequency_delta << 16) / (state->config->mclk / 1000); + + // zap the derotator registers first + stv0299_writeregI(state, 0x22, 0x00); + stv0299_writeregI(state, 0x23, 0x00); + + // now set them as we want + stv0299_writeregI(state, 0x22, Drot_freq >> 8); + stv0299_writeregI(state, 0x23, Drot_freq); + } else { + /* A "normal" tune is requested */ + stv0299_writeregI(state, 0x05, 0xb5); /* enable i2c repeater on stv0299 */ + state->config->pll_set(fe, p); + stv0299_writeregI(state, 0x05, 0x35); /* disable i2c repeater on stv0299 */ + + stv0299_writeregI(state, 0x32, 0x80); + stv0299_writeregI(state, 0x22, 0x00); + stv0299_writeregI(state, 0x23, 0x00); + stv0299_writeregI(state, 0x32, 0x19); + stv0299_set_symbolrate (fe, p->u.qpsk.symbol_rate); + stv0299_set_FEC (state, p->u.qpsk.fec_inner); + } + } else { + stv0299_writeregI(state, 0x05, 0xb5); /* enable i2c repeater on stv0299 */ + state->config->pll_set(fe, p); + stv0299_writeregI(state, 0x05, 0x35); /* disable i2c repeater on stv0299 */ + + stv0299_set_FEC (state, p->u.qpsk.fec_inner); + stv0299_set_symbolrate (fe, p->u.qpsk.symbol_rate); + stv0299_writeregI(state, 0x22, 0x00); + stv0299_writeregI(state, 0x23, 0x00); + stv0299_readreg (state, 0x23); + stv0299_writeregI(state, 0x12, 0xb9); + } + + state->tuner_frequency = p->frequency; + state->fec_inner = p->u.qpsk.fec_inner; + state->symbol_rate = p->u.qpsk.symbol_rate; + + return 0; +} + +static int stv0299_get_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters * p) +{ + struct stv0299_state* state = (struct stv0299_state*) fe->demodulator_priv; + s32 derot_freq; + int invval; + + derot_freq = (s32)(s16) ((stv0299_readreg (state, 0x22) << 8) + | stv0299_readreg (state, 0x23)); + + derot_freq *= (state->config->mclk >> 16); + derot_freq += 500; + derot_freq /= 1000; + + p->frequency += derot_freq; + + invval = stv0299_readreg (state, 0x0c) & 1; + if (state->config->invert) invval = (~invval) & 1; + p->inversion = invval ? INVERSION_ON : INVERSION_OFF; + + p->u.qpsk.fec_inner = stv0299_get_fec (state); + p->u.qpsk.symbol_rate = stv0299_get_symbolrate (state); + + return 0; +} + +static int stv0299_sleep(struct dvb_frontend* fe) +{ + struct stv0299_state* state = (struct stv0299_state*) fe->demodulator_priv; + + stv0299_writeregI(state, 0x02, 0x80); + state->initialised = 0; + + return 0; +} + +static int stv0299_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* fesettings) +{ + struct stv0299_state* state = (struct stv0299_state*) fe->demodulator_priv; + + fesettings->min_delay_ms = state->config->min_delay_ms; + if (fesettings->parameters.u.qpsk.symbol_rate < 10000000) { + fesettings->step_size = fesettings->parameters.u.qpsk.symbol_rate / 32000; + fesettings->max_drift = 5000; + } else { + fesettings->step_size = fesettings->parameters.u.qpsk.symbol_rate / 16000; + fesettings->max_drift = fesettings->parameters.u.qpsk.symbol_rate / 2000; + } + return 0; +} + +static void stv0299_release(struct dvb_frontend* fe) +{ + struct stv0299_state* state = (struct stv0299_state*) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops stv0299_ops; + +struct dvb_frontend* stv0299_attach(const struct stv0299_config* config, + struct i2c_adapter* i2c) +{ + struct stv0299_state* state = NULL; + int id; + + /* allocate memory for the internal state */ + state = (struct stv0299_state*) kmalloc(sizeof(struct stv0299_state), GFP_KERNEL); + if (state == NULL) goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + memcpy(&state->ops, &stv0299_ops, sizeof(struct dvb_frontend_ops)); + state->initialised = 0; + state->tuner_frequency = 0; + state->symbol_rate = 0; + state->fec_inner = 0; + state->errmode = STATUS_BER; + + /* check if the demod is there */ + stv0299_writeregI(state, 0x02, 0x34); /* standby off */ + msleep(200); + id = stv0299_readreg(state, 0x00); + + /* register 0x00 contains 0xa1 for STV0299 and STV0299B */ + /* register 0x00 might contain 0x80 when returning from standby */ + if (id != 0xa1 && id != 0x80) goto error; + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops stv0299_ops = { + + .info = { + .name = "ST STV0299 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 = stv0299_release, + + .init = stv0299_init, + .sleep = stv0299_sleep, + + .set_frontend = stv0299_set_frontend, + .get_frontend = stv0299_get_frontend, + .get_tune_settings = stv0299_get_tune_settings, + + .read_status = stv0299_read_status, + .read_ber = stv0299_read_ber, + .read_signal_strength = stv0299_read_signal_strength, + .read_snr = stv0299_read_snr, + .read_ucblocks = stv0299_read_ucblocks, + + .diseqc_send_master_cmd = stv0299_send_diseqc_msg, + .diseqc_send_burst = stv0299_send_diseqc_burst, + .set_tone = stv0299_set_tone, + .set_voltage = stv0299_set_voltage, + .dishnetwork_send_legacy_command = stv0299_send_legacy_dish_cmd, +}; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("ST STV0299 DVB Demodulator driver"); +MODULE_AUTHOR("Ralph Metzler, Holger Waechtler, Peter Schildmann, Felix Domke, " + "Andreas Oberritter, Andrew de Quincey, Kenneth Aafløy"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(stv0299_writereg); +EXPORT_SYMBOL(stv0299_attach); diff --git a/drivers/media/dvb/frontends/stv0299.h b/drivers/media/dvb/frontends/stv0299.h new file mode 100644 index 000000000000..79457a80a11f --- /dev/null +++ b/drivers/media/dvb/frontends/stv0299.h @@ -0,0 +1,104 @@ +/* + Driver for ST STV0299 demodulator + + Copyright (C) 2001-2002 Convergence Integrated Media GmbH + <ralph@convergence.de>, + <holger@convergence.de>, + <js@convergence.de> + + + Philips SU1278/SH + + Copyright (C) 2002 by Peter Schildmann <peter.schildmann@web.de> + + + LG TDQF-S001F + + Copyright (C) 2002 Felix Domke <tmbinc@elitedvb.net> + & Andreas Oberritter <obi@linuxtv.org> + + + Support for Samsung TBMU24112IMB used on Technisat SkyStar2 rev. 2.6B + + Copyright (C) 2003 Vadim Catana <skystar@moldova.cc>: + + Support for Philips SU1278 on Technotrend hardware + + Copyright (C) 2004 Andrew de Quincey <adq_dvb@lidskialf.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef STV0299_H +#define STV0299_H + +#include <linux/dvb/frontend.h> +#include "dvb_frontend.h" + +#define STV0229_LOCKOUTPUT_0 0 +#define STV0229_LOCKOUTPUT_1 1 +#define STV0229_LOCKOUTPUT_CF 2 +#define STV0229_LOCKOUTPUT_LK 3 + +#define STV0299_VOLT13_OP0 0 +#define STV0299_VOLT13_OP1 1 + +struct stv0299_config +{ + /* the demodulator's i2c address */ + u8 demod_address; + + /* inittab - array of pairs of values. + * First of each pair is the register, second is the value. + * List should be terminated with an 0xff, 0xff pair. + */ + u8* inittab; + + /* master clock to use */ + u32 mclk; + + /* does the inversion require inversion? */ + u8 invert:1; + + /* Should the enhanced tuning code be used? */ + u8 enhanced_tuning:1; + + /* Skip reinitialisation? */ + u8 skip_reinit:1; + + /* LOCK OUTPUT setting */ + u8 lock_output:2; + + /* Is 13v controlled by OP0 or OP1? */ + u8 volt13_op0_op1:1; + + /* minimum delay before retuning */ + int min_delay_ms; + + /* Set the symbol rate */ + int (*set_symbol_rate)(struct dvb_frontend* fe, u32 srate, u32 ratio); + + /* PLL maintenance */ + int (*pll_init)(struct dvb_frontend* fe); + int (*pll_set)(struct dvb_frontend* fe, struct dvb_frontend_parameters* params); +}; + +extern int stv0299_writereg (struct dvb_frontend* fe, u8 reg, u8 data); + +extern struct dvb_frontend* stv0299_attach(const struct stv0299_config* config, + struct i2c_adapter* i2c); + +#endif // STV0299_H diff --git a/drivers/media/dvb/frontends/tda10021.c b/drivers/media/dvb/frontends/tda10021.c new file mode 100644 index 000000000000..4e40d95ee95d --- /dev/null +++ b/drivers/media/dvb/frontends/tda10021.c @@ -0,0 +1,469 @@ +/* + TDA10021 - Single Chip Cable Channel Receiver driver module + used on the the Siemens DVB-C cards + + Copyright (C) 1999 Convergence Integrated Media GmbH <ralph@convergence.de> + Copyright (C) 2004 Markus Schulz <msc@antzsystem.de> + Support for TDA10021 + + 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/config.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/slab.h> + +#include "dvb_frontend.h" +#include "tda10021.h" + + +struct tda10021_state { + struct i2c_adapter* i2c; + struct dvb_frontend_ops ops; + /* configuration settings */ + const struct tda10021_config* config; + struct dvb_frontend frontend; + + u8 pwm; + u8 reg0; +}; + + +#if 0 +#define dprintk(x...) printk(x) +#else +#define dprintk(x...) +#endif + +static int verbose; + +#define XIN 57840000UL +#define DISABLE_INVERSION(reg0) do { reg0 |= 0x20; } while (0) +#define ENABLE_INVERSION(reg0) do { reg0 &= ~0x20; } while (0) +#define HAS_INVERSION(reg0) (!(reg0 & 0x20)) + +#define FIN (XIN >> 4) + +static int tda10021_inittab_size = 0x40; +static u8 tda10021_inittab[0x40]= +{ + 0x73, 0x6a, 0x23, 0x0a, 0x02, 0x37, 0x77, 0x1a, + 0x37, 0x6a, 0x17, 0x8a, 0x1e, 0x86, 0x43, 0x40, + 0xb8, 0x3f, 0xa0, 0x00, 0xcd, 0x01, 0x00, 0xff, + 0x11, 0x00, 0x7c, 0x31, 0x30, 0x20, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x33, 0x11, 0x0d, 0x95, 0x08, 0x58, + 0x00, 0x00, 0x80, 0x00, 0x80, 0xff, 0x00, 0x00, + 0x04, 0x2d, 0x2f, 0xff, 0x00, 0x00, 0x00, 0x00, +}; + +static int tda10021_writereg (struct tda10021_state* state, u8 reg, u8 data) +{ + u8 buf[] = { reg, data }; + struct i2c_msg msg = { .addr = state->config->demod_address, .flags = 0, .buf = buf, .len = 2 }; + int ret; + + ret = i2c_transfer (state->i2c, &msg, 1); + if (ret != 1) + printk("DVB: TDA10021(%d): %s, writereg error " + "(reg == 0x%02x, val == 0x%02x, ret == %i)\n", + state->frontend.dvb->num, __FUNCTION__, reg, data, ret); + + msleep(10); + return (ret != 1) ? -EREMOTEIO : 0; +} + +static u8 tda10021_readreg (struct tda10021_state* state, u8 reg) +{ + 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 } }; + int ret; + + ret = i2c_transfer (state->i2c, msg, 2); + if (ret != 2) + printk("DVB: TDA10021(%d): %s: readreg error (ret == %i)\n", + state->frontend.dvb->num, __FUNCTION__, ret); + return b1[0]; +} + +//get access to tuner +static int lock_tuner(struct tda10021_state* state) +{ + u8 buf[2] = { 0x0f, tda10021_inittab[0x0f] | 0x80 }; + struct i2c_msg msg = {.addr=state->config->demod_address, .flags=0, .buf=buf, .len=2}; + + if(i2c_transfer(state->i2c, &msg, 1) != 1) + { + printk("tda10021: lock tuner fails\n"); + return -EREMOTEIO; + } + return 0; +} + +//release access from tuner +static int unlock_tuner(struct tda10021_state* state) +{ + u8 buf[2] = { 0x0f, tda10021_inittab[0x0f] & 0x7f }; + struct i2c_msg msg_post={.addr=state->config->demod_address, .flags=0, .buf=buf, .len=2}; + + if(i2c_transfer(state->i2c, &msg_post, 1) != 1) + { + printk("tda10021: unlock tuner fails\n"); + return -EREMOTEIO; + } + return 0; +} + +static int tda10021_setup_reg0 (struct tda10021_state* state, u8 reg0, + fe_spectral_inversion_t inversion) +{ + reg0 |= state->reg0 & 0x63; + + if (INVERSION_ON == inversion) + ENABLE_INVERSION(reg0); + else if (INVERSION_OFF == inversion) + DISABLE_INVERSION(reg0); + + tda10021_writereg (state, 0x00, reg0 & 0xfe); + tda10021_writereg (state, 0x00, reg0 | 0x01); + + state->reg0 = reg0; + return 0; +} + +static int tda10021_set_symbolrate (struct tda10021_state* state, u32 symbolrate) +{ + s32 BDR; + s32 BDRI; + s16 SFIL=0; + u16 NDEC = 0; + u32 tmp, ratio; + + if (symbolrate > XIN/2) + symbolrate = XIN/2; + if (symbolrate < 500000) + symbolrate = 500000; + + if (symbolrate < XIN/16) NDEC = 1; + if (symbolrate < XIN/32) NDEC = 2; + if (symbolrate < XIN/64) NDEC = 3; + + if (symbolrate < (u32)(XIN/12.3)) SFIL = 1; + if (symbolrate < (u32)(XIN/16)) SFIL = 0; + if (symbolrate < (u32)(XIN/24.6)) SFIL = 1; + if (symbolrate < (u32)(XIN/32)) SFIL = 0; + if (symbolrate < (u32)(XIN/49.2)) SFIL = 1; + if (symbolrate < (u32)(XIN/64)) SFIL = 0; + if (symbolrate < (u32)(XIN/98.4)) SFIL = 1; + + symbolrate <<= NDEC; + ratio = (symbolrate << 4) / FIN; + tmp = ((symbolrate << 4) % FIN) << 8; + ratio = (ratio << 8) + tmp / FIN; + tmp = (tmp % FIN) << 8; + ratio = (ratio << 8) + (tmp + FIN/2) / FIN; + + BDR = ratio; + BDRI = (((XIN << 5) / symbolrate) + 1) / 2; + + if (BDRI > 0xFF) + BDRI = 0xFF; + + SFIL = (SFIL << 4) | tda10021_inittab[0x0E]; + + NDEC = (NDEC << 6) | tda10021_inittab[0x03]; + + tda10021_writereg (state, 0x03, NDEC); + tda10021_writereg (state, 0x0a, BDR&0xff); + tda10021_writereg (state, 0x0b, (BDR>> 8)&0xff); + tda10021_writereg (state, 0x0c, (BDR>>16)&0x3f); + + tda10021_writereg (state, 0x0d, BDRI); + tda10021_writereg (state, 0x0e, SFIL); + + return 0; +} + +static int tda10021_init (struct dvb_frontend *fe) +{ + struct tda10021_state* state = (struct tda10021_state*) fe->demodulator_priv; + int i; + + dprintk("DVB: TDA10021(%d): init chip\n", fe->adapter->num); + + //tda10021_writereg (fe, 0, 0); + + for (i=0; i<tda10021_inittab_size; i++) + tda10021_writereg (state, i, tda10021_inittab[i]); + + tda10021_writereg (state, 0x34, state->pwm); + + //Comment by markus + //0x2A[3-0] == PDIV -> P multiplaying factor (P=PDIV+1)(default 0) + //0x2A[4] == BYPPLL -> Power down mode (default 1) + //0x2A[5] == LCK -> PLL Lock Flag + //0x2A[6] == POLAXIN -> Polarity of the input reference clock (default 0) + + //Activate PLL + tda10021_writereg(state, 0x2a, tda10021_inittab[0x2a] & 0xef); + + if (state->config->pll_init) { + lock_tuner(state); + state->config->pll_init(fe); + unlock_tuner(state); + } + + return 0; +} + +static int tda10021_set_parameters (struct dvb_frontend *fe, + struct dvb_frontend_parameters *p) +{ + struct tda10021_state* state = (struct tda10021_state*) fe->demodulator_priv; + + //table for QAM4-QAM256 ready QAM4 QAM16 QAM32 QAM64 QAM128 QAM256 + //CONF + static const u8 reg0x00 [] = { 0x14, 0x00, 0x04, 0x08, 0x0c, 0x10 }; + //AGCREF value + static const u8 reg0x01 [] = { 0x78, 0x8c, 0x8c, 0x6a, 0x78, 0x5c }; + //LTHR value + static const u8 reg0x05 [] = { 0x78, 0x87, 0x64, 0x46, 0x36, 0x26 }; + //MSETH + static const u8 reg0x08 [] = { 0x8c, 0xa2, 0x74, 0x43, 0x34, 0x23 }; + //AREF + static const u8 reg0x09 [] = { 0x96, 0x91, 0x96, 0x6a, 0x7e, 0x6b }; + + int qam = p->u.qam.modulation; + + if (qam < 0 || qam > 5) + return -EINVAL; + + //printk("tda10021: set frequency to %d qam=%d symrate=%d\n", p->frequency,qam,p->u.qam.symbol_rate); + + lock_tuner(state); + state->config->pll_set(fe, p); + unlock_tuner(state); + + tda10021_set_symbolrate (state, p->u.qam.symbol_rate); + tda10021_writereg (state, 0x34, state->pwm); + + tda10021_writereg (state, 0x01, reg0x01[qam]); + tda10021_writereg (state, 0x05, reg0x05[qam]); + tda10021_writereg (state, 0x08, reg0x08[qam]); + tda10021_writereg (state, 0x09, reg0x09[qam]); + + tda10021_setup_reg0 (state, reg0x00[qam], p->inversion); + + return 0; +} + +static int tda10021_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct tda10021_state* state = (struct tda10021_state*) fe->demodulator_priv; + int sync; + + *status = 0; + //0x11[0] == EQALGO -> Equalizer algorithms state + //0x11[1] == CARLOCK -> Carrier locked + //0x11[2] == FSYNC -> Frame synchronisation + //0x11[3] == FEL -> Front End locked + //0x11[6] == NODVB -> DVB Mode Information + sync = tda10021_readreg (state, 0x11); + + if (sync & 2) + *status |= FE_HAS_SIGNAL|FE_HAS_CARRIER; + + if (sync & 4) + *status |= FE_HAS_SYNC|FE_HAS_VITERBI; + + if (sync & 8) + *status |= FE_HAS_LOCK; + + return 0; +} + +static int tda10021_read_ber(struct dvb_frontend* fe, u32* ber) +{ + struct tda10021_state* state = (struct tda10021_state*) fe->demodulator_priv; + + u32 _ber = tda10021_readreg(state, 0x14) | + (tda10021_readreg(state, 0x15) << 8) | + ((tda10021_readreg(state, 0x16) & 0x0f) << 16); + *ber = 10 * _ber; + + return 0; +} + +static int tda10021_read_signal_strength(struct dvb_frontend* fe, u16* strength) +{ + struct tda10021_state* state = (struct tda10021_state*) fe->demodulator_priv; + + u8 gain = tda10021_readreg(state, 0x17); + *strength = (gain << 8) | gain; + + return 0; +} + +static int tda10021_read_snr(struct dvb_frontend* fe, u16* snr) +{ + struct tda10021_state* state = (struct tda10021_state*) fe->demodulator_priv; + + u8 quality = ~tda10021_readreg(state, 0x18); + *snr = (quality << 8) | quality; + + return 0; +} + +static int tda10021_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + struct tda10021_state* state = (struct tda10021_state*) fe->demodulator_priv; + + *ucblocks = tda10021_readreg (state, 0x13) & 0x7f; + if (*ucblocks == 0x7f) + *ucblocks = 0xffffffff; + + /* reset uncorrected block counter */ + tda10021_writereg (state, 0x10, tda10021_inittab[0x10] & 0xdf); + tda10021_writereg (state, 0x10, tda10021_inittab[0x10]); + + return 0; +} + +static int tda10021_get_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + struct tda10021_state* state = (struct tda10021_state*) fe->demodulator_priv; + int sync; + s8 afc = 0; + + sync = tda10021_readreg(state, 0x11); + afc = tda10021_readreg(state, 0x19); + if (verbose) { + /* AFC only valid when carrier has been recovered */ + printk(sync & 2 ? "DVB: TDA10021(%d): AFC (%d) %dHz\n" : + "DVB: TDA10021(%d): [AFC (%d) %dHz]\n", + state->frontend.dvb->num, afc, + -((s32)p->u.qam.symbol_rate * afc) >> 10); + } + + p->inversion = HAS_INVERSION(state->reg0) ? INVERSION_ON : INVERSION_OFF; + p->u.qam.modulation = ((state->reg0 >> 2) & 7) + QAM_16; + + p->u.qam.fec_inner = FEC_NONE; + p->frequency = ((p->frequency + 31250) / 62500) * 62500; + + if (sync & 2) + p->frequency -= ((s32)p->u.qam.symbol_rate * afc) >> 10; + + return 0; +} + +static int tda10021_sleep(struct dvb_frontend* fe) +{ + struct tda10021_state* state = (struct tda10021_state*) fe->demodulator_priv; + + tda10021_writereg (state, 0x1b, 0x02); /* pdown ADC */ + tda10021_writereg (state, 0x00, 0x80); /* standby */ + + return 0; +} + +static void tda10021_release(struct dvb_frontend* fe) +{ + struct tda10021_state* state = (struct tda10021_state*) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops tda10021_ops; + +struct dvb_frontend* tda10021_attach(const struct tda10021_config* config, + struct i2c_adapter* i2c, + u8 pwm) +{ + struct tda10021_state* state = NULL; + + /* allocate memory for the internal state */ + state = (struct tda10021_state*) kmalloc(sizeof(struct tda10021_state), GFP_KERNEL); + if (state == NULL) goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + memcpy(&state->ops, &tda10021_ops, sizeof(struct dvb_frontend_ops)); + state->pwm = pwm; + state->reg0 = tda10021_inittab[0]; + + /* check if the demod is there */ + if ((tda10021_readreg(state, 0x1a) & 0xf0) != 0x70) goto error; + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops tda10021_ops = { + + .info = { + .name = "Philips TDA10021 DVB-C", + .type = FE_QAM, + .frequency_stepsize = 62500, + .frequency_min = 51000000, + .frequency_max = 858000000, + .symbol_rate_min = (XIN/2)/64, /* SACLK/64 == (XIN/2)/64 */ + .symbol_rate_max = (XIN/2)/4, /* SACLK/4 */ + #if 0 + .frequency_tolerance = ???, + .symbol_rate_tolerance = ???, /* ppm */ /* == 8% (spec p. 5) */ + #endif + .caps = 0x400 | //FE_CAN_QAM_4 + FE_CAN_QAM_16 | FE_CAN_QAM_32 | FE_CAN_QAM_64 | + FE_CAN_QAM_128 | FE_CAN_QAM_256 | + FE_CAN_FEC_AUTO + }, + + .release = tda10021_release, + + .init = tda10021_init, + .sleep = tda10021_sleep, + + .set_frontend = tda10021_set_parameters, + .get_frontend = tda10021_get_frontend, + + .read_status = tda10021_read_status, + .read_ber = tda10021_read_ber, + .read_signal_strength = tda10021_read_signal_strength, + .read_snr = tda10021_read_snr, + .read_ucblocks = tda10021_read_ucblocks, +}; + +module_param(verbose, int, 0644); +MODULE_PARM_DESC(verbose, "print AFC offset after tuning for debugging the PWM setting"); + +MODULE_DESCRIPTION("Philips TDA10021 DVB-C demodulator driver"); +MODULE_AUTHOR("Ralph Metzler, Holger Waechtler, Markus Schulz"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(tda10021_attach); diff --git a/drivers/media/dvb/frontends/tda10021.h b/drivers/media/dvb/frontends/tda10021.h new file mode 100644 index 000000000000..7d6a51ce291e --- /dev/null +++ b/drivers/media/dvb/frontends/tda10021.h @@ -0,0 +1,42 @@ +/* + TDA10021 - Single Chip Cable Channel Receiver driver module + used on the the Siemens DVB-C cards + + Copyright (C) 1999 Convergence Integrated Media GmbH <ralph@convergence.de> + Copyright (C) 2004 Markus Schulz <msc@antzsystem.de> + Support for TDA10021 + + 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 TDA10021_H +#define TDA10021_H + +#include <linux/dvb/frontend.h> + +struct tda10021_config +{ + /* the demodulator's i2c address */ + u8 demod_address; + + /* PLL maintenance */ + int (*pll_init)(struct dvb_frontend* fe); + int (*pll_set)(struct dvb_frontend* fe, struct dvb_frontend_parameters* params); +}; + +extern struct dvb_frontend* tda10021_attach(const struct tda10021_config* config, + struct i2c_adapter* i2c, u8 pwm); + +#endif // TDA10021_H diff --git a/drivers/media/dvb/frontends/tda1004x.c b/drivers/media/dvb/frontends/tda1004x.c new file mode 100644 index 000000000000..687ad9cf3384 --- /dev/null +++ b/drivers/media/dvb/frontends/tda1004x.c @@ -0,0 +1,1206 @@ + /* + Driver for Philips tda1004xh OFDM Demodulator + + (c) 2003, 2004 Andrew de Quincey & Robert Schlabbach + + 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. + + */ +/* + * This driver needs external firmware. Please use the commands + * "<kerneldir>/Documentation/dvb/get_dvb_firmware tda10045", + * "<kerneldir>/Documentation/dvb/get_dvb_firmware tda10046" to + * download/extract them, and then copy them to /usr/lib/hotplug/firmware. + */ +#define TDA10045_DEFAULT_FIRMWARE "dvb-fe-tda10045.fw" +#define TDA10046_DEFAULT_FIRMWARE "dvb-fe-tda10046.fw" + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include "dvb_frontend.h" +#include "tda1004x.h" + +#define TDA1004X_DEMOD_TDA10045 0 +#define TDA1004X_DEMOD_TDA10046 1 + + +struct tda1004x_state { + struct i2c_adapter* i2c; + struct dvb_frontend_ops ops; + const struct tda1004x_config* config; + struct dvb_frontend frontend; + + /* private demod data */ + u8 initialised:1; + u8 demod_type; +}; + + +static int debug; +#define dprintk(args...) \ + do { \ + if (debug) printk(KERN_DEBUG "tda1004x: " args); \ + } while (0) + +#define TDA1004X_CHIPID 0x00 +#define TDA1004X_AUTO 0x01 +#define TDA1004X_IN_CONF1 0x02 +#define TDA1004X_IN_CONF2 0x03 +#define TDA1004X_OUT_CONF1 0x04 +#define TDA1004X_OUT_CONF2 0x05 +#define TDA1004X_STATUS_CD 0x06 +#define TDA1004X_CONFC4 0x07 +#define TDA1004X_DSSPARE2 0x0C +#define TDA10045H_CODE_IN 0x0D +#define TDA10045H_FWPAGE 0x0E +#define TDA1004X_SCAN_CPT 0x10 +#define TDA1004X_DSP_CMD 0x11 +#define TDA1004X_DSP_ARG 0x12 +#define TDA1004X_DSP_DATA1 0x13 +#define TDA1004X_DSP_DATA2 0x14 +#define TDA1004X_CONFADC1 0x15 +#define TDA1004X_CONFC1 0x16 +#define TDA10045H_S_AGC 0x1a +#define TDA10046H_AGC_TUN_LEVEL 0x1a +#define TDA1004X_SNR 0x1c +#define TDA1004X_CONF_TS1 0x1e +#define TDA1004X_CONF_TS2 0x1f +#define TDA1004X_CBER_RESET 0x20 +#define TDA1004X_CBER_MSB 0x21 +#define TDA1004X_CBER_LSB 0x22 +#define TDA1004X_CVBER_LUT 0x23 +#define TDA1004X_VBER_MSB 0x24 +#define TDA1004X_VBER_MID 0x25 +#define TDA1004X_VBER_LSB 0x26 +#define TDA1004X_UNCOR 0x27 + +#define TDA10045H_CONFPLL_P 0x2D +#define TDA10045H_CONFPLL_M_MSB 0x2E +#define TDA10045H_CONFPLL_M_LSB 0x2F +#define TDA10045H_CONFPLL_N 0x30 + +#define TDA10046H_CONFPLL1 0x2D +#define TDA10046H_CONFPLL2 0x2F +#define TDA10046H_CONFPLL3 0x30 +#define TDA10046H_TIME_WREF1 0x31 +#define TDA10046H_TIME_WREF2 0x32 +#define TDA10046H_TIME_WREF3 0x33 +#define TDA10046H_TIME_WREF4 0x34 +#define TDA10046H_TIME_WREF5 0x35 + +#define TDA10045H_UNSURW_MSB 0x31 +#define TDA10045H_UNSURW_LSB 0x32 +#define TDA10045H_WREF_MSB 0x33 +#define TDA10045H_WREF_MID 0x34 +#define TDA10045H_WREF_LSB 0x35 +#define TDA10045H_MUXOUT 0x36 +#define TDA1004X_CONFADC2 0x37 + +#define TDA10045H_IOFFSET 0x38 + +#define TDA10046H_CONF_TRISTATE1 0x3B +#define TDA10046H_CONF_TRISTATE2 0x3C +#define TDA10046H_CONF_POLARITY 0x3D +#define TDA10046H_FREQ_OFFSET 0x3E +#define TDA10046H_GPIO_OUT_SEL 0x41 +#define TDA10046H_GPIO_SELECT 0x42 +#define TDA10046H_AGC_CONF 0x43 +#define TDA10046H_AGC_GAINS 0x46 +#define TDA10046H_AGC_TUN_MIN 0x47 +#define TDA10046H_AGC_TUN_MAX 0x48 +#define TDA10046H_AGC_IF_MIN 0x49 +#define TDA10046H_AGC_IF_MAX 0x4A + +#define TDA10046H_FREQ_PHY2_MSB 0x4D +#define TDA10046H_FREQ_PHY2_LSB 0x4E + +#define TDA10046H_CVBER_CTRL 0x4F +#define TDA10046H_AGC_IF_LEVEL 0x52 +#define TDA10046H_CODE_CPT 0x57 +#define TDA10046H_CODE_IN 0x58 + + +static int tda1004x_write_byteI(struct tda1004x_state *state, int reg, int data) +{ + int ret; + u8 buf[] = { reg, data }; + struct i2c_msg msg = { .addr=0, .flags=0, .buf=buf, .len=2 }; + + dprintk("%s: reg=0x%x, data=0x%x\n", __FUNCTION__, reg, data); + + msg.addr = state->config->demod_address; + ret = i2c_transfer(state->i2c, &msg, 1); + + if (ret != 1) + dprintk("%s: error reg=0x%x, data=0x%x, ret=%i\n", + __FUNCTION__, reg, data, ret); + + dprintk("%s: success reg=0x%x, data=0x%x, ret=%i\n", __FUNCTION__, + reg, data, ret); + return (ret != 1) ? -1 : 0; +} + +static int tda1004x_read_byte(struct tda1004x_state *state, int reg) +{ + int ret; + u8 b0[] = { reg }; + u8 b1[] = { 0 }; + struct i2c_msg msg[] = {{ .addr=0, .flags=0, .buf=b0, .len=1}, + { .addr=0, .flags=I2C_M_RD, .buf=b1, .len = 1}}; + + dprintk("%s: reg=0x%x\n", __FUNCTION__, reg); + + msg[0].addr = state->config->demod_address; + msg[1].addr = state->config->demod_address; + ret = i2c_transfer(state->i2c, msg, 2); + + if (ret != 2) { + dprintk("%s: error reg=0x%x, ret=%i\n", __FUNCTION__, reg, + ret); + return -1; + } + + dprintk("%s: success reg=0x%x, data=0x%x, ret=%i\n", __FUNCTION__, + reg, b1[0], ret); + return b1[0]; +} + +static int tda1004x_write_mask(struct tda1004x_state *state, int reg, int mask, int data) +{ + int val; + dprintk("%s: reg=0x%x, mask=0x%x, data=0x%x\n", __FUNCTION__, reg, + mask, data); + + // read a byte and check + val = tda1004x_read_byte(state, reg); + if (val < 0) + return val; + + // mask if off + val = val & ~mask; + val |= data & 0xff; + + // write it out again + return tda1004x_write_byteI(state, reg, val); +} + +static int tda1004x_write_buf(struct tda1004x_state *state, int reg, unsigned char *buf, int len) +{ + int i; + int result; + + dprintk("%s: reg=0x%x, len=0x%x\n", __FUNCTION__, reg, len); + + result = 0; + for (i = 0; i < len; i++) { + result = tda1004x_write_byteI(state, reg + i, buf[i]); + if (result != 0) + break; + } + + return result; +} + +static int tda1004x_enable_tuner_i2c(struct tda1004x_state *state) +{ + int result; + dprintk("%s\n", __FUNCTION__); + + result = tda1004x_write_mask(state, TDA1004X_CONFC4, 2, 2); + msleep(1); + return result; +} + +static int tda1004x_disable_tuner_i2c(struct tda1004x_state *state) +{ + dprintk("%s\n", __FUNCTION__); + + return tda1004x_write_mask(state, TDA1004X_CONFC4, 2, 0); +} + +static int tda10045h_set_bandwidth(struct tda1004x_state *state, + fe_bandwidth_t bandwidth) +{ + static u8 bandwidth_6mhz[] = { 0x02, 0x00, 0x3d, 0x00, 0x60, 0x1e, 0xa7, 0x45, 0x4f }; + static u8 bandwidth_7mhz[] = { 0x02, 0x00, 0x37, 0x00, 0x4a, 0x2f, 0x6d, 0x76, 0xdb }; + static u8 bandwidth_8mhz[] = { 0x02, 0x00, 0x3d, 0x00, 0x48, 0x17, 0x89, 0xc7, 0x14 }; + + switch (bandwidth) { + case BANDWIDTH_6_MHZ: + tda1004x_write_buf(state, TDA10045H_CONFPLL_P, bandwidth_6mhz, sizeof(bandwidth_6mhz)); + break; + + case BANDWIDTH_7_MHZ: + tda1004x_write_buf(state, TDA10045H_CONFPLL_P, bandwidth_7mhz, sizeof(bandwidth_7mhz)); + break; + + case BANDWIDTH_8_MHZ: + tda1004x_write_buf(state, TDA10045H_CONFPLL_P, bandwidth_8mhz, sizeof(bandwidth_8mhz)); + break; + + default: + return -EINVAL; + } + + tda1004x_write_byteI(state, TDA10045H_IOFFSET, 0); + + return 0; +} + +static int tda10046h_set_bandwidth(struct tda1004x_state *state, + fe_bandwidth_t bandwidth) +{ + static u8 bandwidth_6mhz[] = { 0x80, 0x15, 0xfe, 0xab, 0x8e }; + static u8 bandwidth_7mhz[] = { 0x6e, 0x02, 0x53, 0xc8, 0x25 }; + static u8 bandwidth_8mhz[] = { 0x60, 0x12, 0xa8, 0xe4, 0xbd }; + + switch (bandwidth) { + case BANDWIDTH_6_MHZ: + tda1004x_write_buf(state, TDA10046H_TIME_WREF1, bandwidth_6mhz, sizeof(bandwidth_6mhz)); + break; + + case BANDWIDTH_7_MHZ: + tda1004x_write_buf(state, TDA10046H_TIME_WREF1, bandwidth_7mhz, sizeof(bandwidth_7mhz)); + break; + + case BANDWIDTH_8_MHZ: + tda1004x_write_buf(state, TDA10046H_TIME_WREF1, bandwidth_8mhz, sizeof(bandwidth_8mhz)); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int tda1004x_do_upload(struct tda1004x_state *state, + unsigned char *mem, unsigned int len, + u8 dspCodeCounterReg, u8 dspCodeInReg) +{ + u8 buf[65]; + struct i2c_msg fw_msg = {.addr = 0,.flags = 0,.buf = buf,.len = 0 }; + int tx_size; + int pos = 0; + + /* clear code counter */ + tda1004x_write_byteI(state, dspCodeCounterReg, 0); + fw_msg.addr = state->config->demod_address; + + buf[0] = dspCodeInReg; + while (pos != len) { + + // work out how much to send this time + tx_size = len - pos; + if (tx_size > 0x10) { + tx_size = 0x10; + } + + // send the chunk + memcpy(buf + 1, mem + pos, tx_size); + fw_msg.len = tx_size + 1; + if (i2c_transfer(state->i2c, &fw_msg, 1) != 1) { + printk("tda1004x: Error during firmware upload\n"); + return -EIO; + } + pos += tx_size; + + dprintk("%s: fw_pos=0x%x\n", __FUNCTION__, pos); + } + return 0; +} + +static int tda1004x_check_upload_ok(struct tda1004x_state *state, u8 dspVersion) +{ + u8 data1, data2; + + // check upload was OK + tda1004x_write_mask(state, TDA1004X_CONFC4, 0x10, 0); // we want to read from the DSP + tda1004x_write_byteI(state, TDA1004X_DSP_CMD, 0x67); + + data1 = tda1004x_read_byte(state, TDA1004X_DSP_DATA1); + data2 = tda1004x_read_byte(state, TDA1004X_DSP_DATA2); + if (data1 != 0x67 || data2 != dspVersion) { + return -EIO; + } + + return 0; +} + +static int tda10045_fwupload(struct dvb_frontend* fe) +{ + struct tda1004x_state* state = fe->demodulator_priv; + int ret; + const struct firmware *fw; + + + /* don't re-upload unless necessary */ + if (tda1004x_check_upload_ok(state, 0x2c) == 0) return 0; + + /* request the firmware, this will block until someone uploads it */ + printk("tda1004x: waiting for firmware upload (%s)...\n", TDA10045_DEFAULT_FIRMWARE); + ret = state->config->request_firmware(fe, &fw, TDA10045_DEFAULT_FIRMWARE); + if (ret) { + printk("tda1004x: no firmware upload (timeout or file not found?)\n"); + return ret; + } + + /* reset chip */ + tda1004x_write_mask(state, TDA1004X_CONFC4, 0x10, 0); + tda1004x_write_mask(state, TDA1004X_CONFC4, 8, 8); + tda1004x_write_mask(state, TDA1004X_CONFC4, 8, 0); + msleep(10); + + /* set parameters */ + tda10045h_set_bandwidth(state, BANDWIDTH_8_MHZ); + + ret = tda1004x_do_upload(state, fw->data, fw->size, TDA10045H_FWPAGE, TDA10045H_CODE_IN); + if (ret) + return ret; + printk("tda1004x: firmware upload complete\n"); + + /* wait for DSP to initialise */ + /* DSPREADY doesn't seem to work on the TDA10045H */ + msleep(100); + + return tda1004x_check_upload_ok(state, 0x2c); +} + +static int tda10046_fwupload(struct dvb_frontend* fe) +{ + struct tda1004x_state* state = fe->demodulator_priv; + unsigned long timeout; + int ret; + const struct firmware *fw; + + /* reset + wake up chip */ + tda1004x_write_mask(state, TDA1004X_CONFC4, 1, 0); + tda1004x_write_mask(state, TDA10046H_CONF_TRISTATE1, 1, 0); + msleep(100); + + /* don't re-upload unless necessary */ + if (tda1004x_check_upload_ok(state, 0x20) == 0) return 0; + + /* request the firmware, this will block until someone uploads it */ + printk("tda1004x: waiting for firmware upload (%s)...\n", TDA10046_DEFAULT_FIRMWARE); + ret = state->config->request_firmware(fe, &fw, TDA10046_DEFAULT_FIRMWARE); + if (ret) { + printk("tda1004x: no firmware upload (timeout or file not found?)\n"); + return ret; + } + + /* set parameters */ + tda1004x_write_byteI(state, TDA10046H_CONFPLL2, 10); + tda1004x_write_byteI(state, TDA10046H_CONFPLL3, 0); + tda1004x_write_byteI(state, TDA10046H_FREQ_OFFSET, 99); + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_MSB, 0xd4); + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_LSB, 0x2c); + tda1004x_write_mask(state, TDA1004X_CONFC4, 8, 8); // going to boot from HOST + + ret = tda1004x_do_upload(state, fw->data, fw->size, TDA10046H_CODE_CPT, TDA10046H_CODE_IN); + if (ret) + return ret; + printk("tda1004x: firmware upload complete\n"); + + /* wait for DSP to initialise */ + timeout = jiffies + HZ; + while(!(tda1004x_read_byte(state, TDA1004X_STATUS_CD) & 0x20)) { + if (time_after(jiffies, timeout)) { + printk("tda1004x: DSP failed to initialised.\n"); + return -EIO; + } + msleep(1); + } + + return tda1004x_check_upload_ok(state, 0x20); +} + +static int tda1004x_encode_fec(int fec) +{ + // convert known FEC values + switch (fec) { + case FEC_1_2: + return 0; + case FEC_2_3: + return 1; + case FEC_3_4: + return 2; + case FEC_5_6: + return 3; + case FEC_7_8: + return 4; + } + + // unsupported + return -EINVAL; +} + +static int tda1004x_decode_fec(int tdafec) +{ + // convert known FEC values + switch (tdafec) { + case 0: + return FEC_1_2; + case 1: + return FEC_2_3; + case 2: + return FEC_3_4; + case 3: + return FEC_5_6; + case 4: + return FEC_7_8; + } + + // unsupported + return -1; +} + +int tda1004x_write_byte(struct dvb_frontend* fe, int reg, int data) +{ + struct tda1004x_state* state = fe->demodulator_priv; + + return tda1004x_write_byteI(state, reg, data); +} + +static int tda10045_init(struct dvb_frontend* fe) +{ + struct tda1004x_state* state = fe->demodulator_priv; + + dprintk("%s\n", __FUNCTION__); + + if (state->initialised) return 0; + + if (tda10045_fwupload(fe)) { + printk("tda1004x: firmware upload failed\n"); + return -EIO; + } + + tda1004x_write_mask(state, TDA1004X_CONFADC1, 0x10, 0); // wake up the ADC + + // Init the PLL + if (state->config->pll_init) { + tda1004x_enable_tuner_i2c(state); + state->config->pll_init(fe); + tda1004x_disable_tuner_i2c(state); + } + + // tda setup + tda1004x_write_mask(state, TDA1004X_CONFC4, 0x20, 0); // disable DSP watchdog timer + tda1004x_write_mask(state, TDA1004X_AUTO, 8, 0); // select HP stream + tda1004x_write_mask(state, TDA1004X_CONFC1, 0x40, 0); // set polarity of VAGC signal + tda1004x_write_mask(state, TDA1004X_CONFC1, 0x80, 0x80); // enable pulse killer + tda1004x_write_mask(state, TDA1004X_AUTO, 0x10, 0x10); // enable auto offset + tda1004x_write_mask(state, TDA1004X_IN_CONF2, 0xC0, 0x0); // no frequency offset + tda1004x_write_byteI(state, TDA1004X_CONF_TS1, 0); // setup MPEG2 TS interface + tda1004x_write_byteI(state, TDA1004X_CONF_TS2, 0); // setup MPEG2 TS interface + tda1004x_write_mask(state, TDA1004X_VBER_MSB, 0xe0, 0xa0); // 10^6 VBER measurement bits + tda1004x_write_mask(state, TDA1004X_CONFC1, 0x10, 0); // VAGC polarity + tda1004x_write_byteI(state, TDA1004X_CONFADC1, 0x2e); + + tda1004x_write_mask(state, 0x1f, 0x01, state->config->invert_oclk); + + state->initialised = 1; + return 0; +} + +static int tda10046_init(struct dvb_frontend* fe) +{ + struct tda1004x_state* state = fe->demodulator_priv; + dprintk("%s\n", __FUNCTION__); + + if (state->initialised) return 0; + + if (tda10046_fwupload(fe)) { + printk("tda1004x: firmware upload failed\n"); + return -EIO; + } + + tda1004x_write_mask(state, TDA1004X_CONFC4, 1, 0); // wake up the chip + + // Init the PLL + if (state->config->pll_init) { + tda1004x_enable_tuner_i2c(state); + state->config->pll_init(fe); + tda1004x_disable_tuner_i2c(state); + } + + // tda setup + tda1004x_write_mask(state, TDA1004X_CONFC4, 0x20, 0); // disable DSP watchdog timer + tda1004x_write_mask(state, TDA1004X_CONFC1, 0x40, 0x40); + tda1004x_write_mask(state, TDA1004X_AUTO, 8, 0); // select HP stream + tda1004x_write_mask(state, TDA1004X_CONFC1, 0x80, 0); // disable pulse killer + tda1004x_write_byteI(state, TDA10046H_CONFPLL2, 10); // PLL M = 10 + tda1004x_write_byteI(state, TDA10046H_CONFPLL3, 0); // PLL P = N = 0 + tda1004x_write_byteI(state, TDA10046H_FREQ_OFFSET, 99); // FREQOFFS = 99 + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_MSB, 0xd4); // } PHY2 = -11221 + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_LSB, 0x2c); // } + tda1004x_write_byteI(state, TDA10046H_AGC_CONF, 0); // AGC setup + tda1004x_write_mask(state, TDA10046H_CONF_POLARITY, 0x60, 0x60); // set AGC polarities + tda1004x_write_byteI(state, TDA10046H_AGC_TUN_MIN, 0); // } + tda1004x_write_byteI(state, TDA10046H_AGC_TUN_MAX, 0xff); // } AGC min/max values + tda1004x_write_byteI(state, TDA10046H_AGC_IF_MIN, 0); // } + tda1004x_write_byteI(state, TDA10046H_AGC_IF_MAX, 0xff); // } + tda1004x_write_mask(state, TDA10046H_CVBER_CTRL, 0x30, 0x10); // 10^6 VBER measurement bits + tda1004x_write_byteI(state, TDA10046H_AGC_GAINS, 1); // IF gain 2, TUN gain 1 + tda1004x_write_mask(state, TDA1004X_AUTO, 0x80, 0); // crystal is 50ppm + tda1004x_write_byteI(state, TDA1004X_CONF_TS1, 7); // MPEG2 interface config + tda1004x_write_mask(state, TDA1004X_CONF_TS2, 0x31, 0); // MPEG2 interface config + tda1004x_write_mask(state, TDA10046H_CONF_TRISTATE1, 0x9e, 0); // disable AGC_TUN + tda1004x_write_byteI(state, TDA10046H_CONF_TRISTATE2, 0xe1); // tristate setup + tda1004x_write_byteI(state, TDA10046H_GPIO_OUT_SEL, 0xcc); // GPIO output config + tda1004x_write_mask(state, TDA10046H_GPIO_SELECT, 8, 8); // GPIO select + tda10046h_set_bandwidth(state, BANDWIDTH_8_MHZ); // default bandwidth 8 MHz + + tda1004x_write_mask(state, 0x3a, 0x80, state->config->invert_oclk << 7); + + state->initialised = 1; + return 0; +} + +static int tda1004x_set_fe(struct dvb_frontend* fe, + struct dvb_frontend_parameters *fe_params) +{ + struct tda1004x_state* state = fe->demodulator_priv; + int tmp; + int inversion; + + dprintk("%s\n", __FUNCTION__); + + if (state->demod_type == TDA1004X_DEMOD_TDA10046) { + // setup auto offset + tda1004x_write_mask(state, TDA1004X_AUTO, 0x10, 0x10); + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x80, 0); + tda1004x_write_mask(state, TDA1004X_IN_CONF2, 0xC0, 0); + + // disable agc_conf[2] + tda1004x_write_mask(state, TDA10046H_AGC_CONF, 4, 0); + } + + // set frequency + tda1004x_enable_tuner_i2c(state); + state->config->pll_set(fe, fe_params); + tda1004x_disable_tuner_i2c(state); + + if (state->demod_type == TDA1004X_DEMOD_TDA10046) + tda1004x_write_mask(state, TDA10046H_AGC_CONF, 4, 4); + + // Hardcoded to use auto as much as possible on the TDA10045 as it + // is very unreliable if AUTO mode is _not_ used. + if (state->demod_type == TDA1004X_DEMOD_TDA10045) { + fe_params->u.ofdm.code_rate_HP = FEC_AUTO; + fe_params->u.ofdm.guard_interval = GUARD_INTERVAL_AUTO; + fe_params->u.ofdm.transmission_mode = TRANSMISSION_MODE_AUTO; + } + + // Set standard params.. or put them to auto + if ((fe_params->u.ofdm.code_rate_HP == FEC_AUTO) || + (fe_params->u.ofdm.code_rate_LP == FEC_AUTO) || + (fe_params->u.ofdm.constellation == QAM_AUTO) || + (fe_params->u.ofdm.hierarchy_information == HIERARCHY_AUTO)) { + tda1004x_write_mask(state, TDA1004X_AUTO, 1, 1); // enable auto + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x03, 0); // turn off constellation bits + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x60, 0); // turn off hierarchy bits + tda1004x_write_mask(state, TDA1004X_IN_CONF2, 0x3f, 0); // turn off FEC bits + } else { + tda1004x_write_mask(state, TDA1004X_AUTO, 1, 0); // disable auto + + // set HP FEC + tmp = tda1004x_encode_fec(fe_params->u.ofdm.code_rate_HP); + if (tmp < 0) return tmp; + tda1004x_write_mask(state, TDA1004X_IN_CONF2, 7, tmp); + + // set LP FEC + tmp = tda1004x_encode_fec(fe_params->u.ofdm.code_rate_LP); + if (tmp < 0) return tmp; + tda1004x_write_mask(state, TDA1004X_IN_CONF2, 0x38, tmp << 3); + + // set constellation + switch (fe_params->u.ofdm.constellation) { + case QPSK: + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 3, 0); + break; + + case QAM_16: + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 3, 1); + break; + + case QAM_64: + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 3, 2); + break; + + default: + return -EINVAL; + } + + // set hierarchy + switch (fe_params->u.ofdm.hierarchy_information) { + case HIERARCHY_NONE: + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x60, 0 << 5); + break; + + case HIERARCHY_1: + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x60, 1 << 5); + break; + + case HIERARCHY_2: + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x60, 2 << 5); + break; + + case HIERARCHY_4: + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x60, 3 << 5); + break; + + default: + return -EINVAL; + } + } + + // set bandwidth + switch(state->demod_type) { + case TDA1004X_DEMOD_TDA10045: + tda10045h_set_bandwidth(state, fe_params->u.ofdm.bandwidth); + break; + + case TDA1004X_DEMOD_TDA10046: + tda10046h_set_bandwidth(state, fe_params->u.ofdm.bandwidth); + break; + } + + // set inversion + inversion = fe_params->inversion; + if (state->config->invert) inversion = inversion ? INVERSION_OFF : INVERSION_ON; + switch (inversion) { + case INVERSION_OFF: + tda1004x_write_mask(state, TDA1004X_CONFC1, 0x20, 0); + break; + + case INVERSION_ON: + tda1004x_write_mask(state, TDA1004X_CONFC1, 0x20, 0x20); + break; + + default: + return -EINVAL; + } + + // set guard interval + switch (fe_params->u.ofdm.guard_interval) { + case GUARD_INTERVAL_1_32: + tda1004x_write_mask(state, TDA1004X_AUTO, 2, 0); + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x0c, 0 << 2); + break; + + case GUARD_INTERVAL_1_16: + tda1004x_write_mask(state, TDA1004X_AUTO, 2, 0); + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x0c, 1 << 2); + break; + + case GUARD_INTERVAL_1_8: + tda1004x_write_mask(state, TDA1004X_AUTO, 2, 0); + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x0c, 2 << 2); + break; + + case GUARD_INTERVAL_1_4: + tda1004x_write_mask(state, TDA1004X_AUTO, 2, 0); + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x0c, 3 << 2); + break; + + case GUARD_INTERVAL_AUTO: + tda1004x_write_mask(state, TDA1004X_AUTO, 2, 2); + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x0c, 0 << 2); + break; + + default: + return -EINVAL; + } + + // set transmission mode + switch (fe_params->u.ofdm.transmission_mode) { + case TRANSMISSION_MODE_2K: + tda1004x_write_mask(state, TDA1004X_AUTO, 4, 0); + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x10, 0 << 4); + break; + + case TRANSMISSION_MODE_8K: + tda1004x_write_mask(state, TDA1004X_AUTO, 4, 0); + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x10, 1 << 4); + break; + + case TRANSMISSION_MODE_AUTO: + tda1004x_write_mask(state, TDA1004X_AUTO, 4, 4); + tda1004x_write_mask(state, TDA1004X_IN_CONF1, 0x10, 0); + break; + + default: + return -EINVAL; + } + + // start the lock + switch(state->demod_type) { + case TDA1004X_DEMOD_TDA10045: + tda1004x_write_mask(state, TDA1004X_CONFC4, 8, 8); + tda1004x_write_mask(state, TDA1004X_CONFC4, 8, 0); + msleep(10); + break; + + case TDA1004X_DEMOD_TDA10046: + tda1004x_write_mask(state, TDA1004X_AUTO, 0x40, 0x40); + msleep(10); + break; + } + + return 0; +} + +static int tda1004x_get_fe(struct dvb_frontend* fe, struct dvb_frontend_parameters *fe_params) +{ + struct tda1004x_state* state = fe->demodulator_priv; + dprintk("%s\n", __FUNCTION__); + + // inversion status + fe_params->inversion = INVERSION_OFF; + if (tda1004x_read_byte(state, TDA1004X_CONFC1) & 0x20) { + fe_params->inversion = INVERSION_ON; + } + if (state->config->invert) fe_params->inversion = fe_params->inversion ? INVERSION_OFF : INVERSION_ON; + + // bandwidth + switch(state->demod_type) { + case TDA1004X_DEMOD_TDA10045: + switch (tda1004x_read_byte(state, TDA10045H_WREF_LSB)) { + case 0x14: + fe_params->u.ofdm.bandwidth = BANDWIDTH_8_MHZ; + break; + case 0xdb: + fe_params->u.ofdm.bandwidth = BANDWIDTH_7_MHZ; + break; + case 0x4f: + fe_params->u.ofdm.bandwidth = BANDWIDTH_6_MHZ; + break; + } + break; + + case TDA1004X_DEMOD_TDA10046: + switch (tda1004x_read_byte(state, TDA10046H_TIME_WREF1)) { + case 0x60: + fe_params->u.ofdm.bandwidth = BANDWIDTH_8_MHZ; + break; + case 0x6e: + fe_params->u.ofdm.bandwidth = BANDWIDTH_7_MHZ; + break; + case 0x80: + fe_params->u.ofdm.bandwidth = BANDWIDTH_6_MHZ; + break; + } + break; + } + + // FEC + fe_params->u.ofdm.code_rate_HP = + tda1004x_decode_fec(tda1004x_read_byte(state, TDA1004X_OUT_CONF2) & 7); + fe_params->u.ofdm.code_rate_LP = + tda1004x_decode_fec((tda1004x_read_byte(state, TDA1004X_OUT_CONF2) >> 3) & 7); + + // constellation + switch (tda1004x_read_byte(state, TDA1004X_OUT_CONF1) & 3) { + case 0: + fe_params->u.ofdm.constellation = QPSK; + break; + case 1: + fe_params->u.ofdm.constellation = QAM_16; + break; + case 2: + fe_params->u.ofdm.constellation = QAM_64; + break; + } + + // transmission mode + fe_params->u.ofdm.transmission_mode = TRANSMISSION_MODE_2K; + if (tda1004x_read_byte(state, TDA1004X_OUT_CONF1) & 0x10) { + fe_params->u.ofdm.transmission_mode = TRANSMISSION_MODE_8K; + } + + // guard interval + switch ((tda1004x_read_byte(state, TDA1004X_OUT_CONF1) & 0x0c) >> 2) { + case 0: + fe_params->u.ofdm.guard_interval = GUARD_INTERVAL_1_32; + break; + case 1: + fe_params->u.ofdm.guard_interval = GUARD_INTERVAL_1_16; + break; + case 2: + fe_params->u.ofdm.guard_interval = GUARD_INTERVAL_1_8; + break; + case 3: + fe_params->u.ofdm.guard_interval = GUARD_INTERVAL_1_4; + break; + } + + // hierarchy + switch ((tda1004x_read_byte(state, TDA1004X_OUT_CONF1) & 0x60) >> 5) { + case 0: + fe_params->u.ofdm.hierarchy_information = HIERARCHY_NONE; + break; + case 1: + fe_params->u.ofdm.hierarchy_information = HIERARCHY_1; + break; + case 2: + fe_params->u.ofdm.hierarchy_information = HIERARCHY_2; + break; + case 3: + fe_params->u.ofdm.hierarchy_information = HIERARCHY_4; + break; + } + + return 0; +} + +static int tda1004x_read_status(struct dvb_frontend* fe, fe_status_t * fe_status) +{ + struct tda1004x_state* state = fe->demodulator_priv; + int status; + int cber; + int vber; + + dprintk("%s\n", __FUNCTION__); + + // read status + status = tda1004x_read_byte(state, TDA1004X_STATUS_CD); + if (status == -1) { + return -EIO; + } + + // decode + *fe_status = 0; + if (status & 4) *fe_status |= FE_HAS_SIGNAL; + if (status & 2) *fe_status |= FE_HAS_CARRIER; + if (status & 8) *fe_status |= FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; + + // if we don't already have VITERBI (i.e. not LOCKED), see if the viterbi + // is getting anything valid + if (!(*fe_status & FE_HAS_VITERBI)) { + // read the CBER + cber = tda1004x_read_byte(state, TDA1004X_CBER_LSB); + if (cber == -1) return -EIO; + status = tda1004x_read_byte(state, TDA1004X_CBER_MSB); + if (status == -1) return -EIO; + cber |= (status << 8); + tda1004x_read_byte(state, TDA1004X_CBER_RESET); + + if (cber != 65535) { + *fe_status |= FE_HAS_VITERBI; + } + } + + // if we DO have some valid VITERBI output, but don't already have SYNC + // bytes (i.e. not LOCKED), see if the RS decoder is getting anything valid. + if ((*fe_status & FE_HAS_VITERBI) && (!(*fe_status & FE_HAS_SYNC))) { + // read the VBER + vber = tda1004x_read_byte(state, TDA1004X_VBER_LSB); + if (vber == -1) return -EIO; + status = tda1004x_read_byte(state, TDA1004X_VBER_MID); + if (status == -1) return -EIO; + vber |= (status << 8); + status = tda1004x_read_byte(state, TDA1004X_VBER_MSB); + if (status == -1) return -EIO; + vber |= ((status << 16) & 0x0f); + tda1004x_read_byte(state, TDA1004X_CVBER_LUT); + + // if RS has passed some valid TS packets, then we must be + // getting some SYNC bytes + if (vber < 16632) { + *fe_status |= FE_HAS_SYNC; + } + } + + // success + dprintk("%s: fe_status=0x%x\n", __FUNCTION__, *fe_status); + return 0; +} + +static int tda1004x_read_signal_strength(struct dvb_frontend* fe, u16 * signal) +{ + struct tda1004x_state* state = fe->demodulator_priv; + int tmp; + int reg = 0; + + dprintk("%s\n", __FUNCTION__); + + // determine the register to use + switch(state->demod_type) { + case TDA1004X_DEMOD_TDA10045: + reg = TDA10045H_S_AGC; + break; + + case TDA1004X_DEMOD_TDA10046: + reg = TDA10046H_AGC_IF_LEVEL; + break; + } + + // read it + tmp = tda1004x_read_byte(state, reg); + if (tmp < 0) + return -EIO; + + *signal = (tmp << 8) | tmp; + dprintk("%s: signal=0x%x\n", __FUNCTION__, *signal); + return 0; +} + +static int tda1004x_read_snr(struct dvb_frontend* fe, u16 * snr) +{ + struct tda1004x_state* state = fe->demodulator_priv; + int tmp; + + dprintk("%s\n", __FUNCTION__); + + // read it + tmp = tda1004x_read_byte(state, TDA1004X_SNR); + if (tmp < 0) + return -EIO; + if (tmp) { + tmp = 255 - tmp; + } + + *snr = ((tmp << 8) | tmp); + dprintk("%s: snr=0x%x\n", __FUNCTION__, *snr); + return 0; +} + +static int tda1004x_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + struct tda1004x_state* state = fe->demodulator_priv; + int tmp; + int tmp2; + int counter; + + dprintk("%s\n", __FUNCTION__); + + // read the UCBLOCKS and reset + counter = 0; + tmp = tda1004x_read_byte(state, TDA1004X_UNCOR); + if (tmp < 0) + return -EIO; + tmp &= 0x7f; + while (counter++ < 5) { + tda1004x_write_mask(state, TDA1004X_UNCOR, 0x80, 0); + tda1004x_write_mask(state, TDA1004X_UNCOR, 0x80, 0); + tda1004x_write_mask(state, TDA1004X_UNCOR, 0x80, 0); + + tmp2 = tda1004x_read_byte(state, TDA1004X_UNCOR); + if (tmp2 < 0) + return -EIO; + tmp2 &= 0x7f; + if ((tmp2 < tmp) || (tmp2 == 0)) + break; + } + + if (tmp != 0x7f) { + *ucblocks = tmp; + } else { + *ucblocks = 0xffffffff; + } + dprintk("%s: ucblocks=0x%x\n", __FUNCTION__, *ucblocks); + return 0; +} + +static int tda1004x_read_ber(struct dvb_frontend* fe, u32* ber) +{ + struct tda1004x_state* state = fe->demodulator_priv; + int tmp; + + dprintk("%s\n", __FUNCTION__); + + // read it in + tmp = tda1004x_read_byte(state, TDA1004X_CBER_LSB); + if (tmp < 0) return -EIO; + *ber = tmp << 1; + tmp = tda1004x_read_byte(state, TDA1004X_CBER_MSB); + if (tmp < 0) return -EIO; + *ber |= (tmp << 9); + tda1004x_read_byte(state, TDA1004X_CBER_RESET); + + dprintk("%s: ber=0x%x\n", __FUNCTION__, *ber); + return 0; +} + +static int tda1004x_sleep(struct dvb_frontend* fe) +{ + struct tda1004x_state* state = fe->demodulator_priv; + + switch(state->demod_type) { + case TDA1004X_DEMOD_TDA10045: + tda1004x_write_mask(state, TDA1004X_CONFADC1, 0x10, 0x10); + break; + + case TDA1004X_DEMOD_TDA10046: + tda1004x_write_mask(state, TDA1004X_CONFC4, 1, 1); + break; + } + state->initialised = 0; + + return 0; +} + +static int tda1004x_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* fesettings) +{ + fesettings->min_delay_ms = 800; + fesettings->step_size = 166667; + fesettings->max_drift = 166667*2; + return 0; +} + +static void tda1004x_release(struct dvb_frontend* fe) +{ + struct tda1004x_state* state = (struct tda1004x_state*) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops tda10045_ops; + +struct dvb_frontend* tda10045_attach(const struct tda1004x_config* config, + struct i2c_adapter* i2c) +{ + struct tda1004x_state* state = NULL; + + /* allocate memory for the internal state */ + state = (struct tda1004x_state*) kmalloc(sizeof(struct tda1004x_state), GFP_KERNEL); + if (state == NULL) goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + memcpy(&state->ops, &tda10045_ops, sizeof(struct dvb_frontend_ops)); + state->initialised = 0; + state->demod_type = TDA1004X_DEMOD_TDA10045; + + /* check if the demod is there */ + if (tda1004x_read_byte(state, TDA1004X_CHIPID) != 0x25) goto error; + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops tda10046_ops; + +struct dvb_frontend* tda10046_attach(const struct tda1004x_config* config, + struct i2c_adapter* i2c) +{ + struct tda1004x_state* state = NULL; + + /* allocate memory for the internal state */ + state = (struct tda1004x_state*) kmalloc(sizeof(struct tda1004x_state), GFP_KERNEL); + if (state == NULL) goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + memcpy(&state->ops, &tda10046_ops, sizeof(struct dvb_frontend_ops)); + state->initialised = 0; + state->demod_type = TDA1004X_DEMOD_TDA10046; + + /* check if the demod is there */ + if (tda1004x_read_byte(state, TDA1004X_CHIPID) != 0x46) goto error; + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + if (state) kfree(state); + return NULL; +} + +static struct dvb_frontend_ops tda10045_ops = { + + .info = { + .name = "Philips TDA10045H DVB-T", + .type = FE_OFDM, + .frequency_min = 51000000, + .frequency_max = 858000000, + .frequency_stepsize = 166667, + .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_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO + }, + + .release = tda1004x_release, + + .init = tda10045_init, + .sleep = tda1004x_sleep, + + .set_frontend = tda1004x_set_fe, + .get_frontend = tda1004x_get_fe, + .get_tune_settings = tda1004x_get_tune_settings, + + .read_status = tda1004x_read_status, + .read_ber = tda1004x_read_ber, + .read_signal_strength = tda1004x_read_signal_strength, + .read_snr = tda1004x_read_snr, + .read_ucblocks = tda1004x_read_ucblocks, +}; + +static struct dvb_frontend_ops tda10046_ops = { + + .info = { + .name = "Philips TDA10046H DVB-T", + .type = FE_OFDM, + .frequency_min = 51000000, + .frequency_max = 858000000, + .frequency_stepsize = 166667, + .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_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO + }, + + .release = tda1004x_release, + + .init = tda10046_init, + .sleep = tda1004x_sleep, + + .set_frontend = tda1004x_set_fe, + .get_frontend = tda1004x_get_fe, + .get_tune_settings = tda1004x_get_tune_settings, + + .read_status = tda1004x_read_status, + .read_ber = tda1004x_read_ber, + .read_signal_strength = tda1004x_read_signal_strength, + .read_snr = tda1004x_read_snr, + .read_ucblocks = tda1004x_read_ucblocks, +}; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("Philips TDA10045H & TDA10046H DVB-T Demodulator"); +MODULE_AUTHOR("Andrew de Quincey & Robert Schlabbach"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(tda10045_attach); +EXPORT_SYMBOL(tda10046_attach); +EXPORT_SYMBOL(tda1004x_write_byte); diff --git a/drivers/media/dvb/frontends/tda1004x.h b/drivers/media/dvb/frontends/tda1004x.h new file mode 100644 index 000000000000..e452fc0bad11 --- /dev/null +++ b/drivers/media/dvb/frontends/tda1004x.h @@ -0,0 +1,56 @@ + /* + Driver for Philips tda1004xh OFDM Frontend + + (c) 2004 Andrew de Quincey + + 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 TDA1004X_H +#define TDA1004X_H + +#include <linux/dvb/frontend.h> +#include <linux/firmware.h> + +struct tda1004x_config +{ + /* the demodulator's i2c address */ + u8 demod_address; + + /* does the "inversion" need inverted? */ + u8 invert:1; + + /* Does the OCLK signal need inverted? */ + u8 invert_oclk:1; + + /* PLL maintenance */ + int (*pll_init)(struct dvb_frontend* fe); + int (*pll_set)(struct dvb_frontend* fe, struct dvb_frontend_parameters* params); + + /* request firmware for device */ + int (*request_firmware)(struct dvb_frontend* fe, const struct firmware **fw, char* name); +}; + +extern struct dvb_frontend* tda10045_attach(const struct tda1004x_config* config, + struct i2c_adapter* i2c); + +extern struct dvb_frontend* tda10046_attach(const struct tda1004x_config* config, + struct i2c_adapter* i2c); + +extern int tda1004x_write_byte(struct dvb_frontend* fe, int reg, int data); + +#endif // TDA1004X_H diff --git a/drivers/media/dvb/frontends/tda8083.c b/drivers/media/dvb/frontends/tda8083.c new file mode 100644 index 000000000000..da82e90d6d13 --- /dev/null +++ b/drivers/media/dvb/frontends/tda8083.c @@ -0,0 +1,456 @@ +/* + Driver for Philips TDA8083 based QPSK Demodulator + + Copyright (C) 2001 Convergence Integrated Media GmbH + + written by Ralph Metzler <ralph@convergence.de> + + adoption to the new DVB frontend API and diagnostic ioctl's + by Holger Waechtler <holger@convergence.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/string.h> +#include <linux/slab.h> +#include "dvb_frontend.h" +#include "tda8083.h" + + +struct tda8083_state { + struct i2c_adapter* i2c; + struct dvb_frontend_ops ops; + /* configuration settings */ + const struct tda8083_config* config; + struct dvb_frontend frontend; +}; + +static int debug; +#define dprintk(args...) \ + do { \ + if (debug) printk(KERN_DEBUG "tda8083: " args); \ + } while (0) + + +static u8 tda8083_init_tab [] = { + 0x04, 0x00, 0x4a, 0x79, 0x04, 0x00, 0xff, 0xea, + 0x48, 0x42, 0x79, 0x60, 0x70, 0x52, 0x9a, 0x10, + 0x0e, 0x10, 0xf2, 0xa7, 0x93, 0x0b, 0x05, 0xc8, + 0x9d, 0x00, 0x42, 0x80, 0x00, 0x60, 0x40, 0x00, + 0x00, 0x75, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + + +static int tda8083_writereg (struct tda8083_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 %02x, ret == %i)\n", + __FUNCTION__, reg, ret); + + return (ret != 1) ? -1 : 0; +} + +static int tda8083_readregs (struct tda8083_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 (reg %02x, ret == %i)\n", + __FUNCTION__, reg1, ret); + + return ret == 2 ? 0 : -1; +} + +static inline u8 tda8083_readreg (struct tda8083_state* state, u8 reg) +{ + u8 val; + + tda8083_readregs (state, reg, &val, 1); + + return val; +} + +static int tda8083_set_inversion (struct tda8083_state* state, fe_spectral_inversion_t inversion) +{ + /* XXX FIXME: implement other modes than FEC_AUTO */ + if (inversion == INVERSION_AUTO) + return 0; + + return -EINVAL; +} + +static int tda8083_set_fec (struct tda8083_state* state, fe_code_rate_t fec) +{ + if (fec == FEC_AUTO) + return tda8083_writereg (state, 0x07, 0xff); + + if (fec >= FEC_1_2 && fec <= FEC_8_9) + return tda8083_writereg (state, 0x07, 1 << (FEC_8_9 - fec)); + + return -EINVAL; +} + +static fe_code_rate_t tda8083_get_fec (struct tda8083_state* state) +{ + u8 index; + static fe_code_rate_t fec_tab [] = { FEC_8_9, FEC_1_2, FEC_2_3, FEC_3_4, + FEC_4_5, FEC_5_6, FEC_6_7, FEC_7_8 }; + + index = tda8083_readreg(state, 0x0e) & 0x07; + + return fec_tab [index]; +} + +static int tda8083_set_symbolrate (struct tda8083_state* state, u32 srate) +{ + u32 ratio; + u32 tmp; + u8 filter; + + if (srate > 32000000) + srate = 32000000; + if (srate < 500000) + srate = 500000; + + filter = 0; + if (srate < 24000000) + filter = 2; + if (srate < 16000000) + filter = 3; + + tmp = 31250 << 16; + ratio = tmp / srate; + + tmp = (tmp % srate) << 8; + ratio = (ratio << 8) + tmp / srate; + + tmp = (tmp % srate) << 8; + ratio = (ratio << 8) + tmp / srate; + + dprintk("tda8083: ratio == %08x\n", (unsigned int) ratio); + + tda8083_writereg (state, 0x05, filter); + tda8083_writereg (state, 0x02, (ratio >> 16) & 0xff); + tda8083_writereg (state, 0x03, (ratio >> 8) & 0xff); + tda8083_writereg (state, 0x04, (ratio ) & 0xff); + + tda8083_writereg (state, 0x00, 0x3c); + tda8083_writereg (state, 0x00, 0x04); + + return 1; +} + +static void tda8083_wait_diseqc_fifo (struct tda8083_state* state, int timeout) +{ + unsigned long start = jiffies; + + while (jiffies - start < timeout && + !(tda8083_readreg(state, 0x02) & 0x80)) + { + msleep(50); + }; +} + +static int tda8083_set_tone (struct tda8083_state* state, fe_sec_tone_mode_t tone) +{ + tda8083_writereg (state, 0x26, 0xf1); + + switch (tone) { + case SEC_TONE_OFF: + return tda8083_writereg (state, 0x29, 0x00); + case SEC_TONE_ON: + return tda8083_writereg (state, 0x29, 0x80); + default: + return -EINVAL; + }; +} + +static int tda8083_set_voltage (struct tda8083_state* state, fe_sec_voltage_t voltage) +{ + switch (voltage) { + case SEC_VOLTAGE_13: + return tda8083_writereg (state, 0x20, 0x00); + case SEC_VOLTAGE_18: + return tda8083_writereg (state, 0x20, 0x11); + default: + return -EINVAL; + }; +} + +static int tda8083_send_diseqc_burst (struct tda8083_state* state, fe_sec_mini_cmd_t burst) +{ + switch (burst) { + case SEC_MINI_A: + tda8083_writereg (state, 0x29, (5 << 2)); /* send burst A */ + break; + case SEC_MINI_B: + tda8083_writereg (state, 0x29, (7 << 2)); /* send B */ + break; + default: + return -EINVAL; + }; + + tda8083_wait_diseqc_fifo (state, 100); + + return 0; +} + +static int tda8083_send_diseqc_msg (struct dvb_frontend* fe, + struct dvb_diseqc_master_cmd *m) +{ + struct tda8083_state* state = (struct tda8083_state*) fe->demodulator_priv; + int i; + + tda8083_writereg (state, 0x29, (m->msg_len - 3) | (1 << 2)); /* enable */ + + for (i=0; i<m->msg_len; i++) + tda8083_writereg (state, 0x23 + i, m->msg[i]); + + tda8083_writereg (state, 0x29, (m->msg_len - 3) | (3 << 2)); /* send!! */ + + tda8083_wait_diseqc_fifo (state, 100); + + return 0; +} + +static int tda8083_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct tda8083_state* state = (struct tda8083_state*) fe->demodulator_priv; + + u8 signal = ~tda8083_readreg (state, 0x01); + u8 sync = tda8083_readreg (state, 0x02); + + *status = 0; + + if (signal > 10) + *status |= FE_HAS_SIGNAL; + + if (sync & 0x01) + *status |= FE_HAS_CARRIER; + + if (sync & 0x02) + *status |= FE_HAS_VITERBI; + + if (sync & 0x10) + *status |= FE_HAS_SYNC; + + if ((sync & 0x1f) == 0x1f) + *status |= FE_HAS_LOCK; + + return 0; +} + +static int tda8083_read_signal_strength(struct dvb_frontend* fe, u16* strength) +{ + struct tda8083_state* state = (struct tda8083_state*) fe->demodulator_priv; + + u8 signal = ~tda8083_readreg (state, 0x01); + *strength = (signal << 8) | signal; + + return 0; +} + +static int tda8083_read_snr(struct dvb_frontend* fe, u16* snr) +{ + struct tda8083_state* state = (struct tda8083_state*) fe->demodulator_priv; + + u8 _snr = tda8083_readreg (state, 0x08); + *snr = (_snr << 8) | _snr; + + return 0; +} + +static int tda8083_set_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + struct tda8083_state* state = (struct tda8083_state*) fe->demodulator_priv; + + state->config->pll_set(fe, p); + tda8083_set_inversion (state, p->inversion); + tda8083_set_fec (state, p->u.qpsk.fec_inner); + tda8083_set_symbolrate (state, p->u.qpsk.symbol_rate); + + tda8083_writereg (state, 0x00, 0x3c); + tda8083_writereg (state, 0x00, 0x04); + + return 0; +} + +static int tda8083_get_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + struct tda8083_state* state = (struct tda8083_state*) fe->demodulator_priv; + + /* FIXME: get symbolrate & frequency offset...*/ + /*p->frequency = ???;*/ + p->inversion = (tda8083_readreg (state, 0x0e) & 0x80) ? + INVERSION_ON : INVERSION_OFF; + p->u.qpsk.fec_inner = tda8083_get_fec (state); + /*p->u.qpsk.symbol_rate = tda8083_get_symbolrate (state);*/ + + return 0; +} + +static int tda8083_sleep(struct dvb_frontend* fe) +{ + struct tda8083_state* state = (struct tda8083_state*) fe->demodulator_priv; + + tda8083_writereg (state, 0x00, 0x02); + return 0; +} + +static int tda8083_init(struct dvb_frontend* fe) +{ + struct tda8083_state* state = (struct tda8083_state*) fe->demodulator_priv; + int i; + + for (i=0; i<44; i++) + tda8083_writereg (state, i, tda8083_init_tab[i]); + + if (state->config->pll_init) state->config->pll_init(fe); + + tda8083_writereg (state, 0x00, 0x3c); + tda8083_writereg (state, 0x00, 0x04); + + return 0; +} + +static int tda8083_diseqc_send_burst(struct dvb_frontend* fe, fe_sec_mini_cmd_t burst) +{ + struct tda8083_state* state = (struct tda8083_state*) fe->demodulator_priv; + + tda8083_send_diseqc_burst (state, burst); + tda8083_writereg (state, 0x00, 0x3c); + tda8083_writereg (state, 0x00, 0x04); + + return 0; +} + +static int tda8083_diseqc_set_tone(struct dvb_frontend* fe, fe_sec_tone_mode_t tone) +{ + struct tda8083_state* state = (struct tda8083_state*) fe->demodulator_priv; + + tda8083_set_tone (state, tone); + tda8083_writereg (state, 0x00, 0x3c); + tda8083_writereg (state, 0x00, 0x04); + + return 0; +} + +static int tda8083_diseqc_set_voltage(struct dvb_frontend* fe, fe_sec_voltage_t voltage) +{ + struct tda8083_state* state = (struct tda8083_state*) fe->demodulator_priv; + + tda8083_set_voltage (state, voltage); + tda8083_writereg (state, 0x00, 0x3c); + tda8083_writereg (state, 0x00, 0x04); + + return 0; +} + +static void tda8083_release(struct dvb_frontend* fe) +{ + struct tda8083_state* state = (struct tda8083_state*) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops tda8083_ops; + +struct dvb_frontend* tda8083_attach(const struct tda8083_config* config, + struct i2c_adapter* i2c) +{ + struct tda8083_state* state = NULL; + + /* allocate memory for the internal state */ + state = (struct tda8083_state*) kmalloc(sizeof(struct tda8083_state), GFP_KERNEL); + if (state == NULL) goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + memcpy(&state->ops, &tda8083_ops, sizeof(struct dvb_frontend_ops)); + + /* check if the demod is there */ + if ((tda8083_readreg(state, 0x00)) != 0x05) goto error; + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops tda8083_ops = { + + .info = { + .name = "Philips TDA8083 DVB-S", + .type = FE_QPSK, + .frequency_min = 950000, /* FIXME: guessed! */ + .frequency_max = 1400000, /* FIXME: guessed! */ + .frequency_stepsize = 125, /* kHz for QPSK frontends */ + /* .frequency_tolerance = ???,*/ + .symbol_rate_min = 1000000, /* FIXME: guessed! */ + .symbol_rate_max = 45000000, /* FIXME: guessed! */ + /* .symbol_rate_tolerance = ???,*/ + .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_8_9 | FE_CAN_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_MUTE_TS + }, + + .release = tda8083_release, + + .init = tda8083_init, + .sleep = tda8083_sleep, + + .set_frontend = tda8083_set_frontend, + .get_frontend = tda8083_get_frontend, + + .read_status = tda8083_read_status, + .read_signal_strength = tda8083_read_signal_strength, + .read_snr = tda8083_read_snr, + + .diseqc_send_master_cmd = tda8083_send_diseqc_msg, + .diseqc_send_burst = tda8083_diseqc_send_burst, + .set_tone = tda8083_diseqc_set_tone, + .set_voltage = tda8083_diseqc_set_voltage, +}; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("Philips TDA8083 DVB-S Demodulator"); +MODULE_AUTHOR("Ralph Metzler, Holger Waechtler"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(tda8083_attach); diff --git a/drivers/media/dvb/frontends/tda8083.h b/drivers/media/dvb/frontends/tda8083.h new file mode 100644 index 000000000000..466663307bf1 --- /dev/null +++ b/drivers/media/dvb/frontends/tda8083.h @@ -0,0 +1,45 @@ +/* + Driver for Grundig 29504-491, a Philips TDA8083 based QPSK Frontend + + Copyright (C) 2001 Convergence Integrated Media GmbH + + written by Ralph Metzler <ralph@convergence.de> + + adoption to the new DVB frontend API and diagnostic ioctl's + by Holger Waechtler <holger@convergence.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef TDA8083_H +#define TDA8083_H + +#include <linux/dvb/frontend.h> + +struct tda8083_config +{ + /* the demodulator's i2c address */ + u8 demod_address; + + /* PLL maintenance */ + int (*pll_init)(struct dvb_frontend* fe); + int (*pll_set)(struct dvb_frontend* fe, struct dvb_frontend_parameters* params); +}; + +extern struct dvb_frontend* tda8083_attach(const struct tda8083_config* config, + struct i2c_adapter* i2c); + +#endif // TDA8083_H diff --git a/drivers/media/dvb/frontends/tda80xx.c b/drivers/media/dvb/frontends/tda80xx.c new file mode 100644 index 000000000000..c99632114283 --- /dev/null +++ b/drivers/media/dvb/frontends/tda80xx.c @@ -0,0 +1,734 @@ +/* + * tda80xx.c + * + * Philips TDA8044 / TDA8083 QPSK demodulator driver + * + * Copyright (C) 2001 Felix Domke <tmbinc@elitedvb.net> + * Copyright (C) 2002-2004 Andreas Oberritter <obi@linuxtv.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/config.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/threads.h> +#include <linux/interrupt.h> +#include <asm/irq.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <asm/div64.h> + +#include "dvb_frontend.h" +#include "tda80xx.h" + +enum { + ID_TDA8044 = 0x04, + ID_TDA8083 = 0x05, +}; + + +struct tda80xx_state { + + struct i2c_adapter* i2c; + + struct dvb_frontend_ops ops; + + /* configuration settings */ + const struct tda80xx_config* config; + + struct dvb_frontend frontend; + + u32 clk; + int afc_loop; + struct work_struct worklet; + fe_code_rate_t code_rate; + fe_spectral_inversion_t spectral_inversion; + fe_status_t status; + u8 id; +}; + +static int debug = 1; +#define dprintk if (debug) printk + +static u8 tda8044_inittab_pre[] = { + 0x02, 0x00, 0x6f, 0xb5, 0x86, 0x22, 0x00, 0xea, + 0x30, 0x42, 0x98, 0x68, 0x70, 0x42, 0x99, 0x58, + 0x95, 0x10, 0xf5, 0xe7, 0x93, 0x0b, 0x15, 0x68, + 0x9a, 0x90, 0x61, 0x80, 0x00, 0xe0, 0x40, 0x00, + 0x0f, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 +}; + +static u8 tda8044_inittab_post[] = { + 0x04, 0x00, 0x6f, 0xb5, 0x86, 0x22, 0x00, 0xea, + 0x30, 0x42, 0x98, 0x68, 0x70, 0x42, 0x99, 0x50, + 0x95, 0x10, 0xf5, 0xe7, 0x93, 0x0b, 0x15, 0x68, + 0x9a, 0x90, 0x61, 0x80, 0x00, 0xe0, 0x40, 0x6c, + 0x0f, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 +}; + +static u8 tda8083_inittab[] = { + 0x04, 0x00, 0x4a, 0x79, 0x04, 0x00, 0xff, 0xea, + 0x48, 0x42, 0x79, 0x60, 0x70, 0x52, 0x9a, 0x10, + 0x0e, 0x10, 0xf2, 0xa7, 0x93, 0x0b, 0x05, 0xc8, + 0x9d, 0x00, 0x42, 0x80, 0x00, 0x60, 0x40, 0x00, + 0x00, 0x75, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + +static __inline__ u32 tda80xx_div(u32 a, u32 b) +{ + return (a + (b / 2)) / b; +} + +static __inline__ u32 tda80xx_gcd(u32 a, u32 b) +{ + u32 r; + + while ((r = a % b)) { + a = b; + b = r; + } + + return b; +} + +static int tda80xx_read(struct tda80xx_state* state, u8 reg, u8 *buf, u8 len) +{ + int ret; + struct i2c_msg msg[] = { { .addr = state->config->demod_address, .flags = 0, .buf = ®, .len = 1 }, + { .addr = state->config->demod_address, .flags = I2C_M_RD, .buf = buf, .len = len } }; + + ret = i2c_transfer(state->i2c, msg, 2); + + if (ret != 2) + dprintk("%s: readreg error (reg %02x, ret == %i)\n", + __FUNCTION__, reg, ret); + + mdelay(10); + + return (ret == 2) ? 0 : -EREMOTEIO; +} + +static int tda80xx_write(struct tda80xx_state* state, u8 reg, const u8 *buf, u8 len) +{ + int ret; + u8 wbuf[len + 1]; + struct i2c_msg msg = { .addr = state->config->demod_address, .flags = 0, .buf = wbuf, .len = len + 1 }; + + wbuf[0] = reg; + memcpy(&wbuf[1], buf, len); + + ret = i2c_transfer(state->i2c, &msg, 1); + + if (ret != 1) + dprintk("%s: i2c xfer error (ret == %i)\n", __FUNCTION__, ret); + + mdelay(10); + + return (ret == 1) ? 0 : -EREMOTEIO; +} + +static __inline__ u8 tda80xx_readreg(struct tda80xx_state* state, u8 reg) +{ + u8 val; + + tda80xx_read(state, reg, &val, 1); + + return val; +} + +static __inline__ int tda80xx_writereg(struct tda80xx_state* state, u8 reg, u8 data) +{ + return tda80xx_write(state, reg, &data, 1); +} + +static int tda80xx_set_parameters(struct tda80xx_state* state, + fe_spectral_inversion_t inversion, + u32 symbol_rate, + fe_code_rate_t fec_inner) +{ + u8 buf[15]; + u64 ratio; + u32 clk; + u32 k; + u32 sr = symbol_rate; + u32 gcd; + u8 scd; + + if (symbol_rate > (state->clk * 3) / 16) + scd = 0; + else if (symbol_rate > (state->clk * 3) / 32) + scd = 1; + else if (symbol_rate > (state->clk * 3) / 64) + scd = 2; + else + scd = 3; + + clk = scd ? (state->clk / (scd * 2)) : state->clk; + + /* + * Viterbi decoder: + * Differential decoding off + * Spectral inversion unknown + * QPSK modulation + */ + if (inversion == INVERSION_ON) + buf[0] = 0x60; + else if (inversion == INVERSION_OFF) + buf[0] = 0x20; + else + buf[0] = 0x00; + + /* + * CLK ratio: + * system clock frequency is up to 64 or 96 MHz + * + * formula: + * r = k * clk / symbol_rate + * + * k: 2^21 for caa 0..3, + * 2^20 for caa 4..5, + * 2^19 for caa 6..7 + */ + if (symbol_rate <= (clk * 3) / 32) + k = (1 << 19); + else if (symbol_rate <= (clk * 3) / 16) + k = (1 << 20); + else + k = (1 << 21); + + gcd = tda80xx_gcd(clk, sr); + clk /= gcd; + sr /= gcd; + + gcd = tda80xx_gcd(k, sr); + k /= gcd; + sr /= gcd; + + ratio = (u64)k * (u64)clk; + do_div(ratio, sr); + + buf[1] = ratio >> 16; + buf[2] = ratio >> 8; + buf[3] = ratio; + + /* nyquist filter roll-off factor 35% */ + buf[4] = 0x20; + + clk = scd ? (state->clk / (scd * 2)) : state->clk; + + /* Anti Alias Filter */ + if (symbol_rate < (clk * 3) / 64) + printk("tda80xx: unsupported symbol rate: %u\n", symbol_rate); + else if (symbol_rate <= clk / 16) + buf[4] |= 0x07; + else if (symbol_rate <= (clk * 3) / 32) + buf[4] |= 0x06; + else if (symbol_rate <= clk / 8) + buf[4] |= 0x05; + else if (symbol_rate <= (clk * 3) / 16) + buf[4] |= 0x04; + else if (symbol_rate <= clk / 4) + buf[4] |= 0x03; + else if (symbol_rate <= (clk * 3) / 8) + buf[4] |= 0x02; + else if (symbol_rate <= clk / 2) + buf[4] |= 0x01; + else + buf[4] |= 0x00; + + /* Sigma Delta converter */ + buf[5] = 0x00; + + /* FEC: Possible puncturing rates */ + if (fec_inner == FEC_NONE) + buf[6] = 0x00; + else if ((fec_inner >= FEC_1_2) && (fec_inner <= FEC_8_9)) + buf[6] = (1 << (8 - fec_inner)); + else if (fec_inner == FEC_AUTO) + buf[6] = 0xff; + else + return -EINVAL; + + /* carrier lock detector threshold value */ + buf[7] = 0x30; + /* AFC1: proportional part settings */ + buf[8] = 0x42; + /* AFC1: integral part settings */ + buf[9] = 0x98; + /* PD: Leaky integrator SCPC mode */ + buf[10] = 0x28; + /* AFC2, AFC1 controls */ + buf[11] = 0x30; + /* PD: proportional part settings */ + buf[12] = 0x42; + /* PD: integral part settings */ + buf[13] = 0x99; + /* AGC */ + buf[14] = 0x50 | scd; + + printk("symbol_rate=%u clk=%u\n", symbol_rate, clk); + + return tda80xx_write(state, 0x01, buf, sizeof(buf)); +} + +static int tda80xx_set_clk(struct tda80xx_state* state) +{ + u8 buf[2]; + + /* CLK proportional part */ + buf[0] = (0x06 << 5) | 0x08; /* CMP[2:0], CSP[4:0] */ + /* CLK integral part */ + buf[1] = (0x04 << 5) | 0x1a; /* CMI[2:0], CSI[4:0] */ + + return tda80xx_write(state, 0x17, buf, sizeof(buf)); +} + +#if 0 +static int tda80xx_set_scpc_freq_offset(struct tda80xx_state* state) +{ + /* a constant value is nonsense here imho */ + return tda80xx_writereg(state, 0x22, 0xf9); +} +#endif + +static int tda80xx_close_loop(struct tda80xx_state* state) +{ + u8 buf[2]; + + /* PD: Loop closed, LD: lock detect enable, SCPC: Sweep mode - AFC1 loop closed */ + buf[0] = 0x68; + /* AFC1: Loop closed, CAR Feedback: 8192 */ + buf[1] = 0x70; + + return tda80xx_write(state, 0x0b, buf, sizeof(buf)); +} + +static irqreturn_t tda80xx_irq(int irq, void *priv, struct pt_regs *pt) +{ + schedule_work(priv); + + return IRQ_HANDLED; +} + +static void tda80xx_read_status_int(struct tda80xx_state* state) +{ + u8 val; + + static const fe_spectral_inversion_t inv_tab[] = { + INVERSION_OFF, INVERSION_ON + }; + + static const fe_code_rate_t fec_tab[] = { + FEC_8_9, FEC_1_2, FEC_2_3, FEC_3_4, + FEC_4_5, FEC_5_6, FEC_6_7, FEC_7_8, + }; + + val = tda80xx_readreg(state, 0x02); + + state->status = 0; + + if (val & 0x01) /* demodulator lock */ + state->status |= FE_HAS_SIGNAL; + if (val & 0x02) /* clock recovery lock */ + state->status |= FE_HAS_CARRIER; + if (val & 0x04) /* viterbi lock */ + state->status |= FE_HAS_VITERBI; + if (val & 0x08) /* deinterleaver lock (packet sync) */ + state->status |= FE_HAS_SYNC; + if (val & 0x10) /* derandomizer lock (frame sync) */ + state->status |= FE_HAS_LOCK; + if (val & 0x20) /* frontend can not lock */ + state->status |= FE_TIMEDOUT; + + if ((state->status & (FE_HAS_CARRIER)) && (state->afc_loop)) { + printk("tda80xx: closing loop\n"); + tda80xx_close_loop(state); + state->afc_loop = 0; + } + + if (state->status & (FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK)) { + val = tda80xx_readreg(state, 0x0e); + state->code_rate = fec_tab[val & 0x07]; + if (state->status & (FE_HAS_SYNC | FE_HAS_LOCK)) + state->spectral_inversion = inv_tab[(val >> 7) & 0x01]; + else + state->spectral_inversion = INVERSION_AUTO; + } + else { + state->code_rate = FEC_AUTO; + } +} + +static void tda80xx_worklet(void *priv) +{ + struct tda80xx_state *state = priv; + + tda80xx_writereg(state, 0x00, 0x04); + enable_irq(state->config->irq); + + tda80xx_read_status_int(state); +} + +static void tda80xx_wait_diseqc_fifo(struct tda80xx_state* state) +{ + size_t i; + + for (i = 0; i < 100; i++) { + if (tda80xx_readreg(state, 0x02) & 0x80) + break; + msleep(10); + } +} + +static int tda8044_init(struct dvb_frontend* fe) +{ + struct tda80xx_state* state = (struct tda80xx_state*) fe->demodulator_priv; + int ret; + + /* + * this function is a mess... + */ + + if ((ret = tda80xx_write(state, 0x00, tda8044_inittab_pre, sizeof(tda8044_inittab_pre)))) + return ret; + + tda80xx_writereg(state, 0x0f, 0x50); +#if 1 + tda80xx_writereg(state, 0x20, 0x8F); /* FIXME */ + tda80xx_writereg(state, 0x20, state->config->volt18setting); /* FIXME */ + //tda80xx_writereg(state, 0x00, 0x04); + tda80xx_writereg(state, 0x00, 0x0C); +#endif + //tda80xx_writereg(state, 0x00, 0x08); /* Reset AFC1 loop filter */ + + tda80xx_write(state, 0x00, tda8044_inittab_post, sizeof(tda8044_inittab_post)); + + if (state->config->pll_init) { + tda80xx_writereg(state, 0x1c, 0x80); + state->config->pll_init(fe); + tda80xx_writereg(state, 0x1c, 0x00); + } + + return 0; +} + +static int tda8083_init(struct dvb_frontend* fe) +{ + struct tda80xx_state* state = (struct tda80xx_state*) fe->demodulator_priv; + + tda80xx_write(state, 0x00, tda8083_inittab, sizeof(tda8083_inittab)); + + if (state->config->pll_init) { + tda80xx_writereg(state, 0x1c, 0x80); + state->config->pll_init(fe); + tda80xx_writereg(state, 0x1c, 0x00); + } + + return 0; +} + +static int tda80xx_set_voltage(struct dvb_frontend* fe, fe_sec_voltage_t voltage) +{ + struct tda80xx_state* state = (struct tda80xx_state*) fe->demodulator_priv; + + switch (voltage) { + case SEC_VOLTAGE_13: + return tda80xx_writereg(state, 0x20, state->config->volt13setting); + case SEC_VOLTAGE_18: + return tda80xx_writereg(state, 0x20, state->config->volt18setting); + case SEC_VOLTAGE_OFF: + return tda80xx_writereg(state, 0x20, 0); + default: + return -EINVAL; + } +} + +static int tda80xx_set_tone(struct dvb_frontend* fe, fe_sec_tone_mode_t tone) +{ + struct tda80xx_state* state = (struct tda80xx_state*) fe->demodulator_priv; + + switch (tone) { + case SEC_TONE_OFF: + return tda80xx_writereg(state, 0x29, 0x00); + case SEC_TONE_ON: + return tda80xx_writereg(state, 0x29, 0x80); + default: + return -EINVAL; + } +} + +static int tda80xx_send_diseqc_msg(struct dvb_frontend* fe, struct dvb_diseqc_master_cmd *cmd) +{ + struct tda80xx_state* state = (struct tda80xx_state*) fe->demodulator_priv; + + if (cmd->msg_len > 6) + return -EINVAL; + + tda80xx_writereg(state, 0x29, 0x08 | (cmd->msg_len - 3)); + tda80xx_write(state, 0x23, cmd->msg, cmd->msg_len); + tda80xx_writereg(state, 0x29, 0x0c | (cmd->msg_len - 3)); + tda80xx_wait_diseqc_fifo(state); + + return 0; +} + +static int tda80xx_send_diseqc_burst(struct dvb_frontend* fe, fe_sec_mini_cmd_t cmd) +{ + struct tda80xx_state* state = (struct tda80xx_state*) fe->demodulator_priv; + + switch (cmd) { + case SEC_MINI_A: + tda80xx_writereg(state, 0x29, 0x14); + break; + case SEC_MINI_B: + tda80xx_writereg(state, 0x29, 0x1c); + break; + default: + return -EINVAL; + } + + tda80xx_wait_diseqc_fifo(state); + + return 0; +} + +static int tda80xx_sleep(struct dvb_frontend* fe) +{ + struct tda80xx_state* state = (struct tda80xx_state*) fe->demodulator_priv; + + tda80xx_writereg(state, 0x00, 0x02); /* enter standby */ + + return 0; +} + +static int tda80xx_set_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + struct tda80xx_state* state = (struct tda80xx_state*) fe->demodulator_priv; + + tda80xx_writereg(state, 0x1c, 0x80); + state->config->pll_set(fe, p); + tda80xx_writereg(state, 0x1c, 0x00); + + tda80xx_set_parameters(state, p->inversion, p->u.qpsk.symbol_rate, p->u.qpsk.fec_inner); + tda80xx_set_clk(state); + //tda80xx_set_scpc_freq_offset(state); + state->afc_loop = 1; + + return 0; +} + +static int tda80xx_get_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + struct tda80xx_state* state = (struct tda80xx_state*) fe->demodulator_priv; + + if (!state->config->irq) + tda80xx_read_status_int(state); + + p->inversion = state->spectral_inversion; + p->u.qpsk.fec_inner = state->code_rate; + + return 0; +} + +static int tda80xx_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct tda80xx_state* state = (struct tda80xx_state*) fe->demodulator_priv; + + if (!state->config->irq) + tda80xx_read_status_int(state); + *status = state->status; + + return 0; +} + +static int tda80xx_read_ber(struct dvb_frontend* fe, u32* ber) +{ + struct tda80xx_state* state = (struct tda80xx_state*) fe->demodulator_priv; + int ret; + u8 buf[3]; + + if ((ret = tda80xx_read(state, 0x0b, buf, sizeof(buf)))) + return ret; + + *ber = ((buf[0] & 0x1f) << 16) | (buf[1] << 8) | buf[2]; + + return 0; +} + +static int tda80xx_read_signal_strength(struct dvb_frontend* fe, u16* strength) +{ + struct tda80xx_state* state = (struct tda80xx_state*) fe->demodulator_priv; + + u8 gain = ~tda80xx_readreg(state, 0x01); + *strength = (gain << 8) | gain; + + return 0; +} + +static int tda80xx_read_snr(struct dvb_frontend* fe, u16* snr) +{ + struct tda80xx_state* state = (struct tda80xx_state*) fe->demodulator_priv; + + u8 quality = tda80xx_readreg(state, 0x08); + *snr = (quality << 8) | quality; + + return 0; +} + +static int tda80xx_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + struct tda80xx_state* state = (struct tda80xx_state*) fe->demodulator_priv; + + *ucblocks = tda80xx_readreg(state, 0x0f); + if (*ucblocks == 0xff) + *ucblocks = 0xffffffff; + + return 0; +} + +static int tda80xx_init(struct dvb_frontend* fe) +{ + struct tda80xx_state* state = (struct tda80xx_state*) fe->demodulator_priv; + + switch(state->id) { + case ID_TDA8044: + return tda8044_init(fe); + + case ID_TDA8083: + return tda8083_init(fe); + } + return 0; +} + +static void tda80xx_release(struct dvb_frontend* fe) +{ + struct tda80xx_state* state = (struct tda80xx_state*) fe->demodulator_priv; + + if (state->config->irq) + free_irq(state->config->irq, &state->worklet); + + kfree(state); +} + +static struct dvb_frontend_ops tda80xx_ops; + +struct dvb_frontend* tda80xx_attach(const struct tda80xx_config* config, + struct i2c_adapter* i2c) +{ + struct tda80xx_state* state = NULL; + int ret; + + /* allocate memory for the internal state */ + state = (struct tda80xx_state*) kmalloc(sizeof(struct tda80xx_state), GFP_KERNEL); + if (state == NULL) goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + memcpy(&state->ops, &tda80xx_ops, sizeof(struct dvb_frontend_ops)); + state->spectral_inversion = INVERSION_AUTO; + state->code_rate = FEC_AUTO; + state->status = 0; + state->afc_loop = 0; + + /* check if the demod is there */ + if (tda80xx_writereg(state, 0x89, 0x00) < 0) goto error; + state->id = tda80xx_readreg(state, 0x00); + + switch (state->id) { + case ID_TDA8044: + state->clk = 96000000; + printk("tda80xx: Detected tda8044\n"); + break; + + case ID_TDA8083: + state->clk = 64000000; + printk("tda80xx: Detected tda8083\n"); + break; + + default: + goto error; + } + + /* setup IRQ */ + if (state->config->irq) { + INIT_WORK(&state->worklet, tda80xx_worklet, state); + if ((ret = request_irq(state->config->irq, tda80xx_irq, SA_ONESHOT, "tda80xx", &state->worklet)) < 0) { + printk(KERN_ERR "tda80xx: request_irq failed (%d)\n", ret); + goto error; + } + } + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops tda80xx_ops = { + + .info = { + .name = "Philips TDA80xx DVB-S", + .type = FE_QPSK, + .frequency_min = 500000, + .frequency_max = 2700000, + .frequency_stepsize = 125, + .symbol_rate_min = 4500000, + .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_8_9 | FE_CAN_FEC_AUTO | + FE_CAN_QPSK | + FE_CAN_MUTE_TS + }, + + .release = tda80xx_release, + + .init = tda80xx_init, + .sleep = tda80xx_sleep, + + .set_frontend = tda80xx_set_frontend, + .get_frontend = tda80xx_get_frontend, + + .read_status = tda80xx_read_status, + .read_ber = tda80xx_read_ber, + .read_signal_strength = tda80xx_read_signal_strength, + .read_snr = tda80xx_read_snr, + .read_ucblocks = tda80xx_read_ucblocks, + + .diseqc_send_master_cmd = tda80xx_send_diseqc_msg, + .diseqc_send_burst = tda80xx_send_diseqc_burst, + .set_tone = tda80xx_set_tone, + .set_voltage = tda80xx_set_voltage, +}; + +module_param(debug, int, 0644); + +MODULE_DESCRIPTION("Philips TDA8044 / TDA8083 DVB-S Demodulator driver"); +MODULE_AUTHOR("Felix Domke, Andreas Oberritter"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(tda80xx_attach); diff --git a/drivers/media/dvb/frontends/tda80xx.h b/drivers/media/dvb/frontends/tda80xx.h new file mode 100644 index 000000000000..cd639a0aad55 --- /dev/null +++ b/drivers/media/dvb/frontends/tda80xx.h @@ -0,0 +1,51 @@ +/* + * tda80xx.c + * + * Philips TDA8044 / TDA8083 QPSK demodulator driver + * + * Copyright (C) 2001 Felix Domke <tmbinc@elitedvb.net> + * Copyright (C) 2002-2004 Andreas Oberritter <obi@linuxtv.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef TDA80XX_H +#define TDA80XX_H + +#include <linux/dvb/frontend.h> + +struct tda80xx_config +{ + /* the demodulator's i2c address */ + u8 demod_address; + + /* IRQ to use (0=>no IRQ used) */ + u32 irq; + + /* Register setting to use for 13v */ + u8 volt13setting; + + /* Register setting to use for 18v */ + u8 volt18setting; + + /* PLL maintenance */ + int (*pll_init)(struct dvb_frontend* fe); + int (*pll_set)(struct dvb_frontend* fe, struct dvb_frontend_parameters* params); +}; + +extern struct dvb_frontend* tda80xx_attach(const struct tda80xx_config* config, + struct i2c_adapter* i2c); + +#endif // TDA80XX_H diff --git a/drivers/media/dvb/frontends/ves1820.c b/drivers/media/dvb/frontends/ves1820.c new file mode 100644 index 000000000000..9c0d23e1d9e5 --- /dev/null +++ b/drivers/media/dvb/frontends/ves1820.c @@ -0,0 +1,450 @@ +/* + VES1820 - Single Chip Cable Channel Receiver driver module + + Copyright (C) 1999 Convergence Integrated Media GmbH <ralph@convergence.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/config.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <asm/div64.h> + +#include "dvb_frontend.h" +#include "ves1820.h" + + + +struct ves1820_state { + struct i2c_adapter* i2c; + struct dvb_frontend_ops ops; + /* configuration settings */ + const struct ves1820_config* config; + struct dvb_frontend frontend; + + /* private demodulator data */ + u8 reg0; + u8 pwm; +}; + + +static int verbose; + +static u8 ves1820_inittab[] = { + 0x69, 0x6A, 0x93, 0x12, 0x12, 0x46, 0x26, 0x1A, + 0x43, 0x6A, 0xAA, 0xAA, 0x1E, 0x85, 0x43, 0x20, + 0xE0, 0x00, 0xA1, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40 +}; + +static int ves1820_writereg(struct ves1820_state *state, u8 reg, u8 data) +{ + u8 buf[] = { 0x00, reg, data }; + struct i2c_msg msg = {.addr = state->config->demod_address,.flags = 0,.buf = buf,.len = 3 }; + int ret; + + ret = i2c_transfer(state->i2c, &msg, 1); + + if (ret != 1) + printk("ves1820: %s(): writereg error (reg == 0x%02x," + "val == 0x%02x, ret == %i)\n", __FUNCTION__, reg, data, ret); + + msleep(10); + return (ret != 1) ? -EREMOTEIO : 0; +} + +static u8 ves1820_readreg(struct ves1820_state *state, u8 reg) +{ + u8 b0[] = { 0x00, reg }; + u8 b1[] = { 0 }; + struct i2c_msg msg[] = { + {.addr = state->config->demod_address,.flags = 0,.buf = b0,.len = 2}, + {.addr = state->config->demod_address,.flags = I2C_M_RD,.buf = b1,.len = 1} + }; + int ret; + + ret = i2c_transfer(state->i2c, msg, 2); + + if (ret != 2) + printk("ves1820: %s(): readreg error (reg == 0x%02x," + "ret == %i)\n", __FUNCTION__, reg, ret); + + return b1[0]; +} + +static int ves1820_setup_reg0(struct ves1820_state *state, u8 reg0, fe_spectral_inversion_t inversion) +{ + reg0 |= state->reg0 & 0x62; + + if (INVERSION_ON == inversion) { + if (!state->config->invert) reg0 |= 0x20; + else reg0 &= ~0x20; + } else if (INVERSION_OFF == inversion) { + if (!state->config->invert) reg0 &= ~0x20; + else reg0 |= 0x20; + } + + ves1820_writereg(state, 0x00, reg0 & 0xfe); + ves1820_writereg(state, 0x00, reg0 | 0x01); + + state->reg0 = reg0; + + return 0; +} + +static int ves1820_set_symbolrate(struct ves1820_state *state, u32 symbolrate) +{ + s32 BDR; + s32 BDRI; + s16 SFIL = 0; + u16 NDEC = 0; + u32 ratio; + u32 fin; + u32 tmp; + u64 fptmp; + u64 fpxin; + + if (symbolrate > state->config->xin / 2) + symbolrate = state->config->xin / 2; + + if (symbolrate < 500000) + symbolrate = 500000; + + if (symbolrate < state->config->xin / 16) + NDEC = 1; + if (symbolrate < state->config->xin / 32) + NDEC = 2; + if (symbolrate < state->config->xin / 64) + NDEC = 3; + + /* yeuch! */ + fpxin = state->config->xin * 10; + fptmp = fpxin; do_div(fptmp, 123); + if (symbolrate < fptmp); + SFIL = 1; + fptmp = fpxin; do_div(fptmp, 160); + if (symbolrate < fptmp); + SFIL = 0; + fptmp = fpxin; do_div(fptmp, 246); + if (symbolrate < fptmp); + SFIL = 1; + fptmp = fpxin; do_div(fptmp, 320); + if (symbolrate < fptmp); + SFIL = 0; + fptmp = fpxin; do_div(fptmp, 492); + if (symbolrate < fptmp); + SFIL = 1; + fptmp = fpxin; do_div(fptmp, 640); + if (symbolrate < fptmp); + SFIL = 0; + fptmp = fpxin; do_div(fptmp, 984); + if (symbolrate < fptmp); + SFIL = 1; + + fin = state->config->xin >> 4; + symbolrate <<= NDEC; + ratio = (symbolrate << 4) / fin; + tmp = ((symbolrate << 4) % fin) << 8; + ratio = (ratio << 8) + tmp / fin; + tmp = (tmp % fin) << 8; + ratio = (ratio << 8) + (tmp + fin / 2) / fin; + + BDR = ratio; + BDRI = (((state->config->xin << 5) / symbolrate) + 1) / 2; + + if (BDRI > 0xFF) + BDRI = 0xFF; + + SFIL = (SFIL << 4) | ves1820_inittab[0x0E]; + + NDEC = (NDEC << 6) | ves1820_inittab[0x03]; + + ves1820_writereg(state, 0x03, NDEC); + ves1820_writereg(state, 0x0a, BDR & 0xff); + ves1820_writereg(state, 0x0b, (BDR >> 8) & 0xff); + ves1820_writereg(state, 0x0c, (BDR >> 16) & 0x3f); + + ves1820_writereg(state, 0x0d, BDRI); + ves1820_writereg(state, 0x0e, SFIL); + + return 0; +} + +static int ves1820_init(struct dvb_frontend* fe) +{ + struct ves1820_state* state = (struct ves1820_state*) fe->demodulator_priv; + int i; + int val; + + ves1820_writereg(state, 0, 0); + + for (i = 0; i < 53; i++) { + val = ves1820_inittab[i]; + if ((i == 2) && (state->config->selagc)) val |= 0x08; + ves1820_writereg(state, i, val); + } + + ves1820_writereg(state, 0x34, state->pwm); + + if (state->config->pll_init) state->config->pll_init(fe); + + return 0; +} + +static int ves1820_set_parameters(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + struct ves1820_state* state = (struct ves1820_state*) fe->demodulator_priv; + static const u8 reg0x00[] = { 0x00, 0x04, 0x08, 0x0c, 0x10 }; + static const u8 reg0x01[] = { 140, 140, 106, 100, 92 }; + static const u8 reg0x05[] = { 135, 100, 70, 54, 38 }; + static const u8 reg0x08[] = { 162, 116, 67, 52, 35 }; + static const u8 reg0x09[] = { 145, 150, 106, 126, 107 }; + int real_qam = p->u.qam.modulation - QAM_16; + + if (real_qam < 0 || real_qam > 4) + return -EINVAL; + + state->config->pll_set(fe, p); + ves1820_set_symbolrate(state, p->u.qam.symbol_rate); + ves1820_writereg(state, 0x34, state->pwm); + + ves1820_writereg(state, 0x01, reg0x01[real_qam]); + ves1820_writereg(state, 0x05, reg0x05[real_qam]); + ves1820_writereg(state, 0x08, reg0x08[real_qam]); + ves1820_writereg(state, 0x09, reg0x09[real_qam]); + + ves1820_setup_reg0(state, reg0x00[real_qam], p->inversion); + + return 0; +} + +static int ves1820_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct ves1820_state* state = (struct ves1820_state*) fe->demodulator_priv; + int sync; + + *status = 0; + sync = ves1820_readreg(state, 0x11); + + if (sync & 1) + *status |= FE_HAS_SIGNAL; + + if (sync & 2) + *status |= FE_HAS_CARRIER; + + if (sync & 2) /* XXX FIXME! */ + *status |= FE_HAS_VITERBI; + + if (sync & 4) + *status |= FE_HAS_SYNC; + + if (sync & 8) + *status |= FE_HAS_LOCK; + + return 0; +} + +static int ves1820_read_ber(struct dvb_frontend* fe, u32* ber) +{ + struct ves1820_state* state = (struct ves1820_state*) fe->demodulator_priv; + + u32 _ber = ves1820_readreg(state, 0x14) | + (ves1820_readreg(state, 0x15) << 8) | + ((ves1820_readreg(state, 0x16) & 0x0f) << 16); + *ber = 10 * _ber; + + return 0; +} + +static int ves1820_read_signal_strength(struct dvb_frontend* fe, u16* strength) +{ + struct ves1820_state* state = (struct ves1820_state*) fe->demodulator_priv; + + u8 gain = ves1820_readreg(state, 0x17); + *strength = (gain << 8) | gain; + + return 0; +} + +static int ves1820_read_snr(struct dvb_frontend* fe, u16* snr) +{ + struct ves1820_state* state = (struct ves1820_state*) fe->demodulator_priv; + + u8 quality = ~ves1820_readreg(state, 0x18); + *snr = (quality << 8) | quality; + + return 0; +} + +static int ves1820_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + struct ves1820_state* state = (struct ves1820_state*) fe->demodulator_priv; + + *ucblocks = ves1820_readreg(state, 0x13) & 0x7f; + if (*ucblocks == 0x7f) + *ucblocks = 0xffffffff; + + /* reset uncorrected block counter */ + ves1820_writereg(state, 0x10, ves1820_inittab[0x10] & 0xdf); + ves1820_writereg(state, 0x10, ves1820_inittab[0x10]); + + return 0; +} + +static int ves1820_get_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + struct ves1820_state* state = (struct ves1820_state*) fe->demodulator_priv; + int sync; + s8 afc = 0; + + sync = ves1820_readreg(state, 0x11); + afc = ves1820_readreg(state, 0x19); + if (verbose) { + /* AFC only valid when carrier has been recovered */ + printk(sync & 2 ? "ves1820: AFC (%d) %dHz\n" : + "ves1820: [AFC (%d) %dHz]\n", afc, -((s32) p->u.qam.symbol_rate * afc) >> 10); + } + + if (!state->config->invert) { + p->inversion = (state->reg0 & 0x20) ? INVERSION_ON : INVERSION_OFF; + } else { + p->inversion = (!(state->reg0 & 0x20)) ? INVERSION_ON : INVERSION_OFF; + } + + p->u.qam.modulation = ((state->reg0 >> 2) & 7) + QAM_16; + + p->u.qam.fec_inner = FEC_NONE; + + p->frequency = ((p->frequency + 31250) / 62500) * 62500; + if (sync & 2) + p->frequency -= ((s32) p->u.qam.symbol_rate * afc) >> 10; + + return 0; +} + +static int ves1820_sleep(struct dvb_frontend* fe) +{ + struct ves1820_state* state = (struct ves1820_state*) fe->demodulator_priv; + + ves1820_writereg(state, 0x1b, 0x02); /* pdown ADC */ + ves1820_writereg(state, 0x00, 0x80); /* standby */ + + return 0; +} + +static int ves1820_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* fesettings) +{ + + fesettings->min_delay_ms = 200; + fesettings->step_size = 0; + fesettings->max_drift = 0; + return 0; +} + +static void ves1820_release(struct dvb_frontend* fe) +{ + struct ves1820_state* state = (struct ves1820_state*) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops ves1820_ops; + +struct dvb_frontend* ves1820_attach(const struct ves1820_config* config, + struct i2c_adapter* i2c, + u8 pwm) +{ + struct ves1820_state* state = NULL; + + /* allocate memory for the internal state */ + state = (struct ves1820_state*) kmalloc(sizeof(struct ves1820_state), GFP_KERNEL); + if (state == NULL) + goto error; + + /* setup the state */ + memcpy(&state->ops, &ves1820_ops, sizeof(struct dvb_frontend_ops)); + state->reg0 = ves1820_inittab[0]; + state->config = config; + state->i2c = i2c; + state->pwm = pwm; + + /* check if the demod is there */ + if ((ves1820_readreg(state, 0x1a) & 0xf0) != 0x70) + goto error; + + if (verbose) + printk("ves1820: pwm=0x%02x\n", state->pwm); + + state->ops.info.symbol_rate_min = (state->config->xin / 2) / 64; /* SACLK/64 == (XIN/2)/64 */ + state->ops.info.symbol_rate_max = (state->config->xin / 2) / 4; /* SACLK/4 */ + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops ves1820_ops = { + + .info = { + .name = "VLSI VES1820 DVB-C", + .type = FE_QAM, + .frequency_stepsize = 62500, + .frequency_min = 51000000, + .frequency_max = 858000000, + .caps = FE_CAN_QAM_16 | + FE_CAN_QAM_32 | + FE_CAN_QAM_64 | + FE_CAN_QAM_128 | + FE_CAN_QAM_256 | + FE_CAN_FEC_AUTO + }, + + .release = ves1820_release, + + .init = ves1820_init, + .sleep = ves1820_sleep, + + .set_frontend = ves1820_set_parameters, + .get_frontend = ves1820_get_frontend, + .get_tune_settings = ves1820_get_tune_settings, + + .read_status = ves1820_read_status, + .read_ber = ves1820_read_ber, + .read_signal_strength = ves1820_read_signal_strength, + .read_snr = ves1820_read_snr, + .read_ucblocks = ves1820_read_ucblocks, +}; + +module_param(verbose, int, 0644); +MODULE_PARM_DESC(verbose, "print AFC offset after tuning for debugging the PWM setting"); + +MODULE_DESCRIPTION("VLSI VES1820 DVB-C Demodulator driver"); +MODULE_AUTHOR("Ralph Metzler, Holger Waechtler"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(ves1820_attach); diff --git a/drivers/media/dvb/frontends/ves1820.h b/drivers/media/dvb/frontends/ves1820.h new file mode 100644 index 000000000000..355f130b1be8 --- /dev/null +++ b/drivers/media/dvb/frontends/ves1820.h @@ -0,0 +1,51 @@ +/* + VES1820 - Single Chip Cable Channel Receiver driver module + + Copyright (C) 1999 Convergence Integrated Media GmbH <ralph@convergence.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef VES1820_H +#define VES1820_H + +#include <linux/dvb/frontend.h> + +#define VES1820_SELAGC_PWM 0 +#define VES1820_SELAGC_SIGNAMPERR 1 + +struct ves1820_config +{ + /* the demodulator's i2c address */ + u8 demod_address; + + /* value of XIN to use */ + u32 xin; + + /* does inversion need inverted? */ + u8 invert:1; + + /* SELAGC control */ + u8 selagc:1; + + /* PLL maintenance */ + int (*pll_init)(struct dvb_frontend* fe); + int (*pll_set)(struct dvb_frontend* fe, struct dvb_frontend_parameters* params); +}; + +extern struct dvb_frontend* ves1820_attach(const struct ves1820_config* config, + struct i2c_adapter* i2c, u8 pwm); + +#endif // VES1820_H diff --git a/drivers/media/dvb/frontends/ves1x93.c b/drivers/media/dvb/frontends/ves1x93.c new file mode 100644 index 000000000000..edcad283aa86 --- /dev/null +++ b/drivers/media/dvb/frontends/ves1x93.c @@ -0,0 +1,545 @@ +/* + Driver for VES1893 and VES1993 QPSK Demodulators + + Copyright (C) 1999 Convergence Integrated Media GmbH <ralph@convergence.de> + Copyright (C) 2001 Ronny Strutz <3des@elitedvb.de> + Copyright (C) 2002 Dennis Noermann <dennis.noermann@noernet.de> + Copyright (C) 2002-2003 Andreas Oberritter <obi@linuxtv.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/delay.h> + +#include "dvb_frontend.h" +#include "ves1x93.h" + + +struct ves1x93_state { + struct i2c_adapter* i2c; + struct dvb_frontend_ops ops; + /* configuration settings */ + const struct ves1x93_config* config; + struct dvb_frontend frontend; + + /* previous uncorrected block counter */ + fe_spectral_inversion_t inversion; + u8 *init_1x93_tab; + u8 *init_1x93_wtab; + u8 tab_size; + u8 demod_type; +}; + +static int debug = 0; +#define dprintk if (debug) printk + +#define DEMOD_VES1893 0 +#define DEMOD_VES1993 1 + +static u8 init_1893_tab [] = { + 0x01, 0xa4, 0x35, 0x80, 0x2a, 0x0b, 0x55, 0xc4, + 0x09, 0x69, 0x00, 0x86, 0x4c, 0x28, 0x7f, 0x00, + 0x00, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x21, 0xb0, 0x14, 0x00, 0xdc, 0x00, + 0x81, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x55, 0x00, 0x00, 0x7f, 0x00 +}; + +static u8 init_1993_tab [] = { + 0x00, 0x9c, 0x35, 0x80, 0x6a, 0x09, 0x72, 0x8c, + 0x09, 0x6b, 0x00, 0x00, 0x4c, 0x08, 0x00, 0x00, + 0x00, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x40, 0x21, 0xb0, 0x00, 0x00, 0x00, 0x10, + 0x81, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x55, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x0e, 0x80, 0x00 +}; + +static u8 init_1893_wtab[] = +{ + 1,1,1,1,1,1,1,1, 1,1,0,0,1,1,0,0, + 0,1,0,0,0,0,0,0, 1,0,1,1,0,0,0,1, + 1,1,1,0,0,0,0,0, 0,0,1,1,0,0,0,0, + 1,1,1,0,1,1 +}; + +static u8 init_1993_wtab[] = +{ + 1,1,1,1,1,1,1,1, 1,1,0,0,1,1,0,0, + 0,1,0,0,0,0,0,0, 1,1,1,1,0,0,0,1, + 1,1,1,0,0,0,0,0, 0,0,1,1,0,0,0,0, + 1,1,1,0,1,1,1,1, 1,1,1,1,1 +}; + +static int ves1x93_writereg (struct ves1x93_state* state, u8 reg, u8 data) +{ + u8 buf [] = { 0x00, reg, data }; + struct i2c_msg msg = { .addr = state->config->demod_address, .flags = 0, .buf = buf, .len = 3 }; + int err; + + if ((err = i2c_transfer (state->i2c, &msg, 1)) != 1) { + dprintk ("%s: writereg error (err == %i, reg == 0x%02x, data == 0x%02x)\n", __FUNCTION__, err, reg, data); + return -EREMOTEIO; + } + + return 0; +} + +static u8 ves1x93_readreg (struct ves1x93_state* state, u8 reg) +{ + int ret; + u8 b0 [] = { 0x00, reg }; + u8 b1 [] = { 0 }; + struct i2c_msg msg [] = { { .addr = state->config->demod_address, .flags = 0, .buf = b0, .len = 2 }, + { .addr = state->config->demod_address, .flags = I2C_M_RD, .buf = b1, .len = 1 } }; + + ret = i2c_transfer (state->i2c, msg, 2); + + if (ret != 2) return ret; + + return b1[0]; +} + +static int ves1x93_clr_bit (struct ves1x93_state* state) +{ + msleep(10); + ves1x93_writereg (state, 0, state->init_1x93_tab[0] & 0xfe); + ves1x93_writereg (state, 0, state->init_1x93_tab[0]); + msleep(50); + return 0; +} + +static int ves1x93_set_inversion (struct ves1x93_state* state, fe_spectral_inversion_t inversion) +{ + u8 val; + + /* + * inversion on/off are interchanged because i and q seem to + * be swapped on the hardware + */ + + switch (inversion) { + case INVERSION_OFF: + val = 0xc0; + break; + case INVERSION_ON: + val = 0x80; + break; + case INVERSION_AUTO: + val = 0x00; + break; + default: + return -EINVAL; + } + + return ves1x93_writereg (state, 0x0c, (state->init_1x93_tab[0x0c] & 0x3f) | val); +} + +static int ves1x93_set_fec (struct ves1x93_state* state, fe_code_rate_t fec) +{ + if (fec == FEC_AUTO) + return ves1x93_writereg (state, 0x0d, 0x08); + else if (fec < FEC_1_2 || fec > FEC_8_9) + return -EINVAL; + else + return ves1x93_writereg (state, 0x0d, fec - FEC_1_2); +} + +static fe_code_rate_t ves1x93_get_fec (struct ves1x93_state* state) +{ + return FEC_1_2 + ((ves1x93_readreg (state, 0x0d) >> 4) & 0x7); +} + +static int ves1x93_set_symbolrate (struct ves1x93_state* state, u32 srate) +{ + u32 BDR; + u32 ratio; + u8 ADCONF, FCONF, FNR, AGCR; + u32 BDRI; + u32 tmp; + u32 FIN; + + dprintk("%s: srate == %d\n", __FUNCTION__, (unsigned int) srate); + + if (srate > state->config->xin/2) + srate = state->config->xin/2; + + if (srate < 500000) + srate = 500000; + +#define MUL (1UL<<26) + + FIN = (state->config->xin + 6000) >> 4; + + tmp = srate << 6; + ratio = tmp / FIN; + + tmp = (tmp % FIN) << 8; + ratio = (ratio << 8) + tmp / FIN; + + tmp = (tmp % FIN) << 8; + ratio = (ratio << 8) + tmp / FIN; + + FNR = 0xff; + + if (ratio < MUL/3) FNR = 0; + if (ratio < (MUL*11)/50) FNR = 1; + if (ratio < MUL/6) FNR = 2; + if (ratio < MUL/9) FNR = 3; + if (ratio < MUL/12) FNR = 4; + if (ratio < (MUL*11)/200) FNR = 5; + if (ratio < MUL/24) FNR = 6; + if (ratio < (MUL*27)/1000) FNR = 7; + if (ratio < MUL/48) FNR = 8; + if (ratio < (MUL*137)/10000) FNR = 9; + + if (FNR == 0xff) { + ADCONF = 0x89; + FCONF = 0x80; + FNR = 0; + } else { + ADCONF = 0x81; + FCONF = 0x88 | (FNR >> 1) | ((FNR & 0x01) << 5); + /*FCONF = 0x80 | ((FNR & 0x01) << 5) | (((FNR > 1) & 0x03) << 3) | ((FNR >> 1) & 0x07);*/ + } + + BDR = (( (ratio << (FNR >> 1)) >> 4) + 1) >> 1; + BDRI = ( ((FIN << 8) / ((srate << (FNR >> 1)) >> 2)) + 1) >> 1; + + dprintk("FNR= %d\n", FNR); + dprintk("ratio= %08x\n", (unsigned int) ratio); + dprintk("BDR= %08x\n", (unsigned int) BDR); + dprintk("BDRI= %02x\n", (unsigned int) BDRI); + + if (BDRI > 0xff) + BDRI = 0xff; + + ves1x93_writereg (state, 0x06, 0xff & BDR); + ves1x93_writereg (state, 0x07, 0xff & (BDR >> 8)); + ves1x93_writereg (state, 0x08, 0x0f & (BDR >> 16)); + + ves1x93_writereg (state, 0x09, BDRI); + ves1x93_writereg (state, 0x20, ADCONF); + ves1x93_writereg (state, 0x21, FCONF); + + AGCR = state->init_1x93_tab[0x05]; + if (state->config->invert_pwm) + AGCR |= 0x20; + + if (srate < 6000000) + AGCR |= 0x80; + else + AGCR &= ~0x80; + + ves1x93_writereg (state, 0x05, AGCR); + + /* ves1993 hates this, will lose lock */ + if (state->demod_type != DEMOD_VES1993) + ves1x93_clr_bit (state); + + return 0; +} + +static int ves1x93_init (struct dvb_frontend* fe) +{ + struct ves1x93_state* state = (struct ves1x93_state*) fe->demodulator_priv; + int i; + int val; + + dprintk("%s: init chip\n", __FUNCTION__); + + for (i = 0; i < state->tab_size; i++) { + if (state->init_1x93_wtab[i]) { + val = state->init_1x93_tab[i]; + + if (state->config->invert_pwm && (i == 0x05)) val |= 0x20; /* invert PWM */ + ves1x93_writereg (state, i, val); + } + } + + if (state->config->pll_init) { + ves1x93_writereg(state, 0x00, 0x11); + state->config->pll_init(fe); + ves1x93_writereg(state, 0x00, 0x01); + } + + return 0; +} + +static int ves1x93_set_voltage (struct dvb_frontend* fe, fe_sec_voltage_t voltage) +{ + struct ves1x93_state* state = (struct ves1x93_state*) fe->demodulator_priv; + + switch (voltage) { + case SEC_VOLTAGE_13: + return ves1x93_writereg (state, 0x1f, 0x20); + case SEC_VOLTAGE_18: + return ves1x93_writereg (state, 0x1f, 0x30); + case SEC_VOLTAGE_OFF: + return ves1x93_writereg (state, 0x1f, 0x00); + default: + return -EINVAL; + } +} + +static int ves1x93_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct ves1x93_state* state = (struct ves1x93_state*) fe->demodulator_priv; + + u8 sync = ves1x93_readreg (state, 0x0e); + + /* + * The ves1893 sometimes returns sync values that make no sense, + * because, e.g., the SIGNAL bit is 0, while some of the higher + * bits are 1 (and how can there be a CARRIER w/o a SIGNAL?). + * Tests showed that the the VITERBI and SYNC bits are returned + * reliably, while the SIGNAL and CARRIER bits ar sometimes wrong. + * If such a case occurs, we read the value again, until we get a + * valid value. + */ + int maxtry = 10; /* just for safety - let's not get stuck here */ + while ((sync & 0x03) != 0x03 && (sync & 0x0c) && maxtry--) { + msleep(10); + sync = ves1x93_readreg (state, 0x0e); + } + + *status = 0; + + if (sync & 1) + *status |= FE_HAS_SIGNAL; + + if (sync & 2) + *status |= FE_HAS_CARRIER; + + if (sync & 4) + *status |= FE_HAS_VITERBI; + + if (sync & 8) + *status |= FE_HAS_SYNC; + + if ((sync & 0x1f) == 0x1f) + *status |= FE_HAS_LOCK; + + return 0; +} + +static int ves1x93_read_ber(struct dvb_frontend* fe, u32* ber) +{ + struct ves1x93_state* state = (struct ves1x93_state*) fe->demodulator_priv; + + *ber = ves1x93_readreg (state, 0x15); + *ber |= (ves1x93_readreg (state, 0x16) << 8); + *ber |= ((ves1x93_readreg (state, 0x17) & 0x0F) << 16); + *ber *= 10; + + return 0; +} + +static int ves1x93_read_signal_strength(struct dvb_frontend* fe, u16* strength) +{ + struct ves1x93_state* state = (struct ves1x93_state*) fe->demodulator_priv; + + u8 signal = ~ves1x93_readreg (state, 0x0b); + *strength = (signal << 8) | signal; + + return 0; +} + +static int ves1x93_read_snr(struct dvb_frontend* fe, u16* snr) +{ + struct ves1x93_state* state = (struct ves1x93_state*) fe->demodulator_priv; + + u8 _snr = ~ves1x93_readreg (state, 0x1c); + *snr = (_snr << 8) | _snr; + + return 0; +} + +static int ves1x93_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + struct ves1x93_state* state = (struct ves1x93_state*) fe->demodulator_priv; + + *ucblocks = ves1x93_readreg (state, 0x18) & 0x7f; + + if (*ucblocks == 0x7f) + *ucblocks = 0xffffffff; /* counter overflow... */ + + ves1x93_writereg (state, 0x18, 0x00); /* reset the counter */ + ves1x93_writereg (state, 0x18, 0x80); /* dto. */ + + return 0; +} + +static int ves1x93_set_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + struct ves1x93_state* state = (struct ves1x93_state*) fe->demodulator_priv; + + ves1x93_writereg(state, 0x00, 0x11); + state->config->pll_set(fe, p); + ves1x93_writereg(state, 0x00, 0x01); + ves1x93_set_inversion (state, p->inversion); + ves1x93_set_fec (state, p->u.qpsk.fec_inner); + ves1x93_set_symbolrate (state, p->u.qpsk.symbol_rate); + state->inversion = p->inversion; + + return 0; +} + +static int ves1x93_get_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + struct ves1x93_state* state = (struct ves1x93_state*) fe->demodulator_priv; + int afc; + + afc = ((int)((char)(ves1x93_readreg (state, 0x0a) << 1)))/2; + afc = (afc * (int)(p->u.qpsk.symbol_rate/1000/8))/16; + + p->frequency -= afc; + + /* + * inversion indicator is only valid + * if auto inversion was used + */ + if (state->inversion == INVERSION_AUTO) + p->inversion = (ves1x93_readreg (state, 0x0f) & 2) ? + INVERSION_OFF : INVERSION_ON; + p->u.qpsk.fec_inner = ves1x93_get_fec (state); + /* XXX FIXME: timing offset !! */ + + return 0; +} + +static int ves1x93_sleep(struct dvb_frontend* fe) +{ + struct ves1x93_state* state = (struct ves1x93_state*) fe->demodulator_priv; + + return ves1x93_writereg (state, 0x00, 0x08); +} + +static void ves1x93_release(struct dvb_frontend* fe) +{ + struct ves1x93_state* state = (struct ves1x93_state*) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops ves1x93_ops; + +struct dvb_frontend* ves1x93_attach(const struct ves1x93_config* config, + struct i2c_adapter* i2c) +{ + struct ves1x93_state* state = NULL; + u8 identity; + + /* allocate memory for the internal state */ + state = (struct ves1x93_state*) kmalloc(sizeof(struct ves1x93_state), GFP_KERNEL); + if (state == NULL) goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + memcpy(&state->ops, &ves1x93_ops, sizeof(struct dvb_frontend_ops)); + state->inversion = INVERSION_OFF; + + /* check if the demod is there + identify it */ + identity = ves1x93_readreg(state, 0x1e); + switch (identity) { + case 0xdc: /* VES1893A rev1 */ + printk("ves1x93: Detected ves1893a rev1\n"); + state->demod_type = DEMOD_VES1893; + state->init_1x93_tab = init_1893_tab; + state->init_1x93_wtab = init_1893_wtab; + state->tab_size = sizeof(init_1893_tab); + break; + + case 0xdd: /* VES1893A rev2 */ + printk("ves1x93: Detected ves1893a rev2\n"); + state->demod_type = DEMOD_VES1893; + state->init_1x93_tab = init_1893_tab; + state->init_1x93_wtab = init_1893_wtab; + state->tab_size = sizeof(init_1893_tab); + break; + + case 0xde: /* VES1993 */ + printk("ves1x93: Detected ves1993\n"); + state->demod_type = DEMOD_VES1993; + state->init_1x93_tab = init_1993_tab; + state->init_1x93_wtab = init_1993_wtab; + state->tab_size = sizeof(init_1993_tab); + break; + + default: + goto error; + } + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops ves1x93_ops = { + + .info = { + .name = "VLSI VES1x93 DVB-S", + .type = FE_QPSK, + .frequency_min = 950000, + .frequency_max = 2150000, + .frequency_stepsize = 125, /* kHz for QPSK frontends */ + .frequency_tolerance = 29500, + .symbol_rate_min = 1000000, + .symbol_rate_max = 45000000, + /* .symbol_rate_tolerance = ???,*/ + .caps = FE_CAN_INVERSION_AUTO | + 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_FEC_AUTO | + FE_CAN_QPSK + }, + + .release = ves1x93_release, + + .init = ves1x93_init, + .sleep = ves1x93_sleep, + + .set_frontend = ves1x93_set_frontend, + .get_frontend = ves1x93_get_frontend, + + .read_status = ves1x93_read_status, + .read_ber = ves1x93_read_ber, + .read_signal_strength = ves1x93_read_signal_strength, + .read_snr = ves1x93_read_snr, + .read_ucblocks = ves1x93_read_ucblocks, + + .set_voltage = ves1x93_set_voltage, +}; + +module_param(debug, int, 0644); + +MODULE_DESCRIPTION("VLSI VES1x93 DVB-S Demodulator driver"); +MODULE_AUTHOR("Ralph Metzler"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(ves1x93_attach); diff --git a/drivers/media/dvb/frontends/ves1x93.h b/drivers/media/dvb/frontends/ves1x93.h new file mode 100644 index 000000000000..1627e37c57a4 --- /dev/null +++ b/drivers/media/dvb/frontends/ves1x93.h @@ -0,0 +1,50 @@ +/* + Driver for VES1893 and VES1993 QPSK Demodulators + + Copyright (C) 1999 Convergence Integrated Media GmbH <ralph@convergence.de> + Copyright (C) 2001 Ronny Strutz <3des@elitedvb.de> + Copyright (C) 2002 Dennis Noermann <dennis.noermann@noernet.de> + Copyright (C) 2002-2003 Andreas Oberritter <obi@linuxtv.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef VES1X93_H +#define VES1X93_H + +#include <linux/dvb/frontend.h> + +struct ves1x93_config +{ + /* the demodulator's i2c address */ + u8 demod_address; + + /* value of XIN to use */ + u32 xin; + + /* should PWM be inverted? */ + u8 invert_pwm:1; + + /* PLL maintenance */ + int (*pll_init)(struct dvb_frontend* fe); + int (*pll_set)(struct dvb_frontend* fe, struct dvb_frontend_parameters* params); +}; + +extern struct dvb_frontend* ves1x93_attach(const struct ves1x93_config* config, + struct i2c_adapter* i2c); + +#endif // VES1X93_H diff --git a/drivers/media/dvb/ttpci/Kconfig b/drivers/media/dvb/ttpci/Kconfig new file mode 100644 index 000000000000..7ffa2c7315b3 --- /dev/null +++ b/drivers/media/dvb/ttpci/Kconfig @@ -0,0 +1,134 @@ +config DVB_AV7110 + tristate "AV7110 cards" + depends on DVB_CORE && PCI + select FW_LOADER + select VIDEO_DEV + select VIDEO_SAA7146_VV + select DVB_VES1820 + select DVB_VES1X93 + select DVB_STV0299 + select DVB_TDA8083 + select DVB_SP8870 + select DVB_STV0297 + select DVB_L64781 + help + Support for SAA7146 and AV7110 based DVB cards as produced + by Fujitsu-Siemens, Technotrend, Hauppauge and others. + + This driver only supports the fullfeatured cards with + onboard MPEG2 decoder. + + This driver needs an external firmware. Please use the script + "<kerneldir>/Documentation/dvb/get_dvb_firmware av7110" to + download/extract it, and then copy it to /usr/lib/hotplug/firmware. + + Say Y if you own such a card and want to use it. + +config DVB_AV7110_FIRMWARE + bool "Compile AV7110 firmware into the driver" + depends on DVB_AV7110 && !STANDALONE + default y if DVB_AV7110=y + help + The AV7110 firmware is normally loaded by the firmware hotplug manager. + If you want to compile the firmware into the driver you need to say + Y here and provide the correct path of the firmware. You need this + option if you want to compile the whole driver statically into the + kernel. + + All other people say N. + +config DVB_AV7110_FIRMWARE_FILE + string "Full pathname of av7110 firmware file" + depends on DVB_AV7110_FIRMWARE + default "/usr/lib/hotplug/firmware/dvb-ttpci-01.fw" + +config DVB_AV7110_OSD + bool "AV7110 OSD support" + depends on DVB_AV7110 + default y if DVB_AV7110=y || DVB_AV7110=m + help + The AV7110 firmware provides some code to generate an OnScreenDisplay + on the video output. This is kind of nonstandard and not guaranteed to + be maintained. + + Anyway, some popular DVB software like VDR uses this OSD to render + its menus, so say Y if you want to use this software. + + All other people say N. + +config DVB_BUDGET + tristate "Budget cards" + depends on DVB_CORE && PCI + select VIDEO_SAA7146 + select DVB_STV0299 + select DVB_VES1X93 + select DVB_VES1820 + select DVB_L64781 + select DVB_TDA8083 + select DVB_TDA10021 + help + Support for simple SAA7146 based DVB cards + (so called Budget- or Nova-PCI cards) without onboard + MPEG2 decoder. + + Say Y if you own such a card and want to use it. + + To compile this driver as a module, choose M here: the + module will be called budget. + +config DVB_BUDGET_CI + tristate "Budget cards with onboard CI connector" + depends on DVB_CORE && PCI + select VIDEO_SAA7146 + select DVB_STV0299 + select DVB_TDA1004X + help + Support for simple SAA7146 based DVB cards + (so called Budget- or Nova-PCI cards) without onboard + MPEG2 decoder, but with onboard Common Interface connector. + + Note: The Common Interface is not yet supported by this driver + due to lack of information from the vendor. + + Say Y if you own such a card and want to use it. + + To compile this driver as a module, choose M here: the + module will be called budget-ci. + +config DVB_BUDGET_AV + tristate "Budget cards with analog video inputs" + depends on DVB_CORE && PCI + select VIDEO_DEV + select VIDEO_SAA7146_VV + select DVB_STV0299 + help + Support for simple SAA7146 based DVB cards + (so called Budget- or Nova-PCI cards) without onboard + MPEG2 decoder, but with one or more analog video inputs. + + Say Y if you own such a card and want to use it. + + To compile this driver as a module, choose M here: the + module will be called budget-av. + +config DVB_BUDGET_PATCH + tristate "AV7110 cards with Budget Patch" + depends on DVB_CORE && DVB_BUDGET + select DVB_AV7110 + select DVB_STV0299 + select DVB_VES1X93 + select DVB_TDA8083 + help + Support for Budget Patch (full TS) modification on + SAA7146+AV7110 based cards (DVB-S cards). This + driver doesn't use onboard MPEG2 decoder. The + card is driven in Budget-only mode. Card is + required to have loaded firmware to tune properly. + Firmware can be loaded by insertion and removal of + standard AV7110 driver prior to loading this + driver. + + Say Y if you own such a card and want to use it. + + To compile this driver as a module, choose M here: the + module will be called budget-patch. diff --git a/drivers/media/dvb/ttpci/Makefile b/drivers/media/dvb/ttpci/Makefile new file mode 100644 index 000000000000..825ab1c38a4f --- /dev/null +++ b/drivers/media/dvb/ttpci/Makefile @@ -0,0 +1,23 @@ +# +# Makefile for the kernel SAA7146 FULL TS DVB device driver +# and the AV7110 DVB device driver +# + +dvb-ttpci-objs := av7110_hw.o av7110_v4l.o av7110_av.o av7110_ca.o av7110.o av7110_ipack.o av7110_ir.o + +obj-$(CONFIG_DVB_BUDGET) += budget-core.o budget.o ttpci-eeprom.o +obj-$(CONFIG_DVB_BUDGET_AV) += budget-core.o budget-av.o ttpci-eeprom.o +obj-$(CONFIG_DVB_BUDGET_CI) += budget-core.o budget-ci.o ttpci-eeprom.o +obj-$(CONFIG_DVB_BUDGET_PATCH) += budget-core.o budget-patch.o ttpci-eeprom.o +obj-$(CONFIG_DVB_AV7110) += dvb-ttpci.o ttpci-eeprom.o + +EXTRA_CFLAGS = -Idrivers/media/dvb/dvb-core/ -Idrivers/media/dvb/frontends/ + +hostprogs-y := fdump + +ifdef CONFIG_DVB_AV7110_FIRMWARE +$(obj)/av7110.o: $(obj)/fdump $(obj)/av7110_firm.h + +$(obj)/av7110_firm.h: + $(obj)/fdump $(CONFIG_DVB_AV7110_FIRMWARE_FILE) dvb_ttpci_fw $@ +endif diff --git a/drivers/media/dvb/ttpci/av7110.c b/drivers/media/dvb/ttpci/av7110.c new file mode 100644 index 000000000000..922c205a2652 --- /dev/null +++ b/drivers/media/dvb/ttpci/av7110.c @@ -0,0 +1,2739 @@ +/* + * driver for the SAA7146 based AV110 cards (like the Fujitsu-Siemens DVB) + * av7110.c: initialization and demux stuff + * + * Copyright (C) 1999-2002 Ralph Metzler + * & Marcus Metzler for convergence integrated media GmbH + * + * originally based on code by: + * Copyright (C) 1998,1999 Christian Theiss <mistert@rz.fh-augsburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + * + * the project's page is at http://www.linuxtv.org/dvb/ + */ + + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kmod.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/timer.h> +#include <linux/poll.h> +#include <linux/byteorder/swabb.h> +#include <linux/smp_lock.h> + +#include <linux/kernel.h> +#include <linux/moduleparam.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/string.h> +#include <linux/pci.h> +#include <linux/vmalloc.h> +#include <linux/firmware.h> +#include <linux/crc32.h> +#include <linux/i2c.h> + +#include <asm/system.h> +#include <asm/semaphore.h> + +#include <linux/dvb/frontend.h> + +#include "dvb_frontend.h" + +#include "ttpci-eeprom.h" +#include "av7110.h" +#include "av7110_hw.h" +#include "av7110_av.h" +#include "av7110_ca.h" +#include "av7110_ipack.h" + +#define TS_WIDTH 376 +#define TS_HEIGHT 512 +#define TS_BUFLEN (TS_WIDTH*TS_HEIGHT) +#define TS_MAX_PACKETS (TS_BUFLEN/TS_SIZE) + + +int av7110_debug; + +static int vidmode = CVBS_RGB_OUT; +static int pids_off; +static int adac = DVB_ADAC_TI; +static int hw_sections; +static int rgb_on; +static int volume = 255; +static int budgetpatch = 0; + +module_param_named(debug, av7110_debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (bitmask, default 0)"); +module_param(vidmode, int, 0444); +MODULE_PARM_DESC(vidmode,"analog video out: 0 off, 1 CVBS+RGB (default), 2 CVBS+YC, 3 YC"); +module_param(pids_off, int, 0444); +MODULE_PARM_DESC(pids_off,"clear video/audio/PCR PID filters when demux is closed"); +module_param(adac, int, 0444); +MODULE_PARM_DESC(adac,"audio DAC type: 0 TI, 1 CRYSTAL, 2 MSP (use if autodetection fails)"); +module_param(hw_sections, int, 0444); +MODULE_PARM_DESC(hw_sections, "0 use software section filter, 1 use hardware"); +module_param(rgb_on, int, 0444); +MODULE_PARM_DESC(rgb_on, "For Siemens DVB-C cards only: Enable RGB control" + " signal on SCART pin 16 to switch SCART video mode from CVBS to RGB"); +module_param(volume, int, 0444); +MODULE_PARM_DESC(volume, "initial volume: default 255 (range 0-255)"); +module_param(budgetpatch, int, 0444); +MODULE_PARM_DESC(budgetpatch, "use budget-patch hardware modification: default 0 (0 no, 1 autodetect, 2 always)"); + +static void restart_feeds(struct av7110 *av7110); + +static int av7110_num = 0; + +#define FE_FUNC_OVERRIDE(fe_func, av7110_copy, av7110_func) \ +{\ + if (fe_func != NULL) { \ + av7110_copy = fe_func; \ + fe_func = av7110_func; \ + } \ +} + + +static void init_av7110_av(struct av7110 *av7110) +{ + struct saa7146_dev *dev = av7110->dev; + + /* set internal volume control to maximum */ + av7110->adac_type = DVB_ADAC_TI; + av7110_set_volume(av7110, av7110->mixer.volume_left, av7110->mixer.volume_right); + + av7710_set_video_mode(av7110, vidmode); + + /* handle different card types */ + /* remaining inits according to card and frontend type */ + av7110->analog_tuner_flags = 0; + av7110->current_input = 0; + if (i2c_writereg(av7110, 0x20, 0x00, 0x00) == 1) { + printk ("dvb-ttpci: Crystal audio DAC @ card %d detected\n", + av7110->dvb_adapter->num); + av7110->adac_type = DVB_ADAC_CRYSTAL; + i2c_writereg(av7110, 0x20, 0x01, 0xd2); + i2c_writereg(av7110, 0x20, 0x02, 0x49); + i2c_writereg(av7110, 0x20, 0x03, 0x00); + i2c_writereg(av7110, 0x20, 0x04, 0x00); + + /** + * some special handling for the Siemens DVB-C cards... + */ + } else if (0 == av7110_init_analog_module(av7110)) { + /* done. */ + } + else if (dev->pci->subsystem_vendor == 0x110a) { + printk("dvb-ttpci: DVB-C w/o analog module @ card %d detected\n", + av7110->dvb_adapter->num); + av7110->adac_type = DVB_ADAC_NONE; + } + else { + av7110->adac_type = adac; + printk("dvb-ttpci: adac type set to %d @ card %d\n", + av7110->dvb_adapter->num, av7110->adac_type); + } + + if (av7110->adac_type == DVB_ADAC_NONE || av7110->adac_type == DVB_ADAC_MSP) { + // switch DVB SCART on + av7110_fw_cmd(av7110, COMTYPE_AUDIODAC, MainSwitch, 1, 0); + av7110_fw_cmd(av7110, COMTYPE_AUDIODAC, ADSwitch, 1, 1); + if (rgb_on && + (av7110->dev->pci->subsystem_vendor == 0x110a) && (av7110->dev->pci->subsystem_device == 0x0000)) { + saa7146_setgpio(dev, 1, SAA7146_GPIO_OUTHI); // RGB on, SCART pin 16 + //saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); // SCARTpin 8 + } + } + + av7110_set_volume(av7110, av7110->mixer.volume_left, av7110->mixer.volume_right); + av7110_setup_irc_config(av7110, 0); +} + +static void recover_arm(struct av7110 *av7110) +{ + dprintk(4, "%p\n",av7110); + + av7110_bootarm(av7110); + msleep(100); + restart_feeds(av7110); + av7110_fw_cmd(av7110, COMTYPE_PIDFILTER, SetIR, 1, av7110->ir_config); +} + +static void arm_error(struct av7110 *av7110) +{ + dprintk(4, "%p\n",av7110); + + av7110->arm_errors++; + av7110->arm_ready = 0; + recover_arm(av7110); +} + +static void av7110_arm_sync(struct av7110 *av7110) +{ + av7110->arm_rmmod = 1; + wake_up_interruptible(&av7110->arm_wait); + + while (av7110->arm_thread) + msleep(1); +} + +static int arm_thread(void *data) +{ + struct av7110 *av7110 = data; + u16 newloops = 0; + int timeout; + + dprintk(4, "%p\n",av7110); + + lock_kernel(); + daemonize("arm_mon"); + sigfillset(¤t->blocked); + unlock_kernel(); + + av7110->arm_thread = current; + + for (;;) { + timeout = wait_event_interruptible_timeout(av7110->arm_wait, + av7110->arm_rmmod, 5 * HZ); + if (-ERESTARTSYS == timeout || av7110->arm_rmmod) { + /* got signal or told to quit*/ + break; + } + + if (!av7110->arm_ready) + continue; + + if (down_interruptible(&av7110->dcomlock)) + break; + + newloops = rdebi(av7110, DEBINOSWAP, STATUS_LOOPS, 0, 2); + up(&av7110->dcomlock); + + if (newloops == av7110->arm_loops) { + printk(KERN_ERR "dvb-ttpci: ARM crashed @ card %d\n", + av7110->dvb_adapter->num); + + arm_error(av7110); + av7710_set_video_mode(av7110, vidmode); + + init_av7110_av(av7110); + + if (down_interruptible(&av7110->dcomlock)) + break; + + newloops = rdebi(av7110, DEBINOSWAP, STATUS_LOOPS, 0, 2) - 1; + up(&av7110->dcomlock); + } + av7110->arm_loops = newloops; + } + + av7110->arm_thread = NULL; + return 0; +} + + +/** + * Hack! we save the last av7110 ptr. This should be ok, since + * you rarely will use more then one IR control. + * + * If we want to support multiple controls we would have to do much more... + */ +void av7110_setup_irc_config(struct av7110 *av7110, u32 ir_config) +{ + static struct av7110 *last; + + dprintk(4, "%p\n", av7110); + + if (!av7110) + av7110 = last; + else + last = av7110; + + if (av7110) { + av7110_fw_cmd(av7110, COMTYPE_PIDFILTER, SetIR, 1, ir_config); + av7110->ir_config = ir_config; + } +} + +static void (*irc_handler)(u32); + +void av7110_register_irc_handler(void (*func)(u32)) +{ + dprintk(4, "registering %p\n", func); + irc_handler = func; +} + +void av7110_unregister_irc_handler(void (*func)(u32)) +{ + dprintk(4, "unregistering %p\n", func); + irc_handler = NULL; +} + +static void run_handlers(unsigned long ircom) +{ + if (irc_handler != NULL) + (*irc_handler)((u32) ircom); +} + +static DECLARE_TASKLET(irtask, run_handlers, 0); + +static void IR_handle(struct av7110 *av7110, u32 ircom) +{ + dprintk(4, "ircommand = %08x\n", ircom); + irtask.data = (unsigned long) ircom; + tasklet_schedule(&irtask); +} + +/**************************************************************************** + * IRQ handling + ****************************************************************************/ + +static int DvbDmxFilterCallback(u8 *buffer1, size_t buffer1_len, + u8 *buffer2, size_t buffer2_len, + struct dvb_demux_filter *dvbdmxfilter, + enum dmx_success success, + struct av7110 *av7110) +{ + if (!dvbdmxfilter->feed->demux->dmx.frontend) + return 0; + if (dvbdmxfilter->feed->demux->dmx.frontend->source == DMX_MEMORY_FE) + return 0; + + switch (dvbdmxfilter->type) { + case DMX_TYPE_SEC: + if ((((buffer1[1] << 8) | buffer1[2]) & 0xfff) + 3 != buffer1_len) + return 0; + if (dvbdmxfilter->doneq) { + struct dmx_section_filter *filter = &dvbdmxfilter->filter; + int i; + u8 xor, neq = 0; + + for (i = 0; i < DVB_DEMUX_MASK_MAX; i++) { + xor = filter->filter_value[i] ^ buffer1[i]; + neq |= dvbdmxfilter->maskandnotmode[i] & xor; + } + if (!neq) + return 0; + } + return dvbdmxfilter->feed->cb.sec(buffer1, buffer1_len, + buffer2, buffer2_len, + &dvbdmxfilter->filter, + DMX_OK); + case DMX_TYPE_TS: + if (!(dvbdmxfilter->feed->ts_type & TS_PACKET)) + return 0; + if (dvbdmxfilter->feed->ts_type & TS_PAYLOAD_ONLY) + return dvbdmxfilter->feed->cb.ts(buffer1, buffer1_len, + buffer2, buffer2_len, + &dvbdmxfilter->feed->feed.ts, + DMX_OK); + else + av7110_p2t_write(buffer1, buffer1_len, + dvbdmxfilter->feed->pid, + &av7110->p2t_filter[dvbdmxfilter->index]); + default: + return 0; + } +} + + +//#define DEBUG_TIMING +static inline void print_time(char *s) +{ +#ifdef DEBUG_TIMING + struct timeval tv; + do_gettimeofday(&tv); + printk("%s: %d.%d\n", s, (int)tv.tv_sec, (int)tv.tv_usec); +#endif +} + +#define DEBI_READ 0 +#define DEBI_WRITE 1 +static inline void start_debi_dma(struct av7110 *av7110, int dir, + unsigned long addr, unsigned int len) +{ + dprintk(8, "%c %08lx %u\n", dir == DEBI_READ ? 'R' : 'W', addr, len); + if (saa7146_wait_for_debi_done(av7110->dev, 0)) { + printk(KERN_ERR "%s: saa7146_wait_for_debi_done timed out\n", __FUNCTION__); + return; + } + + SAA7146_ISR_CLEAR(av7110->dev, MASK_19); /* for good measure */ + SAA7146_IER_ENABLE(av7110->dev, MASK_19); + if (len < 5) + len = 5; /* we want a real DEBI DMA */ + if (dir == DEBI_WRITE) + iwdebi(av7110, DEBISWAB, addr, 0, (len + 3) & ~3); + else + irdebi(av7110, DEBISWAB, addr, 0, len); +} + +static void debiirq(unsigned long data) +{ + struct av7110 *av7110 = (struct av7110 *) data; + int type = av7110->debitype; + int handle = (type >> 8) & 0x1f; + unsigned int xfer = 0; + + print_time("debi"); + dprintk(4, "type 0x%04x\n", type); + + if (type == -1) { + printk("DEBI irq oops @ %ld, psr:0x%08x, ssr:0x%08x\n", + jiffies, saa7146_read(av7110->dev, PSR), + saa7146_read(av7110->dev, SSR)); + goto debi_done; + } + av7110->debitype = -1; + + switch (type & 0xff) { + + case DATA_TS_RECORD: + dvb_dmx_swfilter_packets(&av7110->demux, + (const u8 *) av7110->debi_virt, + av7110->debilen / 188); + xfer = RX_BUFF; + break; + + case DATA_PES_RECORD: + if (av7110->demux.recording) + av7110_record_cb(&av7110->p2t[handle], + (u8 *) av7110->debi_virt, + av7110->debilen); + xfer = RX_BUFF; + break; + + case DATA_IPMPE: + case DATA_FSECTION: + case DATA_PIPING: + if (av7110->handle2filter[handle]) + DvbDmxFilterCallback((u8 *)av7110->debi_virt, + av7110->debilen, NULL, 0, + av7110->handle2filter[handle], + DMX_OK, av7110); + xfer = RX_BUFF; + break; + + case DATA_CI_GET: + { + u8 *data = av7110->debi_virt; + + if ((data[0] < 2) && data[2] == 0xff) { + int flags = 0; + if (data[5] > 0) + flags |= CA_CI_MODULE_PRESENT; + if (data[5] > 5) + flags |= CA_CI_MODULE_READY; + av7110->ci_slot[data[0]].flags = flags; + } else + ci_get_data(&av7110->ci_rbuffer, + av7110->debi_virt, + av7110->debilen); + xfer = RX_BUFF; + break; + } + + case DATA_COMMON_INTERFACE: + CI_handle(av7110, (u8 *)av7110->debi_virt, av7110->debilen); +#if 0 + { + int i; + + printk("av7110%d: ", av7110->num); + printk("%02x ", *(u8 *)av7110->debi_virt); + printk("%02x ", *(1+(u8 *)av7110->debi_virt)); + for (i = 2; i < av7110->debilen; i++) + printk("%02x ", (*(i+(unsigned char *)av7110->debi_virt))); + for (i = 2; i < av7110->debilen; i++) + printk("%c", chtrans(*(i+(unsigned char *)av7110->debi_virt))); + + printk("\n"); + } +#endif + xfer = RX_BUFF; + break; + + case DATA_DEBUG_MESSAGE: + ((s8*)av7110->debi_virt)[Reserved_SIZE - 1] = 0; + printk("%s\n", (s8 *) av7110->debi_virt); + xfer = RX_BUFF; + break; + + case DATA_CI_PUT: + dprintk(4, "debi DATA_CI_PUT\n"); + case DATA_MPEG_PLAY: + dprintk(4, "debi DATA_MPEG_PLAY\n"); + case DATA_BMP_LOAD: + dprintk(4, "debi DATA_BMP_LOAD\n"); + xfer = TX_BUFF; + break; + default: + break; + } +debi_done: + spin_lock(&av7110->debilock); + if (xfer) + iwdebi(av7110, DEBINOSWAP, xfer, 0, 2); + ARM_ClearMailBox(av7110); + spin_unlock(&av7110->debilock); +} + +/* irq from av7110 firmware writing the mailbox register in the DPRAM */ +static void gpioirq(unsigned long data) +{ + struct av7110 *av7110 = (struct av7110 *) data; + u32 rxbuf, txbuf; + int len; + + if (av7110->debitype != -1) + /* we shouldn't get any irq while a debi xfer is running */ + printk("dvb-ttpci: GPIO0 irq oops @ %ld, psr:0x%08x, ssr:0x%08x\n", + jiffies, saa7146_read(av7110->dev, PSR), + saa7146_read(av7110->dev, SSR)); + + if (saa7146_wait_for_debi_done(av7110->dev, 0)) { + printk(KERN_ERR "%s: saa7146_wait_for_debi_done timed out\n", __FUNCTION__); + BUG(); /* maybe we should try resetting the debi? */ + } + + spin_lock(&av7110->debilock); + ARM_ClearIrq(av7110); + + /* see what the av7110 wants */ + av7110->debitype = irdebi(av7110, DEBINOSWAP, IRQ_STATE, 0, 2); + av7110->debilen = irdebi(av7110, DEBINOSWAP, IRQ_STATE_EXT, 0, 2); + rxbuf = irdebi(av7110, DEBINOSWAP, RX_BUFF, 0, 2); + txbuf = irdebi(av7110, DEBINOSWAP, TX_BUFF, 0, 2); + len = (av7110->debilen + 3) & ~3; + + print_time("gpio"); + dprintk(8, "GPIO0 irq 0x%04x %d\n", av7110->debitype, av7110->debilen); + + switch (av7110->debitype & 0xff) { + + case DATA_TS_PLAY: + case DATA_PES_PLAY: + break; + + case DATA_MPEG_VIDEO_EVENT: + { + u32 h_ar; + struct video_event event; + + av7110->video_size.w = irdebi(av7110, DEBINOSWAP, STATUS_MPEG_WIDTH, 0, 2); + h_ar = irdebi(av7110, DEBINOSWAP, STATUS_MPEG_HEIGHT_AR, 0, 2); + + iwdebi(av7110, DEBINOSWAP, IRQ_STATE_EXT, 0, 2); + iwdebi(av7110, DEBINOSWAP, RX_BUFF, 0, 2); + + av7110->video_size.h = h_ar & 0xfff; + dprintk(8, "GPIO0 irq: DATA_MPEG_VIDEO_EVENT: w/h/ar = %u/%u/%u\n", + av7110->video_size.w, + av7110->video_size.h, + av7110->video_size.aspect_ratio); + + event.type = VIDEO_EVENT_SIZE_CHANGED; + event.u.size.w = av7110->video_size.w; + event.u.size.h = av7110->video_size.h; + switch ((h_ar >> 12) & 0xf) + { + case 3: + av7110->video_size.aspect_ratio = VIDEO_FORMAT_16_9; + event.u.size.aspect_ratio = VIDEO_FORMAT_16_9; + av7110->videostate.video_format = VIDEO_FORMAT_16_9; + break; + case 4: + av7110->video_size.aspect_ratio = VIDEO_FORMAT_221_1; + event.u.size.aspect_ratio = VIDEO_FORMAT_221_1; + av7110->videostate.video_format = VIDEO_FORMAT_221_1; + break; + default: + av7110->video_size.aspect_ratio = VIDEO_FORMAT_4_3; + event.u.size.aspect_ratio = VIDEO_FORMAT_4_3; + av7110->videostate.video_format = VIDEO_FORMAT_4_3; + } + dvb_video_add_event(av7110, &event); + break; + } + + case DATA_CI_PUT: + { + int avail; + struct dvb_ringbuffer *cibuf = &av7110->ci_wbuffer; + + avail = dvb_ringbuffer_avail(cibuf); + if (avail <= 2) { + iwdebi(av7110, DEBINOSWAP, IRQ_STATE_EXT, 0, 2); + iwdebi(av7110, DEBINOSWAP, TX_LEN, 0, 2); + iwdebi(av7110, DEBINOSWAP, TX_BUFF, 0, 2); + break; + } + len = DVB_RINGBUFFER_PEEK(cibuf, 0) << 8; + len |= DVB_RINGBUFFER_PEEK(cibuf, 1); + if (avail < len + 2) { + iwdebi(av7110, DEBINOSWAP, IRQ_STATE_EXT, 0, 2); + iwdebi(av7110, DEBINOSWAP, TX_LEN, 0, 2); + iwdebi(av7110, DEBINOSWAP, TX_BUFF, 0, 2); + break; + } + DVB_RINGBUFFER_SKIP(cibuf, 2); + + dvb_ringbuffer_read(cibuf, av7110->debi_virt, len, 0); + + iwdebi(av7110, DEBINOSWAP, TX_LEN, len, 2); + iwdebi(av7110, DEBINOSWAP, IRQ_STATE_EXT, len, 2); + dprintk(8, "DMA: CI\n"); + start_debi_dma(av7110, DEBI_WRITE, DPRAM_BASE + txbuf, len); + spin_unlock(&av7110->debilock); + wake_up(&cibuf->queue); + return; + } + + case DATA_MPEG_PLAY: + if (!av7110->playing) { + iwdebi(av7110, DEBINOSWAP, IRQ_STATE_EXT, 0, 2); + iwdebi(av7110, DEBINOSWAP, TX_LEN, 0, 2); + iwdebi(av7110, DEBINOSWAP, TX_BUFF, 0, 2); + break; + } + len = 0; + if (av7110->debitype & 0x100) { + spin_lock(&av7110->aout.lock); + len = av7110_pes_play(av7110->debi_virt, &av7110->aout, 2048); + spin_unlock(&av7110->aout.lock); + } + if (len <= 0 && (av7110->debitype & 0x200) + &&av7110->videostate.play_state != VIDEO_FREEZED) { + spin_lock(&av7110->avout.lock); + len = av7110_pes_play(av7110->debi_virt, &av7110->avout, 2048); + spin_unlock(&av7110->avout.lock); + } + if (len <= 0) { + iwdebi(av7110, DEBINOSWAP, IRQ_STATE_EXT, 0, 2); + iwdebi(av7110, DEBINOSWAP, TX_LEN, 0, 2); + iwdebi(av7110, DEBINOSWAP, TX_BUFF, 0, 2); + break; + } + dprintk(8, "GPIO0 PES_PLAY len=%04x\n", len); + iwdebi(av7110, DEBINOSWAP, TX_LEN, len, 2); + iwdebi(av7110, DEBINOSWAP, IRQ_STATE_EXT, len, 2); + dprintk(8, "DMA: MPEG_PLAY\n"); + start_debi_dma(av7110, DEBI_WRITE, DPRAM_BASE + txbuf, len); + spin_unlock(&av7110->debilock); + return; + + case DATA_BMP_LOAD: + len = av7110->debilen; + dprintk(8, "gpio DATA_BMP_LOAD len %d\n", len); + if (!len) { + av7110->bmp_state = BMP_LOADED; + iwdebi(av7110, DEBINOSWAP, IRQ_STATE_EXT, 0, 2); + iwdebi(av7110, DEBINOSWAP, TX_LEN, 0, 2); + iwdebi(av7110, DEBINOSWAP, TX_BUFF, 0, 2); + wake_up(&av7110->bmpq); + dprintk(8, "gpio DATA_BMP_LOAD done\n"); + break; + } + if (len > av7110->bmplen) + len = av7110->bmplen; + if (len > 2 * 1024) + len = 2 * 1024; + iwdebi(av7110, DEBINOSWAP, TX_LEN, len, 2); + iwdebi(av7110, DEBINOSWAP, IRQ_STATE_EXT, len, 2); + memcpy(av7110->debi_virt, av7110->bmpbuf+av7110->bmpp, len); + av7110->bmpp += len; + av7110->bmplen -= len; + dprintk(8, "gpio DATA_BMP_LOAD DMA len %d\n", len); + start_debi_dma(av7110, DEBI_WRITE, DPRAM_BASE+txbuf, len); + spin_unlock(&av7110->debilock); + return; + + case DATA_CI_GET: + case DATA_COMMON_INTERFACE: + case DATA_FSECTION: + case DATA_IPMPE: + case DATA_PIPING: + if (!len || len > 4 * 1024) { + iwdebi(av7110, DEBINOSWAP, RX_BUFF, 0, 2); + break; + } + /* fall through */ + + case DATA_TS_RECORD: + case DATA_PES_RECORD: + dprintk(8, "DMA: TS_REC etc.\n"); + start_debi_dma(av7110, DEBI_READ, DPRAM_BASE+rxbuf, len); + spin_unlock(&av7110->debilock); + return; + + case DATA_DEBUG_MESSAGE: + if (!len || len > 0xff) { + iwdebi(av7110, DEBINOSWAP, RX_BUFF, 0, 2); + break; + } + start_debi_dma(av7110, DEBI_READ, Reserved, len); + spin_unlock(&av7110->debilock); + return; + + case DATA_IRCOMMAND: + IR_handle(av7110, + swahw32(irdebi(av7110, DEBINOSWAP, Reserved, 0, 4))); + iwdebi(av7110, DEBINOSWAP, RX_BUFF, 0, 2); + break; + + default: + printk("dvb-ttpci: gpioirq unknown type=%d len=%d\n", + av7110->debitype, av7110->debilen); + break; + } + av7110->debitype = -1; + ARM_ClearMailBox(av7110); + spin_unlock(&av7110->debilock); +} + + +#ifdef CONFIG_DVB_AV7110_OSD +static int dvb_osd_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct av7110 *av7110 = (struct av7110 *) dvbdev->priv; + + dprintk(4, "%p\n", av7110); + + if (cmd == OSD_SEND_CMD) + return av7110_osd_cmd(av7110, (osd_cmd_t *) parg); + if (cmd == OSD_GET_CAPABILITY) + return av7110_osd_capability(av7110, (osd_cap_t *) parg); + + return -EINVAL; +} + + +static struct file_operations dvb_osd_fops = { + .owner = THIS_MODULE, + .ioctl = dvb_generic_ioctl, + .open = dvb_generic_open, + .release = dvb_generic_release, +}; + +static struct dvb_device dvbdev_osd = { + .priv = NULL, + .users = 1, + .writers = 1, + .fops = &dvb_osd_fops, + .kernel_ioctl = dvb_osd_ioctl, +}; +#endif /* CONFIG_DVB_AV7110_OSD */ + + +static inline int SetPIDs(struct av7110 *av7110, u16 vpid, u16 apid, u16 ttpid, + u16 subpid, u16 pcrpid) +{ + dprintk(4, "%p\n", av7110); + + if (vpid == 0x1fff || apid == 0x1fff || + ttpid == 0x1fff || subpid == 0x1fff || pcrpid == 0x1fff) { + vpid = apid = ttpid = subpid = pcrpid = 0; + av7110->pids[DMX_PES_VIDEO] = 0; + av7110->pids[DMX_PES_AUDIO] = 0; + av7110->pids[DMX_PES_TELETEXT] = 0; + av7110->pids[DMX_PES_PCR] = 0; + } + + return av7110_fw_cmd(av7110, COMTYPE_PIDFILTER, MultiPID, 5, + pcrpid, vpid, apid, ttpid, subpid); +} + +void ChangePIDs(struct av7110 *av7110, u16 vpid, u16 apid, u16 ttpid, + u16 subpid, u16 pcrpid) +{ + dprintk(4, "%p\n", av7110); + + if (down_interruptible(&av7110->pid_mutex)) + return; + + if (!(vpid & 0x8000)) + av7110->pids[DMX_PES_VIDEO] = vpid; + if (!(apid & 0x8000)) + av7110->pids[DMX_PES_AUDIO] = apid; + if (!(ttpid & 0x8000)) + av7110->pids[DMX_PES_TELETEXT] = ttpid; + if (!(pcrpid & 0x8000)) + av7110->pids[DMX_PES_PCR] = pcrpid; + + av7110->pids[DMX_PES_SUBTITLE] = 0; + + if (av7110->fe_synced) { + pcrpid = av7110->pids[DMX_PES_PCR]; + SetPIDs(av7110, vpid, apid, ttpid, subpid, pcrpid); + } + + up(&av7110->pid_mutex); +} + + +/****************************************************************************** + * hardware filter functions + ******************************************************************************/ + +static int StartHWFilter(struct dvb_demux_filter *dvbdmxfilter) +{ + struct dvb_demux_feed *dvbdmxfeed = dvbdmxfilter->feed; + struct av7110 *av7110 = (struct av7110 *) dvbdmxfeed->demux->priv; + u16 buf[20]; + int ret, i; + u16 handle; +// u16 mode = 0x0320; + u16 mode = 0xb96a; + + dprintk(4, "%p\n", av7110); + + if (dvbdmxfilter->type == DMX_TYPE_SEC) { + if (hw_sections) { + buf[4] = (dvbdmxfilter->filter.filter_value[0] << 8) | + dvbdmxfilter->maskandmode[0]; + for (i = 3; i < 18; i++) + buf[i + 4 - 2] = + (dvbdmxfilter->filter.filter_value[i] << 8) | + dvbdmxfilter->maskandmode[i]; + mode = 4; + } + } else if ((dvbdmxfeed->ts_type & TS_PACKET) && + !(dvbdmxfeed->ts_type & TS_PAYLOAD_ONLY)) { + av7110_p2t_init(&av7110->p2t_filter[dvbdmxfilter->index], dvbdmxfeed); + } + + buf[0] = (COMTYPE_PID_FILTER << 8) + AddPIDFilter; + buf[1] = 16; + buf[2] = dvbdmxfeed->pid; + buf[3] = mode; + + ret = av7110_fw_request(av7110, buf, 20, &handle, 1); + if (ret != 0 || handle >= 32) { + printk("dvb-ttpci: %s error buf %04x %04x %04x %04x " + "ret %x handle %04x\n", + __FUNCTION__, buf[0], buf[1], buf[2], buf[3], + ret, handle); + dvbdmxfilter->hw_handle = 0xffff; + return -1; + } + + av7110->handle2filter[handle] = dvbdmxfilter; + dvbdmxfilter->hw_handle = handle; + + return ret; +} + +static int StopHWFilter(struct dvb_demux_filter *dvbdmxfilter) +{ + struct av7110 *av7110 = (struct av7110 *) dvbdmxfilter->feed->demux->priv; + u16 buf[3]; + u16 answ[2]; + int ret; + u16 handle; + + dprintk(4, "%p\n", av7110); + + handle = dvbdmxfilter->hw_handle; + if (handle >= 32) { + printk("%s tried to stop invalid filter %04x, filter type = %x\n", + __FUNCTION__, handle, dvbdmxfilter->type); + return 0; + } + + av7110->handle2filter[handle] = NULL; + + buf[0] = (COMTYPE_PID_FILTER << 8) + DelPIDFilter; + buf[1] = 1; + buf[2] = handle; + ret = av7110_fw_request(av7110, buf, 3, answ, 2); + if (ret != 0 || answ[1] != handle) { + printk("dvb-ttpci: %s error cmd %04x %04x %04x ret %x " + "resp %04x %04x pid %d\n", + __FUNCTION__, buf[0], buf[1], buf[2], ret, + answ[0], answ[1], dvbdmxfilter->feed->pid); + ret = -1; + } + return ret; +} + + +static void dvb_feed_start_pid(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + struct av7110 *av7110 = (struct av7110 *) dvbdmx->priv; + u16 *pid = dvbdmx->pids, npids[5]; + int i; + + dprintk(4, "%p\n", av7110); + + npids[0] = npids[1] = npids[2] = npids[3] = npids[4] = 0xffff; + i = dvbdmxfeed->pes_type; + npids[i] = (pid[i]&0x8000) ? 0 : pid[i]; + if ((i == 2) && npids[i] && (dvbdmxfeed->ts_type & TS_PACKET)) { + npids[i] = 0; + ChangePIDs(av7110, npids[1], npids[0], npids[2], npids[3], npids[4]); + StartHWFilter(dvbdmxfeed->filter); + return; + } + if (dvbdmxfeed->pes_type <= 2 || dvbdmxfeed->pes_type == 4) + ChangePIDs(av7110, npids[1], npids[0], npids[2], npids[3], npids[4]); + + if (dvbdmxfeed->pes_type < 2 && npids[0]) + if (av7110->fe_synced) + av7110_fw_cmd(av7110, COMTYPE_PIDFILTER, Scan, 0); + + if ((dvbdmxfeed->ts_type & TS_PACKET)) { + if (dvbdmxfeed->pes_type == 0 && !(dvbdmx->pids[0] & 0x8000)) + av7110_av_start_record(av7110, RP_AUDIO, dvbdmxfeed); + if (dvbdmxfeed->pes_type == 1 && !(dvbdmx->pids[1] & 0x8000)) + av7110_av_start_record(av7110, RP_VIDEO, dvbdmxfeed); + } +} + +static void dvb_feed_stop_pid(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + struct av7110 *av7110 = (struct av7110 *) dvbdmx->priv; + u16 *pid = dvbdmx->pids, npids[5]; + int i; + + dprintk(4, "%p\n", av7110); + + if (dvbdmxfeed->pes_type <= 1) { + av7110_av_stop(av7110, dvbdmxfeed->pes_type ? RP_VIDEO : RP_AUDIO); + if (!av7110->rec_mode) + dvbdmx->recording = 0; + if (!av7110->playing) + dvbdmx->playing = 0; + } + npids[0] = npids[1] = npids[2] = npids[3] = npids[4] = 0xffff; + i = dvbdmxfeed->pes_type; + switch (i) { + case 2: //teletext + if (dvbdmxfeed->ts_type & TS_PACKET) + StopHWFilter(dvbdmxfeed->filter); + npids[2] = 0; + break; + case 0: + case 1: + case 4: + if (!pids_off) + return; + npids[i] = (pid[i]&0x8000) ? 0 : pid[i]; + break; + } + ChangePIDs(av7110, npids[1], npids[0], npids[2], npids[3], npids[4]); +} + +static int av7110_start_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct av7110 *av7110 = demux->priv; + + dprintk(4, "%p\n", av7110); + + if (!demux->dmx.frontend) + return -EINVAL; + + if (feed->pid > 0x1fff) + return -EINVAL; + + if (feed->type == DMX_TYPE_TS) { + if ((feed->ts_type & TS_DECODER) && + (feed->pes_type < DMX_TS_PES_OTHER)) { + switch (demux->dmx.frontend->source) { + case DMX_MEMORY_FE: + if (feed->ts_type & TS_DECODER) + if (feed->pes_type < 2 && + !(demux->pids[0] & 0x8000) && + !(demux->pids[1] & 0x8000)) { + dvb_ringbuffer_flush_spinlock_wakeup(&av7110->avout); + dvb_ringbuffer_flush_spinlock_wakeup(&av7110->aout); + av7110_av_start_play(av7110,RP_AV); + demux->playing = 1; + } + break; + default: + dvb_feed_start_pid(feed); + break; + } + } else if ((feed->ts_type & TS_PACKET) && + (demux->dmx.frontend->source != DMX_MEMORY_FE)) { + StartHWFilter(feed->filter); + } + } + + if (feed->type == DMX_TYPE_SEC) { + int i; + + for (i = 0; i < demux->filternum; i++) { + if (demux->filter[i].state != DMX_STATE_READY) + continue; + if (demux->filter[i].type != DMX_TYPE_SEC) + continue; + if (demux->filter[i].filter.parent != &feed->feed.sec) + continue; + demux->filter[i].state = DMX_STATE_GO; + if (demux->dmx.frontend->source != DMX_MEMORY_FE) + StartHWFilter(&demux->filter[i]); + } + } + + return 0; +} + + +static int av7110_stop_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct av7110 *av7110 = demux->priv; + + dprintk(4, "%p\n", av7110); + + if (feed->type == DMX_TYPE_TS) { + if (feed->ts_type & TS_DECODER) { + if (feed->pes_type >= DMX_TS_PES_OTHER || + !demux->pesfilter[feed->pes_type]) + return -EINVAL; + demux->pids[feed->pes_type] |= 0x8000; + demux->pesfilter[feed->pes_type] = NULL; + } + if (feed->ts_type & TS_DECODER && + feed->pes_type < DMX_TS_PES_OTHER) { + dvb_feed_stop_pid(feed); + } else + if ((feed->ts_type & TS_PACKET) && + (demux->dmx.frontend->source != DMX_MEMORY_FE)) + StopHWFilter(feed->filter); + } + + if (feed->type == DMX_TYPE_SEC) { + int i; + + for (i = 0; i<demux->filternum; i++) + if (demux->filter[i].state == DMX_STATE_GO && + demux->filter[i].filter.parent == &feed->feed.sec) { + demux->filter[i].state = DMX_STATE_READY; + if (demux->dmx.frontend->source != DMX_MEMORY_FE) + StopHWFilter(&demux->filter[i]); + } + } + + return 0; +} + + +static void restart_feeds(struct av7110 *av7110) +{ + struct dvb_demux *dvbdmx = &av7110->demux; + struct dvb_demux_feed *feed; + int mode; + int i; + + dprintk(4, "%p\n", av7110); + + mode = av7110->playing; + av7110->playing = 0; + av7110->rec_mode = 0; + + for (i = 0; i < dvbdmx->filternum; i++) { + feed = &dvbdmx->feed[i]; + if (feed->state == DMX_STATE_GO) + av7110_start_feed(feed); + } + + if (mode) + av7110_av_start_play(av7110, mode); +} + +static int dvb_get_stc(struct dmx_demux *demux, unsigned int num, + uint64_t *stc, unsigned int *base) +{ + int ret; + u16 fwstc[4]; + u16 tag = ((COMTYPE_REQUEST << 8) + ReqSTC); + struct dvb_demux *dvbdemux; + struct av7110 *av7110; + + /* pointer casting paranoia... */ + if (!demux) + BUG(); + dvbdemux = (struct dvb_demux *) demux->priv; + if (!dvbdemux) + BUG(); + av7110 = (struct av7110 *) dvbdemux->priv; + + dprintk(4, "%p\n", av7110); + + if (num != 0) + return -EINVAL; + + ret = av7110_fw_request(av7110, &tag, 0, fwstc, 4); + if (ret) { + printk(KERN_ERR "%s: av7110_fw_request error\n", __FUNCTION__); + return -EIO; + } + dprintk(2, "fwstc = %04hx %04hx %04hx %04hx\n", + fwstc[0], fwstc[1], fwstc[2], fwstc[3]); + + *stc = (((uint64_t) ((fwstc[3] & 0x8000) >> 15)) << 32) | + (((uint64_t) fwstc[1]) << 16) | ((uint64_t) fwstc[0]); + *base = 1; + + dprintk(4, "stc = %lu\n", (unsigned long)*stc); + + return 0; +} + + +/****************************************************************************** + * SEC device file operations + ******************************************************************************/ + + +static int av7110_set_tone(struct dvb_frontend* fe, fe_sec_tone_mode_t tone) +{ + struct av7110* av7110 = (struct av7110*) fe->dvb->priv; + + switch (tone) { + case SEC_TONE_ON: + Set22K(av7110, 1); + break; + + case SEC_TONE_OFF: + Set22K(av7110, 0); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int av7110_diseqc_send_master_cmd(struct dvb_frontend* fe, + struct dvb_diseqc_master_cmd* cmd) +{ + struct av7110* av7110 = fe->dvb->priv; + + av7110_diseqc_send(av7110, cmd->msg_len, cmd->msg, -1); + + return 0; +} + +static int av7110_diseqc_send_burst(struct dvb_frontend* fe, + fe_sec_mini_cmd_t minicmd) +{ + struct av7110* av7110 = fe->dvb->priv; + + av7110_diseqc_send(av7110, 0, NULL, minicmd); + + return 0; +} + +/* simplified code from budget-core.c */ +static int stop_ts_capture(struct av7110 *budget) +{ + dprintk(2, "budget: %p\n", budget); + + if (--budget->feeding1) + return budget->feeding1; + saa7146_write(budget->dev, MC1, MASK_20); /* DMA3 off */ + SAA7146_IER_DISABLE(budget->dev, MASK_10); + SAA7146_ISR_CLEAR(budget->dev, MASK_10); + return 0; +} + +static int start_ts_capture(struct av7110 *budget) +{ + dprintk(2, "budget: %p\n", budget); + + if (budget->feeding1) + return ++budget->feeding1; + memset(budget->grabbing, 0x00, TS_HEIGHT * TS_WIDTH); + budget->tsf = 0xff; + budget->ttbp = 0; + SAA7146_IER_ENABLE(budget->dev, MASK_10); /* VPE */ + saa7146_write(budget->dev, MC1, (MASK_04 | MASK_20)); /* DMA3 on */ + return ++budget->feeding1; +} + +static int budget_start_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct av7110 *budget = (struct av7110 *) demux->priv; + int status; + + dprintk(2, "av7110: %p\n", budget); + + spin_lock(&budget->feedlock1); + feed->pusi_seen = 0; /* have a clean section start */ + status = start_ts_capture(budget); + spin_unlock(&budget->feedlock1); + return status; +} + +static int budget_stop_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct av7110 *budget = (struct av7110 *) demux->priv; + int status; + + dprintk(2, "budget: %p\n", budget); + + spin_lock(&budget->feedlock1); + status = stop_ts_capture(budget); + spin_unlock(&budget->feedlock1); + return status; +} + +static void vpeirq(unsigned long data) +{ + struct av7110 *budget = (struct av7110 *) data; + u8 *mem = (u8 *) (budget->grabbing); + u32 olddma = budget->ttbp; + u32 newdma = saa7146_read(budget->dev, PCI_VDP3); + + if (!budgetpatch) { + printk("av7110.c: vpeirq() called while budgetpatch disabled!" + " check saa7146 IER register\n"); + BUG(); + } + /* nearest lower position divisible by 188 */ + newdma -= newdma % 188; + + if (newdma >= TS_BUFLEN) + return; + + budget->ttbp = newdma; + + if (!budget->feeding1 || (newdma == olddma)) + return; + +#if 0 + /* track rps1 activity */ + printk("vpeirq: %02x Event Counter 1 0x%04x\n", + mem[olddma], + saa7146_read(budget->dev, EC1R) & 0x3fff); +#endif + + if (newdma > olddma) + /* no wraparound, dump olddma..newdma */ + dvb_dmx_swfilter_packets(&budget->demux1, mem + olddma, (newdma - olddma) / 188); + else { + /* wraparound, dump olddma..buflen and 0..newdma */ + dvb_dmx_swfilter_packets(&budget->demux1, mem + olddma, (TS_BUFLEN - olddma) / 188); + dvb_dmx_swfilter_packets(&budget->demux1, mem, newdma / 188); + } +} + +static int av7110_register(struct av7110 *av7110) +{ + int ret, i; + struct dvb_demux *dvbdemux = &av7110->demux; + struct dvb_demux *dvbdemux1 = &av7110->demux1; + + dprintk(4, "%p\n", av7110); + + if (av7110->registered) + return -1; + + av7110->registered = 1; + + dvbdemux->priv = (void *) av7110; + + for (i = 0; i < 32; i++) + av7110->handle2filter[i] = NULL; + + dvbdemux->filternum = 32; + dvbdemux->feednum = 32; + dvbdemux->start_feed = av7110_start_feed; + dvbdemux->stop_feed = av7110_stop_feed; + dvbdemux->write_to_decoder = av7110_write_to_decoder; + dvbdemux->dmx.capabilities = (DMX_TS_FILTERING | DMX_SECTION_FILTERING | + DMX_MEMORY_BASED_FILTERING); + + dvb_dmx_init(&av7110->demux); + av7110->demux.dmx.get_stc = dvb_get_stc; + + av7110->dmxdev.filternum = 32; + av7110->dmxdev.demux = &dvbdemux->dmx; + av7110->dmxdev.capabilities = 0; + + dvb_dmxdev_init(&av7110->dmxdev, av7110->dvb_adapter); + + av7110->hw_frontend.source = DMX_FRONTEND_0; + + ret = dvbdemux->dmx.add_frontend(&dvbdemux->dmx, &av7110->hw_frontend); + + if (ret < 0) + return ret; + + av7110->mem_frontend.source = DMX_MEMORY_FE; + + ret = dvbdemux->dmx.add_frontend(&dvbdemux->dmx, &av7110->mem_frontend); + + if (ret < 0) + return ret; + + ret = dvbdemux->dmx.connect_frontend(&dvbdemux->dmx, + &av7110->hw_frontend); + if (ret < 0) + return ret; + + av7110_av_register(av7110); + av7110_ca_register(av7110); + +#ifdef CONFIG_DVB_AV7110_OSD + dvb_register_device(av7110->dvb_adapter, &av7110->osd_dev, + &dvbdev_osd, av7110, DVB_DEVICE_OSD); +#endif + + dvb_net_init(av7110->dvb_adapter, &av7110->dvb_net, &dvbdemux->dmx); + + if (budgetpatch) { + /* initialize software demux1 without its own frontend + * demux1 hardware is connected to frontend0 of demux0 + */ + dvbdemux1->priv = (void *) av7110; + + dvbdemux1->filternum = 256; + dvbdemux1->feednum = 256; + dvbdemux1->start_feed = budget_start_feed; + dvbdemux1->stop_feed = budget_stop_feed; + dvbdemux1->write_to_decoder = NULL; + + dvbdemux1->dmx.capabilities = (DMX_TS_FILTERING | DMX_SECTION_FILTERING | + DMX_MEMORY_BASED_FILTERING); + + dvb_dmx_init(&av7110->demux1); + + av7110->dmxdev1.filternum = 256; + av7110->dmxdev1.demux = &dvbdemux1->dmx; + av7110->dmxdev1.capabilities = 0; + + dvb_dmxdev_init(&av7110->dmxdev1, av7110->dvb_adapter); + + dvb_net_init(av7110->dvb_adapter, &av7110->dvb_net1, &dvbdemux1->dmx); + printk("dvb-ttpci: additional demux1 for budget-patch registered\n"); + } + return 0; +} + + +static void dvb_unregister(struct av7110 *av7110) +{ + struct dvb_demux *dvbdemux = &av7110->demux; + struct dvb_demux *dvbdemux1 = &av7110->demux1; + + dprintk(4, "%p\n", av7110); + + if (!av7110->registered) + return; + + if (budgetpatch) { + dvb_net_release(&av7110->dvb_net1); + dvbdemux->dmx.close(&dvbdemux1->dmx); + dvb_dmxdev_release(&av7110->dmxdev1); + dvb_dmx_release(&av7110->demux1); + } + + dvb_net_release(&av7110->dvb_net); + + dvbdemux->dmx.close(&dvbdemux->dmx); + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, &av7110->hw_frontend); + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, &av7110->mem_frontend); + + dvb_dmxdev_release(&av7110->dmxdev); + dvb_dmx_release(&av7110->demux); + + if (av7110->fe != NULL) + dvb_unregister_frontend(av7110->fe); + dvb_unregister_device(av7110->osd_dev); + av7110_av_unregister(av7110); + av7110_ca_unregister(av7110); +} + + +/**************************************************************************** + * I2C client commands + ****************************************************************************/ + +int i2c_writereg(struct av7110 *av7110, u8 id, u8 reg, u8 val) +{ + u8 msg[2] = { reg, val }; + struct i2c_msg msgs; + + msgs.flags = 0; + msgs.addr = id / 2; + msgs.len = 2; + msgs.buf = msg; + return i2c_transfer(&av7110->i2c_adap, &msgs, 1); +} + +#if 0 +u8 i2c_readreg(struct av7110 *av7110, u8 id, u8 reg) +{ + u8 mm1[] = {0x00}; + u8 mm2[] = {0x00}; + struct i2c_msg msgs[2]; + + msgs[0].flags = 0; + msgs[1].flags = I2C_M_RD; + msgs[0].addr = msgs[1].addr = id / 2; + mm1[0] = reg; + msgs[0].len = 1; msgs[1].len = 1; + msgs[0].buf = mm1; msgs[1].buf = mm2; + i2c_transfer(&av7110->i2c_adap, msgs, 2); + + return mm2[0]; +} +#endif + +/**************************************************************************** + * INITIALIZATION + ****************************************************************************/ + + +static int check_firmware(struct av7110* av7110) +{ + u32 crc = 0, len = 0; + unsigned char *ptr; + + /* check for firmware magic */ + ptr = av7110->bin_fw; + if (ptr[0] != 'A' || ptr[1] != 'V' || + ptr[2] != 'F' || ptr[3] != 'W') { + printk("dvb-ttpci: this is not an av7110 firmware\n"); + return -EINVAL; + } + ptr += 4; + + /* check dpram file */ + crc = ntohl(*(u32*) ptr); + ptr += 4; + len = ntohl(*(u32*) ptr); + ptr += 4; + if (len >= 512) { + printk("dvb-ttpci: dpram file is way to big.\n"); + return -EINVAL; + } + if (crc != crc32_le(0, ptr, len)) { + printk("dvb-ttpci: crc32 of dpram file does not match.\n"); + return -EINVAL; + } + av7110->bin_dpram = ptr; + av7110->size_dpram = len; + ptr += len; + + /* check root file */ + crc = ntohl(*(u32*) ptr); + ptr += 4; + len = ntohl(*(u32*) ptr); + ptr += 4; + + if (len <= 200000 || len >= 300000 || + len > ((av7110->bin_fw + av7110->size_fw) - ptr)) { + printk("dvb-ttpci: root file has strange size (%d). aborting.\n", len); + return -EINVAL; + } + if( crc != crc32_le(0, ptr, len)) { + printk("dvb-ttpci: crc32 of root file does not match.\n"); + return -EINVAL; + } + av7110->bin_root = ptr; + av7110->size_root = len; + return 0; +} + +#ifdef CONFIG_DVB_AV7110_FIRMWARE_FILE +#include "av7110_firm.h" +static void put_firmware(struct av7110* av7110) +{ + av7110->bin_fw = NULL; +} + +static inline int get_firmware(struct av7110* av7110) +{ + av7110->bin_fw = dvb_ttpci_fw; + av7110->size_fw = sizeof(dvb_ttpci_fw); + return check_firmware(av7110); +} +#else +static void put_firmware(struct av7110* av7110) +{ + vfree(av7110->bin_fw); +} + +static int get_firmware(struct av7110* av7110) +{ + int ret; + const struct firmware *fw; + + /* request the av7110 firmware, this will block until someone uploads it */ + ret = request_firmware(&fw, "dvb-ttpci-01.fw", &av7110->dev->pci->dev); + if (ret) { + if (ret == -ENOENT) { + printk(KERN_ERR "dvb-ttpci: could not load firmware," + " file not found: dvb-ttpci-01.fw\n"); + printk(KERN_ERR "dvb-ttpci: usually this should be in" + " /usr/lib/hotplug/firmware\n"); + printk(KERN_ERR "dvb-ttpci: and can be downloaded here" + " http://www.linuxtv.org/download/dvb/firmware/\n"); + } else + printk(KERN_ERR "dvb-ttpci: cannot request firmware" + " (error %i)\n", ret); + return -EINVAL; + } + + if (fw->size <= 200000) { + printk("dvb-ttpci: this firmware is way too small.\n"); + release_firmware(fw); + return -EINVAL; + } + + /* check if the firmware is available */ + av7110->bin_fw = (unsigned char *) vmalloc(fw->size); + if (NULL == av7110->bin_fw) { + dprintk(1, "out of memory\n"); + release_firmware(fw); + return -ENOMEM; + } + + memcpy(av7110->bin_fw, fw->data, fw->size); + av7110->size_fw = fw->size; + if ((ret = check_firmware(av7110))) + vfree(av7110->bin_fw); + + release_firmware(fw); + return ret; +} +#endif + + +static int alps_bsrv2_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + struct av7110* av7110 = (struct av7110*) fe->dvb->priv; + u8 pwr = 0; + u8 buf[4]; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = buf, .len = sizeof(buf) }; + u32 div = (params->frequency + 479500) / 125; + + if (params->frequency > 2000000) pwr = 3; + else if (params->frequency > 1800000) pwr = 2; + else if (params->frequency > 1600000) pwr = 1; + else if (params->frequency > 1200000) pwr = 0; + else if (params->frequency >= 1100000) pwr = 1; + else pwr = 2; + + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = ((div & 0x18000) >> 10) | 0x95; + buf[3] = (pwr << 6) | 0x30; + + // NOTE: since we're using a prescaler of 2, we set the + // divisor frequency to 62.5kHz and divide by 125 above + + if (i2c_transfer (&av7110->i2c_adap, &msg, 1) != 1) + return -EIO; + return 0; +} + +static struct ves1x93_config alps_bsrv2_config = { + .demod_address = 0x08, + .xin = 90100000UL, + .invert_pwm = 0, + .pll_set = alps_bsrv2_pll_set, +}; + + +static u8 alps_bsru6_inittab[] = { + 0x01, 0x15, + 0x02, 0x30, + 0x03, 0x00, + 0x04, 0x7d, /* F22FR = 0x7d, F22 = f_VCO / 128 / 0x7d = 22 kHz */ + 0x05, 0x35, /* I2CT = 0, SCLT = 1, SDAT = 1 */ + 0x06, 0x40, /* DAC not used, set to high impendance mode */ + 0x07, 0x00, /* DAC LSB */ + 0x08, 0x40, /* DiSEqC off, LNB power on OP2/LOCK pin on */ + 0x09, 0x00, /* FIFO */ + 0x0c, 0x51, /* OP1 ctl = Normal, OP1 val = 1 (LNB Power ON) */ + 0x0d, 0x82, /* DC offset compensation = ON, beta_agc1 = 2 */ + 0x0e, 0x23, /* alpha_tmg = 2, beta_tmg = 3 */ + 0x10, 0x3f, // AGC2 0x3d + 0x11, 0x84, + 0x12, 0xb5, // Lock detect: -64 Carrier freq detect:on + 0x15, 0xc9, // lock detector threshold + 0x16, 0x00, + 0x17, 0x00, + 0x18, 0x00, + 0x19, 0x00, + 0x1a, 0x00, + 0x1f, 0x50, + 0x20, 0x00, + 0x21, 0x00, + 0x22, 0x00, + 0x23, 0x00, + 0x28, 0x00, // out imp: normal out type: parallel FEC mode:0 + 0x29, 0x1e, // 1/2 threshold + 0x2a, 0x14, // 2/3 threshold + 0x2b, 0x0f, // 3/4 threshold + 0x2c, 0x09, // 5/6 threshold + 0x2d, 0x05, // 7/8 threshold + 0x2e, 0x01, + 0x31, 0x1f, // test all FECs + 0x32, 0x19, // viterbi and synchro search + 0x33, 0xfc, // rs control + 0x34, 0x93, // error control + 0x0f, 0x52, + 0xff, 0xff +}; + +static int alps_bsru6_set_symbol_rate(struct dvb_frontend* fe, u32 srate, u32 ratio) +{ + u8 aclk = 0; + u8 bclk = 0; + + if (srate < 1500000) { aclk = 0xb7; bclk = 0x47; } + else if (srate < 3000000) { aclk = 0xb7; bclk = 0x4b; } + else if (srate < 7000000) { aclk = 0xb7; bclk = 0x4f; } + else if (srate < 14000000) { aclk = 0xb7; bclk = 0x53; } + else if (srate < 30000000) { aclk = 0xb6; bclk = 0x53; } + else if (srate < 45000000) { aclk = 0xb4; bclk = 0x51; } + + stv0299_writereg(fe, 0x13, aclk); + stv0299_writereg(fe, 0x14, bclk); + stv0299_writereg(fe, 0x1f, (ratio >> 16) & 0xff); + stv0299_writereg(fe, 0x20, (ratio >> 8) & 0xff); + stv0299_writereg(fe, 0x21, (ratio ) & 0xf0); + + return 0; +} + +static int alps_bsru6_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + struct av7110* av7110 = (struct av7110*) fe->dvb->priv; + int ret; + u8 data[4]; + u32 div; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = data, .len = sizeof(data) }; + + if ((params->frequency < 950000) || (params->frequency > 2150000)) + return -EINVAL; + + div = (params->frequency + (125 - 1)) / 125; // round correctly + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = 0x80 | ((div & 0x18000) >> 10) | 4; + data[3] = 0xC4; + + if (params->frequency > 1530000) data[3] = 0xc0; + + ret = i2c_transfer(&av7110->i2c_adap, &msg, 1); + if (ret != 1) + return -EIO; + return 0; +} + +static struct stv0299_config alps_bsru6_config = { + + .demod_address = 0x68, + .inittab = alps_bsru6_inittab, + .mclk = 88000000UL, + .invert = 1, + .enhanced_tuning = 0, + .skip_reinit = 0, + .lock_output = STV0229_LOCKOUTPUT_1, + .volt13_op0_op1 = STV0299_VOLT13_OP1, + .min_delay_ms = 100, + .set_symbol_rate = alps_bsru6_set_symbol_rate, + .pll_set = alps_bsru6_pll_set, +}; + + + +static int alps_tdbe2_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + struct av7110* av7110 = fe->dvb->priv; + u32 div; + u8 data[4]; + struct i2c_msg msg = { .addr = 0x62, .flags = 0, .buf = data, .len = sizeof(data) }; + + div = (params->frequency + 35937500 + 31250) / 62500; + + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = 0x85 | ((div >> 10) & 0x60); + data[3] = (params->frequency < 174000000 ? 0x88 : params->frequency < 470000000 ? 0x84 : 0x81); + + if (i2c_transfer(&av7110->i2c_adap, &msg, 1) != 1) + return -EIO; + return 0; +} + +static struct ves1820_config alps_tdbe2_config = { + .demod_address = 0x09, + .xin = 57840000UL, + .invert = 1, + .selagc = VES1820_SELAGC_SIGNAMPERR, + .pll_set = alps_tdbe2_pll_set, +}; + + + + +static int grundig_29504_451_pll_set(struct dvb_frontend* fe, + struct dvb_frontend_parameters* params) +{ + struct av7110* av7110 = fe->dvb->priv; + u32 div; + u8 data[4]; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = data, .len = sizeof(data) }; + + div = params->frequency / 125; + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = 0x8e; + data[3] = 0x00; + + if (i2c_transfer(&av7110->i2c_adap, &msg, 1) != 1) + return -EIO; + return 0; +} + +static struct tda8083_config grundig_29504_451_config = { + .demod_address = 0x68, + .pll_set = grundig_29504_451_pll_set, +}; + + + +static int philips_cd1516_pll_set(struct dvb_frontend* fe, + struct dvb_frontend_parameters* params) +{ + struct av7110* av7110 = fe->dvb->priv; + u32 div; + u32 f = params->frequency; + u8 data[4]; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = data, .len = sizeof(data) }; + + div = (f + 36125000 + 31250) / 62500; + + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = 0x8e; + data[3] = (f < 174000000 ? 0xa1 : f < 470000000 ? 0x92 : 0x34); + + if (i2c_transfer(&av7110->i2c_adap, &msg, 1) != 1) + return -EIO; + return 0; +} + +static struct ves1820_config philips_cd1516_config = { + .demod_address = 0x09, + .xin = 57840000UL, + .invert = 1, + .selagc = VES1820_SELAGC_SIGNAMPERR, + .pll_set = philips_cd1516_pll_set, +}; + + + +static int alps_tdlb7_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + struct av7110* av7110 = fe->dvb->priv; + u32 div, pwr; + u8 data[4]; + struct i2c_msg msg = { .addr = 0x60, .flags = 0, .buf = data, .len = sizeof(data) }; + + div = (params->frequency + 36200000) / 166666; + + if (params->frequency <= 782000000) + pwr = 1; + else + pwr = 2; + + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = 0x85; + data[3] = pwr << 6; + + if (i2c_transfer(&av7110->i2c_adap, &msg, 1) != 1) + return -EIO; + return 0; +} + +static int alps_tdlb7_request_firmware(struct dvb_frontend* fe, const struct firmware **fw, char* name) +{ + struct av7110* av7110 = (struct av7110*) fe->dvb->priv; + + return request_firmware(fw, name, &av7110->dev->pci->dev); +} + +static struct sp8870_config alps_tdlb7_config = { + + .demod_address = 0x71, + .pll_set = alps_tdlb7_pll_set, + .request_firmware = alps_tdlb7_request_firmware, +}; + + + +static int nexusca_stv0297_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + struct av7110* av7110 = fe->dvb->priv; + u32 div; + u8 data[4]; + struct i2c_msg msg = { .addr = 0x63, .flags = 0, .buf = data, .len = sizeof(data) }; + struct i2c_msg readmsg = { .addr = 0x63, .flags = I2C_M_RD, .buf = data, .len = 1 }; + int i; + + div = (params->frequency + 36150000 + 31250) / 62500; + + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = 0xce; + + if (params->frequency < 45000000) + return -EINVAL; + else if (params->frequency < 137000000) + data[3] = 0x01; + else if (params->frequency < 403000000) + data[3] = 0x02; + else if (params->frequency < 860000000) + data[3] = 0x04; + else + return -EINVAL; + + stv0297_enable_plli2c(fe); + if (i2c_transfer(&av7110->i2c_adap, &msg, 1) != 1) { + printk("nexusca: pll transfer failed!\n"); + return -EIO; + } + + // wait for PLL lock + for(i = 0; i < 20; i++) { + + stv0297_enable_plli2c(fe); + if (i2c_transfer(&av7110->i2c_adap, &readmsg, 1) == 1) + if (data[0] & 0x40) break; + msleep(10); + } + + return 0; +} + +static struct stv0297_config nexusca_stv0297_config = { + + .demod_address = 0x1C, + .invert = 1, + .pll_set = nexusca_stv0297_pll_set, +}; + + + +static int grundig_29504_401_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + struct av7110* av7110 = (struct av7110*) fe->dvb->priv; + u32 div; + u8 cfg, cpump, band_select; + u8 data[4]; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = data, .len = sizeof(data) }; + + div = (36125000 + params->frequency) / 166666; + + cfg = 0x88; + + if (params->frequency < 175000000) cpump = 2; + else if (params->frequency < 390000000) cpump = 1; + else if (params->frequency < 470000000) cpump = 2; + else if (params->frequency < 750000000) cpump = 1; + else cpump = 3; + + if (params->frequency < 175000000) band_select = 0x0e; + else if (params->frequency < 470000000) band_select = 0x05; + else band_select = 0x03; + + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = ((div >> 10) & 0x60) | cfg; + data[3] = (cpump << 6) | band_select; + + if (i2c_transfer (&av7110->i2c_adap, &msg, 1) != 1) return -EIO; + return 0; +} + +static struct l64781_config grundig_29504_401_config = { + .demod_address = 0x55, + .pll_set = grundig_29504_401_pll_set, +}; + + + +static void av7110_fe_lock_fix(struct av7110* av7110, fe_status_t status) +{ + int synced = (status & FE_HAS_LOCK) ? 1 : 0; + + av7110->fe_status = status; + + if (av7110->fe_synced == synced) + return; + + av7110->fe_synced = synced; + + if (av7110->playing) + return; + + if (down_interruptible(&av7110->pid_mutex)) + return; + + if (av7110->fe_synced) { + SetPIDs(av7110, av7110->pids[DMX_PES_VIDEO], + av7110->pids[DMX_PES_AUDIO], + av7110->pids[DMX_PES_TELETEXT], 0, + av7110->pids[DMX_PES_PCR]); + av7110_fw_cmd(av7110, COMTYPE_PIDFILTER, Scan, 0); + } else { + SetPIDs(av7110, 0, 0, 0, 0, 0); + av7110_fw_cmd(av7110, COMTYPE_PID_FILTER, FlushTSQueue, 0); + av7110_wait_msgstate(av7110, GPMQBusy); + } + + up(&av7110->pid_mutex); +} + +static int av7110_fe_set_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + struct av7110* av7110 = fe->dvb->priv; + av7110_fe_lock_fix(av7110, 0); + return av7110->fe_set_frontend(fe, params); +} + +static int av7110_fe_init(struct dvb_frontend* fe) +{ + struct av7110* av7110 = fe->dvb->priv; + + av7110_fe_lock_fix(av7110, 0); + return av7110->fe_init(fe); +} + +static int av7110_fe_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct av7110* av7110 = fe->dvb->priv; + int ret; + + /* call the real implementation */ + ret = av7110->fe_read_status(fe, status); + if (ret) + return ret; + + if (((*status ^ av7110->fe_status) & FE_HAS_LOCK) && (*status & FE_HAS_LOCK)) { + av7110_fe_lock_fix(av7110, *status); + } + + return 0; +} + +static int av7110_fe_diseqc_reset_overload(struct dvb_frontend* fe) +{ + struct av7110* av7110 = fe->dvb->priv; + + av7110_fe_lock_fix(av7110, 0); + return av7110->fe_diseqc_reset_overload(fe); +} + +static int av7110_fe_diseqc_send_master_cmd(struct dvb_frontend* fe, + struct dvb_diseqc_master_cmd* cmd) +{ + struct av7110* av7110 = fe->dvb->priv; + + av7110_fe_lock_fix(av7110, 0); + return av7110->fe_diseqc_send_master_cmd(fe, cmd); +} + +static int av7110_fe_diseqc_send_burst(struct dvb_frontend* fe, fe_sec_mini_cmd_t minicmd) +{ + struct av7110* av7110 = fe->dvb->priv; + + av7110_fe_lock_fix(av7110, 0); + return av7110->fe_diseqc_send_burst(fe, minicmd); +} + +static int av7110_fe_set_tone(struct dvb_frontend* fe, fe_sec_tone_mode_t tone) +{ + struct av7110* av7110 = fe->dvb->priv; + + av7110_fe_lock_fix(av7110, 0); + return av7110->fe_set_tone(fe, tone); +} + +static int av7110_fe_set_voltage(struct dvb_frontend* fe, fe_sec_voltage_t voltage) +{ + struct av7110* av7110 = fe->dvb->priv; + + av7110_fe_lock_fix(av7110, 0); + return av7110->fe_set_voltage(fe, voltage); +} + +static int av7110_fe_dishnetwork_send_legacy_command(struct dvb_frontend* fe, unsigned int cmd) +{ + struct av7110* av7110 = fe->dvb->priv; + + av7110_fe_lock_fix(av7110, 0); + return av7110->fe_dishnetwork_send_legacy_command(fe, cmd); +} + +static u8 read_pwm(struct av7110* av7110) +{ + u8 b = 0xff; + u8 pwm; + struct i2c_msg msg[] = { { .addr = 0x50,.flags = 0,.buf = &b,.len = 1 }, + { .addr = 0x50,.flags = I2C_M_RD,.buf = &pwm,.len = 1} }; + + if ((i2c_transfer(&av7110->i2c_adap, msg, 2) != 2) || (pwm == 0xff)) + pwm = 0x48; + + return pwm; +} + +static int frontend_init(struct av7110 *av7110) +{ + int ret; + + if (av7110->dev->pci->subsystem_vendor == 0x110a) { + switch(av7110->dev->pci->subsystem_device) { + case 0x0000: // Fujitsu/Siemens DVB-Cable (ves1820/Philips CD1516(??)) + av7110->fe = ves1820_attach(&philips_cd1516_config, + &av7110->i2c_adap, read_pwm(av7110)); + break; + } + + } else if (av7110->dev->pci->subsystem_vendor == 0x13c2) { + switch(av7110->dev->pci->subsystem_device) { + case 0x0000: // Hauppauge/TT WinTV DVB-S rev1.X + case 0x0003: // Hauppauge/TT WinTV Nexus-S Rev 2.X + case 0x1002: // Hauppauge/TT WinTV DVB-S rev1.3SE + + // try the ALPS BSRV2 first of all + av7110->fe = ves1x93_attach(&alps_bsrv2_config, &av7110->i2c_adap); + if (av7110->fe) { + av7110->fe->ops->diseqc_send_master_cmd = av7110_diseqc_send_master_cmd; + av7110->fe->ops->diseqc_send_burst = av7110_diseqc_send_burst; + av7110->fe->ops->set_tone = av7110_set_tone; + break; + } + + // try the ALPS BSRU6 now + av7110->fe = stv0299_attach(&alps_bsru6_config, &av7110->i2c_adap); + if (av7110->fe) { + av7110->fe->ops->diseqc_send_master_cmd = av7110_diseqc_send_master_cmd; + av7110->fe->ops->diseqc_send_burst = av7110_diseqc_send_burst; + av7110->fe->ops->set_tone = av7110_set_tone; + break; + } + + // Try the grundig 29504-451 + av7110->fe = tda8083_attach(&grundig_29504_451_config, &av7110->i2c_adap); + if (av7110->fe) { + av7110->fe->ops->diseqc_send_master_cmd = av7110_diseqc_send_master_cmd; + av7110->fe->ops->diseqc_send_burst = av7110_diseqc_send_burst; + av7110->fe->ops->set_tone = av7110_set_tone; + break; + } + + /* Try DVB-C cards */ + switch(av7110->dev->pci->subsystem_device) { + case 0x0000: + /* Siemens DVB-C (full-length card) VES1820/Philips CD1516 */ + av7110->fe = ves1820_attach(&philips_cd1516_config, &av7110->i2c_adap, + read_pwm(av7110)); + break; + case 0x0003: + /* Haupauge DVB-C 2.1 VES1820/ALPS TDBE2 */ + av7110->fe = ves1820_attach(&alps_tdbe2_config, &av7110->i2c_adap, + read_pwm(av7110)); + break; + } + break; + + case 0x0001: // Hauppauge/TT Nexus-T premium rev1.X + + // ALPS TDLB7 + av7110->fe = sp8870_attach(&alps_tdlb7_config, &av7110->i2c_adap); + break; + + case 0x0002: // Hauppauge/TT DVB-C premium rev2.X + + av7110->fe = ves1820_attach(&alps_tdbe2_config, &av7110->i2c_adap, read_pwm(av7110)); + break; + + case 0x0006: /* Fujitsu-Siemens DVB-S rev 1.6 */ + /* Grundig 29504-451 */ + av7110->fe = tda8083_attach(&grundig_29504_451_config, &av7110->i2c_adap); + if (av7110->fe) { + av7110->fe->ops->diseqc_send_master_cmd = av7110_diseqc_send_master_cmd; + av7110->fe->ops->diseqc_send_burst = av7110_diseqc_send_burst; + av7110->fe->ops->set_tone = av7110_set_tone; + } + break; + + case 0x0008: // Hauppauge/TT DVB-T + + av7110->fe = l64781_attach(&grundig_29504_401_config, &av7110->i2c_adap); + break; + + case 0x000A: // Hauppauge/TT Nexus-CA rev1.X + + av7110->fe = stv0297_attach(&nexusca_stv0297_config, &av7110->i2c_adap, 0x7b); + if (av7110->fe) { + /* set TDA9819 into DVB mode */ + saa7146_setgpio(av7110->dev, 1, SAA7146_GPIO_OUTLO); // TDA9198 pin9(STD) + saa7146_setgpio(av7110->dev, 3, SAA7146_GPIO_OUTLO); // TDA9198 pin30(VIF) + + /* tuner on this needs a slower i2c bus speed */ + av7110->dev->i2c_bitrate = SAA7146_I2C_BUS_BIT_RATE_240; + break; + } + } + } + + if (!av7110->fe) { + /* FIXME: propagate the failure code from the lower layers */ + ret = -ENOMEM; + printk("dvb-ttpci: A frontend driver was not found for device %04x/%04x subsystem %04x/%04x\n", + av7110->dev->pci->vendor, + av7110->dev->pci->device, + av7110->dev->pci->subsystem_vendor, + av7110->dev->pci->subsystem_device); + } else { + FE_FUNC_OVERRIDE(av7110->fe->ops->init, av7110->fe_init, av7110_fe_init); + FE_FUNC_OVERRIDE(av7110->fe->ops->read_status, av7110->fe_read_status, av7110_fe_read_status); + FE_FUNC_OVERRIDE(av7110->fe->ops->diseqc_reset_overload, av7110->fe_diseqc_reset_overload, av7110_fe_diseqc_reset_overload); + FE_FUNC_OVERRIDE(av7110->fe->ops->diseqc_send_master_cmd, av7110->fe_diseqc_send_master_cmd, av7110_fe_diseqc_send_master_cmd); + FE_FUNC_OVERRIDE(av7110->fe->ops->diseqc_send_burst, av7110->fe_diseqc_send_burst, av7110_fe_diseqc_send_burst); + FE_FUNC_OVERRIDE(av7110->fe->ops->set_tone, av7110->fe_set_tone, av7110_fe_set_tone); + FE_FUNC_OVERRIDE(av7110->fe->ops->set_voltage, av7110->fe_set_voltage, av7110_fe_set_voltage;) + FE_FUNC_OVERRIDE(av7110->fe->ops->dishnetwork_send_legacy_command, av7110->fe_dishnetwork_send_legacy_command, av7110_fe_dishnetwork_send_legacy_command); + FE_FUNC_OVERRIDE(av7110->fe->ops->set_frontend, av7110->fe_set_frontend, av7110_fe_set_frontend); + + ret = dvb_register_frontend(av7110->dvb_adapter, av7110->fe); + if (ret < 0) { + printk("av7110: Frontend registration failed!\n"); + if (av7110->fe->ops->release) + av7110->fe->ops->release(av7110->fe); + av7110->fe = NULL; + } + } + return ret; +} + +/* Budgetpatch note: + * Original hardware design by Roberto Deza: + * There is a DVB_Wiki at + * http://212.227.36.83/linuxtv/wiki/index.php/Main_Page + * where is described this 'DVB TT Budget Patch', on Card Modding: + * http://212.227.36.83/linuxtv/wiki/index.php/DVB_TT_Budget_Patch + * On the short description there is also a link to a external file, + * with more details: + * http://perso.wanadoo.es/jesussolano/Ttf_tsc1.zip + * + * New software triggering design by Emard that works on + * original Roberto Deza's hardware: + * + * rps1 code for budgetpatch will copy internal HS event to GPIO3 pin. + * GPIO3 is in budget-patch hardware connectd to port B VSYNC + * HS is an internal event of 7146, accessible with RPS + * and temporarily raised high every n lines + * (n in defined in the RPS_THRESH1 counter threshold) + * I think HS is raised high on the beginning of the n-th line + * and remains high until this n-th line that triggered + * it is completely received. When the receiption of n-th line + * ends, HS is lowered. + * + * To transmit data over DMA, 7146 needs changing state at + * port B VSYNC pin. Any changing of port B VSYNC will + * cause some DMA data transfer, with more or less packets loss. + * It depends on the phase and frequency of VSYNC and + * the way of 7146 is instructed to trigger on port B (defined + * in DD1_INIT register, 3rd nibble from the right valid + * numbers are 0-7, see datasheet) + * + * The correct triggering can minimize packet loss, + * dvbtraffic should give this stable bandwidths: + * 22k transponder = 33814 kbit/s + * 27.5k transponder = 38045 kbit/s + * by experiment it is found that the best results + * (stable bandwidths and almost no packet loss) + * are obtained using DD1_INIT triggering number 2 + * (Va at rising edge of VS Fa = HS x VS-failing forced toggle) + * and a VSYNC phase that occurs in the middle of DMA transfer + * (about byte 188*512=96256 in the DMA window). + * + * Phase of HS is still not clear to me how to control, + * It just happens to be so. It can be seen if one enables + * RPS_IRQ and print Event Counter 1 in vpeirq(). Every + * time RPS_INTERRUPT is called, the Event Counter 1 will + * increment. That's how the 7146 is programmed to do event + * counting in this budget-patch.c + * I *think* HPS setting has something to do with the phase + * of HS but I cant be 100% sure in that. + * + * hardware debug note: a working budget card (including budget patch) + * with vpeirq() interrupt setup in mode "0x90" (every 64K) will + * generate 3 interrupts per 25-Hz DMA frame of 2*188*512 bytes + * and that means 3*25=75 Hz of interrupt freqency, as seen by + * watch cat /proc/interrupts + * + * If this frequency is 3x lower (and data received in the DMA + * buffer don't start with 0x47, but in the middle of packets, + * whose lengths appear to be like 188 292 188 104 etc. + * this means VSYNC line is not connected in the hardware. + * (check soldering pcb and pins) + * The same behaviour of missing VSYNC can be duplicated on budget + * cards, by seting DD1_INIT trigger mode 7 in 3rd nibble. + */ +static int av7110_attach(struct saa7146_dev* dev, struct saa7146_pci_extension_data *pci_ext) +{ + const int length = TS_WIDTH * TS_HEIGHT; + struct pci_dev *pdev = dev->pci; + struct av7110 *av7110; + int ret, count = 0; + + dprintk(4, "dev: %p\n", dev); + + /* Set RPS_IRQ to 1 to track rps1 activity. + * Enabling this won't send any interrupt to PC CPU. + */ +#define RPS_IRQ 0 + + if (budgetpatch == 1) { + budgetpatch = 0; + /* autodetect the presence of budget patch + * this only works if saa7146 has been recently + * reset with with MASK_31 to MC1 + * + * will wait for VBI_B event (vertical blank at port B) + * and will reset GPIO3 after VBI_B is detected. + * (GPIO3 should be raised high by CPU to + * test if GPIO3 will generate vertical blank signal + * in budget patch GPIO3 is connected to VSYNC_B + */ + + /* RESET SAA7146 */ + saa7146_write(dev, MC1, MASK_31); + /* autodetection success seems to be time-dependend after reset */ + + /* Fix VSYNC level */ + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); + /* set vsync_b triggering */ + saa7146_write(dev, DD1_STREAM_B, 0); + /* port B VSYNC at rising edge */ + saa7146_write(dev, DD1_INIT, 0x00000200); + saa7146_write(dev, BRS_CTRL, 0x00000000); // VBI + saa7146_write(dev, MC2, + 1 * (MASK_08 | MASK_24) | // BRS control + 0 * (MASK_09 | MASK_25) | // a + 1 * (MASK_10 | MASK_26) | // b + 0 * (MASK_06 | MASK_22) | // HPS_CTRL1 + 0 * (MASK_05 | MASK_21) | // HPS_CTRL2 + 0 * (MASK_01 | MASK_15) // DEBI + ); + + /* start writing RPS1 code from beginning */ + count = 0; + /* Disable RPS1 */ + saa7146_write(dev, MC1, MASK_29); + /* RPS1 timeout disable */ + saa7146_write(dev, RPS_TOV1, 0); + WRITE_RPS1(cpu_to_le32(CMD_PAUSE | EVT_VBI_B)); + WRITE_RPS1(cpu_to_le32(CMD_WR_REG_MASK | (GPIO_CTRL>>2))); + WRITE_RPS1(cpu_to_le32(GPIO3_MSK)); + WRITE_RPS1(cpu_to_le32(SAA7146_GPIO_OUTLO<<24)); +#if RPS_IRQ + /* issue RPS1 interrupt to increment counter */ + WRITE_RPS1(cpu_to_le32(CMD_INTERRUPT)); +#endif + WRITE_RPS1(cpu_to_le32(CMD_STOP)); + /* Jump to begin of RPS program as safety measure (p37) */ + WRITE_RPS1(cpu_to_le32(CMD_JUMP)); + WRITE_RPS1(cpu_to_le32(dev->d_rps1.dma_handle)); + +#if RPS_IRQ + /* set event counter 1 source as RPS1 interrupt (0x03) (rE4 p53) + * use 0x03 to track RPS1 interrupts - increase by 1 every gpio3 is toggled + * use 0x15 to track VPE interrupts - increase by 1 every vpeirq() is called + */ + saa7146_write(dev, EC1SSR, (0x03<<2) | 3 ); + /* set event counter 1 treshold to maximum allowed value (rEC p55) */ + saa7146_write(dev, ECT1R, 0x3fff ); +#endif + /* Set RPS1 Address register to point to RPS code (r108 p42) */ + saa7146_write(dev, RPS_ADDR1, dev->d_rps1.dma_handle); + /* Enable RPS1, (rFC p33) */ + saa7146_write(dev, MC1, (MASK_13 | MASK_29 )); + + mdelay(10); + /* now send VSYNC_B to rps1 by rising GPIO3 */ + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTHI); + mdelay(10); + /* if rps1 responded by lowering the GPIO3, + * then we have budgetpatch hardware + */ + if ((saa7146_read(dev, GPIO_CTRL) & 0x10000000) == 0) { + budgetpatch = 1; + printk("dvb-ttpci: BUDGET-PATCH DETECTED.\n"); + } + /* Disable RPS1 */ + saa7146_write(dev, MC1, ( MASK_29 )); +#if RPS_IRQ + printk("dvb-ttpci: Event Counter 1 0x%04x\n", saa7146_read(dev, EC1R) & 0x3fff ); +#endif + } + + /* prepare the av7110 device struct */ + av7110 = kmalloc(sizeof(struct av7110), GFP_KERNEL); + if (!av7110) { + dprintk(1, "out of memory\n"); + return -ENOMEM; + } + + memset(av7110, 0, sizeof(struct av7110)); + + av7110->card_name = (char*) pci_ext->ext_priv; + av7110->dev = dev; + dev->ext_priv = av7110; + + ret = get_firmware(av7110); + if (ret < 0) + goto err_kfree_0; + + ret = dvb_register_adapter(&av7110->dvb_adapter, av7110->card_name, + THIS_MODULE); + if (ret < 0) + goto err_put_firmware_1; + + /* the Siemens DVB needs this if you want to have the i2c chips + get recognized before the main driver is fully loaded */ + saa7146_write(dev, GPIO_CTRL, 0x500000); + +#ifdef I2C_ADAP_CLASS_TV_DIGITAL + av7110->i2c_adap.class = I2C_ADAP_CLASS_TV_DIGITAL; +#else + av7110->i2c_adap.class = I2C_CLASS_TV_DIGITAL; +#endif + strlcpy(av7110->i2c_adap.name, pci_ext->ext_priv, sizeof(av7110->i2c_adap.name)); + + saa7146_i2c_adapter_prepare(dev, &av7110->i2c_adap, SAA7146_I2C_BUS_BIT_RATE_120); /* 275 kHz */ + + ret = i2c_add_adapter(&av7110->i2c_adap); + if (ret < 0) + goto err_dvb_unregister_adapter_2; + + ttpci_eeprom_parse_mac(&av7110->i2c_adap, + av7110->dvb_adapter->proposed_mac); + ret = -ENOMEM; + + if (budgetpatch) { + spin_lock_init(&av7110->feedlock1); + av7110->grabbing = saa7146_vmalloc_build_pgtable(pdev, length, + &av7110->pt); + if (!av7110->grabbing) + goto err_i2c_del_3; + + saa7146_write(dev, PCI_BT_V1, 0x1c1f101f); + saa7146_write(dev, BCS_CTRL, 0x80400040); + /* set dd1 stream a & b */ + saa7146_write(dev, DD1_STREAM_B, 0x00000000); + saa7146_write(dev, DD1_INIT, 0x03000200); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + saa7146_write(dev, BRS_CTRL, 0x60000000); + saa7146_write(dev, BASE_ODD3, 0); + saa7146_write(dev, BASE_EVEN3, 0); + saa7146_write(dev, PROT_ADDR3, TS_WIDTH * TS_HEIGHT); + saa7146_write(dev, BASE_PAGE3, av7110->pt.dma | ME1 | 0x90); + + saa7146_write(dev, PITCH3, TS_WIDTH); + saa7146_write(dev, NUM_LINE_BYTE3, (TS_HEIGHT << 16) | TS_WIDTH); + + /* upload all */ + saa7146_write(dev, MC2, 0x077c077c); + saa7146_write(dev, GPIO_CTRL, 0x000000); +#if RPS_IRQ + /* set event counter 1 source as RPS1 interrupt (0x03) (rE4 p53) + * use 0x03 to track RPS1 interrupts - increase by 1 every gpio3 is toggled + * use 0x15 to track VPE interrupts - increase by 1 every vpeirq() is called + */ + saa7146_write(dev, EC1SSR, (0x03<<2) | 3 ); + /* set event counter 1 treshold to maximum allowed value (rEC p55) */ + saa7146_write(dev, ECT1R, 0x3fff ); +#endif + /* Setup BUDGETPATCH MAIN RPS1 "program" (p35) */ + count = 0; + + /* Wait Source Line Counter Threshold (p36) */ + WRITE_RPS1(cpu_to_le32(CMD_PAUSE | EVT_HS)); + /* Set GPIO3=1 (p42) */ + WRITE_RPS1(cpu_to_le32(CMD_WR_REG_MASK | (GPIO_CTRL>>2))); + WRITE_RPS1(cpu_to_le32(GPIO3_MSK)); + WRITE_RPS1(cpu_to_le32(SAA7146_GPIO_OUTHI<<24)); +#if RPS_IRQ + /* issue RPS1 interrupt */ + WRITE_RPS1(cpu_to_le32(CMD_INTERRUPT)); +#endif + /* Wait reset Source Line Counter Threshold (p36) */ + WRITE_RPS1(cpu_to_le32(CMD_PAUSE | RPS_INV | EVT_HS)); + /* Set GPIO3=0 (p42) */ + WRITE_RPS1(cpu_to_le32(CMD_WR_REG_MASK | (GPIO_CTRL>>2))); + WRITE_RPS1(cpu_to_le32(GPIO3_MSK)); + WRITE_RPS1(cpu_to_le32(SAA7146_GPIO_OUTLO<<24)); +#if RPS_IRQ + /* issue RPS1 interrupt */ + WRITE_RPS1(cpu_to_le32(CMD_INTERRUPT)); +#endif + /* Jump to begin of RPS program (p37) */ + WRITE_RPS1(cpu_to_le32(CMD_JUMP)); + WRITE_RPS1(cpu_to_le32(dev->d_rps1.dma_handle)); + + /* Fix VSYNC level */ + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); + /* Set RPS1 Address register to point to RPS code (r108 p42) */ + saa7146_write(dev, RPS_ADDR1, dev->d_rps1.dma_handle); + /* Set Source Line Counter Threshold, using BRS (rCC p43) + * It generates HS event every TS_HEIGHT lines + * this is related to TS_WIDTH set in register + * NUM_LINE_BYTE3. If NUM_LINE_BYTE low 16 bits + * are set to TS_WIDTH bytes (TS_WIDTH=2*188), + * then RPS_THRESH1 should be set to trigger + * every TS_HEIGHT (512) lines. + */ + saa7146_write(dev, RPS_THRESH1, (TS_HEIGHT*1) | MASK_12 ); + + /* Enable RPS1 (rFC p33) */ + saa7146_write(dev, MC1, (MASK_13 | MASK_29)); + + /* end of budgetpatch register initialization */ + tasklet_init (&av7110->vpe_tasklet, vpeirq, (unsigned long) av7110); + } else { + saa7146_write(dev, PCI_BT_V1, 0x1c00101f); + saa7146_write(dev, BCS_CTRL, 0x80400040); + + /* set dd1 stream a & b */ + saa7146_write(dev, DD1_STREAM_B, 0x00000000); + saa7146_write(dev, DD1_INIT, 0x03000000); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + + /* upload all */ + saa7146_write(dev, MC2, 0x077c077c); + saa7146_write(dev, GPIO_CTRL, 0x000000); + } + + tasklet_init (&av7110->debi_tasklet, debiirq, (unsigned long) av7110); + tasklet_init (&av7110->gpio_tasklet, gpioirq, (unsigned long) av7110); + + sema_init(&av7110->pid_mutex, 1); + + /* locks for data transfers from/to AV7110 */ + spin_lock_init(&av7110->debilock); + sema_init(&av7110->dcomlock, 1); + av7110->debitype = -1; + + /* default OSD window */ + av7110->osdwin = 1; + sema_init(&av7110->osd_sema, 1); + + /* ARM "watchdog" */ + init_waitqueue_head(&av7110->arm_wait); + av7110->arm_thread = NULL; + + /* allocate and init buffers */ + av7110->debi_virt = pci_alloc_consistent(pdev, 8192, &av7110->debi_bus); + if (!av7110->debi_virt) + goto err_saa71466_vfree_4; + + + av7110->iobuf = vmalloc(AVOUTLEN+AOUTLEN+BMPLEN+4*IPACKS); + if (!av7110->iobuf) + goto err_pci_free_5; + + ret = av7110_av_init(av7110); + if (ret < 0) + goto err_iobuf_vfree_6; + + /* init BMP buffer */ + av7110->bmpbuf = av7110->iobuf+AVOUTLEN+AOUTLEN; + init_waitqueue_head(&av7110->bmpq); + + ret = av7110_ca_init(av7110); + if (ret < 0) + goto err_av7110_av_exit_7; + + /* load firmware into AV7110 cards */ + ret = av7110_bootarm(av7110); + if (ret < 0) + goto err_av7110_ca_exit_8; + + ret = av7110_firmversion(av7110); + if (ret < 0) + goto err_stop_arm_9; + + if (FW_VERSION(av7110->arm_app)<0x2501) + printk ("dvb-ttpci: Warning, firmware version 0x%04x is too old. " + "System might be unstable!\n", FW_VERSION(av7110->arm_app)); + + ret = kernel_thread(arm_thread, (void *) av7110, 0); + if (ret < 0) + goto err_stop_arm_9; + + /* set initial volume in mixer struct */ + av7110->mixer.volume_left = volume; + av7110->mixer.volume_right = volume; + + init_av7110_av(av7110); + + ret = av7110_register(av7110); + if (ret < 0) + goto err_arm_thread_stop_10; + + /* special case DVB-C: these cards have an analog tuner + plus need some special handling, so we have separate + saa7146_ext_vv data for these... */ + ret = av7110_init_v4l(av7110); + if (ret < 0) + goto err_av7110_unregister_11; + + av7110->dvb_adapter->priv = av7110; + ret = frontend_init(av7110); + if (ret < 0) + goto err_av7110_exit_v4l_12; + +#if defined(CONFIG_INPUT_EVDEV) || defined(CONFIG_INPUT_EVDEV_MODULE) + av7110_ir_init(); +#endif + printk(KERN_INFO "dvb-ttpci: found av7110-%d.\n", av7110_num); + av7110_num++; +out: + return ret; + +err_av7110_exit_v4l_12: + av7110_exit_v4l(av7110); +err_av7110_unregister_11: + dvb_unregister(av7110); +err_arm_thread_stop_10: + av7110_arm_sync(av7110); +err_stop_arm_9: + /* Nothing to do. Rejoice. */ +err_av7110_ca_exit_8: + av7110_ca_exit(av7110); +err_av7110_av_exit_7: + av7110_av_exit(av7110); +err_iobuf_vfree_6: + vfree(av7110->iobuf); +err_pci_free_5: + pci_free_consistent(pdev, 8192, av7110->debi_virt, av7110->debi_bus); +err_saa71466_vfree_4: + if (!av7110->grabbing) + saa7146_pgtable_free(pdev, &av7110->pt); +err_i2c_del_3: + i2c_del_adapter(&av7110->i2c_adap); +err_dvb_unregister_adapter_2: + dvb_unregister_adapter(av7110->dvb_adapter); +err_put_firmware_1: + put_firmware(av7110); +err_kfree_0: + kfree(av7110); + goto out; +} + +static int av7110_detach(struct saa7146_dev* saa) +{ + struct av7110 *av7110 = saa->ext_priv; + dprintk(4, "%p\n", av7110); + + if (budgetpatch) { + /* Disable RPS1 */ + saa7146_write(saa, MC1, MASK_29); + /* VSYNC LOW (inactive) */ + saa7146_setgpio(saa, 3, SAA7146_GPIO_OUTLO); + saa7146_write(saa, MC1, MASK_20); /* DMA3 off */ + SAA7146_IER_DISABLE(saa, MASK_10); + SAA7146_ISR_CLEAR(saa, MASK_10); + msleep(50); + tasklet_kill(&av7110->vpe_tasklet); + saa7146_pgtable_free(saa->pci, &av7110->pt); + } + av7110_exit_v4l(av7110); + + av7110_arm_sync(av7110); + + tasklet_kill(&av7110->debi_tasklet); + tasklet_kill(&av7110->gpio_tasklet); + + dvb_unregister(av7110); + + SAA7146_IER_DISABLE(saa, MASK_19 | MASK_03); + SAA7146_ISR_CLEAR(saa, MASK_19 | MASK_03); + + av7110_ca_exit(av7110); + av7110_av_exit(av7110); + + vfree(av7110->iobuf); + pci_free_consistent(saa->pci, 8192, av7110->debi_virt, + av7110->debi_bus); + + i2c_del_adapter(&av7110->i2c_adap); + + dvb_unregister_adapter (av7110->dvb_adapter); + + av7110_num--; + + put_firmware(av7110); + + kfree(av7110); + + saa->ext_priv = NULL; + + return 0; +} + + +static void av7110_irq(struct saa7146_dev* dev, u32 *isr) +{ + struct av7110 *av7110 = dev->ext_priv; + + //print_time("av7110_irq"); + + /* Note: Don't try to handle the DEBI error irq (MASK_18), in + * intel mode the timeout is asserted all the time... + */ + + if (*isr & MASK_19) { + //printk("av7110_irq: DEBI\n"); + /* Note 1: The DEBI irq is level triggered: We must enable it + * only after we started a DMA xfer, and disable it here + * immediately, or it will be signalled all the time while + * DEBI is idle. + * Note 2: You would think that an irq which is masked is + * not signalled by the hardware. Not so for the SAA7146: + * An irq is signalled as long as the corresponding bit + * in the ISR is set, and disabling irqs just prevents the + * hardware from setting the ISR bit. This means a) that we + * must clear the ISR *after* disabling the irq (which is why + * we must do it here even though saa7146_core did it already), + * and b) that if we were to disable an edge triggered irq + * (like the gpio irqs sadly are) temporarily we would likely + * loose some. This sucks :-( + */ + SAA7146_IER_DISABLE(av7110->dev, MASK_19); + SAA7146_ISR_CLEAR(av7110->dev, MASK_19); + tasklet_schedule(&av7110->debi_tasklet); + } + + if (*isr & MASK_03) { + //printk("av7110_irq: GPIO\n"); + tasklet_schedule(&av7110->gpio_tasklet); + } + + if ((*isr & MASK_10) && budgetpatch) + tasklet_schedule(&av7110->vpe_tasklet); +} + + +static struct saa7146_extension av7110_extension; + +#define MAKE_AV7110_INFO(x_var,x_name) \ +static struct saa7146_pci_extension_data x_var = { \ + .ext_priv = x_name, \ + .ext = &av7110_extension } + +MAKE_AV7110_INFO(tts_1_X, "Technotrend/Hauppauge WinTV DVB-S rev1.X"); +MAKE_AV7110_INFO(ttt_1_X, "Technotrend/Hauppauge WinTV DVB-T rev1.X"); +MAKE_AV7110_INFO(ttc_1_X, "Technotrend/Hauppauge WinTV Nexus-CA rev1.X"); +MAKE_AV7110_INFO(ttc_2_X, "Technotrend/Hauppauge WinTV DVB-C rev2.X"); +MAKE_AV7110_INFO(tts_2_X, "Technotrend/Hauppauge WinTV Nexus-S rev2.X"); +MAKE_AV7110_INFO(tts_1_3se, "Technotrend/Hauppauge WinTV DVB-S rev1.3 SE"); +MAKE_AV7110_INFO(ttt, "Technotrend/Hauppauge DVB-T"); +MAKE_AV7110_INFO(fsc, "Fujitsu Siemens DVB-C"); +MAKE_AV7110_INFO(fss, "Fujitsu Siemens DVB-S rev1.6"); + +static struct pci_device_id pci_tbl[] = { + MAKE_EXTENSION_PCI(tts_1_X, 0x13c2, 0x0000), + MAKE_EXTENSION_PCI(ttt_1_X, 0x13c2, 0x0001), + MAKE_EXTENSION_PCI(ttc_2_X, 0x13c2, 0x0002), + MAKE_EXTENSION_PCI(tts_2_X, 0x13c2, 0x0003), + MAKE_EXTENSION_PCI(tts_1_3se, 0x13c2, 0x1002), + MAKE_EXTENSION_PCI(fsc, 0x110a, 0x0000), + MAKE_EXTENSION_PCI(ttc_1_X, 0x13c2, 0x000a), + MAKE_EXTENSION_PCI(fss, 0x13c2, 0x0006), + MAKE_EXTENSION_PCI(ttt, 0x13c2, 0x0008), + +/* MAKE_EXTENSION_PCI(???, 0x13c2, 0x0004), UNDEFINED CARD */ // Galaxis DVB PC-Sat-Carte +/* MAKE_EXTENSION_PCI(???, 0x13c2, 0x0005), UNDEFINED CARD */ // Technisat SkyStar1 +/* MAKE_EXTENSION_PCI(???, 0x13c2, 0x0009), UNDEFINED CARD */ // TT/Hauppauge WinTV Nexus-CA v???? + + { + .vendor = 0, + } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + + +static struct saa7146_extension av7110_extension = { + .name = "dvb\0", + .flags = SAA7146_I2C_SHORT_DELAY, + + .module = THIS_MODULE, + .pci_tbl = &pci_tbl[0], + .attach = av7110_attach, + .detach = av7110_detach, + + .irq_mask = MASK_19 | MASK_03 | MASK_10, + .irq_func = av7110_irq, +}; + + +static int __init av7110_init(void) +{ + int retval; + retval = saa7146_register_extension(&av7110_extension); + return retval; +} + + +static void __exit av7110_exit(void) +{ +#if defined(CONFIG_INPUT_EVDEV) || defined(CONFIG_INPUT_EVDEV_MODULE) + av7110_ir_exit(); +#endif + saa7146_unregister_extension(&av7110_extension); +} + +module_init(av7110_init); +module_exit(av7110_exit); + +MODULE_DESCRIPTION("driver for the SAA7146 based AV110 PCI DVB cards by " + "Siemens, Technotrend, Hauppauge"); +MODULE_AUTHOR("Ralph Metzler, Marcus Metzler, others"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/ttpci/av7110.h b/drivers/media/dvb/ttpci/av7110.h new file mode 100644 index 000000000000..5070e0523da7 --- /dev/null +++ b/drivers/media/dvb/ttpci/av7110.h @@ -0,0 +1,284 @@ +#ifndef _AV7110_H_ +#define _AV7110_H_ + +#include <linux/interrupt.h> +#include <linux/socket.h> +#include <linux/netdevice.h> +#include <linux/i2c.h> + +#ifdef CONFIG_DEVFS_FS +#include <linux/devfs_fs_kernel.h> +#endif + +#include <linux/dvb/video.h> +#include <linux/dvb/audio.h> +#include <linux/dvb/dmx.h> +#include <linux/dvb/ca.h> +#include <linux/dvb/osd.h> +#include <linux/dvb/net.h> + +#include "dvbdev.h" +#include "demux.h" +#include "dvb_demux.h" +#include "dmxdev.h" +#include "dvb_filter.h" +#include "dvb_net.h" +#include "dvb_ringbuffer.h" +#include "dvb_frontend.h" +#include "ves1820.h" +#include "ves1x93.h" +#include "stv0299.h" +#include "tda8083.h" +#include "sp8870.h" +#include "stv0297.h" +#include "l64781.h" + +#include <media/saa7146_vv.h> + + +#define ANALOG_TUNER_VES1820 1 +#define ANALOG_TUNER_STV0297 2 +#define ANALOG_TUNER_VBI 0x100 + +extern int av7110_debug; + +#define dprintk(level,args...) \ + do { if ((av7110_debug & level)) { printk("dvb-ttpci: %s(): ", __FUNCTION__); printk(args); } } while (0) + +#define MAXFILT 32 + +enum {AV_PES_STREAM, PS_STREAM, TS_STREAM, PES_STREAM}; + +struct av7110_p2t { + u8 pes[TS_SIZE]; + u8 counter; + long int pos; + int frags; + struct dvb_demux_feed *feed; +}; + +/* video MPEG decoder events: */ +/* (code copied from dvb_frontend.c, should maybe be factored out...) */ +#define MAX_VIDEO_EVENT 8 +struct dvb_video_events { + struct video_event events[MAX_VIDEO_EVENT]; + int eventw; + int eventr; + int overflow; + wait_queue_head_t wait_queue; + spinlock_t lock; +}; + + +/* place to store all the necessary device information */ +struct av7110 { + + /* devices */ + + struct dvb_device dvb_dev; + struct dvb_net dvb_net; + + struct video_device *v4l_dev; + struct video_device *vbi_dev; + + struct saa7146_dev *dev; + + struct i2c_adapter i2c_adap; + + char *card_name; + + /* support for analog module of dvb-c */ + int analog_tuner_flags; + int current_input; + u32 current_freq; + + struct tasklet_struct debi_tasklet; + struct tasklet_struct gpio_tasklet; + + int adac_type; /* audio DAC type */ +#define DVB_ADAC_TI 0 +#define DVB_ADAC_CRYSTAL 1 +#define DVB_ADAC_MSP 2 +#define DVB_ADAC_NONE -1 + + + /* buffers */ + + void *iobuf; /* memory for all buffers */ + struct dvb_ringbuffer avout; /* buffer for video or A/V mux */ +#define AVOUTLEN (128*1024) + struct dvb_ringbuffer aout; /* buffer for audio */ +#define AOUTLEN (64*1024) + void *bmpbuf; +#define BMPLEN (8*32768+1024) + + /* bitmap buffers and states */ + + int bmpp; + int bmplen; + volatile int bmp_state; +#define BMP_NONE 0 +#define BMP_LOADING 1 +#define BMP_LOADINGS 2 +#define BMP_LOADED 3 + wait_queue_head_t bmpq; + + + /* DEBI and polled command interface */ + + spinlock_t debilock; + struct semaphore dcomlock; + volatile int debitype; + volatile int debilen; + + + /* Recording and playback flags */ + + int rec_mode; + int playing; +#define RP_NONE 0 +#define RP_VIDEO 1 +#define RP_AUDIO 2 +#define RP_AV 3 + + + /* OSD */ + + int osdwin; /* currently active window */ + u16 osdbpp[8]; + struct semaphore osd_sema; + + /* CA */ + + ca_slot_info_t ci_slot[2]; + + int vidmode; + struct dmxdev dmxdev; + struct dvb_demux demux; + + struct dmx_frontend hw_frontend; + struct dmx_frontend mem_frontend; + + /* for budget mode demux1 */ + struct dmxdev dmxdev1; + struct dvb_demux demux1; + struct dvb_net dvb_net1; + spinlock_t feedlock1; + int feeding1; + u8 tsf; + u32 ttbp; + unsigned char *grabbing; + struct saa7146_pgtable pt; + struct tasklet_struct vpe_tasklet; + + int fe_synced; + struct semaphore pid_mutex; + + int video_blank; + struct video_status videostate; + int display_ar; + int trickmode; +#define TRICK_NONE 0 +#define TRICK_FAST 1 +#define TRICK_SLOW 2 +#define TRICK_FREEZE 3 + struct audio_status audiostate; + + struct dvb_demux_filter *handle2filter[32]; + struct av7110_p2t p2t_filter[MAXFILT]; + struct dvb_filter_pes2ts p2t[2]; + struct ipack ipack[2]; + u8 *kbuf[2]; + + int sinfo; + int feeding; + + int arm_errors; + int registered; + + + /* AV711X */ + + u32 arm_fw; + u32 arm_rtsl; + u32 arm_vid; + u32 arm_app; + u32 avtype; + int arm_ready; + struct task_struct *arm_thread; + wait_queue_head_t arm_wait; + u16 arm_loops; + int arm_rmmod; + + void *debi_virt; + dma_addr_t debi_bus; + + u16 pids[DMX_PES_OTHER]; + + struct dvb_ringbuffer ci_rbuffer; + struct dvb_ringbuffer ci_wbuffer; + + struct audio_mixer mixer; + + struct dvb_adapter *dvb_adapter; + struct dvb_device *video_dev; + struct dvb_device *audio_dev; + struct dvb_device *ca_dev; + struct dvb_device *osd_dev; + + struct dvb_video_events video_events; + video_size_t video_size; + + u32 ir_config; + + /* firmware stuff */ + unsigned char *bin_fw; + unsigned long size_fw; + + unsigned char *bin_dpram; + unsigned long size_dpram; + + unsigned char *bin_root; + unsigned long size_root; + + struct dvb_frontend* fe; + fe_status_t fe_status; + int (*fe_init)(struct dvb_frontend* fe); + int (*fe_read_status)(struct dvb_frontend* fe, fe_status_t* status); + int (*fe_diseqc_reset_overload)(struct dvb_frontend* fe); + int (*fe_diseqc_send_master_cmd)(struct dvb_frontend* fe, struct dvb_diseqc_master_cmd* cmd); + int (*fe_diseqc_send_burst)(struct dvb_frontend* fe, fe_sec_mini_cmd_t minicmd); + int (*fe_set_tone)(struct dvb_frontend* fe, fe_sec_tone_mode_t tone); + int (*fe_set_voltage)(struct dvb_frontend* fe, fe_sec_voltage_t voltage); + int (*fe_dishnetwork_send_legacy_command)(struct dvb_frontend* fe, unsigned int cmd); + int (*fe_set_frontend)(struct dvb_frontend* fe, struct dvb_frontend_parameters* params); +}; + + +extern void ChangePIDs(struct av7110 *av7110, u16 vpid, u16 apid, u16 ttpid, + u16 subpid, u16 pcrpid); + +extern void av7110_register_irc_handler(void (*func)(u32)); +extern void av7110_unregister_irc_handler(void (*func)(u32)); +extern void av7110_setup_irc_config (struct av7110 *av7110, u32 ir_config); + +extern int av7110_ir_init (void); +extern void av7110_ir_exit (void); + +/* msp3400 i2c subaddresses */ +#define MSP_WR_DEM 0x10 +#define MSP_RD_DEM 0x11 +#define MSP_WR_DSP 0x12 +#define MSP_RD_DSP 0x13 + +extern int i2c_writereg(struct av7110 *av7110, u8 id, u8 reg, u8 val); +extern u8 i2c_readreg(struct av7110 *av7110, u8 id, u8 reg); +extern int msp_writereg(struct av7110 *av7110, u8 dev, u16 reg, u16 val); +extern int msp_readreg(struct av7110 *av7110, u8 dev, u16 reg, u16 *val); + + +extern int av7110_init_analog_module(struct av7110 *av7110); +extern int av7110_init_v4l(struct av7110 *av7110); +extern int av7110_exit_v4l(struct av7110 *av7110); + +#endif /* _AV7110_H_ */ diff --git a/drivers/media/dvb/ttpci/av7110_av.c b/drivers/media/dvb/ttpci/av7110_av.c new file mode 100644 index 000000000000..d77e8a00688f --- /dev/null +++ b/drivers/media/dvb/ttpci/av7110_av.c @@ -0,0 +1,1459 @@ +/* + * av7110_av.c: audio and video MPEG decoder stuff + * + * Copyright (C) 1999-2002 Ralph Metzler + * & Marcus Metzler for convergence integrated media GmbH + * + * originally based on code by: + * Copyright (C) 1998,1999 Christian Theiss <mistert@rz.fh-augsburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + * + * the project's page is at http://www.linuxtv.org/dvb/ + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/byteorder/swabb.h> +#include <linux/smp_lock.h> +#include <linux/fs.h> + +#include "av7110.h" +#include "av7110_hw.h" +#include "av7110_av.h" +#include "av7110_ipack.h" + +/* MPEG-2 (ISO 13818 / H.222.0) stream types */ +#define PROG_STREAM_MAP 0xBC +#define PRIVATE_STREAM1 0xBD +#define PADDING_STREAM 0xBE +#define PRIVATE_STREAM2 0xBF +#define AUDIO_STREAM_S 0xC0 +#define AUDIO_STREAM_E 0xDF +#define VIDEO_STREAM_S 0xE0 +#define VIDEO_STREAM_E 0xEF +#define ECM_STREAM 0xF0 +#define EMM_STREAM 0xF1 +#define DSM_CC_STREAM 0xF2 +#define ISO13522_STREAM 0xF3 +#define PROG_STREAM_DIR 0xFF + +#define PTS_DTS_FLAGS 0xC0 + +//pts_dts flags +#define PTS_ONLY 0x80 +#define PTS_DTS 0xC0 +#define TS_SIZE 188 +#define TRANS_ERROR 0x80 +#define PAY_START 0x40 +#define TRANS_PRIO 0x20 +#define PID_MASK_HI 0x1F +//flags +#define TRANS_SCRMBL1 0x80 +#define TRANS_SCRMBL2 0x40 +#define ADAPT_FIELD 0x20 +#define PAYLOAD 0x10 +#define COUNT_MASK 0x0F + +// adaptation flags +#define DISCON_IND 0x80 +#define RAND_ACC_IND 0x40 +#define ES_PRI_IND 0x20 +#define PCR_FLAG 0x10 +#define OPCR_FLAG 0x08 +#define SPLICE_FLAG 0x04 +#define TRANS_PRIV 0x02 +#define ADAP_EXT_FLAG 0x01 + +// adaptation extension flags +#define LTW_FLAG 0x80 +#define PIECE_RATE 0x40 +#define SEAM_SPLICE 0x20 + + +static void p_to_t(u8 const *buf, long int length, u16 pid, + u8 *counter, struct dvb_demux_feed *feed); + + +int av7110_record_cb(struct dvb_filter_pes2ts *p2t, u8 *buf, size_t len) +{ + struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *) p2t->priv; + + if (!(dvbdmxfeed->ts_type & TS_PACKET)) + return 0; + if (buf[3] == 0xe0) // video PES do not have a length in TS + buf[4] = buf[5] = 0; + if (dvbdmxfeed->ts_type & TS_PAYLOAD_ONLY) + return dvbdmxfeed->cb.ts(buf, len, NULL, 0, + &dvbdmxfeed->feed.ts, DMX_OK); + else + return dvb_filter_pes2ts(p2t, buf, len, 1); +} + +static int dvb_filter_pes2ts_cb(void *priv, unsigned char *data) +{ + struct dvb_demux_feed *dvbdmxfeed = (struct dvb_demux_feed *) priv; + + dvbdmxfeed->cb.ts(data, 188, NULL, 0, + &dvbdmxfeed->feed.ts, DMX_OK); + return 0; +} + +int av7110_av_start_record(struct av7110 *av7110, int av, + struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + + dprintk(2, "av7110:%p, , dvb_demux_feed:%p\n", av7110, dvbdmxfeed); + + if (av7110->playing || (av7110->rec_mode & av)) + return -EBUSY; + av7110_fw_cmd(av7110, COMTYPE_REC_PLAY, __Stop, 0); + dvbdmx->recording = 1; + av7110->rec_mode |= av; + + switch (av7110->rec_mode) { + case RP_AUDIO: + dvb_filter_pes2ts_init(&av7110->p2t[0], + dvbdmx->pesfilter[0]->pid, + dvb_filter_pes2ts_cb, + (void *) dvbdmx->pesfilter[0]); + av7110_fw_cmd(av7110, COMTYPE_REC_PLAY, __Record, 2, AudioPES, 0); + break; + + case RP_VIDEO: + dvb_filter_pes2ts_init(&av7110->p2t[1], + dvbdmx->pesfilter[1]->pid, + dvb_filter_pes2ts_cb, + (void *) dvbdmx->pesfilter[1]); + av7110_fw_cmd(av7110, COMTYPE_REC_PLAY, __Record, 2, VideoPES, 0); + break; + + case RP_AV: + dvb_filter_pes2ts_init(&av7110->p2t[0], + dvbdmx->pesfilter[0]->pid, + dvb_filter_pes2ts_cb, + (void *) dvbdmx->pesfilter[0]); + dvb_filter_pes2ts_init(&av7110->p2t[1], + dvbdmx->pesfilter[1]->pid, + dvb_filter_pes2ts_cb, + (void *) dvbdmx->pesfilter[1]); + av7110_fw_cmd(av7110, COMTYPE_REC_PLAY, __Record, 2, AV_PES, 0); + break; + } + return 0; +} + +int av7110_av_start_play(struct av7110 *av7110, int av) +{ + dprintk(2, "av7110:%p, \n", av7110); + + if (av7110->rec_mode) + return -EBUSY; + if (av7110->playing & av) + return -EBUSY; + + av7110_fw_cmd(av7110, COMTYPE_REC_PLAY, __Stop, 0); + + if (av7110->playing == RP_NONE) { + av7110_ipack_reset(&av7110->ipack[0]); + av7110_ipack_reset(&av7110->ipack[1]); + } + + av7110->playing |= av; + switch (av7110->playing) { + case RP_AUDIO: + av7110_fw_cmd(av7110, COMTYPE_REC_PLAY, __Play, 2, AudioPES, 0); + break; + case RP_VIDEO: + av7110_fw_cmd(av7110, COMTYPE_REC_PLAY, __Play, 2, VideoPES, 0); + av7110->sinfo = 0; + break; + case RP_AV: + av7110->sinfo = 0; + av7110_fw_cmd(av7110, COMTYPE_REC_PLAY, __Play, 2, AV_PES, 0); + break; + } + return av7110->playing; +} + +void av7110_av_stop(struct av7110 *av7110, int av) +{ + dprintk(2, "av7110:%p, \n", av7110); + + if (!(av7110->playing & av) && !(av7110->rec_mode & av)) + return; + + av7110_fw_cmd(av7110, COMTYPE_REC_PLAY, __Stop, 0); + if (av7110->playing) { + av7110->playing &= ~av; + switch (av7110->playing) { + case RP_AUDIO: + av7110_fw_cmd(av7110, COMTYPE_REC_PLAY, __Play, 2, AudioPES, 0); + break; + case RP_VIDEO: + av7110_fw_cmd(av7110, COMTYPE_REC_PLAY, __Play, 2, VideoPES, 0); + break; + case RP_NONE: + av7110_set_vidmode(av7110, av7110->vidmode); + break; + } + } else { + av7110->rec_mode &= ~av; + switch (av7110->rec_mode) { + case RP_AUDIO: + av7110_fw_cmd(av7110, COMTYPE_REC_PLAY, __Record, 2, AudioPES, 0); + break; + case RP_VIDEO: + av7110_fw_cmd(av7110, COMTYPE_REC_PLAY, __Record, 2, VideoPES, 0); + break; + case RP_NONE: + break; + } + } +} + + +int av7110_pes_play(void *dest, struct dvb_ringbuffer *buf, int dlen) +{ + int len; + u32 sync; + u16 blen; + + if (!dlen) { + wake_up(&buf->queue); + return -1; + } + while (1) { + if ((len = dvb_ringbuffer_avail(buf)) < 6) + return -1; + sync = DVB_RINGBUFFER_PEEK(buf, 0) << 24; + sync |= DVB_RINGBUFFER_PEEK(buf, 1) << 16; + sync |= DVB_RINGBUFFER_PEEK(buf, 2) << 8; + sync |= DVB_RINGBUFFER_PEEK(buf, 3); + + if (((sync &~ 0x0f) == 0x000001e0) || + ((sync &~ 0x1f) == 0x000001c0) || + (sync == 0x000001bd)) + break; + printk("resync\n"); + DVB_RINGBUFFER_SKIP(buf, 1); + } + blen = DVB_RINGBUFFER_PEEK(buf, 4) << 8; + blen |= DVB_RINGBUFFER_PEEK(buf, 5); + blen += 6; + if (len < blen || blen > dlen) { + //printk("buffer empty - avail %d blen %u dlen %d\n", len, blen, dlen); + wake_up(&buf->queue); + return -1; + } + + dvb_ringbuffer_read(buf, dest, (size_t) blen, 0); + + dprintk(2, "pread=0x%08lx, pwrite=0x%08lx\n", + (unsigned long) buf->pread, (unsigned long) buf->pwrite); + wake_up(&buf->queue); + return blen; +} + + +int av7110_set_volume(struct av7110 *av7110, int volleft, int volright) +{ + int err, vol, val, balance = 0; + + dprintk(2, "av7110:%p, \n", av7110); + + av7110->mixer.volume_left = volleft; + av7110->mixer.volume_right = volright; + + switch (av7110->adac_type) { + case DVB_ADAC_TI: + volleft = (volleft * 256) / 1036; + volright = (volright * 256) / 1036; + if (volleft > 0x3f) + volleft = 0x3f; + if (volright > 0x3f) + volright = 0x3f; + if ((err = SendDAC(av7110, 3, 0x80 + volleft))) + return err; + return SendDAC(av7110, 4, volright); + + case DVB_ADAC_CRYSTAL: + volleft = 127 - volleft / 2; + volright = 127 - volright / 2; + i2c_writereg(av7110, 0x20, 0x03, volleft); + i2c_writereg(av7110, 0x20, 0x04, volright); + return 0; + + case DVB_ADAC_MSP: + vol = (volleft > volright) ? volleft : volright; + val = (vol * 0x73 / 255) << 8; + if (vol > 0) + balance = ((volright - volleft) * 127) / vol; + msp_writereg(av7110, MSP_WR_DSP, 0x0001, balance << 8); + msp_writereg(av7110, MSP_WR_DSP, 0x0000, val); /* loudspeaker */ + msp_writereg(av7110, MSP_WR_DSP, 0x0006, val); /* headphonesr */ + return 0; + } + return 0; +} + +void av7110_set_vidmode(struct av7110 *av7110, int mode) +{ + dprintk(2, "av7110:%p, \n", av7110); + + av7110_fw_cmd(av7110, COMTYPE_ENCODER, LoadVidCode, 1, mode); + + if (!av7110->playing) { + ChangePIDs(av7110, av7110->pids[DMX_PES_VIDEO], + av7110->pids[DMX_PES_AUDIO], + av7110->pids[DMX_PES_TELETEXT], + 0, av7110->pids[DMX_PES_PCR]); + av7110_fw_cmd(av7110, COMTYPE_PIDFILTER, Scan, 0); + } +} + + +static int sw2mode[16] = { + VIDEO_MODE_PAL, VIDEO_MODE_NTSC, VIDEO_MODE_NTSC, VIDEO_MODE_PAL, + VIDEO_MODE_NTSC, VIDEO_MODE_NTSC, VIDEO_MODE_PAL, VIDEO_MODE_NTSC, + VIDEO_MODE_PAL, VIDEO_MODE_PAL, VIDEO_MODE_PAL, VIDEO_MODE_PAL, + VIDEO_MODE_PAL, VIDEO_MODE_PAL, VIDEO_MODE_PAL, VIDEO_MODE_PAL, +}; + +static void get_video_format(struct av7110 *av7110, u8 *buf, int count) +{ + int i; + int hsize, vsize; + int sw; + u8 *p; + + dprintk(2, "av7110:%p, \n", av7110); + + if (av7110->sinfo) + return; + for (i = 7; i < count - 10; i++) { + p = buf + i; + if (p[0] || p[1] || p[2] != 0x01 || p[3] != 0xb3) + continue; + p += 4; + hsize = ((p[1] &0xF0) >> 4) | (p[0] << 4); + vsize = ((p[1] &0x0F) << 8) | (p[2]); + sw = (p[3] & 0x0F); + av7110_set_vidmode(av7110, sw2mode[sw]); + dprintk(2, "playback %dx%d fr=%d\n", hsize, vsize, sw); + av7110->sinfo = 1; + break; + } +} + + +/**************************************************************************** + * I/O buffer management and control + ****************************************************************************/ + +static inline long aux_ring_buffer_write(struct dvb_ringbuffer *rbuf, + const char *buf, unsigned long count) +{ + unsigned long todo = count; + int free; + + while (todo > 0) { + if (dvb_ringbuffer_free(rbuf) < 2048) { + if (wait_event_interruptible(rbuf->queue, + (dvb_ringbuffer_free(rbuf) >= 2048))) + return count - todo; + } + free = dvb_ringbuffer_free(rbuf); + if (free > todo) + free = todo; + dvb_ringbuffer_write(rbuf, buf, free); + todo -= free; + buf += free; + } + + return count - todo; +} + +static void play_video_cb(u8 *buf, int count, void *priv) +{ + struct av7110 *av7110 = (struct av7110 *) priv; + dprintk(2, "av7110:%p, \n", av7110); + + if ((buf[3] & 0xe0) == 0xe0) { + get_video_format(av7110, buf, count); + aux_ring_buffer_write(&av7110->avout, buf, count); + } else + aux_ring_buffer_write(&av7110->aout, buf, count); +} + +static void play_audio_cb(u8 *buf, int count, void *priv) +{ + struct av7110 *av7110 = (struct av7110 *) priv; + dprintk(2, "av7110:%p, \n", av7110); + + aux_ring_buffer_write(&av7110->aout, buf, count); +} + +#define FREE_COND (dvb_ringbuffer_free(&av7110->avout) >= 20 * 1024 && \ + dvb_ringbuffer_free(&av7110->aout) >= 20 * 1024) + +static ssize_t dvb_play(struct av7110 *av7110, const u8 __user *buf, + unsigned long count, int nonblock, int type) +{ + unsigned long todo = count, n; + dprintk(2, "av7110:%p, \n", av7110); + + if (!av7110->kbuf[type]) + return -ENOBUFS; + + if (nonblock && !FREE_COND) + return -EWOULDBLOCK; + + while (todo > 0) { + if (!FREE_COND) { + if (nonblock) + return count - todo; + if (wait_event_interruptible(av7110->avout.queue, + FREE_COND)) + return count - todo; + } + n = todo; + if (n > IPACKS * 2) + n = IPACKS * 2; + if (copy_from_user(av7110->kbuf[type], buf, n)) + return -EFAULT; + av7110_ipack_instant_repack(av7110->kbuf[type], n, + &av7110->ipack[type]); + todo -= n; + buf += n; + } + return count - todo; +} + +static ssize_t dvb_play_kernel(struct av7110 *av7110, const u8 *buf, + unsigned long count, int nonblock, int type) +{ + unsigned long todo = count, n; + dprintk(2, "av7110:%p, \n", av7110); + + if (!av7110->kbuf[type]) + return -ENOBUFS; + + if (nonblock && !FREE_COND) + return -EWOULDBLOCK; + + while (todo > 0) { + if (!FREE_COND) { + if (nonblock) + return count - todo; + if (wait_event_interruptible(av7110->avout.queue, + FREE_COND)) + return count - todo; + } + n = todo; + if (n > IPACKS * 2) + n = IPACKS * 2; + av7110_ipack_instant_repack(buf, n, &av7110->ipack[type]); + todo -= n; + buf += n; + } + return count - todo; +} + +static ssize_t dvb_aplay(struct av7110 *av7110, const u8 __user *buf, + unsigned long count, int nonblock, int type) +{ + unsigned long todo = count, n; + dprintk(2, "av7110:%p, \n", av7110); + + if (!av7110->kbuf[type]) + return -ENOBUFS; + if (nonblock && dvb_ringbuffer_free(&av7110->aout) < 20 * 1024) + return -EWOULDBLOCK; + + while (todo > 0) { + if (dvb_ringbuffer_free(&av7110->aout) < 20 * 1024) { + if (nonblock) + return count - todo; + if (wait_event_interruptible(av7110->aout.queue, + (dvb_ringbuffer_free(&av7110->aout) >= 20 * 1024))) + return count-todo; + } + n = todo; + if (n > IPACKS * 2) + n = IPACKS * 2; + if (copy_from_user(av7110->kbuf[type], buf, n)) + return -EFAULT; + av7110_ipack_instant_repack(av7110->kbuf[type], n, + &av7110->ipack[type]); + todo -= n; + buf += n; + } + return count - todo; +} + +void av7110_p2t_init(struct av7110_p2t *p, struct dvb_demux_feed *feed) +{ + memset(p->pes, 0, TS_SIZE); + p->counter = 0; + p->pos = 0; + p->frags = 0; + if (feed) + p->feed = feed; +} + +static void clear_p2t(struct av7110_p2t *p) +{ + memset(p->pes, 0, TS_SIZE); +// p->counter = 0; + p->pos = 0; + p->frags = 0; +} + + +static int find_pes_header(u8 const *buf, long int length, int *frags) +{ + int c = 0; + int found = 0; + + *frags = 0; + + while (c < length - 3 && !found) { + if (buf[c] == 0x00 && buf[c + 1] == 0x00 && + buf[c + 2] == 0x01) { + switch ( buf[c + 3] ) { + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + case PADDING_STREAM : + case DSM_CC_STREAM : + case ISO13522_STREAM: + case PRIVATE_STREAM1: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + found = 1; + break; + + default: + c++; + break; + } + } else + c++; + } + if (c == length - 3 && !found) { + if (buf[length - 1] == 0x00) + *frags = 1; + if (buf[length - 2] == 0x00 && + buf[length - 1] == 0x00) + *frags = 2; + if (buf[length - 3] == 0x00 && + buf[length - 2] == 0x00 && + buf[length - 1] == 0x01) + *frags = 3; + return -1; + } + + return c; +} + +void av7110_p2t_write(u8 const *buf, long int length, u16 pid, struct av7110_p2t *p) +{ + int c, c2, l, add; + int check, rest; + + c = 0; + c2 = 0; + if (p->frags){ + check = 0; + switch(p->frags) { + case 1: + if (buf[c] == 0x00 && buf[c + 1] == 0x01) { + check = 1; + c += 2; + } + break; + case 2: + if (buf[c] == 0x01) { + check = 1; + c++; + } + break; + case 3: + check = 1; + } + if (check) { + switch (buf[c]) { + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + case PADDING_STREAM : + case DSM_CC_STREAM : + case ISO13522_STREAM: + case PRIVATE_STREAM1: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + p->pes[0] = 0x00; + p->pes[1] = 0x00; + p->pes[2] = 0x01; + p->pes[3] = buf[c]; + p->pos = 4; + memcpy(p->pes + p->pos, buf + c, (TS_SIZE - 4) - p->pos); + c += (TS_SIZE - 4) - p->pos; + p_to_t(p->pes, (TS_SIZE - 4), pid, &p->counter, p->feed); + clear_p2t(p); + break; + + default: + c = 0; + break; + } + } + p->frags = 0; + } + + if (p->pos) { + c2 = find_pes_header(buf + c, length - c, &p->frags); + if (c2 >= 0 && c2 < (TS_SIZE - 4) - p->pos) + l = c2+c; + else + l = (TS_SIZE - 4) - p->pos; + memcpy(p->pes + p->pos, buf, l); + c += l; + p->pos += l; + p_to_t(p->pes, p->pos, pid, &p->counter, p->feed); + clear_p2t(p); + } + + add = 0; + while (c < length) { + c2 = find_pes_header(buf + c + add, length - c - add, &p->frags); + if (c2 >= 0) { + c2 += c + add; + if (c2 > c){ + p_to_t(buf + c, c2 - c, pid, &p->counter, p->feed); + c = c2; + clear_p2t(p); + add = 0; + } else + add = 1; + } else { + l = length - c; + rest = l % (TS_SIZE - 4); + l -= rest; + p_to_t(buf + c, l, pid, &p->counter, p->feed); + memcpy(p->pes, buf + c + l, rest); + p->pos = rest; + c = length; + } + } +} + + +static int write_ts_header2(u16 pid, u8 *counter, int pes_start, u8 *buf, u8 length) +{ + int i; + int c = 0; + int fill; + u8 tshead[4] = { 0x47, 0x00, 0x00, 0x10 }; + + fill = (TS_SIZE - 4) - length; + if (pes_start) + tshead[1] = 0x40; + if (fill) + tshead[3] = 0x30; + tshead[1] |= (u8)((pid & 0x1F00) >> 8); + tshead[2] |= (u8)(pid & 0x00FF); + tshead[3] |= ((*counter)++ & 0x0F); + memcpy(buf, tshead, 4); + c += 4; + + if (fill) { + buf[4] = fill - 1; + c++; + if (fill > 1) { + buf[5] = 0x00; + c++; + } + for (i = 6; i < fill + 4; i++) { + buf[i] = 0xFF; + c++; + } + } + + return c; +} + + +static void p_to_t(u8 const *buf, long int length, u16 pid, u8 *counter, + struct dvb_demux_feed *feed) +{ + int l, pes_start; + u8 obuf[TS_SIZE]; + long c = 0; + + pes_start = 0; + if (length > 3 && + buf[0] == 0x00 && buf[1] == 0x00 && buf[2] == 0x01) + switch (buf[3]) { + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + case PADDING_STREAM : + case DSM_CC_STREAM : + case ISO13522_STREAM: + case PRIVATE_STREAM1: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + pes_start = 1; + break; + + default: + break; + } + + while (c < length) { + memset(obuf, 0, TS_SIZE); + if (length - c >= (TS_SIZE - 4)){ + l = write_ts_header2(pid, counter, pes_start, + obuf, (TS_SIZE - 4)); + memcpy(obuf + l, buf + c, TS_SIZE - l); + c += TS_SIZE - l; + } else { + l = write_ts_header2(pid, counter, pes_start, + obuf, length - c); + memcpy(obuf + l, buf + c, TS_SIZE - l); + c = length; + } + feed->cb.ts(obuf, 188, NULL, 0, &feed->feed.ts, DMX_OK); + pes_start = 0; + } +} + + +int av7110_write_to_decoder(struct dvb_demux_feed *feed, const u8 *buf, size_t len) +{ + struct dvb_demux *demux = feed->demux; + struct av7110 *av7110 = (struct av7110 *) demux->priv; + struct ipack *ipack = &av7110->ipack[feed->pes_type]; + + dprintk(2, "av7110:%p, \n", av7110); + + switch (feed->pes_type) { + case 0: + if (av7110->audiostate.stream_source == AUDIO_SOURCE_MEMORY) + return -EINVAL; + break; + case 1: + if (av7110->videostate.stream_source == VIDEO_SOURCE_MEMORY) + return -EINVAL; + break; + default: + return -1; + } + + if (!(buf[3] & 0x10)) /* no payload? */ + return -1; + if (buf[1] & 0x40) + av7110_ipack_flush(ipack); + + if (buf[3] & 0x20) { /* adaptation field? */ + len -= buf[4] + 1; + buf += buf[4] + 1; + if (!len) + return 0; + } + + av7110_ipack_instant_repack(buf + 4, len - 4, &av7110->ipack[feed->pes_type]); + return 0; +} + + + +/****************************************************************************** + * Video MPEG decoder events + ******************************************************************************/ +void dvb_video_add_event(struct av7110 *av7110, struct video_event *event) +{ + struct dvb_video_events *events = &av7110->video_events; + int wp; + + spin_lock_bh(&events->lock); + + wp = (events->eventw + 1) % MAX_VIDEO_EVENT; + if (wp == events->eventr) { + events->overflow = 1; + events->eventr = (events->eventr + 1) % MAX_VIDEO_EVENT; + } + + //FIXME: timestamp? + memcpy(&events->events[events->eventw], event, sizeof(struct video_event)); + events->eventw = wp; + + spin_unlock_bh(&events->lock); + + wake_up_interruptible(&events->wait_queue); +} + + +static int dvb_video_get_event (struct av7110 *av7110, struct video_event *event, int flags) +{ + struct dvb_video_events *events = &av7110->video_events; + + if (events->overflow) { + events->overflow = 0; + return -EOVERFLOW; + } + if (events->eventw == events->eventr) { + int ret; + + if (flags & O_NONBLOCK) + return -EWOULDBLOCK; + + ret = wait_event_interruptible(events->wait_queue, + events->eventw != events->eventr); + if (ret < 0) + return ret; + } + + spin_lock_bh(&events->lock); + + memcpy(event, &events->events[events->eventr], + sizeof(struct video_event)); + events->eventr = (events->eventr + 1) % MAX_VIDEO_EVENT; + + spin_unlock_bh(&events->lock); + + return 0; +} + + +/****************************************************************************** + * DVB device file operations + ******************************************************************************/ + +static unsigned int dvb_video_poll(struct file *file, poll_table *wait) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct av7110 *av7110 = (struct av7110 *) dvbdev->priv; + unsigned int mask = 0; + + dprintk(2, "av7110:%p, \n", av7110); + + if ((file->f_flags & O_ACCMODE) != O_RDONLY) + poll_wait(file, &av7110->avout.queue, wait); + + poll_wait(file, &av7110->video_events.wait_queue, wait); + + if (av7110->video_events.eventw != av7110->video_events.eventr) + mask = POLLPRI; + + if ((file->f_flags & O_ACCMODE) != O_RDONLY) { + if (av7110->playing) { + if (FREE_COND) + mask |= (POLLOUT | POLLWRNORM); + } else /* if not playing: may play if asked for */ + mask |= (POLLOUT | POLLWRNORM); + } + + return mask; +} + +static ssize_t dvb_video_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct av7110 *av7110 = (struct av7110 *) dvbdev->priv; + + dprintk(2, "av7110:%p, \n", av7110); + + if ((file->f_flags & O_ACCMODE) == O_RDONLY) + return -EPERM; + + if (av7110->videostate.stream_source != VIDEO_SOURCE_MEMORY) + return -EPERM; + + return dvb_play(av7110, buf, count, file->f_flags & O_NONBLOCK, 1); +} + +static unsigned int dvb_audio_poll(struct file *file, poll_table *wait) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct av7110 *av7110 = (struct av7110 *) dvbdev->priv; + unsigned int mask = 0; + + dprintk(2, "av7110:%p, \n", av7110); + + poll_wait(file, &av7110->aout.queue, wait); + + if (av7110->playing) { + if (dvb_ringbuffer_free(&av7110->aout) >= 20 * 1024) + mask |= (POLLOUT | POLLWRNORM); + } else /* if not playing: may play if asked for */ + mask = (POLLOUT | POLLWRNORM); + + return mask; +} + +static ssize_t dvb_audio_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct av7110 *av7110 = (struct av7110 *) dvbdev->priv; + + dprintk(2, "av7110:%p, \n", av7110); + + if (av7110->audiostate.stream_source != AUDIO_SOURCE_MEMORY) { + printk(KERN_ERR "not audio source memory\n"); + return -EPERM; + } + return dvb_aplay(av7110, buf, count, file->f_flags & O_NONBLOCK, 0); +} + +static u8 iframe_header[] = { 0x00, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x80, 0x00, 0x00 }; + +#define MIN_IFRAME 400000 + +static int play_iframe(struct av7110 *av7110, u8 __user *buf, unsigned int len, int nonblock) +{ + int i, n; + + dprintk(2, "av7110:%p, \n", av7110); + + if (!(av7110->playing & RP_VIDEO)) { + if (av7110_av_start_play(av7110, RP_VIDEO) < 0) + return -EBUSY; + } + + /* setting n always > 1, fixes problems when playing stillframes + consisting of I- and P-Frames */ + n = MIN_IFRAME / len + 1; + + /* FIXME: nonblock? */ + dvb_play_kernel(av7110, iframe_header, sizeof(iframe_header), 0, 1); + + for (i = 0; i < n; i++) + dvb_play(av7110, buf, len, 0, 1); + + av7110_ipack_flush(&av7110->ipack[1]); + return 0; +} + + +static int dvb_video_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct av7110 *av7110 = (struct av7110 *) dvbdev->priv; + unsigned long arg = (unsigned long) parg; + int ret = 0; + + dprintk(2, "av7110:%p, \n", av7110); + + if ((file->f_flags & O_ACCMODE) == O_RDONLY) { + if ( cmd != VIDEO_GET_STATUS && cmd != VIDEO_GET_EVENT && + cmd != VIDEO_GET_SIZE ) { + return -EPERM; + } + } + + switch (cmd) { + case VIDEO_STOP: + av7110->videostate.play_state = VIDEO_STOPPED; + if (av7110->videostate.stream_source == VIDEO_SOURCE_MEMORY) + av7110_av_stop(av7110, RP_VIDEO); + else + vidcom(av7110, VIDEO_CMD_STOP, + av7110->videostate.video_blank ? 0 : 1); + av7110->trickmode = TRICK_NONE; + break; + + case VIDEO_PLAY: + av7110->trickmode = TRICK_NONE; + if (av7110->videostate.play_state == VIDEO_FREEZED) { + av7110->videostate.play_state = VIDEO_PLAYING; + vidcom(av7110, VIDEO_CMD_PLAY, 0); + } + + if (av7110->videostate.stream_source == VIDEO_SOURCE_MEMORY) { + if (av7110->playing == RP_AV) { + av7110_fw_cmd(av7110, COMTYPE_REC_PLAY, __Stop, 0); + av7110->playing &= ~RP_VIDEO; + } + av7110_av_start_play(av7110, RP_VIDEO); + vidcom(av7110, VIDEO_CMD_PLAY, 0); + } else { + //av7110_av_stop(av7110, RP_VIDEO); + vidcom(av7110, VIDEO_CMD_PLAY, 0); + } + av7110->videostate.play_state = VIDEO_PLAYING; + break; + + case VIDEO_FREEZE: + av7110->videostate.play_state = VIDEO_FREEZED; + if (av7110->playing & RP_VIDEO) + av7110_fw_cmd(av7110, COMTYPE_REC_PLAY, __Pause, 0); + else + vidcom(av7110, VIDEO_CMD_FREEZE, 1); + av7110->trickmode = TRICK_FREEZE; + break; + + case VIDEO_CONTINUE: + if (av7110->playing & RP_VIDEO) + av7110_fw_cmd(av7110, COMTYPE_REC_PLAY, __Continue, 0); + vidcom(av7110, VIDEO_CMD_PLAY, 0); + av7110->videostate.play_state = VIDEO_PLAYING; + av7110->trickmode = TRICK_NONE; + break; + + case VIDEO_SELECT_SOURCE: + av7110->videostate.stream_source = (video_stream_source_t) arg; + break; + + case VIDEO_SET_BLANK: + av7110->videostate.video_blank = (int) arg; + break; + + case VIDEO_GET_STATUS: + memcpy(parg, &av7110->videostate, sizeof(struct video_status)); + break; + + case VIDEO_GET_EVENT: + ret=dvb_video_get_event(av7110, parg, file->f_flags); + break; + + case VIDEO_GET_SIZE: + memcpy(parg, &av7110->video_size, sizeof(video_size_t)); + break; + + case VIDEO_SET_DISPLAY_FORMAT: + { + video_displayformat_t format = (video_displayformat_t) arg; + u16 val = 0; + + switch (format) { + case VIDEO_PAN_SCAN: + val = VID_PAN_SCAN_PREF; + break; + + case VIDEO_LETTER_BOX: + val = VID_VC_AND_PS_PREF; + break; + + case VIDEO_CENTER_CUT_OUT: + val = VID_CENTRE_CUT_PREF; + break; + + default: + ret = -EINVAL; + } + if (ret < 0) + break; + av7110->videostate.video_format = format; + ret = av7110_fw_cmd(av7110, COMTYPE_ENCODER, SetPanScanType, + 1, (u16) val); + break; + } + + case VIDEO_SET_FORMAT: + if (arg > 1) { + ret = -EINVAL; + break; + } + av7110->display_ar = arg; + ret = av7110_fw_cmd(av7110, COMTYPE_ENCODER, SetMonitorType, + 1, (u16) arg); + break; + + case VIDEO_STILLPICTURE: + { + struct video_still_picture *pic = + (struct video_still_picture *) parg; + av7110->videostate.stream_source = VIDEO_SOURCE_MEMORY; + dvb_ringbuffer_flush_spinlock_wakeup(&av7110->avout); + ret = play_iframe(av7110, pic->iFrame, pic->size, + file->f_flags & O_NONBLOCK); + break; + } + + case VIDEO_FAST_FORWARD: + //note: arg is ignored by firmware + if (av7110->playing & RP_VIDEO) + av7110_fw_cmd(av7110, COMTYPE_REC_PLAY, + __Scan_I, 2, AV_PES, 0); + else + vidcom(av7110, VIDEO_CMD_FFWD, arg); + av7110->trickmode = TRICK_FAST; + av7110->videostate.play_state = VIDEO_PLAYING; + break; + + case VIDEO_SLOWMOTION: + if (av7110->playing&RP_VIDEO) { + av7110_fw_cmd(av7110, COMTYPE_REC_PLAY, __Slow, 2, 0, 0); + vidcom(av7110, VIDEO_CMD_SLOW, arg); + } else { + vidcom(av7110, VIDEO_CMD_PLAY, 0); + vidcom(av7110, VIDEO_CMD_STOP, 0); + vidcom(av7110, VIDEO_CMD_SLOW, arg); + } + av7110->trickmode = TRICK_SLOW; + av7110->videostate.play_state = VIDEO_PLAYING; + break; + + case VIDEO_GET_CAPABILITIES: + *(int *)parg = VIDEO_CAP_MPEG1 | VIDEO_CAP_MPEG2 | + VIDEO_CAP_SYS | VIDEO_CAP_PROG; + break; + + case VIDEO_CLEAR_BUFFER: + dvb_ringbuffer_flush_spinlock_wakeup(&av7110->avout); + av7110_ipack_reset(&av7110->ipack[1]); + + if (av7110->playing == RP_AV) { + av7110_fw_cmd(av7110, COMTYPE_REC_PLAY, + __Play, 2, AV_PES, 0); + if (av7110->trickmode == TRICK_FAST) + av7110_fw_cmd(av7110, COMTYPE_REC_PLAY, + __Scan_I, 2, AV_PES, 0); + if (av7110->trickmode == TRICK_SLOW) { + av7110_fw_cmd(av7110, COMTYPE_REC_PLAY, + __Slow, 2, 0, 0); + vidcom(av7110, VIDEO_CMD_SLOW, arg); + } + if (av7110->trickmode == TRICK_FREEZE) + vidcom(av7110, VIDEO_CMD_STOP, 1); + } + break; + + case VIDEO_SET_STREAMTYPE: + + break; + + default: + ret = -ENOIOCTLCMD; + break; + } + return ret; +} + +static int dvb_audio_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct av7110 *av7110 = (struct av7110 *) dvbdev->priv; + unsigned long arg = (unsigned long) parg; + int ret = 0; + + dprintk(2, "av7110:%p, \n", av7110); + + if (((file->f_flags & O_ACCMODE) == O_RDONLY) && + (cmd != AUDIO_GET_STATUS)) + return -EPERM; + + switch (cmd) { + case AUDIO_STOP: + if (av7110->audiostate.stream_source == AUDIO_SOURCE_MEMORY) + av7110_av_stop(av7110, RP_AUDIO); + else + audcom(av7110, AUDIO_CMD_MUTE); + av7110->audiostate.play_state = AUDIO_STOPPED; + break; + + case AUDIO_PLAY: + if (av7110->audiostate.stream_source == AUDIO_SOURCE_MEMORY) + av7110_av_start_play(av7110, RP_AUDIO); + audcom(av7110, AUDIO_CMD_UNMUTE); + av7110->audiostate.play_state = AUDIO_PLAYING; + break; + + case AUDIO_PAUSE: + audcom(av7110, AUDIO_CMD_MUTE); + av7110->audiostate.play_state = AUDIO_PAUSED; + break; + + case AUDIO_CONTINUE: + if (av7110->audiostate.play_state == AUDIO_PAUSED) { + av7110->audiostate.play_state = AUDIO_PLAYING; + audcom(av7110, AUDIO_CMD_MUTE | AUDIO_CMD_PCM16); + } + break; + + case AUDIO_SELECT_SOURCE: + av7110->audiostate.stream_source = (audio_stream_source_t) arg; + break; + + case AUDIO_SET_MUTE: + { + audcom(av7110, arg ? AUDIO_CMD_MUTE : AUDIO_CMD_UNMUTE); + av7110->audiostate.mute_state = (int) arg; + break; + } + + case AUDIO_SET_AV_SYNC: + av7110->audiostate.AV_sync_state = (int) arg; + audcom(av7110, arg ? AUDIO_CMD_SYNC_ON : AUDIO_CMD_SYNC_OFF); + break; + + case AUDIO_SET_BYPASS_MODE: + ret = -EINVAL; + break; + + case AUDIO_CHANNEL_SELECT: + av7110->audiostate.channel_select = (audio_channel_select_t) arg; + + switch(av7110->audiostate.channel_select) { + case AUDIO_STEREO: + audcom(av7110, AUDIO_CMD_STEREO); + break; + + case AUDIO_MONO_LEFT: + audcom(av7110, AUDIO_CMD_MONO_L); + break; + + case AUDIO_MONO_RIGHT: + audcom(av7110, AUDIO_CMD_MONO_R); + break; + + default: + ret = -EINVAL; + break; + } + break; + + case AUDIO_GET_STATUS: + memcpy(parg, &av7110->audiostate, sizeof(struct audio_status)); + break; + + case AUDIO_GET_CAPABILITIES: + *(int *)parg = AUDIO_CAP_LPCM | AUDIO_CAP_MP1 | AUDIO_CAP_MP2; + break; + + case AUDIO_CLEAR_BUFFER: + dvb_ringbuffer_flush_spinlock_wakeup(&av7110->aout); + av7110_ipack_reset(&av7110->ipack[0]); + if (av7110->playing == RP_AV) + av7110_fw_cmd(av7110, COMTYPE_REC_PLAY, + __Play, 2, AV_PES, 0); + break; + case AUDIO_SET_ID: + + break; + case AUDIO_SET_MIXER: + { + struct audio_mixer *amix = (struct audio_mixer *)parg; + + av7110_set_volume(av7110, amix->volume_left, amix->volume_right); + break; + } + case AUDIO_SET_STREAMTYPE: + break; + default: + ret = -ENOIOCTLCMD; + } + return ret; +} + + +static int dvb_video_open(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct av7110 *av7110 = (struct av7110 *) dvbdev->priv; + int err; + + dprintk(2, "av7110:%p, \n", av7110); + + if ((err = dvb_generic_open(inode, file)) < 0) + return err; + + if ((file->f_flags & O_ACCMODE) != O_RDONLY) { + dvb_ringbuffer_flush_spinlock_wakeup(&av7110->aout); + dvb_ringbuffer_flush_spinlock_wakeup(&av7110->avout); + av7110->video_blank = 1; + av7110->audiostate.AV_sync_state = 1; + av7110->videostate.stream_source = VIDEO_SOURCE_DEMUX; + + /* empty event queue */ + av7110->video_events.eventr = av7110->video_events.eventw = 0; + } + + return 0; +} + +static int dvb_video_release(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct av7110 *av7110 = (struct av7110 *) dvbdev->priv; + + dprintk(2, "av7110:%p, \n", av7110); + + if ((file->f_flags & O_ACCMODE) != O_RDONLY) { + av7110_av_stop(av7110, RP_VIDEO); + } + + return dvb_generic_release(inode, file); +} + +static int dvb_audio_open(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct av7110 *av7110 = (struct av7110 *) dvbdev->priv; + int err=dvb_generic_open(inode, file); + + dprintk(2, "av7110:%p, \n", av7110); + + if (err < 0) + return err; + dvb_ringbuffer_flush_spinlock_wakeup(&av7110->aout); + av7110->audiostate.stream_source = AUDIO_SOURCE_DEMUX; + return 0; +} + +static int dvb_audio_release(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct av7110 *av7110 = (struct av7110 *) dvbdev->priv; + + dprintk(2, "av7110:%p, \n", av7110); + + av7110_av_stop(av7110, RP_AUDIO); + return dvb_generic_release(inode, file); +} + + + +/****************************************************************************** + * driver registration + ******************************************************************************/ + +static struct file_operations dvb_video_fops = { + .owner = THIS_MODULE, + .write = dvb_video_write, + .ioctl = dvb_generic_ioctl, + .open = dvb_video_open, + .release = dvb_video_release, + .poll = dvb_video_poll, +}; + +static struct dvb_device dvbdev_video = { + .priv = NULL, + .users = 6, + .readers = 5, /* arbitrary */ + .writers = 1, + .fops = &dvb_video_fops, + .kernel_ioctl = dvb_video_ioctl, +}; + +static struct file_operations dvb_audio_fops = { + .owner = THIS_MODULE, + .write = dvb_audio_write, + .ioctl = dvb_generic_ioctl, + .open = dvb_audio_open, + .release = dvb_audio_release, + .poll = dvb_audio_poll, +}; + +static struct dvb_device dvbdev_audio = { + .priv = NULL, + .users = 1, + .writers = 1, + .fops = &dvb_audio_fops, + .kernel_ioctl = dvb_audio_ioctl, +}; + + +int av7110_av_register(struct av7110 *av7110) +{ + av7110->audiostate.AV_sync_state = 0; + av7110->audiostate.mute_state = 0; + av7110->audiostate.play_state = AUDIO_STOPPED; + av7110->audiostate.stream_source = AUDIO_SOURCE_DEMUX; + av7110->audiostate.channel_select = AUDIO_STEREO; + av7110->audiostate.bypass_mode = 0; + + av7110->videostate.video_blank = 0; + av7110->videostate.play_state = VIDEO_STOPPED; + av7110->videostate.stream_source = VIDEO_SOURCE_DEMUX; + av7110->videostate.video_format = VIDEO_FORMAT_4_3; + av7110->videostate.display_format = VIDEO_CENTER_CUT_OUT; + av7110->display_ar = VIDEO_FORMAT_4_3; + + init_waitqueue_head(&av7110->video_events.wait_queue); + spin_lock_init(&av7110->video_events.lock); + av7110->video_events.eventw = av7110->video_events.eventr = 0; + av7110->video_events.overflow = 0; + memset(&av7110->video_size, 0, sizeof (video_size_t)); + + dvb_register_device(av7110->dvb_adapter, &av7110->video_dev, + &dvbdev_video, av7110, DVB_DEVICE_VIDEO); + + dvb_register_device(av7110->dvb_adapter, &av7110->audio_dev, + &dvbdev_audio, av7110, DVB_DEVICE_AUDIO); + + return 0; +} + +void av7110_av_unregister(struct av7110 *av7110) +{ + dvb_unregister_device(av7110->audio_dev); + dvb_unregister_device(av7110->video_dev); +} + +int av7110_av_init(struct av7110 *av7110) +{ + void (*play[])(u8 *, int, void *) = { play_audio_cb, play_video_cb }; + int i, ret; + + av7110->vidmode = VIDEO_MODE_PAL; + + for (i = 0; i < 2; i++) { + struct ipack *ipack = av7110->ipack + i; + + ret = av7110_ipack_init(ipack, IPACKS, play[i]); + if (ret < 0) { + if (i) + av7110_ipack_free(--ipack); + goto out; + } + ipack->data = av7110; + } + + dvb_ringbuffer_init(&av7110->avout, av7110->iobuf, AVOUTLEN); + dvb_ringbuffer_init(&av7110->aout, av7110->iobuf + AVOUTLEN, AOUTLEN); + + av7110->kbuf[0] = (u8 *)(av7110->iobuf + AVOUTLEN + AOUTLEN + BMPLEN); + av7110->kbuf[1] = av7110->kbuf[0] + 2 * IPACKS; +out: + return ret; +} + +void av7110_av_exit(struct av7110 *av7110) +{ + av7110_ipack_free(&av7110->ipack[0]); + av7110_ipack_free(&av7110->ipack[1]); +} diff --git a/drivers/media/dvb/ttpci/av7110_av.h b/drivers/media/dvb/ttpci/av7110_av.h new file mode 100644 index 000000000000..cc5e7a7e87c3 --- /dev/null +++ b/drivers/media/dvb/ttpci/av7110_av.h @@ -0,0 +1,29 @@ +#ifndef _AV7110_AV_H_ +#define _AV7110_AV_H_ + +struct av7110; + +extern void av7110_set_vidmode(struct av7110 *av7110, int mode); + +extern int av7110_record_cb(struct dvb_filter_pes2ts *p2t, u8 *buf, size_t len); +extern int av7110_pes_play(void *dest, struct dvb_ringbuffer *buf, int dlen); +extern int av7110_write_to_decoder(struct dvb_demux_feed *feed, const u8 *buf, size_t len); + +extern int av7110_set_volume(struct av7110 *av7110, int volleft, int volright); +extern void av7110_av_stop(struct av7110 *av7110, int av); +extern int av7110_av_start_record(struct av7110 *av7110, int av, + struct dvb_demux_feed *dvbdmxfeed); +extern int av7110_av_start_play(struct av7110 *av7110, int av); + +extern void dvb_video_add_event(struct av7110 *av7110, struct video_event *event); + +extern void av7110_p2t_init(struct av7110_p2t *p, struct dvb_demux_feed *feed); +extern void av7110_p2t_write(u8 const *buf, long int length, u16 pid, struct av7110_p2t *p); + +extern int av7110_av_register(struct av7110 *av7110); +extern void av7110_av_unregister(struct av7110 *av7110); +extern int av7110_av_init(struct av7110 *av7110); +extern void av7110_av_exit(struct av7110 *av7110); + + +#endif /* _AV7110_AV_H_ */ diff --git a/drivers/media/dvb/ttpci/av7110_ca.c b/drivers/media/dvb/ttpci/av7110_ca.c new file mode 100644 index 000000000000..21f7aacf7726 --- /dev/null +++ b/drivers/media/dvb/ttpci/av7110_ca.c @@ -0,0 +1,390 @@ +/* + * av7110_ca.c: CA and CI stuff + * + * Copyright (C) 1999-2002 Ralph Metzler + * & Marcus Metzler for convergence integrated media GmbH + * + * originally based on code by: + * Copyright (C) 1998,1999 Christian Theiss <mistert@rz.fh-augsburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + * + * the project's page is at http://www.linuxtv.org/dvb/ + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/timer.h> +#include <linux/poll.h> +#include <linux/byteorder/swabb.h> +#include <linux/smp_lock.h> + +#include "av7110.h" +#include "av7110_hw.h" + + +void CI_handle(struct av7110 *av7110, u8 *data, u16 len) +{ + dprintk(8, "av7110:%p\n",av7110); + + if (len < 3) + return; + switch (data[0]) { + case CI_MSG_CI_INFO: + if (data[2] != 1 && data[2] != 2) + break; + switch (data[1]) { + case 0: + av7110->ci_slot[data[2] - 1].flags = 0; + break; + case 1: + av7110->ci_slot[data[2] - 1].flags |= CA_CI_MODULE_PRESENT; + break; + case 2: + av7110->ci_slot[data[2] - 1].flags |= CA_CI_MODULE_READY; + break; + } + break; + case CI_SWITCH_PRG_REPLY: + //av7110->ci_stat=data[1]; + break; + default: + break; + } +} + + +void ci_get_data(struct dvb_ringbuffer *cibuf, u8 *data, int len) +{ + if (dvb_ringbuffer_free(cibuf) < len + 2) + return; + + DVB_RINGBUFFER_WRITE_BYTE(cibuf, len >> 8); + DVB_RINGBUFFER_WRITE_BYTE(cibuf, len & 0xff); + dvb_ringbuffer_write(cibuf, data, len); + wake_up_interruptible(&cibuf->queue); +} + + +/****************************************************************************** + * CI link layer file ops + ******************************************************************************/ + +static int ci_ll_init(struct dvb_ringbuffer *cirbuf, struct dvb_ringbuffer *ciwbuf, int size) +{ + struct dvb_ringbuffer *tab[] = { cirbuf, ciwbuf, NULL }, **p; + void *data; + + for (p = tab; *p; p++) { + data = vmalloc(size); + if (!data) { + while (p-- != tab) { + vfree(p[0]->data); + p[0]->data = NULL; + } + return -ENOMEM; + } + dvb_ringbuffer_init(*p, data, size); + } + return 0; +} + +static void ci_ll_flush(struct dvb_ringbuffer *cirbuf, struct dvb_ringbuffer *ciwbuf) +{ + dvb_ringbuffer_flush_spinlock_wakeup(cirbuf); + dvb_ringbuffer_flush_spinlock_wakeup(ciwbuf); +} + +static void ci_ll_release(struct dvb_ringbuffer *cirbuf, struct dvb_ringbuffer *ciwbuf) +{ + vfree(cirbuf->data); + cirbuf->data = NULL; + vfree(ciwbuf->data); + ciwbuf->data = NULL; +} + +static int ci_ll_reset(struct dvb_ringbuffer *cibuf, struct file *file, + int slots, ca_slot_info_t *slot) +{ + int i; + int len = 0; + u8 msg[8] = { 0x00, 0x06, 0x00, 0x00, 0xff, 0x02, 0x00, 0x00 }; + + for (i = 0; i < 2; i++) { + if (slots & (1 << i)) + len += 8; + } + + if (dvb_ringbuffer_free(cibuf) < len) + return -EBUSY; + + for (i = 0; i < 2; i++) { + if (slots & (1 << i)) { + msg[2] = i; + dvb_ringbuffer_write(cibuf, msg, 8); + slot[i].flags = 0; + } + } + + return 0; +} + +static ssize_t ci_ll_write(struct dvb_ringbuffer *cibuf, struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + int free; + int non_blocking = file->f_flags & O_NONBLOCK; + char *page = (char *)__get_free_page(GFP_USER); + int res; + + if (!page) + return -ENOMEM; + + res = -EINVAL; + if (count > 2048) + goto out; + + res = -EFAULT; + if (copy_from_user(page, buf, count)) + goto out; + + free = dvb_ringbuffer_free(cibuf); + if (count + 2 > free) { + res = -EWOULDBLOCK; + if (non_blocking) + goto out; + res = -ERESTARTSYS; + if (wait_event_interruptible(cibuf->queue, + (dvb_ringbuffer_free(cibuf) >= count + 2))) + goto out; + } + + DVB_RINGBUFFER_WRITE_BYTE(cibuf, count >> 8); + DVB_RINGBUFFER_WRITE_BYTE(cibuf, count & 0xff); + + res = dvb_ringbuffer_write(cibuf, page, count); +out: + free_page((unsigned long)page); + return res; +} + +static ssize_t ci_ll_read(struct dvb_ringbuffer *cibuf, struct file *file, + char __user *buf, size_t count, loff_t *ppos) +{ + int avail; + int non_blocking = file->f_flags & O_NONBLOCK; + ssize_t len; + + if (!cibuf->data || !count) + return 0; + if (non_blocking && (dvb_ringbuffer_empty(cibuf))) + return -EWOULDBLOCK; + if (wait_event_interruptible(cibuf->queue, + !dvb_ringbuffer_empty(cibuf))) + return -ERESTARTSYS; + avail = dvb_ringbuffer_avail(cibuf); + if (avail < 4) + return 0; + len = DVB_RINGBUFFER_PEEK(cibuf, 0) << 8; + len |= DVB_RINGBUFFER_PEEK(cibuf, 1); + if (avail < len + 2 || count < len) + return -EINVAL; + DVB_RINGBUFFER_SKIP(cibuf, 2); + + return dvb_ringbuffer_read(cibuf, buf, len, 1); +} + +static int dvb_ca_open(struct inode *inode, struct file *file) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct av7110 *av7110 = (struct av7110 *) dvbdev->priv; + int err = dvb_generic_open(inode, file); + + dprintk(8, "av7110:%p\n",av7110); + + if (err < 0) + return err; + ci_ll_flush(&av7110->ci_rbuffer, &av7110->ci_wbuffer); + return 0; +} + +static unsigned int dvb_ca_poll (struct file *file, poll_table *wait) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct av7110 *av7110 = (struct av7110 *) dvbdev->priv; + struct dvb_ringbuffer *rbuf = &av7110->ci_rbuffer; + struct dvb_ringbuffer *wbuf = &av7110->ci_wbuffer; + unsigned int mask = 0; + + dprintk(8, "av7110:%p\n",av7110); + + poll_wait(file, &rbuf->queue, wait); + poll_wait(file, &wbuf->queue, wait); + + if (!dvb_ringbuffer_empty(rbuf)) + mask |= (POLLIN | POLLRDNORM); + + if (dvb_ringbuffer_free(wbuf) > 1024) + mask |= (POLLOUT | POLLWRNORM); + + return mask; +} + +static int dvb_ca_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *parg) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct av7110 *av7110 = (struct av7110 *) dvbdev->priv; + unsigned long arg = (unsigned long) parg; + + dprintk(8, "av7110:%p\n",av7110); + + switch (cmd) { + case CA_RESET: + return ci_ll_reset(&av7110->ci_wbuffer, file, arg, &av7110->ci_slot[0]); + break; + case CA_GET_CAP: + { + ca_caps_t cap; + + cap.slot_num = 2; + cap.slot_type = (FW_CI_LL_SUPPORT(av7110->arm_app) ? + CA_CI_LINK : CA_CI) | CA_DESCR; + cap.descr_num = 16; + cap.descr_type = CA_ECD; + memcpy(parg, &cap, sizeof(cap)); + break; + } + + case CA_GET_SLOT_INFO: + { + ca_slot_info_t *info=(ca_slot_info_t *)parg; + + if (info->num > 1) + return -EINVAL; + av7110->ci_slot[info->num].num = info->num; + av7110->ci_slot[info->num].type = FW_CI_LL_SUPPORT(av7110->arm_app) ? + CA_CI_LINK : CA_CI; + memcpy(info, &av7110->ci_slot[info->num], sizeof(ca_slot_info_t)); + break; + } + + case CA_GET_MSG: + break; + + case CA_SEND_MSG: + break; + + case CA_GET_DESCR_INFO: + { + ca_descr_info_t info; + + info.num = 16; + info.type = CA_ECD; + memcpy(parg, &info, sizeof (info)); + break; + } + + case CA_SET_DESCR: + { + ca_descr_t *descr = (ca_descr_t*) parg; + + if (descr->index >= 16) + return -EINVAL; + if (descr->parity > 1) + return -EINVAL; + av7110_fw_cmd(av7110, COMTYPE_PIDFILTER, SetDescr, 5, + (descr->index<<8)|descr->parity, + (descr->cw[0]<<8)|descr->cw[1], + (descr->cw[2]<<8)|descr->cw[3], + (descr->cw[4]<<8)|descr->cw[5], + (descr->cw[6]<<8)|descr->cw[7]); + break; + } + + default: + return -EINVAL; + } + return 0; +} + +static ssize_t dvb_ca_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct av7110 *av7110 = (struct av7110 *) dvbdev->priv; + + dprintk(8, "av7110:%p\n",av7110); + return ci_ll_write(&av7110->ci_wbuffer, file, buf, count, ppos); +} + +static ssize_t dvb_ca_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct dvb_device *dvbdev = (struct dvb_device *) file->private_data; + struct av7110 *av7110 = (struct av7110 *) dvbdev->priv; + + dprintk(8, "av7110:%p\n",av7110); + return ci_ll_read(&av7110->ci_rbuffer, file, buf, count, ppos); +} + + + +static struct file_operations dvb_ca_fops = { + .owner = THIS_MODULE, + .read = dvb_ca_read, + .write = dvb_ca_write, + .ioctl = dvb_generic_ioctl, + .open = dvb_ca_open, + .release = dvb_generic_release, + .poll = dvb_ca_poll, +}; + +static struct dvb_device dvbdev_ca = { + .priv = NULL, + .users = 1, + .writers = 1, + .fops = &dvb_ca_fops, + .kernel_ioctl = dvb_ca_ioctl, +}; + + +int av7110_ca_register(struct av7110 *av7110) +{ + return dvb_register_device(av7110->dvb_adapter, &av7110->ca_dev, + &dvbdev_ca, av7110, DVB_DEVICE_CA); +} + +void av7110_ca_unregister(struct av7110 *av7110) +{ + dvb_unregister_device(av7110->ca_dev); +} + +int av7110_ca_init(struct av7110* av7110) +{ + return ci_ll_init(&av7110->ci_rbuffer, &av7110->ci_wbuffer, 8192); +} + +void av7110_ca_exit(struct av7110* av7110) +{ + ci_ll_release(&av7110->ci_rbuffer, &av7110->ci_wbuffer); +} diff --git a/drivers/media/dvb/ttpci/av7110_ca.h b/drivers/media/dvb/ttpci/av7110_ca.h new file mode 100644 index 000000000000..70ee855ece1b --- /dev/null +++ b/drivers/media/dvb/ttpci/av7110_ca.h @@ -0,0 +1,14 @@ +#ifndef _AV7110_CA_H_ +#define _AV7110_CA_H_ + +struct av7110; + +extern void CI_handle(struct av7110 *av7110, u8 *data, u16 len); +extern void ci_get_data(struct dvb_ringbuffer *cibuf, u8 *data, int len); + +extern int av7110_ca_register(struct av7110 *av7110); +extern void av7110_ca_unregister(struct av7110 *av7110); +extern int av7110_ca_init(struct av7110* av7110); +extern void av7110_ca_exit(struct av7110* av7110); + +#endif /* _AV7110_CA_H_ */ diff --git a/drivers/media/dvb/ttpci/av7110_hw.c b/drivers/media/dvb/ttpci/av7110_hw.c new file mode 100644 index 000000000000..bd6e5ea4aefe --- /dev/null +++ b/drivers/media/dvb/ttpci/av7110_hw.c @@ -0,0 +1,1170 @@ +/* + * av7110_hw.c: av7110 low level hardware access and firmware interface + * + * Copyright (C) 1999-2002 Ralph Metzler + * & Marcus Metzler for convergence integrated media GmbH + * + * originally based on code by: + * Copyright (C) 1998,1999 Christian Theiss <mistert@rz.fh-augsburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + * the project's page is at http://www.linuxtv.org/dvb/ + */ + +/* for debugging ARM communication: */ +//#define COM_DEBUG + +#include <stdarg.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/byteorder/swabb.h> +#include <linux/smp_lock.h> +#include <linux/fs.h> + +#include "av7110.h" +#include "av7110_hw.h" + +/**************************************************************************** + * DEBI functions + ****************************************************************************/ + +/* This DEBI code is based on the Stradis driver + by Nathan Laredo <laredo@gnu.org> */ + +int av7110_debiwrite(struct av7110 *av7110, u32 config, + int addr, u32 val, int count) +{ + struct saa7146_dev *dev = av7110->dev; + + if (count <= 0 || count > 32764) { + printk("%s: invalid count %d\n", __FUNCTION__, count); + return -1; + } + if (saa7146_wait_for_debi_done(av7110->dev, 0) < 0) { + printk("%s: wait_for_debi_done failed\n", __FUNCTION__); + return -1; + } + saa7146_write(dev, DEBI_CONFIG, config); + if (count <= 4) /* immediate transfer */ + saa7146_write(dev, DEBI_AD, val); + else /* block transfer */ + saa7146_write(dev, DEBI_AD, av7110->debi_bus); + saa7146_write(dev, DEBI_COMMAND, (count << 17) | (addr & 0xffff)); + saa7146_write(dev, MC2, (2 << 16) | 2); + return 0; +} + +u32 av7110_debiread(struct av7110 *av7110, u32 config, int addr, int count) +{ + struct saa7146_dev *dev = av7110->dev; + u32 result = 0; + + if (count > 32764 || count <= 0) { + printk("%s: invalid count %d\n", __FUNCTION__, count); + return 0; + } + if (saa7146_wait_for_debi_done(av7110->dev, 0) < 0) { + printk("%s: wait_for_debi_done #1 failed\n", __FUNCTION__); + return 0; + } + saa7146_write(dev, DEBI_AD, av7110->debi_bus); + saa7146_write(dev, DEBI_COMMAND, (count << 17) | 0x10000 | (addr & 0xffff)); + + saa7146_write(dev, DEBI_CONFIG, config); + saa7146_write(dev, MC2, (2 << 16) | 2); + if (count > 4) + return count; + if (saa7146_wait_for_debi_done(av7110->dev, 0) < 0) { + printk("%s: wait_for_debi_done #2 failed\n", __FUNCTION__); + return 0; + } + + result = saa7146_read(dev, DEBI_AD); + result &= (0xffffffffUL >> ((4 - count) * 8)); + return result; +} + + + +/* av7110 ARM core boot stuff */ + +void av7110_reset_arm(struct av7110 *av7110) +{ + saa7146_setgpio(av7110->dev, RESET_LINE, SAA7146_GPIO_OUTLO); + + /* Disable DEBI and GPIO irq */ + SAA7146_IER_DISABLE(av7110->dev, MASK_19 | MASK_03); + SAA7146_ISR_CLEAR(av7110->dev, MASK_19 | MASK_03); + + saa7146_setgpio(av7110->dev, RESET_LINE, SAA7146_GPIO_OUTHI); + msleep(30); /* the firmware needs some time to initialize */ + + ARM_ResetMailBox(av7110); + + SAA7146_ISR_CLEAR(av7110->dev, MASK_19 | MASK_03); + SAA7146_IER_ENABLE(av7110->dev, MASK_03); + + av7110->arm_ready = 1; + dprintk(1, "reset ARM\n"); +} + + +static int waitdebi(struct av7110 *av7110, int adr, int state) +{ + int k; + + dprintk(4, "%p\n", av7110); + + for (k = 0; k < 100; k++) { + if (irdebi(av7110, DEBINOSWAP, adr, 0, 2) == state) + return 0; + udelay(5); + } + return -1; +} + +static int load_dram(struct av7110 *av7110, u32 *data, int len) +{ + int i; + int blocks, rest; + u32 base, bootblock = BOOT_BLOCK; + + dprintk(4, "%p\n", av7110); + + blocks = len / BOOT_MAX_SIZE; + rest = len % BOOT_MAX_SIZE; + base = DRAM_START_CODE; + + for (i = 0; i < blocks; i++) { + if (waitdebi(av7110, BOOT_STATE, BOOTSTATE_BUFFER_EMPTY) < 0) { + printk(KERN_ERR "dvb-ttpci: load_dram(): timeout at block %d\n", i); + return -1; + } + dprintk(4, "writing DRAM block %d\n", i); + mwdebi(av7110, DEBISWAB, bootblock, + ((char*)data) + i * BOOT_MAX_SIZE, BOOT_MAX_SIZE); + bootblock ^= 0x1400; + iwdebi(av7110, DEBISWAB, BOOT_BASE, swab32(base), 4); + iwdebi(av7110, DEBINOSWAP, BOOT_SIZE, BOOT_MAX_SIZE, 2); + iwdebi(av7110, DEBINOSWAP, BOOT_STATE, BOOTSTATE_BUFFER_FULL, 2); + base += BOOT_MAX_SIZE; + } + + if (rest > 0) { + if (waitdebi(av7110, BOOT_STATE, BOOTSTATE_BUFFER_EMPTY) < 0) { + printk(KERN_ERR "dvb-ttpci: load_dram(): timeout at last block\n"); + return -1; + } + if (rest > 4) + mwdebi(av7110, DEBISWAB, bootblock, + ((char*)data) + i * BOOT_MAX_SIZE, rest); + else + mwdebi(av7110, DEBISWAB, bootblock, + ((char*)data) + i * BOOT_MAX_SIZE - 4, rest + 4); + + iwdebi(av7110, DEBISWAB, BOOT_BASE, swab32(base), 4); + iwdebi(av7110, DEBINOSWAP, BOOT_SIZE, rest, 2); + iwdebi(av7110, DEBINOSWAP, BOOT_STATE, BOOTSTATE_BUFFER_FULL, 2); + } + if (waitdebi(av7110, BOOT_STATE, BOOTSTATE_BUFFER_EMPTY) < 0) { + printk(KERN_ERR "dvb-ttpci: load_dram(): timeout after last block\n"); + return -1; + } + iwdebi(av7110, DEBINOSWAP, BOOT_SIZE, 0, 2); + iwdebi(av7110, DEBINOSWAP, BOOT_STATE, BOOTSTATE_BUFFER_FULL, 2); + if (waitdebi(av7110, BOOT_STATE, BOOTSTATE_BOOT_COMPLETE) < 0) { + printk(KERN_ERR "dvb-ttpci: load_dram(): final handshake timeout\n"); + return -1; + } + return 0; +} + + +/* we cannot write av7110 DRAM directly, so load a bootloader into + * the DPRAM which implements a simple boot protocol */ +static u8 bootcode[] = { + 0xea, 0x00, 0x00, 0x0e, 0xe1, 0xb0, 0xf0, 0x0e, 0xe2, 0x5e, 0xf0, 0x04, + 0xe2, 0x5e, 0xf0, 0x04, 0xe2, 0x5e, 0xf0, 0x08, 0xe2, 0x5e, 0xf0, 0x04, + 0xe2, 0x5e, 0xf0, 0x04, 0xe2, 0x5e, 0xf0, 0x04, 0x2c, 0x00, 0x00, 0x24, + 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x34, + 0x00, 0x00, 0x00, 0x00, 0xa5, 0xa5, 0x5a, 0x5a, 0x00, 0x1f, 0x15, 0x55, + 0x00, 0x00, 0x00, 0x09, 0xe5, 0x9f, 0xd0, 0x7c, 0xe5, 0x9f, 0x40, 0x74, + 0xe3, 0xa0, 0x00, 0x00, 0xe5, 0x84, 0x00, 0x00, 0xe5, 0x84, 0x00, 0x04, + 0xe5, 0x9f, 0x10, 0x70, 0xe5, 0x9f, 0x20, 0x70, 0xe5, 0x9f, 0x30, 0x64, + 0xe8, 0xb1, 0x1f, 0xe0, 0xe8, 0xa3, 0x1f, 0xe0, 0xe1, 0x51, 0x00, 0x02, + 0xda, 0xff, 0xff, 0xfb, 0xe5, 0x9f, 0xf0, 0x50, 0xe1, 0xd4, 0x10, 0xb0, + 0xe3, 0x51, 0x00, 0x00, 0x0a, 0xff, 0xff, 0xfc, 0xe1, 0xa0, 0x10, 0x0d, + 0xe5, 0x94, 0x30, 0x04, 0xe1, 0xd4, 0x20, 0xb2, 0xe2, 0x82, 0x20, 0x3f, + 0xe1, 0xb0, 0x23, 0x22, 0x03, 0xa0, 0x00, 0x02, 0xe1, 0xc4, 0x00, 0xb0, + 0x0a, 0xff, 0xff, 0xf4, 0xe8, 0xb1, 0x1f, 0xe0, 0xe8, 0xa3, 0x1f, 0xe0, + 0xe8, 0xb1, 0x1f, 0xe0, 0xe8, 0xa3, 0x1f, 0xe0, 0xe2, 0x52, 0x20, 0x01, + 0x1a, 0xff, 0xff, 0xf9, 0xe2, 0x2d, 0xdb, 0x05, 0xea, 0xff, 0xff, 0xec, + 0x2c, 0x00, 0x03, 0xf8, 0x2c, 0x00, 0x04, 0x00, 0x9e, 0x00, 0x08, 0x00, + 0x2c, 0x00, 0x00, 0x74, 0x2c, 0x00, 0x00, 0xc0 +}; + +int av7110_bootarm(struct av7110 *av7110) +{ + struct saa7146_dev *dev = av7110->dev; + u32 ret; + int i; + + dprintk(4, "%p\n", av7110); + + saa7146_setgpio(dev, RESET_LINE, SAA7146_GPIO_OUTLO); + + /* Disable DEBI and GPIO irq */ + SAA7146_IER_DISABLE(av7110->dev, MASK_03 | MASK_19); + SAA7146_ISR_CLEAR(av7110->dev, MASK_19 | MASK_03); + + /* enable DEBI */ + saa7146_write(av7110->dev, MC1, 0x08800880); + saa7146_write(av7110->dev, DD1_STREAM_B, 0x00000000); + saa7146_write(av7110->dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + + /* test DEBI */ + iwdebi(av7110, DEBISWAP, DPRAM_BASE, 0x76543210, 4); + if ((ret=irdebi(av7110, DEBINOSWAP, DPRAM_BASE, 0, 4)) != 0x10325476) { + printk(KERN_ERR "dvb-ttpci: debi test in av7110_bootarm() failed: " + "%08x != %08x (check your BIOS 'Plug&Play OS' settings)\n", + ret, 0x10325476); + return -1; + } + for (i = 0; i < 8192; i += 4) + iwdebi(av7110, DEBISWAP, DPRAM_BASE + i, 0x00, 4); + dprintk(2, "debi test OK\n"); + + /* boot */ + dprintk(1, "load boot code\n"); + saa7146_setgpio(dev, ARM_IRQ_LINE, SAA7146_GPIO_IRQLO); + //saa7146_setgpio(dev, DEBI_DONE_LINE, SAA7146_GPIO_INPUT); + //saa7146_setgpio(dev, 3, SAA7146_GPIO_INPUT); + + mwdebi(av7110, DEBISWAB, DPRAM_BASE, bootcode, sizeof(bootcode)); + iwdebi(av7110, DEBINOSWAP, BOOT_STATE, BOOTSTATE_BUFFER_FULL, 2); + + if (saa7146_wait_for_debi_done(av7110->dev, 1)) { + printk(KERN_ERR "dvb-ttpci: av7110_bootarm(): " + "saa7146_wait_for_debi_done() timed out\n"); + return -1; + } + saa7146_setgpio(dev, RESET_LINE, SAA7146_GPIO_OUTHI); + mdelay(1); + + dprintk(1, "load dram code\n"); + if (load_dram(av7110, (u32 *)av7110->bin_root, av7110->size_root) < 0) { + printk(KERN_ERR "dvb-ttpci: av7110_bootarm(): " + "load_dram() failed\n"); + return -1; + } + + saa7146_setgpio(dev, RESET_LINE, SAA7146_GPIO_OUTLO); + mdelay(1); + + dprintk(1, "load dpram code\n"); + mwdebi(av7110, DEBISWAB, DPRAM_BASE, av7110->bin_dpram, av7110->size_dpram); + + if (saa7146_wait_for_debi_done(av7110->dev, 1)) { + printk(KERN_ERR "dvb-ttpci: av7110_bootarm(): " + "saa7146_wait_for_debi_done() timed out after loading DRAM\n"); + return -1; + } + saa7146_setgpio(dev, RESET_LINE, SAA7146_GPIO_OUTHI); + msleep(30); /* the firmware needs some time to initialize */ + + //ARM_ClearIrq(av7110); + ARM_ResetMailBox(av7110); + SAA7146_ISR_CLEAR(av7110->dev, MASK_19 | MASK_03); + SAA7146_IER_ENABLE(av7110->dev, MASK_03); + + av7110->arm_errors = 0; + av7110->arm_ready = 1; + return 0; +} + + +/**************************************************************************** + * DEBI command polling + ****************************************************************************/ + +int av7110_wait_msgstate(struct av7110 *av7110, u16 flags) +{ + unsigned long start; + u32 stat; + + if (FW_VERSION(av7110->arm_app) <= 0x261c) { + /* not supported by old firmware */ + msleep(50); + return 0; + } + + /* new firmware */ + start = jiffies; + for (;;) { + if (down_interruptible(&av7110->dcomlock)) + return -ERESTARTSYS; + stat = rdebi(av7110, DEBINOSWAP, MSGSTATE, 0, 2); + up(&av7110->dcomlock); + if ((stat & flags) == 0) { + break; + } + if (time_after(jiffies, start + ARM_WAIT_FREE)) { + printk(KERN_ERR "%s: timeout waiting for MSGSTATE %04x\n", + __FUNCTION__, stat & flags); + return -1; + } + msleep(1); + } + return 0; +} + +int __av7110_send_fw_cmd(struct av7110 *av7110, u16* buf, int length) +{ + int i; + unsigned long start; + char *type = NULL; + u16 flags[2] = {0, 0}; + u32 stat; + +// dprintk(4, "%p\n", av7110); + + if (!av7110->arm_ready) { + dprintk(1, "arm not ready.\n"); + return -ENXIO; + } + + start = jiffies; + while (rdebi(av7110, DEBINOSWAP, COMMAND, 0, 2 )) { + msleep(1); + if (time_after(jiffies, start + ARM_WAIT_FREE)) { + printk(KERN_ERR "dvb-ttpci: %s(): timeout waiting for COMMAND idle\n", __FUNCTION__); + return -ETIMEDOUT; + } + } + + wdebi(av7110, DEBINOSWAP, COM_IF_LOCK, 0xffff, 2); + +#ifndef _NOHANDSHAKE + start = jiffies; + while (rdebi(av7110, DEBINOSWAP, HANDSHAKE_REG, 0, 2 )) { + msleep(1); + if (time_after(jiffies, start + ARM_WAIT_SHAKE)) { + printk(KERN_ERR "dvb-ttpci: %s(): timeout waiting for HANDSHAKE_REG\n", __FUNCTION__); + return -ETIMEDOUT; + } + } +#endif + + switch ((buf[0] >> 8) & 0xff) { + case COMTYPE_PIDFILTER: + case COMTYPE_ENCODER: + case COMTYPE_REC_PLAY: + case COMTYPE_MPEGDECODER: + type = "MSG"; + flags[0] = GPMQOver; + flags[1] = GPMQFull; + break; + case COMTYPE_OSD: + type = "OSD"; + flags[0] = OSDQOver; + flags[1] = OSDQFull; + break; + case COMTYPE_MISC: + if (FW_VERSION(av7110->arm_app) >= 0x261d) { + type = "MSG"; + flags[0] = GPMQOver; + flags[1] = GPMQBusy; + } + break; + default: + break; + } + + if (type != NULL) { + /* non-immediate COMMAND type */ + start = jiffies; + for (;;) { + stat = rdebi(av7110, DEBINOSWAP, MSGSTATE, 0, 2); + if (stat & flags[0]) { + printk(KERN_ERR "%s: %s QUEUE overflow\n", + __FUNCTION__, type); + return -1; + } + if ((stat & flags[1]) == 0) + break; + if (time_after(jiffies, start + ARM_WAIT_FREE)) { + printk(KERN_ERR "%s: timeout waiting on busy %s QUEUE\n", + __FUNCTION__, type); + return -1; + } + msleep(1); + } + } + + for (i = 2; i < length; i++) + wdebi(av7110, DEBINOSWAP, COMMAND + 2 * i, (u32) buf[i], 2); + + if (length) + wdebi(av7110, DEBINOSWAP, COMMAND + 2, (u32) buf[1], 2); + else + wdebi(av7110, DEBINOSWAP, COMMAND + 2, 0, 2); + + wdebi(av7110, DEBINOSWAP, COMMAND, (u32) buf[0], 2); + + wdebi(av7110, DEBINOSWAP, COM_IF_LOCK, 0x0000, 2); + +#ifdef COM_DEBUG + start = jiffies; + while (rdebi(av7110, DEBINOSWAP, COMMAND, 0, 2 )) { + msleep(1); + if (time_after(jiffies, start + ARM_WAIT_FREE)) { + printk(KERN_ERR "dvb-ttpci: %s(): timeout waiting for COMMAND to complete\n", + __FUNCTION__); + return -ETIMEDOUT; + } + } + + stat = rdebi(av7110, DEBINOSWAP, MSGSTATE, 0, 2); + if (stat & GPMQOver) { + printk(KERN_ERR "dvb-ttpci: %s(): GPMQOver\n", __FUNCTION__); + return -ENOSPC; + } + else if (stat & OSDQOver) { + printk(KERN_ERR "dvb-ttpci: %s(): OSDQOver\n", __FUNCTION__); + return -ENOSPC; + } +#endif + + return 0; +} + +int av7110_send_fw_cmd(struct av7110 *av7110, u16* buf, int length) +{ + int ret; + +// dprintk(4, "%p\n", av7110); + + if (!av7110->arm_ready) { + dprintk(1, "arm not ready.\n"); + return -1; + } + if (down_interruptible(&av7110->dcomlock)) + return -ERESTARTSYS; + + ret = __av7110_send_fw_cmd(av7110, buf, length); + up(&av7110->dcomlock); + if (ret) + printk(KERN_ERR "dvb-ttpci: %s(): av7110_send_fw_cmd error %d\n", + __FUNCTION__, ret); + return ret; +} + +int av7110_fw_cmd(struct av7110 *av7110, int type, int com, int num, ...) +{ + va_list args; + u16 buf[num + 2]; + int i, ret; + +// dprintk(4, "%p\n", av7110); + + buf[0] = ((type << 8) | com); + buf[1] = num; + + if (num) { + va_start(args, num); + for (i = 0; i < num; i++) + buf[i + 2] = va_arg(args, u32); + va_end(args); + } + + ret = av7110_send_fw_cmd(av7110, buf, num + 2); + if (ret) + printk(KERN_ERR "dvb-ttpci: av7110_fw_cmd error %d\n", ret); + return ret; +} + +int av7110_send_ci_cmd(struct av7110 *av7110, u8 subcom, u8 *buf, u8 len) +{ + int i, ret; + u16 cmd[18] = { ((COMTYPE_COMMON_IF << 8) + subcom), + 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + dprintk(4, "%p\n", av7110); + + for(i = 0; i < len && i < 32; i++) + { + if(i % 2 == 0) + cmd[(i / 2) + 2] = (u16)(buf[i]) << 8; + else + cmd[(i / 2) + 2] |= buf[i]; + } + + ret = av7110_send_fw_cmd(av7110, cmd, 18); + if (ret) + printk(KERN_ERR "dvb-ttpci: av7110_send_ci_cmd error %d\n", ret); + return ret; +} + +int av7110_fw_request(struct av7110 *av7110, u16 *request_buf, + int request_buf_len, u16 *reply_buf, int reply_buf_len) +{ + int err; + s16 i; + unsigned long start; +#ifdef COM_DEBUG + u32 stat; +#endif + + dprintk(4, "%p\n", av7110); + + if (!av7110->arm_ready) { + dprintk(1, "arm not ready.\n"); + return -1; + } + + if (down_interruptible(&av7110->dcomlock)) + return -ERESTARTSYS; + + if ((err = __av7110_send_fw_cmd(av7110, request_buf, request_buf_len)) < 0) { + up(&av7110->dcomlock); + printk(KERN_ERR "dvb-ttpci: av7110_fw_request error %d\n", err); + return err; + } + + start = jiffies; + while (rdebi(av7110, DEBINOSWAP, COMMAND, 0, 2)) { +#ifdef _NOHANDSHAKE + msleep(1); +#endif + if (time_after(jiffies, start + ARM_WAIT_FREE)) { + printk(KERN_ERR "%s: timeout waiting for COMMAND to complete\n", __FUNCTION__); + up(&av7110->dcomlock); + return -1; + } + } + +#ifndef _NOHANDSHAKE + start = jiffies; + while (rdebi(av7110, DEBINOSWAP, HANDSHAKE_REG, 0, 2 )) { + msleep(1); + if (time_after(jiffies, start + ARM_WAIT_SHAKE)) { + printk(KERN_ERR "%s: timeout waiting for HANDSHAKE_REG\n", __FUNCTION__); + up(&av7110->dcomlock); + return -1; + } + } +#endif + +#ifdef COM_DEBUG + stat = rdebi(av7110, DEBINOSWAP, MSGSTATE, 0, 2); + if (stat & GPMQOver) { + printk(KERN_ERR "%s: GPMQOver\n", __FUNCTION__); + up(&av7110->dcomlock); + return -1; + } + else if (stat & OSDQOver) { + printk(KERN_ERR "%s: OSDQOver\n", __FUNCTION__); + up(&av7110->dcomlock); + return -1; + } +#endif + + for (i = 0; i < reply_buf_len; i++) + reply_buf[i] = rdebi(av7110, DEBINOSWAP, COM_BUFF + 2 * i, 0, 2); + + up(&av7110->dcomlock); + return 0; +} + +int av7110_fw_query(struct av7110 *av7110, u16 tag, u16* buf, s16 length) +{ + int ret; + ret = av7110_fw_request(av7110, &tag, 0, buf, length); + if (ret) + printk(KERN_ERR "dvb-ttpci: av7110_fw_query error %d\n", ret); + return ret; +} + + +/**************************************************************************** + * Firmware commands + ****************************************************************************/ + +/* get version of the firmware ROM, RTSL, video ucode and ARM application */ +int av7110_firmversion(struct av7110 *av7110) +{ + u16 buf[20]; + u16 tag = ((COMTYPE_REQUEST << 8) + ReqVersion); + + dprintk(4, "%p\n", av7110); + + if (av7110_fw_query(av7110, tag, buf, 16)) { + printk("dvb-ttpci: failed to boot firmware @ card %d\n", + av7110->dvb_adapter->num); + return -EIO; + } + + av7110->arm_fw = (buf[0] << 16) + buf[1]; + av7110->arm_rtsl = (buf[2] << 16) + buf[3]; + av7110->arm_vid = (buf[4] << 16) + buf[5]; + av7110->arm_app = (buf[6] << 16) + buf[7]; + av7110->avtype = (buf[8] << 16) + buf[9]; + + printk("dvb-ttpci: info @ card %d: firm %08x, rtsl %08x, vid %08x, app %08x\n", + av7110->dvb_adapter->num, av7110->arm_fw, + av7110->arm_rtsl, av7110->arm_vid, av7110->arm_app); + + /* print firmware capabilities */ + if (FW_CI_LL_SUPPORT(av7110->arm_app)) + printk("dvb-ttpci: firmware @ card %d supports CI link layer interface\n", + av7110->dvb_adapter->num); + else + printk("dvb-ttpci: no firmware support for CI link layer interface @ card %d\n", + av7110->dvb_adapter->num); + + return 0; +} + + +int av7110_diseqc_send(struct av7110 *av7110, int len, u8 *msg, unsigned long burst) +{ + int i, ret; + u16 buf[18] = { ((COMTYPE_AUDIODAC << 8) + SendDiSEqC), + 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + dprintk(4, "%p\n", av7110); + + if (len > 10) + len = 10; + + buf[1] = len + 2; + buf[2] = len; + + if (burst != -1) + buf[3] = burst ? 0x01 : 0x00; + else + buf[3] = 0xffff; + + for (i = 0; i < len; i++) + buf[i + 4] = msg[i]; + + if ((ret = av7110_send_fw_cmd(av7110, buf, 18))) + printk(KERN_ERR "dvb-ttpci: av7110_diseqc_send error %d\n", ret); + + return 0; +} + + +#ifdef CONFIG_DVB_AV7110_OSD + +static inline int SetColorBlend(struct av7110 *av7110, u8 windownr) +{ + return av7110_fw_cmd(av7110, COMTYPE_OSD, SetCBlend, 1, windownr); +} + +static inline int SetBlend_(struct av7110 *av7110, u8 windownr, + enum av7110_osd_palette_type colordepth, u16 index, u8 blending) +{ + return av7110_fw_cmd(av7110, COMTYPE_OSD, SetBlend, 4, + windownr, colordepth, index, blending); +} + +static inline int SetColor_(struct av7110 *av7110, u8 windownr, + enum av7110_osd_palette_type colordepth, u16 index, u16 colorhi, u16 colorlo) +{ + return av7110_fw_cmd(av7110, COMTYPE_OSD, SetColor, 5, + windownr, colordepth, index, colorhi, colorlo); +} + +static inline int SetFont(struct av7110 *av7110, u8 windownr, u8 fontsize, + u16 colorfg, u16 colorbg) +{ + return av7110_fw_cmd(av7110, COMTYPE_OSD, Set_Font, 4, + windownr, fontsize, colorfg, colorbg); +} + +static int FlushText(struct av7110 *av7110) +{ + unsigned long start; + + if (down_interruptible(&av7110->dcomlock)) + return -ERESTARTSYS; + start = jiffies; + while (rdebi(av7110, DEBINOSWAP, BUFF1_BASE, 0, 2)) { + msleep(1); + if (time_after(jiffies, start + ARM_WAIT_OSD)) { + printk(KERN_ERR "dvb-ttpci: %s(): timeout waiting for BUFF1_BASE == 0\n", + __FUNCTION__); + up(&av7110->dcomlock); + return -1; + } + } + up(&av7110->dcomlock); + return 0; +} + +static int WriteText(struct av7110 *av7110, u8 win, u16 x, u16 y, u8* buf) +{ + int i, ret; + unsigned long start; + int length = strlen(buf) + 1; + u16 cbuf[5] = { (COMTYPE_OSD << 8) + DText, 3, win, x, y }; + + if (down_interruptible(&av7110->dcomlock)) + return -ERESTARTSYS; + + start = jiffies; + while (rdebi(av7110, DEBINOSWAP, BUFF1_BASE, 0, 2)) { + msleep(1); + if (time_after(jiffies, start + ARM_WAIT_OSD)) { + printk(KERN_ERR "dvb-ttpci: %s: timeout waiting for BUFF1_BASE == 0\n", + __FUNCTION__); + up(&av7110->dcomlock); + return -1; + } + } +#ifndef _NOHANDSHAKE + start = jiffies; + while (rdebi(av7110, DEBINOSWAP, HANDSHAKE_REG, 0, 2)) { + msleep(1); + if (time_after(jiffies, start + ARM_WAIT_SHAKE)) { + printk(KERN_ERR "dvb-ttpci: %s: timeout waiting for HANDSHAKE_REG\n", + __FUNCTION__); + up(&av7110->dcomlock); + return -1; + } + } +#endif + for (i = 0; i < length / 2; i++) + wdebi(av7110, DEBINOSWAP, BUFF1_BASE + i * 2, + swab16(*(u16 *)(buf + 2 * i)), 2); + if (length & 1) + wdebi(av7110, DEBINOSWAP, BUFF1_BASE + i * 2, 0, 2); + ret = __av7110_send_fw_cmd(av7110, cbuf, 5); + up(&av7110->dcomlock); + if (ret) + printk(KERN_ERR "dvb-ttpci: WriteText error %d\n", ret); + return ret; +} + +static inline int DrawLine(struct av7110 *av7110, u8 windownr, + u16 x, u16 y, u16 dx, u16 dy, u16 color) +{ + return av7110_fw_cmd(av7110, COMTYPE_OSD, DLine, 6, + windownr, x, y, dx, dy, color); +} + +static inline int DrawBlock(struct av7110 *av7110, u8 windownr, + u16 x, u16 y, u16 dx, u16 dy, u16 color) +{ + return av7110_fw_cmd(av7110, COMTYPE_OSD, DBox, 6, + windownr, x, y, dx, dy, color); +} + +static inline int HideWindow(struct av7110 *av7110, u8 windownr) +{ + return av7110_fw_cmd(av7110, COMTYPE_OSD, WHide, 1, windownr); +} + +static inline int MoveWindowRel(struct av7110 *av7110, u8 windownr, u16 x, u16 y) +{ + return av7110_fw_cmd(av7110, COMTYPE_OSD, WMoveD, 3, windownr, x, y); +} + +static inline int MoveWindowAbs(struct av7110 *av7110, u8 windownr, u16 x, u16 y) +{ + return av7110_fw_cmd(av7110, COMTYPE_OSD, WMoveA, 3, windownr, x, y); +} + +static inline int DestroyOSDWindow(struct av7110 *av7110, u8 windownr) +{ + return av7110_fw_cmd(av7110, COMTYPE_OSD, WDestroy, 1, windownr); +} + +static inline int CreateOSDWindow(struct av7110 *av7110, u8 windownr, + osd_raw_window_t disptype, + u16 width, u16 height) +{ + return av7110_fw_cmd(av7110, COMTYPE_OSD, WCreate, 4, + windownr, disptype, width, height); +} + + +static enum av7110_osd_palette_type bpp2pal[8] = { + Pal1Bit, Pal2Bit, 0, Pal4Bit, 0, 0, 0, Pal8Bit +}; +static osd_raw_window_t bpp2bit[8] = { + OSD_BITMAP1, OSD_BITMAP2, 0, OSD_BITMAP4, 0, 0, 0, OSD_BITMAP8 +}; + +static inline int LoadBitmap(struct av7110 *av7110, u16 format, + u16 dx, u16 dy, int inc, u8 __user * data) +{ + int bpp; + int i; + int d, delta; + u8 c; + int ret; + + dprintk(4, "%p\n", av7110); + + ret = wait_event_interruptible_timeout(av7110->bmpq, av7110->bmp_state != BMP_LOADING, HZ); + if (ret == -ERESTARTSYS || ret == 0) { + printk("dvb-ttpci: warning: timeout waiting in LoadBitmap: %d, %d\n", + ret, av7110->bmp_state); + av7110->bmp_state = BMP_NONE; + return -1; + } + BUG_ON (av7110->bmp_state == BMP_LOADING); + + av7110->bmp_state = BMP_LOADING; + if (format == OSD_BITMAP8) { + bpp=8; delta = 1; + } else if (format == OSD_BITMAP4) { + bpp=4; delta = 2; + } else if (format == OSD_BITMAP2) { + bpp=2; delta = 4; + } else if (format == OSD_BITMAP1) { + bpp=1; delta = 8; + } else { + av7110->bmp_state = BMP_NONE; + return -1; + } + av7110->bmplen = ((dx * dy * bpp + 7) & ~7) / 8; + av7110->bmpp = 0; + if (av7110->bmplen > 32768) { + av7110->bmp_state = BMP_NONE; + return -1; + } + for (i = 0; i < dy; i++) { + if (copy_from_user(av7110->bmpbuf + 1024 + i * dx, data + i * inc, dx)) { + av7110->bmp_state = BMP_NONE; + return -1; + } + } + if (format != OSD_BITMAP8) { + for (i = 0; i < dx * dy / delta; i++) { + c = ((u8 *)av7110->bmpbuf)[1024 + i * delta + delta - 1]; + for (d = delta - 2; d >= 0; d--) { + c |= (((u8 *)av7110->bmpbuf)[1024 + i * delta + d] + << ((delta - d - 1) * bpp)); + ((u8 *)av7110->bmpbuf)[1024 + i] = c; + } + } + } + av7110->bmplen += 1024; + dprintk(4, "av7110_fw_cmd: LoadBmp size %d\n", av7110->bmplen); + return av7110_fw_cmd(av7110, COMTYPE_OSD, LoadBmp, 3, format, dx, dy); +} + +static int BlitBitmap(struct av7110 *av7110, u16 win, u16 x, u16 y, u16 trans) +{ + int ret; + + dprintk(4, "%p\n", av7110); + + BUG_ON (av7110->bmp_state == BMP_NONE); + + ret = wait_event_interruptible_timeout(av7110->bmpq, + av7110->bmp_state != BMP_LOADING, 10*HZ); + if (ret == -ERESTARTSYS || ret == 0) { + printk("dvb-ttpci: warning: timeout waiting in BlitBitmap: %d, %d\n", + ret, av7110->bmp_state); + av7110->bmp_state = BMP_NONE; + return (ret == 0) ? -ETIMEDOUT : ret; + } + + BUG_ON (av7110->bmp_state != BMP_LOADED); + + return av7110_fw_cmd(av7110, COMTYPE_OSD, BlitBmp, 4, win, x, y, trans); +} + +static inline int ReleaseBitmap(struct av7110 *av7110) +{ + dprintk(4, "%p\n", av7110); + + if (av7110->bmp_state != BMP_LOADED) + return -1; + av7110->bmp_state = BMP_NONE; + return av7110_fw_cmd(av7110, COMTYPE_OSD, ReleaseBmp, 0); +} + +static u32 RGB2YUV(u16 R, u16 G, u16 B) +{ + u16 y, u, v; + u16 Y, Cr, Cb; + + y = R * 77 + G * 150 + B * 29; /* Luma=0.299R+0.587G+0.114B 0..65535 */ + u = 2048 + B * 8 -(y >> 5); /* Cr 0..4095 */ + v = 2048 + R * 8 -(y >> 5); /* Cb 0..4095 */ + + Y = y / 256; + Cb = u / 16; + Cr = v / 16; + + return Cr | (Cb << 16) | (Y << 8); +} + +static void OSDSetColor(struct av7110 *av7110, u8 color, u8 r, u8 g, u8 b, u8 blend) +{ + u16 ch, cl; + u32 yuv; + + yuv = blend ? RGB2YUV(r,g,b) : 0; + cl = (yuv & 0xffff); + ch = ((yuv >> 16) & 0xffff); + SetColor_(av7110, av7110->osdwin, bpp2pal[av7110->osdbpp[av7110->osdwin]], + color, ch, cl); + SetBlend_(av7110, av7110->osdwin, bpp2pal[av7110->osdbpp[av7110->osdwin]], + color, ((blend >> 4) & 0x0f)); +} + +static int OSDSetPalette(struct av7110 *av7110, u32 __user * colors, u8 first, u8 last) +{ + int i; + int length = last - first + 1; + + if (length * 4 > DATA_BUFF3_SIZE) + return -EINVAL; + + for (i = 0; i < length; i++) { + u32 color, blend, yuv; + + if (get_user(color, colors + i)) + return -EFAULT; + blend = (color & 0xF0000000) >> 4; + yuv = blend ? RGB2YUV(color & 0xFF, (color >> 8) & 0xFF, + (color >> 16) & 0xFF) | blend : 0; + yuv = ((yuv & 0xFFFF0000) >> 16) | ((yuv & 0x0000FFFF) << 16); + wdebi(av7110, DEBINOSWAP, DATA_BUFF3_BASE + i * 4, yuv, 4); + } + return av7110_fw_cmd(av7110, COMTYPE_OSD, Set_Palette, 4, + av7110->osdwin, + bpp2pal[av7110->osdbpp[av7110->osdwin]], + first, last); +} + +static int OSDSetBlock(struct av7110 *av7110, int x0, int y0, + int x1, int y1, int inc, u8 __user * data) +{ + uint w, h, bpp, bpl, size, lpb, bnum, brest; + int i; + int rc; + + w = x1 - x0 + 1; + h = y1 - y0 + 1; + if (inc <= 0) + inc = w; + if (w <= 0 || w > 720 || h <= 0 || h > 576) + return -1; + bpp = av7110->osdbpp[av7110->osdwin] + 1; + bpl = ((w * bpp + 7) & ~7) / 8; + size = h * bpl; + lpb = (32 * 1024) / bpl; + bnum = size / (lpb * bpl); + brest = size - bnum * lpb * bpl; + + for (i = 0; i < bnum; i++) { + rc = LoadBitmap(av7110, bpp2bit[av7110->osdbpp[av7110->osdwin]], + w, lpb, inc, data); + if (rc) + return rc; + rc = BlitBitmap(av7110, av7110->osdwin, x0, y0 + i * lpb, 0); + if (rc) + return rc; + data += lpb * inc; + } + if (brest) { + rc = LoadBitmap(av7110, bpp2bit[av7110->osdbpp[av7110->osdwin]], + w, brest / bpl, inc, data); + if (rc) + return rc; + rc = BlitBitmap(av7110, av7110->osdwin, x0, y0 + bnum * lpb, 0); + if (rc) + return rc; + } + ReleaseBitmap(av7110); + return 0; +} + +int av7110_osd_cmd(struct av7110 *av7110, osd_cmd_t *dc) +{ + int ret; + + ret = down_interruptible(&av7110->osd_sema); + if (ret) + return -ERESTARTSYS; + + /* stupid, but OSD functions don't provide a return code anyway */ + ret = 0; + + switch (dc->cmd) { + case OSD_Close: + DestroyOSDWindow(av7110, av7110->osdwin); + goto out; + case OSD_Open: + av7110->osdbpp[av7110->osdwin] = (dc->color - 1) & 7; + CreateOSDWindow(av7110, av7110->osdwin, + bpp2bit[av7110->osdbpp[av7110->osdwin]], + dc->x1 - dc->x0 + 1, dc->y1 - dc->y0 + 1); + if (!dc->data) { + MoveWindowAbs(av7110, av7110->osdwin, dc->x0, dc->y0); + SetColorBlend(av7110, av7110->osdwin); + } + goto out; + case OSD_Show: + MoveWindowRel(av7110, av7110->osdwin, 0, 0); + goto out; + case OSD_Hide: + HideWindow(av7110, av7110->osdwin); + goto out; + case OSD_Clear: + DrawBlock(av7110, av7110->osdwin, 0, 0, 720, 576, 0); + goto out; + case OSD_Fill: + DrawBlock(av7110, av7110->osdwin, 0, 0, 720, 576, dc->color); + goto out; + case OSD_SetColor: + OSDSetColor(av7110, dc->color, dc->x0, dc->y0, dc->x1, dc->y1); + goto out; + case OSD_SetPalette: + { + if (FW_VERSION(av7110->arm_app) >= 0x2618) { + ret = OSDSetPalette(av7110, dc->data, dc->color, dc->x0); + goto out; + } else { + int i, len = dc->x0-dc->color+1; + u8 __user *colors = (u8 __user *)dc->data; + u8 r, g, b, blend; + + for (i = 0; i<len; i++) { + if (get_user(r, colors + i * 4) || + get_user(g, colors + i * 4 + 1) || + get_user(b, colors + i * 4 + 2) || + get_user(blend, colors + i * 4 + 3)) { + ret = -EFAULT; + goto out; + } + OSDSetColor(av7110, dc->color + i, r, g, b, blend); + } + } + ret = 0; + goto out; + } + case OSD_SetTrans: + goto out; + case OSD_SetPixel: + DrawLine(av7110, av7110->osdwin, + dc->x0, dc->y0, 0, 0, dc->color); + goto out; + case OSD_GetPixel: + goto out; + case OSD_SetRow: + dc->y1 = dc->y0; + /* fall through */ + case OSD_SetBlock: + ret = OSDSetBlock(av7110, dc->x0, dc->y0, dc->x1, dc->y1, dc->color, dc->data); + goto out; + case OSD_FillRow: + DrawBlock(av7110, av7110->osdwin, dc->x0, dc->y0, + dc->x1-dc->x0+1, dc->y1, dc->color); + goto out; + case OSD_FillBlock: + DrawBlock(av7110, av7110->osdwin, dc->x0, dc->y0, + dc->x1 - dc->x0 + 1, dc->y1 - dc->y0 + 1, dc->color); + goto out; + case OSD_Line: + DrawLine(av7110, av7110->osdwin, + dc->x0, dc->y0, dc->x1 - dc->x0, dc->y1 - dc->y0, dc->color); + goto out; + case OSD_Query: + goto out; + case OSD_Test: + goto out; + case OSD_Text: + { + char textbuf[240]; + + if (strncpy_from_user(textbuf, dc->data, 240) < 0) { + ret = -EFAULT; + goto out; + } + textbuf[239] = 0; + if (dc->x1 > 3) + dc->x1 = 3; + SetFont(av7110, av7110->osdwin, dc->x1, + (u16) (dc->color & 0xffff), (u16) (dc->color >> 16)); + FlushText(av7110); + WriteText(av7110, av7110->osdwin, dc->x0, dc->y0, textbuf); + goto out; + } + case OSD_SetWindow: + if (dc->x0 < 1 || dc->x0 > 7) { + ret = -EINVAL; + goto out; + } + av7110->osdwin = dc->x0; + goto out; + case OSD_MoveWindow: + MoveWindowAbs(av7110, av7110->osdwin, dc->x0, dc->y0); + SetColorBlend(av7110, av7110->osdwin); + goto out; + case OSD_OpenRaw: + if (dc->color < OSD_BITMAP1 || dc->color > OSD_CURSOR) { + ret = -EINVAL; + goto out; + } + if (dc->color >= OSD_BITMAP1 && dc->color <= OSD_BITMAP8HR) { + av7110->osdbpp[av7110->osdwin] = (1 << (dc->color & 3)) - 1; + } + else { + av7110->osdbpp[av7110->osdwin] = 0; + } + CreateOSDWindow(av7110, av7110->osdwin, (osd_raw_window_t)dc->color, + dc->x1 - dc->x0 + 1, dc->y1 - dc->y0 + 1); + if (!dc->data) { + MoveWindowAbs(av7110, av7110->osdwin, dc->x0, dc->y0); + SetColorBlend(av7110, av7110->osdwin); + } + goto out; + default: + ret = -EINVAL; + goto out; + } + +out: + up(&av7110->osd_sema); + return ret; +} + +int av7110_osd_capability(struct av7110 *av7110, osd_cap_t *cap) +{ + switch (cap->cmd) { + case OSD_CAP_MEMSIZE: + if (FW_4M_SDRAM(av7110->arm_app)) + cap->val = 1000000; + else + cap->val = 92000; + return 0; + default: + return -EINVAL; + } +} +#endif /* CONFIG_DVB_AV7110_OSD */ diff --git a/drivers/media/dvb/ttpci/av7110_hw.h b/drivers/media/dvb/ttpci/av7110_hw.h new file mode 100644 index 000000000000..bf901c624682 --- /dev/null +++ b/drivers/media/dvb/ttpci/av7110_hw.h @@ -0,0 +1,500 @@ +#ifndef _AV7110_HW_H_ +#define _AV7110_HW_H_ + +#include "av7110.h" + +/* DEBI transfer mode defs */ + +#define DEBINOSWAP 0x000e0000 +#define DEBISWAB 0x001e0000 +#define DEBISWAP 0x002e0000 + +#define ARM_WAIT_FREE (HZ) +#define ARM_WAIT_SHAKE (HZ/5) +#define ARM_WAIT_OSD (HZ) + + +enum av7110_bootstate +{ + BOOTSTATE_BUFFER_EMPTY = 0, + BOOTSTATE_BUFFER_FULL = 1, + BOOTSTATE_BOOT_COMPLETE = 2 +}; + +enum av7110_type_rec_play_format +{ RP_None, + AudioPES, + AudioMp2, + AudioPCM, + VideoPES, + AV_PES +}; + +enum av7110_osd_palette_type +{ + NoPalet = 0, /* No palette */ + Pal1Bit = 2, /* 2 colors for 1 Bit Palette */ + Pal2Bit = 4, /* 4 colors for 2 bit palette */ + Pal4Bit = 16, /* 16 colors for 4 bit palette */ + Pal8Bit = 256 /* 256 colors for 16 bit palette */ +}; + +/* switch defines */ +#define SB_GPIO 3 +#define SB_OFF SAA7146_GPIO_OUTLO /* SlowBlank off (TV-Mode) */ +#define SB_ON SAA7146_GPIO_INPUT /* SlowBlank on (AV-Mode) */ +#define SB_WIDE SAA7146_GPIO_OUTHI /* SlowBlank 6V (16/9-Mode) (not implemented) */ + +#define FB_GPIO 1 +#define FB_OFF SAA7146_GPIO_LO /* FastBlank off (CVBS-Mode) */ +#define FB_ON SAA7146_GPIO_OUTHI /* FastBlank on (RGB-Mode) */ +#define FB_LOOP SAA7146_GPIO_INPUT /* FastBlank loop-through (PC graphics ???) */ + +enum av7110_video_output_mode +{ + NO_OUT = 0, /* disable analog output */ + CVBS_RGB_OUT = 1, + CVBS_YC_OUT = 2, + YC_OUT = 3 +}; + +/* firmware internal msg q status: */ +#define GPMQFull 0x0001 /* Main Message Queue Full */ +#define GPMQOver 0x0002 /* Main Message Queue Overflow */ +#define HPQFull 0x0004 /* High Priority Msg Queue Full */ +#define HPQOver 0x0008 +#define OSDQFull 0x0010 /* OSD Queue Full */ +#define OSDQOver 0x0020 +#define GPMQBusy 0x0040 /* Queue not empty, FW >= 261d */ +#define HPQBusy 0x0080 +#define OSDQBusy 0x0100 + +/* hw section filter flags */ +#define SECTION_EIT 0x01 +#define SECTION_SINGLE 0x00 +#define SECTION_CYCLE 0x02 +#define SECTION_CONTINUOS 0x04 +#define SECTION_MODE 0x06 +#define SECTION_IPMPE 0x0C /* size up to 4k */ +#define SECTION_HIGH_SPEED 0x1C /* larger buffer */ +#define DATA_PIPING_FLAG 0x20 /* for Data Piping Filter */ + +#define PBUFSIZE_NONE 0x0000 +#define PBUFSIZE_1P 0x0100 +#define PBUFSIZE_2P 0x0200 +#define PBUFSIZE_1K 0x0300 +#define PBUFSIZE_2K 0x0400 +#define PBUFSIZE_4K 0x0500 +#define PBUFSIZE_8K 0x0600 +#define PBUFSIZE_16K 0x0700 +#define PBUFSIZE_32K 0x0800 + + +/* firmware command codes */ +enum av7110_osd_command { + WCreate, + WDestroy, + WMoveD, + WMoveA, + WHide, + WTop, + DBox, + DLine, + DText, + Set_Font, + SetColor, + SetBlend, + SetWBlend, + SetCBlend, + SetNonBlend, + LoadBmp, + BlitBmp, + ReleaseBmp, + SetWTrans, + SetWNoTrans, + Set_Palette +}; + +enum av7110_pid_command { + MultiPID, + VideoPID, + AudioPID, + InitFilt, + FiltError, + NewVersion, + CacheError, + AddPIDFilter, + DelPIDFilter, + Scan, + SetDescr, + SetIR, + FlushTSQueue +}; + +enum av7110_mpeg_command { + SelAudChannels +}; + +enum av7110_audio_command { + AudioDAC, + CabADAC, + ON22K, + OFF22K, + MainSwitch, + ADSwitch, + SendDiSEqC, + SetRegister +}; + +enum av7110_request_command { + AudioState, + AudioBuffState, + VideoState1, + VideoState2, + VideoState3, + CrashCounter, + ReqVersion, + ReqVCXO, + ReqRegister, + ReqSecFilterError, + ReqSTC +}; + +enum av7110_encoder_command { + SetVidMode, + SetTestMode, + LoadVidCode, + SetMonitorType, + SetPanScanType, + SetFreezeMode +}; + +enum av7110_rec_play_state { + __Record, + __Stop, + __Play, + __Pause, + __Slow, + __FF_IP, + __Scan_I, + __Continue +}; + +enum av7110_fw_cmd_misc { + AV7110_FW_VIDEO_ZOOM = 1, + AV7110_FW_VIDEO_COMMAND, + AV7110_FW_AUDIO_COMMAND +}; + +enum av7110_command_type { + COMTYPE_NOCOM, + COMTYPE_PIDFILTER, + COMTYPE_MPEGDECODER, + COMTYPE_OSD, + COMTYPE_BMP, + COMTYPE_ENCODER, + COMTYPE_AUDIODAC, + COMTYPE_REQUEST, + COMTYPE_SYSTEM, + COMTYPE_REC_PLAY, + COMTYPE_COMMON_IF, + COMTYPE_PID_FILTER, + COMTYPE_PES, + COMTYPE_TS, + COMTYPE_VIDEO, + COMTYPE_AUDIO, + COMTYPE_CI_LL, + COMTYPE_MISC = 0x80 +}; + +#define VID_NONE_PREF 0x00 /* No aspect ration processing preferred */ +#define VID_PAN_SCAN_PREF 0x01 /* Pan and Scan Display preferred */ +#define VID_VERT_COMP_PREF 0x02 /* Vertical compression display preferred */ +#define VID_VC_AND_PS_PREF 0x03 /* PanScan and vertical Compression if allowed */ +#define VID_CENTRE_CUT_PREF 0x05 /* PanScan with zero vector */ + +/* MPEG video decoder commands */ +#define VIDEO_CMD_STOP 0x000e +#define VIDEO_CMD_PLAY 0x000d +#define VIDEO_CMD_FREEZE 0x0102 +#define VIDEO_CMD_FFWD 0x0016 +#define VIDEO_CMD_SLOW 0x0022 + +/* MPEG audio decoder commands */ +#define AUDIO_CMD_MUTE 0x0001 +#define AUDIO_CMD_UNMUTE 0x0002 +#define AUDIO_CMD_PCM16 0x0010 +#define AUDIO_CMD_STEREO 0x0080 +#define AUDIO_CMD_MONO_L 0x0100 +#define AUDIO_CMD_MONO_R 0x0200 +#define AUDIO_CMD_SYNC_OFF 0x000e +#define AUDIO_CMD_SYNC_ON 0x000f + +/* firmware data interface codes */ +#define DATA_NONE 0x00 +#define DATA_FSECTION 0x01 +#define DATA_IPMPE 0x02 +#define DATA_MPEG_RECORD 0x03 +#define DATA_DEBUG_MESSAGE 0x04 +#define DATA_COMMON_INTERFACE 0x05 +#define DATA_MPEG_PLAY 0x06 +#define DATA_BMP_LOAD 0x07 +#define DATA_IRCOMMAND 0x08 +#define DATA_PIPING 0x09 +#define DATA_STREAMING 0x0a +#define DATA_CI_GET 0x0b +#define DATA_CI_PUT 0x0c +#define DATA_MPEG_VIDEO_EVENT 0x0d + +#define DATA_PES_RECORD 0x10 +#define DATA_PES_PLAY 0x11 +#define DATA_TS_RECORD 0x12 +#define DATA_TS_PLAY 0x13 + +/* ancient CI command codes, only two are actually still used + * by the link level CI firmware */ +#define CI_CMD_ERROR 0x00 +#define CI_CMD_ACK 0x01 +#define CI_CMD_SYSTEM_READY 0x02 +#define CI_CMD_KEYPRESS 0x03 +#define CI_CMD_ON_TUNED 0x04 +#define CI_CMD_ON_SWITCH_PROGRAM 0x05 +#define CI_CMD_SECTION_ARRIVED 0x06 +#define CI_CMD_SECTION_TIMEOUT 0x07 +#define CI_CMD_TIME 0x08 +#define CI_CMD_ENTER_MENU 0x09 +#define CI_CMD_FAST_PSI 0x0a +#define CI_CMD_GET_SLOT_INFO 0x0b + +#define CI_MSG_NONE 0x00 +#define CI_MSG_CI_INFO 0x01 +#define CI_MSG_MENU 0x02 +#define CI_MSG_LIST 0x03 +#define CI_MSG_TEXT 0x04 +#define CI_MSG_REQUEST_INPUT 0x05 +#define CI_MSG_INPUT_COMPLETE 0x06 +#define CI_MSG_LIST_MORE 0x07 +#define CI_MSG_MENU_MORE 0x08 +#define CI_MSG_CLOSE_MMI_IMM 0x09 +#define CI_MSG_SECTION_REQUEST 0x0a +#define CI_MSG_CLOSE_FILTER 0x0b +#define CI_PSI_COMPLETE 0x0c +#define CI_MODULE_READY 0x0d +#define CI_SWITCH_PRG_REPLY 0x0e +#define CI_MSG_TEXT_MORE 0x0f + +#define CI_MSG_CA_PMT 0xe0 +#define CI_MSG_ERROR 0xf0 + + +/* base address of the dual ported RAM which serves as communication + * area between PCI bus and av7110, + * as seen by the DEBI bus of the saa7146 */ +#define DPRAM_BASE 0x4000 + +/* boot protocol area */ +#define BOOT_STATE (DPRAM_BASE + 0x3F8) +#define BOOT_SIZE (DPRAM_BASE + 0x3FA) +#define BOOT_BASE (DPRAM_BASE + 0x3FC) +#define BOOT_BLOCK (DPRAM_BASE + 0x400) +#define BOOT_MAX_SIZE 0xc00 + +/* firmware command protocol area */ +#define IRQ_STATE (DPRAM_BASE + 0x0F4) +#define IRQ_STATE_EXT (DPRAM_BASE + 0x0F6) +#define MSGSTATE (DPRAM_BASE + 0x0F8) +#define FILT_STATE (DPRAM_BASE + 0x0FA) +#define COMMAND (DPRAM_BASE + 0x0FC) +#define COM_BUFF (DPRAM_BASE + 0x100) +#define COM_BUFF_SIZE 0x20 + +/* various data buffers */ +#define BUFF1_BASE (DPRAM_BASE + 0x120) +#define BUFF1_SIZE 0xE0 + +#define DATA_BUFF0_BASE (DPRAM_BASE + 0x200) +#define DATA_BUFF0_SIZE 0x0800 + +#define DATA_BUFF1_BASE (DATA_BUFF0_BASE+DATA_BUFF0_SIZE) +#define DATA_BUFF1_SIZE 0x0800 + +#define DATA_BUFF2_BASE (DATA_BUFF1_BASE+DATA_BUFF1_SIZE) +#define DATA_BUFF2_SIZE 0x0800 + +#define DATA_BUFF3_BASE (DATA_BUFF2_BASE+DATA_BUFF2_SIZE) +#define DATA_BUFF3_SIZE 0x0400 + +#define Reserved (DPRAM_BASE + 0x1E00) +#define Reserved_SIZE 0x1C0 + + +/* firmware status area */ +#define STATUS_BASE (DPRAM_BASE + 0x1FC0) +#define STATUS_SCR (STATUS_BASE + 0x00) +#define STATUS_MODES (STATUS_BASE + 0x04) +#define STATUS_LOOPS (STATUS_BASE + 0x08) + +#define STATUS_MPEG_WIDTH (STATUS_BASE + 0x0C) +/* ((aspect_ratio & 0xf) << 12) | (height & 0xfff) */ +#define STATUS_MPEG_HEIGHT_AR (STATUS_BASE + 0x0E) + +/* firmware data protocol area */ +#define RX_TYPE (DPRAM_BASE + 0x1FE8) +#define RX_LEN (DPRAM_BASE + 0x1FEA) +#define TX_TYPE (DPRAM_BASE + 0x1FEC) +#define TX_LEN (DPRAM_BASE + 0x1FEE) + +#define RX_BUFF (DPRAM_BASE + 0x1FF4) +#define TX_BUFF (DPRAM_BASE + 0x1FF6) + +#define HANDSHAKE_REG (DPRAM_BASE + 0x1FF8) +#define COM_IF_LOCK (DPRAM_BASE + 0x1FFA) + +#define IRQ_RX (DPRAM_BASE + 0x1FFC) +#define IRQ_TX (DPRAM_BASE + 0x1FFE) + +/* used by boot protocol to load firmware into av7110 DRAM */ +#define DRAM_START_CODE 0x2e000404 +#define DRAM_MAX_CODE_SIZE 0x00100000 + +/* saa7146 gpio lines */ +#define RESET_LINE 2 +#define DEBI_DONE_LINE 1 +#define ARM_IRQ_LINE 0 + + + +extern void av7110_reset_arm(struct av7110 *av7110); +extern int av7110_bootarm(struct av7110 *av7110); +extern int av7110_firmversion(struct av7110 *av7110); +#define FW_CI_LL_SUPPORT(arm_app) ((arm_app) & 0x80000000) +#define FW_4M_SDRAM(arm_app) ((arm_app) & 0x40000000) +#define FW_VERSION(arm_app) ((arm_app) & 0x0000FFFF) + +extern int av7110_wait_msgstate(struct av7110 *av7110, u16 flags); +extern int av7110_fw_cmd(struct av7110 *av7110, int type, int com, int num, ...); +extern int __av7110_send_fw_cmd(struct av7110 *av7110, u16* buf, int length); +extern int av7110_send_fw_cmd(struct av7110 *av7110, u16* buf, int length); +extern int av7110_send_ci_cmd(struct av7110 *av7110, u8 subcom, u8 *buf, u8 len); +extern int av7110_fw_request(struct av7110 *av7110, u16 *request_buf, + int request_buf_len, u16 *reply_buf, int reply_buf_len); +extern int av7110_fw_query(struct av7110 *av7110, u16 tag, u16* Buff, s16 length); + + +/* DEBI (saa7146 data extension bus interface) access */ +extern int av7110_debiwrite(struct av7110 *av7110, u32 config, + int addr, u32 val, int count); +extern u32 av7110_debiread(struct av7110 *av7110, u32 config, + int addr, int count); + + +/* DEBI during interrupt */ +/* single word writes */ +static inline void iwdebi(struct av7110 *av7110, u32 config, int addr, u32 val, int count) +{ + av7110_debiwrite(av7110, config, addr, val, count); +} + +/* buffer writes */ +static inline void mwdebi(struct av7110 *av7110, u32 config, int addr, char *val, int count) +{ + memcpy(av7110->debi_virt, val, count); + av7110_debiwrite(av7110, config, addr, 0, count); +} + +static inline u32 irdebi(struct av7110 *av7110, u32 config, int addr, u32 val, int count) +{ + u32 res; + + res=av7110_debiread(av7110, config, addr, count); + if (count<=4) + memcpy(av7110->debi_virt, (char *) &res, count); + return res; +} + +/* DEBI outside interrupts, only for count <= 4! */ +static inline void wdebi(struct av7110 *av7110, u32 config, int addr, u32 val, int count) +{ + unsigned long flags; + + spin_lock_irqsave(&av7110->debilock, flags); + av7110_debiwrite(av7110, config, addr, val, count); + spin_unlock_irqrestore(&av7110->debilock, flags); +} + +static inline u32 rdebi(struct av7110 *av7110, u32 config, int addr, u32 val, int count) +{ + unsigned long flags; + u32 res; + + spin_lock_irqsave(&av7110->debilock, flags); + res=av7110_debiread(av7110, config, addr, count); + spin_unlock_irqrestore(&av7110->debilock, flags); + return res; +} + +/* handle mailbox registers of the dual ported RAM */ +static inline void ARM_ResetMailBox(struct av7110 *av7110) +{ + unsigned long flags; + + spin_lock_irqsave(&av7110->debilock, flags); + av7110_debiread(av7110, DEBINOSWAP, IRQ_RX, 2); + av7110_debiwrite(av7110, DEBINOSWAP, IRQ_RX, 0, 2); + spin_unlock_irqrestore(&av7110->debilock, flags); +} + +static inline void ARM_ClearMailBox(struct av7110 *av7110) +{ + iwdebi(av7110, DEBINOSWAP, IRQ_RX, 0, 2); +} + +static inline void ARM_ClearIrq(struct av7110 *av7110) +{ + irdebi(av7110, DEBINOSWAP, IRQ_RX, 0, 2); +} + +/**************************************************************************** + * Firmware commands + ****************************************************************************/ + +static inline int SendDAC(struct av7110 *av7110, u8 addr, u8 data) +{ + return av7110_fw_cmd(av7110, COMTYPE_AUDIODAC, AudioDAC, 2, addr, data); +} + +static inline void av7710_set_video_mode(struct av7110 *av7110, int mode) +{ + av7110_fw_cmd(av7110, COMTYPE_ENCODER, SetVidMode, 1, mode); +} + +static int inline vidcom(struct av7110 *av7110, u32 com, u32 arg) +{ + return av7110_fw_cmd(av7110, COMTYPE_MISC, AV7110_FW_VIDEO_COMMAND, 4, + (com>>16), (com&0xffff), + (arg>>16), (arg&0xffff)); +} + +static int inline audcom(struct av7110 *av7110, u32 com) +{ + return av7110_fw_cmd(av7110, COMTYPE_MISC, AV7110_FW_AUDIO_COMMAND, 2, + (com>>16), (com&0xffff)); +} + +static inline void Set22K(struct av7110 *av7110, int state) +{ + av7110_fw_cmd(av7110, COMTYPE_AUDIODAC, (state ? ON22K : OFF22K), 0); +} + + +extern int av7110_diseqc_send(struct av7110 *av7110, int len, u8 *msg, unsigned long burst); + + +#ifdef CONFIG_DVB_AV7110_OSD +extern int av7110_osd_cmd(struct av7110 *av7110, osd_cmd_t *dc); +extern int av7110_osd_capability(struct av7110 *av7110, osd_cap_t *cap); +#endif /* CONFIG_DVB_AV7110_OSD */ + + + +#endif /* _AV7110_HW_H_ */ diff --git a/drivers/media/dvb/ttpci/av7110_ipack.c b/drivers/media/dvb/ttpci/av7110_ipack.c new file mode 100644 index 000000000000..246640741888 --- /dev/null +++ b/drivers/media/dvb/ttpci/av7110_ipack.c @@ -0,0 +1,403 @@ +#include "dvb_filter.h" +#include "av7110_ipack.h" +#include <linux/string.h> /* for memcpy() */ +#include <linux/vmalloc.h> + + +void av7110_ipack_reset(struct ipack *p) +{ + p->found = 0; + p->cid = 0; + p->plength = 0; + p->flag1 = 0; + p->flag2 = 0; + p->hlength = 0; + p->mpeg = 0; + p->check = 0; + p->which = 0; + p->done = 0; + p->count = 0; +} + + +int av7110_ipack_init(struct ipack *p, int size, + void (*func)(u8 *buf, int size, void *priv)) +{ + if (!(p->buf = vmalloc(size*sizeof(u8)))) { + printk ("Couldn't allocate memory for ipack\n"); + return -ENOMEM; + } + p->size = size; + p->func = func; + p->repack_subids = 0; + av7110_ipack_reset(p); + return 0; +} + + +void av7110_ipack_free(struct ipack *p) +{ + vfree(p->buf); +} + + +static void send_ipack(struct ipack *p) +{ + int off; + struct dvb_audio_info ai; + int ac3_off = 0; + int streamid = 0; + int nframes = 0; + int f = 0; + + switch (p->mpeg) { + case 2: + if (p->count < 10) + return; + p->buf[3] = p->cid; + p->buf[4] = (u8)(((p->count - 6) & 0xff00) >> 8); + p->buf[5] = (u8)((p->count - 6) & 0x00ff); + if (p->repack_subids && p->cid == PRIVATE_STREAM1) { + off = 9 + p->buf[8]; + streamid = p->buf[off]; + if ((streamid & 0xf8) == 0x80) { + ai.off = 0; + ac3_off = ((p->buf[off + 2] << 8)| + p->buf[off + 3]); + if (ac3_off < p->count) + f = dvb_filter_get_ac3info(p->buf + off + 3 + ac3_off, + p->count - ac3_off, &ai, 0); + if (!f) { + nframes = (p->count - off - 3 - ac3_off) / + ai.framesize + 1; + p->buf[off + 2] = (ac3_off >> 8) & 0xff; + p->buf[off + 3] = (ac3_off) & 0xff; + p->buf[off + 1] = nframes; + ac3_off += nframes * ai.framesize - p->count; + } + } + } + p->func(p->buf, p->count, p->data); + + p->buf[6] = 0x80; + p->buf[7] = 0x00; + p->buf[8] = 0x00; + p->count = 9; + if (p->repack_subids && p->cid == PRIVATE_STREAM1 + && (streamid & 0xf8) == 0x80) { + p->count += 4; + p->buf[9] = streamid; + p->buf[10] = (ac3_off >> 8) & 0xff; + p->buf[11] = (ac3_off) & 0xff; + p->buf[12] = 0; + } + break; + + case 1: + if (p->count < 8) + return; + p->buf[3] = p->cid; + p->buf[4] = (u8)(((p->count - 6) & 0xff00) >> 8); + p->buf[5] = (u8)((p->count - 6) & 0x00ff); + p->func(p->buf, p->count, p->data); + + p->buf[6] = 0x0f; + p->count = 7; + break; + } +} + + +void av7110_ipack_flush(struct ipack *p) +{ + if (p->plength != MMAX_PLENGTH - 6 || p->found <= 6) + return; + p->plength = p->found - 6; + p->found = 0; + send_ipack(p); + av7110_ipack_reset(p); +} + + +static void write_ipack(struct ipack *p, const u8 *data, int count) +{ + u8 headr[3] = { 0x00, 0x00, 0x01 }; + + if (p->count < 6) { + memcpy(p->buf, headr, 3); + p->count = 6; + } + + if (p->count + count < p->size){ + memcpy(p->buf+p->count, data, count); + p->count += count; + } else { + int rest = p->size - p->count; + memcpy(p->buf+p->count, data, rest); + p->count += rest; + send_ipack(p); + if (count - rest > 0) + write_ipack(p, data + rest, count - rest); + } +} + + +int av7110_ipack_instant_repack (const u8 *buf, int count, struct ipack *p) +{ + int l; + int c = 0; + + while (c < count && (p->mpeg == 0 || + (p->mpeg == 1 && p->found < 7) || + (p->mpeg == 2 && p->found < 9)) + && (p->found < 5 || !p->done)) { + switch (p->found) { + case 0: + case 1: + if (buf[c] == 0x00) + p->found++; + else + p->found = 0; + c++; + break; + case 2: + if (buf[c] == 0x01) + p->found++; + else if (buf[c] == 0) + p->found = 2; + else + p->found = 0; + c++; + break; + case 3: + p->cid = 0; + switch (buf[c]) { + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + case PADDING_STREAM : + case DSM_CC_STREAM : + case ISO13522_STREAM: + p->done = 1; + /* fall through */ + case PRIVATE_STREAM1: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + p->found++; + p->cid = buf[c]; + c++; + break; + default: + p->found = 0; + break; + } + break; + + case 4: + if (count-c > 1) { + p->plen[0] = buf[c]; + c++; + p->plen[1] = buf[c]; + c++; + p->found += 2; + p->plength = (p->plen[0] << 8) | p->plen[1]; + } else { + p->plen[0] = buf[c]; + p->found++; + return count; + } + break; + case 5: + p->plen[1] = buf[c]; + c++; + p->found++; + p->plength = (p->plen[0] << 8) | p->plen[1]; + break; + case 6: + if (!p->done) { + p->flag1 = buf[c]; + c++; + p->found++; + if ((p->flag1 & 0xc0) == 0x80) + p->mpeg = 2; + else { + p->hlength = 0; + p->which = 0; + p->mpeg = 1; + p->flag2 = 0; + } + } + break; + + case 7: + if (!p->done && p->mpeg == 2) { + p->flag2 = buf[c]; + c++; + p->found++; + } + break; + + case 8: + if (!p->done && p->mpeg == 2) { + p->hlength = buf[c]; + c++; + p->found++; + } + break; + } + } + + if (c == count) + return count; + + if (!p->plength) + p->plength = MMAX_PLENGTH - 6; + + if (p->done || ((p->mpeg == 2 && p->found >= 9) || + (p->mpeg == 1 && p->found >= 7))) { + switch (p->cid) { + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + case PRIVATE_STREAM1: + if (p->mpeg == 2 && p->found == 9) { + write_ipack(p, &p->flag1, 1); + write_ipack(p, &p->flag2, 1); + write_ipack(p, &p->hlength, 1); + } + + if (p->mpeg == 1 && p->found == 7) + write_ipack(p, &p->flag1, 1); + + if (p->mpeg == 2 && (p->flag2 & PTS_ONLY) && + p->found < 14) { + while (c < count && p->found < 14) { + p->pts[p->found - 9] = buf[c]; + write_ipack(p, buf + c, 1); + c++; + p->found++; + } + if (c == count) + return count; + } + + if (p->mpeg == 1 && p->which < 2000) { + + if (p->found == 7) { + p->check = p->flag1; + p->hlength = 1; + } + + while (!p->which && c < count && + p->check == 0xff){ + p->check = buf[c]; + write_ipack(p, buf + c, 1); + c++; + p->found++; + p->hlength++; + } + + if (c == count) + return count; + + if ((p->check & 0xc0) == 0x40 && !p->which) { + p->check = buf[c]; + write_ipack(p, buf + c, 1); + c++; + p->found++; + p->hlength++; + + p->which = 1; + if (c == count) + return count; + p->check = buf[c]; + write_ipack(p, buf + c, 1); + c++; + p->found++; + p->hlength++; + p->which = 2; + if (c == count) + return count; + } + + if (p->which == 1) { + p->check = buf[c]; + write_ipack(p, buf + c, 1); + c++; + p->found++; + p->hlength++; + p->which = 2; + if (c == count) + return count; + } + + if ((p->check & 0x30) && p->check != 0xff) { + p->flag2 = (p->check & 0xf0) << 2; + p->pts[0] = p->check; + p->which = 3; + } + + if (c == count) + return count; + if (p->which > 2){ + if ((p->flag2 & PTS_DTS_FLAGS) == PTS_ONLY) { + while (c < count && p->which < 7) { + p->pts[p->which - 2] = buf[c]; + write_ipack(p, buf + c, 1); + c++; + p->found++; + p->which++; + p->hlength++; + } + if (c == count) + return count; + } else if ((p->flag2 & PTS_DTS_FLAGS) == PTS_DTS) { + while (c < count && p->which < 12) { + if (p->which < 7) + p->pts[p->which - 2] = buf[c]; + write_ipack(p, buf + c, 1); + c++; + p->found++; + p->which++; + p->hlength++; + } + if (c == count) + return count; + } + p->which = 2000; + } + + } + + while (c < count && p->found < p->plength + 6) { + l = count - c; + if (l + p->found > p->plength + 6) + l = p->plength + 6 - p->found; + write_ipack(p, buf + c, l); + p->found += l; + c += l; + } + break; + } + + + if (p->done) { + if (p->found + count - c < p->plength + 6) { + p->found += count - c; + c = count; + } else { + c += p->plength + 6 - p->found; + p->found = p->plength + 6; + } + } + + if (p->plength && p->found == p->plength + 6) { + send_ipack(p); + av7110_ipack_reset(p); + if (c < count) + av7110_ipack_instant_repack(buf + c, count - c, p); + } + } + return count; +} diff --git a/drivers/media/dvb/ttpci/av7110_ipack.h b/drivers/media/dvb/ttpci/av7110_ipack.h new file mode 100644 index 000000000000..becf94d3fdfa --- /dev/null +++ b/drivers/media/dvb/ttpci/av7110_ipack.h @@ -0,0 +1,12 @@ +#ifndef _AV7110_IPACK_H_ +#define _AV7110_IPACK_H_ + +extern int av7110_ipack_init(struct ipack *p, int size, + void (*func)(u8 *buf, int size, void *priv)); + +extern void av7110_ipack_reset(struct ipack *p); +extern int av7110_ipack_instant_repack(const u8 *buf, int count, struct ipack *p); +extern void av7110_ipack_free(struct ipack * p); +extern void av7110_ipack_flush(struct ipack *p); + +#endif diff --git a/drivers/media/dvb/ttpci/av7110_ir.c b/drivers/media/dvb/ttpci/av7110_ir.c new file mode 100644 index 000000000000..6d2256f1e354 --- /dev/null +++ b/drivers/media/dvb/ttpci/av7110_ir.c @@ -0,0 +1,212 @@ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/input.h> +#include <linux/proc_fs.h> +#include <asm/bitops.h> + +#include "av7110.h" + +#define UP_TIMEOUT (HZ/4) + +/* enable ir debugging by or'ing av7110_debug with 16 */ + +static int ir_initialized; +static struct input_dev input_dev; + +static u32 ir_config; + +static u16 key_map [256] = { + KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, + KEY_8, KEY_9, KEY_BACK, 0, KEY_POWER, KEY_MUTE, 0, KEY_INFO, + KEY_VOLUMEUP, KEY_VOLUMEDOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + KEY_CHANNELUP, KEY_CHANNELDOWN, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, KEY_TEXT, 0, 0, KEY_TV, 0, 0, 0, 0, 0, KEY_SETUP, 0, 0, + 0, 0, 0, KEY_SUBTITLE, 0, 0, KEY_LANGUAGE, 0, + KEY_RADIO, 0, 0, 0, 0, KEY_EXIT, 0, 0, + KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_OK, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_RED, KEY_GREEN, KEY_YELLOW, + KEY_BLUE, 0, 0, 0, 0, 0, 0, 0, KEY_MENU, KEY_LIST, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, KEY_UP, KEY_UP, KEY_DOWN, KEY_DOWN, + 0, 0, 0, 0, KEY_EPG, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_VCR +}; + + +static void av7110_emit_keyup(unsigned long data) +{ + if (!data || !test_bit(data, input_dev.key)) + return; + + input_event(&input_dev, EV_KEY, data, !!0); +} + + +static struct timer_list keyup_timer = { .function = av7110_emit_keyup }; + + +static void av7110_emit_key(u32 ircom) +{ + u8 data; + u8 addr; + static u16 old_toggle = 0; + u16 new_toggle; + u16 keycode; + + /* extract device address and data */ + if (ir_config & 0x0001) { + /* TODO RCMM: ? bits device address, 8 bits data */ + data = ircom & 0xff; + addr = (ircom >> 8) & 0xff; + } else { + /* RC5: 5 bits device address, 6 bits data */ + data = ircom & 0x3f; + addr = (ircom >> 6) & 0x1f; + } + + keycode = key_map[data]; + + dprintk(16, "#########%08x######### addr %i data 0x%02x (keycode %i)\n", + ircom, addr, data, keycode); + + /* check device address (if selected) */ + if (ir_config & 0x4000) + if (addr != ((ir_config >> 16) & 0xff)) + return; + + if (!keycode) { + printk ("%s: unknown key 0x%02x!!\n", __FUNCTION__, data); + return; + } + + if (ir_config & 0x0001) + new_toggle = 0; /* RCMM */ + else + new_toggle = (ircom & 0x800); /* RC5 */ + + if (timer_pending(&keyup_timer)) { + del_timer(&keyup_timer); + if (keyup_timer.data != keycode || new_toggle != old_toggle) { + input_event(&input_dev, EV_KEY, keyup_timer.data, !!0); + input_event(&input_dev, EV_KEY, keycode, !0); + } else + input_event(&input_dev, EV_KEY, keycode, 2); + + } else + input_event(&input_dev, EV_KEY, keycode, !0); + + keyup_timer.expires = jiffies + UP_TIMEOUT; + keyup_timer.data = keycode; + + add_timer(&keyup_timer); + + old_toggle = new_toggle; +} + +static void input_register_keys(void) +{ + int i; + + memset(input_dev.keybit, 0, sizeof(input_dev.keybit)); + + for (i = 0; i < sizeof(key_map) / sizeof(key_map[0]); i++) { + if (key_map[i] > KEY_MAX) + key_map[i] = 0; + else if (key_map[i] > KEY_RESERVED) + set_bit(key_map[i], input_dev.keybit); + } +} + + +static void input_repeat_key(unsigned long data) +{ + /* dummy routine to disable autorepeat in the input driver */ +} + + +static int av7110_ir_write_proc(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + char *page; + int size = 4 + 256 * sizeof(u16); + + if (count < size) + return -EINVAL; + + page = (char *) vmalloc(size); + if (!page) + return -ENOMEM; + + if (copy_from_user(page, buffer, size)) { + vfree(page); + return -EFAULT; + } + + memcpy(&ir_config, page, 4); + memcpy(&key_map, page + 4, 256 * sizeof(u16)); + vfree(page); + av7110_setup_irc_config(NULL, ir_config); + input_register_keys(); + return count; +} + + +int __init av7110_ir_init(void) +{ + static struct proc_dir_entry *e; + + if (ir_initialized) + return 0; + + init_timer(&keyup_timer); + keyup_timer.data = 0; + + input_dev.name = "DVB on-card IR receiver"; + + /** + * enable keys + */ + set_bit(EV_KEY, input_dev.evbit); + set_bit(EV_REP, input_dev.evbit); + + input_register_keys(); + + input_register_device(&input_dev); + input_dev.timer.function = input_repeat_key; + + av7110_setup_irc_config(NULL, 0x0001); + av7110_register_irc_handler(av7110_emit_key); + + e = create_proc_entry("av7110_ir", S_IFREG | S_IRUGO | S_IWUSR, NULL); + if (e) { + e->write_proc = av7110_ir_write_proc; + e->size = 4 + 256 * sizeof(u16); + } + + ir_initialized = 1; + return 0; +} + + +void __exit av7110_ir_exit(void) +{ + if (ir_initialized == 0) + return; + del_timer_sync(&keyup_timer); + remove_proc_entry("av7110_ir", NULL); + av7110_unregister_irc_handler(av7110_emit_key); + input_unregister_device(&input_dev); + ir_initialized = 0; +} + +//MODULE_AUTHOR("Holger Waechtler <holger@convergence.de>"); +//MODULE_LICENSE("GPL"); + diff --git a/drivers/media/dvb/ttpci/av7110_v4l.c b/drivers/media/dvb/ttpci/av7110_v4l.c new file mode 100644 index 000000000000..eb84fb08d95c --- /dev/null +++ b/drivers/media/dvb/ttpci/av7110_v4l.c @@ -0,0 +1,771 @@ +/* + * av7110_v4l.c: av7110 video4linux interface for DVB and Siemens DVB-C analog module + * + * Copyright (C) 1999-2002 Ralph Metzler + * & Marcus Metzler for convergence integrated media GmbH + * + * originally based on code by: + * Copyright (C) 1998,1999 Christian Theiss <mistert@rz.fh-augsburg.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + * the project's page is at http://www.linuxtv.org/dvb/ + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/timer.h> +#include <linux/poll.h> +#include <linux/byteorder/swabb.h> +#include <linux/smp_lock.h> + +#include "av7110.h" +#include "av7110_hw.h" +#include "av7110_av.h" + +int msp_writereg(struct av7110 *av7110, u8 dev, u16 reg, u16 val) +{ + u8 msg[5] = { dev, reg >> 8, reg & 0xff, val >> 8 , val & 0xff }; + struct i2c_msg msgs = { .flags = 0, .addr = 0x40, .len = 5, .buf = msg }; + + if (i2c_transfer(&av7110->i2c_adap, &msgs, 1) != 1) { + dprintk(1, "dvb-ttpci: failed @ card %d, %u = %u\n", + av7110->dvb_adapter->num, reg, val); + return -EIO; + } + return 0; +} + +int msp_readreg(struct av7110 *av7110, u8 dev, u16 reg, u16 *val) +{ + u8 msg1[3] = { dev, reg >> 8, reg & 0xff }; + u8 msg2[2]; + struct i2c_msg msgs[2] = { + { .flags = 0, .addr = 0x40, .len = 3, .buf = msg1 }, + { .flags = I2C_M_RD, .addr = 0x40, .len = 2, .buf = msg2 } + }; + + if (i2c_transfer(&av7110->i2c_adap, &msgs[0], 2) != 2) { + dprintk(1, "dvb-ttpci: failed @ card %d, %u\n", + av7110->dvb_adapter->num, reg); + return -EIO; + } + *val = (msg2[0] << 8) | msg2[1]; + return 0; +} + +static struct v4l2_input inputs[2] = { + { + .index = 0, + .name = "DVB", + .type = V4L2_INPUT_TYPE_CAMERA, + .audioset = 1, + .tuner = 0, /* ignored */ + .std = V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, + .status = 0, + }, { + .index = 1, + .name = "Television", + .type = V4L2_INPUT_TYPE_TUNER, + .audioset = 2, + .tuner = 0, + .std = V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, + .status = 0, + } +}; + +static int ves1820_writereg(struct saa7146_dev *dev, u8 addr, u8 reg, u8 data) +{ + u8 buf[] = { 0x00, reg, data }; + struct i2c_msg msg = { .addr = addr, .flags = 0, .buf = buf, .len = 3 }; + + dprintk(4, "dev: %p\n", dev); + + if (1 != saa7146_i2c_transfer(dev, &msg, 1, 1)) + return -1; + return 0; +} + +static int stv0297_writereg(struct saa7146_dev *dev, u8 addr, u8 reg, u8 data) +{ + u8 buf [] = { reg, data }; + struct i2c_msg msg = { .addr = addr, .flags = 0, .buf = buf, .len = 2 }; + + if (1 != saa7146_i2c_transfer(dev, &msg, 1, 1)) + return -1; + return 0; +} + + +static int tuner_write(struct saa7146_dev *dev, u8 addr, u8 data [4]) +{ + struct i2c_msg msg = { .addr = addr, .flags = 0, .buf = data, .len = 4 }; + + dprintk(4, "dev: %p\n", dev); + + if (1 != saa7146_i2c_transfer(dev, &msg, 1, 1)) + return -1; + return 0; +} + +static int ves1820_set_tv_freq(struct saa7146_dev *dev, u32 freq) +{ + u32 div; + u8 config; + u8 buf[4]; + + dprintk(4, "freq: 0x%08x\n", freq); + + /* magic number: 614. tuning with the frequency given by v4l2 + is always off by 614*62.5 = 38375 kHz...*/ + div = freq + 614; + + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = 0x8e; + + if (freq < (u32) (16 * 168.25)) + config = 0xa0; + else if (freq < (u32) (16 * 447.25)) + config = 0x90; + else + config = 0x30; + config &= ~0x02; + + buf[3] = config; + + return tuner_write(dev, 0x61, buf); +} + +static int stv0297_set_tv_freq(struct saa7146_dev *dev, u32 freq) +{ + u32 div; + u8 data[4]; + + div = (freq + 38900000 + 31250) / 62500; + + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = 0xce; + + if (freq < 45000000) + return -EINVAL; + else if (freq < 137000000) + data[3] = 0x01; + else if (freq < 403000000) + data[3] = 0x02; + else if (freq < 860000000) + data[3] = 0x04; + else + return -EINVAL; + + stv0297_writereg(dev, 0x1C, 0x87, 0x78); + stv0297_writereg(dev, 0x1C, 0x86, 0xc8); + return tuner_write(dev, 0x63, data); +} + + + +static struct saa7146_standard analog_standard[]; +static struct saa7146_standard dvb_standard[]; +static struct saa7146_standard standard[]; + +static struct v4l2_audio msp3400_v4l2_audio = { + .index = 0, + .name = "Television", + .capability = V4L2_AUDCAP_STEREO +}; + +static int av7110_dvb_c_switch(struct saa7146_fh *fh) +{ + struct saa7146_dev *dev = fh->dev; + struct saa7146_vv *vv = dev->vv_data; + struct av7110 *av7110 = (struct av7110*)dev->ext_priv; + u16 adswitch; + int source, sync, err; + + dprintk(4, "%p\n", av7110); + + if ((vv->video_status & STATUS_OVERLAY) != 0) { + vv->ov_suspend = vv->video_fh; + err = saa7146_stop_preview(vv->video_fh); /* side effect: video_status is now 0, video_fh is NULL */ + if (err != 0) { + dprintk(2, "suspending video failed\n"); + vv->ov_suspend = NULL; + } + } + + if (0 != av7110->current_input) { + adswitch = 1; + source = SAA7146_HPS_SOURCE_PORT_B; + sync = SAA7146_HPS_SYNC_PORT_B; + memcpy(standard, analog_standard, sizeof(struct saa7146_standard) * 2); + dprintk(1, "switching to analog TV\n"); + msp_writereg(av7110, MSP_WR_DSP, 0x0008, 0x0000); // loudspeaker source + msp_writereg(av7110, MSP_WR_DSP, 0x0009, 0x0000); // headphone source + msp_writereg(av7110, MSP_WR_DSP, 0x000a, 0x0000); // SCART 1 source + msp_writereg(av7110, MSP_WR_DSP, 0x000e, 0x3000); // FM matrix, mono + msp_writereg(av7110, MSP_WR_DSP, 0x0000, 0x4f00); // loudspeaker + headphone + msp_writereg(av7110, MSP_WR_DSP, 0x0007, 0x4f00); // SCART 1 volume + + if (av7110->analog_tuner_flags & ANALOG_TUNER_VES1820) { + if (ves1820_writereg(dev, 0x09, 0x0f, 0x60)) + dprintk(1, "setting band in demodulator failed.\n"); + } else if (av7110->analog_tuner_flags & ANALOG_TUNER_STV0297) { + saa7146_setgpio(dev, 1, SAA7146_GPIO_OUTHI); // TDA9198 pin9(STD) + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTHI); // TDA9198 pin30(VIF) + } + } else { + adswitch = 0; + source = SAA7146_HPS_SOURCE_PORT_A; + sync = SAA7146_HPS_SYNC_PORT_A; + memcpy(standard, dvb_standard, sizeof(struct saa7146_standard) * 2); + dprintk(1, "switching DVB mode\n"); + msp_writereg(av7110, MSP_WR_DSP, 0x0008, 0x0220); // loudspeaker source + msp_writereg(av7110, MSP_WR_DSP, 0x0009, 0x0220); // headphone source + msp_writereg(av7110, MSP_WR_DSP, 0x000a, 0x0220); // SCART 1 source + msp_writereg(av7110, MSP_WR_DSP, 0x000e, 0x3000); // FM matrix, mono + msp_writereg(av7110, MSP_WR_DSP, 0x0000, 0x7f00); // loudspeaker + headphone + msp_writereg(av7110, MSP_WR_DSP, 0x0007, 0x7f00); // SCART 1 volume + + if (av7110->analog_tuner_flags & ANALOG_TUNER_VES1820) { + if (ves1820_writereg(dev, 0x09, 0x0f, 0x20)) + dprintk(1, "setting band in demodulator failed.\n"); + } else if (av7110->analog_tuner_flags & ANALOG_TUNER_STV0297) { + saa7146_setgpio(dev, 1, SAA7146_GPIO_OUTLO); // TDA9198 pin9(STD) + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); // TDA9198 pin30(VIF) + } + } + + /* hmm, this does not do anything!? */ + if (av7110_fw_cmd(av7110, COMTYPE_AUDIODAC, ADSwitch, 1, adswitch)) + dprintk(1, "ADSwitch error\n"); + + saa7146_set_hps_source_and_sync(dev, source, sync); + + if (vv->ov_suspend != NULL) { + saa7146_start_preview(vv->ov_suspend); + vv->ov_suspend = NULL; + } + + return 0; +} + +static int av7110_ioctl(struct saa7146_fh *fh, unsigned int cmd, void *arg) +{ + struct saa7146_dev *dev = fh->dev; + struct av7110 *av7110 = (struct av7110*) dev->ext_priv; + dprintk(4, "saa7146_dev: %p\n", dev); + + switch (cmd) { + case VIDIOC_G_TUNER: + { + struct v4l2_tuner *t = arg; + u16 stereo_det; + s8 stereo; + + dprintk(2, "VIDIOC_G_TUNER: %d\n", t->index); + + if (!av7110->analog_tuner_flags || t->index != 0) + return -EINVAL; + + memset(t, 0, sizeof(*t)); + strcpy(t->name, "Television"); + + t->type = V4L2_TUNER_ANALOG_TV; + t->capability = V4L2_TUNER_CAP_NORM | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2 | V4L2_TUNER_CAP_SAP; + t->rangelow = 772; /* 48.25 MHZ / 62.5 kHz = 772, see fi1216mk2-specs, page 2 */ + t->rangehigh = 13684; /* 855.25 MHz / 62.5 kHz = 13684 */ + /* FIXME: add the real signal strength here */ + t->signal = 0xffff; + t->afc = 0; + + // FIXME: standard / stereo detection is still broken + msp_readreg(av7110, MSP_RD_DEM, 0x007e, &stereo_det); + dprintk(1, "VIDIOC_G_TUNER: msp3400 TV standard detection: 0x%04x\n", stereo_det); + + msp_readreg(av7110, MSP_RD_DSP, 0x0018, &stereo_det); + dprintk(1, "VIDIOC_G_TUNER: msp3400 stereo detection: 0x%04x\n", stereo_det); + stereo = (s8)(stereo_det >> 8); + if (stereo > 0x10) { + /* stereo */ + t->rxsubchans = V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_MONO; + t->audmode = V4L2_TUNER_MODE_STEREO; + } + else if (stereo < -0x10) { + /* bilingual*/ + t->rxsubchans = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + t->audmode = V4L2_TUNER_MODE_LANG1; + } + else /* mono */ + t->rxsubchans = V4L2_TUNER_SUB_MONO; + + return 0; + } + case VIDIOC_S_TUNER: + { + struct v4l2_tuner *t = arg; + u16 fm_matrix, src; + dprintk(2, "VIDIOC_S_TUNER: %d\n", t->index); + + if (!av7110->analog_tuner_flags || av7110->current_input != 1) + return -EINVAL; + + switch (t->audmode) { + case V4L2_TUNER_MODE_STEREO: + dprintk(2, "VIDIOC_S_TUNER: V4L2_TUNER_MODE_STEREO\n"); + fm_matrix = 0x3001; // stereo + src = 0x0020; + break; + case V4L2_TUNER_MODE_LANG1: + dprintk(2, "VIDIOC_S_TUNER: V4L2_TUNER_MODE_LANG1\n"); + fm_matrix = 0x3000; // mono + src = 0x0000; + break; + case V4L2_TUNER_MODE_LANG2: + dprintk(2, "VIDIOC_S_TUNER: V4L2_TUNER_MODE_LANG2\n"); + fm_matrix = 0x3000; // mono + src = 0x0010; + break; + default: /* case V4L2_TUNER_MODE_MONO: {*/ + dprintk(2, "VIDIOC_S_TUNER: TDA9840_SET_MONO\n"); + fm_matrix = 0x3000; // mono + src = 0x0030; + break; + } + msp_writereg(av7110, MSP_WR_DSP, 0x000e, fm_matrix); + msp_writereg(av7110, MSP_WR_DSP, 0x0008, src); + msp_writereg(av7110, MSP_WR_DSP, 0x0009, src); + msp_writereg(av7110, MSP_WR_DSP, 0x000a, src); + return 0; + } + case VIDIOC_G_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + dprintk(2, "VIDIOC_G_FREQ: freq:0x%08x.\n", f->frequency); + + if (!av7110->analog_tuner_flags || av7110->current_input != 1) + return -EINVAL; + + memset(f, 0, sizeof(*f)); + f->type = V4L2_TUNER_ANALOG_TV; + f->frequency = av7110->current_freq; + return 0; + } + case VIDIOC_S_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + dprintk(2, "VIDIOC_S_FREQUENCY: freq:0x%08x.\n", f->frequency); + + if (!av7110->analog_tuner_flags || av7110->current_input != 1) + return -EINVAL; + + if (V4L2_TUNER_ANALOG_TV != f->type) + return -EINVAL; + + msp_writereg(av7110, MSP_WR_DSP, 0x0000, 0xffe0); // fast mute + msp_writereg(av7110, MSP_WR_DSP, 0x0007, 0xffe0); + + /* tune in desired frequency */ + if (av7110->analog_tuner_flags & ANALOG_TUNER_VES1820) { + ves1820_set_tv_freq(dev, f->frequency); + } else if (av7110->analog_tuner_flags & ANALOG_TUNER_STV0297) { + stv0297_set_tv_freq(dev, f->frequency); + } + av7110->current_freq = f->frequency; + + msp_writereg(av7110, MSP_WR_DSP, 0x0015, 0x003f); // start stereo detection + msp_writereg(av7110, MSP_WR_DSP, 0x0015, 0x0000); + msp_writereg(av7110, MSP_WR_DSP, 0x0000, 0x4f00); // loudspeaker + headphone + msp_writereg(av7110, MSP_WR_DSP, 0x0007, 0x4f00); // SCART 1 volume + return 0; + } + case VIDIOC_ENUMINPUT: + { + struct v4l2_input *i = arg; + + dprintk(2, "VIDIOC_ENUMINPUT: %d\n", i->index); + + if (av7110->analog_tuner_flags) { + if (i->index < 0 || i->index >= 2) + return -EINVAL; + } else { + if (i->index != 0) + return -EINVAL; + } + + memcpy(i, &inputs[i->index], sizeof(struct v4l2_input)); + + return 0; + } + case VIDIOC_G_INPUT: + { + int *input = (int *)arg; + *input = av7110->current_input; + dprintk(2, "VIDIOC_G_INPUT: %d\n", *input); + return 0; + } + case VIDIOC_S_INPUT: + { + int input = *(int *)arg; + + dprintk(2, "VIDIOC_S_INPUT: %d\n", input); + + if (!av7110->analog_tuner_flags) + return 0; + + if (input < 0 || input >= 2) + return -EINVAL; + + /* FIXME: switch inputs here */ + av7110->current_input = input; + return av7110_dvb_c_switch(fh); + } + case VIDIOC_G_AUDIO: + { + struct v4l2_audio *a = arg; + + dprintk(2, "VIDIOC_G_AUDIO: %d\n", a->index); + if (a->index != 0) + return -EINVAL; + memcpy(a, &msp3400_v4l2_audio, sizeof(struct v4l2_audio)); + break; + } + case VIDIOC_S_AUDIO: + { + struct v4l2_audio *a = arg; + dprintk(2, "VIDIOC_S_AUDIO: %d\n", a->index); + break; + } + default: + printk("no such ioctl\n"); + return -ENOIOCTLCMD; + } + return 0; +} + + +/**************************************************************************** + * INITIALIZATION + ****************************************************************************/ + +static struct saa7146_extension_ioctls ioctls[] = { + { VIDIOC_ENUMINPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_G_INPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_S_INPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_G_FREQUENCY, SAA7146_EXCLUSIVE }, + { VIDIOC_S_FREQUENCY, SAA7146_EXCLUSIVE }, + { VIDIOC_G_TUNER, SAA7146_EXCLUSIVE }, + { VIDIOC_S_TUNER, SAA7146_EXCLUSIVE }, + { VIDIOC_G_AUDIO, SAA7146_EXCLUSIVE }, + { VIDIOC_S_AUDIO, SAA7146_EXCLUSIVE }, + { 0, 0 } +}; + +static u8 saa7113_init_regs[] = { + 0x02, 0xd0, + 0x03, 0x23, + 0x04, 0x00, + 0x05, 0x00, + 0x06, 0xe9, + 0x07, 0x0d, + 0x08, 0x98, + 0x09, 0x02, + 0x0a, 0x80, + 0x0b, 0x40, + 0x0c, 0x40, + 0x0d, 0x00, + 0x0e, 0x01, + 0x0f, 0x7c, + 0x10, 0x48, + 0x11, 0x0c, + 0x12, 0x8b, + 0x13, 0x1a, + 0x14, 0x00, + 0x15, 0x00, + 0x16, 0x00, + 0x17, 0x00, + 0x18, 0x00, + 0x19, 0x00, + 0x1a, 0x00, + 0x1b, 0x00, + 0x1c, 0x00, + 0x1d, 0x00, + 0x1e, 0x00, + + 0x41, 0x77, + 0x42, 0x77, + 0x43, 0x77, + 0x44, 0x77, + 0x45, 0x77, + 0x46, 0x77, + 0x47, 0x77, + 0x48, 0x77, + 0x49, 0x77, + 0x4a, 0x77, + 0x4b, 0x77, + 0x4c, 0x77, + 0x4d, 0x77, + 0x4e, 0x77, + 0x4f, 0x77, + 0x50, 0x77, + 0x51, 0x77, + 0x52, 0x77, + 0x53, 0x77, + 0x54, 0x77, + 0x55, 0x77, + 0x56, 0x77, + 0x57, 0xff, + + 0xff +}; + + +static struct saa7146_ext_vv av7110_vv_data_st; +static struct saa7146_ext_vv av7110_vv_data_c; + +int av7110_init_analog_module(struct av7110 *av7110) +{ + u16 version1, version2; + + if (i2c_writereg(av7110, 0x80, 0x0, 0x80) != 1 + || i2c_writereg(av7110, 0x80, 0x0, 0) != 1) + return -ENODEV; + + printk("dvb-ttpci: DVB-C analog module @ card %d detected, initializing MSP3400\n", + av7110->dvb_adapter->num); + av7110->adac_type = DVB_ADAC_MSP; + msleep(100); // the probing above resets the msp... + msp_readreg(av7110, MSP_RD_DSP, 0x001e, &version1); + msp_readreg(av7110, MSP_RD_DSP, 0x001f, &version2); + dprintk(1, "dvb-ttpci: @ card %d MSP3400 version 0x%04x 0x%04x\n", + av7110->dvb_adapter->num, version1, version2); + msp_writereg(av7110, MSP_WR_DSP, 0x0013, 0x0c00); + msp_writereg(av7110, MSP_WR_DSP, 0x0000, 0x7f00); // loudspeaker + headphone + msp_writereg(av7110, MSP_WR_DSP, 0x0008, 0x0220); // loudspeaker source + msp_writereg(av7110, MSP_WR_DSP, 0x0009, 0x0220); // headphone source + msp_writereg(av7110, MSP_WR_DSP, 0x0004, 0x7f00); // loudspeaker volume + msp_writereg(av7110, MSP_WR_DSP, 0x000a, 0x0220); // SCART 1 source + msp_writereg(av7110, MSP_WR_DSP, 0x0007, 0x7f00); // SCART 1 volume + msp_writereg(av7110, MSP_WR_DSP, 0x000d, 0x4800); // prescale SCART + + if (i2c_writereg(av7110, 0x48, 0x01, 0x00)!=1) { + INFO(("saa7113 not accessible.\n")); + } else { + u8 *i = saa7113_init_regs; + + if ((av7110->dev->pci->subsystem_vendor == 0x110a) && (av7110->dev->pci->subsystem_device == 0x0000)) { + /* Fujitsu/Siemens DVB-Cable */ + av7110->analog_tuner_flags |= ANALOG_TUNER_VES1820; + } else if ((av7110->dev->pci->subsystem_vendor == 0x13c2) && (av7110->dev->pci->subsystem_device == 0x0002)) { + /* Hauppauge/TT DVB-C premium */ + av7110->analog_tuner_flags |= ANALOG_TUNER_VES1820; + } else if ((av7110->dev->pci->subsystem_vendor == 0x13c2) && (av7110->dev->pci->subsystem_device == 0x000A)) { + /* Hauppauge/TT DVB-C premium */ + av7110->analog_tuner_flags |= ANALOG_TUNER_STV0297; + } + + /* setup for DVB by default */ + if (av7110->analog_tuner_flags & ANALOG_TUNER_VES1820) { + if (ves1820_writereg(av7110->dev, 0x09, 0x0f, 0x20)) + dprintk(1, "setting band in demodulator failed.\n"); + } else if (av7110->analog_tuner_flags & ANALOG_TUNER_STV0297) { + saa7146_setgpio(av7110->dev, 1, SAA7146_GPIO_OUTLO); // TDA9198 pin9(STD) + saa7146_setgpio(av7110->dev, 3, SAA7146_GPIO_OUTLO); // TDA9198 pin30(VIF) + } + + /* init the saa7113 */ + while (*i != 0xff) { + if (i2c_writereg(av7110, 0x48, i[0], i[1]) != 1) { + dprintk(1, "saa7113 initialization failed @ card %d", av7110->dvb_adapter->num); + break; + } + i += 2; + } + /* setup msp for analog sound: B/G Dual-FM */ + msp_writereg(av7110, MSP_WR_DEM, 0x00bb, 0x02d0); // AD_CV + msp_writereg(av7110, MSP_WR_DEM, 0x0001, 3); // FIR1 + msp_writereg(av7110, MSP_WR_DEM, 0x0001, 18); // FIR1 + msp_writereg(av7110, MSP_WR_DEM, 0x0001, 27); // FIR1 + msp_writereg(av7110, MSP_WR_DEM, 0x0001, 48); // FIR1 + msp_writereg(av7110, MSP_WR_DEM, 0x0001, 66); // FIR1 + msp_writereg(av7110, MSP_WR_DEM, 0x0001, 72); // FIR1 + msp_writereg(av7110, MSP_WR_DEM, 0x0005, 4); // FIR2 + msp_writereg(av7110, MSP_WR_DEM, 0x0005, 64); // FIR2 + msp_writereg(av7110, MSP_WR_DEM, 0x0005, 0); // FIR2 + msp_writereg(av7110, MSP_WR_DEM, 0x0005, 3); // FIR2 + msp_writereg(av7110, MSP_WR_DEM, 0x0005, 18); // FIR2 + msp_writereg(av7110, MSP_WR_DEM, 0x0005, 27); // FIR2 + msp_writereg(av7110, MSP_WR_DEM, 0x0005, 48); // FIR2 + msp_writereg(av7110, MSP_WR_DEM, 0x0005, 66); // FIR2 + msp_writereg(av7110, MSP_WR_DEM, 0x0005, 72); // FIR2 + msp_writereg(av7110, MSP_WR_DEM, 0x0083, 0xa000); // MODE_REG + msp_writereg(av7110, MSP_WR_DEM, 0x0093, 0x00aa); // DCO1_LO 5.74MHz + msp_writereg(av7110, MSP_WR_DEM, 0x009b, 0x04fc); // DCO1_HI + msp_writereg(av7110, MSP_WR_DEM, 0x00a3, 0x038e); // DCO2_LO 5.5MHz + msp_writereg(av7110, MSP_WR_DEM, 0x00ab, 0x04c6); // DCO2_HI + msp_writereg(av7110, MSP_WR_DEM, 0x0056, 0); // LOAD_REG 1/2 + } + + memcpy(standard, dvb_standard, sizeof(struct saa7146_standard) * 2); + /* set dd1 stream a & b */ + saa7146_write(av7110->dev, DD1_STREAM_B, 0x00000000); + saa7146_write(av7110->dev, DD1_INIT, 0x03000700); + saa7146_write(av7110->dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + + return 0; +} + +int av7110_init_v4l(struct av7110 *av7110) +{ + struct saa7146_dev* dev = av7110->dev; + int ret; + + /* special case DVB-C: these cards have an analog tuner + plus need some special handling, so we have separate + saa7146_ext_vv data for these... */ + if (av7110->analog_tuner_flags) + ret = saa7146_vv_init(dev, &av7110_vv_data_c); + else + ret = saa7146_vv_init(dev, &av7110_vv_data_st); + + if (ret) { + ERR(("cannot init capture device. skipping.\n")); + return -ENODEV; + } + + if (saa7146_register_device(&av7110->v4l_dev, dev, "av7110", VFL_TYPE_GRABBER)) { + ERR(("cannot register capture device. skipping.\n")); + saa7146_vv_release(dev); + return -ENODEV; + } + if (av7110->analog_tuner_flags) { + if (saa7146_register_device(&av7110->vbi_dev, dev, "av7110", VFL_TYPE_VBI)) { + ERR(("cannot register vbi v4l2 device. skipping.\n")); + } else { + av7110->analog_tuner_flags |= ANALOG_TUNER_VBI; + } + } + return 0; +} + +int av7110_exit_v4l(struct av7110 *av7110) +{ + saa7146_unregister_device(&av7110->v4l_dev, av7110->dev); + if (av7110->analog_tuner_flags & ANALOG_TUNER_VBI) + saa7146_unregister_device(&av7110->vbi_dev, av7110->dev); + return 0; +} + + + +/* FIXME: these values are experimental values that look better than the + values from the latest "official" driver -- at least for me... (MiHu) */ +static struct saa7146_standard standard[] = { + { + .name = "PAL", .id = V4L2_STD_PAL_BG, + .v_offset = 0x15, .v_field = 288, + .h_offset = 0x48, .h_pixels = 708, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "NTSC", .id = V4L2_STD_NTSC, + .v_offset = 0x10, .v_field = 244, + .h_offset = 0x40, .h_pixels = 708, + .v_max_out = 480, .h_max_out = 640, + } +}; + +static struct saa7146_standard analog_standard[] = { + { + .name = "PAL", .id = V4L2_STD_PAL_BG, + .v_offset = 0x1b, .v_field = 288, + .h_offset = 0x08, .h_pixels = 708, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "NTSC", .id = V4L2_STD_NTSC, + .v_offset = 0x10, .v_field = 244, + .h_offset = 0x40, .h_pixels = 708, + .v_max_out = 480, .h_max_out = 640, + } +}; + +static struct saa7146_standard dvb_standard[] = { + { + .name = "PAL", .id = V4L2_STD_PAL_BG, + .v_offset = 0x14, .v_field = 288, + .h_offset = 0x48, .h_pixels = 708, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "NTSC", .id = V4L2_STD_NTSC, + .v_offset = 0x10, .v_field = 244, + .h_offset = 0x40, .h_pixels = 708, + .v_max_out = 480, .h_max_out = 640, + } +}; + +static int std_callback(struct saa7146_dev* dev, struct saa7146_standard *std) +{ + struct av7110 *av7110 = (struct av7110*) dev->ext_priv; + + if (std->id == V4L2_STD_PAL) { + av7110->vidmode = VIDEO_MODE_PAL; + av7110_set_vidmode(av7110, av7110->vidmode); + } + else if (std->id == V4L2_STD_NTSC) { + av7110->vidmode = VIDEO_MODE_NTSC; + av7110_set_vidmode(av7110, av7110->vidmode); + } + else + return -1; + + return 0; +} + + +static struct saa7146_ext_vv av7110_vv_data_st = { + .inputs = 1, + .audios = 1, + .capabilities = 0, + .flags = 0, + + .stds = &standard[0], + .num_stds = ARRAY_SIZE(standard), + .std_callback = &std_callback, + + .ioctls = &ioctls[0], + .ioctl = av7110_ioctl, +}; + +static struct saa7146_ext_vv av7110_vv_data_c = { + .inputs = 1, + .audios = 1, + .capabilities = V4L2_CAP_TUNER | V4L2_CAP_VBI_CAPTURE, + .flags = SAA7146_USE_PORT_B_FOR_VBI, + + .stds = &standard[0], + .num_stds = ARRAY_SIZE(standard), + .std_callback = &std_callback, + + .ioctls = &ioctls[0], + .ioctl = av7110_ioctl, +}; + diff --git a/drivers/media/dvb/ttpci/budget-av.c b/drivers/media/dvb/ttpci/budget-av.c new file mode 100644 index 000000000000..14e963206b89 --- /dev/null +++ b/drivers/media/dvb/ttpci/budget-av.c @@ -0,0 +1,1014 @@ +/* + * budget-av.c: driver for the SAA7146 based Budget DVB cards + * with analog video in + * + * Compiled from various sources by Michael Hunold <michael@mihu.de> + * + * CI interface support (c) 2004 Olivier Gournet <ogournet@anevia.com> & + * Andrew de Quincey <adq_dvb@lidskialf.net> + * + * Copyright (C) 2002 Ralph Metzler <rjkm@metzlerbros.de> + * + * Copyright (C) 1999-2002 Ralph Metzler + * & Marcus Metzler for convergence integrated media GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + * + * the project's page is at http://www.linuxtv.org/dvb/ + */ + +#include "budget.h" +#include "stv0299.h" +#include "tda10021.h" +#include "tda1004x.h" +#include <media/saa7146_vv.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/spinlock.h> + +#include "dvb_ca_en50221.h" + +#define DEBICICAM 0x02420000 + +struct budget_av { + struct budget budget; + struct video_device *vd; + int cur_input; + int has_saa7113; + struct tasklet_struct ciintf_irq_tasklet; + int slot_status; + struct dvb_ca_en50221 ca; +}; + +static int enable_ci = 0; + + +/**************************************************************************** + * INITIALIZATION + ****************************************************************************/ + +static u8 i2c_readreg(struct i2c_adapter *i2c, u8 id, u8 reg) +{ + u8 mm1[] = { 0x00 }; + u8 mm2[] = { 0x00 }; + struct i2c_msg msgs[2]; + + msgs[0].flags = 0; + msgs[1].flags = I2C_M_RD; + msgs[0].addr = msgs[1].addr = id / 2; + mm1[0] = reg; + msgs[0].len = 1; + msgs[1].len = 1; + msgs[0].buf = mm1; + msgs[1].buf = mm2; + + i2c_transfer(i2c, msgs, 2); + + return mm2[0]; +} + +static int i2c_readregs(struct i2c_adapter *i2c, u8 id, u8 reg, u8 * buf, u8 len) +{ + u8 mm1[] = { reg }; + struct i2c_msg msgs[2] = { + {.addr = id / 2,.flags = 0,.buf = mm1,.len = 1}, + {.addr = id / 2,.flags = I2C_M_RD,.buf = buf,.len = len} + }; + + if (i2c_transfer(i2c, msgs, 2) != 2) + return -EIO; + + return 0; +} + +static int i2c_writereg(struct i2c_adapter *i2c, u8 id, u8 reg, u8 val) +{ + u8 msg[2] = { reg, val }; + struct i2c_msg msgs; + + msgs.flags = 0; + msgs.addr = id / 2; + msgs.len = 2; + msgs.buf = msg; + return i2c_transfer(i2c, &msgs, 1); +} + +static int ciintf_read_attribute_mem(struct dvb_ca_en50221 *ca, int slot, int address) +{ + struct budget_av *budget_av = (struct budget_av *) ca->data; + int result; + + if (slot != 0) + return -EINVAL; + + saa7146_setgpio(budget_av->budget.dev, 1, SAA7146_GPIO_OUTHI); + udelay(1); + + result = ttpci_budget_debiread(&budget_av->budget, DEBICICAM, address & 0xfff, 1, 0, 0); + + if (result == -ETIMEDOUT) + budget_av->slot_status = 0; + return result; +} + +static int ciintf_write_attribute_mem(struct dvb_ca_en50221 *ca, int slot, int address, u8 value) +{ + struct budget_av *budget_av = (struct budget_av *) ca->data; + int result; + + if (slot != 0) + return -EINVAL; + + saa7146_setgpio(budget_av->budget.dev, 1, SAA7146_GPIO_OUTHI); + udelay(1); + + result = ttpci_budget_debiwrite(&budget_av->budget, DEBICICAM, address & 0xfff, 1, value, 0, 0); + + if (result == -ETIMEDOUT) + budget_av->slot_status = 0; + return result; +} + +static int ciintf_read_cam_control(struct dvb_ca_en50221 *ca, int slot, u8 address) +{ + struct budget_av *budget_av = (struct budget_av *) ca->data; + int result; + + if (slot != 0) + return -EINVAL; + + saa7146_setgpio(budget_av->budget.dev, 1, SAA7146_GPIO_OUTLO); + udelay(1); + + result = ttpci_budget_debiread(&budget_av->budget, DEBICICAM, address & 3, 1, 0, 0); + + if (result == -ETIMEDOUT) + budget_av->slot_status = 0; + return result; +} + +static int ciintf_write_cam_control(struct dvb_ca_en50221 *ca, int slot, u8 address, u8 value) +{ + struct budget_av *budget_av = (struct budget_av *) ca->data; + int result; + + if (slot != 0) + return -EINVAL; + + saa7146_setgpio(budget_av->budget.dev, 1, SAA7146_GPIO_OUTLO); + udelay(1); + + result = ttpci_budget_debiwrite(&budget_av->budget, DEBICICAM, address & 3, 1, value, 0, 0); + + if (result == -ETIMEDOUT) + budget_av->slot_status = 0; + return result; +} + +static int ciintf_slot_reset(struct dvb_ca_en50221 *ca, int slot) +{ + struct budget_av *budget_av = (struct budget_av *) ca->data; + struct saa7146_dev *saa = budget_av->budget.dev; + int max = 20; + + if (slot != 0) + return -EINVAL; + + dprintk(1, "ciintf_slot_reset\n"); + + /* reset the card */ + saa7146_setgpio(saa, 0, SAA7146_GPIO_OUTHI); + msleep(100); + saa7146_setgpio(saa, 0, SAA7146_GPIO_OUTLO); + + while (--max > 0 && ciintf_read_attribute_mem(ca, slot, 0) != 0x1d) + msleep(100); + + ttpci_budget_set_video_port(saa, BUDGET_VIDEO_PORTB); + return 0; +} + +static int ciintf_slot_shutdown(struct dvb_ca_en50221 *ca, int slot) +{ + struct budget_av *budget_av = (struct budget_av *) ca->data; + struct saa7146_dev *saa = budget_av->budget.dev; + + if (slot != 0) + return -EINVAL; + + dprintk(1, "ciintf_slot_shutdown\n"); + + ttpci_budget_set_video_port(saa, BUDGET_VIDEO_PORTB); + budget_av->slot_status = 0; + return 0; +} + +static int ciintf_slot_ts_enable(struct dvb_ca_en50221 *ca, int slot) +{ + struct budget_av *budget_av = (struct budget_av *) ca->data; + struct saa7146_dev *saa = budget_av->budget.dev; + + if (slot != 0) + return -EINVAL; + + dprintk(1, "ciintf_slot_ts_enable: %d\n", budget_av->slot_status); + + ttpci_budget_set_video_port(saa, BUDGET_VIDEO_PORTA); + return 0; +} + +static int ciintf_poll_slot_status(struct dvb_ca_en50221 *ca, int slot, int open) +{ + struct budget_av *budget_av = (struct budget_av *) ca->data; + struct saa7146_dev *saa = budget_av->budget.dev; + int cam = 0; + + if (slot != 0) + return -EINVAL; + + if (!budget_av->slot_status) { + saa7146_setgpio(saa, 3, SAA7146_GPIO_INPUT); + udelay(1); + cam = saa7146_read(saa, PSR) & MASK_06; + saa7146_setgpio(saa, 3, SAA7146_GPIO_OUTLO); + + if (cam) + budget_av->slot_status = 1; + } else if (!open) { + saa7146_setgpio(budget_av->budget.dev, 1, SAA7146_GPIO_OUTLO); + if (ttpci_budget_debiread(&budget_av->budget, DEBICICAM, 0, 1, 0, 1) == -ETIMEDOUT) + budget_av->slot_status = 0; + } + + if (budget_av->slot_status == 1) + return DVB_CA_EN50221_POLL_CAM_PRESENT | DVB_CA_EN50221_POLL_CAM_READY; + + return 0; +} + +static int ciintf_init(struct budget_av *budget_av) +{ + struct saa7146_dev *saa = budget_av->budget.dev; + int result; + + memset(&budget_av->ca, 0, sizeof(struct dvb_ca_en50221)); + + /* setup GPIOs */ + saa7146_setgpio(saa, 1, SAA7146_GPIO_OUTHI); + saa7146_setgpio(saa, 2, SAA7146_GPIO_OUTLO); + saa7146_setgpio(saa, 3, SAA7146_GPIO_OUTLO); + + /* Reset the card */ + saa7146_setgpio(saa, 0, SAA7146_GPIO_OUTHI); + msleep(50); + saa7146_setgpio(saa, 0, SAA7146_GPIO_OUTLO); + msleep(100); + + /* Enable DEBI pins */ + saa7146_write(saa, MC1, saa7146_read(saa, MC1) | (0x800 << 16) | 0x800); + + /* register CI interface */ + budget_av->ca.owner = THIS_MODULE; + budget_av->ca.read_attribute_mem = ciintf_read_attribute_mem; + budget_av->ca.write_attribute_mem = ciintf_write_attribute_mem; + budget_av->ca.read_cam_control = ciintf_read_cam_control; + budget_av->ca.write_cam_control = ciintf_write_cam_control; + budget_av->ca.slot_reset = ciintf_slot_reset; + budget_av->ca.slot_shutdown = ciintf_slot_shutdown; + budget_av->ca.slot_ts_enable = ciintf_slot_ts_enable; + budget_av->ca.poll_slot_status = ciintf_poll_slot_status; + budget_av->ca.data = budget_av; + if ((result = dvb_ca_en50221_init(budget_av->budget.dvb_adapter, + &budget_av->ca, 0, 1)) != 0) { + printk("budget_av: CI interface detected, but initialisation failed.\n"); + goto error; + } + // success! + printk("ciintf_init: CI interface initialised\n"); + budget_av->budget.ci_present = 1; + return 0; + +error: + saa7146_write(saa, MC1, saa7146_read(saa, MC1) | (0x800 << 16)); + return result; +} + +static void ciintf_deinit(struct budget_av *budget_av) +{ + struct saa7146_dev *saa = budget_av->budget.dev; + + saa7146_setgpio(saa, 0, SAA7146_GPIO_INPUT); + saa7146_setgpio(saa, 1, SAA7146_GPIO_INPUT); + saa7146_setgpio(saa, 2, SAA7146_GPIO_INPUT); + saa7146_setgpio(saa, 3, SAA7146_GPIO_INPUT); + + /* release the CA device */ + dvb_ca_en50221_release(&budget_av->ca); + + /* disable DEBI pins */ + saa7146_write(saa, MC1, saa7146_read(saa, MC1) | (0x800 << 16)); +} + + +static const u8 saa7113_tab[] = { + 0x01, 0x08, + 0x02, 0xc0, + 0x03, 0x33, + 0x04, 0x00, + 0x05, 0x00, + 0x06, 0xeb, + 0x07, 0xe0, + 0x08, 0x28, + 0x09, 0x00, + 0x0a, 0x80, + 0x0b, 0x47, + 0x0c, 0x40, + 0x0d, 0x00, + 0x0e, 0x01, + 0x0f, 0x44, + + 0x10, 0x08, + 0x11, 0x0c, + 0x12, 0x7b, + 0x13, 0x00, + 0x15, 0x00, 0x16, 0x00, 0x17, 0x00, + + 0x57, 0xff, + 0x40, 0x82, 0x58, 0x00, 0x59, 0x54, 0x5a, 0x07, + 0x5b, 0x83, 0x5e, 0x00, + 0xff +}; + +static int saa7113_init(struct budget_av *budget_av) +{ + struct budget *budget = &budget_av->budget; + const u8 *data = saa7113_tab; + + if (i2c_writereg(&budget->i2c_adap, 0x4a, 0x01, 0x08) != 1) { + dprintk(1, "saa7113 not found on KNC card\n"); + return -ENODEV; + } + + dprintk(1, "saa7113 detected and initializing\n"); + + while (*data != 0xff) { + i2c_writereg(&budget->i2c_adap, 0x4a, *data, *(data + 1)); + data += 2; + } + + dprintk(1, "saa7113 status=%02x\n", i2c_readreg(&budget->i2c_adap, 0x4a, 0x1f)); + + return 0; +} + +static int saa7113_setinput(struct budget_av *budget_av, int input) +{ + struct budget *budget = &budget_av->budget; + + if (1 != budget_av->has_saa7113) + return -ENODEV; + + if (input == 1) { + i2c_writereg(&budget->i2c_adap, 0x4a, 0x02, 0xc7); + i2c_writereg(&budget->i2c_adap, 0x4a, 0x09, 0x80); + } else if (input == 0) { + i2c_writereg(&budget->i2c_adap, 0x4a, 0x02, 0xc0); + i2c_writereg(&budget->i2c_adap, 0x4a, 0x09, 0x00); + } else + return -EINVAL; + + budget_av->cur_input = input; + return 0; +} + + +static int philips_su1278_ty_ci_set_symbol_rate(struct dvb_frontend *fe, u32 srate, u32 ratio) +{ + u8 aclk = 0; + u8 bclk = 0; + u8 m1; + + aclk = 0xb5; + if (srate < 2000000) + bclk = 0x86; + else if (srate < 5000000) + bclk = 0x89; + else if (srate < 15000000) + bclk = 0x8f; + else if (srate < 45000000) + bclk = 0x95; + + m1 = 0x14; + if (srate < 4000000) + m1 = 0x10; + + stv0299_writereg(fe, 0x13, aclk); + stv0299_writereg(fe, 0x14, bclk); + stv0299_writereg(fe, 0x1f, (ratio >> 16) & 0xff); + stv0299_writereg(fe, 0x20, (ratio >> 8) & 0xff); + stv0299_writereg(fe, 0x21, (ratio) & 0xf0); + stv0299_writereg(fe, 0x0f, 0x80 | m1); + + return 0; +} + +static int philips_su1278_ty_ci_pll_set(struct dvb_frontend *fe, + struct dvb_frontend_parameters *params) +{ + struct budget_av *budget_av = (struct budget_av *) fe->dvb->priv; + u32 div; + u8 buf[4]; + struct i2c_msg msg = {.addr = 0x61,.flags = 0,.buf = buf,.len = sizeof(buf) }; + + if ((params->frequency < 950000) || (params->frequency > 2150000)) + return -EINVAL; + + div = (params->frequency + (125 - 1)) / 125; // round correctly + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = 0x80 | ((div & 0x18000) >> 10) | 4; + buf[3] = 0x20; + + if (params->u.qpsk.symbol_rate < 4000000) + buf[3] |= 1; + + if (params->frequency < 1250000) + buf[3] |= 0; + else if (params->frequency < 1550000) + buf[3] |= 0x40; + else if (params->frequency < 2050000) + buf[3] |= 0x80; + else if (params->frequency < 2150000) + buf[3] |= 0xC0; + + if (i2c_transfer(&budget_av->budget.i2c_adap, &msg, 1) != 1) + return -EIO; + return 0; +} + +static u8 typhoon_cinergy1200s_inittab[] = { + 0x01, 0x15, + 0x02, 0x30, + 0x03, 0x00, + 0x04, 0x7d, /* F22FR = 0x7d, F22 = f_VCO / 128 / 0x7d = 22 kHz */ + 0x05, 0x35, /* I2CT = 0, SCLT = 1, SDAT = 1 */ + 0x06, 0x40, /* DAC not used, set to high impendance mode */ + 0x07, 0x00, /* DAC LSB */ + 0x08, 0x40, /* DiSEqC off */ + 0x09, 0x00, /* FIFO */ + 0x0c, 0x51, /* OP1 ctl = Normal, OP1 val = 1 (LNB Power ON) */ + 0x0d, 0x82, /* DC offset compensation = ON, beta_agc1 = 2 */ + 0x0e, 0x23, /* alpha_tmg = 2, beta_tmg = 3 */ + 0x10, 0x3f, // AGC2 0x3d + 0x11, 0x84, + 0x12, 0xb5, // Lock detect: -64 Carrier freq detect:on + 0x15, 0xc9, // lock detector threshold + 0x16, 0x00, + 0x17, 0x00, + 0x18, 0x00, + 0x19, 0x00, + 0x1a, 0x00, + 0x1f, 0x50, + 0x20, 0x00, + 0x21, 0x00, + 0x22, 0x00, + 0x23, 0x00, + 0x28, 0x00, // out imp: normal out type: parallel FEC mode:0 + 0x29, 0x1e, // 1/2 threshold + 0x2a, 0x14, // 2/3 threshold + 0x2b, 0x0f, // 3/4 threshold + 0x2c, 0x09, // 5/6 threshold + 0x2d, 0x05, // 7/8 threshold + 0x2e, 0x01, + 0x31, 0x1f, // test all FECs + 0x32, 0x19, // viterbi and synchro search + 0x33, 0xfc, // rs control + 0x34, 0x93, // error control + 0x0f, 0x92, + 0xff, 0xff +}; + +static struct stv0299_config typhoon_config = { + .demod_address = 0x68, + .inittab = typhoon_cinergy1200s_inittab, + .mclk = 88000000UL, + .invert = 0, + .enhanced_tuning = 0, + .skip_reinit = 0, + .lock_output = STV0229_LOCKOUTPUT_1, + .volt13_op0_op1 = STV0299_VOLT13_OP0, + .min_delay_ms = 100, + .set_symbol_rate = philips_su1278_ty_ci_set_symbol_rate, + .pll_set = philips_su1278_ty_ci_pll_set, +}; + + +static struct stv0299_config cinergy_1200s_config = { + .demod_address = 0x68, + .inittab = typhoon_cinergy1200s_inittab, + .mclk = 88000000UL, + .invert = 0, + .enhanced_tuning = 0, + .skip_reinit = 0, + .lock_output = STV0229_LOCKOUTPUT_0, + .volt13_op0_op1 = STV0299_VOLT13_OP0, + .min_delay_ms = 100, + .set_symbol_rate = philips_su1278_ty_ci_set_symbol_rate, + .pll_set = philips_su1278_ty_ci_pll_set, +}; + + +static int philips_cu1216_pll_set(struct dvb_frontend *fe, struct dvb_frontend_parameters *params) +{ + struct budget *budget = (struct budget *) fe->dvb->priv; + u8 buf[4]; + struct i2c_msg msg = {.addr = 0x60,.flags = 0,.buf = buf,.len = sizeof(buf) }; + +#define TUNER_MUL 62500 + + u32 div = (params->frequency + 36125000 + TUNER_MUL / 2) / TUNER_MUL; + + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = 0x8e; + buf[3] = (params->frequency < 174500000 ? 0xa1 : + params->frequency < 454000000 ? 0x92 : 0x34); + + if (i2c_transfer(&budget->i2c_adap, &msg, 1) != 1) + return -EIO; + return 0; +} + +static struct tda10021_config philips_cu1216_config = { + .demod_address = 0x0c, + .pll_set = philips_cu1216_pll_set, +}; + + + + +static int philips_tu1216_pll_init(struct dvb_frontend *fe) +{ + struct budget *budget = (struct budget *) fe->dvb->priv; + static u8 tu1216_init[] = { 0x0b, 0xf5, 0x85, 0xab }; + struct i2c_msg tuner_msg = {.addr = 0x60,.flags = 0,.buf = tu1216_init,.len = sizeof(tu1216_init) }; + + // setup PLL configuration + if (i2c_transfer(&budget->i2c_adap, &tuner_msg, 1) != 1) + return -EIO; + msleep(1); + + return 0; +} + +static int philips_tu1216_pll_set(struct dvb_frontend *fe, struct dvb_frontend_parameters *params) +{ + struct budget *budget = (struct budget *) fe->dvb->priv; + u8 tuner_buf[4]; + struct i2c_msg tuner_msg = {.addr = 0x60,.flags = 0,.buf = tuner_buf,.len = + sizeof(tuner_buf) }; + int tuner_frequency = 0; + u8 band, cp, filter; + + // determine charge pump + tuner_frequency = params->frequency + 36166000; + if (tuner_frequency < 87000000) + return -EINVAL; + else if (tuner_frequency < 130000000) + cp = 3; + else if (tuner_frequency < 160000000) + cp = 5; + else if (tuner_frequency < 200000000) + cp = 6; + else if (tuner_frequency < 290000000) + cp = 3; + else if (tuner_frequency < 420000000) + cp = 5; + else if (tuner_frequency < 480000000) + cp = 6; + else if (tuner_frequency < 620000000) + cp = 3; + else if (tuner_frequency < 830000000) + cp = 5; + else if (tuner_frequency < 895000000) + cp = 7; + else + return -EINVAL; + + // determine band + if (params->frequency < 49000000) + return -EINVAL; + else if (params->frequency < 161000000) + band = 1; + else if (params->frequency < 444000000) + band = 2; + else if (params->frequency < 861000000) + band = 4; + else + return -EINVAL; + + // setup PLL filter + switch (params->u.ofdm.bandwidth) { + case BANDWIDTH_6_MHZ: + filter = 0; + break; + + case BANDWIDTH_7_MHZ: + filter = 0; + break; + + case BANDWIDTH_8_MHZ: + filter = 1; + break; + + default: + return -EINVAL; + } + + // calculate divisor + // ((36166000+((1000000/6)/2)) + Finput)/(1000000/6) + tuner_frequency = (((params->frequency / 1000) * 6) + 217496) / 1000; + + // setup tuner buffer + tuner_buf[0] = (tuner_frequency >> 8) & 0x7f; + tuner_buf[1] = tuner_frequency & 0xff; + tuner_buf[2] = 0xca; + tuner_buf[3] = (cp << 5) | (filter << 3) | band; + + if (i2c_transfer(&budget->i2c_adap, &tuner_msg, 1) != 1) + return -EIO; + + msleep(1); + return 0; +} + +static int philips_tu1216_request_firmware(struct dvb_frontend *fe, + const struct firmware **fw, char *name) +{ + struct budget *budget = (struct budget *) fe->dvb->priv; + + return request_firmware(fw, name, &budget->dev->pci->dev); +} + +static struct tda1004x_config philips_tu1216_config = { + + .demod_address = 0x8, + .invert = 1, + .invert_oclk = 1, + .pll_init = philips_tu1216_pll_init, + .pll_set = philips_tu1216_pll_set, + .request_firmware = philips_tu1216_request_firmware, +}; + + + + +static u8 read_pwm(struct budget_av *budget_av) +{ + u8 b = 0xff; + u8 pwm; + struct i2c_msg msg[] = { {.addr = 0x50,.flags = 0,.buf = &b,.len = 1}, + {.addr = 0x50,.flags = I2C_M_RD,.buf = &pwm,.len = 1} + }; + + if ((i2c_transfer(&budget_av->budget.i2c_adap, msg, 2) != 2) + || (pwm == 0xff)) + pwm = 0x48; + + return pwm; +} + + +static void frontend_init(struct budget_av *budget_av) +{ + switch (budget_av->budget.dev->pci->subsystem_device) { + case 0x4f56: // Typhoon/KNC1 DVB-S budget (stv0299/Philips SU1278(tsa5059)) + budget_av->budget.dvb_frontend = + stv0299_attach(&typhoon_config, &budget_av->budget.i2c_adap); + if (budget_av->budget.dvb_frontend != NULL) { + break; + } + break; + + case 0x0020: // KNC1 DVB-C budget (tda10021/Philips CU1216(tua6034)) + budget_av->budget.dvb_frontend = + tda10021_attach(&philips_cu1216_config, + &budget_av->budget.i2c_adap, read_pwm(budget_av)); + if (budget_av->budget.dvb_frontend != NULL) { + break; + } + break; + + case 0x0030: // KNC1 DVB-T budget (tda10046/Philips TU1216(tda6651tt)) + budget_av->budget.dvb_frontend = + tda10046_attach(&philips_tu1216_config, &budget_av->budget.i2c_adap); + if (budget_av->budget.dvb_frontend != NULL) { + break; + } + break; + + case 0x1154: // TerraTec Cinergy 1200 DVB-S (stv0299/Philips SU1278(tsa5059)) + budget_av->budget.dvb_frontend = + stv0299_attach(&cinergy_1200s_config, &budget_av->budget.i2c_adap); + if (budget_av->budget.dvb_frontend != NULL) { + break; + } + break; + + case 0x1156: // Terratec Cinergy 1200 DVB-C (tda10021/Philips CU1216(tua6034)) + budget_av->budget.dvb_frontend = + tda10021_attach(&philips_cu1216_config, + &budget_av->budget.i2c_adap, read_pwm(budget_av)); + if (budget_av->budget.dvb_frontend) { + break; + } + break; + + case 0x1157: // Terratec Cinergy 1200 DVB-T (tda10046/Philips TU1216(tda6651tt)) + budget_av->budget.dvb_frontend = + tda10046_attach(&philips_tu1216_config, &budget_av->budget.i2c_adap); + if (budget_av->budget.dvb_frontend) { + break; + } + break; + } + + if (budget_av->budget.dvb_frontend == NULL) { + printk("budget_av: A frontend driver was not found for device %04x/%04x subsystem %04x/%04x\n", + budget_av->budget.dev->pci->vendor, + budget_av->budget.dev->pci->device, + budget_av->budget.dev->pci->subsystem_vendor, + budget_av->budget.dev->pci->subsystem_device); + } else { + if (dvb_register_frontend + (budget_av->budget.dvb_adapter, budget_av->budget.dvb_frontend)) { + printk("budget-av: Frontend registration failed!\n"); + if (budget_av->budget.dvb_frontend->ops->release) + budget_av->budget.dvb_frontend->ops->release(budget_av->budget.dvb_frontend); + budget_av->budget.dvb_frontend = NULL; + } + } +} + + +static void budget_av_irq(struct saa7146_dev *dev, u32 * isr) +{ + struct budget_av *budget_av = (struct budget_av *) dev->ext_priv; + + dprintk(8, "dev: %p, budget_av: %p\n", dev, budget_av); + + if (*isr & MASK_10) + ttpci_budget_irq10_handler(dev, isr); +} + +static int budget_av_detach(struct saa7146_dev *dev) +{ + struct budget_av *budget_av = (struct budget_av *) dev->ext_priv; + int err; + + dprintk(2, "dev: %p\n", dev); + + if (1 == budget_av->has_saa7113) { + saa7146_setgpio(dev, 0, SAA7146_GPIO_OUTLO); + + msleep(200); + + saa7146_unregister_device(&budget_av->vd, dev); + } + + if (budget_av->budget.ci_present) + ciintf_deinit(budget_av); + + if (budget_av->budget.dvb_frontend != NULL) + dvb_unregister_frontend(budget_av->budget.dvb_frontend); + err = ttpci_budget_deinit(&budget_av->budget); + + kfree(budget_av); + + return err; +} + +static struct saa7146_ext_vv vv_data; + +static int budget_av_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info) +{ + struct budget_av *budget_av; + u8 *mac; + int err; + + dprintk(2, "dev: %p\n", dev); + + if (!(budget_av = kmalloc(sizeof(struct budget_av), GFP_KERNEL))) + return -ENOMEM; + + memset(budget_av, 0, sizeof(struct budget_av)); + + budget_av->budget.ci_present = 0; + + dev->ext_priv = budget_av; + + if ((err = ttpci_budget_init(&budget_av->budget, dev, info, THIS_MODULE))) { + kfree(budget_av); + return err; + } + + /* knc1 initialization */ + saa7146_write(dev, DD1_STREAM_B, 0x04000000); + saa7146_write(dev, DD1_INIT, 0x07000600); + saa7146_write(dev, MC2, MASK_09 | MASK_25 | MASK_10 | MASK_26); + + saa7146_setgpio(dev, 0, SAA7146_GPIO_OUTHI); + msleep(500); + + if (0 == saa7113_init(budget_av)) { + budget_av->has_saa7113 = 1; + + if (0 != saa7146_vv_init(dev, &vv_data)) { + /* fixme: proper cleanup here */ + ERR(("cannot init vv subsystem.\n")); + return err; + } + + if ((err = saa7146_register_device(&budget_av->vd, dev, "knc1", VFL_TYPE_GRABBER))) { + /* fixme: proper cleanup here */ + ERR(("cannot register capture v4l2 device.\n")); + return err; + } + + /* beware: this modifies dev->vv ... */ + saa7146_set_hps_source_and_sync(dev, SAA7146_HPS_SOURCE_PORT_A, + SAA7146_HPS_SYNC_PORT_A); + + saa7113_setinput(budget_av, 0); + } else { + budget_av->has_saa7113 = 0; + + saa7146_setgpio(dev, 0, SAA7146_GPIO_OUTLO); + } + + /* fixme: find some sane values here... */ + saa7146_write(dev, PCI_BT_V1, 0x1c00101f); + + mac = budget_av->budget.dvb_adapter->proposed_mac; + if (i2c_readregs(&budget_av->budget.i2c_adap, 0xa0, 0x30, mac, 6)) { + printk("KNC1-%d: Could not read MAC from KNC1 card\n", + budget_av->budget.dvb_adapter->num); + memset(mac, 0, 6); + } else { + printk("KNC1-%d: MAC addr = %.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n", + budget_av->budget.dvb_adapter->num, + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + } + + budget_av->budget.dvb_adapter->priv = budget_av; + frontend_init(budget_av); + + if (enable_ci) + ciintf_init(budget_av); + + return 0; +} + +#define KNC1_INPUTS 2 +static struct v4l2_input knc1_inputs[KNC1_INPUTS] = { + {0, "Composite", V4L2_INPUT_TYPE_TUNER, 1, 0, V4L2_STD_PAL_BG | V4L2_STD_NTSC_M, 0}, + {1, "S-Video", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG | V4L2_STD_NTSC_M, 0}, +}; + +static struct saa7146_extension_ioctls ioctls[] = { + {VIDIOC_ENUMINPUT, SAA7146_EXCLUSIVE}, + {VIDIOC_G_INPUT, SAA7146_EXCLUSIVE}, + {VIDIOC_S_INPUT, SAA7146_EXCLUSIVE}, + {0, 0} +}; + +static int av_ioctl(struct saa7146_fh *fh, unsigned int cmd, void *arg) +{ + struct saa7146_dev *dev = fh->dev; + struct budget_av *budget_av = (struct budget_av *) dev->ext_priv; + + switch (cmd) { + case VIDIOC_ENUMINPUT:{ + struct v4l2_input *i = arg; + + dprintk(1, "VIDIOC_ENUMINPUT %d.\n", i->index); + if (i->index < 0 || i->index >= KNC1_INPUTS) { + return -EINVAL; + } + memcpy(i, &knc1_inputs[i->index], sizeof(struct v4l2_input)); + return 0; + } + case VIDIOC_G_INPUT:{ + int *input = (int *) arg; + + *input = budget_av->cur_input; + + dprintk(1, "VIDIOC_G_INPUT %d.\n", *input); + return 0; + } + case VIDIOC_S_INPUT:{ + int input = *(int *) arg; + dprintk(1, "VIDIOC_S_INPUT %d.\n", input); + return saa7113_setinput(budget_av, input); + } + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static struct saa7146_standard standard[] = { + {.name = "PAL",.id = V4L2_STD_PAL, + .v_offset = 0x17,.v_field = 288, + .h_offset = 0x14,.h_pixels = 680, + .v_max_out = 576,.h_max_out = 768 }, + + {.name = "NTSC",.id = V4L2_STD_NTSC, + .v_offset = 0x16,.v_field = 240, + .h_offset = 0x06,.h_pixels = 708, + .v_max_out = 480,.h_max_out = 640, }, +}; + +static struct saa7146_ext_vv vv_data = { + .inputs = 2, + .capabilities = 0, // perhaps later: V4L2_CAP_VBI_CAPTURE, but that need tweaking with the saa7113 + .flags = 0, + .stds = &standard[0], + .num_stds = sizeof(standard) / sizeof(struct saa7146_standard), + .ioctls = &ioctls[0], + .ioctl = av_ioctl, +}; + +static struct saa7146_extension budget_extension; + +MAKE_BUDGET_INFO(knc1s, "KNC1 DVB-S", BUDGET_KNC1S); +MAKE_BUDGET_INFO(knc1c, "KNC1 DVB-C", BUDGET_KNC1C); +MAKE_BUDGET_INFO(knc1t, "KNC1 DVB-T", BUDGET_KNC1T); +MAKE_BUDGET_INFO(cin1200s, "TerraTec Cinergy 1200 DVB-S", BUDGET_CIN1200S); +MAKE_BUDGET_INFO(cin1200c, "Terratec Cinergy 1200 DVB-C", BUDGET_CIN1200C); +MAKE_BUDGET_INFO(cin1200t, "Terratec Cinergy 1200 DVB-T", BUDGET_CIN1200T); + +static struct pci_device_id pci_tbl[] = { + MAKE_EXTENSION_PCI(knc1s, 0x1131, 0x4f56), + MAKE_EXTENSION_PCI(knc1c, 0x1894, 0x0020), + MAKE_EXTENSION_PCI(knc1t, 0x1894, 0x0030), + MAKE_EXTENSION_PCI(cin1200s, 0x153b, 0x1154), + MAKE_EXTENSION_PCI(cin1200c, 0x153b, 0x1156), + MAKE_EXTENSION_PCI(cin1200t, 0x153b, 0x1157), + { + .vendor = 0, + } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_extension budget_extension = { + .name = "budget dvb /w video in\0", + .pci_tbl = pci_tbl, + + .module = THIS_MODULE, + .attach = budget_av_attach, + .detach = budget_av_detach, + + .irq_mask = MASK_10, + .irq_func = budget_av_irq, +}; + +static int __init budget_av_init(void) +{ + return saa7146_register_extension(&budget_extension); +} + +static void __exit budget_av_exit(void) +{ + saa7146_unregister_extension(&budget_extension); +} + +module_init(budget_av_init); +module_exit(budget_av_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ralph Metzler, Marcus Metzler, Michael Hunold, others"); +MODULE_DESCRIPTION("driver for the SAA7146 based so-called " + "budget PCI DVB w/ analog input and CI-module (e.g. the KNC cards)"); +module_param_named(enable_ci, enable_ci, int, 0644); +MODULE_PARM_DESC(enable_ci, "Turn on/off CI module (default:off)."); diff --git a/drivers/media/dvb/ttpci/budget-ci.c b/drivers/media/dvb/ttpci/budget-ci.c new file mode 100644 index 000000000000..521111be3558 --- /dev/null +++ b/drivers/media/dvb/ttpci/budget-ci.c @@ -0,0 +1,995 @@ +/* + * budget-ci.c: driver for the SAA7146 based Budget DVB cards + * + * Compiled from various sources by Michael Hunold <michael@mihu.de> + * + * msp430 IR support contributed by Jack Thomasson <jkt@Helius.COM> + * partially based on the Siemens DVB driver by Ralph+Marcus Metzler + * + * CI interface support (c) 2004 Andrew de Quincey <adq_dvb@lidskialf.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + * + * the project's page is at http://www.linuxtv.org/dvb/ + */ + +#include "budget.h" + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/spinlock.h> + +#include "dvb_ca_en50221.h" +#include "stv0299.h" +#include "tda1004x.h" + +#define DEBIADDR_IR 0x1234 +#define DEBIADDR_CICONTROL 0x0000 +#define DEBIADDR_CIVERSION 0x4000 +#define DEBIADDR_IO 0x1000 +#define DEBIADDR_ATTR 0x3000 + +#define CICONTROL_RESET 0x01 +#define CICONTROL_ENABLETS 0x02 +#define CICONTROL_CAMDETECT 0x08 + +#define DEBICICTL 0x00420000 +#define DEBICICAM 0x02420000 + +#define SLOTSTATUS_NONE 1 +#define SLOTSTATUS_PRESENT 2 +#define SLOTSTATUS_RESET 4 +#define SLOTSTATUS_READY 8 +#define SLOTSTATUS_OCCUPIED (SLOTSTATUS_PRESENT|SLOTSTATUS_RESET|SLOTSTATUS_READY) + +struct budget_ci { + struct budget budget; + struct input_dev input_dev; + struct tasklet_struct msp430_irq_tasklet; + struct tasklet_struct ciintf_irq_tasklet; + int slot_status; + struct dvb_ca_en50221 ca; + char ir_dev_name[50]; +}; + +/* from reading the following remotes: + Zenith Universal 7 / TV Mode 807 / VCR Mode 837 + Hauppauge (from NOVA-CI-s box product) + i've taken a "middle of the road" approach and note the differences +*/ +static u16 key_map[64] = { + /* 0x0X */ + KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, + KEY_9, + KEY_ENTER, + KEY_RED, + KEY_POWER, /* RADIO on Hauppauge */ + KEY_MUTE, + 0, + KEY_A, /* TV on Hauppauge */ + /* 0x1X */ + KEY_VOLUMEUP, KEY_VOLUMEDOWN, + 0, 0, + KEY_B, + 0, 0, 0, 0, 0, 0, 0, + KEY_UP, KEY_DOWN, + KEY_OPTION, /* RESERVED on Hauppauge */ + KEY_BREAK, + /* 0x2X */ + KEY_CHANNELUP, KEY_CHANNELDOWN, + KEY_PREVIOUS, /* Prev. Ch on Zenith, SOURCE on Hauppauge */ + 0, KEY_RESTART, KEY_OK, + KEY_CYCLEWINDOWS, /* MINIMIZE on Hauppauge */ + 0, + KEY_ENTER, /* VCR mode on Zenith */ + KEY_PAUSE, + 0, + KEY_RIGHT, KEY_LEFT, + 0, + KEY_MENU, /* FULL SCREEN on Hauppauge */ + 0, + /* 0x3X */ + KEY_SLOW, + KEY_PREVIOUS, /* VCR mode on Zenith */ + KEY_REWIND, + 0, + KEY_FASTFORWARD, + KEY_PLAY, KEY_STOP, + KEY_RECORD, + KEY_TUNER, /* TV/VCR on Zenith */ + 0, + KEY_C, + 0, + KEY_EXIT, + KEY_POWER2, + KEY_TUNER, /* VCR mode on Zenith */ + 0, +}; + +static void msp430_ir_debounce(unsigned long data) +{ + struct input_dev *dev = (struct input_dev *) data; + + if (dev->rep[0] == 0 || dev->rep[0] == ~0) { + input_event(dev, EV_KEY, key_map[dev->repeat_key], !!0); + return; + } + + dev->rep[0] = 0; + dev->timer.expires = jiffies + HZ * 350 / 1000; + add_timer(&dev->timer); + input_event(dev, EV_KEY, key_map[dev->repeat_key], 2); /* REPEAT */ +} + +static void msp430_ir_interrupt(unsigned long data) +{ + struct budget_ci *budget_ci = (struct budget_ci *) data; + struct input_dev *dev = &budget_ci->input_dev; + unsigned int code = + ttpci_budget_debiread(&budget_ci->budget, DEBINOSWAP, DEBIADDR_IR, 2, 1, 0) >> 8; + + if (code & 0x40) { + code &= 0x3f; + + if (timer_pending(&dev->timer)) { + if (code == dev->repeat_key) { + ++dev->rep[0]; + return; + } + del_timer(&dev->timer); + input_event(dev, EV_KEY, key_map[dev->repeat_key], !!0); + } + + if (!key_map[code]) { + printk("DVB (%s): no key for %02x!\n", __FUNCTION__, code); + return; + } + + /* initialize debounce and repeat */ + dev->repeat_key = code; + /* Zenith remote _always_ sends 2 sequences */ + dev->rep[0] = ~0; + /* 350 milliseconds */ + dev->timer.expires = jiffies + HZ * 350 / 1000; + /* MAKE */ + input_event(dev, EV_KEY, key_map[code], !0); + add_timer(&dev->timer); + } +} + +static int msp430_ir_init(struct budget_ci *budget_ci) +{ + struct saa7146_dev *saa = budget_ci->budget.dev; + int i; + + memset(&budget_ci->input_dev, 0, sizeof(struct input_dev)); + + sprintf(budget_ci->ir_dev_name, "Budget-CI dvb ir receiver %s", saa->name); + budget_ci->input_dev.name = budget_ci->ir_dev_name; + + set_bit(EV_KEY, budget_ci->input_dev.evbit); + + for (i = 0; i < sizeof(key_map) / sizeof(*key_map); i++) + if (key_map[i]) + set_bit(key_map[i], budget_ci->input_dev.keybit); + + input_register_device(&budget_ci->input_dev); + + budget_ci->input_dev.timer.function = msp430_ir_debounce; + + saa7146_write(saa, IER, saa7146_read(saa, IER) | MASK_06); + + saa7146_setgpio(saa, 3, SAA7146_GPIO_IRQHI); + + return 0; +} + +static void msp430_ir_deinit(struct budget_ci *budget_ci) +{ + struct saa7146_dev *saa = budget_ci->budget.dev; + struct input_dev *dev = &budget_ci->input_dev; + + saa7146_write(saa, IER, saa7146_read(saa, IER) & ~MASK_06); + saa7146_setgpio(saa, 3, SAA7146_GPIO_INPUT); + + if (del_timer(&dev->timer)) + input_event(dev, EV_KEY, key_map[dev->repeat_key], !!0); + + input_unregister_device(dev); +} + +static int ciintf_read_attribute_mem(struct dvb_ca_en50221 *ca, int slot, int address) +{ + struct budget_ci *budget_ci = (struct budget_ci *) ca->data; + + if (slot != 0) + return -EINVAL; + + return ttpci_budget_debiread(&budget_ci->budget, DEBICICAM, + DEBIADDR_ATTR | (address & 0xfff), 1, 1, 0); +} + +static int ciintf_write_attribute_mem(struct dvb_ca_en50221 *ca, int slot, int address, u8 value) +{ + struct budget_ci *budget_ci = (struct budget_ci *) ca->data; + + if (slot != 0) + return -EINVAL; + + return ttpci_budget_debiwrite(&budget_ci->budget, DEBICICAM, + DEBIADDR_ATTR | (address & 0xfff), 1, value, 1, 0); +} + +static int ciintf_read_cam_control(struct dvb_ca_en50221 *ca, int slot, u8 address) +{ + struct budget_ci *budget_ci = (struct budget_ci *) ca->data; + + if (slot != 0) + return -EINVAL; + + return ttpci_budget_debiread(&budget_ci->budget, DEBICICAM, + DEBIADDR_IO | (address & 3), 1, 1, 0); +} + +static int ciintf_write_cam_control(struct dvb_ca_en50221 *ca, int slot, u8 address, u8 value) +{ + struct budget_ci *budget_ci = (struct budget_ci *) ca->data; + + if (slot != 0) + return -EINVAL; + + return ttpci_budget_debiwrite(&budget_ci->budget, DEBICICAM, + DEBIADDR_IO | (address & 3), 1, value, 1, 0); +} + +static int ciintf_slot_reset(struct dvb_ca_en50221 *ca, int slot) +{ + struct budget_ci *budget_ci = (struct budget_ci *) ca->data; + struct saa7146_dev *saa = budget_ci->budget.dev; + + if (slot != 0) + return -EINVAL; + + // trigger on RISING edge during reset so we know when READY is re-asserted + saa7146_setgpio(saa, 0, SAA7146_GPIO_IRQHI); + budget_ci->slot_status = SLOTSTATUS_RESET; + ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, 0, 1, 0); + msleep(1); + ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, + CICONTROL_RESET, 1, 0); + + saa7146_setgpio(saa, 1, SAA7146_GPIO_OUTHI); + ttpci_budget_set_video_port(saa, BUDGET_VIDEO_PORTB); + return 0; +} + +static int ciintf_slot_shutdown(struct dvb_ca_en50221 *ca, int slot) +{ + struct budget_ci *budget_ci = (struct budget_ci *) ca->data; + struct saa7146_dev *saa = budget_ci->budget.dev; + + if (slot != 0) + return -EINVAL; + + saa7146_setgpio(saa, 1, SAA7146_GPIO_OUTHI); + ttpci_budget_set_video_port(saa, BUDGET_VIDEO_PORTB); + return 0; +} + +static int ciintf_slot_ts_enable(struct dvb_ca_en50221 *ca, int slot) +{ + struct budget_ci *budget_ci = (struct budget_ci *) ca->data; + struct saa7146_dev *saa = budget_ci->budget.dev; + int tmp; + + if (slot != 0) + return -EINVAL; + + saa7146_setgpio(saa, 1, SAA7146_GPIO_OUTLO); + + tmp = ttpci_budget_debiread(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, 1, 0); + ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, + tmp | CICONTROL_ENABLETS, 1, 0); + + ttpci_budget_set_video_port(saa, BUDGET_VIDEO_PORTA); + return 0; +} + +static void ciintf_interrupt(unsigned long data) +{ + struct budget_ci *budget_ci = (struct budget_ci *) data; + struct saa7146_dev *saa = budget_ci->budget.dev; + unsigned int flags; + + // ensure we don't get spurious IRQs during initialisation + if (!budget_ci->budget.ci_present) + return; + + // read the CAM status + flags = ttpci_budget_debiread(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, 1, 0); + if (flags & CICONTROL_CAMDETECT) { + + // GPIO should be set to trigger on falling edge if a CAM is present + saa7146_setgpio(saa, 0, SAA7146_GPIO_IRQLO); + + if (budget_ci->slot_status & SLOTSTATUS_NONE) { + // CAM insertion IRQ + budget_ci->slot_status = SLOTSTATUS_PRESENT; + dvb_ca_en50221_camchange_irq(&budget_ci->ca, 0, + DVB_CA_EN50221_CAMCHANGE_INSERTED); + + } else if (budget_ci->slot_status & SLOTSTATUS_RESET) { + // CAM ready (reset completed) + budget_ci->slot_status = SLOTSTATUS_READY; + dvb_ca_en50221_camready_irq(&budget_ci->ca, 0); + + } else if (budget_ci->slot_status & SLOTSTATUS_READY) { + // FR/DA IRQ + dvb_ca_en50221_frda_irq(&budget_ci->ca, 0); + } + } else { + + // trigger on rising edge if a CAM is not present - when a CAM is inserted, we + // only want to get the IRQ when it sets READY. If we trigger on the falling edge, + // the CAM might not actually be ready yet. + saa7146_setgpio(saa, 0, SAA7146_GPIO_IRQHI); + + // generate a CAM removal IRQ if we haven't already + if (budget_ci->slot_status & SLOTSTATUS_OCCUPIED) { + // CAM removal IRQ + budget_ci->slot_status = SLOTSTATUS_NONE; + dvb_ca_en50221_camchange_irq(&budget_ci->ca, 0, + DVB_CA_EN50221_CAMCHANGE_REMOVED); + } + } +} + +static int ciintf_init(struct budget_ci *budget_ci) +{ + struct saa7146_dev *saa = budget_ci->budget.dev; + int flags; + int result; + + memset(&budget_ci->ca, 0, sizeof(struct dvb_ca_en50221)); + + // enable DEBI pins + saa7146_write(saa, MC1, saa7146_read(saa, MC1) | (0x800 << 16) | 0x800); + + // test if it is there + if ((ttpci_budget_debiread(&budget_ci->budget, DEBICICTL, DEBIADDR_CIVERSION, 1, 1, 0) & 0xa0) != 0xa0) { + result = -ENODEV; + goto error; + } + // determine whether a CAM is present or not + flags = ttpci_budget_debiread(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, 1, 0); + budget_ci->slot_status = SLOTSTATUS_NONE; + if (flags & CICONTROL_CAMDETECT) + budget_ci->slot_status = SLOTSTATUS_PRESENT; + + // register CI interface + budget_ci->ca.owner = THIS_MODULE; + budget_ci->ca.read_attribute_mem = ciintf_read_attribute_mem; + budget_ci->ca.write_attribute_mem = ciintf_write_attribute_mem; + budget_ci->ca.read_cam_control = ciintf_read_cam_control; + budget_ci->ca.write_cam_control = ciintf_write_cam_control; + budget_ci->ca.slot_reset = ciintf_slot_reset; + budget_ci->ca.slot_shutdown = ciintf_slot_shutdown; + budget_ci->ca.slot_ts_enable = ciintf_slot_ts_enable; + budget_ci->ca.data = budget_ci; + if ((result = dvb_ca_en50221_init(budget_ci->budget.dvb_adapter, + &budget_ci->ca, + DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE | + DVB_CA_EN50221_FLAG_IRQ_FR | + DVB_CA_EN50221_FLAG_IRQ_DA, 1)) != 0) { + printk("budget_ci: CI interface detected, but initialisation failed.\n"); + goto error; + } + // Setup CI slot IRQ + tasklet_init(&budget_ci->ciintf_irq_tasklet, ciintf_interrupt, (unsigned long) budget_ci); + if (budget_ci->slot_status != SLOTSTATUS_NONE) { + saa7146_setgpio(saa, 0, SAA7146_GPIO_IRQLO); + } else { + saa7146_setgpio(saa, 0, SAA7146_GPIO_IRQHI); + } + saa7146_write(saa, IER, saa7146_read(saa, IER) | MASK_03); + ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, + CICONTROL_RESET, 1, 0); + + // success! + printk("budget_ci: CI interface initialised\n"); + budget_ci->budget.ci_present = 1; + + // forge a fake CI IRQ so the CAM state is setup correctly + flags = DVB_CA_EN50221_CAMCHANGE_REMOVED; + if (budget_ci->slot_status != SLOTSTATUS_NONE) + flags = DVB_CA_EN50221_CAMCHANGE_INSERTED; + dvb_ca_en50221_camchange_irq(&budget_ci->ca, 0, flags); + + return 0; + +error: + saa7146_write(saa, MC1, saa7146_read(saa, MC1) | (0x800 << 16)); + return result; +} + +static void ciintf_deinit(struct budget_ci *budget_ci) +{ + struct saa7146_dev *saa = budget_ci->budget.dev; + + // disable CI interrupts + saa7146_write(saa, IER, saa7146_read(saa, IER) & ~MASK_03); + saa7146_setgpio(saa, 0, SAA7146_GPIO_INPUT); + tasklet_kill(&budget_ci->ciintf_irq_tasklet); + ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, 0, 1, 0); + msleep(1); + ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, + CICONTROL_RESET, 1, 0); + + // disable TS data stream to CI interface + saa7146_setgpio(saa, 1, SAA7146_GPIO_INPUT); + + // release the CA device + dvb_ca_en50221_release(&budget_ci->ca); + + // disable DEBI pins + saa7146_write(saa, MC1, saa7146_read(saa, MC1) | (0x800 << 16)); +} + +static void budget_ci_irq(struct saa7146_dev *dev, u32 * isr) +{ + struct budget_ci *budget_ci = (struct budget_ci *) dev->ext_priv; + + dprintk(8, "dev: %p, budget_ci: %p\n", dev, budget_ci); + + if (*isr & MASK_06) + tasklet_schedule(&budget_ci->msp430_irq_tasklet); + + if (*isr & MASK_10) + ttpci_budget_irq10_handler(dev, isr); + + if ((*isr & MASK_03) && (budget_ci->budget.ci_present)) + tasklet_schedule(&budget_ci->ciintf_irq_tasklet); +} + + +static u8 alps_bsru6_inittab[] = { + 0x01, 0x15, + 0x02, 0x00, + 0x03, 0x00, + 0x04, 0x7d, /* F22FR = 0x7d, F22 = f_VCO / 128 / 0x7d = 22 kHz */ + 0x05, 0x35, /* I2CT = 0, SCLT = 1, SDAT = 1 */ + 0x06, 0x40, /* DAC not used, set to high impendance mode */ + 0x07, 0x00, /* DAC LSB */ + 0x08, 0x40, /* DiSEqC off, LNB power on OP2/LOCK pin on */ + 0x09, 0x00, /* FIFO */ + 0x0c, 0x51, /* OP1 ctl = Normal, OP1 val = 1 (LNB Power ON) */ + 0x0d, 0x82, /* DC offset compensation = ON, beta_agc1 = 2 */ + 0x0e, 0x23, /* alpha_tmg = 2, beta_tmg = 3 */ + 0x10, 0x3f, // AGC2 0x3d + 0x11, 0x84, + 0x12, 0xb5, // Lock detect: -64 Carrier freq detect:on + 0x15, 0xc9, // lock detector threshold + 0x16, 0x00, + 0x17, 0x00, + 0x18, 0x00, + 0x19, 0x00, + 0x1a, 0x00, + 0x1f, 0x50, + 0x20, 0x00, + 0x21, 0x00, + 0x22, 0x00, + 0x23, 0x00, + 0x28, 0x00, // out imp: normal out type: parallel FEC mode:0 + 0x29, 0x1e, // 1/2 threshold + 0x2a, 0x14, // 2/3 threshold + 0x2b, 0x0f, // 3/4 threshold + 0x2c, 0x09, // 5/6 threshold + 0x2d, 0x05, // 7/8 threshold + 0x2e, 0x01, + 0x31, 0x1f, // test all FECs + 0x32, 0x19, // viterbi and synchro search + 0x33, 0xfc, // rs control + 0x34, 0x93, // error control + 0x0f, 0x52, + 0xff, 0xff +}; + +static int alps_bsru6_set_symbol_rate(struct dvb_frontend *fe, u32 srate, u32 ratio) +{ + u8 aclk = 0; + u8 bclk = 0; + + if (srate < 1500000) { + aclk = 0xb7; + bclk = 0x47; + } else if (srate < 3000000) { + aclk = 0xb7; + bclk = 0x4b; + } else if (srate < 7000000) { + aclk = 0xb7; + bclk = 0x4f; + } else if (srate < 14000000) { + aclk = 0xb7; + bclk = 0x53; + } else if (srate < 30000000) { + aclk = 0xb6; + bclk = 0x53; + } else if (srate < 45000000) { + aclk = 0xb4; + bclk = 0x51; + } + + stv0299_writereg(fe, 0x13, aclk); + stv0299_writereg(fe, 0x14, bclk); + stv0299_writereg(fe, 0x1f, (ratio >> 16) & 0xff); + stv0299_writereg(fe, 0x20, (ratio >> 8) & 0xff); + stv0299_writereg(fe, 0x21, (ratio) & 0xf0); + + return 0; +} + +static int alps_bsru6_pll_set(struct dvb_frontend *fe, struct dvb_frontend_parameters *params) +{ + struct budget_ci *budget_ci = (struct budget_ci *) fe->dvb->priv; + u8 buf[4]; + u32 div; + struct i2c_msg msg = {.addr = 0x61,.flags = 0,.buf = buf,.len = sizeof(buf) }; + + if ((params->frequency < 950000) || (params->frequency > 2150000)) + return -EINVAL; + + div = (params->frequency + (125 - 1)) / 125; // round correctly + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = 0x80 | ((div & 0x18000) >> 10) | 4; + buf[3] = 0xC4; + + if (params->frequency > 1530000) + buf[3] = 0xc0; + + if (i2c_transfer(&budget_ci->budget.i2c_adap, &msg, 1) != 1) + return -EIO; + return 0; +} + +static struct stv0299_config alps_bsru6_config = { + + .demod_address = 0x68, + .inittab = alps_bsru6_inittab, + .mclk = 88000000UL, + .invert = 1, + .enhanced_tuning = 0, + .skip_reinit = 0, + .lock_output = STV0229_LOCKOUTPUT_1, + .volt13_op0_op1 = STV0299_VOLT13_OP1, + .min_delay_ms = 100, + .set_symbol_rate = alps_bsru6_set_symbol_rate, + .pll_set = alps_bsru6_pll_set, +}; + + + + +static u8 philips_su1278_tt_inittab[] = { + 0x01, 0x0f, + 0x02, 0x30, + 0x03, 0x00, + 0x04, 0x5b, + 0x05, 0x85, + 0x06, 0x02, + 0x07, 0x00, + 0x08, 0x02, + 0x09, 0x00, + 0x0C, 0x01, + 0x0D, 0x81, + 0x0E, 0x44, + 0x0f, 0x14, + 0x10, 0x3c, + 0x11, 0x84, + 0x12, 0xda, + 0x13, 0x97, + 0x14, 0x95, + 0x15, 0xc9, + 0x16, 0x19, + 0x17, 0x8c, + 0x18, 0x59, + 0x19, 0xf8, + 0x1a, 0xfe, + 0x1c, 0x7f, + 0x1d, 0x00, + 0x1e, 0x00, + 0x1f, 0x50, + 0x20, 0x00, + 0x21, 0x00, + 0x22, 0x00, + 0x23, 0x00, + 0x28, 0x00, + 0x29, 0x28, + 0x2a, 0x14, + 0x2b, 0x0f, + 0x2c, 0x09, + 0x2d, 0x09, + 0x31, 0x1f, + 0x32, 0x19, + 0x33, 0xfc, + 0x34, 0x93, + 0xff, 0xff +}; + +static int philips_su1278_tt_set_symbol_rate(struct dvb_frontend *fe, u32 srate, u32 ratio) +{ + stv0299_writereg(fe, 0x0e, 0x44); + if (srate >= 10000000) { + stv0299_writereg(fe, 0x13, 0x97); + stv0299_writereg(fe, 0x14, 0x95); + stv0299_writereg(fe, 0x15, 0xc9); + stv0299_writereg(fe, 0x17, 0x8c); + stv0299_writereg(fe, 0x1a, 0xfe); + stv0299_writereg(fe, 0x1c, 0x7f); + stv0299_writereg(fe, 0x2d, 0x09); + } else { + stv0299_writereg(fe, 0x13, 0x99); + stv0299_writereg(fe, 0x14, 0x8d); + stv0299_writereg(fe, 0x15, 0xce); + stv0299_writereg(fe, 0x17, 0x43); + stv0299_writereg(fe, 0x1a, 0x1d); + stv0299_writereg(fe, 0x1c, 0x12); + stv0299_writereg(fe, 0x2d, 0x05); + } + stv0299_writereg(fe, 0x0e, 0x23); + stv0299_writereg(fe, 0x0f, 0x94); + stv0299_writereg(fe, 0x10, 0x39); + stv0299_writereg(fe, 0x15, 0xc9); + + stv0299_writereg(fe, 0x1f, (ratio >> 16) & 0xff); + stv0299_writereg(fe, 0x20, (ratio >> 8) & 0xff); + stv0299_writereg(fe, 0x21, (ratio) & 0xf0); + + return 0; +} + +static int philips_su1278_tt_pll_set(struct dvb_frontend *fe, + struct dvb_frontend_parameters *params) +{ + struct budget_ci *budget_ci = (struct budget_ci *) fe->dvb->priv; + u32 div; + u8 buf[4]; + struct i2c_msg msg = {.addr = 0x60,.flags = 0,.buf = buf,.len = sizeof(buf) }; + + if ((params->frequency < 950000) || (params->frequency > 2150000)) + return -EINVAL; + + div = (params->frequency + (500 - 1)) / 500; // round correctly + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = 0x80 | ((div & 0x18000) >> 10) | 2; + buf[3] = 0x20; + + if (params->u.qpsk.symbol_rate < 4000000) + buf[3] |= 1; + + if (params->frequency < 1250000) + buf[3] |= 0; + else if (params->frequency < 1550000) + buf[3] |= 0x40; + else if (params->frequency < 2050000) + buf[3] |= 0x80; + else if (params->frequency < 2150000) + buf[3] |= 0xC0; + + if (i2c_transfer(&budget_ci->budget.i2c_adap, &msg, 1) != 1) + return -EIO; + return 0; +} + +static struct stv0299_config philips_su1278_tt_config = { + + .demod_address = 0x68, + .inittab = philips_su1278_tt_inittab, + .mclk = 64000000UL, + .invert = 0, + .enhanced_tuning = 1, + .skip_reinit = 1, + .lock_output = STV0229_LOCKOUTPUT_1, + .volt13_op0_op1 = STV0299_VOLT13_OP1, + .min_delay_ms = 50, + .set_symbol_rate = philips_su1278_tt_set_symbol_rate, + .pll_set = philips_su1278_tt_pll_set, +}; + + + +static int philips_tdm1316l_pll_init(struct dvb_frontend *fe) +{ + struct budget_ci *budget_ci = (struct budget_ci *) fe->dvb->priv; + static u8 td1316_init[] = { 0x0b, 0xf5, 0x85, 0xab }; + static u8 disable_mc44BC374c[] = { 0x1d, 0x74, 0xa0, 0x68 }; + struct i2c_msg tuner_msg = {.addr = 0x63,.flags = 0,.buf = td1316_init,.len = + sizeof(td1316_init) }; + + // setup PLL configuration + if (i2c_transfer(&budget_ci->budget.i2c_adap, &tuner_msg, 1) != 1) + return -EIO; + msleep(1); + + // disable the mc44BC374c (do not check for errors) + tuner_msg.addr = 0x65; + tuner_msg.buf = disable_mc44BC374c; + tuner_msg.len = sizeof(disable_mc44BC374c); + if (i2c_transfer(&budget_ci->budget.i2c_adap, &tuner_msg, 1) != 1) { + i2c_transfer(&budget_ci->budget.i2c_adap, &tuner_msg, 1); + } + + return 0; +} + +static int philips_tdm1316l_pll_set(struct dvb_frontend *fe, struct dvb_frontend_parameters *params) +{ + struct budget_ci *budget_ci = (struct budget_ci *) fe->dvb->priv; + u8 tuner_buf[4]; + struct i2c_msg tuner_msg = {.addr = 0x63,.flags = 0,.buf = tuner_buf,.len = sizeof(tuner_buf) }; + int tuner_frequency = 0; + u8 band, cp, filter; + + // determine charge pump + tuner_frequency = params->frequency + 36130000; + if (tuner_frequency < 87000000) + return -EINVAL; + else if (tuner_frequency < 130000000) + cp = 3; + else if (tuner_frequency < 160000000) + cp = 5; + else if (tuner_frequency < 200000000) + cp = 6; + else if (tuner_frequency < 290000000) + cp = 3; + else if (tuner_frequency < 420000000) + cp = 5; + else if (tuner_frequency < 480000000) + cp = 6; + else if (tuner_frequency < 620000000) + cp = 3; + else if (tuner_frequency < 830000000) + cp = 5; + else if (tuner_frequency < 895000000) + cp = 7; + else + return -EINVAL; + + // determine band + if (params->frequency < 49000000) + return -EINVAL; + else if (params->frequency < 159000000) + band = 1; + else if (params->frequency < 444000000) + band = 2; + else if (params->frequency < 861000000) + band = 4; + else + return -EINVAL; + + // setup PLL filter and TDA9889 + switch (params->u.ofdm.bandwidth) { + case BANDWIDTH_6_MHZ: + tda1004x_write_byte(fe, 0x0C, 0x14); + filter = 0; + break; + + case BANDWIDTH_7_MHZ: + tda1004x_write_byte(fe, 0x0C, 0x80); + filter = 0; + break; + + case BANDWIDTH_8_MHZ: + tda1004x_write_byte(fe, 0x0C, 0x14); + filter = 1; + break; + + default: + return -EINVAL; + } + + // calculate divisor + // ((36130000+((1000000/6)/2)) + Finput)/(1000000/6) + tuner_frequency = (((params->frequency / 1000) * 6) + 217280) / 1000; + + // setup tuner buffer + tuner_buf[0] = tuner_frequency >> 8; + tuner_buf[1] = tuner_frequency & 0xff; + tuner_buf[2] = 0xca; + tuner_buf[3] = (cp << 5) | (filter << 3) | band; + + if (i2c_transfer(&budget_ci->budget.i2c_adap, &tuner_msg, 1) != 1) + return -EIO; + + msleep(1); + return 0; +} + +static int philips_tdm1316l_request_firmware(struct dvb_frontend *fe, + const struct firmware **fw, char *name) +{ + struct budget_ci *budget_ci = (struct budget_ci *) fe->dvb->priv; + + return request_firmware(fw, name, &budget_ci->budget.dev->pci->dev); +} + +static struct tda1004x_config philips_tdm1316l_config = { + + .demod_address = 0x8, + .invert = 0, + .invert_oclk = 0, + .pll_init = philips_tdm1316l_pll_init, + .pll_set = philips_tdm1316l_pll_set, + .request_firmware = philips_tdm1316l_request_firmware, +}; + + + +static void frontend_init(struct budget_ci *budget_ci) +{ + switch (budget_ci->budget.dev->pci->subsystem_device) { + case 0x100c: // Hauppauge/TT Nova-CI budget (stv0299/ALPS BSRU6(tsa5059)) + budget_ci->budget.dvb_frontend = + stv0299_attach(&alps_bsru6_config, &budget_ci->budget.i2c_adap); + if (budget_ci->budget.dvb_frontend) { + break; + } + break; + + case 0x100f: // Hauppauge/TT Nova-CI budget (stv0299b/Philips su1278(tsa5059)) + budget_ci->budget.dvb_frontend = + stv0299_attach(&philips_su1278_tt_config, &budget_ci->budget.i2c_adap); + if (budget_ci->budget.dvb_frontend) { + break; + } + break; + + case 0x1011: // Hauppauge/TT Nova-T budget (tda10045/Philips tdm1316l(tda6651tt) + TDA9889) + budget_ci->budget.dvb_frontend = + tda10045_attach(&philips_tdm1316l_config, &budget_ci->budget.i2c_adap); + if (budget_ci->budget.dvb_frontend) { + break; + } + break; + } + + if (budget_ci->budget.dvb_frontend == NULL) { + printk("budget-ci: A frontend driver was not found for device %04x/%04x subsystem %04x/%04x\n", + budget_ci->budget.dev->pci->vendor, + budget_ci->budget.dev->pci->device, + budget_ci->budget.dev->pci->subsystem_vendor, + budget_ci->budget.dev->pci->subsystem_device); + } else { + if (dvb_register_frontend + (budget_ci->budget.dvb_adapter, budget_ci->budget.dvb_frontend)) { + printk("budget-ci: Frontend registration failed!\n"); + if (budget_ci->budget.dvb_frontend->ops->release) + budget_ci->budget.dvb_frontend->ops->release(budget_ci->budget.dvb_frontend); + budget_ci->budget.dvb_frontend = NULL; + } + } +} + +static int budget_ci_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info) +{ + struct budget_ci *budget_ci; + int err; + + if (!(budget_ci = kmalloc(sizeof(struct budget_ci), GFP_KERNEL))) + return -ENOMEM; + + dprintk(2, "budget_ci: %p\n", budget_ci); + + budget_ci->budget.ci_present = 0; + + dev->ext_priv = budget_ci; + + if ((err = ttpci_budget_init(&budget_ci->budget, dev, info, THIS_MODULE))) { + kfree(budget_ci); + return err; + } + + tasklet_init(&budget_ci->msp430_irq_tasklet, msp430_ir_interrupt, + (unsigned long) budget_ci); + + msp430_ir_init(budget_ci); + + ciintf_init(budget_ci); + + budget_ci->budget.dvb_adapter->priv = budget_ci; + frontend_init(budget_ci); + + return 0; +} + +static int budget_ci_detach(struct saa7146_dev *dev) +{ + struct budget_ci *budget_ci = (struct budget_ci *) dev->ext_priv; + struct saa7146_dev *saa = budget_ci->budget.dev; + int err; + + if (budget_ci->budget.ci_present) + ciintf_deinit(budget_ci); + if (budget_ci->budget.dvb_frontend) + dvb_unregister_frontend(budget_ci->budget.dvb_frontend); + err = ttpci_budget_deinit(&budget_ci->budget); + + tasklet_kill(&budget_ci->msp430_irq_tasklet); + + msp430_ir_deinit(budget_ci); + + // disable frontend and CI interface + saa7146_setgpio(saa, 2, SAA7146_GPIO_INPUT); + + kfree(budget_ci); + + return err; +} + +static struct saa7146_extension budget_extension; + +MAKE_BUDGET_INFO(ttbci, "TT-Budget/WinTV-NOVA-CI PCI", BUDGET_TT_HW_DISEQC); +MAKE_BUDGET_INFO(ttbt2, "TT-Budget/WinTV-NOVA-T PCI", BUDGET_TT); + +static struct pci_device_id pci_tbl[] = { + MAKE_EXTENSION_PCI(ttbci, 0x13c2, 0x100c), + MAKE_EXTENSION_PCI(ttbci, 0x13c2, 0x100f), + MAKE_EXTENSION_PCI(ttbt2, 0x13c2, 0x1011), + { + .vendor = 0, + } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_extension budget_extension = { + .name = "budget_ci dvb\0", + .flags = 0, + + .module = THIS_MODULE, + .pci_tbl = &pci_tbl[0], + .attach = budget_ci_attach, + .detach = budget_ci_detach, + + .irq_mask = MASK_03 | MASK_06 | MASK_10, + .irq_func = budget_ci_irq, +}; + +static int __init budget_ci_init(void) +{ + return saa7146_register_extension(&budget_extension); +} + +static void __exit budget_ci_exit(void) +{ + saa7146_unregister_extension(&budget_extension); +} + +module_init(budget_ci_init); +module_exit(budget_ci_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Michael Hunold, Jack Thomasson, Andrew de Quincey, others"); +MODULE_DESCRIPTION("driver for the SAA7146 based so-called " + "budget PCI DVB cards w/ CI-module produced by " + "Siemens, Technotrend, Hauppauge"); diff --git a/drivers/media/dvb/ttpci/budget-core.c b/drivers/media/dvb/ttpci/budget-core.c new file mode 100644 index 000000000000..93a9b40917e4 --- /dev/null +++ b/drivers/media/dvb/ttpci/budget-core.c @@ -0,0 +1,480 @@ +/* + * budget-core.c: driver for the SAA7146 based Budget DVB cards + * + * Compiled from various sources by Michael Hunold <michael@mihu.de> + * + * Copyright (C) 2002 Ralph Metzler <rjkm@metzlerbros.de> + * + * Copyright (C) 1999-2002 Ralph Metzler + * & Marcus Metzler for convergence integrated media GmbH + * + * 26feb2004 Support for FS Activy Card (Grundig tuner) by + * Michael Dreher <michael@5dot1.de>, + * Oliver Endriss <o.endriss@gmx.de>, + * Andreas 'randy' Weinberger + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + * + * the project's page is at http://www.linuxtv.org/dvb/ + */ + +#include <linux/moduleparam.h> + +#include "budget.h" +#include "ttpci-eeprom.h" + +int budget_debug; +module_param_named(debug, budget_debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off budget debugging (default:off)."); + +/**************************************************************************** + * TT budget / WinTV Nova + ****************************************************************************/ + +static int stop_ts_capture(struct budget *budget) +{ + dprintk(2, "budget: %p\n", budget); + + if (--budget->feeding) + return budget->feeding; + + saa7146_write(budget->dev, MC1, MASK_20); // DMA3 off + SAA7146_IER_DISABLE(budget->dev, MASK_10); + return 0; +} + +static int start_ts_capture(struct budget *budget) +{ + struct saa7146_dev *dev = budget->dev; + + dprintk(2, "budget: %p\n", budget); + + if (budget->feeding) + return ++budget->feeding; + + saa7146_write(dev, MC1, MASK_20); // DMA3 off + + memset(budget->grabbing, 0x00, TS_HEIGHT * TS_WIDTH); + + saa7146_write(dev, PCI_BT_V1, 0x001c0000 | (saa7146_read(dev, PCI_BT_V1) & ~0x001f0000)); + + budget->tsf = 0xff; + budget->ttbp = 0; + + /* + * Signal path on the Activy: + * + * tuner -> SAA7146 port A -> SAA7146 BRS -> SAA7146 DMA3 -> memory + * + * Since the tuner feeds 204 bytes packets into the SAA7146, + * DMA3 is configured to strip the trailing 16 FEC bytes: + * Pitch: 188, NumBytes3: 188, NumLines3: 1024 + */ + + switch(budget->card->type) { + case BUDGET_FS_ACTIVY: + saa7146_write(dev, DD1_INIT, 0x04000000); + saa7146_write(dev, MC2, (MASK_09 | MASK_25)); + saa7146_write(dev, BRS_CTRL, 0x00000000); + break; + case BUDGET_PATCH: + saa7146_write(dev, DD1_INIT, 0x00000200); + saa7146_write(dev, MC2, (MASK_10 | MASK_26)); + saa7146_write(dev, BRS_CTRL, 0x60000000); + break; + default: + if (budget->video_port == BUDGET_VIDEO_PORTA) { + saa7146_write(dev, DD1_INIT, 0x06000200); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + saa7146_write(dev, BRS_CTRL, 0x00000000); + } else { + saa7146_write(dev, DD1_INIT, 0x02000600); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + saa7146_write(dev, BRS_CTRL, 0x60000000); + } + } + + saa7146_write(dev, MC2, (MASK_08 | MASK_24)); + mdelay(10); + + saa7146_write(dev, BASE_ODD3, 0); + saa7146_write(dev, BASE_EVEN3, 0); + saa7146_write(dev, PROT_ADDR3, TS_WIDTH * TS_HEIGHT); + saa7146_write(dev, BASE_PAGE3, budget->pt.dma | ME1 | 0x90); + + if (budget->card->type == BUDGET_FS_ACTIVY) { + saa7146_write(dev, PITCH3, TS_WIDTH / 2); + saa7146_write(dev, NUM_LINE_BYTE3, ((TS_HEIGHT * 2) << 16) | (TS_WIDTH / 2)); + } else { + saa7146_write(dev, PITCH3, TS_WIDTH); + saa7146_write(dev, NUM_LINE_BYTE3, (TS_HEIGHT << 16) | TS_WIDTH); + } + + saa7146_write(dev, MC2, (MASK_04 | MASK_20)); + + SAA7146_ISR_CLEAR(budget->dev, MASK_10); /* VPE */ + SAA7146_IER_ENABLE(budget->dev, MASK_10); /* VPE */ + saa7146_write(dev, MC1, (MASK_04 | MASK_20)); /* DMA3 on */ + + return ++budget->feeding; +} + +static void vpeirq(unsigned long data) +{ + struct budget *budget = (struct budget *) data; + u8 *mem = (u8 *) (budget->grabbing); + u32 olddma = budget->ttbp; + u32 newdma = saa7146_read(budget->dev, PCI_VDP3); + + /* nearest lower position divisible by 188 */ + newdma -= newdma % 188; + + if (newdma >= TS_BUFLEN) + return; + + budget->ttbp = newdma; + + if (budget->feeding == 0 || newdma == olddma) + return; + + if (newdma > olddma) { /* no wraparound, dump olddma..newdma */ + dvb_dmx_swfilter_packets(&budget->demux, mem + olddma, (newdma - olddma) / 188); + } else { /* wraparound, dump olddma..buflen and 0..newdma */ + dvb_dmx_swfilter_packets(&budget->demux, mem + olddma, (TS_BUFLEN - olddma) / 188); + dvb_dmx_swfilter_packets(&budget->demux, mem, newdma / 188); + } +} + + +int ttpci_budget_debiread(struct budget *budget, u32 config, int addr, int count, + int uselocks, int nobusyloop) +{ + struct saa7146_dev *saa = budget->dev; + int result = 0; + unsigned long flags = 0; + + if (count > 4 || count <= 0) + return 0; + + if (uselocks) + spin_lock_irqsave(&budget->debilock, flags); + + if ((result = saa7146_wait_for_debi_done(saa, nobusyloop)) < 0) { + if (uselocks) + spin_unlock_irqrestore(&budget->debilock, flags); + return result; + } + + saa7146_write(saa, DEBI_COMMAND, (count << 17) | 0x10000 | (addr & 0xffff)); + saa7146_write(saa, DEBI_CONFIG, config); + saa7146_write(saa, DEBI_PAGE, 0); + saa7146_write(saa, MC2, (2 << 16) | 2); + + if ((result = saa7146_wait_for_debi_done(saa, nobusyloop)) < 0) { + if (uselocks) + spin_unlock_irqrestore(&budget->debilock, flags); + return result; + } + + result = saa7146_read(saa, DEBI_AD); + result &= (0xffffffffUL >> ((4 - count) * 8)); + + if (uselocks) + spin_unlock_irqrestore(&budget->debilock, flags); + + return result; +} + +int ttpci_budget_debiwrite(struct budget *budget, u32 config, int addr, + int count, u32 value, int uselocks, int nobusyloop) +{ + struct saa7146_dev *saa = budget->dev; + unsigned long flags = 0; + int result; + + if (count > 4 || count <= 0) + return 0; + + if (uselocks) + spin_lock_irqsave(&budget->debilock, flags); + + if ((result = saa7146_wait_for_debi_done(saa, nobusyloop)) < 0) { + if (uselocks) + spin_unlock_irqrestore(&budget->debilock, flags); + return result; + } + + saa7146_write(saa, DEBI_COMMAND, (count << 17) | 0x00000 | (addr & 0xffff)); + saa7146_write(saa, DEBI_CONFIG, config); + saa7146_write(saa, DEBI_PAGE, 0); + saa7146_write(saa, DEBI_AD, value); + saa7146_write(saa, MC2, (2 << 16) | 2); + + if ((result = saa7146_wait_for_debi_done(saa, nobusyloop)) < 0) { + if (uselocks) + spin_unlock_irqrestore(&budget->debilock, flags); + return result; + } + + if (uselocks) + spin_unlock_irqrestore(&budget->debilock, flags); + return 0; +} + + +/**************************************************************************** + * DVB API SECTION + ****************************************************************************/ + +static int budget_start_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct budget *budget = (struct budget *) demux->priv; + int status; + + dprintk(2, "budget: %p\n", budget); + + if (!demux->dmx.frontend) + return -EINVAL; + + spin_lock(&budget->feedlock); + feed->pusi_seen = 0; /* have a clean section start */ + status = start_ts_capture(budget); + spin_unlock(&budget->feedlock); + return status; +} + +static int budget_stop_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct budget *budget = (struct budget *) demux->priv; + int status; + + dprintk(2, "budget: %p\n", budget); + + spin_lock(&budget->feedlock); + status = stop_ts_capture(budget); + spin_unlock(&budget->feedlock); + return status; +} + +static int budget_register(struct budget *budget) +{ + struct dvb_demux *dvbdemux = &budget->demux; + int ret; + + dprintk(2, "budget: %p\n", budget); + + dvbdemux->priv = (void *) budget; + + dvbdemux->filternum = 256; + dvbdemux->feednum = 256; + dvbdemux->start_feed = budget_start_feed; + dvbdemux->stop_feed = budget_stop_feed; + dvbdemux->write_to_decoder = NULL; + + dvbdemux->dmx.capabilities = (DMX_TS_FILTERING | DMX_SECTION_FILTERING | + DMX_MEMORY_BASED_FILTERING); + + dvb_dmx_init(&budget->demux); + + budget->dmxdev.filternum = 256; + budget->dmxdev.demux = &dvbdemux->dmx; + budget->dmxdev.capabilities = 0; + + dvb_dmxdev_init(&budget->dmxdev, budget->dvb_adapter); + + budget->hw_frontend.source = DMX_FRONTEND_0; + + ret = dvbdemux->dmx.add_frontend(&dvbdemux->dmx, &budget->hw_frontend); + + if (ret < 0) + return ret; + + budget->mem_frontend.source = DMX_MEMORY_FE; + ret = dvbdemux->dmx.add_frontend(&dvbdemux->dmx, &budget->mem_frontend); + if (ret < 0) + return ret; + + ret = dvbdemux->dmx.connect_frontend(&dvbdemux->dmx, &budget->hw_frontend); + if (ret < 0) + return ret; + + dvb_net_init(budget->dvb_adapter, &budget->dvb_net, &dvbdemux->dmx); + + return 0; +} + +static void budget_unregister(struct budget *budget) +{ + struct dvb_demux *dvbdemux = &budget->demux; + + dprintk(2, "budget: %p\n", budget); + + dvb_net_release(&budget->dvb_net); + + dvbdemux->dmx.close(&dvbdemux->dmx); + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, &budget->hw_frontend); + dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, &budget->mem_frontend); + + dvb_dmxdev_release(&budget->dmxdev); + dvb_dmx_release(&budget->demux); +} + +int ttpci_budget_init(struct budget *budget, struct saa7146_dev *dev, + struct saa7146_pci_extension_data *info, + struct module *owner) +{ + int length = TS_WIDTH * TS_HEIGHT; + int ret = 0; + struct budget_info *bi = info->ext_priv; + + memset(budget, 0, sizeof(struct budget)); + + dprintk(2, "dev: %p, budget: %p\n", dev, budget); + + budget->card = bi; + budget->dev = (struct saa7146_dev *) dev; + + dvb_register_adapter(&budget->dvb_adapter, budget->card->name, owner); + + /* set dd1 stream a & b */ + saa7146_write(dev, DD1_STREAM_B, 0x00000000); + saa7146_write(dev, MC2, (MASK_09 | MASK_25)); + saa7146_write(dev, MC2, (MASK_10 | MASK_26)); + saa7146_write(dev, DD1_INIT, 0x02000000); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + + if (bi->type != BUDGET_FS_ACTIVY) + budget->video_port = BUDGET_VIDEO_PORTB; + else + budget->video_port = BUDGET_VIDEO_PORTA; + spin_lock_init(&budget->feedlock); + spin_lock_init(&budget->debilock); + + /* the Siemens DVB needs this if you want to have the i2c chips + get recognized before the main driver is loaded */ + if (bi->type != BUDGET_FS_ACTIVY) + saa7146_write(dev, GPIO_CTRL, 0x500000); /* GPIO 3 = 1 */ + +#ifdef I2C_ADAP_CLASS_TV_DIGITAL + budget->i2c_adap.class = I2C_ADAP_CLASS_TV_DIGITAL; +#else + budget->i2c_adap.class = I2C_CLASS_TV_DIGITAL; +#endif + + strlcpy(budget->i2c_adap.name, budget->card->name, sizeof(budget->i2c_adap.name)); + + saa7146_i2c_adapter_prepare(dev, &budget->i2c_adap, SAA7146_I2C_BUS_BIT_RATE_120); + strcpy(budget->i2c_adap.name, budget->card->name); + + if (i2c_add_adapter(&budget->i2c_adap) < 0) { + dvb_unregister_adapter(budget->dvb_adapter); + return -ENOMEM; + } + + ttpci_eeprom_parse_mac(&budget->i2c_adap, budget->dvb_adapter->proposed_mac); + + if (NULL == + (budget->grabbing = saa7146_vmalloc_build_pgtable(dev->pci, length, &budget->pt))) { + ret = -ENOMEM; + goto err; + } + + saa7146_write(dev, PCI_BT_V1, 0x001c0000); + /* upload all */ + saa7146_write(dev, GPIO_CTRL, 0x000000); + + tasklet_init(&budget->vpe_tasklet, vpeirq, (unsigned long) budget); + + /* frontend power on */ + if (bi->type == BUDGET_FS_ACTIVY) + saa7146_setgpio(dev, 1, SAA7146_GPIO_OUTHI); + else + saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTHI); + + if (budget_register(budget) == 0) { + return 0; + } +err: + i2c_del_adapter(&budget->i2c_adap); + + vfree(budget->grabbing); + + dvb_unregister_adapter(budget->dvb_adapter); + + return ret; +} + +int ttpci_budget_deinit(struct budget *budget) +{ + struct saa7146_dev *dev = budget->dev; + + dprintk(2, "budget: %p\n", budget); + + budget_unregister(budget); + + i2c_del_adapter(&budget->i2c_adap); + + dvb_unregister_adapter(budget->dvb_adapter); + + tasklet_kill(&budget->vpe_tasklet); + + saa7146_pgtable_free(dev->pci, &budget->pt); + + vfree(budget->grabbing); + + return 0; +} + +void ttpci_budget_irq10_handler(struct saa7146_dev *dev, u32 * isr) +{ + struct budget *budget = (struct budget *) dev->ext_priv; + + dprintk(8, "dev: %p, budget: %p\n", dev, budget); + + if (*isr & MASK_10) + tasklet_schedule(&budget->vpe_tasklet); +} + +void ttpci_budget_set_video_port(struct saa7146_dev *dev, int video_port) +{ + struct budget *budget = (struct budget *) dev->ext_priv; + + spin_lock(&budget->feedlock); + budget->video_port = video_port; + if (budget->feeding) { + int oldfeeding = budget->feeding; + budget->feeding = 1; + stop_ts_capture(budget); + start_ts_capture(budget); + budget->feeding = oldfeeding; + } + spin_unlock(&budget->feedlock); +} + +EXPORT_SYMBOL_GPL(ttpci_budget_debiread); +EXPORT_SYMBOL_GPL(ttpci_budget_debiwrite); +EXPORT_SYMBOL_GPL(ttpci_budget_init); +EXPORT_SYMBOL_GPL(ttpci_budget_deinit); +EXPORT_SYMBOL_GPL(ttpci_budget_irq10_handler); +EXPORT_SYMBOL_GPL(ttpci_budget_set_video_port); +EXPORT_SYMBOL_GPL(budget_debug); + +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/ttpci/budget-patch.c b/drivers/media/dvb/ttpci/budget-patch.c new file mode 100644 index 000000000000..5d524a4f213f --- /dev/null +++ b/drivers/media/dvb/ttpci/budget-patch.c @@ -0,0 +1,754 @@ +/* + * budget-patch.c: driver for Budget Patch, + * hardware modification of DVB-S cards enabling full TS + * + * Written by Emard <emard@softhome.net> + * + * Original idea by Roberto Deza <rdeza@unav.es> + * + * Special thanks to Holger Waechtler, Michael Hunold, Marian Durkovic + * and Metzlerbros + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + * + * the project's page is at http://www.linuxtv.org/dvb/ + */ + +#include "av7110.h" +#include "av7110_hw.h" +#include "budget.h" +#include "stv0299.h" +#include "ves1x93.h" +#include "tda8083.h" + +#define budget_patch budget + +static struct saa7146_extension budget_extension; + +MAKE_BUDGET_INFO(ttbp, "TT-Budget/Patch DVB-S 1.x PCI", BUDGET_PATCH); +//MAKE_BUDGET_INFO(satel,"TT-Budget/Patch SATELCO PCI", BUDGET_TT_HW_DISEQC); + +static struct pci_device_id pci_tbl[] = { + MAKE_EXTENSION_PCI(ttbp,0x13c2, 0x0000), +// MAKE_EXTENSION_PCI(satel, 0x13c2, 0x1013), + { + .vendor = 0, + } +}; + +/* those lines are for budget-patch to be tried +** on a true budget card and observe the +** behaviour of VSYNC generated by rps1. +** this code was shamelessly copy/pasted from budget.c +*/ +static void gpio_Set22K (struct budget *budget, int state) +{ + struct saa7146_dev *dev=budget->dev; + dprintk(2, "budget: %p\n", budget); + saa7146_setgpio(dev, 3, (state ? SAA7146_GPIO_OUTHI : SAA7146_GPIO_OUTLO)); +} + +/* Diseqc functions only for TT Budget card */ +/* taken from the Skyvision DVB driver by + Ralph Metzler <rjkm@metzlerbros.de> */ + +static void DiseqcSendBit (struct budget *budget, int data) +{ + struct saa7146_dev *dev=budget->dev; + dprintk(2, "budget: %p\n", budget); + + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTHI); + udelay(data ? 500 : 1000); + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); + udelay(data ? 1000 : 500); +} + +static void DiseqcSendByte (struct budget *budget, int data) +{ + int i, par=1, d; + + dprintk(2, "budget: %p\n", budget); + + for (i=7; i>=0; i--) { + d = (data>>i)&1; + par ^= d; + DiseqcSendBit(budget, d); + } + + DiseqcSendBit(budget, par); +} + +static int SendDiSEqCMsg (struct budget *budget, int len, u8 *msg, unsigned long burst) +{ + struct saa7146_dev *dev=budget->dev; + int i; + + dprintk(2, "budget: %p\n", budget); + + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); + mdelay(16); + + for (i=0; i<len; i++) + DiseqcSendByte(budget, msg[i]); + + mdelay(16); + + if (burst!=-1) { + if (burst) + DiseqcSendByte(budget, 0xff); + else { + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTHI); + udelay(12500); + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); + } + msleep(20); + } + + return 0; +} + +/* shamelessly copy/pasted from budget.c +*/ +static int budget_set_tone(struct dvb_frontend* fe, fe_sec_tone_mode_t tone) +{ + struct budget* budget = (struct budget*) fe->dvb->priv; + + switch (tone) { + case SEC_TONE_ON: + gpio_Set22K (budget, 1); + break; + + case SEC_TONE_OFF: + gpio_Set22K (budget, 0); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int budget_diseqc_send_master_cmd(struct dvb_frontend* fe, struct dvb_diseqc_master_cmd* cmd) +{ + struct budget* budget = (struct budget*) fe->dvb->priv; + + SendDiSEqCMsg (budget, cmd->msg_len, cmd->msg, 0); + + return 0; +} + +static int budget_diseqc_send_burst(struct dvb_frontend* fe, fe_sec_mini_cmd_t minicmd) +{ + struct budget* budget = (struct budget*) fe->dvb->priv; + + SendDiSEqCMsg (budget, 0, NULL, minicmd); + + return 0; +} + +static int budget_av7110_send_fw_cmd(struct budget_patch *budget, u16* buf, int length) +{ + int i; + + dprintk(2, "budget: %p\n", budget); + + for (i = 2; i < length; i++) + { + ttpci_budget_debiwrite(budget, DEBINOSWAP, COMMAND + 2*i, 2, (u32) buf[i], 0,0); + msleep(5); + } + if (length) + ttpci_budget_debiwrite(budget, DEBINOSWAP, COMMAND + 2, 2, (u32) buf[1], 0,0); + else + ttpci_budget_debiwrite(budget, DEBINOSWAP, COMMAND + 2, 2, 0, 0,0); + msleep(5); + ttpci_budget_debiwrite(budget, DEBINOSWAP, COMMAND, 2, (u32) buf[0], 0,0); + msleep(5); + return 0; +} + +static void av7110_set22k(struct budget_patch *budget, int state) +{ + u16 buf[2] = {( COMTYPE_AUDIODAC << 8) | (state ? ON22K : OFF22K), 0}; + + dprintk(2, "budget: %p\n", budget); + budget_av7110_send_fw_cmd(budget, buf, 2); +} + +static int av7110_send_diseqc_msg(struct budget_patch *budget, int len, u8 *msg, int burst) +{ + int i; + u16 buf[18] = { ((COMTYPE_AUDIODAC << 8) | SendDiSEqC), + 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + dprintk(2, "budget: %p\n", budget); + + if (len>10) + len=10; + + buf[1] = len+2; + buf[2] = len; + + if (burst != -1) + buf[3]=burst ? 0x01 : 0x00; + else + buf[3]=0xffff; + + for (i=0; i<len; i++) + buf[i+4]=msg[i]; + + budget_av7110_send_fw_cmd(budget, buf, 18); + return 0; +} + +static int budget_patch_set_tone(struct dvb_frontend* fe, fe_sec_tone_mode_t tone) +{ + struct budget_patch* budget = (struct budget_patch*) fe->dvb->priv; + + switch (tone) { + case SEC_TONE_ON: + av7110_set22k (budget, 1); + break; + + case SEC_TONE_OFF: + av7110_set22k (budget, 0); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int budget_patch_diseqc_send_master_cmd(struct dvb_frontend* fe, struct dvb_diseqc_master_cmd* cmd) +{ + struct budget_patch* budget = (struct budget_patch*) fe->dvb->priv; + + av7110_send_diseqc_msg (budget, cmd->msg_len, cmd->msg, 0); + + return 0; +} + +static int budget_patch_diseqc_send_burst(struct dvb_frontend* fe, fe_sec_mini_cmd_t minicmd) +{ + struct budget_patch* budget = (struct budget_patch*) fe->dvb->priv; + + av7110_send_diseqc_msg (budget, 0, NULL, minicmd); + + return 0; +} + +static int alps_bsrv2_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + struct budget_patch* budget = (struct budget_patch*) fe->dvb->priv; + u8 pwr = 0; + u8 buf[4]; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = buf, .len = sizeof(buf) }; + u32 div = (params->frequency + 479500) / 125; + + if (params->frequency > 2000000) pwr = 3; + else if (params->frequency > 1800000) pwr = 2; + else if (params->frequency > 1600000) pwr = 1; + else if (params->frequency > 1200000) pwr = 0; + else if (params->frequency >= 1100000) pwr = 1; + else pwr = 2; + + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = ((div & 0x18000) >> 10) | 0x95; + buf[3] = (pwr << 6) | 0x30; + + // NOTE: since we're using a prescaler of 2, we set the + // divisor frequency to 62.5kHz and divide by 125 above + + if (i2c_transfer (&budget->i2c_adap, &msg, 1) != 1) return -EIO; + return 0; +} + +static struct ves1x93_config alps_bsrv2_config = { + .demod_address = 0x08, + .xin = 90100000UL, + .invert_pwm = 0, + .pll_set = alps_bsrv2_pll_set, +}; + +static u8 alps_bsru6_inittab[] = { + 0x01, 0x15, + 0x02, 0x00, + 0x03, 0x00, + 0x04, 0x7d, /* F22FR = 0x7d, F22 = f_VCO / 128 / 0x7d = 22 kHz */ + 0x05, 0x35, /* I2CT = 0, SCLT = 1, SDAT = 1 */ + 0x06, 0x40, /* DAC not used, set to high impendance mode */ + 0x07, 0x00, /* DAC LSB */ + 0x08, 0x40, /* DiSEqC off, LNB power on OP2/LOCK pin on */ + 0x09, 0x00, /* FIFO */ + 0x0c, 0x51, /* OP1 ctl = Normal, OP1 val = 1 (LNB Power ON) */ + 0x0d, 0x82, /* DC offset compensation = ON, beta_agc1 = 2 */ + 0x0e, 0x23, /* alpha_tmg = 2, beta_tmg = 3 */ + 0x10, 0x3f, // AGC2 0x3d + 0x11, 0x84, + 0x12, 0xb5, // Lock detect: -64 Carrier freq detect:on + 0x15, 0xc9, // lock detector threshold + 0x16, 0x00, + 0x17, 0x00, + 0x18, 0x00, + 0x19, 0x00, + 0x1a, 0x00, + 0x1f, 0x50, + 0x20, 0x00, + 0x21, 0x00, + 0x22, 0x00, + 0x23, 0x00, + 0x28, 0x00, // out imp: normal out type: parallel FEC mode:0 + 0x29, 0x1e, // 1/2 threshold + 0x2a, 0x14, // 2/3 threshold + 0x2b, 0x0f, // 3/4 threshold + 0x2c, 0x09, // 5/6 threshold + 0x2d, 0x05, // 7/8 threshold + 0x2e, 0x01, + 0x31, 0x1f, // test all FECs + 0x32, 0x19, // viterbi and synchro search + 0x33, 0xfc, // rs control + 0x34, 0x93, // error control + 0x0f, 0x52, + 0xff, 0xff +}; + +static int alps_bsru6_set_symbol_rate(struct dvb_frontend* fe, u32 srate, u32 ratio) +{ + u8 aclk = 0; + u8 bclk = 0; + + if (srate < 1500000) { aclk = 0xb7; bclk = 0x47; } + else if (srate < 3000000) { aclk = 0xb7; bclk = 0x4b; } + else if (srate < 7000000) { aclk = 0xb7; bclk = 0x4f; } + else if (srate < 14000000) { aclk = 0xb7; bclk = 0x53; } + else if (srate < 30000000) { aclk = 0xb6; bclk = 0x53; } + else if (srate < 45000000) { aclk = 0xb4; bclk = 0x51; } + + stv0299_writereg (fe, 0x13, aclk); + stv0299_writereg (fe, 0x14, bclk); + stv0299_writereg (fe, 0x1f, (ratio >> 16) & 0xff); + stv0299_writereg (fe, 0x20, (ratio >> 8) & 0xff); + stv0299_writereg (fe, 0x21, (ratio ) & 0xf0); + + return 0; +} + +static int alps_bsru6_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + struct budget_patch* budget = (struct budget_patch*) fe->dvb->priv; + u8 data[4]; + u32 div; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = data, .len = sizeof(data) }; + + if ((params->frequency < 950000) || (params->frequency > 2150000)) return -EINVAL; + + div = (params->frequency + (125 - 1)) / 125; // round correctly + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = 0x80 | ((div & 0x18000) >> 10) | 4; + data[3] = 0xC4; + + if (params->frequency > 1530000) data[3] = 0xc0; + + if (i2c_transfer (&budget->i2c_adap, &msg, 1) != 1) return -EIO; + return 0; +} + +static struct stv0299_config alps_bsru6_config = { + + .demod_address = 0x68, + .inittab = alps_bsru6_inittab, + .mclk = 88000000UL, + .invert = 1, + .enhanced_tuning = 0, + .skip_reinit = 0, + .lock_output = STV0229_LOCKOUTPUT_1, + .volt13_op0_op1 = STV0299_VOLT13_OP1, + .min_delay_ms = 100, + .set_symbol_rate = alps_bsru6_set_symbol_rate, + .pll_set = alps_bsru6_pll_set, +}; + +static int grundig_29504_451_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + struct budget_patch* budget = (struct budget_patch*) fe->dvb->priv; + u32 div; + u8 data[4]; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = data, .len = sizeof(data) }; + + div = params->frequency / 125; + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = 0x8e; + data[3] = 0x00; + + if (i2c_transfer (&budget->i2c_adap, &msg, 1) != 1) return -EIO; + return 0; +} + +static struct tda8083_config grundig_29504_451_config = { + .demod_address = 0x68, + .pll_set = grundig_29504_451_pll_set, +}; + +static void frontend_init(struct budget_patch* budget) +{ + switch(budget->dev->pci->subsystem_device) { + case 0x0000: // Hauppauge/TT WinTV DVB-S rev1.X + case 0x1013: // SATELCO Multimedia PCI + + // try the ALPS BSRV2 first of all + budget->dvb_frontend = ves1x93_attach(&alps_bsrv2_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops->diseqc_send_master_cmd = budget_patch_diseqc_send_master_cmd; + budget->dvb_frontend->ops->diseqc_send_burst = budget_patch_diseqc_send_burst; + budget->dvb_frontend->ops->set_tone = budget_patch_set_tone; + break; + } + + // try the ALPS BSRU6 now + budget->dvb_frontend = stv0299_attach(&alps_bsru6_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops->diseqc_send_master_cmd = budget_diseqc_send_master_cmd; + budget->dvb_frontend->ops->diseqc_send_burst = budget_diseqc_send_burst; + budget->dvb_frontend->ops->set_tone = budget_set_tone; + break; + } + + // Try the grundig 29504-451 + budget->dvb_frontend = tda8083_attach(&grundig_29504_451_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops->diseqc_send_master_cmd = budget_diseqc_send_master_cmd; + budget->dvb_frontend->ops->diseqc_send_burst = budget_diseqc_send_burst; + budget->dvb_frontend->ops->set_tone = budget_set_tone; + break; + } + break; + } + + if (budget->dvb_frontend == NULL) { + printk("dvb-ttpci: A frontend driver was not found for device %04x/%04x subsystem %04x/%04x\n", + budget->dev->pci->vendor, + budget->dev->pci->device, + budget->dev->pci->subsystem_vendor, + budget->dev->pci->subsystem_device); + } else { + if (dvb_register_frontend(budget->dvb_adapter, budget->dvb_frontend)) { + printk("budget-av: Frontend registration failed!\n"); + if (budget->dvb_frontend->ops->release) + budget->dvb_frontend->ops->release(budget->dvb_frontend); + budget->dvb_frontend = NULL; + } + } +} + +/* written by Emard */ +static int budget_patch_attach (struct saa7146_dev* dev, struct saa7146_pci_extension_data *info) +{ + struct budget_patch *budget; + int err; + int count = 0; + int detected = 0; + +#define PATCH_RESET 0 +#define RPS_IRQ 0 +#define HPS_SETUP 0 +#if PATCH_RESET + saa7146_write(dev, MC1, MASK_31); + msleep(40); +#endif +#if HPS_SETUP + // initialize registers. Better to have it like this + // than leaving something unconfigured + saa7146_write(dev, DD1_STREAM_B, 0); + // port B VSYNC at rising edge + saa7146_write(dev, DD1_INIT, 0x00000200); // have this in budget-core too! + saa7146_write(dev, BRS_CTRL, 0x00000000); // VBI + + // debi config + // saa7146_write(dev, DEBI_CONFIG, MASK_30|MASK_28|MASK_18); + + // zero all HPS registers + saa7146_write(dev, HPS_H_PRESCALE, 0); // r68 + saa7146_write(dev, HPS_H_SCALE, 0); // r6c + saa7146_write(dev, BCS_CTRL, 0); // r70 + saa7146_write(dev, HPS_V_SCALE, 0); // r60 + saa7146_write(dev, HPS_V_GAIN, 0); // r64 + saa7146_write(dev, CHROMA_KEY_RANGE, 0); // r74 + saa7146_write(dev, CLIP_FORMAT_CTRL, 0); // r78 + // Set HPS prescaler for port B input + saa7146_write(dev, HPS_CTRL, (1<<30) | (0<<29) | (1<<28) | (0<<12) ); + saa7146_write(dev, MC2, + 0 * (MASK_08 | MASK_24) | // BRS control + 0 * (MASK_09 | MASK_25) | // a + 0 * (MASK_10 | MASK_26) | // b + 1 * (MASK_06 | MASK_22) | // HPS_CTRL1 + 1 * (MASK_05 | MASK_21) | // HPS_CTRL2 + 0 * (MASK_01 | MASK_15) // DEBI + ); +#endif + // Disable RPS1 and RPS0 + saa7146_write(dev, MC1, ( MASK_29 | MASK_28)); + // RPS1 timeout disable + saa7146_write(dev, RPS_TOV1, 0); + + // code for autodetection + // will wait for VBI_B event (vertical blank at port B) + // and will reset GPIO3 after VBI_B is detected. + // (GPIO3 should be raised high by CPU to + // test if GPIO3 will generate vertical blank signal + // in budget patch GPIO3 is connected to VSYNC_B + count = 0; +#if 0 + WRITE_RPS1(cpu_to_le32(CMD_UPLOAD | + MASK_10 | MASK_09 | MASK_08 | MASK_06 | MASK_05 | MASK_04 | MASK_03 | MASK_02 )); +#endif + WRITE_RPS1(cpu_to_le32(CMD_PAUSE | EVT_VBI_B)); + WRITE_RPS1(cpu_to_le32(CMD_WR_REG_MASK | (GPIO_CTRL>>2))); + WRITE_RPS1(cpu_to_le32(GPIO3_MSK)); + WRITE_RPS1(cpu_to_le32(SAA7146_GPIO_OUTLO<<24)); +#if RPS_IRQ + // issue RPS1 interrupt to increment counter + WRITE_RPS1(cpu_to_le32(CMD_INTERRUPT)); + // at least a NOP is neede between two interrupts + WRITE_RPS1(cpu_to_le32(CMD_NOP)); + // interrupt again + WRITE_RPS1(cpu_to_le32(CMD_INTERRUPT)); +#endif + WRITE_RPS1(cpu_to_le32(CMD_STOP)); + +#if RPS_IRQ + // set event counter 1 source as RPS1 interrupt (0x03) (rE4 p53) + // use 0x03 to track RPS1 interrupts - increase by 1 every gpio3 is toggled + // use 0x15 to track VPE interrupts - increase by 1 every vpeirq() is called + saa7146_write(dev, EC1SSR, (0x03<<2) | 3 ); + // set event counter 1 treshold to maximum allowed value (rEC p55) + saa7146_write(dev, ECT1R, 0x3fff ); +#endif + // Fix VSYNC level + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); + // Set RPS1 Address register to point to RPS code (r108 p42) + saa7146_write(dev, RPS_ADDR1, dev->d_rps1.dma_handle); + // Enable RPS1, (rFC p33) + saa7146_write(dev, MC1, (MASK_13 | MASK_29 )); + + + mdelay(50); + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTHI); + mdelay(150); + + + if( (saa7146_read(dev, GPIO_CTRL) & 0x10000000) == 0) + detected = 1; + +#if RPS_IRQ + printk("Event Counter 1 0x%04x\n", saa7146_read(dev, EC1R) & 0x3fff ); +#endif + // Disable RPS1 + saa7146_write(dev, MC1, ( MASK_29 )); + + if(detected == 0) + printk("budget-patch not detected or saa7146 in non-default state.\n" + "try enabling ressetting of 7146 with MASK_31 in MC1 register\n"); + + else + printk("BUDGET-PATCH DETECTED.\n"); + + +/* OLD (Original design by Roberto Deza): +** This code will setup the SAA7146_RPS1 to generate a square +** wave on GPIO3, changing when a field (TS_HEIGHT/2 "lines" of +** TS_WIDTH packets) has been acquired on SAA7146_D1B video port; +** then, this GPIO3 output which is connected to the D1B_VSYNC +** input, will trigger the acquisition of the alternate field +** and so on. +** Currently, the TT_budget / WinTV_Nova cards have two ICs +** (74HCT4040, LVC74) for the generation of this VSYNC signal, +** which seems that can be done perfectly without this :-)). +*/ + +/* New design (By Emard) +** this rps1 code will copy internal HS event to GPIO3 pin. +** GPIO3 is in budget-patch hardware connectd to port B VSYNC + +** HS is an internal event of 7146, accessible with RPS +** and temporarily raised high every n lines +** (n in defined in the RPS_THRESH1 counter threshold) +** I think HS is raised high on the beginning of the n-th line +** and remains high until this n-th line that triggered +** it is completely received. When the receiption of n-th line +** ends, HS is lowered. + +** To transmit data over DMA, 7146 needs changing state at +** port B VSYNC pin. Any changing of port B VSYNC will +** cause some DMA data transfer, with more or less packets loss. +** It depends on the phase and frequency of VSYNC and +** the way of 7146 is instructed to trigger on port B (defined +** in DD1_INIT register, 3rd nibble from the right valid +** numbers are 0-7, see datasheet) +** +** The correct triggering can minimize packet loss, +** dvbtraffic should give this stable bandwidths: +** 22k transponder = 33814 kbit/s +** 27.5k transponder = 38045 kbit/s +** by experiment it is found that the best results +** (stable bandwidths and almost no packet loss) +** are obtained using DD1_INIT triggering number 2 +** (Va at rising edge of VS Fa = HS x VS-failing forced toggle) +** and a VSYNC phase that occurs in the middle of DMA transfer +** (about byte 188*512=96256 in the DMA window). +** +** Phase of HS is still not clear to me how to control, +** It just happens to be so. It can be seen if one enables +** RPS_IRQ and print Event Counter 1 in vpeirq(). Every +** time RPS_INTERRUPT is called, the Event Counter 1 will +** increment. That's how the 7146 is programmed to do event +** counting in this budget-patch.c +** I *think* HPS setting has something to do with the phase +** of HS but I cant be 100% sure in that. + +** hardware debug note: a working budget card (including budget patch) +** with vpeirq() interrupt setup in mode "0x90" (every 64K) will +** generate 3 interrupts per 25-Hz DMA frame of 2*188*512 bytes +** and that means 3*25=75 Hz of interrupt freqency, as seen by +** watch cat /proc/interrupts +** +** If this frequency is 3x lower (and data received in the DMA +** buffer don't start with 0x47, but in the middle of packets, +** whose lengths appear to be like 188 292 188 104 etc. +** this means VSYNC line is not connected in the hardware. +** (check soldering pcb and pins) +** The same behaviour of missing VSYNC can be duplicated on budget +** cards, by seting DD1_INIT trigger mode 7 in 3rd nibble. +*/ + + // Setup RPS1 "program" (p35) + count = 0; + + + // Wait Source Line Counter Threshold (p36) + WRITE_RPS1(cpu_to_le32(CMD_PAUSE | EVT_HS)); + // Set GPIO3=1 (p42) + WRITE_RPS1(cpu_to_le32(CMD_WR_REG_MASK | (GPIO_CTRL>>2))); + WRITE_RPS1(cpu_to_le32(GPIO3_MSK)); + WRITE_RPS1(cpu_to_le32(SAA7146_GPIO_OUTHI<<24)); +#if RPS_IRQ + // issue RPS1 interrupt + WRITE_RPS1(cpu_to_le32(CMD_INTERRUPT)); +#endif + // Wait reset Source Line Counter Threshold (p36) + WRITE_RPS1(cpu_to_le32(CMD_PAUSE | RPS_INV | EVT_HS)); + // Set GPIO3=0 (p42) + WRITE_RPS1(cpu_to_le32(CMD_WR_REG_MASK | (GPIO_CTRL>>2))); + WRITE_RPS1(cpu_to_le32(GPIO3_MSK)); + WRITE_RPS1(cpu_to_le32(SAA7146_GPIO_OUTLO<<24)); +#if RPS_IRQ + // issue RPS1 interrupt + WRITE_RPS1(cpu_to_le32(CMD_INTERRUPT)); +#endif + // Jump to begin of RPS program (p37) + WRITE_RPS1(cpu_to_le32(CMD_JUMP)); + WRITE_RPS1(cpu_to_le32(dev->d_rps1.dma_handle)); + + // Fix VSYNC level + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); + // Set RPS1 Address register to point to RPS code (r108 p42) + saa7146_write(dev, RPS_ADDR1, dev->d_rps1.dma_handle); + // Set Source Line Counter Threshold, using BRS (rCC p43) + // It generates HS event every TS_HEIGHT lines + // this is related to TS_WIDTH set in register + // NUM_LINE_BYTE3 in budget-core.c. If NUM_LINE_BYTE + // low 16 bits are set to TS_WIDTH bytes (TS_WIDTH=2*188 + //,then RPS_THRESH1 + // should be set to trigger every TS_HEIGHT (512) lines. + // + saa7146_write(dev, RPS_THRESH1, (TS_HEIGHT*1) | MASK_12 ); + + // saa7146_write(dev, RPS_THRESH0, ((TS_HEIGHT/2)<<16) |MASK_28| (TS_HEIGHT/2) |MASK_12 ); + // Enable RPS1 (rFC p33) + saa7146_write(dev, MC1, (MASK_13 | MASK_29)); + + + if (!(budget = kmalloc (sizeof(struct budget_patch), GFP_KERNEL))) + return -ENOMEM; + + dprintk(2, "budget: %p\n", budget); + + if ((err = ttpci_budget_init (budget, dev, info, THIS_MODULE))) { + kfree (budget); + return err; + } + + + dev->ext_priv = budget; + + budget->dvb_adapter->priv = budget; + frontend_init(budget); + + return 0; +} + +static int budget_patch_detach (struct saa7146_dev* dev) +{ + struct budget_patch *budget = (struct budget_patch*) dev->ext_priv; + int err; + + if (budget->dvb_frontend) dvb_unregister_frontend(budget->dvb_frontend); + + err = ttpci_budget_deinit (budget); + + kfree (budget); + + return err; +} + +static int __init budget_patch_init(void) +{ + return saa7146_register_extension(&budget_extension); +} + +static void __exit budget_patch_exit(void) +{ + saa7146_unregister_extension(&budget_extension); +} + +static struct saa7146_extension budget_extension = { + .name = "budget_patch dvb\0", + .flags = 0, + + .module = THIS_MODULE, + .pci_tbl = pci_tbl, + .attach = budget_patch_attach, + .detach = budget_patch_detach, + + .irq_mask = MASK_10, + .irq_func = ttpci_budget_irq10_handler, +}; + +module_init(budget_patch_init); +module_exit(budget_patch_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Emard, Roberto Deza, Holger Waechtler, Michael Hunold, others"); +MODULE_DESCRIPTION("Driver for full TS modified DVB-S SAA7146+AV7110 " + "based so-called Budget Patch cards"); diff --git a/drivers/media/dvb/ttpci/budget.c b/drivers/media/dvb/ttpci/budget.c new file mode 100644 index 000000000000..5e6a10f4ad95 --- /dev/null +++ b/drivers/media/dvb/ttpci/budget.c @@ -0,0 +1,573 @@ +/* + * budget.c: driver for the SAA7146 based Budget DVB cards + * + * Compiled from various sources by Michael Hunold <michael@mihu.de> + * + * Copyright (C) 2002 Ralph Metzler <rjkm@metzlerbros.de> + * + * Copyright (C) 1999-2002 Ralph Metzler + * & Marcus Metzler for convergence integrated media GmbH + * + * 26feb2004 Support for FS Activy Card (Grundig tuner) by + * Michael Dreher <michael@5dot1.de>, + * Oliver Endriss <o.endriss@gmx.de> and + * Andreas 'randy' Weinberger + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + * + * the project's page is at http://www.linuxtv.org/dvb/ + */ + +#include "budget.h" +#include "stv0299.h" +#include "ves1x93.h" +#include "ves1820.h" +#include "l64781.h" +#include "tda8083.h" + +static void Set22K (struct budget *budget, int state) +{ + struct saa7146_dev *dev=budget->dev; + dprintk(2, "budget: %p\n", budget); + saa7146_setgpio(dev, 3, (state ? SAA7146_GPIO_OUTHI : SAA7146_GPIO_OUTLO)); +} + +/* Diseqc functions only for TT Budget card */ +/* taken from the Skyvision DVB driver by + Ralph Metzler <rjkm@metzlerbros.de> */ + +static void DiseqcSendBit (struct budget *budget, int data) +{ + struct saa7146_dev *dev=budget->dev; + dprintk(2, "budget: %p\n", budget); + + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTHI); + udelay(data ? 500 : 1000); + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); + udelay(data ? 1000 : 500); +} + +static void DiseqcSendByte (struct budget *budget, int data) +{ + int i, par=1, d; + + dprintk(2, "budget: %p\n", budget); + + for (i=7; i>=0; i--) { + d = (data>>i)&1; + par ^= d; + DiseqcSendBit(budget, d); + } + + DiseqcSendBit(budget, par); +} + +static int SendDiSEqCMsg (struct budget *budget, int len, u8 *msg, unsigned long burst) +{ + struct saa7146_dev *dev=budget->dev; + int i; + + dprintk(2, "budget: %p\n", budget); + + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); + mdelay(16); + + for (i=0; i<len; i++) + DiseqcSendByte(budget, msg[i]); + + mdelay(16); + + if (burst!=-1) { + if (burst) + DiseqcSendByte(budget, 0xff); + else { + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTHI); + udelay(12500); + saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); + } + msleep(20); + } + + return 0; +} + +/* + * Routines for the Fujitsu Siemens Activy budget card + * 22 kHz tone and DiSEqC are handled by the frontend. + * Voltage must be set here. + */ +static int SetVoltage_Activy (struct budget *budget, fe_sec_voltage_t voltage) +{ + struct saa7146_dev *dev=budget->dev; + + dprintk(2, "budget: %p\n", budget); + + switch (voltage) { + case SEC_VOLTAGE_13: + saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTLO); + break; + case SEC_VOLTAGE_18: + saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTHI); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int siemens_budget_set_voltage(struct dvb_frontend* fe, fe_sec_voltage_t voltage) +{ + struct budget* budget = (struct budget*) fe->dvb->priv; + + return SetVoltage_Activy (budget, voltage); +} + +static int budget_set_tone(struct dvb_frontend* fe, fe_sec_tone_mode_t tone) +{ + struct budget* budget = (struct budget*) fe->dvb->priv; + + switch (tone) { + case SEC_TONE_ON: + Set22K (budget, 1); + break; + + case SEC_TONE_OFF: + Set22K (budget, 0); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int budget_diseqc_send_master_cmd(struct dvb_frontend* fe, struct dvb_diseqc_master_cmd* cmd) +{ + struct budget* budget = (struct budget*) fe->dvb->priv; + + SendDiSEqCMsg (budget, cmd->msg_len, cmd->msg, 0); + + return 0; +} + +static int budget_diseqc_send_burst(struct dvb_frontend* fe, fe_sec_mini_cmd_t minicmd) +{ + struct budget* budget = (struct budget*) fe->dvb->priv; + + SendDiSEqCMsg (budget, 0, NULL, minicmd); + + return 0; +} + +static int alps_bsrv2_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + struct budget* budget = (struct budget*) fe->dvb->priv; + u8 pwr = 0; + u8 buf[4]; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = buf, .len = sizeof(buf) }; + u32 div = (params->frequency + 479500) / 125; + + if (params->frequency > 2000000) pwr = 3; + else if (params->frequency > 1800000) pwr = 2; + else if (params->frequency > 1600000) pwr = 1; + else if (params->frequency > 1200000) pwr = 0; + else if (params->frequency >= 1100000) pwr = 1; + else pwr = 2; + + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = ((div & 0x18000) >> 10) | 0x95; + buf[3] = (pwr << 6) | 0x30; + + // NOTE: since we're using a prescaler of 2, we set the + // divisor frequency to 62.5kHz and divide by 125 above + + if (i2c_transfer (&budget->i2c_adap, &msg, 1) != 1) return -EIO; + return 0; +} + +static struct ves1x93_config alps_bsrv2_config = +{ + .demod_address = 0x08, + .xin = 90100000UL, + .invert_pwm = 0, + .pll_set = alps_bsrv2_pll_set, +}; + +static u8 alps_bsru6_inittab[] = { + 0x01, 0x15, + 0x02, 0x00, + 0x03, 0x00, + 0x04, 0x7d, /* F22FR = 0x7d, F22 = f_VCO / 128 / 0x7d = 22 kHz */ + 0x05, 0x35, /* I2CT = 0, SCLT = 1, SDAT = 1 */ + 0x06, 0x40, /* DAC not used, set to high impendance mode */ + 0x07, 0x00, /* DAC LSB */ + 0x08, 0x40, /* DiSEqC off, LNB power on OP2/LOCK pin on */ + 0x09, 0x00, /* FIFO */ + 0x0c, 0x51, /* OP1 ctl = Normal, OP1 val = 1 (LNB Power ON) */ + 0x0d, 0x82, /* DC offset compensation = ON, beta_agc1 = 2 */ + 0x0e, 0x23, /* alpha_tmg = 2, beta_tmg = 3 */ + 0x10, 0x3f, // AGC2 0x3d + 0x11, 0x84, + 0x12, 0xb5, // Lock detect: -64 Carrier freq detect:on + 0x15, 0xc9, // lock detector threshold + 0x16, 0x00, + 0x17, 0x00, + 0x18, 0x00, + 0x19, 0x00, + 0x1a, 0x00, + 0x1f, 0x50, + 0x20, 0x00, + 0x21, 0x00, + 0x22, 0x00, + 0x23, 0x00, + 0x28, 0x00, // out imp: normal out type: parallel FEC mode:0 + 0x29, 0x1e, // 1/2 threshold + 0x2a, 0x14, // 2/3 threshold + 0x2b, 0x0f, // 3/4 threshold + 0x2c, 0x09, // 5/6 threshold + 0x2d, 0x05, // 7/8 threshold + 0x2e, 0x01, + 0x31, 0x1f, // test all FECs + 0x32, 0x19, // viterbi and synchro search + 0x33, 0xfc, // rs control + 0x34, 0x93, // error control + 0x0f, 0x52, + 0xff, 0xff +}; + +static int alps_bsru6_set_symbol_rate(struct dvb_frontend* fe, u32 srate, u32 ratio) +{ + u8 aclk = 0; + u8 bclk = 0; + + if (srate < 1500000) { aclk = 0xb7; bclk = 0x47; } + else if (srate < 3000000) { aclk = 0xb7; bclk = 0x4b; } + else if (srate < 7000000) { aclk = 0xb7; bclk = 0x4f; } + else if (srate < 14000000) { aclk = 0xb7; bclk = 0x53; } + else if (srate < 30000000) { aclk = 0xb6; bclk = 0x53; } + else if (srate < 45000000) { aclk = 0xb4; bclk = 0x51; } + + stv0299_writereg (fe, 0x13, aclk); + stv0299_writereg (fe, 0x14, bclk); + stv0299_writereg (fe, 0x1f, (ratio >> 16) & 0xff); + stv0299_writereg (fe, 0x20, (ratio >> 8) & 0xff); + stv0299_writereg (fe, 0x21, (ratio ) & 0xf0); + + return 0; +} + +static int alps_bsru6_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + struct budget* budget = (struct budget*) fe->dvb->priv; + u8 data[4]; + u32 div; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = data, .len = sizeof(data) }; + + if ((params->frequency < 950000) || (params->frequency > 2150000)) return -EINVAL; + + div = (params->frequency + (125 - 1)) / 125; // round correctly + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = 0x80 | ((div & 0x18000) >> 10) | 4; + data[3] = 0xC4; + + if (params->frequency > 1530000) data[3] = 0xc0; + + if (i2c_transfer (&budget->i2c_adap, &msg, 1) != 1) return -EIO; + return 0; +} + +static struct stv0299_config alps_bsru6_config = { + + .demod_address = 0x68, + .inittab = alps_bsru6_inittab, + .mclk = 88000000UL, + .invert = 1, + .enhanced_tuning = 0, + .skip_reinit = 0, + .lock_output = STV0229_LOCKOUTPUT_1, + .volt13_op0_op1 = STV0299_VOLT13_OP1, + .min_delay_ms = 100, + .set_symbol_rate = alps_bsru6_set_symbol_rate, + .pll_set = alps_bsru6_pll_set, +}; + +static int alps_tdbe2_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + struct budget* budget = (struct budget*) fe->dvb->priv; + u32 div; + u8 data[4]; + struct i2c_msg msg = { .addr = 0x62, .flags = 0, .buf = data, .len = sizeof(data) }; + + div = (params->frequency + 35937500 + 31250) / 62500; + + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = 0x85 | ((div >> 10) & 0x60); + data[3] = (params->frequency < 174000000 ? 0x88 : params->frequency < 470000000 ? 0x84 : 0x81); + + if (i2c_transfer (&budget->i2c_adap, &msg, 1) != 1) return -EIO; + return 0; +} + +static struct ves1820_config alps_tdbe2_config = { + .demod_address = 0x09, + .xin = 57840000UL, + .invert = 1, + .selagc = VES1820_SELAGC_SIGNAMPERR, + .pll_set = alps_tdbe2_pll_set, +}; + +static int grundig_29504_401_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + struct budget* budget = (struct budget*) fe->dvb->priv; + u32 div; + u8 cfg, cpump, band_select; + u8 data[4]; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = data, .len = sizeof(data) }; + + div = (36125000 + params->frequency) / 166666; + + cfg = 0x88; + + if (params->frequency < 175000000) cpump = 2; + else if (params->frequency < 390000000) cpump = 1; + else if (params->frequency < 470000000) cpump = 2; + else if (params->frequency < 750000000) cpump = 1; + else cpump = 3; + + if (params->frequency < 175000000) band_select = 0x0e; + else if (params->frequency < 470000000) band_select = 0x05; + else band_select = 0x03; + + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = ((div >> 10) & 0x60) | cfg; + data[3] = (cpump << 6) | band_select; + + if (i2c_transfer (&budget->i2c_adap, &msg, 1) != 1) return -EIO; + return 0; +} + +static struct l64781_config grundig_29504_401_config = { + .demod_address = 0x55, + .pll_set = grundig_29504_401_pll_set, +}; + +static int grundig_29504_451_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + struct budget* budget = (struct budget*) fe->dvb->priv; + u32 div; + u8 data[4]; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = data, .len = sizeof(data) }; + + div = params->frequency / 125; + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = 0x8e; + data[3] = 0x00; + + if (i2c_transfer (&budget->i2c_adap, &msg, 1) != 1) return -EIO; + return 0; +} + +static struct tda8083_config grundig_29504_451_config = { + .demod_address = 0x68, + .pll_set = grundig_29504_451_pll_set, +}; + +static u8 read_pwm(struct budget* budget) +{ + u8 b = 0xff; + u8 pwm; + struct i2c_msg msg[] = { { .addr = 0x50,.flags = 0,.buf = &b,.len = 1 }, + { .addr = 0x50,.flags = I2C_M_RD,.buf = &pwm,.len = 1} }; + + if ((i2c_transfer(&budget->i2c_adap, msg, 2) != 2) || (pwm == 0xff)) + pwm = 0x48; + + return pwm; +} + +static void frontend_init(struct budget *budget) +{ + switch(budget->dev->pci->subsystem_device) { + case 0x1003: // Hauppauge/TT Nova budget (stv0299/ALPS BSRU6(tsa5059) OR ves1893/ALPS BSRV2(sp5659)) + case 0x1013: + // try the ALPS BSRV2 first of all + budget->dvb_frontend = ves1x93_attach(&alps_bsrv2_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops->diseqc_send_master_cmd = budget_diseqc_send_master_cmd; + budget->dvb_frontend->ops->diseqc_send_burst = budget_diseqc_send_burst; + budget->dvb_frontend->ops->set_tone = budget_set_tone; + break; + } + + // try the ALPS BSRU6 now + budget->dvb_frontend = stv0299_attach(&alps_bsru6_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops->diseqc_send_master_cmd = budget_diseqc_send_master_cmd; + budget->dvb_frontend->ops->diseqc_send_burst = budget_diseqc_send_burst; + budget->dvb_frontend->ops->set_tone = budget_set_tone; + break; + } + break; + + case 0x1004: // Hauppauge/TT DVB-C budget (ves1820/ALPS TDBE2(sp5659)) + + budget->dvb_frontend = ves1820_attach(&alps_tdbe2_config, &budget->i2c_adap, read_pwm(budget)); + if (budget->dvb_frontend) break; + break; + + case 0x1005: // Hauppauge/TT Nova-T budget (L64781/Grundig 29504-401(tsa5060)) + + budget->dvb_frontend = l64781_attach(&grundig_29504_401_config, &budget->i2c_adap); + if (budget->dvb_frontend) break; + break; + + case 0x4f60: // Fujitsu Siemens Activy Budget-S PCI rev AL (stv0299/ALPS BSRU6(tsa5059)) + budget->dvb_frontend = stv0299_attach(&alps_bsru6_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops->set_voltage = siemens_budget_set_voltage; + break; + } + break; + + case 0x4f61: // Fujitsu Siemens Activy Budget-S PCI rev GR (tda8083/Grundig 29504-451(tsa5522)) + budget->dvb_frontend = tda8083_attach(&grundig_29504_451_config, &budget->i2c_adap); + if (budget->dvb_frontend) { + budget->dvb_frontend->ops->set_voltage = siemens_budget_set_voltage; + break; + } + break; + } + + if (budget->dvb_frontend == NULL) { + printk("budget: A frontend driver was not found for device %04x/%04x subsystem %04x/%04x\n", + budget->dev->pci->vendor, + budget->dev->pci->device, + budget->dev->pci->subsystem_vendor, + budget->dev->pci->subsystem_device); + } else { + if (dvb_register_frontend(budget->dvb_adapter, budget->dvb_frontend)) { + printk("budget: Frontend registration failed!\n"); + if (budget->dvb_frontend->ops->release) + budget->dvb_frontend->ops->release(budget->dvb_frontend); + budget->dvb_frontend = NULL; + } + } +} + +static int budget_attach (struct saa7146_dev* dev, struct saa7146_pci_extension_data *info) +{ + struct budget *budget = NULL; + int err; + + budget = kmalloc(sizeof(struct budget), GFP_KERNEL); + if( NULL == budget ) { + return -ENOMEM; + } + + dprintk(2, "dev:%p, info:%p, budget:%p\n", dev, info, budget); + + dev->ext_priv = budget; + + if ((err = ttpci_budget_init (budget, dev, info, THIS_MODULE))) { + printk("==> failed\n"); + kfree (budget); + return err; + } + + budget->dvb_adapter->priv = budget; + frontend_init(budget); + + return 0; +} + +static int budget_detach (struct saa7146_dev* dev) +{ + struct budget *budget = (struct budget*) dev->ext_priv; + int err; + + if (budget->dvb_frontend) dvb_unregister_frontend(budget->dvb_frontend); + + err = ttpci_budget_deinit (budget); + + kfree (budget); + dev->ext_priv = NULL; + + return err; +} + +static struct saa7146_extension budget_extension; + +MAKE_BUDGET_INFO(ttbs, "TT-Budget/WinTV-NOVA-S PCI", BUDGET_TT); +MAKE_BUDGET_INFO(ttbc, "TT-Budget/WinTV-NOVA-C PCI", BUDGET_TT); +MAKE_BUDGET_INFO(ttbt, "TT-Budget/WinTV-NOVA-T PCI", BUDGET_TT); +MAKE_BUDGET_INFO(satel, "SATELCO Multimedia PCI", BUDGET_TT_HW_DISEQC); +MAKE_BUDGET_INFO(fsacs0, "Fujitsu Siemens Activy Budget-S PCI (rev GR/grundig frontend)", BUDGET_FS_ACTIVY); +MAKE_BUDGET_INFO(fsacs1, "Fujitsu Siemens Activy Budget-S PCI (rev AL/alps frontend)", BUDGET_FS_ACTIVY); + +static struct pci_device_id pci_tbl[] = { + MAKE_EXTENSION_PCI(ttbs, 0x13c2, 0x1003), + MAKE_EXTENSION_PCI(ttbc, 0x13c2, 0x1004), + MAKE_EXTENSION_PCI(ttbt, 0x13c2, 0x1005), + MAKE_EXTENSION_PCI(satel, 0x13c2, 0x1013), + MAKE_EXTENSION_PCI(fsacs1,0x1131, 0x4f60), + MAKE_EXTENSION_PCI(fsacs0,0x1131, 0x4f61), + { + .vendor = 0, + } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_extension budget_extension = { + .name = "budget dvb\0", + .flags = 0, + + .module = THIS_MODULE, + .pci_tbl = pci_tbl, + .attach = budget_attach, + .detach = budget_detach, + + .irq_mask = MASK_10, + .irq_func = ttpci_budget_irq10_handler, +}; + +static int __init budget_init(void) +{ + return saa7146_register_extension(&budget_extension); +} + +static void __exit budget_exit(void) +{ + saa7146_unregister_extension(&budget_extension); +} + +module_init(budget_init); +module_exit(budget_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ralph Metzler, Marcus Metzler, Michael Hunold, others"); +MODULE_DESCRIPTION("driver for the SAA7146 based so-called " + "budget PCI DVB cards by Siemens, Technotrend, Hauppauge"); diff --git a/drivers/media/dvb/ttpci/budget.h b/drivers/media/dvb/ttpci/budget.h new file mode 100644 index 000000000000..10bd41f0363b --- /dev/null +++ b/drivers/media/dvb/ttpci/budget.h @@ -0,0 +1,110 @@ +#ifndef __BUDGET_DVB__ +#define __BUDGET_DVB__ + +#include "dvb_frontend.h" +#include "dvbdev.h" +#include "demux.h" +#include "dvb_demux.h" +#include "dmxdev.h" +#include "dvb_filter.h" +#include "dvb_net.h" + +#include <linux/module.h> +#include <media/saa7146.h> + +extern int budget_debug; + +#ifdef dprintk +#undef dprintk +#endif + +#define dprintk(level,args...) \ + do { if ((budget_debug & level)) { printk("%s: %s(): ",__stringify(KBUILD_MODNAME), __FUNCTION__); printk(args); } } while (0) + +struct budget_info { + char *name; + int type; +}; + +/* place to store all the necessary device information */ +struct budget { + + /* devices */ + struct dvb_device dvb_dev; + struct dvb_net dvb_net; + + struct saa7146_dev *dev; + + struct i2c_adapter i2c_adap; + struct budget_info *card; + + unsigned char *grabbing; + struct saa7146_pgtable pt; + + struct tasklet_struct fidb_tasklet; + struct tasklet_struct vpe_tasklet; + + struct dmxdev dmxdev; + struct dvb_demux demux; + + struct dmx_frontend hw_frontend; + struct dmx_frontend mem_frontend; + + int fe_synced; + struct semaphore pid_mutex; + + int ci_present; + int video_port; + + u8 tsf; + u32 ttbp; + int feeding; + + spinlock_t feedlock; + + spinlock_t debilock; + + struct dvb_adapter *dvb_adapter; + struct dvb_frontend *dvb_frontend; + void *priv; +}; + +#define MAKE_BUDGET_INFO(x_var,x_name,x_type) \ +static struct budget_info x_var ## _info = { \ + .name=x_name, \ + .type=x_type }; \ +static struct saa7146_pci_extension_data x_var = { \ + .ext_priv = &x_var ## _info, \ + .ext = &budget_extension }; + +#define TS_WIDTH (376) +#define TS_HEIGHT (512) +#define TS_BUFLEN (TS_WIDTH*TS_HEIGHT) +#define TS_MAX_PACKETS (TS_BUFLEN/TS_SIZE) + +#define BUDGET_TT 0 +#define BUDGET_TT_HW_DISEQC 1 +#define BUDGET_PATCH 3 +#define BUDGET_FS_ACTIVY 4 +#define BUDGET_CIN1200S 5 +#define BUDGET_CIN1200C 6 +#define BUDGET_CIN1200T 7 +#define BUDGET_KNC1S 8 +#define BUDGET_KNC1C 9 +#define BUDGET_KNC1T 10 + +#define BUDGET_VIDEO_PORTA 0 +#define BUDGET_VIDEO_PORTB 1 + +extern int ttpci_budget_init(struct budget *budget, struct saa7146_dev *dev, + struct saa7146_pci_extension_data *info, + struct module *owner); +extern int ttpci_budget_deinit(struct budget *budget); +extern void ttpci_budget_irq10_handler(struct saa7146_dev *dev, u32 * isr); +extern void ttpci_budget_set_video_port(struct saa7146_dev *dev, int video_port); +extern int ttpci_budget_debiread(struct budget *budget, u32 config, int addr, int count, + int uselocks, int nobusyloop); +extern int ttpci_budget_debiwrite(struct budget *budget, u32 config, int addr, int count, u32 value, + int uselocks, int nobusyloop); + +#endif diff --git a/drivers/media/dvb/ttpci/fdump.c b/drivers/media/dvb/ttpci/fdump.c new file mode 100644 index 000000000000..0b478db3e744 --- /dev/null +++ b/drivers/media/dvb/ttpci/fdump.c @@ -0,0 +1,44 @@ +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +int main(int argc, char **argv) +{ + unsigned char buf[8]; + unsigned int i, count, bytes = 0; + FILE *fd_in, *fd_out; + + if (argc != 4) { + fprintf(stderr, "\n\tusage: %s <ucode.bin> <array_name> <output_name>\n\n", argv[0]); + return -1; + } + + fd_in = fopen(argv[1], "rb"); + if (fd_in == NULL) { + fprintf(stderr, "firmware file '%s' not found\n", argv[1]); + return -1; + } + + fd_out = fopen(argv[3], "w+"); + if (fd_out == NULL) { + fprintf(stderr, "cannot create output file '%s'\n", argv[3]); + return -1; + } + + fprintf(fd_out, "\n#include <asm/types.h>\n\nu8 %s [] = {", argv[2]); + + while ((count = fread(buf, 1, 8, fd_in)) > 0) { + fprintf(fd_out, "\n\t"); + for (i = 0; i < count; i++, bytes++) + fprintf(fd_out, "0x%02x, ", buf[i]); + } + + fprintf(fd_out, "\n};\n\n"); + + fclose(fd_in); + fclose(fd_out); + + return 0; +} diff --git a/drivers/media/dvb/ttpci/ttpci-eeprom.c b/drivers/media/dvb/ttpci/ttpci-eeprom.c new file mode 100644 index 000000000000..e9a8457b0727 --- /dev/null +++ b/drivers/media/dvb/ttpci/ttpci-eeprom.c @@ -0,0 +1,146 @@ +/* + Retrieve encoded MAC address from 24C16 serial 2-wire EEPROM, + decode it and store it in the associated adapter struct for + use by dvb_net.c + + This card appear to have the 24C16 write protect held to ground, + thus permitting normal read/write operation. Theoretically it + would be possible to write routines to burn a different (encoded) + MAC address into the EEPROM. + + Robert Schlabbach GMX + Michael Glaum KVH Industries + Holger Waechtler Convergence + + Copyright (C) 2002-2003 Ralph Metzler <rjkm@metzlerbros.de> + Metzler Brothers Systementwicklung GbR + + 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 <asm/errno.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/i2c.h> + + +#if 1 +#define dprintk(x...) do { printk(x); } while (0) +#else +#define dprintk(x...) do { } while (0) +#endif + + +static int check_mac_tt(u8 *buf) +{ + int i; + u16 tmp = 0xffff; + + for (i = 0; i < 8; i++) { + tmp = (tmp << 8) | ((tmp >> 8) ^ buf[i]); + tmp ^= (tmp >> 4) & 0x0f; + tmp ^= (tmp << 12) ^ ((tmp & 0xff) << 5); + } + tmp ^= 0xffff; + return (((tmp >> 8) ^ buf[8]) | ((tmp & 0xff) ^ buf[9])); +} + +static int getmac_tt(u8 * decodedMAC, u8 * encodedMAC) +{ + u8 xor[20] = { 0x72, 0x23, 0x68, 0x19, 0x5c, 0xa8, 0x71, 0x2c, + 0x54, 0xd3, 0x7b, 0xf1, 0x9E, 0x23, 0x16, 0xf6, + 0x1d, 0x36, 0x64, 0x78}; + u8 data[20]; + int i; + + /* In case there is a sig check failure have the orig contents available */ + memcpy(data, encodedMAC, 20); + + for (i = 0; i < 20; i++) + data[i] ^= xor[i]; + for (i = 0; i < 10; i++) + data[i] = ((data[2 * i + 1] << 8) | data[2 * i]) + >> ((data[2 * i + 1] >> 6) & 3); + + if (check_mac_tt(data)) + return -ENODEV; + + decodedMAC[0] = data[2]; decodedMAC[1] = data[1]; decodedMAC[2] = data[0]; + decodedMAC[3] = data[6]; decodedMAC[4] = data[5]; decodedMAC[5] = data[4]; + return 0; +} + +static int ttpci_eeprom_read_encodedMAC(struct i2c_adapter *adapter, u8 * encodedMAC) +{ + int ret; + u8 b0[] = { 0xcc }; + + struct i2c_msg msg[] = { + { .addr = 0x50, .flags = 0, .buf = b0, .len = 1 }, + { .addr = 0x50, .flags = I2C_M_RD, .buf = encodedMAC, .len = 20 } + }; + + /* dprintk("%s\n", __FUNCTION__); */ + + ret = i2c_transfer(adapter, msg, 2); + + if (ret != 2) /* Assume EEPROM isn't there */ + return (-ENODEV); + + return 0; +} + + +int ttpci_eeprom_parse_mac(struct i2c_adapter *adapter, u8 *proposed_mac) +{ + int ret, i; + u8 encodedMAC[20]; + u8 decodedMAC[6]; + + ret = ttpci_eeprom_read_encodedMAC(adapter, encodedMAC); + + if (ret != 0) { /* Will only be -ENODEV */ + dprintk("Couldn't read from EEPROM: not there?\n"); + memset(proposed_mac, 0, 6); + return ret; + } + + ret = getmac_tt(decodedMAC, encodedMAC); + if( ret != 0 ) { + dprintk("adapter failed MAC signature check\n"); + dprintk("encoded MAC from EEPROM was " ); + for(i=0; i<19; i++) { + dprintk( "%.2x:", encodedMAC[i]); + } + dprintk("%.2x\n", encodedMAC[19]); + memset(proposed_mac, 0, 6); + return ret; + } + + memcpy(proposed_mac, decodedMAC, 6); + dprintk("adapter has MAC addr = %.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n", + decodedMAC[0], decodedMAC[1], decodedMAC[2], + decodedMAC[3], decodedMAC[4], decodedMAC[5]); + return 0; +} + +EXPORT_SYMBOL(ttpci_eeprom_parse_mac); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ralph Metzler, Marcus Metzler, others"); +MODULE_DESCRIPTION("Decode dvb_net MAC address from EEPROM of PCI DVB cards " + "made by Siemens, Technotrend, Hauppauge"); diff --git a/drivers/media/dvb/ttpci/ttpci-eeprom.h b/drivers/media/dvb/ttpci/ttpci-eeprom.h new file mode 100644 index 000000000000..e2dc6cfe205c --- /dev/null +++ b/drivers/media/dvb/ttpci/ttpci-eeprom.h @@ -0,0 +1,33 @@ +/* + Retrieve encoded MAC address from ATMEL ttpci_eeprom serial 2-wire EEPROM, + decode it and store it in associated adapter net device + + Robert Schlabbach GMX + Michael Glaum KVH Industries + Holger Waechtler Convergence + + 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 __TTPCI_EEPROM_H__ +#define __TTPCI_EEPROM_H__ + +#include <linux/types.h> +#include <linux/i2c.h> + +extern int ttpci_eeprom_parse_mac(struct i2c_adapter *adapter, u8 *propsed_mac); + +#endif diff --git a/drivers/media/dvb/ttusb-budget/Kconfig b/drivers/media/dvb/ttusb-budget/Kconfig new file mode 100644 index 000000000000..4aa714ab4c28 --- /dev/null +++ b/drivers/media/dvb/ttusb-budget/Kconfig @@ -0,0 +1,15 @@ +config DVB_TTUSB_BUDGET + tristate "Technotrend/Hauppauge Nova-USB devices" + depends on DVB_CORE && USB + select DVB_CX22700 + select DVB_TDA1004X + select DVB_TDA8083 + select DVB_STV0299 + help + Support for external USB adapters designed by Technotrend and + produced by Hauppauge, shipped under the brand name 'Nova-USB'. + + These devices don't have a MPEG decoder built in, so you need + an external software decoder to watch TV. + + Say Y if you own such a device and want to use it. diff --git a/drivers/media/dvb/ttusb-budget/Makefile b/drivers/media/dvb/ttusb-budget/Makefile new file mode 100644 index 000000000000..6ab97f6b53fc --- /dev/null +++ b/drivers/media/dvb/ttusb-budget/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_DVB_TTUSB_BUDGET) += dvb-ttusb-budget.o + +EXTRA_CFLAGS = -Idrivers/media/dvb/dvb-core/ -Idrivers/media/dvb/frontends diff --git a/drivers/media/dvb/ttusb-budget/dvb-ttusb-budget.c b/drivers/media/dvb/ttusb-budget/dvb-ttusb-budget.c new file mode 100644 index 000000000000..4c046ece883a --- /dev/null +++ b/drivers/media/dvb/ttusb-budget/dvb-ttusb-budget.c @@ -0,0 +1,1610 @@ +/* + * TTUSB DVB driver + * + * Copyright (c) 2002 Holger Waechtler <holger@convergence.de> + * Copyright (c) 2003 Felix Domke <tmbinc@elitedvb.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/wait.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/usb.h> +#include <linux/delay.h> +#include <linux/time.h> +#include <linux/errno.h> +#include <asm/semaphore.h> + +#include "dvb_frontend.h" +#include "dmxdev.h" +#include "dvb_demux.h" +#include "dvb_net.h" +#include "cx22700.h" +#include "tda1004x.h" +#include "stv0299.h" +#include "tda8083.h" + +#include <linux/dvb/frontend.h> +#include <linux/dvb/dmx.h> +#include <linux/pci.h> + + +/* + TTUSB_HWSECTIONS: + the DSP supports filtering in hardware, however, since the "muxstream" + is a bit braindead (no matching channel masks or no matching filter mask), + we won't support this - yet. it doesn't event support negative filters, + so the best way is maybe to keep TTUSB_HWSECTIONS undef'd and just + parse TS data. USB bandwith will be a problem when having large + datastreams, especially for dvb-net, but hey, that's not my problem. + + TTUSB_DISEQC, TTUSB_TONE: + let the STC do the diseqc/tone stuff. this isn't supported at least with + my TTUSB, so let it undef'd unless you want to implement another + frontend. never tested. + + DEBUG: + define it to > 3 for really hardcore debugging. you probably don't want + this unless the device doesn't load at all. > 2 for bandwidth statistics. +*/ + +static int debug; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off)."); + +#define dprintk(x...) do { if (debug) printk(KERN_DEBUG x); } while (0) + +#define ISO_BUF_COUNT 4 +#define FRAMES_PER_ISO_BUF 4 +#define ISO_FRAME_SIZE 912 +#define TTUSB_MAXCHANNEL 32 +#ifdef TTUSB_HWSECTIONS +#define TTUSB_MAXFILTER 16 /* ??? */ +#endif + +#define TTUSB_REV_2_2 0x22 +#define TTUSB_BUDGET_NAME "ttusb_stc_fw" + +/** + * since we're casting (struct ttusb*) <-> (struct dvb_demux*) around + * the dvb_demux field must be the first in struct!! + */ +struct ttusb { + struct dvb_demux dvb_demux; + struct dmxdev dmxdev; + struct dvb_net dvbnet; + + /* and one for USB access. */ + struct semaphore semi2c; + struct semaphore semusb; + + struct dvb_adapter *adapter; + struct usb_device *dev; + + struct i2c_adapter i2c_adap; + + int disconnecting; + int iso_streaming; + + unsigned int bulk_out_pipe; + unsigned int bulk_in_pipe; + unsigned int isoc_in_pipe; + + void *iso_buffer; + dma_addr_t iso_dma_handle; + + struct urb *iso_urb[ISO_BUF_COUNT]; + + int running_feed_count; + int last_channel; + int last_filter; + + u8 c; /* transaction counter, wraps around... */ + fe_sec_tone_mode_t tone; + fe_sec_voltage_t voltage; + + int mux_state; // 0..2 - MuxSyncWord, 3 - nMuxPacks, 4 - muxpack + u8 mux_npacks; + u8 muxpack[256 + 8]; + int muxpack_ptr, muxpack_len; + + int insync; + + int cc; /* MuxCounter - will increment on EVERY MUX PACKET */ + /* (including stuffing. yes. really.) */ + + u8 last_result[32]; + + int revision; + +#if 0 + devfs_handle_t stc_devfs_handle; +#endif + + struct dvb_frontend* fe; +}; + +/* ugly workaround ... don't know why it's neccessary to read */ +/* all result codes. */ + +#define DEBUG 0 +static int ttusb_cmd(struct ttusb *ttusb, + const u8 * data, int len, int needresult) +{ + int actual_len; + int err; +#if DEBUG >= 3 + int i; + + printk(">"); + for (i = 0; i < len; ++i) + printk(" %02x", data[i]); + printk("\n"); +#endif + + if (down_interruptible(&ttusb->semusb) < 0) + return -EAGAIN; + + err = usb_bulk_msg(ttusb->dev, ttusb->bulk_out_pipe, + (u8 *) data, len, &actual_len, 1000); + if (err != 0) { + dprintk("%s: usb_bulk_msg(send) failed, err == %i!\n", + __FUNCTION__, err); + up(&ttusb->semusb); + return err; + } + if (actual_len != len) { + dprintk("%s: only wrote %d of %d bytes\n", __FUNCTION__, + actual_len, len); + up(&ttusb->semusb); + return -1; + } + + err = usb_bulk_msg(ttusb->dev, ttusb->bulk_in_pipe, + ttusb->last_result, 32, &actual_len, 1000); + + if (err != 0) { + printk("%s: failed, receive error %d\n", __FUNCTION__, + err); + up(&ttusb->semusb); + return err; + } +#if DEBUG >= 3 + actual_len = ttusb->last_result[3] + 4; + printk("<"); + for (i = 0; i < actual_len; ++i) + printk(" %02x", ttusb->last_result[i]); + printk("\n"); +#endif + if (!needresult) + up(&ttusb->semusb); + return 0; +} + +static int ttusb_result(struct ttusb *ttusb, u8 * data, int len) +{ + memcpy(data, ttusb->last_result, len); + up(&ttusb->semusb); + return 0; +} + +static int ttusb_i2c_msg(struct ttusb *ttusb, + u8 addr, u8 * snd_buf, u8 snd_len, u8 * rcv_buf, + u8 rcv_len) +{ + u8 b[0x28]; + u8 id = ++ttusb->c; + int i, err; + + if (snd_len > 0x28 - 7 || rcv_len > 0x20 - 7) + return -EINVAL; + + b[0] = 0xaa; + b[1] = id; + b[2] = 0x31; + b[3] = snd_len + 3; + b[4] = addr << 1; + b[5] = snd_len; + b[6] = rcv_len; + + for (i = 0; i < snd_len; i++) + b[7 + i] = snd_buf[i]; + + err = ttusb_cmd(ttusb, b, snd_len + 7, 1); + + if (err) + return -EREMOTEIO; + + err = ttusb_result(ttusb, b, 0x20); + + /* check if the i2c transaction was successful */ + if ((snd_len != b[5]) || (rcv_len != b[6])) return -EREMOTEIO; + + if (rcv_len > 0) { + + if (err || b[0] != 0x55 || b[1] != id) { + dprintk + ("%s: usb_bulk_msg(recv) failed, err == %i, id == %02x, b == ", + __FUNCTION__, err, id); + return -EREMOTEIO; + } + + for (i = 0; i < rcv_len; i++) + rcv_buf[i] = b[7 + i]; + } + + return rcv_len; +} + +static int master_xfer(struct i2c_adapter* adapter, struct i2c_msg *msg, int num) +{ + struct ttusb *ttusb = i2c_get_adapdata(adapter); + int i = 0; + int inc; + + if (down_interruptible(&ttusb->semi2c) < 0) + return -EAGAIN; + + while (i < num) { + u8 addr, snd_len, rcv_len, *snd_buf, *rcv_buf; + int err; + + if (num > i + 1 && (msg[i + 1].flags & I2C_M_RD)) { + addr = msg[i].addr; + snd_buf = msg[i].buf; + snd_len = msg[i].len; + rcv_buf = msg[i + 1].buf; + rcv_len = msg[i + 1].len; + inc = 2; + } else { + addr = msg[i].addr; + snd_buf = msg[i].buf; + snd_len = msg[i].len; + rcv_buf = NULL; + rcv_len = 0; + inc = 1; + } + + err = ttusb_i2c_msg(ttusb, addr, + snd_buf, snd_len, rcv_buf, rcv_len); + + if (err < rcv_len) { + dprintk("%s: i == %i\n", __FUNCTION__, i); + break; + } + + i += inc; + } + + up(&ttusb->semi2c); + return i; +} + +#include "dvb-ttusb-dspbootcode.h" + +static int ttusb_boot_dsp(struct ttusb *ttusb) +{ + int i, err; + u8 b[40]; + + /* BootBlock */ + b[0] = 0xaa; + b[2] = 0x13; + b[3] = 28; + + /* upload dsp code in 32 byte steps (36 didn't work for me ...) */ + /* 32 is max packet size, no messages should be splitted. */ + for (i = 0; i < sizeof(dsp_bootcode); i += 28) { + memcpy(&b[4], &dsp_bootcode[i], 28); + + b[1] = ++ttusb->c; + + err = ttusb_cmd(ttusb, b, 32, 0); + if (err) + goto done; + } + + /* last block ... */ + b[1] = ++ttusb->c; + b[2] = 0x13; + b[3] = 0; + + err = ttusb_cmd(ttusb, b, 4, 0); + if (err) + goto done; + + /* BootEnd */ + b[1] = ++ttusb->c; + b[2] = 0x14; + b[3] = 0; + + err = ttusb_cmd(ttusb, b, 4, 0); + + done: + if (err) { + dprintk("%s: usb_bulk_msg() failed, return value %i!\n", + __FUNCTION__, err); + } + + return err; +} + +static int ttusb_set_channel(struct ttusb *ttusb, int chan_id, int filter_type, + int pid) +{ + int err; + /* SetChannel */ + u8 b[] = { 0xaa, ++ttusb->c, 0x22, 4, chan_id, filter_type, + (pid >> 8) & 0xff, pid & 0xff + }; + + err = ttusb_cmd(ttusb, b, sizeof(b), 0); + return err; +} + +static int ttusb_del_channel(struct ttusb *ttusb, int channel_id) +{ + int err; + /* DelChannel */ + u8 b[] = { 0xaa, ++ttusb->c, 0x23, 1, channel_id }; + + err = ttusb_cmd(ttusb, b, sizeof(b), 0); + return err; +} + +#ifdef TTUSB_HWSECTIONS +static int ttusb_set_filter(struct ttusb *ttusb, int filter_id, + int associated_chan, u8 filter[8], u8 mask[8]) +{ + int err; + /* SetFilter */ + u8 b[] = { 0xaa, 0, 0x24, 0x1a, filter_id, associated_chan, + filter[0], filter[1], filter[2], filter[3], + filter[4], filter[5], filter[6], filter[7], + filter[8], filter[9], filter[10], filter[11], + mask[0], mask[1], mask[2], mask[3], + mask[4], mask[5], mask[6], mask[7], + mask[8], mask[9], mask[10], mask[11] + }; + + err = ttusb_cmd(ttusb, b, sizeof(b), 0); + return err; +} + +static int ttusb_del_filter(struct ttusb *ttusb, int filter_id) +{ + int err; + /* DelFilter */ + u8 b[] = { 0xaa, ++ttusb->c, 0x25, 1, filter_id }; + + err = ttusb_cmd(ttusb, b, sizeof(b), 0); + return err; +} +#endif + +static int ttusb_init_controller(struct ttusb *ttusb) +{ + u8 b0[] = { 0xaa, ++ttusb->c, 0x15, 1, 0 }; + u8 b1[] = { 0xaa, ++ttusb->c, 0x15, 1, 1 }; + u8 b2[] = { 0xaa, ++ttusb->c, 0x32, 1, 0 }; + /* i2c write read: 5 bytes, addr 0x10, 0x02 bytes write, 1 bytes read. */ + u8 b3[] = + { 0xaa, ++ttusb->c, 0x31, 5, 0x10, 0x02, 0x01, 0x00, 0x1e }; + u8 b4[] = + { 0x55, ttusb->c, 0x31, 4, 0x10, 0x02, 0x01, 0x00, 0x1e }; + + u8 get_version[] = { 0xaa, ++ttusb->c, 0x17, 5, 0, 0, 0, 0, 0 }; + u8 get_dsp_version[0x20] = + { 0xaa, ++ttusb->c, 0x26, 28, 0, 0, 0, 0, 0 }; + int err; + + /* reset board */ + if ((err = ttusb_cmd(ttusb, b0, sizeof(b0), 0))) + return err; + + /* reset board (again?) */ + if ((err = ttusb_cmd(ttusb, b1, sizeof(b1), 0))) + return err; + + ttusb_boot_dsp(ttusb); + + /* set i2c bit rate */ + if ((err = ttusb_cmd(ttusb, b2, sizeof(b2), 0))) + return err; + + if ((err = ttusb_cmd(ttusb, b3, sizeof(b3), 1))) + return err; + + err = ttusb_result(ttusb, b4, sizeof(b4)); + + if ((err = ttusb_cmd(ttusb, get_version, sizeof(get_version), 1))) + return err; + + if ((err = ttusb_result(ttusb, get_version, sizeof(get_version)))) + return err; + + dprintk("%s: stc-version: %c%c%c%c%c\n", __FUNCTION__, + get_version[4], get_version[5], get_version[6], + get_version[7], get_version[8]); + + if (memcmp(get_version + 4, "V 0.0", 5) && + memcmp(get_version + 4, "V 1.1", 5) && + memcmp(get_version + 4, "V 2.1", 5) && + memcmp(get_version + 4, "V 2.2", 5)) { + printk + ("%s: unknown STC version %c%c%c%c%c, please report!\n", + __FUNCTION__, get_version[4], get_version[5], + get_version[6], get_version[7], get_version[8]); + } + + ttusb->revision = ((get_version[6] - '0') << 4) | + (get_version[8] - '0'); + + err = + ttusb_cmd(ttusb, get_dsp_version, sizeof(get_dsp_version), 1); + if (err) + return err; + + err = + ttusb_result(ttusb, get_dsp_version, sizeof(get_dsp_version)); + if (err) + return err; + printk("%s: dsp-version: %c%c%c\n", __FUNCTION__, + get_dsp_version[4], get_dsp_version[5], get_dsp_version[6]); + return 0; +} + +#ifdef TTUSB_DISEQC +static int ttusb_send_diseqc(struct dvb_frontend* fe, + const struct dvb_diseqc_master_cmd *cmd) +{ + struct ttusb* ttusb = (struct ttusb*) fe->dvb->priv; + u8 b[12] = { 0xaa, ++ttusb->c, 0x18 }; + + int err; + + b[3] = 4 + 2 + cmd->msg_len; + b[4] = 0xFF; /* send diseqc master, not burst */ + b[5] = cmd->msg_len; + + memcpy(b + 5, cmd->msg, cmd->msg_len); + + /* Diseqc */ + if ((err = ttusb_cmd(ttusb, b, 4 + b[3], 0))) { + dprintk("%s: usb_bulk_msg() failed, return value %i!\n", + __FUNCTION__, err); + } + + return err; +} +#endif + +static int lnbp21_set_voltage(struct dvb_frontend* fe, fe_sec_voltage_t voltage) +{ + struct ttusb* ttusb = (struct ttusb*) fe->dvb->priv; + int ret; + u8 data[1]; + struct i2c_msg msg = { .addr = 0x08, .flags = 0, .buf = data, .len = sizeof(data) }; + + switch(voltage) { + case SEC_VOLTAGE_OFF: + data[0] = 0x00; + break; + case SEC_VOLTAGE_13: + data[0] = 0x44; + break; + case SEC_VOLTAGE_18: + data[0] = 0x4c; + break; + default: + return -EINVAL; + }; + + ret = i2c_transfer(&ttusb->i2c_adap, &msg, 1); + return (ret != 1) ? -EIO : 0; +} + +static int ttusb_update_lnb(struct ttusb *ttusb) +{ + u8 b[] = { 0xaa, ++ttusb->c, 0x16, 5, /*power: */ 1, + ttusb->voltage == SEC_VOLTAGE_18 ? 0 : 1, + ttusb->tone == SEC_TONE_ON ? 1 : 0, 1, 1 + }; + int err; + + /* SetLNB */ + if ((err = ttusb_cmd(ttusb, b, sizeof(b), 0))) { + dprintk("%s: usb_bulk_msg() failed, return value %i!\n", + __FUNCTION__, err); + } + + return err; +} + +static int ttusb_set_voltage(struct dvb_frontend* fe, fe_sec_voltage_t voltage) +{ + struct ttusb* ttusb = (struct ttusb*) fe->dvb->priv; + + ttusb->voltage = voltage; + return ttusb_update_lnb(ttusb); +} + +#ifdef TTUSB_TONE +static int ttusb_set_tone(struct dvb_frontend* fe, fe_sec_tone_mode_t tone) +{ + struct ttusb* ttusb = (struct ttusb*) fe->dvb->priv; + + ttusb->tone = tone; + return ttusb_update_lnb(ttusb); +} +#endif + + +#if 0 +static void ttusb_set_led_freq(struct ttusb *ttusb, u8 freq) +{ + u8 b[] = { 0xaa, ++ttusb->c, 0x19, 1, freq }; + int err, actual_len; + + err = ttusb_cmd(ttusb, b, sizeof(b), 0); + if (err) { + dprintk("%s: usb_bulk_msg() failed, return value %i!\n", + __FUNCTION__, err); + } +} +#endif + +/*****************************************************************************/ + +#ifdef TTUSB_HWSECTIONS +static void ttusb_handle_ts_data(struct ttusb_channel *channel, + const u8 * data, int len); +static void ttusb_handle_sec_data(struct ttusb_channel *channel, + const u8 * data, int len); +#endif + +static int numpkt = 0, lastj, numts, numstuff, numsec, numinvalid; + +static void ttusb_process_muxpack(struct ttusb *ttusb, const u8 * muxpack, + int len) +{ + u16 csum = 0, cc; + int i; + for (i = 0; i < len; i += 2) + csum ^= le16_to_cpup((u16 *) (muxpack + i)); + if (csum) { + printk("%s: muxpack with incorrect checksum, ignoring\n", + __FUNCTION__); + numinvalid++; + return; + } + + cc = (muxpack[len - 4] << 8) | muxpack[len - 3]; + cc &= 0x7FFF; + if ((cc != ttusb->cc) && (ttusb->cc != -1)) + printk("%s: cc discontinuity (%d frames missing)\n", + __FUNCTION__, (cc - ttusb->cc) & 0x7FFF); + ttusb->cc = (cc + 1) & 0x7FFF; + if (muxpack[0] & 0x80) { +#ifdef TTUSB_HWSECTIONS + /* section data */ + int pusi = muxpack[0] & 0x40; + int channel = muxpack[0] & 0x1F; + int payload = muxpack[1]; + const u8 *data = muxpack + 2; + /* check offset flag */ + if (muxpack[0] & 0x20) + data++; + + ttusb_handle_sec_data(ttusb->channel + channel, data, + payload); + data += payload; + + if ((!!(ttusb->muxpack[0] & 0x20)) ^ + !!(ttusb->muxpack[1] & 1)) + data++; +#warning TODO: pusi + printk("cc: %04x\n", (data[0] << 8) | data[1]); +#endif + numsec++; + } else if (muxpack[0] == 0x47) { +#ifdef TTUSB_HWSECTIONS + /* we have TS data here! */ + int pid = ((muxpack[1] & 0x0F) << 8) | muxpack[2]; + int channel; + for (channel = 0; channel < TTUSB_MAXCHANNEL; ++channel) + if (ttusb->channel[channel].active + && (pid == ttusb->channel[channel].pid)) + ttusb_handle_ts_data(ttusb->channel + + channel, muxpack, + 188); +#endif + numts++; + dvb_dmx_swfilter_packets(&ttusb->dvb_demux, muxpack, 1); + } else if (muxpack[0] != 0) { + numinvalid++; + printk("illegal muxpack type %02x\n", muxpack[0]); + } else + numstuff++; +} + +static void ttusb_process_frame(struct ttusb *ttusb, u8 * data, int len) +{ + int maxwork = 1024; + while (len) { + if (!(maxwork--)) { + printk("%s: too much work\n", __FUNCTION__); + break; + } + + switch (ttusb->mux_state) { + case 0: + case 1: + case 2: + len--; + if (*data++ == 0xAA) + ++ttusb->mux_state; + else { + ttusb->mux_state = 0; +#if DEBUG > 3 + if (ttusb->insync) + printk("%02x ", data[-1]); +#else + if (ttusb->insync) { + printk("%s: lost sync.\n", + __FUNCTION__); + ttusb->insync = 0; + } +#endif + } + break; + case 3: + ttusb->insync = 1; + len--; + ttusb->mux_npacks = *data++; + ++ttusb->mux_state; + ttusb->muxpack_ptr = 0; + /* maximum bytes, until we know the length */ + ttusb->muxpack_len = 2; + break; + case 4: + { + int avail; + avail = len; + if (avail > + (ttusb->muxpack_len - + ttusb->muxpack_ptr)) + avail = + ttusb->muxpack_len - + ttusb->muxpack_ptr; + memcpy(ttusb->muxpack + ttusb->muxpack_ptr, + data, avail); + ttusb->muxpack_ptr += avail; + if (ttusb->muxpack_ptr > 264) + BUG(); + data += avail; + len -= avail; + /* determine length */ + if (ttusb->muxpack_ptr == 2) { + if (ttusb->muxpack[0] & 0x80) { + ttusb->muxpack_len = + ttusb->muxpack[1] + 2; + if (ttusb-> + muxpack[0] & 0x20) + ttusb-> + muxpack_len++; + if ((!! + (ttusb-> + muxpack[0] & 0x20)) ^ + !!(ttusb-> + muxpack[1] & 1)) + ttusb-> + muxpack_len++; + ttusb->muxpack_len += 4; + } else if (ttusb->muxpack[0] == + 0x47) + ttusb->muxpack_len = + 188 + 4; + else if (ttusb->muxpack[0] == 0x00) + ttusb->muxpack_len = + ttusb->muxpack[1] + 2 + + 4; + else { + dprintk + ("%s: invalid state: first byte is %x\n", + __FUNCTION__, + ttusb->muxpack[0]); + ttusb->mux_state = 0; + } + } + + /** + * if length is valid and we reached the end: + * goto next muxpack + */ + if ((ttusb->muxpack_ptr >= 2) && + (ttusb->muxpack_ptr == + ttusb->muxpack_len)) { + ttusb_process_muxpack(ttusb, + ttusb-> + muxpack, + ttusb-> + muxpack_ptr); + ttusb->muxpack_ptr = 0; + /* maximum bytes, until we know the length */ + ttusb->muxpack_len = 2; + + /** + * no muxpacks left? + * return to search-sync state + */ + if (!ttusb->mux_npacks--) { + ttusb->mux_state = 0; + break; + } + } + break; + } + default: + BUG(); + break; + } + } +} + +static void ttusb_iso_irq(struct urb *urb, struct pt_regs *ptregs) +{ + struct ttusb *ttusb = urb->context; + + if (!ttusb->iso_streaming) + return; + +#if 0 + printk("%s: status %d, errcount == %d, length == %i\n", + __FUNCTION__, + urb->status, urb->error_count, urb->actual_length); +#endif + + if (!urb->status) { + int i; + for (i = 0; i < urb->number_of_packets; ++i) { + struct usb_iso_packet_descriptor *d; + u8 *data; + int len; + numpkt++; + if ((jiffies - lastj) >= HZ) { +#if DEBUG > 2 + printk + ("frames/s: %d (ts: %d, stuff %d, sec: %d, invalid: %d, all: %d)\n", + numpkt * HZ / (jiffies - lastj), + numts, numstuff, numsec, numinvalid, + numts + numstuff + numsec + + numinvalid); +#endif + numts = numstuff = numsec = numinvalid = 0; + lastj = jiffies; + numpkt = 0; + } + d = &urb->iso_frame_desc[i]; + data = urb->transfer_buffer + d->offset; + len = d->actual_length; + d->actual_length = 0; + d->status = 0; + ttusb_process_frame(ttusb, data, len); + } + } + usb_submit_urb(urb, GFP_ATOMIC); +} + +static void ttusb_free_iso_urbs(struct ttusb *ttusb) +{ + int i; + + for (i = 0; i < ISO_BUF_COUNT; i++) + if (ttusb->iso_urb[i]) + usb_free_urb(ttusb->iso_urb[i]); + + pci_free_consistent(NULL, + ISO_FRAME_SIZE * FRAMES_PER_ISO_BUF * + ISO_BUF_COUNT, ttusb->iso_buffer, + ttusb->iso_dma_handle); +} + +static int ttusb_alloc_iso_urbs(struct ttusb *ttusb) +{ + int i; + + ttusb->iso_buffer = pci_alloc_consistent(NULL, + ISO_FRAME_SIZE * + FRAMES_PER_ISO_BUF * + ISO_BUF_COUNT, + &ttusb->iso_dma_handle); + + memset(ttusb->iso_buffer, 0, + ISO_FRAME_SIZE * FRAMES_PER_ISO_BUF * ISO_BUF_COUNT); + + for (i = 0; i < ISO_BUF_COUNT; i++) { + struct urb *urb; + + if (! + (urb = + usb_alloc_urb(FRAMES_PER_ISO_BUF, GFP_ATOMIC))) { + ttusb_free_iso_urbs(ttusb); + return -ENOMEM; + } + + ttusb->iso_urb[i] = urb; + } + + return 0; +} + +static void ttusb_stop_iso_xfer(struct ttusb *ttusb) +{ + int i; + + for (i = 0; i < ISO_BUF_COUNT; i++) + usb_kill_urb(ttusb->iso_urb[i]); + + ttusb->iso_streaming = 0; +} + +static int ttusb_start_iso_xfer(struct ttusb *ttusb) +{ + int i, j, err, buffer_offset = 0; + + if (ttusb->iso_streaming) { + printk("%s: iso xfer already running!\n", __FUNCTION__); + return 0; + } + + ttusb->cc = -1; + ttusb->insync = 0; + ttusb->mux_state = 0; + + for (i = 0; i < ISO_BUF_COUNT; i++) { + int frame_offset = 0; + struct urb *urb = ttusb->iso_urb[i]; + + urb->dev = ttusb->dev; + urb->context = ttusb; + urb->complete = ttusb_iso_irq; + urb->pipe = ttusb->isoc_in_pipe; + urb->transfer_flags = URB_ISO_ASAP; + urb->interval = 1; + urb->number_of_packets = FRAMES_PER_ISO_BUF; + urb->transfer_buffer_length = + ISO_FRAME_SIZE * FRAMES_PER_ISO_BUF; + urb->transfer_buffer = ttusb->iso_buffer + buffer_offset; + buffer_offset += ISO_FRAME_SIZE * FRAMES_PER_ISO_BUF; + + for (j = 0; j < FRAMES_PER_ISO_BUF; j++) { + urb->iso_frame_desc[j].offset = frame_offset; + urb->iso_frame_desc[j].length = ISO_FRAME_SIZE; + frame_offset += ISO_FRAME_SIZE; + } + } + + for (i = 0; i < ISO_BUF_COUNT; i++) { + if ((err = usb_submit_urb(ttusb->iso_urb[i], GFP_ATOMIC))) { + ttusb_stop_iso_xfer(ttusb); + printk + ("%s: failed urb submission (%i: err = %i)!\n", + __FUNCTION__, i, err); + return err; + } + } + + ttusb->iso_streaming = 1; + + return 0; +} + +#ifdef TTUSB_HWSECTIONS +static void ttusb_handle_ts_data(struct dvb_demux_feed *dvbdmxfeed, const u8 * data, + int len) +{ + dvbdmxfeed->cb.ts(data, len, 0, 0, &dvbdmxfeed->feed.ts, 0); +} + +static void ttusb_handle_sec_data(struct dvb_demux_feed *dvbdmxfeed, const u8 * data, + int len) +{ +// struct dvb_demux_feed *dvbdmxfeed = channel->dvbdmxfeed; +#error TODO: handle ugly stuff +// dvbdmxfeed->cb.sec(data, len, 0, 0, &dvbdmxfeed->feed.sec, 0); +} +#endif + +static int ttusb_start_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct ttusb *ttusb = (struct ttusb *) dvbdmxfeed->demux; + int feed_type = 1; + + dprintk("ttusb_start_feed\n"); + + switch (dvbdmxfeed->type) { + case DMX_TYPE_TS: + break; + case DMX_TYPE_SEC: + break; + default: + return -EINVAL; + } + + if (dvbdmxfeed->type == DMX_TYPE_TS) { + switch (dvbdmxfeed->pes_type) { + case DMX_TS_PES_VIDEO: + case DMX_TS_PES_AUDIO: + case DMX_TS_PES_TELETEXT: + case DMX_TS_PES_PCR: + case DMX_TS_PES_OTHER: + break; + default: + return -EINVAL; + } + } + +#ifdef TTUSB_HWSECTIONS +#error TODO: allocate filters + if (dvbdmxfeed->type == DMX_TYPE_TS) { + feed_type = 1; + } else if (dvbdmxfeed->type == DMX_TYPE_SEC) { + feed_type = 2; + } +#endif + + ttusb_set_channel(ttusb, dvbdmxfeed->index, feed_type, dvbdmxfeed->pid); + + if (0 == ttusb->running_feed_count++) + ttusb_start_iso_xfer(ttusb); + + return 0; +} + +static int ttusb_stop_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct ttusb *ttusb = (struct ttusb *) dvbdmxfeed->demux; + + ttusb_del_channel(ttusb, dvbdmxfeed->index); + + if (--ttusb->running_feed_count == 0) + ttusb_stop_iso_xfer(ttusb); + + return 0; +} + +static int ttusb_setup_interfaces(struct ttusb *ttusb) +{ + usb_set_interface(ttusb->dev, 1, 1); + + ttusb->bulk_out_pipe = usb_sndbulkpipe(ttusb->dev, 1); + ttusb->bulk_in_pipe = usb_rcvbulkpipe(ttusb->dev, 1); + ttusb->isoc_in_pipe = usb_rcvisocpipe(ttusb->dev, 2); + + return 0; +} + +#if 0 +static u8 stc_firmware[8192]; + +static int stc_open(struct inode *inode, struct file *file) +{ + struct ttusb *ttusb = file->private_data; + int addr; + + for (addr = 0; addr < 8192; addr += 16) { + u8 snd_buf[2] = { addr >> 8, addr & 0xFF }; + ttusb_i2c_msg(ttusb, 0x50, snd_buf, 2, stc_firmware + addr, + 16); + } + + return 0; +} + +static ssize_t stc_read(struct file *file, char *buf, size_t count, + loff_t * offset) +{ + int tc = count; + + if ((tc + *offset) > 8192) + tc = 8192 - *offset; + + if (tc < 0) + return 0; + + if (copy_to_user(buf, stc_firmware + *offset, tc)) + return -EFAULT; + + *offset += tc; + + return tc; +} + +static int stc_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static struct file_operations stc_fops = { + .owner = THIS_MODULE, + .read = stc_read, + .open = stc_open, + .release = stc_release, +}; +#endif + +static u32 functionality(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + + + +static int alps_tdmb7_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + struct ttusb* ttusb = (struct ttusb*) fe->dvb->priv; + u8 data[4]; + struct i2c_msg msg = {.addr=0x61, .flags=0, .buf=data, .len=sizeof(data) }; + u32 div; + + div = (params->frequency + 36166667) / 166667; + + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = ((div >> 10) & 0x60) | 0x85; + data[3] = params->frequency < 592000000 ? 0x40 : 0x80; + + if (i2c_transfer(&ttusb->i2c_adap, &msg, 1) != 1) return -EIO; + return 0; +} + +struct cx22700_config alps_tdmb7_config = { + .demod_address = 0x43, + .pll_set = alps_tdmb7_pll_set, +}; + + + + + +static int philips_tdm1316l_pll_init(struct dvb_frontend* fe) +{ + struct ttusb* ttusb = (struct ttusb*) fe->dvb->priv; + static u8 td1316_init[] = { 0x0b, 0xf5, 0x85, 0xab }; + static u8 disable_mc44BC374c[] = { 0x1d, 0x74, 0xa0, 0x68 }; + struct i2c_msg tuner_msg = { .addr=0x60, .flags=0, .buf=td1316_init, .len=sizeof(td1316_init) }; + + // setup PLL configuration + if (i2c_transfer(&ttusb->i2c_adap, &tuner_msg, 1) != 1) return -EIO; + msleep(1); + + // disable the mc44BC374c (do not check for errors) + tuner_msg.addr = 0x65; + tuner_msg.buf = disable_mc44BC374c; + tuner_msg.len = sizeof(disable_mc44BC374c); + if (i2c_transfer(&ttusb->i2c_adap, &tuner_msg, 1) != 1) { + i2c_transfer(&ttusb->i2c_adap, &tuner_msg, 1); + } + + return 0; +} + +static int philips_tdm1316l_pll_set(struct dvb_frontend* fe, struct dvb_frontend_parameters* params) +{ + struct ttusb* ttusb = (struct ttusb*) fe->dvb->priv; + u8 tuner_buf[4]; + struct i2c_msg tuner_msg = {.addr=0x60, .flags=0, .buf=tuner_buf, .len=sizeof(tuner_buf) }; + int tuner_frequency = 0; + u8 band, cp, filter; + + // determine charge pump + tuner_frequency = params->frequency + 36130000; + if (tuner_frequency < 87000000) return -EINVAL; + else if (tuner_frequency < 130000000) cp = 3; + else if (tuner_frequency < 160000000) cp = 5; + else if (tuner_frequency < 200000000) cp = 6; + else if (tuner_frequency < 290000000) cp = 3; + else if (tuner_frequency < 420000000) cp = 5; + else if (tuner_frequency < 480000000) cp = 6; + else if (tuner_frequency < 620000000) cp = 3; + else if (tuner_frequency < 830000000) cp = 5; + else if (tuner_frequency < 895000000) cp = 7; + else return -EINVAL; + + // determine band + if (params->frequency < 49000000) return -EINVAL; + else if (params->frequency < 159000000) band = 1; + else if (params->frequency < 444000000) band = 2; + else if (params->frequency < 861000000) band = 4; + else return -EINVAL; + + // setup PLL filter + switch (params->u.ofdm.bandwidth) { + case BANDWIDTH_6_MHZ: + tda1004x_write_byte(fe, 0x0C, 0); + filter = 0; + break; + + case BANDWIDTH_7_MHZ: + tda1004x_write_byte(fe, 0x0C, 0); + filter = 0; + break; + + case BANDWIDTH_8_MHZ: + tda1004x_write_byte(fe, 0x0C, 0xFF); + filter = 1; + break; + + default: + return -EINVAL; + } + + // calculate divisor + // ((36130000+((1000000/6)/2)) + Finput)/(1000000/6) + tuner_frequency = (((params->frequency / 1000) * 6) + 217280) / 1000; + + // setup tuner buffer + tuner_buf[0] = tuner_frequency >> 8; + tuner_buf[1] = tuner_frequency & 0xff; + tuner_buf[2] = 0xca; + tuner_buf[3] = (cp << 5) | (filter << 3) | band; + + if (i2c_transfer(&ttusb->i2c_adap, &tuner_msg, 1) != 1) + return -EIO; + + msleep(1); + return 0; +} + +static int philips_tdm1316l_request_firmware(struct dvb_frontend* fe, const struct firmware **fw, char* name) +{ + struct ttusb* ttusb = (struct ttusb*) fe->dvb->priv; + + return request_firmware(fw, name, &ttusb->dev->dev); +} + +static struct tda1004x_config philips_tdm1316l_config = { + + .demod_address = 0x8, + .invert = 1, + .invert_oclk = 0, + .pll_init = philips_tdm1316l_pll_init, + .pll_set = philips_tdm1316l_pll_set, + .request_firmware = philips_tdm1316l_request_firmware, +}; + +static u8 alps_bsbe1_inittab[] = { + 0x01, 0x15, + 0x02, 0x30, + 0x03, 0x00, + 0x04, 0x7d, /* F22FR = 0x7d, F22 = f_VCO / 128 / 0x7d = 22 kHz */ + 0x05, 0x35, /* I2CT = 0, SCLT = 1, SDAT = 1 */ + 0x06, 0x40, /* DAC not used, set to high impendance mode */ + 0x07, 0x00, /* DAC LSB */ + 0x08, 0x40, /* DiSEqC off, LNB power on OP2/LOCK pin on */ + 0x09, 0x00, /* FIFO */ + 0x0c, 0x51, /* OP1 ctl = Normal, OP1 val = 1 (LNB Power ON) */ + 0x0d, 0x82, /* DC offset compensation = ON, beta_agc1 = 2 */ + 0x0e, 0x23, /* alpha_tmg = 2, beta_tmg = 3 */ + 0x10, 0x3f, // AGC2 0x3d + 0x11, 0x84, + 0x12, 0xb5, // Lock detect: -64 Carrier freq detect:on + 0x15, 0xc9, // lock detector threshold + 0x16, 0x00, + 0x17, 0x00, + 0x18, 0x00, + 0x19, 0x00, + 0x1a, 0x00, + 0x1f, 0x50, + 0x20, 0x00, + 0x21, 0x00, + 0x22, 0x00, + 0x23, 0x00, + 0x28, 0x00, // out imp: normal out type: parallel FEC mode:0 + 0x29, 0x1e, // 1/2 threshold + 0x2a, 0x14, // 2/3 threshold + 0x2b, 0x0f, // 3/4 threshold + 0x2c, 0x09, // 5/6 threshold + 0x2d, 0x05, // 7/8 threshold + 0x2e, 0x01, + 0x31, 0x1f, // test all FECs + 0x32, 0x19, // viterbi and synchro search + 0x33, 0xfc, // rs control + 0x34, 0x93, // error control + 0x0f, 0x92, + 0xff, 0xff +}; + +static u8 alps_bsru6_inittab[] = { + 0x01, 0x15, + 0x02, 0x30, + 0x03, 0x00, + 0x04, 0x7d, /* F22FR = 0x7d, F22 = f_VCO / 128 / 0x7d = 22 kHz */ + 0x05, 0x35, /* I2CT = 0, SCLT = 1, SDAT = 1 */ + 0x06, 0x40, /* DAC not used, set to high impendance mode */ + 0x07, 0x00, /* DAC LSB */ + 0x08, 0x40, /* DiSEqC off, LNB power on OP2/LOCK pin on */ + 0x09, 0x00, /* FIFO */ + 0x0c, 0x51, /* OP1 ctl = Normal, OP1 val = 1 (LNB Power ON) */ + 0x0d, 0x82, /* DC offset compensation = ON, beta_agc1 = 2 */ + 0x0e, 0x23, /* alpha_tmg = 2, beta_tmg = 3 */ + 0x10, 0x3f, // AGC2 0x3d + 0x11, 0x84, + 0x12, 0xb5, // Lock detect: -64 Carrier freq detect:on + 0x15, 0xc9, // lock detector threshold + 0x16, 0x00, + 0x17, 0x00, + 0x18, 0x00, + 0x19, 0x00, + 0x1a, 0x00, + 0x1f, 0x50, + 0x20, 0x00, + 0x21, 0x00, + 0x22, 0x00, + 0x23, 0x00, + 0x28, 0x00, // out imp: normal out type: parallel FEC mode:0 + 0x29, 0x1e, // 1/2 threshold + 0x2a, 0x14, // 2/3 threshold + 0x2b, 0x0f, // 3/4 threshold + 0x2c, 0x09, // 5/6 threshold + 0x2d, 0x05, // 7/8 threshold + 0x2e, 0x01, + 0x31, 0x1f, // test all FECs + 0x32, 0x19, // viterbi and synchro search + 0x33, 0xfc, // rs control + 0x34, 0x93, // error control + 0x0f, 0x52, + 0xff, 0xff +}; + +static int alps_stv0299_set_symbol_rate(struct dvb_frontend *fe, u32 srate, u32 ratio) +{ + u8 aclk = 0; + u8 bclk = 0; + + if (srate < 1500000) { + aclk = 0xb7; + bclk = 0x47; + } else if (srate < 3000000) { + aclk = 0xb7; + bclk = 0x4b; + } else if (srate < 7000000) { + aclk = 0xb7; + bclk = 0x4f; + } else if (srate < 14000000) { + aclk = 0xb7; + bclk = 0x53; + } else if (srate < 30000000) { + aclk = 0xb6; + bclk = 0x53; + } else if (srate < 45000000) { + aclk = 0xb4; + bclk = 0x51; + } + + stv0299_writereg(fe, 0x13, aclk); + stv0299_writereg(fe, 0x14, bclk); + stv0299_writereg(fe, 0x1f, (ratio >> 16) & 0xff); + stv0299_writereg(fe, 0x20, (ratio >> 8) & 0xff); + stv0299_writereg(fe, 0x21, (ratio) & 0xf0); + + return 0; +} + +static int philips_tsa5059_pll_set(struct dvb_frontend *fe, struct dvb_frontend_parameters *params) +{ + struct ttusb* ttusb = (struct ttusb*) fe->dvb->priv; + u8 buf[4]; + u32 div; + struct i2c_msg msg = {.addr = 0x61,.flags = 0,.buf = buf,.len = sizeof(buf) }; + + if ((params->frequency < 950000) || (params->frequency > 2150000)) + return -EINVAL; + + div = (params->frequency + (125 - 1)) / 125; // round correctly + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = 0x80 | ((div & 0x18000) >> 10) | 4; + buf[3] = 0xC4; + + if (params->frequency > 1530000) + buf[3] = 0xC0; + + /* BSBE1 wants XCE bit set */ + if (ttusb->revision == TTUSB_REV_2_2) + buf[3] |= 0x20; + + if (i2c_transfer(&ttusb->i2c_adap, &msg, 1) != 1) + return -EIO; + + return 0; +} + +static struct stv0299_config alps_stv0299_config = { + .demod_address = 0x68, + .inittab = alps_bsru6_inittab, + .mclk = 88000000UL, + .invert = 1, + .enhanced_tuning = 0, + .skip_reinit = 0, + .lock_output = STV0229_LOCKOUTPUT_1, + .volt13_op0_op1 = STV0299_VOLT13_OP1, + .min_delay_ms = 100, + .set_symbol_rate = alps_stv0299_set_symbol_rate, + .pll_set = philips_tsa5059_pll_set, +}; + +static int ttusb_novas_grundig_29504_491_pll_set(struct dvb_frontend *fe, struct dvb_frontend_parameters *params) +{ + struct ttusb* ttusb = (struct ttusb*) fe->dvb->priv; + u8 buf[4]; + u32 div; + struct i2c_msg msg = {.addr = 0x61,.flags = 0,.buf = buf,.len = sizeof(buf) }; + + div = params->frequency / 125; + + buf[0] = (div >> 8) & 0x7f; + buf[1] = div & 0xff; + buf[2] = 0x8e; + buf[3] = 0x00; + + if (i2c_transfer(&ttusb->i2c_adap, &msg, 1) != 1) + return -EIO; + + return 0; +} + +static struct tda8083_config ttusb_novas_grundig_29504_491_config = { + + .demod_address = 0x68, + .pll_set = ttusb_novas_grundig_29504_491_pll_set, +}; + + + +static void frontend_init(struct ttusb* ttusb) +{ + switch(le16_to_cpu(ttusb->dev->descriptor.idProduct)) { + case 0x1003: // Hauppauge/TT Nova-USB-S budget (stv0299/ALPS BSRU6|BSBE1(tsa5059)) + // try the stv0299 based first + ttusb->fe = stv0299_attach(&alps_stv0299_config, &ttusb->i2c_adap); + if (ttusb->fe != NULL) { + if(ttusb->revision == TTUSB_REV_2_2) { // ALPS BSBE1 + alps_stv0299_config.inittab = alps_bsbe1_inittab; + ttusb->fe->ops->set_voltage = lnbp21_set_voltage; + } else { // ALPS BSRU6 + ttusb->fe->ops->set_voltage = ttusb_set_voltage; + } + break; + } + + // Grundig 29504-491 + ttusb->fe = tda8083_attach(&ttusb_novas_grundig_29504_491_config, &ttusb->i2c_adap); + if (ttusb->fe != NULL) { + ttusb->fe->ops->set_voltage = ttusb_set_voltage; + break; + } + + break; + + case 0x1005: // Hauppauge/TT Nova-USB-t budget (tda10046/Philips td1316(tda6651tt) OR cx22700/ALPS TDMB7(??)) + // try the ALPS TDMB7 first + ttusb->fe = cx22700_attach(&alps_tdmb7_config, &ttusb->i2c_adap); + if (ttusb->fe != NULL) + break; + + // Philips td1316 + ttusb->fe = tda10046_attach(&philips_tdm1316l_config, &ttusb->i2c_adap); + if (ttusb->fe != NULL) + break; + break; + } + + if (ttusb->fe == NULL) { + printk("dvb-ttusb-budget: A frontend driver was not found for device %04x/%04x\n", + le16_to_cpu(ttusb->dev->descriptor.idVendor), + le16_to_cpu(ttusb->dev->descriptor.idProduct)); + } else { + if (dvb_register_frontend(ttusb->adapter, ttusb->fe)) { + printk("dvb-ttusb-budget: Frontend registration failed!\n"); + if (ttusb->fe->ops->release) + ttusb->fe->ops->release(ttusb->fe); + ttusb->fe = NULL; + } + } +} + + + +static struct i2c_algorithm ttusb_dec_algo = { + .name = "ttusb dec i2c algorithm", + .id = I2C_ALGO_BIT, + .master_xfer = master_xfer, + .functionality = functionality, +}; + +static int ttusb_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev; + struct ttusb *ttusb; + int result; + + dprintk("%s: TTUSB DVB connected\n", __FUNCTION__); + + udev = interface_to_usbdev(intf); + + if (intf->altsetting->desc.bInterfaceNumber != 1) return -ENODEV; + + if (!(ttusb = kmalloc(sizeof(struct ttusb), GFP_KERNEL))) + return -ENOMEM; + + memset(ttusb, 0, sizeof(struct ttusb)); + + ttusb->dev = udev; + ttusb->c = 0; + ttusb->mux_state = 0; + sema_init(&ttusb->semi2c, 0); + sema_init(&ttusb->semusb, 1); + + ttusb_setup_interfaces(ttusb); + + ttusb_alloc_iso_urbs(ttusb); + if (ttusb_init_controller(ttusb)) + printk("ttusb_init_controller: error\n"); + + up(&ttusb->semi2c); + + dvb_register_adapter(&ttusb->adapter, "Technotrend/Hauppauge Nova-USB", THIS_MODULE); + ttusb->adapter->priv = ttusb; + + /* i2c */ + memset(&ttusb->i2c_adap, 0, sizeof(struct i2c_adapter)); + strcpy(ttusb->i2c_adap.name, "TTUSB DEC"); + + i2c_set_adapdata(&ttusb->i2c_adap, ttusb); + +#ifdef I2C_ADAP_CLASS_TV_DIGITAL + ttusb->i2c_adap.class = I2C_ADAP_CLASS_TV_DIGITAL; +#else + ttusb->i2c_adap.class = I2C_CLASS_TV_DIGITAL; +#endif + ttusb->i2c_adap.algo = &ttusb_dec_algo; + ttusb->i2c_adap.algo_data = NULL; + ttusb->i2c_adap.id = I2C_ALGO_BIT; + + result = i2c_add_adapter(&ttusb->i2c_adap); + if (result) { + dvb_unregister_adapter (ttusb->adapter); + return result; + } + + memset(&ttusb->dvb_demux, 0, sizeof(ttusb->dvb_demux)); + + ttusb->dvb_demux.dmx.capabilities = + DMX_TS_FILTERING | DMX_SECTION_FILTERING; + ttusb->dvb_demux.priv = NULL; +#ifdef TTUSB_HWSECTIONS + ttusb->dvb_demux.filternum = TTUSB_MAXFILTER; +#else + ttusb->dvb_demux.filternum = 32; +#endif + ttusb->dvb_demux.feednum = TTUSB_MAXCHANNEL; + ttusb->dvb_demux.start_feed = ttusb_start_feed; + ttusb->dvb_demux.stop_feed = ttusb_stop_feed; + ttusb->dvb_demux.write_to_decoder = NULL; + + if ((result = dvb_dmx_init(&ttusb->dvb_demux)) < 0) { + printk("ttusb_dvb: dvb_dmx_init failed (errno = %d)\n", result); + i2c_del_adapter(&ttusb->i2c_adap); + dvb_unregister_adapter (ttusb->adapter); + return -ENODEV; + } +//FIXME dmxdev (nur WAS?) + ttusb->dmxdev.filternum = ttusb->dvb_demux.filternum; + ttusb->dmxdev.demux = &ttusb->dvb_demux.dmx; + ttusb->dmxdev.capabilities = 0; + + if ((result = dvb_dmxdev_init(&ttusb->dmxdev, ttusb->adapter)) < 0) { + printk("ttusb_dvb: dvb_dmxdev_init failed (errno = %d)\n", + result); + dvb_dmx_release(&ttusb->dvb_demux); + i2c_del_adapter(&ttusb->i2c_adap); + dvb_unregister_adapter (ttusb->adapter); + return -ENODEV; + } + + if (dvb_net_init(ttusb->adapter, &ttusb->dvbnet, &ttusb->dvb_demux.dmx)) { + printk("ttusb_dvb: dvb_net_init failed!\n"); + dvb_dmxdev_release(&ttusb->dmxdev); + dvb_dmx_release(&ttusb->dvb_demux); + i2c_del_adapter(&ttusb->i2c_adap); + dvb_unregister_adapter (ttusb->adapter); + return -ENODEV; + } + +#if 0 + ttusb->stc_devfs_handle = + devfs_register(ttusb->adapter->devfs_handle, TTUSB_BUDGET_NAME, + DEVFS_FL_DEFAULT, 0, 192, + S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP + | S_IROTH | S_IWOTH, &stc_fops, ttusb); +#endif + usb_set_intfdata(intf, (void *) ttusb); + + frontend_init(ttusb); + + return 0; +} + +static void ttusb_disconnect(struct usb_interface *intf) +{ + struct ttusb *ttusb = usb_get_intfdata(intf); + + usb_set_intfdata(intf, NULL); + + ttusb->disconnecting = 1; + + ttusb_stop_iso_xfer(ttusb); + + ttusb->dvb_demux.dmx.close(&ttusb->dvb_demux.dmx); + dvb_net_release(&ttusb->dvbnet); + dvb_dmxdev_release(&ttusb->dmxdev); + dvb_dmx_release(&ttusb->dvb_demux); + if (ttusb->fe != NULL) dvb_unregister_frontend(ttusb->fe); + i2c_del_adapter(&ttusb->i2c_adap); + dvb_unregister_adapter(ttusb->adapter); + + ttusb_free_iso_urbs(ttusb); + + kfree(ttusb); + + dprintk("%s: TTUSB DVB disconnected\n", __FUNCTION__); +} + +static struct usb_device_id ttusb_table[] = { + {USB_DEVICE(0xb48, 0x1003)}, +/* {USB_DEVICE(0xb48, 0x1004)},UNDEFINED HARDWARE - mail linuxtv.org list*/ /* to be confirmed ???? */ + {USB_DEVICE(0xb48, 0x1005)}, + {} +}; + +MODULE_DEVICE_TABLE(usb, ttusb_table); + +static struct usb_driver ttusb_driver = { + .name = "Technotrend/Hauppauge USB-Nova", + .probe = ttusb_probe, + .disconnect = ttusb_disconnect, + .id_table = ttusb_table, +}; + +static int __init ttusb_init(void) +{ + int err; + + if ((err = usb_register(&ttusb_driver)) < 0) { + printk("%s: usb_register failed! Error number %d", + __FILE__, err); + return err; + } + + return 0; +} + +static void __exit ttusb_exit(void) +{ + usb_deregister(&ttusb_driver); +} + +module_init(ttusb_init); +module_exit(ttusb_exit); + +MODULE_AUTHOR("Holger Waechtler <holger@convergence.de>"); +MODULE_DESCRIPTION("TTUSB DVB Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/ttusb-budget/dvb-ttusb-dspbootcode.h b/drivers/media/dvb/ttusb-budget/dvb-ttusb-dspbootcode.h new file mode 100644 index 000000000000..95ee7995455e --- /dev/null +++ b/drivers/media/dvb/ttusb-budget/dvb-ttusb-dspbootcode.h @@ -0,0 +1,1644 @@ + +#include <asm/types.h> + +static u8 dsp_bootcode [] = { + 0x08, 0xaa, 0x00, 0x18, 0x00, 0x03, 0x08, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x01, 0x80, 0x18, 0x5f, + 0x00, 0x00, 0x01, 0x80, 0x77, 0x18, 0x2a, 0xeb, + 0x6b, 0xf8, 0x00, 0x18, 0x03, 0xff, 0x68, 0xf8, + 0x00, 0x18, 0xff, 0xfe, 0xf7, 0xb8, 0xf7, 0xbe, + 0xf6, 0xb9, 0xf4, 0xa0, 0xf6, 0xb7, 0xf6, 0xb5, + 0xf6, 0xb6, 0xf0, 0x20, 0x19, 0xdf, 0xf1, 0x00, + 0x00, 0x01, 0xf8, 0x4d, 0x01, 0xab, 0xf6, 0xb8, + 0xf0, 0x20, 0x19, 0xdf, 0xf0, 0x73, 0x01, 0xa5, + 0x7e, 0xf8, 0x00, 0x12, 0xf0, 0x00, 0x00, 0x01, + 0x47, 0xf8, 0x00, 0x11, 0x7e, 0x92, 0x00, 0xf8, + 0x00, 0x11, 0xf0, 0x00, 0x00, 0x01, 0x7e, 0xf8, + 0x00, 0x11, 0xf0, 0x00, 0x00, 0x01, 0x6c, 0x89, + 0x01, 0x9a, 0xf7, 0xb8, 0xee, 0xfc, 0xf0, 0x20, + 0xff, 0xff, 0xf1, 0x00, 0x00, 0x01, 0xf8, 0x4d, + 0x01, 0xbf, 0xf2, 0x73, 0x01, 0xb9, 0x4e, 0x02, + 0xf4, 0x95, 0xf5, 0xe3, 0x56, 0x02, 0x7e, 0x00, + 0x11, 0x00, 0xfa, 0x4c, 0x01, 0xb7, 0x6b, 0x03, + 0x00, 0x01, 0xf6, 0xb8, 0xee, 0x04, 0xf0, 0x74, + 0x0d, 0xa7, 0xf0, 0x74, 0x01, 0xc5, 0x4a, 0x11, + 0x4a, 0x16, 0x72, 0x11, 0x2a, 0xe6, 0x10, 0xf8, + 0x00, 0x11, 0xfa, 0x45, 0x01, 0xdb, 0xf4, 0x95, + 0xee, 0xff, 0x48, 0x11, 0xf0, 0x00, 0x2a, 0xc6, + 0x88, 0x16, 0xf4, 0x95, 0xf4, 0x95, 0x10, 0xee, + 0xff, 0xff, 0xf4, 0xe3, 0x6c, 0xe9, 0xff, 0xff, + 0x01, 0xd5, 0x10, 0xf8, 0x2a, 0xe7, 0xf8, 0x45, + 0x01, 0xe2, 0x10, 0xf8, 0x2a, 0xe7, 0xf4, 0xe3, + 0xf0, 0x74, 0x01, 0xff, 0xee, 0x01, 0x8a, 0x16, + 0x8a, 0x11, 0xfc, 0x00, 0xf7, 0xb8, 0xe9, 0x20, + 0x4a, 0x11, 0x09, 0xf8, 0x2a, 0xe6, 0xf8, 0x4e, + 0x01, 0xf3, 0xf2, 0x73, 0x01, 0xfd, 0xf4, 0x95, + 0xe8, 0x01, 0x72, 0x11, 0x2a, 0xe6, 0x49, 0x11, + 0x80, 0xe1, 0x2a, 0xc6, 0xf3, 0x00, 0x00, 0x01, + 0xe8, 0x00, 0x81, 0xf8, 0x2a, 0xe6, 0x8a, 0x11, + 0xfc, 0x00, 0xf4, 0x95, 0xf0, 0x73, 0x02, 0x00, + 0x10, 0xf8, 0x2a, 0x0f, 0xfc, 0x00, 0x4a, 0x11, + 0xf0, 0x74, 0x02, 0x02, 0x80, 0xf8, 0x2a, 0x10, + 0x73, 0x08, 0x00, 0x09, 0x40, 0xf8, 0x2a, 0x15, + 0x82, 0xf8, 0x00, 0x11, 0xf4, 0x95, 0x77, 0x10, + 0x03, 0xe8, 0xf5, 0xa9, 0xf8, 0x30, 0x02, 0x21, + 0x71, 0xf8, 0x2a, 0x10, 0x2a, 0x15, 0x56, 0xf8, + 0x2a, 0x0c, 0xf0, 0xe3, 0x4e, 0xf8, 0x2a, 0x16, + 0xe8, 0x00, 0x4e, 0xf8, 0x2a, 0x0c, 0x8a, 0x11, + 0xfc, 0x00, 0x4a, 0x06, 0x4a, 0x07, 0x4a, 0x1d, + 0x68, 0xf8, 0x00, 0x07, 0x7d, 0x3f, 0x69, 0xf8, + 0x00, 0x07, 0x40, 0x00, 0x68, 0xf8, 0x00, 0x1d, + 0xff, 0xfc, 0x6b, 0xf8, 0x2a, 0x0f, 0x00, 0x01, + 0x8a, 0x1d, 0x8a, 0x07, 0x8a, 0x06, 0xf4, 0xeb, + 0xee, 0xfd, 0x76, 0xf8, 0x2a, 0x0f, 0x00, 0x00, + 0x76, 0x00, 0x00, 0x00, 0xfb, 0x80, 0x19, 0x4c, + 0xf4, 0x95, 0xe8, 0x00, 0x80, 0xf8, 0x2a, 0x11, + 0xf9, 0x80, 0x19, 0x07, 0x80, 0xf8, 0x2a, 0x0e, + 0xf9, 0x80, 0x16, 0x66, 0x76, 0x00, 0x2a, 0x12, + 0x10, 0xf8, 0x2a, 0x11, 0xf9, 0x80, 0x18, 0xe3, + 0x10, 0xf8, 0x2a, 0x0e, 0xf9, 0x80, 0x16, 0x66, + 0x10, 0xf8, 0x2a, 0x0e, 0xf9, 0x80, 0x16, 0x87, + 0xee, 0x03, 0xfc, 0x00, 0x4a, 0x11, 0xf6, 0xb8, + 0xf4, 0x95, 0xf0, 0x20, 0x80, 0x00, 0x11, 0xf8, + 0x2a, 0x5a, 0xf8, 0x4d, 0x02, 0x93, 0x11, 0xf8, + 0x2a, 0x9f, 0xf8, 0x4c, 0x02, 0x7c, 0x77, 0x12, + 0x2a, 0x39, 0x49, 0x12, 0x01, 0xf8, 0x2a, 0x9f, + 0x89, 0x11, 0xf4, 0x95, 0xf4, 0x95, 0x71, 0x81, + 0x00, 0x11, 0x6c, 0xe1, 0xff, 0xab, 0x02, 0x93, + 0x6b, 0xf8, 0x2a, 0x9f, 0x00, 0x01, 0xe9, 0x05, + 0x01, 0xe2, 0x00, 0x03, 0x81, 0xf8, 0x2a, 0xa0, + 0xf0, 0x73, 0x02, 0x95, 0x72, 0x11, 0x2a, 0x9f, + 0xf4, 0x95, 0x10, 0xe1, 0x2a, 0x39, 0x6b, 0xf8, + 0x2a, 0x9f, 0x00, 0x01, 0x11, 0xf8, 0x2a, 0x9f, + 0x09, 0xf8, 0x2a, 0xa0, 0xf8, 0x4c, 0x02, 0x93, + 0x76, 0xf8, 0x2a, 0x5a, 0x00, 0x00, 0x76, 0xf8, + 0x2a, 0x9f, 0x00, 0x00, 0x76, 0xf8, 0x2a, 0xa0, + 0x00, 0x00, 0x88, 0x11, 0xf4, 0x95, 0x48, 0x11, + 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, 0xee, 0xfe, + 0x10, 0xf8, 0x2a, 0x5a, 0xf8, 0x44, 0x02, 0xb2, + 0x76, 0xf8, 0x2a, 0x5a, 0x00, 0x01, 0xf0, 0x74, + 0x02, 0x58, 0x88, 0x11, 0xf4, 0x95, 0x77, 0x10, + 0x80, 0x00, 0xf4, 0xa9, 0xf8, 0x30, 0x02, 0xb2, + 0x48, 0x11, 0xf0, 0x30, 0x00, 0xff, 0x80, 0x00, + 0x10, 0xf8, 0x2a, 0x5b, 0xf9, 0x80, 0x18, 0xd6, + 0xee, 0x02, 0x8a, 0x11, 0xfc, 0x00, 0xf4, 0x95, + 0x4a, 0x08, 0x4a, 0x09, 0x4a, 0x0a, 0x4a, 0x0b, + 0x4a, 0x0c, 0x4a, 0x0d, 0x4a, 0x10, 0x4a, 0x11, + 0x4a, 0x12, 0x4a, 0x13, 0x4a, 0x14, 0x4a, 0x15, + 0x4a, 0x16, 0x4a, 0x17, 0x4a, 0x17, 0x4a, 0x19, + 0x4a, 0x0e, 0x4a, 0x06, 0x4a, 0x07, 0x4a, 0x1a, + 0x4a, 0x1d, 0x4a, 0x1b, 0x4a, 0x1c, 0x68, 0xf8, + 0x00, 0x07, 0x7d, 0x3f, 0x69, 0xf8, 0x00, 0x07, + 0x40, 0x00, 0x68, 0xf8, 0x00, 0x1d, 0xff, 0xfc, + 0x48, 0x18, 0x68, 0xf8, 0x00, 0x18, 0xff, 0xfe, + 0xf4, 0x95, 0xf4, 0x95, 0x4a, 0x08, 0xee, 0xfd, + 0xf0, 0x74, 0x02, 0x58, 0x88, 0x11, 0xf4, 0x95, + 0x77, 0x10, 0x80, 0x00, 0xf4, 0xa9, 0xf8, 0x30, + 0x02, 0xef, 0x48, 0x11, 0xf0, 0x30, 0x00, 0xff, + 0x80, 0x00, 0x10, 0xf8, 0x2a, 0x5b, 0xf9, 0x80, + 0x18, 0xd6, 0xee, 0x03, 0x8a, 0x18, 0xf4, 0x95, + 0x8a, 0x1c, 0x8a, 0x1b, 0x8a, 0x1d, 0x8a, 0x1a, + 0x8a, 0x07, 0x8a, 0x06, 0x8a, 0x0e, 0x8a, 0x19, + 0x8a, 0x17, 0x8a, 0x17, 0x8a, 0x16, 0x8a, 0x15, + 0x8a, 0x14, 0x8a, 0x13, 0x8a, 0x12, 0x8a, 0x11, + 0x8a, 0x10, 0x8a, 0x0d, 0x8a, 0x0c, 0x8a, 0x0b, + 0x8a, 0x0a, 0x8a, 0x09, 0x8a, 0x08, 0xf4, 0xeb, + 0x4a, 0x11, 0x77, 0x11, 0x2a, 0x39, 0x76, 0x81, + 0x00, 0x55, 0x77, 0x12, 0x2a, 0x18, 0x10, 0xe2, + 0x00, 0x01, 0x80, 0xe1, 0x00, 0x01, 0x10, 0xe2, + 0x00, 0x02, 0x80, 0xe1, 0x00, 0x02, 0x76, 0xe1, + 0x00, 0x03, 0x00, 0x00, 0x76, 0xe1, 0x00, 0x04, + 0x00, 0xaa, 0xf0, 0x74, 0x02, 0x98, 0x8a, 0x11, + 0xfc, 0x00, 0x4a, 0x11, 0x88, 0x11, 0xf4, 0x95, + 0xf4, 0x95, 0x10, 0x81, 0x6f, 0xf8, 0x2a, 0x9e, + 0x0c, 0x88, 0xe8, 0xff, 0x18, 0xe1, 0x00, 0x01, + 0x1a, 0xf8, 0x2a, 0x9e, 0xf0, 0x30, 0x1f, 0xff, + 0x80, 0xf8, 0x2a, 0x9e, 0x8a, 0x11, 0xfc, 0x00, + 0x4a, 0x11, 0x77, 0x11, 0x2a, 0x39, 0x76, 0x81, + 0x00, 0x55, 0x77, 0x12, 0x2a, 0x18, 0x11, 0xe2, + 0x00, 0x01, 0x81, 0xe1, 0x00, 0x01, 0x11, 0xe2, + 0x00, 0x02, 0x81, 0xe1, 0x00, 0x02, 0x76, 0xe1, + 0x00, 0x03, 0x00, 0x02, 0x48, 0x08, 0x6f, 0xe1, + 0x00, 0x04, 0x0c, 0x98, 0xf0, 0x30, 0x00, 0xff, + 0x80, 0xe1, 0x00, 0x05, 0x76, 0xe1, 0x00, 0x06, + 0x00, 0xaa, 0xf0, 0x74, 0x02, 0x98, 0x8a, 0x11, + 0xfc, 0x00, 0x4a, 0x11, 0x77, 0x11, 0x2a, 0x39, + 0x76, 0x81, 0x00, 0x55, 0x77, 0x12, 0x2a, 0x18, + 0x10, 0xe2, 0x00, 0x01, 0x80, 0xe1, 0x00, 0x01, + 0x10, 0xe2, 0x00, 0x02, 0x80, 0xe1, 0x00, 0x02, + 0x76, 0xe1, 0x00, 0x03, 0x00, 0x04, 0x48, 0x11, + 0xf0, 0x00, 0x00, 0x04, 0x88, 0x12, 0xf4, 0x95, + 0x77, 0x13, 0x2a, 0x76, 0xe9, 0x00, 0xe5, 0x98, + 0xf3, 0x00, 0x00, 0x01, 0xf6, 0xb8, 0x48, 0x0b, + 0x08, 0xf8, 0x2a, 0x3c, 0xf8, 0x43, 0x03, 0x71, + 0x76, 0x82, 0x00, 0xaa, 0xf0, 0x74, 0x02, 0x98, + 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, 0xee, 0xf0, + 0x88, 0x11, 0xf4, 0x95, 0xf4, 0x95, 0x71, 0x81, + 0x00, 0x14, 0x71, 0xe1, 0x00, 0x01, 0x00, 0x15, + 0x49, 0x11, 0xf3, 0x00, 0x00, 0x02, 0x89, 0x11, + 0xe7, 0x82, 0x6d, 0xea, 0x00, 0x04, 0xe7, 0x83, + 0x6d, 0xeb, 0x00, 0x0a, 0x77, 0x1a, 0x00, 0x05, + 0xf0, 0x72, 0x03, 0xaa, 0x11, 0x81, 0xf2, 0xe8, + 0x80, 0x82, 0xe9, 0xff, 0x19, 0xe1, 0x00, 0x01, + 0xf1, 0xa0, 0x81, 0x92, 0x11, 0xe1, 0x00, 0x0c, + 0xf2, 0xe8, 0x80, 0x83, 0xe9, 0xff, 0x19, 0xe1, + 0x00, 0x0d, 0xf1, 0xa0, 0x81, 0x93, 0x6d, 0xe9, + 0x00, 0x02, 0x48, 0x18, 0x49, 0x18, 0x70, 0x00, + 0x00, 0x15, 0xf0, 0x00, 0x00, 0x04, 0xf3, 0x00, + 0x00, 0x0a, 0x80, 0x01, 0x81, 0x02, 0xf2, 0x74, + 0x0e, 0x54, 0xf4, 0x95, 0x48, 0x14, 0xee, 0x10, + 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, 0xf0, 0x74, + 0x0c, 0x5e, 0x80, 0xf8, 0x2a, 0x5c, 0x77, 0x12, + 0x2a, 0x39, 0x76, 0x82, 0x00, 0x55, 0x77, 0x11, + 0x2a, 0x18, 0x10, 0xe1, 0x00, 0x01, 0x80, 0xe2, + 0x00, 0x01, 0x10, 0xe1, 0x00, 0x02, 0x80, 0xe2, + 0x00, 0x02, 0x76, 0xe2, 0x00, 0x03, 0x00, 0x1c, + 0xf6, 0xb8, 0x56, 0xf8, 0x2a, 0x16, 0xf0, 0xf0, + 0xf0, 0xf8, 0x80, 0xe2, 0x00, 0x07, 0x56, 0xf8, + 0x2a, 0x16, 0xf1, 0xf0, 0xe8, 0xff, 0xf2, 0x80, + 0x80, 0xe2, 0x00, 0x06, 0x56, 0xf8, 0x2a, 0x16, + 0xf1, 0xf8, 0xe8, 0xff, 0xf2, 0x80, 0x80, 0xe2, + 0x00, 0x05, 0x57, 0xf8, 0x2a, 0x16, 0xe8, 0xff, + 0xf2, 0x80, 0x80, 0xe2, 0x00, 0x04, 0x56, 0xf8, + 0x27, 0x6c, 0xf0, 0xf0, 0xf0, 0xf8, 0x80, 0xe2, + 0x00, 0x0b, 0x56, 0xf8, 0x27, 0x6c, 0xf1, 0xf0, + 0xe8, 0xff, 0xf2, 0x80, 0x80, 0xe2, 0x00, 0x0a, + 0x56, 0xf8, 0x27, 0x6c, 0xf1, 0xf8, 0xe8, 0xff, + 0xf2, 0x80, 0x80, 0xe2, 0x00, 0x09, 0xe8, 0xff, + 0x57, 0xf8, 0x27, 0x6c, 0xf2, 0x80, 0x80, 0xe2, + 0x00, 0x08, 0x56, 0xf8, 0x27, 0x6a, 0xf0, 0xf0, + 0xf0, 0xf8, 0x80, 0xe2, 0x00, 0x0f, 0x56, 0xf8, + 0x27, 0x6a, 0xf1, 0xf0, 0xe8, 0xff, 0xf2, 0x80, + 0x80, 0xe2, 0x00, 0x0e, 0x56, 0xf8, 0x27, 0x6a, + 0xf1, 0xf8, 0xe8, 0xff, 0xf2, 0x80, 0x80, 0xe2, + 0x00, 0x0d, 0x57, 0xf8, 0x27, 0x6a, 0xe8, 0xff, + 0xf2, 0x80, 0x80, 0xe2, 0x00, 0x0c, 0x76, 0xe2, + 0x00, 0x13, 0x00, 0x00, 0x76, 0xe2, 0x00, 0x12, + 0x00, 0x00, 0x6f, 0xf8, 0x2a, 0x5c, 0x0c, 0x58, + 0x80, 0xe2, 0x00, 0x11, 0xe8, 0xff, 0x18, 0xf8, + 0x2a, 0x5c, 0x80, 0xe2, 0x00, 0x10, 0x76, 0xe2, + 0x00, 0x17, 0x00, 0x00, 0x76, 0xe2, 0x00, 0x16, + 0x00, 0x00, 0x6f, 0xf8, 0x2a, 0x9e, 0x0c, 0x58, + 0x80, 0xe2, 0x00, 0x15, 0xe8, 0xff, 0x18, 0xf8, + 0x2a, 0x9e, 0x80, 0xe2, 0x00, 0x14, 0x76, 0xe2, + 0x00, 0x1b, 0x00, 0x00, 0x76, 0xe2, 0x00, 0x1a, + 0x00, 0x00, 0x76, 0xe2, 0x00, 0x19, 0x00, 0x00, + 0x70, 0xe2, 0x00, 0x18, 0x27, 0x6e, 0x76, 0xe2, + 0x00, 0x1f, 0x00, 0x00, 0x76, 0xe2, 0x00, 0x1e, + 0x00, 0x00, 0x76, 0xe2, 0x00, 0x1d, 0x00, 0x00, + 0x76, 0xe2, 0x00, 0x1c, 0x00, 0x00, 0x76, 0xe2, + 0x00, 0x20, 0x00, 0xaa, 0xf0, 0x74, 0x02, 0x98, + 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, 0xee, 0xfe, + 0x10, 0xf8, 0x2a, 0x38, 0xf8, 0x45, 0x04, 0xed, + 0x77, 0x12, 0x2a, 0x18, 0x10, 0xe2, 0x00, 0x02, + 0x88, 0x11, 0xf4, 0x95, 0x77, 0x10, 0x00, 0x08, + 0x6d, 0xe9, 0xff, 0xdf, 0xf6, 0xa9, 0xf8, 0x20, + 0x04, 0x75, 0xf0, 0x73, 0x04, 0x7d, 0xf0, 0x10, + 0x00, 0x21, 0xf0, 0x00, 0x1a, 0x83, 0x48, 0x08, + 0x7e, 0xf8, 0x00, 0x08, 0xf4, 0xe2, 0xf0, 0x74, + 0x03, 0x0a, 0xf0, 0x73, 0x04, 0xea, 0x48, 0x12, + 0xf2, 0x74, 0x03, 0x23, 0xf0, 0x00, 0x00, 0x04, + 0xf2, 0x74, 0x03, 0x36, 0xf4, 0x95, 0xe8, 0x00, + 0xf0, 0x73, 0x04, 0xea, 0x77, 0x11, 0x2a, 0x18, + 0xe8, 0xff, 0x6f, 0xe1, 0x00, 0x04, 0x0d, 0x48, + 0x18, 0xe1, 0x00, 0x05, 0xf2, 0x74, 0x09, 0x69, + 0xf4, 0x95, 0xf2, 0xa0, 0xf0, 0x74, 0x03, 0x36, + 0xf0, 0x73, 0x04, 0xea, 0x77, 0x11, 0x2a, 0x18, + 0xe8, 0xff, 0x6f, 0xe1, 0x00, 0x04, 0x0d, 0x48, + 0x18, 0xe1, 0x00, 0x05, 0xf2, 0x74, 0x09, 0x41, + 0xf4, 0x95, 0xf2, 0xa0, 0xf0, 0x74, 0x03, 0x36, + 0xf0, 0x73, 0x04, 0xea, 0xf0, 0x74, 0x03, 0x57, + 0xf0, 0x73, 0x04, 0xea, 0x10, 0xf8, 0x2a, 0x1c, + 0xf0, 0x74, 0x12, 0xa4, 0xf2, 0x74, 0x03, 0x36, + 0xf4, 0x95, 0xe8, 0x00, 0xf0, 0x73, 0x04, 0xea, + 0x48, 0x12, 0xf2, 0x74, 0x03, 0x80, 0xf0, 0x00, + 0x00, 0x04, 0xf2, 0x74, 0x03, 0x36, 0xf4, 0x95, + 0xe8, 0x00, 0xf0, 0x73, 0x04, 0xea, 0x10, 0xf8, + 0x2a, 0x1c, 0xf0, 0x74, 0x12, 0xc5, 0xf2, 0x74, + 0x03, 0x36, 0xf4, 0x95, 0xe8, 0x00, 0xf0, 0x73, + 0x04, 0xea, 0x77, 0x11, 0x2a, 0x18, 0xe8, 0xff, + 0x6f, 0xe1, 0x00, 0x06, 0x0d, 0x48, 0x18, 0xe1, + 0x00, 0x07, 0x71, 0xe1, 0x00, 0x05, 0x00, 0x12, + 0xf2, 0xa0, 0x70, 0x00, 0x00, 0x12, 0x80, 0x01, + 0x10, 0xe1, 0x00, 0x04, 0xf0, 0x74, 0x0e, 0x7a, + 0xf2, 0x74, 0x03, 0x36, 0xf4, 0x95, 0xe8, 0x00, + 0xf0, 0x73, 0x04, 0xea, 0xf0, 0x74, 0x03, 0xbc, + 0x76, 0xf8, 0x2a, 0x38, 0x00, 0x00, 0xee, 0x02, + 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, 0x77, 0x11, + 0x2a, 0x39, 0x76, 0x81, 0x00, 0x55, 0x77, 0x12, + 0x2a, 0x18, 0x10, 0xe2, 0x00, 0x01, 0x80, 0xe1, + 0x00, 0x01, 0x10, 0xe2, 0x00, 0x02, 0x80, 0xe1, + 0x00, 0x02, 0x76, 0xe1, 0x00, 0x03, 0x00, 0x09, + 0x48, 0x11, 0xf0, 0x00, 0x00, 0x04, 0x88, 0x12, + 0xf4, 0x95, 0x77, 0x13, 0x2a, 0x86, 0xe9, 0x00, + 0xe5, 0x98, 0xf3, 0x00, 0x00, 0x01, 0xf6, 0xb8, + 0x48, 0x0b, 0x08, 0xf8, 0x2a, 0x3c, 0xf8, 0x43, + 0x05, 0x0a, 0x76, 0x82, 0x00, 0xaa, 0xf0, 0x74, + 0x02, 0x98, 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, + 0x77, 0x11, 0x2a, 0x39, 0x76, 0x81, 0x00, 0x55, + 0x77, 0x13, 0x2a, 0x18, 0x10, 0xe3, 0x00, 0x01, + 0x80, 0xe1, 0x00, 0x01, 0x10, 0xe3, 0x00, 0x02, + 0x80, 0xe1, 0x00, 0x02, 0x13, 0xe3, 0x00, 0x03, + 0x81, 0xe1, 0x00, 0x03, 0x48, 0x11, 0x77, 0x11, + 0x00, 0x00, 0xf8, 0x4d, 0x05, 0x44, 0xf0, 0x00, + 0x00, 0x04, 0x88, 0x12, 0x48, 0x13, 0xf0, 0x00, + 0x00, 0x04, 0x88, 0x13, 0xf4, 0x95, 0xf4, 0x95, + 0xe5, 0x98, 0x6d, 0x91, 0xf6, 0xb8, 0x48, 0x11, + 0x08, 0xf8, 0x2a, 0x3c, 0xf8, 0x43, 0x05, 0x3a, + 0xf0, 0x20, 0x2a, 0x39, 0x49, 0x11, 0xf5, 0x00, + 0x89, 0x11, 0xf4, 0x95, 0xf4, 0x95, 0x76, 0xe1, + 0x00, 0x04, 0x00, 0xaa, 0xf0, 0x74, 0x02, 0x98, + 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, 0x77, 0x11, + 0x2a, 0x39, 0x76, 0x81, 0x00, 0x55, 0x77, 0x12, + 0x2a, 0x18, 0x10, 0xe2, 0x00, 0x01, 0x80, 0xe1, + 0x00, 0x01, 0x10, 0xe2, 0x00, 0x02, 0x80, 0xe1, + 0x00, 0x02, 0x76, 0xe1, 0x00, 0x03, 0x00, 0x0c, + 0x48, 0x11, 0xf0, 0x00, 0x00, 0x04, 0x88, 0x12, + 0xf4, 0x95, 0x77, 0x13, 0x2a, 0x7a, 0xe9, 0x00, + 0xe5, 0x98, 0xf3, 0x00, 0x00, 0x01, 0xf6, 0xb8, + 0x48, 0x0b, 0x08, 0xf8, 0x2a, 0x3c, 0xf8, 0x43, + 0x05, 0x6a, 0x76, 0x82, 0x00, 0xaa, 0xf0, 0x74, + 0x02, 0x98, 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, + 0x77, 0x11, 0x2a, 0x39, 0x76, 0x81, 0x00, 0x55, + 0x77, 0x12, 0x2a, 0x18, 0x10, 0xe2, 0x00, 0x01, + 0x80, 0xe1, 0x00, 0x01, 0x10, 0xe2, 0x00, 0x02, + 0x80, 0xe1, 0x00, 0x02, 0x76, 0xe1, 0x00, 0x03, + 0x00, 0x19, 0x48, 0x11, 0xf0, 0x00, 0x00, 0x04, + 0x88, 0x12, 0xf4, 0x95, 0x77, 0x13, 0x2a, 0x5d, + 0xe9, 0x00, 0xe5, 0x98, 0xf3, 0x00, 0x00, 0x01, + 0xf6, 0xb8, 0x48, 0x0b, 0x08, 0xf8, 0x2a, 0x3c, + 0xf8, 0x43, 0x05, 0x93, 0x76, 0x82, 0x00, 0xaa, + 0xf0, 0x74, 0x02, 0x98, 0x8a, 0x11, 0xfc, 0x00, + 0x4a, 0x11, 0x88, 0x11, 0x10, 0xf8, 0x2a, 0x38, + 0xf8, 0x44, 0x05, 0xe3, 0x10, 0xf8, 0x2a, 0xa1, + 0xf8, 0x44, 0x05, 0xba, 0x6c, 0xe1, 0xff, 0x56, + 0x05, 0xe3, 0x72, 0x12, 0x2a, 0xa1, 0xf4, 0x95, + 0x70, 0xe2, 0x2a, 0x18, 0x00, 0x11, 0x6b, 0xf8, + 0x2a, 0xa1, 0x00, 0x01, 0xf0, 0x73, 0x05, 0xe3, + 0x72, 0x12, 0x2a, 0xa1, 0xf4, 0x95, 0x70, 0xe2, + 0x2a, 0x18, 0x00, 0x11, 0x10, 0xf8, 0x2a, 0xa1, + 0xf0, 0x00, 0x00, 0x01, 0x88, 0x12, 0xf4, 0x95, + 0xf4, 0x95, 0x6e, 0xe2, 0xff, 0xfc, 0x05, 0xd1, + 0x73, 0x12, 0x2a, 0xa1, 0x48, 0x11, 0xf0, 0x00, + 0x00, 0x05, 0x80, 0xf8, 0x2a, 0xa2, 0x10, 0xf8, + 0x2a, 0xa1, 0x08, 0xf8, 0x2a, 0xa2, 0xf8, 0x44, + 0x05, 0xe3, 0x6c, 0xe1, 0xff, 0xab, 0x05, 0xdd, + 0x76, 0xf8, 0x2a, 0x38, 0x00, 0x01, 0x76, 0xf8, + 0x2a, 0xa1, 0x00, 0x00, 0x76, 0xf8, 0x2a, 0xa2, + 0x00, 0x00, 0x8a, 0x11, 0xfc, 0x00, 0xf4, 0x95, + 0x4a, 0x08, 0x4a, 0x09, 0x4a, 0x0a, 0x4a, 0x0b, + 0x4a, 0x0c, 0x4a, 0x0d, 0x4a, 0x10, 0x4a, 0x11, + 0x4a, 0x12, 0x4a, 0x13, 0x4a, 0x14, 0x4a, 0x15, + 0x4a, 0x16, 0x4a, 0x17, 0x4a, 0x17, 0x4a, 0x19, + 0x4a, 0x0e, 0x4a, 0x06, 0x4a, 0x07, 0x4a, 0x1a, + 0x4a, 0x1d, 0x4a, 0x1b, 0x4a, 0x1c, 0x68, 0xf8, + 0x00, 0x07, 0x7d, 0x3f, 0x69, 0xf8, 0x00, 0x07, + 0x40, 0x00, 0x68, 0xf8, 0x00, 0x1d, 0xff, 0xfc, + 0x48, 0x18, 0x68, 0xf8, 0x00, 0x18, 0xff, 0xfe, + 0xf4, 0x95, 0xf4, 0x95, 0x4a, 0x08, 0xee, 0xff, + 0x10, 0xf8, 0x2a, 0x5b, 0xf9, 0x80, 0x18, 0x04, + 0xf0, 0x74, 0x05, 0xa2, 0xee, 0x01, 0x8a, 0x18, + 0xf4, 0x95, 0x8a, 0x1c, 0x8a, 0x1b, 0x8a, 0x1d, + 0x8a, 0x1a, 0x8a, 0x07, 0x8a, 0x06, 0x8a, 0x0e, + 0x8a, 0x19, 0x8a, 0x17, 0x8a, 0x17, 0x8a, 0x16, + 0x8a, 0x15, 0x8a, 0x14, 0x8a, 0x13, 0x8a, 0x12, + 0x8a, 0x11, 0x8a, 0x10, 0x8a, 0x0d, 0x8a, 0x0c, + 0x8a, 0x0b, 0x8a, 0x0a, 0x8a, 0x09, 0x8a, 0x08, + 0xf4, 0xeb, 0xee, 0xfd, 0x76, 0xf8, 0x2a, 0x38, + 0x00, 0x00, 0x76, 0xf8, 0x2a, 0x5a, 0x00, 0x00, + 0xe8, 0x01, 0x4e, 0x00, 0xfb, 0x80, 0x17, 0xd6, + 0xf4, 0x95, 0xe8, 0x01, 0x80, 0xf8, 0x2a, 0x5b, + 0x76, 0x00, 0x2a, 0x8f, 0xf9, 0x80, 0x16, 0xaa, + 0x10, 0xf8, 0x2a, 0x5b, 0xf9, 0x80, 0x17, 0x5c, + 0x10, 0xf8, 0x2a, 0x5b, 0xf9, 0x80, 0x17, 0x6f, + 0xfb, 0x80, 0x16, 0x66, 0xf4, 0x95, 0xe8, 0x1a, + 0xfb, 0x80, 0x16, 0x87, 0xf4, 0x95, 0xe8, 0x1a, + 0xfb, 0x80, 0x16, 0x66, 0xf4, 0x95, 0xe8, 0x1b, + 0xfb, 0x80, 0x16, 0x87, 0xf4, 0x95, 0xe8, 0x1b, + 0xee, 0x03, 0xfc, 0x00, 0x4a, 0x11, 0xf4, 0x95, + 0x13, 0x02, 0x88, 0x11, 0xe8, 0x00, 0xf8, 0x4d, + 0x06, 0x6a, 0xf3, 0x10, 0x00, 0x01, 0x89, 0x1a, + 0xf4, 0x95, 0xf0, 0x72, 0x06, 0x69, 0x1c, 0x91, + 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, 0x88, 0x11, + 0x12, 0x03, 0x11, 0x02, 0xf8, 0x45, 0x06, 0x79, + 0xf0, 0x10, 0x00, 0x01, 0x88, 0x1a, 0xf4, 0x95, + 0xf0, 0x72, 0x06, 0x78, 0x81, 0x91, 0x8a, 0x11, + 0xfc, 0x00, 0x4a, 0x11, 0xf4, 0x95, 0x71, 0x02, + 0x00, 0x11, 0x11, 0x03, 0x61, 0xf8, 0x00, 0x11, + 0x00, 0x01, 0xf8, 0x30, 0x06, 0x91, 0xf6, 0xb8, + 0x6f, 0xf8, 0x00, 0x11, 0x0c, 0x1f, 0x88, 0x11, + 0xf3, 0xe8, 0xe8, 0xff, 0x18, 0x81, 0xf1, 0xa0, + 0x81, 0x81, 0xf0, 0x73, 0x06, 0x9d, 0xf6, 0xb8, + 0x6f, 0xf8, 0x00, 0x11, 0x0c, 0x1f, 0x88, 0x11, + 0xf3, 0x30, 0x00, 0xff, 0xf0, 0x20, 0xff, 0x00, + 0x18, 0x81, 0xf1, 0xa0, 0x81, 0x81, 0x8a, 0x11, + 0xfc, 0x00, 0x4a, 0x11, 0xf4, 0x95, 0x11, 0x02, + 0x61, 0xf8, 0x00, 0x0b, 0x00, 0x01, 0xf8, 0x20, + 0x06, 0xb1, 0x49, 0x0b, 0xf6, 0x1f, 0x88, 0x11, + 0xf4, 0x95, 0xf4, 0x95, 0x10, 0x81, 0xf2, 0x73, + 0x06, 0xb8, 0xf0, 0x30, 0x00, 0xff, 0x49, 0x0b, + 0xf6, 0x1f, 0x88, 0x11, 0xf4, 0x95, 0xf4, 0x95, + 0x12, 0x81, 0xf4, 0x78, 0x8a, 0x11, 0xfc, 0x00, + 0x4a, 0x11, 0xf4, 0x95, 0x71, 0x02, 0x00, 0x12, + 0x13, 0x03, 0x88, 0x11, 0xe8, 0x00, 0xf8, 0x4d, + 0x06, 0xcc, 0xf3, 0x10, 0x00, 0x01, 0x89, 0x1a, + 0xf4, 0x95, 0xf0, 0x72, 0x06, 0xcb, 0x11, 0x92, + 0xf2, 0xc0, 0x81, 0x91, 0x8a, 0x11, 0xfc, 0x00, + 0x88, 0x12, 0x12, 0x02, 0x71, 0x01, 0x00, 0x13, + 0xf8, 0x45, 0x06, 0xdb, 0xf0, 0x10, 0x00, 0x01, + 0x88, 0x1a, 0xf4, 0x95, 0xf0, 0x72, 0x06, 0xda, + 0xe5, 0x98, 0xfc, 0x00, 0x4a, 0x11, 0xee, 0xfe, + 0x88, 0x11, 0x11, 0x04, 0x10, 0x06, 0x71, 0x05, + 0x00, 0x12, 0x61, 0xf8, 0x00, 0x12, 0x00, 0x01, + 0xf8, 0x20, 0x06, 0xea, 0xf0, 0x00, 0x00, 0x01, + 0xf6, 0xb8, 0xf0, 0x00, 0x00, 0x01, 0x6f, 0xf8, + 0x00, 0x12, 0x0f, 0x1f, 0x48, 0x08, 0x81, 0x00, + 0xf4, 0x7f, 0x80, 0x01, 0xf2, 0x74, 0x06, 0xba, + 0xf4, 0x95, 0x48, 0x11, 0xee, 0x02, 0x8a, 0x11, + 0xfc, 0x00, 0x4a, 0x11, 0xee, 0xfe, 0x88, 0x12, + 0x11, 0x04, 0x10, 0x06, 0x71, 0x05, 0x00, 0x13, + 0x61, 0xf8, 0x00, 0x13, 0x00, 0x01, 0xf8, 0x20, + 0x07, 0x09, 0xf0, 0x00, 0x00, 0x01, 0xf0, 0x00, + 0x00, 0x01, 0x88, 0x11, 0xf6, 0xb8, 0x6f, 0xf8, + 0x00, 0x13, 0x0f, 0x1f, 0x81, 0x00, 0x48, 0x11, + 0xf4, 0x7f, 0x80, 0x01, 0xf2, 0x74, 0x06, 0xce, + 0xf4, 0x95, 0x48, 0x12, 0x48, 0x11, 0xf0, 0x30, + 0xff, 0xfe, 0xee, 0x02, 0x8a, 0x11, 0xfc, 0x00, + 0x4a, 0x11, 0x4a, 0x16, 0x4a, 0x17, 0xee, 0xfc, + 0xf4, 0x95, 0x80, 0x02, 0x71, 0x08, 0x00, 0x16, + 0x10, 0x09, 0x71, 0x0b, 0x00, 0x17, 0x80, 0x03, + 0x71, 0x0a, 0x00, 0x11, 0x48, 0x17, 0xf8, 0x45, + 0x07, 0x3f, 0x70, 0x00, 0x00, 0x11, 0x10, 0x03, + 0xf0, 0x74, 0x06, 0x9f, 0x80, 0x01, 0x70, 0x00, + 0x00, 0x16, 0x10, 0x02, 0xf0, 0x74, 0x06, 0x7b, + 0x6d, 0x91, 0x6d, 0x96, 0x6c, 0xef, 0xff, 0xff, + 0x07, 0x2f, 0xee, 0x04, 0x8a, 0x17, 0x8a, 0x16, + 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, 0xee, 0xfe, + 0x10, 0xf8, 0x2a, 0xe8, 0x08, 0xf8, 0x2a, 0xe9, + 0xf8, 0x45, 0x07, 0x64, 0x76, 0x00, 0x00, 0x01, + 0x62, 0xf8, 0x2a, 0xe9, 0x00, 0x5e, 0xf2, 0x74, + 0x12, 0x0b, 0xf0, 0x00, 0x30, 0x40, 0x72, 0x11, + 0x2a, 0xe9, 0x77, 0x10, 0x00, 0x0f, 0xf5, 0xa9, + 0xf8, 0x20, 0x07, 0x61, 0x6b, 0xf8, 0x2a, 0xe9, + 0x00, 0x01, 0xf0, 0x73, 0x07, 0x64, 0x76, 0xf8, + 0x2a, 0xe9, 0x00, 0x00, 0xee, 0x02, 0x8a, 0x11, + 0xfc, 0x00, 0x4a, 0x11, 0x88, 0x11, 0xe8, 0x00, + 0x75, 0xf8, 0x00, 0x08, 0x00, 0x08, 0xe8, 0x00, + 0x75, 0xf8, 0x00, 0x08, 0x00, 0x09, 0xf6, 0xb8, + 0xf4, 0x95, 0xf0, 0x20, 0xfc, 0x3f, 0x75, 0xf8, + 0x00, 0x08, 0x00, 0x0d, 0xf0, 0x20, 0x0c, 0x30, + 0x75, 0xf8, 0x00, 0x08, 0x00, 0x0c, 0x76, 0xf8, + 0x2a, 0xe8, 0x00, 0x00, 0x76, 0xf8, 0x2a, 0xe9, + 0x00, 0x00, 0x6c, 0x81, 0x07, 0x92, 0x76, 0xf8, + 0x2a, 0xea, 0x00, 0x00, 0xfb, 0x80, 0x16, 0x76, + 0xf4, 0x95, 0xe8, 0x10, 0xe8, 0x00, 0x75, 0xf8, + 0x00, 0x08, 0x00, 0x00, 0xf0, 0x73, 0x07, 0xa8, + 0x76, 0xf8, 0x2a, 0xea, 0x00, 0x01, 0xfb, 0x80, + 0x16, 0x66, 0xf4, 0x95, 0xe8, 0x10, 0xfb, 0x80, + 0x16, 0x87, 0xf4, 0x95, 0xe8, 0x10, 0xe8, 0x00, + 0x75, 0xf8, 0x00, 0x08, 0x00, 0x00, 0xf6, 0xb8, + 0xf4, 0x95, 0xf0, 0x20, 0xff, 0xff, 0x75, 0xf8, + 0x00, 0x08, 0x00, 0x00, 0x8a, 0x11, 0xfc, 0x00, + 0xf4, 0x95, 0x4a, 0x08, 0x4a, 0x09, 0x4a, 0x0a, + 0x4a, 0x06, 0x4a, 0x07, 0x4a, 0x1d, 0x68, 0xf8, + 0x00, 0x07, 0x7d, 0x3f, 0x69, 0xf8, 0x00, 0x07, + 0x40, 0x00, 0x68, 0xf8, 0x00, 0x1d, 0xff, 0xfc, + 0x10, 0xf8, 0x2a, 0xea, 0xf8, 0x45, 0x07, 0xe1, + 0x10, 0xf8, 0x2a, 0xe8, 0xf0, 0x00, 0x00, 0x01, + 0xf0, 0x30, 0x00, 0x0f, 0x80, 0xf8, 0x2a, 0xe8, + 0x10, 0xf8, 0x2a, 0xe8, 0xf8, 0x44, 0x07, 0xd6, + 0xf6, 0xb8, 0xf4, 0x95, 0xf0, 0x20, 0xfc, 0x3f, + 0x75, 0xf8, 0x00, 0x08, 0x00, 0x0d, 0xf0, 0x20, + 0x0c, 0x30, 0x75, 0xf8, 0x00, 0x08, 0x00, 0x0c, + 0xe8, 0x00, 0x75, 0xf8, 0x00, 0x08, 0x00, 0x00, + 0xf6, 0xb8, 0xf4, 0x95, 0xf0, 0x20, 0xff, 0xff, + 0x75, 0xf8, 0x00, 0x08, 0x00, 0x00, 0x8a, 0x1d, + 0x8a, 0x07, 0x8a, 0x06, 0x8a, 0x0a, 0x8a, 0x09, + 0x8a, 0x08, 0xf4, 0xeb, 0xee, 0xff, 0xf2, 0x74, + 0x07, 0x67, 0xf4, 0x95, 0xe8, 0x01, 0xee, 0x01, + 0xfc, 0x00, 0x4a, 0x07, 0x4a, 0x1d, 0x68, 0xf8, + 0x00, 0x07, 0x7d, 0x3f, 0x69, 0xf8, 0x00, 0x07, + 0x40, 0x00, 0x68, 0xf8, 0x00, 0x1d, 0xff, 0xfc, + 0x8a, 0x1d, 0x8a, 0x07, 0xf4, 0xeb, 0x4a, 0x11, + 0x77, 0x11, 0x00, 0x28, 0x76, 0x81, 0x24, 0x00, + 0xe8, 0x00, 0x75, 0xf8, 0x00, 0x08, 0x00, 0x01, + 0xf2, 0x74, 0x07, 0x67, 0xf4, 0x95, 0xe8, 0x00, + 0x77, 0x11, 0x00, 0x1d, 0x68, 0x81, 0x00, 0x7f, + 0xf6, 0xb8, 0xf4, 0x95, 0xf0, 0x20, 0xff, 0x80, + 0x77, 0x11, 0x00, 0x1d, 0xf0, 0x30, 0x01, 0x00, + 0x1a, 0x81, 0x80, 0x81, 0xf0, 0x74, 0x0a, 0x33, + 0xf0, 0x74, 0x11, 0xac, 0xf9, 0x80, 0x13, 0x25, + 0xf9, 0x80, 0x16, 0x53, 0xf9, 0x80, 0x17, 0x82, + 0xf0, 0x74, 0x06, 0x2f, 0xf9, 0x80, 0x14, 0xb2, + 0xf9, 0x80, 0x19, 0x10, 0xf0, 0x74, 0x0d, 0xe3, + 0xf0, 0x74, 0x07, 0xe8, 0xf0, 0x74, 0x02, 0x36, + 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, 0x60, 0xf8, + 0x27, 0x7b, 0xff, 0xff, 0xf8, 0x30, 0x08, 0x39, + 0x71, 0xf8, 0x27, 0x7b, 0x27, 0x79, 0x60, 0xf8, + 0x27, 0x79, 0xff, 0xff, 0xf8, 0x30, 0x08, 0xb2, + 0x10, 0xf8, 0x29, 0x86, 0x08, 0xf8, 0x27, 0x79, + 0xf0, 0x30, 0x7f, 0xff, 0x88, 0x11, 0xf4, 0x95, + 0x77, 0x10, 0x40, 0x00, 0xf6, 0xa9, 0xf8, 0x30, + 0x08, 0x58, 0x10, 0xf8, 0x27, 0x79, 0x08, 0xf8, + 0x27, 0x7a, 0xf0, 0x30, 0x7f, 0xff, 0x88, 0x11, + 0xf4, 0x95, 0x77, 0x10, 0x40, 0x00, 0xf6, 0xa9, + 0xf8, 0x20, 0x08, 0x63, 0x76, 0xf8, 0x27, 0x79, + 0xff, 0xff, 0x76, 0xf8, 0x27, 0x7b, 0xff, 0xff, + 0xf7, 0xb8, 0xf2, 0x73, 0x08, 0xd9, 0xf0, 0x20, + 0xff, 0xff, 0xf6, 0xb8, 0x56, 0xf8, 0x27, 0x74, + 0xf0, 0xf9, 0x88, 0x11, 0x56, 0xf8, 0x27, 0x72, + 0xf0, 0xf9, 0x88, 0x12, 0xf4, 0x95, 0xf4, 0x95, + 0xe7, 0x20, 0xf4, 0xa9, 0xf8, 0x30, 0x08, 0x8f, + 0xf1, 0x20, 0x27, 0x7c, 0x48, 0x11, 0xf6, 0x00, + 0x88, 0x13, 0xf4, 0x95, 0xf4, 0x95, 0x10, 0x83, + 0x08, 0xf8, 0x27, 0x79, 0xf0, 0x30, 0x7f, 0xff, + 0x88, 0x13, 0xf4, 0x95, 0x77, 0x10, 0x40, 0x00, + 0xf5, 0xab, 0xf8, 0x30, 0x08, 0x8f, 0x6d, 0x91, + 0x48, 0x11, 0xf0, 0x30, 0x01, 0xff, 0x88, 0x11, + 0xf4, 0x95, 0xe7, 0x20, 0xf7, 0xa9, 0xf8, 0x30, + 0x08, 0x74, 0x6d, 0x89, 0x48, 0x11, 0xf0, 0x30, + 0x01, 0xff, 0xf0, 0xe7, 0xf4, 0x95, 0x48, 0x08, + 0x4e, 0xf8, 0x27, 0x74, 0x48, 0x08, 0xf1, 0xf9, + 0x89, 0x11, 0xf4, 0x95, 0xf4, 0x95, 0x71, 0xe1, + 0x27, 0x7c, 0x27, 0x7a, 0x60, 0xf8, 0x27, 0x7b, + 0xff, 0xff, 0xf8, 0x30, 0x08, 0xab, 0x48, 0x08, + 0x4e, 0xf8, 0x27, 0x72, 0x76, 0xf8, 0x27, 0x7b, + 0xff, 0xff, 0x76, 0xf8, 0x27, 0x79, 0xff, 0xff, + 0xf2, 0x73, 0x08, 0xd9, 0xf4, 0x95, 0xe8, 0x00, + 0x44, 0xf8, 0x27, 0x73, 0x40, 0xf8, 0x27, 0x75, + 0x82, 0xf8, 0x00, 0x11, 0xf4, 0x95, 0x77, 0x10, + 0x80, 0x00, 0xf6, 0xa9, 0xf8, 0x20, 0x08, 0xd8, + 0xf6, 0xb8, 0x10, 0xf8, 0x27, 0x73, 0xf0, 0x00, + 0x80, 0x00, 0x48, 0x08, 0x4e, 0xf8, 0x27, 0x74, + 0x48, 0x08, 0xf0, 0xf9, 0x88, 0x11, 0xf4, 0x95, + 0xf4, 0x95, 0x71, 0xe1, 0x27, 0x7c, 0x27, 0x7a, + 0xf7, 0xb8, 0x57, 0xf8, 0x27, 0x74, 0xf0, 0x62, + 0xff, 0xff, 0xf0, 0x40, 0xff, 0x80, 0xf2, 0x80, + 0x4e, 0xf8, 0x27, 0x74, 0xe8, 0x00, 0x8a, 0x11, + 0xfc, 0x00, 0x4a, 0x11, 0x4a, 0x16, 0xee, 0xfb, + 0x11, 0xf8, 0x27, 0x71, 0x09, 0xf8, 0x27, 0x73, + 0x89, 0x11, 0x88, 0x10, 0xf4, 0x95, 0xf4, 0x95, + 0xf6, 0xa9, 0xf8, 0x20, 0x08, 0xed, 0xf2, 0x73, + 0x09, 0x0e, 0xf4, 0x95, 0xe8, 0x00, 0xf6, 0x20, + 0x76, 0x00, 0x00, 0x41, 0xf0, 0x74, 0x12, 0xee, + 0x88, 0x16, 0xf4, 0x95, 0xf7, 0xb8, 0x6d, 0x96, + 0x10, 0xf8, 0x00, 0x16, 0xf8, 0x47, 0x09, 0x0a, + 0xe7, 0x61, 0x76, 0x00, 0x00, 0x00, 0x76, 0x01, + 0x00, 0x80, 0x76, 0x02, 0x00, 0xff, 0x76, 0x03, + 0x00, 0x00, 0xf2, 0x74, 0x0c, 0xb9, 0xf4, 0x95, + 0xe8, 0x00, 0x6c, 0xe9, 0xff, 0xff, 0x08, 0xfb, + 0x73, 0x16, 0x00, 0x0e, 0xf0, 0x66, 0x00, 0x41, + 0xee, 0x05, 0x8a, 0x16, 0x8a, 0x11, 0xfc, 0x00, + 0x4a, 0x11, 0xf4, 0x95, 0x71, 0x02, 0x00, 0x13, + 0xf6, 0xb8, 0x77, 0x11, 0x7f, 0xff, 0x57, 0xf8, + 0x27, 0x72, 0x48, 0x11, 0xf2, 0x80, 0xf0, 0x00, + 0x80, 0x00, 0x88, 0x11, 0xf6, 0x40, 0xf0, 0xe0, + 0xf1, 0xf1, 0xe8, 0x01, 0xf2, 0x80, 0x80, 0xf8, + 0x27, 0x78, 0x77, 0x12, 0x80, 0x00, 0x57, 0xf8, + 0x27, 0x72, 0x48, 0x12, 0xf2, 0x80, 0x88, 0x12, + 0xf4, 0x95, 0xf4, 0x95, 0x6c, 0x82, 0x09, 0x38, + 0xe8, 0x00, 0x75, 0xf8, 0x00, 0x08, 0x00, 0x01, + 0xf0, 0x73, 0x09, 0x3d, 0xf0, 0x20, 0x80, 0x01, + 0x75, 0xf8, 0x00, 0x08, 0x00, 0x01, 0x70, 0x81, + 0x00, 0x13, 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, + 0xf0, 0x30, 0x7f, 0xff, 0x11, 0xf8, 0x29, 0x86, + 0xf5, 0x20, 0xf3, 0x30, 0x7f, 0xff, 0x89, 0x11, + 0xf4, 0x95, 0x77, 0x10, 0x40, 0x00, 0xf6, 0xa9, + 0xf8, 0x20, 0x09, 0x54, 0xf2, 0x73, 0x09, 0x67, + 0xf4, 0x95, 0xe8, 0x02, 0x6f, 0xf8, 0x27, 0x7a, + 0x0d, 0x20, 0xf3, 0x30, 0x7f, 0xff, 0x89, 0x11, + 0xf4, 0x95, 0x77, 0x10, 0x40, 0x00, 0xf6, 0xa9, + 0xf8, 0x20, 0x09, 0x64, 0xf2, 0x73, 0x09, 0x67, + 0xf4, 0x95, 0xe8, 0x01, 0x80, 0xf8, 0x27, 0x7b, + 0xe8, 0x00, 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, + 0x11, 0xf8, 0x29, 0x86, 0xf5, 0x20, 0xf3, 0x30, + 0x7f, 0xff, 0x89, 0x11, 0xf4, 0x95, 0x77, 0x10, + 0x40, 0x00, 0xf6, 0xa9, 0xf8, 0x20, 0x09, 0x7a, + 0xf2, 0x73, 0x09, 0x8d, 0xf4, 0x95, 0xe8, 0x02, + 0x6f, 0xf8, 0x27, 0x7a, 0x0d, 0x20, 0xf3, 0x30, + 0x7f, 0xff, 0x89, 0x11, 0xf4, 0x95, 0x77, 0x10, + 0x40, 0x00, 0xf6, 0xa9, 0xf8, 0x20, 0x09, 0x8a, + 0xf2, 0x73, 0x09, 0x8d, 0xf4, 0x95, 0xe8, 0x01, + 0x80, 0xf8, 0x27, 0x79, 0xe8, 0x00, 0x8a, 0x11, + 0xfc, 0x00, 0x4a, 0x11, 0xf4, 0x95, 0x71, 0x02, + 0x00, 0x12, 0x88, 0x11, 0xf6, 0xb8, 0x57, 0xf8, + 0x27, 0x72, 0xf0, 0x20, 0x7f, 0xff, 0xf2, 0x80, + 0xf0, 0x00, 0x80, 0x00, 0x80, 0x81, 0x57, 0xf8, + 0x27, 0x72, 0xe8, 0x01, 0xf3, 0xf1, 0xf2, 0x80, + 0x80, 0xf8, 0x27, 0x78, 0x77, 0x11, 0x80, 0x00, + 0x48, 0x11, 0x57, 0xf8, 0x27, 0x72, 0xf2, 0x80, + 0x88, 0x11, 0xf4, 0x95, 0xf4, 0x95, 0x6c, 0x81, + 0x09, 0xb5, 0xe8, 0x00, 0x75, 0xf8, 0x00, 0x08, + 0x00, 0x01, 0xf0, 0x73, 0x09, 0xba, 0xf0, 0x20, + 0x80, 0x01, 0x75, 0xf8, 0x00, 0x08, 0x00, 0x01, + 0x45, 0xf8, 0x27, 0x71, 0x43, 0xf8, 0x27, 0x73, + 0x83, 0xf8, 0x00, 0x11, 0xf4, 0x95, 0xe7, 0x20, + 0xf6, 0xa9, 0xf8, 0x30, 0x09, 0xc9, 0xf2, 0x73, + 0x09, 0xe4, 0x77, 0x12, 0x00, 0x00, 0x57, 0xf8, + 0x27, 0x72, 0xf0, 0x20, 0x7f, 0xff, 0xf2, 0x80, + 0x49, 0x12, 0xf5, 0x00, 0xf3, 0x00, 0x80, 0x00, + 0x61, 0xf8, 0x00, 0x0b, 0x80, 0x00, 0xf8, 0x30, + 0x09, 0xdc, 0xf1, 0x20, 0x80, 0x00, 0xf5, 0x20, + 0x89, 0x12, 0xf4, 0x95, 0x48, 0x12, 0x6f, 0xf8, + 0x27, 0x73, 0x0d, 0x00, 0xf4, 0x95, 0x49, 0x0b, + 0x4f, 0xf8, 0x27, 0x72, 0x8a, 0x11, 0xfe, 0x00, + 0x48, 0x12, 0xf4, 0x95, 0x4a, 0x11, 0x4a, 0x16, + 0x4a, 0x17, 0xee, 0xfc, 0xf4, 0x95, 0x71, 0x08, + 0x00, 0x16, 0x88, 0x17, 0xf0, 0x74, 0x08, 0x30, + 0x48, 0x18, 0x70, 0x00, 0x00, 0x16, 0xf2, 0x74, + 0x09, 0x8f, 0xf0, 0x00, 0x00, 0x02, 0x88, 0x11, + 0xf4, 0x95, 0xf4, 0x95, 0x6c, 0x81, 0x0a, 0x0a, + 0xf2, 0x74, 0x08, 0xdb, 0xf4, 0x95, 0x48, 0x16, + 0x48, 0x18, 0x70, 0x00, 0x00, 0x16, 0xf2, 0x74, + 0x09, 0x8f, 0xf0, 0x00, 0x00, 0x02, 0x88, 0x11, + 0x10, 0x02, 0x70, 0x01, 0x00, 0x11, 0x80, 0x00, + 0xf2, 0x74, 0x06, 0xce, 0xf4, 0x95, 0x48, 0x17, + 0x49, 0x11, 0x48, 0x17, 0xf6, 0x00, 0x88, 0x17, + 0xe7, 0x60, 0xf5, 0xa9, 0xf8, 0x20, 0x0a, 0x2d, + 0x48, 0x16, 0xf6, 0x20, 0x88, 0x11, 0x48, 0x18, + 0x70, 0x00, 0x00, 0x11, 0xf2, 0x74, 0x09, 0x8f, + 0xf0, 0x00, 0x00, 0x02, 0x88, 0x11, 0x70, 0x01, + 0x00, 0x11, 0x10, 0x02, 0x80, 0x00, 0xf2, 0x74, + 0x06, 0xce, 0xf4, 0x95, 0x48, 0x17, 0xee, 0x04, + 0x48, 0x16, 0x8a, 0x17, 0x8a, 0x16, 0x8a, 0x11, + 0xfc, 0x00, 0xee, 0xfd, 0xe8, 0x00, 0x4e, 0xf8, + 0x27, 0x70, 0xe8, 0x00, 0x4e, 0xf8, 0x27, 0x72, + 0xe8, 0x00, 0x4e, 0xf8, 0x27, 0x74, 0xe8, 0x00, + 0x4e, 0xf8, 0x27, 0x76, 0x76, 0xf8, 0x27, 0x79, + 0xff, 0xff, 0x76, 0xf8, 0x27, 0x7a, 0x00, 0x00, + 0x76, 0xf8, 0x27, 0x7b, 0xff, 0xff, 0x76, 0xf8, + 0x27, 0x78, 0x00, 0x00, 0xe8, 0x00, 0x75, 0xf8, + 0x00, 0x08, 0x00, 0x01, 0x76, 0x00, 0x00, 0x00, + 0x76, 0x01, 0x02, 0x00, 0xf2, 0x74, 0x12, 0xdc, + 0xf0, 0x20, 0x27, 0x7c, 0xee, 0x03, 0xfc, 0x00, + 0x4a, 0x11, 0xee, 0xfc, 0xf4, 0x95, 0x4e, 0x00, + 0x77, 0x12, 0x7f, 0xff, 0xf6, 0xb8, 0x49, 0x12, + 0xf1, 0x80, 0xf3, 0x00, 0x80, 0x00, 0x89, 0x12, + 0xf0, 0xe0, 0xf1, 0xf1, 0x4f, 0x02, 0xe9, 0x01, + 0xf4, 0x95, 0x48, 0x0b, 0xf5, 0x40, 0x56, 0x02, + 0xf1, 0x80, 0x81, 0xf8, 0x27, 0x78, 0x77, 0x11, + 0x80, 0x00, 0x56, 0x00, 0x49, 0x11, 0xf1, 0x80, + 0x89, 0x11, 0xf4, 0x95, 0xf4, 0x95, 0x6c, 0x81, + 0x0a, 0x81, 0xe8, 0x00, 0x75, 0xf8, 0x00, 0x08, + 0x00, 0x01, 0xf0, 0x73, 0x0a, 0x86, 0xf0, 0x20, + 0x80, 0x01, 0x75, 0xf8, 0x00, 0x08, 0x00, 0x01, + 0x10, 0x82, 0xee, 0x04, 0x8a, 0x11, 0xfc, 0x00, + 0x4a, 0x11, 0xee, 0xfe, 0xf4, 0x95, 0x4e, 0x00, + 0x77, 0x11, 0x7f, 0xff, 0xf6, 0xb8, 0x49, 0x11, + 0xf1, 0x80, 0xf3, 0x00, 0x80, 0x00, 0x89, 0x11, + 0xf0, 0xe0, 0xf1, 0xf1, 0xe8, 0x01, 0xf2, 0x80, + 0x80, 0xf8, 0x27, 0x78, 0x56, 0x00, 0xf1, 0x20, + 0x80, 0x00, 0xf1, 0x80, 0xf4, 0x95, 0x49, 0x0b, + 0xf8, 0x4d, 0x0a, 0xab, 0xf0, 0x20, 0x80, 0x01, + 0x75, 0xf8, 0x00, 0x08, 0x00, 0x01, 0xf0, 0x73, + 0x0a, 0xaf, 0xe8, 0x00, 0x75, 0xf8, 0x00, 0x08, + 0x00, 0x01, 0xee, 0x02, 0x48, 0x11, 0x8a, 0x11, + 0xfc, 0x00, 0x4a, 0x11, 0x88, 0x12, 0x13, 0x02, + 0x77, 0x11, 0x00, 0x00, 0xf8, 0x4d, 0x0a, 0xcb, + 0xf3, 0x10, 0x00, 0x01, 0x89, 0x1a, 0xf4, 0x95, + 0xf0, 0x72, 0x0a, 0xca, 0x48, 0x11, 0x1c, 0xf8, + 0x29, 0x7e, 0x88, 0x11, 0x11, 0xf8, 0x29, 0x7e, + 0xf2, 0x00, 0x00, 0x01, 0x80, 0xf8, 0x29, 0x7e, + 0x81, 0x92, 0x48, 0x11, 0x8a, 0x11, 0xfc, 0x00, + 0x4a, 0x11, 0xf4, 0x95, 0x71, 0x02, 0x00, 0x11, + 0x88, 0x12, 0xf6, 0xb8, 0xf0, 0x20, 0x7f, 0xff, + 0x57, 0xf8, 0x27, 0x70, 0xf2, 0x80, 0xf0, 0x00, + 0x80, 0x00, 0x80, 0x82, 0x57, 0xf8, 0x27, 0x70, + 0xe8, 0x01, 0xf3, 0xf1, 0xf2, 0x80, 0x80, 0xf8, + 0x27, 0x78, 0x77, 0x12, 0x80, 0x00, 0x48, 0x12, + 0x57, 0xf8, 0x27, 0x70, 0xf2, 0x80, 0x88, 0x12, + 0xf4, 0x95, 0xf4, 0x95, 0x6c, 0x82, 0x0a, 0xf4, + 0xe8, 0x00, 0x75, 0xf8, 0x00, 0x08, 0x00, 0x01, + 0xf0, 0x73, 0x0a, 0xf9, 0xf0, 0x20, 0x80, 0x01, + 0x75, 0xf8, 0x00, 0x08, 0x00, 0x01, 0x45, 0xf8, + 0x27, 0x75, 0xe7, 0x10, 0x43, 0xf8, 0x27, 0x71, + 0x83, 0xf8, 0x00, 0x12, 0x6d, 0xe8, 0x00, 0x04, + 0x6d, 0x8a, 0xf6, 0xaa, 0xf8, 0x30, 0x0b, 0x0a, + 0xf2, 0x73, 0x0b, 0x25, 0x77, 0x11, 0x00, 0x00, + 0x57, 0xf8, 0x27, 0x70, 0xf0, 0x20, 0x7f, 0xff, + 0xf2, 0x80, 0x49, 0x11, 0xf5, 0x00, 0xf3, 0x00, + 0x80, 0x00, 0x61, 0xf8, 0x00, 0x0b, 0x80, 0x00, + 0xf8, 0x30, 0x0b, 0x1d, 0xf1, 0x20, 0x80, 0x00, + 0xf5, 0x20, 0x89, 0x11, 0xf4, 0x95, 0x48, 0x11, + 0x6f, 0xf8, 0x27, 0x71, 0x0d, 0x00, 0xf4, 0x95, + 0x49, 0x0b, 0x4f, 0xf8, 0x27, 0x70, 0x48, 0x11, + 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, 0x4a, 0x16, + 0x4a, 0x17, 0xee, 0xf0, 0x88, 0x17, 0x10, 0x17, + 0x80, 0x05, 0x10, 0x16, 0x80, 0x06, 0x10, 0x15, + 0x80, 0x07, 0x71, 0x14, 0x00, 0x11, 0x10, 0x05, + 0xf0, 0x30, 0x00, 0x01, 0x88, 0x10, 0x10, 0x06, + 0xf0, 0x30, 0x00, 0x01, 0x80, 0x08, 0x49, 0x11, + 0x10, 0x05, 0xf6, 0x01, 0x80, 0x09, 0x10, 0x06, + 0x61, 0xf8, 0x00, 0x08, 0x00, 0x01, 0xf8, 0x20, + 0x0b, 0x4b, 0x10, 0x09, 0xf0, 0x00, 0x00, 0x01, + 0x80, 0x09, 0x71, 0x08, 0x00, 0x12, 0xf4, 0xaa, + 0xf8, 0x30, 0x0b, 0x54, 0x10, 0x09, 0xf0, 0x00, + 0x00, 0x01, 0x80, 0x09, 0x12, 0x09, 0x49, 0x11, + 0xf4, 0x7f, 0x80, 0x09, 0xf6, 0x20, 0x80, 0x0a, + 0x56, 0xf8, 0x27, 0x70, 0x4e, 0x0c, 0x10, 0x09, + 0x80, 0x00, 0x48, 0x18, 0xf2, 0x74, 0x0a, 0xce, + 0xf0, 0x00, 0x00, 0x04, 0x88, 0x16, 0xf4, 0x95, + 0xf4, 0x95, 0x6c, 0x86, 0x0b, 0x6d, 0xf2, 0x73, + 0x0c, 0x59, 0xf4, 0x95, 0xe8, 0x00, 0xf6, 0xb8, + 0xf4, 0x95, 0x56, 0x0c, 0xf0, 0xf9, 0x88, 0x12, + 0xf4, 0x95, 0xf4, 0x95, 0x70, 0xe2, 0x27, 0x7c, + 0x29, 0x86, 0xe8, 0x00, 0x80, 0x0e, 0x48, 0x11, + 0xf8, 0x45, 0x0b, 0xcc, 0x77, 0x10, 0x00, 0x01, + 0xf4, 0xa9, 0xf8, 0x30, 0x0b, 0x89, 0x6c, 0xe1, + 0xff, 0xfd, 0x0b, 0x8b, 0x10, 0xe7, 0x00, 0x02, + 0x80, 0x0e, 0xf0, 0x73, 0x0b, 0x8b, 0x10, 0x87, + 0x80, 0x0e, 0xe7, 0x10, 0xf5, 0xae, 0xf8, 0x20, + 0x0b, 0xb2, 0x70, 0x00, 0x00, 0x17, 0x70, 0x01, + 0x00, 0x16, 0x10, 0x04, 0xf0, 0x74, 0x06, 0xce, + 0x48, 0x17, 0x49, 0x16, 0xf6, 0x00, 0x88, 0x17, + 0x48, 0x11, 0xf6, 0x20, 0x88, 0x11, 0x10, 0x09, + 0xf6, 0x20, 0x80, 0x00, 0x48, 0x18, 0xf2, 0x74, + 0x0a, 0xce, 0xf0, 0x00, 0x00, 0x04, 0x88, 0x16, + 0x10, 0x04, 0x70, 0x00, 0x00, 0x17, 0x70, 0x01, + 0x00, 0x11, 0xf0, 0x74, 0x06, 0xce, 0x48, 0x11, + 0x00, 0x04, 0x80, 0x04, 0xf0, 0x73, 0x0b, 0xbc, + 0x70, 0x00, 0x00, 0x17, 0x70, 0x01, 0x00, 0x11, + 0x10, 0x04, 0xf0, 0x74, 0x06, 0xce, 0x48, 0x11, + 0x00, 0x04, 0x80, 0x04, 0x49, 0x11, 0x48, 0x16, + 0xf6, 0x20, 0x88, 0x16, 0xf4, 0x95, 0xf4, 0x95, + 0x6c, 0x86, 0x0b, 0xcc, 0x10, 0x0a, 0x80, 0x00, + 0x48, 0x18, 0xf2, 0x74, 0x0a, 0xce, 0xf0, 0x00, + 0x00, 0x04, 0x88, 0x16, 0x12, 0x0a, 0xf8, 0x45, + 0x0c, 0x33, 0x71, 0x0a, 0x00, 0x10, 0xf4, 0xae, + 0xf8, 0x30, 0x0c, 0x1c, 0x48, 0x16, 0xf0, 0xe1, + 0x88, 0x11, 0x12, 0x08, 0xf8, 0x45, 0x0b, 0xdb, + 0x6d, 0x89, 0x12, 0x07, 0xf8, 0x45, 0x0b, 0xe9, + 0x10, 0x07, 0x80, 0x00, 0x70, 0x02, 0x00, 0x11, + 0x10, 0x06, 0x80, 0x01, 0x10, 0x04, 0xf0, 0x74, + 0x06, 0xdc, 0xf0, 0x73, 0x0b, 0xef, 0x48, 0x11, + 0x6f, 0x00, 0x0c, 0x9f, 0x10, 0x04, 0xf0, 0x74, + 0x0a, 0xb3, 0x11, 0x0e, 0xf1, 0xc0, 0x81, 0x0e, + 0x10, 0x06, 0x49, 0x11, 0xf6, 0x00, 0x80, 0x06, + 0x10, 0x05, 0xf6, 0x20, 0x88, 0x11, 0xf0, 0x00, + 0x00, 0x01, 0x48, 0x08, 0x6f, 0x00, 0x0c, 0x9f, + 0x48, 0x18, 0xf2, 0x74, 0x0a, 0xce, 0xf0, 0x00, + 0x00, 0x04, 0x12, 0x07, 0xf8, 0x45, 0x0c, 0x11, + 0x10, 0x07, 0x80, 0x00, 0x70, 0x02, 0x00, 0x11, + 0x10, 0x06, 0x80, 0x01, 0x10, 0x04, 0xf0, 0x74, + 0x06, 0xdc, 0xf0, 0x73, 0x0c, 0x17, 0x48, 0x11, + 0x6f, 0x00, 0x0c, 0x9f, 0x10, 0x04, 0xf0, 0x74, + 0x0a, 0xb3, 0x11, 0x0e, 0xf1, 0xc0, 0x81, 0x0e, + 0xf0, 0x73, 0x0c, 0x33, 0x12, 0x07, 0xf8, 0x45, + 0x0c, 0x2a, 0x10, 0x07, 0x80, 0x00, 0x10, 0x06, + 0x80, 0x01, 0x10, 0x05, 0x80, 0x02, 0x10, 0x04, + 0xf0, 0x74, 0x06, 0xdc, 0xf0, 0x73, 0x0c, 0x30, + 0x12, 0x05, 0x6f, 0x00, 0x0c, 0x9f, 0x10, 0x04, + 0xf0, 0x74, 0x0a, 0xb3, 0x11, 0x0e, 0xf1, 0xc0, + 0x81, 0x0e, 0x76, 0x00, 0x00, 0x01, 0x48, 0x18, + 0xf2, 0x74, 0x0a, 0xce, 0xf0, 0x00, 0x00, 0x04, + 0x71, 0x04, 0x00, 0x11, 0x70, 0x81, 0x29, 0x86, + 0x10, 0x0e, 0x1c, 0xf8, 0x29, 0x86, 0x80, 0x0e, + 0x76, 0x00, 0x00, 0x01, 0x48, 0x18, 0xf2, 0x74, + 0x0a, 0xce, 0xf0, 0x00, 0x00, 0x04, 0x10, 0x0e, + 0x71, 0x04, 0x00, 0x11, 0x80, 0x81, 0x10, 0xf8, + 0x29, 0x86, 0xf0, 0x00, 0x00, 0x01, 0xf0, 0x30, + 0x7f, 0xff, 0x80, 0xf8, 0x29, 0x86, 0x10, 0x09, + 0xf0, 0x00, 0x00, 0x02, 0x80, 0x09, 0xee, 0x10, + 0x8a, 0x17, 0x8a, 0x16, 0x8a, 0x11, 0xfc, 0x00, + 0x10, 0xf8, 0x27, 0x75, 0x08, 0xf8, 0x27, 0x71, + 0xf0, 0x10, 0x00, 0x01, 0x48, 0x08, 0xfc, 0x00, + 0x4a, 0x11, 0x4a, 0x16, 0xee, 0xff, 0xf4, 0x95, + 0x71, 0x04, 0x00, 0x16, 0xf0, 0x00, 0x00, 0x01, + 0x48, 0x08, 0x4e, 0xf8, 0x29, 0x7c, 0x6d, 0xee, + 0xff, 0xfd, 0x48, 0x16, 0xf8, 0x45, 0x0c, 0x99, + 0x56, 0xf8, 0x29, 0x7c, 0xf0, 0x74, 0x0a, 0x5a, + 0x88, 0x11, 0x10, 0xf8, 0x29, 0x7d, 0xf0, 0x00, + 0x00, 0x01, 0x48, 0x08, 0x4e, 0xf8, 0x29, 0x7c, + 0x10, 0xf8, 0x29, 0x82, 0xf0, 0x00, 0x00, 0x01, + 0x88, 0x10, 0xf4, 0x95, 0xf4, 0x95, 0xf4, 0xa9, + 0xfa, 0x30, 0x0c, 0x96, 0x80, 0xf8, 0x29, 0x82, + 0x56, 0xf8, 0x29, 0x80, 0xf0, 0x00, 0x00, 0x01, + 0x4e, 0xf8, 0x29, 0x80, 0x73, 0x11, 0x29, 0x82, + 0x6c, 0xee, 0xff, 0xff, 0x0c, 0x76, 0xee, 0x01, + 0x8a, 0x16, 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, + 0x76, 0xf8, 0x29, 0x84, 0x00, 0x00, 0x76, 0xf8, + 0x29, 0x85, 0x00, 0x01, 0xe8, 0x00, 0x4e, 0xf8, + 0x2a, 0x0c, 0x76, 0xf8, 0x29, 0x86, 0x00, 0x00, + 0x76, 0xf8, 0x29, 0x87, 0x00, 0x00, 0x77, 0x11, + 0x29, 0x88, 0x76, 0x81, 0xaa, 0xaa, 0x76, 0xe1, + 0x00, 0x01, 0xaa, 0xaa, 0x76, 0xe1, 0x00, 0x02, + 0x00, 0x00, 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, + 0xee, 0xfc, 0xf4, 0x95, 0x71, 0x06, 0x00, 0x14, + 0x71, 0x07, 0x00, 0x13, 0x71, 0x08, 0x00, 0x12, + 0x71, 0x09, 0x00, 0x15, 0x77, 0x10, 0x00, 0xff, + 0xf4, 0xaa, 0xf8, 0x30, 0x0d, 0x44, 0x49, 0x13, + 0x53, 0xf8, 0x2a, 0x0c, 0x4f, 0xf8, 0x2a, 0x0c, + 0x73, 0x12, 0x00, 0x0e, 0xf1, 0x66, 0x00, 0x0d, + 0x89, 0x11, 0xf4, 0x95, 0x77, 0x10, 0x00, 0x01, + 0x71, 0xe1, 0x24, 0x00, 0x00, 0x11, 0xf4, 0xa9, + 0xf8, 0x30, 0x0d, 0x17, 0x77, 0x10, 0x00, 0x02, + 0xf4, 0xa9, 0xf8, 0x30, 0x0c, 0xec, 0x77, 0x11, + 0x29, 0x8a, 0x76, 0x81, 0x00, 0x00, 0xe8, 0x00, + 0x77, 0x14, 0x00, 0x00, 0x77, 0x13, 0x00, 0x00, + 0xf0, 0x73, 0x0d, 0x48, 0x6c, 0x83, 0x0c, 0xfa, + 0x77, 0x11, 0x29, 0x8a, 0x48, 0x12, 0xf0, 0xe8, + 0xf0, 0x40, 0x80, 0x00, 0x80, 0x81, 0xe8, 0x00, + 0x77, 0x14, 0x00, 0x00, 0xf0, 0x73, 0x0d, 0x48, + 0x49, 0x13, 0xf3, 0x40, 0x80, 0x00, 0x81, 0xf8, + 0x29, 0x8a, 0x61, 0xf8, 0x00, 0x15, 0x00, 0x01, + 0xf8, 0x20, 0x0d, 0x07, 0x69, 0xf8, 0x29, 0x8a, + 0x40, 0x00, 0x61, 0xf8, 0x00, 0x14, 0x00, 0x01, + 0xf8, 0x20, 0x0d, 0x0f, 0x69, 0xf8, 0x29, 0x8a, + 0x20, 0x00, 0x77, 0x11, 0x29, 0x8a, 0x49, 0x12, + 0xf3, 0xe8, 0x1b, 0x81, 0x81, 0x81, 0xf0, 0x73, + 0x0d, 0x48, 0x11, 0xf8, 0x29, 0x84, 0xf8, 0x4c, + 0x0d, 0x37, 0x77, 0x11, 0x29, 0x88, 0x76, 0x81, + 0xaa, 0xaa, 0x11, 0xf8, 0x29, 0x85, 0xf3, 0x10, + 0x00, 0x01, 0xf3, 0x40, 0xaa, 0x00, 0x81, 0xe1, + 0x00, 0x01, 0x76, 0x00, 0x00, 0x02, 0x80, 0x01, + 0x70, 0x02, 0x00, 0x14, 0x70, 0x03, 0x00, 0x13, + 0xf2, 0x74, 0x0b, 0x28, 0xf4, 0x95, 0x48, 0x11, + 0x71, 0xf8, 0x29, 0x85, 0x29, 0x84, 0xf0, 0x73, + 0x0d, 0x73, 0x76, 0x00, 0x00, 0x00, 0x80, 0x01, + 0x76, 0x02, 0x00, 0x00, 0x70, 0x03, 0x00, 0x13, + 0xf2, 0x74, 0x0b, 0x28, 0xf4, 0x95, 0xe8, 0x00, + 0xf0, 0x73, 0x0d, 0x73, 0x77, 0x11, 0x29, 0x8a, + 0x70, 0x81, 0x00, 0x13, 0x11, 0xf8, 0x29, 0x84, + 0xf8, 0x4c, 0x0d, 0x68, 0x77, 0x11, 0x29, 0x88, + 0x76, 0x81, 0xaa, 0xaa, 0x11, 0xf8, 0x29, 0x85, + 0xf3, 0x10, 0x00, 0x01, 0xf3, 0x40, 0xaa, 0x00, + 0x81, 0xe1, 0x00, 0x01, 0x76, 0x00, 0x00, 0x03, + 0x80, 0x01, 0x70, 0x02, 0x00, 0x14, 0x70, 0x03, + 0x00, 0x13, 0xf2, 0x74, 0x0b, 0x28, 0xf4, 0x95, + 0x48, 0x11, 0x71, 0xf8, 0x29, 0x85, 0x29, 0x84, + 0xf0, 0x73, 0x0d, 0x73, 0x76, 0x00, 0x00, 0x01, + 0x80, 0x01, 0x70, 0x02, 0x00, 0x14, 0x70, 0x03, + 0x00, 0x13, 0xf2, 0x74, 0x0b, 0x28, 0xf4, 0x95, + 0x48, 0x11, 0x6b, 0xf8, 0x29, 0x84, 0xff, 0xff, + 0xee, 0x04, 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, + 0xf5, 0x40, 0xf4, 0x95, 0x48, 0x0b, 0xf4, 0x78, + 0x88, 0x11, 0xf4, 0x95, 0xf4, 0x95, 0x6c, 0xe1, + 0xff, 0xb9, 0x0d, 0x88, 0xf2, 0x73, 0x0d, 0xa5, + 0xf4, 0x95, 0xe8, 0x60, 0xf2, 0x00, 0x00, 0x06, + 0x61, 0xf8, 0x00, 0x11, 0x00, 0x20, 0xf8, 0x30, + 0x0d, 0x98, 0x61, 0xf8, 0x00, 0x0b, 0x00, 0x01, + 0xf8, 0x20, 0x0d, 0xa3, 0xf2, 0x00, 0x00, 0x07, + 0xf0, 0x73, 0x0d, 0xa3, 0x61, 0xf8, 0x00, 0x0b, + 0x00, 0x01, 0xf8, 0x20, 0x0d, 0xa1, 0xf2, 0x73, + 0x0d, 0xa3, 0xf0, 0x00, 0x00, 0x01, 0xf0, 0x00, + 0x00, 0x02, 0x48, 0x08, 0xf4, 0x7f, 0x8a, 0x11, + 0xfc, 0x00, 0xee, 0xff, 0xf0, 0x74, 0x07, 0xfd, + 0xf0, 0x74, 0x07, 0x44, 0xf0, 0x74, 0x0d, 0xb4, + 0xf0, 0x74, 0x02, 0x05, 0xf0, 0x74, 0x04, 0x60, + 0xf0, 0x73, 0x0d, 0xaa, 0xee, 0xfd, 0x10, 0xf8, + 0x2a, 0xa3, 0xf8, 0x44, 0x0d, 0xcb, 0x10, 0xf8, + 0x2a, 0xa4, 0xf8, 0x45, 0x0d, 0xd7, 0x76, 0x00, + 0x02, 0x00, 0xf2, 0x74, 0x09, 0xe8, 0xf0, 0x20, + 0x22, 0x00, 0x76, 0xf8, 0x2a, 0xa4, 0x00, 0x00, + 0x76, 0xf8, 0x2a, 0xa7, 0x00, 0x00, 0xf0, 0x73, + 0x0d, 0xd7, 0x76, 0x00, 0x02, 0x00, 0xf2, 0x74, + 0x09, 0xe8, 0xf0, 0x20, 0x20, 0x00, 0x76, 0xf8, + 0x2a, 0xa3, 0x00, 0x00, 0x76, 0xf8, 0x2a, 0xa7, + 0x00, 0x01, 0xf0, 0x74, 0x0c, 0x5e, 0xf0, 0xe0, + 0xf0, 0x10, 0x3a, 0x98, 0xf8, 0x47, 0x0d, 0xe1, + 0x76, 0xf8, 0x27, 0x6e, 0x00, 0x00, 0xee, 0x03, + 0xfc, 0x00, 0x4a, 0x11, 0xee, 0xfe, 0x77, 0x11, + 0x20, 0x00, 0x76, 0x00, 0xaa, 0xaa, 0x76, 0x01, + 0x02, 0x00, 0xf2, 0x74, 0x06, 0x6c, 0xf4, 0x95, + 0x48, 0x11, 0x76, 0x00, 0x55, 0x55, 0x76, 0x01, + 0x02, 0x00, 0x48, 0x11, 0xf2, 0x74, 0x06, 0x6c, + 0xf0, 0x00, 0x02, 0x00, 0x76, 0xf8, 0x2a, 0xa3, + 0x00, 0x00, 0x76, 0xf8, 0x2a, 0xa4, 0x00, 0x00, + 0xe8, 0x00, 0x4e, 0x00, 0xfb, 0x80, 0x15, 0x3e, + 0xf4, 0x95, 0xe8, 0x04, 0x80, 0xf8, 0x2a, 0xa5, + 0x76, 0x00, 0x2a, 0xa8, 0xf9, 0x80, 0x14, 0x87, + 0x76, 0x00, 0x2a, 0xad, 0xfb, 0x80, 0x13, 0x62, + 0xf4, 0x95, 0xe8, 0x02, 0x10, 0xf8, 0x2a, 0xa5, + 0xf9, 0x80, 0x14, 0x63, 0xfb, 0x80, 0x16, 0x66, + 0xf4, 0x95, 0xe8, 0x1c, 0xfb, 0x80, 0x16, 0x87, + 0xf4, 0x95, 0xe8, 0x1c, 0xe8, 0x01, 0x4e, 0x00, + 0xfb, 0x80, 0x17, 0xd6, 0xf4, 0x95, 0xe8, 0x00, + 0x80, 0xf8, 0x2a, 0xa6, 0x76, 0x00, 0x2a, 0xb7, + 0xf9, 0x80, 0x16, 0xaa, 0x10, 0xf8, 0x2a, 0xa6, + 0xf9, 0x80, 0x17, 0x5c, 0x10, 0xf8, 0x2a, 0xa6, + 0xf9, 0x80, 0x17, 0x6f, 0xee, 0x02, 0x8a, 0x11, + 0xfc, 0x00, 0xf4, 0x95, 0x4a, 0x08, 0x4a, 0x09, + 0x4a, 0x0a, 0x4a, 0x07, 0x4a, 0x1d, 0x68, 0xf8, + 0x00, 0x07, 0x7d, 0x3f, 0x69, 0xf8, 0x00, 0x07, + 0x40, 0x00, 0x68, 0xf8, 0x00, 0x1d, 0xff, 0xfc, + 0x10, 0xf8, 0x2a, 0xa7, 0xf8, 0x44, 0x0e, 0x4b, + 0x76, 0xf8, 0x2a, 0xa3, 0x00, 0x01, 0xf0, 0x73, + 0x0e, 0x4e, 0x76, 0xf8, 0x2a, 0xa4, 0x00, 0x01, + 0x8a, 0x1d, 0x8a, 0x07, 0x8a, 0x0a, 0x8a, 0x09, + 0x8a, 0x08, 0xf4, 0xeb, 0x4a, 0x11, 0x4a, 0x16, + 0x4a, 0x17, 0xee, 0xfe, 0x88, 0x0e, 0x71, 0x08, + 0x00, 0x16, 0x71, 0x06, 0x00, 0x17, 0x11, 0x07, + 0xf0, 0x66, 0x00, 0x0d, 0xf0, 0x00, 0x25, 0xa0, + 0x88, 0x11, 0x76, 0x01, 0x00, 0x06, 0x81, 0x00, + 0xf2, 0x74, 0x06, 0xce, 0xf0, 0x00, 0x00, 0x01, + 0x76, 0x01, 0x00, 0x06, 0x70, 0x00, 0x00, 0x16, + 0x48, 0x11, 0xf2, 0x74, 0x06, 0xce, 0xf0, 0x00, + 0x00, 0x07, 0x70, 0x81, 0x00, 0x17, 0xee, 0x02, + 0x8a, 0x17, 0x8a, 0x16, 0x8a, 0x11, 0xfc, 0x00, + 0x4a, 0x11, 0x88, 0x0e, 0x71, 0x02, 0x00, 0x12, + 0x11, 0x03, 0xf0, 0x66, 0x00, 0x0d, 0xf0, 0x00, + 0x24, 0x00, 0x88, 0x11, 0xf4, 0x95, 0x70, 0x81, + 0x00, 0x12, 0x6e, 0xe2, 0xff, 0xfe, 0x0e, 0x8d, + 0xf4, 0x95, 0xe8, 0x00, 0xe8, 0x01, 0x80, 0xe1, + 0x00, 0x02, 0x76, 0xe1, 0x00, 0x03, 0x00, 0xff, + 0x76, 0xe1, 0x00, 0x04, 0x00, 0x00, 0x76, 0xe1, + 0x00, 0x0b, 0x00, 0x00, 0x76, 0xe1, 0x00, 0x0c, + 0x00, 0x00, 0x81, 0xe1, 0x00, 0x01, 0x8a, 0x11, + 0xfc, 0x00, 0x4a, 0x11, 0xee, 0xfc, 0x88, 0x0e, + 0xf4, 0x95, 0xf1, 0x66, 0x00, 0x0d, 0xf3, 0x00, + 0x24, 0x00, 0x89, 0x11, 0xf4, 0x95, 0xf4, 0x95, + 0x76, 0xe1, 0x00, 0x0c, 0x00, 0x00, 0x76, 0xe1, + 0x00, 0x0b, 0x00, 0x00, 0x76, 0xe1, 0x00, 0x02, + 0x00, 0x01, 0x76, 0x00, 0x00, 0x00, 0x76, 0x01, + 0x00, 0x00, 0x80, 0x02, 0x76, 0x03, 0x00, 0x00, + 0xf2, 0x74, 0x0c, 0xb9, 0xf4, 0x95, 0xe8, 0x00, + 0xee, 0x04, 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, + 0x88, 0x19, 0xf4, 0x95, 0x73, 0x19, 0x00, 0x0e, + 0xf1, 0x66, 0x00, 0x0d, 0xf2, 0x00, 0x24, 0x00, + 0x77, 0x15, 0x25, 0xa0, 0x77, 0x14, 0x00, 0x00, + 0x77, 0x1a, 0x00, 0x1f, 0xf0, 0x72, 0x0f, 0x14, + 0xf6, 0xb8, 0x49, 0x19, 0x09, 0x85, 0xf8, 0x4c, + 0x0f, 0x13, 0xf1, 0x00, 0x00, 0x05, 0x89, 0x11, + 0x49, 0x15, 0xf3, 0x00, 0x00, 0x01, 0x89, 0x13, + 0x49, 0x15, 0xf3, 0x00, 0x00, 0x07, 0x89, 0x12, + 0x11, 0x93, 0x1d, 0x91, 0x19, 0x92, 0x89, 0x10, + 0xf4, 0x95, 0xf4, 0x95, 0x6c, 0x80, 0x0f, 0x13, + 0x11, 0x93, 0x1d, 0x91, 0x19, 0x92, 0x89, 0x10, + 0xf4, 0x95, 0xf4, 0x95, 0x6c, 0x80, 0x0f, 0x13, + 0x11, 0x93, 0x1d, 0x91, 0x19, 0x92, 0x89, 0x10, + 0xf4, 0x95, 0xf4, 0x95, 0x6c, 0x80, 0x0f, 0x13, + 0x11, 0x93, 0x1d, 0x91, 0x19, 0x92, 0x89, 0x10, + 0xf4, 0x95, 0xf4, 0x95, 0x6c, 0x80, 0x0f, 0x13, + 0x11, 0x93, 0x1d, 0x91, 0x19, 0x92, 0x89, 0x10, + 0xf4, 0x95, 0xf4, 0x95, 0x6c, 0x80, 0x0f, 0x13, + 0x11, 0x93, 0x1d, 0x91, 0x19, 0x92, 0x89, 0x11, + 0xf4, 0x95, 0xf4, 0x95, 0x6c, 0x81, 0x0f, 0x13, + 0x6d, 0x94, 0x6d, 0xed, 0x00, 0x0d, 0x48, 0x14, + 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, 0x4a, 0x16, + 0x4a, 0x17, 0xee, 0xf8, 0x88, 0x17, 0x10, 0x0d, + 0x80, 0x04, 0x10, 0x0c, 0x80, 0x05, 0x71, 0x0e, + 0x00, 0x16, 0x73, 0x17, 0x00, 0x0e, 0xf0, 0x66, + 0x00, 0x0d, 0xf0, 0x00, 0x24, 0x00, 0x88, 0x11, + 0x10, 0xf8, 0x27, 0x63, 0xf8, 0x45, 0x0f, 0x32, + 0xf2, 0x74, 0x0e, 0x9f, 0xf4, 0x95, 0x48, 0x17, + 0x10, 0xf8, 0x27, 0x60, 0xf8, 0x44, 0x0f, 0x3d, + 0x60, 0xe1, 0x00, 0x02, 0x00, 0x01, 0xf8, 0x20, + 0x0f, 0x6d, 0xf0, 0x73, 0x11, 0x33, 0x10, 0x04, + 0x80, 0x00, 0x10, 0x05, 0xf0, 0x74, 0x06, 0x9f, + 0x11, 0x04, 0xf3, 0x00, 0x00, 0x01, 0x81, 0x04, + 0x6d, 0x8e, 0x77, 0x10, 0x00, 0x01, 0x71, 0xe1, + 0x00, 0x02, 0x00, 0x12, 0xf4, 0xaa, 0xf8, 0x30, + 0x0f, 0x62, 0x77, 0x10, 0x00, 0x02, 0xf4, 0xaa, + 0xf8, 0x30, 0x0f, 0x6d, 0x45, 0xe1, 0x00, 0x0b, + 0x88, 0x10, 0x43, 0xe1, 0x00, 0x0c, 0x83, 0xf8, + 0x00, 0x12, 0xf4, 0x95, 0xf4, 0x95, 0xf4, 0xaa, + 0xf8, 0x30, 0x0f, 0x6d, 0xf0, 0x73, 0x0f, 0x96, + 0xf5, 0x00, 0x81, 0x04, 0x49, 0x16, 0xf5, 0x20, + 0x89, 0x16, 0x76, 0xe1, 0x00, 0x0c, 0x00, 0x00, + 0x76, 0xe1, 0x00, 0x04, 0x00, 0x00, 0x48, 0x16, + 0xf8, 0x45, 0x11, 0x33, 0xf7, 0xb8, 0x71, 0xe1, + 0x00, 0x02, 0x00, 0x12, 0x10, 0xf8, 0x00, 0x12, + 0xf0, 0x10, 0x00, 0x03, 0xf8, 0x46, 0x0f, 0x8c, + 0x10, 0xf8, 0x00, 0x12, 0xf0, 0x10, 0x00, 0x03, + 0xf8, 0x45, 0x10, 0x16, 0x77, 0x10, 0x00, 0x01, + 0xf4, 0xaa, 0xf8, 0x30, 0x0f, 0x9c, 0x77, 0x10, + 0x00, 0x02, 0xf4, 0xaa, 0xf8, 0x30, 0x0f, 0xa8, + 0xf0, 0x73, 0x0f, 0x96, 0x77, 0x10, 0x00, 0x04, + 0xf4, 0xaa, 0xf8, 0x30, 0x10, 0xb7, 0x77, 0x10, + 0x00, 0x05, 0xf4, 0xaa, 0xf8, 0x30, 0x10, 0xbc, + 0xf2, 0x74, 0x0e, 0x9f, 0xf4, 0x95, 0x48, 0x17, + 0xf0, 0x73, 0x11, 0x31, 0x76, 0xe1, 0x00, 0x0c, + 0x00, 0x00, 0x76, 0xe1, 0x00, 0x0b, 0x00, 0x00, + 0x76, 0xe1, 0x00, 0x04, 0x00, 0x00, 0x76, 0xe1, + 0x00, 0x02, 0x00, 0x02, 0x11, 0xe1, 0x00, 0x0c, + 0xe8, 0x03, 0xf6, 0x20, 0x89, 0x12, 0xf4, 0x95, + 0x77, 0x10, 0x00, 0x03, 0xf5, 0xaa, 0xf8, 0x30, + 0x0f, 0xb6, 0x6b, 0xf8, 0x27, 0x6f, 0x00, 0x01, + 0x88, 0x10, 0xf4, 0x95, 0xf4, 0x95, 0xf5, 0xae, + 0xf8, 0x20, 0x0f, 0xbd, 0x48, 0x16, 0x80, 0x06, + 0x88, 0x13, 0xf4, 0x95, 0x77, 0x10, 0x00, 0x03, + 0xf6, 0xab, 0xf8, 0x20, 0x0f, 0xc8, 0x6b, 0xf8, + 0x27, 0x6f, 0x00, 0x01, 0x12, 0x06, 0xf8, 0x45, + 0x10, 0x00, 0x10, 0xe1, 0x00, 0x04, 0x80, 0x00, + 0x10, 0x05, 0x80, 0x01, 0x10, 0x04, 0x80, 0x02, + 0x10, 0x06, 0x80, 0x03, 0x48, 0x11, 0xf2, 0x74, + 0x07, 0x1e, 0xf0, 0x00, 0x00, 0x05, 0x10, 0x06, + 0x00, 0xe1, 0x00, 0x04, 0x80, 0xe1, 0x00, 0x04, + 0x10, 0x06, 0x00, 0xe1, 0x00, 0x0c, 0x80, 0xe1, + 0x00, 0x0c, 0x88, 0x12, 0x11, 0x06, 0x10, 0x04, + 0xf6, 0x00, 0x80, 0x04, 0x48, 0x16, 0xf6, 0x20, + 0x88, 0x16, 0x89, 0x13, 0xf4, 0x95, 0x77, 0x10, + 0x00, 0x03, 0xf6, 0xab, 0xf8, 0x20, 0x0f, 0xf5, + 0x6b, 0xf8, 0x27, 0x6f, 0x00, 0x01, 0x77, 0x10, + 0x00, 0x0c, 0x71, 0xe1, 0x00, 0x04, 0x00, 0x13, + 0xf6, 0xab, 0xf8, 0x20, 0x10, 0x00, 0x6b, 0xf8, + 0x27, 0x6f, 0x00, 0x01, 0x6c, 0xe2, 0xff, 0xfd, + 0x11, 0x31, 0xf6, 0xb8, 0x6f, 0xe1, 0x00, 0x05, + 0x0c, 0x48, 0x6f, 0xe1, 0x00, 0x06, 0x0c, 0x18, + 0xf0, 0x30, 0x0f, 0xff, 0xf0, 0x00, 0x00, 0x03, + 0x80, 0xe1, 0x00, 0x0b, 0x76, 0xe1, 0x00, 0x02, + 0x00, 0x03, 0x48, 0x16, 0xf8, 0x45, 0x11, 0x33, + 0x71, 0xe1, 0x00, 0x0c, 0x00, 0x12, 0x10, 0xe1, + 0x00, 0x0b, 0x49, 0x12, 0xf6, 0x20, 0x88, 0x13, + 0xe8, 0x0c, 0xf6, 0x20, 0x88, 0x10, 0xf4, 0x95, + 0xf4, 0x95, 0xf5, 0xab, 0xf8, 0x20, 0x10, 0x27, + 0x48, 0x13, 0x80, 0x06, 0x88, 0x10, 0xf4, 0x95, + 0xf4, 0x95, 0xf5, 0xae, 0xf8, 0x20, 0x10, 0x30, + 0x70, 0x06, 0x00, 0x16, 0x12, 0x06, 0xf8, 0x45, + 0x10, 0x5f, 0x10, 0xe1, 0x00, 0x04, 0x80, 0x00, + 0x10, 0x05, 0x80, 0x01, 0x10, 0x04, 0x80, 0x02, + 0x10, 0x06, 0x80, 0x03, 0x48, 0x11, 0xf2, 0x74, + 0x07, 0x1e, 0xf0, 0x00, 0x00, 0x05, 0x10, 0x06, + 0x00, 0xe1, 0x00, 0x04, 0x80, 0xe1, 0x00, 0x04, + 0x10, 0x06, 0x00, 0xe1, 0x00, 0x0c, 0x80, 0xe1, + 0x00, 0x0c, 0x88, 0x12, 0x11, 0x06, 0x10, 0x04, + 0xf6, 0x00, 0x80, 0x04, 0x48, 0x16, 0xf6, 0x20, + 0x88, 0x16, 0xf4, 0x95, 0x77, 0x10, 0x00, 0x0c, + 0x71, 0xe1, 0x00, 0x04, 0x00, 0x13, 0xf6, 0xab, + 0xf8, 0x20, 0x10, 0x5f, 0x6b, 0xf8, 0x27, 0x6f, + 0x00, 0x01, 0x77, 0x10, 0x00, 0x0c, 0xf6, 0xaa, + 0xf8, 0x20, 0x10, 0x6b, 0xf2, 0x74, 0x0e, 0x9f, + 0xf4, 0x95, 0x48, 0x17, 0x71, 0xe1, 0x00, 0x0c, + 0x00, 0x12, 0x77, 0x10, 0x00, 0x0c, 0xf4, 0xaa, + 0xf8, 0x30, 0x10, 0x7c, 0x77, 0x10, 0x00, 0x0c, + 0x71, 0xe1, 0x00, 0x0b, 0x00, 0x13, 0xf6, 0xab, + 0xf8, 0x30, 0x10, 0xb4, 0xe7, 0x30, 0xf7, 0xaa, + 0xf8, 0x30, 0x10, 0xb4, 0xf2, 0x74, 0x0e, 0xc1, + 0xf4, 0x95, 0x48, 0x17, 0x88, 0x12, 0xf4, 0x95, + 0xf4, 0x95, 0x6c, 0x82, 0x10, 0x8d, 0x76, 0xe1, + 0x00, 0x04, 0x00, 0x00, 0x76, 0xe1, 0x00, 0x02, + 0x00, 0x05, 0xf0, 0x73, 0x10, 0xb4, 0x76, 0xe1, + 0x00, 0x02, 0x00, 0x04, 0x77, 0x10, 0x00, 0x0c, + 0x71, 0xe1, 0x00, 0x0b, 0x00, 0x12, 0xf5, 0xaa, + 0xf8, 0x20, 0x10, 0x9a, 0xf0, 0x73, 0x10, 0x9c, + 0x77, 0x12, 0x00, 0x0c, 0x76, 0x00, 0x00, 0x00, + 0x70, 0x01, 0x00, 0x12, 0x70, 0x02, 0x00, 0x17, + 0x76, 0x03, 0x00, 0x01, 0x48, 0x11, 0xf2, 0x74, + 0x0c, 0xb9, 0xf0, 0x00, 0x00, 0x05, 0x76, 0xe1, + 0x00, 0x04, 0x00, 0x00, 0x77, 0x10, 0x00, 0x0c, + 0x71, 0xe1, 0x00, 0x0b, 0x00, 0x12, 0xf6, 0xaa, + 0xf8, 0x20, 0x11, 0x1c, 0x48, 0x16, 0xf8, 0x45, + 0x11, 0x33, 0x60, 0xe1, 0x00, 0x02, 0x00, 0x05, + 0xf8, 0x20, 0x10, 0xdf, 0x10, 0xe1, 0x00, 0x0b, + 0x08, 0xe1, 0x00, 0x0c, 0x11, 0xe1, 0x00, 0x04, + 0xf8, 0x4d, 0x10, 0xc7, 0x6b, 0xf8, 0x27, 0x6f, + 0x00, 0x01, 0x88, 0x10, 0xf4, 0x95, 0xf4, 0x95, + 0xf5, 0xae, 0xf8, 0x20, 0x10, 0xcf, 0x48, 0x16, + 0xf4, 0x95, 0x48, 0x08, 0xf8, 0x45, 0x11, 0x16, + 0x6f, 0xe1, 0x00, 0x0c, 0x0d, 0x00, 0x81, 0xe1, + 0x00, 0x0c, 0x11, 0x04, 0xf5, 0x00, 0x81, 0x04, + 0x49, 0x16, 0xf5, 0x20, 0x89, 0x16, 0xf0, 0x73, + 0x11, 0x0e, 0x10, 0xe1, 0x00, 0x0b, 0x71, 0xe1, + 0x00, 0x0c, 0x00, 0x12, 0x88, 0x10, 0xf4, 0x95, + 0xf4, 0x95, 0xf6, 0xaa, 0xf8, 0x30, 0x11, 0x16, + 0x49, 0x12, 0xf6, 0x20, 0x88, 0x10, 0xf4, 0x95, + 0xf4, 0x95, 0xf5, 0xae, 0xf8, 0x20, 0x10, 0xf3, + 0x48, 0x16, 0x80, 0x06, 0x48, 0x08, 0xf8, 0x45, + 0x11, 0x16, 0x10, 0x04, 0x70, 0x02, 0x00, 0x17, + 0x80, 0x00, 0x76, 0x03, 0x00, 0x00, 0x10, 0x06, + 0x80, 0x01, 0x10, 0x05, 0xf0, 0x74, 0x0c, 0xb9, + 0x10, 0x06, 0x00, 0xe1, 0x00, 0x0c, 0x80, 0xe1, + 0x00, 0x0c, 0x11, 0x06, 0x10, 0x04, 0xf6, 0x00, + 0x80, 0x04, 0x48, 0x16, 0xf6, 0x20, 0x88, 0x16, + 0x10, 0xe1, 0x00, 0x0c, 0x08, 0xe1, 0x00, 0x0b, + 0xf8, 0x45, 0x11, 0x1c, 0xf0, 0x73, 0x11, 0x31, + 0xf2, 0x74, 0x0e, 0x9f, 0xf4, 0x95, 0x48, 0x17, + 0xf0, 0x73, 0x11, 0x33, 0x76, 0xe1, 0x00, 0x0c, + 0x00, 0x00, 0x76, 0xe1, 0x00, 0x0b, 0x00, 0x00, + 0x76, 0xe1, 0x00, 0x02, 0x00, 0x01, 0x10, 0x04, + 0x80, 0x00, 0x10, 0x05, 0xf0, 0x74, 0x06, 0x9f, + 0x88, 0x12, 0xf4, 0x95, 0x77, 0x10, 0x00, 0xff, + 0xf4, 0xaa, 0xf8, 0x30, 0x11, 0x33, 0x6c, 0x86, + 0x0f, 0x70, 0xee, 0x08, 0x8a, 0x17, 0x8a, 0x16, + 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, 0xee, 0xfc, + 0xf4, 0x95, 0x71, 0x06, 0x00, 0x12, 0x88, 0x11, + 0x73, 0x12, 0x00, 0x0e, 0xf1, 0x66, 0x00, 0x0d, + 0xf3, 0x00, 0x24, 0x00, 0x89, 0x14, 0x13, 0x81, + 0xf7, 0x7a, 0xf3, 0x30, 0x00, 0x01, 0x81, 0xf8, + 0x27, 0x60, 0x13, 0xe1, 0x00, 0x01, 0xf7, 0x7c, + 0xf3, 0x30, 0x00, 0x03, 0x81, 0xf8, 0x27, 0x61, + 0xe9, 0x0f, 0x19, 0xe1, 0x00, 0x01, 0x81, 0xf8, + 0x27, 0x62, 0x71, 0xe4, 0x00, 0x03, 0x00, 0x13, + 0xf6, 0xb8, 0x49, 0x13, 0xf3, 0x00, 0x00, 0x01, + 0xf3, 0x30, 0x00, 0x0f, 0x49, 0x0b, 0x09, 0xf8, + 0x27, 0x62, 0xf8, 0x4d, 0x11, 0x75, 0x77, 0x10, + 0x00, 0xff, 0xf4, 0xab, 0xf8, 0x30, 0x11, 0x75, + 0x57, 0xf8, 0x27, 0x6c, 0xf3, 0x00, 0x00, 0x01, + 0x4f, 0xf8, 0x27, 0x6c, 0x76, 0xf8, 0x27, 0x63, + 0x00, 0x01, 0xf0, 0x73, 0x11, 0x78, 0x76, 0xf8, + 0x27, 0x63, 0x00, 0x00, 0x70, 0xe4, 0x00, 0x03, + 0x27, 0x62, 0x76, 0xf8, 0x27, 0x64, 0x00, 0x00, + 0x11, 0xf8, 0x27, 0x61, 0x61, 0xf8, 0x00, 0x0b, + 0x00, 0x02, 0xf8, 0x20, 0x11, 0x8d, 0xe9, 0x01, + 0x6f, 0xe1, 0x00, 0x02, 0x0f, 0x18, 0x81, 0xf8, + 0x27, 0x64, 0x11, 0xf8, 0x27, 0x61, 0x61, 0xf8, + 0x00, 0x0b, 0x00, 0x01, 0xf8, 0x20, 0x11, 0xa9, + 0x10, 0xf8, 0x27, 0x64, 0xf1, 0x00, 0x00, 0x04, + 0x89, 0x13, 0xe9, 0xb8, 0xf5, 0x20, 0x81, 0xf8, + 0x27, 0x65, 0x60, 0x84, 0x00, 0x02, 0xf8, 0x20, + 0x11, 0xa9, 0x70, 0x00, 0x00, 0x11, 0x70, 0x01, + 0x00, 0x13, 0x70, 0x02, 0x27, 0x65, 0xf2, 0x74, + 0x0f, 0x18, 0xf4, 0x95, 0x48, 0x12, 0xee, 0x04, + 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, 0x4a, 0x16, + 0x4a, 0x17, 0xee, 0xfc, 0xe8, 0x00, 0x4e, 0xf8, + 0x27, 0x66, 0xe8, 0x00, 0x4e, 0xf8, 0x27, 0x68, + 0xe8, 0x00, 0x4e, 0xf8, 0x27, 0x6c, 0xe8, 0x00, + 0x4e, 0xf8, 0x27, 0x6a, 0x77, 0x12, 0x27, 0x40, + 0x77, 0x11, 0x24, 0x00, 0x77, 0x1a, 0x00, 0x1f, + 0xf0, 0x72, 0x11, 0xdb, 0x70, 0x92, 0x00, 0x11, + 0x76, 0xe1, 0x00, 0x01, 0xff, 0xff, 0x76, 0x81, + 0x00, 0x00, 0x76, 0xe1, 0x00, 0x02, 0x00, 0x00, + 0x76, 0xe1, 0x00, 0x03, 0x00, 0xff, 0x76, 0xe1, + 0x00, 0x0c, 0x00, 0x00, 0x76, 0xe1, 0x00, 0x0b, + 0x00, 0x00, 0x76, 0xe1, 0x00, 0x04, 0x00, 0x00, + 0x6d, 0xe9, 0x00, 0x0d, 0xf0, 0x20, 0x25, 0xa0, + 0xf1, 0x00, 0x00, 0x07, 0x89, 0x11, 0xf1, 0x00, + 0x00, 0x01, 0x81, 0x02, 0x88, 0x16, 0xf4, 0x95, + 0x77, 0x17, 0x00, 0x20, 0x76, 0x86, 0x00, 0xff, + 0x76, 0x00, 0x00, 0x00, 0x76, 0x01, 0x00, 0x06, + 0x10, 0x02, 0xf0, 0x74, 0x06, 0x6c, 0x76, 0x00, + 0x00, 0x00, 0x76, 0x01, 0x00, 0x06, 0xf2, 0x74, + 0x06, 0x6c, 0xf4, 0x95, 0x48, 0x11, 0x10, 0x02, + 0xf0, 0x00, 0x00, 0x0d, 0x80, 0x02, 0x6d, 0xe9, + 0x00, 0x0d, 0x6d, 0xee, 0x00, 0x0d, 0x6c, 0xef, + 0xff, 0xff, 0x11, 0xe8, 0xf0, 0x74, 0x0c, 0x9d, + 0xee, 0x04, 0x8a, 0x17, 0x8a, 0x16, 0x8a, 0x11, + 0xfc, 0x00, 0x4a, 0x11, 0x4a, 0x16, 0x4a, 0x17, + 0xee, 0xfa, 0x88, 0x11, 0x10, 0x0a, 0x49, 0x11, + 0xf8, 0x4d, 0x12, 0x9f, 0x48, 0x08, 0xf8, 0x45, + 0x12, 0x9f, 0x80, 0x04, 0x12, 0x81, 0xf5, 0x78, + 0x89, 0x12, 0xf4, 0x95, 0xf4, 0x95, 0x6c, 0xe2, + 0xff, 0xb9, 0x12, 0x8a, 0x61, 0xf8, 0x00, 0x08, + 0x00, 0x80, 0xf8, 0x30, 0x12, 0x8a, 0x13, 0xe1, + 0x00, 0x01, 0xf0, 0xe8, 0xf7, 0x78, 0xf1, 0xa0, + 0xf2, 0x30, 0x1f, 0xff, 0x88, 0x17, 0xf4, 0x95, + 0x77, 0x12, 0x24, 0x00, 0x77, 0x16, 0x00, 0x00, + 0x77, 0x13, 0x00, 0x20, 0xf6, 0xb8, 0x48, 0x17, + 0x08, 0xe2, 0x00, 0x01, 0xf8, 0x45, 0x12, 0x42, + 0x6d, 0xea, 0x00, 0x0d, 0x6d, 0x96, 0x6c, 0xeb, + 0xff, 0xff, 0x12, 0x34, 0xf0, 0x73, 0x12, 0x90, + 0x56, 0xf8, 0x27, 0x6a, 0xf0, 0x00, 0x00, 0x01, + 0x4e, 0xf8, 0x27, 0x6a, 0x60, 0x82, 0x00, 0x01, + 0xf8, 0x30, 0x12, 0x54, 0x70, 0x00, 0x00, 0x16, + 0xf2, 0x74, 0x11, 0x38, 0xf4, 0x95, 0x48, 0x11, + 0xf0, 0x73, 0x12, 0x90, 0x70, 0x00, 0x00, 0x16, + 0xf2, 0x74, 0x11, 0x38, 0xf4, 0x95, 0x48, 0x11, + 0x72, 0x10, 0x2a, 0x9e, 0xf4, 0x95, 0xf4, 0xaf, + 0xf8, 0x30, 0x12, 0x6e, 0x76, 0x00, 0x00, 0x00, + 0x76, 0x01, 0x00, 0xbc, 0x70, 0x02, 0x00, 0x16, + 0x76, 0x03, 0x00, 0x00, 0xf2, 0x74, 0x0c, 0xb9, + 0xf4, 0x95, 0x48, 0x11, 0xf0, 0x73, 0x12, 0x90, + 0x10, 0xf8, 0x27, 0x6e, 0xf8, 0x44, 0x12, 0x90, + 0x76, 0x00, 0x00, 0x00, 0x76, 0x01, 0x00, 0xbc, + 0x70, 0x02, 0x00, 0x16, 0x76, 0x03, 0x00, 0x00, + 0xf2, 0x74, 0x0c, 0xb9, 0xf4, 0x95, 0x48, 0x11, + 0xf0, 0x74, 0x0c, 0x5e, 0xf0, 0xe0, 0xf0, 0x10, + 0x13, 0x88, 0xf8, 0x42, 0x12, 0x90, 0x76, 0xf8, + 0x27, 0x6e, 0x00, 0x01, 0xf0, 0x73, 0x12, 0x90, + 0x56, 0xf8, 0x27, 0x66, 0xf0, 0x00, 0x00, 0x01, + 0x4e, 0xf8, 0x27, 0x66, 0x6d, 0xe9, 0x00, 0x5e, + 0x56, 0xf8, 0x27, 0x68, 0xf0, 0x00, 0x00, 0x01, + 0x4e, 0xf8, 0x27, 0x68, 0x71, 0x04, 0x00, 0x12, + 0x6e, 0xea, 0xff, 0xff, 0x12, 0x18, 0x70, 0x04, + 0x00, 0x12, 0xee, 0x06, 0x8a, 0x17, 0x8a, 0x16, + 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, 0xee, 0xfe, + 0x88, 0x0e, 0xf4, 0x95, 0xf0, 0x66, 0x00, 0x0d, + 0xf0, 0x00, 0x25, 0xa0, 0x88, 0x11, 0xf4, 0x95, + 0xf4, 0x95, 0x76, 0x81, 0x00, 0xff, 0x76, 0x00, + 0x00, 0x00, 0x76, 0x01, 0x00, 0x06, 0xf2, 0x74, + 0x06, 0x6c, 0xf0, 0x00, 0x00, 0x01, 0x76, 0x00, + 0x00, 0x00, 0x76, 0x01, 0x00, 0x06, 0x48, 0x11, + 0xf2, 0x74, 0x06, 0x6c, 0xf0, 0x00, 0x00, 0x07, + 0xee, 0x02, 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, + 0x88, 0x0e, 0xf4, 0x95, 0xf0, 0x66, 0x00, 0x0d, + 0xf0, 0x00, 0x24, 0x00, 0x88, 0x11, 0xf4, 0x95, + 0xf4, 0x95, 0x76, 0xe1, 0x00, 0x01, 0xff, 0xff, + 0x76, 0x81, 0x00, 0x00, 0x76, 0xe1, 0x00, 0x02, + 0x00, 0x00, 0x76, 0xe1, 0x00, 0x03, 0x00, 0xff, + 0x8a, 0x11, 0xfc, 0x00, 0x4a, 0x11, 0xf4, 0x95, + 0x13, 0x03, 0x88, 0x11, 0xfa, 0x4d, 0x12, 0xec, + 0x71, 0x02, 0x00, 0x12, 0xf3, 0x10, 0x00, 0x01, + 0x89, 0x1a, 0xf4, 0x95, 0xf0, 0x72, 0x12, 0xeb, + 0x70, 0x91, 0x00, 0x12, 0x8a, 0x11, 0xfc, 0x00, + 0xf4, 0x95, 0x4a, 0x0b, 0x4a, 0x0c, 0x4a, 0x0d, + 0xf7, 0xb8, 0xee, 0xfe, 0x10, 0xf8, 0x00, 0x08, + 0x11, 0x06, 0xf1, 0xc0, 0x83, 0x00, 0xf4, 0x85, + 0x11, 0x06, 0xf7, 0x85, 0x81, 0x06, 0xf6, 0xb8, + 0xec, 0x0f, 0x1e, 0x06, 0x61, 0x00, 0x80, 0x00, + 0xf8, 0x20, 0x13, 0x05, 0xf4, 0x84, 0xee, 0x02, + 0x8a, 0x0d, 0x8a, 0x0c, 0x8a, 0x0b, 0xfc, 0x00, + 0xf4, 0x95, 0x4a, 0x0b, 0x4a, 0x0c, 0x4a, 0x0d, + 0xee, 0xfe, 0xf7, 0xb8, 0x80, 0x00, 0x10, 0xf8, + 0x00, 0x08, 0xf4, 0x85, 0x11, 0x06, 0xf7, 0x85, + 0x81, 0x06, 0xf6, 0xb8, 0xec, 0x0f, 0x1e, 0x06, + 0xf0, 0xf0, 0x61, 0x00, 0x80, 0x00, 0xf8, 0x20, + 0x13, 0x20, 0xf4, 0x84, 0xee, 0x02, 0x8a, 0x0d, + 0x8a, 0x0c, 0x8a, 0x0b, 0xfc, 0x00, 0x4a, 0x11, + 0x77, 0x11, 0x00, 0x7b, 0x76, 0x81, 0x2e, 0xec, + 0x77, 0x11, 0x00, 0x7b, 0xee, 0xff, 0x71, 0x81, + 0x00, 0x11, 0xee, 0x01, 0x76, 0xe1, 0x00, 0x01, + 0x00, 0x00, 0x76, 0xe1, 0x00, 0x04, 0x00, 0x00, + 0x76, 0xe1, 0x00, 0x06, 0x00, 0x00, 0x76, 0xe1, + 0x00, 0x62, 0x00, 0x00, 0x76, 0xe1, 0x00, 0x76, + 0x00, 0x00, 0x76, 0xe1, 0x00, 0x92, 0x00, 0x00, + 0x76, 0xe1, 0x00, 0x94, 0x00, 0x00, 0x76, 0xe1, + 0x00, 0xb0, 0x00, 0x00, 0x76, 0xe1, 0x00, 0xb3, + 0x00, 0x00, 0x76, 0xe1, 0x00, 0xbe, 0x00, 0x00, + 0x76, 0xe1, 0x00, 0xbf, 0x00, 0x00, 0x76, 0xe1, + 0x00, 0xc1, 0x00, 0x00, 0x76, 0xe1, 0x00, 0xc3, + 0x00, 0x00, 0x76, 0xe1, 0x00, 0xc5, 0x00, 0x00, + 0x76, 0xe1, 0x00, 0xc7, 0x00, 0x00, 0x76, 0x81, + 0x00, 0x00, 0x8a, 0x11, 0xf4, 0x95, 0xf4, 0xe4, + 0x4a, 0x11, 0x4a, 0x16, 0x4a, 0x17, 0xee, 0xff, + 0xf4, 0x95, 0x71, 0x06, 0x00, 0x16, 0xfb, 0x80, + 0x16, 0xa2, 0x88, 0x17, 0xf4, 0x95, 0xf7, 0xb8, + 0x10, 0xf8, 0x00, 0x17, 0xf0, 0x10, 0x00, 0x02, + 0xfa, 0x46, 0x13, 0x88, 0x77, 0x11, 0x00, 0x00, + 0x10, 0xf8, 0x00, 0x17, 0xf0, 0x10, 0x00, 0x02, + 0xf8, 0x45, 0x13, 0xf9, 0x10, 0xf8, 0x00, 0x17, + 0xf8, 0x45, 0x14, 0x39, 0x10, 0xf8, 0x00, 0x17, + 0xf0, 0x10, 0x00, 0x01, 0xf8, 0x45, 0x14, 0x1f, + 0xf0, 0x73, 0x14, 0x52, 0x10, 0xf8, 0x00, 0x17, + 0xf0, 0x10, 0x00, 0x03, 0xf8, 0x45, 0x13, 0xd3, + 0x10, 0xf8, 0x00, 0x17, 0xf0, 0x10, 0x00, 0x06, + 0xf8, 0x44, 0x14, 0x52, 0x77, 0x12, 0x00, 0x7b, + 0x71, 0x82, 0x00, 0x14, 0x61, 0xe4, 0x00, 0x07, + 0x00, 0x40, 0xf8, 0x30, 0x14, 0x52, 0x49, 0x14, + 0x48, 0x17, 0xf6, 0x00, 0x88, 0x12, 0xf4, 0x95, + 0x77, 0x13, 0x00, 0x55, 0x77, 0x11, 0x00, 0x57, + 0x6d, 0xea, 0x00, 0x3b, 0xe5, 0x01, 0x10, 0xe6, + 0x00, 0x06, 0x80, 0x81, 0x48, 0x14, 0x00, 0xf8, + 0x00, 0x17, 0x88, 0x12, 0xf4, 0x95, 0x77, 0x11, + 0x00, 0x55, 0x10, 0xe2, 0x00, 0x40, 0x80, 0x81, + 0x77, 0x11, 0x00, 0x57, 0x10, 0xe6, 0x00, 0x07, + 0x80, 0x81, 0x77, 0x11, 0x00, 0x55, 0x10, 0xe2, + 0x00, 0x45, 0x80, 0x81, 0x10, 0xe6, 0x00, 0x08, + 0x77, 0x11, 0x00, 0x57, 0x80, 0x81, 0x77, 0x11, + 0x00, 0x55, 0x10, 0xe2, 0x00, 0x4a, 0x80, 0x81, + 0x77, 0x11, 0x00, 0x57, 0x10, 0xe6, 0x00, 0x09, + 0x80, 0x81, 0xf2, 0x73, 0x14, 0x52, 0x77, 0x11, + 0x03, 0xc0, 0x77, 0x12, 0x00, 0x7b, 0x10, 0x82, + 0xf0, 0x00, 0x00, 0x07, 0x88, 0x13, 0xf4, 0x95, + 0xf4, 0x95, 0x96, 0x1b, 0xf8, 0x30, 0x14, 0x52, + 0x10, 0xe3, 0x00, 0x35, 0x77, 0x12, 0x00, 0x55, + 0x80, 0x82, 0x77, 0x12, 0x00, 0x57, 0x10, 0xe6, + 0x00, 0x04, 0x80, 0x82, 0x77, 0x12, 0x00, 0x55, + 0x10, 0xe3, 0x00, 0x37, 0x80, 0x82, 0x77, 0x12, + 0x00, 0x57, 0x10, 0xe6, 0x00, 0x05, 0x80, 0x82, + 0x48, 0x11, 0xf0, 0x40, 0x00, 0x10, 0xf2, 0x73, + 0x14, 0x50, 0xf0, 0x40, 0x00, 0x20, 0x77, 0x12, + 0x00, 0x7b, 0x10, 0x82, 0xf0, 0x00, 0x00, 0x07, + 0x88, 0x12, 0xf4, 0x95, 0xf4, 0x95, 0x96, 0x0d, + 0xf8, 0x30, 0x14, 0x52, 0x10, 0xe2, 0x00, 0x34, + 0x77, 0x13, 0x00, 0x55, 0x80, 0x83, 0x77, 0x13, + 0x00, 0x57, 0x10, 0xe6, 0x00, 0x02, 0x80, 0x83, + 0x10, 0xe2, 0x00, 0x36, 0x77, 0x12, 0x00, 0x55, + 0x80, 0x82, 0x77, 0x12, 0x00, 0x57, 0x10, 0xe6, + 0x00, 0x03, 0x80, 0x82, 0x48, 0x11, 0xf0, 0x40, + 0x00, 0x04, 0xf2, 0x73, 0x14, 0x50, 0xf0, 0x40, + 0x00, 0x08, 0x77, 0x12, 0x00, 0x7b, 0x10, 0x82, + 0xf0, 0x00, 0x00, 0x07, 0x88, 0x12, 0xf4, 0x95, + 0xf4, 0x95, 0x96, 0x0e, 0xf8, 0x30, 0x14, 0x52, + 0x10, 0xe2, 0x00, 0x33, 0x77, 0x12, 0x00, 0x55, + 0x80, 0x82, 0x77, 0x12, 0x00, 0x57, 0x10, 0xe6, + 0x00, 0x01, 0x80, 0x82, 0x48, 0x11, 0xf2, 0x73, + 0x14, 0x50, 0xf0, 0x40, 0x00, 0x02, 0x77, 0x12, + 0x00, 0x7b, 0x10, 0x82, 0xf0, 0x00, 0x00, 0x07, + 0x88, 0x12, 0xf4, 0x95, 0xf4, 0x95, 0x96, 0x0f, + 0xf8, 0x30, 0x14, 0x52, 0x10, 0xe2, 0x00, 0x32, + 0x77, 0x12, 0x00, 0x55, 0x77, 0x13, 0x00, 0x57, + 0x80, 0x82, 0x48, 0x11, 0xe7, 0x62, 0xf0, 0x40, + 0x00, 0x01, 0xe5, 0x01, 0x88, 0x11, 0xf4, 0x95, + 0x77, 0x12, 0x00, 0x7b, 0x48, 0x11, 0x71, 0x82, + 0x00, 0x12, 0x1a, 0xe2, 0x00, 0x07, 0x80, 0xe2, + 0x00, 0x07, 0xf9, 0x80, 0x16, 0x9a, 0xee, 0x01, + 0x8a, 0x17, 0x48, 0x11, 0x8a, 0x16, 0x8a, 0x11, + 0xf4, 0xe4, 0x4a, 0x11, 0x88, 0x11, 0x77, 0x0e, + 0x00, 0x05, 0x77, 0x12, 0x00, 0x55, 0xe8, 0x04, + 0xf6, 0xb8, 0x28, 0xe1, 0x00, 0x02, 0xee, 0xff, + 0x80, 0x82, 0x77, 0x12, 0x00, 0x57, 0xf0, 0x20, + 0x80, 0x00, 0xee, 0x01, 0x1a, 0x82, 0x77, 0x12, + 0x00, 0x57, 0x80, 0x82, 0xe8, 0x01, 0x32, 0xe1, + 0x00, 0x02, 0xf5, 0x82, 0x77, 0x11, 0x00, 0x54, + 0xf6, 0x93, 0x18, 0x81, 0x77, 0x11, 0x00, 0x54, + 0xf2, 0xa0, 0x80, 0x81, 0x8a, 0x11, 0xf4, 0x95, + 0xf4, 0xe4, 0x4a, 0x11, 0x4a, 0x16, 0xf4, 0x95, + 0x71, 0x04, 0x00, 0x11, 0xfb, 0x80, 0x16, 0xa2, + 0x88, 0x16, 0xf4, 0x95, 0x77, 0x12, 0x00, 0x55, + 0x10, 0xe6, 0x00, 0x03, 0x80, 0x82, 0x77, 0x12, + 0x00, 0x56, 0x10, 0xe1, 0x00, 0x02, 0x77, 0x13, + 0x00, 0x56, 0x80, 0x82, 0x77, 0x12, 0x00, 0x56, + 0x10, 0xe1, 0x00, 0x03, 0x80, 0x82, 0x10, 0xe1, + 0x00, 0x04, 0x77, 0x12, 0x00, 0x56, 0x80, 0x82, + 0x77, 0x12, 0x00, 0x56, 0x10, 0xe1, 0x00, 0x01, + 0x80, 0x82, 0xe7, 0x12, 0xe5, 0x01, 0xf9, 0x80, + 0x16, 0x9a, 0x8a, 0x16, 0x8a, 0x11, 0xf4, 0xe4, + 0x4a, 0x11, 0x4a, 0x16, 0x4a, 0x17, 0xee, 0xf9, + 0x77, 0x11, 0x00, 0x7b, 0x76, 0x00, 0x00, 0x16, + 0x76, 0x01, 0x00, 0x17, 0x76, 0x02, 0x00, 0x1a, + 0x76, 0x03, 0x00, 0x1b, 0x76, 0x04, 0x00, 0x1c, + 0x76, 0x05, 0x00, 0x1d, 0x71, 0x81, 0x00, 0x17, + 0x71, 0xe7, 0x00, 0x06, 0x00, 0x11, 0x10, 0x81, + 0xf8, 0x44, 0x14, 0xdf, 0xf9, 0x80, 0x16, 0x53, + 0xf6, 0xb8, 0xfb, 0x80, 0x15, 0x85, 0xf0, 0x20, + 0xff, 0xff, 0xf6, 0xb8, 0xfb, 0x80, 0x16, 0x08, + 0xf0, 0x20, 0xff, 0xff, 0x77, 0x11, 0x00, 0x7b, + 0x71, 0x81, 0x00, 0x17, 0x76, 0xe7, 0x00, 0x06, + 0x00, 0x01, 0x48, 0x17, 0x77, 0x16, 0x00, 0x00, + 0x77, 0x10, 0x00, 0x04, 0x77, 0x15, 0x00, 0x03, + 0x77, 0x14, 0x00, 0x02, 0x77, 0x13, 0x00, 0x01, + 0xf0, 0x00, 0x00, 0x39, 0x76, 0xe7, 0x00, 0x08, + 0x00, 0x1f, 0x76, 0xe7, 0x00, 0x07, 0x00, 0x00, + 0x88, 0x0e, 0x77, 0x1a, 0x00, 0x05, 0x48, 0x17, + 0xf0, 0x00, 0x00, 0x09, 0x88, 0x12, 0x48, 0x18, + 0x88, 0x19, 0xe8, 0x00, 0xf0, 0x72, 0x15, 0x2c, + 0x73, 0x19, 0x00, 0x11, 0x76, 0x82, 0x00, 0x00, + 0x11, 0x91, 0x73, 0x11, 0x00, 0x19, 0x70, 0xe2, + 0x00, 0x03, 0x00, 0x16, 0x70, 0xe2, 0x00, 0x04, + 0x00, 0x13, 0x70, 0xe2, 0x00, 0x05, 0x00, 0x14, + 0x81, 0xe2, 0x00, 0x01, 0x70, 0xe2, 0x00, 0x06, + 0x00, 0x15, 0x70, 0xe2, 0x00, 0x07, 0x00, 0x10, + 0x80, 0xe2, 0x00, 0x02, 0x73, 0x0e, 0x00, 0x11, + 0xf1, 0x00, 0x00, 0x1e, 0x6d, 0xee, 0x00, 0x05, + 0x6d, 0xeb, 0x00, 0x05, 0x6d, 0xec, 0x00, 0x05, + 0x6d, 0xed, 0x00, 0x05, 0x6d, 0xe8, 0x00, 0x05, + 0xf0, 0x00, 0x00, 0x01, 0x81, 0x91, 0x6d, 0xea, + 0x00, 0x08, 0x73, 0x11, 0x00, 0x0e, 0xee, 0x07, + 0x76, 0xe7, 0x00, 0x41, 0x00, 0x24, 0x76, 0xe7, + 0x00, 0x46, 0x00, 0x25, 0x76, 0xe7, 0x00, 0x4b, + 0x00, 0x26, 0x76, 0xe7, 0x00, 0x50, 0x00, 0x27, + 0x8a, 0x17, 0x8a, 0x16, 0x8a, 0x11, 0xf4, 0xe4, + 0x4a, 0x11, 0x4a, 0x16, 0xee, 0xfe, 0x88, 0x11, + 0x56, 0x06, 0x4e, 0x00, 0xf9, 0x80, 0x16, 0xa2, + 0xf7, 0xb8, 0x10, 0xf8, 0x00, 0x11, 0xf0, 0x10, + 0xff, 0xff, 0xfa, 0x45, 0x15, 0x60, 0x77, 0x16, + 0xff, 0xff, 0x77, 0x12, 0x00, 0x7b, 0x49, 0x11, + 0x10, 0x82, 0xf6, 0x03, 0xf0, 0x00, 0x00, 0x09, + 0x88, 0x11, 0xf4, 0x95, 0xf4, 0x95, 0x10, 0x81, + 0xf8, 0x44, 0x15, 0x71, 0xf2, 0x73, 0x15, 0x71, + 0xf4, 0x95, 0xe7, 0x16, 0x77, 0x11, 0x00, 0x7b, + 0x10, 0x81, 0xf0, 0x00, 0x00, 0x09, 0x88, 0x11, + 0xf4, 0x95, 0x77, 0x12, 0x00, 0x06, 0x10, 0x81, + 0xf8, 0x45, 0x15, 0x5c, 0x6e, 0xea, 0xff, 0xff, + 0x15, 0x69, 0x6d, 0xe9, 0x00, 0x08, 0x76, 0x86, + 0x00, 0x01, 0xe9, 0x01, 0x56, 0x00, 0xf1, 0x80, + 0x10, 0xf8, 0x00, 0x0b, 0xf8, 0x45, 0x15, 0x7e, + 0xfb, 0x80, 0x15, 0x85, 0xf4, 0x95, 0x48, 0x16, + 0xf9, 0x80, 0x16, 0x9a, 0xee, 0x02, 0x48, 0x16, + 0x8a, 0x16, 0x8a, 0x11, 0xf4, 0xe4, 0x4a, 0x11, + 0xee, 0xff, 0xfb, 0x80, 0x16, 0xa2, 0x88, 0x11, + 0xf4, 0x95, 0x77, 0x10, 0xff, 0xff, 0xf4, 0xa9, + 0xf8, 0x30, 0x15, 0xc4, 0x10, 0xe1, 0x00, 0x03, + 0x77, 0x12, 0x00, 0x55, 0x80, 0x82, 0x77, 0x12, + 0x00, 0x56, 0x76, 0x82, 0x00, 0x00, 0x77, 0x12, + 0x00, 0x56, 0x76, 0x82, 0x00, 0x00, 0x77, 0x12, + 0x00, 0x56, 0x76, 0x82, 0x00, 0x00, 0x77, 0x12, + 0x00, 0x56, 0x76, 0x82, 0x00, 0x00, 0x77, 0x12, + 0x00, 0x56, 0x76, 0x82, 0x00, 0x00, 0x10, 0xe1, + 0x00, 0x02, 0xf0, 0x00, 0x00, 0x08, 0x32, 0xf8, + 0x00, 0x08, 0x77, 0x12, 0x00, 0x54, 0xe8, 0x01, + 0xf4, 0x82, 0xf4, 0x93, 0x18, 0x82, 0x77, 0x12, + 0x00, 0x54, 0xf0, 0x40, 0x00, 0x00, 0x80, 0x82, + 0x10, 0xe1, 0x00, 0x01, 0xf9, 0x80, 0x16, 0x76, + 0x10, 0xe1, 0x00, 0x01, 0xf9, 0x80, 0x16, 0x66, + 0xf0, 0x73, 0x16, 0x03, 0x77, 0x11, 0x00, 0x7b, + 0x71, 0x81, 0x00, 0x11, 0x71, 0xe1, 0x00, 0x07, + 0x00, 0x12, 0x76, 0x82, 0x00, 0x00, 0x10, 0xe1, + 0x00, 0x09, 0xf9, 0x80, 0x15, 0x85, 0x77, 0x11, + 0x00, 0x7b, 0x71, 0x81, 0x00, 0x11, 0x10, 0xe1, + 0x00, 0x09, 0xfb, 0x80, 0x15, 0x85, 0xf0, 0x00, + 0x00, 0x08, 0x77, 0x11, 0x00, 0x7b, 0x71, 0x81, + 0x00, 0x11, 0x10, 0xe1, 0x00, 0x09, 0xfb, 0x80, + 0x15, 0x85, 0xf0, 0x00, 0x00, 0x10, 0x77, 0x11, + 0x00, 0x7b, 0x71, 0x81, 0x00, 0x11, 0x10, 0xe1, + 0x00, 0x09, 0xfb, 0x80, 0x15, 0x85, 0xf0, 0x00, + 0x00, 0x18, 0x77, 0x11, 0x00, 0x7b, 0x71, 0x81, + 0x00, 0x11, 0x10, 0xe1, 0x00, 0x09, 0xfb, 0x80, + 0x15, 0x85, 0xf0, 0x00, 0x00, 0x20, 0x77, 0x11, + 0x00, 0x7b, 0x71, 0x81, 0x00, 0x11, 0x10, 0xe1, + 0x00, 0x09, 0xfb, 0x80, 0x15, 0x85, 0xf0, 0x00, + 0x00, 0x28, 0xf9, 0x80, 0x16, 0x9a, 0xee, 0x01, + 0x8a, 0x11, 0xf4, 0xe4, 0x4a, 0x11, 0xee, 0xff, + 0xfb, 0x80, 0x16, 0xa2, 0x88, 0x11, 0xf4, 0x95, + 0x77, 0x10, 0xff, 0xff, 0xf4, 0xa9, 0xf8, 0x30, + 0x16, 0x41, 0x77, 0x11, 0x00, 0x55, 0x76, 0x81, + 0x00, 0x1e, 0x77, 0x11, 0x00, 0x56, 0x76, 0x81, + 0x00, 0x00, 0x77, 0x11, 0x00, 0x56, 0x76, 0x81, + 0x00, 0x00, 0x77, 0x11, 0x00, 0x56, 0x76, 0x81, + 0x00, 0x00, 0x77, 0x11, 0x00, 0x56, 0x76, 0x81, + 0x00, 0x00, 0x77, 0x11, 0x00, 0x56, 0x76, 0x81, + 0x00, 0x00, 0x77, 0x11, 0x00, 0x56, 0x76, 0x81, + 0x00, 0x00, 0x77, 0x11, 0x00, 0x56, 0x76, 0x81, + 0x00, 0x00, 0x77, 0x11, 0x00, 0x56, 0x76, 0x81, + 0x00, 0x00, 0x77, 0x11, 0x00, 0x56, 0x76, 0x81, + 0x00, 0x00, 0x77, 0x11, 0x00, 0x56, 0xf2, 0x73, + 0x16, 0x4e, 0x76, 0x81, 0x00, 0x00, 0x77, 0x11, + 0x00, 0x7b, 0x71, 0x81, 0x00, 0x11, 0x71, 0xe1, + 0x00, 0x07, 0x00, 0x12, 0x76, 0x82, 0x00, 0x00, + 0x10, 0xe1, 0x00, 0x39, 0xf9, 0x80, 0x16, 0x08, + 0xf9, 0x80, 0x16, 0x9a, 0xee, 0x01, 0x8a, 0x11, + 0xf4, 0xe4, 0x4a, 0x11, 0x77, 0x11, 0x00, 0x7b, + 0x10, 0x81, 0xf0, 0x00, 0x00, 0x04, 0x88, 0x11, + 0xf4, 0x95, 0xf4, 0x95, 0x10, 0x81, 0xfa, 0x44, + 0x16, 0x63, 0xf4, 0x95, 0xee, 0xff, 0x76, 0x81, + 0x00, 0x01, 0xee, 0x01, 0x8a, 0x11, 0xf4, 0xe4, + 0xf0, 0x10, 0x00, 0x10, 0x4a, 0x11, 0x32, 0xf8, + 0x00, 0x08, 0xee, 0xff, 0x77, 0x11, 0x00, 0x01, + 0xe8, 0x01, 0xee, 0x01, 0xf4, 0x82, 0x1a, 0x81, + 0x80, 0x81, 0x8a, 0x11, 0xf4, 0x95, 0xf4, 0xe4, + 0xf0, 0x10, 0x00, 0x10, 0x4a, 0x11, 0x32, 0xf8, + 0x00, 0x08, 0xee, 0xff, 0xe8, 0x01, 0x77, 0x11, + 0x00, 0x00, 0xf4, 0x82, 0xee, 0x01, 0xf4, 0x93, + 0x18, 0x81, 0x80, 0x81, 0x8a, 0x11, 0xf4, 0x95, + 0xf4, 0xe4, 0x4a, 0x11, 0xf0, 0x10, 0x00, 0x10, + 0x77, 0x11, 0x00, 0x00, 0x32, 0xf8, 0x00, 0x08, + 0xee, 0xff, 0x11, 0x81, 0xe8, 0x01, 0xee, 0x01, + 0x77, 0x11, 0x00, 0x00, 0xf4, 0x82, 0xf2, 0xa0, + 0x80, 0x81, 0x8a, 0x11, 0xf4, 0x95, 0xf4, 0xe4, + 0xf2, 0x73, 0x16, 0x9e, 0xf6, 0xbb, 0xf4, 0x95, + 0xf4, 0x95, 0xf4, 0x95, 0xf4, 0x95, 0xf4, 0xe4, + 0xf2, 0x73, 0x16, 0xa6, 0xf7, 0xbb, 0xf4, 0x95, + 0xf4, 0x95, 0xf4, 0x95, 0xf4, 0x95, 0xf4, 0xe4, + 0x4a, 0x11, 0x4a, 0x16, 0xf4, 0x95, 0x71, 0x04, + 0x00, 0x16, 0xfb, 0x80, 0x16, 0xa2, 0x88, 0x11, + 0xf4, 0x95, 0x71, 0xe1, 0x00, 0x05, 0x00, 0x12, + 0x76, 0x82, 0x00, 0x0e, 0x10, 0xe6, 0x00, 0x0e, + 0x71, 0xe1, 0x00, 0x06, 0x00, 0x12, 0x80, 0x82, + 0x71, 0xe1, 0x00, 0x05, 0x00, 0x12, 0x76, 0x82, + 0x00, 0x0d, 0x71, 0xe1, 0x00, 0x06, 0x00, 0x12, + 0x10, 0xe6, 0x00, 0x0d, 0x80, 0x82, 0x71, 0xe1, + 0x00, 0x05, 0x00, 0x12, 0x76, 0x82, 0x00, 0x0c, + 0x10, 0xe6, 0x00, 0x0c, 0x71, 0xe1, 0x00, 0x06, + 0x00, 0x12, 0x80, 0x82, 0x71, 0xe1, 0x00, 0x05, + 0x00, 0x12, 0x76, 0x82, 0x00, 0x0b, 0x10, 0xe6, + 0x00, 0x0b, 0x71, 0xe1, 0x00, 0x06, 0x00, 0x12, + 0x80, 0x82, 0x71, 0xe1, 0x00, 0x05, 0x00, 0x12, + 0x76, 0x82, 0x00, 0x0a, 0x71, 0xe1, 0x00, 0x06, + 0x00, 0x12, 0x10, 0xe6, 0x00, 0x0a, 0x80, 0x82, + 0x71, 0xe1, 0x00, 0x05, 0x00, 0x12, 0x76, 0x82, + 0x00, 0x09, 0x10, 0xe6, 0x00, 0x09, 0x71, 0xe1, + 0x00, 0x06, 0x00, 0x12, 0x80, 0x82, 0x71, 0xe1, + 0x00, 0x05, 0x00, 0x12, 0x76, 0x82, 0x00, 0x08, + 0x71, 0xe1, 0x00, 0x06, 0x00, 0x12, 0x10, 0xe6, + 0x00, 0x08, 0x80, 0x82, 0x71, 0xe1, 0x00, 0x05, + 0x00, 0x12, 0x76, 0x82, 0x00, 0x07, 0x10, 0xe6, + 0x00, 0x07, 0x71, 0xe1, 0x00, 0x06, 0x00, 0x12, + 0x80, 0x82, 0x71, 0xe1, 0x00, 0x05, 0x00, 0x12, + 0x76, 0x82, 0x00, 0x06, 0x71, 0xe1, 0x00, 0x06, + 0x00, 0x12, 0x10, 0xe6, 0x00, 0x06, 0x80, 0x82, + 0x71, 0xe1, 0x00, 0x05, 0x00, 0x12, 0x76, 0x82, + 0x00, 0x05, 0x71, 0xe1, 0x00, 0x06, 0x00, 0x12, + 0x10, 0xe6, 0x00, 0x05, 0x80, 0x82, 0x71, 0xe1, + 0x00, 0x05, 0x00, 0x12, 0x76, 0x82, 0x00, 0x04, + 0x71, 0xe1, 0x00, 0x06, 0x00, 0x12, 0x10, 0xe6, + 0x00, 0x04, 0x80, 0x82, 0x71, 0xe1, 0x00, 0x05, + 0x00, 0x12, 0x76, 0x82, 0x00, 0x03, 0x71, 0xe1, + 0x00, 0x06, 0x00, 0x12, 0x10, 0xe6, 0x00, 0x03, + 0x80, 0x82, 0x71, 0xe1, 0x00, 0x05, 0x00, 0x12, + 0x76, 0x82, 0x00, 0x02, 0x10, 0xe6, 0x00, 0x02, + 0x71, 0xe1, 0x00, 0x06, 0x00, 0x12, 0x80, 0x82, + 0x71, 0xe1, 0x00, 0x05, 0x00, 0x12, 0x76, 0x82, + 0x00, 0x01, 0x10, 0xe6, 0x00, 0x01, 0x71, 0xe1, + 0x00, 0x06, 0x00, 0x12, 0x80, 0x82, 0x71, 0xe1, + 0x00, 0x05, 0x00, 0x12, 0x76, 0x82, 0x00, 0x00, + 0x71, 0xe1, 0x00, 0x06, 0x00, 0x13, 0xe7, 0x62, + 0xe5, 0x01, 0xf9, 0x80, 0x16, 0x9a, 0x8a, 0x16, + 0x8a, 0x11, 0xf4, 0xe4, 0x4a, 0x11, 0x88, 0x11, + 0xf4, 0x95, 0xf4, 0x95, 0x71, 0xe1, 0x00, 0x05, + 0x00, 0x12, 0xee, 0xff, 0x76, 0x82, 0x00, 0x00, + 0xee, 0x01, 0x71, 0xe1, 0x00, 0x06, 0x00, 0x11, + 0x69, 0x81, 0x00, 0x01, 0x8a, 0x11, 0xf4, 0x95, + 0xf4, 0xe4, 0x4a, 0x11, 0x88, 0x11, 0xf4, 0x95, + 0xf4, 0x95, 0x71, 0xe1, 0x00, 0x05, 0x00, 0x12, + 0xee, 0xff, 0x76, 0x82, 0x00, 0x01, 0xee, 0x01, + 0x71, 0xe1, 0x00, 0x06, 0x00, 0x11, 0x69, 0x81, + 0x00, 0x01, 0x8a, 0x11, 0xf4, 0x95, 0xf4, 0xe4, + 0x4a, 0x11, 0x77, 0x11, 0x00, 0x7b, 0x10, 0x81, + 0xf0, 0x00, 0x00, 0x94, 0x88, 0x11, 0xf4, 0x95, + 0xf4, 0x95, 0x10, 0x81, 0xfa, 0x44, 0x17, 0x9c, + 0xf4, 0x95, 0xee, 0xff, 0xf9, 0x80, 0x16, 0x53, + 0x77, 0x11, 0x00, 0x7b, 0x10, 0x81, 0xf0, 0x00, + 0x00, 0x94, 0x88, 0x11, 0xf4, 0x95, 0xf4, 0x95, + 0x76, 0x81, 0x00, 0x01, 0xee, 0x01, 0x76, 0xe1, + 0x00, 0x01, 0x00, 0x00, 0x76, 0xe1, 0x00, 0x02, + 0x00, 0x21, 0x76, 0xe1, 0x00, 0x03, 0x00, 0x20, + 0x76, 0xe1, 0x00, 0x04, 0x00, 0x23, 0x76, 0xe1, + 0x00, 0x05, 0x00, 0x22, 0x76, 0xe1, 0x00, 0x06, + 0x00, 0x38, 0x76, 0xe1, 0x00, 0x07, 0x00, 0x39, + 0x76, 0xe1, 0x00, 0x08, 0x00, 0x15, 0x76, 0xe1, + 0x00, 0x09, 0x00, 0x14, 0x76, 0xe1, 0x00, 0x0a, + 0x00, 0x00, 0x76, 0xe1, 0x00, 0x0b, 0x00, 0x41, + 0x76, 0xe1, 0x00, 0x0c, 0x00, 0x40, 0x76, 0xe1, + 0x00, 0x0d, 0x00, 0x43, 0x76, 0xe1, 0x00, 0x0e, + 0x00, 0x42, 0x76, 0xe1, 0x00, 0x0f, 0x00, 0x48, + 0x76, 0xe1, 0x00, 0x10, 0x00, 0x49, 0x76, 0xe1, + 0x00, 0x11, 0x00, 0x1b, 0x76, 0xe1, 0x00, 0x12, + 0x00, 0x1a, 0x8a, 0x11, 0xf4, 0x95, 0xf4, 0xe4, + 0x4a, 0x11, 0xee, 0xfd, 0x88, 0x11, 0x56, 0x06, + 0x4e, 0x00, 0xf9, 0x80, 0x16, 0xa2, 0x77, 0x12, + 0x00, 0x7b, 0x77, 0x0e, 0x00, 0x09, 0x10, 0x82, + 0x28, 0xf8, 0x00, 0x11, 0xf0, 0x00, 0x00, 0x95, + 0x88, 0x11, 0xf4, 0x95, 0xf4, 0x95, 0x10, 0x81, + 0xf8, 0x45, 0x17, 0xf0, 0xf2, 0x73, 0x17, 0xfd, + 0x77, 0x11, 0xff, 0xff, 0x76, 0x81, 0x00, 0x01, + 0xe9, 0x01, 0x56, 0x00, 0xf1, 0x80, 0x10, 0xf8, + 0x00, 0x0b, 0xf8, 0x45, 0x17, 0xfd, 0xfb, 0x80, + 0x18, 0x10, 0xf4, 0x95, 0x48, 0x11, 0xf9, 0x80, + 0x16, 0x9a, 0xee, 0x03, 0x48, 0x11, 0x8a, 0x11, + 0xf4, 0x95, 0xf4, 0xe4, 0x4a, 0x11, 0x88, 0x11, + 0xf4, 0x95, 0xee, 0xff, 0x71, 0xe1, 0x00, 0x01, + 0x00, 0x11, 0xee, 0x01, 0x10, 0x81, 0x8a, 0x11, + 0xf4, 0x95, 0xf4, 0xe4, 0x4a, 0x11, 0xee, 0xff, + 0xfb, 0x80, 0x16, 0xa2, 0x88, 0x11, 0xf4, 0x95, + 0x77, 0x10, 0xff, 0xff, 0xf4, 0xa9, 0xf8, 0x30, + 0x18, 0xc3, 0x71, 0xe1, 0x00, 0x05, 0x00, 0x12, + 0x76, 0x82, 0x00, 0x00, 0x71, 0xe1, 0x00, 0x06, + 0x00, 0x12, 0x76, 0x82, 0x00, 0x00, 0x71, 0xe1, + 0x00, 0x05, 0x00, 0x12, 0x76, 0x82, 0x00, 0x01, + 0x71, 0xe1, 0x00, 0x06, 0x00, 0x12, 0x76, 0x82, + 0x00, 0x00, 0x71, 0xe1, 0x00, 0x05, 0x00, 0x12, + 0x76, 0x82, 0x00, 0x02, 0x71, 0xe1, 0x00, 0x06, + 0x00, 0x12, 0x76, 0x82, 0x00, 0x00, 0x71, 0xe1, + 0x00, 0x05, 0x00, 0x12, 0x76, 0x82, 0x00, 0x03, + 0x71, 0xe1, 0x00, 0x06, 0x00, 0x12, 0x76, 0x82, + 0x00, 0x00, 0x71, 0xe1, 0x00, 0x05, 0x00, 0x12, + 0x76, 0x82, 0x00, 0x04, 0x71, 0xe1, 0x00, 0x06, + 0x00, 0x12, 0x76, 0x82, 0x00, 0x00, 0x71, 0xe1, + 0x00, 0x05, 0x00, 0x12, 0x76, 0x82, 0x00, 0x05, + 0x71, 0xe1, 0x00, 0x06, 0x00, 0x12, 0x76, 0x82, + 0x00, 0x00, 0x71, 0xe1, 0x00, 0x05, 0x00, 0x12, + 0x76, 0x82, 0x00, 0x06, 0x71, 0xe1, 0x00, 0x06, + 0x00, 0x12, 0x76, 0x82, 0x00, 0x01, 0x71, 0xe1, + 0x00, 0x05, 0x00, 0x12, 0x76, 0x82, 0x00, 0x07, + 0x71, 0xe1, 0x00, 0x06, 0x00, 0x12, 0x76, 0x82, + 0x20, 0x00, 0x71, 0xe1, 0x00, 0x05, 0x00, 0x12, + 0x76, 0x82, 0x00, 0x08, 0x71, 0xe1, 0x00, 0x06, + 0x00, 0x12, 0x76, 0x82, 0x00, 0x00, 0x71, 0xe1, + 0x00, 0x05, 0x00, 0x12, 0x76, 0x82, 0x00, 0x09, + 0x71, 0xe1, 0x00, 0x06, 0x00, 0x12, 0x76, 0x82, + 0x00, 0x00, 0x71, 0xe1, 0x00, 0x05, 0x00, 0x12, + 0x76, 0x82, 0x00, 0x0a, 0x71, 0xe1, 0x00, 0x06, + 0x00, 0x12, 0x76, 0x82, 0x00, 0x00, 0x71, 0xe1, + 0x00, 0x05, 0x00, 0x12, 0x76, 0x82, 0x00, 0x0b, + 0x71, 0xe1, 0x00, 0x06, 0x00, 0x12, 0x76, 0x82, + 0x00, 0x00, 0x71, 0xe1, 0x00, 0x05, 0x00, 0x12, + 0x76, 0x82, 0x00, 0x0c, 0x71, 0xe1, 0x00, 0x06, + 0x00, 0x12, 0x76, 0x82, 0x00, 0x00, 0x71, 0xe1, + 0x00, 0x05, 0x00, 0x12, 0x76, 0x82, 0x00, 0x0d, + 0x71, 0xe1, 0x00, 0x06, 0x00, 0x12, 0x76, 0x82, + 0x00, 0x00, 0x71, 0xe1, 0x00, 0x05, 0x00, 0x12, + 0x76, 0x82, 0x00, 0x0e, 0x71, 0xe1, 0x00, 0x06, + 0x00, 0x12, 0x76, 0x82, 0x00, 0x00, 0x10, 0xe1, + 0x00, 0x07, 0xf9, 0x80, 0x16, 0x76, 0x10, 0xe1, + 0x00, 0x08, 0xf9, 0x80, 0x16, 0x76, 0x10, 0xe1, + 0x00, 0x07, 0xf9, 0x80, 0x16, 0x66, 0x10, 0xe1, + 0x00, 0x08, 0xf9, 0x80, 0x16, 0x66, 0xf0, 0x73, + 0x18, 0xd1, 0x77, 0x11, 0x00, 0x7b, 0x10, 0x81, + 0xfb, 0x80, 0x18, 0x10, 0xf0, 0x00, 0x00, 0x95, + 0x77, 0x11, 0x00, 0x7b, 0x10, 0x81, 0xfb, 0x80, + 0x18, 0x10, 0xf0, 0x00, 0x00, 0x9e, 0xf9, 0x80, + 0x16, 0x9a, 0xee, 0x01, 0x8a, 0x11, 0xf4, 0xe4, + 0x4a, 0x11, 0x88, 0x11, 0xee, 0xff, 0xf4, 0x95, + 0x10, 0x04, 0x71, 0xe1, 0x00, 0x03, 0x00, 0x11, + 0xee, 0x01, 0x80, 0x81, 0x8a, 0x11, 0xf4, 0x95, + 0xf4, 0xe4, 0x4a, 0x11, 0x4a, 0x16, 0xf4, 0x95, + 0x71, 0x04, 0x00, 0x16, 0xfb, 0x80, 0x16, 0xa2, + 0x88, 0x11, 0xf4, 0x95, 0x71, 0xe1, 0x00, 0x02, + 0x00, 0x12, 0x76, 0x82, 0x00, 0x10, 0x10, 0xe6, + 0x00, 0x01, 0x71, 0xe1, 0x00, 0x03, 0x00, 0x12, + 0x80, 0x82, 0x71, 0xe1, 0x00, 0x04, 0x00, 0x12, + 0x10, 0xe6, 0x00, 0x02, 0x80, 0x82, 0xe7, 0x62, + 0x71, 0xe1, 0x00, 0x02, 0x00, 0x13, 0xe5, 0x01, + 0xf9, 0x80, 0x16, 0x9a, 0x8a, 0x16, 0x8a, 0x11, + 0xf4, 0xe4, 0x4a, 0x11, 0x88, 0x11, 0xee, 0xff, + 0xee, 0x01, 0x10, 0xe1, 0x00, 0x01, 0x8a, 0x11, + 0xf4, 0x95, 0xf4, 0xe4, 0x4a, 0x11, 0x77, 0x11, + 0x00, 0x7b, 0x10, 0x81, 0xf0, 0x00, 0x00, 0xb3, + 0x88, 0x11, 0xf4, 0x95, 0xf4, 0x95, 0x10, 0x81, + 0xfa, 0x44, 0x19, 0x2a, 0xf4, 0x95, 0xee, 0xff, + 0xf9, 0x80, 0x16, 0x53, 0x77, 0x11, 0x00, 0x7b, + 0x10, 0x81, 0xf0, 0x00, 0x00, 0xb3, 0x88, 0x11, + 0xf4, 0x95, 0xf4, 0x95, 0x76, 0x81, 0x00, 0x01, + 0xee, 0x01, 0x76, 0xe1, 0x00, 0x01, 0x00, 0x00, + 0x76, 0xe1, 0x00, 0x02, 0x00, 0x13, 0x76, 0xe1, + 0x00, 0x03, 0x00, 0x26, 0x76, 0xe1, 0x00, 0x04, + 0x00, 0x25, 0x76, 0xe1, 0x00, 0x05, 0x00, 0x24, + 0x76, 0xe1, 0x00, 0x06, 0x00, 0x00, 0x76, 0xe1, + 0x00, 0x07, 0x00, 0x17, 0x76, 0xe1, 0x00, 0x08, + 0x00, 0x32, 0x76, 0xe1, 0x00, 0x09, 0x00, 0x31, + 0x76, 0xe1, 0x00, 0x0a, 0x00, 0x30, 0x8a, 0x11, + 0xf4, 0x95, 0xf4, 0xe4, 0x4a, 0x11, 0x4a, 0x16, + 0x4a, 0x17, 0xee, 0xff, 0xf4, 0x95, 0x71, 0x06, + 0x00, 0x17, 0xfb, 0x80, 0x16, 0xa2, 0x88, 0x11, + 0xf4, 0x95, 0xf7, 0xb8, 0x10, 0xf8, 0x00, 0x11, + 0xf0, 0x10, 0xff, 0xff, 0xfa, 0x45, 0x19, 0x73, + 0x77, 0x16, 0xff, 0xff, 0x77, 0x12, 0x00, 0x7b, + 0x77, 0x0e, 0x00, 0x05, 0x10, 0x82, 0x28, 0xf8, + 0x00, 0x11, 0xf0, 0x00, 0x00, 0xb4, 0x88, 0x11, + 0xf4, 0x95, 0xf4, 0x95, 0x10, 0x81, 0xf8, 0x44, + 0x19, 0x84, 0xf2, 0x73, 0x19, 0x84, 0xf4, 0x95, + 0xe7, 0x16, 0x77, 0x11, 0x00, 0x7b, 0x10, 0x81, + 0xf0, 0x00, 0x00, 0xb4, 0x88, 0x11, 0xf4, 0x95, + 0x77, 0x12, 0x00, 0x02, 0x10, 0x81, 0xf8, 0x45, + 0x19, 0x6f, 0x6e, 0xea, 0xff, 0xff, 0x19, 0x7c, + 0x6d, 0xe9, 0x00, 0x05, 0x61, 0xf8, 0x00, 0x17, + 0x00, 0x01, 0xfa, 0x20, 0x19, 0x8f, 0x76, 0x86, + 0x00, 0x01, 0xfb, 0x80, 0x19, 0x97, 0xf4, 0x95, + 0x48, 0x16, 0xf9, 0x80, 0x16, 0x9a, 0xee, 0x01, + 0x8a, 0x17, 0x48, 0x16, 0x8a, 0x16, 0x8a, 0x11, + 0xf4, 0xe4, 0x4a, 0x11, 0xee, 0xff, 0xfb, 0x80, + 0x16, 0xa2, 0x88, 0x11, 0xf4, 0x95, 0x77, 0x10, + 0xff, 0xff, 0xf4, 0xa9, 0xf8, 0x30, 0x19, 0xcc, + 0x71, 0xe1, 0x00, 0x02, 0x00, 0x12, 0x69, 0x82, + 0x00, 0x10, 0x71, 0xe1, 0x00, 0x02, 0x00, 0x12, + 0x68, 0x82, 0xf7, 0xff, 0x71, 0xe1, 0x00, 0x02, + 0x00, 0x12, 0x68, 0x82, 0xfb, 0xff, 0x71, 0xe1, + 0x00, 0x02, 0x00, 0x12, 0x68, 0x82, 0xff, 0xf0, + 0x71, 0xe1, 0x00, 0x03, 0x00, 0x12, 0x76, 0x82, + 0xff, 0xff, 0x71, 0xe1, 0x00, 0x04, 0x00, 0x12, + 0x76, 0x82, 0xff, 0xff, 0x71, 0xe1, 0x00, 0x02, + 0x00, 0x12, 0x69, 0x82, 0x00, 0x20, 0x71, 0xe1, + 0x00, 0x02, 0x00, 0x11, 0xf2, 0x73, 0x19, 0xda, + 0x68, 0x81, 0xff, 0xef, 0x77, 0x11, 0x00, 0x7b, + 0x10, 0x81, 0xfb, 0x80, 0x19, 0x97, 0xf0, 0x00, + 0x00, 0xb4, 0x77, 0x11, 0x00, 0x7b, 0x10, 0x81, + 0xfb, 0x80, 0x19, 0x97, 0xf0, 0x00, 0x00, 0xb9, + 0xf9, 0x80, 0x16, 0x9a, 0xee, 0x01, 0x8a, 0x11, + 0xf4, 0xe4, 0x00, 0xa4, 0x00, 0x00, 0x19, 0xdf, + 0x00, 0x01, 0x2a, 0xe6, 0x00, 0x00, 0x00, 0x01, + 0x2a, 0xe7, 0x00, 0x00, 0x00, 0x03, 0x2a, 0x12, + 0x0c, 0x01, 0xc3, 0x4f, 0x00, 0x00, 0x00, 0x01, + 0x2a, 0x15, 0x00, 0x00, 0x00, 0x02, 0x2a, 0x16, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x2a, 0x5d, + 0x00, 0x43, 0x00, 0x6f, 0x00, 0x70, 0x00, 0x79, + 0x00, 0x72, 0x00, 0x69, 0x00, 0x67, 0x00, 0x68, + 0x00, 0x74, 0x00, 0x20, 0x00, 0x54, 0x00, 0x65, + 0x00, 0x63, 0x00, 0x68, 0x00, 0x6e, 0x00, 0x6f, + 0x00, 0x54, 0x00, 0x72, 0x00, 0x65, 0x00, 0x6e, + 0x00, 0x64, 0x00, 0x20, 0x00, 0x41, 0x00, 0x47, + 0x00, 0x00, 0x00, 0x04, 0x2a, 0x76, 0x00, 0x30, + 0x00, 0x2e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x0c, + 0x2a, 0x7a, 0x00, 0x46, 0x00, 0x65, 0x00, 0x62, + 0x00, 0x20, 0x00, 0x32, 0x00, 0x37, 0x00, 0x20, + 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, 0x31, + 0x00, 0x00, 0x00, 0x09, 0x2a, 0x86, 0x00, 0x31, + 0x00, 0x34, 0x00, 0x3a, 0x00, 0x33, 0x00, 0x35, + 0x00, 0x3a, 0x00, 0x33, 0x00, 0x33, 0x00, 0x00, + 0x00, 0x0f, 0x2a, 0x8f, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x2a, 0x9e, 0x00, 0x00, + 0x00, 0x01, 0x2a, 0x9f, 0x00, 0x00, 0x00, 0x01, + 0x2a, 0xa0, 0x00, 0x00, 0x00, 0x01, 0x2a, 0xa1, + 0x00, 0x00, 0x00, 0x01, 0x2a, 0xa2, 0x00, 0x00, + 0x00, 0x01, 0x29, 0x7e, 0x00, 0x00, 0x00, 0x02, + 0x29, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x29, 0x82, 0xff, 0xff, 0x00, 0x01, 0x2a, 0xa7, + 0x00, 0x00, 0x00, 0x05, 0x2a, 0xa8, 0x71, 0x41, + 0x20, 0x00, 0x20, 0x00, 0x00, 0x23, 0x04, 0x00, + 0x00, 0x0a, 0x2a, 0xad, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0f, 0x2a, 0xb7, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x40, 0x00, 0xa0, 0x82, 0x40, + 0x00, 0x08, 0x30, 0x7f, 0x00, 0x80, 0x01, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x27, 0x6e, 0x00, 0x00, + 0x00, 0x01, 0x27, 0x6f, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x09, 0x00, 0x00, 0x1a, 0x83, 0x04, 0xe8, + 0x04, 0xcf, 0x04, 0xc5, 0x04, 0xba, 0x04, 0xb0, + 0x04, 0xac, 0x04, 0x9c, 0x04, 0x8c, 0x04, 0x81, + 0x00, 0x78, 0x00, 0x00, 0x01, 0x00, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xaa, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x02, 0x23, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x05, 0xe5, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x02, 0xb5, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x0e, 0x33, 0xf4, 0x95, 0xf4, 0x95, 0xf2, 0x73, + 0x07, 0xef, 0xf4, 0x95, 0xf4, 0x95, 0x00, 0x00, +}; + diff --git a/drivers/media/dvb/ttusb-dec/Kconfig b/drivers/media/dvb/ttusb-dec/Kconfig new file mode 100644 index 000000000000..c334526af66f --- /dev/null +++ b/drivers/media/dvb/ttusb-dec/Kconfig @@ -0,0 +1,21 @@ +config DVB_TTUSB_DEC + tristate "Technotrend/Hauppauge USB DEC devices" + depends on DVB_CORE && USB + select FW_LOADER + select CRC32 + help + Support for external USB adapters designed by Technotrend and + produced by Hauppauge, shipped under the brand name 'DEC2000-t' + and 'DEC3000-s'. + + Even if these devices have a MPEG decoder built in, they transmit + only compressed MPEG data over the USB bus, so you need + an external software decoder to watch TV on your computer. + + This driver needs external firmware. Please use the commands + "<kerneldir>/Documentation/dvb/get_dvb_firmware dec2000t", + "<kerneldir>/Documentation/dvb/get_dvb_firmware dec2540t", + "<kerneldir>/Documentation/dvb/get_dvb_firmware dec3000s", + download/extract them, and then copy them to /usr/lib/hotplug/firmware. + + Say Y if you own such a device and want to use it. diff --git a/drivers/media/dvb/ttusb-dec/Makefile b/drivers/media/dvb/ttusb-dec/Makefile new file mode 100644 index 000000000000..b41bf1f06a9f --- /dev/null +++ b/drivers/media/dvb/ttusb-dec/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_DVB_TTUSB_DEC) += ttusb_dec.o ttusbdecfe.o + +EXTRA_CFLAGS = -Idrivers/media/dvb/dvb-core/ diff --git a/drivers/media/dvb/ttusb-dec/ttusb_dec.c b/drivers/media/dvb/ttusb-dec/ttusb_dec.c new file mode 100644 index 000000000000..64e771bd8907 --- /dev/null +++ b/drivers/media/dvb/ttusb-dec/ttusb_dec.c @@ -0,0 +1,1744 @@ +/* + * TTUSB DEC Driver + * + * Copyright (C) 2003-2004 Alex Woods <linux-dvb@giblets.org> + * IR support by Peter Beutner <p.beutner@gmx.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <asm/semaphore.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/usb.h> +#include <linux/version.h> +#include <linux/interrupt.h> +#include <linux/firmware.h> +#include <linux/crc32.h> +#include <linux/init.h> +#include <linux/input.h> + +#include "dmxdev.h" +#include "dvb_demux.h" +#include "dvb_filter.h" +#include "dvb_frontend.h" +#include "dvb_net.h" +#include "ttusbdecfe.h" + +static int debug; +static int output_pva; +static int enable_rc; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off)."); +module_param(output_pva, int, 0444); +MODULE_PARM_DESC(output_pva, "Output PVA from dvr device (default:off)"); +module_param(enable_rc, int, 0644); +MODULE_PARM_DESC(enable_rc, "Turn on/off IR remote control(default: off)"); + +#define dprintk if (debug) printk + +#define DRIVER_NAME "TechnoTrend/Hauppauge DEC USB" + +#define COMMAND_PIPE 0x03 +#define RESULT_PIPE 0x04 +#define IN_PIPE 0x08 +#define OUT_PIPE 0x07 +#define IRQ_PIPE 0x0A + +#define COMMAND_PACKET_SIZE 0x3c +#define ARM_PACKET_SIZE 0x1000 +#define IRQ_PACKET_SIZE 0x8 + +#define ISO_BUF_COUNT 0x04 +#define FRAMES_PER_ISO_BUF 0x04 +#define ISO_FRAME_SIZE 0x0380 + +#define MAX_PVA_LENGTH 6144 + +enum ttusb_dec_model { + TTUSB_DEC2000T, + TTUSB_DEC2540T, + TTUSB_DEC3000S +}; + +enum ttusb_dec_packet_type { + TTUSB_DEC_PACKET_PVA, + TTUSB_DEC_PACKET_SECTION, + TTUSB_DEC_PACKET_EMPTY +}; + +enum ttusb_dec_interface { + TTUSB_DEC_INTERFACE_INITIAL, + TTUSB_DEC_INTERFACE_IN, + TTUSB_DEC_INTERFACE_OUT +}; + +struct ttusb_dec { + enum ttusb_dec_model model; + char *model_name; + char *firmware_name; + int can_playback; + + /* DVB bits */ + struct dvb_adapter *adapter; + struct dmxdev dmxdev; + struct dvb_demux demux; + struct dmx_frontend frontend; + struct dvb_net dvb_net; + struct dvb_frontend* fe; + + u16 pid[DMX_PES_OTHER]; + + /* USB bits */ + struct usb_device *udev; + u8 trans_count; + unsigned int command_pipe; + unsigned int result_pipe; + unsigned int in_pipe; + unsigned int out_pipe; + unsigned int irq_pipe; + enum ttusb_dec_interface interface; + struct semaphore usb_sem; + + void *irq_buffer; + struct urb *irq_urb; + dma_addr_t irq_dma_handle; + void *iso_buffer; + dma_addr_t iso_dma_handle; + struct urb *iso_urb[ISO_BUF_COUNT]; + int iso_stream_count; + struct semaphore iso_sem; + + u8 packet[MAX_PVA_LENGTH + 4]; + enum ttusb_dec_packet_type packet_type; + int packet_state; + int packet_length; + int packet_payload_length; + u16 next_packet_id; + + int pva_stream_count; + int filter_stream_count; + + struct dvb_filter_pes2ts a_pes2ts; + struct dvb_filter_pes2ts v_pes2ts; + + u8 v_pes[16 + MAX_PVA_LENGTH]; + int v_pes_length; + int v_pes_postbytes; + + struct list_head urb_frame_list; + struct tasklet_struct urb_tasklet; + spinlock_t urb_frame_list_lock; + + struct dvb_demux_filter *audio_filter; + struct dvb_demux_filter *video_filter; + struct list_head filter_info_list; + spinlock_t filter_info_list_lock; + + struct input_dev rc_input_dev; + + int active; /* Loaded successfully */ +}; + +struct urb_frame { + u8 data[ISO_FRAME_SIZE]; + int length; + struct list_head urb_frame_list; +}; + +struct filter_info { + u8 stream_id; + struct dvb_demux_filter *filter; + struct list_head filter_info_list; +}; + +static u16 rc_keys[] = { + KEY_POWER, + KEY_MUTE, + KEY_1, + KEY_2, + KEY_3, + KEY_4, + KEY_5, + KEY_6, + KEY_7, + KEY_8, + KEY_9, + KEY_0, + KEY_CHANNELUP, + KEY_VOLUMEDOWN, + KEY_OK, + KEY_VOLUMEUP, + KEY_CHANNELDOWN, + KEY_PREVIOUS, + KEY_ESC, + KEY_RED, + KEY_GREEN, + KEY_YELLOW, + KEY_BLUE, + KEY_OPTION, + KEY_M, + KEY_RADIO +}; + +static void ttusb_dec_set_model(struct ttusb_dec *dec, + enum ttusb_dec_model model); + +static void ttusb_dec_handle_irq( struct urb *urb, struct pt_regs *regs) +{ + struct ttusb_dec * dec = urb->context; + char *buffer = dec->irq_buffer; + int retval; + + switch(urb->status) { + case 0: /*success*/ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ETIMEDOUT: + /* this urb is dead, cleanup */ + dprintk("%s:urb shutting down with status: %d\n", + __FUNCTION__, urb->status); + return; + default: + dprintk("%s:nonzero status received: %d\n", + __FUNCTION__,urb->status); + goto exit; + } + + if( (buffer[0] == 0x1) && (buffer[2] == 0x15) ) { + /* IR - Event */ + /* this is an fact a bit too simple implementation; + * the box also reports a keyrepeat signal + * (with buffer[3] == 0x40) in an intervall of ~100ms. + * But to handle this correctly we had to imlemenent some + * kind of timer which signals a 'key up' event if no + * keyrepeat signal is recieved for lets say 200ms. + * this should/could be added later ... + * for now lets report each signal as a key down and up*/ + dprintk("%s:rc signal:%d\n", __FUNCTION__, buffer[4]); + input_report_key(&dec->rc_input_dev,rc_keys[buffer[4]-1],1); + input_report_key(&dec->rc_input_dev,rc_keys[buffer[4]-1],0); + input_sync(&dec->rc_input_dev); + } + +exit: retval = usb_submit_urb(urb, GFP_ATOMIC); + if(retval) + printk("%s - usb_commit_urb failed with result: %d\n", + __FUNCTION__, retval); +} + +static u16 crc16(u16 crc, const u8 *buf, size_t len) +{ + u16 tmp; + + while (len--) { + crc ^= *buf++; + crc ^= (u8)crc >> 4; + tmp = (u8)crc; + crc ^= (tmp ^ (tmp << 1)) << 4; + } + return crc; +} + +static int ttusb_dec_send_command(struct ttusb_dec *dec, const u8 command, + int param_length, const u8 params[], + int *result_length, u8 cmd_result[]) +{ + int result, actual_len, i; + u8 *b; + + dprintk("%s\n", __FUNCTION__); + + b = kmalloc(COMMAND_PACKET_SIZE + 4, GFP_KERNEL); + if (!b) + return -ENOMEM; + + if ((result = down_interruptible(&dec->usb_sem))) { + kfree(b); + printk("%s: Failed to down usb semaphore.\n", __FUNCTION__); + return result; + } + + b[0] = 0xaa; + b[1] = ++dec->trans_count; + b[2] = command; + b[3] = param_length; + + if (params) + memcpy(&b[4], params, param_length); + + if (debug) { + printk("%s: command: ", __FUNCTION__); + for (i = 0; i < param_length + 4; i++) + printk("0x%02X ", b[i]); + printk("\n"); + } + + result = usb_bulk_msg(dec->udev, dec->command_pipe, b, + COMMAND_PACKET_SIZE + 4, &actual_len, 1000); + + if (result) { + printk("%s: command bulk message failed: error %d\n", + __FUNCTION__, result); + up(&dec->usb_sem); + kfree(b); + return result; + } + + result = usb_bulk_msg(dec->udev, dec->result_pipe, b, + COMMAND_PACKET_SIZE + 4, &actual_len, 1000); + + if (result) { + printk("%s: result bulk message failed: error %d\n", + __FUNCTION__, result); + up(&dec->usb_sem); + kfree(b); + return result; + } else { + if (debug) { + printk("%s: result: ", __FUNCTION__); + for (i = 0; i < actual_len; i++) + printk("0x%02X ", b[i]); + printk("\n"); + } + + if (result_length) + *result_length = b[3]; + if (cmd_result && b[3] > 0) + memcpy(cmd_result, &b[4], b[3]); + + up(&dec->usb_sem); + + kfree(b); + return 0; + } +} + +static int ttusb_dec_get_stb_state (struct ttusb_dec *dec, unsigned int *mode, + unsigned int *model, unsigned int *version) +{ + u8 c[COMMAND_PACKET_SIZE]; + int c_length; + int result; + unsigned int tmp; + + dprintk("%s\n", __FUNCTION__); + + result = ttusb_dec_send_command(dec, 0x08, 0, NULL, &c_length, c); + if (result) + return result; + + if (c_length >= 0x0c) { + if (mode != NULL) { + memcpy(&tmp, c, 4); + *mode = ntohl(tmp); + } + if (model != NULL) { + memcpy(&tmp, &c[4], 4); + *model = ntohl(tmp); + } + if (version != NULL) { + memcpy(&tmp, &c[8], 4); + *version = ntohl(tmp); + } + return 0; + } else { + return -1; + } +} + +static int ttusb_dec_audio_pes2ts_cb(void *priv, unsigned char *data) +{ + struct ttusb_dec *dec = (struct ttusb_dec *)priv; + + dec->audio_filter->feed->cb.ts(data, 188, NULL, 0, + &dec->audio_filter->feed->feed.ts, + DMX_OK); + + return 0; +} + +static int ttusb_dec_video_pes2ts_cb(void *priv, unsigned char *data) +{ + struct ttusb_dec *dec = (struct ttusb_dec *)priv; + + dec->video_filter->feed->cb.ts(data, 188, NULL, 0, + &dec->video_filter->feed->feed.ts, + DMX_OK); + + return 0; +} + +static void ttusb_dec_set_pids(struct ttusb_dec *dec) +{ + u8 b[] = { 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff }; + + u16 pcr = htons(dec->pid[DMX_PES_PCR]); + u16 audio = htons(dec->pid[DMX_PES_AUDIO]); + u16 video = htons(dec->pid[DMX_PES_VIDEO]); + + dprintk("%s\n", __FUNCTION__); + + memcpy(&b[0], &pcr, 2); + memcpy(&b[2], &audio, 2); + memcpy(&b[4], &video, 2); + + ttusb_dec_send_command(dec, 0x50, sizeof(b), b, NULL, NULL); + + dvb_filter_pes2ts_init(&dec->a_pes2ts, dec->pid[DMX_PES_AUDIO], + ttusb_dec_audio_pes2ts_cb, dec); + dvb_filter_pes2ts_init(&dec->v_pes2ts, dec->pid[DMX_PES_VIDEO], + ttusb_dec_video_pes2ts_cb, dec); + dec->v_pes_length = 0; + dec->v_pes_postbytes = 0; +} + +static void ttusb_dec_process_pva(struct ttusb_dec *dec, u8 *pva, int length) +{ + if (length < 8) { + printk("%s: packet too short - discarding\n", __FUNCTION__); + return; + } + + if (length > 8 + MAX_PVA_LENGTH) { + printk("%s: packet too long - discarding\n", __FUNCTION__); + return; + } + + switch (pva[2]) { + + case 0x01: { /* VideoStream */ + int prebytes = pva[5] & 0x03; + int postbytes = (pva[5] & 0x0c) >> 2; + u16 v_pes_payload_length; + + if (output_pva) { + dec->video_filter->feed->cb.ts(pva, length, NULL, 0, + &dec->video_filter->feed->feed.ts, DMX_OK); + return; + } + + if (dec->v_pes_postbytes > 0 && + dec->v_pes_postbytes == prebytes) { + memcpy(&dec->v_pes[dec->v_pes_length], + &pva[12], prebytes); + + dvb_filter_pes2ts(&dec->v_pes2ts, dec->v_pes, + dec->v_pes_length + prebytes, 1); + } + + if (pva[5] & 0x10) { + dec->v_pes[7] = 0x80; + dec->v_pes[8] = 0x05; + + dec->v_pes[9] = 0x21 | ((pva[8] & 0xc0) >> 5); + dec->v_pes[10] = ((pva[8] & 0x3f) << 2) | + ((pva[9] & 0xc0) >> 6); + dec->v_pes[11] = 0x01 | + ((pva[9] & 0x3f) << 2) | + ((pva[10] & 0x80) >> 6); + dec->v_pes[12] = ((pva[10] & 0x7f) << 1) | + ((pva[11] & 0xc0) >> 7); + dec->v_pes[13] = 0x01 | ((pva[11] & 0x7f) << 1); + + memcpy(&dec->v_pes[14], &pva[12 + prebytes], + length - 12 - prebytes); + dec->v_pes_length = 14 + length - 12 - prebytes; + } else { + dec->v_pes[7] = 0x00; + dec->v_pes[8] = 0x00; + + memcpy(&dec->v_pes[9], &pva[8], length - 8); + dec->v_pes_length = 9 + length - 8; + } + + dec->v_pes_postbytes = postbytes; + + if (dec->v_pes[9 + dec->v_pes[8]] == 0x00 && + dec->v_pes[10 + dec->v_pes[8]] == 0x00 && + dec->v_pes[11 + dec->v_pes[8]] == 0x01) + dec->v_pes[6] = 0x84; + else + dec->v_pes[6] = 0x80; + + v_pes_payload_length = htons(dec->v_pes_length - 6 + + postbytes); + memcpy(&dec->v_pes[4], &v_pes_payload_length, 2); + + if (postbytes == 0) + dvb_filter_pes2ts(&dec->v_pes2ts, dec->v_pes, + dec->v_pes_length, 1); + + break; + } + + case 0x02: /* MainAudioStream */ + if (output_pva) { + dec->audio_filter->feed->cb.ts(pva, length, NULL, 0, + &dec->audio_filter->feed->feed.ts, DMX_OK); + return; + } + + dvb_filter_pes2ts(&dec->a_pes2ts, &pva[8], length - 8, + pva[5] & 0x10); + break; + + default: + printk("%s: unknown PVA type: %02x.\n", __FUNCTION__, + pva[2]); + break; + } +} + +static void ttusb_dec_process_filter(struct ttusb_dec *dec, u8 *packet, + int length) +{ + struct list_head *item; + struct filter_info *finfo; + struct dvb_demux_filter *filter = NULL; + unsigned long flags; + u8 sid; + + sid = packet[1]; + spin_lock_irqsave(&dec->filter_info_list_lock, flags); + for (item = dec->filter_info_list.next; item != &dec->filter_info_list; + item = item->next) { + finfo = list_entry(item, struct filter_info, filter_info_list); + if (finfo->stream_id == sid) { + filter = finfo->filter; + break; + } + } + spin_unlock_irqrestore(&dec->filter_info_list_lock, flags); + + if (filter) + filter->feed->cb.sec(&packet[2], length - 2, NULL, 0, + &filter->filter, DMX_OK); +} + +static void ttusb_dec_process_packet(struct ttusb_dec *dec) +{ + int i; + u16 csum = 0; + u16 packet_id; + + if (dec->packet_length % 2) { + printk("%s: odd sized packet - discarding\n", __FUNCTION__); + return; + } + + for (i = 0; i < dec->packet_length; i += 2) + csum ^= ((dec->packet[i] << 8) + dec->packet[i + 1]); + + if (csum) { + printk("%s: checksum failed - discarding\n", __FUNCTION__); + return; + } + + packet_id = dec->packet[dec->packet_length - 4] << 8; + packet_id += dec->packet[dec->packet_length - 3]; + + if ((packet_id != dec->next_packet_id) && dec->next_packet_id) { + printk("%s: warning: lost packets between %u and %u\n", + __FUNCTION__, dec->next_packet_id - 1, packet_id); + } + + if (packet_id == 0xffff) + dec->next_packet_id = 0x8000; + else + dec->next_packet_id = packet_id + 1; + + switch (dec->packet_type) { + case TTUSB_DEC_PACKET_PVA: + if (dec->pva_stream_count) + ttusb_dec_process_pva(dec, dec->packet, + dec->packet_payload_length); + break; + + case TTUSB_DEC_PACKET_SECTION: + if (dec->filter_stream_count) + ttusb_dec_process_filter(dec, dec->packet, + dec->packet_payload_length); + break; + + case TTUSB_DEC_PACKET_EMPTY: + break; + } +} + +static void swap_bytes(u8 *b, int length) +{ + u8 c; + + length -= length % 2; + for (; length; b += 2, length -= 2) { + c = *b; + *b = *(b + 1); + *(b + 1) = c; + } +} + +static void ttusb_dec_process_urb_frame(struct ttusb_dec *dec, u8 *b, + int length) +{ + swap_bytes(b, length); + + while (length) { + switch (dec->packet_state) { + + case 0: + case 1: + case 2: + if (*b++ == 0xaa) + dec->packet_state++; + else + dec->packet_state = 0; + + length--; + break; + + case 3: + if (*b == 0x00) { + dec->packet_state++; + dec->packet_length = 0; + } else if (*b != 0xaa) { + dec->packet_state = 0; + } + + b++; + length--; + break; + + case 4: + dec->packet[dec->packet_length++] = *b++; + + if (dec->packet_length == 2) { + if (dec->packet[0] == 'A' && + dec->packet[1] == 'V') { + dec->packet_type = + TTUSB_DEC_PACKET_PVA; + dec->packet_state++; + } else if (dec->packet[0] == 'S') { + dec->packet_type = + TTUSB_DEC_PACKET_SECTION; + dec->packet_state++; + } else if (dec->packet[0] == 0x00) { + dec->packet_type = + TTUSB_DEC_PACKET_EMPTY; + dec->packet_payload_length = 2; + dec->packet_state = 7; + } else { + printk("%s: unknown packet type: " + "%02x%02x\n", __FUNCTION__, + dec->packet[0], dec->packet[1]); + dec->packet_state = 0; + } + } + + length--; + break; + + case 5: + dec->packet[dec->packet_length++] = *b++; + + if (dec->packet_type == TTUSB_DEC_PACKET_PVA && + dec->packet_length == 8) { + dec->packet_state++; + dec->packet_payload_length = 8 + + (dec->packet[6] << 8) + + dec->packet[7]; + } else if (dec->packet_type == + TTUSB_DEC_PACKET_SECTION && + dec->packet_length == 5) { + dec->packet_state++; + dec->packet_payload_length = 5 + + ((dec->packet[3] & 0x0f) << 8) + + dec->packet[4]; + } + + length--; + break; + + case 6: { + int remainder = dec->packet_payload_length - + dec->packet_length; + + if (length >= remainder) { + memcpy(dec->packet + dec->packet_length, + b, remainder); + dec->packet_length += remainder; + b += remainder; + length -= remainder; + dec->packet_state++; + } else { + memcpy(&dec->packet[dec->packet_length], + b, length); + dec->packet_length += length; + length = 0; + } + + break; + } + + case 7: { + int tail = 4; + + dec->packet[dec->packet_length++] = *b++; + + if (dec->packet_type == TTUSB_DEC_PACKET_SECTION && + dec->packet_payload_length % 2) + tail++; + + if (dec->packet_length == + dec->packet_payload_length + tail) { + ttusb_dec_process_packet(dec); + dec->packet_state = 0; + } + + length--; + break; + } + + default: + printk("%s: illegal packet state encountered.\n", + __FUNCTION__); + dec->packet_state = 0; + } + } +} + +static void ttusb_dec_process_urb_frame_list(unsigned long data) +{ + struct ttusb_dec *dec = (struct ttusb_dec *)data; + struct list_head *item; + struct urb_frame *frame; + unsigned long flags; + + while (1) { + spin_lock_irqsave(&dec->urb_frame_list_lock, flags); + if ((item = dec->urb_frame_list.next) != &dec->urb_frame_list) { + frame = list_entry(item, struct urb_frame, + urb_frame_list); + list_del(&frame->urb_frame_list); + } else { + spin_unlock_irqrestore(&dec->urb_frame_list_lock, + flags); + return; + } + spin_unlock_irqrestore(&dec->urb_frame_list_lock, flags); + + ttusb_dec_process_urb_frame(dec, frame->data, frame->length); + kfree(frame); + } +} + +static void ttusb_dec_process_urb(struct urb *urb, struct pt_regs *ptregs) +{ + struct ttusb_dec *dec = urb->context; + + if (!urb->status) { + int i; + + for (i = 0; i < FRAMES_PER_ISO_BUF; i++) { + struct usb_iso_packet_descriptor *d; + u8 *b; + int length; + struct urb_frame *frame; + + d = &urb->iso_frame_desc[i]; + b = urb->transfer_buffer + d->offset; + length = d->actual_length; + + if ((frame = kmalloc(sizeof(struct urb_frame), + GFP_ATOMIC))) { + unsigned long flags; + + memcpy(frame->data, b, length); + frame->length = length; + + spin_lock_irqsave(&dec->urb_frame_list_lock, + flags); + list_add_tail(&frame->urb_frame_list, + &dec->urb_frame_list); + spin_unlock_irqrestore(&dec->urb_frame_list_lock, + flags); + + tasklet_schedule(&dec->urb_tasklet); + } + } + } else { + /* -ENOENT is expected when unlinking urbs */ + if (urb->status != -ENOENT) + dprintk("%s: urb error: %d\n", __FUNCTION__, + urb->status); + } + + if (dec->iso_stream_count) + usb_submit_urb(urb, GFP_ATOMIC); +} + +static void ttusb_dec_setup_urbs(struct ttusb_dec *dec) +{ + int i, j, buffer_offset = 0; + + dprintk("%s\n", __FUNCTION__); + + for (i = 0; i < ISO_BUF_COUNT; i++) { + int frame_offset = 0; + struct urb *urb = dec->iso_urb[i]; + + urb->dev = dec->udev; + urb->context = dec; + urb->complete = ttusb_dec_process_urb; + urb->pipe = dec->in_pipe; + urb->transfer_flags = URB_ISO_ASAP; + urb->interval = 1; + urb->number_of_packets = FRAMES_PER_ISO_BUF; + urb->transfer_buffer_length = ISO_FRAME_SIZE * + FRAMES_PER_ISO_BUF; + urb->transfer_buffer = dec->iso_buffer + buffer_offset; + buffer_offset += ISO_FRAME_SIZE * FRAMES_PER_ISO_BUF; + + for (j = 0; j < FRAMES_PER_ISO_BUF; j++) { + urb->iso_frame_desc[j].offset = frame_offset; + urb->iso_frame_desc[j].length = ISO_FRAME_SIZE; + frame_offset += ISO_FRAME_SIZE; + } + } +} + +static void ttusb_dec_stop_iso_xfer(struct ttusb_dec *dec) +{ + int i; + + dprintk("%s\n", __FUNCTION__); + + if (down_interruptible(&dec->iso_sem)) + return; + + dec->iso_stream_count--; + + if (!dec->iso_stream_count) { + for (i = 0; i < ISO_BUF_COUNT; i++) + usb_kill_urb(dec->iso_urb[i]); + } + + up(&dec->iso_sem); +} + +/* Setting the interface of the DEC tends to take down the USB communications + * for a short period, so it's important not to call this function just before + * trying to talk to it. + */ +static int ttusb_dec_set_interface(struct ttusb_dec *dec, + enum ttusb_dec_interface interface) +{ + int result = 0; + u8 b[] = { 0x05 }; + + if (interface != dec->interface) { + switch (interface) { + case TTUSB_DEC_INTERFACE_INITIAL: + result = usb_set_interface(dec->udev, 0, 0); + break; + case TTUSB_DEC_INTERFACE_IN: + result = ttusb_dec_send_command(dec, 0x80, sizeof(b), + b, NULL, NULL); + if (result) + return result; + result = usb_set_interface(dec->udev, 0, 8); + break; + case TTUSB_DEC_INTERFACE_OUT: + result = usb_set_interface(dec->udev, 0, 1); + break; + } + + if (result) + return result; + + dec->interface = interface; + } + + return 0; +} + +static int ttusb_dec_start_iso_xfer(struct ttusb_dec *dec) +{ + int i, result; + + dprintk("%s\n", __FUNCTION__); + + if (down_interruptible(&dec->iso_sem)) + return -EAGAIN; + + if (!dec->iso_stream_count) { + ttusb_dec_setup_urbs(dec); + + dec->packet_state = 0; + dec->v_pes_postbytes = 0; + dec->next_packet_id = 0; + + for (i = 0; i < ISO_BUF_COUNT; i++) { + if ((result = usb_submit_urb(dec->iso_urb[i], + GFP_ATOMIC))) { + printk("%s: failed urb submission %d: " + "error %d\n", __FUNCTION__, i, result); + + while (i) { + usb_kill_urb(dec->iso_urb[i - 1]); + i--; + } + + up(&dec->iso_sem); + return result; + } + } + } + + dec->iso_stream_count++; + + up(&dec->iso_sem); + + return 0; +} + +static int ttusb_dec_start_ts_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + struct ttusb_dec *dec = dvbdmx->priv; + u8 b0[] = { 0x05 }; + int result = 0; + + dprintk("%s\n", __FUNCTION__); + + dprintk(" ts_type:"); + + if (dvbdmxfeed->ts_type & TS_DECODER) + dprintk(" TS_DECODER"); + + if (dvbdmxfeed->ts_type & TS_PACKET) + dprintk(" TS_PACKET"); + + if (dvbdmxfeed->ts_type & TS_PAYLOAD_ONLY) + dprintk(" TS_PAYLOAD_ONLY"); + + dprintk("\n"); + + switch (dvbdmxfeed->pes_type) { + + case DMX_TS_PES_VIDEO: + dprintk(" pes_type: DMX_TS_PES_VIDEO\n"); + dec->pid[DMX_PES_PCR] = dvbdmxfeed->pid; + dec->pid[DMX_PES_VIDEO] = dvbdmxfeed->pid; + dec->video_filter = dvbdmxfeed->filter; + ttusb_dec_set_pids(dec); + break; + + case DMX_TS_PES_AUDIO: + dprintk(" pes_type: DMX_TS_PES_AUDIO\n"); + dec->pid[DMX_PES_AUDIO] = dvbdmxfeed->pid; + dec->audio_filter = dvbdmxfeed->filter; + ttusb_dec_set_pids(dec); + break; + + case DMX_TS_PES_TELETEXT: + dec->pid[DMX_PES_TELETEXT] = dvbdmxfeed->pid; + dprintk(" pes_type: DMX_TS_PES_TELETEXT\n"); + break; + + case DMX_TS_PES_PCR: + dprintk(" pes_type: DMX_TS_PES_PCR\n"); + dec->pid[DMX_PES_PCR] = dvbdmxfeed->pid; + ttusb_dec_set_pids(dec); + break; + + case DMX_TS_PES_OTHER: + dprintk(" pes_type: DMX_TS_PES_OTHER\n"); + break; + + default: + dprintk(" pes_type: unknown (%d)\n", dvbdmxfeed->pes_type); + return -EINVAL; + + } + + result = ttusb_dec_send_command(dec, 0x80, sizeof(b0), b0, NULL, NULL); + if (result) + return result; + + dec->pva_stream_count++; + return ttusb_dec_start_iso_xfer(dec); +} + +static int ttusb_dec_start_sec_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct ttusb_dec *dec = dvbdmxfeed->demux->priv; + u8 b0[] = { 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00 }; + u16 pid; + u8 c[COMMAND_PACKET_SIZE]; + int c_length; + int result; + struct filter_info *finfo; + unsigned long flags; + u8 x = 1; + + dprintk("%s\n", __FUNCTION__); + + pid = htons(dvbdmxfeed->pid); + memcpy(&b0[0], &pid, 2); + memcpy(&b0[4], &x, 1); + memcpy(&b0[5], &dvbdmxfeed->filter->filter.filter_value[0], 1); + + result = ttusb_dec_send_command(dec, 0x60, sizeof(b0), b0, + &c_length, c); + + if (!result) { + if (c_length == 2) { + if (!(finfo = kmalloc(sizeof(struct filter_info), + GFP_ATOMIC))) + return -ENOMEM; + + finfo->stream_id = c[1]; + finfo->filter = dvbdmxfeed->filter; + + spin_lock_irqsave(&dec->filter_info_list_lock, flags); + list_add_tail(&finfo->filter_info_list, + &dec->filter_info_list); + spin_unlock_irqrestore(&dec->filter_info_list_lock, + flags); + + dvbdmxfeed->priv = finfo; + + dec->filter_stream_count++; + return ttusb_dec_start_iso_xfer(dec); + } + + return -EAGAIN; + } else + return result; +} + +static int ttusb_dec_start_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *dvbdmx = dvbdmxfeed->demux; + + dprintk("%s\n", __FUNCTION__); + + if (!dvbdmx->dmx.frontend) + return -EINVAL; + + dprintk(" pid: 0x%04X\n", dvbdmxfeed->pid); + + switch (dvbdmxfeed->type) { + + case DMX_TYPE_TS: + return ttusb_dec_start_ts_feed(dvbdmxfeed); + break; + + case DMX_TYPE_SEC: + return ttusb_dec_start_sec_feed(dvbdmxfeed); + break; + + default: + dprintk(" type: unknown (%d)\n", dvbdmxfeed->type); + return -EINVAL; + + } +} + +static int ttusb_dec_stop_ts_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct ttusb_dec *dec = dvbdmxfeed->demux->priv; + u8 b0[] = { 0x00 }; + + ttusb_dec_send_command(dec, 0x81, sizeof(b0), b0, NULL, NULL); + + dec->pva_stream_count--; + + ttusb_dec_stop_iso_xfer(dec); + + return 0; +} + +static int ttusb_dec_stop_sec_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct ttusb_dec *dec = dvbdmxfeed->demux->priv; + u8 b0[] = { 0x00, 0x00 }; + struct filter_info *finfo = (struct filter_info *)dvbdmxfeed->priv; + unsigned long flags; + + b0[1] = finfo->stream_id; + spin_lock_irqsave(&dec->filter_info_list_lock, flags); + list_del(&finfo->filter_info_list); + spin_unlock_irqrestore(&dec->filter_info_list_lock, flags); + kfree(finfo); + ttusb_dec_send_command(dec, 0x62, sizeof(b0), b0, NULL, NULL); + + dec->filter_stream_count--; + + ttusb_dec_stop_iso_xfer(dec); + + return 0; +} + +static int ttusb_dec_stop_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + dprintk("%s\n", __FUNCTION__); + + switch (dvbdmxfeed->type) { + case DMX_TYPE_TS: + return ttusb_dec_stop_ts_feed(dvbdmxfeed); + break; + + case DMX_TYPE_SEC: + return ttusb_dec_stop_sec_feed(dvbdmxfeed); + break; + } + + return 0; +} + +static void ttusb_dec_free_iso_urbs(struct ttusb_dec *dec) +{ + int i; + + dprintk("%s\n", __FUNCTION__); + + for (i = 0; i < ISO_BUF_COUNT; i++) + if (dec->iso_urb[i]) + usb_free_urb(dec->iso_urb[i]); + + pci_free_consistent(NULL, + ISO_FRAME_SIZE * (FRAMES_PER_ISO_BUF * + ISO_BUF_COUNT), + dec->iso_buffer, dec->iso_dma_handle); +} + +static int ttusb_dec_alloc_iso_urbs(struct ttusb_dec *dec) +{ + int i; + + dprintk("%s\n", __FUNCTION__); + + dec->iso_buffer = pci_alloc_consistent(NULL, + ISO_FRAME_SIZE * + (FRAMES_PER_ISO_BUF * + ISO_BUF_COUNT), + &dec->iso_dma_handle); + + memset(dec->iso_buffer, 0, + ISO_FRAME_SIZE * (FRAMES_PER_ISO_BUF * ISO_BUF_COUNT)); + + for (i = 0; i < ISO_BUF_COUNT; i++) { + struct urb *urb; + + if (!(urb = usb_alloc_urb(FRAMES_PER_ISO_BUF, GFP_ATOMIC))) { + ttusb_dec_free_iso_urbs(dec); + return -ENOMEM; + } + + dec->iso_urb[i] = urb; + } + + ttusb_dec_setup_urbs(dec); + + return 0; +} + +static void ttusb_dec_init_tasklet(struct ttusb_dec *dec) +{ + spin_lock_init(&dec->urb_frame_list_lock); + INIT_LIST_HEAD(&dec->urb_frame_list); + tasklet_init(&dec->urb_tasklet, ttusb_dec_process_urb_frame_list, + (unsigned long)dec); +} + +static void ttusb_init_rc( struct ttusb_dec *dec) +{ + u8 b[] = { 0x00, 0x01 }; + int i; + + init_input_dev(&dec->rc_input_dev); + + dec->rc_input_dev.name = "ttusb_dec remote control"; + dec->rc_input_dev.evbit[0] = BIT(EV_KEY); + dec->rc_input_dev.keycodesize = sizeof(u16); + dec->rc_input_dev.keycodemax = 0x1a; + dec->rc_input_dev.keycode = rc_keys; + + for (i = 0; i < sizeof(rc_keys)/sizeof(rc_keys[0]); i++) + set_bit(rc_keys[i], dec->rc_input_dev.keybit); + + input_register_device(&dec->rc_input_dev); + + if(usb_submit_urb(dec->irq_urb,GFP_KERNEL)) { + printk("%s: usb_submit_urb failed\n",__FUNCTION__); + } + /* enable irq pipe */ + ttusb_dec_send_command(dec,0xb0,sizeof(b),b,NULL,NULL); +} + +static void ttusb_dec_init_v_pes(struct ttusb_dec *dec) +{ + dprintk("%s\n", __FUNCTION__); + + dec->v_pes[0] = 0x00; + dec->v_pes[1] = 0x00; + dec->v_pes[2] = 0x01; + dec->v_pes[3] = 0xe0; +} + +static int ttusb_dec_init_usb(struct ttusb_dec *dec) +{ + dprintk("%s\n", __FUNCTION__); + + sema_init(&dec->usb_sem, 1); + sema_init(&dec->iso_sem, 1); + + dec->command_pipe = usb_sndbulkpipe(dec->udev, COMMAND_PIPE); + dec->result_pipe = usb_rcvbulkpipe(dec->udev, RESULT_PIPE); + dec->in_pipe = usb_rcvisocpipe(dec->udev, IN_PIPE); + dec->out_pipe = usb_sndisocpipe(dec->udev, OUT_PIPE); + dec->irq_pipe = usb_rcvintpipe(dec->udev, IRQ_PIPE); + + if(enable_rc) { + dec->irq_urb = usb_alloc_urb(0, GFP_KERNEL); + if(!dec->irq_urb) { + return -ENOMEM; + } + dec->irq_buffer = usb_buffer_alloc(dec->udev,IRQ_PACKET_SIZE, + SLAB_ATOMIC, &dec->irq_dma_handle); + if(!dec->irq_buffer) { + return -ENOMEM; + } + usb_fill_int_urb(dec->irq_urb, dec->udev,dec->irq_pipe, + dec->irq_buffer, IRQ_PACKET_SIZE, + ttusb_dec_handle_irq, dec, 1); + dec->irq_urb->transfer_dma = dec->irq_dma_handle; + dec->irq_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + } + + return ttusb_dec_alloc_iso_urbs(dec); +} + +static int ttusb_dec_boot_dsp(struct ttusb_dec *dec) +{ + int i, j, actual_len, result, size, trans_count; + u8 b0[] = { 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x61, 0x00 }; + u8 b1[] = { 0x61 }; + u8 *b; + char idstring[21]; + u8 *firmware = NULL; + size_t firmware_size = 0; + u16 firmware_csum = 0; + u16 firmware_csum_ns; + u32 firmware_size_nl; + u32 crc32_csum, crc32_check, tmp; + const struct firmware *fw_entry = NULL; + + dprintk("%s\n", __FUNCTION__); + + if (request_firmware(&fw_entry, dec->firmware_name, &dec->udev->dev)) { + printk(KERN_ERR "%s: Firmware (%s) unavailable.\n", + __FUNCTION__, dec->firmware_name); + return 1; + } + + firmware = fw_entry->data; + firmware_size = fw_entry->size; + + if (firmware_size < 60) { + printk("%s: firmware size too small for DSP code (%zu < 60).\n", + __FUNCTION__, firmware_size); + return -1; + } + + /* a 32 bit checksum over the first 56 bytes of the DSP Code is stored + at offset 56 of file, so use it to check if the firmware file is + valid. */ + crc32_csum = crc32(~0L, firmware, 56) ^ ~0L; + memcpy(&tmp, &firmware[56], 4); + crc32_check = htonl(tmp); + if (crc32_csum != crc32_check) { + printk("%s: crc32 check of DSP code failed (calculated " + "0x%08x != 0x%08x in file), file invalid.\n", + __FUNCTION__, crc32_csum, crc32_check); + return -1; + } + memcpy(idstring, &firmware[36], 20); + idstring[20] = '\0'; + printk(KERN_INFO "ttusb_dec: found DSP code \"%s\".\n", idstring); + + firmware_size_nl = htonl(firmware_size); + memcpy(b0, &firmware_size_nl, 4); + firmware_csum = crc16(~0, firmware, firmware_size) ^ ~0; + firmware_csum_ns = htons(firmware_csum); + memcpy(&b0[6], &firmware_csum_ns, 2); + + result = ttusb_dec_send_command(dec, 0x41, sizeof(b0), b0, NULL, NULL); + + if (result) + return result; + + trans_count = 0; + j = 0; + + b = kmalloc(ARM_PACKET_SIZE, GFP_KERNEL); + if (b == NULL) + return -ENOMEM; + + for (i = 0; i < firmware_size; i += COMMAND_PACKET_SIZE) { + size = firmware_size - i; + if (size > COMMAND_PACKET_SIZE) + size = COMMAND_PACKET_SIZE; + + b[j + 0] = 0xaa; + b[j + 1] = trans_count++; + b[j + 2] = 0xf0; + b[j + 3] = size; + memcpy(&b[j + 4], &firmware[i], size); + + j += COMMAND_PACKET_SIZE + 4; + + if (j >= ARM_PACKET_SIZE) { + result = usb_bulk_msg(dec->udev, dec->command_pipe, b, + ARM_PACKET_SIZE, &actual_len, + 100); + j = 0; + } else if (size < COMMAND_PACKET_SIZE) { + result = usb_bulk_msg(dec->udev, dec->command_pipe, b, + j - COMMAND_PACKET_SIZE + size, + &actual_len, 100); + } + } + + result = ttusb_dec_send_command(dec, 0x43, sizeof(b1), b1, NULL, NULL); + + kfree(b); + + return result; +} + +static int ttusb_dec_init_stb(struct ttusb_dec *dec) +{ + int result; + unsigned int mode, model, version; + + dprintk("%s\n", __FUNCTION__); + + result = ttusb_dec_get_stb_state(dec, &mode, &model, &version); + + if (!result) { + if (!mode) { + if (version == 0xABCDEFAB) + printk(KERN_INFO "ttusb_dec: no version " + "info in Firmware\n"); + else + printk(KERN_INFO "ttusb_dec: Firmware " + "%x.%02x%c%c\n", + version >> 24, (version >> 16) & 0xff, + (version >> 8) & 0xff, version & 0xff); + + result = ttusb_dec_boot_dsp(dec); + if (result) + return result; + else + return 1; + } else { + /* We can't trust the USB IDs that some firmwares + give the box */ + switch (model) { + case 0x00070008: + case 0x0007000c: + ttusb_dec_set_model(dec, TTUSB_DEC3000S); + break; + case 0x00070009: + case 0x00070013: + ttusb_dec_set_model(dec, TTUSB_DEC2000T); + break; + case 0x00070011: + ttusb_dec_set_model(dec, TTUSB_DEC2540T); + break; + default: + printk(KERN_ERR "%s: unknown model returned " + "by firmware (%08x) - please report\n", + __FUNCTION__, model); + return -1; + break; + } + + if (version >= 0x01770000) + dec->can_playback = 1; + + return 0; + } + } + else + return result; +} + +static int ttusb_dec_init_dvb(struct ttusb_dec *dec) +{ + int result; + + dprintk("%s\n", __FUNCTION__); + + if ((result = dvb_register_adapter(&dec->adapter, + dec->model_name, THIS_MODULE)) < 0) { + printk("%s: dvb_register_adapter failed: error %d\n", + __FUNCTION__, result); + + return result; + } + + dec->demux.dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING; + + dec->demux.priv = (void *)dec; + dec->demux.filternum = 31; + dec->demux.feednum = 31; + dec->demux.start_feed = ttusb_dec_start_feed; + dec->demux.stop_feed = ttusb_dec_stop_feed; + dec->demux.write_to_decoder = NULL; + + if ((result = dvb_dmx_init(&dec->demux)) < 0) { + printk("%s: dvb_dmx_init failed: error %d\n", __FUNCTION__, + result); + + dvb_unregister_adapter(dec->adapter); + + return result; + } + + dec->dmxdev.filternum = 32; + dec->dmxdev.demux = &dec->demux.dmx; + dec->dmxdev.capabilities = 0; + + if ((result = dvb_dmxdev_init(&dec->dmxdev, dec->adapter)) < 0) { + printk("%s: dvb_dmxdev_init failed: error %d\n", + __FUNCTION__, result); + + dvb_dmx_release(&dec->demux); + dvb_unregister_adapter(dec->adapter); + + return result; + } + + dec->frontend.source = DMX_FRONTEND_0; + + if ((result = dec->demux.dmx.add_frontend(&dec->demux.dmx, + &dec->frontend)) < 0) { + printk("%s: dvb_dmx_init failed: error %d\n", __FUNCTION__, + result); + + dvb_dmxdev_release(&dec->dmxdev); + dvb_dmx_release(&dec->demux); + dvb_unregister_adapter(dec->adapter); + + return result; + } + + if ((result = dec->demux.dmx.connect_frontend(&dec->demux.dmx, + &dec->frontend)) < 0) { + printk("%s: dvb_dmx_init failed: error %d\n", __FUNCTION__, + result); + + dec->demux.dmx.remove_frontend(&dec->demux.dmx, &dec->frontend); + dvb_dmxdev_release(&dec->dmxdev); + dvb_dmx_release(&dec->demux); + dvb_unregister_adapter(dec->adapter); + + return result; + } + + dvb_net_init(dec->adapter, &dec->dvb_net, &dec->demux.dmx); + + return 0; +} + +static void ttusb_dec_exit_dvb(struct ttusb_dec *dec) +{ + dprintk("%s\n", __FUNCTION__); + + dvb_net_release(&dec->dvb_net); + dec->demux.dmx.close(&dec->demux.dmx); + dec->demux.dmx.remove_frontend(&dec->demux.dmx, &dec->frontend); + dvb_dmxdev_release(&dec->dmxdev); + dvb_dmx_release(&dec->demux); + if (dec->fe) dvb_unregister_frontend(dec->fe); + dvb_unregister_adapter(dec->adapter); +} + +static void ttusb_dec_exit_rc(struct ttusb_dec *dec) +{ + + dprintk("%s\n", __FUNCTION__); + /* we have to check whether the irq URB is already submitted. + * As the irq is submitted after the interface is changed, + * this is the best method i figured out. + * Any others?*/ + if(dec->interface == TTUSB_DEC_INTERFACE_IN) + usb_kill_urb(dec->irq_urb); + + usb_free_urb(dec->irq_urb); + + usb_buffer_free(dec->udev,IRQ_PACKET_SIZE, + dec->irq_buffer, dec->irq_dma_handle); + + input_unregister_device(&dec->rc_input_dev); +} + + +static void ttusb_dec_exit_usb(struct ttusb_dec *dec) +{ + int i; + + dprintk("%s\n", __FUNCTION__); + + dec->iso_stream_count = 0; + + for (i = 0; i < ISO_BUF_COUNT; i++) + usb_kill_urb(dec->iso_urb[i]); + + ttusb_dec_free_iso_urbs(dec); +} + +static void ttusb_dec_exit_tasklet(struct ttusb_dec *dec) +{ + struct list_head *item; + struct urb_frame *frame; + + tasklet_kill(&dec->urb_tasklet); + + while ((item = dec->urb_frame_list.next) != &dec->urb_frame_list) { + frame = list_entry(item, struct urb_frame, urb_frame_list); + list_del(&frame->urb_frame_list); + kfree(frame); + } +} + +static void ttusb_dec_init_filters(struct ttusb_dec *dec) +{ + INIT_LIST_HEAD(&dec->filter_info_list); + spin_lock_init(&dec->filter_info_list_lock); +} + +static void ttusb_dec_exit_filters(struct ttusb_dec *dec) +{ + struct list_head *item; + struct filter_info *finfo; + + while ((item = dec->filter_info_list.next) != &dec->filter_info_list) { + finfo = list_entry(item, struct filter_info, filter_info_list); + list_del(&finfo->filter_info_list); + kfree(finfo); + } +} + +int fe_send_command(struct dvb_frontend* fe, const u8 command, + int param_length, const u8 params[], + int *result_length, u8 cmd_result[]) +{ + struct ttusb_dec* dec = (struct ttusb_dec*) fe->dvb->priv; + return ttusb_dec_send_command(dec, command, param_length, params, result_length, cmd_result); +} + +struct ttusbdecfe_config fe_config = { + .send_command = fe_send_command +}; + +static int ttusb_dec_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev; + struct ttusb_dec *dec; + + dprintk("%s\n", __FUNCTION__); + + udev = interface_to_usbdev(intf); + + if (!(dec = kmalloc(sizeof(struct ttusb_dec), GFP_KERNEL))) { + printk("%s: couldn't allocate memory.\n", __FUNCTION__); + return -ENOMEM; + } + + usb_set_intfdata(intf, (void *)dec); + + memset(dec, 0, sizeof(struct ttusb_dec)); + + switch (le16_to_cpu(id->idProduct)) { + case 0x1006: + ttusb_dec_set_model(dec, TTUSB_DEC3000S); + break; + + case 0x1008: + ttusb_dec_set_model(dec, TTUSB_DEC2000T); + break; + + case 0x1009: + ttusb_dec_set_model(dec, TTUSB_DEC2540T); + break; + } + + dec->udev = udev; + + if (ttusb_dec_init_usb(dec)) + return 0; + if (ttusb_dec_init_stb(dec)) { + ttusb_dec_exit_usb(dec); + return 0; + } + ttusb_dec_init_dvb(dec); + + dec->adapter->priv = dec; + switch (le16_to_cpu(id->idProduct)) { + case 0x1006: + dec->fe = ttusbdecfe_dvbs_attach(&fe_config); + break; + + case 0x1008: + case 0x1009: + dec->fe = ttusbdecfe_dvbt_attach(&fe_config); + break; + } + + if (dec->fe == NULL) { + printk("dvb-ttusb-dec: A frontend driver was not found for device %04x/%04x\n", + le16_to_cpu(dec->udev->descriptor.idVendor), + le16_to_cpu(dec->udev->descriptor.idProduct)); + } else { + if (dvb_register_frontend(dec->adapter, dec->fe)) { + printk("budget-ci: Frontend registration failed!\n"); + if (dec->fe->ops->release) + dec->fe->ops->release(dec->fe); + dec->fe = NULL; + } + } + + ttusb_dec_init_v_pes(dec); + ttusb_dec_init_filters(dec); + ttusb_dec_init_tasklet(dec); + + dec->active = 1; + + ttusb_dec_set_interface(dec, TTUSB_DEC_INTERFACE_IN); + + if(enable_rc) + ttusb_init_rc(dec); + + return 0; +} + +static void ttusb_dec_disconnect(struct usb_interface *intf) +{ + struct ttusb_dec *dec = usb_get_intfdata(intf); + + usb_set_intfdata(intf, NULL); + + dprintk("%s\n", __FUNCTION__); + + if (dec->active) { + ttusb_dec_exit_tasklet(dec); + ttusb_dec_exit_filters(dec); + if(enable_rc) + ttusb_dec_exit_rc(dec); + ttusb_dec_exit_usb(dec); + ttusb_dec_exit_dvb(dec); + } + + kfree(dec); +} + +static void ttusb_dec_set_model(struct ttusb_dec *dec, + enum ttusb_dec_model model) +{ + dec->model = model; + + switch (model) { + case TTUSB_DEC2000T: + dec->model_name = "DEC2000-t"; + dec->firmware_name = "dvb-ttusb-dec-2000t.fw"; + break; + + case TTUSB_DEC2540T: + dec->model_name = "DEC2540-t"; + dec->firmware_name = "dvb-ttusb-dec-2540t.fw"; + break; + + case TTUSB_DEC3000S: + dec->model_name = "DEC3000-s"; + dec->firmware_name = "dvb-ttusb-dec-3000s.fw"; + break; + } +} + +static struct usb_device_id ttusb_dec_table[] = { + {USB_DEVICE(0x0b48, 0x1006)}, /* DEC3000-s */ + /*{USB_DEVICE(0x0b48, 0x1007)}, Unconfirmed */ + {USB_DEVICE(0x0b48, 0x1008)}, /* DEC2000-t */ + {USB_DEVICE(0x0b48, 0x1009)}, /* DEC2540-t */ + {} +}; + +static struct usb_driver ttusb_dec_driver = { + .name = "ttusb-dec", + .probe = ttusb_dec_probe, + .disconnect = ttusb_dec_disconnect, + .id_table = ttusb_dec_table, +}; + +static int __init ttusb_dec_init(void) +{ + int result; + + if ((result = usb_register(&ttusb_dec_driver)) < 0) { + printk("%s: initialisation failed: error %d.\n", __FUNCTION__, + result); + return result; + } + + return 0; +} + +static void __exit ttusb_dec_exit(void) +{ + usb_deregister(&ttusb_dec_driver); +} + +module_init(ttusb_dec_init); +module_exit(ttusb_dec_exit); + +MODULE_AUTHOR("Alex Woods <linux-dvb@giblets.org>"); +MODULE_DESCRIPTION(DRIVER_NAME); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(usb, ttusb_dec_table); diff --git a/drivers/media/dvb/ttusb-dec/ttusbdecfe.c b/drivers/media/dvb/ttusb-dec/ttusbdecfe.c new file mode 100644 index 000000000000..1699cc9f6bb0 --- /dev/null +++ b/drivers/media/dvb/ttusb-dec/ttusbdecfe.c @@ -0,0 +1,255 @@ +/* + * TTUSB DEC Frontend Driver + * + * Copyright (C) 2003-2004 Alex Woods <linux-dvb@giblets.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "dvb_frontend.h" +#include "ttusbdecfe.h" + + +#define LOF_HI 10600000 +#define LOF_LO 9750000 + +struct ttusbdecfe_state { + + struct dvb_frontend_ops ops; + + /* configuration settings */ + const struct ttusbdecfe_config* config; + + struct dvb_frontend frontend; + + u8 hi_band; + u8 voltage; +}; + + +static int ttusbdecfe_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + *status = FE_HAS_SIGNAL | FE_HAS_VITERBI | + FE_HAS_SYNC | FE_HAS_CARRIER | FE_HAS_LOCK; + + return 0; +} + +static int ttusbdecfe_dvbt_set_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + struct ttusbdecfe_state* state = (struct ttusbdecfe_state*) fe->demodulator_priv; + u8 b[] = { 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff }; + + u32 freq = htonl(p->frequency / 1000); + memcpy(&b[4], &freq, sizeof (u32)); + state->config->send_command(fe, 0x71, sizeof(b), b, NULL, NULL); + + return 0; +} + +static int ttusbdecfe_dvbs_set_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + struct ttusbdecfe_state* state = (struct ttusbdecfe_state*) fe->demodulator_priv; + + u8 b[] = { 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 }; + u32 freq; + u32 sym_rate; + u32 band; + u32 lnb_voltage; + + freq = htonl(p->frequency + + (state->hi_band ? LOF_HI : LOF_LO)); + memcpy(&b[4], &freq, sizeof(u32)); + sym_rate = htonl(p->u.qam.symbol_rate); + memcpy(&b[12], &sym_rate, sizeof(u32)); + band = htonl(state->hi_band ? LOF_HI : LOF_LO); + memcpy(&b[24], &band, sizeof(u32)); + lnb_voltage = htonl(state->voltage); + memcpy(&b[28], &lnb_voltage, sizeof(u32)); + + state->config->send_command(fe, 0x71, sizeof(b), b, NULL, NULL); + + return 0; +} + +static int ttusbdecfe_dvbs_diseqc_send_master_cmd(struct dvb_frontend* fe, struct dvb_diseqc_master_cmd *cmd) +{ + struct ttusbdecfe_state* state = (struct ttusbdecfe_state*) fe->demodulator_priv; + u8 b[] = { 0x00, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 }; + + memcpy(&b[4], cmd->msg, cmd->msg_len); + + state->config->send_command(fe, 0x72, + sizeof(b) - (6 - cmd->msg_len), b, + NULL, NULL); + + return 0; +} + + +static int ttusbdecfe_dvbs_set_tone(struct dvb_frontend* fe, fe_sec_tone_mode_t tone) +{ + struct ttusbdecfe_state* state = (struct ttusbdecfe_state*) fe->demodulator_priv; + + state->hi_band = (SEC_TONE_ON == tone); + + return 0; +} + + +static int ttusbdecfe_dvbs_set_voltage(struct dvb_frontend* fe, fe_sec_voltage_t voltage) +{ + struct ttusbdecfe_state* state = (struct ttusbdecfe_state*) fe->demodulator_priv; + + switch (voltage) { + case SEC_VOLTAGE_13: + state->voltage = 13; + break; + case SEC_VOLTAGE_18: + state->voltage = 18; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void ttusbdecfe_release(struct dvb_frontend* fe) +{ + struct ttusbdecfe_state* state = (struct ttusbdecfe_state*) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops ttusbdecfe_dvbt_ops; + +struct dvb_frontend* ttusbdecfe_dvbt_attach(const struct ttusbdecfe_config* config) +{ + struct ttusbdecfe_state* state = NULL; + + /* allocate memory for the internal state */ + state = (struct ttusbdecfe_state*) kmalloc(sizeof(struct ttusbdecfe_state), GFP_KERNEL); + if (state == NULL) goto error; + + /* setup the state */ + state->config = config; + memcpy(&state->ops, &ttusbdecfe_dvbt_ops, sizeof(struct dvb_frontend_ops)); + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops ttusbdecfe_dvbs_ops; + +struct dvb_frontend* ttusbdecfe_dvbs_attach(const struct ttusbdecfe_config* config) +{ + struct ttusbdecfe_state* state = NULL; + + /* allocate memory for the internal state */ + state = (struct ttusbdecfe_state*) kmalloc(sizeof(struct ttusbdecfe_state), GFP_KERNEL); + if (state == NULL) goto error; + + /* setup the state */ + state->config = config; + state->voltage = 0; + state->hi_band = 0; + memcpy(&state->ops, &ttusbdecfe_dvbs_ops, sizeof(struct dvb_frontend_ops)); + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} + +static struct dvb_frontend_ops ttusbdecfe_dvbt_ops = { + + .info = { + .name = "TechnoTrend/Hauppauge DEC2000-t Frontend", + .type = FE_OFDM, + .frequency_min = 51000000, + .frequency_max = 858000000, + .frequency_stepsize = 62500, + .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_FEC_AUTO | + FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_HIERARCHY_AUTO, + }, + + .release = ttusbdecfe_release, + + .set_frontend = ttusbdecfe_dvbt_set_frontend, + + .read_status = ttusbdecfe_read_status, +}; + +static struct dvb_frontend_ops ttusbdecfe_dvbs_ops = { + + .info = { + .name = "TechnoTrend/Hauppauge DEC3000-s Frontend", + .type = FE_QPSK, + .frequency_min = 950000, + .frequency_max = 2150000, + .frequency_stepsize = 125, + .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_FEC_AUTO | + FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_HIERARCHY_AUTO, + }, + + .release = ttusbdecfe_release, + + .set_frontend = ttusbdecfe_dvbs_set_frontend, + + .read_status = ttusbdecfe_read_status, + + .diseqc_send_master_cmd = ttusbdecfe_dvbs_diseqc_send_master_cmd, + .set_voltage = ttusbdecfe_dvbs_set_voltage, + .set_tone = ttusbdecfe_dvbs_set_tone, +}; + +MODULE_DESCRIPTION("TTUSB DEC DVB-T/S Demodulator driver"); +MODULE_AUTHOR("Alex Woods/Andrew de Quincey"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(ttusbdecfe_dvbt_attach); +EXPORT_SYMBOL(ttusbdecfe_dvbs_attach); diff --git a/drivers/media/dvb/ttusb-dec/ttusbdecfe.h b/drivers/media/dvb/ttusb-dec/ttusbdecfe.h new file mode 100644 index 000000000000..15ccc3d1a20e --- /dev/null +++ b/drivers/media/dvb/ttusb-dec/ttusbdecfe.h @@ -0,0 +1,38 @@ +/* + * TTUSB DEC Driver + * + * Copyright (C) 2003-2004 Alex Woods <linux-dvb@giblets.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef TTUSBDECFE_H +#define TTUSBDECFE_H + +#include <linux/dvb/frontend.h> + +struct ttusbdecfe_config +{ + int (*send_command)(struct dvb_frontend* fe, const u8 command, + int param_length, const u8 params[], + int *result_length, u8 cmd_result[]); +}; + +extern struct dvb_frontend* ttusbdecfe_dvbs_attach(const struct ttusbdecfe_config* config); + +extern struct dvb_frontend* ttusbdecfe_dvbt_attach(const struct ttusbdecfe_config* config); + +#endif // TTUSBDECFE_H |