Mailing List archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[linux-dvb] Re: Terratec Cinergy 1200 DVB-C Card supported? [SOLVED]



Okay, demodulator and tuner works now.
I can view tv with my Cinergy 1200 DVB-C Card.

Really great thanks to Robert Schlabbach who invited me into the depths of dvb programming :)

I've attached the tda10021.c source and little modified versions of budget.c and budget.h (defines for pci-id)

One problem still exists, if i unload the module my system hangs.

Markus
/*
 * budget.c: driver for the SAA7146 based Budget DVB cards 
 *
 * Compiled from various sources by Michael Hunold <michael@mihu.de> 
 *
 * Copyright (C) 2002 Ralph Metzler <rjkm@metzlerbros.de>
 *
 * Copyright (C) 1999-2002 Ralph  Metzler 
 *                       & Marcus Metzler for convergence integrated media GmbH
 *
 * 26feb2004 Support for FS Activy Card (Grundig tuner) by
 *           Michael Dreher <michael@5dot1.de>,
 *           Oliver Endriss <o.endriss@gmx.de> and
 *           Andreas 'randy' Weinberger
 * 
 * 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/dvb/
 */

#include "budget.h"

static void Set22K (struct budget *budget, int state)
{
	struct saa7146_dev *dev=budget->dev;
	dprintk(2, "budget: %p\n", budget);
	saa7146_setgpio(dev, 3, (state ? SAA7146_GPIO_OUTHI : SAA7146_GPIO_OUTLO));
}


/* Diseqc functions only for TT Budget card */
/* taken from the Skyvision DVB driver by
   Ralph Metzler <rjkm@metzlerbros.de> */

static void DiseqcSendBit (struct budget *budget, int data)
{
	struct saa7146_dev *dev=budget->dev;
	dprintk(2, "budget: %p\n", budget);

	saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTHI);
	udelay(data ? 500 : 1000);
	saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO);
	udelay(data ? 1000 : 500);
}


static void DiseqcSendByte (struct budget *budget, int data)
{
	int i, par=1, d;

	dprintk(2, "budget: %p\n", budget);

	for (i=7; i>=0; i--) {
		d = (data>>i)&1;
		par ^= d;
		DiseqcSendBit(budget, d);
	}

	DiseqcSendBit(budget, par);
}


static int SendDiSEqCMsg (struct budget *budget, int len, u8 *msg, unsigned long burst)
{
	struct saa7146_dev *dev=budget->dev;
	int i;

	dprintk(2, "budget: %p\n", budget);

	saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO);
	mdelay(16);

	for (i=0; i<len; i++)
		DiseqcSendByte(budget, msg[i]);

	mdelay(16);

	if (burst!=-1) {
		if (burst)
			DiseqcSendByte(budget, 0xff);
		else {
			saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTHI);
			udelay(12500);
			saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO);
		}
		msleep(20);
	}

	return 0;
}


int budget_diseqc_ioctl (struct dvb_frontend *fe, unsigned int cmd, void *arg)
{
       struct budget *budget = fe->before_after_data;

       dprintk(2, "budget: %p\n", budget);

       switch (cmd) {
       case FE_SET_TONE:
               switch ((fe_sec_tone_mode_t) arg) {
               case SEC_TONE_ON:
                       Set22K (budget, 1);
                       break;
               case SEC_TONE_OFF:
                       Set22K (budget, 0);
                       break;
               default:
                       return -EINVAL;
               };
               break;

       case FE_DISEQC_SEND_MASTER_CMD:
       {
               struct dvb_diseqc_master_cmd *cmd = arg;

               SendDiSEqCMsg (budget, cmd->msg_len, cmd->msg, 0);
               break;
       }

       case FE_DISEQC_SEND_BURST:
               SendDiSEqCMsg (budget, 0, NULL, (unsigned long)arg);
               break;

       default:
               return -EOPNOTSUPP;
       };

       return 0;
}


/*
 *   Routines for the Fujitsu Siemens Activy budget card
 *   22 kHz tone and DiSEqC are handled by the frontend.
 *   Voltage must be set here.
 */
static int SetVoltage_Activy (struct budget *budget, fe_sec_voltage_t voltage)
{
	struct saa7146_dev *dev=budget->dev;

	dprintk(2, "budget: %p\n", budget);

	switch (voltage) {
		case SEC_VOLTAGE_13:
			saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTLO);
			break;
		case SEC_VOLTAGE_18:
			saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTHI);
			break;
		default:
			return -EINVAL;
	}

	return 0;
}


static int budget_ioctl_activy (struct dvb_frontend *fe, unsigned int cmd, void *arg)
{
	struct budget *budget = fe->before_after_data;

	dprintk(2, "budget: %p\n", budget);

	switch (cmd) {
		case FE_SET_VOLTAGE:
			return SetVoltage_Activy (budget, (fe_sec_voltage_t) arg);
		default:
			return -EOPNOTSUPP;
	}

	return 0;
}


