// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * budget-ci.ko: driver for the SAA7146 based Budget DVB cards
 *               with CI (but without analog video input)
 *
 * Compiled from various sources by Michael Hunold <michael@mihu.de>
 *
 *     msp430 IR support contributed by Jack Thomasson <jkt@Helius.COM>
 *     partially based on the Siemens DVB driver by Ralph+Marcus Metzler
 *
 * CI interface support (c) 2004 Andrew de Quincey <adq_dvb@lidskialf.net>
 *
 * the project's page is at https://linuxtv.org
 */

#include <linux/module.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
#include <media/rc-core.h>

#include "budget.h"

#include <media/dvb_ca_en50221.h>
#include "stv0299.h"
#include "stv0297.h"
#include "tda1004x.h"
#include "stb0899_drv.h"
#include "stb0899_reg.h"
#include "stb0899_cfg.h"
#include "stb6100.h"
#include "stb6100_cfg.h"
#include "lnbp21.h"
#include "bsbe1.h"
#include "bsru6.h"
#include "tda1002x.h"
#include "tda827x.h"
#include "bsbe1-d01a.h"

#define MODULE_NAME "budget_ci"

/*
 * Regarding DEBIADDR_IR:
 * Some CI modules hang if random addresses are read.
 * Using address 0x4000 for the IR read means that we
 * use the same address as for CI version, which should
 * be a safe default.
 */
#define DEBIADDR_IR		0x4000
#define DEBIADDR_CICONTROL	0x0000
#define DEBIADDR_CIVERSION	0x4000
#define DEBIADDR_IO		0x1000
#define DEBIADDR_ATTR		0x3000

#define CICONTROL_RESET		0x01
#define CICONTROL_ENABLETS	0x02
#define CICONTROL_CAMDETECT	0x08

#define DEBICICTL		0x00420000
#define DEBICICAM		0x02420000

#define SLOTSTATUS_NONE		1
#define SLOTSTATUS_PRESENT	2
#define SLOTSTATUS_RESET	4
#define SLOTSTATUS_READY	8
#define SLOTSTATUS_OCCUPIED	(SLOTSTATUS_PRESENT|SLOTSTATUS_RESET|SLOTSTATUS_READY)

/* RC5 device wildcard */
#define IR_DEVICE_ANY		255

static int rc5_device = -1;
module_param(rc5_device, int, 0644);
MODULE_PARM_DESC(rc5_device, "only IR commands to given RC5 device (device = 0 - 31, any device = 255, default: autodetect)");

static int ir_debug;
module_param(ir_debug, int, 0644);
MODULE_PARM_DESC(ir_debug, "enable debugging information for IR decoding");

DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);

struct budget_ci_ir {
	struct rc_dev *dev;
	struct work_struct msp430_irq_bh_work;
	char name[72]; /* 40 + 32 for (struct saa7146_dev).name */
	char phys[32];
	int rc5_device;
	u32 ir_key;
	bool have_command;
	bool full_rc5;		/* Outputs a full RC5 code */
};

struct budget_ci {
	struct budget budget;
	struct work_struct ciintf_irq_bh_work;
	int slot_status;
	int ci_irq;
	struct dvb_ca_en50221 ca;
	struct budget_ci_ir ir;
	u8 tuner_pll_address; /* used for philips_tdm1316l configs */
};

static void msp430_ir_interrupt(struct work_struct *t)
{
	struct budget_ci_ir *ir = from_work(ir, t, msp430_irq_bh_work);
	struct budget_ci *budget_ci = container_of(ir, typeof(*budget_ci), ir);
	struct rc_dev *dev = budget_ci->ir.dev;
	u32 command = ttpci_budget_debiread(&budget_ci->budget, DEBINOSWAP, DEBIADDR_IR, 2, 1, 0) >> 8;

	/*
	 * The msp430 chip can generate two different bytes, command and device
	 *
	 * type1: X1CCCCCC, C = command bits (0 - 63)
	 * type2: X0TDDDDD, D = device bits (0 - 31), T = RC5 toggle bit
	 *
	 * Each signal from the remote control can generate one or more command
	 * bytes and one or more device bytes. For the repeated bytes, the
	 * highest bit (X) is set. The first command byte is always generated
	 * before the first device byte. Other than that, no specific order
	 * seems to apply. To make life interesting, bytes can also be lost.
	 *
	 * Only when we have a command and device byte, a keypress is
	 * generated.
	 */

	if (ir_debug)
		pr_info("received byte 0x%02x\n", command);

	/* Remove repeat bit, we use every command */
	command = command & 0x7f;

	/* Is this a RC5 command byte? */
	if (command & 0x40) {
		budget_ci->ir.have_command = true;
		budget_ci->ir.ir_key = command & 0x3f;
		return;
	}

	/* It's a RC5 device byte */
	if (!budget_ci->ir.have_command)
		return;
	budget_ci->ir.have_command = false;

	if (budget_ci->ir.rc5_device != IR_DEVICE_ANY &&
	    budget_ci->ir.rc5_device != (command & 0x1f))
		return;

	if (budget_ci->ir.full_rc5) {
		rc_keydown(dev, RC_PROTO_RC5,
			   RC_SCANCODE_RC5(budget_ci->ir.rc5_device, budget_ci->ir.ir_key),
			   !!(command & 0x20));
		return;
	}

	/* FIXME: We should generate complete scancodes for all devices */
	rc_keydown(dev, RC_PROTO_UNKNOWN, budget_ci->ir.ir_key,
		   !!(command & 0x20));
}

static int msp430_ir_init(struct budget_ci *budget_ci)
{
	struct saa7146_dev *saa = budget_ci->budget.dev;
	struct rc_dev *dev;
	int error;

	dev = rc_allocate_device(RC_DRIVER_SCANCODE);
	if (!dev) {
		pr_err("IR interface initialisation failed\n");
		return -ENOMEM;
	}

	snprintf(budget_ci->ir.name, sizeof(budget_ci->ir.name),
		 "Budget-CI dvb ir receiver %s", saa->name);
	snprintf(budget_ci->ir.phys, sizeof(budget_ci->ir.phys),
		 "pci-%s/ir0", pci_name(saa->pci));

	dev->driver_name = MODULE_NAME;
	dev->device_name = budget_ci->ir.name;
	dev->input_phys = budget_ci->ir.phys;
	dev->input_id.bustype = BUS_PCI;
	dev->input_id.version = 1;
	if (saa->pci->subsystem_vendor) {
		dev->input_id.vendor = saa->pci->subsystem_vendor;
		dev->input_id.product = saa->pci->subsystem_device;
	} else {
		dev->input_id.vendor = saa->pci->vendor;
		dev->input_id.product = saa->pci->device;
	}
	dev->dev.parent = &saa->pci->dev;

	if (rc5_device < 0)
		budget_ci->ir.rc5_device = IR_DEVICE_ANY;
	else
		budget_ci->ir.rc5_device = rc5_device;

	/* Select keymap and address */
	switch (budget_ci->budget.dev->pci->subsystem_device) {
	case 0x100c:
	case 0x100f:
	case 0x1011:
	case 0x1012:
		/* The hauppauge keymap is a superset of these remotes */
		dev->map_name = RC_MAP_HAUPPAUGE;
		budget_ci->ir.full_rc5 = true;

		if (rc5_device < 0)
			budget_ci->ir.rc5_device = 0x1f;
		break;
	case 0x1010:
	case 0x1017:
	case 0x1019:
	case 0x101a:
	case 0x101b:
		/* for the Technotrend 1500 bundled remote */
		dev->map_name = RC_MAP_TT_1500;
		break;
	default:
		/* unknown remote */
		dev->map_name = RC_MAP_BUDGET_CI_OLD;
		break;
	}
	if (!budget_ci->ir.full_rc5)
		dev->scancode_mask = 0xff;

	error = rc_register_device(dev);
	if (error) {
		pr_err("could not init driver for IR device (code %d)\n", error);
		rc_free_device(dev);
		return error;
	}

	budget_ci->ir.dev = dev;

	INIT_WORK(&budget_ci->ir.msp430_irq_bh_work, msp430_ir_interrupt);

	SAA7146_IER_ENABLE(saa, MASK_06);
	saa7146_setgpio(saa, 3, SAA7146_GPIO_IRQHI);

	return 0;
}

