/* Driver for Philips TDA10045H OFDM Frontend This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* This driver needs a copy of the DLL "ttlcdacc.dll" from the Haupauge or Technotrend windows driver saved as '/usr/lib/DVB/driver/frontends/tda10045h.mc'. You can also pass the complete file name with the module parameter 'tda10045h_firmware'. Currently the DLL from v2.15a of the technotrend driver is supported. Other versions can be added reasonably painlessly. Windows driver URL: http://www.technotrend.de/ */ #define __KERNEL_SYSCALLS__ #include #include #include #include #include #include #include #include #include #include #include "dvb_frontend.h" #include "dvb_functions.h" static int tda10045h_debug = 0; static char *tda10045h_firmware = "/usr/lib/DVB/driver/frontends/tda10045h.mc"; #define TDA10045H_ADDRESS 0x08 #define TDA10045H_TUNERA_ADDRESS 0x61 #define TDA10045H_TDM1316L_ADDRESS 0x63 #define TDA10045H_MC44BC374_ADDRESS 0x65 #define TDA10045H_CHIPID 0x00 #define TDA10045H_AUTO 0x01 #define TDA10045H_IN_CONF1 0x02 #define TDA10045H_IN_CONF2 0x03 #define TDA10045H_OUT_CONF1 0x04 #define TDA10045H_OUT_CONF2 0x05 #define TDA10045H_STATUS_CD 0x06 #define TDA10045H_CONFC4 0x07 #define TDA10045H_REG0C 0x0C #define TDA10045H_CODE_IN 0x0D #define TDA10045H_FWPAGE 0x0E #define TDA10045H_SCAN_CPT 0x10 #define TDA10045H_DSP_CMD 0x11 #define TDA10045H_DSP_ARG 0x12 #define TDA10045H_DSP_DATA1 0x13 #define TDA10045H_DSP_DATA2 0x14 #define TDA10045H_CONFADC1 0x15 #define TDA10045H_CONFC1 0x16 #define TDA10045H_SNR 0x1c #define TDA10045H_REG1E 0x1e #define TDA10045H_REG1F 0x1f #define TDA10045H_CBER_MSB 0x21 #define TDA10045H_CBER_LSB 0x22 #define TDA10045H_CVBER_LUT 0x23 #define TDA10045H_VBER_MSB 0x24 #define TDA10045H_VBER_MID 0x25 #define TDA10045H_VBER_LSB 0x26 #define TDA10045H_UNCOR 0x27 #define TDA10045H_CONFPLL_P 0x2D #define TDA10045H_CONFPLL_M_MSB 0x2E #define TDA10045H_CONFPLL_M_LSB 0x2F #define TDA10045H_CONFPLL_N 0x30 #define TDA10045H_UNSURW_MSB 0x31 #define TDA10045H_UNSURW_LSB 0x32 #define TDA10045H_WREF_MSB 0x33 #define TDA10045H_WREF_MID 0x34 #define TDA10045H_WREF_LSB 0x35 #define TDA10045H_MUXOUT 0x36 #define TDA10045H_CONFADC2 0x37 #define TDA10045H_IOFFSET 0x38 #define dprintk if (tda10045h_debug) printk static struct dvb_frontend_info tda10045h_info = { .name = "Philips TDA10045H", .type = FE_OFDM, .frequency_min = 87000000, .frequency_max = 895000000, .frequency_stepsize = 166667, .caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO }; #pragma pack(1) struct tda10045h_state { u8 tuner_address; u8 initialised; }; #pragma pack() struct fwinfo { int file_size; int fw_offset; int fw_size; }; static struct fwinfo tda10045h_fwinfo[] = { {.file_size = 286720,.fw_offset = 0x34cc5,.fw_size = 30555}, /* 2.15a */ }; static int tda10045h_fwinfo_count = sizeof(tda10045h_fwinfo) / sizeof(struct fwinfo); static u8 disable_mc44BC374c[] = { 0x1d, 0x74, 0xa0, 0x68 }; static u8 bandwidth_8mhz[] = { 0x02, 0x00, 0x3d, 0x00, 0x48, 0x17, 0x89, 0xc7, 0x14 }; static u8 bandwidth_7mhz[] = { 0x02, 0x00, 0x37, 0x00, 0x4a, 0x2f, 0x6d, 0x76, 0xdb }; static u8 bandwidth_6mhz[] = { 0x02, 0x00, 0x3d, 0x00, 0x60, 0x1e, 0xa7, 0x45, 0x4f }; static u8 tuner_data[] = { 0x0b, 0xf5, 0x88, 0xab, 0x00 }; static int errno; static int tda10045h_write_byte(struct dvb_i2c_bus *i2c, int reg, int data) { int ret; u8 buf[] = { reg, data }; struct i2c_msg msg = {.addr = TDA10045H_ADDRESS,.flags = 0,.buf = buf,.len = 2 }; dprintk("%s: reg=0x%x, data=0x%x\n", __FUNCTION__, reg, data); ret = i2c->xfer(i2c, &msg, 1); if (ret != 1) dprintk("%s: error reg=0x%x, data=0x%x, ret=%i\n", __FUNCTION__, reg, data, ret); dprintk("%s: success reg=0x%x, data=0x%x, ret=%i\n", __FUNCTION__, reg, data, ret); return (ret != 1) ? -1 : 0; } static int tda10045h_read_byte(struct dvb_i2c_bus *i2c, int reg) { int ret; u8 b0[] = { reg }; u8 b1[] = { 0 }; struct i2c_msg msg[] = { {.addr = TDA10045H_ADDRESS,.flags = 0,.buf = b0,.len = 1}, {.addr = TDA10045H_ADDRESS,.flags = I2C_M_RD,.buf = b1,.len = 1} }; dprintk("%s: reg=0x%x\n", __FUNCTION__, reg); ret = i2c->xfer(i2c, msg, 2); if (ret != 2) { dprintk("%s: error reg=0x%x, ret=%i\n", __FUNCTION__, reg, ret); return -1; } dprintk("%s: success reg=0x%x, data=0x%x, ret=%i\n", __FUNCTION__, reg, b1[0], ret); return b1[0]; } static int tda10045h_write_mask(struct dvb_i2c_bus *i2c, int reg, int mask, int data) { int val; dprintk("%s: reg=0x%x, mask=0x%x, data=0x%x\n", __FUNCTION__, reg, mask, data); // read a byte and check val = tda10045h_read_byte(i2c, reg); if (val < 0) return val; // mask if off val = val & ~mask; val |= data & 0xff; // write it out again return tda10045h_write_byte(i2c, reg, val); } static int tda10045h_write_buf(struct dvb_i2c_bus *i2c, int reg, unsigned char *buf, int len) { int i; int result; dprintk("%s: reg=0x%x, len=0x%x\n", __FUNCTION__, reg, len); result = 0; for (i = 0; i < len; i++) { result = tda10045h_write_byte(i2c, reg + i, buf[i]); if (result != 0) break; } return result; } static int tda10045h_enable_tuner_i2c(struct dvb_i2c_bus *i2c) { int result; dprintk("%s\n", __FUNCTION__); result = tda10045h_write_mask(i2c, TDA10045H_CONFC4, 2, 2); dvb_delay(1); return result; } static int tda10045h_disable_tuner_i2c(struct dvb_i2c_bus *i2c) { dprintk("%s\n", __FUNCTION__); return tda10045h_write_mask(i2c, TDA10045H_CONFC4, 2, 0); } static int tda10045h_dsp_command(struct dvb_i2c_bus *i2c, int cmd, int arg) { int counter; int data1; int data2; dprintk("%s: cmd=0x%x, arg=0x%x\n", __FUNCTION__, cmd, arg); // send command and argument if (tda10045h_write_byte(i2c, TDA10045H_DSP_ARG, arg) < 0) return -1; if (tda10045h_write_byte(i2c, TDA10045H_DSP_CMD, cmd) < 0) return -1; // command retry loop counter = 0; while (counter++ < 5) { // read in the two data bytes data1 = tda10045h_read_byte(i2c, TDA10045H_DSP_DATA1); data2 = tda10045h_read_byte(i2c, TDA10045H_DSP_DATA2); if ((data1 < 0) || (data2 < 0)) return -1; // finshed yet? if (data1 == cmd) continue; if (data2 == arg) continue; // OK, resend command if (tda10045h_write_byte(i2c, TDA10045H_DSP_CMD, cmd) < 0) return -1; } // OK, did it work? if (data1 != cmd) return -1; if (data2 != arg) return -1; // success return 0; } static int tda10045h_init(struct dvb_i2c_bus *i2c) { int fw_pos; int tx_size; int counter; u8 fw_buf[65]; struct i2c_msg fw_msg = {.addr = TDA10045H_ADDRESS,.flags = 0,.buf = fw_buf,.len = 0 }; struct i2c_msg tuner_msg = {.addr = 0,.flags = 0,.buf = 0,.len = 0 }; unsigned char *firmware = NULL; int filesize; int fw_size = 0; int fd; int data1; int fwinfo_idx; mm_segment_t fs = get_fs(); dprintk("%s\n", __FUNCTION__); // Load the firmware set_fs(get_ds()); fd = open(tda10045h_firmware, 0, 0); if (fd < 0) { printk("%s: Unable to open firmware %s\n", __FUNCTION__, tda10045h_firmware); return -EIO; } filesize = lseek(fd, 0L, 2); if (filesize <= 0) { printk("%s: Firmware %s is empty\n", __FUNCTION__, tda10045h_firmware); sys_close(fd); return -EIO; } // find extraction parameters for (fwinfo_idx = 0; fwinfo_idx < tda10045h_fwinfo_count; fwinfo_idx++) { if (tda10045h_fwinfo[fwinfo_idx].file_size == filesize) break; } if (fwinfo_idx >= tda10045h_fwinfo_count) { printk("%s: Unsupported firmware %s\n", __FUNCTION__, tda10045h_firmware); sys_close(fd); return -EIO; } fw_size = tda10045h_fwinfo[fwinfo_idx].fw_size; // allocate buffer for it firmware = vmalloc(fw_size); if (firmware == NULL) { printk("%s: Out of memory loading firmware\n", __FUNCTION__); sys_close(fd); return -EIO; } // read it! lseek(fd, tda10045h_fwinfo[fwinfo_idx].fw_offset, 0); if (read(fd, firmware, fw_size) != fw_size) { printk("%s: Failed to read firmware\n", __FUNCTION__); vfree(firmware); sys_close(fd); return -EIO; } sys_close(fd); set_fs(fs); // Disable the MC44BC374C tda10045h_enable_tuner_i2c(i2c); tuner_msg.addr = TDA10045H_MC44BC374_ADDRESS; tuner_msg.buf = disable_mc44BC374c; tuner_msg.len = sizeof(disable_mc44BC374c); if (i2c->xfer(i2c, &tuner_msg, 1) != 1) { if (i2c->xfer(i2c, &tuner_msg, 1) != 1) { if (firmware) vfree(firmware); return -EIO; } } tda10045h_disable_tuner_i2c(i2c); // setup for firmware upload tda10045h_write_buf(i2c, TDA10045H_CONFPLL_P, bandwidth_8mhz, sizeof(bandwidth_8mhz)); tda10045h_write_byte(i2c, TDA10045H_IOFFSET, 0); dvb_delay(500); // do the firmware upload tda10045h_write_byte(i2c, TDA10045H_FWPAGE, 0); fw_pos = 0; while (fw_pos != fw_size) { // work out how much to send this time tx_size = fw_size - fw_pos; if (tx_size > 64) { tx_size = 64; } // send the chunk fw_buf[0] = TDA10045H_CODE_IN; memcpy(fw_buf + 1, firmware + fw_pos, tx_size); fw_msg.len = tx_size + 1; if (i2c->xfer(i2c, &fw_msg, 1) != 1) { vfree(firmware); return -EIO; } fw_pos += tx_size; dprintk("%s: fw_pos=0x%x\n", __FUNCTION__, fw_pos); } dvb_delay(100); vfree(firmware); // Initialise the DSP and check upload was OK tda10045h_write_mask(i2c, TDA10045H_CONFC4, 0x10, 0); tda10045h_write_byte(i2c, TDA10045H_DSP_CMD, 0x67); if ((tda10045h_read_byte(i2c, TDA10045H_DSP_DATA1) != 0x67) || (tda10045h_read_byte(i2c, TDA10045H_DSP_DATA2) != 0x2c)) { printk("%s: firmware upload failed!\n", __FUNCTION__); return -EIO; } // tda setup tda10045h_write_byte(i2c, TDA10045H_CONFADC1, 0x2e); tda10045h_write_mask(i2c, TDA10045H_CONFC1, 0x40, 0); tda10045h_write_mask(i2c, TDA10045H_CONFC4, 0x20, 0); tda10045h_write_mask(i2c, TDA10045H_VBER_MSB, 0xe0, 0xa0); tda10045h_write_byte(i2c, TDA10045H_REG1F, 0); tda10045h_write_byte(i2c, TDA10045H_REG1E, 0); tda10045h_write_mask(i2c, TDA10045H_CONFC1, 0x80, 0x80); // DSP init tda10045h_write_mask(i2c, TDA10045H_CONFC4, 0x10, 0); if (tda10045h_write_byte(i2c, TDA10045H_DSP_CMD, 0x61) < 0) return -1; // command retry loop counter = 0; while (counter++ < 5) { // read in the data byte data1 = tda10045h_read_byte(i2c, TDA10045H_DSP_DATA1); if (data1 < 0) return data1; // finshed yet? if (data1 & 1) continue; // OK, resend command if (tda10045h_write_byte(i2c, TDA10045H_DSP_CMD, 0x61) < 0) return -1; } tda10045h_write_byte(i2c, TDA10045H_DSP_DATA1, 0x01); tda10045h_write_byte(i2c, TDA10045H_DSP_DATA2, 0x0e); tda10045h_dsp_command(i2c, 0x69, 0); tda10045h_write_byte(i2c, TDA10045H_DSP_DATA2, 0x01); tda10045h_dsp_command(i2c, 0x69, 1); tda10045h_write_byte(i2c, TDA10045H_DSP_DATA2, 0x03); tda10045h_dsp_command(i2c, 0x69, 2); // tda setup tda10045h_write_mask(i2c, TDA10045H_CONFADC2, 0x20, 0x20); tda10045h_write_mask(i2c, TDA10045H_CONFADC1, 0x80, 0); tda10045h_write_mask(i2c, TDA10045H_CONFC1, 0x10, 0); tda10045h_write_mask(i2c, TDA10045H_AUTO, 0x10, 0x10); tda10045h_write_mask(i2c, TDA10045H_IN_CONF2, 0xC0, 0x0); tda10045h_write_mask(i2c, TDA10045H_AUTO, 8, 0); // done return 0; } static int tda10045h_encode_fec(int fec) { // convert known FEC values switch (fec) { case FEC_1_2: return 0; case FEC_2_3: return 1; case FEC_3_4: return 2; case FEC_5_6: return 3; case FEC_7_8: return 4; } // unsupported return -EINVAL; } static int tda10045h_decode_fec(int tdafec) { // convert known FEC values switch (tdafec) { case 0: return FEC_1_2; case 1: return FEC_2_3; case 2: return FEC_3_4; case 3: return FEC_5_6; case 4: return FEC_7_8; } // unsupported return -1; } static int tda10045h_set_frequency(struct dvb_i2c_bus *i2c, struct tda10045h_state *tda_state, struct dvb_frontend_parameters *fe_params) { int counter, counter2; u8 tuner_buf[5]; u8 v1, v2, v3; struct i2c_msg tuner_msg = {.addr = 0,.flags = 0,.buf = tuner_buf,.len = sizeof(tuner_buf) }; int tuner_frequency; dprintk("%s\n", __FUNCTION__); // setup the frequency buffer switch (tda_state->tuner_address) { case TDA10045H_TUNERA_ADDRESS: // setup tuner buffer tuner_frequency = (((fe_params->frequency / 1000) * 6) + 217502) / 1000; tuner_buf[0] = tuner_frequency >> 8; tuner_buf[1] = tuner_frequency & 0xff; tuner_buf[2] = 0x88; if (fe_params->frequency < 550000000) { tuner_buf[3] = 0xab; } else { tuner_buf[3] = 0xeb; } // tune it tda10045h_enable_tuner_i2c(i2c); tuner_msg.addr = tda_state->tuner_address; tuner_msg.len = 4; i2c->xfer(i2c, &tuner_msg, 1); // wait for it to finish tuner_msg.len = 1; tuner_msg.flags = I2C_M_RD; counter = 0; counter2 = 0; while (counter++ < 100) { if (i2c->xfer(i2c, &tuner_msg, 1) == 1) { if (tuner_buf[0] & 0x40) { counter2++; } else { counter2 = 0; } } if (counter2 > 10) { break; } } tda10045h_disable_tuner_i2c(i2c); break; case TDA10045H_TDM1316L_ADDRESS: // determine settings tuner_frequency = fe_params->frequency + 36167000; if (tuner_frequency < 87000000) { return -EINVAL; } else if (tuner_frequency < 130000000) { v1 = 1; v2 = 3; } else if (tuner_frequency < 160000000) { v1 = 1; v2 = 5; } else if (tuner_frequency < 200000000) { v1 = 1; v2 = 6; } else if (tuner_frequency < 290000000) { v1 = 2; v2 = 3; } else if (tuner_frequency < 420000000) { v1 = 2; v2 = 5; } else if (tuner_frequency < 480000000) { v1 = 2; v2 = 6; } else if (tuner_frequency < 620000000) { v1 = 4; v2 = 3; } else if (tuner_frequency < 830000000) { v1 = 4; v2 = 5; } else if (tuner_frequency < 895000000) { v1 = 4; v2 = 7; } else { return -EINVAL; } // work out v3 switch (fe_params->u.ofdm.bandwidth) { case BANDWIDTH_6_MHZ: // FIXME: IS THIS CORRECT??????? if (fe_params->frequency <= 300000000) { v3 = 0; } else { v3 = 1; } break; case BANDWIDTH_7_MHZ: v3 = 0; break; case BANDWIDTH_8_MHZ: v3 = 1; break; default: return -EINVAL; } // calculate tuner parameters tuner_frequency = (((fe_params->frequency / 1000) * 6) + 217502) / 1000; tuner_buf[0] = tuner_frequency >> 8; tuner_buf[1] = tuner_frequency & 0xff; tuner_buf[2] = 0xca; tuner_buf[3] = (3 << 5) | (v3 << 3) | v1; tuner_buf[4] = 0x85; // tune it tda10045h_enable_tuner_i2c(i2c); tuner_msg.addr = tda_state->tuner_address; tuner_msg.len = 5; if (i2c->xfer(i2c, &tuner_msg, 1) != 1) { return -EIO; } dvb_delay(50); tuner_buf[3] = (v2 << 5) | (v3 << 3) | v1; if (i2c->xfer(i2c, &tuner_msg, 1) != 1) { return -EIO; } dvb_delay(1); tda10045h_disable_tuner_i2c(i2c); break; default: return -EINVAL; } dprintk("%s: success\n", __FUNCTION__); // done return 0; } static int tda10045h_set_fe(struct dvb_i2c_bus *i2c, struct tda10045h_state *tda_state, struct dvb_frontend_parameters *fe_params) { int tmp; dprintk("%s\n", __FUNCTION__); // set frequency tmp = tda10045h_set_frequency(i2c, tda_state, fe_params); if (tmp < 0) return tmp; // Set standard params.. or put them to auto if ((fe_params->u.ofdm.code_rate_HP == FEC_AUTO) || (fe_params->u.ofdm.code_rate_LP == FEC_AUTO) || (fe_params->u.ofdm.constellation == QAM_AUTO) || (fe_params->u.ofdm.hierarchy_information == HIERARCHY_AUTO)) { tda10045h_write_mask(i2c, TDA10045H_AUTO, 1, 1); // enable auto tda10045h_write_mask(i2c, TDA10045H_IN_CONF1, 0x03, 0); // turn off constellation bits tda10045h_write_mask(i2c, TDA10045H_IN_CONF1, 0x60, 0); // turn off hierarchy bits tda10045h_write_mask(i2c, TDA10045H_IN_CONF2, 0x3f, 0); // turn off FEC bits } else { tda10045h_write_mask(i2c, TDA10045H_AUTO, 1, 0); // disable auto // set HP FEC tmp = tda10045h_encode_fec(fe_params->u.ofdm.code_rate_HP); if (tmp < 0) return tmp; tda10045h_write_mask(i2c, TDA10045H_IN_CONF2, 7, tmp); // set LP FEC if (fe_params->u.ofdm.code_rate_LP != FEC_NONE) { tmp = tda10045h_encode_fec(fe_params->u.ofdm. code_rate_LP); if (tmp < 0) return tmp; tda10045h_write_mask(i2c, TDA10045H_IN_CONF2, 0x38, tmp << 3); } // set constellation switch (fe_params->u.ofdm.constellation) { case QPSK: tda10045h_write_mask(i2c, TDA10045H_IN_CONF1, 3, 0); break; case QAM_16: tda10045h_write_mask(i2c, TDA10045H_IN_CONF1, 3, 1); break; case QAM_64: tda10045h_write_mask(i2c, TDA10045H_IN_CONF1, 3, 2); break; default: return -EINVAL; } // set hierarchy switch (fe_params->u.ofdm.hierarchy_information) { case HIERARCHY_NONE: tda10045h_write_mask(i2c, TDA10045H_IN_CONF1, 0x60, 0 << 5); break; case HIERARCHY_1: tda10045h_write_mask(i2c, TDA10045H_IN_CONF1, 0x60, 1 << 5); break; case HIERARCHY_2: tda10045h_write_mask(i2c, TDA10045H_IN_CONF1, 0x60, 2 << 5); break; case HIERARCHY_4: tda10045h_write_mask(i2c, TDA10045H_IN_CONF1, 0x60, 3 << 5); break; default: return -EINVAL; } } // set bandwidth switch (fe_params->u.ofdm.bandwidth) { case BANDWIDTH_6_MHZ: tda10045h_write_byte(i2c, TDA10045H_REG0C, 0x14); tda10045h_write_buf(i2c, TDA10045H_CONFPLL_P, bandwidth_6mhz, sizeof(bandwidth_6mhz)); break; case BANDWIDTH_7_MHZ: tda10045h_write_byte(i2c, TDA10045H_REG0C, 0x80); tda10045h_write_buf(i2c, TDA10045H_CONFPLL_P, bandwidth_7mhz, sizeof(bandwidth_7mhz)); break; case BANDWIDTH_8_MHZ: tda10045h_write_byte(i2c, TDA10045H_REG0C, 0x14); tda10045h_write_buf(i2c, TDA10045H_CONFPLL_P, bandwidth_8mhz, sizeof(bandwidth_8mhz)); break; default: return -EINVAL; } // set inversion switch (fe_params->inversion) { case INVERSION_OFF: tda10045h_write_mask(i2c, TDA10045H_CONFC1, 0x20, 0); break; case INVERSION_ON: tda10045h_write_mask(i2c, TDA10045H_CONFC1, 0x20, 0x20); break; default: return -EINVAL; } // set guard interval switch (fe_params->u.ofdm.guard_interval) { case GUARD_INTERVAL_1_32: tda10045h_write_mask(i2c, TDA10045H_AUTO, 2, 0); tda10045h_write_mask(i2c, TDA10045H_IN_CONF1, 0x0c, 0 << 2); break; case GUARD_INTERVAL_1_16: tda10045h_write_mask(i2c, TDA10045H_AUTO, 2, 0); tda10045h_write_mask(i2c, TDA10045H_IN_CONF1, 0x0c, 1 << 2); break; case GUARD_INTERVAL_1_8: tda10045h_write_mask(i2c, TDA10045H_AUTO, 2, 0); tda10045h_write_mask(i2c, TDA10045H_IN_CONF1, 0x0c, 2 << 2); break; case GUARD_INTERVAL_1_4: tda10045h_write_mask(i2c, TDA10045H_AUTO, 2, 0); tda10045h_write_mask(i2c, TDA10045H_IN_CONF1, 0x0c, 3 << 2); break; case GUARD_INTERVAL_AUTO: tda10045h_write_mask(i2c, TDA10045H_AUTO, 2, 2); tda10045h_write_mask(i2c, TDA10045H_IN_CONF1, 0x0c, 0 << 2); break; default: return -EINVAL; } // set transmission mode switch (fe_params->u.ofdm.transmission_mode) { case TRANSMISSION_MODE_2K: tda10045h_write_mask(i2c, TDA10045H_AUTO, 4, 0); tda10045h_write_mask(i2c, TDA10045H_IN_CONF1, 0x10, 0 << 4); break; case TRANSMISSION_MODE_8K: tda10045h_write_mask(i2c, TDA10045H_AUTO, 4, 0); tda10045h_write_mask(i2c, TDA10045H_IN_CONF1, 0x10, 1 << 4); break; case TRANSMISSION_MODE_AUTO: tda10045h_write_mask(i2c, TDA10045H_AUTO, 4, 4); tda10045h_write_mask(i2c, TDA10045H_IN_CONF1, 0x10, 0); break; default: return -EINVAL; } // reset DSP tda10045h_write_mask(i2c, TDA10045H_CONFC4, 8, 8); tda10045h_write_mask(i2c, TDA10045H_CONFC4, 8, 0); dvb_delay(10); // done return 0; } static int tda10045h_get_fe(struct dvb_i2c_bus *i2c, struct dvb_frontend_parameters *fe_params) { dprintk("%s\n", __FUNCTION__); // inversion status fe_params->inversion = INVERSION_OFF; if (tda10045h_read_byte(i2c, TDA10045H_CONFC1) & 0x20) { fe_params->inversion = INVERSION_ON; } // bandwidth switch (tda10045h_read_byte(i2c, TDA10045H_WREF_LSB)) { case 0x14: fe_params->u.ofdm.bandwidth = BANDWIDTH_8_MHZ; break; case 0xdb: fe_params->u.ofdm.bandwidth = BANDWIDTH_7_MHZ; break; case 0x4f: fe_params->u.ofdm.bandwidth = BANDWIDTH_6_MHZ; break; } // FEC fe_params->u.ofdm.code_rate_HP = tda10045h_decode_fec(tda10045h_read_byte (i2c, TDA10045H_OUT_CONF2) & 7); fe_params->u.ofdm.code_rate_LP = tda10045h_decode_fec((tda10045h_read_byte (i2c, TDA10045H_OUT_CONF2) >> 3) & 7); // constellation switch (tda10045h_read_byte(i2c, TDA10045H_OUT_CONF1) & 3) { case 0: fe_params->u.ofdm.constellation = QPSK; break; case 1: fe_params->u.ofdm.constellation = QAM_16; break; case 2: fe_params->u.ofdm.constellation = QAM_64; break; } // transmission mode fe_params->u.ofdm.transmission_mode = TRANSMISSION_MODE_2K; if (tda10045h_read_byte(i2c, TDA10045H_OUT_CONF1) & 0x10) { fe_params->u.ofdm.transmission_mode = TRANSMISSION_MODE_8K; } // guard interval switch ((tda10045h_read_byte(i2c, TDA10045H_OUT_CONF1) & 0x0c) >> 2) { case 0: fe_params->u.ofdm.guard_interval = GUARD_INTERVAL_1_32; break; case 1: fe_params->u.ofdm.guard_interval = GUARD_INTERVAL_1_16; break; case 2: fe_params->u.ofdm.guard_interval = GUARD_INTERVAL_1_8; break; case 3: fe_params->u.ofdm.guard_interval = GUARD_INTERVAL_1_4; break; } // hierarchy switch ((tda10045h_read_byte(i2c, TDA10045H_OUT_CONF1) & 0x60) >> 5) { case 0: fe_params->u.ofdm.hierarchy_information = HIERARCHY_NONE; break; case 1: fe_params->u.ofdm.hierarchy_information = HIERARCHY_1; break; case 2: fe_params->u.ofdm.hierarchy_information = HIERARCHY_2; break; case 3: fe_params->u.ofdm.hierarchy_information = HIERARCHY_4; break; } // done return 0; } static int tda10045h_read_status(struct dvb_i2c_bus *i2c, fe_status_t * fe_status) { int status; dprintk("%s\n", __FUNCTION__); // read status status = tda10045h_read_byte(i2c, TDA10045H_STATUS_CD); if (status == -1) { return -EIO; } // decode *fe_status = 0; if (status == 0x2f) { *fe_status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK; } // FIXME: decode statuses better dprintk("%s: ------------------ raw_status=0x%x\n", __FUNCTION__, status); // success dprintk("%s: fe_status=0x%x\n", __FUNCTION__, *fe_status); return 0; } static int tda10045h_read_snr(struct dvb_i2c_bus *i2c, u16 * snr) { int tmp; dprintk("%s\n", __FUNCTION__); // read it tmp = tda10045h_read_byte(i2c, TDA10045H_SNR); if (tmp < 0) return -EIO; // FIXME: calculate this properly // done *snr = tmp; dprintk("%s: snr=0x%x\n", __FUNCTION__, *snr); return 0; } static int tda10045h_read_ucblocks(struct dvb_i2c_bus *i2c, u32 * ucblocks) { int tmp; int tmp2; int counter; dprintk("%s\n", __FUNCTION__); // read the UCBLOCKS and reset counter = 0; tmp = tda10045h_read_byte(i2c, TDA10045H_UNCOR) & 0x7f; if (tmp < 0) return -EIO; while (counter++ < 5) { tda10045h_write_mask(i2c, TDA10045H_UNCOR, 0x80, 0); tda10045h_write_mask(i2c, TDA10045H_UNCOR, 0x80, 0); tda10045h_write_mask(i2c, TDA10045H_UNCOR, 0x80, 0); tmp2 = tda10045h_read_byte(i2c, TDA10045H_UNCOR); if (tmp2 < 0) return -EIO; tmp2 &= 0x7f; if ((tmp2 < tmp) || (tmp2 == 0)) break; } // FIXME: calculate this properly // done if (tmp != 0x7f) { *ucblocks = tmp; } else { *ucblocks = 0xffffffff; } dprintk("%s: ucblocks=0x%x\n", __FUNCTION__, *ucblocks); return 0; } static int tda10045h_read_vber(struct dvb_i2c_bus *i2c, u32 * vber) { dprintk("%s\n", __FUNCTION__); // read it in *vber = 0; *vber |= tda10045h_read_byte(i2c, TDA10045H_VBER_LSB); *vber |= tda10045h_read_byte(i2c, TDA10045H_VBER_MID) << 8; *vber |= (tda10045h_read_byte(i2c, TDA10045H_VBER_MSB) & 0x0f) << 16; // reset counter tda10045h_read_byte(i2c, TDA10045H_CVBER_LUT); // FIXME: calculate this properly // done dprintk("%s: vber=0x%x\n", __FUNCTION__, *vber); return 0; } static int tda10045h_ioctl(struct dvb_frontend *fe, unsigned int cmd, void *arg) { int status; struct dvb_i2c_bus *i2c = fe->i2c; struct tda10045h_state *tda_state = (struct tda10045h_state *) &(fe->data); dprintk("%s: cmd=0x%x\n", __FUNCTION__, cmd); switch (cmd) { case FE_GET_INFO: memcpy(arg, &tda10045h_info, sizeof(struct dvb_frontend_info)); break; case FE_READ_STATUS: return tda10045h_read_status(i2c, (fe_status_t *) arg); case FE_READ_BER: return tda10045h_read_vber(i2c, (u32 *) arg); case FE_READ_SIGNAL_STRENGTH: // FIXME: implement break; case FE_READ_SNR: return tda10045h_read_snr(i2c, (u16 *) arg); case FE_READ_UNCORRECTED_BLOCKS: return tda10045h_read_ucblocks(i2c, (u32 *) arg); case FE_SET_FRONTEND: return tda10045h_set_fe(i2c, tda_state, (struct dvb_frontend_parameters *) arg); case FE_GET_FRONTEND: return tda10045h_get_fe(i2c, (struct dvb_frontend_parameters *) arg); case FE_SLEEP: break; case FE_INIT: // don't bother reinitialising if (tda_state->initialised) return 0; // OK, perform initialisation status = tda10045h_init(i2c); if (status == 0) tda_state->initialised = 1; return status; case FE_RESET: // reset DSP tda10045h_write_mask(i2c, TDA10045H_CONFC4, 8, 8); tda10045h_write_mask(i2c, TDA10045H_CONFC4, 8, 0); dvb_delay(10); // reinitialise tda_state->initialised = 0; status = tda10045h_init(i2c); if (status == 0) tda_state->initialised = 1; return status; default: return -EOPNOTSUPP; } return 0; } static int tda10045h_attach(struct dvb_i2c_bus *i2c) { int tuner_address = -1; struct i2c_msg tuner_msg = {.addr = 0,.flags = 0,.buf = 0,.len = 0 }; struct tda10045h_state tda_state; dprintk("%s\n", __FUNCTION__); // supported frontend? if (tda10045h_read_byte(i2c, TDA10045H_CHIPID) != 0x25) return -ENODEV; // supported tuner? tda10045h_enable_tuner_i2c(i2c); tuner_msg.addr = TDA10045H_TUNERA_ADDRESS; tuner_msg.buf = tuner_data; tuner_msg.len = 4; if (i2c->xfer(i2c, &tuner_msg, 1) == 1) { tuner_address = TDA10045H_TUNERA_ADDRESS; printk("tda10045h: Detected, tuner type A.\n"); } else { tuner_msg.addr = TDA10045H_TDM1316L_ADDRESS; tuner_msg.buf = tuner_data; tuner_msg.len = 5; if (i2c->xfer(i2c, &tuner_msg, 1) == 1) { tuner_address = TDA10045H_TDM1316L_ADDRESS; printk ("tda10045h: Detected Philips TDM1316L tuner.\n"); } } tda10045h_disable_tuner_i2c(i2c); // did we find a tuner? if (tuner_address == -1) { printk("tda10045h: Detected, but with unknown tuner.\n"); return -ENODEV; } // create state tda_state.tuner_address = tuner_address; tda_state.initialised = 0; // register dvb_register_frontend(tda10045h_ioctl, i2c, (void *)(*((u32*) &tda_state)), &tda10045h_info); // success return 0; } static void tda10045h_detach(struct dvb_i2c_bus *i2c) { dprintk("%s\n", __FUNCTION__); dvb_unregister_frontend(tda10045h_ioctl, i2c); } static int __init init_tda10045h(void) { return dvb_register_i2c_device(THIS_MODULE, tda10045h_attach, tda10045h_detach); } static void __exit exit_tda10045h(void) { dvb_unregister_i2c_device(tda10045h_attach); } module_init(init_tda10045h); module_exit(exit_tda10045h); MODULE_DESCRIPTION("Philips TDA10045H DVB-T Frontend"); MODULE_AUTHOR("Andrew de Quincey & Robert Schlabbach"); MODULE_LICENSE("GPL"); MODULE_PARM(tda10045h_debug, "i"); MODULE_PARM_DESC(tda10045h_debug, "enable verbose debug messages"); MODULE_PARM(tda10045h_firmware, "s"); MODULE_PARM_DESC(tda10045h_firmware, "where to find the firmware file");