static int budget_attach (struct saa7146_dev* dev, struct saa7146_pci_extension_data *info)
{
	struct budget *budget = NULL;
	int err;

	budget = kmalloc(sizeof(struct budget), GFP_KERNEL);
	if( NULL == budget ) {
		return -ENOMEM;
	}

	dprintk(2, "dev:%p, info:%p, budget:%p\n", dev, info, budget);

	dev->ext_priv = budget;

	if ((err = ttpci_budget_init (budget, dev, info))) {
		printk("==> failed\n");
		kfree (budget);
		return err;
	}

	if (budget->card->type == BUDGET_FS_ACTIVY)
		dvb_add_frontend_ioctls (budget->dvb_adapter,
				 budget_ioctl_activy, NULL, budget);
	else
		dvb_add_frontend_ioctls (budget->dvb_adapter,
				 budget_diseqc_ioctl, NULL, budget);

	return 0;
}


static int budget_detach (struct saa7146_dev* dev)
{
	struct budget *budget = (struct budget*) dev->ext_priv;
	int err;

	if (budget->card->type == BUDGET_FS_ACTIVY)
		dvb_remove_frontend_ioctls (budget->dvb_adapter,
				    budget_ioctl_activy, NULL);
	else
		dvb_remove_frontend_ioctls (budget->dvb_adapter,
				    budget_diseqc_ioctl, NULL);

	err = ttpci_budget_deinit (budget);

	kfree (budget);
	dev->ext_priv = NULL;
	
	return err;
}



static struct saa7146_extension budget_extension;

MAKE_BUDGET_INFO(ttbs,	"TT-Budget/WinTV-NOVA-S  PCI",	BUDGET_TT);
MAKE_BUDGET_INFO(ttbc,	"TT-Budget/WinTV-NOVA-C  PCI",	BUDGET_TT);
MAKE_BUDGET_INFO(ttbt,	"TT-Budget/WinTV-NOVA-T  PCI",	BUDGET_TT);
MAKE_BUDGET_INFO(satel,	"SATELCO Multimedia PCI",	BUDGET_TT_HW_DISEQC);
MAKE_BUDGET_INFO(fsacs, "Fujitsu Siemens Activy Budget-S PCI", BUDGET_FS_ACTIVY);
MAKE_BUDGET_INFO(cin12c, "Terratec Cinergy 1200 DVB-C", BUDGET_CIN1200C);

/* Uncomment for Budget Patch */
/*MAKE_BUDGET_INFO(fs_1_3,"Siemens/Technotrend/Hauppauge PCI rev1.3+Budget_Patch", BUDGET_PATCH);*/

static struct pci_device_id pci_tbl[] = {
	/* Uncomment for Budget Patch */
	/*MAKE_EXTENSION_PCI(fs_1_3,0x13c2, 0x0000),*/
	MAKE_EXTENSION_PCI(ttbs,  0x13c2, 0x1003),
	MAKE_EXTENSION_PCI(ttbc,  0x13c2, 0x1004),//13c2,1004
	MAKE_EXTENSION_PCI(ttbt,  0x13c2, 0x1005),
	MAKE_EXTENSION_PCI(satel, 0x13c2, 0x1013),
	MAKE_EXTENSION_PCI(fsacs, 0x1131, 0x4f61),
	MAKE_EXTENSION_PCI(cin12c, 0x153b, 0x1156),
	{
		.vendor    = 0,
	}
};

MODULE_DEVICE_TABLE(pci, pci_tbl);

static struct saa7146_extension budget_extension = {
	.name		= "budget dvb\0",
	.flags	 	= 0,
	
	.module		= THIS_MODULE,
	.pci_tbl	= pci_tbl,
	.attach		= budget_attach,
	.detach		= budget_detach,

	.irq_mask	= MASK_10,
	.irq_func	= ttpci_budget_irq10_handler,
};	


static int __init budget_init(void) 
{
	return saa7146_register_extension(&budget_extension);
}


static void __exit budget_exit(void)
{
	saa7146_unregister_extension(&budget_extension); 
}

