diff --git a/arch/arm64/boot/dts/qcom/apq8098-batfish.dts b/arch/arm64/boot/dts/qcom/apq8098-batfish.dts
index 29d59ecad138..06cd2663e089 100644
--- a/arch/arm64/boot/dts/qcom/apq8098-batfish.dts
+++ b/arch/arm64/boot/dts/qcom/apq8098-batfish.dts
@@ -30,6 +30,48 @@
 	status = "ok";
 };
 
+&blsp1_i2c5 {
+	status = "ok";
+	clock-frequency = <100000>;
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c5_default>;
+
+	demod: demod@64 {
+		compatible = "silabs,si2168";
+		reg = <0x64>;
+		reset-gpios = <&tlmm 84 GPIO_ACTIVE_LOW>;
+		i2c-gate {
+			tuner@60 {
+				compatible = "silabs,si2141";
+				reg = <0x60>;
+				demod = <&demod>;
+			};
+		};
+	};
+};
+
+&tlmm {
+	i2c5_default: i2c5-default {
+		pins = "gpio87", "gpio88";
+		function = "blsp_i2c5";
+		drive-strength = <2>;
+		bias-disable;
+	};
+
+	tsif0_default: tsif0-default {
+		pins = "gpio89", "gpio90", "gpio91";
+		function = "tsif0";
+		drive-strength = <2>;
+		bias-pull-down;
+	};
+};
+
+&tsif {
+	demod = <&demod>;
+	pinctrl-0 = <&tsif0_default>;
+	pinctrl-names = "default";
+};
+
 &qusb2phy {
 	status = "ok";
 	vdda-pll-supply = <&vreg_l12a_1p8>;
diff --git a/arch/arm64/boot/dts/qcom/msm8998.dtsi b/arch/arm64/boot/dts/qcom/msm8998.dtsi
index f8671a46392d..62518dcbe2a3 100644
--- a/arch/arm64/boot/dts/qcom/msm8998.dtsi
+++ b/arch/arm64/boot/dts/qcom/msm8998.dtsi
@@ -1206,6 +1206,16 @@
 			status = "disabled";
 		};
 
+		tsif: tsif@c1e7000 {
+			compatible = "qcom,msm8998-tsif";
+			reg = <0x0c1e7000 0x104>;
+			reg-names = "tsif0";
+			interrupts = <GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-names = "tsif0";
+			clocks = <&gcc GCC_TSIF_AHB_CLK>;
+			clock-names = "iface";
+		};
+
 		timer@17920000 {
 			#address-cells = <1>;
 			#size-cells = <1>;
diff --git a/drivers/media/dvb-core/dvb_frontend.c b/drivers/media/dvb-core/dvb_frontend.c
index 6351a97f3d18..5a55c6bd0bd2 100644
--- a/drivers/media/dvb-core/dvb_frontend.c
+++ b/drivers/media/dvb-core/dvb_frontend.c
@@ -3030,3 +3030,17 @@ void dvb_frontend_detach(struct dvb_frontend *fe)
 	dvb_frontend_put(fe);
 }
 EXPORT_SYMBOL(dvb_frontend_detach);
+
+/*
+ * DT-enabled demodulator drivers are required to have 'struct dvb_frontend'
+ * as the first field of their state struct (stored as clientdata) in order
+ * to allow this function to return 'struct dvb_frontend' generically.
+ */
+struct dvb_frontend *dvb_get_demod_fe(struct device_node *np)
+{
+	struct device_node *demod_node = of_parse_phandle(np, "demod", 0);
+	struct i2c_client *demod = of_find_i2c_device_by_node(demod_node);
+	of_node_put(demod_node);
+	return demod ? i2c_get_clientdata(demod) : NULL;
+}
+EXPORT_SYMBOL(dvb_get_demod_fe);
diff --git a/drivers/media/dvb-frontends/si2168.c b/drivers/media/dvb-frontends/si2168.c
index 48e8a376766e..09e1382ed128 100644
--- a/drivers/media/dvb-frontends/si2168.c
+++ b/drivers/media/dvb-frontends/si2168.c
@@ -6,6 +6,7 @@
  */
 
 #include <linux/delay.h>
