diff options
author | Mauro Carvalho Chehab <mchehab@redhat.com> | 2009-03-10 21:10:26 -0300 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2009-03-10 21:10:26 -0300 |
commit | 299225379cd2a1d3bf6de810ff49382f96a02d11 (patch) | |
tree | 23455ba035a926ae3178977dee3f0a5126c64cd2 | |
parent | 02b5c443f53867b693751a9aff1753ffea8891d4 (diff) | |
parent | ea09aea560cb0eabd0221c64177257a11e3e6d83 (diff) | |
download | mediapointer-dvb-s2-299225379cd2a1d3bf6de810ff49382f96a02d11.tar.gz mediapointer-dvb-s2-299225379cd2a1d3bf6de810ff49382f96a02d11.tar.bz2 |
merge: http://linuxtv.org/hg/~mkrufky/lgdt3305
From: Mauro Carvalho Chehab <mchehab@redhat.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
-rw-r--r-- | linux/Documentation/video4linux/CARDLIST.saa7134 | 2 | ||||
-rw-r--r-- | linux/drivers/media/dvb/dvb-usb/Kconfig | 2 | ||||
-rw-r--r-- | linux/drivers/media/dvb/dvb-usb/dib0700_devices.c | 99 | ||||
-rw-r--r-- | linux/drivers/media/dvb/dvb-usb/dvb-usb-ids.h | 2 | ||||
-rw-r--r-- | linux/drivers/media/dvb/frontends/Kconfig | 8 | ||||
-rw-r--r-- | linux/drivers/media/dvb/frontends/Makefile | 1 | ||||
-rw-r--r-- | linux/drivers/media/dvb/frontends/lgdt3305.c | 1115 | ||||
-rw-r--r-- | linux/drivers/media/dvb/frontends/lgdt3305.h | 85 | ||||
-rw-r--r-- | linux/drivers/media/video/saa7134/Kconfig | 3 | ||||
-rw-r--r-- | linux/drivers/media/video/saa7134/saa7134-cards.c | 22 | ||||
-rw-r--r-- | linux/drivers/media/video/saa7134/saa7134-dvb.c | 44 | ||||
-rw-r--r-- | linux/drivers/media/video/saa7134/saa7134-ts.c | 15 | ||||
-rw-r--r-- | linux/drivers/media/video/saa7134/saa7134.h | 8 |
13 files changed, 1392 insertions, 14 deletions
diff --git a/linux/Documentation/video4linux/CARDLIST.saa7134 b/linux/Documentation/video4linux/CARDLIST.saa7134 index 325c69fe9..6dacf2825 100644 --- a/linux/Documentation/video4linux/CARDLIST.saa7134 +++ b/linux/Documentation/video4linux/CARDLIST.saa7134 @@ -153,5 +153,5 @@ 152 -> Asus Tiger Rev:1.00 [1043:4857] 153 -> Kworld Plus TV Analog Lite PCI [17de:7128] 154 -> Avermedia AVerTV GO 007 FM Plus [1461:f31d] -155 -> Hauppauge WinTV-HVR1150 [0070:6706,0070:6708] +155 -> Hauppauge WinTV-HVR1120 ATSC/QAM-Hybrid [0070:6706,0070:6708] 156 -> Hauppauge WinTV-HVR1110r3 [0070:6707,0070:6709,0070:670a] diff --git a/linux/drivers/media/dvb/dvb-usb/Kconfig b/linux/drivers/media/dvb/dvb-usb/Kconfig index 64fa1d182..b899d509a 100644 --- a/linux/drivers/media/dvb/dvb-usb/Kconfig +++ b/linux/drivers/media/dvb/dvb-usb/Kconfig @@ -73,11 +73,13 @@ config DVB_USB_DIB0700 select DVB_DIB7000M if !DVB_FE_CUSTOMISE select DVB_DIB3000MC if !DVB_FE_CUSTOMISE select DVB_S5H1411 if !DVB_FE_CUSTOMISE + select DVB_LGDT3305 if !DVB_FE_CUSTOMISE select DVB_TUNER_DIB0070 if !DVB_FE_CUSTOMISE select MEDIA_TUNER_MT2060 if !MEDIA_TUNER_CUSTOMIZE select MEDIA_TUNER_MT2266 if !MEDIA_TUNER_CUSTOMIZE select MEDIA_TUNER_XC2028 if !MEDIA_TUNER_CUSTOMIZE select MEDIA_TUNER_XC5000 if !MEDIA_TUNER_CUSTOMIZE + select MEDIA_TUNER_MXL5007T if !MEDIA_TUNER_CUSTOMIZE help Support for USB2.0/1.1 DVB receivers based on the DiB0700 USB bridge. The USB bridge is also present in devices having the DiB7700 DVB-T-USB diff --git a/linux/drivers/media/dvb/dvb-usb/dib0700_devices.c b/linux/drivers/media/dvb/dvb-usb/dib0700_devices.c index 8877215cb..d8901f7a8 100644 --- a/linux/drivers/media/dvb/dvb-usb/dib0700_devices.c +++ b/linux/drivers/media/dvb/dvb-usb/dib0700_devices.c @@ -17,6 +17,8 @@ #include "xc5000.h" #include "s5h1411.h" #include "dib0070.h" +#include "lgdt3305.h" +#include "mxl5007t.h" static int force_lna_activation; module_param(force_lna_activation, int, 0644); @@ -1370,6 +1372,76 @@ static int xc5000_tuner_attach(struct dvb_usb_adapter *adap) == NULL ? -ENODEV : 0; } +static struct lgdt3305_config hcw_lgdt3305_config = { + .i2c_addr = 0x0e, + .mpeg_mode = LGDT3305_MPEG_PARALLEL, + .tpclk_edge = LGDT3305_TPCLK_FALLING_EDGE, + .tpvalid_polarity = LGDT3305_TP_VALID_LOW, + .deny_i2c_rptr = 0, + .spectral_inversion = 1, + .qam_if_khz = 6000, + .vsb_if_khz = 6000, + .usref_8vsb = 0x0500, +}; + +static struct mxl5007t_config hcw_mxl5007t_config = { + .xtal_freq_hz = MxL_XTAL_25_MHZ, + .if_freq_hz = MxL_IF_6_MHZ, + .invert_if = 1, +#if 0 + .loop_thru_enable = 1, + .clk_out_enable = 1, +#endif +}; + +/* TIGER-ATSC map: + GPIO0 - LNA_CTR (H: LNA power enabled, L: LNA power disabled) + GPIO1 - ANT_SEL (H: VPA, L: MCX) + GPIO4 - SCL2 + GPIO6 - EN_TUNER + GPIO7 - SDA2 + GPIO10 - DEM_RST + + MXL is behind LG's i2c repeater. LG is on SCL2/SDA2 gpios on the DIB + */ +static int lgdt3305_frontend_attach(struct dvb_usb_adapter *adap) +{ + struct dib0700_state *st = adap->dev->priv; + + /* Make use of the new i2c functions from FW 1.20 */ + st->fw_use_new_i2c_api = 1; + + st->disable_streaming_master_mode = 1; + + /* fe power enable */ + dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 0); + msleep(30); + dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1); + msleep(30); + + /* demod reset */ + dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1); + msleep(30); + dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0); + msleep(30); + dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1); + msleep(30); + + adap->fe = dvb_attach(lgdt3305_attach, + &hcw_lgdt3305_config, + &adap->dev->i2c_adap); + + return adap->fe == NULL ? -ENODEV : 0; +} + +static int mxl5007t_tuner_attach(struct dvb_usb_adapter *adap) +{ + return dvb_attach(mxl5007t_attach, adap->fe, + &adap->dev->i2c_adap, 0x60, + &hcw_mxl5007t_config) == NULL ? -ENODEV : 0; +} + + /* DVB-USB and USB stuff follows */ struct usb_device_id dib0700_usb_id_table[] = { /* 0 */ { USB_DEVICE(USB_VID_DIBCOM, USB_PID_DIBCOM_STK7700P) }, @@ -1421,6 +1493,8 @@ struct usb_device_id dib0700_usb_id_table[] = { USB_PID_TERRATEC_CINERGY_DT_XS_DIVERSITY_2) }, { USB_DEVICE(USB_VID_SONY, USB_PID_SONY_PLAYTV) }, /* 45 */{ USB_DEVICE(USB_VID_YUAN, USB_PID_YUAN_PD378S) }, + { USB_DEVICE(USB_VID_HAUPPAUGE, USB_PID_HAUPPAUGE_TIGER_ATSC) }, + { USB_DEVICE(USB_VID_HAUPPAUGE, USB_PID_HAUPPAUGE_TIGER_ATSC_B210) }, { 0 } /* Terminating entry */ }; MODULE_DEVICE_TABLE(usb, dib0700_usb_id_table); @@ -1796,6 +1870,31 @@ struct dvb_usb_device_properties dib0700_devices[] = { .rc_key_map = dib0700_rc_keys, .rc_key_map_size = ARRAY_SIZE(dib0700_rc_keys), .rc_query = dib0700_rc_query + }, { DIB0700_DEFAULT_DEVICE_PROPERTIES, + .num_adapters = 1, + .adapter = { + { + .frontend_attach = lgdt3305_frontend_attach, + .tuner_attach = mxl5007t_tuner_attach, + + DIB0700_DEFAULT_STREAMING_CONFIG(0x02), + + .size_of_priv = sizeof(struct + dib0700_adapter_state), + }, + }, + + .num_device_descs = 2, + .devices = { + { "Hauppauge ATSC MiniCard (B200)", + { &dib0700_usb_id_table[46], NULL }, + { NULL }, + }, + { "Hauppauge ATSC MiniCard (B210)", + { &dib0700_usb_id_table[47], NULL }, + { NULL }, + }, + }, }, }; diff --git a/linux/drivers/media/dvb/dvb-usb/dvb-usb-ids.h b/linux/drivers/media/dvb/dvb-usb/dvb-usb-ids.h index 6d70576ad..3dfb27174 100644 --- a/linux/drivers/media/dvb/dvb-usb/dvb-usb-ids.h +++ b/linux/drivers/media/dvb/dvb-usb/dvb-usb-ids.h @@ -151,6 +151,8 @@ #define USB_PID_HAUPPAUGE_MYTV_T 0x7080 #define USB_PID_HAUPPAUGE_NOVA_TD_STICK 0x9580 #define USB_PID_HAUPPAUGE_NOVA_TD_STICK_52009 0x5200 +#define USB_PID_HAUPPAUGE_TIGER_ATSC 0xb200 +#define USB_PID_HAUPPAUGE_TIGER_ATSC_B210 0xb210 #define USB_PID_AVERMEDIA_EXPRESS 0xb568 #define USB_PID_AVERMEDIA_VOLAR 0xa807 #define USB_PID_AVERMEDIA_VOLAR_2 0xb808 diff --git a/linux/drivers/media/dvb/frontends/Kconfig b/linux/drivers/media/dvb/frontends/Kconfig index 2887d3398..5c78f6329 100644 --- a/linux/drivers/media/dvb/frontends/Kconfig +++ b/linux/drivers/media/dvb/frontends/Kconfig @@ -419,6 +419,14 @@ config DVB_LGDT3304 An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want to support this frontend. +config DVB_LGDT3305 + tristate "LG Electronics LGDT3305 based" + depends on DVB_CORE && I2C + default m if DVB_FE_CUSTOMISE + help + An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want + to support this frontend. + config DVB_S5H1409 tristate "Samsung S5H1409 based" depends on DVB_CORE && I2C diff --git a/linux/drivers/media/dvb/frontends/Makefile b/linux/drivers/media/dvb/frontends/Makefile index 1e3866b2f..2a250399b 100644 --- a/linux/drivers/media/dvb/frontends/Makefile +++ b/linux/drivers/media/dvb/frontends/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_DVB_BCM3510) += bcm3510.o obj-$(CONFIG_DVB_S5H1420) += s5h1420.o obj-$(CONFIG_DVB_LGDT330X) += lgdt330x.o obj-$(CONFIG_DVB_LGDT3304) += lgdt3304.o +obj-$(CONFIG_DVB_LGDT3305) += lgdt3305.o obj-$(CONFIG_DVB_CX24123) += cx24123.o obj-$(CONFIG_DVB_LNBP21) += lnbp21.o obj-$(CONFIG_DVB_ISL6405) += isl6405.o diff --git a/linux/drivers/media/dvb/frontends/lgdt3305.c b/linux/drivers/media/dvb/frontends/lgdt3305.c new file mode 100644 index 000000000..aeb34e924 --- /dev/null +++ b/linux/drivers/media/dvb/frontends/lgdt3305.c @@ -0,0 +1,1115 @@ +/* + * Support for LGDT3305 - VSB/QAM + * + * Copyright (C) 2008, 2009 Michael Krufky <mkrufky@linuxtv.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include "compat.h" +#include <linux/dvb/frontend.h> +#include "dvb_math.h" +#include "lgdt3305.h" + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "set debug level (info=1, reg=2 (or-able))"); + +#define DBG_INFO 1 +#define DBG_REG 2 + +#define lg_printk(kern, fmt, arg...) \ + printk(kern "%s: " fmt, __func__, ##arg) + +#define lg_info(fmt, arg...) printk(KERN_INFO "lgdt3305: " fmt, ##arg) +#define lg_warn(fmt, arg...) lg_printk(KERN_WARNING, fmt, ##arg) +#define lg_err(fmt, arg...) lg_printk(KERN_ERR, fmt, ##arg) +#define lg_dbg(fmt, arg...) if (debug & DBG_INFO) \ + lg_printk(KERN_DEBUG, fmt, ##arg) +#define lg_reg(fmt, arg...) if (debug & DBG_REG) \ + lg_printk(KERN_DEBUG, fmt, ##arg) + +#define lg_fail(ret) \ +({ \ + int __ret; \ + __ret = (ret < 0); \ + if (__ret) \ + lg_err("error %d on line %d\n", ret, __LINE__); \ + __ret; \ +}) + +struct lgdt3305_state { + struct i2c_adapter *i2c_adap; + const struct lgdt3305_config *cfg; + + struct dvb_frontend frontend; + + fe_modulation_t current_modulation; + u32 current_frequency; + u32 snr; +}; + +/* ------------------------------------------------------------------------ */ + +#define LGDT3305_GEN_CTRL_1 0x0000 +#define LGDT3305_GEN_CTRL_2 0x0001 +#define LGDT3305_GEN_CTRL_3 0x0002 +#define LGDT3305_GEN_STATUS 0x0003 +#define LGDT3305_GEN_CONTROL 0x0007 +#define LGDT3305_GEN_CTRL_4 0x000a +#define LGDT3305_DGTL_AGC_REF_1 0x0012 +#define LGDT3305_DGTL_AGC_REF_2 0x0013 +#define LGDT3305_CR_CTR_FREQ_1 0x0106 +#define LGDT3305_CR_CTR_FREQ_2 0x0107 +#define LGDT3305_CR_CTR_FREQ_3 0x0108 +#define LGDT3305_CR_CTR_FREQ_4 0x0109 +#define LGDT3305_CR_MSE_1 0x011b +#define LGDT3305_CR_MSE_2 0x011c +#define LGDT3305_CR_LOCK_STATUS 0x011d +#define LGDT3305_CR_CTRL_7 0x0126 +#define LGDT3305_AGC_POWER_REF_1 0x0300 +#define LGDT3305_AGC_POWER_REF_2 0x0301 +#define LGDT3305_AGC_DELAY_PT_1 0x0302 +#define LGDT3305_AGC_DELAY_PT_2 0x0303 +#define LGDT3305_RFAGC_LOOP_FLTR_BW_1 0x0306 +#define LGDT3305_RFAGC_LOOP_FLTR_BW_2 0x0307 +#define LGDT3305_IFBW_1 0x0308 +#define LGDT3305_IFBW_2 0x0309 +#define LGDT3305_AGC_CTRL_1 0x030c +#define LGDT3305_AGC_CTRL_4 0x0314 +#define LGDT3305_EQ_MSE_1 0x0413 +#define LGDT3305_EQ_MSE_2 0x0414 +#define LGDT3305_EQ_MSE_3 0x0415 +#define LGDT3305_PT_MSE_1 0x0417 +#define LGDT3305_PT_MSE_2 0x0418 +#define LGDT3305_PT_MSE_3 0x0419 +#define LGDT3305_FEC_BLOCK_CTRL 0x0504 +#define LGDT3305_FEC_LOCK_STATUS 0x050a +#define LGDT3305_FEC_PKT_ERR_1 0x050c +#define LGDT3305_FEC_PKT_ERR_2 0x050d +#define LGDT3305_TP_CTRL_1 0x050e +#define LGDT3305_BERT_PERIOD 0x0801 +#define LGDT3305_BERT_ERROR_COUNT_1 0x080a +#define LGDT3305_BERT_ERROR_COUNT_2 0x080b +#define LGDT3305_BERT_ERROR_COUNT_3 0x080c +#define LGDT3305_BERT_ERROR_COUNT_4 0x080d + +static int lgdt3305_write_reg(struct lgdt3305_state *state, u16 reg, u8 val) +{ + int ret; + u8 buf[] = { reg >> 8, reg & 0xff, val }; + struct i2c_msg msg = { + .addr = state->cfg->i2c_addr, .flags = 0, + .buf = buf, .len = 3, + }; + + lg_reg("reg: 0x%04x, val: 0x%02x\n", reg, val); + + ret = i2c_transfer(state->i2c_adap, &msg, 1); + + if (ret != 1) { + lg_err("error (addr %02x %02x <- %02x, err = %i)\n", + msg.buf[0], msg.buf[1], msg.buf[2], ret); + if (ret < 0) + return ret; + else + return -EREMOTEIO; + } + return 0; +} + +static int lgdt3305_read_reg(struct lgdt3305_state *state, u16 reg, u8 *val) +{ + int ret; + u8 reg_buf[] = { reg >> 8, reg & 0xff }; + struct i2c_msg msg[] = { + { .addr = state->cfg->i2c_addr, + .flags = 0, .buf = reg_buf, .len = 2 }, + { .addr = state->cfg->i2c_addr, + .flags = I2C_M_RD, .buf = val, .len = 1 }, + }; + + lg_reg("reg: 0x%04x\n", reg); + + ret = i2c_transfer(state->i2c_adap, msg, 2); + + if (ret != 2) { + lg_err("error (addr %02x reg %04x error (ret == %i)\n", + state->cfg->i2c_addr, reg, ret); + if (ret < 0) + return ret; + else + return -EREMOTEIO; + } + return 0; +} + +#define read_reg(state, reg) \ +({ \ + u8 __val; \ + int ret = lgdt3305_read_reg(state, reg, &__val); \ + if (lg_fail(ret)) \ + __val = 0; \ + __val; \ +}) + +static int lgdt3305_set_reg_bit(struct lgdt3305_state *state, + u16 reg, int bit, int onoff) +{ + u8 val; + int ret; + + lg_reg("reg: 0x%04x, bit: %d, level: %d\n", reg, bit, onoff); + + ret = lgdt3305_read_reg(state, reg, &val); + if (lg_fail(ret)) + goto fail; + + val &= ~(1 << bit); + val |= (onoff & 1) << bit; + + ret = lgdt3305_write_reg(state, reg, val); +fail: + return ret; +} + +struct lgdt3305_reg { + u16 reg; + u8 val; +}; + +static int lgdt3305_write_regs(struct lgdt3305_state *state, + struct lgdt3305_reg *regs, int len) +{ + int i, ret; + + lg_reg("writing %d registers...\n", len); + + for (i = 0; i < len - 1; i++) { + ret = lgdt3305_write_reg(state, regs[i].reg, regs[i].val); + if (lg_fail(ret)) + return ret; + } + return 0; +} + +/* ------------------------------------------------------------------------ */ + +static int lgdt3305_soft_reset(struct lgdt3305_state *state) +{ + int ret; + + lg_dbg("\n"); + + ret = lgdt3305_set_reg_bit(state, LGDT3305_GEN_CTRL_3, 0, 0); + if (lg_fail(ret)) + goto fail; + + msleep(20); + ret = lgdt3305_set_reg_bit(state, LGDT3305_GEN_CTRL_3, 0, 1); +fail: + return ret; +} + +static inline int lgdt3305_mpeg_mode(struct lgdt3305_state *state, + enum lgdt3305_mpeg_mode mode) +{ + lg_dbg("(%d)\n", mode); + return lgdt3305_set_reg_bit(state, LGDT3305_TP_CTRL_1, 5, mode); +} + +static int lgdt3305_mpeg_mode_polarity(struct lgdt3305_state *state, + enum lgdt3305_tp_clock_edge edge, + enum lgdt3305_tp_valid_polarity valid) +{ + u8 val; + int ret; + + lg_dbg("edge = %d, valid = %d\n", edge, valid); + + ret = lgdt3305_read_reg(state, LGDT3305_TP_CTRL_1, &val); + if (lg_fail(ret)) + goto fail; + + val &= ~0x09; + + if (edge) + val |= 0x08; + if (valid) + val |= 0x01; + + ret = lgdt3305_write_reg(state, LGDT3305_TP_CTRL_1, val); + if (lg_fail(ret)) + goto fail; + + ret = lgdt3305_soft_reset(state); +fail: + return ret; +} + +static int lgdt3305_set_modulation(struct lgdt3305_state *state, + struct dvb_frontend_parameters *param) +{ + u8 opermode; + int ret; + + lg_dbg("\n"); + + ret = lgdt3305_read_reg(state, LGDT3305_GEN_CTRL_1, &opermode); + if (lg_fail(ret)) + goto fail; + + opermode &= ~0x03; + + switch (param->u.vsb.modulation) { + case VSB_8: + opermode |= 0x03; + break; + case QAM_64: + opermode |= 0x00; + break; + case QAM_256: + opermode |= 0x01; + break; + default: + return -EINVAL; + } + ret = lgdt3305_write_reg(state, LGDT3305_GEN_CTRL_1, opermode); +fail: + return ret; +} + +static int lgdt3305_set_filter_extension(struct lgdt3305_state *state, + struct dvb_frontend_parameters *param) +{ + int val; + + switch (param->u.vsb.modulation) { + case VSB_8: + val = 0; + break; + case QAM_64: + case QAM_256: + val = 1; + break; + default: + return -EINVAL; + } + lg_dbg("val = %d\n", val); + + return lgdt3305_set_reg_bit(state, 0x043f, 2, val); +} + +/* ------------------------------------------------------------------------ */ + +static int lgdt3305_passband_digital_agc(struct lgdt3305_state *state, + struct dvb_frontend_parameters *param) +{ + u16 agc_ref; + + switch (param->u.vsb.modulation) { + case VSB_8: + agc_ref = 0x32c4; + break; + case QAM_64: + agc_ref = 0x2a00; + break; + case QAM_256: + agc_ref = 0x2a80; + break; + default: + return -EINVAL; + } + + lg_dbg("agc ref: 0x%04x\n", agc_ref); + + lgdt3305_write_reg(state, LGDT3305_DGTL_AGC_REF_1, agc_ref >> 8); + lgdt3305_write_reg(state, LGDT3305_DGTL_AGC_REF_2, agc_ref & 0xff); + + return 0; +} + +static int lgdt3305_rfagc_loop(struct lgdt3305_state *state, + struct dvb_frontend_parameters *param) +{ + u16 ifbw, rfbw, agcdelay; + + switch (param->u.vsb.modulation) { + case VSB_8: + agcdelay = 0x04c0; + rfbw = 0x8000; + ifbw = 0x8000; + break; + case QAM_64: + case QAM_256: + agcdelay = 0x046b; + rfbw = 0x8889; + ifbw = 0x8888; + break; + default: + return -EINVAL; + } + + if (state->cfg->rf_agc_loop) { + lg_dbg("agcdelay: 0x%04x, rfbw: 0x%04x\n", agcdelay, rfbw); + + /* rf agc loop filter bandwidth */ + lgdt3305_write_reg(state, LGDT3305_AGC_DELAY_PT_1, + agcdelay >> 8); + lgdt3305_write_reg(state, LGDT3305_AGC_DELAY_PT_2, + agcdelay & 0xff); + + lgdt3305_write_reg(state, LGDT3305_RFAGC_LOOP_FLTR_BW_1, + rfbw >> 8); + lgdt3305_write_reg(state, LGDT3305_RFAGC_LOOP_FLTR_BW_2, + rfbw & 0xff); + } else { + lg_dbg("ifbw: 0x%04x\n", ifbw); + + /* if agc loop filter bandwidth */ + lgdt3305_write_reg(state, LGDT3305_IFBW_1, ifbw >> 8); + lgdt3305_write_reg(state, LGDT3305_IFBW_2, ifbw & 0xff); + } + + return 0; +} + +static int lgdt3305_agc_setup(struct lgdt3305_state *state, + struct dvb_frontend_parameters *param) +{ + int lockdten, acqen; + + switch (param->u.vsb.modulation) { + case VSB_8: + lockdten = 0; + acqen = 0; + break; + case QAM_64: + case QAM_256: + lockdten = 1; + acqen = 1; + break; + default: + return -EINVAL; + } + + lg_dbg("lockdten = %d, acqen = %d\n", lockdten, acqen); + + /* control agc function */ + lgdt3305_write_reg(state, LGDT3305_AGC_CTRL_4, 0xe1 | lockdten << 1); + lgdt3305_set_reg_bit(state, LGDT3305_AGC_CTRL_1, 2, acqen); + + return lgdt3305_rfagc_loop(state, param); +} + +static int lgdt3305_set_agc_power_ref(struct lgdt3305_state *state, + struct dvb_frontend_parameters *param) +{ + u16 usref = 0; + + switch (param->u.vsb.modulation) { + case VSB_8: + if (state->cfg->usref_8vsb) + usref = state->cfg->usref_8vsb; + break; + case QAM_64: + if (state->cfg->usref_qam64) + usref = state->cfg->usref_qam64; + break; + case QAM_256: + if (state->cfg->usref_qam256) + usref = state->cfg->usref_qam256; + break; + default: + return -EINVAL; + } + + if (usref) { + lg_dbg("set manual mode: 0x%04x\n", usref); + + lgdt3305_set_reg_bit(state, LGDT3305_AGC_CTRL_1, 3, 1); + + lgdt3305_write_reg(state, LGDT3305_AGC_POWER_REF_1, + 0xff & (usref >> 8)); + lgdt3305_write_reg(state, LGDT3305_AGC_POWER_REF_2, + 0xff & (usref >> 0)); + } + return 0; +} + +/* ------------------------------------------------------------------------ */ + +static int lgdt3305_spectral_inversion(struct lgdt3305_state *state, + struct dvb_frontend_parameters *param, + int inversion) +{ + int ret; + + lg_dbg("(%d)\n", inversion); + + switch (param->u.vsb.modulation) { + case VSB_8: + ret = lgdt3305_write_reg(state, LGDT3305_CR_CTRL_7, + inversion ? 0xf9 : 0x79); + break; + case QAM_64: + case QAM_256: + ret = lgdt3305_write_reg(state, LGDT3305_FEC_BLOCK_CTRL, + inversion ? 0xfd : 0xff); + break; + default: + ret = -EINVAL; + } + return ret; +} + +static int lgdt3305_set_if(struct lgdt3305_state *state, + struct dvb_frontend_parameters *param) +{ + u16 if_freq_khz; + u8 nco1, nco2, nco3, nco4; + u64 nco; + + switch (param->u.vsb.modulation) { + case VSB_8: + if_freq_khz = state->cfg->vsb_if_khz; + break; + case QAM_64: + case QAM_256: + if_freq_khz = state->cfg->qam_if_khz; + break; + default: + return -EINVAL; + } + + nco = if_freq_khz / 10; + +#define LGDT3305_64BIT_DIVISION_ENABLED 0 + /* FIXME: 64bit division disabled to avoid linking error: + * WARNING: "__udivdi3" [lgdt3305.ko] undefined! + */ + switch (param->u.vsb.modulation) { + case VSB_8: +#if LGDT3305_64BIT_DIVISION_ENABLED + nco <<= 24; + nco /= 625; +#else + nco *= ((1 << 24) / 625); +#endif + break; + case QAM_64: + case QAM_256: +#if LGDT3305_64BIT_DIVISION_ENABLED + nco <<= 28; + nco /= 625; +#else + nco *= ((1 << 28) / 625); +#endif + break; + default: + return -EINVAL; + } + + nco1 = (nco >> 24) & 0x3f; + nco1 |= 0x40; + nco2 = (nco >> 16) & 0xff; + nco3 = (nco >> 8) & 0xff; + nco4 = nco & 0xff; + + lgdt3305_write_reg(state, LGDT3305_CR_CTR_FREQ_1, nco1); + lgdt3305_write_reg(state, LGDT3305_CR_CTR_FREQ_2, nco2); + lgdt3305_write_reg(state, LGDT3305_CR_CTR_FREQ_3, nco3); + lgdt3305_write_reg(state, LGDT3305_CR_CTR_FREQ_4, nco4); + + lg_dbg("%d KHz -> [%02x%02x%02x%02x]\n", + if_freq_khz, nco1, nco2, nco3, nco4); + + return 0; +} + +/* ------------------------------------------------------------------------ */ + +static int lgdt3305_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + struct lgdt3305_state *state = fe->demodulator_priv; + + if (state->cfg->deny_i2c_rptr) + return 0; + + lg_dbg("(%d)\n", enable); + + return lgdt3305_set_reg_bit(state, LGDT3305_GEN_CTRL_2, 5, + enable ? 0 : 1); +} + +static int lgdt3305_sleep(struct dvb_frontend *fe) +{ + struct lgdt3305_state *state = fe->demodulator_priv; + u8 gen_ctrl_3, gen_ctrl_4; + + lg_dbg("\n"); + + gen_ctrl_3 = read_reg(state, LGDT3305_GEN_CTRL_3); + gen_ctrl_4 = read_reg(state, LGDT3305_GEN_CTRL_4); + + /* hold in software reset while sleeping */ + gen_ctrl_3 &= ~0x01; + /* tristate the IF-AGC pin */ + gen_ctrl_3 |= 0x02; + /* tristate the RF-AGC pin */ + gen_ctrl_3 |= 0x04; + + /* disable vsb/qam module */ + gen_ctrl_4 &= ~0x01; + /* disable adc module */ + gen_ctrl_4 &= ~0x02; + + lgdt3305_write_reg(state, LGDT3305_GEN_CTRL_3, gen_ctrl_3); + lgdt3305_write_reg(state, LGDT3305_GEN_CTRL_4, gen_ctrl_4); + + return 0; +} + +static int lgdt3305_init(struct dvb_frontend *fe) +{ + struct lgdt3305_state *state = fe->demodulator_priv; + int ret; + + static struct lgdt3305_reg lgdt3305_init_data[] = { + { .reg = LGDT3305_GEN_CTRL_1, + .val = 0x03, }, + { .reg = LGDT3305_GEN_CTRL_2, + .val = 0xb0, }, + { .reg = LGDT3305_GEN_CTRL_3, + .val = 0x01, }, + { .reg = LGDT3305_GEN_CONTROL, + .val = 0x6f, }, + { .reg = LGDT3305_GEN_CTRL_4, + .val = 0x03, }, + { .reg = LGDT3305_DGTL_AGC_REF_1, + .val = 0x32, }, + { .reg = LGDT3305_DGTL_AGC_REF_2, + .val = 0xc4, }, + { .reg = LGDT3305_CR_CTR_FREQ_1, + .val = 0x00, }, + { .reg = LGDT3305_CR_CTR_FREQ_2, + .val = 0x00, }, + { .reg = LGDT3305_CR_CTR_FREQ_3, + .val = 0x00, }, + { .reg = LGDT3305_CR_CTR_FREQ_4, + .val = 0x00, }, + { .reg = LGDT3305_CR_CTRL_7, + .val = 0x79, }, + { .reg = LGDT3305_AGC_POWER_REF_1, + .val = 0x32, }, + { .reg = LGDT3305_AGC_POWER_REF_2, + .val = 0xc4, }, + { .reg = LGDT3305_AGC_DELAY_PT_1, + .val = 0x0d, }, + { .reg = LGDT3305_AGC_DELAY_PT_2, + .val = 0x30, }, + { .reg = LGDT3305_RFAGC_LOOP_FLTR_BW_1, + .val = 0x80, }, + { .reg = LGDT3305_RFAGC_LOOP_FLTR_BW_2, + .val = 0x00, }, + { .reg = LGDT3305_IFBW_1, + .val = 0x80, }, + { .reg = LGDT3305_IFBW_2, + .val = 0x00, }, + { .reg = LGDT3305_AGC_CTRL_1, + .val = 0x30, }, + { .reg = LGDT3305_AGC_CTRL_4, + .val = 0x61, }, + { .reg = LGDT3305_FEC_BLOCK_CTRL, + .val = 0xff, }, + { .reg = LGDT3305_TP_CTRL_1, + .val = 0x1b, }, + }; + + lg_dbg("\n"); + + ret = lgdt3305_write_regs(state, lgdt3305_init_data, + ARRAY_SIZE(lgdt3305_init_data)); + if (lg_fail(ret)) + goto fail; + + ret = lgdt3305_soft_reset(state); +fail: + return ret; +} + +static int lgdt3305_set_parameters(struct dvb_frontend *fe, + struct dvb_frontend_parameters *param) +{ + struct lgdt3305_state *state = fe->demodulator_priv; + int ret; + + lg_dbg("(%d, %d)\n", param->frequency, param->u.vsb.modulation); + + if (fe->ops.tuner_ops.set_params) { + ret = fe->ops.tuner_ops.set_params(fe, param); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + if (lg_fail(ret)) + goto fail; + state->current_frequency = param->frequency; + } + + ret = lgdt3305_set_modulation(state, param); + if (lg_fail(ret)) + goto fail; + + ret = lgdt3305_passband_digital_agc(state, param); + if (lg_fail(ret)) + goto fail; + ret = lgdt3305_set_agc_power_ref(state, param); + if (lg_fail(ret)) + goto fail; + ret = lgdt3305_agc_setup(state, param); + if (lg_fail(ret)) + goto fail; + + /* low if */ + ret = lgdt3305_write_reg(state, LGDT3305_GEN_CONTROL, 0x2f); + if (lg_fail(ret)) + goto fail; + ret = lgdt3305_set_reg_bit(state, LGDT3305_CR_CTR_FREQ_1, 6, 1); + if (lg_fail(ret)) + goto fail; + + ret = lgdt3305_set_if(state, param); + if (lg_fail(ret)) + goto fail; + ret = lgdt3305_spectral_inversion(state, param, + state->cfg->spectral_inversion + ? 1 : 0); + if (lg_fail(ret)) + goto fail; + + ret = lgdt3305_set_filter_extension(state, param); + if (lg_fail(ret)) + goto fail; + + state->current_modulation = param->u.vsb.modulation; + + ret = lgdt3305_mpeg_mode(state, state->cfg->mpeg_mode); + if (lg_fail(ret)) + goto fail; + + /* lgdt3305_mpeg_mode_polarity calls lgdt3305_soft_reset */ + ret = lgdt3305_mpeg_mode_polarity(state, + state->cfg->tpclk_edge, + state->cfg->tpvalid_polarity); +fail: + return ret; +} + +static int lgdt3305_get_frontend(struct dvb_frontend *fe, + struct dvb_frontend_parameters *param) +{ + struct lgdt3305_state *state = fe->demodulator_priv; + + lg_dbg("\n"); + + param->u.vsb.modulation = state->current_modulation; + param->frequency = state->current_frequency; + return 0; +} + +/* ------------------------------------------------------------------------ */ + +static int lgdt3305_read_cr_lock_status(struct lgdt3305_state *state, + int *locked) +{ + u8 val; + int ret; + char *cr_lock_state = ""; + + *locked = 0; + + ret = lgdt3305_read_reg(state, LGDT3305_CR_LOCK_STATUS, &val); + if (lg_fail(ret)) + goto fail; + + switch (state->current_modulation) { + case QAM_256: + case QAM_64: + if (val & (1 << 1)) + *locked = 1; + + switch (val & 0x07) { + case 0: + cr_lock_state = "QAM UNLOCK"; + break; + case 4: + cr_lock_state = "QAM 1stLock"; + break; + case 6: + cr_lock_state = "QAM 2ndLock"; + break; + case 7: + cr_lock_state = "QAM FinalLock"; + break; + default: + cr_lock_state = "CLOCKQAM-INVALID!"; + break; + } + break; + case VSB_8: + if (val & (1 << 7)) { + *locked = 1; + cr_lock_state = "CLOCKVSB"; + } + break; + default: + ret = -EINVAL; + } + lg_dbg("(%d) %s\n", *locked, cr_lock_state); +fail: + return ret; +} + +static int lgdt3305_read_fec_lock_status(struct lgdt3305_state *state, + int *locked) +{ + u8 val; + int ret, mpeg_lock, fec_lock, viterbi_lock; + + *locked = 0; + + switch (state->current_modulation) { + case QAM_256: + case QAM_64: + ret = lgdt3305_read_reg(state, + LGDT3305_FEC_LOCK_STATUS, &val); + if (lg_fail(ret)) + goto fail; + + mpeg_lock = (val & (1 << 0)) ? 1 : 0; + fec_lock = (val & (1 << 2)) ? 1 : 0; + viterbi_lock = (val & (1 << 3)) ? 1 : 0; + + *locked = mpeg_lock && fec_lock && viterbi_lock; + + lg_dbg("(%d) %s%s%s\n", *locked, + mpeg_lock ? "mpeg lock " : "", + fec_lock ? "fec lock " : "", + viterbi_lock ? "viterbi lock" : ""); + break; + case VSB_8: + default: + ret = -EINVAL; + } +fail: + return ret; +} + +static int lgdt3305_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct lgdt3305_state *state = fe->demodulator_priv; + u8 val; + int ret, signal, inlock, nofecerr, snrgood, + cr_lock, fec_lock, sync_lock; + + *status = 0; + + ret = lgdt3305_read_reg(state, LGDT3305_GEN_STATUS, &val); + if (lg_fail(ret)) + goto fail; + + signal = (val & (1 << 4)) ? 1 : 0; + inlock = (val & (1 << 3)) ? 0 : 1; + sync_lock = (val & (1 << 2)) ? 1 : 0; + nofecerr = (val & (1 << 1)) ? 1 : 0; + snrgood = (val & (1 << 0)) ? 1 : 0; + + lg_dbg("%s%s%s%s%s\n", + signal ? "SIGNALEXIST " : "", + inlock ? "INLOCK " : "", + sync_lock ? "SYNCLOCK " : "", + nofecerr ? "NOFECERR " : "", + snrgood ? "SNRGOOD " : ""); + + ret = lgdt3305_read_cr_lock_status(state, &cr_lock); + if (lg_fail(ret)) + goto fail; + + if (signal) + *status |= FE_HAS_SIGNAL; + if (cr_lock) + *status |= FE_HAS_CARRIER; + if (nofecerr) + *status |= FE_HAS_VITERBI; + if (sync_lock) + *status |= FE_HAS_SYNC; + + switch (state->current_modulation) { + case QAM_256: + case QAM_64: + ret = lgdt3305_read_fec_lock_status(state, &fec_lock); + if (lg_fail(ret)) + goto fail; + + if (fec_lock) + *status |= FE_HAS_LOCK; + break; + case VSB_8: + if (inlock) + *status |= FE_HAS_LOCK; + break; + default: + ret = -EINVAL; + } +fail: + return ret; +} + +/* ------------------------------------------------------------------------ */ + +/* borrowed from lgdt330x.c */ +static u32 calculate_snr(u32 mse, u32 c) +{ + if (mse == 0) /* no signal */ + return 0; + + mse = intlog10(mse); + if (mse > c) { + /* Negative SNR, which is possible, but realisticly the + demod will lose lock before the signal gets this bad. The + API only allows for unsigned values, so just return 0 */ + return 0; + } + return 10*(c - mse); +} + +static int lgdt3305_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct lgdt3305_state *state = fe->demodulator_priv; + u32 noise; /* noise value */ + u32 c; /* per-modulation SNR calculation constant */ + + switch (state->current_modulation) { + case VSB_8: +#ifdef USE_PTMSE + /* Use Phase Tracker Mean-Square Error Register */ + /* SNR for ranges from -13.11 to +44.08 */ + noise = ((read_reg(state, LGDT3305_PT_MSE_1) & 0x07) << 16) | + (read_reg(state, LGDT3305_PT_MSE_2) << 8) | + (read_reg(state, LGDT3305_PT_MSE_3) & 0xff); + c = 73957994; /* log10(25*32^2)*2^24 */ +#else + /* Use Equalizer Mean-Square Error Register */ + /* SNR for ranges from -16.12 to +44.08 */ + noise = ((read_reg(state, LGDT3305_EQ_MSE_1) & 0x0f) << 16) | + (read_reg(state, LGDT3305_EQ_MSE_2) << 8) | + (read_reg(state, LGDT3305_EQ_MSE_3) & 0xff); + c = 73957994; /* log10(25*32^2)*2^24 */ +#endif + break; + case QAM_64: + case QAM_256: + noise = (read_reg(state, LGDT3305_CR_MSE_1) << 8) | + (read_reg(state, LGDT3305_CR_MSE_2) & 0xff); + + c = (state->current_modulation == QAM_64) ? + 97939837 : 98026066; + /* log10(688128)*2^24 and log10(696320)*2^24 */ + break; + default: + return -EINVAL; + } + state->snr = calculate_snr(noise, c); +#if 0 + /* convert from 8.24 fixed-point to 8.8 */ + *snr = (state->snr) >> 16; +#else + /*report SNR in dB * 10 */ + *snr = (state->snr / ((1 << 24) / 10)); +#endif + lg_dbg("noise = 0x%08x, snr = %d.%02d dB\n", noise, + state->snr >> 24, (((state->snr >> 8) & 0xffff) * 100) >> 16); + + return 0; +} + +static int lgdt3305_read_signal_strength(struct dvb_frontend *fe, + u16 *strength) +{ + /* borrowed from lgdt330x.c + * + * Calculate strength from SNR up to 35dB + * Even though the SNR can go higher than 35dB, + * there is some comfort factor in having a range of + * strong signals that can show at 100% + */ + struct lgdt3305_state *state = fe->demodulator_priv; + u16 snr; + int ret; + + *strength = 0; + + ret = fe->ops.read_snr(fe, &snr); + if (lg_fail(ret)) + goto fail; + /* Rather than use the 8.8 value snr, use state->snr which is 8.24 */ + /* scale the range 0 - 35*2^24 into 0 - 65535 */ + if (state->snr >= 8960 * 0x10000) + *strength = 0xffff; + else + *strength = state->snr / 8960; +fail: + return ret; +} + +/* ------------------------------------------------------------------------ */ + +static int lgdt3305_read_ber(struct dvb_frontend *fe, u32 *ber) +{ +#if 0 + struct lgdt3305_state *state = fe->demodulator_priv; + u32 period, biterror; + u8 bertperd; + + bertperd = read_reg(state, LGDT3305_BERT_PERIOD) & 0x1f; + if (bertperd < 4) + bertperd = 4; + period = (1 << bertperd); + + biterror = + (read_reg(state, LGDT3305_BERT_ERROR_COUNT_1) << 24) | + (read_reg(state, LGDT3305_BERT_ERROR_COUNT_2) << 16) | + (read_reg(state, LGDT3305_BERT_ERROR_COUNT_3) << 8) | + (read_reg(state, LGDT3305_BERT_ERROR_COUNT_4) & 0xff); + + *ber = (biterror) / period; + + lg_dbg("biterror = %d, period = %d, ber = 0x%x\n", + biterror, period, *ber); +#else + *ber = 0; +#endif + return 0; +} + +static int lgdt3305_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + struct lgdt3305_state *state = fe->demodulator_priv; + + *ucblocks = + (read_reg(state, LGDT3305_FEC_PKT_ERR_1) << 8) | + (read_reg(state, LGDT3305_FEC_PKT_ERR_2) & 0xff); + + return 0; +} + +static int lgdt3305_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings + *fe_tune_settings) +{ + fe_tune_settings->min_delay_ms = 500; + lg_dbg("\n"); + return 0; +} + +static void lgdt3305_release(struct dvb_frontend *fe) +{ + struct lgdt3305_state *state = fe->demodulator_priv; + lg_dbg("\n"); + kfree(state); +} + +static struct dvb_frontend_ops lgdt3305_ops; + +struct dvb_frontend *lgdt3305_attach(const struct lgdt3305_config *config, + struct i2c_adapter *i2c_adap) +{ + struct lgdt3305_state *state = NULL; + int ret; + u8 val; + + lg_dbg("(%d-%04x)\n", + i2c_adap ? i2c_adapter_id(i2c_adap) : 0, + config ? config->i2c_addr : 0); + + state = kzalloc(sizeof(struct lgdt3305_state), GFP_KERNEL); + if (state == NULL) + goto fail; + + state->cfg = config; + state->i2c_adap = i2c_adap; + + memcpy(&state->frontend.ops, &lgdt3305_ops, + sizeof(struct dvb_frontend_ops)); + state->frontend.demodulator_priv = state; + + /* verify that we're talking to a lg dt3305 */ + ret = lgdt3305_read_reg(state, LGDT3305_GEN_CTRL_2, &val); + if ((lg_fail(ret)) | (val == 0)) + goto fail; + ret = lgdt3305_write_reg(state, 0x0808, 0x80); + if (lg_fail(ret)) + goto fail; + ret = lgdt3305_read_reg(state, 0x0808, &val); + if ((lg_fail(ret)) | (val != 0x80)) + goto fail; + ret = lgdt3305_write_reg(state, 0x0808, 0x00); + if (lg_fail(ret)) + goto fail; + + state->current_frequency = -1; + state->current_modulation = -1; + + return &state->frontend; +fail: + lg_warn("unable to detect LGDT3305 hardware\n"); + state->frontend.demodulator_priv = NULL; + kfree(state); + return NULL; +} +EXPORT_SYMBOL(lgdt3305_attach); + +static struct dvb_frontend_ops lgdt3305_ops = { + .info = { + .name = "LG Electronics LGDT3305 VSB/QAM Frontend", + .type = FE_ATSC, + .frequency_min = 54000000, + .frequency_max = 858000000, + .frequency_stepsize = 62500, + .caps = FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_8VSB + }, + .i2c_gate_ctrl = lgdt3305_i2c_gate_ctrl, + .init = lgdt3305_init, + .sleep = lgdt3305_sleep, + .set_frontend = lgdt3305_set_parameters, + .get_frontend = lgdt3305_get_frontend, + .get_tune_settings = lgdt3305_get_tune_settings, + .read_status = lgdt3305_read_status, + .read_ber = lgdt3305_read_ber, + .read_signal_strength = lgdt3305_read_signal_strength, + .read_snr = lgdt3305_read_snr, + .read_ucblocks = lgdt3305_read_ucblocks, + .release = lgdt3305_release, +}; + +MODULE_DESCRIPTION("LG Electronics LGDT3305 ATSC/QAM-B Demodulator Driver"); +MODULE_AUTHOR("Michael Krufky"); +MODULE_LICENSE("GPL"); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/linux/drivers/media/dvb/frontends/lgdt3305.h b/linux/drivers/media/dvb/frontends/lgdt3305.h new file mode 100644 index 000000000..4fa6e52d1 --- /dev/null +++ b/linux/drivers/media/dvb/frontends/lgdt3305.h @@ -0,0 +1,85 @@ +/* + * Support for LGDT3305 - VSB/QAM + * + * Copyright (C) 2008, 2009 Michael Krufky <mkrufky@linuxtv.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef _LGDT3305_H_ +#define _LGDT3305_H_ + +#include <linux/i2c.h> +#include "dvb_frontend.h" + + +enum lgdt3305_mpeg_mode { + LGDT3305_MPEG_PARALLEL = 0, + LGDT3305_MPEG_SERIAL = 1, +}; + +enum lgdt3305_tp_clock_edge { + LGDT3305_TPCLK_RISING_EDGE = 0, + LGDT3305_TPCLK_FALLING_EDGE = 1, +}; + +enum lgdt3305_tp_valid_polarity { + LGDT3305_TP_VALID_LOW = 0, + LGDT3305_TP_VALID_HIGH = 1, +}; + +struct lgdt3305_config { + u8 i2c_addr; + + /* user defined IF frequency in KHz */ + u16 qam_if_khz; + u16 vsb_if_khz; + + /* AGC Power reference - defaults are used if left unset */ + u16 usref_8vsb; /* default: 0x32c4 */ + u16 usref_qam64; /* default: 0x5400 */ + u16 usref_qam256; /* default: 0x2a80 */ + + /* disable i2c repeater - 0:repeater enabled 1:repeater disabled */ + int deny_i2c_rptr:1; + + /* spectral inversion - 0:disabled 1:enabled */ + int spectral_inversion:1; + + /* use RF AGC loop - 0:disabled 1:enabled */ + int rf_agc_loop:1; + + enum lgdt3305_mpeg_mode mpeg_mode; + enum lgdt3305_tp_clock_edge tpclk_edge; + enum lgdt3305_tp_valid_polarity tpvalid_polarity; +}; + +#if defined(CONFIG_DVB_LGDT3305) || (defined(CONFIG_DVB_LGDT3305_MODULE) && \ + defined(MODULE)) +extern +struct dvb_frontend *lgdt3305_attach(const struct lgdt3305_config *config, + struct i2c_adapter *i2c_adap); +#else +static inline +struct dvb_frontend *lgdt3305_attach(const struct lgdt3305_config *config, + struct i2c_adapter *i2c_adap) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif /* CONFIG_DVB_LGDT3305 */ + +#endif /* _LGDT3305_H_ */ diff --git a/linux/drivers/media/video/saa7134/Kconfig b/linux/drivers/media/video/saa7134/Kconfig index 51f17c82b..e62b29967 100644 --- a/linux/drivers/media/video/saa7134/Kconfig +++ b/linux/drivers/media/video/saa7134/Kconfig @@ -42,6 +42,9 @@ config VIDEO_SAA7134_DVB select DVB_MT312 if !DVB_FE_CUSTOMISE select DVB_LNBP21 if !DVB_FE_CUSTOMISE select DVB_ZL10353 if !DVB_FE_CUSTOMISE + select DVB_LGDT3305 if !DVB_FE_CUSTOMISE + select MEDIA_TUNER_TDA18271 if !MEDIA_TUNER_CUSTOMIZE + select MEDIA_TUNER_TDA8290 if !MEDIA_TUNER_CUSTOMIZE ---help--- This adds support for DVB cards based on the Philips saa7134 chip. diff --git a/linux/drivers/media/video/saa7134/saa7134-cards.c b/linux/drivers/media/video/saa7134/saa7134-cards.c index 725c05f9e..e8efc883c 100644 --- a/linux/drivers/media/video/saa7134/saa7134-cards.c +++ b/linux/drivers/media/video/saa7134/saa7134-cards.c @@ -3331,14 +3331,16 @@ struct saa7134_board saa7134_boards[] = { .gpio = 0x0200100, }, }, - [SAA7134_BOARD_HAUPPAUGE_HVR1150] = { - .name = "Hauppauge WinTV-HVR1150", + [SAA7134_BOARD_HAUPPAUGE_HVR1120] = { + .name = "Hauppauge WinTV-HVR1120 ATSC/QAM-Hybrid", .audio_clock = 0x00187de7, .tuner_type = TUNER_PHILIPS_TDA8290, .radio_type = UNSET, .tuner_addr = ADDR_UNSET, .radio_addr = ADDR_UNSET, .tuner_config = 3, + .mpeg = SAA7134_MPEG_DVB, + .ts_type = SAA7134_MPEG_TS_SERIAL, .gpiomask = 0x0800100, /* GPIO 21 is an INPUT */ .inputs = {{ .name = name_tv, @@ -5505,7 +5507,7 @@ struct pci_device_id saa7134_pci_tbl[] = { .device = PCI_DEVICE_ID_PHILIPS_SAA7133, .subvendor = 0x0070, .subdevice = 0x6706, - .driver_data = SAA7134_BOARD_HAUPPAUGE_HVR1150, + .driver_data = SAA7134_BOARD_HAUPPAUGE_HVR1120, },{ .vendor = PCI_VENDOR_ID_PHILIPS, .device = PCI_DEVICE_ID_PHILIPS_SAA7133, @@ -5517,7 +5519,7 @@ struct pci_device_id saa7134_pci_tbl[] = { .device = PCI_DEVICE_ID_PHILIPS_SAA7133, .subvendor = 0x0070, .subdevice = 0x6708, - .driver_data = SAA7134_BOARD_HAUPPAUGE_HVR1150, + .driver_data = SAA7134_BOARD_HAUPPAUGE_HVR1120, },{ .vendor = PCI_VENDOR_ID_PHILIPS, .device = PCI_DEVICE_ID_PHILIPS_SAA7133, @@ -6057,7 +6059,7 @@ static int saa7134_tda8290_18271_callback(struct saa7134_dev *dev, switch (command) { case TDA18271_CALLBACK_CMD_AGC_ENABLE: /* 0 */ switch (dev->board) { - case SAA7134_BOARD_HAUPPAUGE_HVR1150: + case SAA7134_BOARD_HAUPPAUGE_HVR1120: case SAA7134_BOARD_HAUPPAUGE_HVR1110R3: ret = saa7134_tda18271_hvr11x0_toggle_agc(dev, arg); break; @@ -6078,7 +6080,7 @@ static int saa7134_tda8290_callback(struct saa7134_dev *dev, int ret; switch (dev->board) { - case SAA7134_BOARD_HAUPPAUGE_HVR1150: + case SAA7134_BOARD_HAUPPAUGE_HVR1120: case SAA7134_BOARD_HAUPPAUGE_HVR1110R3: /* tda8290 + tda18271 */ ret = saa7134_tda8290_18271_callback(dev, command, arg); @@ -6125,7 +6127,7 @@ static void hauppauge_eeprom(struct saa7134_dev *dev, u8 *eeprom_data) switch (tv.model) { case 67019: /* WinTV-HVR1110 (Retail, IR Blaster, hybrid, FM, SVid/Comp, 3.5mm audio in) */ case 67109: /* WinTV-HVR1000 (Retail, IR Receive, analog, no FM, SVid/Comp, 3.5mm audio in) */ - case 67201: /* WinTV-HVR1150 (Retail, IR Receive, hybrid, FM, SVid/Comp, 3.5mm audio in) */ + case 67201: /* WinTV-HVR1120 (Retail, IR Receive, hybrid, FM, SVid/Comp, 3.5mm audio in) */ case 67301: /* WinTV-HVR1000 (Retail, IR Receive, analog, no FM, SVid/Comp, 3.5mm audio in) */ case 67209: /* WinTV-HVR1110 (Retail, IR Receive, hybrid, FM, SVid/Comp, 3.5mm audio in) */ case 67559: /* WinTV-HVR1110 (OEM, no IR, hybrid, FM, SVid/Comp, RCA aud) */ @@ -6133,7 +6135,7 @@ static void hauppauge_eeprom(struct saa7134_dev *dev, u8 *eeprom_data) case 67579: /* WinTV-HVR1110 (OEM, no IR, hybrid, no FM) */ case 67589: /* WinTV-HVR1110 (OEM, no IR, hybrid, no FM, SVid/Comp, RCA aud) */ case 67599: /* WinTV-HVR1110 (OEM, no IR, hybrid, no FM, SVid/Comp, RCA aud) */ - case 67651: /* WinTV-HVR1150 (OEM, no IR, hybrid, FM, SVid/Comp, RCA aud) */ + case 67651: /* WinTV-HVR1120 (OEM, no IR, hybrid, FM, SVid/Comp, RCA aud) */ case 67659: /* WinTV-HVR1110 (OEM, no IR, hybrid, FM, SVid/Comp, RCA aud) */ break; default: @@ -6315,7 +6317,7 @@ int saa7134_board_init1(struct saa7134_dev *dev) saa_writeb (SAA7134_PRODUCTION_TEST_MODE, 0x00); break; - case SAA7134_BOARD_HAUPPAUGE_HVR1150: + case SAA7134_BOARD_HAUPPAUGE_HVR1120: case SAA7134_BOARD_HAUPPAUGE_HVR1110R3: /* GPIO 26 high for digital, low for analog */ saa7134_set_gpio(dev, 26, 0); @@ -6577,7 +6579,7 @@ int saa7134_board_init2(struct saa7134_dev *dev) dev->name, saa7134_boards[dev->board].name); } break; - case SAA7134_BOARD_HAUPPAUGE_HVR1150: + case SAA7134_BOARD_HAUPPAUGE_HVR1120: case SAA7134_BOARD_HAUPPAUGE_HVR1110R3: hauppauge_eeprom(dev, dev->eedata+0x80); break; diff --git a/linux/drivers/media/video/saa7134/saa7134-dvb.c b/linux/drivers/media/video/saa7134/saa7134-dvb.c index 7745cad95..085589998 100644 --- a/linux/drivers/media/video/saa7134/saa7134-dvb.c +++ b/linux/drivers/media/video/saa7134/saa7134-dvb.c @@ -48,6 +48,9 @@ #include "isl6405.h" #include "lnbp21.h" #include "tuner-simple.h" +#include "tda18271.h" +#include "lgdt3305.h" +#include "tda8290.h" #include "zl10353.h" @@ -963,6 +966,34 @@ static struct zl10036_config avertv_a700_tuner = { .tuner_address = 0x60, }; +static struct lgdt3305_config hcw_lgdt3305_config = { + .i2c_addr = 0x0e, + .mpeg_mode = LGDT3305_MPEG_SERIAL, + .tpclk_edge = LGDT3305_TPCLK_RISING_EDGE, + .tpvalid_polarity = LGDT3305_TP_VALID_HIGH, + .deny_i2c_rptr = 1, + .spectral_inversion = 1, + .qam_if_khz = 4000, + .vsb_if_khz = 3250, +}; + +static struct tda18271_std_map hauppauge_tda18271_std_map = { + .atsc_6 = { .if_freq = 3250, .agc_mode = 3, .std = 4, + .if_lvl = 1, .rfagc_top = 0x58, }, + .qam_6 = { .if_freq = 4000, .agc_mode = 3, .std = 5, + .if_lvl = 1, .rfagc_top = 0x58, }, +}; + +static struct tda18271_config hcw_tda18271_config = { + .std_map = &hauppauge_tda18271_std_map, + .gate = TDA18271_GATE_ANALOG, + .config = 3, +}; + +static struct tda829x_config tda829x_no_probe = { + .probe_tuner = TDA829X_DONT_PROBE, +}; + /* ================================================================== * Core code */ @@ -1089,6 +1120,19 @@ static int dvb_init(struct saa7134_dev *dev) &tda827x_cfg_1) < 0) goto dettach_frontend; break; + case SAA7134_BOARD_HAUPPAUGE_HVR1120: + fe0->dvb.frontend = dvb_attach(lgdt3305_attach, + &hcw_lgdt3305_config, + &dev->i2c_adap); + if (fe0->dvb.frontend) { + dvb_attach(tda829x_attach, fe0->dvb.frontend, + &dev->i2c_adap, 0x4b, + &tda829x_no_probe); + dvb_attach(tda18271_attach, fe0->dvb.frontend, + 0x60, &dev->i2c_adap, + &hcw_tda18271_config); + } + break; case SAA7134_BOARD_ASUSTeK_P7131_DUAL: if (configure_tda827x_fe(dev, &asus_p7131_dual_config, &tda827x_cfg_0) < 0) diff --git a/linux/drivers/media/video/saa7134/saa7134-ts.c b/linux/drivers/media/video/saa7134/saa7134-ts.c index ef55a59f0..cc8b923af 100644 --- a/linux/drivers/media/video/saa7134/saa7134-ts.c +++ b/linux/drivers/media/video/saa7134/saa7134-ts.c @@ -79,8 +79,19 @@ static int buffer_activate(struct saa7134_dev *dev, saa_writeb(SAA7134_TS_SERIAL1, 0x00); /* Start TS stream */ - saa_writeb(SAA7134_TS_SERIAL0, 0x40); - saa_writeb(SAA7134_TS_PARALLEL, 0xEC); + switch (saa7134_boards[dev->board].ts_type) { + case SAA7134_MPEG_TS_PARALLEL: + saa_writeb(SAA7134_TS_SERIAL0, 0x40); + saa_writeb(SAA7134_TS_PARALLEL, 0xec); + break; + case SAA7134_MPEG_TS_SERIAL: + saa_writeb(SAA7134_TS_SERIAL0, 0xd8); + saa_writeb(SAA7134_TS_PARALLEL, 0x6c); + saa_writeb(SAA7134_TS_PARALLEL_SERIAL, 0xbc); + saa_writeb(SAA7134_TS_SERIAL1, 0x02); + break; + } + dev->ts_state = SAA7134_TS_STARTED; } diff --git a/linux/drivers/media/video/saa7134/saa7134.h b/linux/drivers/media/video/saa7134/saa7134.h index 0e45c0931..d2b29ed9e 100644 --- a/linux/drivers/media/video/saa7134/saa7134.h +++ b/linux/drivers/media/video/saa7134/saa7134.h @@ -279,7 +279,7 @@ struct saa7134_format { #define SAA7134_BOARD_ASUSTeK_TIGER 152 #define SAA7134_BOARD_KWORLD_PLUS_TV_ANALOG 153 #define SAA7134_BOARD_AVERMEDIA_GO_007_FM_PLUS 154 -#define SAA7134_BOARD_HAUPPAUGE_HVR1150 155 +#define SAA7134_BOARD_HAUPPAUGE_HVR1120 155 #define SAA7134_BOARD_HAUPPAUGE_HVR1110R3 156 #define SAA7134_MAXBOARDS 32 @@ -313,6 +313,11 @@ enum saa7134_mpeg_type { SAA7134_MPEG_DVB, }; +enum saa7134_mpeg_ts_type { + SAA7134_MPEG_TS_PARALLEL = 0, + SAA7134_MPEG_TS_SERIAL, +}; + struct saa7134_board { char *name; unsigned int audio_clock; @@ -335,6 +340,7 @@ struct saa7134_board { /* peripheral I/O */ enum saa7134_video_out video_out; enum saa7134_mpeg_type mpeg; + enum saa7134_mpeg_ts_type ts_type; unsigned int vid_port_opts; }; |