module_init(budget_init);
module_exit(budget_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Ralph Metzler, Marcus Metzler, Michael Hunold, others");
MODULE_DESCRIPTION("driver for the SAA7146 based so-called "
		   "budget PCI DVB cards by Siemens, Technotrend, Hauppauge");

#ifndef __BUDGET_DVB__
#define __BUDGET_DVB__

#include "dvb_frontend.h"
#include "dvbdev.h"
#include "demux.h"
#include "dvb_demux.h"
#include "dmxdev.h"
#include "dvb_filter.h"
#include "dvb_net.h"

#include <media/saa7146.h>

extern int budget_debug;

#ifdef dprintk
#undef dprintk
#endif

#define dprintk(level,args...) \
            do { if ((budget_debug & level)) { printk("%s: %s(): ",__stringify(KBUILD_MODNAME), __FUNCTION__); printk(args); } } while (0)

struct budget_info {
	char *name;
	int type;
};

/* place to store all the necessary device information */
struct budget {

        /* devices */
        struct dvb_device       dvb_dev;
        struct dvb_net		dvb_net;

        struct saa7146_dev	*dev;

	struct i2c_adapter	i2c_adap;	
	struct budget_info	*card;

	unsigned char		*grabbing;
	struct saa7146_pgtable	pt;

	struct tasklet_struct   fidb_tasklet;
	struct tasklet_struct   vpe_tasklet;

        struct dmxdev           dmxdev;
        struct dvb_demux	demux;

        struct dmx_frontend     hw_frontend;
        struct dmx_frontend     mem_frontend;

        int                     fe_synced; 
        struct semaphore        pid_mutex;
	
	int                     ci_present;
        int                     video_port;

        u8 tsf;
        u32 ttbp;
        int feeding;

	spinlock_t feedlock;

        struct dvb_adapter       *dvb_adapter;
	void			 *priv;
};

#define MAKE_BUDGET_INFO(x_var,x_name,x_type) \
static struct budget_info x_var ## _info = { \
	.name=x_name,	\
	.type=x_type };	\
static struct saa7146_pci_extension_data x_var = { \
	.ext_priv = &x_var ## _info, \
	.ext = &budget_extension };

#define TS_WIDTH  (376)
#define TS_HEIGHT (512)
#define TS_BUFLEN (TS_WIDTH*TS_HEIGHT)
#define TS_MAX_PACKETS (TS_BUFLEN/TS_SIZE)

#define BUDGET_TT		   0
#define BUDGET_TT_HW_DISEQC	   1
#define BUDGET_KNC1		   2
#define BUDGET_PATCH		   3
#define BUDGET_FS_ACTIVY	   4
#define BUDGET_CIN1200		   5
#define BUDGET_CIN1200C 		6

#define BUDGET_VIDEO_PORTA         0
#define BUDGET_VIDEO_PORTB         1

extern int ttpci_budget_init (struct budget *budget,
			      struct saa7146_dev* dev,
			      struct saa7146_pci_extension_data *info);
extern int ttpci_budget_deinit (struct budget *budget);
extern void ttpci_budget_irq10_handler (struct saa7146_dev* dev, u32 *isr);
extern void ttpci_budget_set_video_port(struct saa7146_dev* dev, int video_port);

#endif

/*
    TDA10021  - Single Chip Cable Channel Receiver driver module
               used on the the Siemens DVB-C cards

    Copyright (C) 1999 Convergence Integrated Media GmbH <ralph@convergence.de>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include <linux/config.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/slab.h>

#include "dvb_frontend.h"


static u8 addr [] = { 0x60, 0x61, 0x62, 0x63 };

/* I2C_DRIVERID_TDA10021 is already defined in i2c-id.h */

#if 0
static int debug = 0;
#define dprintk	if (debug) printk
#endif

static int verbose = 1;
#define TUNER_MUL 62500
//#define TUNER_MUL 78125

struct tda10021_state {
	u8 reg0;
	int tuner;
	u8 demod_addr;
	struct i2c_adapter *i2c;
	struct dvb_adapter *dvb;
};

#define XIN 57840000UL
#define DISABLE_INVERSION(reg0)		do { reg0 &= 0xdf; } while (0)
#define ENABLE_INVERSION(reg0)		do { reg0 |= 0x20; } while (0)
#define HAS_INVERSION(reg0)		(!(reg0 & 0x20))

#define FIN (XIN >> 4)

static struct dvb_frontend_info tda10021_info = {
	.name = "TDA10021 based DVB-C frontend",
	.type = FE_QAM,
	.frequency_stepsize = TUNER_MUL,
	.frequency_min = 51000000,
	.frequency_max = 858000000,
	.symbol_rate_min = (XIN / 2) / 64,	/* SACLK/64 == (XIN/2)/64 */
	.symbol_rate_max = (XIN / 2) / 4,	/* SACLK/4 */
#if 0
	.frequency_tolerance = ? ? ?,
	.symbol_rate_tolerance = ? ? ?,	/* ppm *//* == 8% (spec p. 5) */
	.notifier_delay = ?,
#endif
	.caps = 
		FE_CAN_QAM_16 |
		FE_CAN_QAM_32 |
		FE_CAN_QAM_64 |
		FE_CAN_QAM_128 |
		FE_CAN_QAM_256 |
		FE_CAN_FEC_AUTO |
		FE_CAN_INVERSION_AUTO,
};