static void msp430_ir_deinit(struct budget_ci *budget_ci)
{
	struct saa7146_dev *saa = budget_ci->budget.dev;

	SAA7146_IER_DISABLE(saa, MASK_06);
	saa7146_setgpio(saa, 3, SAA7146_GPIO_INPUT);
	cancel_work_sync(&budget_ci->ir.msp430_irq_bh_work);

	rc_unregister_device(budget_ci->ir.dev);
}

static int ciintf_read_attribute_mem(struct dvb_ca_en50221 *ca, int slot, int address)
{
	struct budget_ci *budget_ci = ca->data;

	if (slot != 0)
		return -EINVAL;

	return ttpci_budget_debiread(&budget_ci->budget, DEBICICAM,
				     DEBIADDR_ATTR | (address & 0xfff), 1, 1, 0);
}

static int ciintf_write_attribute_mem(struct dvb_ca_en50221 *ca, int slot, int address, u8 value)
{
	struct budget_ci *budget_ci = ca->data;

	if (slot != 0)
		return -EINVAL;

	return ttpci_budget_debiwrite(&budget_ci->budget, DEBICICAM,
				      DEBIADDR_ATTR | (address & 0xfff), 1, value, 1, 0);
}

static int ciintf_read_cam_control(struct dvb_ca_en50221 *ca, int slot, u8 address)
{
	struct budget_ci *budget_ci = ca->data;

	if (slot != 0)
		return -EINVAL;

	return ttpci_budget_debiread(&budget_ci->budget, DEBICICAM,
				     DEBIADDR_IO | (address & 3), 1, 1, 0);
}

static int ciintf_write_cam_control(struct dvb_ca_en50221 *ca, int slot, u8 address, u8 value)
{
	struct budget_ci *budget_ci = ca->data;

	if (slot != 0)
		return -EINVAL;

	return ttpci_budget_debiwrite(&budget_ci->budget, DEBICICAM,
				      DEBIADDR_IO | (address & 3), 1, value, 1, 0);
}

static int ciintf_slot_reset(struct dvb_ca_en50221 *ca, int slot)
{
	struct budget_ci *budget_ci = ca->data;
	struct saa7146_dev *saa = budget_ci->budget.dev;

	if (slot != 0)
		return -EINVAL;

	if (budget_ci->ci_irq) {
		// trigger on RISING edge during reset so we know when READY is re-asserted
		saa7146_setgpio(saa, 0, SAA7146_GPIO_IRQHI);
	}
	budget_ci->slot_status = SLOTSTATUS_RESET;
	ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, 0, 1, 0);
	msleep(1);
	ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1,
			       CICONTROL_RESET, 1, 0);

	saa7146_setgpio(saa, 1, SAA7146_GPIO_OUTHI);
	ttpci_budget_set_video_port(saa, BUDGET_VIDEO_PORTB);
	return 0;
}

static int ciintf_slot_shutdown(struct dvb_ca_en50221 *ca, int slot)
{
	struct budget_ci *budget_ci = ca->data;
	struct saa7146_dev *saa = budget_ci->budget.dev;

	if (slot != 0)
		return -EINVAL;

	saa7146_setgpio(saa, 1, SAA7146_GPIO_OUTHI);
	ttpci_budget_set_video_port(saa, BUDGET_VIDEO_PORTB);
	return 0;
}

static int ciintf_slot_ts_enable(struct dvb_ca_en50221 *ca, int slot)
{
	struct budget_ci *budget_ci = ca->data;
	struct saa7146_dev *saa = budget_ci->budget.dev;
	int tmp;

	if (slot != 0)
		return -EINVAL;

	saa7146_setgpio(saa, 1, SAA7146_GPIO_OUTLO);

	tmp = ttpci_budget_debiread(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, 1, 0);
	ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1,
			       tmp | CICONTROL_ENABLETS, 1, 0);

	ttpci_budget_set_video_port(saa, BUDGET_VIDEO_PORTA);
	return 0;
}

static void ciintf_interrupt(struct work_struct *t)
{
	struct budget_ci *budget_ci = from_work(budget_ci, t,
						   ciintf_irq_bh_work);
	struct saa7146_dev *saa = budget_ci->budget.dev;
	unsigned int flags;

	// ensure we don't get spurious IRQs during initialisation
	if (!budget_ci->budget.ci_present)
		return;

	// read the CAM status
	flags = ttpci_budget_debiread(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, 1, 0);
	if (flags & CICONTROL_CAMDETECT) {

		// GPIO should be set to trigger on falling edge if a CAM is present
		saa7146_setgpio(saa, 0, SAA7146_GPIO_IRQLO);

		if (budget_ci->slot_status & SLOTSTATUS_NONE) {
			// CAM insertion IRQ
			budget_ci->slot_status = SLOTSTATUS_PRESENT;
			dvb_ca_en50221_camchange_irq(&budget_ci->ca, 0,
						     DVB_CA_EN50221_CAMCHANGE_INSERTED);

		} else if (budget_ci->slot_status & SLOTSTATUS_RESET) {
			// CAM ready (reset completed)
			budget_ci->slot_status = SLOTSTATUS_READY;
			dvb_ca_en50221_camready_irq(&budget_ci->ca, 0);

		} else if (budget_ci->slot_status & SLOTSTATUS_READY) {
			// FR/DA IRQ
			dvb_ca_en50221_frda_irq(&budget_ci->ca, 0);
		}
	} else {

		// trigger on rising edge if a CAM is not present - when a CAM is inserted, we
		// only want to get the IRQ when it sets READY. If we trigger on the falling edge,
		// the CAM might not actually be ready yet.
		saa7146_setgpio(saa, 0, SAA7146_GPIO_IRQHI);

		// generate a CAM removal IRQ if we haven't already
		if (budget_ci->slot_status & SLOTSTATUS_OCCUPIED) {
			// CAM removal IRQ
			budget_ci->slot_status = SLOTSTATUS_NONE;
			dvb_ca_en50221_camchange_irq(&budget_ci->ca, 0,
						     DVB_CA_EN50221_CAMCHANGE_REMOVED);
		}
	}
}

static int ciintf_poll_slot_status(struct dvb_ca_en50221 *ca, int slot, int open)
{
	struct budget_ci *budget_ci = ca->data;
	unsigned int flags;

	// ensure we don't get spurious IRQs during initialisation
	if (!budget_ci->budget.ci_present)
		return -EINVAL;

	// read the CAM status
	flags = ttpci_budget_debiread(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, 1, 0);
	if (flags & CICONTROL_CAMDETECT) {
		// mark it as present if it wasn't before
		if (budget_ci->slot_status & SLOTSTATUS_NONE)
			budget_ci->slot_status = SLOTSTATUS_PRESENT;

		// during a RESET, we check if we can read from IO memory to see when CAM is ready
		if (budget_ci->slot_status & SLOTSTATUS_RESET) {
			if (ciintf_read_attribute_mem(ca, slot, 0) == 0x1d)
				budget_ci->slot_status = SLOTSTATUS_READY;
		}
	} else {
		budget_ci->slot_status = SLOTSTATUS_NONE;
	}

	if (budget_ci->slot_status != SLOTSTATUS_NONE) {
		if (budget_ci->slot_status & SLOTSTATUS_READY)
			return DVB_CA_EN50221_POLL_CAM_PRESENT | DVB_CA_EN50221_POLL_CAM_READY;
		return DVB_CA_EN50221_POLL_CAM_PRESENT;
	}

	return 0;
}

