📄 tda1004x.c
字号:
/* 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 '/etc/dvb/tda1004x.mc'. You can also pass the complete file name with the module parameter 'tda1004x_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 <linux/kernel.h>#include <linux/vmalloc.h>#include <linux/module.h>#include <linux/init.h>#include <linux/string.h>#include <linux/slab.h>#include <linux/fs.h>#include <linux/unistd.h>#include <linux/fcntl.h>#include <linux/errno.h>#include "dvb_frontend.h"/** * a sleeping delay function, waits i ms * */static inlinevoid dvb_delay(int i){ current->state=TASK_INTERRUPTIBLE; schedule_timeout((HZ*i)/1000);}#ifndef CONFIG_TDA1004X_MC_LOCATION#define CONFIG_TDA1004X_MC_LOCATION "/etc/dvb/tda1004x.mc"#endifstatic int tda1004x_debug = 0;static char *tda1004x_firmware = CONFIG_TDA1004X_MC_LOCATION;#define TDA10045H_ADDRESS 0x08#define TD1344_ADDRESS 0x61#define TDM1316L_ADDRESS 0x63#define MC44BC374_ADDRESS 0x65#define TDA1004X_CHIPID 0x00#define TDA1004X_AUTO 0x01#define TDA1004X_IN_CONF1 0x02#define TDA1004X_IN_CONF2 0x03#define TDA1004X_OUT_CONF1 0x04#define TDA1004X_OUT_CONF2 0x05#define TDA1004X_STATUS_CD 0x06#define TDA1004X_CONFC4 0x07#define TDA1004X_DSSPARE2 0x0C#define TDA1004X_CODE_IN 0x0D#define TDA1004X_FWPAGE 0x0E#define TDA1004X_SCAN_CPT 0x10#define TDA1004X_DSP_CMD 0x11#define TDA1004X_DSP_ARG 0x12#define TDA1004X_DSP_DATA1 0x13#define TDA1004X_DSP_DATA2 0x14#define TDA1004X_CONFADC1 0x15#define TDA1004X_CONFC1 0x16#define TDA1004X_SIGNAL_STRENGTH 0x1a#define TDA1004X_SNR 0x1c#define TDA1004X_REG1E 0x1e#define TDA1004X_REG1F 0x1f#define TDA1004X_CBER_RESET 0x20#define TDA1004X_CBER_MSB 0x21#define TDA1004X_CBER_LSB 0x22#define TDA1004X_CVBER_LUT 0x23#define TDA1004X_VBER_MSB 0x24#define TDA1004X_VBER_MID 0x25#define TDA1004X_VBER_LSB 0x26#define TDA1004X_UNCOR 0x27#define TDA1004X_CONFPLL_P 0x2D#define TDA1004X_CONFPLL_M_MSB 0x2E#define TDA1004X_CONFPLL_M_LSB 0x2F#define TDA1004X_CONFPLL_N 0x30#define TDA1004X_UNSURW_MSB 0x31#define TDA1004X_UNSURW_LSB 0x32#define TDA1004X_WREF_MSB 0x33#define TDA1004X_WREF_MID 0x34#define TDA1004X_WREF_LSB 0x35#define TDA1004X_MUXOUT 0x36#define TDA1004X_CONFADC2 0x37#define TDA1004X_IOFFSET 0x38#define dprintk if (tda1004x_debug) printkstatic struct dvb_frontend_info tda10045h_info = { .name = "Philips TDA10045H", .type = FE_OFDM, .frequency_min = 51000000, .frequency_max = 858000000, .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 tda1004x_state { u8 tda1004x_address; u8 tuner_address; u8 initialised:1;};#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} };static int tda10045h_fwinfo_count = sizeof(tda10045h_fwinfo) / sizeof(struct fwinfo);static int errno;static int tda1004x_write_byte(struct dvb_i2c_bus *i2c, struct tda1004x_state *tda_state, int reg, int data){ int ret; u8 buf[] = { reg, data }; struct i2c_msg msg = { .addr=0, .flags=0, .buf=buf, .len=2 }; dprintk("%s: reg=0x%x, data=0x%x\n", __FUNCTION__, reg, data); msg.addr = tda_state->tda1004x_address; 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 tda1004x_read_byte(struct dvb_i2c_bus *i2c, struct tda1004x_state *tda_state, int reg){ int ret; u8 b0[] = { reg }; u8 b1[] = { 0 }; struct i2c_msg msg[] = {{ .addr=0, .flags=0, .buf=b0, .len=1}, { .addr=0, .flags=I2C_M_RD, .buf=b1, .len = 1}}; dprintk("%s: reg=0x%x\n", __FUNCTION__, reg); msg[0].addr = tda_state->tda1004x_address; msg[1].addr = tda_state->tda1004x_address; 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 tda1004x_write_mask(struct dvb_i2c_bus *i2c, struct tda1004x_state *tda_state, int reg, int mask, int data){ int val; dprintk("%s: reg=0x%x, mask=0x%x, data=0x%x\n", __FUNCTION__, reg, mask, data); // read a byte and check val = tda1004x_read_byte(i2c, tda_state, reg); if (val < 0) return val; // mask if off val = val & ~mask; val |= data & 0xff; // write it out again return tda1004x_write_byte(i2c, tda_state, reg, val);}static int tda1004x_write_buf(struct dvb_i2c_bus *i2c, struct tda1004x_state *tda_state, int reg, unsigned char *buf, int len){ int i; int result; dprintk("%s: reg=0x%x, len=0x%x\n", __FUNCTION__, reg, len); result = 0; for (i = 0; i < len; i++) { result = tda1004x_write_byte(i2c, tda_state, reg + i, buf[i]); if (result != 0) break; } return result;}static int tda1004x_enable_tuner_i2c(struct dvb_i2c_bus *i2c, struct tda1004x_state *tda_state){ int result; dprintk("%s\n", __FUNCTION__); result = tda1004x_write_mask(i2c, tda_state, TDA1004X_CONFC4, 2, 2); dvb_delay(1); return result;}static int tda1004x_disable_tuner_i2c(struct dvb_i2c_bus *i2c, struct tda1004x_state *tda_state){ dprintk("%s\n", __FUNCTION__); return tda1004x_write_mask(i2c, tda_state, TDA1004X_CONFC4, 2, 0);}static int tda10045h_set_bandwidth(struct dvb_i2c_bus *i2c, struct tda1004x_state *tda_state, fe_bandwidth_t bandwidth){ static u8 bandwidth_6mhz[] = { 0x02, 0x00, 0x3d, 0x00, 0x60, 0x1e, 0xa7, 0x45, 0x4f }; static u8 bandwidth_7mhz[] = { 0x02, 0x00, 0x37, 0x00, 0x4a, 0x2f, 0x6d, 0x76, 0xdb }; static u8 bandwidth_8mhz[] = { 0x02, 0x00, 0x3d, 0x00, 0x48, 0x17, 0x89, 0xc7, 0x14 }; switch (bandwidth) { case BANDWIDTH_6_MHZ: tda1004x_write_byte(i2c, tda_state, TDA1004X_DSSPARE2, 0x14); tda1004x_write_buf(i2c, tda_state, TDA1004X_CONFPLL_P, bandwidth_6mhz, sizeof(bandwidth_6mhz)); break; case BANDWIDTH_7_MHZ: tda1004x_write_byte(i2c, tda_state, TDA1004X_DSSPARE2, 0x80); tda1004x_write_buf(i2c, tda_state, TDA1004X_CONFPLL_P, bandwidth_7mhz, sizeof(bandwidth_7mhz)); break; case BANDWIDTH_8_MHZ: tda1004x_write_byte(i2c, tda_state, TDA1004X_DSSPARE2, 0x14); tda1004x_write_buf(i2c, tda_state, TDA1004X_CONFPLL_P, bandwidth_8mhz, sizeof(bandwidth_8mhz)); break; default: return -EINVAL; } tda1004x_write_byte(i2c, tda_state, TDA1004X_IOFFSET, 0); // done return 0;}static int tda1004x_init(struct dvb_i2c_bus *i2c, struct tda1004x_state *tda_state){ u8 fw_buf[65]; struct i2c_msg fw_msg = {.addr = 0,.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 fd; int fwinfo_idx; int fw_size = 0; int fw_pos; int tx_size; static u8 disable_mc44BC374c[] = { 0x1d, 0x74, 0xa0, 0x68 }; mm_segment_t fs = get_fs(); dprintk("%s\n", __FUNCTION__); // Load the firmware set_fs(get_ds()); fd = open(tda1004x_firmware, 0, 0); if (fd < 0) { printk("%s: Unable to open firmware %s\n", __FUNCTION__, tda1004x_firmware); return -EIO; } filesize = lseek(fd, 0L, 2); if (filesize <= 0) { printk("%s: Firmware %s is empty\n", __FUNCTION__, tda1004x_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__, tda1004x_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 tda1004x_enable_tuner_i2c(i2c, tda_state); tuner_msg.addr = MC44BC374_ADDRESS; tuner_msg.buf = disable_mc44BC374c; tuner_msg.len = sizeof(disable_mc44BC374c); if (i2c->xfer(i2c, &tuner_msg, 1) != 1) { i2c->xfer(i2c, &tuner_msg, 1); } tda1004x_disable_tuner_i2c(i2c, tda_state); // set some valid bandwith parameters switch(tda_state->tda1004x_address) { case TDA10045H_ADDRESS: tda10045h_set_bandwidth(i2c, tda_state, BANDWIDTH_8_MHZ); break; } dvb_delay(500); // do the firmware upload tda1004x_write_byte(i2c, tda_state, TDA1004X_FWPAGE, 0); fw_msg.addr = tda_state->tda1004x_address; 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] = TDA1004X_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);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -