Merge branch 'android-x86_64-fugu-3.10-security-next' into android-x86_64-fugu-3.10

Security February 2017.1
diff --git a/arch/x86/configs/fugu_defconfig b/arch/x86/configs/fugu_defconfig
index 0cfde13..c0bdb54 100644
--- a/arch/x86/configs/fugu_defconfig
+++ b/arch/x86/configs/fugu_defconfig
@@ -44,6 +44,9 @@
 # CONFIG_SCHED_OMIT_FRAME_POINTER is not set
 CONFIG_MSLM=y
 CONFIG_NR_CPUS=4
+CONFIG_SCHED_SMT=y
+CONFIG_CPU_CONCURRENCY=y
+CONFIG_WORKLOAD_CONSOLIDATION=y
 CONFIG_PREEMPT=y
 # CONFIG_X86_MCE_AMD is not set
 CONFIG_X86_MSR=y
@@ -260,6 +263,7 @@
 # CONFIG_HW_RANDOM_VIA is not set
 CONFIG_NVRAM=y
 CONFIG_I2C_CHARDEV=y
+CONFIG_I2C_MUX=y
 CONFIG_I2C_GPIO=y
 CONFIG_PTP_1588_CLOCK=y
 CONFIG_GPIOLIB=y
@@ -294,6 +298,9 @@
 CONFIG_VIDEO_CX231XX=y
 # CONFIG_VIDEO_CX231XX_RC is not set
 CONFIG_VIDEO_CX231XX_DVB=y
+CONFIG_VIDEO_EM28XX=y
+CONFIG_VIDEO_EM28XX_DVB=y
+# CONFIG_VIDEO_EM28XX_RC is not set
 CONFIG_DRM=y
 CONFIG_INTEL_NO_FB_PANIC_NOTIFY=y
 CONFIG_VIDEO_OUTPUT_CONTROL=y
diff --git a/drivers/media/dvb-frontends/Kconfig b/drivers/media/dvb-frontends/Kconfig
index dea17b0c..f268934 100644
--- a/drivers/media/dvb-frontends/Kconfig
+++ b/drivers/media/dvb-frontends/Kconfig
@@ -432,6 +432,13 @@
 	help
 	  Say Y when you want to support this frontend.
 
+config DVB_SI2168
+	tristate "Silicon Labs Si2168"
+	depends on DVB_CORE && I2C && I2C_MUX
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y when you want to support this frontend.
+
 comment "DVB-C (cable) frontends"
 	depends on DVB_CORE
 
diff --git a/drivers/media/dvb-frontends/Makefile b/drivers/media/dvb-frontends/Makefile
index 3b79d38..92bb882 100644
--- a/drivers/media/dvb-frontends/Makefile
+++ b/drivers/media/dvb-frontends/Makefile
@@ -104,4 +104,5 @@
 obj-$(CONFIG_DVB_RTL2832) += rtl2832.o
 obj-$(CONFIG_DVB_M88RS2000) += m88rs2000.o
 obj-$(CONFIG_DVB_AF9033) += af9033.o
+obj-$(CONFIG_DVB_SI2168) += si2168.o
 
diff --git a/drivers/media/dvb-frontends/lgdt3306a.c b/drivers/media/dvb-frontends/lgdt3306a.c
index 73ee1cc..9c7e412 100644
--- a/drivers/media/dvb-frontends/lgdt3306a.c
+++ b/drivers/media/dvb-frontends/lgdt3306a.c
@@ -16,7 +16,6 @@
  *    GNU General Public License for more details.
  */
 
-#undef pr_fmt
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
 #include <asm/div64.h>
@@ -59,11 +58,12 @@
 
 struct lgdt3306a_state {
 	struct i2c_adapter *i2c_adap;
+	struct lgdt3306a_config config;
 	const struct lgdt3306a_config *cfg;
-
+	struct i2c_client *client;
 	struct dvb_frontend frontend;
 
-	fe_modulation_t current_modulation;
+	enum fe_modulation current_modulation;
 	u32 current_frequency;
 	u32 snr;
 };