static int ciintf_init(struct budget_ci *budget_ci)
{
	struct saa7146_dev *saa = budget_ci->budget.dev;
	int flags;
	int result;
	int ci_version;
	int ca_flags;

	memset(&budget_ci->ca, 0, sizeof(struct dvb_ca_en50221));

	// enable DEBI pins
	saa7146_write(saa, MC1, MASK_27 | MASK_11);

	// test if it is there
	ci_version = ttpci_budget_debiread(&budget_ci->budget, DEBICICTL, DEBIADDR_CIVERSION, 1, 1, 0);
	if ((ci_version & 0xa0) != 0xa0) {
		result = -ENODEV;
		goto error;
	}

	// determine whether a CAM is present or not
	flags = ttpci_budget_debiread(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, 1, 0);
	budget_ci->slot_status = SLOTSTATUS_NONE;
	if (flags & CICONTROL_CAMDETECT)
		budget_ci->slot_status = SLOTSTATUS_PRESENT;

	// version 0xa2 of the CI firmware doesn't generate interrupts
	if (ci_version == 0xa2) {
		ca_flags = 0;
		budget_ci->ci_irq = 0;
	} else {
		ca_flags = DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE |
				DVB_CA_EN50221_FLAG_IRQ_FR |
				DVB_CA_EN50221_FLAG_IRQ_DA;
		budget_ci->ci_irq = 1;
	}

	// register CI interface
	budget_ci->ca.owner = THIS_MODULE;
	budget_ci->ca.read_attribute_mem = ciintf_read_attribute_mem;
	budget_ci->ca.write_attribute_mem = ciintf_write_attribute_mem;
	budget_ci->ca.read_cam_control = ciintf_read_cam_control;
	budget_ci->ca.write_cam_control = ciintf_write_cam_control;
	budget_ci->ca.slot_reset = ciintf_slot_reset;
	budget_ci->ca.slot_shutdown = ciintf_slot_shutdown;
	budget_ci->ca.slot_ts_enable = ciintf_slot_ts_enable;
	budget_ci->ca.poll_slot_status = ciintf_poll_slot_status;
	budget_ci->ca.data = budget_ci;

	result = dvb_ca_en50221_init(&budget_ci->budget.dvb_adapter,
				     &budget_ci->ca, ca_flags, 1);
	if (result != 0) {
		pr_err("CI interface detected, but initialisation failed.\n");
		goto error;
	}

	// Setup CI slot IRQ
	if (budget_ci->ci_irq) {
		INIT_WORK(&budget_ci->ciintf_irq_bh_work, ciintf_interrupt);
		if (budget_ci->slot_status != SLOTSTATUS_NONE)
			saa7146_setgpio(saa, 0, SAA7146_GPIO_IRQLO);
		else
			saa7146_setgpio(saa, 0, SAA7146_GPIO_IRQHI);
		SAA7146_IER_ENABLE(saa, MASK_03);
	}

	// enable interface
	ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1,
			       CICONTROL_RESET, 1, 0);

	// success!
	pr_info("CI interface initialised\n");
	budget_ci->budget.ci_present = 1;

	// forge a fake CI IRQ so the CAM state is setup correctly
	if (budget_ci->ci_irq) {
		flags = DVB_CA_EN50221_CAMCHANGE_REMOVED;
		if (budget_ci->slot_status != SLOTSTATUS_NONE)
			flags = DVB_CA_EN50221_CAMCHANGE_INSERTED;
		dvb_ca_en50221_camchange_irq(&budget_ci->ca, 0, flags);
	}

	return 0;

error:
	saa7146_write(saa, MC1, MASK_27);
	return result;
}

static void ciintf_deinit(struct budget_ci *budget_ci)
{
	struct saa7146_dev *saa = budget_ci->budget.dev;

	// disable CI interrupts
	if (budget_ci->ci_irq) {
		SAA7146_IER_DISABLE(saa, MASK_03);
		saa7146_setgpio(saa, 0, SAA7146_GPIO_INPUT);
		cancel_work_sync(&budget_ci->ciintf_irq_bh_work);
	}

	// reset interface
	ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1, 0, 1, 0);
	msleep(1);
	ttpci_budget_debiwrite(&budget_ci->budget, DEBICICTL, DEBIADDR_CICONTROL, 1,
			       CICONTROL_RESET, 1, 0);

	// disable TS data stream to CI interface
	saa7146_setgpio(saa, 1, SAA7146_GPIO_INPUT);

	// release the CA device
	dvb_ca_en50221_release(&budget_ci->ca);

	// disable DEBI pins
	saa7146_write(saa, MC1, MASK_27);
}

static void budget_ci_irq(struct saa7146_dev *dev, u32 *isr)
{
	struct budget_ci *budget_ci = dev->ext_priv;

	dprintk(8, "dev: %p, budget_ci: %p\n", dev, budget_ci);

	if (*isr & MASK_06)
		queue_work(system_bh_wq, &budget_ci->ir.msp430_irq_bh_work);

	if (*isr & MASK_10)
		ttpci_budget_irq10_handler(dev, isr);

	if ((*isr & MASK_03) && (budget_ci->budget.ci_present) && (budget_ci->ci_irq))
		queue_work(system_bh_wq, &budget_ci->ciintf_irq_bh_work);
}

static u8 philips_su1278_tt_inittab[] = {
	0x01, 0x0f,
	0x02, 0x30,
	0x03, 0x00,
	0x04, 0x5b,
	0x05, 0x85,
	0x06, 0x02,
	0x07, 0x00,
	0x08, 0x02,
	0x09, 0x00,
	0x0C, 0x01,
	0x0D, 0x81,
	0x0E, 0x44,
	0x0f, 0x14,
	0x10, 0x3c,
	0x11, 0x84,
	0x12, 0xda,
	0x13, 0x97,
	0x14, 0x95,
	0x15, 0xc9,
	0x16, 0x19,
	0x17, 0x8c,
	0x18, 0x59,
	0x19, 0xf8,
	0x1a, 0xfe,
	0x1c, 0x7f,
	0x1d, 0x00,
	0x1e, 0x00,
	0x1f, 0x50,
	0x20, 0x00,
	0x21, 0x00,
	0x22, 0x00,
	0x23, 0x00,
	0x28, 0x00,
	0x29, 0x28,
	0x2a, 0x14,
	0x2b, 0x0f,
	0x2c, 0x09,
	0x2d, 0x09,
	0x31, 0x1f,
	0x32, 0x19,
	0x33, 0xfc,
	0x34, 0x93,
	0xff, 0xff
};

static int philips_su1278_tt_set_symbol_rate(struct dvb_frontend *fe, u32 srate, u32 ratio)
{
	stv0299_writereg(fe, 0x0e, 0x44);
	if (srate >= 10000000) {
		stv0299_writereg(fe, 0x13, 0x97);
		stv0299_writereg(fe, 0x14, 0x95);
		stv0299_writereg(fe, 0x15, 0xc9);
		stv0299_writereg(fe, 0x17, 0x8c);
		stv0299_writereg(fe, 0x1a, 0xfe);
		stv0299_writereg(fe, 0x1c, 0x7f);
		stv0299_writereg(fe, 0x2d, 0x09);
	} else {
		stv0299_writereg(fe, 0x13, 0x99);
		stv0299_writereg(fe, 0x14, 0x8d);
		stv0299_writereg(fe, 0x15, 0xce);
		stv0299_writereg(fe, 0x17, 0x43);
		stv0299_writereg(fe, 0x1a, 0x1d);
		stv0299_writereg(fe, 0x1c, 0x12);
		stv0299_writereg(fe, 0x2d, 0x05);
	}
	stv0299_writereg(fe, 0x0e, 0x23);
	stv0299_writereg(fe, 0x0f, 0x94);
	stv0299_writereg(fe, 0x10, 0x39);
	stv0299_writereg(fe, 0x15, 0xc9);

	stv0299_writereg(fe, 0x1f, (ratio >> 16) & 0xff);
	stv0299_writereg(fe, 0x20, (ratio >> 8) & 0xff);
	stv0299_writereg(fe, 0x21, (ratio) & 0xf0);

	return 0;
}

