diff options
Diffstat (limited to 'drivers/media/dvb/frontends')
24 files changed, 3343 insertions, 177 deletions
diff --git a/drivers/media/dvb/frontends/Kconfig b/drivers/media/dvb/frontends/Kconfig index 75fb556ec01..e83256d0fd1 100644 --- a/drivers/media/dvb/frontends/Kconfig +++ b/drivers/media/dvb/frontends/Kconfig @@ -40,6 +40,12 @@ config DVB_VES1X93 help A DVB-S tuner module. Say Y when you want to support this frontend. +config DVB_S5H1420 + tristate "Samsung S5H1420 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 @@ -173,4 +179,19 @@ config DVB_OR51132 An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want to support this frontend. +config DVB_BCM3510 + tristate "Broadcom BCM3510" + depends on DVB_CORE + select FW_LOADER + help + An ATSC 8VSB/16VSB and QAM64/256 tuner module. Say Y when you want to + support this frontend. + +config DVB_LGDT330X + tristate "LGDT3302 or LGDT3303 based (DViCO FusionHDTV Gold)" + depends on DVB_CORE + help + An ATSC 8VSB and QAM64/256 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 index 7f8784870ea..ad8658ffd60 100644 --- a/drivers/media/dvb/frontends/Makefile +++ b/drivers/media/dvb/frontends/Makefile @@ -28,3 +28,6 @@ obj-$(CONFIG_DVB_STV0297) += stv0297.o obj-$(CONFIG_DVB_NXT2002) += nxt2002.o obj-$(CONFIG_DVB_OR51211) += or51211.o obj-$(CONFIG_DVB_OR51132) += or51132.o +obj-$(CONFIG_DVB_BCM3510) += bcm3510.o +obj-$(CONFIG_DVB_S5H1420) += s5h1420.o +obj-$(CONFIG_DVB_LGDT330X) += lgdt330x.o diff --git a/drivers/media/dvb/frontends/bcm3510.c b/drivers/media/dvb/frontends/bcm3510.c new file mode 100644 index 00000000000..f5fdc5c3e60 --- /dev/null +++ b/drivers/media/dvb/frontends/bcm3510.c @@ -0,0 +1,853 @@ +/* + * Support for the Broadcom BCM3510 ATSC demodulator (1st generation Air2PC) + * + * Copyright (C) 2001-5, B2C2 inc. + * + * GPL/Linux driver written by Patrick Boettcher <patrick.boettcher@desy.de> + * + * This driver is "hard-coded" to be used with the 1st generation of + * Technisat/B2C2's Air2PC ATSC PCI/USB cards/boxes. The pll-programming + * (Panasonic CT10S) is located here, which is actually wrong. Unless there is + * another device with a BCM3510, this is no problem. + * + * The driver works also with QAM64 DVB-C, but had an unreasonable high + * UNC. (Tested with the Air2PC ATSC 1st generation) + * + * You'll need a firmware for this driver in order to get it running. It is + * called "dvb-fe-bcm3510-01.fw". + * + * 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/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/firmware.h> + +#include "dvb_frontend.h" +#include "bcm3510.h" +#include "bcm3510_priv.h" + +struct bcm3510_state { + + struct i2c_adapter* i2c; + struct dvb_frontend_ops ops; + const struct bcm3510_config* config; + struct dvb_frontend frontend; + + /* demodulator private data */ + struct semaphore hab_sem; + u8 firmware_loaded:1; + + unsigned long next_status_check; + unsigned long status_check_interval; + struct bcm3510_hab_cmd_status1 status1; + struct bcm3510_hab_cmd_status2 status2; +}; + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "set debugging level (1=info,2=i2c (|-able))."); + +#define dprintk(level,x...) if (level & debug) printk(x) +#define dbufout(b,l,m) {\ + int i; \ + for (i = 0; i < l; i++) \ + m("%02x ",b[i]); \ +} +#define deb_info(args...) dprintk(0x01,args) +#define deb_i2c(args...) dprintk(0x02,args) +#define deb_hab(args...) dprintk(0x04,args) + +/* transfer functions */ +static int bcm3510_writebytes (struct bcm3510_state *state, u8 reg, u8 *buf, u8 len) +{ + u8 b[256]; + int err; + struct i2c_msg msg = { .addr = state->config->demod_address, .flags = 0, .buf = b, .len = len + 1 }; + + b[0] = reg; + memcpy(&b[1],buf,len); + + deb_i2c("i2c wr %02x: ",reg); + dbufout(buf,len,deb_i2c); + deb_i2c("\n"); + + if ((err = i2c_transfer (state->i2c, &msg, 1)) != 1) { + + deb_info("%s: i2c write error (addr %02x, reg %02x, err == %i)\n", + __FUNCTION__, state->config->demod_address, reg, err); + return -EREMOTEIO; + } + + return 0; +} + +static int bcm3510_readbytes (struct bcm3510_state *state, u8 reg, u8 *buf, u8 len) +{ + 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 } + }; + int err; + + memset(buf,0,len); + + if ((err = i2c_transfer (state->i2c, msg, 2)) != 2) { + deb_info("%s: i2c read error (addr %02x, reg %02x, err == %i)\n", + __FUNCTION__, state->config->demod_address, reg, err); + return -EREMOTEIO; + } + deb_i2c("i2c rd %02x: ",reg); + dbufout(buf,len,deb_i2c); + deb_i2c("\n"); + + return 0; +} + +static int bcm3510_writeB(struct bcm3510_state *state, u8 reg, bcm3510_register_value v) +{ + return bcm3510_writebytes(state,reg,&v.raw,1); +} + +static int bcm3510_readB(struct bcm3510_state *state, u8 reg, bcm3510_register_value *v) +{ + return bcm3510_readbytes(state,reg,&v->raw,1); +} + +/* Host Access Buffer transfers */ +static int bcm3510_hab_get_response(struct bcm3510_state *st, u8 *buf, int len) +{ + bcm3510_register_value v; + int ret,i; + + v.HABADR_a6.HABADR = 0; + if ((ret = bcm3510_writeB(st,0xa6,v)) < 0) + return ret; + + for (i = 0; i < len; i++) { + if ((ret = bcm3510_readB(st,0xa7,&v)) < 0) + return ret; + buf[i] = v.HABDATA_a7; + } + return 0; +} + +static int bcm3510_hab_send_request(struct bcm3510_state *st, u8 *buf, int len) +{ + bcm3510_register_value v,hab; + int ret,i; + unsigned long t; + +/* Check if any previous HAB request still needs to be serviced by the + * Aquisition Processor before sending new request */ + if ((ret = bcm3510_readB(st,0xa8,&v)) < 0) + return ret; + if (v.HABSTAT_a8.HABR) { + deb_info("HAB is running already - clearing it.\n"); + v.HABSTAT_a8.HABR = 0; + bcm3510_writeB(st,0xa8,v); +// return -EBUSY; + } + +/* Send the start HAB Address (automatically incremented after write of + * HABDATA) and write the HAB Data */ + hab.HABADR_a6.HABADR = 0; + if ((ret = bcm3510_writeB(st,0xa6,hab)) < 0) + return ret; + + for (i = 0; i < len; i++) { + hab.HABDATA_a7 = buf[i]; + if ((ret = bcm3510_writeB(st,0xa7,hab)) < 0) + return ret; + } + +/* Set the HABR bit to indicate AP request in progress (LBHABR allows HABR to + * be written) */ + v.raw = 0; v.HABSTAT_a8.HABR = 1; v.HABSTAT_a8.LDHABR = 1; + if ((ret = bcm3510_writeB(st,0xa8,v)) < 0) + return ret; + +/* Polling method: Wait until the AP finishes processing the HAB request */ + t = jiffies + 1*HZ; + while (time_before(jiffies, t)) { + deb_info("waiting for HAB to complete\n"); + msleep(10); + if ((ret = bcm3510_readB(st,0xa8,&v)) < 0) + return ret; + + if (!v.HABSTAT_a8.HABR) + return 0; + } + + deb_info("send_request execution timed out.\n"); + return -ETIMEDOUT; +} + +static int bcm3510_do_hab_cmd(struct bcm3510_state *st, u8 cmd, u8 msgid, u8 *obuf, u8 olen, u8 *ibuf, u8 ilen) +{ + u8 ob[olen+2],ib[ilen+2]; + int ret = 0; + + ob[0] = cmd; + ob[1] = msgid; + memcpy(&ob[2],obuf,olen); + + deb_hab("hab snd: "); + dbufout(ob,olen+2,deb_hab); + deb_hab("\n"); + + if (down_interruptible(&st->hab_sem) < 0) + return -EAGAIN; + + if ((ret = bcm3510_hab_send_request(st, ob, olen+2)) < 0 || + (ret = bcm3510_hab_get_response(st, ib, ilen+2)) < 0) + goto error; + + deb_hab("hab get: "); + dbufout(ib,ilen+2,deb_hab); + deb_hab("\n"); + + memcpy(ibuf,&ib[2],ilen); +error: + up(&st->hab_sem); + return ret; +} + +#if 0 +/* not needed, we use a semaphore to prevent HAB races */ +static int bcm3510_is_ap_ready(struct bcm3510_state *st) +{ + bcm3510_register_value ap,hab; + int ret; + + if ((ret = bcm3510_readB(st,0xa8,&hab)) < 0 || + (ret = bcm3510_readB(st,0xa2,&ap) < 0)) + return ret; + + if (ap.APSTAT1_a2.RESET || ap.APSTAT1_a2.IDLE || ap.APSTAT1_a2.STOP || hab.HABSTAT_a8.HABR) { + deb_info("AP is busy\n"); + return -EBUSY; + } + + return 0; +} +#endif + +static int bcm3510_bert_reset(struct bcm3510_state *st) +{ + bcm3510_register_value b; + int ret; + + if ((ret < bcm3510_readB(st,0xfa,&b)) < 0) + return ret; + + b.BERCTL_fa.RESYNC = 0; bcm3510_writeB(st,0xfa,b); + b.BERCTL_fa.RESYNC = 1; bcm3510_writeB(st,0xfa,b); + b.BERCTL_fa.RESYNC = 0; bcm3510_writeB(st,0xfa,b); + b.BERCTL_fa.CNTCTL = 1; b.BERCTL_fa.BITCNT = 1; bcm3510_writeB(st,0xfa,b); + + /* clear residual bit counter TODO */ + return 0; +} + +static int bcm3510_refresh_state(struct bcm3510_state *st) +{ + if (time_after(jiffies,st->next_status_check)) { + bcm3510_do_hab_cmd(st, CMD_STATUS, MSGID_STATUS1, NULL,0, (u8 *)&st->status1, sizeof(st->status1)); + bcm3510_do_hab_cmd(st, CMD_STATUS, MSGID_STATUS2, NULL,0, (u8 *)&st->status2, sizeof(st->status2)); + st->next_status_check = jiffies + (st->status_check_interval*HZ)/1000; + } + return 0; +} + +static int bcm3510_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct bcm3510_state* st = fe->demodulator_priv; + bcm3510_refresh_state(st); + + *status = 0; + if (st->status1.STATUS1.RECEIVER_LOCK) + *status |= FE_HAS_LOCK | FE_HAS_SYNC; + + if (st->status1.STATUS1.FEC_LOCK) + *status |= FE_HAS_VITERBI; + + if (st->status1.STATUS1.OUT_PLL_LOCK) + *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER; + + if (*status & FE_HAS_LOCK) + st->status_check_interval = 1500; + else /* more frequently checks if no lock has been achieved yet */ + st->status_check_interval = 500; + + deb_info("real_status: %02x\n",*status); + return 0; +} + +static int bcm3510_read_ber(struct dvb_frontend* fe, u32* ber) +{ + struct bcm3510_state* st = fe->demodulator_priv; + bcm3510_refresh_state(st); + + *ber = (st->status2.LDBER0 << 16) | (st->status2.LDBER1 << 8) | st->status2.LDBER2; + return 0; +} + +static int bcm3510_read_unc(struct dvb_frontend* fe, u32* unc) +{ + struct bcm3510_state* st = fe->demodulator_priv; + bcm3510_refresh_state(st); + *unc = (st->status2.LDUERC0 << 8) | st->status2.LDUERC1; + return 0; +} + +static int bcm3510_read_signal_strength(struct dvb_frontend* fe, u16* strength) +{ + struct bcm3510_state* st = fe->demodulator_priv; + s32 t; + + bcm3510_refresh_state(st); + t = st->status2.SIGNAL; + + if (t > 190) + t = 190; + if (t < 90) + t = 90; + + t -= 90; + t = t * 0xff / 100; + /* normalize if necessary */ + *strength = (t << 8) | t; + return 0; +} + +static int bcm3510_read_snr(struct dvb_frontend* fe, u16* snr) +{ + struct bcm3510_state* st = fe->demodulator_priv; + bcm3510_refresh_state(st); + + *snr = st->status1.SNR_EST0*1000 + ((st->status1.SNR_EST1*1000) >> 8); + return 0; +} + +/* tuner frontend programming */ +static int bcm3510_tuner_cmd(struct bcm3510_state* st,u8 bc, u16 n, u8 a) +{ + struct bcm3510_hab_cmd_tune c; + memset(&c,0,sizeof(struct bcm3510_hab_cmd_tune)); + +/* I2C Mode disabled, set 16 control / Data pairs */ + c.length = 0x10; + c.clock_width = 0; +/* CS1, CS0, DATA, CLK bits control the tuner RF_AGC_SEL pin is set to + * logic high (as Configuration) */ + c.misc = 0x10; +/* Set duration of the initial state of TUNCTL = 3.34 micro Sec */ + c.TUNCTL_state = 0x40; + +/* PRESCALER DEVIDE RATIO | BC1_2_3_4; (band switch), 1stosc REFERENCE COUNTER REF_S12 and REF_S11 */ + c.ctl_dat[0].ctrl.size = BITS_8; + c.ctl_dat[0].data = 0x80 | bc; + +/* Control DATA pin, 1stosc REFERENCE COUNTER REF_S10 to REF_S3 */ + c.ctl_dat[1].ctrl.size = BITS_8; + c.ctl_dat[1].data = 4; + +/* set CONTROL BIT 1 to 1, 1stosc REFERENCE COUNTER REF_S2 to REF_S1 */ + c.ctl_dat[2].ctrl.size = BITS_3; + c.ctl_dat[2].data = 0x20; + +/* control CS0 pin, pulse byte ? */ + c.ctl_dat[3].ctrl.size = BITS_3; + c.ctl_dat[3].ctrl.clk_off = 1; + c.ctl_dat[3].ctrl.cs0 = 1; + c.ctl_dat[3].data = 0x40; + +/* PGM_S18 to PGM_S11 */ + c.ctl_dat[4].ctrl.size = BITS_8; + c.ctl_dat[4].data = n >> 3; + +/* PGM_S10 to PGM_S8, SWL_S7 to SWL_S3 */ + c.ctl_dat[5].ctrl.size = BITS_8; + c.ctl_dat[5].data = ((n & 0x7) << 5) | (a >> 2); + +/* SWL_S2 and SWL_S1, set CONTROL BIT 2 to 0 */ + c.ctl_dat[6].ctrl.size = BITS_3; + c.ctl_dat[6].data = (a << 6) & 0xdf; + +/* control CS0 pin, pulse byte ? */ + c.ctl_dat[7].ctrl.size = BITS_3; + c.ctl_dat[7].ctrl.clk_off = 1; + c.ctl_dat[7].ctrl.cs0 = 1; + c.ctl_dat[7].data = 0x40; + +/* PRESCALER DEVIDE RATIO, 2ndosc REFERENCE COUNTER REF_S12 and REF_S11 */ + c.ctl_dat[8].ctrl.size = BITS_8; + c.ctl_dat[8].data = 0x80; + +/* 2ndosc REFERENCE COUNTER REF_S10 to REF_S3 */ + c.ctl_dat[9].ctrl.size = BITS_8; + c.ctl_dat[9].data = 0x10; + +/* set CONTROL BIT 1 to 1, 2ndosc REFERENCE COUNTER REF_S2 to REF_S1 */ + c.ctl_dat[10].ctrl.size = BITS_3; + c.ctl_dat[10].data = 0x20; + +/* pulse byte */ + c.ctl_dat[11].ctrl.size = BITS_3; + c.ctl_dat[11].ctrl.clk_off = 1; + c.ctl_dat[11].ctrl.cs1 = 1; + c.ctl_dat[11].data = 0x40; + +/* PGM_S18 to PGM_S11 */ + c.ctl_dat[12].ctrl.size = BITS_8; + c.ctl_dat[12].data = 0x2a; + +/* PGM_S10 to PGM_S8 and SWL_S7 to SWL_S3 */ + c.ctl_dat[13].ctrl.size = BITS_8; + c.ctl_dat[13].data = 0x8e; + +/* SWL_S2 and SWL_S1 and set CONTROL BIT 2 to 0 */ + c.ctl_dat[14].ctrl.size = BITS_3; + c.ctl_dat[14].data = 0; + +/* Pulse Byte */ + c.ctl_dat[15].ctrl.size = BITS_3; + c.ctl_dat[15].ctrl.clk_off = 1; + c.ctl_dat[15].ctrl.cs1 = 1; + c.ctl_dat[15].data = 0x40; + + return bcm3510_do_hab_cmd(st,CMD_TUNE, MSGID_TUNE,(u8 *) &c,sizeof(c), NULL, 0); +} + +static int bcm3510_set_freq(struct bcm3510_state* st,u32 freq) +{ + u8 bc,a; + u16 n; + s32 YIntercept,Tfvco1; + + freq /= 1000; + + deb_info("%dkHz:",freq); + /* set Band Switch */ + if (freq <= 168000) + bc = 0x1c; + else if (freq <= 378000) + bc = 0x2c; + else + bc = 0x30; + + if (freq >= 470000) { + freq -= 470001; + YIntercept = 18805; + } else if (freq >= 90000) { + freq -= 90001; + YIntercept = 15005; + } else if (freq >= 76000){ + freq -= 76001; + YIntercept = 14865; + } else { + freq -= 54001; + YIntercept = 14645; + } + + Tfvco1 = (((freq/6000)*60 + YIntercept)*4)/10; + + n = Tfvco1 >> 6; + a = Tfvco1 & 0x3f; + + deb_info(" BC1_2_3_4: %x, N: %x A: %x\n", bc, n, a); + if (n >= 16 && n <= 2047) + return bcm3510_tuner_cmd(st,bc,n,a); + + return -EINVAL; +} + +static int bcm3510_set_frontend(struct dvb_frontend* fe, + struct dvb_frontend_parameters *p) +{ + struct bcm3510_state* st = fe->demodulator_priv; + struct bcm3510_hab_cmd_ext_acquire cmd; + struct bcm3510_hab_cmd_bert_control bert; + int ret; + + memset(&cmd,0,sizeof(cmd)); + switch (p->u.vsb.modulation) { + case QAM_256: + cmd.ACQUIRE0.MODE = 0x1; + cmd.ACQUIRE1.SYM_RATE = 0x1; + cmd.ACQUIRE1.IF_FREQ = 0x1; + break; + case QAM_64: + cmd.ACQUIRE0.MODE = 0x2; + cmd.ACQUIRE1.SYM_RATE = 0x2; + cmd.ACQUIRE1.IF_FREQ = 0x1; + break; +/* case QAM_256: + cmd.ACQUIRE0.MODE = 0x3; + break; + case QAM_128: + cmd.ACQUIRE0.MODE = 0x4; + break; + case QAM_64: + cmd.ACQUIRE0.MODE = 0x5; + break; + case QAM_32: + cmd.ACQUIRE0.MODE = 0x6; + break; + case QAM_16: + cmd.ACQUIRE0.MODE = 0x7; + break;*/ + case VSB_8: + cmd.ACQUIRE0.MODE = 0x8; + cmd.ACQUIRE1.SYM_RATE = 0x0; + cmd.ACQUIRE1.IF_FREQ = 0x0; + break; + case VSB_16: + cmd.ACQUIRE0.MODE = 0x9; + cmd.ACQUIRE1.SYM_RATE = 0x0; + cmd.ACQUIRE1.IF_FREQ = 0x0; + default: + return -EINVAL; + }; + cmd.ACQUIRE0.OFFSET = 0; + cmd.ACQUIRE0.NTSCSWEEP = 1; + cmd.ACQUIRE0.FA = 1; + cmd.ACQUIRE0.BW = 0; + +/* if (enableOffset) { + cmd.IF_OFFSET0 = xx; + cmd.IF_OFFSET1 = xx; + + cmd.SYM_OFFSET0 = xx; + cmd.SYM_OFFSET1 = xx; + if (enableNtscSweep) { + cmd.NTSC_OFFSET0; + cmd.NTSC_OFFSET1; + } + } */ + bcm3510_do_hab_cmd(st, CMD_ACQUIRE, MSGID_EXT_TUNER_ACQUIRE, (u8 *) &cmd, sizeof(cmd), NULL, 0); + +/* doing it with different MSGIDs, data book and source differs */ + bert.BE = 0; + bert.unused = 0; + bcm3510_do_hab_cmd(st, CMD_STATE_CONTROL, MSGID_BERT_CONTROL, (u8 *) &bert, sizeof(bert), NULL, 0); + bcm3510_do_hab_cmd(st, CMD_STATE_CONTROL, MSGID_BERT_SET, (u8 *) &bert, sizeof(bert), NULL, 0); + + bcm3510_bert_reset(st); + + if ((ret = bcm3510_set_freq(st,p->frequency)) < 0) + return ret; + + memset(&st->status1,0,sizeof(st->status1)); + memset(&st->status2,0,sizeof(st->status2)); + st->status_check_interval = 500; + +/* Give the AP some time */ + msleep(200); + + return 0; +} + +static int bcm3510_sleep(struct dvb_frontend* fe) +{ + return 0; +} + +static int bcm3510_get_tune_settings(struct dvb_frontend *fe, struct dvb_frontend_tune_settings *s) +{ + s->min_delay_ms = 1000; + s->step_size = 0; + s->max_drift = 0; + return 0; +} + +static void bcm3510_release(struct dvb_frontend* fe) +{ + struct bcm3510_state* state = fe->demodulator_priv; + kfree(state); +} + +/* firmware download: + * firmware file is build up like this: + * 16bit addr, 16bit length, 8byte of length + */ +#define BCM3510_DEFAULT_FIRMWARE "dvb-fe-bcm3510-01.fw" + +static int bcm3510_write_ram(struct bcm3510_state *st, u16 addr, u8 *b, u16 len) +{ + int ret = 0,i; + bcm3510_register_value vH, vL,vD; + + vH.MADRH_a9 = addr >> 8; + vL.MADRL_aa = addr; + if ((ret = bcm3510_writeB(st,0xa9,vH)) < 0) return ret; + if ((ret = bcm3510_writeB(st,0xaa,vL)) < 0) return ret; + + for (i = 0; i < len; i++) { + vD.MDATA_ab = b[i]; + if ((ret = bcm3510_writeB(st,0xab,vD)) < 0) + return ret; + } + + return 0; +} + +static int bcm3510_download_firmware(struct dvb_frontend* fe) +{ + struct bcm3510_state* st = fe->demodulator_priv; + const struct firmware *fw; + u16 addr,len; + u8 *b; + int ret,i; + + deb_info("requesting firmware\n"); + if ((ret = st->config->request_firmware(fe, &fw, BCM3510_DEFAULT_FIRMWARE)) < 0) { + err("could not load firmware (%s): %d",BCM3510_DEFAULT_FIRMWARE,ret); + return ret; + } + deb_info("got firmware: %d\n",fw->size); + + b = fw->data; + for (i = 0; i < fw->size;) { + addr = le16_to_cpu( *( (u16 *)&b[i] ) ); + len = le16_to_cpu( *( (u16 *)&b[i+2] ) ); + deb_info("firmware chunk, addr: 0x%04x, len: 0x%04x, total length: 0x%04x\n",addr,len,fw->size); + if ((ret = bcm3510_write_ram(st,addr,&b[i+4],len)) < 0) { + err("firmware download failed: %d\n",ret); + return ret; + } + i += 4 + len; + } + release_firmware(fw); + deb_info("firmware download successfully completed\n"); + return 0; +} + +static int bcm3510_check_firmware_version(struct bcm3510_state *st) +{ + struct bcm3510_hab_cmd_get_version_info ver; + bcm3510_do_hab_cmd(st,CMD_GET_VERSION_INFO,MSGID_GET_VERSION_INFO,NULL,0,(u8*)&ver,sizeof(ver)); + + deb_info("Version information: 0x%02x 0x%02x 0x%02x 0x%02x\n", + ver.microcode_version, ver.script_version, ver.config_version, ver.demod_version); + + if (ver.script_version == BCM3510_DEF_SCRIPT_VERSION && + ver.config_version == BCM3510_DEF_CONFIG_VERSION && + ver.demod_version == BCM3510_DEF_DEMOD_VERSION) + return 0; + + deb_info("version check failed\n"); + return -ENODEV; +} + +/* (un)resetting the AP */ +static int bcm3510_reset(struct bcm3510_state *st) +{ + int ret; + unsigned long t; + bcm3510_register_value v; + + bcm3510_readB(st,0xa0,&v); v.HCTL1_a0.RESET = 1; + if ((ret = bcm3510_writeB(st,0xa0,v)) < 0) + return ret; + + t = jiffies + 3*HZ; + while (time_before(jiffies, t)) { + msleep(10); + if ((ret = bcm3510_readB(st,0xa2,&v)) < 0) + return ret; + + if (v.APSTAT1_a2.RESET) + return 0; + } + deb_info("reset timed out\n"); + return -ETIMEDOUT; +} + +static int bcm3510_clear_reset(struct bcm3510_state *st) +{ + bcm3510_register_value v; + int ret; + unsigned long t; + + v.raw = 0; + if ((ret = bcm3510_writeB(st,0xa0,v)) < 0) + return ret; + + t = jiffies + 3*HZ; + while (time_before(jiffies, t)) { + msleep(10); + if ((ret = bcm3510_readB(st,0xa2,&v)) < 0) + return ret; + + /* verify that reset is cleared */ + if (!v.APSTAT1_a2.RESET) + return 0; + } + deb_info("reset clear timed out\n"); + return -ETIMEDOUT; +} + +static int bcm3510_init_cold(struct bcm3510_state *st) +{ + int ret; + bcm3510_register_value v; + + /* read Acquisation Processor status register and check it is not in RUN mode */ + if ((ret = bcm3510_readB(st,0xa2,&v)) < 0) + return ret; + if (v.APSTAT1_a2.RUN) { + deb_info("AP is already running - firmware already loaded.\n"); + return 0; + } + + deb_info("reset?\n"); + if ((ret = bcm3510_reset(st)) < 0) + return ret; + + deb_info("tristate?\n"); + /* tri-state */ + v.TSTCTL_2e.CTL = 0; + if ((ret = bcm3510_writeB(st,0x2e,v)) < 0) + return ret; + + deb_info("firmware?\n"); + if ((ret = bcm3510_download_firmware(&st->frontend)) < 0 || + (ret = bcm3510_clear_reset(st)) < 0) + return ret; + + /* anything left here to Let the acquisition processor begin execution at program counter 0000 ??? */ + + return 0; +} + +static int bcm3510_init(struct dvb_frontend* fe) +{ + struct bcm3510_state* st = fe->demodulator_priv; + bcm3510_register_value j; + struct bcm3510_hab_cmd_set_agc c; + int ret; + + if ((ret = bcm3510_readB(st,0xca,&j)) < 0) + return ret; + + deb_info("JDEC: %02x\n",j.raw); + + switch (j.JDEC_ca.JDEC) { + case JDEC_WAIT_AT_RAM: + deb_info("attempting to download firmware\n"); + if ((ret = bcm3510_init_cold(st)) < 0) + return ret; + case JDEC_EEPROM_LOAD_WAIT: /* fall-through is wanted */ + deb_info("firmware is loaded\n"); + bcm3510_check_firmware_version(st); + break; + default: + return -ENODEV; + } + + memset(&c,0,1); + c.SEL = 1; + bcm3510_do_hab_cmd(st,CMD_AUTO_PARAM,MSGID_SET_RF_AGC_SEL,(u8 *)&c,sizeof(c),NULL,0); + + return 0; +} + + +static struct dvb_frontend_ops bcm3510_ops; + +struct dvb_frontend* bcm3510_attach(const struct bcm3510_config *config, + struct i2c_adapter *i2c) +{ + struct bcm3510_state* state = NULL; + int ret; + bcm3510_register_value v; + + /* allocate memory for the internal state */ + state = kmalloc(sizeof(struct bcm3510_state), GFP_KERNEL); + if (state == NULL) + goto error; + memset(state,0,sizeof(struct bcm3510_state)); + + /* setup the state */ + + state->config = config; + state->i2c = i2c; + memcpy(&state->ops, &bcm3510_ops, sizeof(struct dvb_frontend_ops)); + + /* create dvb_frontend */ + state->frontend.ops = &state->ops; + state->frontend.demodulator_priv = state; + + sema_init(&state->hab_sem, 1); + + if ((ret = bcm3510_readB(state,0xe0,&v)) < 0) + goto error; + + deb_info("Revision: 0x%1x, Layer: 0x%1x.\n",v.REVID_e0.REV,v.REVID_e0.LAYER); + + if ((v.REVID_e0.REV != 0x1 && v.REVID_e0.LAYER != 0xb) && /* cold */ + (v.REVID_e0.REV != 0x8 && v.REVID_e0.LAYER != 0x0)) /* warm */ + goto error; + + info("Revision: 0x%1x, Layer: 0x%1x.",v.REVID_e0.REV,v.REVID_e0.LAYER); + + bcm3510_reset(state); + + return &state->frontend; + +error: + kfree(state); + return NULL; +} +EXPORT_SYMBOL(bcm3510_attach); + +static struct dvb_frontend_ops bcm3510_ops = { + + .info = { + .name = "Broadcom BCM3510 VSB/QAM frontend", + .type = FE_ATSC, + .frequency_min = 54000000, + .frequency_max = 803000000, + /* stepsize is just a guess */ + .frequency_stepsize = 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_8VSB | FE_CAN_16VSB | + FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_128 | FE_CAN_QAM_256 + }, + + .release = bcm3510_release, + + .init = bcm3510_init, + .sleep = bcm3510_sleep, + + .set_frontend = bcm3510_set_frontend, + .get_tune_settings = bcm3510_get_tune_settings, + + .read_status = bcm3510_read_status, + .read_ber = bcm3510_read_ber, + .read_signal_strength = bcm3510_read_signal_strength, + .read_snr = bcm3510_read_snr, + .read_ucblocks = bcm3510_read_unc, +}; + +MODULE_DESCRIPTION("Broadcom BCM3510 ATSC (8VSB/16VSB & ITU J83 AnnexB FEC QAM64/256) demodulator driver"); +MODULE_AUTHOR("Patrick Boettcher <patrick.boettcher@desy.de>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/bcm3510.h b/drivers/media/dvb/frontends/bcm3510.h new file mode 100644 index 00000000000..80f5d0953d0 --- /dev/null +++ b/drivers/media/dvb/frontends/bcm3510.h @@ -0,0 +1,40 @@ +/* + * Support for the Broadcom BCM3510 ATSC demodulator (1st generation Air2PC) + * + * Copyright (C) 2001-5, B2C2 inc. + * + * GPL/Linux driver written by 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; 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 BCM3510_H +#define BCM3510_H + +#include <linux/dvb/frontend.h> +#include <linux/firmware.h> + +struct bcm3510_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* bcm3510_attach(const struct bcm3510_config* config, + struct i2c_adapter* i2c); + +#endif diff --git a/drivers/media/dvb/frontends/bcm3510_priv.h b/drivers/media/dvb/frontends/bcm3510_priv.h new file mode 100644 index 00000000000..3bb1bc2a04f --- /dev/null +++ b/drivers/media/dvb/frontends/bcm3510_priv.h @@ -0,0 +1,460 @@ +/* + * Support for the Broadcom BCM3510 ATSC demodulator (1st generation Air2PC) + * + * Copyright (C) 2001-5, B2C2 inc. + * + * GPL/Linux driver written by 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; 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 __BCM3510_PRIV_H__ +#define __BCM3510_PRIV_H__ + +#define PACKED __attribute__((packed)) + +#undef err +#define err(format, arg...) printk(KERN_ERR "bcm3510: " format "\n" , ## arg) +#undef info +#define info(format, arg...) printk(KERN_INFO "bcm3510: " format "\n" , ## arg) +#undef warn +#define warn(format, arg...) printk(KERN_WARNING "bcm3510: " format "\n" , ## arg) + + +#define PANASONIC_FIRST_IF_BASE_IN_KHz 1407500 +#define BCM3510_SYMBOL_RATE 5381000 + +typedef union { + u8 raw; + + struct { + u8 CTL :8; + } TSTCTL_2e; + + u8 LDCERC_4e; + u8 LDUERC_4f; + u8 LD_BER0_65; + u8 LD_BER1_66; + u8 LD_BER2_67; + u8 LD_BER3_68; + + struct { + u8 RESET :1; + u8 IDLE :1; + u8 STOP :1; + u8 HIRQ0 :1; + u8 HIRQ1 :1; + u8 na0 :1; + u8 HABAV :1; + u8 na1 :1; + } HCTL1_a0; + + struct { + u8 na0 :1; + u8 IDLMSK :1; + u8 STMSK :1; + u8 I0MSK :1; + u8 I1MSK :1; + u8 na1 :1; + u8 HABMSK :1; + u8 na2 :1; + } HCTLMSK_a1; + + struct { + u8 RESET :1; + u8 IDLE :1; + u8 STOP :1; + u8 RUN :1; + u8 HABAV :1; + u8 MEMAV :1; + u8 ALDONE :1; + u8 REIRQ :1; + } APSTAT1_a2; + + struct { + u8 RSTMSK :1; + u8 IMSK :1; + u8 SMSK :1; + u8 RMSK :1; + u8 HABMSK :1; + u8 MAVMSK :1; + u8 ALDMSK :1; + u8 REMSK :1; + } APMSK1_a3; + + u8 APSTAT2_a4; + u8 APMSK2_a5; + + struct { + u8 HABADR :7; + u8 na :1; + } HABADR_a6; + + u8 HABDATA_a7; + + struct { + u8 HABR :1; + u8 LDHABR :1; + u8 APMSK :1; + u8 HMSK :1; + u8 LDMSK :1; + u8 na :3; + } HABSTAT_a8; + + u8 MADRH_a9; + u8 MADRL_aa; + u8 MDATA_ab; + + struct { +#define JDEC_WAIT_AT_RAM 0x7 +#define JDEC_EEPROM_LOAD_WAIT 0x4 + u8 JDEC :3; + u8 na :5; + } JDEC_ca; + + struct { + u8 REV :4; + u8 LAYER :4; + } REVID_e0; + + struct { + u8 unk0 :1; + u8 CNTCTL :1; + u8 BITCNT :1; + u8 unk1 :1; + u8 RESYNC :1; + u8 unk2 :3; + } BERCTL_fa; + + struct { + u8 CSEL0 :1; + u8 CLKED0 :1; + u8 CSEL1 :1; + u8 CLKED1 :1; + u8 CLKLEV :1; + u8 SPIVAR :1; + u8 na :2; + } TUNSET_fc; + + struct { + u8 CLK :1; + u8 DATA :1; + u8 CS0 :1; + u8 CS1 :1; + u8 AGCSEL :1; + u8 na0 :1; + u8 TUNSEL :1; + u8 na1 :1; + } TUNCTL_fd; + + u8 TUNSEL0_fe; + u8 TUNSEL1_ff; + +} bcm3510_register_value; + +/* HAB commands */ + +/* version */ +#define CMD_GET_VERSION_INFO 0x3D +#define MSGID_GET_VERSION_INFO 0x15 +struct bcm3510_hab_cmd_get_version_info { + u8 microcode_version; + u8 script_version; + u8 config_version; + u8 demod_version; +} PACKED; + +#define BCM3510_DEF_MICROCODE_VERSION 0x0E +#define BCM3510_DEF_SCRIPT_VERSION 0x06 +#define BCM3510_DEF_CONFIG_VERSION 0x01 +#define BCM3510_DEF_DEMOD_VERSION 0xB1 + +/* acquire */ +#define CMD_ACQUIRE 0x38 + +#define MSGID_EXT_TUNER_ACQUIRE 0x0A +struct bcm3510_hab_cmd_ext_acquire { + struct { + u8 MODE :4; + u8 BW :1; + u8 FA :1; + u8 NTSCSWEEP :1; + u8 OFFSET :1; + } PACKED ACQUIRE0; /* control_byte */ + + struct { + u8 IF_FREQ :3; + u8 zero0 :1; + u8 SYM_RATE :3; + u8 zero1 :1; + } PACKED ACQUIRE1; /* sym_if */ + + u8 IF_OFFSET0; /* IF_Offset_10hz */ + u8 IF_OFFSET1; + u8 SYM_OFFSET0; /* SymbolRateOffset */ + u8 SYM_OFFSET1; + u8 NTSC_OFFSET0; /* NTSC_Offset_10hz */ + u8 NTSC_OFFSET1; +} PACKED; + +#define MSGID_INT_TUNER_ACQUIRE 0x0B +struct bcm3510_hab_cmd_int_acquire { + struct { + u8 MODE :4; + u8 BW :1; + u8 FA :1; + u8 NTSCSWEEP :1; + u8 OFFSET :1; + } PACKED ACQUIRE0; /* control_byte */ + + struct { + u8 IF_FREQ :3; + u8 zero0 :1; + u8 SYM_RATE :3; + u8 zero1 :1; + } PACKED ACQUIRE1; /* sym_if */ + + u8 TUNER_FREQ0; + u8 TUNER_FREQ1; + u8 TUNER_FREQ2; + u8 TUNER_FREQ3; + u8 IF_OFFSET0; /* IF_Offset_10hz */ + u8 IF_OFFSET1; + u8 SYM_OFFSET0; /* SymbolRateOffset */ + u8 SYM_OFFSET1; + u8 NTSC_OFFSET0; /* NTSC_Offset_10hz */ + u8 NTSC_OFFSET1; +} PACKED; + +/* modes */ +#define BCM3510_QAM16 = 0x01 +#define BCM3510_QAM32 = 0x02 +#define BCM3510_QAM64 = 0x03 +#define BCM3510_QAM128 = 0x04 +#define BCM3510_QAM256 = 0x05 +#define BCM3510_8VSB = 0x0B +#define BCM3510_16VSB = 0x0D + +/* IF_FREQS */ +#define BCM3510_IF_TERRESTRIAL 0x0 +#define BCM3510_IF_CABLE 0x1 +#define BCM3510_IF_USE_CMD 0x7 + +/* SYM_RATE */ +#define BCM3510_SR_8VSB 0x0 /* 5381119 s/sec */ +#define BCM3510_SR_256QAM 0x1 /* 5360537 s/sec */ +#define BCM3510_SR_16QAM 0x2 /* 5056971 s/sec */ +#define BCM3510_SR_MISC 0x3 /* 5000000 s/sec */ +#define BCM3510_SR_USE_CMD 0x7 + +/* special symbol rate */ +#define CMD_SET_VALUE_NOT_LISTED 0x2d +#define MSGID_SET_SYMBOL_RATE_NOT_LISTED 0x0c +struct bcm3510_hab_cmd_set_sr_not_listed { + u8 HOST_SYM_RATE0; + u8 HOST_SYM_RATE1; + u8 HOST_SYM_RATE2; + u8 HOST_SYM_RATE3; +} PACKED; + +/* special IF */ +#define MSGID_SET_IF_FREQ_NOT_LISTED 0x0d +struct bcm3510_hab_cmd_set_if_freq_not_listed { + u8 HOST_IF_FREQ0; + u8 HOST_IF_FREQ1; + u8 HOST_IF_FREQ2; + u8 HOST_IF_FREQ3; +} PACKED; + +/* auto reacquire */ +#define CMD_AUTO_PARAM 0x2a +#define MSGID_AUTO_REACQUIRE 0x0e +struct bcm3510_hab_cmd_auto_reacquire { + u8 ACQ :1; /* on/off*/ + u8 unused :7; +} PACKED; + +#define MSGID_SET_RF_AGC_SEL 0x12 +struct bcm3510_hab_cmd_set_agc { + u8 LVL :1; + u8 unused :6; + u8 SEL :1; +} PACKED; + +#define MSGID_SET_AUTO_INVERSION 0x14 +struct bcm3510_hab_cmd_auto_inversion { + u8 AI :1; + u8 unused :7; +} PACKED; + + +/* bert control */ +#define CMD_STATE_CONTROL 0x12 +#define MSGID_BERT_CONTROL 0x0e +#define MSGID_BERT_SET 0xfa +struct bcm3510_hab_cmd_bert_control { + u8 BE :1; + u8 unused :7; +} PACKED; + +#define MSGID_TRI_STATE 0x2e +struct bcm3510_hab_cmd_tri_state { + u8 RE :1; /* a/d ram port pins */ + u8 PE :1; /* baud clock pin */ + u8 AC :1; /* a/d clock pin */ + u8 BE :1; /* baud clock pin */ + u8 unused :4; +} PACKED; + + +/* tune */ +#define CMD_TUNE 0x38 +#define MSGID_TUNE 0x16 +struct bcm3510_hab_cmd_tune_ctrl_data_pair { + struct { +#define BITS_8 0x07 +#define BITS_7 0x06 +#define BITS_6 0x05 +#define BITS_5 0x04 +#define BITS_4 0x03 +#define BITS_3 0x02 +#define BITS_2 0x01 +#define BITS_1 0x00 + u8 size :3; + u8 unk :2; + u8 clk_off :1; + u8 cs0 :1; + u8 cs1 :1; + + } PACKED ctrl; + + u8 data; +} PACKED; + +struct bcm3510_hab_cmd_tune { + u8 length; + u8 clock_width; + u8 misc; + u8 TUNCTL_state; + + struct bcm3510_hab_cmd_tune_ctrl_data_pair ctl_dat[16]; +} PACKED; + +#define CMD_STATUS 0x38 +#define MSGID_STATUS1 0x08 +struct bcm3510_hab_cmd_status1 { + struct { + u8 EQ_MODE :4; + u8 reserved :2; + u8 QRE :1; /* if QSE and the spectrum is inversed */ + u8 QSE :1; /* automatic spectral inversion */ + } PACKED STATUS0; + + struct { + u8 RECEIVER_LOCK :1; + u8 FEC_LOCK :1; + u8 OUT_PLL_LOCK :1; + u8 reserved :5; + } PACKED STATUS1; + + struct { + u8 reserved :2; + u8 BW :1; + u8 NTE :1; /* NTSC filter sweep enabled */ + u8 AQI :1; /* currently acquiring */ + u8 FA :1; /* fast acquisition */ + u8 ARI :1; /* auto reacquire */ + u8 TI :1; /* programming the tuner */ + } PACKED STATUS2; + u8 STATUS3; + u8 SNR_EST0; + u8 SNR_EST1; + u8 TUNER_FREQ0; + u8 TUNER_FREQ1; + u8 TUNER_FREQ2; + u8 TUNER_FREQ3; + u8 SYM_RATE0; + u8 SYM_RATE1; + u8 SYM_RATE2; + u8 SYM_RATE3; + u8 SYM_OFFSET0; + u8 SYM_OFFSET1; + u8 SYM_ERROR0; + u8 SYM_ERROR1; + u8 IF_FREQ0; + u8 IF_FREQ1; + u8 IF_FREQ2; + u8 IF_FREQ3; + u8 IF_OFFSET0; + u8 IF_OFFSET1; + u8 IF_ERROR0; + u8 IF_ERROR1; + u8 NTSC_FILTER0; + u8 NTSC_FILTER1; + u8 NTSC_FILTER2; + u8 NTSC_FILTER3; + u8 NTSC_OFFSET0; + u8 NTSC_OFFSET1; + u8 NTSC_ERROR0; + u8 NTSC_ERROR1; + u8 INT_AGC_LEVEL0; + u8 INT_AGC_LEVEL1; + u8 EXT_AGC_LEVEL0; + u8 EXT_AGC_LEVEL1; +} PACKED; + +#define MSGID_STATUS2 0x14 +struct bcm3510_hab_cmd_status2 { + struct { + u8 EQ_MODE :4; + u8 reserved :2; + u8 QRE :1; + u8 QSR :1; + } PACKED STATUS0; + struct { + u8 RL :1; + u8 FL :1; + u8 OL :1; + u8 reserved :5; + } PACKED STATUS1; + u8 SYMBOL_RATE0; + u8 SYMBOL_RATE1; + u8 SYMBOL_RATE2; + u8 SYMBOL_RATE3; + u8 LDCERC0; + u8 LDCERC1; + u8 LDCERC2; + u8 LDCERC3; + u8 LDUERC0; + u8 LDUERC1; + u8 LDUERC2; + u8 LDUERC3; + u8 LDBER0; + u8 LDBER1; + u8 LDBER2; + u8 LDBER3; + struct { + u8 MODE_TYPE :4; /* acquire mode 0 */ + u8 reservd :4; + } MODE_TYPE; + u8 SNR_EST0; + u8 SNR_EST1; + u8 SIGNAL; +} PACKED; + +#define CMD_SET_RF_BW_NOT_LISTED 0x3f +#define MSGID_SET_RF_BW_NOT_LISTED 0x11 +/* TODO */ + +#endif diff --git a/drivers/media/dvb/frontends/cx22702.c b/drivers/media/dvb/frontends/cx22702.c index f4aa44136c7..9f639297a9f 100644 --- a/drivers/media/dvb/frontends/cx22702.c +++ b/drivers/media/dvb/frontends/cx22702.c @@ -76,7 +76,6 @@ static u8 init_tab [] = { 0x49, 0x56, 0x6b, 0x1e, 0xc8, 0x02, - 0xf8, 0x02, 0xf9, 0x00, 0xfa, 0x00, 0xfb, 0x00, @@ -203,7 +202,7 @@ static int cx22702_set_tps (struct dvb_frontend* fe, struct dvb_frontend_paramet struct cx22702_state* state = fe->demodulator_priv; /* set PLL */ - cx22702_writereg (state, 0x0D, cx22702_readreg(state,0x0D) &0xfe); + cx22702_writereg (state, 0x0D, cx22702_readreg(state,0x0D) &0xfe); if (state->config->pll_set) { state->config->pll_set(fe, p); } else if (state->config->pll_desc) { @@ -217,7 +216,7 @@ static int cx22702_set_tps (struct dvb_frontend* fe, struct dvb_frontend_paramet } else { BUG(); } - cx22702_writereg (state, 0x0D, cx22702_readreg(state,0x0D) | 1); + cx22702_writereg (state, 0x0D, cx22702_readreg(state,0x0D) | 1); /* set inversion */ cx22702_set_inversion (state, p->inversion); @@ -256,7 +255,7 @@ static int cx22702_set_tps (struct dvb_frontend* fe, struct dvb_frontend_paramet 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__); + dprintk("%s: Autodetecting\n",__FUNCTION__); return 0; } @@ -347,10 +346,11 @@ static int cx22702_init (struct dvb_frontend* fe) for (i=0; i<sizeof(init_tab); i+=2) cx22702_writereg (state, init_tab[i], init_tab[i+1]); + cx22702_writereg (state, 0xf8, (state->config->output_mode << 1) & 0x02); /* init PLL */ if (state->config->pll_init) { - cx22702_writereg (state, 0x0D, cx22702_readreg(state,0x0D) &0xfe); + cx22702_writereg (state, 0x0D, cx22702_readreg(state,0x0D) & 0xfe); state->config->pll_init(fe); cx22702_writereg (state, 0x0D, cx22702_readreg(state,0x0D) | 1); } @@ -440,8 +440,10 @@ static int cx22702_read_ucblocks(struct dvb_frontend* fe, u32* 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; + if (state->prevUCBlocks < _ucblocks) + *ucblocks = (_ucblocks - state->prevUCBlocks); + else + *ucblocks = state->prevUCBlocks - _ucblocks; state->prevUCBlocks = _ucblocks; return 0; @@ -457,6 +459,12 @@ static int cx22702_get_frontend(struct dvb_frontend* fe, struct dvb_frontend_par return cx22702_get_tps (state, &p->u.ofdm); } +static int cx22702_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings *tune) +{ + tune->min_delay_ms = 1000; + return 0; +} + static void cx22702_release(struct dvb_frontend* fe) { struct cx22702_state* state = fe->demodulator_priv; @@ -472,7 +480,8 @@ struct dvb_frontend* cx22702_attach(const struct cx22702_config* config, /* allocate memory for the internal state */ state = kmalloc(sizeof(struct cx22702_state), GFP_KERNEL); - if (state == NULL) goto error; + if (state == NULL) + goto error; /* setup the state */ state->config = config; @@ -481,7 +490,8 @@ struct dvb_frontend* cx22702_attach(const struct cx22702_config* config, state->prevUCBlocks = 0; /* check if the demod is there */ - if (cx22702_readreg(state, 0x1f) != 0x3) goto error; + if (cx22702_readreg(state, 0x1f) != 0x3) + goto error; /* create dvb_frontend */ state->frontend.ops = &state->ops; @@ -514,6 +524,7 @@ static struct dvb_frontend_ops cx22702_ops = { .set_frontend = cx22702_set_tps, .get_frontend = cx22702_get_frontend, + .get_tune_settings = cx22702_get_tune_settings, .read_status = cx22702_read_status, .read_ber = cx22702_read_ber, diff --git a/drivers/media/dvb/frontends/cx22702.h b/drivers/media/dvb/frontends/cx22702.h index 559fdb90666..11f86806756 100644 --- a/drivers/media/dvb/frontends/cx22702.h +++ b/drivers/media/dvb/frontends/cx22702.h @@ -35,6 +35,11 @@ struct cx22702_config /* the demodulator's i2c address */ u8 demod_address; + /* serial/parallel output */ +#define CX22702_PARALLEL_OUTPUT 0 +#define CX22702_SERIAL_OUTPUT 1 + u8 output_mode; + /* PLL maintenance */ u8 pll_address; struct dvb_pll_desc *pll_desc; diff --git a/drivers/media/dvb/frontends/dib3000-common.c b/drivers/media/dvb/frontends/dib3000-common.c index 47ab02e133d..1a4f1f7c228 100644 --- a/drivers/media/dvb/frontends/dib3000-common.c +++ b/drivers/media/dvb/frontends/dib3000-common.c @@ -73,7 +73,7 @@ u16 dib3000_seq[2][2][2] = /* fft,gua, inv */ }; MODULE_AUTHOR("Patrick Boettcher <patrick.boettcher@desy.de"); -MODULE_DESCRIPTION("Common functions for the dib3000mb/dib3000mc dvb-frontend drivers"); +MODULE_DESCRIPTION("Common functions for the dib3000mb/dib3000mc dvb frontend drivers"); MODULE_LICENSE("GPL"); EXPORT_SYMBOL(dib3000_seq); diff --git a/drivers/media/dvb/frontends/dib3000.h b/drivers/media/dvb/frontends/dib3000.h index 80687c13083..2d5475b5c06 100644 --- a/drivers/media/dvb/frontends/dib3000.h +++ b/drivers/media/dvb/frontends/dib3000.h @@ -32,9 +32,8 @@ struct dib3000_config 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]); + int (*pll_init)(struct dvb_frontend *fe); + int (*pll_set)(struct dvb_frontend *fe, struct dvb_frontend_parameters* params); }; struct dib_fe_xfer_ops diff --git a/drivers/media/dvb/frontends/dib3000mb.c b/drivers/media/dvb/frontends/dib3000mb.c index 6f52d649e97..cd434b7cf9d 100644 --- a/drivers/media/dvb/frontends/dib3000mb.c +++ b/drivers/media/dvb/frontends/dib3000mb.c @@ -48,8 +48,6 @@ MODULE_PARM_DESC(debug, "set debugging level (1=info,2=xfer,4=setfe,8=getfe (|-a #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); @@ -61,10 +59,8 @@ static int dib3000mb_set_frontend(struct dvb_frontend* fe, fe_code_rate_t fe_cr = FEC_NONE; int search_state, seq; - if (tuner && state->config.pll_addr && state->config.pll_set) { - 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)); + if (tuner && state->config.pll_set) { + state->config.pll_set(fe, fep); deb_setf("bandwidth: "); switch (ofdm->bandwidth) { @@ -389,11 +385,8 @@ static int dib3000mb_fe_init(struct dvb_frontend* fe, int mobile_mode) 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)); - } + if (state->config.pll_init) + state->config.pll_init(fe); return 0; } @@ -623,7 +616,7 @@ static int dib3000mb_read_unc_blocks(struct dvb_frontend* fe, u32 *unc) { struct dib3000_state* state = fe->demodulator_priv; - *unc = rd(DIB3000MB_REG_UNC); + *unc = rd(DIB3000MB_REG_PACKET_ERROR_RATE); return 0; } @@ -638,9 +631,6 @@ static int dib3000mb_sleep(struct dvb_frontend* fe) 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; } diff --git a/drivers/media/dvb/frontends/dib3000mb_priv.h b/drivers/media/dvb/frontends/dib3000mb_priv.h index 57e61aa5b07..999b1904781 100644 --- a/drivers/media/dvb/frontends/dib3000mb_priv.h +++ b/drivers/media/dvb/frontends/dib3000mb_priv.h @@ -294,7 +294,7 @@ static u16 dib3000mb_reg_filter_coeffs[] = { static u16 dib3000mb_filter_coeffs[] = { 226, 160, 29, - 979, 998, 19, + 979, 998, 19, 22, 1019, 1006, 1022, 12, 6, 1017, 1017, 3, diff --git a/drivers/media/dvb/frontends/dib3000mc.c b/drivers/media/dvb/frontends/dib3000mc.c index 888f10a5e96..cd33705a432 100644 --- a/drivers/media/dvb/frontends/dib3000mc.c +++ b/drivers/media/dvb/frontends/dib3000mc.c @@ -48,8 +48,6 @@ MODULE_PARM_DESC(debug, "set debugging level (1=info,2=xfer,4=setfe,8=getfe,16=s #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) { @@ -463,10 +461,8 @@ static int dib3000mc_set_frontend(struct dvb_frontend* fe, int search_state,auto_val; u16 val; - if (tuner && state->config.pll_addr && state->config.pll_set) { /* 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)); + if (tuner && state->config.pll_set) { /* initial call from dvb */ + state->config.pll_set(fe,fep); state->last_tuned_freq = fep->frequency; // if (!scanboost) { @@ -554,19 +550,15 @@ static int dib3000mc_set_frontend(struct dvb_frontend* fe, 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; - + struct dib3000_state *state = fe->demodulator_priv; deb_info("init start\n"); - state = fe->demodulator_priv; state->timing_offset = 0; state->timing_offset_comp_done = 0; @@ -649,11 +641,9 @@ static int dib3000mc_fe_init(struct dvb_frontend* fe, int mobile_mode) 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)); - }*/ + if (state->config.pll_init) + state->config.pll_init(fe); + deb_info("init end\n"); return 0; } @@ -688,7 +678,7 @@ static int dib3000mc_read_unc_blocks(struct dvb_frontend* fe, u32 *unc) { struct dib3000_state* state = fe->demodulator_priv; - *unc = rd(DIB3000MC_REG_PACKET_ERROR_COUNT); + *unc = rd(DIB3000MC_REG_PACKET_ERRORS); return 0; } @@ -737,10 +727,7 @@ static int dib3000mc_sleep(struct dvb_frontend* fe) 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; - + tune->min_delay_ms = 1000; return 0; } diff --git a/drivers/media/dvb/frontends/dvb-pll.c b/drivers/media/dvb/frontends/dvb-pll.c index 2a3c2ce7b2a..5264310c070 100644 --- a/drivers/media/dvb/frontends/dvb-pll.c +++ b/drivers/media/dvb/frontends/dvb-pll.c @@ -1,6 +1,4 @@ /* - * $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] @@ -57,7 +55,7 @@ struct dvb_pll_desc dvb_pll_thomson_dtt7610 = { }; EXPORT_SYMBOL(dvb_pll_thomson_dtt7610); -static void thomson_dtt759x_bw(u8 *buf, int bandwidth) +static void thomson_dtt759x_bw(u8 *buf, u32 freq, int bandwidth) { if (BANDWIDTH_7_MHZ == bandwidth) buf[3] |= 0x10; @@ -84,17 +82,44 @@ struct dvb_pll_desc dvb_pll_lg_z201 = { .name = "LG z201", .min = 174000000, .max = 862000000, - .count = 5, + .count = 6, .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 }, + { 157500000, 36166667, 166666, 0xbc, 0x01 }, + { 443250000, 36166667, 166666, 0xbc, 0x02 }, + { 542000000, 36166667, 166666, 0xbc, 0x04 }, + { 830000000, 36166667, 166666, 0xf4, 0x04 }, + { 999999999, 36166667, 166666, 0xfc, 0x04 }, }, }; EXPORT_SYMBOL(dvb_pll_lg_z201); +struct dvb_pll_desc dvb_pll_microtune_4042 = { + .name = "Microtune 4042 FI5", + .min = 57000000, + .max = 858000000, + .count = 3, + .entries = { + { 162000000, 44000000, 62500, 0x8e, 0xa1 }, + { 457000000, 44000000, 62500, 0x8e, 0x91 }, + { 999999999, 44000000, 62500, 0x8e, 0x31 }, + }, +}; +EXPORT_SYMBOL(dvb_pll_microtune_4042); + +struct dvb_pll_desc dvb_pll_thomson_dtt7611 = { + .name = "Thomson dtt7611", + .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_dtt7611); + struct dvb_pll_desc dvb_pll_unknown_1 = { .name = "unknown 1", /* used by dntv live dvb-t */ .min = 174000000, @@ -114,6 +139,143 @@ struct dvb_pll_desc dvb_pll_unknown_1 = { }; EXPORT_SYMBOL(dvb_pll_unknown_1); +/* Infineon TUA6010XS + * used in Thomson Cable Tuner + */ +struct dvb_pll_desc dvb_pll_tua6010xs = { + .name = "Infineon TUA6010XS", + .min = 44250000, + .max = 858000000, + .count = 3, + .entries = { + { 115750000, 36125000, 62500, 0x8e, 0x03 }, + { 403250000, 36125000, 62500, 0x8e, 0x06 }, + { 999999999, 36125000, 62500, 0x8e, 0x85 }, + }, +}; +EXPORT_SYMBOL(dvb_pll_tua6010xs); + +/* Panasonic env57h1xd5 (some Philips PLL ?) */ +struct dvb_pll_desc dvb_pll_env57h1xd5 = { + .name = "Panasonic ENV57H1XD5", + .min = 44250000, + .max = 858000000, + .count = 4, + .entries = { + { 153000000, 36291666, 166666, 0xc2, 0x41 }, + { 470000000, 36291666, 166666, 0xc2, 0x42 }, + { 526000000, 36291666, 166666, 0xc2, 0x84 }, + { 999999999, 36291666, 166666, 0xc2, 0xa4 }, + }, +}; +EXPORT_SYMBOL(dvb_pll_env57h1xd5); + +/* Philips TDA6650/TDA6651 + * used in Panasonic ENV77H11D5 + */ +static void tda665x_bw(u8 *buf, u32 freq, int bandwidth) +{ + if (bandwidth == BANDWIDTH_8_MHZ) + buf[3] |= 0x08; +} + +struct dvb_pll_desc dvb_pll_tda665x = { + .name = "Philips TDA6650/TDA6651", + .min = 44250000, + .max = 858000000, + .setbw = tda665x_bw, + .count = 12, + .entries = { + { 93834000, 36249333, 166667, 0xca, 0x61 /* 011 0 0 0 01 */ }, + { 123834000, 36249333, 166667, 0xca, 0xa1 /* 101 0 0 0 01 */ }, + { 161000000, 36249333, 166667, 0xca, 0xa1 /* 101 0 0 0 01 */ }, + { 163834000, 36249333, 166667, 0xca, 0xc2 /* 110 0 0 0 10 */ }, + { 253834000, 36249333, 166667, 0xca, 0x62 /* 011 0 0 0 10 */ }, + { 383834000, 36249333, 166667, 0xca, 0xa2 /* 101 0 0 0 10 */ }, + { 443834000, 36249333, 166667, 0xca, 0xc2 /* 110 0 0 0 10 */ }, + { 444000000, 36249333, 166667, 0xca, 0xc3 /* 110 0 0 0 11 */ }, + { 583834000, 36249333, 166667, 0xca, 0x63 /* 011 0 0 0 11 */ }, + { 793834000, 36249333, 166667, 0xca, 0xa3 /* 101 0 0 0 11 */ }, + { 444834000, 36249333, 166667, 0xca, 0xc3 /* 110 0 0 0 11 */ }, + { 861000000, 36249333, 166667, 0xca, 0xe3 /* 111 0 0 0 11 */ }, + } +}; +EXPORT_SYMBOL(dvb_pll_tda665x); + +/* Infineon TUA6034 + * used in LG TDTP E102P + */ +static void tua6034_bw(u8 *buf, u32 freq, int bandwidth) +{ + if (BANDWIDTH_7_MHZ != bandwidth) + buf[3] |= 0x08; +} + +struct dvb_pll_desc dvb_pll_tua6034 = { + .name = "Infineon TUA6034", + .min = 44250000, + .max = 858000000, + .count = 3, + .setbw = tua6034_bw, + .entries = { + { 174500000, 36166667, 62500, 0xce, 0x01 }, + { 230000000, 36166667, 62500, 0xce, 0x02 }, + { 999999999, 36166667, 62500, 0xce, 0x04 }, + }, +}; +EXPORT_SYMBOL(dvb_pll_tua6034); + +/* Philips FMD1216ME + * used in Medion Hybrid PCMCIA card and USB Box + */ +static void fmd1216me_bw(u8 *buf, u32 freq, int bandwidth) +{ + if (bandwidth == BANDWIDTH_8_MHZ && freq >= 158870000) + buf[3] |= 0x08; +} + +struct dvb_pll_desc dvb_pll_fmd1216me = { + .name = "Philips FMD1216ME", + .min = 50870000, + .max = 858000000, + .setbw = fmd1216me_bw, + .count = 7, + .entries = { + { 143870000, 36213333, 166667, 0xbc, 0x41 }, + { 158870000, 36213333, 166667, 0xf4, 0x41 }, + { 329870000, 36213333, 166667, 0xbc, 0x42 }, + { 441870000, 36213333, 166667, 0xf4, 0x42 }, + { 625870000, 36213333, 166667, 0xbc, 0x44 }, + { 803870000, 36213333, 166667, 0xf4, 0x44 }, + { 999999999, 36213333, 166667, 0xfc, 0x44 }, + } +}; +EXPORT_SYMBOL(dvb_pll_fmd1216me); + +/* ALPS TDED4 + * used in Nebula-Cards and USB boxes + */ +static void tded4_bw(u8 *buf, u32 freq, int bandwidth) +{ + if (bandwidth == BANDWIDTH_8_MHZ) + buf[3] |= 0x04; +} + +struct dvb_pll_desc dvb_pll_tded4 = { + .name = "ALPS TDED4", + .min = 47000000, + .max = 863000000, + .setbw = tded4_bw, + .count = 4, + .entries = { + { 153000000, 36166667, 166667, 0x85, 0x01 }, + { 470000000, 36166667, 166667, 0x85, 0x02 }, + { 823000000, 36166667, 166667, 0x85, 0x08 }, + { 999999999, 36166667, 166667, 0x85, 0x88 }, + } +}; +EXPORT_SYMBOL(dvb_pll_tded4); + /* ----------------------------------------------------------- */ /* code */ @@ -147,7 +309,7 @@ int dvb_pll_configure(struct dvb_pll_desc *desc, u8 *buf, buf[3] = desc->entries[i].cb2; if (desc->setbw) - desc->setbw(buf, bandwidth); + desc->setbw(buf, freq, bandwidth); if (debug) printk("pll: %s: div=%d | buf=0x%02x,0x%02x,0x%02x,0x%02x\n", @@ -160,9 +322,3 @@ 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 index c4c3c56c4a8..cb794759d89 100644 --- a/drivers/media/dvb/frontends/dvb-pll.h +++ b/drivers/media/dvb/frontends/dvb-pll.h @@ -1,5 +1,5 @@ /* - * $Id: dvb-pll.h,v 1.2 2005/02/10 11:43:41 kraxel Exp $ + * descriptions + helper functions for simple dvb plls. */ #ifndef __DVB_PLL_H__ @@ -9,7 +9,7 @@ struct dvb_pll_desc { char *name; u32 min; u32 max; - void (*setbw)(u8 *buf, int bandwidth); + void (*setbw)(u8 *buf, u32 freq, int bandwidth); int count; struct { u32 limit; @@ -17,15 +17,24 @@ struct dvb_pll_desc { u32 stepsize; u8 cb1; u8 cb2; - } entries[9]; + } entries[12]; }; 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_microtune_4042; +extern struct dvb_pll_desc dvb_pll_thomson_dtt7611; extern struct dvb_pll_desc dvb_pll_unknown_1; +extern struct dvb_pll_desc dvb_pll_tua6010xs; +extern struct dvb_pll_desc dvb_pll_env57h1xd5; +extern struct dvb_pll_desc dvb_pll_tua6034; +extern struct dvb_pll_desc dvb_pll_tda665x; +extern struct dvb_pll_desc dvb_pll_fmd1216me; +extern struct dvb_pll_desc dvb_pll_tded4; + int dvb_pll_configure(struct dvb_pll_desc *desc, u8 *buf, u32 freq, int bandwidth); diff --git a/drivers/media/dvb/frontends/l64781.c b/drivers/media/dvb/frontends/l64781.c index 031a1ddc7d1..faaad1ae855 100644 --- a/drivers/media/dvb/frontends/l64781.c +++ b/drivers/media/dvb/frontends/l64781.c @@ -474,11 +474,12 @@ static int l64781_init(struct dvb_frontend* fe) return 0; } -static int l64781_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* fesettings) +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; + fesettings->min_delay_ms = 4000; + fesettings->step_size = 0; + fesettings->max_drift = 0; return 0; } diff --git a/drivers/media/dvb/frontends/lgdt330x.c b/drivers/media/dvb/frontends/lgdt330x.c new file mode 100644 index 00000000000..e94dee50eec --- /dev/null +++ b/drivers/media/dvb/frontends/lgdt330x.c @@ -0,0 +1,606 @@ +/* + * Support for LGDT3302 & LGDT3303 (DViCO FusionHDTV Gold) - VSB/QAM + * + * Copyright (C) 2005 Wilson Michaels <wilsonmichaels@earthlink.net> + * + * Based on code from Kirk Lapray <kirk_lapray@bigfoot.com> + * Copyright (C) 2005 + * + * 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. + * + */ + +/* + * NOTES ABOUT THIS DRIVER + * + * This driver supports DViCO FusionHDTV Gold under Linux. + * + * TODO: + * BER and signal strength always return 0. + * Include support for LGDT3303 + * + */ + +#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 "lgdt330x_priv.h" +#include "lgdt330x.h" + +static int debug = 0; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug,"Turn on/off lgdt330x frontend debugging (default:off)."); +#define dprintk(args...) \ +do { \ +if (debug) printk(KERN_DEBUG "lgdt330x: " args); \ +} while (0) + +struct lgdt330x_state +{ + struct i2c_adapter* i2c; + struct dvb_frontend_ops ops; + + /* Configuration settings */ + const struct lgdt330x_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 lgdt330x_state* state, + u8 addr, /* demod_address or pll_address */ + u8 *buf, /* data bytes to send */ + int len /* number of bytes to send */ ) +{ + u8 tmp[] = { buf[0], buf[1] }; + struct i2c_msg msg = + { .addr = addr, .flags = 0, .buf = tmp, .len = 2 }; + int err; + int i; + + for (i=1; i<len; i++) { + tmp[1] = buf[i]; + if ((err = i2c_transfer(state->i2c, &msg, 1)) != 1) { + printk(KERN_WARNING "lgdt330x: %s error (addr %02x <- %02x, err == %i)\n", __FUNCTION__, addr, buf[0], err); + if (err < 0) + return err; + else + return -EREMOTEIO; + } + tmp[0]++; + } + return 0; +} + +#if 0 +static int i2c_readbytes (struct lgdt330x_state* state, + u8 addr, /* demod_address or pll_address */ + u8 *buf, /* holds data bytes read */ + int len /* number of bytes to read */ ) +{ + struct i2c_msg msg = + { .addr = addr, .flags = I2C_M_RD, .buf = buf, .len = len }; + int err; + + if ((err = i2c_transfer(state->i2c, &msg, 1)) != 1) { + printk(KERN_WARNING "lgdt330x: %s error (addr %02x, err == %i)\n", __FUNCTION__, addr, err); + return -EREMOTEIO; + } + return 0; +} +#endif + +/* + * This routine writes the register (reg) to the demod bus + * then reads the data returned for (len) bytes. + */ + +static u8 i2c_selectreadbytes (struct lgdt330x_state* state, + enum I2C_REG reg, u8* buf, int len) +{ + u8 wr [] = { reg }; + struct i2c_msg msg [] = { + { .addr = state->config->demod_address, + .flags = 0, .buf = wr, .len = 1 }, + { .addr = state->config->demod_address, + .flags = I2C_M_RD, .buf = buf, .len = len }, + }; + int ret; + ret = i2c_transfer(state->i2c, msg, 2); + if (ret != 2) { + printk(KERN_WARNING "lgdt330x: %s: addr 0x%02x select 0x%02x error (ret == %i)\n", __FUNCTION__, state->config->demod_address, reg, ret); + } else { + ret = 0; + } + return ret; +} + +/* Software reset */ +int lgdt330x_SwReset(struct lgdt330x_state* state) +{ + u8 ret; + u8 reset[] = { + IRQ_MASK, + 0x00 /* bit 6 is active low software reset + * bits 5-0 are 1 to mask interrupts */ + }; + + ret = i2c_writebytes(state, + state->config->demod_address, + reset, sizeof(reset)); + if (ret == 0) { + /* spec says reset takes 100 ns why wait */ + /* mdelay(100); */ /* keep low for 100mS */ + reset[1] = 0x7f; /* force reset high (inactive) + * and unmask interrupts */ + ret = i2c_writebytes(state, + state->config->demod_address, + reset, sizeof(reset)); + } + /* Spec does not indicate a need for this either */ + /*mdelay(5); */ /* wait 5 msec before doing more */ + return ret; +} + +static int lgdt330x_init(struct dvb_frontend* fe) +{ + /* Hardware reset is done using gpio[0] of cx23880x chip. + * I'd like to do it here, but don't know how to find chip address. + * cx88-cards.c arranges for the reset bit to be inactive (high). + * Maybe there needs to be a callable function in cx88-core or + * the caller of this function needs to do it. */ + + dprintk("%s entered\n", __FUNCTION__); + return lgdt330x_SwReset((struct lgdt330x_state*) fe->demodulator_priv); +} + +static int lgdt330x_read_ber(struct dvb_frontend* fe, u32* ber) +{ + *ber = 0; /* Dummy out for now */ + return 0; +} + +static int lgdt330x_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + struct lgdt330x_state* state = (struct lgdt330x_state*) fe->demodulator_priv; + u8 buf[2]; + + i2c_selectreadbytes(state, PACKET_ERR_COUNTER1, buf, sizeof(buf)); + + *ucblocks = (buf[0] << 8) | buf[1]; + return 0; +} + +static int lgdt330x_set_parameters(struct dvb_frontend* fe, + struct dvb_frontend_parameters *param) +{ + struct lgdt330x_state* state = + (struct lgdt330x_state*) fe->demodulator_priv; + + /* Use 50MHz parameter values from spec sheet since xtal is 50 */ + static u8 top_ctrl_cfg[] = { TOP_CONTROL, 0x03 }; + static u8 vsb_freq_cfg[] = { VSB_CARRIER_FREQ0, 0x00, 0x87, 0x8e, 0x01 }; + static u8 demux_ctrl_cfg[] = { DEMUX_CONTROL, 0xfb }; + static u8 agc_rf_cfg[] = { AGC_RF_BANDWIDTH0, 0x40, 0x93, 0x00 }; + static u8 agc_ctrl_cfg[] = { AGC_FUNC_CTRL2, 0xc6, 0x40 }; + static u8 agc_delay_cfg[] = { AGC_DELAY0, 0x07, 0x00, 0xfe }; + static u8 agc_loop_cfg[] = { AGC_LOOP_BANDWIDTH0, 0x08, 0x9a }; + + /* 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("%s: VSB_8 MODE\n", __FUNCTION__); + + /* Select VSB mode and serial MPEG interface */ + top_ctrl_cfg[1] = 0x07; + + /* Select ANT connector if supported by card */ + if (state->config->pll_rf_set) + state->config->pll_rf_set(fe, 1); + break; + + case QAM_64: + dprintk("%s: QAM_64 MODE\n", __FUNCTION__); + + /* Select QAM_64 mode and serial MPEG interface */ + top_ctrl_cfg[1] = 0x04; + + /* Select CABLE connector if supported by card */ + if (state->config->pll_rf_set) + state->config->pll_rf_set(fe, 0); + break; + + case QAM_256: + dprintk("%s: QAM_256 MODE\n", __FUNCTION__); + + /* Select QAM_256 mode and serial MPEG interface */ + top_ctrl_cfg[1] = 0x05; + + /* Select CABLE connector if supported by card */ + if (state->config->pll_rf_set) + state->config->pll_rf_set(fe, 0); + break; + default: + printk(KERN_WARNING "lgdt330x: %s: Modulation type(%d) UNSUPPORTED\n", __FUNCTION__, param->u.vsb.modulation); + return -1; + } + /* Initializations common to all modes */ + + /* Select the requested mode */ + i2c_writebytes(state, state->config->demod_address, + top_ctrl_cfg, sizeof(top_ctrl_cfg)); + + /* Change the value of IFBW[11:0] + of AGC IF/RF loop filter bandwidth register */ + i2c_writebytes(state, state->config->demod_address, + agc_rf_cfg, sizeof(agc_rf_cfg)); + + /* Change the value of bit 6, 'nINAGCBY' and + 'NSSEL[1:0] of ACG function control register 2 */ + /* Change the value of bit 6 'RFFIX' + of AGC function control register 3 */ + i2c_writebytes(state, state->config->demod_address, + agc_ctrl_cfg, sizeof(agc_ctrl_cfg)); + + /* Change the TPCLK pin polarity + data is valid on falling clock */ + i2c_writebytes(state, state->config->demod_address, + demux_ctrl_cfg, sizeof(demux_ctrl_cfg)); + + /* Change the value of NCOCTFV[25:0] of carrier + recovery center frequency register */ + i2c_writebytes(state, state->config->demod_address, + vsb_freq_cfg, sizeof(vsb_freq_cfg)); + + /* Set the value of 'INLVTHD' register 0x2a/0x2c to 0x7fe */ + i2c_writebytes(state, state->config->demod_address, + agc_delay_cfg, sizeof(agc_delay_cfg)); + + /* Change the value of IAGCBW[15:8] + of inner AGC loop filter bandwith */ + i2c_writebytes(state, state->config->demod_address, + agc_loop_cfg, sizeof(agc_loop_cfg)); + + state->config->set_ts_params(fe, 0); + state->current_modulation = param->u.vsb.modulation; + } + + /* Change only if we are actually changing the channel */ + if (state->current_frequency != param->frequency) { + u8 buf[5]; + struct i2c_msg msg = { .flags = 0, .buf = &buf[1], .len = 4 }; + int err; + + state->config->pll_set(fe, param, buf); + msg.addr = buf[0]; + + dprintk("%s: tuner at 0x%02x bytes: 0x%02x 0x%02x " + "0x%02x 0x%02x\n", __FUNCTION__, + buf[0],buf[1],buf[2],buf[3],buf[4]); + if ((err = i2c_transfer(state->i2c, &msg, 1)) != 1) { + printk(KERN_WARNING "lgdt330x: %s error (addr %02x <- %02x, err = %i)\n", __FUNCTION__, buf[0], buf[1], err); + if (err < 0) + return err; + else + return -EREMOTEIO; + } +#if 0 + /* Check the status of the tuner pll */ + i2c_readbytes(state, buf[0], &buf[1], 1); + dprintk("%s: tuner status byte = 0x%02x\n", __FUNCTION__, buf[1]); +#endif + /* Update current frequency */ + state->current_frequency = param->frequency; + } + lgdt330x_SwReset(state); + return 0; +} + +static int lgdt330x_get_frontend(struct dvb_frontend* fe, + struct dvb_frontend_parameters* param) +{ + struct lgdt330x_state *state = fe->demodulator_priv; + param->frequency = state->current_frequency; + return 0; +} + +static int lgdt330x_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct lgdt330x_state* state = (struct lgdt330x_state*) fe->demodulator_priv; + u8 buf[3]; + + *status = 0; /* Reset status result */ + + /* + * You must set the Mask bits to 1 in the IRQ_MASK in order + * to see that status bit in the IRQ_STATUS register. + * This is done in SwReset(); + */ + + /* AGC status register */ + i2c_selectreadbytes(state, AGC_STATUS, buf, 1); + dprintk("%s: AGC_STATUS = 0x%02x\n", __FUNCTION__, buf[0]); + if ((buf[0] & 0x0c) == 0x8){ + /* Test signal does not exist flag */ + /* as well as the AGC lock flag. */ + *status |= FE_HAS_SIGNAL; + } else { + /* Without a signal all other status bits are meaningless */ + return 0; + } + + /* signal status */ + i2c_selectreadbytes(state, TOP_CONTROL, buf, sizeof(buf)); + dprintk("%s: TOP_CONTROL = 0x%02x, IRO_MASK = 0x%02x, IRQ_STATUS = 0x%02x\n", __FUNCTION__, buf[0], buf[1], buf[2]); + +#if 0 + /* Alternative method to check for a signal */ + /* using the SNR good/bad interrupts. */ + if ((buf[2] & 0x30) == 0x10) + *status |= FE_HAS_SIGNAL; +#endif + + /* sync status */ + if ((buf[2] & 0x03) == 0x01) { + *status |= FE_HAS_SYNC; + } + + /* FEC error status */ + if ((buf[2] & 0x0c) == 0x08) { + *status |= FE_HAS_LOCK; + *status |= FE_HAS_VITERBI; + } + + /* Carrier Recovery Lock Status Register */ + i2c_selectreadbytes(state, CARRIER_LOCK, buf, 1); + dprintk("%s: CARRIER_LOCK = 0x%02x\n", __FUNCTION__, buf[0]); + switch (state->current_modulation) { + case QAM_256: + case QAM_64: + /* Need to undestand why there are 3 lock levels here */ + if ((buf[0] & 0x07) == 0x07) + *status |= FE_HAS_CARRIER; + break; + case VSB_8: + if ((buf[0] & 0x80) == 0x80) + *status |= FE_HAS_CARRIER; + break; + default: + printk("KERN_WARNING lgdt330x: %s: Modulation set to unsupported value\n", __FUNCTION__); + } + + return 0; +} + +static int lgdt330x_read_signal_strength(struct dvb_frontend* fe, u16* strength) +{ + /* not directly available. */ + return 0; +} + +static int lgdt330x_read_snr(struct dvb_frontend* fe, u16* snr) +{ +#ifdef SNR_IN_DB + /* + * Spec sheet shows formula for SNR_EQ = 10 log10(25 * 24**2 / noise) + * and SNR_PH = 10 log10(25 * 32**2 / noise) for equalizer and phase tracker + * respectively. The following tables are built on these formulas. + * The usual definition is SNR = 20 log10(signal/noise) + * If the specification is wrong the value retuned is 1/2 the actual SNR in db. + * + * This table is a an ordered list of noise values computed by the + * formula from the spec sheet such that the index into the table + * starting at 43 or 45 is the SNR value in db. There are duplicate noise + * value entries at the beginning because the SNR varies more than + * 1 db for a change of 1 digit in noise at very small values of noise. + * + * Examples from SNR_EQ table: + * noise SNR + * 0 43 + * 1 42 + * 2 39 + * 3 37 + * 4 36 + * 5 35 + * 6 34 + * 7 33 + * 8 33 + * 9 32 + * 10 32 + * 11 31 + * 12 31 + * 13 30 + */ + + static const u32 SNR_EQ[] = + { 1, 2, 2, 2, 3, 3, 4, 4, 5, 7, + 9, 11, 13, 17, 21, 26, 33, 41, 52, 65, + 81, 102, 129, 162, 204, 257, 323, 406, 511, 644, + 810, 1020, 1284, 1616, 2035, 2561, 3224, 4059, 5110, 6433, + 8098, 10195, 12835, 16158, 20341, 25608, 32238, 40585, 51094, 64323, + 80978, 101945, 128341, 161571, 203406, 256073, 0x40000 + }; + + static const u32 SNR_PH[] = + { 1, 2, 2, 2, 3, 3, 4, 5, 6, 8, + 10, 12, 15, 19, 23, 29, 37, 46, 58, 73, + 91, 115, 144, 182, 229, 288, 362, 456, 574, 722, + 909, 1144, 1440, 1813, 2282, 2873, 3617, 4553, 5732, 7216, + 9084, 11436, 14396, 18124, 22817, 28724, 36161, 45524, 57312, 72151, + 90833, 114351, 143960, 181235, 228161, 0x040000 + }; + + static u8 buf[5];/* read data buffer */ + static u32 noise; /* noise value */ + static u32 snr_db; /* index into SNR_EQ[] */ + struct lgdt330x_state* state = (struct lgdt330x_state*) fe->demodulator_priv; + + /* read both equalizer and pase tracker noise data */ + i2c_selectreadbytes(state, EQPH_ERR0, buf, sizeof(buf)); + + if (state->current_modulation == VSB_8) { + /* Equalizer Mean-Square Error Register for VSB */ + noise = ((buf[0] & 7) << 16) | (buf[1] << 8) | buf[2]; + + /* + * Look up noise value in table. + * A better search algorithm could be used... + * watch out there are duplicate entries. + */ + for (snr_db = 0; snr_db < sizeof(SNR_EQ); snr_db++) { + if (noise < SNR_EQ[snr_db]) { + *snr = 43 - snr_db; + break; + } + } + } else { + /* Phase Tracker Mean-Square Error Register for QAM */ + noise = ((buf[0] & 7<<3) << 13) | (buf[3] << 8) | buf[4]; + + /* Look up noise value in table. */ + for (snr_db = 0; snr_db < sizeof(SNR_PH); snr_db++) { + if (noise < SNR_PH[snr_db]) { + *snr = 45 - snr_db; + break; + } + } + } +#else + /* Return the raw noise value */ + static u8 buf[5];/* read data buffer */ + static u32 noise; /* noise value */ + struct lgdt330x_state* state = (struct lgdt330x_state*) fe->demodulator_priv; + + /* read both equalizer and pase tracker noise data */ + i2c_selectreadbytes(state, EQPH_ERR0, buf, sizeof(buf)); + + if (state->current_modulation == VSB_8) { + /* Equalizer Mean-Square Error Register for VSB */ + noise = ((buf[0] & 7) << 16) | (buf[1] << 8) | buf[2]; + } else { + /* Phase Tracker Mean-Square Error Register for QAM */ + noise = ((buf[0] & 7<<3) << 13) | (buf[3] << 8) | buf[4]; + } + + /* Small values for noise mean signal is better so invert noise */ + /* Noise is 19 bit value so discard 3 LSB*/ + *snr = ~noise>>3; +#endif + + dprintk("%s: noise = 0x%05x, snr = %idb\n",__FUNCTION__, noise, *snr); + + return 0; +} + +static int lgdt330x_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* fe_tune_settings) +{ + /* I have no idea about this - it may not be needed */ + fe_tune_settings->min_delay_ms = 500; + fe_tune_settings->step_size = 0; + fe_tune_settings->max_drift = 0; + return 0; +} + +static void lgdt330x_release(struct dvb_frontend* fe) +{ + struct lgdt330x_state* state = (struct lgdt330x_state*) fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops lgdt330x_ops; + +struct dvb_frontend* lgdt330x_attach(const struct lgdt330x_config* config, + struct i2c_adapter* i2c) +{ + struct lgdt330x_state* state = NULL; + u8 buf[1]; + + /* Allocate memory for the internal state */ + state = (struct lgdt330x_state*) kmalloc(sizeof(struct lgdt330x_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, &lgdt330x_ops, sizeof(struct dvb_frontend_ops)); + /* Verify communication with demod chip */ + if (i2c_selectreadbytes(state, 2, buf, 1)) + goto error; + + 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); + dprintk("%s: ERROR\n",__FUNCTION__); + return NULL; +} + +static struct dvb_frontend_ops lgdt330x_ops = { + .info = { + .name= "LG Electronics lgdt330x VSB/QAM Frontend", + .type = FE_ATSC, + .frequency_min= 54000000, + .frequency_max= 858000000, + .frequency_stepsize= 62500, + /* Symbol rate is for all VSB modes need to check QAM */ + .symbol_rate_min = 10762000, + .symbol_rate_max = 10762000, + .caps = FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_8VSB + }, + .init = lgdt330x_init, + .set_frontend = lgdt330x_set_parameters, + .get_frontend = lgdt330x_get_frontend, + .get_tune_settings = lgdt330x_get_tune_settings, + .read_status = lgdt330x_read_status, + .read_ber = lgdt330x_read_ber, + .read_signal_strength = lgdt330x_read_signal_strength, + .read_snr = lgdt330x_read_snr, + .read_ucblocks = lgdt330x_read_ucblocks, + .release = lgdt330x_release, +}; + +MODULE_DESCRIPTION("lgdt330x [DViCO FusionHDTV 3 Gold] (ATSC 8VSB & ITU-T J.83 AnnexB 64/256 QAM) Demodulator Driver"); +MODULE_AUTHOR("Wilson Michaels"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(lgdt330x_attach); + +/* + * Local variables: + * c-basic-offset: 8 + * compile-command: "make DVB=1" + * End: + */ diff --git a/drivers/media/dvb/frontends/lgdt330x.h b/drivers/media/dvb/frontends/lgdt330x.h new file mode 100644 index 00000000000..04986f8e756 --- /dev/null +++ b/drivers/media/dvb/frontends/lgdt330x.h @@ -0,0 +1,49 @@ +/* + * Support for LGDT3302 & LGDT3303 (DViCO FustionHDTV Gold) - VSB/QAM + * + * Copyright (C) 2005 Wilson Michaels <wilsonmichaels@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. + * + */ + +#ifndef LGDT330X_H +#define LGDT330X_H + +#include <linux/dvb/frontend.h> + +struct lgdt330x_config +{ + /* The demodulator's i2c address */ + u8 demod_address; + + /* PLL interface */ + int (*pll_rf_set) (struct dvb_frontend* fe, int index); + int (*pll_set)(struct dvb_frontend* fe, struct dvb_frontend_parameters* params, u8* pll_address); + + /* Need to set device param for start_dma */ + int (*set_ts_params)(struct dvb_frontend* fe, int is_punctured); +}; + +extern struct dvb_frontend* lgdt330x_attach(const struct lgdt330x_config* config, + struct i2c_adapter* i2c); + +#endif /* LGDT330X_H */ + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/dvb/frontends/lgdt330x_priv.h b/drivers/media/dvb/frontends/lgdt330x_priv.h new file mode 100644 index 00000000000..4143ce8f1a9 --- /dev/null +++ b/drivers/media/dvb/frontends/lgdt330x_priv.h @@ -0,0 +1,70 @@ +/* + * Support for LGDT3302 & LGDT3303 (DViCO FustionHDTV Gold) - VSB/QAM + * + * Copyright (C) 2005 Wilson Michaels <wilsonmichaels@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. + * + */ + +#ifndef _LGDT330X_PRIV_ +#define _LGDT330X_PRIV_ + +/* i2c control register addresses */ +enum I2C_REG { + TOP_CONTROL= 0x00, + IRQ_MASK= 0x01, + IRQ_STATUS= 0x02, + VSB_CARRIER_FREQ0= 0x16, + VSB_CARRIER_FREQ1= 0x17, + VSB_CARRIER_FREQ2= 0x18, + VSB_CARRIER_FREQ3= 0x19, + CARRIER_MSEQAM1= 0x1a, + CARRIER_MSEQAM2= 0x1b, + CARRIER_LOCK= 0x1c, + TIMING_RECOVERY= 0x1d, + AGC_DELAY0= 0x2a, + AGC_DELAY1= 0x2b, + AGC_DELAY2= 0x2c, + AGC_RF_BANDWIDTH0= 0x2d, + AGC_RF_BANDWIDTH1= 0x2e, + AGC_RF_BANDWIDTH2= 0x2f, + AGC_LOOP_BANDWIDTH0= 0x30, + AGC_LOOP_BANDWIDTH1= 0x31, + AGC_FUNC_CTRL1= 0x32, + AGC_FUNC_CTRL2= 0x33, + AGC_FUNC_CTRL3= 0x34, + AGC_RFIF_ACC0= 0x39, + AGC_RFIF_ACC1= 0x3a, + AGC_RFIF_ACC2= 0x3b, + AGC_STATUS= 0x3f, + SYNC_STATUS_VSB= 0x43, + EQPH_ERR0= 0x47, + EQ_ERR1= 0x48, + EQ_ERR2= 0x49, + PH_ERR1= 0x4a, + PH_ERR2= 0x4b, + DEMUX_CONTROL= 0x66, + PACKET_ERR_COUNTER1= 0x6a, + PACKET_ERR_COUNTER2= 0x6b, +}; + +#endif /* _LGDT330X_PRIV_ */ + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/dvb/frontends/s5h1420.c b/drivers/media/dvb/frontends/s5h1420.c new file mode 100644 index 00000000000..4f396ac8de7 --- /dev/null +++ b/drivers/media/dvb/frontends/s5h1420.c @@ -0,0 +1,800 @@ +/* +Driver for Samsung S5H1420 QPSK Demodulator + +Copyright (C) 2005 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/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 "s5h1420.h" + + + +#define TONE_FREQ 22000 + +struct s5h1420_state { + struct i2c_adapter* i2c; + struct dvb_frontend_ops ops; + const struct s5h1420_config* config; + struct dvb_frontend frontend; + + u8 postlocked:1; + u32 fclk; + u32 tunedfreq; + fe_code_rate_t fec_inner; + u32 symbol_rate; +}; + +static u32 s5h1420_getsymbolrate(struct s5h1420_state* state); +static int s5h1420_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* fesettings); + + +static int debug = 0; +#define dprintk if (debug) printk + +static int s5h1420_writereg (struct s5h1420_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 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 s5h1420_readreg (struct s5h1420_state* state, u8 reg) +{ + int ret; + u8 b0 [] = { reg }; + u8 b1 [] = { 0 }; + struct i2c_msg msg1 = { .addr = state->config->demod_address, .flags = 0, .buf = b0, .len = 1 }; + struct i2c_msg msg2 = { .addr = state->config->demod_address, .flags = I2C_M_RD, .buf = b1, .len = 1 }; + + if ((ret = i2c_transfer (state->i2c, &msg1, 1)) != 1) + return ret; + + if ((ret = i2c_transfer (state->i2c, &msg2, 1)) != 1) + return ret; + + return b1[0]; +} + +static int s5h1420_set_voltage (struct dvb_frontend* fe, fe_sec_voltage_t voltage) +{ + struct s5h1420_state* state = fe->demodulator_priv; + + switch(voltage) { + case SEC_VOLTAGE_13: + s5h1420_writereg(state, 0x3c, (s5h1420_readreg(state, 0x3c) & 0xfe) | 0x02); + break; + + case SEC_VOLTAGE_18: + s5h1420_writereg(state, 0x3c, s5h1420_readreg(state, 0x3c) | 0x03); + break; + + case SEC_VOLTAGE_OFF: + s5h1420_writereg(state, 0x3c, s5h1420_readreg(state, 0x3c) & 0xfd); + break; + } + + return 0; +} + +static int s5h1420_set_tone (struct dvb_frontend* fe, fe_sec_tone_mode_t tone) +{ + struct s5h1420_state* state = fe->demodulator_priv; + + switch(tone) { + case SEC_TONE_ON: + s5h1420_writereg(state, 0x3b, (s5h1420_readreg(state, 0x3b) & 0x74) | 0x08); + break; + + case SEC_TONE_OFF: + s5h1420_writereg(state, 0x3b, (s5h1420_readreg(state, 0x3b) & 0x74) | 0x01); + break; + } + + return 0; +} + +static int s5h1420_send_master_cmd (struct dvb_frontend* fe, struct dvb_diseqc_master_cmd* cmd) +{ + struct s5h1420_state* state = fe->demodulator_priv; + u8 val; + int i; + unsigned long timeout; + int result = 0; + + /* setup for DISEQC */ + val = s5h1420_readreg(state, 0x3b); + s5h1420_writereg(state, 0x3b, 0x02); + msleep(15); + + /* write the DISEQC command bytes */ + for(i=0; i< cmd->msg_len; i++) { + s5h1420_writereg(state, 0x3c + i, cmd->msg[i]); + } + + /* kick off transmission */ + s5h1420_writereg(state, 0x3b, s5h1420_readreg(state, 0x3b) | ((cmd->msg_len-1) << 4) | 0x08); + + /* wait for transmission to complete */ + timeout = jiffies + ((100*HZ) / 1000); + while(time_before(jiffies, timeout)) { + if (s5h1420_readreg(state, 0x3b) & 0x08) + break; + + msleep(5); + } + if (time_after(jiffies, timeout)) + result = -ETIMEDOUT; + + /* restore original settings */ + s5h1420_writereg(state, 0x3b, val); + msleep(15); + return result; +} + +static int s5h1420_recv_slave_reply (struct dvb_frontend* fe, struct dvb_diseqc_slave_reply* reply) +{ + struct s5h1420_state* state = fe->demodulator_priv; + u8 val; + int i; + int length; + unsigned long timeout; + int result = 0; + + /* setup for DISEQC recieve */ + val = s5h1420_readreg(state, 0x3b); + s5h1420_writereg(state, 0x3b, 0x82); /* FIXME: guess - do we need to set DIS_RDY(0x08) in receive mode? */ + msleep(15); + + /* wait for reception to complete */ + timeout = jiffies + ((reply->timeout*HZ) / 1000); + while(time_before(jiffies, timeout)) { + if (!(s5h1420_readreg(state, 0x3b) & 0x80)) /* FIXME: do we test DIS_RDY(0x08) or RCV_EN(0x80)? */ + break; + + msleep(5); + } + if (time_after(jiffies, timeout)) { + result = -ETIMEDOUT; + goto exit; + } + + /* check error flag - FIXME: not sure what this does - docs do not describe + * beyond "error flag for diseqc receive data :( */ + if (s5h1420_readreg(state, 0x49)) { + result = -EIO; + goto exit; + } + + /* check length */ + length = (s5h1420_readreg(state, 0x3b) & 0x70) >> 4; + if (length > sizeof(reply->msg)) { + result = -EOVERFLOW; + goto exit; + } + reply->msg_len = length; + + /* extract data */ + for(i=0; i< length; i++) { + reply->msg[i] = s5h1420_readreg(state, 0x3c + i); + } + +exit: + /* restore original settings */ + s5h1420_writereg(state, 0x3b, val); + msleep(15); + return result; +} + +static int s5h1420_send_burst (struct dvb_frontend* fe, fe_sec_mini_cmd_t minicmd) +{ + struct s5h1420_state* state = fe->demodulator_priv; + u8 val; + int result = 0; + unsigned long timeout; + + /* setup for tone burst */ + val = s5h1420_readreg(state, 0x3b); + s5h1420_writereg(state, 0x3b, (s5h1420_readreg(state, 0x3b) & 0x70) | 0x01); + + /* set value for B position if requested */ + if (minicmd == SEC_MINI_B) { + s5h1420_writereg(state, 0x3b, s5h1420_readreg(state, 0x3b) | 0x04); + } + msleep(15); + + /* start transmission */ + s5h1420_writereg(state, 0x3b, s5h1420_readreg(state, 0x3b) | 0x08); + + /* wait for transmission to complete */ + timeout = jiffies + ((20*HZ) / 1000); + while(time_before(jiffies, timeout)) { + if (!(s5h1420_readreg(state, 0x3b) & 0x08)) + break; + + msleep(5); + } + if (time_after(jiffies, timeout)) + result = -ETIMEDOUT; + + /* restore original settings */ + s5h1420_writereg(state, 0x3b, val); + msleep(15); + return result; +} + +static fe_status_t s5h1420_get_status_bits(struct s5h1420_state* state) +{ + u8 val; + fe_status_t status = 0; + + val = s5h1420_readreg(state, 0x14); + if (val & 0x02) + status |= FE_HAS_SIGNAL; // FIXME: not sure if this is right + if (val & 0x01) + status |= FE_HAS_CARRIER; // FIXME: not sure if this is right + val = s5h1420_readreg(state, 0x36); + if (val & 0x01) + status |= FE_HAS_VITERBI; + if (val & 0x20) + status |= FE_HAS_SYNC; + if (status == (FE_HAS_SIGNAL|FE_HAS_CARRIER|FE_HAS_VITERBI|FE_HAS_SYNC)) + status |= FE_HAS_LOCK; + + return status; +} + +static int s5h1420_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct s5h1420_state* state = fe->demodulator_priv; + u8 val; + + if (status == NULL) + return -EINVAL; + + /* determine lock state */ + *status = s5h1420_get_status_bits(state); + + /* fix for FEC 5/6 inversion issue - if it doesn't quite lock, invert the inversion, + wait a bit and check again */ + if (*status == (FE_HAS_SIGNAL|FE_HAS_CARRIER|FE_HAS_VITERBI)) { + val = s5h1420_readreg(state, 0x32); + if ((val & 0x07) == 0x03) { + if (val & 0x08) + s5h1420_writereg(state, 0x31, 0x13); + else + s5h1420_writereg(state, 0x31, 0x1b); + + /* wait a bit then update lock status */ + mdelay(200); + *status = s5h1420_get_status_bits(state); + } + } + + /* perform post lock setup */ + if ((*status & FE_HAS_LOCK) && (!state->postlocked)) { + + /* calculate the data rate */ + u32 tmp = s5h1420_getsymbolrate(state); + switch(s5h1420_readreg(state, 0x32) & 0x07) { + case 0: + tmp = (tmp * 2 * 1) / 2; + break; + + case 1: + tmp = (tmp * 2 * 2) / 3; + break; + + case 2: + tmp = (tmp * 2 * 3) / 4; + break; + + case 3: + tmp = (tmp * 2 * 5) / 6; + break; + + case 4: + tmp = (tmp * 2 * 6) / 7; + break; + + case 5: + tmp = (tmp * 2 * 7) / 8; + break; + } + tmp = state->fclk / tmp; + + /* set the MPEG_CLK_INTL for the calculated data rate */ + if (tmp < 4) + val = 0x00; + else if (tmp < 8) + val = 0x01; + else if (tmp < 12) + val = 0x02; + else if (tmp < 16) + val = 0x03; + else if (tmp < 24) + val = 0x04; + else if (tmp < 32) + val = 0x05; + else + val = 0x06; + s5h1420_writereg(state, 0x22, val); + + /* DC freeze */ + s5h1420_writereg(state, 0x1f, s5h1420_readreg(state, 0x1f) | 0x01); + + /* kicker disable + remove DC offset */ + s5h1420_writereg(state, 0x05, s5h1420_readreg(state, 0x05) & 0x6f); + + /* post-lock processing has been done! */ + state->postlocked = 1; + } + + return 0; +} + +static int s5h1420_read_ber(struct dvb_frontend* fe, u32* ber) +{ + struct s5h1420_state* state = fe->demodulator_priv; + + s5h1420_writereg(state, 0x46, 0x1d); + mdelay(25); + return (s5h1420_readreg(state, 0x48) << 8) | s5h1420_readreg(state, 0x47); +} + +static int s5h1420_read_signal_strength(struct dvb_frontend* fe, u16* strength) +{ + struct s5h1420_state* state = fe->demodulator_priv; + + u8 val = 0xff - s5h1420_readreg(state, 0x15); + + return (int) ((val << 8) | val); +} + +static int s5h1420_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + struct s5h1420_state* state = fe->demodulator_priv; + + s5h1420_writereg(state, 0x46, 0x1f); + mdelay(25); + return (s5h1420_readreg(state, 0x48) << 8) | s5h1420_readreg(state, 0x47); +} + +static void s5h1420_reset(struct s5h1420_state* state) +{ + s5h1420_writereg (state, 0x01, 0x08); + s5h1420_writereg (state, 0x01, 0x00); + udelay(10); +} + +static void s5h1420_setsymbolrate(struct s5h1420_state* state, struct dvb_frontend_parameters *p) +{ + u64 val; + + val = (p->u.qpsk.symbol_rate / 1000) * (1<<24); + if (p->u.qpsk.symbol_rate <= 21000000) { + val *= 2; + } + do_div(val, (state->fclk / 1000)); + + s5h1420_writereg(state, 0x09, s5h1420_readreg(state, 0x09) & 0x7f); + s5h1420_writereg(state, 0x11, val >> 16); + s5h1420_writereg(state, 0x12, val >> 8); + s5h1420_writereg(state, 0x13, val & 0xff); + s5h1420_writereg(state, 0x09, s5h1420_readreg(state, 0x09) | 0x80); +} + +static u32 s5h1420_getsymbolrate(struct s5h1420_state* state) +{ + u64 val; + int sampling = 2; + + if (s5h1420_readreg(state, 0x05) & 0x2) + sampling = 1; + + s5h1420_writereg(state, 0x06, s5h1420_readreg(state, 0x06) | 0x08); + val = s5h1420_readreg(state, 0x11) << 16; + val |= s5h1420_readreg(state, 0x12) << 8; + val |= s5h1420_readreg(state, 0x13); + s5h1420_writereg(state, 0x06, s5h1420_readreg(state, 0x06) & 0xf7); + + val *= (state->fclk / 1000); + do_div(val, ((1<<24) * sampling)); + + return (u32) (val * 1000); +} + +static void s5h1420_setfreqoffset(struct s5h1420_state* state, int freqoffset) +{ + int val; + + /* remember freqoffset is in kHz, but the chip wants the offset in Hz, so + * divide fclk by 1000000 to get the correct value. */ + val = -(int) ((freqoffset * (1<<24)) / (state->fclk / 1000000)); + + s5h1420_writereg(state, 0x09, s5h1420_readreg(state, 0x09) & 0xbf); + s5h1420_writereg(state, 0x0e, val >> 16); + s5h1420_writereg(state, 0x0f, val >> 8); + s5h1420_writereg(state, 0x10, val & 0xff); + s5h1420_writereg(state, 0x09, s5h1420_readreg(state, 0x09) | 0x40); +} + +static int s5h1420_getfreqoffset(struct s5h1420_state* state) +{ + int val; + + s5h1420_writereg(state, 0x06, s5h1420_readreg(state, 0x06) | 0x08); + val = s5h1420_readreg(state, 0x0e) << 16; + val |= s5h1420_readreg(state, 0x0f) << 8; + val |= s5h1420_readreg(state, 0x10); + s5h1420_writereg(state, 0x06, s5h1420_readreg(state, 0x06) & 0xf7); + + if (val & 0x800000) + val |= 0xff000000; + + /* remember freqoffset is in kHz, but the chip wants the offset in Hz, so + * divide fclk by 1000000 to get the correct value. */ + val = - ((val * (state->fclk/1000000)) / (1<<24)); + + return val; +} + +static void s5h1420_setfec(struct s5h1420_state* state, struct dvb_frontend_parameters *p) +{ + if ((p->u.qpsk.fec_inner == FEC_AUTO) || (p->inversion == INVERSION_AUTO)) { + s5h1420_writereg(state, 0x31, 0x00); + s5h1420_writereg(state, 0x30, 0x3f); + } else { + switch(p->u.qpsk.fec_inner) { + case FEC_1_2: + s5h1420_writereg(state, 0x31, 0x10); + s5h1420_writereg(state, 0x30, 0x01); + break; + + case FEC_2_3: + s5h1420_writereg(state, 0x31, 0x11); + s5h1420_writereg(state, 0x30, 0x02); + break; + + case FEC_3_4: + s5h1420_writereg(state, 0x31, 0x12); + s5h1420_writereg(state, 0x30, 0x04); + break; + + case FEC_5_6: + s5h1420_writereg(state, 0x31, 0x13); + s5h1420_writereg(state, 0x30, 0x08); + break; + + case FEC_6_7: + s5h1420_writereg(state, 0x31, 0x14); + s5h1420_writereg(state, 0x30, 0x10); + break; + + case FEC_7_8: + s5h1420_writereg(state, 0x31, 0x15); + s5h1420_writereg(state, 0x30, 0x20); + break; + + default: + return; + } + } +} + +static fe_code_rate_t s5h1420_getfec(struct s5h1420_state* state) +{ + switch(s5h1420_readreg(state, 0x32) & 0x07) { + 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_6_7; + + case 5: + return FEC_7_8; + } + + return FEC_NONE; +} + +static void s5h1420_setinversion(struct s5h1420_state* state, struct dvb_frontend_parameters *p) +{ + if ((p->u.qpsk.fec_inner == FEC_AUTO) || (p->inversion == INVERSION_AUTO)) { + s5h1420_writereg(state, 0x31, 0x00); + s5h1420_writereg(state, 0x30, 0x3f); + } else { + u8 tmp = s5h1420_readreg(state, 0x31) & 0xf7; + tmp |= 0x10; + + if (p->inversion == INVERSION_ON) + tmp |= 0x80; + + s5h1420_writereg(state, 0x31, tmp); + } +} + +static fe_spectral_inversion_t s5h1420_getinversion(struct s5h1420_state* state) +{ + if (s5h1420_readreg(state, 0x32) & 0x08) + return INVERSION_ON; + + return INVERSION_OFF; +} + +static int s5h1420_set_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + struct s5h1420_state* state = fe->demodulator_priv; + u32 frequency_delta; + struct dvb_frontend_tune_settings fesettings; + + /* check if we should do a fast-tune */ + memcpy(&fesettings.parameters, p, sizeof(struct dvb_frontend_parameters)); + s5h1420_get_tune_settings(fe, &fesettings); + frequency_delta = p->frequency - state->tunedfreq; + if ((frequency_delta > -fesettings.max_drift) && (frequency_delta < fesettings.max_drift) && + (frequency_delta != 0) && + (state->fec_inner == p->u.qpsk.fec_inner) && + (state->symbol_rate == p->u.qpsk.symbol_rate)) { + + s5h1420_setfreqoffset(state, frequency_delta); + return 0; + } + + /* first of all, software reset */ + s5h1420_reset(state); + + /* set tuner PLL */ + if (state->config->pll_set) { + s5h1420_writereg (state, 0x02, s5h1420_readreg(state,0x02) | 1); + state->config->pll_set(fe, p, &state->tunedfreq); + s5h1420_writereg (state, 0x02, s5h1420_readreg(state,0x02) & 0xfe); + } + + /* set s5h1420 fclk PLL according to desired symbol rate */ + if (p->u.qpsk.symbol_rate > 28000000) { + state->fclk = 88000000; + s5h1420_writereg(state, 0x03, 0x50); + s5h1420_writereg(state, 0x04, 0x40); + s5h1420_writereg(state, 0x05, 0xae); + } else if (p->u.qpsk.symbol_rate > 21000000) { + state->fclk = 59000000; + s5h1420_writereg(state, 0x03, 0x33); + s5h1420_writereg(state, 0x04, 0x40); + s5h1420_writereg(state, 0x05, 0xae); + } else { + state->fclk = 88000000; + s5h1420_writereg(state, 0x03, 0x50); + s5h1420_writereg(state, 0x04, 0x40); + s5h1420_writereg(state, 0x05, 0xac); + } + + /* set misc registers */ + s5h1420_writereg(state, 0x02, 0x00); + s5h1420_writereg(state, 0x07, 0xb0); + s5h1420_writereg(state, 0x0a, 0x67); + s5h1420_writereg(state, 0x0b, 0x78); + s5h1420_writereg(state, 0x0c, 0x48); + s5h1420_writereg(state, 0x0d, 0x6b); + s5h1420_writereg(state, 0x2e, 0x8e); + s5h1420_writereg(state, 0x35, 0x33); + s5h1420_writereg(state, 0x38, 0x01); + s5h1420_writereg(state, 0x39, 0x7d); + s5h1420_writereg(state, 0x3a, (state->fclk + (TONE_FREQ * 32) - 1) / (TONE_FREQ * 32)); + s5h1420_writereg(state, 0x3c, 0x00); + s5h1420_writereg(state, 0x45, 0x61); + s5h1420_writereg(state, 0x46, 0x1d); + + /* start QPSK */ + s5h1420_writereg(state, 0x05, s5h1420_readreg(state, 0x05) | 1); + + /* set the frequency offset to adjust for PLL inaccuracy */ + s5h1420_setfreqoffset(state, p->frequency - state->tunedfreq); + + /* set the reset of the parameters */ + s5h1420_setsymbolrate(state, p); + s5h1420_setinversion(state, p); + s5h1420_setfec(state, p); + + state->fec_inner = p->u.qpsk.fec_inner; + state->symbol_rate = p->u.qpsk.symbol_rate; + state->postlocked = 0; + return 0; +} + +static int s5h1420_get_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + struct s5h1420_state* state = fe->demodulator_priv; + + p->frequency = state->tunedfreq + s5h1420_getfreqoffset(state); + p->inversion = s5h1420_getinversion(state); + p->u.qpsk.symbol_rate = s5h1420_getsymbolrate(state); + p->u.qpsk.fec_inner = s5h1420_getfec(state); + + return 0; +} + +static int s5h1420_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* fesettings) +{ + if (fesettings->parameters.u.qpsk.symbol_rate > 20000000) { + fesettings->min_delay_ms = 50; + fesettings->step_size = 2000; + fesettings->max_drift = 8000; + } else if (fesettings->parameters.u.qpsk.symbol_rate > 12000000) { + fesettings->min_delay_ms = 100; + fesettings->step_size = 1500; + fesettings->max_drift = 9000; + } else if (fesettings->parameters.u.qpsk.symbol_rate > 8000000) { + fesettings->min_delay_ms = 100; + fesettings->step_size = 1000; + fesettings->max_drift = 8000; + } else if (fesettings->parameters.u.qpsk.symbol_rate > 4000000) { + fesettings->min_delay_ms = 100; + fesettings->step_size = 500; + fesettings->max_drift = 7000; + } else if (fesettings->parameters.u.qpsk.symbol_rate > 2000000) { + fesettings->min_delay_ms = 200; + fesettings->step_size = (fesettings->parameters.u.qpsk.symbol_rate / 8000); + fesettings->max_drift = 14 * fesettings->step_size; + } else { + fesettings->min_delay_ms = 200; + fesettings->step_size = (fesettings->parameters.u.qpsk.symbol_rate / 8000); + fesettings->max_drift = 18 * fesettings->step_size; + } + + return 0; +} + +static int s5h1420_init (struct dvb_frontend* fe) +{ + struct s5h1420_state* state = fe->demodulator_priv; + + /* disable power down and do reset */ + s5h1420_writereg(state, 0x02, 0x10); + msleep(10); + s5h1420_reset(state); + + /* init PLL */ + if (state->config->pll_init) { + s5h1420_writereg (state, 0x02, s5h1420_readreg(state,0x02) | 1); + state->config->pll_init(fe); + s5h1420_writereg (state, 0x02, s5h1420_readreg(state,0x02) & 0xfe); + } + + return 0; +} + +static int s5h1420_sleep(struct dvb_frontend* fe) +{ + struct s5h1420_state* state = fe->demodulator_priv; + + return s5h1420_writereg(state, 0x02, 0x12); +} + +static void s5h1420_release(struct dvb_frontend* fe) +{ + struct s5h1420_state* state = fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops s5h1420_ops; + +struct dvb_frontend* s5h1420_attach(const struct s5h1420_config* config, struct i2c_adapter* i2c) +{ + struct s5h1420_state* state = NULL; + u8 identity; + + /* allocate memory for the internal state */ + state = kmalloc(sizeof(struct s5h1420_state), GFP_KERNEL); + if (state == NULL) + goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + memcpy(&state->ops, &s5h1420_ops, sizeof(struct dvb_frontend_ops)); + state->postlocked = 0; + state->fclk = 88000000; + state->tunedfreq = 0; + state->fec_inner = FEC_NONE; + state->symbol_rate = 0; + + /* check if the demod is there + identify it */ + identity = s5h1420_readreg(state, 0x00); + if (identity != 0x03) + 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 s5h1420_ops = { + + .info = { + .name = "Samsung S5H1420 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_6_7 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | + FE_CAN_QPSK + }, + + .release = s5h1420_release, + + .init = s5h1420_init, + .sleep = s5h1420_sleep, + + .set_frontend = s5h1420_set_frontend, + .get_frontend = s5h1420_get_frontend, + .get_tune_settings = s5h1420_get_tune_settings, + + .read_status = s5h1420_read_status, + .read_ber = s5h1420_read_ber, + .read_signal_strength = s5h1420_read_signal_strength, + .read_ucblocks = s5h1420_read_ucblocks, + + .diseqc_send_master_cmd = s5h1420_send_master_cmd, + .diseqc_recv_slave_reply = s5h1420_recv_slave_reply, + .diseqc_send_burst = s5h1420_send_burst, + .set_tone = s5h1420_set_tone, + .set_voltage = s5h1420_set_voltage, +}; + +module_param(debug, int, 0644); + +MODULE_DESCRIPTION("Samsung S5H1420 DVB-S Demodulator driver"); +MODULE_AUTHOR("Andrew de Quincey"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(s5h1420_attach); diff --git a/drivers/media/dvb/frontends/s5h1420.h b/drivers/media/dvb/frontends/s5h1420.h new file mode 100644 index 00000000000..b687fc77ceb --- /dev/null +++ b/drivers/media/dvb/frontends/s5h1420.h @@ -0,0 +1,41 @@ +/* + Driver for S5H1420 QPSK Demodulators + + Copyright (C) 2005 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 S5H1420_H +#define S5H1420_H + +#include <linux/dvb/frontend.h> + +struct s5h1420_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, u32* freqout); +}; + +extern struct dvb_frontend* s5h1420_attach(const struct s5h1420_config* config, + struct i2c_adapter* i2c); + +#endif // S5H1420_H diff --git a/drivers/media/dvb/frontends/stv0297.c b/drivers/media/dvb/frontends/stv0297.c index e681263bf07..928aca052af 100644 --- a/drivers/media/dvb/frontends/stv0297.c +++ b/drivers/media/dvb/frontends/stv0297.c @@ -617,7 +617,7 @@ static int stv0297_set_frontend(struct dvb_frontend *fe, struct dvb_frontend_par /* wait for WGAGC lock */ starttime = jiffies; - timeout = jiffies + (200 * HZ) / 1000; + timeout = jiffies + msecs_to_jiffies(2000); while (time_before(jiffies, timeout)) { msleep(10); if (stv0297_readreg(state, 0x43) & 0x08) @@ -629,7 +629,7 @@ static int stv0297_set_frontend(struct dvb_frontend *fe, struct dvb_frontend_par msleep(20); /* wait for equaliser partial convergence */ - timeout = jiffies + (50 * HZ) / 1000; + timeout = jiffies + msecs_to_jiffies(500); while (time_before(jiffies, timeout)) { msleep(10); @@ -642,7 +642,7 @@ static int stv0297_set_frontend(struct dvb_frontend *fe, struct dvb_frontend_par } /* wait for equaliser full convergence */ - timeout = jiffies + (delay * HZ) / 1000; + timeout = jiffies + msecs_to_jiffies(delay); while (time_before(jiffies, timeout)) { msleep(10); @@ -659,7 +659,7 @@ static int stv0297_set_frontend(struct dvb_frontend *fe, struct dvb_frontend_par stv0297_writereg_mask(state, 0x88, 8, 0); /* wait for main lock */ - timeout = jiffies + (20 * HZ) / 1000; + timeout = jiffies + msecs_to_jiffies(20); while (time_before(jiffies, timeout)) { msleep(10); diff --git a/drivers/media/dvb/frontends/tda1004x.c b/drivers/media/dvb/frontends/tda1004x.c index 0beb370792a..ab0c032472c 100644 --- a/drivers/media/dvb/frontends/tda1004x.c +++ b/drivers/media/dvb/frontends/tda1004x.c @@ -49,10 +49,8 @@ struct tda1004x_state { /* private demod data */ u8 initialised; enum tda1004x_demod demod_type; - u8 fw_version; }; - static int debug; #define dprintk(args...) \ do { \ @@ -122,6 +120,8 @@ static int debug; #define TDA10046H_GPIO_OUT_SEL 0x41 #define TDA10046H_GPIO_SELECT 0x42 #define TDA10046H_AGC_CONF 0x43 +#define TDA10046H_AGC_THR 0x44 +#define TDA10046H_AGC_RENORM 0x45 #define TDA10046H_AGC_GAINS 0x46 #define TDA10046H_AGC_TUN_MIN 0x47 #define TDA10046H_AGC_TUN_MAX 0x48 @@ -274,14 +274,26 @@ static int tda10046h_set_bandwidth(struct tda1004x_state *state, switch (bandwidth) { case BANDWIDTH_6_MHZ: tda1004x_write_buf(state, TDA10046H_TIME_WREF1, bandwidth_6mhz, sizeof(bandwidth_6mhz)); + if (state->config->if_freq == TDA10046_FREQ_045) { + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_MSB, 0x09); + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_LSB, 0x4f); + } break; case BANDWIDTH_7_MHZ: tda1004x_write_buf(state, TDA10046H_TIME_WREF1, bandwidth_7mhz, sizeof(bandwidth_7mhz)); + if (state->config->if_freq == TDA10046_FREQ_045) { + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_MSB, 0x0a); + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_LSB, 0x79); + } break; case BANDWIDTH_8_MHZ: tda1004x_write_buf(state, TDA10046H_TIME_WREF1, bandwidth_8mhz, sizeof(bandwidth_8mhz)); + if (state->config->if_freq == TDA10046_FREQ_045) { + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_MSB, 0x0b); + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_LSB, 0xa3); + } break; default: @@ -315,20 +327,35 @@ static int tda1004x_do_upload(struct tda1004x_state *state, 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"); + printk(KERN_ERR "tda1004x: Error during firmware upload\n"); return -EIO; } pos += tx_size; dprintk("%s: fw_pos=0x%x\n", __FUNCTION__, pos); } + // give the DSP a chance to settle 03/10/05 Hac + msleep(100); return 0; } -static int tda1004x_check_upload_ok(struct tda1004x_state *state, u8 dspVersion) +static int tda1004x_check_upload_ok(struct tda1004x_state *state) { u8 data1, data2; + unsigned long timeout; + + if (state->demod_type == TDA1004X_DEMOD_TDA10046) { + timeout = jiffies + 2 * HZ; + while(!(tda1004x_read_byte(state, TDA1004X_STATUS_CD) & 0x20)) { + if (time_after(jiffies, timeout)) { + printk(KERN_ERR "tda1004x: timeout waiting for DSP ready\n"); + break; + } + msleep(1); + } + } else + msleep(100); // check upload was OK tda1004x_write_mask(state, TDA1004X_CONFC4, 0x10, 0); // we want to read from the DSP @@ -336,9 +363,11 @@ static int tda1004x_check_upload_ok(struct tda1004x_state *state, u8 dspVersion) data1 = tda1004x_read_byte(state, TDA1004X_DSP_DATA1); data2 = tda1004x_read_byte(state, TDA1004X_DSP_DATA2); - if ((data1 != 0x67) || (data2 != dspVersion)) + if (data1 != 0x67 || data2 < 0x20 || data2 > 0x2e) { + printk(KERN_INFO "tda1004x: found firmware revision %x -- invalid\n", data2); return -EIO; - + } + printk(KERN_INFO "tda1004x: found firmware revision %x -- ok\n", data2); return 0; } @@ -349,14 +378,14 @@ static int tda10045_fwupload(struct dvb_frontend* fe) const struct firmware *fw; /* don't re-upload unless necessary */ - if (tda1004x_check_upload_ok(state, 0x2c) == 0) + if (tda1004x_check_upload_ok(state) == 0) return 0; /* request the firmware, this will block until someone uploads it */ - printk("tda1004x: waiting for firmware upload (%s)...\n", TDA10045_DEFAULT_FIRMWARE); + printk(KERN_INFO "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"); + printk(KERN_ERR "tda1004x: no firmware upload (timeout or file not found?)\n"); return ret; } @@ -370,95 +399,93 @@ static int tda10045_fwupload(struct dvb_frontend* fe) tda10045h_set_bandwidth(state, BANDWIDTH_8_MHZ); ret = tda1004x_do_upload(state, fw->data, fw->size, TDA10045H_FWPAGE, TDA10045H_CODE_IN); + release_firmware(fw); if (ret) return ret; - printk("tda1004x: firmware upload complete\n"); + printk(KERN_INFO "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); + return tda1004x_check_upload_ok(state); } -static int tda10046_get_fw_version(struct tda1004x_state *state, - const struct firmware *fw) +static void tda10046_init_plls(struct dvb_frontend* fe) { - const unsigned char pattern[] = { 0x67, 0x00, 0x50, 0x62, 0x5e, 0x18, 0x67 }; - unsigned int i; - - /* area guessed from firmware v20, v21 and v25 */ - for (i = 0x660; i < 0x700; i++) { - if (!memcmp(&fw->data[i], pattern, sizeof(pattern))) { - state->fw_version = fw->data[i + sizeof(pattern)]; - printk(KERN_INFO "tda1004x: using firmware v%02x\n", - state->fw_version); - return 0; - } - } + struct tda1004x_state* state = fe->demodulator_priv; - return -EINVAL; + tda1004x_write_byteI(state, TDA10046H_CONFPLL1, 0xf0); + tda1004x_write_byteI(state, TDA10046H_CONFPLL2, 10); // PLL M = 10 + if (state->config->xtal_freq == TDA10046_XTAL_4M ) { + dprintk("%s: setting up PLLs for a 4 MHz Xtal\n", __FUNCTION__); + tda1004x_write_byteI(state, TDA10046H_CONFPLL3, 0); // PLL P = N = 0 + } else { + dprintk("%s: setting up PLLs for a 16 MHz Xtal\n", __FUNCTION__); + tda1004x_write_byteI(state, TDA10046H_CONFPLL3, 3); // PLL P = 0, N = 3 + } + tda1004x_write_byteI(state, TDA10046H_FREQ_OFFSET, 99); + switch (state->config->if_freq) { + case TDA10046_FREQ_3617: + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_MSB, 0xd4); + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_LSB, 0x2c); + break; + case TDA10046_FREQ_3613: + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_MSB, 0xd4); + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_LSB, 0x13); + break; + case TDA10046_FREQ_045: + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_MSB, 0x0b); + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_LSB, 0xa3); + break; + case TDA10046_FREQ_052: + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_MSB, 0x0c); + tda1004x_write_byteI(state, TDA10046H_FREQ_PHY2_LSB, 0x06); + break; + } + tda10046h_set_bandwidth(state, BANDWIDTH_8_MHZ); // default bandwidth 8 MHz } 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_byteI(state, TDA1004X_CONFC4, 0); tda1004x_write_mask(state, TDA10046H_CONF_TRISTATE1, 1, 0); - msleep(100); + /* let the clocks recover from sleep */ + msleep(5); /* don't re-upload unless necessary */ - if (tda1004x_check_upload_ok(state, state->fw_version) == 0) + if (tda1004x_check_upload_ok(state) == 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; - } - - if (fw->size < 24478) { /* size of firmware v20, which is the smallest of v20, v21 and v25 */ - printk("tda1004x: firmware file seems to be too small (%d bytes)\n", fw->size); - return -EINVAL; - } - - ret = tda10046_get_fw_version(state, fw); - if (ret < 0) { - printk("tda1004x: unable to find firmware version\n"); - return ret; - } - /* set parameters */ - tda1004x_write_byteI(state, TDA10046H_CONFPLL2, 10); - tda1004x_write_byteI(state, TDA10046H_CONFPLL3, state->config->n_i2c); - 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; + tda10046_init_plls(fe); + + if (state->config->request_firmware != NULL) { + /* request the firmware, this will block until someone uploads it */ + printk(KERN_INFO "tda1004x: waiting for firmware upload...\n"); + ret = state->config->request_firmware(fe, &fw, TDA10046_DEFAULT_FIRMWARE); + if (ret) { + printk(KERN_ERR "tda1004x: no firmware upload (timeout or file not found?)\n"); + return ret; } - msleep(1); + 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); + release_firmware(fw); + if (ret) + return ret; + } else { + /* boot from firmware eeprom */ + /* Hac Note: we might need to do some GPIO Magic here */ + printk(KERN_INFO "tda1004x: booting from eeprom\n"); + tda1004x_write_mask(state, TDA1004X_CONFC4, 4, 4); + msleep(300); } - - return tda1004x_check_upload_ok(state, state->fw_version); + return tda1004x_check_upload_ok(state); } static int tda1004x_encode_fec(int fec) @@ -560,12 +587,10 @@ static int tda10046_init(struct dvb_frontend* fe) if (tda10046_fwupload(fe)) { printk("tda1004x: firmware upload failed\n"); - return -EIO; + return -EIO; } - tda1004x_write_mask(state, TDA1004X_CONFC4, 1, 0); // wake up the chip - - // Init the PLL + // Init the tuner PLL if (state->config->pll_init) { tda1004x_enable_tuner_i2c(state); state->config->pll_init(fe); @@ -574,32 +599,44 @@ static int tda10046_init(struct dvb_frontend* fe) // 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, state->config->n_i2c); // 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, TDA1004X_AUTO, 7); // select HP stream + tda1004x_write_byteI(state, TDA1004X_CONFC1, 8); // disable pulse killer + + tda10046_init_plls(fe); + switch (state->config->agc_config) { + case TDA10046_AGC_DEFAULT: + tda1004x_write_byteI(state, TDA10046H_AGC_CONF, 0x00); // AGC setup + tda1004x_write_byteI(state, TDA10046H_CONF_POLARITY, 0x60); // set AGC polarities + break; + case TDA10046_AGC_IFO_AUTO_NEG: + tda1004x_write_byteI(state, TDA10046H_AGC_CONF, 0x0a); // AGC setup + tda1004x_write_byteI(state, TDA10046H_CONF_POLARITY, 0x60); // set AGC polarities + break; + case TDA10046_AGC_IFO_AUTO_POS: + tda1004x_write_byteI(state, TDA10046H_AGC_CONF, 0x0a); // AGC setup + tda1004x_write_byteI(state, TDA10046H_CONF_POLARITY, 0x00); // set AGC polarities + break; + case TDA10046_AGC_TDA827X: + tda1004x_write_byteI(state, TDA10046H_AGC_CONF, 0x02); // AGC setup + tda1004x_write_byteI(state, TDA10046H_AGC_THR, 0x70); // AGC Threshold + tda1004x_write_byteI(state, TDA10046H_AGC_RENORM, 0x0E); // Gain Renormalize + tda1004x_write_byteI(state, TDA10046H_CONF_POLARITY, 0x60); // set AGC polarities + break; + } + tda1004x_write_byteI(state, TDA10046H_CONF_TRISTATE1, 0x61); // Turn both AGC outputs on 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, TDA10046H_CVBER_CTRL, 0x1a); // 10^6 VBER measurement bits 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, TDA1004X_CONF_TS2, 0xc0); // MPEG2 interface config + tda1004x_write_mask(state, 0x3a, 0x80, state->config->invert_oclk << 7); + 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); + tda1004x_write_byteI(state, TDA10046H_GPIO_SELECT, 8); // GPIO select state->initialised = 1; return 0; @@ -629,9 +666,6 @@ static int tda1004x_set_fe(struct dvb_frontend* fe, 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) { @@ -1089,6 +1123,11 @@ static int tda1004x_sleep(struct dvb_frontend* fe) break; case TDA1004X_DEMOD_TDA10046: + if (state->config->pll_sleep != NULL) { + tda1004x_enable_tuner_i2c(state); + state->config->pll_sleep(fe); + tda1004x_disable_tuner_i2c(state); + } tda1004x_write_mask(state, TDA1004X_CONFC4, 1, 1); break; } @@ -1100,8 +1139,9 @@ static int tda1004x_sleep(struct dvb_frontend* fe) 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; + /* Drift compensation makes no sense for DVB-T */ + fesettings->step_size = 0; + fesettings->max_drift = 0; return 0; } @@ -1216,7 +1256,6 @@ struct dvb_frontend* tda10046_attach(const struct tda1004x_config* config, memcpy(&state->ops, &tda10046_ops, sizeof(struct dvb_frontend_ops)); state->initialised = 0; state->demod_type = TDA1004X_DEMOD_TDA10046; - state->fw_version = 0x20; /* dummy default value */ /* check if the demod is there */ if (tda1004x_read_byte(state, TDA1004X_CHIPID) != 0x46) { diff --git a/drivers/media/dvb/frontends/tda1004x.h b/drivers/media/dvb/frontends/tda1004x.h index c8e1d54ff26..8659c52647a 100644 --- a/drivers/media/dvb/frontends/tda1004x.h +++ b/drivers/media/dvb/frontends/tda1004x.h @@ -26,6 +26,25 @@ #include <linux/dvb/frontend.h> #include <linux/firmware.h> +enum tda10046_xtal { + TDA10046_XTAL_4M, + TDA10046_XTAL_16M, +}; + +enum tda10046_agc { + TDA10046_AGC_DEFAULT, /* original configuration */ + TDA10046_AGC_IFO_AUTO_NEG, /* IF AGC only, automatic, negtive */ + TDA10046_AGC_IFO_AUTO_POS, /* IF AGC only, automatic, positive */ + TDA10046_AGC_TDA827X, /* IF AGC only, special setup for tda827x */ +}; + +enum tda10046_if { + TDA10046_FREQ_3617, /* original config, 36,166 MHZ */ + TDA10046_FREQ_3613, /* 36,13 MHZ */ + TDA10046_FREQ_045, /* low IF, 4.0, 4.5, or 5.0 MHZ */ + TDA10046_FREQ_052, /* low IF, 5.1667 MHZ for tda9889 */ +}; + struct tda1004x_config { /* the demodulator's i2c address */ @@ -37,14 +56,22 @@ struct tda1004x_config /* Does the OCLK signal need inverted? */ u8 invert_oclk; - /* value of N_I2C of the CONF_PLL3 register */ - u8 n_i2c; + /* Xtal frequency, 4 or 16MHz*/ + enum tda10046_xtal xtal_freq; + + /* IF frequency */ + enum tda10046_if if_freq; + + /* AGC configuration */ + enum tda10046_agc agc_config; /* PLL maintenance */ int (*pll_init)(struct dvb_frontend* fe); + void (*pll_sleep)(struct dvb_frontend* fe); int (*pll_set)(struct dvb_frontend* fe, struct dvb_frontend_parameters* params); /* request firmware for device */ + /* set this to NULL if the card has a firmware EEPROM */ int (*request_firmware)(struct dvb_frontend* fe, const struct firmware **fw, char* name); }; diff --git a/drivers/media/dvb/frontends/tda80xx.c b/drivers/media/dvb/frontends/tda80xx.c index 032d348dafb..88e125079ca 100644 --- a/drivers/media/dvb/frontends/tda80xx.c +++ b/drivers/media/dvb/frontends/tda80xx.c @@ -27,7 +27,6 @@ #include <linux/spinlock.h> #include <linux/threads.h> #include <linux/interrupt.h> -#include <linux/irq.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/slab.h> |