@@ -118,6 +118,7 @@
 static int lgdt3306a_write_reg(struct lgdt3306a_state *state, u16 reg, u8 val)
 {
 	int ret;
+	struct i2c_adapter *i2c_adapter;
 	u8 buf[] = { reg >> 8, reg & 0xff, val };
 	struct i2c_msg msg = {
 		.addr = state->cfg->i2c_addr, .flags = 0,
@@ -126,8 +127,11 @@
 
 	dbg_reg("reg: 0x%04x, val: 0x%02x\n", reg, val);
 
-	ret = i2c_transfer(state->i2c_adap, &msg, 1);
-
+	if (state->cfg->has_tuner_i2c_adapter)
+		i2c_adapter = state->client->adapter;
+	else
+		i2c_adapter = state->i2c_adap;
+	ret = i2c_transfer(i2c_adapter, &msg, 1);
 	if (ret != 1) {
 		pr_err("error (addr %02x %02x <- %02x, err = %i)\n",
 		       msg.buf[0], msg.buf[1], msg.buf[2], ret);
@@ -142,6 +146,7 @@
 static int lgdt3306a_read_reg(struct lgdt3306a_state *state, u16 reg, u8 *val)
 {
 	int ret;
+	struct i2c_adapter *i2c_adapter;
 	u8 reg_buf[] = { reg >> 8, reg & 0xff };
 	struct i2c_msg msg[] = {
 		{ .addr = state->cfg->i2c_addr,
@@ -150,8 +155,12 @@
 		  .flags = I2C_M_RD, .buf = val, .len = 1 },
 	};
 
-	ret = i2c_transfer(state->i2c_adap, msg, 2);
+	if (state->cfg->has_tuner_i2c_adapter)
+		i2c_adapter = state->client->adapter;
+	else
+		i2c_adapter = state->i2c_adap;
 
+	ret = i2c_transfer(i2c_adapter, msg, 2);
 	if (ret != 2) {
 		pr_err("error (addr %02x reg %04x error (ret == %i)\n",
 		       state->cfg->i2c_addr, reg, ret);
@@ -622,6 +631,9 @@
 	case QAM_256:
 		ret = lgdt3306a_set_qam(state, QAM_256);
 		break;
+	case QAM_AUTO:
+		ret = lgdt3306a_set_qam(state, QAM_64);
+		break;
 	default:
 		return -EINVAL;
 	}
@@ -647,6 +659,7 @@
 		break;
 	case QAM_64:
 	case QAM_256:
+	case QAM_AUTO:
 		break;
 	default:
 		return -EINVAL;
@@ -701,6 +714,7 @@
 		break;
 	case QAM_64:
 	case QAM_256:
+	case QAM_AUTO:
 		/* Auto ok for QAM */
 		ret = lgdt3306a_set_inversion_auto(state, 1);
 		break;
@@ -724,6 +738,7 @@
 		break;
 	case QAM_64:
 	case QAM_256:
+	case QAM_AUTO:
 		if_freq_khz = state->cfg->qam_if_khz;
 		break;
 	default:
@@ -1559,7 +1574,8 @@
 	return LG3306_UNLOCK;
 }
 
-static int lgdt3306a_read_status(struct dvb_frontend *fe, fe_status_t *status)
+static int lgdt3306a_read_status(struct dvb_frontend *fe,
+				 enum fe_status *status)
 {
 	struct lgdt3306a_state *state = fe->demodulator_priv;
 	u16 strength = 0;
@@ -1581,6 +1597,7 @@
 		switch (state->current_modulation) {
 		case QAM_256:
 		case QAM_64:
+		case QAM_AUTO:
 			if (lgdt3306a_qam_lock_poll(state) == LG3306_LOCK) {
 				*status |= FE_HAS_VITERBI;
 				*status |= FE_HAS_SYNC;
@@ -1641,6 +1658,9 @@
 	case QAM_256:
 		 ref_snr = 2800; /* 28dB */
 		 break;
+	case QAM_AUTO:
+		 ref_snr = 2200; /* 22dB */
+		 break;
 	default:
 		return -EINVAL;
 	}
@@ -1706,7 +1726,7 @@
 
 static int lgdt3306a_tune(struct dvb_frontend *fe, bool re_tune,
 			  unsigned int mode_flags, unsigned int *delay,
-			  fe_status_t *status)
+			  enum fe_status *status)
 {
 	int ret = 0;
 	struct lgdt3306a_state *state = fe->demodulator_priv;
@@ -1736,7 +1756,7 @@
 
 static int lgdt3306a_search(struct dvb_frontend *fe)
 {
-	fe_status_t status = 0;
+	enum fe_status status = 0;
 	int i = 0, ret;
 	unsigned long timeout;
 
@@ -2111,7 +2131,144 @@
 }
 #endif /* DBG_DUMP */
 
+/*
+ * I2C gate logic
+ * We must use unlocked I2C I/O because I2C adapter lock is already taken
+ * by the caller (usually tuner driver).
+ */
+static int lgdt3306a_select(struct i2c_adapter *adap, void *mux_priv, u32 chan)
+{
+	struct lgdt3306a_state *state = mux_priv;
+	struct i2c_client *client = state->client;
 
+	int ret;
+	u8 val;
+	u8 buf[3];
+
+	struct i2c_msg read_msg_1 = {
+		.addr = client->addr,
+		.flags = 0,
+		.buf = "\x00\x02",
+		.len = 2,
+	};
+	struct i2c_msg read_msg_2 = {
+		.addr = client->addr,
+		.flags = I2C_M_RD,
+		.buf = &val,
+		.len = 1,
+	};
+
+	struct i2c_msg write_msg = {
+		.addr = client->addr,
+		.flags = 0,
+		.buf = buf,
+		.len = 3,
+	};
+
+	ret = __i2c_transfer(client->adapter, &read_msg_1, 1);
+	if (ret != 1) {
+		pr_err("error (addr %02x reg 0x002 error (ret == %i)\n",
+		       client->addr, ret);
+		if (ret < 0)
+			return ret;
+		else
+			return -EREMOTEIO;
+	}
+
+	ret = __i2c_transfer(client->adapter, &read_msg_2, 1);
+	if (ret != 1) {
+		pr_err("error (addr %02x reg 0x002 error (ret == %i)\n",
+		       client->addr, ret);
+		if (ret < 0)
+			return ret;
+		else
+			return -EREMOTEIO;
+	}
+
+	buf[0] = 0x00;
+	buf[1] = 0x02;
+	val &= 0x7F;
+	val |= LG3306_TUNERI2C_ON;
+	buf[2] = val;
+	ret = __i2c_transfer(client->adapter, &write_msg, 1);
+	if (ret != 1) {
+		pr_err("error (addr %02x %02x <- %02x, err = %i)\n",
+		       write_msg.buf[0], write_msg.buf[1], write_msg.buf[2],
+		       ret);
+		if (ret < 0)
+			return ret;
+		else
+			return -EREMOTEIO;
+	}
+	return 0;
+}
+
+static int lgdt3306a_deselect(struct i2c_adapter *adap, void *mux_priv,
+			      u32 chan)
+{
+	struct lgdt3306a_state *state = mux_priv;
+	struct i2c_client *client = state->client;
+	int ret;
+	u8 val;
+	u8 buf[3];
+
+	struct i2c_msg read_msg_1 = {
+		.addr = client->addr,
+		.flags = 0,
+		.buf = "\x00\x02",
+		.len = 2,
+	};
+	struct i2c_msg read_msg_2 = {
+		.addr = client->addr,
+		.flags = I2C_M_RD,
+		.buf = &val,
+		.len = 1,
+	};
+
+	struct i2c_msg write_msg = {
+		.addr = client->addr,
+		.flags = 0,
+		.len = 3,
+		.buf = buf,
+	};
+
+	ret = __i2c_transfer(client->adapter, &read_msg_1, 1);
+	if (ret != 1) {
+		pr_err("error (addr %02x reg 0x002 error (ret == %i)\n",
+		       client->addr, ret);
+		if (ret < 0)
+			return ret;
+		else
+			return -EREMOTEIO;
+	}
+
+	ret = __i2c_transfer(client->adapter, &read_msg_2, 1);
+	if (ret != 1) {
+		pr_err("error (addr %02x reg 0x002 error (ret == %i)\n",
+		       client->addr, ret);
+		if (ret < 0)
+			return ret;
+		else
+			return -EREMOTEIO;
+	}
+
+	buf[0] = 0x00;
+	buf[1] = 0x02;
+	val &= 0x7F;
+	val |= LG3306_TUNERI2C_OFF;
+	buf[2] = val;
+	ret = __i2c_transfer(client->adapter, &write_msg, 1);
+	if (ret != 1) {
+		pr_err("error (addr %02x %02x <- %02x, err = %i)\n",
+		       write_msg.buf[0], write_msg.buf[1], write_msg.buf[2],
+		       ret);
+		if (ret < 0)
+			return ret;
+		else
+			return -EREMOTEIO;
+	}
+	return 0;
+}
 
 static struct dvb_frontend_ops lgdt3306a_ops = {
 	.delsys = { SYS_ATSC, SYS_DVBC_ANNEX_B },
@@ -2141,6 +2298,135 @@
 	.search               = lgdt3306a_search,
 };
 
+static int lgdt3306a_probe(struct i2c_client *client,
+		const struct i2c_device_id *id)
+{
+	struct lgdt3306a_config *config = client->dev.platform_data;
+	struct lgdt3306a_state *state;
+	u8 val;
+	int ret;
+
+	dev_dbg(&client->dev, "\n");
+
+	state = kzalloc(sizeof(*state), GFP_KERNEL);
+	if (!state) {
+		ret = -ENOMEM;
+		dev_err(&client->dev, "kzalloc() failed\n");
+		goto err;
+	}
+
+	state->client = client;
+	state->config.i2c_addr = config->i2c_addr;
+	state->config.qam_if_khz = config->qam_if_khz;
+	state->config.vsb_if_khz = config->vsb_if_khz;
+	state->config.deny_i2c_rptr = config->deny_i2c_rptr;
+	state->config.spectral_inversion = config->spectral_inversion;
+	state->config.mpeg_mode = config->mpeg_mode;
+	state->config.tpclk_edge = config->tpclk_edge;
+	state->config.tpvalid_polarity = config->tpvalid_polarity;
+	state->config.xtalMHz = config->xtalMHz;
+	state->config.has_tuner_i2c_adapter = config->has_tuner_i2c_adapter;
+	state->cfg = &state->config;
+
+	/* create mux i2c adapter for tuner */
+	state->i2c_adap = i2c_add_mux_adapter(client->adapter, &client->dev,
+			state, 0, 0, 0, lgdt3306a_select, lgdt3306a_deselect);
+	if (state->i2c_adap == NULL) {
+		ret = -ENODEV;
+		goto err_kfree;
+	}
+
+	/* create dvb_frontend */
+	memcpy(&state->frontend.ops, &lgdt3306a_ops,
+	       sizeof(state->frontend.ops));
+	state->frontend.demodulator_priv = state;
+	*config->i2c_adapter = state->i2c_adap;
+	*config->fe = &state->frontend;
+
+	/* verify that we're talking to a lg3306a */
+	/* FGR - NOTE - there is no obvious ChipId to check; we check
+	 * some "known" bits after reset, but it's still just a guess */
+	ret = lgdt3306a_read_reg(state, 0x0000, &val);
+	if (lg_chkerr(ret))
+		goto err_kfree;
+	if ((val & 0x74) != 0x74) {
+		pr_warn("expected 0x74, got 0x%x\n", (val & 0x74));
+#if 0
+		/* FIXME - re-enable when we know this is right */
+		goto err_kfree;
+#endif
+	}
+	ret = lgdt3306a_read_reg(state, 0x0001, &val);
+	if (lg_chkerr(ret))
+		goto err_kfree;
+	if ((val & 0xf6) != 0xc6) {
+		pr_warn("expected 0xc6, got 0x%x\n", (val & 0xf6));
+#if 0
+		/* FIXME - re-enable when we know this is right */
+		goto err_kfree;
+#endif
+	}
+	ret = lgdt3306a_read_reg(state, 0x0002, &val);
+	if (lg_chkerr(ret))
+		goto err_kfree;
+	if ((val & 0x73) != 0x03) {
+		pr_warn("expected 0x03, got 0x%x\n", (val & 0x73));
+#if 0
+		/* FIXME - re-enable when we know this is right */
+		goto err_kfree;
+#endif
+	}
+
+	state->current_frequency = -1;
+	state->current_modulation = -1;
+
+	lgdt3306a_sleep(state);
+
+	i2c_set_clientdata(client, state);
+
+	dev_info(&client->dev, "LG Electronics LGDT3306A successfully attached\n");
+	return 0;
+err_kfree:
+	kfree(state);
+err:
+	dev_dbg(&client->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int lgdt3306a_remove(struct i2c_client *client)
+{
+	struct lgdt3306a_state *state = i2c_get_clientdata(client);
+
+	dev_dbg(&client->dev, "\n");
+
+	i2c_del_mux_adapter(state->i2c_adap);
+
+	state->frontend.ops.release = NULL;
+	state->frontend.demodulator_priv = NULL;
+
+	kfree(state);
+
+	return 0;
+}
+
+static const struct i2c_device_id lgdt3306a_id_table[] = {
+	{"lgdt3306a", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, lgdt3306a_id_table);
+
+static struct i2c_driver lgdt3306a_driver = {
+	.driver = {
+		.owner	= THIS_MODULE,
+		.name	= "lgdt3306a",
+	},
+	.probe		= lgdt3306a_probe,
+	.remove		= lgdt3306a_remove,
+	.id_table	= lgdt3306a_id_table,
+};
+
+module_i2c_driver(lgdt3306a_driver);
+
 MODULE_DESCRIPTION("LG Electronics LGDT3306A ATSC/QAM-B Demodulator Driver");
 MODULE_AUTHOR("Fred Richter <frichter@hauppauge.com>");
 MODULE_LICENSE("GPL");
diff --git a/drivers/media/dvb-frontends/lgdt3306a.h b/drivers/media/dvb-frontends/lgdt3306a.h
index ed8aa3e..b54e7e5 100644
--- a/drivers/media/dvb-frontends/lgdt3306a.h
+++ b/drivers/media/dvb-frontends/lgdt3306a.h
@@ -19,8 +19,11 @@
 #define _LGDT3306A_H_
 
 #include <linux/i2c.h>
+#include <linux/i2c-mux.h>
 #include "dvb_frontend.h"
 
+#define LG3306_TUNERI2C_ON  0x00
+#define LG3306_TUNERI2C_OFF 0x80
 
 enum lgdt3306a_mpeg_mode {
 	LGDT3306A_MPEG_PARALLEL = 0,
@@ -40,6 +43,18 @@
 struct lgdt3306a_config {
 	u8 i2c_addr;
 
+	/*
+	 * frontend
+	 * returned by driver
+	 */
+	struct dvb_frontend **fe;
+
+	/*
+	 * tuner I2C adapter
+	 * returned by driver
+	 */
+	struct i2c_adapter **i2c_adapter;
+
 	/* user defined IF frequency in KHz */
 	u16 qam_if_khz;
 	u16 vsb_if_khz;
@@ -56,6 +71,8 @@
 
 	/* demod clock freq in MHz; 24 or 25 supported */
 	int  xtalMHz;
+
+	int has_tuner_i2c_adapter;
 };
 
 #if IS_ENABLED(CONFIG_DVB_LGDT3306A)
diff --git a/drivers/media/dvb-frontends/s921.h b/drivers/media/dvb-frontends/s921.h
index 8d5e2a6..9a58f01 100644
--- a/drivers/media/dvb-frontends/s921.h
+++ b/drivers/media/dvb-frontends/s921.h
@@ -36,7 +36,7 @@
 	printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__);
 	return NULL;
 }
-static struct i2c_adapter *
+static inline struct i2c_adapter *
 	s921_get_tuner_i2c_adapter(struct dvb_frontend *fe)
 {
 	printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__);
diff --git a/drivers/media/dvb-frontends/si2168.c b/drivers/media/dvb-frontends/si2168.c
new file mode 100644
index 0000000..d40090b
--- /dev/null
+++ b/drivers/media/dvb-frontends/si2168.c
@@ -0,0 +1,777 @@
+/*
+ * Silicon Labs Si2168 DVB-T/T2/C demodulator driver
+ *
+ * Copyright (C) 2014 Antti Palosaari <crope@iki.fi>
+ *
+ *    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.
+ */
+
+#include "si2168_priv.h"
+
+static const struct dvb_frontend_ops si2168_ops;
+
+/* Own I2C adapter locking is needed because of I2C gate logic. */
+static int si2168_i2c_master_send_unlocked(const struct i2c_client *client,
+					   const char *buf, int count)
+{
+	int ret;
+	struct i2c_msg msg = {
+		.addr = client->addr,
+		.flags = 0,
+		.len = count,
+		.buf = (char *)buf,
+	};
+
+	ret = __i2c_transfer(client->adapter, &msg, 1);
+	return (ret == 1) ? count : ret;
+}
+
+static int si2168_i2c_master_recv_unlocked(const struct i2c_client *client,
+					   char *buf, int count)
+{
+	int ret;
+	struct i2c_msg msg = {
+		.addr = client->addr,
+		.flags = I2C_M_RD,
+		.len = count,
+		.buf = buf,
+	};
+
+	ret = __i2c_transfer(client->adapter, &msg, 1);
+	return (ret == 1) ? count : ret;
+}
+
+/* execute firmware command */
+static int si2168_cmd_execute_unlocked(struct i2c_client *client,
+				       struct si2168_cmd *cmd)
+{
+	int ret;
+	unsigned long timeout;
+
+	if (cmd->wlen) {
+		/* write cmd and args for firmware */
+		ret = si2168_i2c_master_send_unlocked(client, cmd->args,
+						      cmd->wlen);
+		if (ret < 0) {
+			goto err;
+		} else if (ret != cmd->wlen) {
+			ret = -EREMOTEIO;
+			goto err;
+		}
+	}
+
+	if (cmd->rlen) {
+		/* wait cmd execution terminate */
+		timeout = jiffies + msecs_to_jiffies(70);
+		while (!time_after(jiffies, timeout)) {
+			ret = si2168_i2c_master_recv_unlocked(client, cmd->args,
+							      cmd->rlen);
+			if (ret < 0) {
+				goto err;
+			} else if (ret != cmd->rlen) {
+				ret = -EREMOTEIO;
+				goto err;
+			}
+
+			/* firmware ready? */
+			if ((cmd->args[0] >> 7) & 0x01)
+				break;
+		}
+
+		dev_dbg(&client->dev, "cmd execution took %d ms\n",
+				jiffies_to_msecs(jiffies) -
+				(jiffies_to_msecs(timeout) - 70));
+
+		/* error bit set? */
+		if ((cmd->args[0] >> 6) & 0x01) {
+			ret = -EREMOTEIO;
+			goto err;
+		}
+
+		if (!((cmd->args[0] >> 7) & 0x01)) {
+			ret = -ETIMEDOUT;
+			goto err;
+		}
+	}
+
+	return 0;
+err:
+	dev_dbg(&client->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int si2168_cmd_execute(struct i2c_client *client, struct si2168_cmd *cmd)
+{
+	int ret;
+
+	i2c_lock_adapter(client->adapter);
+	ret = si2168_cmd_execute_unlocked(client, cmd);
+	i2c_unlock_adapter(client->adapter);
+
+	return ret;
+}
+
+static int si2168_read_status(struct dvb_frontend *fe, enum fe_status *status)
+{
+	struct i2c_client *client = fe->demodulator_priv;
+	struct si2168_dev *dev = i2c_get_clientdata(client);
+	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+	int ret;
+	struct si2168_cmd cmd;
+
+	*status = 0;
+
+	if (!dev->active) {
+		ret = -EAGAIN;
+		goto err;
+	}
+
+	switch (c->delivery_system) {
+	case SYS_DVBT:
+		memcpy(cmd.args, "\xa0\x01", 2);
+		cmd.wlen = 2;
+		cmd.rlen = 13;
+		break;
+	case SYS_DVBC_ANNEX_A:
+		memcpy(cmd.args, "\x90\x01", 2);
+		cmd.wlen = 2;
+		cmd.rlen = 9;
+		break;
+	case SYS_DVBT2:
+		memcpy(cmd.args, "\x50\x01", 2);
+		cmd.wlen = 2;
+		cmd.rlen = 14;
+		break;
+	default:
+		ret = -EINVAL;
+		goto err;
+	}
+
+	ret = si2168_cmd_execute(client, &cmd);
+	if (ret)
+		goto err;
+
+	switch ((cmd.args[2] >> 1) & 0x03) {
+	case 0x01:
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER;
+		break;
+	case 0x03:
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_VITERBI |
+				FE_HAS_SYNC | FE_HAS_LOCK;
+		break;
+	}
+
+	dev->fe_status = *status;
+
+	if (*status & FE_HAS_LOCK) {
+		c->cnr.len = 1;
+		c->cnr.stat[0].scale = FE_SCALE_DECIBEL;
+		c->cnr.stat[0].svalue = cmd.args[3] * 1000 / 4;
+	} else {
+		c->cnr.len = 1;
+		c->cnr.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
+	}
+
+	dev_dbg(&client->dev, "status=%02x args=%*ph\n",
+			*status, cmd.rlen, cmd.args);
+
+	return 0;
+err:
+	dev_dbg(&client->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int si2168_set_frontend(struct dvb_frontend *fe)
+{
+	struct i2c_client *client = fe->demodulator_priv;
+	struct si2168_dev *dev = i2c_get_clientdata(client);
+	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+	int ret;
+	struct si2168_cmd cmd;
+	u8 bandwidth, delivery_system;
+
+	dev_dbg(&client->dev,
+			"delivery_system=%u modulation=%u frequency=%u "
+			"bandwidth_hz=%u symbol_rate=%u inversion=%u "
+			"stream_id=%u\n",
+			c->delivery_system, c->modulation, c->frequency,
+			c->bandwidth_hz, c->symbol_rate, c->inversion,
+			c->stream_id);
+
+	if (!dev->active) {
+		ret = -EAGAIN;
+		goto err;
+	}
+
+	switch (c->delivery_system) {
+	case SYS_DVBT:
+		delivery_system = 0x20;
+		break;
+	case SYS_DVBC_ANNEX_A:
+		delivery_system = 0x30;
+		break;
+	case SYS_DVBT2:
+		delivery_system = 0x70;
+		break;
+	default:
+		ret = -EINVAL;
+		goto err;
+	}
+
+	if (c->bandwidth_hz == 0) {
+		ret = -EINVAL;
+		goto err;
+	} else if (c->bandwidth_hz <= 2000000)
+		bandwidth = 0x02;
+	else if (c->bandwidth_hz <= 5000000)
+		bandwidth = 0x05;
+	else if (c->bandwidth_hz <= 6000000)
+		bandwidth = 0x06;
+	else if (c->bandwidth_hz <= 7000000)
+		bandwidth = 0x07;
+	else if (c->bandwidth_hz <= 8000000)
+		bandwidth = 0x08;
+	else if (c->bandwidth_hz <= 9000000)
+		bandwidth = 0x09;
+	else if (c->bandwidth_hz <= 10000000)
+		bandwidth = 0x0a;
+	else
+		bandwidth = 0x0f;
+
+	/* program tuner */
+	if (fe->ops.tuner_ops.set_params) {
+		ret = fe->ops.tuner_ops.set_params(fe);
+		if (ret)
+			goto err;
+	}
+
+	memcpy(cmd.args, "\x88\x02\x02\x02\x02", 5);
+	cmd.wlen = 5;
+	cmd.rlen = 5;
+	ret = si2168_cmd_execute(client, &cmd);
+	if (ret)
+		goto err;
+
+	/* that has no big effect */
+	if (c->delivery_system == SYS_DVBT)
+		memcpy(cmd.args, "\x89\x21\x06\x11\xff\x98", 6);
+	else if (c->delivery_system == SYS_DVBC_ANNEX_A)
+		memcpy(cmd.args, "\x89\x21\x06\x11\x89\xf0", 6);
+	else if (c->delivery_system == SYS_DVBT2)
+		memcpy(cmd.args, "\x89\x21\x06\x11\x89\x20", 6);
+	cmd.wlen = 6;
+	cmd.rlen = 3;
+	ret = si2168_cmd_execute(client, &cmd);
+	if (ret)
+		goto err;
+
+	if (c->delivery_system == SYS_DVBT2) {
+		/* select PLP */
+		cmd.args[0] = 0x52;
+		cmd.args[1] = c->stream_id & 0xff;
+		cmd.args[2] = c->stream_id == NO_STREAM_ID_FILTER ? 0 : 1;
+		cmd.wlen = 3;
+		cmd.rlen = 1;
+		ret = si2168_cmd_execute(client, &cmd);
+		if (ret)
+			goto err;
+	}
+
+	memcpy(cmd.args, "\x51\x03", 2);
+	cmd.wlen = 2;
+	cmd.rlen = 12;
+	ret = si2168_cmd_execute(client, &cmd);
+	if (ret)
+		goto err;
+
+	memcpy(cmd.args, "\x12\x08\x04", 3);
+	cmd.wlen = 3;
+	cmd.rlen = 3;
+	ret = si2168_cmd_execute(client, &cmd);
+	if (ret)
+		goto err;
+
+	memcpy(cmd.args, "\x14\x00\x0c\x10\x12\x00", 6);
+	cmd.wlen = 6;
+	cmd.rlen = 4;
+	ret = si2168_cmd_execute(client, &cmd);
+	if (ret)
+		goto err;
+
+	memcpy(cmd.args, "\x14\x00\x06\x10\x24\x00", 6);
+	cmd.wlen = 6;
+	cmd.rlen = 4;
+	ret = si2168_cmd_execute(client, &cmd);
+	if (ret)
+		goto err;
+
+	memcpy(cmd.args, "\x14\x00\x07\x10\x00\x24", 6);
+	cmd.wlen = 6;
+	cmd.rlen = 4;
+	ret = si2168_cmd_execute(client, &cmd);
+	if (ret)
+		goto err;
+
+	memcpy(cmd.args, "\x14\x00\x0a\x10\x00\x00", 6);
+	cmd.args[4] = delivery_system | bandwidth;
+	cmd.wlen = 6;
+	cmd.rlen = 4;
+	ret = si2168_cmd_execute(client, &cmd);
+	if (ret)
+		goto err;
+
+	/* set DVB-C symbol rate */
+	if (c->delivery_system == SYS_DVBC_ANNEX_A) {
+		memcpy(cmd.args, "\x14\x00\x02\x11", 4);
+		cmd.args[4] = ((c->symbol_rate / 1000) >> 0) & 0xff;
+		cmd.args[5] = ((c->symbol_rate / 1000) >> 8) & 0xff;
+		cmd.wlen = 6;
+		cmd.rlen = 4;
+		ret = si2168_cmd_execute(client, &cmd);
+		if (ret)
+			goto err;
+	}
+
+	memcpy(cmd.args, "\x14\x00\x0f\x10\x10\x00", 6);
+	cmd.wlen = 6;
+	cmd.rlen = 4;
+	ret = si2168_cmd_execute(client, &cmd);
+	if (ret)
+		goto err;
+
+	memcpy(cmd.args, "\x14\x00\x09\x10\xe3\x08", 6);
+	cmd.args[5] |= dev->ts_clock_inv ? 0x00 : 0x10;
+	cmd.wlen = 6;
+	cmd.rlen = 4;
+	ret = si2168_cmd_execute(client, &cmd);
+	if (ret)
+		goto err;
+
+	memcpy(cmd.args, "\x14\x00\x08\x10\xd7\x05", 6);
+	cmd.args[5] |= dev->ts_clock_inv ? 0x00 : 0x10;
+	cmd.wlen = 6;
+	cmd.rlen = 4;
+	ret = si2168_cmd_execute(client, &cmd);
+	if (ret)
+		goto err;
+
+	memcpy(cmd.args, "\x14\x00\x01\x12\x00\x00", 6);
+	cmd.wlen = 6;
+	cmd.rlen = 4;
+	ret = si2168_cmd_execute(client, &cmd);
+	if (ret)
+		goto err;
+
+	memcpy(cmd.args, "\x14\x00\x01\x03\x0c\x00", 6);
+	cmd.wlen = 6;
+	cmd.rlen = 4;
+	ret = si2168_cmd_execute(client, &cmd);
+	if (ret)
+		goto err;
+
+	memcpy(cmd.args, "\x85", 1);
+	cmd.wlen = 1;
+	cmd.rlen = 1;
+	ret = si2168_cmd_execute(client, &cmd);
+	if (ret)
+		goto err;
+
+	dev->delivery_system = c->delivery_system;
+
+	return 0;
+err:
+	dev_dbg(&client->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int si2168_init(struct dvb_frontend *fe)
+{
+	struct i2c_client *client = fe->demodulator_priv;
+	struct si2168_dev *dev = i2c_get_clientdata(client);
+	int ret, len, remaining;
+	const struct firmware *fw;
+	const char *fw_name;
+	struct si2168_cmd cmd;
+	unsigned int chip_id;
+	enum si2168_chip_id {
+		SI2168_A20 = ('A' << 24 | 68 << 16 | '2' << 8 | '0' << 0),
+		SI2168_A30 = ('A' << 24 | 68 << 16 | '3' << 8 | '0' << 0),
+		SI2168_B40 = ('B' << 24 | 68 << 16 | '4' << 8 | '0' << 0)
+	};
+
+	dev_dbg(&client->dev, "\n");
+
+	/* initialize */
+	memcpy(cmd.args, "\xc0\x12\x00\x0c\x00\x0d\x16\x00\x00\x00\x00\x00\x00", 13);
+	cmd.wlen = 13;
+	cmd.rlen = 0;
+	ret = si2168_cmd_execute(client, &cmd);
+	if (ret)
+		goto err;
+
+	if (dev->fw_loaded) {
+		/* resume */
+		memcpy(cmd.args, "\xc0\x06\x08\x0f\x00\x20\x21\x01", 8);
+		cmd.wlen = 8;
+		cmd.rlen = 1;
+		ret = si2168_cmd_execute(client, &cmd);
+		if (ret)
+			goto err;
+
+		memcpy(cmd.args, "\x85", 1);
+		cmd.wlen = 1;
+		cmd.rlen = 1;
+		ret = si2168_cmd_execute(client, &cmd);
+		if (ret)
+			goto err;
+
+		goto warm;
+	}
+
+	/* power up */
+	memcpy(cmd.args, "\xc0\x06\x01\x0f\x00\x20\x20\x01", 8);
+	cmd.wlen = 8;
+	cmd.rlen = 1;
+	ret = si2168_cmd_execute(client, &cmd);
+	if (ret)
+		goto err;
+
+	/* query chip revision */
+	memcpy(cmd.args, "\x02", 1);
+	cmd.wlen = 1;
+	cmd.rlen = 13;
+	ret = si2168_cmd_execute(client, &cmd);
+	if (ret)
+		goto err;
+
+	chip_id = cmd.args[1] << 24 | cmd.args[2] << 16 | cmd.args[3] << 8 |
+			cmd.args[4] << 0;
+
+	switch (chip_id) {
+	case SI2168_A20:
+		fw_name = SI2168_A20_FIRMWARE;
+		break;
+	case SI2168_A30:
+		fw_name = SI2168_A30_FIRMWARE;
+		break;
+	case SI2168_B40:
+		fw_name = SI2168_B40_FIRMWARE;
+		break;
+	default:
+		dev_err(&client->dev, "unknown chip version Si21%d-%c%c%c\n",
+				cmd.args[2], cmd.args[1],
+				cmd.args[3], cmd.args[4]);
+		ret = -EINVAL;
+		goto err;
+	}
+
+	dev_info(&client->dev, "found a 'Silicon Labs Si21%d-%c%c%c'\n",
+			cmd.args[2], cmd.args[1], cmd.args[3], cmd.args[4]);
+
+	/* request the firmware, this will block and timeout */
+	ret = request_firmware(&fw, fw_name, &client->dev);
+	if (ret) {
+		/* fallback mechanism to handle old name for Si2168 B40 fw */
+		if (chip_id == SI2168_B40) {
+			fw_name = SI2168_B40_FIRMWARE_FALLBACK;
+			ret = request_firmware(&fw, fw_name, &client->dev);
+		}
+
+		if (ret == 0) {
+			dev_notice(&client->dev,
+					"please install firmware file '%s'\n",
+					SI2168_B40_FIRMWARE);
+		} else {
+			dev_err(&client->dev,
+					"firmware file '%s' not found\n",
+					fw_name);
+			goto err_release_firmware;
+		}
+	}
+
+	dev_info(&client->dev, "downloading firmware from file '%s'\n",
+			fw_name);
+
+	if ((fw->size % 17 == 0) && (fw->data[0] > 5)) {
+		/* firmware is in the new format */
+		for (remaining = fw->size; remaining > 0; remaining -= 17) {
+			len = fw->data[fw->size - remaining];
+			memcpy(cmd.args, &fw->data[(fw->size - remaining) + 1], len);
+			cmd.wlen = len;
+			cmd.rlen = 1;
+			ret = si2168_cmd_execute(client, &cmd);
+			if (ret)
+				break;
+		}
+	} else if (fw->size % 8 == 0) {
+		/* firmware is in the old format */
+		for (remaining = fw->size; remaining > 0; remaining -= 8) {
+			len = 8;
+			memcpy(cmd.args, &fw->data[fw->size - remaining], len);
+			cmd.wlen = len;
+			cmd.rlen = 1;
+			ret = si2168_cmd_execute(client, &cmd);
+			if (ret)
+				break;
+		}
+	} else {
+		/* bad or unknown firmware format */
+		ret = -EINVAL;
+	}
+
+	if (ret) {
+		dev_err(&client->dev, "firmware download failed %d\n", ret);
+		goto err_release_firmware;
+	}
+
+	release_firmware(fw);
+
+	memcpy(cmd.args, "\x01\x01", 2);
+	cmd.wlen = 2;
+	cmd.rlen = 1;
+	ret = si2168_cmd_execute(client, &cmd);
+	if (ret)
+		goto err;
+
+	/* query firmware version */
+	memcpy(cmd.args, "\x11", 1);
+	cmd.wlen = 1;
+	cmd.rlen = 10;
+	ret = si2168_cmd_execute(client, &cmd);
+	if (ret)
+		goto err;
+
+	dev_info(&client->dev, "firmware version: %c.%c.%d\n",
+			cmd.args[6], cmd.args[7], cmd.args[8]);
+
+	/* set ts mode */
+	memcpy(cmd.args, "\x14\x00\x01\x10\x10\x00", 6);
+	cmd.args[4] |= dev->ts_mode;
+	if (dev->ts_clock_gapped)
+		cmd.args[4] |= 0x40;
+	cmd.wlen = 6;
+	cmd.rlen = 4;
+	ret = si2168_cmd_execute(client, &cmd);
+	if (ret)
+		goto err;
+
+	dev->fw_loaded = true;
+warm:
+	dev->active = true;
+
+	return 0;
+
+err_release_firmware:
+	release_firmware(fw);
+err:
+	dev_dbg(&client->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int si2168_sleep(struct dvb_frontend *fe)
+{
+	struct i2c_client *client = fe->demodulator_priv;
+	struct si2168_dev *dev = i2c_get_clientdata(client);
+	int ret;
+	struct si2168_cmd cmd;
+
+	dev_dbg(&client->dev, "\n");
+
+	dev->active = false;
+
+	memcpy(cmd.args, "\x13", 1);
+	cmd.wlen = 1;
+	cmd.rlen = 0;
+	ret = si2168_cmd_execute(client, &cmd);
+	if (ret)
+		goto err;
+
+	return 0;
+err:
+	dev_dbg(&client->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int si2168_get_tune_settings(struct dvb_frontend *fe,
+	struct dvb_frontend_tune_settings *s)
+{
+	s->min_delay_ms = 900;
+
+	return 0;
+}
+
+/*
+ * I2C gate logic
+ * We must use unlocked I2C I/O because I2C adapter lock is already taken
+ * by the caller (usually tuner driver).
+ */
+static int si2168_select(struct i2c_adapter *adap, void *mux_priv, u32 chan)
+{
+	struct i2c_client *client = mux_priv;
+	int ret;
+	struct si2168_cmd cmd;
+
+	/* open I2C gate */
+	memcpy(cmd.args, "\xc0\x0d\x01", 3);
+	cmd.wlen = 3;
+	cmd.rlen = 0;
+	ret = si2168_cmd_execute_unlocked(client, &cmd);
+	if (ret)
+		goto err;
+
+	return 0;
+err:
+	dev_dbg(&client->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int si2168_deselect(struct i2c_adapter *adap, void *mux_priv, u32 chan)
+{
+	struct i2c_client *client = mux_priv;
+	int ret;
+	struct si2168_cmd cmd;
+
+	/* close I2C gate */
+	memcpy(cmd.args, "\xc0\x0d\x00", 3);
+	cmd.wlen = 3;
+	cmd.rlen = 0;
+	ret = si2168_cmd_execute_unlocked(client, &cmd);
+	if (ret)
+		goto err;
+
+	return 0;
+err:
+	dev_dbg(&client->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static const struct dvb_frontend_ops si2168_ops = {
+	.delsys = {SYS_DVBT, SYS_DVBT2, SYS_DVBC_ANNEX_A},
+	.info = {
+		.name = "Silicon Labs Si2168",
+		.symbol_rate_min = 1000000,
+		.symbol_rate_max = 7200000,
+		.caps =	FE_CAN_FEC_1_2 |
+			FE_CAN_FEC_2_3 |
+			FE_CAN_FEC_3_4 |
+			FE_CAN_FEC_5_6 |
+			FE_CAN_FEC_7_8 |
+			FE_CAN_FEC_AUTO |
+			FE_CAN_QPSK |
+			FE_CAN_QAM_16 |
+			FE_CAN_QAM_32 |
+			FE_CAN_QAM_64 |
+			FE_CAN_QAM_128 |
+			FE_CAN_QAM_256 |
+			FE_CAN_QAM_AUTO |
+			FE_CAN_TRANSMISSION_MODE_AUTO |
+			FE_CAN_GUARD_INTERVAL_AUTO |
+			FE_CAN_HIERARCHY_AUTO |
+			FE_CAN_MUTE_TS |
+			FE_CAN_2G_MODULATION |
+			FE_CAN_MULTISTREAM
+	},
+
+	.get_tune_settings = si2168_get_tune_settings,
+
+	.init = si2168_init,
+	.sleep = si2168_sleep,
+
+	.set_frontend = si2168_set_frontend,
+
+	.read_status = si2168_read_status,
+};
+
+static int si2168_probe(struct i2c_client *client,
+		const struct i2c_device_id *id)
+{
+	struct si2168_config *config = client->dev.platform_data;
+	struct si2168_dev *dev;
+	int ret;
+
+	dev_dbg(&client->dev, "\n");
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev) {
+		ret = -ENOMEM;
+		dev_err(&client->dev, "kzalloc() failed\n");
+		goto err;
+	}
+
+	/* create mux i2c adapter for tuner */
+	dev->adapter = i2c_add_mux_adapter(client->adapter, &client->dev,
+			client, 0, 0, 0, si2168_select, si2168_deselect);
+	if (dev->adapter == NULL) {
+		ret = -ENODEV;
+		goto err_kfree;
+	}
+
+	/* create dvb_frontend */
+	memcpy(&dev->fe.ops, &si2168_ops, sizeof(struct dvb_frontend_ops));
+	dev->fe.demodulator_priv = client;
+	*config->i2c_adapter = dev->adapter;
+	*config->fe = &dev->fe;
+	dev->ts_mode = config->ts_mode;
+	dev->ts_clock_inv = config->ts_clock_inv;
+	dev->ts_clock_gapped = config->ts_clock_gapped;
+	dev->fw_loaded = false;
+
+	i2c_set_clientdata(client, dev);
+
+	dev_info(&client->dev, "Silicon Labs Si2168 successfully attached\n");
+	return 0;
+err_kfree:
+	kfree(dev);
+err:
+	dev_dbg(&client->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int si2168_remove(struct i2c_client *client)
+{
+	struct si2168_dev *dev = i2c_get_clientdata(client);
+
+	dev_dbg(&client->dev, "\n");
+
+	i2c_del_mux_adapter(dev->adapter);
+
+	dev->fe.ops.release = NULL;
+	dev->fe.demodulator_priv = NULL;
+
+	kfree(dev);
+
+	return 0;
+}
+
+static const struct i2c_device_id si2168_id_table[] = {
+	{"si2168", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, si2168_id_table);
+
+static struct i2c_driver si2168_driver = {
+	.driver = {
+		.owner	= THIS_MODULE,
+		.name	= "si2168",
+	},
+	.probe		= si2168_probe,
+	.remove		= si2168_remove,
+	.id_table	= si2168_id_table,
+};
+
+module_i2c_driver(si2168_driver);
+
+MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
+MODULE_DESCRIPTION("Silicon Labs Si2168 DVB-T/T2/C demodulator driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(SI2168_A20_FIRMWARE);
+MODULE_FIRMWARE(SI2168_A30_FIRMWARE);
+MODULE_FIRMWARE(SI2168_B40_FIRMWARE);
diff --git a/drivers/media/dvb-frontends/si2168.h b/drivers/media/dvb-frontends/si2168.h
new file mode 100644
index 0000000..3225d0c
--- /dev/null
+++ b/drivers/media/dvb-frontends/si2168.h
@@ -0,0 +1,50 @@
+/*
+ * Silicon Labs Si2168 DVB-T/T2/C demodulator driver
+ *
+ * Copyright (C) 2014 Antti Palosaari <crope@iki.fi>
+ *
+ *    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.
+ */
+
+#ifndef SI2168_H
+#define SI2168_H
+
+#include <linux/dvb/frontend.h>
+/*
+ * I2C address
+ * 0x64
+ */
+struct si2168_config {
+	/*
+	 * frontend
+	 * returned by driver
+	 */
+	struct dvb_frontend **fe;
+
+	/*
+	 * tuner I2C adapter
+	 * returned by driver
+	 */
+	struct i2c_adapter **i2c_adapter;
+
+	/* TS mode */
+#define SI2168_TS_PARALLEL	0x06
+#define SI2168_TS_SERIAL	0x03
+	u8 ts_mode;
+
+	/* TS clock inverted */
+	bool ts_clock_inv;
+
+	/* TS clock gapped */
+	bool ts_clock_gapped;
+};
+
+#endif
diff --git a/drivers/media/dvb-frontends/si2168_priv.h b/drivers/media/dvb-frontends/si2168_priv.h
new file mode 100644
index 0000000..c07e6fe
--- /dev/null
+++ b/drivers/media/dvb-frontends/si2168_priv.h
@@ -0,0 +1,51 @@
+/*
+ * Silicon Labs Si2168 DVB-T/T2/C demodulator driver
+ *
+ * Copyright (C) 2014 Antti Palosaari <crope@iki.fi>
+ *
+ *    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.
+ */
+
+#ifndef SI2168_PRIV_H
+#define SI2168_PRIV_H
+
+#include "si2168.h"
+#include "dvb_frontend.h"
+#include <linux/firmware.h>
+#include <linux/i2c-mux.h>
+
+#define SI2168_A20_FIRMWARE "dvb-demod-si2168-a20-01.fw"
+#define SI2168_A30_FIRMWARE "dvb-demod-si2168-a30-01.fw"
+#define SI2168_B40_FIRMWARE "dvb-demod-si2168-b40-01.fw"
+#define SI2168_B40_FIRMWARE_FALLBACK "dvb-demod-si2168-02.fw"
+
+/* state struct */
+struct si2168_dev {
+	struct i2c_adapter *adapter;
+	struct dvb_frontend fe;
+	enum fe_delivery_system delivery_system;
+	enum fe_status fe_status;
+	bool active;
+	bool fw_loaded;
+	u8 ts_mode;
+	bool ts_clock_inv;
+	bool ts_clock_gapped;
+};
+
+/* firmware command struct */
+#define SI2168_ARGLEN      30
+struct si2168_cmd {
+	u8 args[SI2168_ARGLEN];
+	unsigned wlen;
+	unsigned rlen;
+};
+
+#endif
diff --git a/drivers/media/tuners/si2157.c b/drivers/media/tuners/si2157.c
index 7c2f621..942ab21 100644
--- a/drivers/media/tuners/si2157.c
+++ b/drivers/media/tuners/si2157.c
@@ -18,51 +18,10 @@
 
 static const struct dvb_tuner_ops si2157_ops;
 
-/* si2157_i2c_master_send - issue a single I2C message in master transmit mode */
-static int si2157_i2c_master_send(struct i2c_adapter *adap, u8 i2c_addr,
-				  const char *buf, int count)
-{
-	int ret;
-	struct i2c_msg msg;
-
-	msg.addr = i2c_addr;
-	msg.flags = 0;
-	msg.len = count;
-	msg.buf = (char *)buf;
-
-	ret = i2c_transfer(adap, &msg, 1);
-
-	/*
-	 * If everything went ok (i.e. 1 msg transmitted), return #bytes
-	 * transmitted, else error code.
-	 */
-	return (ret == 1) ? count : ret;
-}
-
-/* si2157_i2c_master_recv - issue a single I2C message in master receive mode */
-static int si2157_i2c_master_recv(struct i2c_adapter *adap, u8 i2c_addr,
-				  char *buf, int count)
-{
-	struct i2c_msg msg;
-	int ret;
-
-	msg.addr = i2c_addr;
-	msg.flags = I2C_M_RD;
-	msg.len = count;
-	msg.buf = buf;
-
-	ret = i2c_transfer(adap, &msg, 1);
-
-	/*
-	 * If everything went ok (i.e. 1 msg received), return #bytes received,
-	 * else error code.
-	 */
-	return (ret == 1) ? count : ret;
-}
-
 /* execute firmware command */
-static int si2157_cmd_execute(struct si2157_dev *dev, struct si2157_cmd *cmd)
+static int si2157_cmd_execute(struct i2c_client *client, struct si2157_cmd *cmd)
 {
+	struct si2157_dev *dev = i2c_get_clientdata(client);
 	int ret;
 	unsigned long timeout;
 
@@ -70,8 +29,7 @@
 
 	if (cmd->wlen) {
 		/* write cmd and args for firmware */
-		ret = si2157_i2c_master_send(dev->i2c_adap, dev->i2c_addr,
-									 cmd->args, cmd->wlen);
+		ret = i2c_master_send(client, cmd->args, cmd->wlen);
 		if (ret < 0) {
 			goto err_mutex_unlock;
 		} else if (ret != cmd->wlen) {
@@ -85,8 +43,7 @@
 		#define TIMEOUT 80
 		timeout = jiffies + msecs_to_jiffies(TIMEOUT);
 		while (!time_after(jiffies, timeout)) {
-			ret = si2157_i2c_master_recv(dev->i2c_adap, dev->i2c_addr,
-										 cmd->args, cmd->rlen);
+			ret = i2c_master_recv(client, cmd->args, cmd->rlen);
 			if (ret < 0) {
 				goto err_mutex_unlock;
 			} else if (ret != cmd->rlen) {
@@ -99,7 +56,7 @@
 				break;
 		}
 
-		dev_dbg(&dev->i2c_adap->dev, "cmd execution took %d ms\n",
+		dev_dbg(&client->dev, "cmd execution took %d ms\n",
 				jiffies_to_msecs(jiffies) -
 				(jiffies_to_msecs(timeout) - TIMEOUT));
 
@@ -114,20 +71,22 @@
 
 err_mutex_unlock:
 	mutex_unlock(&dev->i2c_mutex);
-	dev_dbg(&dev->i2c_adap->dev, "failed=%d\n", ret);
+	dev_dbg(&client->dev, "failed=%d\n", ret);
 	return ret;
 }
 
 static int si2157_init(struct dvb_frontend *fe)
 {
-	struct si2157_dev *dev = fe->tuner_priv;
+	struct i2c_client *client = fe->tuner_priv;
+	struct si2157_dev *dev = i2c_get_clientdata(client);
+	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
 	int ret, len, remaining;
 	struct si2157_cmd cmd;
 	const struct firmware *fw;
 	const char *fw_name;
 	unsigned int chip_id;
 
-	dev_dbg(&dev->i2c_adap->dev, "\n");
+	dev_dbg(&client->dev, "\n");
 
 	if (dev->fw_loaded)
 		goto warm;
@@ -141,7 +100,7 @@
 		cmd.wlen = 15;
 	}
 	cmd.rlen = 1;
-	ret = si2157_cmd_execute(dev, &cmd);
+	ret = si2157_cmd_execute(client, &cmd);
 	if (ret)
 		goto err;
 
@@ -149,7 +108,7 @@
 	memcpy(cmd.args, "\x02", 1);
 	cmd.wlen = 1;
 	cmd.rlen = 13;
-	ret = si2157_cmd_execute(dev, &cmd);
+	ret = si2157_cmd_execute(client, &cmd);
 	if (ret)
 		goto err;
 
@@ -173,36 +132,36 @@
 		fw_name = NULL;
 		break;
 	default:
-		dev_err(&dev->i2c_adap->dev,
-			"unknown chip version Si21%d-%c%c%c\n", cmd.args[2],
-			cmd.args[1], cmd.args[3], cmd.args[4]);
+		dev_err(&client->dev, "unknown chip version Si21%d-%c%c%c\n",
+				cmd.args[2], cmd.args[1],
+				cmd.args[3], cmd.args[4]);
 		ret = -EINVAL;
 		goto err;
 	}
 
-	dev_info(&dev->i2c_adap->dev, "found a 'Silicon Labs Si21%d-%c%c%c'\n",
+	dev_info(&client->dev, "found a 'Silicon Labs Si21%d-%c%c%c'\n",
 			cmd.args[2], cmd.args[1], cmd.args[3], cmd.args[4]);
 
 	if (fw_name == NULL)
 		goto skip_fw_download;
 
 	/* request the firmware, this will block and timeout */
-	ret = request_firmware(&fw, fw_name, &dev->i2c_adap->dev);
+	ret = request_firmware(&fw, fw_name, &client->dev);
 	if (ret) {
-		dev_err(&dev->i2c_adap->dev, "firmware file '%s' not found\n",
+		dev_err(&client->dev, "firmware file '%s' not found\n",
 				fw_name);
 		goto err;
 	}
 
 	/* firmware should be n chunks of 17 bytes */
 	if (fw->size % 17 != 0) {
-		dev_err(&dev->i2c_adap->dev, "firmware file '%s' is invalid\n",
+		dev_err(&client->dev, "firmware file '%s' is invalid\n",
 				fw_name);
 		ret = -EINVAL;
 		goto err_release_firmware;
 	}
 
-	dev_info(&dev->i2c_adap->dev, "downloading firmware from file '%s'\n",
+	dev_info(&client->dev, "downloading firmware from file '%s'\n",
 			fw_name);
 
 	for (remaining = fw->size; remaining > 0; remaining -= 17) {
@@ -210,9 +169,9 @@
 		memcpy(cmd.args, &fw->data[(fw->size - remaining) + 1], len);
 		cmd.wlen = len;
 		cmd.rlen = 1;
-		ret = si2157_cmd_execute(dev, &cmd);
+		ret = si2157_cmd_execute(client, &cmd);
 		if (ret) {
-			dev_err(&dev->i2c_adap->dev, "firmware download failed %d\n",
+			dev_err(&client->dev, "firmware download failed %d\n",
 					ret);
 			goto err_release_firmware;
 		}
@@ -225,7 +184,7 @@
 	memcpy(cmd.args, "\x01\x01", 2);
 	cmd.wlen = 2;
 	cmd.rlen = 1;
-	ret = si2157_cmd_execute(dev, &cmd);
+	ret = si2157_cmd_execute(client, &cmd);
 	if (ret)
 		goto err;
 
@@ -233,60 +192,70 @@
 	memcpy(cmd.args, "\x11", 1);
 	cmd.wlen = 1;
 	cmd.rlen = 10;
-	ret = si2157_cmd_execute(dev, &cmd);
+	ret = si2157_cmd_execute(client, &cmd);
 	if (ret)
 		goto err;
 
-	dev_info(&dev->i2c_adap->dev, "firmware version: %c.%c.%d\n",
+	dev_info(&client->dev, "firmware version: %c.%c.%d\n",
 			cmd.args[6], cmd.args[7], cmd.args[8]);
 
 	dev->fw_loaded = true;
 
 warm:
+	/* init statistics in order signal app which are supported */
+	c->strength.len = 1;
+	c->strength.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
+	/* start statistics polling */
+	schedule_delayed_work(&dev->stat_work, msecs_to_jiffies(1000));
+
 	dev->active = true;
 	return 0;
-
 err_release_firmware:
 	release_firmware(fw);
 err:
-	dev_dbg(&dev->i2c_adap->dev, "failed=%d\n", ret);
+	dev_dbg(&client->dev, "failed=%d\n", ret);
 	return ret;
 }
 
 static int si2157_sleep(struct dvb_frontend *fe)
 {
-	struct si2157_dev *dev = fe->tuner_priv;
+	struct i2c_client *client = fe->tuner_priv;
+	struct si2157_dev *dev = i2c_get_clientdata(client);
 	int ret;
 	struct si2157_cmd cmd;
 
-	dev_dbg(&dev->i2c_adap->dev, "\n");
+	dev_dbg(&client->dev, "\n");
 
 	dev->active = false;
 
+	/* stop statistics polling */
+	cancel_delayed_work_sync(&dev->stat_work);
+
 	/* standby */
 	memcpy(cmd.args, "\x16\x00", 2);
 	cmd.wlen = 2;
 	cmd.rlen = 1;
-	ret = si2157_cmd_execute(dev, &cmd);
+	ret = si2157_cmd_execute(client, &cmd);
 	if (ret)
 		goto err;
 
 	return 0;
 err:
-	dev_dbg(&dev->i2c_adap->dev, "failed=%d\n", ret);
+	dev_dbg(&client->dev, "failed=%d\n", ret);
 	return ret;
 }
 
 static int si2157_set_params(struct dvb_frontend *fe)
 {
-	struct si2157_dev *dev = fe->tuner_priv;
+	struct i2c_client *client = fe->tuner_priv;
+	struct si2157_dev *dev = i2c_get_clientdata(client);
 	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
 	int ret;
 	struct si2157_cmd cmd;
 	u8 bandwidth, delivery_system;
 	u32 if_frequency = 5000000;
 
-	dev_dbg(&dev->i2c_adap->dev,
+	dev_dbg(&client->dev,
 			"delivery_system=%d frequency=%u bandwidth_hz=%u\n",
 			c->delivery_system, c->frequency, c->bandwidth_hz);
 
@@ -331,17 +300,18 @@
 		cmd.args[5] = 0x01;
 	cmd.wlen = 6;
 	cmd.rlen = 4;
-	ret = si2157_cmd_execute(dev, &cmd);
+	ret = si2157_cmd_execute(client, &cmd);
 	if (ret)
 		goto err;
 
 	if (dev->chiptype == SI2157_CHIPTYPE_SI2146)
 		memcpy(cmd.args, "\x14\x00\x02\x07\x00\x01", 6);
 	else
-		memcpy(cmd.args, "\x14\x00\x02\x07\x01\x00", 6);
+		memcpy(cmd.args, "\x14\x00\x02\x07\x00\x00", 6);
+	cmd.args[4] = dev->if_port;
 	cmd.wlen = 6;
 	cmd.rlen = 4;
-	ret = si2157_cmd_execute(dev, &cmd);
+	ret = si2157_cmd_execute(client, &cmd);
 	if (ret)
 		goto err;
 
@@ -352,7 +322,7 @@
 		cmd.args[5] = ((if_frequency / 1000) >> 8) & 0xff;
 		cmd.wlen = 6;
 		cmd.rlen = 4;
-		ret = si2157_cmd_execute(dev, &cmd);
+		ret = si2157_cmd_execute(client, &cmd);
 		if (ret)
 			goto err;
 
@@ -367,99 +337,145 @@
 	cmd.args[7] = (c->frequency >> 24) & 0xff;
 	cmd.wlen = 8;
 	cmd.rlen = 1;
-	ret = si2157_cmd_execute(dev, &cmd);
+	ret = si2157_cmd_execute(client, &cmd);
 	if (ret)
 		goto err;
 
 	return 0;
 err:
-	dev_dbg(&dev->i2c_adap->dev, "failed=%d\n", ret);
+	dev_dbg(&client->dev, "failed=%d\n", ret);
 	return ret;
 }
 
 static int si2157_get_if_frequency(struct dvb_frontend *fe, u32 *frequency)
 {
-	struct si2157_dev *dev = fe->tuner_priv;
+	struct i2c_client *client = fe->tuner_priv;
+	struct si2157_dev *dev = i2c_get_clientdata(client);
 
 	*frequency = dev->if_frequency;
 	return 0;
 }
 
-static int si2157_release(struct dvb_frontend *fe)
-{
-	struct si2157_dev *dev = fe->tuner_priv;
-
-	dev_dbg(&dev->i2c_adap->dev, "%s:\n", __func__);
-
-	kfree(fe->tuner_priv);
-
-	return 0;
-}
-
 static const struct dvb_tuner_ops si2157_ops = {
 	.info = {
 		.name           = "Silicon Labs Si2146/2147/2148/2157/2158",
-		.frequency_min  = 55000000,
-		.frequency_max  = 862000000,
+		.frequency_min  = 42000000,
+		.frequency_max  = 870000000,
 	},
 
-	.release = si2157_release,
 	.init = si2157_init,
 	.sleep = si2157_sleep,
 	.set_params = si2157_set_params,
 	.get_if_frequency = si2157_get_if_frequency,
 };
 
-struct dvb_frontend *si2157_attach(struct dvb_frontend *fe,
-		struct i2c_adapter *i2c, const struct si2157_config *cfg)
+static void si2157_stat_work(struct work_struct *work)
 {
+	struct si2157_dev *dev = container_of(work, struct si2157_dev, stat_work.work);
+	struct dvb_frontend *fe = dev->fe;
+	struct i2c_client *client = fe->tuner_priv;
+	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+	struct si2157_cmd cmd;
+	int ret;
+
+	dev_dbg(&client->dev, "\n");
+
+	memcpy(cmd.args, "\x42\x00", 2);
+	cmd.wlen = 2;
+	cmd.rlen = 12;
+	ret = si2157_cmd_execute(client, &cmd);
+	if (ret)
+		goto err;
+
+	c->strength.stat[0].scale = FE_SCALE_DECIBEL;
+	c->strength.stat[0].svalue = ((s8) cmd.args[3]) * 1000;
+
+	schedule_delayed_work(&dev->stat_work, msecs_to_jiffies(2000));
+	return;
+err:
+	c->strength.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
+	dev_dbg(&client->dev, "failed=%d\n", ret);
+}
+
+static int si2157_probe(struct i2c_client *client,
+		const struct i2c_device_id *id)
+{
+	struct si2157_config *cfg = client->dev.platform_data;
+	struct dvb_frontend *fe = cfg->fe;
 	struct si2157_dev *dev;
 	struct si2157_cmd cmd;
 	int ret;
 
-	if (!cfg) {
-		dev_err(&i2c->dev, "no configuration submitted\n");
-		goto err;
-	}
-
 	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
 	if (!dev) {
 		ret = -ENOMEM;
-		dev_err(&i2c->dev, "%s: kzalloc() failed\n", KBUILD_MODNAME);
+		dev_err(&client->dev, "kzalloc() failed\n");
 		goto err;
 	}
 
-	dev->i2c_adap = i2c;
-	dev->i2c_addr = cfg->i2c_addr;
+	i2c_set_clientdata(client, dev);
+	dev->fe = cfg->fe;
 	dev->inversion = cfg->inversion;
+	dev->if_port = cfg->if_port;
 	dev->fw_loaded = false;
-	dev->chiptype = SI2157_CHIPTYPE_SI2157; /* (u8)id->driver_data; */
+	dev->chiptype = (u8)id->driver_data;
 	dev->if_frequency = 5000000; /* default value of property 0x0706 */
 	mutex_init(&dev->i2c_mutex);
+	INIT_DELAYED_WORK(&dev->stat_work, si2157_stat_work);
 
 	/* check if the tuner is there */
 	cmd.wlen = 0;
 	cmd.rlen = 1;
-	ret = si2157_cmd_execute(dev, &cmd);
+	ret = si2157_cmd_execute(client, &cmd);
 	if (ret)
 		goto err_kfree;
 
 	memcpy(&fe->ops.tuner_ops, &si2157_ops, sizeof(struct dvb_tuner_ops));
-	fe->tuner_priv = dev;
+	fe->tuner_priv = client;
 
-	dev_info(&i2c->dev, "%s: Silicon Labs %s successfully attached\n",
-			KBUILD_MODNAME, dev->chiptype == SI2157_CHIPTYPE_SI2146 ?
+	dev_info(&client->dev, "Silicon Labs %s successfully attached\n",
+			dev->chiptype == SI2157_CHIPTYPE_SI2146 ?
 			"Si2146" : "Si2147/2148/2157/2158");
 
-	return fe;
+	return 0;
 
 err_kfree:
 	kfree(dev);
 err:
-	dev_dbg(&i2c->dev, "failed=%d\n", ret);
-	return NULL;
+	dev_dbg(&client->dev, "failed=%d\n", ret);
+	return ret;
 }
-EXPORT_SYMBOL(si2157_attach);
+
+static int si2157_remove(struct i2c_client *client)
+{
+	struct si2157_dev *dev = i2c_get_clientdata(client);
+	struct dvb_frontend *fe = dev->fe;
+
+	memset(&fe->ops.tuner_ops, 0, sizeof(fe->ops.tuner_ops));
+	fe->tuner_priv = NULL;
+	kfree(dev);
+
+	return 0;
+}
+
+static const struct i2c_device_id si2157_id_table[] = {
+	{"si2157", SI2157_CHIPTYPE_SI2157},
+	{"si2146", SI2157_CHIPTYPE_SI2146},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, si2157_id_table);
+
+static struct i2c_driver si2157_driver = {
+	.driver = {
+		.owner	= THIS_MODULE,
+		.name	= "si2157",
+	},
+	.probe		= si2157_probe,
+	.remove		= si2157_remove,
+	.id_table	= si2157_id_table,
+};
+
+module_i2c_driver(si2157_driver);
 
 MODULE_DESCRIPTION("Silicon Labs Si2146/2147/2148/2157/2158 silicon tuner driver");
 MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
diff --git a/drivers/media/tuners/si2157.h b/drivers/media/tuners/si2157.h
index 1755ba3..4db97ab 100644
--- a/drivers/media/tuners/si2157.h
+++ b/drivers/media/tuners/si2157.h
@@ -20,28 +20,26 @@
 #include <linux/kconfig.h>
 #include "dvb_frontend.h"
 
+/*
+ * I2C address
+ * 0x60
+ */
 struct si2157_config {
 	/*
-	 * I2C address
+	 * frontend
 	 */
-	u8 i2c_addr;
+	struct dvb_frontend *fe;
 
 	/*
 	 * Spectral Inversion
 	 */
 	bool inversion;
-};
 
-#if IS_ENABLED(CONFIG_MEDIA_TUNER_SI2157)
-extern struct dvb_frontend *si2157_attach(struct dvb_frontend *fe,
-		struct i2c_adapter *i2c, const struct si2157_config *cfg);
-#else
-static inline struct dvb_frontend *si2157_attach(struct dvb_frontend *fe,
-		struct i2c_adapter *i2c, const struct si2157_config *cfg)
-{
-	pr_warn("%s: driver disabled by Kconfig\n", __func__);
-	return NULL;
-}
-#endif
+	/*
+	 * Port selection
+	 * Select the RF interface to use (pins 9+11 or 12+13)
+	 */
+	u8 if_port;
+};
 
 #endif
diff --git a/drivers/media/tuners/si2157_priv.h b/drivers/media/tuners/si2157_priv.h
index 7830ccd..ecc463d 100644
--- a/drivers/media/tuners/si2157_priv.h
+++ b/drivers/media/tuners/si2157_priv.h
@@ -23,13 +23,14 @@
 /* state struct */
 struct si2157_dev {
 	struct mutex i2c_mutex;
-	struct i2c_adapter *i2c_adap;
-	u8 i2c_addr;
+	struct dvb_frontend *fe;
 	bool active;
 	bool fw_loaded;
 	bool inversion;
 	u8 chiptype;
+	u8 if_port;
 	u32 if_frequency;
+	struct delayed_work stat_work;
 };
 
 #define SI2157_CHIPTYPE_SI2157 0
diff --git a/drivers/media/usb/cx231xx/cx231xx-dvb.c b/drivers/media/usb/cx231xx/cx231xx-dvb.c
index 66340b5..773306c 100644
--- a/drivers/media/usb/cx231xx/cx231xx-dvb.c
+++ b/drivers/media/usb/cx231xx/cx231xx-dvb.c
@@ -69,6 +69,7 @@
 	struct dmx_frontend fe_hw;
 	struct dmx_frontend fe_mem;
 	struct dvb_net net;
+	struct i2c_client *i2c_client_tuner;
 	int    power_on;
 };
 
@@ -183,11 +184,6 @@
 	.xtalMHz            = 25,
 };
 
-static struct si2157_config si2157_config = {
-	.i2c_addr           = 0x60,
-	.inversion			= true,
-};
-
 static inline void print_err_status(struct cx231xx *dev, int packet, int status)
 {
 	char *errmsg = "Unknown";
@@ -608,11 +604,18 @@
 
 static void unregister_dvb(struct cx231xx_dvb *dvb)
 {
+	struct i2c_client *client;
 	dvb_net_release(&dvb->net);
 	dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem);
 	dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw);
 	dvb_dmxdev_release(&dvb->dmxdev);
 	dvb_dmx_release(&dvb->demux);
+	client = dvb->i2c_client_tuner;
+	/* remove I2C tuner */
+	if (client) {
+		module_put(client->dev.driver->owner);
+		i2c_unregister_device(client);
+	}
 	dvb_unregister_frontend(dvb->frontend);
 	dvb_frontend_detach(dvb->frontend);
 	dvb_unregister_adapter(&dvb->adapter);
@@ -769,7 +772,11 @@
 			   &hcw_tda18271_config);
 		break;
 
-	case CX231XX_BOARD_HAUPPAUGE_955Q:
+	case CX231XX_BOARD_HAUPPAUGE_955Q: {
+
+		struct i2c_client *client;
+		struct i2c_board_info info;
+		struct si2157_config si2157_config;
 
 		printk(KERN_INFO "%s: looking for tuner / demod on i2c bus: %d\n",
 		       __func__, i2c_adapter_id(&dev->i2c_bus[dev->board.tuner_i2c_master].i2c_adap));
@@ -788,11 +795,38 @@
 		/* define general-purpose callback pointer */
 		dvb->frontend->callback = cx231xx_tuner_callback;
 
-		dvb_attach(si2157_attach, dev->dvb->frontend,
-			   &dev->i2c_bus[dev->board.tuner_i2c_master].i2c_adap,
-			   &si2157_config);
-		break;
+		/* attach tuner */
+		memset(&info, 0, sizeof(info));
+		memset(&si2157_config, 0, sizeof(si2157_config));
+		si2157_config.fe = dev->dvb->frontend;
+		si2157_config.if_port = 1;
+		si2157_config.inversion = true;
+		strlcpy(info.type, "si2157", I2C_NAME_SIZE);
+		info.addr = 0x60;
+		info.platform_data = &si2157_config;
+		request_module("si2157");
 
+		client = i2c_new_device(
+			&dev->i2c_bus[dev->board.tuner_i2c_master].i2c_adap,
+			&info);
+		if (client == NULL || client->dev.driver == NULL) {
+			dvb_frontend_detach(dev->dvb->frontend);
+			result = -ENODEV;
+			goto out_free;
+		}
+
+		if (!try_module_get(client->dev.driver->owner)) {
+			i2c_unregister_device(client);
+			dvb_frontend_detach(dev->dvb->frontend);
+			result = -ENODEV;
+			goto out_free;
+		}
+
+		dev->cx231xx_reset_analog_tuner = NULL;
+
+		dev->dvb->i2c_client_tuner = client;
+		break;
+	}
 	case CX231XX_BOARD_PV_PLAYTV_USB_HYBRID:
 	case CX231XX_BOARD_KWORLD_UB430_USB_HYBRID:
 
diff --git a/drivers/media/usb/em28xx/Kconfig b/drivers/media/usb/em28xx/Kconfig
index ca5ee6a..45ff31e 100644
--- a/drivers/media/usb/em28xx/Kconfig
+++ b/drivers/media/usb/em28xx/Kconfig
@@ -35,6 +35,7 @@
 	depends on VIDEO_EM28XX && DVB_CORE
 	select DVB_LGDT330X if MEDIA_SUBDRV_AUTOSELECT
 	select DVB_LGDT3305 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_LGDT3306A if MEDIA_SUBDRV_AUTOSELECT
 	select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT
 	select DVB_TDA10023 if MEDIA_SUBDRV_AUTOSELECT
 	select DVB_S921 if MEDIA_SUBDRV_AUTOSELECT
@@ -47,8 +48,10 @@
 	select DVB_MT352 if MEDIA_SUBDRV_AUTOSELECT
 	select DVB_S5H1409 if MEDIA_SUBDRV_AUTOSELECT
 	select DVB_MB86A20S if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_SI2168 if MEDIA_SUBDRV_AUTOSELECT
 	select MEDIA_TUNER_QT1010 if MEDIA_SUBDRV_AUTOSELECT
 	select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_SI2157 if MEDIA_SUBDRV_AUTOSELECT
 	---help---
 	  This adds support for DVB cards based on the
 	  Empiatech em28xx chips.
diff --git a/drivers/media/usb/em28xx/em28xx-cards.c b/drivers/media/usb/em28xx/em28xx-cards.c
index 83bfbe4..6804b11 100644
--- a/drivers/media/usb/em28xx/em28xx-cards.c
+++ b/drivers/media/usb/em28xx/em28xx-cards.c
@@ -411,6 +411,29 @@
 	{             -1,   -1,   -1,  -1},
 };
 
+/* 2040:0265 Hauppauge WinTV-dualHD DVB
+ * reg 0x80/0x84:
+ * GPIO_0: Yellow LED tuner 1, 0=on, 1=off
+ * GPIO_1: Green LED tuner 1, 0=on, 1=off
+ * GPIO_2: Yellow LED tuner 2, 0=on, 1=off
+ * GPIO_3: Green LED tuner 2, 0=on, 1=off
+ * GPIO_5: Reset #2, 0=active
+ * GPIO_6: Reset #1, 0=active
+ */
+static struct em28xx_reg_seq hauppauge_dualhd_dvb[] = {
+	{EM2874_R80_GPIO,         0xff, 0xff,      0},
+	{0x0d,                    0xff, 0xff,    200},
+	{0x50,                    0x04, 0xff,    300},
+	{EM2874_R80_GPIO,         0xbf, 0xff,    100}, /* demod 1 reset */
+	{EM2874_R80_GPIO,         0xff, 0xff,    100},
+	{EM2874_R80_GPIO,         0xdf, 0xff,    100}, /* demod 2 reset */
+	{EM2874_R80_GPIO,         0xff, 0xff,    100},
+	{EM2874_R5F_TS_ENABLE,    0x44, 0xff,     50},
+	{EM2874_R5D_TS1_PKT_SIZE, 0x05, 0xff,     50},
+	{EM2874_R5E_TS2_PKT_SIZE, 0x05, 0xff,     50},
+	{                     -1,   -1,   -1,     -1},
+};
+
 /*
  *  Board definitions
  */
@@ -2017,6 +2040,30 @@
 		.i2c_speed    = EM28XX_I2C_CLK_WAIT_ENABLE |
 				EM28XX_I2C_FREQ_400_KHZ,
 	},
+	/* 2040:0265 Hauppauge WinTV-dualHD (DVB version).
+	 * Empia EM28274, 2x Silicon Labs Si2168, 2x Silicon Labs Si2157 */
+	[EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB] = {
+		.name          = "Hauppauge WinTV-dualHD DVB",
+		.def_i2c_bus   = 1,
+		.i2c_speed     = EM28XX_I2C_CLK_WAIT_ENABLE | EM28XX_I2C_FREQ_400_KHZ,
+		.tuner_type    = TUNER_ABSENT,
+		.tuner_gpio    = hauppauge_dualhd_dvb,
+		.has_dvb       = 1,
+		.has_dual_ts   = 1,
+		.ir_codes      = RC_MAP_HAUPPAUGE,
+	},
+	/* 2040:026D Hauppauge WinTV-dualHD (DVB version).
+	 * Empia EM28274, 2x LGDT3306A, 2x Silicon Labs Si2157 */
+	[EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_ATSC] = {
+		.name          = "Hauppauge WinTV-dualHD ATSC",
+		.def_i2c_bus   = 1,
+		.i2c_speed     = EM28XX_I2C_CLK_WAIT_ENABLE | EM28XX_I2C_FREQ_400_KHZ,
+		.tuner_type    = TUNER_ABSENT,
+		.tuner_gpio    = hauppauge_dualhd_dvb,
+		.has_dvb       = 1,
+		.has_dual_ts   = 1,
+		.ir_codes      = RC_MAP_HAUPPAUGE,
+	},
 };
 const unsigned int em28xx_bcount = ARRAY_SIZE(em28xx_boards);
 
@@ -2178,6 +2225,14 @@
 			.driver_info = EM2884_BOARD_PCTV_510E },
 	{ USB_DEVICE(0x2013, 0x0251),
 			.driver_info = EM2884_BOARD_PCTV_520E },
+	{ USB_DEVICE(0x2040, 0x0265), /* ISOC */
+			.driver_info = EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB },
+	{ USB_DEVICE(0x2040, 0x8265), /* BULK */
+			.driver_info = EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB },
+	{ USB_DEVICE(0x2040, 0x026D), /* ISOC */
+			.driver_info = EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_ATSC },
+	{ USB_DEVICE(0x2040, 0x826D), /* BULK */
+			.driver_info = EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_ATSC },
 	{ },
 };
 MODULE_DEVICE_TABLE(usb, em28xx_id_table);
@@ -2638,6 +2693,8 @@
 	case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_850:
 	case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950:
 	case EM2884_BOARD_HAUPPAUGE_WINTV_HVR_930C:
+	case EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB:
+	case EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_ATSC:
 	{
 		struct tveeprom tv;
 
@@ -2847,7 +2904,8 @@
 
 	v4l2_device_unregister(&dev->v4l2_dev);
 
-	usb_put_dev(dev->udev);
+	if (dev->ts == PRIMARY_TS)
+		usb_put_dev(dev->udev);
 
 	/* Mark device as unused */
 	clear_bit(dev->devno, &em28xx_devused);
@@ -3115,6 +3173,21 @@
 /* high bandwidth multiplier, as encoded in highspeed endpoint descriptors */
 #define hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03))
 
+int em28xx_duplicate_dev(struct em28xx *dev)
+{
+	struct em28xx *sec_dev = NULL;
+	int nr;
+	sec_dev = kzalloc(sizeof(struct em28xx), GFP_KERNEL);
+	memcpy(sec_dev, dev, sizeof(struct em28xx));
+	nr = find_first_zero_bit(&em28xx_devused, EM28XX_MAXBOARDS);
+	test_and_set_bit(nr, &em28xx_devused);
+	sec_dev->devno = nr;
+	snprintf(sec_dev->name, 28, "em28xx #%d", nr);
+	dev->dev_next = sec_dev;
+	sec_dev->dev_next = NULL;
+	return 0;
+}
+
 /*
  * em28xx_usb_probe()
  * checks for supported devices
@@ -3125,7 +3198,7 @@
 	struct usb_device *udev;
 	struct em28xx *dev = NULL;
 	int retval;
-	bool has_audio = false, has_video = false, has_dvb = false;
+	bool has_audio = false, has_video = false, has_dvb = false, has_dvb_ts2 = false;
 	int i, nr, try_bulk;
 	const int ifnum = interface->altsetting[0].desc.bInterfaceNumber;
 	char *speed;
@@ -3232,6 +3305,19 @@
 						}
 					}
 					break;
+				case 0x85:
+					if (usb_endpoint_xfer_isoc(e)) {
+						if (size > dev->dvb_max_pkt_size_isoc_ts2) {
+							has_dvb_ts2 = true; /* see NOTE (~) */
+							dev->dvb_ep_isoc_ts2 = e->bEndpointAddress;
+							dev->dvb_max_pkt_size_isoc_ts2 = size;
+							dev->dvb_alt_isoc = i;
+						}
+					} else {
+						has_dvb_ts2 = true;
+						dev->dvb_ep_bulk_ts2 = e->bEndpointAddress;
+					}
+					break;
 				}
 			}
 			/* NOTE:
@@ -3313,6 +3399,8 @@
 	dev->is_audio_only = has_audio && !(has_video || has_dvb);
 	dev->has_alsa_audio = has_audio;
 	dev->audio_ifnum = ifnum;
+	dev->ts = PRIMARY_TS;
+	dev->dev_next = NULL;
 
 	/* Checks if audio is provided by some interface */
 	for (i = 0; i < udev->config->desc.bNumInterfaces; i++) {
@@ -3382,26 +3470,49 @@
 
 		em28xx_info("dvb set to %s mode.\n",
 			    dev->dvb_xfer_bulk ? "bulk" : "isoc");
+	}
 
-		/* pre-allocate DVB usb transfer buffers */
-		if (dev->dvb_xfer_bulk) {
-			retval = em28xx_alloc_urbs(dev, EM28XX_DIGITAL_MODE,
-					    dev->dvb_xfer_bulk,
-					    EM28XX_DVB_NUM_BUFS,
-					    512,
-					    EM28XX_DVB_BULK_PACKET_MULTIPLIER);
+	if (dev->board.has_dual_ts) {
+		em28xx_duplicate_dev(dev);
+		dev->dev_next->ts = SECONDARY_TS;
+		dev->dev_next->alt = -1;
+		dev->dev_next->is_audio_only = has_audio && !(has_video || has_dvb);
+		dev->dev_next->has_video = false;
+		dev->dev_next->ifnum = ifnum;
+		dev->dev_next->model = id->driver_info;
+
+		mutex_init(&dev->dev_next->lock);
+		mutex_lock(&dev->dev_next->lock);
+		retval = em28xx_init_dev(dev->dev_next, udev, interface, dev->dev_next->devno);
+		if (retval)
+			goto err_free;
+
+		if (usb_xfer_mode < 0) {
+			if (dev->dev_next->board.is_webcam)
+				try_bulk = 1;
+			else
+				try_bulk = 0;
 		} else {
-			retval = em28xx_alloc_urbs(dev, EM28XX_DIGITAL_MODE,
-					    dev->dvb_xfer_bulk,
-					    EM28XX_DVB_NUM_BUFS,
-					    dev->dvb_max_pkt_size_isoc,
-					    EM28XX_DVB_NUM_ISOC_PACKETS);
+			try_bulk = usb_xfer_mode > 0;
 		}
-		if (retval) {
-			printk(DRIVER_NAME
-			       ": Failed to pre-allocate USB transfer buffers for DVB.\n");
-			goto unlock_and_free;
+
+		/* Select USB transfer types to use */
+		if (has_dvb) {
+			if (!dev->dvb_ep_isoc_ts2 || (try_bulk && dev->dvb_ep_bulk_ts2))
+				dev->dev_next->dvb_xfer_bulk = 1;
+				em28xx_info("dvb ts2 set to %s mode.\n",
+					    dev->dev_next->dvb_xfer_bulk ? "bulk" : "isoc");
 		}
+
+		dev->dev_next->dvb_ep_isoc = dev->dvb_ep_isoc_ts2;
+		dev->dev_next->dvb_max_pkt_size_isoc = dev->dvb_max_pkt_size_isoc_ts2;
+		dev->dev_next->dvb_alt_isoc = dev->dvb_alt_isoc;
+
+		/* Configure hardware to support TS2*/
+		em28xx_write_reg(dev, 0x0b, 0x96);
+		mdelay(100);
+		em28xx_write_reg(dev, 0x0b, 0x82);
+		mdelay(100);
 	}
 
 	request_modules(dev);
@@ -3410,6 +3521,8 @@
 	   open the device before fully initializing it
 	 */
 	mutex_unlock(&dev->lock);
+	if (dev->dev_next != NULL)
+		mutex_unlock(&dev->dev_next->lock);
 
 	return 0;
 
@@ -3422,6 +3535,8 @@
 
 err:
 	clear_bit(nr, &em28xx_devused);
+	if (dev->dev_next != NULL)
+		clear_bit(dev->dev_next->devno, &em28xx_devused);
 
 err_no_slot:
 	usb_put_dev(udev);
@@ -3443,6 +3558,14 @@
 	if (!dev)
 		return;
 
+	if (dev->dev_next != NULL) {
+		dev->dev_next->disconnected = 1;
+
+		em28xx_info("Disconnecting %s\n", dev->dev_next->name);
+
+		flush_request_modules(dev->dev_next);
+	}
+
 	dev->disconnected = 1;
 
 	if (dev->is_audio_only) {
@@ -3471,11 +3594,20 @@
 	em28xx_close_extension(dev);
 	/* NOTE: must be called BEFORE the resources are released */
 
+	if ((!dev->users) && (dev->dev_next != NULL))
+		em28xx_release_resources(dev->dev_next);
+
 	if (!dev->users)
 		em28xx_release_resources(dev);
 
 	mutex_unlock(&dev->lock);
 
+	if ((!dev->users) && (dev->dev_next != NULL)) {
+		kfree(dev->dev_next->alt_max_pkt_size_isoc);
+		kfree(dev->dev_next);
+		dev->dev_next = NULL;
+	}
+
 	if (!dev->users) {
 		kfree(dev->alt_max_pkt_size_isoc);
 		kfree(dev);
diff --git a/drivers/media/usb/em28xx/em28xx-core.c b/drivers/media/usb/em28xx/em28xx-core.c
index a802128..e9efc92 100644
--- a/drivers/media/usb/em28xx/em28xx-core.c
+++ b/drivers/media/usb/em28xx/em28xx-core.c
@@ -249,6 +249,24 @@
 EXPORT_SYMBOL_GPL(em28xx_write_reg_bits);
 
 /*
+ * em28xx_toggle_reg_bits()
+ * toggles/inverts the bits (specified by bitmask) of a register
+ */
+static int em28xx_toggle_reg_bits(struct em28xx *dev, u16 reg, u8 bitmask)
+{
+	int oldval;
+	u8 newval;
+
+	oldval = em28xx_read_reg(dev, reg);
+	if (oldval < 0)
+		return oldval;
+
+	newval = (~oldval & bitmask) | (oldval & ~bitmask);
+
+	return em28xx_write_reg(dev, reg, newval);
+}
+
+/*
  * em28xx_is_ac97_ready()
  * Checks if ac97 is ready
  */
@@ -631,18 +649,32 @@
 	    dev->chip_id == CHIP_ID_EM2884 ||
 	    dev->chip_id == CHIP_ID_EM28174) {
 		/* The Transport Stream Enable Register moved in em2874 */
-		if (!start) {
+		if (dev->board.has_dual_ts) {
+			if (start) {
+				rc = em28xx_write_reg_bits(dev, EM2874_R5F_TS_ENABLE,
+							   (EM2874_TS1_CAPTURE_ENABLE | EM2874_TS2_CAPTURE_ENABLE),
+							   (EM2874_TS1_CAPTURE_ENABLE | EM2874_TS2_CAPTURE_ENABLE));
+			} else {
+				if (dev->ts == PRIMARY_TS)
+					rc = em28xx_toggle_reg_bits(dev, EM2874_R5F_TS_ENABLE, EM2874_TS1_CAPTURE_ENABLE);
+				else
+					rc = em28xx_toggle_reg_bits(dev, EM2874_R5F_TS_ENABLE, EM2874_TS2_CAPTURE_ENABLE);
+			}
+			return rc;
+		} else {
+			if (!start) {
+				rc = em28xx_write_reg_bits(dev, EM2874_R5F_TS_ENABLE,
+							   0x00,
+							   EM2874_TS1_CAPTURE_ENABLE);
+				return rc;
+			}
+
+			/* Enable Transport Stream */
 			rc = em28xx_write_reg_bits(dev, EM2874_R5F_TS_ENABLE,
-						   0x00,
+						   EM2874_TS1_CAPTURE_ENABLE,
 						   EM2874_TS1_CAPTURE_ENABLE);
 			return rc;
 		}
-
-		/* Enable Transport Stream */
-		rc = em28xx_write_reg_bits(dev, EM2874_R5F_TS_ENABLE,
-					   EM2874_TS1_CAPTURE_ENABLE,
-					   EM2874_TS1_CAPTURE_ENABLE);
-		return rc;
 	}
 
 
@@ -1293,6 +1325,8 @@
 	list_add_tail(&ops->next, &em28xx_extension_devlist);
 	list_for_each_entry(dev, &em28xx_devlist, devlist) {
 		ops->init(dev);
+		if (dev->dev_next != NULL)
+			ops->init(dev->dev_next);
 	}
 	mutex_unlock(&em28xx_devlist_mutex);
 	printk(KERN_INFO "Em28xx: Initialized (%s) extension\n", ops->name);
@@ -1307,6 +1341,8 @@
 	mutex_lock(&em28xx_devlist_mutex);
 	list_for_each_entry(dev, &em28xx_devlist, devlist) {
 		ops->fini(dev);
+		if (dev->dev_next != NULL)
+			ops->fini(dev->dev_next);
 	}
 	list_del(&ops->next);
 	mutex_unlock(&em28xx_devlist_mutex);
@@ -1323,6 +1359,8 @@
 	list_for_each_entry(ops, &em28xx_extension_devlist, next) {
 		if (ops->init)
 			ops->init(dev);
+		if (dev->dev_next != NULL)
+			ops->init(dev->dev_next);
 	}
 	mutex_unlock(&em28xx_devlist_mutex);
 }
@@ -1335,6 +1373,8 @@
 	list_for_each_entry(ops, &em28xx_extension_devlist, next) {
 		if (ops->fini)
 			ops->fini(dev);
+		if (dev->dev_next != NULL)
+			ops->fini(dev->dev_next);
 	}
 	list_del(&dev->devlist);
 	mutex_unlock(&em28xx_devlist_mutex);
diff --git a/drivers/media/usb/em28xx/em28xx-dvb.c b/drivers/media/usb/em28xx/em28xx-dvb.c
index b22f8fe..c902f9d 100644
--- a/drivers/media/usb/em28xx/em28xx-dvb.c
+++ b/drivers/media/usb/em28xx/em28xx-dvb.c
@@ -36,6 +36,7 @@
 
 #include "lgdt330x.h"
 #include "lgdt3305.h"
+#include "lgdt3306a.h"
 #include "zl10353.h"
 #include "s5h1409.h"
 #include "mt352.h"
@@ -51,6 +52,8 @@
 #include "a8293.h"
 #include "qt1010.h"
 #include "mb86a20s.h"
+#include "si2168.h"
+#include "si2157.h"
 
 MODULE_DESCRIPTION("driver for em28xx based DVB cards");
 MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@infradead.org>");
@@ -87,6 +90,9 @@
 	struct semaphore      pll_mutex;
 	bool			dont_attach_fe1;
 	int			lna_gpio;
+	struct i2c_client	*i2c_client_demod;
+	struct i2c_client	*i2c_client_tuner;
+	struct i2c_client	*i2c_client_sec;
 };
 
 
@@ -198,7 +204,8 @@
 		dvb_alt = dev->dvb_alt_isoc;
 	}
 
-	usb_set_interface(dev->udev, 0, dvb_alt);
+	/* moved to em28xx_dvb_init*/
+	/* usb_set_interface(dev->udev, 0, dvb_alt);*/
 	rc = em28xx_set_mode(dev, EM28XX_DIGITAL_MODE);
 	if (rc < 0)
 		return rc;
@@ -956,7 +963,7 @@
 
 static int em28xx_dvb_init(struct em28xx *dev)
 {
-	int result = 0, mfe_shared = 0;
+	int result = 0, mfe_shared = 0, dvb_alt = 0;
 	struct em28xx_dvb *dvb;
 
 	if (!dev->board.has_dvb) {
@@ -974,6 +981,26 @@
 	dev->dvb = dvb;
 	dvb->fe[0] = dvb->fe[1] = NULL;
 
+	/* pre-allocate DVB usb transfer buffers */
+	if (dev->dvb_xfer_bulk) {
+		result = em28xx_alloc_urbs(dev, EM28XX_DIGITAL_MODE,
+				    dev->dvb_xfer_bulk,
+				    EM28XX_DVB_NUM_BUFS,
+				    512,
+				    EM28XX_DVB_BULK_PACKET_MULTIPLIER);
+	} else {
+		result = em28xx_alloc_urbs(dev, EM28XX_DIGITAL_MODE,
+				    dev->dvb_xfer_bulk,
+				    EM28XX_DVB_NUM_BUFS,
+				    dev->dvb_max_pkt_size_isoc,
+				    EM28XX_DVB_NUM_ISOC_PACKETS);
+	}
+	if (result) {
+		printk(KERN_INFO "em28xx_dvb: Failed to pre-allocate USB transfer buffers for DVB.\n");
+		kfree(dvb);
+		dev->dvb = NULL;
+		return result;
+	}
 	mutex_lock(&dev->lock);
 	em28xx_set_mode(dev, EM28XX_DIGITAL_MODE);
 	/* init frontend */
@@ -1296,6 +1323,153 @@
 			goto out_free;
 		}
 		break;
+	case EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB:
+		{
+			struct i2c_adapter *adapter;
+			struct i2c_client *client;
+			struct i2c_board_info info;
+			struct si2168_config si2168_config;
+			struct si2157_config si2157_config;
+
+			/* attach demod */
+			memset(&si2168_config, 0, sizeof(si2168_config));
+			si2168_config.i2c_adapter = &adapter;
+			si2168_config.fe = &dvb->fe[0];
+			si2168_config.ts_mode = SI2168_TS_SERIAL;
+			memset(&info, 0, sizeof(info));
+			strlcpy(info.type, "si2168", I2C_NAME_SIZE);
+			if (dev->ts == PRIMARY_TS)
+				info.addr = 0x64;
+			else
+				info.addr = 0x67;
+			info.platform_data = &si2168_config;
+			request_module(info.type);
+			client = i2c_new_device(&dev->i2c_adap[dev->def_i2c_bus], &info);
+			if (client == NULL || client->dev.driver == NULL) {
+				result = -ENODEV;
+				goto out_free;
+			}
+
+			if (!try_module_get(client->dev.driver->owner)) {
+				i2c_unregister_device(client);
+				result = -ENODEV;
+				goto out_free;
+			}
+
+			dvb->i2c_client_demod = client;
+			em28xx_info("demod si2168 successfully loaded\n");
+
+			/* attach tuner */
+			memset(&si2157_config, 0, sizeof(si2157_config));
+			si2157_config.fe = dvb->fe[0];
+			si2157_config.if_port = 1;
+			memset(&info, 0, sizeof(info));
+			strlcpy(info.type, "si2157", I2C_NAME_SIZE);
+			if (dev->ts == PRIMARY_TS)
+				info.addr = 0x60;
+			else
+				info.addr = 0x63;
+			info.platform_data = &si2157_config;
+			request_module(info.type);
+			client = i2c_new_device(adapter, &info);
+			if (client == NULL || client->dev.driver == NULL) {
+				module_put(dvb->i2c_client_demod->dev.driver->owner);
+				i2c_unregister_device(dvb->i2c_client_demod);
+				result = -ENODEV;
+				goto out_free;
+			}
+
+			if (!try_module_get(client->dev.driver->owner)) {
+				i2c_unregister_device(client);
+				module_put(dvb->i2c_client_demod->dev.driver->owner);
+				i2c_unregister_device(dvb->i2c_client_demod);
+				result = -ENODEV;
+				goto out_free;
+			}
+
+			dvb->i2c_client_tuner = client;
+		}
+		break;
+	case EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_ATSC:
+		{
+			struct i2c_adapter *adapter;
+			struct i2c_client *client;
+			struct i2c_board_info info;
+			struct lgdt3306a_config lgdt3306a_config;
+			struct si2157_config si2157_config;
+
+			/* attach demod */
+			memset(&lgdt3306a_config, 0, sizeof(lgdt3306a_config));
+			lgdt3306a_config.i2c_adapter = &adapter;
+			lgdt3306a_config.fe = &dvb->fe[0];
+			if (dev->ts == PRIMARY_TS)
+				lgdt3306a_config.i2c_addr = 0x59;
+			else
+				lgdt3306a_config.i2c_addr = 0x0e;
+			lgdt3306a_config.qam_if_khz         = 4000;
+			lgdt3306a_config.vsb_if_khz         = 3250;
+			lgdt3306a_config.deny_i2c_rptr      = 1;
+			lgdt3306a_config.spectral_inversion = 1;
+			lgdt3306a_config.mpeg_mode          = LGDT3306A_MPEG_SERIAL;
+			lgdt3306a_config.tpclk_edge         = LGDT3306A_TPCLK_RISING_EDGE;
+			lgdt3306a_config.tpvalid_polarity   = LGDT3306A_TP_VALID_HIGH;
+			lgdt3306a_config.xtalMHz            = 25;
+			lgdt3306a_config.has_tuner_i2c_adapter = 1;
+			memset(&info, 0, sizeof(info));
+			strlcpy(info.type, "lgdt3306a", I2C_NAME_SIZE);
+			if (dev->ts == PRIMARY_TS)
+				info.addr = 0x59;
+			else
+				info.addr = 0x0e;
+			info.platform_data = &lgdt3306a_config;
+			request_module(info.type);
+			client = i2c_new_device(&dev->i2c_adap[dev->def_i2c_bus], &info);
+			if (client == NULL || client->dev.driver == NULL) {
+				result = -ENODEV;
+				goto out_free;
+			}
+
+			if (!try_module_get(client->dev.driver->owner)) {
+				i2c_unregister_device(client);
+				result = -ENODEV;
+				goto out_free;
+			}
+
+			dvb->fe[0]->ops.i2c_gate_ctrl = NULL;
+			dvb->i2c_client_demod = client;
+
+			/* attach tuner */
+			memset(&si2157_config, 0, sizeof(si2157_config));
+			si2157_config.fe = dvb->fe[0];
+			si2157_config.if_port = 1;
+			si2157_config.inversion = true;
+			memset(&info, 0, sizeof(info));
+			strlcpy(info.type, "si2157", I2C_NAME_SIZE);
+			if (dev->ts == PRIMARY_TS)
+				info.addr = 0x60;
+			else
+				info.addr = 0x62;
+			info.platform_data = &si2157_config;
+			request_module(info.type);
+			client = i2c_new_device(adapter, &info);
+			if (client == NULL || client->dev.driver == NULL) {
+				module_put(dvb->i2c_client_demod->dev.driver->owner);
+				i2c_unregister_device(dvb->i2c_client_demod);
+				result = -ENODEV;
+				goto out_free;
+			}
+
+			if (!try_module_get(client->dev.driver->owner)) {
+				i2c_unregister_device(client);
+				module_put(dvb->i2c_client_demod->dev.driver->owner);
+				i2c_unregister_device(dvb->i2c_client_demod);
+				result = -ENODEV;
+				goto out_free;
+			}
+
+			dvb->i2c_client_tuner = client;
+		}
+		break;
 	default:
 		em28xx_errdev("/2: The frontend of your DVB/ATSC card"
 				" isn't supported yet\n");
@@ -1320,6 +1494,12 @@
 	/* MFE lock */
 	dvb->adapter.mfe_shared = mfe_shared;
 
+	if (dev->dvb_xfer_bulk) {
+		dvb_alt = 0;
+	} else { /* isoc */
+		dvb_alt = dev->dvb_alt_isoc;
+	}
+	usb_set_interface(dev->udev, dev->ifnum, dvb_alt);
 	em28xx_info("Successfully loaded em28xx-dvb\n");
 ret:
 	em28xx_set_mode(dev, EM28XX_SUSPEND);
@@ -1341,6 +1521,8 @@
 
 static int em28xx_dvb_fini(struct em28xx *dev)
 {
+	struct i2c_client *client;
+
 	if (!dev->board.has_dvb) {
 		/* This device does not support the extension */
 		return 0;
@@ -1358,6 +1540,27 @@
 				prevent_sleep(&dvb->fe[1]->ops);
 		}
 
+		/* remove I2C SEC */
+		client = dvb->i2c_client_sec;
+		if (client) {
+			module_put(client->dev.driver->owner);
+			i2c_unregister_device(client);
+		}
+
+		/* remove I2C tuner */
+		client = dvb->i2c_client_tuner;
+		if (client) {
+			module_put(client->dev.driver->owner);
+			i2c_unregister_device(client);
+		}
+
+		/* remove I2C demod */
+		client = dvb->i2c_client_demod;
+		if (client) {
+			module_put(client->dev.driver->owner);
+			i2c_unregister_device(client);
+		}
+
 		em28xx_unregister_dvb(dvb);
 		kfree(dvb);
 		dev->dvb = NULL;
diff --git a/drivers/media/usb/em28xx/em28xx-reg.h b/drivers/media/usb/em28xx/em28xx-reg.h
index 622871d..18e715d 100644
--- a/drivers/media/usb/em28xx/em28xx-reg.h
+++ b/drivers/media/usb/em28xx/em28xx-reg.h
@@ -192,6 +192,18 @@
 /* em2874 registers */
 #define EM2874_R50_IR_CONFIG    0x50
 #define EM2874_R51_IR           0x51
+#define EM2874_R5D_TS1_PKT_SIZE 0x5d
+#define EM2874_R5E_TS2_PKT_SIZE 0x5e
+	/*
+	 * For both TS1 and TS2, In isochronous mode:
+	 *  0x01  188 bytes
+	 *  0x02  376 bytes
+	 *  0x03  564 bytes
+	 *  0x04  752 bytes
+	 *  0x05  940 bytes
+	 * In bulk mode:
+	 *  0x01..0xff  total packet count in 188-byte
+	 */
 #define EM2874_R5F_TS_ENABLE    0x5f
 #define EM2874_R80_GPIO         0x80
 
diff --git a/drivers/media/usb/em28xx/em28xx.h b/drivers/media/usb/em28xx/em28xx.h
index a9323b6..e488113 100644
--- a/drivers/media/usb/em28xx/em28xx.h
+++ b/drivers/media/usb/em28xx/em28xx.h
@@ -130,6 +130,8 @@
 #define EM2884_BOARD_PCTV_520E			  86
 #define EM2884_BOARD_TERRATEC_HTC_USB_XS	  87
 #define EM2884_BOARD_C3TECH_DIGITAL_DUO		  88
+#define EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB  89
+#define EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_ATSC 90
 
 /* Limits minimum and default number of buffers */
 #define EM28XX_MIN_BUF 4
@@ -179,6 +181,10 @@
 /* time in msecs to wait for i2c writes to finish */
 #define EM2800_I2C_XFER_TIMEOUT		20
 
+#define PRIMARY_TS		0
+
+#define SECONDARY_TS	1
+
 enum em28xx_mode {
 	EM28XX_SUSPEND,
 	EM28XX_ANALOG_MODE,
@@ -393,6 +399,7 @@
 	unsigned int mts_firmware:1;
 	unsigned int max_range_640_480:1;
 	unsigned int has_dvb:1;
+	unsigned int has_dual_ts:1;
 	unsigned int has_snapshot_button:1;
 	unsigned int is_webcam:1;
 	unsigned int valid:1;
@@ -506,6 +513,7 @@
 
 	unsigned int has_audio_class:1;
 	unsigned int has_alsa_audio:1;
+	unsigned int has_video:1;
 	unsigned int is_audio_only:1;
 
 	/* Controls audio streaming */
@@ -605,10 +613,13 @@
 
 	/* usb transfer */
 	struct usb_device *udev;	/* the usb device */
+	u8 ifnum;			/* number of the assigned usb interface */
 	u8 analog_ep_isoc;	/* address of isoc endpoint for analog */
 	u8 analog_ep_bulk;	/* address of bulk endpoint for analog */
 	u8 dvb_ep_isoc;		/* address of isoc endpoint for DVB */
 	u8 dvb_ep_bulk;		/* address of bulk endpoint for DVB */
+	u8 dvb_ep_isoc_ts2;	/* address of isoc endpoint for DVB TS2*/
+	u8 dvb_ep_bulk_ts2;	/* address of bulk endpoint for DVB TS2*/
 	int alt;		/* alternate setting */
 	int max_pkt_size;	/* max packet size of the selected ep at alt */
 	int packet_multiplier;	/* multiplier for wMaxPacketSize, used for
@@ -620,6 +631,8 @@
 	int dvb_alt_isoc;	/* alternate setting for DVB isoc transfers */
 	unsigned int dvb_max_pkt_size_isoc;	/* isoc max packet size of the
 						   selected DVB ep at dvb_alt */
+	unsigned int dvb_max_pkt_size_isoc_ts2;	/* isoc max packet size of the
+						   selected DVB ep at dvb_alt */
 	unsigned int dvb_xfer_bulk:1;		/* use bulk instead of isoc
 						   transfers for DVB          */
 	char urb_buf[URB_MAX_CTRL_SIZE];	/* urb control msg buffer */
@@ -648,6 +661,8 @@
 	struct delayed_work sbutton_query_work;
 
 	struct em28xx_dvb *dvb;
+	struct em28xx *dev_next;
+	int ts;
 };
 
 struct em28xx_ops {