static int philips_su1278_tt_tuner_set_params(struct dvb_frontend *fe)
{
	struct dtv_frontend_properties *p = &fe->dtv_property_cache;
	struct budget_ci *budget_ci = fe->dvb->priv;
	u32 div;
	u8 buf[4];
	struct i2c_msg msg = {.addr = 0x60, .flags = 0, .buf = buf, .len = sizeof(buf) };

	if ((p->frequency < 950000) || (p->frequency > 2150000))
		return -EINVAL;

	div = (p->frequency + (500 - 1)) / 500;	/* round correctly */
	buf[0] = (div >> 8) & 0x7f;
	buf[1] = div & 0xff;
	buf[2] = 0x80 | ((div & 0x18000) >> 10) | 2;
	buf[3] = 0x20;

	if (p->symbol_rate < 4000000)
		buf[3] |= 1;

	if (p->frequency < 1250000)
		buf[3] |= 0;
	else if (p->frequency < 1550000)
		buf[3] |= 0x40;
	else if (p->frequency < 2050000)
		buf[3] |= 0x80;
	else if (p->frequency < 2150000)
		buf[3] |= 0xC0;

	if (fe->ops.i2c_gate_ctrl)
		fe->ops.i2c_gate_ctrl(fe, 1);
	if (i2c_transfer(&budget_ci->budget.i2c_adap, &msg, 1) != 1)
		return -EIO;
	return 0;
}

static const struct stv0299_config philips_su1278_tt_config = {

	.demod_address = 0x68,
	.inittab = philips_su1278_tt_inittab,
	.mclk = 64000000UL,
	.invert = 0,
	.skip_reinit = 1,
	.lock_output = STV0299_LOCKOUTPUT_1,
	.volt13_op0_op1 = STV0299_VOLT13_OP1,
	.min_delay_ms = 50,
	.set_symbol_rate = philips_su1278_tt_set_symbol_rate,
};



static int philips_tdm1316l_tuner_init(struct dvb_frontend *fe)
{
	struct budget_ci *budget_ci = fe->dvb->priv;
	static u8 td1316_init[] = { 0x0b, 0xf5, 0x85, 0xab };
	static u8 disable_mc44BC374c[] = { 0x1d, 0x74, 0xa0, 0x68 };
	struct i2c_msg tuner_msg = {.addr = budget_ci->tuner_pll_address, .flags = 0, .buf = td1316_init, .len =
			sizeof(td1316_init) };

	// setup PLL configuration
	if (fe->ops.i2c_gate_ctrl)
		fe->ops.i2c_gate_ctrl(fe, 1);
	if (i2c_transfer(&budget_ci->budget.i2c_adap, &tuner_msg, 1) != 1)
		return -EIO;
	msleep(1);

	// disable the mc44BC374c (do not check for errors)
	tuner_msg.addr = 0x65;
	tuner_msg.buf = disable_mc44BC374c;
	tuner_msg.len = sizeof(disable_mc44BC374c);
	if (fe->ops.i2c_gate_ctrl)
		fe->ops.i2c_gate_ctrl(fe, 1);
	if (i2c_transfer(&budget_ci->budget.i2c_adap, &tuner_msg, 1) != 1) {
		if (fe->ops.i2c_gate_ctrl)
			fe->ops.i2c_gate_ctrl(fe, 1);
		i2c_transfer(&budget_ci->budget.i2c_adap, &tuner_msg, 1);
	}

	return 0;
}

static int philips_tdm1316l_tuner_set_params(struct dvb_frontend *fe)
{
	struct dtv_frontend_properties *p = &fe->dtv_property_cache;
	struct budget_ci *budget_ci = fe->dvb->priv;
	u8 tuner_buf[4];
	struct i2c_msg tuner_msg = {.addr = budget_ci->tuner_pll_address, .flags = 0, .buf = tuner_buf, .len = sizeof(tuner_buf) };
	int tuner_frequency = 0;
	u8 band, cp, filter;

	// determine charge pump
	tuner_frequency = p->frequency + 36130000;
	if (tuner_frequency < 87000000)
		return -EINVAL;
	else if (tuner_frequency < 130000000)
		cp = 3;
	else if (tuner_frequency < 160000000)
		cp = 5;
	else if (tuner_frequency < 200000000)
		cp = 6;
	else if (tuner_frequency < 290000000)
		cp = 3;
	else if (tuner_frequency < 420000000)
		cp = 5;
	else if (tuner_frequency < 480000000)
		cp = 6;
	else if (tuner_frequency < 620000000)
		cp = 3;
	else if (tuner_frequency < 830000000)
		cp = 5;
	else if (tuner_frequency < 895000000)
		cp = 7;
	else
		return -EINVAL;

	// determine band
	if (p->frequency < 49000000)
		return -EINVAL;
	else if (p->frequency < 159000000)
		band = 1;
	else if (p->frequency < 444000000)
		band = 2;
	else if (p->frequency < 861000000)
		band = 4;
	else
		return -EINVAL;

	// setup PLL filter and TDA9889
	switch (p->bandwidth_hz) {
	case 6000000:
		tda1004x_writereg(fe, 0x0C, 0x14);
		filter = 0;
		break;

	case 7000000:
		tda1004x_writereg(fe, 0x0C, 0x80);
		filter = 0;
		break;

	case 8000000:
		tda1004x_writereg(fe, 0x0C, 0x14);
		filter = 1;
		break;

	default:
		return -EINVAL;
	}

	// calculate divisor
	// ((36130000+((1000000/6)/2)) + Finput)/(1000000/6)
	tuner_frequency = (((p->frequency / 1000) * 6) + 217280) / 1000;

	// setup tuner buffer
	tuner_buf[0] = tuner_frequency >> 8;
	tuner_buf[1] = tuner_frequency & 0xff;
	tuner_buf[2] = 0xca;
	tuner_buf[3] = (cp << 5) | (filter << 3) | band;

	if (fe->ops.i2c_gate_ctrl)
		fe->ops.i2c_gate_ctrl(fe, 1);
	if (i2c_transfer(&budget_ci->budget.i2c_adap, &tuner_msg, 1) != 1)
		return -EIO;

	msleep(1);
	return 0;
}

static int philips_tdm1316l_request_firmware(struct dvb_frontend *fe,
					     const struct firmware **fw, char *name)
{
	struct budget_ci *budget_ci = fe->dvb->priv;

	return request_firmware(fw, name, &budget_ci->budget.dev->pci->dev);
}

static struct tda1004x_config philips_tdm1316l_config = {

	.demod_address = 0x8,
	.invert = 0,
	.invert_oclk = 0,
	.xtal_freq = TDA10046_XTAL_4M,
	.agc_config = TDA10046_AGC_DEFAULT,
	.if_freq = TDA10046_FREQ_3617,
	.request_firmware = philips_tdm1316l_request_firmware,
};

static struct tda1004x_config philips_tdm1316l_config_invert = {

	.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 = philips_tdm1316l_request_firmware,
};

static int dvbc_philips_tdm1316l_tuner_set_params(struct dvb_frontend *fe)
{
	struct dtv_frontend_properties *p = &fe->dtv_property_cache;
	struct budget_ci *budget_ci = fe->dvb->priv;
	u8 tuner_buf[5];
	struct i2c_msg tuner_msg = {.addr = budget_ci->tuner_pll_address,
				    .flags = 0,
				    .buf = tuner_buf,
				    .len = sizeof(tuner_buf) };
	int tuner_frequency = 0;
	u8 band, cp, filter;

	// determine charge pump
	tuner_frequency = p->frequency + 36125000;
	if (tuner_frequency < 87000000) {
		return -EINVAL;
	} else if (tuner_frequency < 130000000) {
		cp = 3;
		band = 1;
	} else if (tuner_frequency < 160000000) {
		cp = 5;
		band = 1;
	} else if (tuner_frequency < 200000000) {
		cp = 6;
		band = 1;
	} else if (tuner_frequency < 290000000) {
		cp = 3;
		band = 2;
	} else if (tuner_frequency < 420000000) {
		cp = 5;
		band = 2;
	} else if (tuner_frequency < 480000000) {
		cp = 6;
		band = 2;
	} else if (tuner_frequency < 620000000) {
		cp = 3;
		band = 4;
	} else if (tuner_frequency < 830000000) {
		cp = 5;
		band = 4;
	} else if (tuner_frequency < 895000000) {
		cp = 7;
		band = 4;
	} else {
		return -EINVAL;
	}

	// assume PLL filter should always be 8MHz for the moment.
	filter = 1;

	// calculate divisor
	tuner_frequency = (p->frequency + 36125000 + (62500/2)) / 62500;

	// setup tuner buffer
	tuner_buf[0] = tuner_frequency >> 8;
	tuner_buf[1] = tuner_frequency & 0xff;
	tuner_buf[2] = 0xc8;
	tuner_buf[3] = (cp << 5) | (filter << 3) | band;
	tuner_buf[4] = 0x80;

	if (fe->ops.i2c_gate_ctrl)
		fe->ops.i2c_gate_ctrl(fe, 1);
	if (i2c_transfer(&budget_ci->budget.i2c_adap, &tuner_msg, 1) != 1)
		return -EIO;

	msleep(50);

	if (fe->ops.i2c_gate_ctrl)
		fe->ops.i2c_gate_ctrl(fe, 1);
	if (i2c_transfer(&budget_ci->budget.i2c_adap, &tuner_msg, 1) != 1)
		return -EIO;

	msleep(1);

	return 0;
}