// //table from ves1820
// static int tabsize = 0x3D;
// static u8 tda10021_inittab[] = {
// 	0x69, 0x6A, 0x9B, 0x12, 0x12, 0x46, 0x26, 0x1A,
// 	0x43, 0x6A, 0xAA, 0xAA, 0x1E, 0x85, 0x43, 0x20,
// 	0xE0, 0x00, 0xA1, 0x00, 0x00, 0x00, 0x00, 0x00,
// 	0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
// 	0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 	0x00, 0x00, 0x00, 0x00, 0x40
// };



//table from ralph tda10021 version
static int tabsize = 0x40;
static u8 tda10021_inittab[0x40]={
	0x73, 0x6a, 0x23, 0x0a, 0x02, 0x37, 0x77, 0x1a,
	0x37, 0x6a, 0x17, 0x8a, 0x1e, 0x86, 0x43, 0x40,
	0xb8, 0x3f, 0xa1, 0x00, 0xcd, 0x01, 0x00, 0xff,	
	0x11, 0x00, 0x7c, 0x31, 0x30, 0x20, 0x00, 0x00,
	0x02, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x00,	
	0x07, 0x00, 0x33, 0x11, 0x0d, 0x95, 0x08, 0x58,
	0x00, 0x00, 0x80, 0x00, 0x80, 0xff, 0x00, 0x00,
	0x04, 0x2d, 0x2f, 0xff, 0x00, 0x00, 0x00, 0x00,
};


// //table generated from paper for tda10021
// static int tabsize = 0x3D;
// static u8 tda10021_inittab[0x3D]={
//    0x6b, 0x6a, 0x04, 0x13, 0x02, 0x47, 0x27, 0x1a, 
// 	0x43, 0x6a, 0x66, 0xfb, 0x1e, 0x04, 0x02, 0x40, 
// 	0x78, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 
// 	0x00, 0x00, 0x7c, 0x00, 0x10, 0x00, 0x00, 0x00,
// 	0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 	0x00, 0x00, 0x10, 0x50, 0x0c, 0x06, 0x00, 0x00,
// 	0x00, 0x00, 0x80, 0x00, 0x00, 0xff, 0x00, 0x00,
// 	0x00, 0x00, 0x00, 0xff, 0x00, 
// };

//get access to tuner
static int lock_tuner(struct i2c_adapter *i2c, u8 demu_addr)
{
	u8 buf[2] = { 0x0f, tda10021_inittab[0x0f] | 0x80 };
	struct i2c_msg msg = {.addr=demu_addr, .flags=0, .buf=buf, .len=2};
	
	if(i2c_transfer(i2c, &msg, 1) != 1)
	{
		printk("tda10021: lock tuner fails\n");
		return -1;
	}
	return 0;
}

//release access from tuner
static int unlock_tuner(struct i2c_adapter *i2c, u8 demu_addr)
{
	u8 buf[2] = { 0x0f, tda10021_inittab[0x0f] & 0x7f };
	struct i2c_msg msg_post={.addr=demu_addr, .flags=0, .buf=buf, .len=2};
	
	if(i2c_transfer(i2c, &msg_post, 1) != 1)
	{
		printk("tda10021: unlock tuner fails\n");
		return -1;
	}
	return 0;
}

static int tda10021_writereg2(struct i2c_adapter *i2c, u8 demod_addr, u8 reg, u8 data)
{
	u8 buf[] = { reg, data };
	struct i2c_msg msg = {.addr = demod_addr,.flags = 0,.buf = buf,.len = 2 };
	int ret;
	ret = i2c_transfer(i2c, &msg, 1);
	if (ret != 1)
		printk("tda10021: %s(): writereg2 error (reg == 0x%02x,"
			"val == 0x%02x, ret == %i)\n", __FUNCTION__, reg, data, ret);
	return (ret != 1) ? -EREMOTEIO : 0;
}

static int tda10021_writereg(struct tda10021_state *state, u8 reg, u8 data)
{
	return tda10021_writereg2(state->i2c, state->demod_addr, reg, data);
}

static u8 tda10021_readreg2(struct i2c_adapter *i2c, u8 demod_addr, u8 reg)
{
	u8 b0[] = { reg };
	u8 b1[] = { 0 };
	struct i2c_msg msg[] = {
	{.addr = demod_addr,.flags = 0,.buf = b0,.len = 1},
	{.addr = demod_addr,.flags = I2C_M_RD,.buf = b1,.len = 1}
	};
	int ret;
	ret = i2c_transfer(i2c, msg, 2);
	if (ret != 2)
		printk("tda10021: %s(): readreg error (reg == 0x%02x,"
		"ret == %i)\n", __FUNCTION__, reg, ret);
	return b1[0];
}

static u8 tda10021_readreg(struct tda10021_state *state, u8 reg)
{
	return tda10021_readreg2(state->i2c, state->demod_addr, reg);
}