+#include <linux/gpio/consumer.h>
 
 #include "si2168_priv.h"
 
@@ -663,6 +664,8 @@ static const struct dvb_frontend_ops si2168_ops = {
 static int si2168_probe(struct i2c_client *client,
 		const struct i2c_device_id *id)
 {
+	struct device *cdev = &client->dev;
+	struct si2168_config defconfig = { .ts_mode = SI2168_TS_SERIAL };
 	struct si2168_config *config = client->dev.platform_data;
 	struct si2168_dev *dev;
 	int ret;
@@ -670,6 +673,13 @@ static int si2168_probe(struct i2c_client *client,
 
 	dev_dbg(&client->dev, "\n");
 
+	if (cdev->of_node) {
+		struct gpio_desc *desc;
+		desc = devm_gpiod_get_optional(cdev, "reset", GPIOD_OUT_LOW);
+		if (IS_ERR(desc)) return PTR_ERR(desc);
+		config = &defconfig;
+	}
+
 	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
 	if (!dev) {
 		ret = -ENOMEM;
@@ -723,9 +733,21 @@ static int si2168_probe(struct i2c_client *client,
 	dev->version = (cmd.args[1]) << 24 | (cmd.args[3] - '0') << 16 |
 		       (cmd.args[4] - '0') << 8 | (cmd.args[5]) << 0;
 
+	/* create dvb_frontend */
+	memcpy(&dev->fe.ops, &si2168_ops, sizeof(si2168_ops));
+	dev->fe.demodulator_priv = client;
+	if (config->i2c_adapter)
+		*config->i2c_adapter = dev->muxc->adapter[0];
+	if (config->fe)
+		*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->spectral_inversion = config->spectral_inversion;
+
 	/* create mux i2c adapter for tuner */
 	dev->muxc = i2c_mux_alloc(client->adapter, &client->dev,
-				  1, 0, I2C_MUX_LOCKED,
+				  1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE,
 				  si2168_select, si2168_deselect);
 	if (!dev->muxc) {
 		ret = -ENOMEM;
@@ -736,16 +758,6 @@ static int si2168_probe(struct i2c_client *client,
 	if (ret)
 		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->muxc->adapter[0];
-	*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->spectral_inversion = config->spectral_inversion;
-
 	dev_info(&client->dev, "Silicon Labs Si2168-%c%d%d successfully identified\n",
 		 dev->version >> 24 & 0xff, dev->version >> 16 & 0xff,
 		 dev->version >> 8 & 0xff);
diff --git a/drivers/media/dvb-frontends/si2168_priv.h b/drivers/media/dvb-frontends/si2168_priv.h
index 804d5b30c697..1e3cac3b8381 100644
--- a/drivers/media/dvb-frontends/si2168_priv.h
+++ b/drivers/media/dvb-frontends/si2168_priv.h
@@ -22,9 +22,9 @@
 
 /* state struct */
 struct si2168_dev {
+	struct dvb_frontend fe; /* see dvb_get_demod_fe() */
 	struct mutex i2c_mutex;
 	struct i2c_mux_core *muxc;
-	struct dvb_frontend fe;
 	enum fe_delivery_system delivery_system;
 	enum fe_status fe_status;
 	#define SI2168_CHIP_ID_A20 ('A' << 24 | 68 << 16 | '2' << 8 | '0' << 0)
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index 7cbbd925124c..6a522e647a9d 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -101,3 +101,5 @@ obj-y					+= meson/
 obj-y					+= cros-ec-cec/
 
 obj-$(CONFIG_VIDEO_SUN6I_CSI)		+= sunxi/sun6i-csi/
+
+obj-y += tsif.o
diff --git a/drivers/media/platform/tsif.c b/drivers/media/platform/tsif.c
new file mode 100644
index 000000000000..b136f334e9c6
--- /dev/null
+++ b/drivers/media/platform/tsif.c
@@ -0,0 +1,185 @@
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <media/dvb_frontend.h>
+#include <media/dvb_demux.h>
+#include <media/dmxdev.h>
+
+/* TSIF register offsets */
+#define TSIF_STS_CTL	0x0	/* status and control */
+#define TSIF_DATA_PORT	0x100
+
+/* TSIF_STS_CTL bits */
+#define ENABLE_IRQ	BIT(28)
+#define TSIF_STOP	BIT(3)
+#define TSIF_START	BIT(0)
+
+struct tsif {
+	void __iomem *base;
+	struct clk *clk;
+	int ref_count; /*** TODO: use atomic_t ??? or refcount_t ??? or kref ??? ***/
+	u32 buf[48];
+	struct dvb_frontend *fe;
+	/*** DO I NEED ALL 4 ***/
+	//struct dmx_frontend dmx_frontend;
+	struct dvb_adapter dvb_adapter;
+	struct dvb_demux dvb_demux;
+	struct dmxdev dmxdev;
+};
+
+static int start_tsif(struct dvb_demux_feed *feed)
+{
+	struct tsif *tsif = feed->demux->priv;
+	printk("%s: feed PID=%u\n", __func__, feed->pid);
+
+	if (tsif->ref_count++ == 0) {
+		u32 val = TSIF_START | ENABLE_IRQ | BIT(29);
+		writel_relaxed(val, tsif->base + TSIF_STS_CTL);
+	}
+
+	return 0;
+}
+
+static int stop_tsif(struct dvb_demux_feed *feed)
+{
+	struct tsif *tsif = feed->demux->priv;
+	printk("%s: feed PID=%u\n", __func__, feed->pid);
+
+	if (--tsif->ref_count == 0) {
+		writel_relaxed(TSIF_STOP, tsif->base + TSIF_STS_CTL);
+	}
+
+	return 0;
+}
+
+static irqreturn_t tsif_isr(int irq, void *arg)
+{
+	int i;
+	u32 status;
+	struct tsif *tsif = arg;
+
+	status = readl_relaxed(tsif->base + TSIF_STS_CTL);
+	writel_relaxed(status, tsif->base + TSIF_STS_CTL);
+
+	for (i = 0; i < 48; ++i)
+		tsif->buf[i] = readl_relaxed(tsif->base + TSIF_DATA_PORT);
+
+	dvb_dmx_swfilter_packets(&tsif->dvb_demux, (void *)tsif->buf, 1);
+
+	return IRQ_HANDLED;
+}
+
+static int tsif_probe(struct platform_device *pdev)
+{
+	int err, virq;
+	struct tsif *tsif;
+	struct resource *res;
+	struct device *dev = &pdev->dev;
+
+	tsif = devm_kzalloc(dev, sizeof(*tsif), GFP_KERNEL);
+	if (!tsif)
+		return -ENOMEM;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tsif0");
+	tsif->base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(tsif->base))
+		return PTR_ERR(tsif->base);
+
+	virq = platform_get_irq_byname(pdev, "tsif0");
+	err = devm_request_irq(dev, virq, tsif_isr, IRQF_SHARED, "tsif", tsif);
+	if (err)
+		return err;
+
+	tsif->clk = devm_clk_get(dev, "iface");
+	if (IS_ERR(tsif->clk))
+		return PTR_ERR(tsif->clk);
+
+	tsif->fe = dvb_get_demod_fe(dev->of_node);
+	if (!tsif->fe)
+		return -ENXIO;
+
+	/*** TODO: use devm version ***/
+	clk_prepare_enable(tsif->clk);
+
+	{
+	int ret;
+	short any = DVB_UNSET;
+
+	ret = dvb_register_adapter(&tsif->dvb_adapter, "tsif", THIS_MODULE, dev, &any);
+	if (ret < 0) panic("dvb_register_adapter");
+
+	tsif->dvb_demux.priv = tsif;
+	/* Not sure the diff between filter and feed? */
+	tsif->dvb_demux.filternum = 16; /*** Dunno what to put here ***/
+	tsif->dvb_demux.feednum = 16;	/*** Dunno what to put here ***/
+	tsif->dvb_demux.start_feed = start_tsif;
+	tsif->dvb_demux.stop_feed = stop_tsif;
+
+	ret = dvb_dmx_init(&tsif->dvb_demux);
+	if (ret < 0) panic("dvb_dmx_init");
+
+	/* What relation to dvb_demux.filternum??? */
+	/* Do I need this object?? */
+	tsif->dmxdev.filternum = 16;
+	tsif->dmxdev.demux = &tsif->dvb_demux.dmx;
+
+	ret = dvb_dmxdev_init(&tsif->dmxdev, &tsif->dvb_adapter);
+	if (ret < 0) panic("dvb_dmxdev_init");
+
+#if 0
+	/*** These calls don't seem required??? ***/
+	/*** Who reads this? Do I need to set it? ***/
+	tsif->dmx_frontend.source = DMX_FRONTEND_0;
+
+	/* Required or done elsewhere? */
+	ret = tsif->dvb_demux.dmx.add_frontend(&tsif->dvb_demux.dmx, &tsif->dmx_frontend);
+	if (ret < 0) panic("add_frontend");
+
+	/* Required or done elsewhere? */
+	ret = tsif->dvb_demux.dmx.connect_frontend(&tsif->dvb_demux.dmx, &tsif->dmx_frontend);
+	if (ret < 0) panic("connect_frontend");
+#endif
+
+	ret = dvb_register_frontend(&tsif->dvb_adapter, tsif->fe);
+	if (ret < 0) panic("dvb_register_frontend");
+	}
+
+	platform_set_drvdata(pdev, tsif);
+	return 0;
+}
+
+/*** TODO: Double check .remove callback ***/
+static int tsif_remove(struct platform_device *pdev)
+{
+	struct tsif *tsif = platform_get_drvdata(pdev);
+
+	clk_disable_unprepare(tsif->clk);
+
+	dvb_unregister_frontend(tsif->fe);
+	//tsif->dvb_demux.dmx.remove_frontend(&tsif->dvb_demux.dmx, &tsif->dmx_frontend);
+	dvb_dmxdev_release(&tsif->dmxdev);
+	dvb_dmx_release(&tsif->dvb_demux);
+	dvb_unregister_adapter(&tsif->dvb_adapter);
+
+	return 0;
+}
+
+static const struct of_device_id tsif_of_ids[] = {
+	{ .compatible = "qcom,msm8998-tsif" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, tsif_of_ids);
+
+static struct platform_driver qcom_tsif_driver = {
+	.probe  = tsif_probe,
+	.remove = tsif_remove,
+	.driver = {
+		.name = "qcom-tsif",
+		.of_match_table = tsif_of_ids,
+	},
+};
+
+module_platform_driver(qcom_tsif_driver);
+
+MODULE_DESCRIPTION("Qualcomm TSIF driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/tuners/si2157.c b/drivers/media/tuners/si2157.c
index 7be893def190..9d2ca775ada9 100644
--- a/drivers/media/tuners/si2157.c
+++ b/drivers/media/tuners/si2157.c
@@ -420,12 +420,18 @@ static void si2157_stat_work(struct work_struct *work)
 static int si2157_probe(struct i2c_client *client,
 			const struct i2c_device_id *id)
 {
+	struct device *cdev = &client->dev;
+	struct si2157_config defconfig = { 0 };
 	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 (cdev->of_node) {
+		cfg = &defconfig;
+		cfg->fe = dvb_get_demod_fe(cdev->of_node);
+	}
+
 	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
 	if (!dev) {
 		ret = -ENOMEM;
@@ -449,8 +455,8 @@ static int si2157_probe(struct i2c_client *client,
 	if (ret)
 		goto err_kfree;
 
-	memcpy(&fe->ops.tuner_ops, &si2157_ops, sizeof(struct dvb_tuner_ops));
-	fe->tuner_priv = client;
+	memcpy(&dev->fe->ops.tuner_ops, &si2157_ops, sizeof(si2157_ops));
+	dev->fe->tuner_priv = client;
 
 #ifdef CONFIG_MEDIA_CONTROLLER
 	if (cfg->mdev) {
diff --git a/include/media/dvb_frontend.h b/include/media/dvb_frontend.h
index f05cd7b94a2c..f8ea4839095f 100644
--- a/include/media/dvb_frontend.h
+++ b/include/media/dvb_frontend.h
@@ -821,4 +821,6 @@ void dvb_frontend_reinitialise(struct dvb_frontend *fe);
  */
 void dvb_frontend_sleep_until(ktime_t *waketime, u32 add_usec);
 
+struct dvb_frontend *dvb_get_demod_fe(struct device_node *np);
+
 #endif