static u8 dvbc_philips_tdm1316l_inittab[] = {
	0x80, 0x01,
	0x80, 0x00,
	0x81, 0x01,
	0x81, 0x00,
	0x00, 0x09,
	0x01, 0x69,
	0x03, 0x00,
	0x04, 0x00,
	0x07, 0x00,
	0x08, 0x00,
	0x20, 0x00,
	0x21, 0x40,
	0x22, 0x00,
	0x23, 0x00,
	0x24, 0x40,
	0x25, 0x88,
	0x30, 0xff,
	0x31, 0x00,
	0x32, 0xff,
	0x33, 0x00,
	0x34, 0x50,
	0x35, 0x7f,
	0x36, 0x00,
	0x37, 0x20,
	0x38, 0x00,
	0x40, 0x1c,
	0x41, 0xff,
	0x42, 0x29,
	0x43, 0x20,
	0x44, 0xff,
	0x45, 0x00,
	0x46, 0x00,
	0x49, 0x04,
	0x4a, 0x00,
	0x4b, 0x7b,
	0x52, 0x30,
	0x55, 0xae,
	0x56, 0x47,
	0x57, 0xe1,
	0x58, 0x3a,
	0x5a, 0x1e,
	0x5b, 0x34,
	0x60, 0x00,
	0x63, 0x00,
	0x64, 0x00,
	0x65, 0x00,
	0x66, 0x00,
	0x67, 0x00,
	0x68, 0x00,
	0x69, 0x00,
	0x6a, 0x02,
	0x6b, 0x00,
	0x70, 0xff,
	0x71, 0x00,
	0x72, 0x00,
	0x73, 0x00,
	0x74, 0x0c,
	0x80, 0x00,
	0x81, 0x00,
	0x82, 0x00,
	0x83, 0x00,
	0x84, 0x04,
	0x85, 0x80,
	0x86, 0x24,
	0x87, 0x78,
	0x88, 0x10,
	0x89, 0x00,
	0x90, 0x01,
	0x91, 0x01,
	0xa0, 0x04,
	0xa1, 0x00,
	0xa2, 0x00,
	0xb0, 0x91,
	0xb1, 0x0b,
	0xc0, 0x53,
	0xc1, 0x70,
	0xc2, 0x12,
	0xd0, 0x00,
	0xd1, 0x00,
	0xd2, 0x00,
	0xd3, 0x00,
	0xd4, 0x00,
	0xd5, 0x00,
	0xde, 0x00,
	0xdf, 0x00,
	0x61, 0x38,
	0x62, 0x0a,
	0x53, 0x13,
	0x59, 0x08,
	0xff, 0xff,
};

static struct stv0297_config dvbc_philips_tdm1316l_config = {
	.demod_address = 0x1c,
	.inittab = dvbc_philips_tdm1316l_inittab,
	.invert = 0,
	.stop_during_read = 1,
};

static struct tda10023_config tda10023_config = {
	.demod_address = 0xc,
	.invert = 0,
	.xtal = 16000000,
	.pll_m = 11,
	.pll_p = 3,
	.pll_n = 1,
	.deltaf = 0xa511,
};

static struct tda827x_config tda827x_config = {
	.config = 0,
};

/* TT S2-3200 DVB-S (STB0899) Inittab */
static const struct stb0899_s1_reg tt3200_stb0899_s1_init_1[] = {

	{ STB0899_DEV_ID,		0x81 },
	{ STB0899_DISCNTRL1,		0x32 },
	{ STB0899_DISCNTRL2,		0x80 },
	{ STB0899_DISRX_ST0,		0x04 },
	{ STB0899_DISRX_ST1,		0x00 },
	{ STB0899_DISPARITY,		0x00 },
	{ STB0899_DISSTATUS,		0x20 },
	{ STB0899_DISF22,		0x8c },
	{ STB0899_DISF22RX,		0x9a },
	{ STB0899_SYSREG,		0x0b },
	{ STB0899_ACRPRESC,		0x11 },
	{ STB0899_ACRDIV1,		0x0a },
	{ STB0899_ACRDIV2,		0x05 },
	{ STB0899_DACR1,		0x00 },
	{ STB0899_DACR2,		0x00 },
	{ STB0899_OUTCFG,		0x00 },
	{ STB0899_MODECFG,		0x00 },
	{ STB0899_IRQSTATUS_3,		0x30 },
	{ STB0899_IRQSTATUS_2,		0x00 },
	{ STB0899_IRQSTATUS_1,		0x00 },
	{ STB0899_IRQSTATUS_0,		0x00 },
	{ STB0899_IRQMSK_3,		0xf3 },
	{ STB0899_IRQMSK_2,		0xfc },
	{ STB0899_IRQMSK_1,		0xff },
	{ STB0899_IRQMSK_0,		0xff },
	{ STB0899_IRQCFG,		0x00 },
	{ STB0899_I2CCFG,		0x88 },
	{ STB0899_I2CRPT,		0x48 }, /* 12k Pullup, Repeater=16, Stop=disabled */
	{ STB0899_IOPVALUE5,		0x00 },
	{ STB0899_IOPVALUE4,		0x20 },
	{ STB0899_IOPVALUE3,		0xc9 },
	{ STB0899_IOPVALUE2,		0x90 },
	{ STB0899_IOPVALUE1,		0x40 },
	{ STB0899_IOPVALUE0,		0x00 },
	{ STB0899_GPIO00CFG,		0x82 },
	{ STB0899_GPIO01CFG,		0x82 },
	{ STB0899_GPIO02CFG,		0x82 },
	{ STB0899_GPIO03CFG,		0x82 },
	{ STB0899_GPIO04CFG,		0x82 },
	{ STB0899_GPIO05CFG,		0x82 },
	{ STB0899_GPIO06CFG,		0x82 },
	{ STB0899_GPIO07CFG,		0x82 },
	{ STB0899_GPIO08CFG,		0x82 },
	{ STB0899_GPIO09CFG,		0x82 },
	{ STB0899_GPIO10CFG,		0x82 },
	{ STB0899_GPIO11CFG,		0x82 },
	{ STB0899_GPIO12CFG,		0x82 },
	{ STB0899_GPIO13CFG,		0x82 },
	{ STB0899_GPIO14CFG,		0x82 },
	{ STB0899_GPIO15CFG,		0x82 },
	{ STB0899_GPIO16CFG,		0x82 },
	{ STB0899_GPIO17CFG,		0x82 },
	{ STB0899_GPIO18CFG,		0x82 },
	{ STB0899_GPIO19CFG,		0x82 },
	{ STB0899_GPIO20CFG,		0x82 },
	{ STB0899_SDATCFG,		0xb8 },
	{ STB0899_SCLTCFG,		0xba },
	{ STB0899_AGCRFCFG,		0x1c }, /* 0x11 */
	{ STB0899_GPIO22,		0x82 }, /* AGCBB2CFG */
	{ STB0899_GPIO21,		0x91 }, /* AGCBB1CFG */
	{ STB0899_DIRCLKCFG,		0x82 },
	{ STB0899_CLKOUT27CFG,		0x7e },
	{ STB0899_STDBYCFG,		0x82 },
	{ STB0899_CS0CFG,		0x82 },
	{ STB0899_CS1CFG,		0x82 },
	{ STB0899_DISEQCOCFG,		0x20 },
	{ STB0899_GPIO32CFG,		0x82 },
	{ STB0899_GPIO33CFG,		0x82 },
	{ STB0899_GPIO34CFG,		0x82 },
	{ STB0899_GPIO35CFG,		0x82 },
	{ STB0899_GPIO36CFG,		0x82 },
	{ STB0899_GPIO37CFG,		0x82 },
	{ STB0899_GPIO38CFG,		0x82 },
	{ STB0899_GPIO39CFG,		0x82 },
	{ STB0899_NCOARSE,		0x15 }, /* 0x15 = 27 Mhz Clock, F/3 = 198MHz, F/6 = 99MHz */
	{ STB0899_SYNTCTRL,		0x02 }, /* 0x00 = CLK from CLKI, 0x02 = CLK from XTALI */
	{ STB0899_FILTCTRL,		0x00 },
	{ STB0899_SYSCTRL,		0x00 },
	{ STB0899_STOPCLK1,		0x20 },
	{ STB0899_STOPCLK2,		0x00 },
	{ STB0899_INTBUFSTATUS,		0x00 },
	{ STB0899_INTBUFCTRL,		0x0a },
	{ 0xffff,			0xff },
};