static int tuner_write(struct tda10021_state *state, u8 addr, u8 data[4])
{
	int ret;
	struct i2c_msg msg={.addr=addr,.flags=0,.buf=data,.len=4 };
	
	lock_tuner(state->i2c, state->demod_addr);
	ret = i2c_transfer(state->i2c, &msg, 1);
	if (ret != 1)
		printk("tda10021: %s(): i/o error addr=0x%02x (ret == %i)\n", __FUNCTION__, addr, ret);
		
	unlock_tuner(state->i2c, state->demod_addr);
	
	return (ret != 1) ? -EREMOTEIO : 0;
}
/**  set up the downconverter frequency divisor for a
 *   reference clock comparision frequency of 62.5 kHz.*/
static int tuner_set_tv_freq(struct tda10021_state *state, u32 freq)
{
	int tuner_type = state->tuner;
	u32 div = (freq + 36125000 + TUNER_MUL/2) / TUNER_MUL;
	u8 buf[4] = { (div>>8)&0x7f, div&0xff, 0x00, 0x00 };
	
	if (tuner_type == 0xff)	/*  PLL not reachable over i2c ...  */
		return 0;
	printk("tda10021: set freq to %d\n", freq);
	tuner_type = 0;
	switch(tuner_type)
	{
		case 0:
		buf[2] = 0x8e;
		buf[3] =(freq < 174500000 ? 0xa1 :
			  freq < 454000000 ? 0x92 : 0x34);		
		break;
	case 1:
		buf[2] = 0x85;
		buf[3] =(freq < 178000000 ? 0x06 :
			  freq < 449000000 ? 0x05 : 0x03);
		break;
	case 2:
		buf[2] = 0x8e;
		buf[3] =(freq < 168000000 ? 0x01 :
			  freq < 448000000 ? 0x02 : 0x04);
		break;		
	}
	printk("tda10021: write tuner values: 0x%02x 0x%02x 0x%02x 0x%02x\n", buf[0], buf[1], buf[2], buf[3]);
	tuner_type = 0;
	return tuner_write(state, addr[tuner_type], buf);
}

static int set_inversion(struct tda10021_state *state, fe_spectral_inversion_t inversion)
{
	switch (inversion) {
	case INVERSION_AUTO:
		return -EOPNOTSUPP;
	default:
	case INVERSION_ON:
		//enable inversion bit
		state->reg0|=0x20;
		printk("tda10021: switch inversion on\n");
		break;
	case INVERSION_OFF:
		//disable inversion bit
		state->reg0&=0xdf;
		printk("tda10021: switch inversion off\n");
		break;
	}
//	if (state->type&FE_TYPE_INVERSE)
		state->reg0^=0x20;
	return 0;
}

static int set_conf(struct tda10021_state *state, u8 val)
{
	//INVIQ must be off (Bit 5)
	val&=0xdf;
	//or with current reg0 but without QAM Bits (2,3,4)
	val|=state->reg0&0x63;
	//val&0xfe set CLB to zero and do an reset
	printk("tda10021: set_conf1 = 0x%02x\n", val&0xfe);
	tda10021_writereg(state, 0x00, val&0xfe);
	//don't know why, cause bit 0 will autoreset after
	//5 X IN Periods
	printk("tda10021: set_conf2 = 0x%02x\n", val|0x01);
   tda10021_writereg(state, 0x00, val|0x01);
	state->reg0=val;
	return 0;

}

static long probe_tuner(struct i2c_adapter *i2c, long demod_addr);

static int tda10021_init(struct tda10021_state *state)
{
	int i;

	//tda10021_writereg(state, 0, 0);

	for (i = 0; i < tabsize; i++)
		tda10021_writereg(state, i, tda10021_inittab[i]);

	//INSERT BY NIAS
	//0x2A[3-0] == PDIV -> P multiplaying factor (P=PDIV+1)(default 0)
	//0x2A[4] == BYPPLL -> Power down mode (default 1)
	//0x2A[5] == LCK -> PLL Lock Flag
	//0x2A[6] == POLAXIN -> Polarity of the input reference clock (default 0)
	//Activate PLL
	tda10021_writereg(state, 0x2a, tda10021_inittab[0x2a] & 0xef);
		
	//COMMENT BY NIAS
	//tda10021_writereg(state, 0x34, state->pwm);
	probe_tuner(state->i2c, state->demod_addr);

	return 0;
}

