diff options
Diffstat (limited to 'drivers/media/dvb/frontends')
32 files changed, 7279 insertions, 171 deletions
diff --git a/drivers/media/dvb/frontends/Kconfig b/drivers/media/dvb/frontends/Kconfig index 7dbb4a223c99..96b93e21a84b 100644 --- a/drivers/media/dvb/frontends/Kconfig +++ b/drivers/media/dvb/frontends/Kconfig @@ -43,6 +43,20 @@ config DVB_S5H1420 help A DVB-S tuner module. Say Y when you want to support this frontend. +config DVB_STV0288 + tristate "ST STV0288 based" + depends on DVB_CORE && I2C + default m if DVB_FE_CUSTOMISE + help + A DVB-S tuner module. Say Y when you want to support this frontend. + +config DVB_STB6000 + tristate "ST STB6000 silicon tuner" + depends on DVB_CORE && I2C + default m if DVB_FE_CUSTOMISE + help + A DVB-S silicon tuner module. Say Y when you want to support this tuner. + config DVB_STV0299 tristate "ST STV0299 based" depends on DVB_CORE && I2C @@ -92,6 +106,20 @@ config DVB_TUA6100 help A DVB-S PLL chip. +config DVB_CX24116 + tristate "Conexant CX24116 based" + depends on DVB_CORE && I2C + default m if DVB_FE_CUSTOMISE + help + A DVB-S/S2 tuner module. Say Y when you want to support this frontend. + +config DVB_SI21XX + tristate "Silicon Labs SI21XX based" + depends on DVB_CORE && I2C + default m if DVB_FE_CUSTOMISE + help + A DVB-S tuner module. Say Y when you want to support this frontend. + comment "DVB-T (terrestrial) frontends" depends on DVB_CORE @@ -385,4 +413,23 @@ config DVB_ISL6421 help An SEC control chip. +config DVB_LGS8GL5 + tristate "Silicon Legend LGS-8GL5 demodulator (OFDM)" + depends on DVB_CORE && I2C + default m if DVB_FE_CUSTOMISE + help + A DMB-TH tuner module. Say Y when you want to support this frontend. + +comment "Tools to develop new frontends" + +config DVB_DUMMY_FE + tristate "Dummy frontend driver" + default n + +config DVB_AF9013 + tristate "Afatech AF9013 demodulator" + depends on DVB_CORE && I2C + default m if DVB_FE_CUSTOMISE + help + 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 028da55611c0..aba79f4a63a7 100644 --- a/drivers/media/dvb/frontends/Makefile +++ b/drivers/media/dvb/frontends/Makefile @@ -48,3 +48,10 @@ obj-$(CONFIG_DVB_TUNER_ITD1000) += itd1000.o obj-$(CONFIG_DVB_AU8522) += au8522.o obj-$(CONFIG_DVB_TDA10048) += tda10048.o obj-$(CONFIG_DVB_S5H1411) += s5h1411.o +obj-$(CONFIG_DVB_LGS8GL5) += lgs8gl5.o +obj-$(CONFIG_DVB_DUMMY_FE) += dvb_dummy_fe.o +obj-$(CONFIG_DVB_AF9013) += af9013.o +obj-$(CONFIG_DVB_CX24116) += cx24116.o +obj-$(CONFIG_DVB_SI21XX) += si21xx.o +obj-$(CONFIG_DVB_STV0288) += stv0288.o +obj-$(CONFIG_DVB_STB6000) += stb6000.o diff --git a/drivers/media/dvb/frontends/af9013.c b/drivers/media/dvb/frontends/af9013.c new file mode 100644 index 000000000000..21c1060cf10e --- /dev/null +++ b/drivers/media/dvb/frontends/af9013.c @@ -0,0 +1,1685 @@ +/* + * DVB USB Linux driver for Afatech AF9015 DVB-T USB2.0 receiver + * + * Copyright (C) 2007 Antti Palosaari <crope@iki.fi> + * + * Thanks to Afatech who kindly provided information. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/firmware.h> + +#include "dvb_frontend.h" +#include "af9013_priv.h" +#include "af9013.h" + +int af9013_debug; + +struct af9013_state { + struct i2c_adapter *i2c; + struct dvb_frontend frontend; + + struct af9013_config config; + + u16 signal_strength; + u32 ber; + u32 ucblocks; + u16 snr; + u32 frequency; + unsigned long next_statistics_check; +}; + +static u8 regmask[8] = { 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff }; + +static int af9013_write_regs(struct af9013_state *state, u8 mbox, u16 reg, + u8 *val, u8 len) +{ + u8 buf[3+len]; + struct i2c_msg msg = { + .addr = state->config.demod_address, + .flags = 0, + .len = sizeof(buf), + .buf = buf }; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + buf[2] = mbox; + memcpy(&buf[3], val, len); + + if (i2c_transfer(state->i2c, &msg, 1) != 1) { + warn("I2C write failed reg:%04x len:%d", reg, len); + return -EREMOTEIO; + } + return 0; +} + +static int af9013_write_ofdm_regs(struct af9013_state *state, u16 reg, u8 *val, + u8 len) +{ + u8 mbox = (1 << 0)|(1 << 1)|((len - 1) << 2)|(0 << 6)|(0 << 7); + return af9013_write_regs(state, mbox, reg, val, len); +} + +static int af9013_write_ofsm_regs(struct af9013_state *state, u16 reg, u8 *val, + u8 len) +{ + u8 mbox = (1 << 0)|(1 << 1)|((len - 1) << 2)|(1 << 6)|(1 << 7); + return af9013_write_regs(state, mbox, reg, val, len); +} + +/* write single register */ +static int af9013_write_reg(struct af9013_state *state, u16 reg, u8 val) +{ + return af9013_write_ofdm_regs(state, reg, &val, 1); +} + +/* read single register */ +static int af9013_read_reg(struct af9013_state *state, u16 reg, u8 *val) +{ + u8 obuf[3] = { reg >> 8, reg & 0xff, 0 }; + u8 ibuf[1]; + struct i2c_msg msg[2] = { + { + .addr = state->config.demod_address, + .flags = 0, + .len = sizeof(obuf), + .buf = obuf + }, { + .addr = state->config.demod_address, + .flags = I2C_M_RD, + .len = sizeof(ibuf), + .buf = ibuf + } + }; + + if (i2c_transfer(state->i2c, msg, 2) != 2) { + warn("I2C read failed reg:%04x", reg); + return -EREMOTEIO; + } + *val = ibuf[0]; + return 0; +} + +static int af9013_write_reg_bits(struct af9013_state *state, u16 reg, u8 pos, + u8 len, u8 val) +{ + int ret; + u8 tmp, mask; + + ret = af9013_read_reg(state, reg, &tmp); + if (ret) + return ret; + + mask = regmask[len - 1] << pos; + tmp = (tmp & ~mask) | ((val << pos) & mask); + + return af9013_write_reg(state, reg, tmp); +} + +static int af9013_read_reg_bits(struct af9013_state *state, u16 reg, u8 pos, + u8 len, u8 *val) +{ + int ret; + u8 tmp; + + ret = af9013_read_reg(state, reg, &tmp); + if (ret) + return ret; + *val = (tmp >> pos) & regmask[len - 1]; + return 0; +} + +static int af9013_set_gpio(struct af9013_state *state, u8 gpio, u8 gpioval) +{ + int ret; + u8 pos; + u16 addr; + deb_info("%s: gpio:%d gpioval:%02x\n", __func__, gpio, gpioval); + +/* GPIO0 & GPIO1 0xd735 + GPIO2 & GPIO3 0xd736 */ + + switch (gpio) { + case 0: + case 1: + addr = 0xd735; + break; + case 2: + case 3: + addr = 0xd736; + break; + + default: + err("invalid gpio:%d\n", gpio); + ret = -EINVAL; + goto error; + }; + + switch (gpio) { + case 0: + case 2: + pos = 0; + break; + case 1: + case 3: + default: + pos = 4; + break; + }; + + ret = af9013_write_reg_bits(state, addr, pos, 4, gpioval); + +error: + return ret; +} + +static u32 af913_div(u32 a, u32 b, u32 x) +{ + u32 r = 0, c = 0, i; + deb_info("%s: a:%d b:%d x:%d\n", __func__, a, b, x); + + if (a > b) { + c = a / b; + a = a - c * b; + } + + for (i = 0; i < x; i++) { + if (a >= b) { + r += 1; + a -= b; + } + a <<= 1; + r <<= 1; + } + r = (c << (u32)x) + r; + + deb_info("%s: a:%d b:%d x:%d r:%d r:%x\n", __func__, a, b, x, r, r); + return r; +} + +static int af9013_set_coeff(struct af9013_state *state, fe_bandwidth_t bw) +{ + int ret = 0; + u8 i = 0; + u8 buf[24]; + u32 ns_coeff1_2048nu; + u32 ns_coeff1_8191nu; + u32 ns_coeff1_8192nu; + u32 ns_coeff1_8193nu; + u32 ns_coeff2_2k; + u32 ns_coeff2_8k; + + deb_info("%s: adc_clock:%d bw:%d\n", __func__, + state->config.adc_clock, bw); + + switch (state->config.adc_clock) { + case 28800: /* 28.800 MHz */ + switch (bw) { + case BANDWIDTH_6_MHZ: + ns_coeff1_2048nu = 0x01e79e7a; + ns_coeff1_8191nu = 0x0079eb6e; + ns_coeff1_8192nu = 0x0079e79e; + ns_coeff1_8193nu = 0x0079e3cf; + ns_coeff2_2k = 0x00f3cf3d; + ns_coeff2_8k = 0x003cf3cf; + break; + case BANDWIDTH_7_MHZ: + ns_coeff1_2048nu = 0x0238e38e; + ns_coeff1_8191nu = 0x008e3d55; + ns_coeff1_8192nu = 0x008e38e4; + ns_coeff1_8193nu = 0x008e3472; + ns_coeff2_2k = 0x011c71c7; + ns_coeff2_8k = 0x00471c72; + break; + case BANDWIDTH_8_MHZ: + ns_coeff1_2048nu = 0x028a28a3; + ns_coeff1_8191nu = 0x00a28f3d; + ns_coeff1_8192nu = 0x00a28a29; + ns_coeff1_8193nu = 0x00a28514; + ns_coeff2_2k = 0x01451451; + ns_coeff2_8k = 0x00514514; + break; + default: + ret = -EINVAL; + } + break; + case 20480: /* 20.480 MHz */ + switch (bw) { + case BANDWIDTH_6_MHZ: + ns_coeff1_2048nu = 0x02adb6dc; + ns_coeff1_8191nu = 0x00ab7313; + ns_coeff1_8192nu = 0x00ab6db7; + ns_coeff1_8193nu = 0x00ab685c; + ns_coeff2_2k = 0x0156db6e; + ns_coeff2_8k = 0x0055b6dc; + break; + case BANDWIDTH_7_MHZ: + ns_coeff1_2048nu = 0x03200001; + ns_coeff1_8191nu = 0x00c80640; + ns_coeff1_8192nu = 0x00c80000; + ns_coeff1_8193nu = 0x00c7f9c0; + ns_coeff2_2k = 0x01900000; + ns_coeff2_8k = 0x00640000; + break; + case BANDWIDTH_8_MHZ: + ns_coeff1_2048nu = 0x03924926; + ns_coeff1_8191nu = 0x00e4996e; + ns_coeff1_8192nu = 0x00e49249; + ns_coeff1_8193nu = 0x00e48b25; + ns_coeff2_2k = 0x01c92493; + ns_coeff2_8k = 0x00724925; + break; + default: + ret = -EINVAL; + } + break; + case 28000: /* 28.000 MHz */ + switch (bw) { + case BANDWIDTH_6_MHZ: + ns_coeff1_2048nu = 0x01f58d10; + ns_coeff1_8191nu = 0x007d672f; + ns_coeff1_8192nu = 0x007d6344; + ns_coeff1_8193nu = 0x007d5f59; + ns_coeff2_2k = 0x00fac688; + ns_coeff2_8k = 0x003eb1a2; + break; + case BANDWIDTH_7_MHZ: + ns_coeff1_2048nu = 0x02492492; + ns_coeff1_8191nu = 0x00924db7; + ns_coeff1_8192nu = 0x00924925; + ns_coeff1_8193nu = 0x00924492; + ns_coeff2_2k = 0x01249249; + ns_coeff2_8k = 0x00492492; + break; + case BANDWIDTH_8_MHZ: + ns_coeff1_2048nu = 0x029cbc15; + ns_coeff1_8191nu = 0x00a7343f; + ns_coeff1_8192nu = 0x00a72f05; + ns_coeff1_8193nu = 0x00a729cc; + ns_coeff2_2k = 0x014e5e0a; + ns_coeff2_8k = 0x00539783; + break; + default: + ret = -EINVAL; + } + break; + case 25000: /* 25.000 MHz */ + switch (bw) { + case BANDWIDTH_6_MHZ: + ns_coeff1_2048nu = 0x0231bcb5; + ns_coeff1_8191nu = 0x008c7391; + ns_coeff1_8192nu = 0x008c6f2d; + ns_coeff1_8193nu = 0x008c6aca; + ns_coeff2_2k = 0x0118de5b; + ns_coeff2_8k = 0x00463797; + break; + case BANDWIDTH_7_MHZ: + ns_coeff1_2048nu = 0x028f5c29; + ns_coeff1_8191nu = 0x00a3dc29; + ns_coeff1_8192nu = 0x00a3d70a; + ns_coeff1_8193nu = 0x00a3d1ec; + ns_coeff2_2k = 0x0147ae14; + ns_coeff2_8k = 0x0051eb85; + break; + case BANDWIDTH_8_MHZ: + ns_coeff1_2048nu = 0x02ecfb9d; + ns_coeff1_8191nu = 0x00bb44c1; + ns_coeff1_8192nu = 0x00bb3ee7; + ns_coeff1_8193nu = 0x00bb390d; + ns_coeff2_2k = 0x01767dce; + ns_coeff2_8k = 0x005d9f74; + break; + default: + ret = -EINVAL; + } + break; + default: + err("invalid xtal"); + return -EINVAL; + } + if (ret) { + err("invalid bandwidth"); + return ret; + } + + buf[i++] = (u8) ((ns_coeff1_2048nu & 0x03000000) >> 24); + buf[i++] = (u8) ((ns_coeff1_2048nu & 0x00ff0000) >> 16); + buf[i++] = (u8) ((ns_coeff1_2048nu & 0x0000ff00) >> 8); + buf[i++] = (u8) ((ns_coeff1_2048nu & 0x000000ff)); + buf[i++] = (u8) ((ns_coeff2_2k & 0x01c00000) >> 22); + buf[i++] = (u8) ((ns_coeff2_2k & 0x003fc000) >> 14); + buf[i++] = (u8) ((ns_coeff2_2k & 0x00003fc0) >> 6); + buf[i++] = (u8) ((ns_coeff2_2k & 0x0000003f)); + buf[i++] = (u8) ((ns_coeff1_8191nu & 0x03000000) >> 24); + buf[i++] = (u8) ((ns_coeff1_8191nu & 0x00ffc000) >> 16); + buf[i++] = (u8) ((ns_coeff1_8191nu & 0x0000ff00) >> 8); + buf[i++] = (u8) ((ns_coeff1_8191nu & 0x000000ff)); + buf[i++] = (u8) ((ns_coeff1_8192nu & 0x03000000) >> 24); + buf[i++] = (u8) ((ns_coeff1_8192nu & 0x00ffc000) >> 16); + buf[i++] = (u8) ((ns_coeff1_8192nu & 0x0000ff00) >> 8); + buf[i++] = (u8) ((ns_coeff1_8192nu & 0x000000ff)); + buf[i++] = (u8) ((ns_coeff1_8193nu & 0x03000000) >> 24); + buf[i++] = (u8) ((ns_coeff1_8193nu & 0x00ffc000) >> 16); + buf[i++] = (u8) ((ns_coeff1_8193nu & 0x0000ff00) >> 8); + buf[i++] = (u8) ((ns_coeff1_8193nu & 0x000000ff)); + buf[i++] = (u8) ((ns_coeff2_8k & 0x01c00000) >> 22); + buf[i++] = (u8) ((ns_coeff2_8k & 0x003fc000) >> 14); + buf[i++] = (u8) ((ns_coeff2_8k & 0x00003fc0) >> 6); + buf[i++] = (u8) ((ns_coeff2_8k & 0x0000003f)); + + deb_info("%s: coeff:", __func__); + debug_dump(buf, sizeof(buf), deb_info); + + /* program */ + for (i = 0; i < sizeof(buf); i++) { + ret = af9013_write_reg(state, 0xae00 + i, buf[i]); + if (ret) + break; + } + + return ret; +} + +static int af9013_set_adc_ctrl(struct af9013_state *state) +{ + int ret; + u8 buf[3], tmp, i; + u32 adc_cw; + + deb_info("%s: adc_clock:%d\n", __func__, state->config.adc_clock); + + /* adc frequency type */ + switch (state->config.adc_clock) { + case 28800: /* 28.800 MHz */ + tmp = 0; + break; + case 20480: /* 20.480 MHz */ + tmp = 1; + break; + case 28000: /* 28.000 MHz */ + tmp = 2; + break; + case 25000: /* 25.000 MHz */ + tmp = 3; + break; + default: + err("invalid xtal"); + return -EINVAL; + } + + adc_cw = af913_div(state->config.adc_clock*1000, 1000000ul, 19ul); + + buf[0] = (u8) ((adc_cw & 0x000000ff)); + buf[1] = (u8) ((adc_cw & 0x0000ff00) >> 8); + buf[2] = (u8) ((adc_cw & 0x00ff0000) >> 16); + + deb_info("%s: adc_cw:", __func__); + debug_dump(buf, sizeof(buf), deb_info); + + /* program */ + for (i = 0; i < sizeof(buf); i++) { + ret = af9013_write_reg(state, 0xd180 + i, buf[i]); + if (ret) + goto error; + } + ret = af9013_write_reg_bits(state, 0x9bd2, 0, 4, tmp); +error: + return ret; +} + +static int af9013_set_freq_ctrl(struct af9013_state *state, fe_bandwidth_t bw) +{ + int ret; + u16 addr; + u8 buf[3], i, j; + u32 adc_freq, freq_cw; + s8 bfs_spec_inv; + int if_sample_freq; + + for (j = 0; j < 3; j++) { + if (j == 0) { + addr = 0xd140; /* fcw normal */ + bfs_spec_inv = state->config.rf_spec_inv ? -1 : 1; + } else if (j == 1) { + addr = 0x9be7; /* fcw dummy ram */ + bfs_spec_inv = state->config.rf_spec_inv ? -1 : 1; + } else { + addr = 0x9bea; /* fcw inverted */ + bfs_spec_inv = state->config.rf_spec_inv ? 1 : -1; + } + + adc_freq = state->config.adc_clock * 1000; + if_sample_freq = state->config.tuner_if * 1000; + + /* TDA18271 uses different sampling freq for every bw */ + if (state->config.tuner == AF9013_TUNER_TDA18271) { + switch (bw) { + case BANDWIDTH_6_MHZ: + if_sample_freq = 3300000; /* 3.3 MHz */ + break; + case BANDWIDTH_7_MHZ: + if_sample_freq = 3800000; /* 3.8 MHz */ + break; + case BANDWIDTH_8_MHZ: + default: + if_sample_freq = 4300000; /* 4.3 MHz */ + break; + } + } + + while (if_sample_freq > (adc_freq / 2)) + if_sample_freq = if_sample_freq - adc_freq; + + if (if_sample_freq >= 0) + bfs_spec_inv = bfs_spec_inv * (-1); + else + if_sample_freq = if_sample_freq * (-1); + + freq_cw = af913_div(if_sample_freq, adc_freq, 23ul); + + if (bfs_spec_inv == -1) + freq_cw = 0x00800000 - freq_cw; + + buf[0] = (u8) ((freq_cw & 0x000000ff)); + buf[1] = (u8) ((freq_cw & 0x0000ff00) >> 8); + buf[2] = (u8) ((freq_cw & 0x007f0000) >> 16); + + + deb_info("%s: freq_cw:", __func__); + debug_dump(buf, sizeof(buf), deb_info); + + /* program */ + for (i = 0; i < sizeof(buf); i++) { + ret = af9013_write_reg(state, addr++, buf[i]); + if (ret) + goto error; + } + } +error: + return ret; +} + +static int af9013_set_ofdm_params(struct af9013_state *state, + struct dvb_ofdm_parameters *params, u8 *auto_mode) +{ + int ret; + u8 i, buf[3] = {0, 0, 0}; + *auto_mode = 0; /* set if parameters are requested to auto set */ + + switch (params->transmission_mode) { + case TRANSMISSION_MODE_AUTO: + *auto_mode = 1; + case TRANSMISSION_MODE_2K: + break; + case TRANSMISSION_MODE_8K: + buf[0] |= (1 << 0); + break; + default: + return -EINVAL; + } + + switch (params->guard_interval) { + case GUARD_INTERVAL_AUTO: + *auto_mode = 1; + case GUARD_INTERVAL_1_32: + break; + case GUARD_INTERVAL_1_16: + buf[0] |= (1 << 2); + break; + case GUARD_INTERVAL_1_8: + buf[0] |= (2 << 2); + break; + case GUARD_INTERVAL_1_4: + buf[0] |= (3 << 2); + break; + default: + return -EINVAL; + } + + switch (params->hierarchy_information) { + case HIERARCHY_AUTO: + *auto_mode = 1; + case HIERARCHY_NONE: + break; + case HIERARCHY_1: + buf[0] |= (1 << 4); + break; + case HIERARCHY_2: + buf[0] |= (2 << 4); + break; + case HIERARCHY_4: + buf[0] |= (3 << 4); + break; + default: + return -EINVAL; + }; + + switch (params->constellation) { + case QAM_AUTO: + *auto_mode = 1; + case QPSK: + break; + case QAM_16: + buf[1] |= (1 << 6); + break; + case QAM_64: + buf[1] |= (2 << 6); + break; + default: + return -EINVAL; + } + + /* Use HP. How and which case we can switch to LP? */ + buf[1] |= (1 << 4); + + switch (params->code_rate_HP) { + case FEC_AUTO: + *auto_mode = 1; + case FEC_1_2: + break; + case FEC_2_3: + buf[2] |= (1 << 0); + break; + case FEC_3_4: + buf[2] |= (2 << 0); + break; + case FEC_5_6: + buf[2] |= (3 << 0); + break; + case FEC_7_8: + buf[2] |= (4 << 0); + break; + default: + return -EINVAL; + } + + switch (params->code_rate_LP) { + case FEC_AUTO: + /* if HIERARCHY_NONE and FEC_NONE then LP FEC is set to FEC_AUTO + by dvb_frontend.c for compatibility */ + if (params->hierarchy_information != HIERARCHY_NONE) + *auto_mode = 1; + case FEC_1_2: + break; + case FEC_2_3: + buf[2] |= (1 << 3); + break; + case FEC_3_4: + buf[2] |= (2 << 3); + break; + case FEC_5_6: + buf[2] |= (3 << 3); + break; + case FEC_7_8: + buf[2] |= (4 << 3); + break; + case FEC_NONE: + if (params->hierarchy_information == HIERARCHY_AUTO) + break; + default: + return -EINVAL; + } + + switch (params->bandwidth) { + case BANDWIDTH_6_MHZ: + break; + case BANDWIDTH_7_MHZ: + buf[1] |= (1 << 2); + break; + case BANDWIDTH_8_MHZ: + buf[1] |= (2 << 2); + break; + default: + return -EINVAL; + } + + /* program */ + for (i = 0; i < sizeof(buf); i++) { + ret = af9013_write_reg(state, 0xd3c0 + i, buf[i]); + if (ret) + break; + } + + return ret; +} + +static int af9013_reset(struct af9013_state *state, u8 sleep) +{ + int ret; + u8 tmp, i; + deb_info("%s\n", __func__); + + /* enable OFDM reset */ + ret = af9013_write_reg_bits(state, 0xd417, 4, 1, 1); + if (ret) + goto error; + + /* start reset mechanism */ + ret = af9013_write_reg(state, 0xaeff, 1); + if (ret) + goto error; + + /* reset is done when bit 1 is set */ + for (i = 0; i < 150; i++) { + ret = af9013_read_reg_bits(state, 0xd417, 1, 1, &tmp); + if (ret) + goto error; + if (tmp) + break; /* reset done */ + msleep(10); + } + if (!tmp) + return -ETIMEDOUT; + + /* don't clear reset when going to sleep */ + if (!sleep) { + /* clear OFDM reset */ + ret = af9013_write_reg_bits(state, 0xd417, 1, 1, 0); + if (ret) + goto error; + + /* disable OFDM reset */ + ret = af9013_write_reg_bits(state, 0xd417, 4, 1, 0); + } +error: + return ret; +} + +static int af9013_power_ctrl(struct af9013_state *state, u8 onoff) +{ + int ret; + deb_info("%s: onoff:%d\n", __func__, onoff); + + if (onoff) { + /* power on */ + ret = af9013_write_reg_bits(state, 0xd73a, 3, 1, 0); + if (ret) + goto error; + ret = af9013_write_reg_bits(state, 0xd417, 1, 1, 0); + if (ret) + goto error; + ret = af9013_write_reg_bits(state, 0xd417, 4, 1, 0); + } else { + /* power off */ + ret = af9013_reset(state, 1); + if (ret) + goto error; + ret = af9013_write_reg_bits(state, 0xd73a, 3, 1, 1); + } +error: + return ret; +} + +static int af9013_lock_led(struct af9013_state *state, u8 onoff) +{ + deb_info("%s: onoff:%d\n", __func__, onoff); + + return af9013_write_reg_bits(state, 0xd730, 0, 1, onoff); +} + +static int af9013_set_frontend(struct dvb_frontend *fe, + struct dvb_frontend_parameters *params) +{ + struct af9013_state *state = fe->demodulator_priv; + int ret; + u8 auto_mode; /* auto set TPS */ + + deb_info("%s: freq:%d bw:%d\n", __func__, params->frequency, + params->u.ofdm.bandwidth); + + state->frequency = params->frequency; + + /* program CFOE coefficients */ + ret = af9013_set_coeff(state, params->u.ofdm.bandwidth); + if (ret) + goto error; + + /* program frequency control */ + ret = af9013_set_freq_ctrl(state, params->u.ofdm.bandwidth); + if (ret) + goto error; + + /* clear TPS lock flag (inverted flag) */ + ret = af9013_write_reg_bits(state, 0xd330, 3, 1, 1); + if (ret) + goto error; + + /* clear MPEG2 lock flag */ + ret = af9013_write_reg_bits(state, 0xd507, 6, 1, 0); + if (ret) + goto error; + + /* empty channel function */ + ret = af9013_write_reg_bits(state, 0x9bfe, 0, 1, 0); + if (ret) + goto error; + + /* empty DVB-T channel function */ + ret = af9013_write_reg_bits(state, 0x9bc2, 0, 1, 0); + if (ret) + goto error; + + /* program tuner */ + if (fe->ops.tuner_ops.set_params) + fe->ops.tuner_ops.set_params(fe, params); + + /* program TPS and bandwidth, check if auto mode needed */ + ret = af9013_set_ofdm_params(state, ¶ms->u.ofdm, &auto_mode); + if (ret) + goto error; + + if (auto_mode) { + /* clear easy mode flag */ + ret = af9013_write_reg(state, 0xaefd, 0); + deb_info("%s: auto TPS\n", __func__); + } else { + /* set easy mode flag */ + ret = af9013_write_reg(state, 0xaefd, 1); + if (ret) + goto error; + ret = af9013_write_reg(state, 0xaefe, 0); + deb_info("%s: manual TPS\n", __func__); + } + if (ret) + goto error; + + /* everything is set, lets try to receive channel - OFSM GO! */ + ret = af9013_write_reg(state, 0xffff, 0); + if (ret) + goto error; + +error: + return ret; +} + +static int af9013_get_frontend(struct dvb_frontend *fe, + struct dvb_frontend_parameters *p) +{ + struct af9013_state *state = fe->demodulator_priv; + int ret; + u8 i, buf[3]; + deb_info("%s\n", __func__); + + /* read TPS registers */ + for (i = 0; i < 3; i++) { + ret = af9013_read_reg(state, 0xd3c0 + i, &buf[i]); + if (ret) + goto error; + } + + switch ((buf[1] >> 6) & 3) { + case 0: + p->u.ofdm.constellation = QPSK; + break; + case 1: + p->u.ofdm.constellation = QAM_16; + break; + case 2: + p->u.ofdm.constellation = QAM_64; + break; + } + + switch ((buf[0] >> 0) & 3) { + case 0: + p->u.ofdm.transmission_mode = TRANSMISSION_MODE_2K; + break; + case 1: + p->u.ofdm.transmission_mode = TRANSMISSION_MODE_8K; + } + + switch ((buf[0] >> 2) & 3) { + case 0: + p->u.ofdm.guard_interval = GUARD_INTERVAL_1_32; + break; + case 1: + p->u.ofdm.guard_interval = GUARD_INTERVAL_1_16; + break; + case 2: + p->u.ofdm.guard_interval = GUARD_INTERVAL_1_8; + break; + case 3: + p->u.ofdm.guard_interval = GUARD_INTERVAL_1_4; + break; + } + + switch ((buf[0] >> 4) & 7) { + case 0: + p->u.ofdm.hierarchy_information = HIERARCHY_NONE; + break; + case 1: + p->u.ofdm.hierarchy_information = HIERARCHY_1; + break; + case 2: + p->u.ofdm.hierarchy_information = HIERARCHY_2; + break; + case 3: + p->u.ofdm.hierarchy_information = HIERARCHY_4; + break; + } + + switch ((buf[2] >> 0) & 7) { + case 0: + p->u.ofdm.code_rate_HP = FEC_1_2; + break; + case 1: + p->u.ofdm.code_rate_HP = FEC_2_3; + break; + case 2: + p->u.ofdm.code_rate_HP = FEC_3_4; + break; + case 3: + p->u.ofdm.code_rate_HP = FEC_5_6; + break; + case 4: + p->u.ofdm.code_rate_HP = FEC_7_8; + break; + } + + switch ((buf[2] >> 3) & 7) { + case 0: + p->u.ofdm.code_rate_LP = FEC_1_2; + break; + case 1: + p->u.ofdm.code_rate_LP = FEC_2_3; + break; + case 2: + p->u.ofdm.code_rate_LP = FEC_3_4; + break; + case 3: + p->u.ofdm.code_rate_LP = FEC_5_6; + break; + case 4: + p->u.ofdm.code_rate_LP = FEC_7_8; + break; + } + + switch ((buf[1] >> 2) & 3) { + case 0: + p->u.ofdm.bandwidth = BANDWIDTH_6_MHZ; + break; + case 1: + p->u.ofdm.bandwidth = BANDWIDTH_7_MHZ; + break; + case 2: + p->u.ofdm.bandwidth = BANDWIDTH_8_MHZ; + break; + } + + p->inversion = INVERSION_AUTO; + p->frequency = state->frequency; + +error: + return ret; +} + +static int af9013_update_ber_unc(struct dvb_frontend *fe) +{ + struct af9013_state *state = fe->demodulator_priv; + int ret; + u8 buf[3], i; + u32 error_bit_count = 0; + u32 total_bit_count = 0; + u32 abort_packet_count = 0; + + state->ber = 0; + + /* check if error bit count is ready */ + ret = af9013_read_reg_bits(state, 0xd391, 4, 1, &buf[0]); + if (ret) + goto error; + if (!buf[0]) + goto exit; + + /* get RSD packet abort count */ + for (i = 0; i < 2; i++) { + ret = af9013_read_reg(state, 0xd38a + i, &buf[i]); + if (ret) + goto error; + } + abort_packet_count = (buf[1] << 8) + buf[0]; + + /* get error bit count */ + for (i = 0; i < 3; i++) { + ret = af9013_read_reg(state, 0xd387 + i, &buf[i]); + if (ret) + goto error; + } + error_bit_count = (buf[2] << 16) + (buf[1] << 8) + buf[0]; + error_bit_count = error_bit_count - abort_packet_count * 8 * 8; + + /* get used RSD counting period (10000 RSD packets used) */ + for (i = 0; i < 2; i++) { + ret = af9013_read_reg(state, 0xd385 + i, &buf[i]); + if (ret) + goto error; + } + total_bit_count = (buf[1] << 8) + buf[0]; + total_bit_count = total_bit_count - abort_packet_count; + total_bit_count = total_bit_count * 204 * 8; + + if (total_bit_count) + state->ber = error_bit_count * 1000000000 / total_bit_count; + + state->ucblocks += abort_packet_count; + + deb_info("%s: err bits:%d total bits:%d abort count:%d\n", __func__, + error_bit_count, total_bit_count, abort_packet_count); + + /* set BER counting range */ + ret = af9013_write_reg(state, 0xd385, 10000 & 0xff); + if (ret) + goto error; + ret = af9013_write_reg(state, 0xd386, 10000 >> 8); + if (ret) + goto error; + /* reset and start BER counter */ + ret = af9013_write_reg_bits(state, 0xd391, 4, 1, 1); + if (ret) + goto error; + +exit: +error: + return ret; +} + +static int af9013_update_snr(struct dvb_frontend *fe) +{ + struct af9013_state *state = fe->demodulator_priv; + int ret; + u8 buf[3], i, len; + u32 quant = 0; + struct snr_table *snr_table; + + /* check if quantizer ready (for snr) */ + ret = af9013_read_reg_bits(state, 0xd2e1, 3, 1, &buf[0]); + if (ret) + goto error; + if (buf[0]) { + /* quantizer ready - read it */ + for (i = 0; i < 3; i++) { + ret = af9013_read_reg(state, 0xd2e3 + i, &buf[i]); + if (ret) + goto error; + } + quant = (buf[2] << 16) + (buf[1] << 8) + buf[0]; + + /* read current constellation */ + ret = af9013_read_reg(state, 0xd3c1, &buf[0]); + if (ret) + goto error; + + switch ((buf[0] >> 6) & 3) { + case 0: + len = ARRAY_SIZE(qpsk_snr_table); + snr_table = qpsk_snr_table; + break; + case 1: + len = ARRAY_SIZE(qam16_snr_table); + snr_table = qam16_snr_table; + break; + case 2: + len = ARRAY_SIZE(qam64_snr_table); + snr_table = qam64_snr_table; + break; + default: + len = 0; + break; + } + + if (len) { + for (i = 0; i < len; i++) { + if (quant < snr_table[i].val) { + state->snr = snr_table[i].snr * 10; + break; + } + } + } + + /* set quantizer super frame count */ + ret = af9013_write_reg(state, 0xd2e2, 1); + if (ret) + goto error; + + /* check quantizer availability */ + for (i = 0; i < 10; i++) { + msleep(10); + ret = af9013_read_reg_bits(state, 0xd2e6, 0, 1, + &buf[0]); + if (ret) + goto error; + if (!buf[0]) + break; + } + + /* reset quantizer */ + ret = af9013_write_reg_bits(state, 0xd2e1, 3, 1, 1); + if (ret) + goto error; + } + +error: + return ret; +} + +static int af9013_update_signal_strength(struct dvb_frontend *fe) +{ + struct af9013_state *state = fe->demodulator_priv; + int ret; + u8 tmp0; + u8 rf_gain, rf_50, rf_80, if_gain, if_50, if_80; + int signal_strength; + + deb_info("%s\n", __func__); + + state->signal_strength = 0; + + ret = af9013_read_reg_bits(state, 0x9bee, 0, 1, &tmp0); + if (ret) + goto error; + if (tmp0) { + ret = af9013_read_reg(state, 0x9bbd, &rf_50); + if (ret) + goto error; + ret = af9013_read_reg(state, 0x9bd0, &rf_80); + if (ret) + goto error; + ret = af9013_read_reg(state, 0x9be2, &if_50); + if (ret) + goto error; + ret = af9013_read_reg(state, 0x9be4, &if_80); + if (ret) + goto error; + ret = af9013_read_reg(state, 0xd07c, &rf_gain); + if (ret) + goto error; + ret = af9013_read_reg(state, 0xd07d, &if_gain); + if (ret) + goto error; + signal_strength = (0xffff / (9 * (rf_50 + if_50) - \ + 11 * (rf_80 + if_80))) * (10 * (rf_gain + if_gain) - \ + 11 * (rf_80 + if_80)); + if (signal_strength < 0) + signal_strength = 0; + else if (signal_strength > 0xffff) + signal_strength = 0xffff; + + state->signal_strength = signal_strength; + } + +error: + return ret; +} + +static int af9013_update_statistics(struct dvb_frontend *fe) +{ + struct af9013_state *state = fe->demodulator_priv; + int ret; + + if (time_before(jiffies, state->next_statistics_check)) + return 0; + + /* set minimum statistic update interval */ + state->next_statistics_check = jiffies + msecs_to_jiffies(1200); + + ret = af9013_update_signal_strength(fe); + if (ret) + goto error; + ret = af9013_update_snr(fe); + if (ret) + goto error; + ret = af9013_update_ber_unc(fe); + if (ret) + goto error; + +error: + return ret; +} + +static int af9013_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *fesettings) +{ + fesettings->min_delay_ms = 800; + fesettings->step_size = 0; + fesettings->max_drift = 0; + + return 0; +} + +static int af9013_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct af9013_state *state = fe->demodulator_priv; + int ret = 0; + u8 tmp; + *status = 0; + + /* TPS lock */ + ret = af9013_read_reg_bits(state, 0xd330, 3, 1, &tmp); + if (ret) + goto error; + if (tmp) + *status |= FE_HAS_VITERBI | FE_HAS_CARRIER | FE_HAS_SIGNAL; + + /* MPEG2 lock */ + ret = af9013_read_reg_bits(state, 0xd507, 6, 1, &tmp); + if (ret) + goto error; + if (tmp) + *status |= FE_HAS_SYNC | FE_HAS_LOCK; + + if (!*status & FE_HAS_SIGNAL) { + /* AGC lock */ + ret = af9013_read_reg_bits(state, 0xd1a0, 6, 1, &tmp); + if (ret) + goto error; + if (tmp) + *status |= FE_HAS_SIGNAL; + } + + if (!*status & FE_HAS_CARRIER) { + /* CFO lock */ + ret = af9013_read_reg_bits(state, 0xd333, 7, 1, &tmp); + if (ret) + goto error; + if (tmp) + *status |= FE_HAS_CARRIER; + } + + if (!*status & FE_HAS_CARRIER) { + /* SFOE lock */ + ret = af9013_read_reg_bits(state, 0xd334, 6, 1, &tmp); + if (ret) + goto error; + if (tmp) + *status |= FE_HAS_CARRIER; + } + + ret = af9013_update_statistics(fe); + +error: + return ret; +} + + +static int af9013_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct af9013_state *state = fe->demodulator_priv; + int ret; + ret = af9013_update_statistics(fe); + *ber = state->ber; + return ret; +} + +static int af9013_read_signal_strength(struct dvb_frontend *fe, u16 *strength) +{ + struct af9013_state *state = fe->demodulator_priv; + int ret; + ret = af9013_update_statistics(fe); + *strength = state->signal_strength; + return ret; +} + +static int af9013_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct af9013_state *state = fe->demodulator_priv; + int ret; + ret = af9013_update_statistics(fe); + *snr = state->snr; + return ret; +} + +static int af9013_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + struct af9013_state *state = fe->demodulator_priv; + int ret; + ret = af9013_update_statistics(fe); + *ucblocks = state->ucblocks; + return ret; +} + +static int af9013_sleep(struct dvb_frontend *fe) +{ + struct af9013_state *state = fe->demodulator_priv; + int ret; + deb_info("%s\n", __func__); + + ret = af9013_lock_led(state, 0); + if (ret) + goto error; + + ret = af9013_power_ctrl(state, 0); +error: + return ret; +} + +static int af9013_init(struct dvb_frontend *fe) +{ + struct af9013_state *state = fe->demodulator_priv; + int ret, i, len; + u8 tmp0, tmp1; + struct regdesc *init; + deb_info("%s\n", __func__); + + /* reset OFDM */ + ret = af9013_reset(state, 0); + if (ret) + goto error; + + /* power on */ + ret = af9013_power_ctrl(state, 1); + if (ret) + goto error; + + /* enable ADC */ + ret = af9013_write_reg(state, 0xd73a, 0xa4); + if (ret) + goto error; + + /* write API version to firmware */ + for (i = 0; i < sizeof(state->config.api_version); i++) { + ret = af9013_write_reg(state, 0x9bf2 + i, + state->config.api_version[i]); + if (ret) + goto error; + } + + /* program ADC control */ + ret = af9013_set_adc_ctrl(state); + if (ret) + goto error; + + /* set I2C master clock */ + ret = af9013_write_reg(state, 0xd416, 0x14); + if (ret) + goto error; + + /* set 16 embx */ + ret = af9013_write_reg_bits(state, 0xd700, 1, 1, 1); + if (ret) + goto error; + + /* set no trigger */ + ret = af9013_write_reg_bits(state, 0xd700, 2, 1, 0); + if (ret) + goto error; + + /* set read-update bit for constellation */ + ret = af9013_write_reg_bits(state, 0xd371, 1, 1, 1); + if (ret) + goto error; + + /* enable FEC monitor */ + ret = af9013_write_reg_bits(state, 0xd392, 1, 1, 1); + if (ret) + goto error; + + /* load OFSM settings */ + deb_info("%s: load ofsm settings\n", __func__); + len = ARRAY_SIZE(ofsm_init); + init = ofsm_init; + for (i = 0; i < len; i++) { + ret = af9013_write_reg_bits(state, init[i].addr, init[i].pos, + init[i].len, init[i].val); + if (ret) + goto error; + } + + /* load tuner specific settings */ + deb_info("%s: load tuner specific settings\n", __func__); + switch (state->config.tuner) { + case AF9013_TUNER_MXL5003D: + len = ARRAY_SIZE(tuner_init_mxl5003d); + init = tuner_init_mxl5003d; + break; + case AF9013_TUNER_MXL5005D: + case AF9013_TUNER_MXL5005R: + len = ARRAY_SIZE(tuner_init_mxl5005); + init = tuner_init_mxl5005; + break; + case AF9013_TUNER_ENV77H11D5: + len = ARRAY_SIZE(tuner_init_env77h11d5); + init = tuner_init_env77h11d5; + break; + case AF9013_TUNER_MT2060: + len = ARRAY_SIZE(tuner_init_mt2060); + init = tuner_init_mt2060; + break; + case AF9013_TUNER_MC44S803: + len = ARRAY_SIZE(tuner_init_mc44s803); + init = tuner_init_mc44s803; + break; + case AF9013_TUNER_QT1010: + case AF9013_TUNER_QT1010A: + len = ARRAY_SIZE(tuner_init_qt1010); + init = tuner_init_qt1010; + break; + case AF9013_TUNER_MT2060_2: + len = ARRAY_SIZE(tuner_init_mt2060_2); + init = tuner_init_mt2060_2; + break; + case AF9013_TUNER_TDA18271: + len = ARRAY_SIZE(tuner_init_tda18271); + init = tuner_init_tda18271; + break; + case AF9013_TUNER_UNKNOWN: + default: + len = ARRAY_SIZE(tuner_init_unknown); + init = tuner_init_unknown; + break; + } + + for (i = 0; i < len; i++) { + ret = af9013_write_reg_bits(state, init[i].addr, init[i].pos, + init[i].len, init[i].val); + if (ret) + goto error; + } + + /* set TS mode */ + deb_info("%s: setting ts mode\n", __func__); + tmp0 = 0; /* parallel mode */ + tmp1 = 0; /* serial mode */ + switch (state->config.output_mode) { + case AF9013_OUTPUT_MODE_PARALLEL: + tmp0 = 1; + break; + case AF9013_OUTPUT_MODE_SERIAL: + tmp1 = 1; + break; + case AF9013_OUTPUT_MODE_USB: + /* usb mode for AF9015 */ + default: + break; + } + ret = af9013_write_reg_bits(state, 0xd500, 1, 1, tmp0); /* parallel */ + if (ret) + goto error; + ret = af9013_write_reg_bits(state, 0xd500, 2, 1, tmp1); /* serial */ + if (ret) + goto error; + + /* enable lock led */ + ret = af9013_lock_led(state, 1); + if (ret) + goto error; + +error: + return ret; +} + +static struct dvb_frontend_ops af9013_ops; + +static int af9013_download_firmware(struct af9013_state *state) +{ + int i, len, packets, remainder, ret; + const struct firmware *fw; + u16 addr = 0x5100; /* firmware start address */ + u16 checksum = 0; + u8 val; + u8 fw_params[4]; + u8 *data; + u8 *fw_file = AF9013_DEFAULT_FIRMWARE; + + msleep(100); + /* check whether firmware is already running */ + ret = af9013_read_reg(state, 0x98be, &val); + if (ret) + goto error; + else + deb_info("%s: firmware status:%02x\n", __func__, val); + + if (val == 0x0c) /* fw is running, no need for download */ + goto exit; + + info("found a '%s' in cold state, will try to load a firmware", + af9013_ops.info.name); + + /* request the firmware, this will block and timeout */ + ret = request_firmware(&fw, fw_file, &state->i2c->dev); + if (ret) { + err("did not find the firmware file. (%s) " + "Please see linux/Documentation/dvb/ for more details" \ + " on firmware-problems. (%d)", + fw_file, ret); + goto error; + } + + info("downloading firmware from file '%s'", fw_file); + + /* calc checksum */ + for (i = 0; i < fw->size; i++) + checksum += fw->data[i]; + + fw_params[0] = checksum >> 8; + fw_params[1] = checksum & 0xff; + fw_params[2] = fw->size >> 8; + fw_params[3] = fw->size & 0xff; + + /* write fw checksum & size */ + ret = af9013_write_ofsm_regs(state, 0x50fc, + fw_params, sizeof(fw_params)); + if (ret) + goto error_release; + + #define FW_PACKET_MAX_DATA 16 + + packets = fw->size / FW_PACKET_MAX_DATA; + remainder = fw->size % FW_PACKET_MAX_DATA; + len = FW_PACKET_MAX_DATA; + for (i = 0; i <= packets; i++) { + if (i == packets) /* set size of the last packet */ + len = remainder; + + data = (u8 *)(fw->data + i * FW_PACKET_MAX_DATA); + ret = af9013_write_ofsm_regs(state, addr, data, len); + addr += FW_PACKET_MAX_DATA; + + if (ret) { + err("firmware download failed at %d with %d", i, ret); + goto error_release; + } + } + + /* request boot firmware */ + ret = af9013_write_reg(state, 0xe205, 1); + if (ret) + goto error_release; + + for (i = 0; i < 15; i++) { + msleep(100); + + /* check firmware status */ + ret = af9013_read_reg(state, 0x98be, &val); + if (ret) + goto error_release; + + deb_info("%s: firmware status:%02x\n", __func__, val); + + if (val == 0x0c || val == 0x04) /* success or fail */ + break; + } + + if (val == 0x04) { + err("firmware did not run"); + ret = -1; + } else if (val != 0x0c) { + err("firmware boot timeout"); + ret = -1; + } + +error_release: + release_firmware(fw); +error: +exit: + if (!ret) + info("found a '%s' in warm state.", af9013_ops.info.name); + return ret; +} + +static int af9013_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + int ret; + struct af9013_state *state = fe->demodulator_priv; + deb_info("%s: enable:%d\n", __func__, enable); + + if (state->config.output_mode == AF9013_OUTPUT_MODE_USB) + ret = af9013_write_reg_bits(state, 0xd417, 3, 1, enable); + else + ret = af9013_write_reg_bits(state, 0xd607, 2, 1, enable); + + return ret; +} + +static void af9013_release(struct dvb_frontend *fe) +{ + struct af9013_state *state = fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops af9013_ops; + +struct dvb_frontend *af9013_attach(const struct af9013_config *config, + struct i2c_adapter *i2c) +{ + int ret; + struct af9013_state *state = NULL; + u8 buf[3], i; + + /* allocate memory for the internal state */ + state = kzalloc(sizeof(struct af9013_state), GFP_KERNEL); + if (state == NULL) + goto error; + + /* setup the state */ + state->i2c = i2c; + memcpy(&state->config, config, sizeof(struct af9013_config)); + + /* chip version */ + ret = af9013_read_reg_bits(state, 0xd733, 4, 4, &buf[2]); + if (ret) + goto error; + + /* ROM version */ + for (i = 0; i < 2; i++) { + ret = af9013_read_reg(state, 0x116b + i, &buf[i]); + if (ret) + goto error; + } + deb_info("%s: chip version:%d ROM version:%d.%d\n", __func__, + buf[2], buf[0], buf[1]); + + /* download firmware */ + if (state->config.output_mode != AF9013_OUTPUT_MODE_USB) { + ret = af9013_download_firmware(state); + if (ret) + goto error; + } + + /* firmware version */ + for (i = 0; i < 3; i++) { + ret = af9013_read_reg(state, 0x5103 + i, &buf[i]); + if (ret) + goto error; + } + info("firmware version:%d.%d.%d", buf[0], buf[1], buf[2]); + + /* settings for mp2if */ + if (state->config.output_mode == AF9013_OUTPUT_MODE_USB) { + /* AF9015 split PSB to 1.5k + 0.5k */ + ret = af9013_write_reg_bits(state, 0xd50b, 2, 1, 1); + } else { + /* AF9013 change the output bit to data7 */ + ret = af9013_write_reg_bits(state, 0xd500, 3, 1, 1); + if (ret) + goto error; + /* AF9013 set mpeg to full speed */ + ret = af9013_write_reg_bits(state, 0xd502, 4, 1, 1); + } + if (ret) + goto error; + ret = af9013_write_reg_bits(state, 0xd520, 4, 1, 1); + if (ret) + goto error; + + /* set GPIOs */ + for (i = 0; i < sizeof(state->config.gpio); i++) { + ret = af9013_set_gpio(state, i, state->config.gpio[i]); + if (ret) + goto error; + } + + /* create dvb_frontend */ + memcpy(&state->frontend.ops, &af9013_ops, + sizeof(struct dvb_frontend_ops)); + state->frontend.demodulator_priv = state; + + return &state->frontend; +error: + kfree(state); + return NULL; +} +EXPORT_SYMBOL(af9013_attach); + +static struct dvb_frontend_ops af9013_ops = { + .info = { + .name = "Afatech AF9013 DVB-T", + .type = FE_OFDM, + .frequency_min = 174000000, + .frequency_max = 862000000, + .frequency_stepsize = 250000, + .frequency_tolerance = 0, + .caps = + FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_QAM_16 | + FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_HIERARCHY_AUTO | + FE_CAN_RECOVER | + FE_CAN_MUTE_TS + }, + + .release = af9013_release, + .init = af9013_init, + .sleep = af9013_sleep, + .i2c_gate_ctrl = af9013_i2c_gate_ctrl, + + .set_frontend = af9013_set_frontend, + .get_frontend = af9013_get_frontend, + + .get_tune_settings = af9013_get_tune_settings, + + .read_status = af9013_read_status, + .read_ber = af9013_read_ber, + .read_signal_strength = af9013_read_signal_strength, + .read_snr = af9013_read_snr, + .read_ucblocks = af9013_read_ucblocks, +}; + +module_param_named(debug, af9013_debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); +MODULE_DESCRIPTION("Afatech AF9013 DVB-T demodulator driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/af9013.h b/drivers/media/dvb/frontends/af9013.h new file mode 100644 index 000000000000..28b90c91c766 --- /dev/null +++ b/drivers/media/dvb/frontends/af9013.h @@ -0,0 +1,107 @@ +/* + * DVB USB Linux driver for Afatech AF9015 DVB-T USB2.0 receiver + * + * Copyright (C) 2007 Antti Palosaari <crope@iki.fi> + * + * Thanks to Afatech who kindly provided information. + * + * 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 _AF9013_H_ +#define _AF9013_H_ + +#include <linux/dvb/frontend.h> + +enum af9013_ts_mode { + AF9013_OUTPUT_MODE_PARALLEL, + AF9013_OUTPUT_MODE_SERIAL, + AF9013_OUTPUT_MODE_USB, /* only for AF9015 */ +}; + +enum af9013_tuner { + AF9013_TUNER_MXL5003D = 3, /* MaxLinear */ + AF9013_TUNER_MXL5005D = 13, /* MaxLinear */ + AF9013_TUNER_MXL5005R = 30, /* MaxLinear */ + AF9013_TUNER_ENV77H11D5 = 129, /* Panasonic */ + AF9013_TUNER_MT2060 = 130, /* Microtune */ + AF9013_TUNER_MC44S803 = 133, /* Freescale */ + AF9013_TUNER_QT1010 = 134, /* Quantek */ + AF9013_TUNER_UNKNOWN = 140, /* for can tuners ? */ + AF9013_TUNER_MT2060_2 = 147, /* Microtune */ + AF9013_TUNER_TDA18271 = 156, /* NXP */ + AF9013_TUNER_QT1010A = 162, /* Quantek */ +}; + +/* AF9013/5 GPIOs (mostly guessed) + demod#1-gpio#0 - set demod#2 i2c-addr for dual devices + demod#1-gpio#1 - xtal setting (?) + demod#1-gpio#3 - tuner#1 + demod#2-gpio#0 - tuner#2 + demod#2-gpio#1 - xtal setting (?) +*/ +#define AF9013_GPIO_ON (1 << 0) +#define AF9013_GPIO_EN (1 << 1) +#define AF9013_GPIO_O (1 << 2) +#define AF9013_GPIO_I (1 << 3) + +#define AF9013_GPIO_LO (AF9013_GPIO_ON|AF9013_GPIO_EN) +#define AF9013_GPIO_HI (AF9013_GPIO_ON|AF9013_GPIO_EN|AF9013_GPIO_O) + +#define AF9013_GPIO_TUNER_ON (AF9013_GPIO_ON|AF9013_GPIO_EN) +#define AF9013_GPIO_TUNER_OFF (AF9013_GPIO_ON|AF9013_GPIO_EN|AF9013_GPIO_O) + +struct af9013_config { + /* demodulator's I2C address */ + u8 demod_address; + + /* frequencies in kHz */ + u32 adc_clock; + + /* tuner ID */ + u8 tuner; + + /* tuner IF */ + u16 tuner_if; + + /* TS data output mode */ + u8 output_mode:2; + + /* RF spectrum inversion */ + u8 rf_spec_inv:1; + + /* API version */ + u8 api_version[4]; + + /* GPIOs */ + u8 gpio[4]; +}; + + +#if defined(CONFIG_DVB_AF9013) || \ + (defined(CONFIG_DVB_AF9013_MODULE) && defined(MODULE)) +extern struct dvb_frontend *af9013_attach(const struct af9013_config *config, + struct i2c_adapter *i2c); +#else +static inline struct dvb_frontend *af9013_attach( +const struct af9013_config *config, struct i2c_adapter *i2c) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif /* CONFIG_DVB_AF9013 */ + +#endif /* _AF9013_H_ */ diff --git a/drivers/media/dvb/frontends/af9013_priv.h b/drivers/media/dvb/frontends/af9013_priv.h new file mode 100644 index 000000000000..163e251d0b73 --- /dev/null +++ b/drivers/media/dvb/frontends/af9013_priv.h @@ -0,0 +1,869 @@ +/* + * DVB USB Linux driver for Afatech AF9015 DVB-T USB2.0 receiver + * + * Copyright (C) 2007 Antti Palosaari <crope@iki.fi> + * + * Thanks to Afatech who kindly provided information. + * + * 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 _AF9013_PRIV_ +#define _AF9013_PRIV_ + +#define LOG_PREFIX "af9013" +extern int af9013_debug; + +#define dprintk(var, level, args...) \ + do { if ((var & level)) printk(args); } while (0) + +#define debug_dump(b, l, func) {\ + int loop_; \ + for (loop_ = 0; loop_ < l; loop_++) \ + func("%02x ", b[loop_]); \ + func("\n");\ +} + +#define deb_info(args...) dprintk(af9013_debug, 0x01, args) + +#undef err +#define err(f, arg...) printk(KERN_ERR LOG_PREFIX": " f "\n" , ## arg) +#undef info +#define info(f, arg...) printk(KERN_INFO LOG_PREFIX": " f "\n" , ## arg) +#undef warn +#define warn(f, arg...) printk(KERN_WARNING LOG_PREFIX": " f "\n" , ## arg) + +#define AF9013_DEFAULT_FIRMWARE "dvb-fe-af9013.fw" + +struct regdesc { + u16 addr; + u8 pos:4; + u8 len:4; + u8 val; +}; + +struct snr_table { + u32 val; + u8 snr; +}; + +/* QPSK SNR lookup table */ +static struct snr_table qpsk_snr_table[] = { + { 0x0b4771, 0 }, + { 0x0c1aed, 1 }, + { 0x0d0d27, 2 }, + { 0x0e4d19, 3 }, + { 0x0e5da8, 4 }, + { 0x107097, 5 }, + { 0x116975, 6 }, + { 0x1252d9, 7 }, + { 0x131fa4, 8 }, + { 0x13d5e1, 9 }, + { 0x148e53, 10 }, + { 0x15358b, 11 }, + { 0x15dd29, 12 }, + { 0x168112, 13 }, + { 0x170b61, 14 }, + { 0xffffff, 15 }, +}; + +/* QAM16 SNR lookup table */ +static struct snr_table qam16_snr_table[] = { + { 0x05eb62, 5 }, + { 0x05fecf, 6 }, + { 0x060b80, 7 }, + { 0x062501, 8 }, + { 0x064865, 9 }, + { 0x069604, 10 }, + { 0x06f356, 11 }, + { 0x07706a, 12 }, + { 0x0804d3, 13 }, + { 0x089d1a, 14 }, + { 0x093e3d, 15 }, + { 0x09e35d, 16 }, + { 0x0a7c3c, 17 }, + { 0x0afaf8, 18 }, + { 0x0b719d, 19 }, + { 0xffffff, 20 }, +}; + +/* QAM64 SNR lookup table */ +static struct snr_table qam64_snr_table[] = { + { 0x03109b, 12 }, + { 0x0310d4, 13 }, + { 0x031920, 14 }, + { 0x0322d0, 15 }, + { 0x0339fc, 16 }, + { 0x0364a1, 17 }, + { 0x038bcc, 18 }, + { 0x03c7d3, 19 }, + { 0x0408cc, 20 }, + { 0x043bed, 21 }, + { 0x048061, 22 }, + { 0x04be95, 23 }, + { 0x04fa7d, 24 }, + { 0x052405, 25 }, + { 0x05570d, 26 }, + { 0xffffff, 27 }, +}; + +static struct regdesc ofsm_init[] = { + { 0xd73a, 0, 8, 0xa1 }, + { 0xd73b, 0, 8, 0x1f }, + { 0xd73c, 4, 4, 0x0a }, + { 0xd732, 3, 1, 0x00 }, + { 0xd731, 4, 2, 0x03 }, + { 0xd73d, 7, 1, 0x01 }, + { 0xd740, 0, 1, 0x00 }, + { 0xd740, 1, 1, 0x00 }, + { 0xd740, 2, 1, 0x00 }, + { 0xd740, 3, 1, 0x01 }, + { 0xd3c1, 4, 1, 0x01 }, + { 0xd3a2, 0, 8, 0x00 }, + { 0xd3a3, 0, 8, 0x04 }, + { 0xd305, 0, 8, 0x32 }, + { 0xd306, 0, 8, 0x10 }, + { 0xd304, 0, 8, 0x04 }, + { 0x9112, 0, 1, 0x01 }, + { 0x911d, 0, 1, 0x01 }, + { 0x911a, 0, 1, 0x01 }, + { 0x911b, 0, 1, 0x01 }, + { 0x9bce, 0, 4, 0x02 }, + { 0x9116, 0, 1, 0x01 }, + { 0x9bd1, 0, 1, 0x01 }, + { 0xd2e0, 0, 8, 0xd0 }, + { 0xd2e9, 0, 4, 0x0d }, + { 0xd38c, 0, 8, 0xfc }, + { 0xd38d, 0, 8, 0x00 }, + { 0xd38e, 0, 8, 0x7e }, + { 0xd38f, 0, 8, 0x00 }, + { 0xd390, 0, 8, 0x2f }, + { 0xd145, 4, 1, 0x01 }, + { 0xd1a9, 4, 1, 0x01 }, + { 0xd158, 5, 3, 0x01 }, + { 0xd159, 0, 6, 0x06 }, + { 0xd167, 0, 8, 0x00 }, + { 0xd168, 0, 4, 0x07 }, + { 0xd1c3, 5, 3, 0x00 }, + { 0xd1c4, 0, 6, 0x00 }, + { 0xd1c5, 0, 7, 0x10 }, + { 0xd1c6, 0, 3, 0x02 }, + { 0xd080, 2, 5, 0x03 }, + { 0xd081, 4, 4, 0x09 }, + { 0xd098, 4, 4, 0x0f }, + { 0xd098, 0, 4, 0x03 }, + { 0xdbc0, 3, 1, 0x01 }, + { 0xdbc0, 4, 1, 0x01 }, + { 0xdbc7, 0, 8, 0x08 }, + { 0xdbc8, 4, 4, 0x00 }, + { 0xdbc9, 0, 5, 0x01 }, + { 0xd280, 0, 8, 0xe0 }, + { 0xd281, 0, 8, 0xff }, + { 0xd282, 0, 8, 0xff }, + { 0xd283, 0, 8, 0xc3 }, + { 0xd284, 0, 8, 0xff }, + { 0xd285, 0, 4, 0x01 }, + { 0xd0f0, 0, 7, 0x1a }, + { 0xd0f1, 4, 1, 0x01 }, + { 0xd0f2, 0, 8, 0x0c }, + { 0xd103, 0, 4, 0x08 }, + { 0xd0f8, 0, 7, 0x20 }, + { 0xd111, 5, 1, 0x00 }, + { 0xd111, 6, 1, 0x00 }, + { 0x910b, 0, 8, 0x0a }, + { 0x9115, 0, 8, 0x02 }, + { 0x910c, 0, 8, 0x02 }, + { 0x910d, 0, 8, 0x08 }, + { 0x910e, 0, 8, 0x0a }, + { 0x9bf6, 0, 8, 0x06 }, + { 0x9bf8, 0, 8, 0x02 }, + { 0x9bf7, 0, 8, 0x05 }, + { 0x9bf9, 0, 8, 0x0f }, + { 0x9bfc, 0, 8, 0x13 }, + { 0x9bd3, 0, 8, 0xff }, + { 0x9bbe, 0, 1, 0x01 }, + { 0x9bcc, 0, 1, 0x01 }, +}; + +/* Panasonic ENV77H11D5 tuner init + AF9013_TUNER_ENV77H11D5 = 129 */ +static struct regdesc tuner_init_env77h11d5[] = { + { 0x9bd5, 0, 8, 0x01 }, + { 0x9bd6, 0, 8, 0x03 }, + { 0x9bbe, 0, 8, 0x01 }, + { 0xd1a0, 1, 1, 0x01 }, + { 0xd000, 0, 1, 0x01 }, + { 0xd000, 1, 1, 0x00 }, + { 0xd001, 1, 1, 0x01 }, + { 0xd001, 0, 1, 0x00 }, + { 0xd001, 5, 1, 0x00 }, + { 0xd002, 0, 5, 0x19 }, + { 0xd003, 0, 5, 0x1a }, + { 0xd004, 0, 5, 0x19 }, + { 0xd005, 0, 5, 0x1a }, + { 0xd00e, 0, 5, 0x10 }, + { 0xd00f, 0, 3, 0x04 }, + { 0xd00f, 3, 3, 0x05 }, + { 0xd010, 0, 3, 0x04 }, + { 0xd010, 3, 3, 0x05 }, + { 0xd016, 4, 4, 0x03 }, + { 0xd01f, 0, 6, 0x0a }, + { 0xd020, 0, 6, 0x0a }, + { 0x9bda, 0, 8, 0x00 }, + { 0x9be3, 0, 8, 0x00 }, + { 0xd015, 0, 8, 0x50 }, + { 0xd016, 0, 1, 0x00 }, + { 0xd044, 0, 8, 0x46 }, + { 0xd045, 0, 1, 0x00 }, + { 0xd008, 0, 8, 0xdf }, + { 0xd009, 0, 2, 0x02 }, + { 0xd006, 0, 8, 0x44 }, + { 0xd007, 0, 2, 0x01 }, + { 0xd00c, 0, 8, 0xeb }, + { 0xd00d, 0, 2, 0x02 }, + { 0xd00a, 0, 8, 0xf4 }, + { 0xd00b, 0, 2, 0x01 }, + { 0x9bba, 0, 8, 0xf9 }, + { 0x9bc3, 0, 8, 0xdf }, + { 0x9bc4, 0, 8, 0x02 }, + { 0x9bc5, 0, 8, 0xeb }, + { 0x9bc6, 0, 8, 0x02 }, + { 0x9bc9, 0, 8, 0x52 }, + { 0xd011, 0, 8, 0x3c }, + { 0xd012, 0, 2, 0x01 }, + { 0xd013, 0, 8, 0xf7 }, + { 0xd014, 0, 2, 0x02 }, + { 0xd040, 0, 8, 0x0b }, + { 0xd041, 0, 2, 0x02 }, + { 0xd042, 0, 8, 0x4d }, + { 0xd043, 0, 2, 0x00 }, + { 0xd045, 1, 1, 0x00 }, + { 0x9bcf, 0, 1, 0x01 }, + { 0xd045, 2, 1, 0x01 }, + { 0xd04f, 0, 8, 0x9a }, + { 0xd050, 0, 1, 0x01 }, + { 0xd051, 0, 8, 0x5a }, + { 0xd052, 0, 1, 0x01 }, + { 0xd053, 0, 8, 0x50 }, + { 0xd054, 0, 8, 0x46 }, + { 0x9bd7, 0, 8, 0x0a }, + { 0x9bd8, 0, 8, 0x14 }, + { 0x9bd9, 0, 8, 0x08 }, +}; + +/* Microtune MT2060 tuner init + AF9013_TUNER_MT2060 = 130 */ +static struct regdesc tuner_init_mt2060[] = { + { 0x9bd5, 0, 8, 0x01 }, + { 0x9bd6, 0, 8, 0x07 }, + { 0xd1a0, 1, 1, 0x01 }, + { 0xd000, 0, 1, 0x01 }, + { 0xd000, 1, 1, 0x00 }, + { 0xd001, 1, 1, 0x01 }, + { 0xd001, 0, 1, 0x00 }, + { 0xd001, 5, 1, 0x00 }, + { 0xd002, 0, 5, 0x19 }, + { 0xd003, 0, 5, 0x1a }, + { 0xd004, 0, 5, 0x19 }, + { 0xd005, 0, 5, 0x1a }, + { 0xd00e, 0, 5, 0x10 }, + { 0xd00f, 0, 3, 0x04 }, + { 0xd00f, 3, 3, 0x05 }, + { 0xd010, 0, 3, 0x04 }, + { 0xd010, 3, 3, 0x05 }, + { 0xd016, 4, 4, 0x03 }, + { 0xd01f, 0, 6, 0x0a }, + { 0xd020, 0, 6, 0x0a }, + { 0x9bda, 0, 8, 0x00 }, + { 0x9be3, 0, 8, 0x00 }, + { 0x9bbe, 0, 1, 0x00 }, + { 0x9bcc, 0, 1, 0x00 }, + { 0x9bb9, 0, 8, 0x75 }, + { 0x9bcd, 0, 8, 0x24 }, + { 0x9bff, 0, 8, 0x30 }, + { 0xd015, 0, 8, 0x46 }, + { 0xd016, 0, 1, 0x00 }, + { 0xd044, 0, 8, 0x46 }, + { 0xd045, 0, 1, 0x00 }, + { 0xd008, 0, 8, 0x0f }, + { 0xd009, 0, 2, 0x02 }, + { 0xd006, 0, 8, 0x32 }, + { 0xd007, 0, 2, 0x01 }, + { 0xd00c, 0, 8, 0x36 }, + { 0xd00d, 0, 2, 0x03 }, + { 0xd00a, 0, 8, 0x35 }, + { 0xd00b, 0, 2, 0x01 }, + { 0x9bc7, 0, 8, 0x07 }, + { 0x9bc8, 0, 8, 0x90 }, + { 0x9bc3, 0, 8, 0x0f }, + { 0x9bc4, 0, 8, 0x02 }, + { 0x9bc5, 0, 8, 0x36 }, + { 0x9bc6, 0, 8, 0x03 }, + { 0x9bba, 0, 8, 0xc9 }, + { 0x9bc9, 0, 8, 0x79 }, + { 0xd011, 0, 8, 0x10 }, + { 0xd012, 0, 2, 0x01 }, + { 0xd013, 0, 8, 0x45 }, + { 0xd014, 0, 2, 0x03 }, + { 0xd040, 0, 8, 0x98 }, + { 0xd041, 0, 2, 0x00 }, + { 0xd042, 0, 8, 0xcf }, + { 0xd043, 0, 2, 0x03 }, + { 0xd045, 1, 1, 0x00 }, + { 0x9bcf, 0, 1, 0x01 }, + { 0xd045, 2, 1, 0x01 }, + { 0xd04f, 0, 8, 0x9a }, + { 0xd050, 0, 1, 0x01 }, + { 0xd051, 0, 8, 0x5a }, + { 0xd052, 0, 1, 0x01 }, + { 0xd053, 0, 8, 0x50 }, + { 0xd054, 0, 8, 0x46 }, + { 0x9bd7, 0, 8, 0x0a }, + { 0x9bd8, 0, 8, 0x14 }, + { 0x9bd9, 0, 8, 0x08 }, + { 0x9bd0, 0, 8, 0xcc }, + { 0x9be4, 0, 8, 0xa0 }, + { 0x9bbd, 0, 8, 0x8e }, + { 0x9be2, 0, 8, 0x4d }, + { 0x9bee, 0, 1, 0x01 }, +}; + +/* Microtune MT2060 tuner init + AF9013_TUNER_MT2060_2 = 147 */ +static struct regdesc tuner_init_mt2060_2[] = { + { 0x9bd5, 0, 8, 0x01 }, + { 0x9bd6, 0, 8, 0x06 }, + { 0x9bbe, 0, 8, 0x01 }, + { 0xd1a0, 1, 1, 0x01 }, + { 0xd000, 0, 1, 0x01 }, + { 0xd000, 1, 1, 0x00 }, + { 0xd001, 1, 1, 0x01 }, + { 0xd001, 0, 1, 0x00 }, + { 0xd001, 5, 1, 0x00 }, + { 0xd002, 0, 5, 0x19 }, + { 0xd003, 0, 5, 0x1a }, + { 0xd004, 0, 5, 0x19 }, + { 0xd005, 0, 5, 0x1a }, + { 0xd00e, 0, 5, 0x10 }, + { 0xd00f, 0, 3, 0x04 }, + { 0xd00f, 3, 3, 0x05 }, + { 0xd010, 0, 3, 0x04 }, + { 0xd010, 3, 3, 0x05 }, + { 0xd016, 4, 4, 0x03 }, + { 0xd01f, 0, 6, 0x0a }, + { 0xd020, 0, 6, 0x0a }, + { 0xd015, 0, 8, 0x46 }, + { 0xd016, 0, 1, 0x00 }, + { 0xd044, 0, 8, 0x46 }, + { 0xd045, 0, 1, 0x00 }, + { 0xd008, 0, 8, 0x0f }, + { 0xd009, 0, 2, 0x02 }, + { 0xd006, 0, 8, 0x32 }, + { 0xd007, 0, 2, 0x01 }, + { 0xd00c, 0, 8, 0x36 }, + { 0xd00d, 0, 2, 0x03 }, + { 0xd00a, 0, 8, 0x35 }, + { 0xd00b, 0, 2, 0x01 }, + { 0x9bc7, 0, 8, 0x07 }, + { 0x9bc8, 0, 8, 0x90 }, + { 0x9bc3, 0, 8, 0x0f }, + { 0x9bc4, 0, 8, 0x02 }, + { 0x9bc5, 0, 8, 0x36 }, + { 0x9bc6, 0, 8, 0x03 }, + { 0x9bba, 0, 8, 0xc9 }, + { 0x9bc9, 0, 8, 0x79 }, + { 0xd011, 0, 8, 0x10 }, + { 0xd012, 0, 2, 0x01 }, + { 0xd013, 0, 8, 0x45 }, + { 0xd014, 0, 2, 0x03 }, + { 0xd040, 0, 8, 0x98 }, + { 0xd041, 0, 2, 0x00 }, + { 0xd042, 0, 8, 0xcf }, + { 0xd043, 0, 2, 0x03 }, + { 0xd045, 1, 1, 0x00 }, + { 0x9bcf, 0, 8, 0x01 }, + { 0xd045, 2, 1, 0x01 }, + { 0xd04f, 0, 8, 0x9a }, + { 0xd050, 0, 1, 0x01 }, + { 0xd051, 0, 8, 0x5a }, + { 0xd052, 0, 1, 0x01 }, + { 0xd053, 0, 8, 0x96 }, + { 0xd054, 0, 8, 0x46 }, + { 0xd045, 7, 1, 0x00 }, + { 0x9bd7, 0, 8, 0x0a }, + { 0x9bd8, 0, 8, 0x14 }, + { 0x9bd9, 0, 8, 0x08 }, +}; + +/* MaxLinear MXL5003 tuner init + AF9013_TUNER_MXL5003D = 3 */ +static struct regdesc tuner_init_mxl5003d[] = { + { 0x9bd5, 0, 8, 0x01 }, + { 0x9bd6, 0, 8, 0x09 }, + { 0xd1a0, 1, 1, 0x01 }, + { 0xd000, 0, 1, 0x01 }, + { 0xd000, 1, 1, 0x00 }, + { 0xd001, 1, 1, 0x01 }, + { 0xd001, 0, 1, 0x00 }, + { 0xd001, 5, 1, 0x00 }, + { 0xd002, 0, 5, 0x19 }, + { 0xd003, 0, 5, 0x1a }, + { 0xd004, 0, 5, 0x19 }, + { 0xd005, 0, 5, 0x1a }, + { 0xd00e, 0, 5, 0x10 }, + { 0xd00f, 0, 3, 0x04 }, + { 0xd00f, 3, 3, 0x05 }, + { 0xd010, 0, 3, 0x04 }, + { 0xd010, 3, 3, 0x05 }, + { 0xd016, 4, 4, 0x03 }, + { 0xd01f, 0, 6, 0x0a }, + { 0xd020, 0, 6, 0x0a }, + { 0x9bda, 0, 8, 0x00 }, + { 0x9be3, 0, 8, 0x00 }, + { 0x9bfc, 0, 8, 0x0f }, + { 0x9bf6, 0, 8, 0x01 }, + { 0x9bbe, 0, 1, 0x01 }, + { 0xd015, 0, 8, 0x33 }, + { 0xd016, 0, 1, 0x00 }, + { 0xd044, 0, 8, 0x40 }, + { 0xd045, 0, 1, 0x00 }, + { 0xd008, 0, 8, 0x0f }, + { 0xd009, 0, 2, 0x02 }, + { 0xd006, 0, 8, 0x6c }, + { 0xd007, 0, 2, 0x00 }, + { 0xd00c, 0, 8, 0x3d }, + { 0xd00d, 0, 2, 0x00 }, + { 0xd00a, 0, 8, 0x45 }, + { 0xd00b, 0, 2, 0x01 }, + { 0x9bc7, 0, 8, 0x07 }, + { 0x9bc8, 0, 8, 0x52 }, + { 0x9bc3, 0, 8, 0x0f }, + { 0x9bc4, 0, 8, 0x02 }, + { 0x9bc5, 0, 8, 0x3d }, + { 0x9bc6, 0, 8, 0x00 }, + { 0x9bba, 0, 8, 0xa2 }, + { 0x9bc9, 0, 8, 0xa0 }, + { 0xd011, 0, 8, 0x56 }, + { 0xd012, 0, 2, 0x00 }, + { 0xd013, 0, 8, 0x50 }, + { 0xd014, 0, 2, 0x00 }, + { 0xd040, 0, 8, 0x56 }, + { 0xd041, 0, 2, 0x00 }, + { 0xd042, 0, 8, 0x50 }, + { 0xd043, 0, 2, 0x00 }, + { 0xd045, 1, 1, 0x00 }, + { 0x9bcf, 0, 8, 0x01 }, + { 0xd045, 2, 1, 0x01 }, + { 0xd04f, 0, 8, 0x9a }, + { 0xd050, 0, 1, 0x01 }, + { 0xd051, 0, 8, 0x5a }, + { 0xd052, 0, 1, 0x01 }, + { 0xd053, 0, 8, 0x50 }, + { 0xd054, 0, 8, 0x46 }, + { 0x9bd7, 0, 8, 0x0a }, + { 0x9bd8, 0, 8, 0x14 }, + { 0x9bd9, 0, 8, 0x08 }, +}; + +/* MaxLinear MXL5005 tuner init + AF9013_TUNER_MXL5005D = 13 + AF9013_TUNER_MXL5005R = 30 */ +static struct regdesc tuner_init_mxl5005[] = { + { 0x9bd5, 0, 8, 0x01 }, + { 0x9bd6, 0, 8, 0x07 }, + { 0xd1a0, 1, 1, 0x01 }, + { 0xd000, 0, 1, 0x01 }, + { 0xd000, 1, 1, 0x00 }, + { 0xd001, 1, 1, 0x01 }, + { 0xd001, 0, 1, 0x00 }, + { 0xd001, 5, 1, 0x00 }, + { 0xd002, 0, 5, 0x19 }, + { 0xd003, 0, 5, 0x1a }, + { 0xd004, 0, 5, 0x19 }, + { 0xd005, 0, 5, 0x1a }, + { 0xd00e, 0, 5, 0x10 }, + { 0xd00f, 0, 3, 0x04 }, + { 0xd00f, 3, 3, 0x05 }, + { 0xd010, 0, 3, 0x04 }, + { 0xd010, 3, 3, 0x05 }, + { 0xd016, 4, 4, 0x03 }, + { 0xd01f, 0, 6, 0x0a }, + { 0xd020, 0, 6, 0x0a }, + { 0x9bda, 0, 8, 0x01 }, + { 0x9be3, 0, 8, 0x01 }, + { 0x9bbe, 0, 1, 0x01 }, + { 0x9bcc, 0, 1, 0x01 }, + { 0x9bb9, 0, 8, 0x00 }, + { 0x9bcd, 0, 8, 0x28 }, + { 0x9bff, 0, 8, 0x24 }, + { 0xd015, 0, 8, 0x40 }, + { 0xd016, 0, 1, 0x00 }, + { 0xd044, 0, 8, 0x40 }, + { 0xd045, 0, 1, 0x00 }, + { 0xd008, 0, 8, 0x0f }, + { 0xd009, 0, 2, 0x02 }, + { 0xd006, 0, 8, 0x73 }, + { 0xd007, 0, 2, 0x01 }, + { 0xd00c, 0, 8, 0xfa }, + { 0xd00d, 0, 2, 0x01 }, + { 0xd00a, 0, 8, 0xff }, + { 0xd00b, 0, 2, 0x01 }, + { 0x9bc7, 0, 8, 0x23 }, + { 0x9bc8, 0, 8, 0x55 }, + { 0x9bc3, 0, 8, 0x01 }, + { 0x9bc4, 0, 8, 0x02 }, + { 0x9bc5, 0, 8, 0xfa }, + { 0x9bc6, 0, 8, 0x01 }, + { 0x9bba, 0, 8, 0xff }, + { 0x9bc9, 0, 8, 0xff }, + { 0x9bd3, 0, 8, 0x95 }, + { 0xd011, 0, 8, 0x70 }, + { 0xd012, 0, 2, 0x01 }, + { 0xd013, 0, 8, 0xfb }, + { 0xd014, 0, 2, 0x01 }, + { 0xd040, 0, 8, 0x70 }, + { 0xd041, 0, 2, 0x01 }, + { 0xd042, 0, 8, 0xfb }, + { 0xd043, 0, 2, 0x01 }, + { 0xd045, 1, 1, 0x00 }, + { 0x9bcf, 0, 1, 0x01 }, + { 0xd045, 2, 1, 0x01 }, + { 0xd04f, 0, 8, 0x9a }, + { 0xd050, 0, 1, 0x01 }, + { 0xd051, 0, 8, 0x5a }, + { 0xd052, 0, 1, 0x01 }, + { 0xd053, 0, 8, 0x50 }, + { 0xd054, 0, 8, 0x46 }, + { 0x9bd7, 0, 8, 0x0a }, + { 0x9bd8, 0, 8, 0x14 }, + { 0x9bd9, 0, 8, 0x08 }, + { 0x9bd0, 0, 8, 0x93 }, + { 0x9be4, 0, 8, 0xfe }, + { 0x9bbd, 0, 8, 0x63 }, + { 0x9be2, 0, 8, 0xfe }, + { 0x9bee, 0, 1, 0x01 }, +}; + +/* Quantek QT1010 tuner init + AF9013_TUNER_QT1010 = 134 + AF9013_TUNER_QT1010A = 162 */ +static struct regdesc tuner_init_qt1010[] = { + { 0x9bd5, 0, 8, 0x01 }, + { 0x9bd6, 0, 8, 0x09 }, + { 0xd1a0, 1, 1, 0x01 }, + { 0xd000, 0, 1, 0x01 }, + { 0xd000, 1, 1, 0x00 }, + { 0xd001, 1, 1, 0x01 }, + { 0xd001, 0, 1, 0x00 }, + { 0xd001, 5, 1, 0x00 }, + { 0xd002, 0, 5, 0x19 }, + { 0xd003, 0, 5, 0x1a }, + { 0xd004, 0, 5, 0x19 }, + { 0xd005, 0, 5, 0x1a }, + { 0xd00e, 0, 5, 0x10 }, + { 0xd00f, 0, 3, 0x04 }, + { 0xd00f, 3, 3, 0x05 }, + { 0xd010, 0, 3, 0x04 }, + { 0xd010, 3, 3, 0x05 }, + { 0xd016, 4, 4, 0x03 }, + { 0xd01f, 0, 6, 0x0a }, + { 0xd020, 0, 6, 0x0a }, + { 0x9bda, 0, 8, 0x01 }, + { 0x9be3, 0, 8, 0x01 }, + { 0xd015, 0, 8, 0x46 }, + { 0xd016, 0, 1, 0x00 }, + { 0xd044, 0, 8, 0x46 }, + { 0xd045, 0, 1, 0x00 }, + { 0x9bbe, 0, 1, 0x01 }, + { 0x9bcc, 0, 1, 0x01 }, + { 0x9bb9, 0, 8, 0x00 }, + { 0x9bcd, 0, 8, 0x28 }, + { 0x9bff, 0, 8, 0x20 }, + { 0xd008, 0, 8, 0x0f }, + { 0xd009, 0, 2, 0x02 }, + { 0xd006, 0, 8, 0x99 }, + { 0xd007, 0, 2, 0x01 }, + { 0xd00c, 0, 8, 0x0f }, + { 0xd00d, 0, 2, 0x02 }, + { 0xd00a, 0, 8, 0x50 }, + { 0xd00b, 0, 2, 0x01 }, + { 0x9bc7, 0, 8, 0x00 }, + { 0x9bc8, 0, 8, 0x00 }, + { 0x9bc3, 0, 8, 0x0f }, + { 0x9bc4, 0, 8, 0x02 }, + { 0x9bc5, 0, 8, 0x0f }, + { 0x9bc6, 0, 8, 0x02 }, + { 0x9bba, 0, 8, 0xc5 }, + { 0x9bc9, 0, 8, 0xff }, + { 0xd011, 0, 8, 0x58 }, + { 0xd012, 0, 2, 0x02 }, + { 0xd013, 0, 8, 0x89 }, + { 0xd014, 0, 2, 0x01 }, + { 0xd040, 0, 8, 0x58 }, + { 0xd041, 0, 2, 0x02 }, + { 0xd042, 0, 8, 0x89 }, + { 0xd043, 0, 2, 0x01 }, + { 0xd045, 1, 1, 0x00 }, + { 0x9bcf, 0, 1, 0x01 }, + { 0xd045, 2, 1, 0x01 }, + { 0xd04f, 0, 8, 0x9a }, + { 0xd050, 0, 1, 0x01 }, + { 0xd051, 0, 8, 0x5a }, + { 0xd052, 0, 1, 0x01 }, + { 0xd053, 0, 8, 0x50 }, + { 0xd054, 0, 8, 0x46 }, + { 0x9bd7, 0, 8, 0x0a }, + { 0x9bd8, 0, 8, 0x14 }, + { 0x9bd9, 0, 8, 0x08 }, + { 0x9bd0, 0, 8, 0xcd }, + { 0x9be4, 0, 8, 0xbb }, + { 0x9bbd, 0, 8, 0x93 }, + { 0x9be2, 0, 8, 0x80 }, + { 0x9bee, 0, 1, 0x01 }, +}; + +/* Freescale MC44S803 tuner init + AF9013_TUNER_MC44S803 = 133 */ +static struct regdesc tuner_init_mc44s803[] = { + { 0x9bd5, 0, 8, 0x01 }, + { 0x9bd6, 0, 8, 0x06 }, + { 0xd1a0, 1, 1, 0x01 }, + { 0xd000, 0, 1, 0x01 }, + { 0xd000, 1, 1, 0x00 }, + { 0xd001, 1, 1, 0x01 }, + { 0xd001, 0, 1, 0x00 }, + { 0xd001, 5, 1, 0x00 }, + { 0xd002, 0, 5, 0x19 }, + { 0xd003, 0, 5, 0x1a }, + { 0xd004, 0, 5, 0x19 }, + { 0xd005, 0, 5, 0x1a }, + { 0xd00e, 0, 5, 0x10 }, + { 0xd00f, 0, 3, 0x04 }, + { 0xd00f, 3, 3, 0x05 }, + { 0xd010, 0, 3, 0x04 }, + { 0xd010, 3, 3, 0x05 }, + { 0xd016, 4, 4, 0x03 }, + { 0xd01f, 0, 6, 0x0a }, + { 0xd020, 0, 6, 0x0a }, + { 0x9bda, 0, 8, 0x00 }, + { 0x9be3, 0, 8, 0x00 }, + { 0x9bf6, 0, 8, 0x01 }, + { 0x9bf8, 0, 8, 0x02 }, + { 0x9bf9, 0, 8, 0x02 }, + { 0x9bfc, 0, 8, 0x1f }, + { 0x9bbe, 0, 1, 0x01 }, + { 0x9bcc, 0, 1, 0x01 }, + { 0x9bb9, 0, 8, 0x00 }, + { 0x9bcd, 0, 8, 0x24 }, + { 0x9bff, 0, 8, 0x24 }, + { 0xd015, 0, 8, 0x46 }, + { 0xd016, 0, 1, 0x00 }, + { 0xd044, 0, 8, 0x46 }, + { 0xd045, 0, 1, 0x00 }, + { 0xd008, 0, 8, 0x01 }, + { 0xd009, 0, 2, 0x02 }, + { 0xd006, 0, 8, 0x7b }, + { 0xd007, 0, 2, 0x00 }, + { 0xd00c, 0, 8, 0x7c }, + { 0xd00d, 0, 2, 0x02 }, + { 0xd00a, 0, 8, 0xfe }, + { 0xd00b, 0, 2, 0x01 }, + { 0x9bc7, 0, 8, 0x08 }, + { 0x9bc8, 0, 8, 0x9a }, + { 0x9bc3, 0, 8, 0x01 }, + { 0x9bc4, 0, 8, 0x02 }, + { 0x9bc5, 0, 8, 0x7c }, + { 0x9bc6, 0, 8, 0x02 }, + { 0x9bba, 0, 8, 0xfc }, + { 0x9bc9, 0, 8, 0xaa }, + { 0xd011, 0, 8, 0x6b }, + { 0xd012, 0, 2, 0x00 }, + { 0xd013, 0, 8, 0x88 }, + { 0xd014, 0, 2, 0x02 }, + { 0xd040, 0, 8, 0x6b }, + { 0xd041, 0, 2, 0x00 }, + { 0xd042, 0, 8, 0x7c }, + { 0xd043, 0, 2, 0x02 }, + { 0xd045, 1, 1, 0x00 }, + { 0x9bcf, 0, 1, 0x01 }, + { 0xd045, 2, 1, 0x01 }, + { 0xd04f, 0, 8, 0x9a }, + { 0xd050, 0, 1, 0x01 }, + { 0xd051, 0, 8, 0x5a }, + { 0xd052, 0, 1, 0x01 }, + { 0xd053, 0, 8, 0x50 }, + { 0xd054, 0, 8, 0x46 }, + { 0x9bd7, 0, 8, 0x0a }, + { 0x9bd8, 0, 8, 0x14 }, + { 0x9bd9, 0, 8, 0x08 }, + { 0x9bd0, 0, 8, 0x9e }, + { 0x9be4, 0, 8, 0xff }, + { 0x9bbd, 0, 8, 0x9e }, + { 0x9be2, 0, 8, 0x25 }, + { 0x9bee, 0, 1, 0x01 }, + { 0xd73b, 3, 1, 0x00 }, +}; + +/* unknown, probably for tin can tuner, tuner init + AF9013_TUNER_UNKNOWN = 140 */ +static struct regdesc tuner_init_unknown[] = { + { 0x9bd5, 0, 8, 0x01 }, + { 0x9bd6, 0, 8, 0x02 }, + { 0xd1a0, 1, 1, 0x01 }, + { 0xd000, 0, 1, 0x01 }, + { 0xd000, 1, 1, 0x00 }, + { 0xd001, 1, 1, 0x01 }, + { 0xd001, 0, 1, 0x00 }, + { 0xd001, 5, 1, 0x00 }, + { 0xd002, 0, 5, 0x19 }, + { 0xd003, 0, 5, 0x1a }, + { 0xd004, 0, 5, 0x19 }, + { 0xd005, 0, 5, 0x1a }, + { 0xd00e, 0, 5, 0x10 }, + { 0xd00f, 0, 3, 0x04 }, + { 0xd00f, 3, 3, 0x05 }, + { 0xd010, 0, 3, 0x04 }, + { 0xd010, 3, 3, 0x05 }, + { 0xd016, 4, 4, 0x03 }, + { 0xd01f, 0, 6, 0x0a }, + { 0xd020, 0, 6, 0x0a }, + { 0x9bda, 0, 8, 0x01 }, + { 0x9be3, 0, 8, 0x01 }, + { 0xd1a0, 1, 1, 0x00 }, + { 0x9bbe, 0, 1, 0x01 }, + { 0x9bcc, 0, 1, 0x01 }, + { 0x9bb9, 0, 8, 0x00 }, + { 0x9bcd, 0, 8, 0x18 }, + { 0x9bff, 0, 8, 0x2c }, + { 0xd015, 0, 8, 0x46 }, + { 0xd016, 0, 1, 0x00 }, + { 0xd044, 0, 8, 0x46 }, + { 0xd045, 0, 1, 0x00 }, + { 0xd008, 0, 8, 0xdf }, + { 0xd009, 0, 2, 0x02 }, + { 0xd006, 0, 8, 0x44 }, + { 0xd007, 0, 2, 0x01 }, + { 0xd00c, 0, 8, 0x00 }, + { 0xd00d, 0, 2, 0x02 }, + { 0xd00a, 0, 8, 0xf6 }, + { 0xd00b, 0, 2, 0x01 }, + { 0x9bba, 0, 8, 0xf9 }, + { 0x9bc8, 0, 8, 0xaa }, + { 0x9bc3, 0, 8, 0xdf }, + { 0x9bc4, 0, 8, 0x02 }, + { 0x9bc5, 0, 8, 0x00 }, + { 0x9bc6, 0, 8, 0x02 }, + { 0x9bc9, 0, 8, 0xf0 }, + { 0xd011, 0, 8, 0x3c }, + { 0xd012, 0, 2, 0x01 }, + { 0xd013, 0, 8, 0xf7 }, + { 0xd014, 0, 2, 0x02 }, + { 0xd040, 0, 8, 0x0b }, + { 0xd041, 0, 2, 0x02 }, + { 0xd042, 0, 8, 0x4d }, + { 0xd043, 0, 2, 0x00 }, + { 0xd045, 1, 1, 0x00 }, + { 0x9bcf, 0, 1, 0x01 }, + { 0xd045, 2, 1, 0x01 }, + { 0xd04f, 0, 8, 0x9a }, + { 0xd050, 0, 1, 0x01 }, + { 0xd051, 0, 8, 0x5a }, + { 0xd052, 0, 1, 0x01 }, + { 0xd053, 0, 8, 0x50 }, + { 0xd054, 0, 8, 0x46 }, + { 0x9bd7, 0, 8, 0x0a }, + { 0x9bd8, 0, 8, 0x14 }, + { 0x9bd9, 0, 8, 0x08 }, +}; + +/* NXP TDA18271 tuner init + AF9013_TUNER_TDA18271 = 156 */ +static struct regdesc tuner_init_tda18271[] = { + { 0x9bd5, 0, 8, 0x01 }, + { 0x9bd6, 0, 8, 0x04 }, + { 0xd1a0, 1, 1, 0x01 }, + { 0xd000, 0, 1, 0x01 }, + { 0xd000, 1, 1, 0x00 }, + { 0xd001, 1, 1, 0x01 }, + { 0xd001, 0, 1, 0x00 }, + { 0xd001, 5, 1, 0x00 }, + { 0xd002, 0, 5, 0x19 }, + { 0xd003, 0, 5, 0x1a }, + { 0xd004, 0, 5, 0x19 }, + { 0xd005, 0, 5, 0x1a }, + { 0xd00e, 0, 5, 0x10 }, + { 0xd00f, 0, 3, 0x04 }, + { 0xd00f, 3, 3, 0x05 }, + { 0xd010, 0, 3, 0x04 }, + { 0xd010, 3, 3, 0x05 }, + { 0xd016, 4, 4, 0x03 }, + { 0xd01f, 0, 6, 0x0a }, + { 0xd020, 0, 6, 0x0a }, + { 0x9bda, 0, 8, 0x01 }, + { 0x9be3, 0, 8, 0x01 }, + { 0xd1a0, 1, 1, 0x00 }, + { 0x9bbe, 0, 1, 0x01 }, + { 0x9bcc, 0, 1, 0x01 }, + { 0x9bb9, 0, 8, 0x00 }, + { 0x9bcd, 0, 8, 0x18 }, + { 0x9bff, 0, 8, 0x2c }, + { 0xd015, 0, 8, 0x46 }, + { 0xd016, 0, 1, 0x00 }, + { 0xd044, 0, 8, 0x46 }, + { 0xd045, 0, 1, 0x00 }, + { 0xd008, 0, 8, 0xdf }, + { 0xd009, 0, 2, 0x02 }, + { 0xd006, 0, 8, 0x44 }, + { 0xd007, 0, 2, 0x01 }, + { 0xd00c, 0, 8, 0x00 }, + { 0xd00d, 0, 2, 0x02 }, + { 0xd00a, 0, 8, 0xf6 }, + { 0xd00b, 0, 2, 0x01 }, + { 0x9bba, 0, 8, 0xf9 }, + { 0x9bc8, 0, 8, 0xaa }, + { 0x9bc3, 0, 8, 0xdf }, + { 0x9bc4, 0, 8, 0x02 }, + { 0x9bc5, 0, 8, 0x00 }, + { 0x9bc6, 0, 8, 0x02 }, + { 0x9bc9, 0, 8, 0xf0 }, + { 0xd011, 0, 8, 0x3c }, + { 0xd012, 0, 2, 0x01 }, + { 0xd013, 0, 8, 0xf7 }, + { 0xd014, 0, 2, 0x02 }, + { 0xd040, 0, 8, 0x0b }, + { 0xd041, 0, 2, 0x02 }, + { 0xd042, 0, 8, 0x4d }, + { 0xd043, 0, 2, 0x00 }, + { 0xd045, 1, 1, 0x00 }, + { 0x9bcf, 0, 1, 0x01 }, + { 0xd045, 2, 1, 0x01 }, + { 0xd04f, 0, 8, 0x9a }, + { 0xd050, 0, 1, 0x01 }, + { 0xd051, 0, 8, 0x5a }, + { 0xd052, 0, 1, 0x01 }, + { 0xd053, 0, 8, 0x50 }, + { 0xd054, 0, 8, 0x46 }, + { 0x9bd7, 0, 8, 0x0a }, + { 0x9bd8, 0, 8, 0x14 }, + { 0x9bd9, 0, 8, 0x08 }, + { 0x9bd0, 0, 8, 0xa8 }, + { 0x9be4, 0, 8, 0x7f }, + { 0x9bbd, 0, 8, 0xa8 }, + { 0x9be2, 0, 8, 0x20 }, + { 0x9bee, 0, 1, 0x01 }, +}; + +#endif /* _AF9013_PRIV_ */ diff --git a/drivers/media/dvb/frontends/au8522.c b/drivers/media/dvb/frontends/au8522.c index 0b82cc2a1e16..eabf9a68e7ec 100644 --- a/drivers/media/dvb/frontends/au8522.c +++ b/drivers/media/dvb/frontends/au8522.c @@ -40,6 +40,8 @@ struct au8522_state { u32 current_frequency; fe_modulation_t current_modulation; + u32 fe_status; + unsigned int led_state; }; static int debug; @@ -538,11 +540,98 @@ static int au8522_init(struct dvb_frontend *fe) return 0; } +static int au8522_led_gpio_enable(struct au8522_state *state, int onoff) +{ + struct au8522_led_config *led_config = state->config->led_cfg; + u8 val; + + /* bail out if we cant control an LED */ + if (!led_config || !led_config->gpio_output || + !led_config->gpio_output_enable || !led_config->gpio_output_disable) + return 0; + + val = au8522_readreg(state, 0x4000 | + (led_config->gpio_output & ~0xc000)); + if (onoff) { + /* enable GPIO output */ + val &= ~((led_config->gpio_output_enable >> 8) & 0xff); + val |= (led_config->gpio_output_enable & 0xff); + } else { + /* disable GPIO output */ + val &= ~((led_config->gpio_output_disable >> 8) & 0xff); + val |= (led_config->gpio_output_disable & 0xff); + } + return au8522_writereg(state, 0x8000 | + (led_config->gpio_output & ~0xc000), val); +} + +/* led = 0 | off + * led = 1 | signal ok + * led = 2 | signal strong + * led < 0 | only light led if leds are currently off + */ +static int au8522_led_ctrl(struct au8522_state *state, int led) +{ + struct au8522_led_config *led_config = state->config->led_cfg; + int i, ret = 0; + + /* bail out if we cant control an LED */ + if (!led_config || !led_config->gpio_leds || + !led_config->num_led_states || !led_config->led_states) + return 0; + + if (led < 0) { + /* if LED is already lit, then leave it as-is */ + if (state->led_state) + return 0; + else + led *= -1; + } + + /* toggle LED if changing state */ + if (state->led_state != led) { + u8 val; + + dprintk("%s: %d\n", __func__, led); + + au8522_led_gpio_enable(state, 1); + + val = au8522_readreg(state, 0x4000 | + (led_config->gpio_leds & ~0xc000)); + + /* start with all leds off */ + for (i = 0; i < led_config->num_led_states; i++) + val &= ~led_config->led_states[i]; + + /* set selected LED state */ + if (led < led_config->num_led_states) + val |= led_config->led_states[led]; + else if (led_config->num_led_states) + val |= + led_config->led_states[led_config->num_led_states - 1]; + + ret = au8522_writereg(state, 0x8000 | + (led_config->gpio_leds & ~0xc000), val); + if (ret < 0) + return ret; + + state->led_state = led; + + if (led == 0) + au8522_led_gpio_enable(state, 0); + } + + return 0; +} + static int au8522_sleep(struct dvb_frontend *fe) { struct au8522_state *state = fe->demodulator_priv; dprintk("%s()\n", __func__); + /* turn off led */ + au8522_led_ctrl(state, 0); + state->current_frequency = 0; return 0; @@ -592,12 +681,53 @@ static int au8522_read_status(struct dvb_frontend *fe, fe_status_t *status) *status |= FE_HAS_CARRIER | FE_HAS_SIGNAL; break; } + state->fe_status = *status; + + if (*status & FE_HAS_LOCK) + /* turn on LED, if it isn't on already */ + au8522_led_ctrl(state, -1); + else + /* turn off LED */ + au8522_led_ctrl(state, 0); dprintk("%s() status 0x%08x\n", __func__, *status); return 0; } +static int au8522_led_status(struct au8522_state *state, const u16 *snr) +{ + struct au8522_led_config *led_config = state->config->led_cfg; + int led; + u16 strong; + + /* bail out if we cant control an LED */ + if (!led_config) + return 0; + + if (0 == (state->fe_status & FE_HAS_LOCK)) + return au8522_led_ctrl(state, 0); + else if (state->current_modulation == QAM_256) + strong = led_config->qam256_strong; + else if (state->current_modulation == QAM_64) + strong = led_config->qam64_strong; + else /* (state->current_modulation == VSB_8) */ + strong = led_config->vsb8_strong; + + if (*snr >= strong) + led = 2; + else + led = 1; + + if ((state->led_state) && + (((strong < *snr) ? (*snr - strong) : (strong - *snr)) <= 10)) + /* snr didn't change enough to bother + * changing the color of the led */ + return 0; + + return au8522_led_ctrl(state, led); +} + static int au8522_read_snr(struct dvb_frontend *fe, u16 *snr) { struct au8522_state *state = fe->demodulator_priv; @@ -621,6 +751,9 @@ static int au8522_read_snr(struct dvb_frontend *fe, u16 *snr) au8522_readreg(state, 0x4311), snr); + if (state->config->led_cfg) + au8522_led_status(state, snr); + return ret; } diff --git a/drivers/media/dvb/frontends/au8522.h b/drivers/media/dvb/frontends/au8522.h index 595915ade8c3..7b94f554a093 100644 --- a/drivers/media/dvb/frontends/au8522.h +++ b/drivers/media/dvb/frontends/au8522.h @@ -30,6 +30,21 @@ enum au8522_if_freq { AU8522_IF_3_25MHZ, }; +struct au8522_led_config { + u16 vsb8_strong; + u16 qam64_strong; + u16 qam256_strong; + + u16 gpio_output; + /* unset hi bits, set low bits */ + u16 gpio_output_enable; + u16 gpio_output_disable; + + u16 gpio_leds; + u8 *led_states; + unsigned int num_led_states; +}; + struct au8522_config { /* the demodulator's i2c address */ u8 demod_address; @@ -39,6 +54,8 @@ struct au8522_config { #define AU8522_DEMODLOCKING 1 u8 status_mode; + struct au8522_led_config *led_cfg; + enum au8522_if_freq vsb_if; enum au8522_if_freq qam_if; }; diff --git a/drivers/media/dvb/frontends/cx24110.h b/drivers/media/dvb/frontends/cx24110.h index 1792adb23c4d..fdcceee91f3a 100644 --- a/drivers/media/dvb/frontends/cx24110.h +++ b/drivers/media/dvb/frontends/cx24110.h @@ -33,12 +33,17 @@ struct cx24110_config u8 demod_address; }; -static inline int cx24110_pll_write(struct dvb_frontend *fe, u32 val) { - int r = 0; - u8 buf[] = {(u8) (val>>24), (u8) (val>>16), (u8) (val>>8)}; +static inline int cx24110_pll_write(struct dvb_frontend *fe, u32 val) +{ + u8 buf[] = { + (u8)((val >> 24) & 0xff), + (u8)((val >> 16) & 0xff), + (u8)((val >> 8) & 0xff) + }; + if (fe->ops.write) - r = fe->ops.write(fe, buf, 3); - return r; + return fe->ops.write(fe, buf, 3); + return 0; } #if defined(CONFIG_DVB_CX24110) || (defined(CONFIG_DVB_CX24110_MODULE) && defined(MODULE)) diff --git a/drivers/media/dvb/frontends/cx24116.c b/drivers/media/dvb/frontends/cx24116.c new file mode 100644 index 000000000000..deb36f469ada --- /dev/null +++ b/drivers/media/dvb/frontends/cx24116.c @@ -0,0 +1,1423 @@ +/* + Conexant cx24116/cx24118 - DVBS/S2 Satellite demod/tuner driver + + Copyright (C) 2006-2008 Steven Toth <stoth@hauppauge.com> + Copyright (C) 2006-2007 Georg Acher + Copyright (C) 2007-2008 Darron Broad + March 2007 + Fixed some bugs. + Added diseqc support. + Added corrected signal strength support. + August 2007 + Sync with legacy version. + Some clean ups. + Copyright (C) 2008 Igor Liplianin + September, 9th 2008 + Fixed locking on high symbol rates (>30000). + Implement MPEG initialization parameter. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/firmware.h> + +#include "dvb_frontend.h" +#include "cx24116.h" + +static int debug = 0; +#define dprintk(args...) \ + do { \ + if (debug) printk ("cx24116: " args); \ + } while (0) + +#define CX24116_DEFAULT_FIRMWARE "dvb-fe-cx24116.fw" +#define CX24116_SEARCH_RANGE_KHZ 5000 + +/* known registers */ +#define CX24116_REG_COMMAND (0x00) /* command args 0x00..0x1e */ +#define CX24116_REG_EXECUTE (0x1f) /* execute command */ +#define CX24116_REG_MAILBOX (0x96) /* FW or multipurpose mailbox? */ +#define CX24116_REG_RESET (0x20) /* reset status > 0 */ +#define CX24116_REG_SIGNAL (0x9e) /* signal low */ +#define CX24116_REG_SSTATUS (0x9d) /* signal high / status */ +#define CX24116_REG_QUALITY8 (0xa3) +#define CX24116_REG_QSTATUS (0xbc) +#define CX24116_REG_QUALITY0 (0xd5) +#define CX24116_REG_BER0 (0xc9) +#define CX24116_REG_BER8 (0xc8) +#define CX24116_REG_BER16 (0xc7) +#define CX24116_REG_BER24 (0xc6) +#define CX24116_REG_UCB0 (0xcb) +#define CX24116_REG_UCB8 (0xca) +#define CX24116_REG_CLKDIV (0xf3) +#define CX24116_REG_RATEDIV (0xf9) +#define CX24116_REG_FECSTATUS (0x9c) /* configured fec (not tuned) or actual FEC (tuned) 1=1/2 2=2/3 etc */ + +/* FECSTATUS bits */ +#define CX24116_FEC_FECMASK (0x1f) /* mask to determine configured fec (not tuned) or actual fec (tuned) */ +#define CX24116_FEC_DVBS (0x20) /* Select DVB-S demodulator, else DVB-S2 */ +#define CX24116_FEC_UNKNOWN (0x40) /* Unknown/unused */ +#define CX24116_FEC_PILOT (0x80) /* Pilot mode requested when tuning else always reset when tuned */ + +/* arg buffer size */ +#define CX24116_ARGLEN (0x1e) + +/* rolloff */ +#define CX24116_ROLLOFF_020 (0x00) +#define CX24116_ROLLOFF_025 (0x01) +#define CX24116_ROLLOFF_035 (0x02) + +/* pilot bit */ +#define CX24116_PILOT_OFF (0x00) +#define CX24116_PILOT_ON (0x40) + +/* signal status */ +#define CX24116_HAS_SIGNAL (0x01) +#define CX24116_HAS_CARRIER (0x02) +#define CX24116_HAS_VITERBI (0x04) +#define CX24116_HAS_SYNCLOCK (0x08) +#define CX24116_HAS_UNKNOWN1 (0x10) +#define CX24116_HAS_UNKNOWN2 (0x20) +#define CX24116_STATUS_MASK (0x3f) +#define CX24116_SIGNAL_MASK (0xc0) + +#define CX24116_DISEQC_TONEOFF (0) /* toneburst never sent */ +#define CX24116_DISEQC_TONECACHE (1) /* toneburst cached */ +#define CX24116_DISEQC_MESGCACHE (2) /* message cached */ + +/* arg offset for DiSEqC */ +#define CX24116_DISEQC_BURST (1) +#define CX24116_DISEQC_ARG2_2 (2) /* unknown value=2 */ +#define CX24116_DISEQC_ARG3_0 (3) /* unknown value=0 */ +#define CX24116_DISEQC_ARG4_0 (4) /* unknown value=0 */ +#define CX24116_DISEQC_MSGLEN (5) +#define CX24116_DISEQC_MSGOFS (6) + +/* DiSEqC burst */ +#define CX24116_DISEQC_MINI_A (0) +#define CX24116_DISEQC_MINI_B (1) + +/* DiSEqC tone burst */ +static int toneburst = 1; + +/* SNR measurements */ +static int esno_snr = 0; + +enum cmds +{ + CMD_SET_VCO = 0x10, + CMD_TUNEREQUEST = 0x11, + CMD_MPEGCONFIG = 0x13, + CMD_TUNERINIT = 0x14, + CMD_BANDWIDTH = 0x15, + CMD_GETAGC = 0x19, + CMD_LNBCONFIG = 0x20, + CMD_LNBSEND = 0x21, /* Formerly CMD_SEND_DISEQC */ + CMD_SET_TONEPRE = 0x22, + CMD_SET_TONE = 0x23, + CMD_UPDFWVERS = 0x35, + CMD_TUNERSLEEP = 0x36, + CMD_AGCCONTROL = 0x3b, /* Unknown */ +}; + +/* The Demod/Tuner can't easily provide these, we cache them */ +struct cx24116_tuning +{ + u32 frequency; + u32 symbol_rate; + fe_spectral_inversion_t inversion; + fe_code_rate_t fec; + + fe_modulation_t modulation; + fe_pilot_t pilot; + fe_rolloff_t rolloff; + + /* Demod values */ + u8 fec_val; + u8 fec_mask; + u8 inversion_val; + u8 pilot_val; + u8 rolloff_val; +}; + +/* Basic commands that are sent to the firmware */ +struct cx24116_cmd +{ + u8 len; + u8 args[CX24116_ARGLEN]; +}; + +struct cx24116_state +{ + struct i2c_adapter* i2c; + const struct cx24116_config* config; + + struct dvb_frontend frontend; + + struct cx24116_tuning dcur; + struct cx24116_tuning dnxt; + + u8 skip_fw_load; + u8 burst; + struct cx24116_cmd dsec_cmd; +}; + +static int cx24116_writereg(struct cx24116_state* state, int reg, int data) +{ + u8 buf[] = { reg, data }; + struct i2c_msg msg = { .addr = state->config->demod_address, + .flags = 0, .buf = buf, .len = 2 }; + int err; + + if (debug>1) + printk("cx24116: %s: write reg 0x%02x, value 0x%02x\n", + __func__,reg, data); + + if ((err = i2c_transfer(state->i2c, &msg, 1)) != 1) { + printk("%s: writereg error(err == %i, reg == 0x%02x," + " value == 0x%02x)\n", __func__, err, reg, data); + return -EREMOTEIO; + } + + return 0; +} + +/* Bulk byte writes to a single I2C address, for 32k firmware load */ +static int cx24116_writeregN(struct cx24116_state* state, int reg, u8 *data, u16 len) +{ + int ret = -EREMOTEIO; + struct i2c_msg msg; + u8 *buf; + + buf = kmalloc(len + 1, GFP_KERNEL); + if (buf == NULL) { + printk("Unable to kmalloc\n"); + ret = -ENOMEM; + goto error; + } + + *(buf) = reg; + memcpy(buf + 1, data, len); + + msg.addr = state->config->demod_address; + msg.flags = 0; + msg.buf = buf; + msg.len = len + 1; + + if (debug>1) + printk("cx24116: %s: write regN 0x%02x, len = %d\n", + __func__,reg, len); + + if ((ret = i2c_transfer(state->i2c, &msg, 1)) != 1) { + printk("%s: writereg error(err == %i, reg == 0x%02x\n", + __func__, ret, reg); + ret = -EREMOTEIO; + } + +error: + kfree(buf); + + return ret; +} + +static int cx24116_readreg(struct cx24116_state* state, u8 reg) +{ + int ret; + u8 b0[] = { reg }; + u8 b1[] = { 0 }; + struct i2c_msg msg[] = { + { .addr = state->config->demod_address, .flags = 0, .buf = b0, .len = 1 }, + { .addr = state->config->demod_address, .flags = I2C_M_RD, .buf = b1, .len = 1 } + }; + + ret = i2c_transfer(state->i2c, msg, 2); + + if (ret != 2) { + printk("%s: reg=0x%x (error=%d)\n", __func__, reg, ret); + return ret; + } + + if (debug>1) + printk("cx24116: read reg 0x%02x, value 0x%02x\n",reg, b1[0]); + + return b1[0]; +} + +static int cx24116_set_inversion(struct cx24116_state* state, fe_spectral_inversion_t inversion) +{ + dprintk("%s(%d)\n", __func__, inversion); + + switch (inversion) { + case INVERSION_OFF: + state->dnxt.inversion_val = 0x00; + break; + case INVERSION_ON: + state->dnxt.inversion_val = 0x04; + break; + case INVERSION_AUTO: + state->dnxt.inversion_val = 0x0C; + break; + default: + return -EINVAL; + } + + state->dnxt.inversion = inversion; + + return 0; +} + +/* + * modfec (modulation and FEC) + * =========================== + * + * MOD FEC mask/val standard + * ---- -------- ----------- -------- + * QPSK FEC_1_2 0x02 0x02+X DVB-S + * QPSK FEC_2_3 0x04 0x02+X DVB-S + * QPSK FEC_3_4 0x08 0x02+X DVB-S + * QPSK FEC_4_5 0x10 0x02+X DVB-S (?) + * QPSK FEC_5_6 0x20 0x02+X DVB-S + * QPSK FEC_6_7 0x40 0x02+X DVB-S + * QPSK FEC_7_8 0x80 0x02+X DVB-S + * QPSK FEC_8_9 0x01 0x02+X DVB-S (?) (NOT SUPPORTED?) + * QPSK AUTO 0xff 0x02+X DVB-S + * + * For DVB-S high byte probably represents FEC + * and low byte selects the modulator. The high + * byte is search range mask. Bit 5 may turn + * on DVB-S and remaining bits represent some + * kind of calibration (how/what i do not know). + * + * Eg.(2/3) szap "Zone Horror" + * + * mask/val = 0x04, 0x20 + * status 1f | signal c3c0 | snr a333 | ber 00000098 | unc 00000000 | FE_HAS_LOCK + * + * mask/val = 0x04, 0x30 + * status 1f | signal c3c0 | snr a333 | ber 00000000 | unc 00000000 | FE_HAS_LOCK + * + * After tuning FECSTATUS contains actual FEC + * in use numbered 1 through to 8 for 1/2 .. 2/3 etc + * + * NBC=NOT/NON BACKWARD COMPATIBLE WITH DVB-S (DVB-S2 only) + * + * NBC-QPSK FEC_1_2 0x00, 0x04 DVB-S2 + * NBC-QPSK FEC_3_5 0x00, 0x05 DVB-S2 + * NBC-QPSK FEC_2_3 0x00, 0x06 DVB-S2 + * NBC-QPSK FEC_3_4 0x00, 0x07 DVB-S2 + * NBC-QPSK FEC_4_5 0x00, 0x08 DVB-S2 + * NBC-QPSK FEC_5_6 0x00, 0x09 DVB-S2 + * NBC-QPSK FEC_8_9 0x00, 0x0a DVB-S2 + * NBC-QPSK FEC_9_10 0x00, 0x0b DVB-S2 + * + * NBC-8PSK FEC_3_5 0x00, 0x0c DVB-S2 + * NBC-8PSK FEC_2_3 0x00, 0x0d DVB-S2 + * NBC-8PSK FEC_3_4 0x00, 0x0e DVB-S2 + * NBC-8PSK FEC_5_6 0x00, 0x0f DVB-S2 + * NBC-8PSK FEC_8_9 0x00, 0x10 DVB-S2 + * NBC-8PSK FEC_9_10 0x00, 0x11 DVB-S2 + * + * For DVB-S2 low bytes selects both modulator + * and FEC. High byte is meaningless here. To + * set pilot, bit 6 (0x40) is set. When inspecting + * FECSTATUS bit 7 (0x80) represents the pilot + * selection whilst not tuned. When tuned, actual FEC + * in use is found in FECSTATUS as per above. Pilot + * value is reset. + */ + +/* A table of modulation, fec and configuration bytes for the demod. + * Not all S2 mmodulation schemes are support and not all rates with + * a scheme are support. Especially, no auto detect when in S2 mode. + */ +struct cx24116_modfec { + fe_delivery_system_t delivery_system; + fe_modulation_t modulation; + fe_code_rate_t fec; + u8 mask; /* In DVBS mode this is used to autodetect */ + u8 val; /* Passed to the firmware to indicate mode selection */ +} CX24116_MODFEC_MODES[] = { + /* QPSK. For unknown rates we set hardware to auto detect 0xfe 0x30 */ + + /*mod fec mask val */ + { SYS_DVBS, QPSK, FEC_NONE, 0xfe, 0x30 }, + { SYS_DVBS, QPSK, FEC_1_2, 0x02, 0x2e }, /* 00000010 00101110 */ + { SYS_DVBS, QPSK, FEC_2_3, 0x04, 0x2f }, /* 00000100 00101111 */ + { SYS_DVBS, QPSK, FEC_3_4, 0x08, 0x30 }, /* 00001000 00110000 */ + { SYS_DVBS, QPSK, FEC_4_5, 0xfe, 0x30 }, /* 000?0000 ? */ + { SYS_DVBS, QPSK, FEC_5_6, 0x20, 0x31 }, /* 00100000 00110001 */ + { SYS_DVBS, QPSK, FEC_6_7, 0xfe, 0x30 }, /* 0?000000 ? */ + { SYS_DVBS, QPSK, FEC_7_8, 0x80, 0x32 }, /* 10000000 00110010 */ + { SYS_DVBS, QPSK, FEC_8_9, 0xfe, 0x30 }, /* 0000000? ? */ + { SYS_DVBS, QPSK, FEC_AUTO, 0xfe, 0x30 }, + /* NBC-QPSK */ + { SYS_DVBS2, QPSK, FEC_1_2, 0x00, 0x04 }, + { SYS_DVBS2, QPSK, FEC_3_5, 0x00, 0x05 }, + { SYS_DVBS2, QPSK, FEC_2_3, 0x00, 0x06 }, + { SYS_DVBS2, QPSK, FEC_3_4, 0x00, 0x07 }, + { SYS_DVBS2, QPSK, FEC_4_5, 0x00, 0x08 }, + { SYS_DVBS2, QPSK, FEC_5_6, 0x00, 0x09 }, + { SYS_DVBS2, QPSK, FEC_8_9, 0x00, 0x0a }, + { SYS_DVBS2, QPSK, FEC_9_10, 0x00, 0x0b }, + /* 8PSK */ + { SYS_DVBS2, PSK_8, FEC_3_5, 0x00, 0x0c }, + { SYS_DVBS2, PSK_8, FEC_2_3, 0x00, 0x0d }, + { SYS_DVBS2, PSK_8, FEC_3_4, 0x00, 0x0e }, + { SYS_DVBS2, PSK_8, FEC_5_6, 0x00, 0x0f }, + { SYS_DVBS2, PSK_8, FEC_8_9, 0x00, 0x10 }, + { SYS_DVBS2, PSK_8, FEC_9_10, 0x00, 0x11 }, + /* + * `val' can be found in the FECSTATUS register when tuning. + * FECSTATUS will give the actual FEC in use if tuning was successful. + */ +}; + +static int cx24116_lookup_fecmod(struct cx24116_state* state, + fe_modulation_t m, fe_code_rate_t f) +{ + int i, ret = -EOPNOTSUPP; + + dprintk("%s(0x%02x,0x%02x)\n", __func__, m, f); + + for(i=0 ; i < sizeof(CX24116_MODFEC_MODES) / sizeof(struct cx24116_modfec) ; i++) + { + if( (m == CX24116_MODFEC_MODES[i].modulation) && + (f == CX24116_MODFEC_MODES[i].fec) ) + { + ret = i; + break; + } + } + + return ret; +} + +static int cx24116_set_fec(struct cx24116_state* state, fe_modulation_t mod, fe_code_rate_t fec) +{ + int ret = 0; + + dprintk("%s(0x%02x,0x%02x)\n", __func__, mod, fec); + + ret = cx24116_lookup_fecmod(state, mod, fec); + + if(ret < 0) + return ret; + + state->dnxt.fec = fec; + state->dnxt.fec_val = CX24116_MODFEC_MODES[ret].val; + state->dnxt.fec_mask = CX24116_MODFEC_MODES[ret].mask; + dprintk("%s() mask/val = 0x%02x/0x%02x\n", __func__, + state->dnxt.fec_mask, state->dnxt.fec_val); + + return 0; +} + +static int cx24116_set_symbolrate(struct cx24116_state* state, u32 rate) +{ + dprintk("%s(%d)\n", __func__, rate); + + /* check if symbol rate is within limits */ + if ((rate > state->frontend.ops.info.symbol_rate_max) || + (rate < state->frontend.ops.info.symbol_rate_min)) { + dprintk("%s() unsupported symbol_rate = %d\n", __func__, rate); + return -EOPNOTSUPP; + } + + state->dnxt.symbol_rate = rate; + dprintk("%s() symbol_rate = %d\n", __func__, rate); + + return 0; +} + +static int cx24116_load_firmware (struct dvb_frontend* fe, const struct firmware *fw); + +static int cx24116_firmware_ondemand(struct dvb_frontend* fe) +{ + struct cx24116_state *state = fe->demodulator_priv; + const struct firmware *fw; + int ret = 0; + + dprintk("%s()\n",__func__); + + if (cx24116_readreg(state, 0x20) > 0) + { + + if (state->skip_fw_load) + return 0; + + /* Load firmware */ + /* request the firmware, this will block until someone uploads it */ + printk("%s: Waiting for firmware upload (%s)...\n", __func__, CX24116_DEFAULT_FIRMWARE); + ret = request_firmware(&fw, CX24116_DEFAULT_FIRMWARE, &state->i2c->dev); + printk("%s: Waiting for firmware upload(2)...\n", __func__); + if (ret) { + printk("%s: No firmware uploaded (timeout or file not found?)\n", __func__); + return ret; + } + + /* Make sure we don't recurse back through here during loading */ + state->skip_fw_load = 1; + + ret = cx24116_load_firmware(fe, fw); + if (ret) + printk("%s: Writing firmware to device failed\n", __func__); + + release_firmware(fw); + + printk("%s: Firmware upload %s\n", __func__, ret == 0 ? "complete" : "failed"); + + /* Ensure firmware is always loaded if required */ + state->skip_fw_load = 0; + } + + return ret; +} + +/* Take a basic firmware command structure, format it and forward it for processing */ +static int cx24116_cmd_execute(struct dvb_frontend* fe, struct cx24116_cmd *cmd) +{ + struct cx24116_state *state = fe->demodulator_priv; + int i, ret; + + dprintk("%s()\n", __func__); + + /* Load the firmware if required */ + if ( (ret = cx24116_firmware_ondemand(fe)) != 0) + { + printk("%s(): Unable initialise the firmware\n", __func__); + return ret; + } + + /* Write the command */ + for(i = 0; i < cmd->len ; i++) + { + dprintk("%s: 0x%02x == 0x%02x\n", __func__, i, cmd->args[i]); + cx24116_writereg(state, i, cmd->args[i]); + } + + /* Start execution and wait for cmd to terminate */ + cx24116_writereg(state, CX24116_REG_EXECUTE, 0x01); + while( cx24116_readreg(state, CX24116_REG_EXECUTE) ) + { + msleep(10); + if(i++ > 64) + { + /* Avoid looping forever if the firmware does no respond */ + printk("%s() Firmware not responding\n", __func__); + return -EREMOTEIO; + } + } + return 0; +} + +static int cx24116_load_firmware (struct dvb_frontend* fe, const struct firmware *fw) +{ + struct cx24116_state* state = fe->demodulator_priv; + struct cx24116_cmd cmd; + int i, ret; + unsigned char vers[4]; + + dprintk("%s\n", __func__); + dprintk("Firmware is %zu bytes (%02x %02x .. %02x %02x)\n" + ,fw->size + ,fw->data[0] + ,fw->data[1] + ,fw->data[ fw->size-2 ] + ,fw->data[ fw->size-1 ] + ); + + /* Toggle 88x SRST pin to reset demod */ + if (state->config->reset_device) + state->config->reset_device(fe); + + /* Begin the firmware load process */ + /* Prepare the demod, load the firmware, cleanup after load */ + + /* Init PLL */ + cx24116_writereg(state, 0xE5, 0x00); + cx24116_writereg(state, 0xF1, 0x08); + cx24116_writereg(state, 0xF2, 0x13); + + /* Start PLL */ + cx24116_writereg(state, 0xe0, 0x03); + cx24116_writereg(state, 0xe0, 0x00); + + /* Unknown */ + cx24116_writereg(state, CX24116_REG_CLKDIV, 0x46); + cx24116_writereg(state, CX24116_REG_RATEDIV, 0x00); + + /* Unknown */ + cx24116_writereg(state, 0xF0, 0x03); + cx24116_writereg(state, 0xF4, 0x81); + cx24116_writereg(state, 0xF5, 0x00); + cx24116_writereg(state, 0xF6, 0x00); + + /* write the entire firmware as one transaction */ + cx24116_writeregN(state, 0xF7, fw->data, fw->size); + + cx24116_writereg(state, 0xF4, 0x10); + cx24116_writereg(state, 0xF0, 0x00); + cx24116_writereg(state, 0xF8, 0x06); + + /* Firmware CMD 10: VCO config */ + cmd.args[0x00] = CMD_SET_VCO; + cmd.args[0x01] = 0x05; + cmd.args[0x02] = 0xdc; + cmd.args[0x03] = 0xda; + cmd.args[0x04] = 0xae; + cmd.args[0x05] = 0xaa; + cmd.args[0x06] = 0x04; + cmd.args[0x07] = 0x9d; + cmd.args[0x08] = 0xfc; + cmd.args[0x09] = 0x06; + cmd.len= 0x0a; + ret = cx24116_cmd_execute(fe, &cmd); + if (ret != 0) + return ret; + + cx24116_writereg(state, CX24116_REG_SSTATUS, 0x00); + + /* Firmware CMD 14: Tuner config */ + cmd.args[0x00] = CMD_TUNERINIT; + cmd.args[0x01] = 0x00; + cmd.args[0x02] = 0x00; + cmd.len= 0x03; + ret = cx24116_cmd_execute(fe, &cmd); + if (ret != 0) + return ret; + + cx24116_writereg(state, 0xe5, 0x00); + + /* Firmware CMD 13: MPEG config */ + cmd.args[0x00] = CMD_MPEGCONFIG; + cmd.args[0x01] = 0x01; + cmd.args[0x02] = 0x75; + cmd.args[0x03] = 0x00; + if (state->config->mpg_clk_pos_pol) + cmd.args[0x04] = state->config->mpg_clk_pos_pol; + else + cmd.args[0x04] = 0x02; + cmd.args[0x05] = 0x00; + cmd.len= 0x06; + ret = cx24116_cmd_execute(fe, &cmd); + if (ret != 0) + return ret; + + /* Firmware CMD 35: Get firmware version */ + cmd.args[0x00] = CMD_UPDFWVERS; + cmd.len= 0x02; + for(i=0; i<4; i++) { + cmd.args[0x01] = i; + ret = cx24116_cmd_execute(fe, &cmd); + if (ret != 0) + return ret; + vers[i]= cx24116_readreg(state, CX24116_REG_MAILBOX); + } + printk("%s: FW version %i.%i.%i.%i\n", __func__, + vers[0], vers[1], vers[2], vers[3]); + + return 0; +} + +static int cx24116_set_voltage(struct dvb_frontend* fe, fe_sec_voltage_t voltage) +{ + /* The isl6421 module will override this function in the fops. */ + dprintk("%s() This should never appear if the isl6421 module is loaded correctly\n",__func__); + + return -EOPNOTSUPP; +} + +static int cx24116_read_status(struct dvb_frontend* fe, fe_status_t* status) +{ + struct cx24116_state *state = fe->demodulator_priv; + + int lock = cx24116_readreg(state, CX24116_REG_SSTATUS); + + dprintk("%s: status = 0x%02x\n", __func__, lock); + + *status = 0; + + if (lock & CX24116_HAS_SIGNAL) + *status |= FE_HAS_SIGNAL; + if (lock & CX24116_HAS_CARRIER) + *status |= FE_HAS_CARRIER; + if (lock & CX24116_HAS_VITERBI) + *status |= FE_HAS_VITERBI; + if (lock & CX24116_HAS_SYNCLOCK) + *status |= FE_HAS_SYNC | FE_HAS_LOCK; + + return 0; +} + +static int cx24116_read_ber(struct dvb_frontend* fe, u32* ber) +{ + struct cx24116_state *state = fe->demodulator_priv; + + dprintk("%s()\n", __func__); + + *ber = ( cx24116_readreg(state, CX24116_REG_BER24) << 24 ) | + ( cx24116_readreg(state, CX24116_REG_BER16) << 16 ) | + ( cx24116_readreg(state, CX24116_REG_BER8 ) << 8 ) | + cx24116_readreg(state, CX24116_REG_BER0 ); + + return 0; +} + +/* TODO Determine function and scale appropriately */ +static int cx24116_read_signal_strength(struct dvb_frontend* fe, u16* signal_strength) +{ + struct cx24116_state *state = fe->demodulator_priv; + struct cx24116_cmd cmd; + int ret; + u16 sig_reading; + + dprintk("%s()\n", __func__); + + /* Firmware CMD 19: Get AGC */ + cmd.args[0x00] = CMD_GETAGC; + cmd.len= 0x01; + ret = cx24116_cmd_execute(fe, &cmd); + if (ret != 0) + return ret; + + sig_reading = ( cx24116_readreg(state, CX24116_REG_SSTATUS) & CX24116_SIGNAL_MASK ) | + ( cx24116_readreg(state, CX24116_REG_SIGNAL) << 6 ); + *signal_strength= 0 - sig_reading; + + dprintk("%s: raw / cooked = 0x%04x / 0x%04x\n", __func__, sig_reading, *signal_strength); + + return 0; +} + +/* SNR (0..100)% = (sig & 0xf0) * 10 + (sig & 0x0f) * 10 / 16 */ +static int cx24116_read_snr_pct(struct dvb_frontend* fe, u16* snr) +{ + struct cx24116_state *state = fe->demodulator_priv; + u8 snr_reading; + static const u32 snr_tab[] = { /* 10 x Table (rounded up) */ + 0x00000,0x0199A,0x03333,0x04ccD,0x06667, + 0x08000,0x0999A,0x0b333,0x0cccD,0x0e667, + 0x10000,0x1199A,0x13333,0x14ccD,0x16667,0x18000 }; + + dprintk("%s()\n", __func__); + + snr_reading = cx24116_readreg(state, CX24116_REG_QUALITY0); + + if(snr_reading >= 0xa0 /* 100% */) + *snr = 0xffff; + else + *snr = snr_tab [ ( snr_reading & 0xf0 ) >> 4 ] + + ( snr_tab [ ( snr_reading & 0x0f ) ] >> 4 ); + + dprintk("%s: raw / cooked = 0x%02x / 0x%04x\n", __func__, + snr_reading, *snr); + + return 0; +} + +/* The reelbox patches show the value in the registers represents + * ESNO, from 0->30db (values 0->300). We provide this value by + * default. + */ +static int cx24116_read_snr_esno(struct dvb_frontend* fe, u16* snr) +{ + struct cx24116_state *state = fe->demodulator_priv; + + dprintk("%s()\n", __func__); + + *snr = cx24116_readreg(state, CX24116_REG_QUALITY8) << 8 | + cx24116_readreg(state, CX24116_REG_QUALITY0); + + dprintk("%s: raw 0x%04x\n", __func__, *snr); + + return 0; +} + +static int cx24116_read_snr(struct dvb_frontend* fe, u16* snr) +{ + if (esno_snr == 1) + return cx24116_read_snr_esno(fe, snr); + else + return cx24116_read_snr_pct(fe, snr); +} + +static int cx24116_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks) +{ + struct cx24116_state *state = fe->demodulator_priv; + + dprintk("%s()\n", __func__); + + *ucblocks = ( cx24116_readreg(state, CX24116_REG_UCB8) << 8 ) | + cx24116_readreg(state, CX24116_REG_UCB0); + + return 0; +} + +/* Overwrite the current tuning params, we are about to tune */ +static void cx24116_clone_params(struct dvb_frontend* fe) +{ + struct cx24116_state *state = fe->demodulator_priv; + memcpy(&state->dcur, &state->dnxt, sizeof(state->dcur)); +} + +/* Wait for LNB */ +static int cx24116_wait_for_lnb(struct dvb_frontend* fe) +{ + struct cx24116_state *state = fe->demodulator_priv; + int i; + + dprintk("%s() qstatus = 0x%02x\n", __func__, + cx24116_readreg(state, CX24116_REG_QSTATUS)); + + /* Wait for up to 300 ms */ + for(i = 0; i < 30 ; i++) { + if (cx24116_readreg(state, CX24116_REG_QSTATUS) & 0x20) + return 0; + msleep(10); + } + + dprintk("%s(): LNB not ready\n", __func__); + + return -ETIMEDOUT; /* -EBUSY ? */ +} + +static int cx24116_set_tone(struct dvb_frontend* fe, fe_sec_tone_mode_t tone) +{ + struct cx24116_cmd cmd; + int ret; + + dprintk("%s(%d)\n", __func__, tone); + if ( (tone != SEC_TONE_ON) && (tone != SEC_TONE_OFF) ) { + printk("%s: Invalid, tone=%d\n", __func__, tone); + return -EINVAL; + } + + /* Wait for LNB ready */ + ret = cx24116_wait_for_lnb(fe); + if(ret != 0) + return ret; + + /* Min delay time after DiSEqC send */ + msleep(15); /* XXX determine is FW does this, see send_diseqc/burst */ + + /* This is always done before the tone is set */ + cmd.args[0x00] = CMD_SET_TONEPRE; + cmd.args[0x01] = 0x00; + cmd.len= 0x02; + ret = cx24116_cmd_execute(fe, &cmd); + if (ret != 0) + return ret; + + /* Now we set the tone */ + cmd.args[0x00] = CMD_SET_TONE; + cmd.args[0x01] = 0x00; + cmd.args[0x02] = 0x00; + + switch (tone) { + case SEC_TONE_ON: + dprintk("%s: setting tone on\n", __func__); + cmd.args[0x03] = 0x01; + break; + case SEC_TONE_OFF: + dprintk("%s: setting tone off\n",__func__); + cmd.args[0x03] = 0x00; + break; + } + cmd.len= 0x04; + + /* Min delay time before DiSEqC send */ + msleep(15); /* XXX determine is FW does this, see send_diseqc/burst */ + + return cx24116_cmd_execute(fe, &cmd); +} + +/* Initialise DiSEqC */ +static int cx24116_diseqc_init(struct dvb_frontend* fe) +{ + struct cx24116_state *state = fe->demodulator_priv; + struct cx24116_cmd cmd; + int ret; + + /* Firmware CMD 20: LNB/DiSEqC config */ + cmd.args[0x00] = CMD_LNBCONFIG; + cmd.args[0x01] = 0x00; + cmd.args[0x02] = 0x10; + cmd.args[0x03] = 0x00; + cmd.args[0x04] = 0x8f; + cmd.args[0x05] = 0x28; + cmd.args[0x06] = (toneburst == CX24116_DISEQC_TONEOFF) ? 0x00 : 0x01; + cmd.args[0x07] = 0x01; + cmd.len= 0x08; + ret = cx24116_cmd_execute(fe, &cmd); + if (ret != 0) + return ret; + + /* Prepare a DiSEqC command */ + state->dsec_cmd.args[0x00] = CMD_LNBSEND; + + /* DiSEqC burst */ + state->dsec_cmd.args[CX24116_DISEQC_BURST] = CX24116_DISEQC_MINI_A; + + /* Unknown */ + state->dsec_cmd.args[CX24116_DISEQC_ARG2_2] = 0x02; + state->dsec_cmd.args[CX24116_DISEQC_ARG3_0] = 0x00; + state->dsec_cmd.args[CX24116_DISEQC_ARG4_0] = 0x00; /* Continuation flag? */ + + /* DiSEqC message length */ + state->dsec_cmd.args[CX24116_DISEQC_MSGLEN] = 0x00; + + /* Command length */ + state->dsec_cmd.len= CX24116_DISEQC_MSGOFS; + + return 0; +} + +/* Send DiSEqC message with derived burst (hack) || previous burst */ +static int cx24116_send_diseqc_msg(struct dvb_frontend* fe, struct dvb_diseqc_master_cmd *d) +{ + struct cx24116_state *state = fe->demodulator_priv; + int i, ret; + + /* Dump DiSEqC message */ + if (debug) { + printk("cx24116: %s(", __func__); + for(i = 0 ; i < d->msg_len ;) { + printk("0x%02x", d->msg[i]); + if(++i < d->msg_len) + printk(", "); + } + printk(") toneburst=%d\n", toneburst); + } + + /* Validate length */ + if(d->msg_len > (CX24116_ARGLEN - CX24116_DISEQC_MSGOFS)) + return -EINVAL; + + /* DiSEqC message */ + for (i = 0; i < d->msg_len; i++) + state->dsec_cmd.args[CX24116_DISEQC_MSGOFS + i] = d->msg[i]; + + /* DiSEqC message length */ + state->dsec_cmd.args[CX24116_DISEQC_MSGLEN] = d->msg_len; + + /* Command length */ + state->dsec_cmd.len= CX24116_DISEQC_MSGOFS + state->dsec_cmd.args[CX24116_DISEQC_MSGLEN]; + + /* DiSEqC toneburst */ + if(toneburst == CX24116_DISEQC_MESGCACHE) + /* Message is cached */ + return 0; + + else if(toneburst == CX24116_DISEQC_TONEOFF) + /* Message is sent without burst */ + state->dsec_cmd.args[CX24116_DISEQC_BURST] = 0; + + else if(toneburst == CX24116_DISEQC_TONECACHE) { + /* + * Message is sent with derived else cached burst + * + * WRITE PORT GROUP COMMAND 38 + * + * 0/A/A: E0 10 38 F0..F3 + * 1/B/B: E0 10 38 F4..F7 + * 2/C/A: E0 10 38 F8..FB + * 3/D/B: E0 10 38 FC..FF + * + * databyte[3]= 8421:8421 + * ABCD:WXYZ + * CLR :SET + * + * WX= PORT SELECT 0..3 (X=TONEBURST) + * Y = VOLTAGE (0=13V, 1=18V) + * Z = BAND (0=LOW, 1=HIGH(22K)) + */ + if(d->msg_len >= 4 && d->msg[2] == 0x38) + state->dsec_cmd.args[CX24116_DISEQC_BURST] = ((d->msg[3] & 4) >> 2); + if(debug) + dprintk("%s burst=%d\n", __func__, state->dsec_cmd.args[CX24116_DISEQC_BURST]); + } + + /* Wait for LNB ready */ + ret = cx24116_wait_for_lnb(fe); + if(ret != 0) + return ret; + + /* Wait for voltage/min repeat delay */ + msleep(100); + + /* Command */ + ret = cx24116_cmd_execute(fe, &state->dsec_cmd); + if(ret != 0) + return ret; + /* + * Wait for send + * + * Eutelsat spec: + * >15ms delay + (XXX determine if FW does this, see set_tone) + * 13.5ms per byte + + * >15ms delay + + * 12.5ms burst + + * >15ms delay (XXX determine if FW does this, see set_tone) + */ + msleep( (state->dsec_cmd.args[CX24116_DISEQC_MSGLEN] << 4) + ((toneburst == CX24116_DISEQC_TONEOFF) ? 30 : 60) ); + + return 0; +} + +/* Send DiSEqC burst */ +static int cx24116_diseqc_send_burst(struct dvb_frontend* fe, fe_sec_mini_cmd_t burst) +{ + struct cx24116_state *state = fe->demodulator_priv; + int ret; + + dprintk("%s(%d) toneburst=%d\n",__func__, burst, toneburst); + + /* DiSEqC burst */ + if (burst == SEC_MINI_A) + state->dsec_cmd.args[CX24116_DISEQC_BURST] = CX24116_DISEQC_MINI_A; + else if(burst == SEC_MINI_B) + state->dsec_cmd.args[CX24116_DISEQC_BURST] = CX24116_DISEQC_MINI_B; + else + return -EINVAL; + + /* DiSEqC toneburst */ + if(toneburst != CX24116_DISEQC_MESGCACHE) + /* Burst is cached */ + return 0; + + /* Burst is to be sent with cached message */ + + /* Wait for LNB ready */ + ret = cx24116_wait_for_lnb(fe); + if(ret != 0) + return ret; + + /* Wait for voltage/min repeat delay */ + msleep(100); + + /* Command */ + ret = cx24116_cmd_execute(fe, &state->dsec_cmd); + if(ret != 0) + return ret; + + /* + * Wait for send + * + * Eutelsat spec: + * >15ms delay + (XXX determine if FW does this, see set_tone) + * 13.5ms per byte + + * >15ms delay + + * 12.5ms burst + + * >15ms delay (XXX determine if FW does this, see set_tone) + */ + msleep( (state->dsec_cmd.args[CX24116_DISEQC_MSGLEN] << 4) + 60 ); + + return 0; +} + +static void cx24116_release(struct dvb_frontend* fe) +{ + struct cx24116_state* state = fe->demodulator_priv; + dprintk("%s\n",__func__); + kfree(state); +} + +static struct dvb_frontend_ops cx24116_ops; + +struct dvb_frontend* cx24116_attach(const struct cx24116_config* config, + struct i2c_adapter* i2c) +{ + struct cx24116_state* state = NULL; + int ret; + + dprintk("%s\n",__func__); + + /* allocate memory for the internal state */ + state = kmalloc(sizeof(struct cx24116_state), GFP_KERNEL); + if (state == NULL) { + printk("Unable to kmalloc\n"); + goto error1; + } + + /* setup the state */ + memset(state, 0, sizeof(struct cx24116_state)); + + state->config = config; + state->i2c = i2c; + + /* check if the demod is present */ + ret = (cx24116_readreg(state, 0xFF) << 8) | cx24116_readreg(state, 0xFE); + if (ret != 0x0501) { + printk("Invalid probe, probably not a CX24116 device\n"); + goto error2; + } + + /* create dvb_frontend */ + memcpy(&state->frontend.ops, &cx24116_ops, sizeof(struct dvb_frontend_ops)); + state->frontend.demodulator_priv = state; + return &state->frontend; + +error2: kfree(state); +error1: return NULL; +} +/* + * Initialise or wake up device + * + * Power config will reset and load initial firmware if required + */ +static int cx24116_initfe(struct dvb_frontend* fe) +{ + struct cx24116_state* state = fe->demodulator_priv; + struct cx24116_cmd cmd; + int ret; + + dprintk("%s()\n",__func__); + + /* Power on */ + cx24116_writereg(state, 0xe0, 0); + cx24116_writereg(state, 0xe1, 0); + cx24116_writereg(state, 0xea, 0); + + /* Firmware CMD 36: Power config */ + cmd.args[0x00] = CMD_TUNERSLEEP; + cmd.args[0x01] = 0; + cmd.len= 0x02; + ret = cx24116_cmd_execute(fe, &cmd); + if(ret != 0) + return ret; + + return cx24116_diseqc_init(fe); +} + +/* + * Put device to sleep + */ +static int cx24116_sleep(struct dvb_frontend* fe) +{ + struct cx24116_state* state = fe->demodulator_priv; + struct cx24116_cmd cmd; + int ret; + + dprintk("%s()\n",__func__); + + /* Firmware CMD 36: Power config */ + cmd.args[0x00] = CMD_TUNERSLEEP; + cmd.args[0x01] = 1; + cmd.len= 0x02; + ret = cx24116_cmd_execute(fe, &cmd); + if(ret != 0) + return ret; + + /* Power off (Shutdown clocks) */ + cx24116_writereg(state, 0xea, 0xff); + cx24116_writereg(state, 0xe1, 1); + cx24116_writereg(state, 0xe0, 1); + + return 0; +} + +static int cx24116_set_property(struct dvb_frontend *fe, struct dtv_property* tvp) +{ + dprintk("%s(..)\n", __func__); + return 0; +} + +static int cx24116_get_property(struct dvb_frontend *fe, struct dtv_property* tvp) +{ + dprintk("%s(..)\n", __func__); + return 0; +} + +/* dvb-core told us to tune, the tv property cache will be complete, + * it's safe for is to pull values and use them for tuning purposes. + */ +static int cx24116_set_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) +{ + struct cx24116_state *state = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct cx24116_cmd cmd; + fe_status_t tunerstat; + int i, status, ret, retune; + + dprintk("%s()\n",__func__); + + switch(c->delivery_system) { + case SYS_DVBS: + dprintk("%s: DVB-S delivery system selected\n",__func__); + + /* Only QPSK is supported for DVB-S */ + if(c->modulation != QPSK) { + dprintk("%s: unsupported modulation selected (%d)\n", + __func__, c->modulation); + return -EOPNOTSUPP; + } + + /* Pilot doesn't exist in DVB-S, turn bit off */ + state->dnxt.pilot_val = CX24116_PILOT_OFF; + retune = 1; + + /* DVB-S only supports 0.35 */ + if(c->rolloff != ROLLOFF_35) { + dprintk("%s: unsupported rolloff selected (%d)\n", + __func__, c->rolloff); + return -EOPNOTSUPP; + } + state->dnxt.rolloff_val = CX24116_ROLLOFF_035; + break; + + case SYS_DVBS2: + dprintk("%s: DVB-S2 delivery system selected\n",__func__); + + /* + * NBC 8PSK/QPSK with DVB-S is supported for DVB-S2, + * but not hardware auto detection + */ + if(c->modulation != PSK_8 && c->modulation != QPSK) { + dprintk("%s: unsupported modulation selected (%d)\n", + __func__, c->modulation); + return -EOPNOTSUPP; + } + + switch(c->pilot) { + case PILOT_AUTO: /* Not supported but emulated */ + retune = 2; /* Fall-through */ + case PILOT_OFF: + state->dnxt.pilot_val = CX24116_PILOT_OFF; + break; + case PILOT_ON: + state->dnxt.pilot_val = CX24116_PILOT_ON; + break; + default: + dprintk("%s: unsupported pilot mode selected (%d)\n", + __func__, c->pilot); + return -EOPNOTSUPP; + } + + switch(c->rolloff) { + case ROLLOFF_20: + state->dnxt.rolloff_val= CX24116_ROLLOFF_020; + break; + case ROLLOFF_25: + state->dnxt.rolloff_val= CX24116_ROLLOFF_025; + break; + case ROLLOFF_35: + state->dnxt.rolloff_val= CX24116_ROLLOFF_035; + break; + case ROLLOFF_AUTO: /* Rolloff must be explicit */ + default: + dprintk("%s: unsupported rolloff selected (%d)\n", + __func__, c->rolloff); + return -EOPNOTSUPP; + } + break; + + default: + dprintk("%s: unsupported delivery system selected (%d)\n", + __func__, c->delivery_system); + return -EOPNOTSUPP; + } + state->dnxt.modulation = c->modulation; + state->dnxt.frequency = c->frequency; + state->dnxt.pilot = c->pilot; + state->dnxt.rolloff = c->rolloff; + + if ((ret = cx24116_set_inversion(state, c->inversion)) != 0) + return ret; + + /* FEC_NONE/AUTO for DVB-S2 is not supported and detected here */ + if ((ret = cx24116_set_fec(state, c->modulation, c->fec_inner)) != 0) + return ret; + + if ((ret = cx24116_set_symbolrate(state, c->symbol_rate)) != 0) + return ret; + + /* discard the 'current' tuning parameters and prepare to tune */ + cx24116_clone_params(fe); + + dprintk("%s: modulation = %d\n", __func__, state->dcur.modulation); + dprintk("%s: frequency = %d\n", __func__, state->dcur.frequency); + dprintk("%s: pilot = %d (val = 0x%02x)\n", __func__, + state->dcur.pilot, state->dcur.pilot_val); + dprintk("%s: retune = %d\n", __func__, retune); + dprintk("%s: rolloff = %d (val = 0x%02x)\n", __func__, + state->dcur.rolloff, state->dcur.rolloff_val); + dprintk("%s: symbol_rate = %d\n", __func__, state->dcur.symbol_rate); + dprintk("%s: FEC = %d (mask/val = 0x%02x/0x%02x)\n", __func__, + state->dcur.fec, state->dcur.fec_mask, state->dcur.fec_val); + dprintk("%s: Inversion = %d (val = 0x%02x)\n", __func__, + state->dcur.inversion, state->dcur.inversion_val); + + /* This is also done in advise/acquire on HVR4000 but not on LITE */ + if (state->config->set_ts_params) + state->config->set_ts_params(fe, 0); + + /* Set/Reset B/W */ + cmd.args[0x00] = CMD_BANDWIDTH; + cmd.args[0x01] = 0x01; + cmd.len= 0x02; + ret = cx24116_cmd_execute(fe, &cmd); + if (ret != 0) + return ret; + + /* Prepare a tune request */ + cmd.args[0x00] = CMD_TUNEREQUEST; + + /* Frequency */ + cmd.args[0x01] = (state->dcur.frequency & 0xff0000) >> 16; + cmd.args[0x02] = (state->dcur.frequency & 0x00ff00) >> 8; + cmd.args[0x03] = (state->dcur.frequency & 0x0000ff); + + /* Symbol Rate */ + cmd.args[0x04] = ((state->dcur.symbol_rate / 1000) & 0xff00) >> 8; + cmd.args[0x05] = ((state->dcur.symbol_rate / 1000) & 0x00ff); + + /* Automatic Inversion */ + cmd.args[0x06] = state->dcur.inversion_val; + + /* Modulation / FEC / Pilot */ + cmd.args[0x07] = state->dcur.fec_val | state->dcur.pilot_val; + + cmd.args[0x08] = CX24116_SEARCH_RANGE_KHZ >> 8; + cmd.args[0x09] = CX24116_SEARCH_RANGE_KHZ & 0xff; + cmd.args[0x0a] = 0x00; + cmd.args[0x0b] = 0x00; + cmd.args[0x0c] = state->dcur.rolloff_val; + cmd.args[0x0d] = state->dcur.fec_mask; + + if (state->dcur.symbol_rate > 30000000) { + cmd.args[0x0e] = 0x04; + cmd.args[0x0f] = 0x00; + cmd.args[0x10] = 0x01; + cmd.args[0x11] = 0x77; + cmd.args[0x12] = 0x36; + cx24116_writereg(state, CX24116_REG_CLKDIV, 0x44); + cx24116_writereg(state, CX24116_REG_RATEDIV, 0x01); + } else { + cmd.args[0x0e] = 0x06; + cmd.args[0x0f] = 0x00; + cmd.args[0x10] = 0x00; + cmd.args[0x11] = 0xFA; + cmd.args[0x12] = 0x24; + cx24116_writereg(state, CX24116_REG_CLKDIV, 0x46); + cx24116_writereg(state, CX24116_REG_RATEDIV, 0x00); + } + + cmd.len= 0x13; + + /* We need to support pilot and non-pilot tuning in the + * driver automatically. This is a workaround for because + * the demod does not support autodetect. + */ + do { + /* Reset status register */ + status = cx24116_readreg(state, CX24116_REG_SSTATUS) & CX24116_SIGNAL_MASK; + cx24116_writereg(state, CX24116_REG_SSTATUS, status); + + /* Tune */ + ret = cx24116_cmd_execute(fe, &cmd); + if( ret != 0 ) + break; + + /* + * Wait for up to 500 ms before retrying + * + * If we are able to tune then generally it occurs within 100ms. + * If it takes longer, try a different toneburst setting. + */ + for(i = 0; i < 50 ; i++) { + cx24116_read_status(fe, &tunerstat); + status = tunerstat & (FE_HAS_SIGNAL | FE_HAS_SYNC); + if(status == (FE_HAS_SIGNAL | FE_HAS_SYNC)) { + dprintk("%s: Tuned\n",__func__); + goto tuned; + } + msleep(10); + } + + dprintk("%s: Not tuned\n",__func__); + + /* Toggle pilot bit when in auto-pilot */ + if(state->dcur.pilot == PILOT_AUTO) + cmd.args[0x07] ^= CX24116_PILOT_ON; + } + while(--retune); + +tuned: /* Set/Reset B/W */ + cmd.args[0x00] = CMD_BANDWIDTH; + cmd.args[0x01] = 0x00; + cmd.len= 0x02; + ret = cx24116_cmd_execute(fe, &cmd); + if (ret != 0) + return ret; + + return ret; +} + +static struct dvb_frontend_ops cx24116_ops = { + + .info = { + .name = "Conexant CX24116/CX24118", + .type = FE_QPSK, + .frequency_min = 950000, + .frequency_max = 2150000, + .frequency_stepsize = 1011, /* kHz for QPSK frontends */ + .frequency_tolerance = 5000, + .symbol_rate_min = 1000000, + .symbol_rate_max = 45000000, + .caps = FE_CAN_INVERSION_AUTO | + FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | + FE_CAN_FEC_4_5 | FE_CAN_FEC_5_6 | FE_CAN_FEC_6_7 | + FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_RECOVER + }, + + .release = cx24116_release, + + .init = cx24116_initfe, + .sleep = cx24116_sleep, + .read_status = cx24116_read_status, + .read_ber = cx24116_read_ber, + .read_signal_strength = cx24116_read_signal_strength, + .read_snr = cx24116_read_snr, + .read_ucblocks = cx24116_read_ucblocks, + .set_tone = cx24116_set_tone, + .set_voltage = cx24116_set_voltage, + .diseqc_send_master_cmd = cx24116_send_diseqc_msg, + .diseqc_send_burst = cx24116_diseqc_send_burst, + + .set_property = cx24116_set_property, + .get_property = cx24116_get_property, + .set_frontend = cx24116_set_frontend, +}; + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Activates frontend debugging (default:0)"); + +module_param(toneburst, int, 0644); +MODULE_PARM_DESC(toneburst, "DiSEqC toneburst 0=OFF, 1=TONE CACHE, 2=MESSAGE CACHE (default:1)"); + +module_param(esno_snr, int, 0644); +MODULE_PARM_DESC(debug, "SNR return units, 0=PERCENTAGE 0-100, 1=ESNO(db * 10) (default:0)"); + +MODULE_DESCRIPTION("DVB Frontend module for Conexant cx24116/cx24118 hardware"); +MODULE_AUTHOR("Steven Toth"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(cx24116_attach); diff --git a/drivers/media/dvb/frontends/cx24116.h b/drivers/media/dvb/frontends/cx24116.h new file mode 100644 index 000000000000..8dbcec268394 --- /dev/null +++ b/drivers/media/dvb/frontends/cx24116.h @@ -0,0 +1,53 @@ +/* + Conexant cx24116/cx24118 - DVBS/S2 Satellite demod/tuner driver + + Copyright (C) 2006 Steven Toth <stoth@linuxtv.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef CX24116_H +#define CX24116_H + +#include <linux/dvb/frontend.h> + +struct cx24116_config +{ + /* the demodulator's i2c address */ + u8 demod_address; + + /* Need to set device param for start_dma */ + int (*set_ts_params)(struct dvb_frontend* fe, int is_punctured); + + /* Need to reset device during firmware loading */ + int (*reset_device)(struct dvb_frontend* fe); + + /* Need to set MPEG parameters */ + u8 mpg_clk_pos_pol:0x02; +}; + +#if defined(CONFIG_DVB_CX24116) || defined(CONFIG_DVB_CX24116_MODULE) +extern struct dvb_frontend* cx24116_attach(const struct cx24116_config* config, + struct i2c_adapter* i2c); +#else +static inline struct dvb_frontend* cx24116_attach(const struct cx24116_config* config, + struct i2c_adapter* i2c) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __FUNCTION__); + return NULL; +} +#endif // CONFIG_DVB_CX24116 + +#endif /* CX24116_H */ diff --git a/drivers/media/dvb/frontends/dib0070.h b/drivers/media/dvb/frontends/dib0070.h index 3eedfdf505bc..21f2c5161af4 100644 --- a/drivers/media/dvb/frontends/dib0070.h +++ b/drivers/media/dvb/frontends/dib0070.h @@ -41,6 +41,7 @@ struct dib0070_config { extern struct dvb_frontend *dib0070_attach(struct dvb_frontend *fe, struct i2c_adapter *i2c, struct dib0070_config *cfg); +extern u16 dib0070_wbd_offset(struct dvb_frontend *); #else static inline struct dvb_frontend *dib0070_attach(struct dvb_frontend *fe, struct i2c_adapter *i2c, @@ -49,9 +50,14 @@ static inline struct dvb_frontend *dib0070_attach(struct dvb_frontend *fe, printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); return NULL; } + +static inline u16 dib0070_wbd_offset(struct dvb_frontend *fe) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return -ENODEV; +} #endif extern void dib0070_ctrl_agc_filter(struct dvb_frontend *, uint8_t open); -extern u16 dib0070_wbd_offset(struct dvb_frontend *); #endif diff --git a/drivers/media/dvb/frontends/dib7000m.c b/drivers/media/dvb/frontends/dib7000m.c index 5f1375e30dfc..0109720353bd 100644 --- a/drivers/media/dvb/frontends/dib7000m.c +++ b/drivers/media/dvb/frontends/dib7000m.c @@ -1284,7 +1284,10 @@ struct i2c_adapter * dib7000m_get_i2c_master(struct dvb_frontend *demod, enum di } EXPORT_SYMBOL(dib7000m_get_i2c_master); -int dib7000m_i2c_enumeration(struct i2c_adapter *i2c, int no_of_demods, u8 default_addr, struct dib7000m_config cfg[]) +#if 0 +/* used with some prototype boards */ +int dib7000m_i2c_enumeration(struct i2c_adapter *i2c, int no_of_demods, + u8 default_addr, struct dib7000m_config cfg[]) { struct dib7000m_state st = { .i2c_adap = i2c }; int k = 0; @@ -1329,6 +1332,7 @@ int dib7000m_i2c_enumeration(struct i2c_adapter *i2c, int no_of_demods, u8 defau return 0; } EXPORT_SYMBOL(dib7000m_i2c_enumeration); +#endif static struct dvb_frontend_ops dib7000m_ops; struct dvb_frontend * dib7000m_attach(struct i2c_adapter *i2c_adap, u8 i2c_addr, struct dib7000m_config *cfg) diff --git a/drivers/media/dvb/frontends/dib7000p.c b/drivers/media/dvb/frontends/dib7000p.c index 1a0142e0d741..8217e5b38f47 100644 --- a/drivers/media/dvb/frontends/dib7000p.c +++ b/drivers/media/dvb/frontends/dib7000p.c @@ -1333,7 +1333,8 @@ struct dvb_frontend * dib7000p_attach(struct i2c_adapter *i2c_adap, u8 i2c_addr, /* Ensure the output mode remains at the previous default if it's * not specifically set by the caller. */ - if (st->cfg.output_mode != OUTMODE_MPEG2_SERIAL) + if ((st->cfg.output_mode != OUTMODE_MPEG2_SERIAL) && + (st->cfg.output_mode != OUTMODE_MPEG2_PAR_GATED_CLK)) st->cfg.output_mode = OUTMODE_MPEG2_FIFO; demod = &st->demod; diff --git a/drivers/media/dvb/frontends/dib7000p.h b/drivers/media/dvb/frontends/dib7000p.h index 07c4d12ed5b7..3e8126857127 100644 --- a/drivers/media/dvb/frontends/dib7000p.h +++ b/drivers/media/dvb/frontends/dib7000p.h @@ -41,6 +41,14 @@ struct dib7000p_config { extern struct dvb_frontend *dib7000p_attach(struct i2c_adapter *i2c_adap, u8 i2c_addr, struct dib7000p_config *cfg); +extern struct i2c_adapter *dib7000p_get_i2c_master(struct dvb_frontend *, + enum dibx000_i2c_interface, + int); +extern int dib7000p_i2c_enumeration(struct i2c_adapter *i2c, + int no_of_demods, u8 default_addr, + struct dib7000p_config cfg[]); +extern int dib7000p_set_gpio(struct dvb_frontend *, u8 num, u8 dir, u8 val); +extern int dib7000p_set_wbd_ref(struct dvb_frontend *, u16 value); #else static inline struct dvb_frontend *dib7000p_attach(struct i2c_adapter *i2c_adap, u8 i2c_addr, @@ -49,13 +57,36 @@ static inline struct dvb_frontend *dib7000p_attach(struct i2c_adapter *i2c_adap, printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); return NULL; } -#endif -extern int dib7000p_i2c_enumeration(struct i2c_adapter *i2c, int no_of_demods, u8 default_addr, struct dib7000p_config cfg[]); +static inline +struct i2c_adapter *dib7000p_get_i2c_master(struct dvb_frontend *fe, + enum dibx000_i2c_interface i, int x) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} + +extern int dib7000p_i2c_enumeration(struct i2c_adapter *i2c, + int no_of_demods, u8 default_addr, + struct dib7000p_config cfg[]) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return -ENODEV; +} + +extern int dib7000p_set_gpio(struct dvb_frontend *fe, u8 num, u8 dir, u8 val) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return -ENODEV; +} + +extern int dib7000p_set_wbd_ref(struct dvb_frontend *fe, u16 value) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return -ENODEV; +} +#endif -extern struct i2c_adapter * dib7000p_get_i2c_master(struct dvb_frontend *, enum dibx000_i2c_interface, int); extern int dib7000pc_detection(struct i2c_adapter *i2c_adap); -extern int dib7000p_set_gpio(struct dvb_frontend *, u8 num, u8 dir, u8 val); -extern int dib7000p_set_wbd_ref(struct dvb_frontend *, u16 value); #endif diff --git a/drivers/media/dvb/frontends/drx397xD.c b/drivers/media/dvb/frontends/drx397xD.c index 3cbed874a6f8..b9ca5c8d2dd9 100644 --- a/drivers/media/dvb/frontends/drx397xD.c +++ b/drivers/media/dvb/frontends/drx397xD.c @@ -38,35 +38,32 @@ static const char mod_name[] = "drx397xD"; #define F_SET_0D0h 1 #define F_SET_0D4h 2 -typedef enum fw_ix { +enum fw_ix { #define _FW_ENTRY(a, b) b #include "drx397xD_fw.h" -} fw_ix_t; +}; /* chip specifics */ struct drx397xD_state { struct i2c_adapter *i2c; struct dvb_frontend frontend; struct drx397xD_config config; - fw_ix_t chip_rev; + enum fw_ix chip_rev; int flags; u32 bandwidth_parm; /* internal bandwidth conversions */ u32 f_osc; /* w90: actual osc frequency [Hz] */ }; -/******************************************************************************* - * Firmware - ******************************************************************************/ - +/* Firmware */ static const char *blob_name[] = { #define _BLOB_ENTRY(a, b) a #include "drx397xD_fw.h" }; -typedef enum blob_ix { +enum blob_ix { #define _BLOB_ENTRY(a, b) b #include "drx397xD_fw.h" -} blob_ix_t; +}; static struct { const char *name; @@ -85,7 +82,7 @@ static struct { }; /* use only with writer lock aquired */ -static void _drx_release_fw(struct drx397xD_state *s, fw_ix_t ix) +static void _drx_release_fw(struct drx397xD_state *s, enum fw_ix ix) { memset(&fw[ix].data[0], 0, sizeof(fw[0].data)); if (fw[ix].file) @@ -94,9 +91,9 @@ static void _drx_release_fw(struct drx397xD_state *s, fw_ix_t ix) static void drx_release_fw(struct drx397xD_state *s) { - fw_ix_t ix = s->chip_rev; + enum fw_ix ix = s->chip_rev; - pr_debug("%s\n", __FUNCTION__); + pr_debug("%s\n", __func__); write_lock(&fw[ix].lock); if (fw[ix].refcnt) { @@ -107,13 +104,13 @@ static void drx_release_fw(struct drx397xD_state *s) write_unlock(&fw[ix].lock); } -static int drx_load_fw(struct drx397xD_state *s, fw_ix_t ix) +static int drx_load_fw(struct drx397xD_state *s, enum fw_ix ix) { const u8 *data; size_t size, len; int i = 0, j, rc = -EINVAL; - pr_debug("%s\n", __FUNCTION__); + pr_debug("%s\n", __func__); if (ix < 0 || ix >= ARRAY_SIZE(fw)) return -EINVAL; @@ -175,32 +172,34 @@ static int drx_load_fw(struct drx397xD_state *s, fw_ix_t ix) goto exit_corrupt; } } while (i < size); - exit_corrupt: + +exit_corrupt: printk(KERN_ERR "%s: Firmware is corrupt\n", mod_name); - exit_err: +exit_err: _drx_release_fw(s, ix); fw[ix].refcnt--; - exit_ok: +exit_ok: fw[ix].refcnt++; write_unlock(&fw[ix].lock); + return rc; } -/******************************************************************************* - * i2c bus IO - ******************************************************************************/ - -static int write_fw(struct drx397xD_state *s, blob_ix_t ix) +/* i2c bus IO */ +static int write_fw(struct drx397xD_state *s, enum blob_ix ix) { - struct i2c_msg msg = {.addr = s->config.demod_address,.flags = 0 }; const u8 *data; int len, rc = 0, i = 0; + struct i2c_msg msg = { + .addr = s->config.demod_address, + .flags = 0 + }; if (ix < 0 || ix >= ARRAY_SIZE(blob_name)) { - pr_debug("%s drx_fw_ix_t out of range\n", __FUNCTION__); + pr_debug("%s drx_fw_ix_t out of range\n", __func__); return -EINVAL; } - pr_debug("%s %s\n", __FUNCTION__, blob_name[ix]); + pr_debug("%s %s\n", __func__, blob_name[ix]); read_lock(&fw[s->chip_rev].lock); data = fw[s->chip_rev].data[ix]; @@ -229,33 +228,33 @@ static int write_fw(struct drx397xD_state *s, blob_ix_t ix) goto exit_rc; } } - exit_rc: +exit_rc: read_unlock(&fw[s->chip_rev].lock); + return 0; } /* Function is not endian safe, use the RD16 wrapper below */ -static int _read16(struct drx397xD_state *s, u32 i2c_adr) +static int _read16(struct drx397xD_state *s, __le32 i2c_adr) { int rc; u8 a[4]; - u16 v; + __le16 v; struct i2c_msg msg[2] = { { - .addr = s->config.demod_address, - .flags = 0, - .buf = a, - .len = sizeof(a) - } - , { - .addr = s->config.demod_address, - .flags = I2C_M_RD, - .buf = (u8 *) & v, - .len = sizeof(v) - } + .addr = s->config.demod_address, + .flags = 0, + .buf = a, + .len = sizeof(a) + }, { + .addr = s->config.demod_address, + .flags = I2C_M_RD, + .buf = (u8 *)&v, + .len = sizeof(v) + } }; - *(u32 *) a = i2c_adr; + *(__le32 *) a = i2c_adr; rc = i2c_transfer(s->i2c, msg, 2); if (rc != 2) @@ -265,7 +264,7 @@ static int _read16(struct drx397xD_state *s, u32 i2c_adr) } /* Function is not endian safe, use the WR16.. wrappers below */ -static int _write16(struct drx397xD_state *s, u32 i2c_adr, u16 val) +static int _write16(struct drx397xD_state *s, __le32 i2c_adr, __le16 val) { u8 a[6]; int rc; @@ -276,28 +275,28 @@ static int _write16(struct drx397xD_state *s, u32 i2c_adr, u16 val) .len = sizeof(a) }; - *(u32 *) a = i2c_adr; - *(u16 *) & a[4] = val; + *(__le32 *)a = i2c_adr; + *(__le16 *)&a[4] = val; rc = i2c_transfer(s->i2c, &msg, 1); if (rc != 1) return -EIO; + return 0; } -#define WR16(ss,adr, val) \ +#define WR16(ss, adr, val) \ _write16(ss, I2C_ADR_C0(adr), cpu_to_le16(val)) -#define WR16_E0(ss,adr, val) \ +#define WR16_E0(ss, adr, val) \ _write16(ss, I2C_ADR_E0(adr), cpu_to_le16(val)) -#define RD16(ss,adr) \ +#define RD16(ss, adr) \ _read16(ss, I2C_ADR_C0(adr)) -#define EXIT_RC( cmd ) if ( (rc = (cmd)) < 0) goto exit_rc - -/******************************************************************************* - * Tuner callback - ******************************************************************************/ +#define EXIT_RC(cmd) \ + if ((rc = (cmd)) < 0) \ + goto exit_rc +/* Tuner callback */ static int PLL_Set(struct drx397xD_state *s, struct dvb_frontend_parameters *fep, int *df_tuner) { @@ -305,7 +304,7 @@ static int PLL_Set(struct drx397xD_state *s, u32 f_tuner, f = fep->frequency; int rc; - pr_debug("%s\n", __FUNCTION__); + pr_debug("%s\n", __func__); if ((f > s->frontend.ops.tuner_ops.info.frequency_max) || (f < s->frontend.ops.tuner_ops.info.frequency_min)) @@ -325,28 +324,26 @@ static int PLL_Set(struct drx397xD_state *s, return rc; *df_tuner = f_tuner - f; - pr_debug("%s requested %d [Hz] tuner %d [Hz]\n", __FUNCTION__, f, + pr_debug("%s requested %d [Hz] tuner %d [Hz]\n", __func__, f, f_tuner); return 0; } -/******************************************************************************* - * Demodulator helper functions - ******************************************************************************/ - +/* Demodulator helper functions */ static int SC_WaitForReady(struct drx397xD_state *s) { int cnt = 1000; int rc; - pr_debug("%s\n", __FUNCTION__); + pr_debug("%s\n", __func__); while (cnt--) { rc = RD16(s, 0x820043); if (rc == 0) return 0; } + return -1; } @@ -354,13 +351,14 @@ static int SC_SendCommand(struct drx397xD_state *s, int cmd) { int rc; - pr_debug("%s\n", __FUNCTION__); + pr_debug("%s\n", __func__); WR16(s, 0x820043, cmd); SC_WaitForReady(s); rc = RD16(s, 0x820042); if ((rc & 0xffff) == 0xffff) return -1; + return 0; } @@ -368,7 +366,7 @@ static int HI_Command(struct drx397xD_state *s, u16 cmd) { int rc, cnt = 1000; - pr_debug("%s\n", __FUNCTION__); + pr_debug("%s\n", __func__); rc = WR16(s, 0x420032, cmd); if (rc < 0) @@ -383,22 +381,24 @@ static int HI_Command(struct drx397xD_state *s, u16 cmd) if (rc < 0) return rc; } while (--cnt); + return rc; } static int HI_CfgCommand(struct drx397xD_state *s) { - pr_debug("%s\n", __FUNCTION__); + pr_debug("%s\n", __func__); WR16(s, 0x420033, 0x3973); - WR16(s, 0x420034, s->config.w50); // code 4, log 4 - WR16(s, 0x420035, s->config.w52); // code 15, log 9 + WR16(s, 0x420034, s->config.w50); /* code 4, log 4 */ + WR16(s, 0x420035, s->config.w52); /* code 15, log 9 */ WR16(s, 0x420036, s->config.demod_address << 1); - WR16(s, 0x420037, s->config.w56); // code (set_i2c ?? initX 1 ), log 1 -// WR16(s, 0x420033, 0x3973); + WR16(s, 0x420037, s->config.w56); /* code (set_i2c ?? initX 1 ), log 1 */ + /* WR16(s, 0x420033, 0x3973); */ if ((s->config.w56 & 8) == 0) return HI_Command(s, 3); + return WR16(s, 0x420032, 0x3); } @@ -419,7 +419,7 @@ static int SetCfgIfAgc(struct drx397xD_state *s, struct drx397xD_CfgIfAgc *agc) u16 w0C = agc->w0C; int quot, rem, i, rc = -EINVAL; - pr_debug("%s\n", __FUNCTION__); + pr_debug("%s\n", __func__); if (agc->w04 > 0x3ff) goto exit_rc; @@ -468,7 +468,7 @@ static int SetCfgIfAgc(struct drx397xD_state *s, struct drx397xD_CfgIfAgc *agc) i = slowIncrDecLUT_15272[rem / 28]; EXIT_RC(WR16(s, 0x0c2002b, i)); rc = WR16(s, 0x0c2002c, i); - exit_rc: +exit_rc: return rc; } @@ -478,7 +478,7 @@ static int SetCfgRfAgc(struct drx397xD_state *s, struct drx397xD_CfgRfAgc *agc) u16 w06 = agc->w06; int rc = -1; - pr_debug("%s %d 0x%x 0x%x\n", __FUNCTION__, agc->d00, w04, w06); + pr_debug("%s %d 0x%x 0x%x\n", __func__, agc->d00, w04, w06); if (w04 > 0x3ff) goto exit_rc; @@ -498,7 +498,7 @@ static int SetCfgRfAgc(struct drx397xD_state *s, struct drx397xD_CfgRfAgc *agc) rc &= ~2; break; case 0: - // loc_8000659 + /* loc_8000659 */ s->config.w9C &= ~2; EXIT_RC(WR16(s, 0x0c20015, s->config.w9C)); EXIT_RC(RD16(s, 0x0c20010)); @@ -522,7 +522,8 @@ static int SetCfgRfAgc(struct drx397xD_state *s, struct drx397xD_CfgRfAgc *agc) rc |= 2; } rc = WR16(s, 0x0c20013, rc); - exit_rc: + +exit_rc: return rc; } @@ -554,7 +555,7 @@ static int CorrectSysClockDeviation(struct drx397xD_state *s) int lockstat; u32 clk, clk_limit; - pr_debug("%s\n", __FUNCTION__); + pr_debug("%s\n", __func__); if (s->config.d5C == 0) { EXIT_RC(WR16(s, 0x08200e8, 0x010)); @@ -598,11 +599,12 @@ static int CorrectSysClockDeviation(struct drx397xD_state *s) if (clk - s->config.f_osc * 1000 + clk_limit <= 2 * clk_limit) { s->f_osc = clk; - pr_debug("%s: osc %d %d [Hz]\n", __FUNCTION__, + pr_debug("%s: osc %d %d [Hz]\n", __func__, s->config.f_osc * 1000, clk - s->config.f_osc * 1000); } rc = WR16(s, 0x08200e8, 0); - exit_rc: + +exit_rc: return rc; } @@ -610,7 +612,7 @@ static int ConfigureMPEGOutput(struct drx397xD_state *s, int type) { int rc, si, bp; - pr_debug("%s\n", __FUNCTION__); + pr_debug("%s\n", __func__); si = s->config.wA0; if (s->config.w98 == 0) { @@ -620,17 +622,17 @@ static int ConfigureMPEGOutput(struct drx397xD_state *s, int type) si &= ~1; bp = 0x200; } - if (s->config.w9A == 0) { + if (s->config.w9A == 0) si |= 0x80; - } else { + else si &= ~0x80; - } EXIT_RC(WR16(s, 0x2150045, 0)); EXIT_RC(WR16(s, 0x2150010, si)); EXIT_RC(WR16(s, 0x2150011, bp)); rc = WR16(s, 0x2150012, (type == 0 ? 0xfff : 0)); - exit_rc: + +exit_rc: return rc; } @@ -646,7 +648,7 @@ static int drx_tune(struct drx397xD_state *s, int rc, df_tuner; int a, b, c, d; - pr_debug("%s %d\n", __FUNCTION__, s->config.d60); + pr_debug("%s %d\n", __func__, s->config.d60); if (s->config.d60 != 2) goto set_tuner; @@ -658,7 +660,7 @@ static int drx_tune(struct drx397xD_state *s, rc = ConfigureMPEGOutput(s, 0); if (rc < 0) goto set_tuner; - set_tuner: +set_tuner: rc = PLL_Set(s, fep, &df_tuner); if (rc < 0) { @@ -835,16 +837,16 @@ static int drx_tune(struct drx397xD_state *s, rc = WR16(s, 0x2010012, 0); if (rc < 0) goto exit_rc; - // QPSK QAM16 QAM64 - ebx = 0x19f; // 62 - ebp = 0x1fb; // 15 - v20 = 0x16a; // 62 - v1E = 0x195; // 62 - v16 = 0x1bb; // 15 - v14 = 0x1ef; // 15 - v12 = 5; // 16 - v10 = 5; // 16 - v0E = 5; // 16 + /* QPSK QAM16 QAM64 */ + ebx = 0x19f; /* 62 */ + ebp = 0x1fb; /* 15 */ + v20 = 0x16a; /* 62 */ + v1E = 0x195; /* 62 */ + v16 = 0x1bb; /* 15 */ + v14 = 0x1ef; /* 15 */ + v12 = 5; /* 16 */ + v10 = 5; /* 16 */ + v0E = 5; /* 16 */ } switch (fep->u.ofdm.constellation) { @@ -997,17 +999,17 @@ static int drx_tune(struct drx397xD_state *s, case BANDWIDTH_8_MHZ: /* 0 */ case BANDWIDTH_AUTO: rc = WR16(s, 0x0c2003f, 0x32); - s->bandwidth_parm = ebx = 0x8b8249; // 9142857 + s->bandwidth_parm = ebx = 0x8b8249; edx = 0; break; case BANDWIDTH_7_MHZ: rc = WR16(s, 0x0c2003f, 0x3b); - s->bandwidth_parm = ebx = 0x7a1200; // 8000000 + s->bandwidth_parm = ebx = 0x7a1200; edx = 0x4807; break; case BANDWIDTH_6_MHZ: rc = WR16(s, 0x0c2003f, 0x47); - s->bandwidth_parm = ebx = 0x68a1b6; // 6857142 + s->bandwidth_parm = ebx = 0x68a1b6; edx = 0x0f07; break; }; @@ -1060,8 +1062,6 @@ static int drx_tune(struct drx397xD_state *s, WR16(s, 0x0820040, 1); SC_SendCommand(s, 1); -// rc = WR16(s, 0x2150000, 1); -// if (rc < 0) goto exit_rc; rc = WR16(s, 0x2150000, 2); rc = WR16(s, 0x2150016, a); @@ -1069,7 +1069,8 @@ static int drx_tune(struct drx397xD_state *s, rc = WR16(s, 0x2150036, 0); rc = WR16(s, 0x2150000, 1); s->config.d60 = 2; - exit_rc: + +exit_rc: return rc; } @@ -1082,7 +1083,7 @@ static int drx397x_init(struct dvb_frontend *fe) struct drx397xD_state *s = fe->demodulator_priv; int rc; - pr_debug("%s\n", __FUNCTION__); + pr_debug("%s\n", __func__); s->config.rfagc.d00 = 2; /* 0x7c */ s->config.rfagc.w04 = 0; @@ -1102,18 +1103,18 @@ static int drx397x_init(struct dvb_frontend *fe) /* HI_CfgCommand */ s->config.w50 = 4; - s->config.w52 = 9; // 0xf; + s->config.w52 = 9; - s->config.f_if = 42800000; /* d14: intermediate frequency [Hz] */ - s->config.f_osc = 48000; /* s66 : oscillator frequency [kHz] */ - s->config.w92 = 12000; // 20000; + s->config.f_if = 42800000; /* d14: intermediate frequency [Hz] */ + s->config.f_osc = 48000; /* s66 : oscillator frequency [kHz] */ + s->config.w92 = 12000; s->config.w9C = 0x000e; s->config.w9E = 0x0000; /* ConfigureMPEGOutput params */ s->config.wA0 = 4; - s->config.w98 = 1; // 0; + s->config.w98 = 1; s->config.w9A = 1; /* get chip revision */ @@ -1248,7 +1249,7 @@ static int drx397x_init(struct dvb_frontend *fe) rc = WR16(s, 0x0c20012, 1); } - write_DRXD_InitFE_1: +write_DRXD_InitFE_1: rc = write_fw(s, DRXD_InitFE_1); if (rc < 0) @@ -1311,7 +1312,8 @@ static int drx397x_init(struct dvb_frontend *fe) s->config.d5C = 0; s->config.d60 = 1; s->config.d48 = 1; - error: + +error: return rc; } @@ -1326,7 +1328,8 @@ static int drx397x_set_frontend(struct dvb_frontend *fe, { struct drx397xD_state *s = fe->demodulator_priv; - s->config.s20d24 = 1; // 0; + s->config.s20d24 = 1; + return drx_tune(s, params); } @@ -1337,18 +1340,16 @@ static int drx397x_get_tune_settings(struct dvb_frontend *fe, fe_tune_settings->min_delay_ms = 10000; fe_tune_settings->step_size = 0; fe_tune_settings->max_drift = 0; + return 0; } -static int drx397x_read_status(struct dvb_frontend *fe, fe_status_t * status) +static int drx397x_read_status(struct dvb_frontend *fe, fe_status_t *status) { struct drx397xD_state *s = fe->demodulator_priv; int lockstat; GetLockStatus(s, &lockstat); - /* TODO */ -// if (lockstat & 1) -// CorrectSysClockDeviation(s); *status = 0; if (lockstat & 2) { @@ -1356,9 +1357,8 @@ static int drx397x_read_status(struct dvb_frontend *fe, fe_status_t * status) ConfigureMPEGOutput(s, 1); *status = FE_HAS_LOCK | FE_HAS_SYNC | FE_HAS_VITERBI; } - if (lockstat & 4) { + if (lockstat & 4) *status |= FE_HAS_CARRIER | FE_HAS_SIGNAL; - } return 0; } @@ -1366,16 +1366,18 @@ static int drx397x_read_status(struct dvb_frontend *fe, fe_status_t * status) static int drx397x_read_ber(struct dvb_frontend *fe, unsigned int *ber) { *ber = 0; + return 0; } -static int drx397x_read_snr(struct dvb_frontend *fe, u16 * snr) +static int drx397x_read_snr(struct dvb_frontend *fe, u16 *snr) { *snr = 0; + return 0; } -static int drx397x_read_signal_strength(struct dvb_frontend *fe, u16 * strength) +static int drx397x_read_signal_strength(struct dvb_frontend *fe, u16 *strength) { struct drx397xD_state *s = fe->demodulator_priv; int rc; @@ -1401,6 +1403,7 @@ static int drx397x_read_signal_strength(struct dvb_frontend *fe, u16 * strength) * The following does the same but with less rounding errors: */ *strength = ~(7720 + (rc * 30744 >> 10)); + return 0; } @@ -1408,6 +1411,7 @@ static int drx397x_read_ucblocks(struct dvb_frontend *fe, unsigned int *ucblocks) { *ucblocks = 0; + return 0; } @@ -1436,22 +1440,22 @@ static struct dvb_frontend_ops drx397x_ops = { .frequency_max = 855250000, .frequency_stepsize = 166667, .frequency_tolerance = 0, - .caps = /* 0x0C01B2EAE */ - FE_CAN_FEC_1_2 | // = 0x2, - FE_CAN_FEC_2_3 | // = 0x4, - FE_CAN_FEC_3_4 | // = 0x8, - FE_CAN_FEC_5_6 | // = 0x20, - FE_CAN_FEC_7_8 | // = 0x80, - FE_CAN_FEC_AUTO | // = 0x200, - FE_CAN_QPSK | // = 0x400, - FE_CAN_QAM_16 | // = 0x800, - FE_CAN_QAM_64 | // = 0x2000, - FE_CAN_QAM_AUTO | // = 0x10000, - FE_CAN_TRANSMISSION_MODE_AUTO | // = 0x20000, - FE_CAN_GUARD_INTERVAL_AUTO | // = 0x80000, - FE_CAN_HIERARCHY_AUTO | // = 0x100000, - FE_CAN_RECOVER | // = 0x40000000, - FE_CAN_MUTE_TS // = 0x80000000 + .caps = /* 0x0C01B2EAE */ + FE_CAN_FEC_1_2 | /* = 0x2, */ + FE_CAN_FEC_2_3 | /* = 0x4, */ + FE_CAN_FEC_3_4 | /* = 0x8, */ + FE_CAN_FEC_5_6 | /* = 0x20, */ + FE_CAN_FEC_7_8 | /* = 0x80, */ + FE_CAN_FEC_AUTO | /* = 0x200, */ + FE_CAN_QPSK | /* = 0x400, */ + FE_CAN_QAM_16 | /* = 0x800, */ + FE_CAN_QAM_64 | /* = 0x2000, */ + FE_CAN_QAM_AUTO | /* = 0x10000, */ + FE_CAN_TRANSMISSION_MODE_AUTO | /* = 0x20000, */ + FE_CAN_GUARD_INTERVAL_AUTO | /* = 0x80000, */ + FE_CAN_HIERARCHY_AUTO | /* = 0x100000, */ + FE_CAN_RECOVER | /* = 0x40000000, */ + FE_CAN_MUTE_TS /* = 0x80000000 */ }, .release = drx397x_release, @@ -1472,33 +1476,35 @@ static struct dvb_frontend_ops drx397x_ops = { struct dvb_frontend *drx397xD_attach(const struct drx397xD_config *config, struct i2c_adapter *i2c) { - struct drx397xD_state *s = NULL; + struct drx397xD_state *state; /* allocate memory for the internal state */ - s = kzalloc(sizeof(struct drx397xD_state), GFP_KERNEL); - if (s == NULL) + state = kzalloc(sizeof(struct drx397xD_state), GFP_KERNEL); + if (!state) goto error; /* setup the state */ - s->i2c = i2c; - memcpy(&s->config, config, sizeof(struct drx397xD_config)); + state->i2c = i2c; + memcpy(&state->config, config, sizeof(struct drx397xD_config)); /* check if the demod is there */ - if (RD16(s, 0x2410019) < 0) + if (RD16(state, 0x2410019) < 0) goto error; /* create dvb_frontend */ - memcpy(&s->frontend.ops, &drx397x_ops, sizeof(struct dvb_frontend_ops)); - s->frontend.demodulator_priv = s; + memcpy(&state->frontend.ops, &drx397x_ops, + sizeof(struct dvb_frontend_ops)); + state->frontend.demodulator_priv = state; + + return &state->frontend; +error: + kfree(state); - return &s->frontend; - error: - kfree(s); return NULL; } +EXPORT_SYMBOL(drx397xD_attach); MODULE_DESCRIPTION("Micronas DRX397xD DVB-T Frontend"); MODULE_AUTHOR("Henk Vergonet"); MODULE_LICENSE("GPL"); -EXPORT_SYMBOL(drx397xD_attach); diff --git a/drivers/media/dvb/frontends/drx397xD.h b/drivers/media/dvb/frontends/drx397xD.h index ddc7a07971b7..ba05d17290c6 100644 --- a/drivers/media/dvb/frontends/drx397xD.h +++ b/drivers/media/dvb/frontends/drx397xD.h @@ -28,7 +28,7 @@ #define DRX_F_OFFSET 36000000 #define I2C_ADR_C0(x) \ -( (u32)cpu_to_le32( \ +( cpu_to_le32( \ (u32)( \ (((u32)(x) & (u32)0x000000ffUL) ) | \ (((u32)(x) & (u32)0x0000ff00UL) << 16) | \ @@ -38,7 +38,7 @@ ) #define I2C_ADR_E0(x) \ -( (u32)cpu_to_le32( \ +( cpu_to_le32( \ (u32)( \ (((u32)(x) & (u32)0x000000ffUL) ) | \ (((u32)(x) & (u32)0x0000ff00UL) << 16) | \ @@ -122,7 +122,7 @@ extern struct dvb_frontend* drx397xD_attach(const struct drx397xD_config *config static inline struct dvb_frontend* drx397xD_attach(const struct drx397xD_config *config, struct i2c_adapter *i2c) { - printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __FUNCTION__); + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); return NULL; } #endif /* CONFIG_DVB_DRX397XD */ diff --git a/drivers/media/dvb/frontends/dvb_dummy_fe.c b/drivers/media/dvb/frontends/dvb_dummy_fe.c index fed09dfb2b7c..db8a937cc630 100644 --- a/drivers/media/dvb/frontends/dvb_dummy_fe.c +++ b/drivers/media/dvb/frontends/dvb_dummy_fe.c @@ -75,9 +75,10 @@ static int dvb_dummy_fe_get_frontend(struct dvb_frontend* fe, struct dvb_fronten static int dvb_dummy_fe_set_frontend(struct dvb_frontend* fe, struct dvb_frontend_parameters *p) { - if (fe->ops->tuner_ops->set_params) { - fe->ops->tuner_ops->set_params(fe, p); - if (fe->ops->i2c_gate_ctrl) fe->ops->i2c_gate_ctrl(fe, 0); + if (fe->ops.tuner_ops.set_params) { + fe->ops.tuner_ops.set_params(fe, p); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); } return 0; @@ -131,7 +132,7 @@ error: static struct dvb_frontend_ops dvb_dummy_fe_qpsk_ops; -struct dvb_frontend* dvb_dummy_fe_qpsk_attach() +struct dvb_frontend *dvb_dummy_fe_qpsk_attach(void) { struct dvb_dummy_fe_state* state = NULL; @@ -151,7 +152,7 @@ error: static struct dvb_frontend_ops dvb_dummy_fe_qam_ops; -struct dvb_frontend* dvb_dummy_fe_qam_attach() +struct dvb_frontend *dvb_dummy_fe_qam_attach(void) { struct dvb_dummy_fe_state* state = NULL; diff --git a/drivers/media/dvb/frontends/eds1547.h b/drivers/media/dvb/frontends/eds1547.h new file mode 100644 index 000000000000..fa79b7c83dd2 --- /dev/null +++ b/drivers/media/dvb/frontends/eds1547.h @@ -0,0 +1,133 @@ +/* eds1547.h Earda EDS-1547 tuner support +* +* Copyright (C) 2008 Igor M. Liplianin (liplianin@me.by) +* +* This program is free software; you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the +* Free Software Foundation, version 2. +* +* see Documentation/dvb/README.dvb-usb for more information +*/ + +#ifndef EDS1547 +#define EDS1547 + +static u8 stv0288_earda_inittab[] = { + 0x01, 0x57, + 0x02, 0x20, + 0x03, 0x8e, + 0x04, 0x8e, + 0x05, 0x12, + 0x06, 0x00, + 0x07, 0x00, + 0x09, 0x00, + 0x0a, 0x04, + 0x0b, 0x00, + 0x0c, 0x00, + 0x0d, 0x00, + 0x0e, 0xd4, + 0x0f, 0x30, + 0x11, 0x44, + 0x12, 0x03, + 0x13, 0x48, + 0x14, 0x84, + 0x15, 0x45, + 0x16, 0xb7, + 0x17, 0x9c, + 0x18, 0x00, + 0x19, 0xa6, + 0x1a, 0x88, + 0x1b, 0x8f, + 0x1c, 0xf0, + 0x20, 0x0b, + 0x21, 0x54, + 0x22, 0x00, + 0x23, 0x00, + 0x2b, 0xff, + 0x2c, 0xf7, + 0x30, 0x00, + 0x31, 0x1e, + 0x32, 0x14, + 0x33, 0x0f, + 0x34, 0x09, + 0x35, 0x0c, + 0x36, 0x05, + 0x37, 0x2f, + 0x38, 0x16, + 0x39, 0xbd, + 0x3a, 0x00, + 0x3b, 0x13, + 0x3c, 0x11, + 0x3d, 0x30, + 0x40, 0x63, + 0x41, 0x04, + 0x42, 0x60, + 0x43, 0x00, + 0x44, 0x00, + 0x45, 0x00, + 0x46, 0x00, + 0x47, 0x00, + 0x4a, 0x00, + 0x50, 0x10, + 0x51, 0x36, + 0x52, 0x09, + 0x53, 0x94, + 0x54, 0x62, + 0x55, 0x29, + 0x56, 0x64, + 0x57, 0x2b, + 0x58, 0x54, + 0x59, 0x86, + 0x5a, 0x00, + 0x5b, 0x9b, + 0x5c, 0x08, + 0x5d, 0x7f, + 0x5e, 0x00, + 0x5f, 0xff, + 0x70, 0x00, + 0x71, 0x00, + 0x72, 0x00, + 0x74, 0x00, + 0x75, 0x00, + 0x76, 0x00, + 0x81, 0x00, + 0x82, 0x3f, + 0x83, 0x3f, + 0x84, 0x00, + 0x85, 0x00, + 0x88, 0x00, + 0x89, 0x00, + 0x8a, 0x00, + 0x8b, 0x00, + 0x8c, 0x00, + 0x90, 0x00, + 0x91, 0x00, + 0x92, 0x00, + 0x93, 0x00, + 0x94, 0x1c, + 0x97, 0x00, + 0xa0, 0x48, + 0xa1, 0x00, + 0xb0, 0xb8, + 0xb1, 0x3a, + 0xb2, 0x10, + 0xb3, 0x82, + 0xb4, 0x80, + 0xb5, 0x82, + 0xb6, 0x82, + 0xb7, 0x82, + 0xb8, 0x20, + 0xb9, 0x00, + 0xf0, 0x00, + 0xf1, 0x00, + 0xf2, 0xc0, + 0xff,0xff, +}; + +static struct stv0288_config earda_config = { + .demod_address = 0x68, + .min_delay_ms = 100, + .inittab = stv0288_earda_inittab, +}; + +#endif diff --git a/drivers/media/dvb/frontends/lgs8gl5.c b/drivers/media/dvb/frontends/lgs8gl5.c new file mode 100644 index 000000000000..855852fddf22 --- /dev/null +++ b/drivers/media/dvb/frontends/lgs8gl5.c @@ -0,0 +1,454 @@ +/* + Legend Silicon LGS-8GL5 DMB-TH OFDM demodulator driver + + Copyright (C) 2008 Sirius International (Hong Kong) Limited + Timothy Lee <timothy.lee@siriushk.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/slab.h> +#include "dvb_frontend.h" +#include "lgs8gl5.h" + + +#define REG_RESET 0x02 +#define REG_RESET_OFF 0x01 +#define REG_03 0x03 +#define REG_04 0x04 +#define REG_07 0x07 +#define REG_09 0x09 +#define REG_0A 0x0a +#define REG_0B 0x0b +#define REG_0C 0x0c +#define REG_37 0x37 +#define REG_STRENGTH 0x4b +#define REG_STRENGTH_MASK 0x7f +#define REG_STRENGTH_CARRIER 0x80 +#define REG_INVERSION 0x7c +#define REG_INVERSION_ON 0x80 +#define REG_7D 0x7d +#define REG_7E 0x7e +#define REG_A2 0xa2 +#define REG_STATUS 0xa4 +#define REG_STATUS_SYNC 0x04 +#define REG_STATUS_LOCK 0x01 + + +struct lgs8gl5_state { + struct i2c_adapter *i2c; + const struct lgs8gl5_config *config; + struct dvb_frontend frontend; +}; + + +static int debug; +#define dprintk(args...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG "lgs8gl5: " args); \ + } while (0) + + +/* Writes into demod's register */ +static int +lgs8gl5_write_reg(struct lgs8gl5_state *state, u8 reg, u8 data) +{ + int ret; + u8 buf[] = {reg, data}; + struct i2c_msg msg = { + .addr = state->config->demod_address, + .flags = 0, + .buf = buf, + .len = 2 + }; + + ret = i2c_transfer(state->i2c, &msg, 1); + if (ret != 1) + dprintk("%s: error (reg=0x%02x, val=0x%02x, ret=%i)\n", + __func__, reg, data, ret); + return (ret != 1) ? -1 : 0; +} + + +/* Reads from demod's register */ +static int +lgs8gl5_read_reg(struct lgs8gl5_state *state, u8 reg) +{ + int ret; + u8 b0[] = {reg}; + u8 b1[] = {0}; + struct i2c_msg msg[2] = { + { + .addr = state->config->demod_address, + .flags = 0, + .buf = b0, + .len = 1 + }, + { + .addr = state->config->demod_address, + .flags = I2C_M_RD, + .buf = b1, + .len = 1 + } + }; + + ret = i2c_transfer(state->i2c, msg, 2); + if (ret != 2) + return -EIO; + + return b1[0]; +} + + +static int +lgs8gl5_update_reg(struct lgs8gl5_state *state, u8 reg, u8 data) +{ + lgs8gl5_read_reg(state, reg); + lgs8gl5_write_reg(state, reg, data); + return 0; +} + + +/* Writes into alternate device's register */ +/* TODO: Find out what that device is for! */ +static int +lgs8gl5_update_alt_reg(struct lgs8gl5_state *state, u8 reg, u8 data) +{ + int ret; + u8 b0[] = {reg}; + u8 b1[] = {0}; + u8 b2[] = {reg, data}; + struct i2c_msg msg[3] = { + { + .addr = state->config->demod_address + 2, + .flags = 0, + .buf = b0, + .len = 1 + }, + { + .addr = state->config->demod_address + 2, + .flags = I2C_M_RD, + .buf = b1, + .len = 1 + }, + { + .addr = state->config->demod_address + 2, + .flags = 0, + .buf = b2, + .len = 2 + }, + }; + + ret = i2c_transfer(state->i2c, msg, 3); + return (ret != 3) ? -1 : 0; +} + + +static void +lgs8gl5_soft_reset(struct lgs8gl5_state *state) +{ + u8 val; + + dprintk("%s\n", __func__); + + val = lgs8gl5_read_reg(state, REG_RESET); + lgs8gl5_write_reg(state, REG_RESET, val & ~REG_RESET_OFF); + lgs8gl5_write_reg(state, REG_RESET, val | REG_RESET_OFF); + msleep(5); +} + + +/* Starts demodulation */ +static void +lgs8gl5_start_demod(struct lgs8gl5_state *state) +{ + u8 val; + int n; + + dprintk("%s\n", __func__); + + lgs8gl5_update_alt_reg(state, 0xc2, 0x28); + lgs8gl5_soft_reset(state); + lgs8gl5_update_reg(state, REG_07, 0x10); + lgs8gl5_update_reg(state, REG_07, 0x10); + lgs8gl5_write_reg(state, REG_09, 0x0e); + lgs8gl5_write_reg(state, REG_0A, 0xe5); + lgs8gl5_write_reg(state, REG_0B, 0x35); + lgs8gl5_write_reg(state, REG_0C, 0x30); + + lgs8gl5_update_reg(state, REG_03, 0x00); + lgs8gl5_update_reg(state, REG_7E, 0x01); + lgs8gl5_update_alt_reg(state, 0xc5, 0x00); + lgs8gl5_update_reg(state, REG_04, 0x02); + lgs8gl5_update_reg(state, REG_37, 0x01); + lgs8gl5_soft_reset(state); + + /* Wait for carrier */ + for (n = 0; n < 10; n++) { + val = lgs8gl5_read_reg(state, REG_STRENGTH); + dprintk("Wait for carrier[%d] 0x%02X\n", n, val); + if (val & REG_STRENGTH_CARRIER) + break; + msleep(4); + } + if (!(val & REG_STRENGTH_CARRIER)) + return; + + /* Wait for lock */ + for (n = 0; n < 20; n++) { + val = lgs8gl5_read_reg(state, REG_STATUS); + dprintk("Wait for lock[%d] 0x%02X\n", n, val); + if (val & REG_STATUS_LOCK) + break; + msleep(12); + } + if (!(val & REG_STATUS_LOCK)) + return; + + lgs8gl5_write_reg(state, REG_7D, lgs8gl5_read_reg(state, REG_A2)); + lgs8gl5_soft_reset(state); +} + + +static int +lgs8gl5_init(struct dvb_frontend *fe) +{ + struct lgs8gl5_state *state = fe->demodulator_priv; + + dprintk("%s\n", __func__); + + lgs8gl5_update_alt_reg(state, 0xc2, 0x28); + lgs8gl5_soft_reset(state); + lgs8gl5_update_reg(state, REG_07, 0x10); + lgs8gl5_update_reg(state, REG_07, 0x10); + lgs8gl5_write_reg(state, REG_09, 0x0e); + lgs8gl5_write_reg(state, REG_0A, 0xe5); + lgs8gl5_write_reg(state, REG_0B, 0x35); + lgs8gl5_write_reg(state, REG_0C, 0x30); + + return 0; +} + + +static int +lgs8gl5_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct lgs8gl5_state *state = fe->demodulator_priv; + u8 level = lgs8gl5_read_reg(state, REG_STRENGTH); + u8 flags = lgs8gl5_read_reg(state, REG_STATUS); + + *status = 0; + + if ((level & REG_STRENGTH_MASK) > 0) + *status |= FE_HAS_SIGNAL; + if (level & REG_STRENGTH_CARRIER) + *status |= FE_HAS_CARRIER; + if (flags & REG_STATUS_SYNC) + *status |= FE_HAS_SYNC; + if (flags & REG_STATUS_LOCK) + *status |= FE_HAS_LOCK; + + return 0; +} + + +static int +lgs8gl5_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + *ber = 0; + + return 0; +} + + +static int +lgs8gl5_read_signal_strength(struct dvb_frontend *fe, u16 *signal_strength) +{ + struct lgs8gl5_state *state = fe->demodulator_priv; + u8 level = lgs8gl5_read_reg(state, REG_STRENGTH); + *signal_strength = (level & REG_STRENGTH_MASK) << 8; + + return 0; +} + + +static int +lgs8gl5_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct lgs8gl5_state *state = fe->demodulator_priv; + u8 level = lgs8gl5_read_reg(state, REG_STRENGTH); + *snr = (level & REG_STRENGTH_MASK) << 8; + + return 0; +} + + +static int +lgs8gl5_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + *ucblocks = 0; + + return 0; +} + + +static int +lgs8gl5_set_frontend(struct dvb_frontend *fe, + struct dvb_frontend_parameters *p) +{ + struct lgs8gl5_state *state = fe->demodulator_priv; + + dprintk("%s\n", __func__); + + if (p->u.ofdm.bandwidth != BANDWIDTH_8_MHZ) + return -EINVAL; + + if (fe->ops.tuner_ops.set_params) { + fe->ops.tuner_ops.set_params(fe, p); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + } + + /* lgs8gl5_set_inversion(state, p->inversion); */ + + lgs8gl5_start_demod(state); + + return 0; +} + + +static int +lgs8gl5_get_frontend(struct dvb_frontend *fe, + struct dvb_frontend_parameters *p) +{ + struct lgs8gl5_state *state = fe->demodulator_priv; + u8 inv = lgs8gl5_read_reg(state, REG_INVERSION); + struct dvb_ofdm_parameters *o = &p->u.ofdm; + + p->inversion = (inv & REG_INVERSION_ON) ? INVERSION_ON : INVERSION_OFF; + + o->code_rate_HP = FEC_1_2; + o->code_rate_LP = FEC_7_8; + o->guard_interval = GUARD_INTERVAL_1_32; + o->transmission_mode = TRANSMISSION_MODE_2K; + o->constellation = QAM_64; + o->hierarchy_information = HIERARCHY_NONE; + o->bandwidth = BANDWIDTH_8_MHZ; + + return 0; +} + + +static int +lgs8gl5_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *fesettings) +{ + fesettings->min_delay_ms = 240; + fesettings->step_size = 0; + fesettings->max_drift = 0; + return 0; +} + + +static void +lgs8gl5_release(struct dvb_frontend *fe) +{ + struct lgs8gl5_state *state = fe->demodulator_priv; + kfree(state); +} + + +static struct dvb_frontend_ops lgs8gl5_ops; + + +struct dvb_frontend* +lgs8gl5_attach(const struct lgs8gl5_config *config, struct i2c_adapter *i2c) +{ + struct lgs8gl5_state *state = NULL; + + dprintk("%s\n", __func__); + + /* Allocate memory for the internal state */ + state = kmalloc(sizeof(struct lgs8gl5_state), GFP_KERNEL); + if (state == NULL) + goto error; + + /* Setup the state */ + state->config = config; + state->i2c = i2c; + + /* Check if the demod is there */ + if (lgs8gl5_read_reg(state, REG_RESET) < 0) + goto error; + + /* Create dvb_frontend */ + memcpy(&state->frontend.ops, &lgs8gl5_ops, + sizeof(struct dvb_frontend_ops)); + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} +EXPORT_SYMBOL(lgs8gl5_attach); + + +static struct dvb_frontend_ops lgs8gl5_ops = { + .info = { + .name = "Legend Silicon LGS-8GL5 DMB-TH", + .type = FE_OFDM, + .frequency_min = 474000000, + .frequency_max = 858000000, + .frequency_stepsize = 10000, + .frequency_tolerance = 0, + .caps = FE_CAN_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_32 | + FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_BANDWIDTH_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_HIERARCHY_AUTO | + FE_CAN_RECOVER + }, + + .release = lgs8gl5_release, + + .init = lgs8gl5_init, + + .set_frontend = lgs8gl5_set_frontend, + .get_frontend = lgs8gl5_get_frontend, + .get_tune_settings = lgs8gl5_get_tune_settings, + + .read_status = lgs8gl5_read_status, + .read_ber = lgs8gl5_read_ber, + .read_signal_strength = lgs8gl5_read_signal_strength, + .read_snr = lgs8gl5_read_snr, + .read_ucblocks = lgs8gl5_read_ucblocks, +}; + + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("Legend Silicon LGS-8GL5 DMB-TH Demodulator driver"); +MODULE_AUTHOR("Timothy Lee"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/lgs8gl5.h b/drivers/media/dvb/frontends/lgs8gl5.h new file mode 100644 index 000000000000..d14176787a7d --- /dev/null +++ b/drivers/media/dvb/frontends/lgs8gl5.h @@ -0,0 +1,45 @@ +/* + Legend Silicon LGS-8GL5 DMB-TH OFDM demodulator driver + + Copyright (C) 2008 Sirius International (Hong Kong) Limited + Timothy Lee <timothy.lee@siriushk.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef LGS8GL5_H +#define LGS8GL5_H + +#include <linux/dvb/frontend.h> + +struct lgs8gl5_config { + /* the demodulator's i2c address */ + u8 demod_address; +}; + +#if defined(CONFIG_DVB_LGS8GL5) || \ + (defined(CONFIG_DVB_LGS8GL5_MODULE) && defined(MODULE)) +extern struct dvb_frontend *lgs8gl5_attach( + const struct lgs8gl5_config *config, struct i2c_adapter *i2c); +#else +static inline struct dvb_frontend *lgs8gl5_attach( + const struct lgs8gl5_config *config, struct i2c_adapter *i2c) { + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif /* CONFIG_DVB_LGS8GL5 */ + +#endif /* LGS8GL5_H */ diff --git a/drivers/media/dvb/frontends/nxt200x.c b/drivers/media/dvb/frontends/nxt200x.c index af298358e822..a8429ebfa8a2 100644 --- a/drivers/media/dvb/frontends/nxt200x.c +++ b/drivers/media/dvb/frontends/nxt200x.c @@ -80,7 +80,7 @@ static int i2c_writebytes (struct nxt200x_state* state, u8 addr, u8 *buf, u8 len return 0; } -static u8 i2c_readbytes (struct nxt200x_state* state, u8 addr, u8* buf, u8 len) +static int i2c_readbytes(struct nxt200x_state *state, u8 addr, u8 *buf, u8 len) { int err; struct i2c_msg msg = { .addr = addr, .flags = I2C_M_RD, .buf = buf, .len = len }; @@ -111,7 +111,7 @@ static int nxt200x_writebytes (struct nxt200x_state* state, u8 reg, return 0; } -static u8 nxt200x_readbytes (struct nxt200x_state* state, u8 reg, u8* buf, u8 len) +static int nxt200x_readbytes(struct nxt200x_state *state, u8 reg, u8 *buf, u8 len) { u8 reg2 [] = { reg }; diff --git a/drivers/media/dvb/frontends/or51211.c b/drivers/media/dvb/frontends/or51211.c index 6afe12aaca4e..16cf2fdd5d7d 100644 --- a/drivers/media/dvb/frontends/or51211.c +++ b/drivers/media/dvb/frontends/or51211.c @@ -88,7 +88,7 @@ static int i2c_writebytes (struct or51211_state* state, u8 reg, const u8 *buf, return 0; } -static u8 i2c_readbytes (struct or51211_state* state, u8 reg, u8* buf, int len) +static int i2c_readbytes(struct or51211_state *state, u8 reg, u8 *buf, int len) { int err; struct i2c_msg msg; diff --git a/drivers/media/dvb/frontends/si21xx.c b/drivers/media/dvb/frontends/si21xx.c new file mode 100644 index 000000000000..3ddbe69c45ce --- /dev/null +++ b/drivers/media/dvb/frontends/si21xx.c @@ -0,0 +1,974 @@ +/* DVB compliant Linux driver for the DVB-S si2109/2110 demodulator +* +* Copyright (C) 2008 Igor M. Liplianin (liplianin@me.by) +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +*/ +#include <linux/version.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <asm/div64.h> + +#include "dvb_frontend.h" +#include "si21xx.h" + +#define REVISION_REG 0x00 +#define SYSTEM_MODE_REG 0x01 +#define TS_CTRL_REG_1 0x02 +#define TS_CTRL_REG_2 0x03 +#define PIN_CTRL_REG_1 0x04 +#define PIN_CTRL_REG_2 0x05 +#define LOCK_STATUS_REG_1 0x0f +#define LOCK_STATUS_REG_2 0x10 +#define ACQ_STATUS_REG 0x11 +#define ACQ_CTRL_REG_1 0x13 +#define ACQ_CTRL_REG_2 0x14 +#define PLL_DIVISOR_REG 0x15 +#define COARSE_TUNE_REG 0x16 +#define FINE_TUNE_REG_L 0x17 +#define FINE_TUNE_REG_H 0x18 + +#define ANALOG_AGC_POWER_LEVEL_REG 0x28 +#define CFO_ESTIMATOR_CTRL_REG_1 0x29 +#define CFO_ESTIMATOR_CTRL_REG_2 0x2a +#define CFO_ESTIMATOR_CTRL_REG_3 0x2b + +#define SYM_RATE_ESTIMATE_REG_L 0x31 +#define SYM_RATE_ESTIMATE_REG_M 0x32 +#define SYM_RATE_ESTIMATE_REG_H 0x33 + +#define CFO_ESTIMATOR_OFFSET_REG_L 0x36 +#define CFO_ESTIMATOR_OFFSET_REG_H 0x37 +#define CFO_ERROR_REG_L 0x38 +#define CFO_ERROR_REG_H 0x39 +#define SYM_RATE_ESTIMATOR_CTRL_REG 0x3a + +#define SYM_RATE_REG_L 0x3f +#define SYM_RATE_REG_M 0x40 +#define SYM_RATE_REG_H 0x41 +#define SYM_RATE_ESTIMATOR_MAXIMUM_REG 0x42 +#define SYM_RATE_ESTIMATOR_MINIMUM_REG 0x43 + +#define C_N_ESTIMATOR_CTRL_REG 0x7c +#define C_N_ESTIMATOR_THRSHLD_REG 0x7d +#define C_N_ESTIMATOR_LEVEL_REG_L 0x7e +#define C_N_ESTIMATOR_LEVEL_REG_H 0x7f + +#define BLIND_SCAN_CTRL_REG 0x80 + +#define LSA_CTRL_REG_1 0x8D +#define SPCTRM_TILT_CORR_THRSHLD_REG 0x8f +#define ONE_DB_BNDWDTH_THRSHLD_REG 0x90 +#define TWO_DB_BNDWDTH_THRSHLD_REG 0x91 +#define THREE_DB_BNDWDTH_THRSHLD_REG 0x92 +#define INBAND_POWER_THRSHLD_REG 0x93 +#define REF_NOISE_LVL_MRGN_THRSHLD_REG 0x94 + +#define VIT_SRCH_CTRL_REG_1 0xa0 +#define VIT_SRCH_CTRL_REG_2 0xa1 +#define VIT_SRCH_CTRL_REG_3 0xa2 +#define VIT_SRCH_STATUS_REG 0xa3 +#define VITERBI_BER_COUNT_REG_L 0xab +#define REED_SOLOMON_CTRL_REG 0xb0 +#define REED_SOLOMON_ERROR_COUNT_REG_L 0xb1 +#define PRBS_CTRL_REG 0xb5 + +#define LNB_CTRL_REG_1 0xc0 +#define LNB_CTRL_REG_2 0xc1 +#define LNB_CTRL_REG_3 0xc2 +#define LNB_CTRL_REG_4 0xc3 +#define LNB_CTRL_STATUS_REG 0xc4 +#define LNB_FIFO_REGS_0 0xc5 +#define LNB_FIFO_REGS_1 0xc6 +#define LNB_FIFO_REGS_2 0xc7 +#define LNB_FIFO_REGS_3 0xc8 +#define LNB_FIFO_REGS_4 0xc9 +#define LNB_FIFO_REGS_5 0xca +#define LNB_SUPPLY_CTRL_REG_1 0xcb +#define LNB_SUPPLY_CTRL_REG_2 0xcc +#define LNB_SUPPLY_CTRL_REG_3 0xcd +#define LNB_SUPPLY_CTRL_REG_4 0xce +#define LNB_SUPPLY_STATUS_REG 0xcf + +#define FALSE 0 +#define TRUE 1 +#define FAIL -1 +#define PASS 0 + +#define ALLOWABLE_FS_COUNT 10 +#define STATUS_BER 0 +#define STATUS_UCBLOCKS 1 + +static int debug; +#define dprintk(args...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG "si21xx: " args); \ + } while (0) + +enum { + ACTIVE_HIGH, + ACTIVE_LOW +}; +enum { + BYTE_WIDE, + BIT_WIDE +}; +enum { + CLK_GAPPED_MODE, + CLK_CONTINUOUS_MODE +}; +enum { + RISING_EDGE, + FALLING_EDGE +}; +enum { + MSB_FIRST, + LSB_FIRST +}; +enum { + SERIAL, + PARALLEL +}; + +struct si21xx_state { + struct i2c_adapter *i2c; + const struct si21xx_config *config; + struct dvb_frontend frontend; + u8 initialised:1; + int errmode; + int fs; /*Sampling rate of the ADC in MHz*/ +}; + +/* register default initialization */ +static u8 serit_sp1511lhb_inittab[] = { + 0x01, 0x28, /* set i2c_inc_disable */ + 0x20, 0x03, + 0x27, 0x20, + 0xe0, 0x45, + 0xe1, 0x08, + 0xfe, 0x01, + 0x01, 0x28, + 0x89, 0x09, + 0x04, 0x80, + 0x05, 0x01, + 0x06, 0x00, + 0x20, 0x03, + 0x24, 0x88, + 0x29, 0x09, + 0x2a, 0x0f, + 0x2c, 0x10, + 0x2d, 0x19, + 0x2e, 0x08, + 0x2f, 0x10, + 0x30, 0x19, + 0x34, 0x20, + 0x35, 0x03, + 0x45, 0x02, + 0x46, 0x45, + 0x47, 0xd0, + 0x48, 0x00, + 0x49, 0x40, + 0x4a, 0x03, + 0x4c, 0xfd, + 0x4f, 0x2e, + 0x50, 0x2e, + 0x51, 0x10, + 0x52, 0x10, + 0x56, 0x92, + 0x59, 0x00, + 0x5a, 0x2d, + 0x5b, 0x33, + 0x5c, 0x1f, + 0x5f, 0x76, + 0x62, 0xc0, + 0x63, 0xc0, + 0x64, 0xf3, + 0x65, 0xf3, + 0x79, 0x40, + 0x6a, 0x40, + 0x6b, 0x0a, + 0x6c, 0x80, + 0x6d, 0x27, + 0x71, 0x06, + 0x75, 0x60, + 0x78, 0x00, + 0x79, 0xb5, + 0x7c, 0x05, + 0x7d, 0x1a, + 0x87, 0x55, + 0x88, 0x72, + 0x8f, 0x08, + 0x90, 0xe0, + 0x94, 0x40, + 0xa0, 0x3f, + 0xa1, 0xc0, + 0xa4, 0xcc, + 0xa5, 0x66, + 0xa6, 0x66, + 0xa7, 0x7b, + 0xa8, 0x7b, + 0xa9, 0x7b, + 0xaa, 0x9a, + 0xed, 0x04, + 0xad, 0x00, + 0xae, 0x03, + 0xcc, 0xab, + 0x01, 0x08, + 0xff, 0xff +}; + +/* low level read/writes */ +static int si21_writeregs(struct si21xx_state *state, u8 reg1, + u8 *data, int len) +{ + int ret; + u8 buf[60];/* = { reg1, data };*/ + struct i2c_msg msg = { + .addr = state->config->demod_address, + .flags = 0, + .buf = buf, + .len = len + 1 + }; + + msg.buf[0] = reg1; + memcpy(msg.buf + 1, data, len); + + ret = i2c_transfer(state->i2c, &msg, 1); + + if (ret != 1) + dprintk("%s: writereg error (reg1 == 0x%02x, data == 0x%02x, " + "ret == %i)\n", __func__, reg1, data[0], ret); + + return (ret != 1) ? -EREMOTEIO : 0; +} + +static int si21_writereg(struct si21xx_state *state, u8 reg, u8 data) +{ + int ret; + u8 buf[] = { reg, data }; + struct i2c_msg msg = { + .addr = state->config->demod_address, + .flags = 0, + .buf = buf, + .len = 2 + }; + + ret = i2c_transfer(state->i2c, &msg, 1); + + if (ret != 1) + dprintk("%s: writereg error (reg == 0x%02x, data == 0x%02x, " + "ret == %i)\n", __func__, reg, data, ret); + + return (ret != 1) ? -EREMOTEIO : 0; +} + +static int si21_write(struct dvb_frontend *fe, u8 *buf, int len) +{ + struct si21xx_state *state = fe->demodulator_priv; + + if (len != 2) + return -EINVAL; + + return si21_writereg(state, buf[0], buf[1]); +} + +static u8 si21_readreg(struct si21xx_state *state, u8 reg) +{ + int ret; + u8 b0[] = { reg }; + u8 b1[] = { 0 }; + struct i2c_msg msg[] = { + { + .addr = state->config->demod_address, + .flags = 0, + .buf = b0, + .len = 1 + }, { + .addr = state->config->demod_address, + .flags = I2C_M_RD, + .buf = b1, + .len = 1 + } + }; + + ret = i2c_transfer(state->i2c, msg, 2); + + if (ret != 2) + dprintk("%s: readreg error (reg == 0x%02x, ret == %i)\n", + __func__, reg, ret); + + return b1[0]; +} + +static int si21_readregs(struct si21xx_state *state, u8 reg1, u8 *b, u8 len) +{ + int ret; + struct i2c_msg msg[] = { + { + .addr = state->config->demod_address, + .flags = 0, + .buf = ®1, + .len = 1 + }, { + .addr = state->config->demod_address, + .flags = I2C_M_RD, + .buf = b, + .len = len + } + }; + + ret = i2c_transfer(state->i2c, msg, 2); + + if (ret != 2) + dprintk("%s: readreg error (ret == %i)\n", __func__, ret); + + return ret == 2 ? 0 : -1; +} + +static int si21xx_wait_diseqc_idle(struct si21xx_state *state, int timeout) +{ + unsigned long start = jiffies; + + dprintk("%s\n", __func__); + + while ((si21_readreg(state, LNB_CTRL_REG_1) & 0x8) == 8) { + if (jiffies - start > timeout) { + dprintk("%s: timeout!!\n", __func__); + return -ETIMEDOUT; + } + msleep(10); + }; + + return 0; +} + +static int si21xx_set_symbolrate(struct dvb_frontend *fe, u32 srate) +{ + struct si21xx_state *state = fe->demodulator_priv; + u32 sym_rate, data_rate; + int i; + u8 sym_rate_bytes[3]; + + dprintk("%s : srate = %i\n", __func__ , srate); + + if ((srate < 1000000) || (srate > 45000000)) + return -EINVAL; + + data_rate = srate; + sym_rate = 0; + + for (i = 0; i < 4; ++i) { + sym_rate /= 100; + sym_rate = sym_rate + ((data_rate % 100) * 0x800000) / + state->fs; + data_rate /= 100; + } + for (i = 0; i < 3; ++i) + sym_rate_bytes[i] = (u8)((sym_rate >> (i * 8)) & 0xff); + + si21_writeregs(state, SYM_RATE_REG_L, sym_rate_bytes, 0x03); + + return 0; +} + +static int si21xx_send_diseqc_msg(struct dvb_frontend *fe, + struct dvb_diseqc_master_cmd *m) +{ + struct si21xx_state *state = fe->demodulator_priv; + u8 lnb_status; + u8 LNB_CTRL_1; + int status; + + dprintk("%s\n", __func__); + + status = PASS; + LNB_CTRL_1 = 0; + + status |= si21_readregs(state, LNB_CTRL_STATUS_REG, &lnb_status, 0x01); + status |= si21_readregs(state, LNB_CTRL_REG_1, &lnb_status, 0x01); + + /*fill the FIFO*/ + status |= si21_writeregs(state, LNB_FIFO_REGS_0, m->msg, m->msg_len); + + LNB_CTRL_1 = (lnb_status & 0x70); + LNB_CTRL_1 |= m->msg_len; + + LNB_CTRL_1 |= 0x80; /* begin LNB signaling */ + + status |= si21_writeregs(state, LNB_CTRL_REG_1, &LNB_CTRL_1, 0x01); + + return status; +} + +static int si21xx_send_diseqc_burst(struct dvb_frontend *fe, + fe_sec_mini_cmd_t burst) +{ + struct si21xx_state *state = fe->demodulator_priv; + u8 val; + + dprintk("%s\n", __func__); + + if (si21xx_wait_diseqc_idle(state, 100) < 0) + return -ETIMEDOUT; + + val = (0x80 | si21_readreg(state, 0xc1)); + if (si21_writereg(state, LNB_CTRL_REG_1, + burst == SEC_MINI_A ? (val & ~0x10) : (val | 0x10))) + return -EREMOTEIO; + + if (si21xx_wait_diseqc_idle(state, 100) < 0) + return -ETIMEDOUT; + + if (si21_writereg(state, LNB_CTRL_REG_1, val)) + return -EREMOTEIO; + + return 0; +} +/* 30.06.2008 */ +static int si21xx_set_tone(struct dvb_frontend *fe, fe_sec_tone_mode_t tone) +{ + struct si21xx_state *state = fe->demodulator_priv; + u8 val; + + dprintk("%s\n", __func__); + val = (0x80 | si21_readreg(state, LNB_CTRL_REG_1)); + + switch (tone) { + case SEC_TONE_ON: + return si21_writereg(state, LNB_CTRL_REG_1, val | 0x20); + + case SEC_TONE_OFF: + return si21_writereg(state, LNB_CTRL_REG_1, (val & ~0x20)); + + default: + return -EINVAL; + } +} + +static int si21xx_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t volt) +{ + struct si21xx_state *state = fe->demodulator_priv; + + u8 val; + dprintk("%s: %s\n", __func__, + volt == SEC_VOLTAGE_13 ? "SEC_VOLTAGE_13" : + volt == SEC_VOLTAGE_18 ? "SEC_VOLTAGE_18" : "??"); + + + val = (0x80 | si21_readreg(state, LNB_CTRL_REG_1)); + + switch (volt) { + case SEC_VOLTAGE_18: + return si21_writereg(state, LNB_CTRL_REG_1, val | 0x40); + break; + case SEC_VOLTAGE_13: + return si21_writereg(state, LNB_CTRL_REG_1, (val & ~0x40)); + break; + default: + return -EINVAL; + }; +} + +static int si21xx_init(struct dvb_frontend *fe) +{ + struct si21xx_state *state = fe->demodulator_priv; + int i; + int status = 0; + u8 reg1; + u8 val; + u8 reg2[2]; + + dprintk("%s\n", __func__); + + for (i = 0; ; i += 2) { + reg1 = serit_sp1511lhb_inittab[i]; + val = serit_sp1511lhb_inittab[i+1]; + if (reg1 == 0xff && val == 0xff) + break; + si21_writeregs(state, reg1, &val, 1); + } + + /*DVB QPSK SYSTEM MODE REG*/ + reg1 = 0x08; + si21_writeregs(state, SYSTEM_MODE_REG, ®1, 0x01); + + /*transport stream config*/ + /* + mode = PARALLEL; + sdata_form = LSB_FIRST; + clk_edge = FALLING_EDGE; + clk_mode = CLK_GAPPED_MODE; + strt_len = BYTE_WIDE; + sync_pol = ACTIVE_HIGH; + val_pol = ACTIVE_HIGH; + err_pol = ACTIVE_HIGH; + sclk_rate = 0x00; + parity = 0x00 ; + data_delay = 0x00; + clk_delay = 0x00; + pclk_smooth = 0x00; + */ + reg2[0] = + PARALLEL + (LSB_FIRST << 1) + + (FALLING_EDGE << 2) + (CLK_GAPPED_MODE << 3) + + (BYTE_WIDE << 4) + (ACTIVE_HIGH << 5) + + (ACTIVE_HIGH << 6) + (ACTIVE_HIGH << 7); + + reg2[1] = 0; + /* sclk_rate + (parity << 2) + + (data_delay << 3) + (clk_delay << 4) + + (pclk_smooth << 5); + */ + status |= si21_writeregs(state, TS_CTRL_REG_1, reg2, 0x02); + if (status != 0) + dprintk(" %s : TS Set Error\n", __func__); + + return 0; + +} + +static int si21_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct si21xx_state *state = fe->demodulator_priv; + u8 regs_read[2]; + u8 reg_read; + u8 i; + u8 lock; + u8 signal = si21_readreg(state, ANALOG_AGC_POWER_LEVEL_REG); + + si21_readregs(state, LOCK_STATUS_REG_1, regs_read, 0x02); + reg_read = 0; + + for (i = 0; i < 7; ++i) + reg_read |= ((regs_read[0] >> i) & 0x01) << (6 - i); + + lock = ((reg_read & 0x7f) | (regs_read[1] & 0x80)); + + dprintk("%s : FE_READ_STATUS : VSTATUS: 0x%02x\n", __func__, lock); + *status = 0; + + if (signal > 10) + *status |= FE_HAS_SIGNAL; + + if (lock & 0x2) + *status |= FE_HAS_CARRIER; + + if (lock & 0x20) + *status |= FE_HAS_VITERBI; + + if (lock & 0x40) + *status |= FE_HAS_SYNC; + + if ((lock & 0x7b) == 0x7b) + *status |= FE_HAS_LOCK; + + return 0; +} + +static int si21_read_signal_strength(struct dvb_frontend *fe, u16 *strength) +{ + struct si21xx_state *state = fe->demodulator_priv; + + /*status = si21_readreg(state, ANALOG_AGC_POWER_LEVEL_REG, + (u8*)agclevel, 0x01);*/ + + u16 signal = (3 * si21_readreg(state, 0x27) * + si21_readreg(state, 0x28)); + + dprintk("%s : AGCPWR: 0x%02x%02x, signal=0x%04x\n", __func__, + si21_readreg(state, 0x27), + si21_readreg(state, 0x28), (int) signal); + + signal <<= 4; + *strength = signal; + + return 0; +} + +static int si21_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct si21xx_state *state = fe->demodulator_priv; + + dprintk("%s\n", __func__); + + if (state->errmode != STATUS_BER) + return 0; + + *ber = (si21_readreg(state, 0x1d) << 8) | + si21_readreg(state, 0x1e); + + return 0; +} + +static int si21_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct si21xx_state *state = fe->demodulator_priv; + + s32 xsnr = 0xffff - ((si21_readreg(state, 0x24) << 8) | + si21_readreg(state, 0x25)); + xsnr = 3 * (xsnr - 0xa100); + *snr = (xsnr > 0xffff) ? 0xffff : (xsnr < 0) ? 0 : xsnr; + + dprintk("%s\n", __func__); + + return 0; +} + +static int si21_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + struct si21xx_state *state = fe->demodulator_priv; + + dprintk("%s\n", __func__); + + if (state->errmode != STATUS_UCBLOCKS) + *ucblocks = 0; + else + *ucblocks = (si21_readreg(state, 0x1d) << 8) | + si21_readreg(state, 0x1e); + + return 0; +} + +/* initiates a channel acquisition sequence + using the specified symbol rate and code rate */ +static int si21xx_setacquire(struct dvb_frontend *fe, int symbrate, + fe_code_rate_t crate) +{ + + struct si21xx_state *state = fe->demodulator_priv; + u8 coderates[] = { + 0x0, 0x01, 0x02, 0x04, 0x00, + 0x8, 0x10, 0x20, 0x00, 0x3f + }; + + u8 coderate_ptr; + int status; + u8 start_acq = 0x80; + u8 reg, regs[3]; + + dprintk("%s\n", __func__); + + status = PASS; + coderate_ptr = coderates[crate]; + + si21xx_set_symbolrate(fe, symbrate); + + /* write code rates to use in the Viterbi search */ + status |= si21_writeregs(state, + VIT_SRCH_CTRL_REG_1, + &coderate_ptr, 0x01); + + /* clear acq_start bit */ + status |= si21_readregs(state, ACQ_CTRL_REG_2, ®, 0x01); + reg &= ~start_acq; + status |= si21_writeregs(state, ACQ_CTRL_REG_2, ®, 0x01); + + /* use new Carrier Frequency Offset Estimator (QuickLock) */ + regs[0] = 0xCB; + regs[1] = 0x40; + regs[2] = 0xCB; + + status |= si21_writeregs(state, + TWO_DB_BNDWDTH_THRSHLD_REG, + ®s[0], 0x03); + reg = 0x56; + status |= si21_writeregs(state, + LSA_CTRL_REG_1, ®, 1); + reg = 0x05; + status |= si21_writeregs(state, + BLIND_SCAN_CTRL_REG, ®, 1); + /* start automatic acq */ + status |= si21_writeregs(state, + ACQ_CTRL_REG_2, &start_acq, 0x01); + + return status; +} + +static int si21xx_set_property(struct dvb_frontend *fe, struct dtv_property *p) +{ + dprintk("%s(..)\n", __func__); + return 0; +} + +static int si21xx_get_property(struct dvb_frontend *fe, struct dtv_property *p) +{ + dprintk("%s(..)\n", __func__); + return 0; +} + +static int si21xx_set_frontend(struct dvb_frontend *fe, + struct dvb_frontend_parameters *dfp) +{ + struct si21xx_state *state = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + + /* freq Channel carrier frequency in KHz (i.e. 1550000 KHz) + datarate Channel symbol rate in Sps (i.e. 22500000 Sps)*/ + + /* in MHz */ + unsigned char coarse_tune_freq; + int fine_tune_freq; + unsigned char sample_rate = 0; + /* boolean */ + unsigned int inband_interferer_ind; + + /* INTERMEDIATE VALUES */ + int icoarse_tune_freq; /* MHz */ + int ifine_tune_freq; /* MHz */ + unsigned int band_high; + unsigned int band_low; + unsigned int x1; + unsigned int x2; + int i; + unsigned int inband_interferer_div2[ALLOWABLE_FS_COUNT] = { + FALSE, FALSE, FALSE, FALSE, FALSE, + FALSE, FALSE, FALSE, FALSE, FALSE + }; + unsigned int inband_interferer_div4[ALLOWABLE_FS_COUNT] = { + FALSE, FALSE, FALSE, FALSE, FALSE, + FALSE, FALSE, FALSE, FALSE, FALSE + }; + + int status; + + /* allowable sample rates for ADC in MHz */ + int afs[ALLOWABLE_FS_COUNT] = { 200, 192, 193, 194, 195, + 196, 204, 205, 206, 207 + }; + /* in MHz */ + int if_limit_high; + int if_limit_low; + int lnb_lo; + int lnb_uncertanity; + + int rf_freq; + int data_rate; + unsigned char regs[4]; + + dprintk("%s : FE_SET_FRONTEND\n", __func__); + + if (c->delivery_system != SYS_DVBS) { + dprintk("%s: unsupported delivery system selected (%d)\n", + __func__, c->delivery_system); + return -EOPNOTSUPP; + } + + for (i = 0; i < ALLOWABLE_FS_COUNT; ++i) + inband_interferer_div2[i] = inband_interferer_div4[i] = FALSE; + + if_limit_high = -700000; + if_limit_low = -100000; + /* in MHz */ + lnb_lo = 0; + lnb_uncertanity = 0; + + rf_freq = 10 * c->frequency ; + data_rate = c->symbol_rate / 100; + + status = PASS; + + band_low = (rf_freq - lnb_lo) - ((lnb_uncertanity * 200) + + (data_rate * 135)) / 200; + + band_high = (rf_freq - lnb_lo) + ((lnb_uncertanity * 200) + + (data_rate * 135)) / 200; + + + icoarse_tune_freq = 100000 * + (((rf_freq - lnb_lo) - + (if_limit_low + if_limit_high) / 2) + / 100000); + + ifine_tune_freq = (rf_freq - lnb_lo) - icoarse_tune_freq ; + + for (i = 0; i < ALLOWABLE_FS_COUNT; ++i) { + x1 = ((rf_freq - lnb_lo) / (afs[i] * 2500)) * + (afs[i] * 2500) + afs[i] * 2500; + + x2 = ((rf_freq - lnb_lo) / (afs[i] * 2500)) * + (afs[i] * 2500); + + if (((band_low < x1) && (x1 < band_high)) || + ((band_low < x2) && (x2 < band_high))) + inband_interferer_div4[i] = TRUE; + + } + + for (i = 0; i < ALLOWABLE_FS_COUNT; ++i) { + x1 = ((rf_freq - lnb_lo) / (afs[i] * 5000)) * + (afs[i] * 5000) + afs[i] * 5000; + + x2 = ((rf_freq - lnb_lo) / (afs[i] * 5000)) * + (afs[i] * 5000); + + if (((band_low < x1) && (x1 < band_high)) || + ((band_low < x2) && (x2 < band_high))) + inband_interferer_div2[i] = TRUE; + } + + inband_interferer_ind = TRUE; + for (i = 0; i < ALLOWABLE_FS_COUNT; ++i) + inband_interferer_ind &= inband_interferer_div2[i] | + inband_interferer_div4[i]; + + if (inband_interferer_ind) { + for (i = 0; i < ALLOWABLE_FS_COUNT; ++i) { + if (inband_interferer_div2[i] == FALSE) { + sample_rate = (u8) afs[i]; + break; + } + } + } else { + for (i = 0; i < ALLOWABLE_FS_COUNT; ++i) { + if ((inband_interferer_div2[i] | + inband_interferer_div4[i]) == FALSE) { + sample_rate = (u8) afs[i]; + break; + } + } + + } + + if (sample_rate > 207 || sample_rate < 192) + sample_rate = 200; + + fine_tune_freq = ((0x4000 * (ifine_tune_freq / 10)) / + ((sample_rate) * 1000)); + + coarse_tune_freq = (u8)(icoarse_tune_freq / 100000); + + regs[0] = sample_rate; + regs[1] = coarse_tune_freq; + regs[2] = fine_tune_freq & 0xFF; + regs[3] = fine_tune_freq >> 8 & 0xFF; + + status |= si21_writeregs(state, PLL_DIVISOR_REG, ®s[0], 0x04); + + state->fs = sample_rate;/*ADC MHz*/ + si21xx_setacquire(fe, c->symbol_rate, c->fec_inner); + + return 0; +} + +static int si21xx_sleep(struct dvb_frontend *fe) +{ + struct si21xx_state *state = fe->demodulator_priv; + u8 regdata; + + dprintk("%s\n", __func__); + + si21_readregs(state, SYSTEM_MODE_REG, ®data, 0x01); + regdata |= 1 << 6; + si21_writeregs(state, SYSTEM_MODE_REG, ®data, 0x01); + state->initialised = 0; + + return 0; +} + +static void si21xx_release(struct dvb_frontend *fe) +{ + struct si21xx_state *state = fe->demodulator_priv; + + dprintk("%s\n", __func__); + + kfree(state); +} + +static struct dvb_frontend_ops si21xx_ops = { + + .info = { + .name = "SL SI21XX DVB-S", + .type = FE_QPSK, + .frequency_min = 950000, + .frequency_max = 2150000, + .frequency_stepsize = 125, /* kHz for QPSK frontends */ + .frequency_tolerance = 0, + .symbol_rate_min = 1000000, + .symbol_rate_max = 45000000, + .symbol_rate_tolerance = 500, /* ppm */ + .caps = FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | + FE_CAN_QPSK | + FE_CAN_FEC_AUTO + }, + + .release = si21xx_release, + .init = si21xx_init, + .sleep = si21xx_sleep, + .write = si21_write, + .read_status = si21_read_status, + .read_ber = si21_read_ber, + .read_signal_strength = si21_read_signal_strength, + .read_snr = si21_read_snr, + .read_ucblocks = si21_read_ucblocks, + .diseqc_send_master_cmd = si21xx_send_diseqc_msg, + .diseqc_send_burst = si21xx_send_diseqc_burst, + .set_tone = si21xx_set_tone, + .set_voltage = si21xx_set_voltage, + + .set_property = si21xx_set_property, + .get_property = si21xx_get_property, + .set_frontend = si21xx_set_frontend, +}; + +struct dvb_frontend *si21xx_attach(const struct si21xx_config *config, + struct i2c_adapter *i2c) +{ + struct si21xx_state *state = NULL; + int id; + + dprintk("%s\n", __func__); + + /* allocate memory for the internal state */ + state = kmalloc(sizeof(struct si21xx_state), GFP_KERNEL); + if (state == NULL) + goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + state->initialised = 0; + state->errmode = STATUS_BER; + + /* check if the demod is there */ + id = si21_readreg(state, SYSTEM_MODE_REG); + si21_writereg(state, SYSTEM_MODE_REG, id | 0x40); /* standby off */ + msleep(200); + id = si21_readreg(state, 0x00); + + /* register 0x00 contains: + 0x34 for SI2107 + 0x24 for SI2108 + 0x14 for SI2109 + 0x04 for SI2110 + */ + if (id != 0x04 && id != 0x14) + goto error; + + /* create dvb_frontend */ + memcpy(&state->frontend.ops, &si21xx_ops, + sizeof(struct dvb_frontend_ops)); + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + return NULL; +} +EXPORT_SYMBOL(si21xx_attach); + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("SL SI21XX DVB Demodulator driver"); +MODULE_AUTHOR("Igor M. Liplianin"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/si21xx.h b/drivers/media/dvb/frontends/si21xx.h new file mode 100644 index 000000000000..141b5b8a5f63 --- /dev/null +++ b/drivers/media/dvb/frontends/si21xx.h @@ -0,0 +1,37 @@ +#ifndef SI21XX_H +#define SI21XX_H + +#include <linux/dvb/frontend.h> +#include "dvb_frontend.h" + +struct si21xx_config { + /* the demodulator's i2c address */ + u8 demod_address; + + /* minimum delay before retuning */ + int min_delay_ms; +}; + +#if defined(CONFIG_DVB_SI21XX) || \ + (defined(CONFIG_DVB_SI21XX_MODULE) && defined(MODULE)) +extern struct dvb_frontend *si21xx_attach(const struct si21xx_config *config, + struct i2c_adapter *i2c); +#else +static inline struct dvb_frontend *si21xx_attach( + const struct si21xx_config *config, struct i2c_adapter *i2c) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif + +static inline int si21xx_writeregister(struct dvb_frontend *fe, u8 reg, u8 val) +{ + int r = 0; + u8 buf[] = {reg, val}; + if (fe->ops.write) + r = fe->ops.write(fe, buf, 2); + return r; +} + +#endif diff --git a/drivers/media/dvb/frontends/sp887x.c b/drivers/media/dvb/frontends/sp887x.c index 4543609e1816..559509ab4dab 100644 --- a/drivers/media/dvb/frontends/sp887x.c +++ b/drivers/media/dvb/frontends/sp887x.c @@ -337,7 +337,8 @@ static int sp887x_setup_frontend_parameters (struct dvb_frontend* fe, struct dvb_frontend_parameters *p) { struct sp887x_state* state = fe->demodulator_priv; - int actual_freq, err; + unsigned actual_freq; + int err; u16 val, reg0xc05; if (p->u.ofdm.bandwidth != BANDWIDTH_8_MHZ && diff --git a/drivers/media/dvb/frontends/stb6000.c b/drivers/media/dvb/frontends/stb6000.c new file mode 100644 index 000000000000..0e2cb0df1441 --- /dev/null +++ b/drivers/media/dvb/frontends/stb6000.c @@ -0,0 +1,255 @@ + /* + Driver for ST STB6000 DVBS Silicon tuner + + Copyright (C) 2008 Igor M. Liplianin (liplianin@me.by) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ + +#include <linux/module.h> +#include <linux/dvb/frontend.h> +#include <asm/types.h> + +#include "stb6000.h" + +static int debug; +#define dprintk(args...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG "stb6000: " args); \ + } while (0) + +struct stb6000_priv { + /* i2c details */ + int i2c_address; + struct i2c_adapter *i2c; + u32 frequency; +}; + +static int stb6000_release(struct dvb_frontend *fe) +{ + kfree(fe->tuner_priv); + fe->tuner_priv = NULL; + return 0; +} + +static int stb6000_sleep(struct dvb_frontend *fe) +{ + struct stb6000_priv *priv = fe->tuner_priv; + int ret; + u8 buf[] = { 10, 0 }; + struct i2c_msg msg = { + .addr = priv->i2c_address, + .flags = 0, + .buf = buf, + .len = 2 + }; + + dprintk("%s:\n", __func__); + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + + ret = i2c_transfer(priv->i2c, &msg, 1); + if (ret != 1) + dprintk("%s: i2c error\n", __func__); + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + + return (ret == 1) ? 0 : ret; +} + +static int stb6000_set_params(struct dvb_frontend *fe, + struct dvb_frontend_parameters *params) +{ + struct stb6000_priv *priv = fe->tuner_priv; + unsigned int n, m; + int ret; + u32 freq_mhz; + int bandwidth; + u8 buf[12]; + struct i2c_msg msg = { + .addr = priv->i2c_address, + .flags = 0, + .buf = buf, + .len = 12 + }; + + dprintk("%s:\n", __func__); + + freq_mhz = params->frequency / 1000; + bandwidth = params->u.qpsk.symbol_rate / 1000000; + + if (bandwidth > 31) + bandwidth = 31; + + if ((freq_mhz > 949) && (freq_mhz < 2151)) { + buf[0] = 0x01; + buf[1] = 0xac; + if (freq_mhz < 1950) + buf[1] = 0xaa; + if (freq_mhz < 1800) + buf[1] = 0xa8; + if (freq_mhz < 1650) + buf[1] = 0xa6; + if (freq_mhz < 1530) + buf[1] = 0xa5; + if (freq_mhz < 1470) + buf[1] = 0xa4; + if (freq_mhz < 1370) + buf[1] = 0xa2; + if (freq_mhz < 1300) + buf[1] = 0xa1; + if (freq_mhz < 1200) + buf[1] = 0xa0; + if (freq_mhz < 1075) + buf[1] = 0xbc; + if (freq_mhz < 1000) + buf[1] = 0xba; + if (freq_mhz < 1075) { + n = freq_mhz / 8; /* vco=lo*4 */ + m = 2; + } else { + n = freq_mhz / 16; /* vco=lo*2 */ + m = 1; + } + buf[2] = n >> 1; + buf[3] = (unsigned char)(((n & 1) << 7) | + (m * freq_mhz - n * 16) | 0x60); + buf[4] = 0x04; + buf[5] = 0x0e; + + buf[6] = (unsigned char)(bandwidth); + + buf[7] = 0xd8; + buf[8] = 0xd0; + buf[9] = 0x50; + buf[10] = 0xeb; + buf[11] = 0x4f; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + + ret = i2c_transfer(priv->i2c, &msg, 1); + if (ret != 1) + dprintk("%s: i2c error\n", __func__); + + udelay(10); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + + buf[0] = 0x07; + buf[1] = 0xdf; + buf[2] = 0xd0; + buf[3] = 0x50; + buf[4] = 0xfb; + msg.len = 5; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + + ret = i2c_transfer(priv->i2c, &msg, 1); + if (ret != 1) + dprintk("%s: i2c error\n", __func__); + + udelay(10); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + + priv->frequency = freq_mhz * 1000; + + return (ret == 1) ? 0 : ret; + } + return -1; +} + +static int stb6000_get_frequency(struct dvb_frontend *fe, u32 *frequency) +{ + struct stb6000_priv *priv = fe->tuner_priv; + *frequency = priv->frequency; + return 0; +} + +static struct dvb_tuner_ops stb6000_tuner_ops = { + .info = { + .name = "ST STB6000", + .frequency_min = 950000, + .frequency_max = 2150000 + }, + .release = stb6000_release, + .sleep = stb6000_sleep, + .set_params = stb6000_set_params, + .get_frequency = stb6000_get_frequency, +}; + +struct dvb_frontend *stb6000_attach(struct dvb_frontend *fe, int addr, + struct i2c_adapter *i2c) +{ + struct stb6000_priv *priv = NULL; + u8 b0[] = { 0 }; + u8 b1[] = { 0, 0 }; + struct i2c_msg msg[2] = { + { + .addr = addr, + .flags = 0, + .buf = b0, + .len = 0 + }, { + .addr = addr, + .flags = I2C_M_RD, + .buf = b1, + .len = 2 + } + }; + int ret; + + dprintk("%s:\n", __func__); + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + + /* is some i2c device here ? */ + ret = i2c_transfer(i2c, msg, 2); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + + if (ret != 2) + return NULL; + + priv = kzalloc(sizeof(struct stb6000_priv), GFP_KERNEL); + if (priv == NULL) + return NULL; + + priv->i2c_address = addr; + priv->i2c = i2c; + + memcpy(&fe->ops.tuner_ops, &stb6000_tuner_ops, + sizeof(struct dvb_tuner_ops)); + + fe->tuner_priv = priv; + + return fe; +} +EXPORT_SYMBOL(stb6000_attach); + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("DVB STB6000 driver"); +MODULE_AUTHOR("Igor M. Liplianin <liplianin@me.by>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/stb6000.h b/drivers/media/dvb/frontends/stb6000.h new file mode 100644 index 000000000000..7be479c22d5b --- /dev/null +++ b/drivers/media/dvb/frontends/stb6000.h @@ -0,0 +1,51 @@ + /* + Driver for ST stb6000 DVBS Silicon tuner + + Copyright (C) 2008 Igor M. Liplianin (liplianin@me.by) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ + +#ifndef __DVB_STB6000_H__ +#define __DVB_STB6000_H__ + +#include <linux/i2c.h> +#include "dvb_frontend.h" + +/** + * Attach a stb6000 tuner to the supplied frontend structure. + * + * @param fe Frontend to attach to. + * @param addr i2c address of the tuner. + * @param i2c i2c adapter to use. + * @return FE pointer on success, NULL on failure. + */ +#if defined(CONFIG_DVB_STB6000) || (defined(CONFIG_DVB_STB6000_MODULE) \ + && defined(MODULE)) +extern struct dvb_frontend *stb6000_attach(struct dvb_frontend *fe, int addr, + struct i2c_adapter *i2c); +#else +static inline struct dvb_frontend *stb6000_attach(struct dvb_frontend *fe, + int addr, + struct i2c_adapter *i2c) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif /* CONFIG_DVB_STB6000 */ + +#endif /* __DVB_STB6000_H__ */ diff --git a/drivers/media/dvb/frontends/stv0288.c b/drivers/media/dvb/frontends/stv0288.c new file mode 100644 index 000000000000..ff1194de34c0 --- /dev/null +++ b/drivers/media/dvb/frontends/stv0288.c @@ -0,0 +1,618 @@ +/* + Driver for ST STV0288 demodulator + Copyright (C) 2006 Georg Acher, BayCom GmbH, acher (at) baycom (dot) de + for Reel Multimedia + Copyright (C) 2008 TurboSight.com, Bob Liu <bob@turbosight.com> + Copyright (C) 2008 Igor M. Liplianin <liplianin@me.by> + Removed stb6000 specific tuner code and revised some + procedures. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <asm/div64.h> + +#include "dvb_frontend.h" +#include "stv0288.h" + +struct stv0288_state { + struct i2c_adapter *i2c; + const struct stv0288_config *config; + struct dvb_frontend frontend; + + u8 initialised:1; + u32 tuner_frequency; + u32 symbol_rate; + fe_code_rate_t fec_inner; + int errmode; +}; + +#define STATUS_BER 0 +#define STATUS_UCBLOCKS 1 + +static int debug; +static int debug_legacy_dish_switch; +#define dprintk(args...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG "stv0288: " args); \ + } while (0) + + +static int stv0288_writeregI(struct stv0288_state *state, u8 reg, u8 data) +{ + int ret; + u8 buf[] = { reg, data }; + struct i2c_msg msg = { + .addr = state->config->demod_address, + .flags = 0, + .buf = buf, + .len = 2 + }; + + ret = i2c_transfer(state->i2c, &msg, 1); + + if (ret != 1) + dprintk("%s: writereg error (reg == 0x%02x, val == 0x%02x, " + "ret == %i)\n", __func__, reg, data, ret); + + return (ret != 1) ? -EREMOTEIO : 0; +} + +static int stv0288_write(struct dvb_frontend *fe, u8 *buf, int len) +{ + struct stv0288_state *state = fe->demodulator_priv; + + if (len != 2) + return -EINVAL; + + return stv0288_writeregI(state, buf[0], buf[1]); +} + +static u8 stv0288_readreg(struct stv0288_state *state, u8 reg) +{ + int ret; + u8 b0[] = { reg }; + u8 b1[] = { 0 }; + struct i2c_msg msg[] = { + { + .addr = state->config->demod_address, + .flags = 0, + .buf = b0, + .len = 1 + }, { + .addr = state->config->demod_address, + .flags = I2C_M_RD, + .buf = b1, + .len = 1 + } + }; + + ret = i2c_transfer(state->i2c, msg, 2); + + if (ret != 2) + dprintk("%s: readreg error (reg == 0x%02x, ret == %i)\n", + __func__, reg, ret); + + return b1[0]; +} + +static int stv0288_set_symbolrate(struct dvb_frontend *fe, u32 srate) +{ + struct stv0288_state *state = fe->demodulator_priv; + unsigned int temp; + unsigned char b[3]; + + if ((srate < 1000000) || (srate > 45000000)) + return -EINVAL; + + temp = (unsigned int)srate / 1000; + + temp = temp * 32768; + temp = temp / 25; + temp = temp / 125; + b[0] = (unsigned char)((temp >> 12) & 0xff); + b[1] = (unsigned char)((temp >> 4) & 0xff); + b[2] = (unsigned char)((temp << 4) & 0xf0); + stv0288_writeregI(state, 0x28, 0x80); /* SFRH */ + stv0288_writeregI(state, 0x29, 0); /* SFRM */ + stv0288_writeregI(state, 0x2a, 0); /* SFRL */ + + stv0288_writeregI(state, 0x28, b[0]); + stv0288_writeregI(state, 0x29, b[1]); + stv0288_writeregI(state, 0x2a, b[2]); + dprintk("stv0288: stv0288_set_symbolrate\n"); + + return 0; +} + +static int stv0288_send_diseqc_msg(struct dvb_frontend *fe, + struct dvb_diseqc_master_cmd *m) +{ + struct stv0288_state *state = fe->demodulator_priv; + + int i; + + dprintk("%s\n", __func__); + + stv0288_writeregI(state, 0x09, 0); + msleep(30); + stv0288_writeregI(state, 0x05, 0x16); + + for (i = 0; i < m->msg_len; i++) { + if (stv0288_writeregI(state, 0x06, m->msg[i])) + return -EREMOTEIO; + msleep(12); + } + + return 0; +} + +static int stv0288_send_diseqc_burst(struct dvb_frontend *fe, + fe_sec_mini_cmd_t burst) +{ + struct stv0288_state *state = fe->demodulator_priv; + + dprintk("%s\n", __func__); + + if (stv0288_writeregI(state, 0x05, 0x16))/* burst mode */ + return -EREMOTEIO; + + if (stv0288_writeregI(state, 0x06, burst == SEC_MINI_A ? 0x00 : 0xff)) + return -EREMOTEIO; + + if (stv0288_writeregI(state, 0x06, 0x12)) + return -EREMOTEIO; + + return 0; +} + +static int stv0288_set_tone(struct dvb_frontend *fe, fe_sec_tone_mode_t tone) +{ + struct stv0288_state *state = fe->demodulator_priv; + + switch (tone) { + case SEC_TONE_ON: + if (stv0288_writeregI(state, 0x05, 0x10))/* burst mode */ + return -EREMOTEIO; + return stv0288_writeregI(state, 0x06, 0xff); + + case SEC_TONE_OFF: + if (stv0288_writeregI(state, 0x05, 0x13))/* burst mode */ + return -EREMOTEIO; + return stv0288_writeregI(state, 0x06, 0x00); + + default: + return -EINVAL; + } +} + +static u8 stv0288_inittab[] = { + 0x01, 0x15, + 0x02, 0x20, + 0x09, 0x0, + 0x0a, 0x4, + 0x0b, 0x0, + 0x0c, 0x0, + 0x0d, 0x0, + 0x0e, 0xd4, + 0x0f, 0x30, + 0x11, 0x80, + 0x12, 0x03, + 0x13, 0x48, + 0x14, 0x84, + 0x15, 0x45, + 0x16, 0xb7, + 0x17, 0x9c, + 0x18, 0x0, + 0x19, 0xa6, + 0x1a, 0x88, + 0x1b, 0x8f, + 0x1c, 0xf0, + 0x20, 0x0b, + 0x21, 0x54, + 0x22, 0x0, + 0x23, 0x0, + 0x2b, 0xff, + 0x2c, 0xf7, + 0x30, 0x0, + 0x31, 0x1e, + 0x32, 0x14, + 0x33, 0x0f, + 0x34, 0x09, + 0x35, 0x0c, + 0x36, 0x05, + 0x37, 0x2f, + 0x38, 0x16, + 0x39, 0xbe, + 0x3a, 0x0, + 0x3b, 0x13, + 0x3c, 0x11, + 0x3d, 0x30, + 0x40, 0x63, + 0x41, 0x04, + 0x42, 0x60, + 0x43, 0x00, + 0x44, 0x00, + 0x45, 0x00, + 0x46, 0x00, + 0x47, 0x00, + 0x4a, 0x00, + 0x50, 0x10, + 0x51, 0x38, + 0x52, 0x21, + 0x58, 0x54, + 0x59, 0x86, + 0x5a, 0x0, + 0x5b, 0x9b, + 0x5c, 0x08, + 0x5d, 0x7f, + 0x5e, 0x0, + 0x5f, 0xff, + 0x70, 0x0, + 0x71, 0x0, + 0x72, 0x0, + 0x74, 0x0, + 0x75, 0x0, + 0x76, 0x0, + 0x81, 0x0, + 0x82, 0x3f, + 0x83, 0x3f, + 0x84, 0x0, + 0x85, 0x0, + 0x88, 0x0, + 0x89, 0x0, + 0x8a, 0x0, + 0x8b, 0x0, + 0x8c, 0x0, + 0x90, 0x0, + 0x91, 0x0, + 0x92, 0x0, + 0x93, 0x0, + 0x94, 0x1c, + 0x97, 0x0, + 0xa0, 0x48, + 0xa1, 0x0, + 0xb0, 0xb8, + 0xb1, 0x3a, + 0xb2, 0x10, + 0xb3, 0x82, + 0xb4, 0x80, + 0xb5, 0x82, + 0xb6, 0x82, + 0xb7, 0x82, + 0xb8, 0x20, + 0xb9, 0x0, + 0xf0, 0x0, + 0xf1, 0x0, + 0xf2, 0xc0, + 0x51, 0x36, + 0x52, 0x09, + 0x53, 0x94, + 0x54, 0x62, + 0x55, 0x29, + 0x56, 0x64, + 0x57, 0x2b, + 0xff, 0xff, +}; + +static int stv0288_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t volt) +{ + dprintk("%s: %s\n", __func__, + volt == SEC_VOLTAGE_13 ? "SEC_VOLTAGE_13" : + volt == SEC_VOLTAGE_18 ? "SEC_VOLTAGE_18" : "??"); + + return 0; +} + +static int stv0288_init(struct dvb_frontend *fe) +{ + struct stv0288_state *state = fe->demodulator_priv; + int i; + u8 reg; + u8 val; + + dprintk("stv0288: init chip\n"); + stv0288_writeregI(state, 0x41, 0x04); + msleep(50); + + /* we have default inittab */ + if (state->config->inittab == NULL) { + for (i = 0; !(stv0288_inittab[i] == 0xff && + stv0288_inittab[i + 1] == 0xff); i += 2) + stv0288_writeregI(state, stv0288_inittab[i], + stv0288_inittab[i + 1]); + } else { + for (i = 0; ; i += 2) { + reg = state->config->inittab[i]; + val = state->config->inittab[i+1]; + if (reg == 0xff && val == 0xff) + break; + stv0288_writeregI(state, reg, val); + } + } + return 0; +} + +static int stv0288_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + struct stv0288_state *state = fe->demodulator_priv; + + u8 sync = stv0288_readreg(state, 0x24); + if (sync == 255) + sync = 0; + + dprintk("%s : FE_READ_STATUS : VSTATUS: 0x%02x\n", __func__, sync); + + *status = 0; + + if ((sync & 0x08) == 0x08) { + *status |= FE_HAS_LOCK; + dprintk("stv0288 has locked\n"); + } + + return 0; +} + +static int stv0288_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct stv0288_state *state = fe->demodulator_priv; + + if (state->errmode != STATUS_BER) + return 0; + *ber = (stv0288_readreg(state, 0x26) << 8) | + stv0288_readreg(state, 0x27); + dprintk("stv0288_read_ber %d\n", *ber); + + return 0; +} + + +static int stv0288_read_signal_strength(struct dvb_frontend *fe, u16 *strength) +{ + struct stv0288_state *state = fe->demodulator_priv; + + s32 signal = 0xffff - ((stv0288_readreg(state, 0x10) << 8)); + + + signal = signal * 5 / 4; + *strength = (signal > 0xffff) ? 0xffff : (signal < 0) ? 0 : signal; + dprintk("stv0288_read_signal_strength %d\n", *strength); + + return 0; +} +static int stv0288_sleep(struct dvb_frontend *fe) +{ + struct stv0288_state *state = fe->demodulator_priv; + + stv0288_writeregI(state, 0x41, 0x84); + state->initialised = 0; + + return 0; +} +static int stv0288_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct stv0288_state *state = fe->demodulator_priv; + + s32 xsnr = 0xffff - ((stv0288_readreg(state, 0x2d) << 8) + | stv0288_readreg(state, 0x2e)); + xsnr = 3 * (xsnr - 0xa100); + *snr = (xsnr > 0xffff) ? 0xffff : (xsnr < 0) ? 0 : xsnr; + dprintk("stv0288_read_snr %d\n", *snr); + + return 0; +} + +static int stv0288_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + struct stv0288_state *state = fe->demodulator_priv; + + if (state->errmode != STATUS_BER) + return 0; + *ucblocks = (stv0288_readreg(state, 0x26) << 8) | + stv0288_readreg(state, 0x27); + dprintk("stv0288_read_ber %d\n", *ucblocks); + + return 0; +} + +static int stv0288_set_property(struct dvb_frontend *fe, struct dtv_property *p) +{ + dprintk("%s(..)\n", __func__); + return 0; +} + +static int stv0288_get_property(struct dvb_frontend *fe, struct dtv_property *p) +{ + dprintk("%s(..)\n", __func__); + return 0; +} + +static int stv0288_set_frontend(struct dvb_frontend *fe, + struct dvb_frontend_parameters *dfp) +{ + struct stv0288_state *state = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + + char tm; + unsigned char tda[3]; + + dprintk("%s : FE_SET_FRONTEND\n", __func__); + + if (c->delivery_system != SYS_DVBS) { + dprintk("%s: unsupported delivery " + "system selected (%d)\n", + __func__, c->delivery_system); + return -EOPNOTSUPP; + } + + if (state->config->set_ts_params) + state->config->set_ts_params(fe, 0); + + /* only frequency & symbol_rate are used for tuner*/ + dfp->frequency = c->frequency; + dfp->u.qpsk.symbol_rate = c->symbol_rate; + if (fe->ops.tuner_ops.set_params) { + fe->ops.tuner_ops.set_params(fe, dfp); + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + } + + udelay(10); + stv0288_set_symbolrate(fe, c->symbol_rate); + /* Carrier lock control register */ + stv0288_writeregI(state, 0x15, 0xc5); + + tda[0] = 0x2b; /* CFRM */ + tda[2] = 0x0; /* CFRL */ + for (tm = -6; tm < 7;) { + /* Viterbi status */ + if (stv0288_readreg(state, 0x24) & 0x80) + break; + + tda[2] += 40; + if (tda[2] < 40) + tm++; + tda[1] = (unsigned char)tm; + stv0288_writeregI(state, 0x2b, tda[1]); + stv0288_writeregI(state, 0x2c, tda[2]); + udelay(30); + } + + state->tuner_frequency = c->frequency; + state->fec_inner = FEC_AUTO; + state->symbol_rate = c->symbol_rate; + + return 0; +} + +static int stv0288_i2c_gate_ctrl(struct dvb_frontend *fe, int enable) +{ + struct stv0288_state *state = fe->demodulator_priv; + + if (enable) + stv0288_writeregI(state, 0x01, 0xb5); + else + stv0288_writeregI(state, 0x01, 0x35); + + udelay(1); + + return 0; +} + +static void stv0288_release(struct dvb_frontend *fe) +{ + struct stv0288_state *state = fe->demodulator_priv; + kfree(state); +} + +static struct dvb_frontend_ops stv0288_ops = { + + .info = { + .name = "ST STV0288 DVB-S", + .type = FE_QPSK, + .frequency_min = 950000, + .frequency_max = 2150000, + .frequency_stepsize = 1000, /* kHz for QPSK frontends */ + .frequency_tolerance = 0, + .symbol_rate_min = 1000000, + .symbol_rate_max = 45000000, + .symbol_rate_tolerance = 500, /* ppm */ + .caps = FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | + FE_CAN_QPSK | + FE_CAN_FEC_AUTO + }, + + .release = stv0288_release, + .init = stv0288_init, + .sleep = stv0288_sleep, + .write = stv0288_write, + .i2c_gate_ctrl = stv0288_i2c_gate_ctrl, + .read_status = stv0288_read_status, + .read_ber = stv0288_read_ber, + .read_signal_strength = stv0288_read_signal_strength, + .read_snr = stv0288_read_snr, + .read_ucblocks = stv0288_read_ucblocks, + .diseqc_send_master_cmd = stv0288_send_diseqc_msg, + .diseqc_send_burst = stv0288_send_diseqc_burst, + .set_tone = stv0288_set_tone, + .set_voltage = stv0288_set_voltage, + + .set_property = stv0288_set_property, + .get_property = stv0288_get_property, + .set_frontend = stv0288_set_frontend, +}; + +struct dvb_frontend *stv0288_attach(const struct stv0288_config *config, + struct i2c_adapter *i2c) +{ + struct stv0288_state *state = NULL; + int id; + + /* allocate memory for the internal state */ + state = kmalloc(sizeof(struct stv0288_state), GFP_KERNEL); + if (state == NULL) + goto error; + + /* setup the state */ + state->config = config; + state->i2c = i2c; + state->initialised = 0; + state->tuner_frequency = 0; + state->symbol_rate = 0; + state->fec_inner = 0; + state->errmode = STATUS_BER; + + stv0288_writeregI(state, 0x41, 0x04); + msleep(200); + id = stv0288_readreg(state, 0x00); + dprintk("stv0288 id %x\n", id); + + /* register 0x00 contains 0x11 for STV0288 */ + if (id != 0x11) + goto error; + + /* create dvb_frontend */ + memcpy(&state->frontend.ops, &stv0288_ops, + sizeof(struct dvb_frontend_ops)); + state->frontend.demodulator_priv = state; + return &state->frontend; + +error: + kfree(state); + + return NULL; +} +EXPORT_SYMBOL(stv0288_attach); + +module_param(debug_legacy_dish_switch, int, 0444); +MODULE_PARM_DESC(debug_legacy_dish_switch, + "Enable timing analysis for Dish Network legacy switches"); + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)."); + +MODULE_DESCRIPTION("ST STV0288 DVB Demodulator driver"); +MODULE_AUTHOR("Georg Acher, Bob Liu, Igor liplianin"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/media/dvb/frontends/stv0288.h b/drivers/media/dvb/frontends/stv0288.h new file mode 100644 index 000000000000..f2b53db0606d --- /dev/null +++ b/drivers/media/dvb/frontends/stv0288.h @@ -0,0 +1,67 @@ +/* + Driver for ST STV0288 demodulator + + Copyright (C) 2006 Georg Acher, BayCom GmbH, acher (at) baycom (dot) de + for Reel Multimedia + Copyright (C) 2008 TurboSight.com, <bob@turbosight.com> + Copyright (C) 2008 Igor M. Liplianin <liplianin@me.by> + Removed stb6000 specific tuner code and revised some + procedures. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef STV0288_H +#define STV0288_H + +#include <linux/dvb/frontend.h> +#include "dvb_frontend.h" + +struct stv0288_config { + /* the demodulator's i2c address */ + u8 demod_address; + + u8* inittab; + + /* minimum delay before retuning */ + int min_delay_ms; + + int (*set_ts_params)(struct dvb_frontend *fe, int is_punctured); +}; + +#if defined(CONFIG_DVB_STV0288) || (defined(CONFIG_DVB_STV0288_MODULE) && \ + defined(MODULE)) +extern struct dvb_frontend *stv0288_attach(const struct stv0288_config *config, + struct i2c_adapter *i2c); +#else +static inline struct dvb_frontend *stv0288_attach(const struct stv0288_config *config, + struct i2c_adapter *i2c) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif /* CONFIG_DVB_STV0288 */ + +static inline int stv0288_writereg(struct dvb_frontend *fe, u8 reg, u8 val) +{ + int r = 0; + u8 buf[] = { reg, val }; + if (fe->ops.write) + r = fe->ops.write(fe, buf, 2); + return r; +} + +#endif /* STV0288_H */ diff --git a/drivers/media/dvb/frontends/stv0299.c b/drivers/media/dvb/frontends/stv0299.c index 35435bef8e79..6c1cb1973c6e 100644 --- a/drivers/media/dvb/frontends/stv0299.c +++ b/drivers/media/dvb/frontends/stv0299.c @@ -559,6 +559,8 @@ static int stv0299_set_frontend(struct dvb_frontend* fe, struct dvb_frontend_par int invval = 0; dprintk ("%s : FE_SET_FRONTEND\n", __func__); + if (state->config->set_ts_params) + state->config->set_ts_params(fe, 0); // set the inversion if (p->inversion == INVERSION_OFF) invval = 0; diff --git a/drivers/media/dvb/frontends/stv0299.h b/drivers/media/dvb/frontends/stv0299.h index 3282f43022f5..0fd96e22b650 100644 --- a/drivers/media/dvb/frontends/stv0299.h +++ b/drivers/media/dvb/frontends/stv0299.h @@ -89,15 +89,18 @@ struct stv0299_config int min_delay_ms; /* Set the symbol rate */ - int (*set_symbol_rate)(struct dvb_frontend* fe, u32 srate, u32 ratio); + int (*set_symbol_rate)(struct dvb_frontend *fe, u32 srate, u32 ratio); + + /* Set device param to start dma */ + int (*set_ts_params)(struct dvb_frontend *fe, int is_punctured); }; #if defined(CONFIG_DVB_STV0299) || (defined(CONFIG_DVB_STV0299_MODULE) && defined(MODULE)) -extern struct dvb_frontend* stv0299_attach(const struct stv0299_config* config, - struct i2c_adapter* i2c); +extern struct dvb_frontend *stv0299_attach(const struct stv0299_config *config, + struct i2c_adapter *i2c); #else -static inline struct dvb_frontend* stv0299_attach(const struct stv0299_config* config, - struct i2c_adapter* i2c) +static inline struct dvb_frontend *stv0299_attach(const struct stv0299_config *config, + struct i2c_adapter *i2c) { printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); return NULL; diff --git a/drivers/media/dvb/frontends/tdhd1.h b/drivers/media/dvb/frontends/tdhd1.h new file mode 100644 index 000000000000..51f170678650 --- /dev/null +++ b/drivers/media/dvb/frontends/tdhd1.h @@ -0,0 +1,73 @@ +/* + * tdhd1.h - ALPS TDHD1-204A tuner support + * + * Copyright (C) 2008 Oliver Endriss <o.endriss@gmx.de> + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + * + * The project's page is at http://www.linuxtv.org + */ + +#ifndef TDHD1_H +#define TDHD1_H + +#include "tda1004x.h" + +static int alps_tdhd1_204_request_firmware(struct dvb_frontend *fe, const struct firmware **fw, char *name); + +static struct tda1004x_config alps_tdhd1_204a_config = { + .demod_address = 0x8, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_4M, + .agc_config = TDA10046_AGC_DEFAULT, + .if_freq = TDA10046_FREQ_3617, + .request_firmware = alps_tdhd1_204_request_firmware +}; + +static int alps_tdhd1_204a_tuner_set_params(struct dvb_frontend *fe, struct dvb_frontend_parameters *params) +{ + struct i2c_adapter *i2c = fe->tuner_priv; + u8 data[4]; + struct i2c_msg msg = { .addr = 0x61, .flags = 0, .buf = data, .len = sizeof(data) }; + u32 div; + + div = (params->frequency + 36166666) / 166666; + + data[0] = (div >> 8) & 0x7f; + data[1] = div & 0xff; + data[2] = 0x85; + + if (params->frequency >= 174000000 && params->frequency <= 230000000) + data[3] = 0x02; + else if (params->frequency >= 470000000 && params->frequency <= 823000000) + data[3] = 0x0C; + else if (params->frequency > 823000000 && params->frequency <= 862000000) + data[3] = 0x8C; + else + return -EINVAL; + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(i2c, &msg, 1) != 1) + return -EIO; + + return 0; +} + +#endif /* TDHD1_H */ |