static const struct stb0899_s1_reg tt3200_stb0899_s1_init_3[] = {
	{ STB0899_DEMOD,		0x00 },
	{ STB0899_RCOMPC,		0xc9 },
	{ STB0899_AGC1CN,		0x41 },
	{ STB0899_AGC1REF,		0x10 },
	{ STB0899_RTC,			0x7a },
	{ STB0899_TMGCFG,		0x4e },
	{ STB0899_AGC2REF,		0x34 },
	{ STB0899_TLSR,			0x84 },
	{ STB0899_CFD,			0xc7 },
	{ STB0899_ACLC,			0x87 },
	{ STB0899_BCLC,			0x94 },
	{ STB0899_EQON,			0x41 },
	{ STB0899_LDT,			0xdd },
	{ STB0899_LDT2,			0xc9 },
	{ STB0899_EQUALREF,		0xb4 },
	{ STB0899_TMGRAMP,		0x10 },
	{ STB0899_TMGTHD,		0x30 },
	{ STB0899_IDCCOMP,		0xfb },
	{ STB0899_QDCCOMP,		0x03 },
	{ STB0899_POWERI,		0x3b },
	{ STB0899_POWERQ,		0x3d },
	{ STB0899_RCOMP,		0x81 },
	{ STB0899_AGCIQIN,		0x80 },
	{ STB0899_AGC2I1,		0x04 },
	{ STB0899_AGC2I2,		0xf5 },
	{ STB0899_TLIR,			0x25 },
	{ STB0899_RTF,			0x80 },
	{ STB0899_DSTATUS,		0x00 },
	{ STB0899_LDI,			0xca },
	{ STB0899_CFRM,			0xf1 },
	{ STB0899_CFRL,			0xf3 },
	{ STB0899_NIRM,			0x2a },
	{ STB0899_NIRL,			0x05 },
	{ STB0899_ISYMB,		0x17 },
	{ STB0899_QSYMB,		0xfa },
	{ STB0899_SFRH,			0x2f },
	{ STB0899_SFRM,			0x68 },
	{ STB0899_SFRL,			0x40 },
	{ STB0899_SFRUPH,		0x2f },
	{ STB0899_SFRUPM,		0x68 },
	{ STB0899_SFRUPL,		0x40 },
	{ STB0899_EQUAI1,		0xfd },
	{ STB0899_EQUAQ1,		0x04 },
	{ STB0899_EQUAI2,		0x0f },
	{ STB0899_EQUAQ2,		0xff },
	{ STB0899_EQUAI3,		0xdf },
	{ STB0899_EQUAQ3,		0xfa },
	{ STB0899_EQUAI4,		0x37 },
	{ STB0899_EQUAQ4,		0x0d },
	{ STB0899_EQUAI5,		0xbd },
	{ STB0899_EQUAQ5,		0xf7 },
	{ STB0899_DSTATUS2,		0x00 },
	{ STB0899_VSTATUS,		0x00 },
	{ STB0899_VERROR,		0xff },
	{ STB0899_IQSWAP,		0x2a },
	{ STB0899_ECNT1M,		0x00 },
	{ STB0899_ECNT1L,		0x00 },
	{ STB0899_ECNT2M,		0x00 },
	{ STB0899_ECNT2L,		0x00 },
	{ STB0899_ECNT3M,		0x00 },
	{ STB0899_ECNT3L,		0x00 },
	{ STB0899_FECAUTO1,		0x06 },
	{ STB0899_FECM,			0x01 },
	{ STB0899_VTH12,		0xf0 },
	{ STB0899_VTH23,		0xa0 },
	{ STB0899_VTH34,		0x78 },
	{ STB0899_VTH56,		0x4e },
	{ STB0899_VTH67,		0x48 },
	{ STB0899_VTH78,		0x38 },
	{ STB0899_PRVIT,		0xff },
	{ STB0899_VITSYNC,		0x19 },
	{ STB0899_RSULC,		0xb1 }, /* DVB = 0xb1, DSS = 0xa1 */
	{ STB0899_TSULC,		0x42 },
	{ STB0899_RSLLC,		0x40 },
	{ STB0899_TSLPL,		0x12 },
	{ STB0899_TSCFGH,		0x0c },
	{ STB0899_TSCFGM,		0x00 },
	{ STB0899_TSCFGL,		0x0c },
	{ STB0899_TSOUT,		0x4d }, /* 0x0d for CAM */
	{ STB0899_RSSYNCDEL,		0x00 },
	{ STB0899_TSINHDELH,		0x02 },
	{ STB0899_TSINHDELM,		0x00 },
	{ STB0899_TSINHDELL,		0x00 },
	{ STB0899_TSLLSTKM,		0x00 },
	{ STB0899_TSLLSTKL,		0x00 },
	{ STB0899_TSULSTKM,		0x00 },
	{ STB0899_TSULSTKL,		0xab },
	{ STB0899_PCKLENUL,		0x00 },
	{ STB0899_PCKLENLL,		0xcc },
	{ STB0899_RSPCKLEN,		0xcc },
	{ STB0899_TSSTATUS,		0x80 },
	{ STB0899_ERRCTRL1,		0xb6 },
	{ STB0899_ERRCTRL2,		0x96 },
	{ STB0899_ERRCTRL3,		0x89 },
	{ STB0899_DMONMSK1,		0x27 },
	{ STB0899_DMONMSK0,		0x03 },
	{ STB0899_DEMAPVIT,		0x5c },
	{ STB0899_PLPARM,		0x1f },
	{ STB0899_PDELCTRL,		0x48 },
	{ STB0899_PDELCTRL2,		0x00 },
	{ STB0899_BBHCTRL1,		0x00 },
	{ STB0899_BBHCTRL2,		0x00 },
	{ STB0899_HYSTTHRESH,		0x77 },
	{ STB0899_MATCSTM,		0x00 },
	{ STB0899_MATCSTL,		0x00 },
	{ STB0899_UPLCSTM,		0x00 },
	{ STB0899_UPLCSTL,		0x00 },
	{ STB0899_DFLCSTM,		0x00 },
	{ STB0899_DFLCSTL,		0x00 },
	{ STB0899_SYNCCST,		0x00 },
	{ STB0899_SYNCDCSTM,		0x00 },
	{ STB0899_SYNCDCSTL,		0x00 },
	{ STB0899_ISI_ENTRY,		0x00 },
	{ STB0899_ISI_BIT_EN,		0x00 },
	{ STB0899_MATSTRM,		0x00 },
	{ STB0899_MATSTRL,		0x00 },
	{ STB0899_UPLSTRM,		0x00 },
	{ STB0899_UPLSTRL,		0x00 },
	{ STB0899_DFLSTRM,		0x00 },
	{ STB0899_DFLSTRL,		0x00 },
	{ STB0899_SYNCSTR,		0x00 },
	{ STB0899_SYNCDSTRM,		0x00 },
	{ STB0899_SYNCDSTRL,		0x00 },
	{ STB0899_CFGPDELSTATUS1,	0x10 },
	{ STB0899_CFGPDELSTATUS2,	0x00 },
	{ STB0899_BBFERRORM,		0x00 },
	{ STB0899_BBFERRORL,		0x00 },
	{ STB0899_UPKTERRORM,		0x00 },
	{ STB0899_UPKTERRORL,		0x00 },
	{ 0xffff,			0xff },
};