#define MCLK   57840000ULL
#define MCLK16 (MCLK>>4)
#define MCLK10 (MCLK*10)
static int tda10021_set_symbolrate(struct tda10021_state *state, u32 srate)
{
   u32 bdr, bdri; 
   u8 sfil=0, ndec;
	u64 tmp;

	if (srate<500000 || srate>MCLK/2)
		return -EINVAL;

	//test if anti-aliasing filter must be on or off
	if (srate<MCLK10/984) sfil=1;
	else 
		if (srate>MCLK10/640 && srate<MCLK10/492) sfil = 1;
		else 
			if (srate>MCLK10/320 && srate<MCLK10/246) sfil = 1;
			else 
				if (srate>MCLK10/160 && srate<MCLK10/123) sfil = 1;
        
	ndec=srate<MCLK16/4 ? 3 : srate<MCLK16/2 ? 2 : srate<MCLK16   ? 1 : 0;
	
	srate<<=ndec;
	//srate is now SR * 2^NDEC
	
	tmp=((u64)srate<<20)+MCLK16/2;
	do_div(tmp, MCLK16);
	
	bdr=tmp;
	bdri=(((MCLK<<5)/srate)+1)/2;

	//Set Baudrate  BDR
	//0x0a[7-0] == BDR 
	tda10021_writereg(state, 0x0a, 0xff&bdr);
	//0x0b[7-0] == BDR 
	tda10021_writereg(state, 0x0b, 0xff&(bdr>>8));
	//0x0C[4-0] == BDR
	tda10021_writereg(state, 0x0c, 0xff&(bdr>>16));
	//0x0d[7-0] == BDRI -> Baudrate inverse
	tda10021_writereg(state, 0x0d, (bdri>0xFF)?0xff:bdri);
	
	//0x03[2-0] == CLK_C -> Parameter of the timing loop filter (default 2)
	//0x03[3] == DYN -> Acquisition range (default 0)
	//0x03[4] == GAIN3 -> Gain of the third decimation filter (default 1)
	//0x03[5] == GAIN2 -> Gain of the second decimation filter (default 0)
	//0x03[7-6] == NDEC -> Number of decimations (default 0)
	tda10021_writereg(state, 0x03, (ndec<<6)|tda10021_inittab[0x03]);
	
	//0x0E[7-5] == GNYQ -> Gain of Nyquist filter from 1 to 12
	//0x0E[4] == SFIL -> Anti-Aliasing Filter selection (default 0)
	//0x0E[3-2] == GAAF -> Anti-Aliasing Filter Gain (default 0)
	//0x0E[1-0] == SSAT -> Number of samples used to count the saturation (default 2)
	tda10021_writereg(state, 0x0e, (sfil<<4)|tda10021_inittab[0x0E]);

	return 0;
}

static const struct {
	u8 reg0, reg1, reg5, reg8, reg9;
} qampars[6] = {
	{  0x14, 0x78, 0x78, 0x8c, 0x96},  // QAM4
	{  0x00, 0x8c, 0x87, 0xa2, 0x91},  // QAM16
	{  0x04, 0x8c, 0x64, 0x74, 0x96},  // QAM32
	{  0x08, 0x6a, 0x46, 0x43, 0x6a},  // QAM64
	{  0x0c, 0x78, 0x36, 0x34, 0x7e},  // QAM128
	{  0x10, 0x5c, 0x26, 0x23, 0x6b},  // QAM256
};

static int tda10021_set_parameters(struct tda10021_state *state, struct dvb_frontend_parameters *p)
{
	int qam = p->u.qam.modulation;
	int res;
	
	if (qam>QAM_256)
		return -EINVAL;

	res = tuner_set_tv_freq(state, p->frequency);
	if(res != 0)
		return res;
	msleep(100);
	//set inversion bit in reg0 variable, will written back later (set_conf)
	res = set_inversion(state, p->inversion);	
	if(res != 0)
		return res;
		
	res = tda10021_set_symbolrate(state, p->u.qam.symbol_rate);
	if(res != 0)
		return res;
		
	//don't know for what i need this pwm value (can be read back encoded?!?)
	//tda10021_writereg(state, 0x34, state->pwm);
	
	//AGCREF value
	tda10021_writereg(state, 0x01, qampars[qam].reg1);
	//LTHR value
	tda10021_writereg(state, 0x05, qampars[qam].reg5);
	//MSETH
	tda10021_writereg(state, 0x08, qampars[qam].reg8);
	//AREF
	tda10021_writereg(state, 0x09, qampars[qam].reg9);

	//write reg0x0 with qam and inversion settings
	set_conf(state, qampars[qam].reg0);

	return 0;
}