static struct stb0899_config tt3200_config = {
	.init_dev		= tt3200_stb0899_s1_init_1,
	.init_s2_demod		= stb0899_s2_init_2,
	.init_s1_demod		= tt3200_stb0899_s1_init_3,
	.init_s2_fec		= stb0899_s2_init_4,
	.init_tst		= stb0899_s1_init_5,

	.postproc		= NULL,

	.demod_address		= 0x68,

	.xtal_freq		= 27000000,
	.inversion		= IQ_SWAP_ON,

	.lo_clk			= 76500000,
	.hi_clk			= 99000000,

	.esno_ave		= STB0899_DVBS2_ESNO_AVE,
	.esno_quant		= STB0899_DVBS2_ESNO_QUANT,
	.avframes_coarse	= STB0899_DVBS2_AVFRAMES_COARSE,
	.avframes_fine		= STB0899_DVBS2_AVFRAMES_FINE,
	.miss_threshold		= STB0899_DVBS2_MISS_THRESHOLD,
	.uwp_threshold_acq	= STB0899_DVBS2_UWP_THRESHOLD_ACQ,
	.uwp_threshold_track	= STB0899_DVBS2_UWP_THRESHOLD_TRACK,
	.uwp_threshold_sof	= STB0899_DVBS2_UWP_THRESHOLD_SOF,
	.sof_search_timeout	= STB0899_DVBS2_SOF_SEARCH_TIMEOUT,

	.btr_nco_bits		= STB0899_DVBS2_BTR_NCO_BITS,
	.btr_gain_shift_offset	= STB0899_DVBS2_BTR_GAIN_SHIFT_OFFSET,
	.crl_nco_bits		= STB0899_DVBS2_CRL_NCO_BITS,
	.ldpc_max_iter		= STB0899_DVBS2_LDPC_MAX_ITER,

	.tuner_get_frequency	= stb6100_get_frequency,
	.tuner_set_frequency	= stb6100_set_frequency,
	.tuner_set_bandwidth	= stb6100_set_bandwidth,
	.tuner_get_bandwidth	= stb6100_get_bandwidth,
	.tuner_set_rfsiggain	= NULL
};

static struct stb6100_config tt3200_stb6100_config = {
	.tuner_address	= 0x60,
	.refclock	= 27000000,
};

static void frontend_init(struct budget_ci *budget_ci)
{
	switch (budget_ci->budget.dev->pci->subsystem_device) {
	case 0x100c:		// Hauppauge/TT Nova-CI budget (stv0299/ALPS BSRU6(tsa5059))
		budget_ci->budget.dvb_frontend =
			dvb_attach(stv0299_attach, &alps_bsru6_config, &budget_ci->budget.i2c_adap);
		if (budget_ci->budget.dvb_frontend) {
			budget_ci->budget.dvb_frontend->ops.tuner_ops.set_params = alps_bsru6_tuner_set_params;
			budget_ci->budget.dvb_frontend->tuner_priv = &budget_ci->budget.i2c_adap;
			break;
		}
		break;

	case 0x100f:		// Hauppauge/TT Nova-CI budget (stv0299b/Philips su1278(tsa5059))
		budget_ci->budget.dvb_frontend =
			dvb_attach(stv0299_attach, &philips_su1278_tt_config, &budget_ci->budget.i2c_adap);
		if (budget_ci->budget.dvb_frontend) {
			budget_ci->budget.dvb_frontend->ops.tuner_ops.set_params = philips_su1278_tt_tuner_set_params;
			break;
		}
		break;

	case 0x1010:		// TT DVB-C CI budget (stv0297/Philips tdm1316l(tda6651tt))
		budget_ci->tuner_pll_address = 0x61;
		budget_ci->budget.dvb_frontend =
			dvb_attach(stv0297_attach, &dvbc_philips_tdm1316l_config, &budget_ci->budget.i2c_adap);
		if (budget_ci->budget.dvb_frontend) {
			budget_ci->budget.dvb_frontend->ops.tuner_ops.set_params = dvbc_philips_tdm1316l_tuner_set_params;
			break;
		}
		break;

	case 0x1011:		// Hauppauge/TT Nova-T budget (tda10045/Philips tdm1316l(tda6651tt) + TDA9889)
		budget_ci->tuner_pll_address = 0x63;
		budget_ci->budget.dvb_frontend =
			dvb_attach(tda10045_attach, &philips_tdm1316l_config, &budget_ci->budget.i2c_adap);
		if (budget_ci->budget.dvb_frontend) {
			budget_ci->budget.dvb_frontend->ops.tuner_ops.init = philips_tdm1316l_tuner_init;
			budget_ci->budget.dvb_frontend->ops.tuner_ops.set_params = philips_tdm1316l_tuner_set_params;
			break;
		}
		break;

	case 0x1012:		// TT DVB-T CI budget (tda10046/Philips tdm1316l(tda6651tt))
		budget_ci->tuner_pll_address = 0x60;
		budget_ci->budget.dvb_frontend =
			dvb_attach(tda10046_attach, &philips_tdm1316l_config_invert, &budget_ci->budget.i2c_adap);
		if (budget_ci->budget.dvb_frontend) {
			budget_ci->budget.dvb_frontend->ops.tuner_ops.init = philips_tdm1316l_tuner_init;
			budget_ci->budget.dvb_frontend->ops.tuner_ops.set_params = philips_tdm1316l_tuner_set_params;
			break;
		}
		break;

	case 0x1017:		// TT S-1500 PCI
		budget_ci->budget.dvb_frontend = dvb_attach(stv0299_attach, &alps_bsbe1_config, &budget_ci->budget.i2c_adap);
		if (budget_ci->budget.dvb_frontend) {
			budget_ci->budget.dvb_frontend->ops.tuner_ops.set_params = alps_bsbe1_tuner_set_params;
			budget_ci->budget.dvb_frontend->tuner_priv = &budget_ci->budget.i2c_adap;

			budget_ci->budget.dvb_frontend->ops.dishnetwork_send_legacy_command = NULL;
			if (dvb_attach(lnbp21_attach, budget_ci->budget.dvb_frontend, &budget_ci->budget.i2c_adap, LNBP21_LLC, 0) == NULL) {
				pr_err("%s(): No LNBP21 found!\n", __func__);
				dvb_frontend_detach(budget_ci->budget.dvb_frontend);
				budget_ci->budget.dvb_frontend = NULL;
			}
		}
		break;

	case 0x101a: /* TT Budget-C-1501 (philips tda10023/philips tda8274A) */
		budget_ci->budget.dvb_frontend = dvb_attach(tda10023_attach, &tda10023_config, &budget_ci->budget.i2c_adap, 0x48);
		if (budget_ci->budget.dvb_frontend) {
			if (dvb_attach(tda827x_attach, budget_ci->budget.dvb_frontend, 0x61, &budget_ci->budget.i2c_adap, &tda827x_config) == NULL) {
				pr_err("%s(): No tda827x found!\n", __func__);
				dvb_frontend_detach(budget_ci->budget.dvb_frontend);
				budget_ci->budget.dvb_frontend = NULL;
			}
		}
		break;

	case 0x101b: /* TT S-1500B (BSBE1-D01A - STV0288/STB6000/LNBP21) */
		budget_ci->budget.dvb_frontend = dvb_attach(stv0288_attach, &stv0288_bsbe1_d01a_config, &budget_ci->budget.i2c_adap);
		if (budget_ci->budget.dvb_frontend) {
			if (dvb_attach(stb6000_attach, budget_ci->budget.dvb_frontend, 0x63, &budget_ci->budget.i2c_adap)) {
				if (!dvb_attach(lnbp21_attach, budget_ci->budget.dvb_frontend, &budget_ci->budget.i2c_adap, 0, 0)) {
					pr_err("%s(): No LNBP21 found!\n", __func__);
					dvb_frontend_detach(budget_ci->budget.dvb_frontend);
					budget_ci->budget.dvb_frontend = NULL;
				}
			} else {
				pr_err("%s(): No STB6000 found!\n", __func__);
				dvb_frontend_detach(budget_ci->budget.dvb_frontend);
				budget_ci->budget.dvb_frontend = NULL;
			}
		}
		break;

	case 0x1019:		// TT S2-3200 PCI
		/*
		 * NOTE! on some STB0899 versions, the internal PLL takes a longer time
		 * to settle, aka LOCK. On the older revisions of the chip, we don't see
		 * this, as a result on the newer chips the entire clock tree, will not
		 * be stable after a freshly POWER 'ed up situation.
		 * In this case, we should RESET the STB0899 (Active LOW) and wait for
		 * PLL stabilization.
		 *
		 * On the TT S2 3200 and clones, the STB0899 demodulator's RESETB is
		 * connected to the SAA7146 GPIO, GPIO2, Pin 142
		 */
		/* Reset Demodulator */
		saa7146_setgpio(budget_ci->budget.dev, 2, SAA7146_GPIO_OUTLO);
		/* Wait for everything to die */
		msleep(50);
		/* Pull it up out of Reset state */
		saa7146_setgpio(budget_ci->budget.dev, 2, SAA7146_GPIO_OUTHI);
		/* Wait for PLL to stabilize */
		msleep(250);
		/*
		 * PLL state should be stable now. Ideally, we should check
		 * for PLL LOCK status. But well, never mind!
		 */
		budget_ci->budget.dvb_frontend = dvb_attach(stb0899_attach, &tt3200_config, &budget_ci->budget.i2c_adap);
		if (budget_ci->budget.dvb_frontend) {
			if (dvb_attach(stb6100_attach, budget_ci->budget.dvb_frontend, &tt3200_stb6100_config, &budget_ci->budget.i2c_adap)) {
				if (!dvb_attach(lnbp21_attach, budget_ci->budget.dvb_frontend, &budget_ci->budget.i2c_adap, 0, 0)) {
					pr_err("%s(): No LNBP21 found!\n", __func__);
					dvb_frontend_detach(budget_ci->budget.dvb_frontend);
					budget_ci->budget.dvb_frontend = NULL;
				}
			} else {
				dvb_frontend_detach(budget_ci->budget.dvb_frontend);
				budget_ci->budget.dvb_frontend = NULL;
			}
		}
		break;

	}

	if (budget_ci->budget.dvb_frontend == NULL) {
		pr_err("A frontend driver was not found for device [%04x:%04x] subsystem [%04x:%04x]\n",
		       budget_ci->budget.dev->pci->vendor,
		       budget_ci->budget.dev->pci->device,
		       budget_ci->budget.dev->pci->subsystem_vendor,
		       budget_ci->budget.dev->pci->subsystem_device);
	} else {
		if (dvb_register_frontend
		    (&budget_ci->budget.dvb_adapter, budget_ci->budget.dvb_frontend)) {
			pr_err("Frontend registration failed!\n");
			dvb_frontend_detach(budget_ci->budget.dvb_frontend);
			budget_ci->budget.dvb_frontend = NULL;
		}
	}
}