static int tda10021_ioctl(struct dvb_frontend *fe, unsigned int cmd, void *arg)
{
	struct tda10021_state *state = (struct tda10021_state *) fe->data;

	switch (cmd) {
	case FE_GET_INFO:
		//printk("tda10021: get info\n");
		memcpy(arg, &tda10021_info, sizeof(struct dvb_frontend_info));
		break;

	case FE_READ_STATUS:
		{
			fe_status_t *status = (fe_status_t *) arg;
			int sync;

			*status = 0;

			//0x11[0] == EQALGO -> Equalizer algorithms state
			//0x11[1] == CARLOCK -> Carrier locked
			//0x11[2] == FSYNC -> Frame synchronisation
			//0x11[3] == FEL -> Front End locked
			//0x11[6] == NODVB -> DVB Mode Information
			sync = tda10021_readreg(state, 0x11);

			if (sync&2) 
				*status|=FE_HAS_SIGNAL|FE_HAS_CARRIER;
			if (sync&4)
				*status|=FE_HAS_SYNC|FE_HAS_VITERBI;
			if (sync&8)
				*status|=FE_HAS_LOCK;
				
			//printk("tda10021: read status sync=0x%02x\n",sync);
			break;
		}

	case FE_READ_BER:
		{
			u32 ber = tda10021_readreg(state, 0x14) |
					(tda10021_readreg(state, 0x15) << 8) |
					((tda10021_readreg(state, 0x16) & 0x0f) << 16);
			*((u32 *) arg) = 10 * ber;
			//printk("tda10021: read ber %d\n", *((u32 *) arg));
			break;
		}
	case FE_READ_SIGNAL_STRENGTH:
		{
			u8 gain = tda10021_readreg(state, 0x17);
			*((u16 *) arg) = (gain << 8) | gain;
			//printk("tda10021: read signal strength %d\n", *((u32 *) arg));
			break;
		}

	case FE_READ_SNR:
		{
			u8 quality = ~tda10021_readreg(state, 0x18);
			*((u16 *) arg) = (quality << 8) | quality;
			//printk("tda10021: read snr %d\n", *((u32 *) arg));
			break;
		}

	case FE_READ_UNCORRECTED_BLOCKS:
		*((u32 *) arg) = tda10021_readreg(state, 0x13) & 0x7f;
		if (*((u32 *) arg) == 0x7f)
			*((u32 *) arg) = 0xffffffff;
		/* reset uncorrected block counter */
		tda10021_writereg(state, 0x10, tda10021_inittab[0x10] & 0xdf);
		tda10021_writereg(state, 0x10, tda10021_inittab[0x10]);
		printk("tda10021: read uncorrected blocks %d\n", *((u32 *) arg));
		break;

	case FE_SET_FRONTEND:
		//printk("tda10021: calling set frontend\n");
		return tda10021_set_parameters(state, arg);

	case FE_GET_FRONTEND:
		{
			//printk("tda10021: calling get frontend\n");
			struct dvb_frontend_parameters *p = (struct dvb_frontend_parameters *) arg;
			int sync;
			s8 afc = 0;

			sync = tda10021_readreg(state, 0x11);
			afc = tda10021_readreg(state, 0x19);
			if (verbose) 
			{
				/* AFC only valid when carrier has been recovered */
				printk(sync & 2 ? "tda10021: AFC (%d) %dHz\n" :
					"tda10021: [AFC (%d) %dHz]\n", afc, -((s32) p->u.qam.symbol_rate * afc) >> 10);
			}

			p->inversion = HAS_INVERSION(state->reg0) ? INVERSION_ON : INVERSION_OFF;
			p->u.qam.modulation = ((state->reg0 >> 2) & 7) + QAM_16;

			p->u.qam.fec_inner = FEC_NONE;

			p->frequency = ((p->frequency + 31250) / TUNER_MUL) * TUNER_MUL;
			if (sync & 2)
				p->frequency -= ((s32) p->u.qam.symbol_rate * afc) >> 10;
			break;
		}
	case FE_SLEEP:
		//printk("tda10021: sleep\n");
		tda10021_writereg(state, 0x1b, 0x02);	/* pdown ADC */
		//tda10021_writereg(state, 0x00, 0x80);	/* standby */
		break;

	case FE_INIT:
		//printk("tda10021: calling init\n");
		return tda10021_init(state);

	default:
		return -EINVAL;
	}

	return 0;
}


static int
test_tuner(struct i2c_adapter *i2c, long demod_addr, u8 addr)
{
	u8 buf[1]={ 0x00 };
   struct i2c_msg msg={.addr=addr, .flags=I2C_M_RD, .buf=buf, .len=1};
	int res;
		
	lock_tuner(i2c, demod_addr);
	res = i2c_transfer(i2c, &msg, 1);
	unlock_tuner(i2c, demod_addr);
	return res == 1?0:-1;
}

static long probe_tuner(struct i2c_adapter *i2c, long demod_addr)
{
	int type=0;
	u8 addr;
	for(addr=0x60;addr<0x64;addr++,type++)
	{
		if(test_tuner(i2c, demod_addr, addr) == 0)
		{
			printk("tda10021: tuner type %d found\n", type);
			return type;
		}
	}
	printk("tda10021: no valid tuner type found\n");
	return -1;
}