static int budget_ci_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info)
{
	struct budget_ci *budget_ci;
	int err;

	budget_ci = kzalloc(sizeof(struct budget_ci), GFP_KERNEL);
	if (!budget_ci) {
		err = -ENOMEM;
		goto out1;
	}

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

	dev->ext_priv = budget_ci;

	err = ttpci_budget_init(&budget_ci->budget, dev, info, THIS_MODULE,
				adapter_nr);
	if (err)
		goto out2;

	err = msp430_ir_init(budget_ci);
	if (err)
		goto out3;

	ciintf_init(budget_ci);

	budget_ci->budget.dvb_adapter.priv = budget_ci;
	frontend_init(budget_ci);

	ttpci_budget_init_hooks(&budget_ci->budget);

	return 0;

out3:
	ttpci_budget_deinit(&budget_ci->budget);
out2:
	kfree(budget_ci);
out1:
	return err;
}

static int budget_ci_detach(struct saa7146_dev *dev)
{
	struct budget_ci *budget_ci = dev->ext_priv;
	struct saa7146_dev *saa = budget_ci->budget.dev;
	int err;

	if (budget_ci->budget.ci_present)
		ciintf_deinit(budget_ci);
	msp430_ir_deinit(budget_ci);
	if (budget_ci->budget.dvb_frontend) {
		dvb_unregister_frontend(budget_ci->budget.dvb_frontend);
		dvb_frontend_detach(budget_ci->budget.dvb_frontend);
	}
	err = ttpci_budget_deinit(&budget_ci->budget);

	// disable frontend and CI interface
	saa7146_setgpio(saa, 2, SAA7146_GPIO_INPUT);

	kfree(budget_ci);

	return err;
}

static struct saa7146_extension budget_extension;

MAKE_BUDGET_INFO(ttbs2, "TT-Budget/S-1500 PCI", BUDGET_TT);
MAKE_BUDGET_INFO(ttbci, "TT-Budget/WinTV-NOVA-CI PCI", BUDGET_TT_HW_DISEQC);
MAKE_BUDGET_INFO(ttbt2, "TT-Budget/WinTV-NOVA-T	 PCI", BUDGET_TT);
MAKE_BUDGET_INFO(ttbtci, "TT-Budget-T-CI PCI", BUDGET_TT);
MAKE_BUDGET_INFO(ttbcci, "TT-Budget-C-CI PCI", BUDGET_TT);
MAKE_BUDGET_INFO(ttc1501, "TT-Budget C-1501 PCI", BUDGET_TT);
MAKE_BUDGET_INFO(tt3200, "TT-Budget S2-3200 PCI", BUDGET_TT);
MAKE_BUDGET_INFO(ttbs1500b, "TT-Budget S-1500B PCI", BUDGET_TT);

static const struct pci_device_id pci_tbl[] = {
	MAKE_EXTENSION_PCI(ttbci, 0x13c2, 0x100c),
	MAKE_EXTENSION_PCI(ttbci, 0x13c2, 0x100f),
	MAKE_EXTENSION_PCI(ttbcci, 0x13c2, 0x1010),
	MAKE_EXTENSION_PCI(ttbt2, 0x13c2, 0x1011),
	MAKE_EXTENSION_PCI(ttbtci, 0x13c2, 0x1012),
	MAKE_EXTENSION_PCI(ttbs2, 0x13c2, 0x1017),
	MAKE_EXTENSION_PCI(ttc1501, 0x13c2, 0x101a),
	MAKE_EXTENSION_PCI(tt3200, 0x13c2, 0x1019),
	MAKE_EXTENSION_PCI(ttbs1500b, 0x13c2, 0x101b),
	{
	 .vendor = 0,
	}
};

MODULE_DEVICE_TABLE(pci, pci_tbl);

static struct saa7146_extension budget_extension = {
	.name = "budget_ci dvb",
	.flags = SAA7146_USE_I2C_IRQ,

	.module = THIS_MODULE,
	.pci_tbl = &pci_tbl[0],
	.attach = budget_ci_attach,
	.detach = budget_ci_detach,

	.irq_mask = MASK_03 | MASK_06 | MASK_10,
	.irq_func = budget_ci_irq,
};

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

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

module_init(budget_ci_init);
module_exit(budget_ci_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Michael Hunold, Jack Thomasson, Andrew de Quincey, others");
MODULE_DESCRIPTION("driver for the SAA7146 based so-called budget PCI DVB cards w/ CI-module produced by Siemens, Technotrend, Hauppauge");