static long probe_demod_addr(struct i2c_adapter *i2c)
{
	u8 b[] = { 0x1a };
	u8 id = 0;
	struct i2c_msg msg[] = 
	{ 
		{.addr = 0x0c,.flags = 0,.buf = b,.len = 1},
		{.addr = 0x0c,.flags = I2C_M_RD,.buf = &id,.len = 1}
	};
	int res;
	int iaddr;
	if(!i2c->algo->master_xfer)
		printk("tda10021: I2C level transfers not supported\n");
	else
	for(iaddr=0x0c; iaddr < 0x0e; iaddr++)
	{
		msg[0].addr = msg[1].addr = iaddr;
		res = i2c_transfer(i2c, msg, 2);
		if(res == 2 && ((id & 0xf0) == 0x70))
		{
			printk("tda10021: probe addr=0x%02x success. id = 0x%02x\n", iaddr, id);
			return msg[0].addr;
		}
		else
			printk("tda10021: probe of addr=0x%02x failed\n", iaddr);
	}
	return -1;
}

static struct i2c_client client_template;

static int attach_adapter(struct i2c_adapter *adapter)
{
	struct i2c_client *client;
	struct tda10021_state *state;
	long demod_addr;
	int tuner_type;
	int ret;
	int n;

	demod_addr = probe_demod_addr(adapter);
	if (demod_addr < 0)
		return -ENODEV;

	for (n=0; n < tabsize; n++)
		tda10021_writereg2(adapter,demod_addr,n, tda10021_inittab[n]);
	tda10021_writereg2(adapter, demod_addr,0x2a,tda10021_inittab[0x2a] & 0xef);
	
	tuner_type = probe_tuner(adapter, demod_addr);
	if (tuner_type < 0) {
		printk("tda10021: demod found, but unknown tuner type.\n");
		return -ENODEV;
	}

	if ((state = kmalloc(sizeof(struct tda10021_state), GFP_KERNEL)) == NULL) {
		return -ENOMEM;
	}

	if (NULL == (client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL))) {
		kfree(state);
		return -ENOMEM;
	}

	memset(state, 0, sizeof(*state));
	state->i2c = adapter;
	state->tuner = tuner_type;
	state->reg0 = tda10021_inittab[0];
	state->demod_addr = demod_addr;

	memcpy(client, &client_template, sizeof(struct i2c_client));
	client->adapter = adapter;
	client->addr = addr[tuner_type];

	i2c_set_clientdata(client, (void *) state);

	ret = i2c_attach_client(client);
	if (ret) {
		kfree(client);
		kfree(state);
		return ret;
	}

	BUG_ON(!state->dvb);

	ret = dvb_register_frontend(tda10021_ioctl, state->dvb, state, &tda10021_info, THIS_MODULE);
	if (ret) {
		i2c_detach_client(client);
		kfree(client);
		kfree(state);
		return ret;
	}

	return 0;
}

static int detach_client(struct i2c_client *client)
{
	struct tda10021_state *state = (struct tda10021_state *) i2c_get_clientdata(client);
	dvb_unregister_frontend(tda10021_ioctl, state->dvb);
	i2c_detach_client(client);
	BUG_ON(state->dvb);
	kfree(client);
	kfree(state);
	return 0;
}

static int command(struct i2c_client *client, unsigned int cmd, void *arg)
{
	struct tda10021_state *state = (struct tda10021_state *) i2c_get_clientdata(client);

	switch (cmd) {
	case FE_REGISTER:{
			state->dvb = (struct dvb_adapter *) arg;
			break;
		}
	case FE_UNREGISTER:{
			state->dvb = NULL;
			break;
		}
	default:
		return -EOPNOTSUPP;
	}
	return 0;
}

static struct i2c_driver driver = {
	.owner = THIS_MODULE,
	.name = "tda10021",
	.id = 0xF2, //I2C_DRIVERID_TDA10021 <- I2C_DRIVERID_VES1820
	.flags = I2C_DF_NOTIFY,
	.attach_adapter = attach_adapter,
	.detach_client = detach_client,
	.command = command,
};

static struct i2c_client client_template = {
	I2C_DEVNAME("tda10021"),
	.flags = I2C_CLIENT_ALLOW_USE,
	.driver = &driver,
};

static int __init init_tda10021(void)
{
	return i2c_add_driver(&driver);
}

static void __exit exit_tda10021(void)
{
	if (i2c_del_driver(&driver))
		printk("tda10021: driver deregistration failed\n");
}

module_init(init_tda10021);
module_exit(exit_tda10021);

MODULE_PARM(verbose, "i");
MODULE_PARM_DESC(verbose, "print AFC offset after tuning for debugging the PWM setting");

MODULE_DESCRIPTION("TDA10021 DVB-C frontend driver");
MODULE_AUTHOR("Ralph Metzler, Holger Waechtler");
MODULE_LICENSE("GPL");

Home | Main Index | Thread Index