All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] Full DVB driver package for Earthsoft PT3 (ISDB-S/T) cards
@ 2014-04-22 17:26 Буди Романто, AreMa Inc
  2014-05-17 19:03 ` Antti Palosaari
  0 siblings, 1 reply; 21+ messages in thread
From: Буди Романто, AreMa Inc @ 2014-04-22 17:26 UTC (permalink / raw)
  To: linux-media
  Cc: Буди
	Романто,
	AreMa Inc, crope, m.chehab, hdegoede, hverkuil, laurent.pinchart,
	mkrufky, sylvester.nawrocki, g.liakhovetski, peter.senna

From: Буди Роман то, AreMa Inc <knightrider@are.ma>

DVB driver for Earthsoft PT3 (ISDB-S/T) receiver PCI Express cards.
It behaves similarly as PT1 DVB, plus some tuning enhancements:
1. in addition to the real frequency:
	ISDB-S : freq. channel ID
	ISDB-T : freq# (I/O# +128), ch#, ch# +64 for CATV
2. in addition to TSID:
	ISDB-S : slot#

Feature changes:
- dropped DKMS & standalone compile
- dropped verbosity (debug levels), use single level -DDEBUG instead
- moved demodulator FE to drivers/media/dvb-frontends
- moved tuners to drivers/media/tuners
- translated to standard (?) I2C protocol
- dropped unused features
- added DVBv5 CNR support

The full package (buildable as standalone, DKMS or tree embedded module) is available at
https://github.com/knight-rider/ptx/tree/master/pt3_dvb

Signed-off-by: Буди Романто, AreMa Inc <knightrider@are.ma>
---
 drivers/media/dvb-frontends/Kconfig   |  11 +-
 drivers/media/dvb-frontends/Makefile  |   1 +
 drivers/media/dvb-frontends/tc90522.c | 539 ++++++++++++++++++++++++++++++++++
 drivers/media/dvb-frontends/tc90522.h |  36 +++
 drivers/media/pci/Kconfig             |   2 +-
 drivers/media/pci/Makefile            |   1 +
 drivers/media/pci/pt3/Kconfig         |  11 +
 drivers/media/pci/pt3/Makefile        |   6 +
 drivers/media/pci/pt3/pt3_common.h    |  86 ++++++
 drivers/media/pci/pt3/pt3_dma.c       | 333 +++++++++++++++++++++
 drivers/media/pci/pt3/pt3_dma.h       |  50 ++++
 drivers/media/pci/pt3/pt3_i2c.c       | 189 ++++++++++++
 drivers/media/pci/pt3/pt3_i2c.h       |  25 ++
 drivers/media/pci/pt3/pt3_pci.c       | 397 +++++++++++++++++++++++++
 drivers/media/tuners/Kconfig          |  15 +
 drivers/media/tuners/Makefile         |   2 +
 drivers/media/tuners/mxl301rf.c       | 390 ++++++++++++++++++++++++
 drivers/media/tuners/mxl301rf.h       |  36 +++
 drivers/media/tuners/qm1d1c0042.c     | 382 ++++++++++++++++++++++++
 drivers/media/tuners/qm1d1c0042.h     |  36 +++
 20 files changed, 2546 insertions(+), 2 deletions(-)
 create mode 100644 drivers/media/dvb-frontends/tc90522.c
 create mode 100644 drivers/media/dvb-frontends/tc90522.h
 create mode 100644 drivers/media/pci/pt3/Kconfig
 create mode 100644 drivers/media/pci/pt3/Makefile
 create mode 100644 drivers/media/pci/pt3/pt3_common.h
 create mode 100644 drivers/media/pci/pt3/pt3_dma.c
 create mode 100644 drivers/media/pci/pt3/pt3_dma.h
 create mode 100644 drivers/media/pci/pt3/pt3_i2c.c
 create mode 100644 drivers/media/pci/pt3/pt3_i2c.h
 create mode 100644 drivers/media/pci/pt3/pt3_pci.c
 create mode 100644 drivers/media/tuners/mxl301rf.c
 create mode 100644 drivers/media/tuners/mxl301rf.h
 create mode 100644 drivers/media/tuners/qm1d1c0042.c
 create mode 100644 drivers/media/tuners/qm1d1c0042.h

diff --git a/drivers/media/dvb-frontends/Kconfig b/drivers/media/dvb-frontends/Kconfig
index 025fc54..0047b3f 100644
--- a/drivers/media/dvb-frontends/Kconfig
+++ b/drivers/media/dvb-frontends/Kconfig
@@ -591,7 +591,7 @@ config DVB_S5H1411
 	  An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want
 	  to support this frontend.
 
-comment "ISDB-T (terrestrial) frontends"
+comment "ISDB-S (satellite) & ISDB-T (terrestrial) frontends"
 	depends on DVB_CORE
 
 config DVB_S921
@@ -618,6 +618,15 @@ config DVB_MB86A20S
 	  A driver for Fujitsu mb86a20s ISDB-T/ISDB-Tsb demodulator.
 	  Say Y when you want to support this frontend.
 
+config DVB_TC90522
+	tristate "Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S)"
+	depends on DVB_CORE && I2C
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S) DVB demodulator
+	  frontend for Earthsoft PT3 PCIE cards.
+	  Say Y when you want to support this frontend.
+
 comment "Digital terrestrial only tuners/PLL"
 	depends on DVB_CORE
 
diff --git a/drivers/media/dvb-frontends/Makefile b/drivers/media/dvb-frontends/Makefile
index 282aba2..a80d212 100644
--- a/drivers/media/dvb-frontends/Makefile
+++ b/drivers/media/dvb-frontends/Makefile
@@ -105,4 +105,5 @@ obj-$(CONFIG_DVB_RTL2830) += rtl2830.o
 obj-$(CONFIG_DVB_RTL2832) += rtl2832.o
 obj-$(CONFIG_DVB_M88RS2000) += m88rs2000.o
 obj-$(CONFIG_DVB_AF9033) += af9033.o
+obj-$(CONFIG_DVB_TC90522) += tc90522.o
 
diff --git a/drivers/media/dvb-frontends/tc90522.c b/drivers/media/dvb-frontends/tc90522.c
new file mode 100644
index 0000000..a767600
--- /dev/null
+++ b/drivers/media/dvb-frontends/tc90522.c
@@ -0,0 +1,539 @@
+/*
+ * Earthsoft PT3 demodulator frontend Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S)
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "dvb_math.h"
+#include "tc90522.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 Toshiba TC90522 OFDM(ISDB-T)/8PSK(ISDB-S) demodulator");
+MODULE_LICENSE("GPL");
+
+#define TC90522_PASSTHROUGH 0xfe
+
+enum tc90522_state {
+	TC90522_IDLE,
+	TC90522_SET_FREQUENCY,
+	TC90522_SET_MODULATION,
+	TC90522_TRACK,
+	TC90522_ABORT,
+};
+
+struct tc90522 {
+	struct dvb_frontend fe;
+	struct i2c_adapter *i2c;
+	fe_delivery_system_t type;
+	u8 idx, addr_demod;
+	s32 offset;
+	enum tc90522_state state;
+};
+
+int tc90522_write(struct dvb_frontend *fe, const u8 *data, int len)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	struct i2c_msg msg[3];
+	u8 buf[6];
+
+	if (data) {
+		msg[0].addr = demod->addr_demod;
+		msg[0].buf = (u8 *)data;
+		msg[0].flags = 0;			/* write */
+		msg[0].len = len;
+
+		return i2c_transfer(demod->i2c, msg, 1) == 1 ? 0 : -EREMOTEIO;
+	} else {
+		u8 addr_tuner = (len >> 8) & 0xff,
+		   addr_data = len & 0xff;
+		if (len >> 16) {			/* read tuner without address */
+			buf[0] = TC90522_PASSTHROUGH;
+			buf[1] = (addr_tuner << 1) | 1;
+			msg[0].buf = buf;
+			msg[0].len = 2;
+			msg[0].addr = demod->addr_demod;
+			msg[0].flags = 0;		/* write */
+
+			msg[1].buf = buf + 2;
+			msg[1].len = 1;
+			msg[1].addr = demod->addr_demod;
+			msg[1].flags = I2C_M_RD;	/* read */
+
+			return i2c_transfer(demod->i2c, msg, 2) == 2 ? buf[2] : -EREMOTEIO;
+		} else {				/* read tuner */
+			buf[0] = TC90522_PASSTHROUGH;
+			buf[1] = addr_tuner << 1;
+			buf[2] = addr_data;
+			msg[0].buf = buf;
+			msg[0].len = 3;
+			msg[0].addr = demod->addr_demod;
+			msg[0].flags = 0;		/* write */
+
+			buf[3] = TC90522_PASSTHROUGH;
+			buf[4] = (addr_tuner << 1) | 1;
+			msg[1].buf = buf + 3;
+			msg[1].len = 2;
+			msg[1].addr = demod->addr_demod;
+			msg[1].flags = 0;		/* write */
+
+			msg[2].buf = buf + 5;
+			msg[2].len = 1;
+			msg[2].addr = demod->addr_demod;
+			msg[2].flags = I2C_M_RD;	/* read */
+
+			return i2c_transfer(demod->i2c, msg, 3) == 3 ? buf[5] : -EREMOTEIO;
+		}
+	}
+}
+
+int tc90522_write_data(struct dvb_frontend *fe, u8 addr_data, u8 *data, u8 len)
+{
+	u8 buf[len + 1];
+	buf[0] = addr_data;
+	memcpy(buf + 1, data, len);
+	return tc90522_write(fe, buf, len + 1);
+}
+
+int tc90522_read(struct tc90522 *demod, u8 addr, u8 *buf, u8 buflen)
+{
+	struct i2c_msg msg[2];
+	if (!buf || !buflen)
+		return -EINVAL;
+
+	buf[0] = addr;
+	msg[0].addr = demod->addr_demod;
+	msg[0].flags = 0;			/* write */
+	msg[0].buf = buf;
+	msg[0].len = 1;
+
+	msg[1].addr = demod->addr_demod;
+	msg[1].flags = I2C_M_RD;		/* read */
+	msg[1].buf = buf;
+	msg[1].len = buflen;
+
+	return i2c_transfer(demod->i2c, msg, 2) == 2 ? 0 : -EREMOTEIO;
+}
+
+u32 tc90522_byten(const u8 *data, u32 n)
+{
+	u32 i, val = 0;
+
+	for (i = 0; i < n; i++) {
+		val <<= 8;
+		val |= data[i];
+	}
+	return val;
+}
+
+int tc90522_read_id_s(struct tc90522 *demod, u16 *id)
+{
+	u8 buf[2];
+	int ret = tc90522_read(demod, 0xe6, buf, 2);
+	if (!ret)
+		*id = tc90522_byten(buf, 2);
+	return ret;
+}
+
+struct tmcc_s {			/* Transmission and Multiplexing Configuration Control */
+	u32 mode[4];
+	u32 slot[4];
+	u32 id[8];
+};
+
+int tc90522_read_tmcc_s(struct tc90522 *demod, struct tmcc_s *tmcc)
+{
+	enum {
+		BASE = 0xc5,
+		SIZE = 0xe5 - BASE + 1
+	};
+	u8 data[SIZE];
+	u32 i, byte_offset, bit_offset;
+
+	int err = tc90522_read(demod, 0xc3, data, 1)	||
+		((data[0] >> 4) & 1)			||
+		tc90522_read(demod, 0xce, data, 2)	||
+		(tc90522_byten(data, 2) == 0)		||
+		tc90522_read(demod, 0xc3, data, 1)	||
+		tc90522_read(demod, 0xc5, data, SIZE);
+	if (err)
+		return err;
+	for (i = 0; i < 4; i++) {
+		byte_offset = i >> 1;
+		bit_offset = (i & 1) ? 0 : 4;
+		tmcc->mode[i] = (data[0xc8 + byte_offset - BASE] >> bit_offset) & 0b00001111;
+		tmcc->slot[i] = (data[0xca + i           - BASE] >>          0) & 0b00111111;
+	}
+	for (i = 0; i < 8; i++)
+		tmcc->id[i] = tc90522_byten(data + 0xce + i * 2 - BASE, 2);
+	return 0;
+}
+
+enum tc90522_pwr {
+	TC90522_PWR_OFF		= 0x00,
+	TC90522_PWR_AMP_ON	= 0x04,
+	TC90522_PWR_TUNER_ON	= 0x40,
+};
+
+static enum tc90522_pwr tc90522_pwr = TC90522_PWR_OFF;
+
+int tc90522_set_powers(struct tc90522 *demod, enum tc90522_pwr pwr)
+{
+	u8 data = pwr | 0b10011001;
+	pr_debug("#%d tuner %s amp %s\n", demod->idx, pwr & TC90522_PWR_TUNER_ON ? "ON" : "OFF", pwr & TC90522_PWR_AMP_ON ? "ON" : "OFF");
+	tc90522_pwr = pwr;
+	return tc90522_write_data(&demod->fe, 0x1e, &data, 1);
+}
+
+/* dvb_frontend_ops */
+int tc90522_get_frontend_algo(struct dvb_frontend *fe)
+{
+	return DVBFE_ALGO_HW;
+}
+
+int tc90522_sleep(struct dvb_frontend *fe)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	pr_debug("#%d %s %s\n", demod->idx, __func__, demod->type == SYS_ISDBS ? "S" : "T");
+	return fe->ops.tuner_ops.sleep(fe);
+}
+
+int tc90522_wakeup(struct dvb_frontend *fe)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	pr_debug("#%d %s %s 0x%x\n", demod->idx, __func__, demod->type == SYS_ISDBS ? "S" : "T", tc90522_pwr);
+
+	if (!tc90522_pwr)
+		return	tc90522_set_powers(demod, TC90522_PWR_TUNER_ON)	||
+			i2c_transfer(demod->i2c, NULL, 0)		||
+			tc90522_set_powers(demod, TC90522_PWR_TUNER_ON | TC90522_PWR_AMP_ON);
+	demod->state = TC90522_IDLE;
+	return fe->ops.tuner_ops.init(fe);
+}
+
+void tc90522_release(struct dvb_frontend *fe)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	pr_debug("#%d %s\n", demod->idx, __func__);
+
+	if (tc90522_pwr)
+		tc90522_set_powers(demod, TC90522_PWR_OFF);
+	tc90522_sleep(fe);
+	fe->ops.tuner_ops.release(fe);
+	kfree(demod);
+}
+
+s64 tc90522_get_cn_raw(struct tc90522 *demod)
+{
+	u8 buf[3], buflen = demod->type == SYS_ISDBS ? 2 : 3, addr = demod->type == SYS_ISDBS ? 0xbc : 0x8b;
+	int err = tc90522_read(demod, addr, buf, buflen);
+	return err < 0 ? err : tc90522_byten(buf, buflen);
+}
+
+s64 tc90522_get_cn_s(s64 raw)	/* @ .0001 dB */
+{
+	s64 x, y;
+
+	raw -= 3000;
+	if (raw < 0)
+		raw = 0;
+	x = int_sqrt(raw << 20);
+	y = 16346ll * x - (143410ll << 16);
+	y = ((x * y) >> 16) + (502590ll << 16);
+	y = ((x * y) >> 16) - (889770ll << 16);
+	y = ((x * y) >> 16) + (895650ll << 16);
+	y = (588570ll << 16) - ((x * y) >> 16);
+	return y < 0 ? 0 : y >> 16;
+}
+
+s64 tc90522_get_cn_t(s64 raw)	/* @ .0001 dB */
+{
+	s64 x, y;
+	if (!raw)
+		return 0;
+	x = (1130911733ll - 10ll * intlog10(raw)) >> 2;
+	y = (6ll * x / 25ll) - (16ll << 22);
+	y = ((x * y) >> 22) + (398ll << 22);
+	y = ((x * y) >> 22) + (5491ll << 22);
+	y = ((x * y) >> 22) + (30965ll << 22);
+	return y >> 22;
+}
+
+int tc90522_read_signal_strength(struct dvb_frontend *fe, u16 *cn)	/* raw C/N */
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	s64 ret = tc90522_get_cn_raw(demod);
+	*cn = ret < 0 ? 0 : ret;
+	pr_debug("v3 CN %d (%lld dB)\n", (int)*cn, demod->type == SYS_ISDBS ? (long long int)tc90522_get_cn_s(*cn) : (long long int)tc90522_get_cn_t(*cn));
+	return ret < 0 ? ret : 0;
+}
+
+int tc90522_read_status(struct dvb_frontend *fe, fe_status_t *status)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+	s64 ret = tc90522_get_cn_raw(demod),
+	    raw = ret < 0 ? 0 : ret;
+
+	switch (demod->state) {
+	case TC90522_IDLE:
+	case TC90522_SET_FREQUENCY:
+		*status = 0;
+		break;
+
+	case TC90522_SET_MODULATION:
+	case TC90522_ABORT:
+		*status |= FE_HAS_SIGNAL;
+		break;
+
+	case TC90522_TRACK:
+		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		break;
+	}
+
+	c->cnr.len = 1;
+	c->cnr.stat[0].svalue = demod->type == SYS_ISDBS ? tc90522_get_cn_s(raw) : tc90522_get_cn_t(raw);
+	c->cnr.stat[0].scale = FE_SCALE_DECIBEL;
+	pr_debug("v5 CN %lld (%lld dB)\n", raw, c->cnr.stat[0].svalue);
+	return ret < 0 ? ret : 0;
+}
+
+/**** ISDB-S ****/
+int tc90522_write_id_s(struct dvb_frontend *fe, u16 id)
+{
+	u8 data[2] = { id >> 8, (u8)id };
+	return tc90522_write_data(fe, 0x8f, data, sizeof(data));
+}
+
+int tc90522_tune_s(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	struct tmcc_s tmcc;
+	int i, ret,
+	    freq = fe->dtv_property_cache.frequency,
+	    tsid = fe->dtv_property_cache.stream_id;
+
+	if (re_tune)
+		demod->state = TC90522_SET_FREQUENCY;
+
+	switch (demod->state) {
+	case TC90522_IDLE:
+		*delay = msecs_to_jiffies(3000);
+		*status = 0;
+		return 0;
+
+	case TC90522_SET_FREQUENCY:
+		pr_debug("#%d tsid 0x%x freq %d\n", demod->idx, tsid, freq);
+		ret = fe->ops.tuner_ops.set_frequency(fe, freq);
+		if (ret)
+			return ret;
+		demod->offset = 0;
+		demod->state = TC90522_SET_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case TC90522_SET_MODULATION:
+		for (i = 0; i < 1000; i++) {
+			ret = tc90522_read_tmcc_s(demod, &tmcc);
+			if (!ret)
+				break;
+			msleep_interruptible(1);
+		}
+		if (ret) {
+			pr_debug("fail tc_read_tmcc_s ret=0x%x\n", ret);
+			demod->state = TC90522_ABORT;
+			*delay = msecs_to_jiffies(1000);
+			return ret;
+		}
+		pr_debug("slots=%d,%d,%d,%d mode=%d,%d,%d,%d tmcc.id=0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x\n",
+				tmcc.slot[0], tmcc.slot[1], tmcc.slot[2], tmcc.slot[3],
+				tmcc.mode[0], tmcc.mode[1], tmcc.mode[2], tmcc.mode[3],
+				tmcc.id[0], tmcc.id[1], tmcc.id[2], tmcc.id[3],
+				tmcc.id[4], tmcc.id[5], tmcc.id[6], tmcc.id[7]);
+		for (i = 0; i < ARRAY_SIZE(tmcc.id); i++) {
+			pr_debug("tsid %x i %d tmcc.id %x\n", tsid, i, tmcc.id[i]);
+			if (tmcc.id[i] == tsid)
+				break;
+		}
+		if (tsid < ARRAY_SIZE(tmcc.id))		/* treat as slot# */
+			i = tsid;
+		if (i == ARRAY_SIZE(tmcc.id)) {
+			pr_debug("#%d i%d tsid 0x%x not found\n", demod->idx, i, tsid);
+			return -EINVAL;
+		}
+		demod->offset = i;
+		pr_debug("#%d found tsid 0x%x on slot %d\n", demod->idx, tsid, i);
+		ret = tc90522_write_id_s(fe, (u16)tmcc.id[demod->offset]);
+		if (ret) {
+			pr_debug("fail set_tmcc_s ret=%d\n", ret);
+			return ret;
+		}
+		for (i = 0; i < 1000; i++) {
+			u16 short_id;
+			ret = tc90522_read_id_s(demod, &short_id);
+			if (ret) {
+				pr_debug("fail get_id_s ret=%d\n", ret);
+				return ret;
+			}
+			tsid = short_id;
+			pr_debug("#%d tsid=0x%x\n", demod->idx, tsid);
+			if ((tsid & 0xffff) == tmcc.id[demod->offset])
+				break;
+			msleep_interruptible(1);
+		}
+		demod->state = TC90522_TRACK;
+		/* fallthrough */
+
+	case TC90522_TRACK:
+		*delay = msecs_to_jiffies(3000);
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+
+	case TC90522_ABORT:
+		*delay = msecs_to_jiffies(3000);
+		*status = FE_HAS_SIGNAL;
+		return 0;
+	}
+	return -ERANGE;
+}
+
+static struct dvb_frontend_ops tc90522_ops_s = {
+	.delsys = { SYS_ISDBS },
+	.info = {
+		.name = "TC90522 ISDB-S",
+		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO | FE_CAN_MULTISTREAM |
+			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
+	},
+	.init = tc90522_wakeup,
+	.sleep = tc90522_sleep,
+	.release = tc90522_release,
+	.write = tc90522_write,
+	.get_frontend_algo = tc90522_get_frontend_algo,
+	.read_signal_strength = tc90522_read_signal_strength,
+	.read_status = tc90522_read_status,
+	.tune = tc90522_tune_s,
+};
+
+/**** ISDB-T ****/
+int tc90522_get_tmcc_t(struct tc90522 *demod)
+{
+	u8 buf;
+	bool b = false, retryov, fulock;
+
+	while (1) {
+		if (tc90522_read(demod, 0x80, &buf, 1))
+			return -EBADMSG;
+		retryov = buf & 0b10000000 ? true : false;
+		fulock  = buf & 0b00001000 ? true : false;
+		if (!fulock) {
+			b = true;
+			break;
+		} else {
+			if (retryov)
+				break;
+		}
+		msleep_interruptible(1);
+	}
+	return b ? 0 : -EBADMSG;
+}
+
+int tc90522_tune_t(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	int ret, i;
+
+	if (re_tune)
+		demod->state = TC90522_SET_FREQUENCY;
+
+	switch (demod->state) {
+	case TC90522_IDLE:
+		*delay = msecs_to_jiffies(3000);
+		*status = 0;
+		return 0;
+
+	case TC90522_SET_FREQUENCY:
+		if (fe->ops.tuner_ops.set_frequency(fe, fe->dtv_property_cache.frequency)) {
+			*delay = msecs_to_jiffies(1000);
+			*status = 0;
+			return 0;
+		}
+		demod->state = TC90522_SET_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case TC90522_SET_MODULATION:
+		for (i = 0; i < 1000; i++) {
+			ret = tc90522_get_tmcc_t(demod);
+			if (!ret)
+				break;
+			msleep_interruptible(2);
+		}
+		if (ret) {
+			pr_debug("#%d fail get_tmcc_t ret=%d\n", demod->idx, ret);
+				demod->state = TC90522_ABORT;
+				*delay = msecs_to_jiffies(1000);
+				return 0;
+		}
+		demod->state = TC90522_TRACK;
+		/* fallthrough */
+
+	case TC90522_TRACK:
+		*delay = msecs_to_jiffies(3000);
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+
+	case TC90522_ABORT:
+		*delay = msecs_to_jiffies(3000);
+		*status = FE_HAS_SIGNAL;
+		return 0;
+	}
+	return -ERANGE;
+}
+
+static struct dvb_frontend_ops tc90522_ops_t = {
+	.delsys = { SYS_ISDBT },
+	.info = {
+		.name = "TC90522 ISDB-T",
+		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO |
+			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
+	},
+	.init = tc90522_wakeup,
+	.sleep = tc90522_sleep,
+	.release = tc90522_release,
+	.write = tc90522_write,
+	.get_frontend_algo = tc90522_get_frontend_algo,
+	.read_signal_strength = tc90522_read_signal_strength,
+	.read_status = tc90522_read_status,
+	.tune = tc90522_tune_t,
+};
+
+/**** Common ****/
+struct dvb_frontend *tc90522_attach(struct i2c_adapter *i2c, u8 idx, fe_delivery_system_t type, u8 addr_demod)
+{
+	struct dvb_frontend *fe;
+	struct tc90522 *demod = kzalloc(sizeof(struct tc90522), GFP_KERNEL);
+	if (!demod)
+		return NULL;
+
+	demod->i2c	= i2c;
+	demod->idx	= idx;
+	demod->type	= type;
+	demod->addr_demod = addr_demod;
+	fe = &demod->fe;
+	memcpy(&fe->ops, (demod->type == SYS_ISDBS) ? &tc90522_ops_s : &tc90522_ops_t, sizeof(struct dvb_frontend_ops));
+	fe->demodulator_priv = demod;
+	return fe;
+}
+EXPORT_SYMBOL(tc90522_attach);
+
diff --git a/drivers/media/dvb-frontends/tc90522.h b/drivers/media/dvb-frontends/tc90522.h
new file mode 100644
index 0000000..78c5298
--- /dev/null
+++ b/drivers/media/dvb-frontends/tc90522.h
@@ -0,0 +1,36 @@
+/*
+ * Earthsoft PT3 demodulator frontend Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S)
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__TC90522_H__
+#define	__TC90522_H__
+
+#ifndef pr_fmt
+ #define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+#endif
+#include "dvb_frontend.h"
+
+#if IS_ENABLED(CONFIG_DVB_TC90522)
+extern struct dvb_frontend *tc90522_attach(struct i2c_adapter *i2c, u8 idx, fe_delivery_system_t type, u8 addr_demod);
+#else
+static inline struct dvb_frontend *tc90522_attach(struct i2c_adapter *i2c, u8 idx, fe_delivery_system_t type, u8 addr_demod)
+{
+	pr_warn("%s: driver disabled by Kconfig\n", __func__);
+	return NULL;
+}
+#endif
+
+#endif
+
diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
index 53196f1..87018c8 100644
--- a/drivers/media/pci/Kconfig
+++ b/drivers/media/pci/Kconfig
@@ -30,7 +30,6 @@ source "drivers/media/pci/cx88/Kconfig"
 source "drivers/media/pci/bt8xx/Kconfig"
 source "drivers/media/pci/saa7134/Kconfig"
 source "drivers/media/pci/saa7164/Kconfig"
-
 endif
 
 if MEDIA_DIGITAL_TV_SUPPORT
@@ -40,6 +39,7 @@ source "drivers/media/pci/b2c2/Kconfig"
 source "drivers/media/pci/pluto2/Kconfig"
 source "drivers/media/pci/dm1105/Kconfig"
 source "drivers/media/pci/pt1/Kconfig"
+source "drivers/media/pci/pt3/Kconfig"
 source "drivers/media/pci/mantis/Kconfig"
 source "drivers/media/pci/ngene/Kconfig"
 source "drivers/media/pci/ddbridge/Kconfig"
diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
index 35cc578..f7be6bc 100644
--- a/drivers/media/pci/Makefile
+++ b/drivers/media/pci/Makefile
@@ -7,6 +7,7 @@ obj-y        +=	ttpci/		\
 		pluto2/		\
 		dm1105/		\
 		pt1/		\
+		pt3/		\
 		mantis/		\
 		ngene/		\
 		ddbridge/	\
diff --git a/drivers/media/pci/pt3/Kconfig b/drivers/media/pci/pt3/Kconfig
new file mode 100644
index 0000000..0d866a0
--- /dev/null
+++ b/drivers/media/pci/pt3/Kconfig
@@ -0,0 +1,11 @@
+config PT3_DVB
+	tristate "Earthsoft PT3 ISDB-S/T cards"
+	depends on DVB_CORE && PCI
+	select DVB_TC90522 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_QM1D1C0042 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MXL301RF if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Support for Earthsoft PT3 PCI-Express cards.
+	  You need to enable frontend (TC90522) & tuners (QM1D1C0042, MXL301RF)
+	  Say Y or M if you own such a device and want to use it.
+
diff --git a/drivers/media/pci/pt3/Makefile b/drivers/media/pci/pt3/Makefile
new file mode 100644
index 0000000..ede00e1
--- /dev/null
+++ b/drivers/media/pci/pt3/Makefile
@@ -0,0 +1,6 @@
+pt3_dvb-objs := pt3_pci.o pt3_dma.o pt3_i2c.o
+
+obj-$(CONFIG_PT3_DVB) += pt3_dvb.o
+
+ccflags-y += -Idrivers/media/dvb-core -Idrivers/media/dvb-frontends -Idrivers/media/tuners
+
diff --git a/drivers/media/pci/pt3/pt3_common.h b/drivers/media/pci/pt3/pt3_common.h
new file mode 100644
index 0000000..21b36f5
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_common.h
@@ -0,0 +1,86 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_COMMON_H__
+#define	__PT3_COMMON_H__
+
+#define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+
+#include <linux/pci.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include "dvb_demux.h"
+#include "dmxdev.h"
+#include "dvb_frontend.h"
+
+#define DRV_NAME "pt3_dvb"
+
+/* register idx */
+#define PT3_REG_VERSION	0x00	/*	R	Version		*/
+#define PT3_REG_BUS	0x04	/*	R	Bus		*/
+#define PT3_REG_SYS_W	0x08	/*	W	System		*/
+#define PT3_REG_SYS_R	0x0c	/*	R	System		*/
+#define PT3_REG_I2C_W	0x10	/*	W	I2C		*/
+#define PT3_REG_I2C_R	0x14	/*	R	I2C		*/
+#define PT3_REG_RAM_W	0x18	/*	W	RAM		*/
+#define PT3_REG_RAM_R	0x1c	/*	R	RAM		*/
+#define PT3_REG_BASE	0x40	/* + 0x18*idx			*/
+#define PT3_REG_DMA_D_L	0x00	/*	W	DMA descriptor	*/
+#define PT3_REG_DMA_D_H	0x04	/*	W	DMA descriptor	*/
+#define PT3_REG_DMA_CTL	0x08	/*	W	DMA		*/
+#define PT3_REG_TS_CTL	0x0c	/*	W	TS		*/
+#define PT3_REG_STATUS	0x10	/*	R	DMA/FIFO/TS	*/
+#define PT3_REG_TS_ERR	0x14	/*	R	TS		*/
+
+struct pt3_adapter;
+
+struct pt3_board {
+	struct mutex lock;
+	int lnb;
+	bool reset;
+
+	struct pci_dev *pdev;
+	int bars;
+	void __iomem *bar_reg, *bar_mem;
+	struct i2c_adapter i2c;
+	u8 i2c_buf;
+	u32 i2c_addr;
+	bool i2c_filled;
+
+	struct pt3_adapter **adap;
+};
+
+struct pt3_adapter {
+	struct mutex lock;
+	struct pt3_board *pt3;
+
+	u8 idx;
+	bool sleep;
+	struct pt3_dma *dma;
+	struct task_struct *kthread;
+	struct dvb_adapter dvb;
+	struct dvb_demux demux;
+	struct dmxdev dmxdev;
+	int users, voltage;
+
+	struct dvb_frontend *fe;
+	int (*orig_voltage)(struct dvb_frontend *fe, fe_sec_voltage_t voltage);
+	int (*orig_sleep)(struct dvb_frontend *fe);
+	int (*orig_init)(struct dvb_frontend *fe);
+};
+
+#endif
+
diff --git a/drivers/media/pci/pt3/pt3_dma.c b/drivers/media/pci/pt3/pt3_dma.c
new file mode 100644
index 0000000..a1fb82e
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_dma.c
@@ -0,0 +1,333 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_dma.h"
+
+#define PT3_DMA_MAX_DESCS	204
+#define PT3_DMA_PAGE_SIZE	(PT3_DMA_MAX_DESCS * sizeof(struct pt3_dma_desc))
+#define PT3_DMA_BLOCK_COUNT	17
+#define PT3_DMA_BLOCK_SIZE	(PT3_DMA_PAGE_SIZE * 47)
+#define PT3_DMA_TS_BUF_SIZE	(PT3_DMA_BLOCK_SIZE * PT3_DMA_BLOCK_COUNT)
+#define PT3_DMA_TS_SYNC		0x47
+#define PT3_DMA_TS_NOT_SYNC	0x74
+
+void pt3_dma_free(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *page;
+	u32 i;
+
+	if (dma->ts_info) {
+		for (i = 0; i < dma->ts_count; i++) {
+			page = &dma->ts_info[i];
+			if (page->data)
+				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
+		}
+		kfree(dma->ts_info);
+	}
+	if (dma->desc_info) {
+		for (i = 0; i < dma->desc_count; i++) {
+			page = &dma->desc_info[i];
+			if (page->data)
+				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
+		}
+		kfree(dma->desc_info);
+	}
+	kfree(dma);
+}
+
+struct pt3_dma_desc {
+	u64 page_addr;
+	u32 page_size;
+	u64 next_desc;
+} __packed;
+
+void pt3_dma_build_page_descriptor(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *desc_info, *ts_info;
+	u64 ts_addr, desc_addr;
+	u32 i, j, ts_size, desc_remain, ts_info_pos, desc_info_pos;
+	struct pt3_dma_desc *prev, *curr;
+
+	pr_debug("#%d %s ts_count=%d ts_size=%d desc_count=%d desc_size=%d\n",
+		dma->adap->idx, __func__, dma->ts_count, dma->ts_info[0].size, dma->desc_count, dma->desc_info[0].size);
+	desc_info_pos = ts_info_pos = 0;
+	desc_info = &dma->desc_info[desc_info_pos];
+	desc_addr   = desc_info->addr;
+	desc_remain = desc_info->size;
+	desc_info->data_pos = 0;
+	prev = NULL;
+	curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+	desc_info_pos++;
+
+	for (i = 0; i < dma->ts_count; i++) {
+		if (unlikely(ts_info_pos >= dma->ts_count)) {
+			pr_debug("#%d ts_info overflow max=%d curr=%d\n", dma->adap->idx, dma->ts_count, ts_info_pos);
+			return;
+		}
+		ts_info = &dma->ts_info[ts_info_pos];
+		ts_addr = ts_info->addr;
+		ts_size = ts_info->size;
+		ts_info_pos++;
+		pr_debug("#%d i=%d, ts_info addr=0x%llx ts_size=%d\n", dma->adap->idx, i, ts_addr, ts_size);
+		for (j = 0; j < ts_size / PT3_DMA_PAGE_SIZE; j++) {
+			if (desc_remain < sizeof(struct pt3_dma_desc)) {
+				if (unlikely(desc_info_pos >= dma->desc_count)) {
+					pr_debug("#%d desc_info overflow max=%d curr=%d\n",
+						dma->adap->idx, dma->desc_count, desc_info_pos);
+					return;
+				}
+				desc_info = &dma->desc_info[desc_info_pos];
+				desc_info->data_pos = 0;
+				curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+				pr_debug("#%d desc_info_pos=%d ts_addr=0x%llx remain=%d\n",
+					dma->adap->idx, desc_info_pos, ts_addr, desc_remain);
+				desc_addr = desc_info->addr;
+				desc_remain = desc_info->size;
+				desc_info_pos++;
+			}
+			if (prev)
+				prev->next_desc = desc_addr | 0b10;
+			curr->page_addr = ts_addr           | 0b111;
+			curr->page_size = PT3_DMA_PAGE_SIZE | 0b111;
+			curr->next_desc = 0b10;
+			pr_debug("#%d j=%d dma write desc ts_addr=0x%llx desc_info_pos=%d desc_remain=%d\n",
+				dma->adap->idx, j, ts_addr, desc_info_pos, desc_remain);
+			ts_addr += PT3_DMA_PAGE_SIZE;
+
+			prev = curr;
+			desc_info->data_pos += sizeof(struct pt3_dma_desc);
+			if (unlikely(desc_info->data_pos > desc_info->size)) {
+				pr_debug("#%d dma desc_info data overflow max=%d curr=%d\n",
+					dma->adap->idx, desc_info->size, desc_info->data_pos);
+				return;
+			}
+			curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+			desc_addr += sizeof(struct pt3_dma_desc);
+			desc_remain -= sizeof(struct pt3_dma_desc);
+		}
+	}
+	if (prev)
+		prev->next_desc = dma->desc_info->addr | 0b10;
+}
+
+struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap)
+{
+	struct pt3_dma_page *page;
+	u32 i;
+
+	struct pt3_dma *dma = kzalloc(sizeof(struct pt3_dma), GFP_KERNEL);
+	if (!dma) {
+		pr_debug("#%d fail allocate PT3_DMA\n", adap->idx);
+		goto fail;
+	}
+	dma->adap = adap;
+	dma->enabled = false;
+	mutex_init(&dma->lock);
+
+	dma->ts_count = PT3_DMA_BLOCK_COUNT;
+	dma->ts_info = kzalloc(sizeof(struct pt3_dma_page) * dma->ts_count, GFP_KERNEL);
+	if (!dma->ts_info) {
+		pr_debug("#%d fail allocate TS DMA page\n", adap->idx);
+		goto fail;
+	}
+	pr_debug("#%d Alloc TS buf (ts_count %d)\n", adap->idx, dma->ts_count);
+	for (i = 0; i < dma->ts_count; i++) {
+		page = &dma->ts_info[i];
+		page->size = PT3_DMA_BLOCK_SIZE;
+		page->data_pos = 0;
+		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
+		if (!page->data) {
+			pr_debug("#%d fail alloc_consistent. %d\n", adap->idx, i);
+			goto fail;
+		}
+	}
+
+	dma->desc_count = 1 + (PT3_DMA_TS_BUF_SIZE / PT3_DMA_PAGE_SIZE - 1) / PT3_DMA_MAX_DESCS;
+	dma->desc_info = kzalloc(sizeof(struct pt3_dma_page) * dma->desc_count, GFP_KERNEL);
+	if (!dma->desc_info) {
+		pr_debug("#%d fail allocate Desc DMA page\n", adap->idx);
+		goto fail;
+	}
+	pr_debug("#%d Alloc Descriptor buf (desc_count %d)\n", adap->idx, dma->desc_count);
+	for (i = 0; i < dma->desc_count; i++) {
+		page = &dma->desc_info[i];
+		page->size = PT3_DMA_PAGE_SIZE;
+		page->data_pos = 0;
+		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
+		if (!page->data) {
+			pr_debug("#%d fail alloc_consistent %d\n", adap->idx, i);
+			goto fail;
+		}
+	}
+
+	pr_debug("#%d build page descriptor\n", adap->idx);
+	pt3_dma_build_page_descriptor(dma);
+	return dma;
+fail:
+	if (dma)
+		pt3_dma_free(dma);
+	return NULL;
+}
+
+void __iomem *pt3_dma_get_base_addr(struct pt3_dma *dma)
+{
+	return dma->adap->pt3->bar_reg + PT3_REG_BASE + (0x18 * dma->adap->idx);
+}
+
+void pt3_dma_reset(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *ts;
+	u32 i;
+
+	for (i = 0; i < dma->ts_count; i++) {
+		ts = &dma->ts_info[i];
+		memset(ts->data, 0, ts->size);
+		ts->data_pos = 0;
+		*ts->data = PT3_DMA_TS_NOT_SYNC;
+	}
+	dma->ts_pos = 0;
+}
+
+void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled)
+{
+	void __iomem *base = pt3_dma_get_base_addr(dma);
+	u64 start_addr = dma->desc_info->addr;
+
+	if (enabled) {
+		pr_debug("#%d DMA enable start_addr=%llx\n", dma->adap->idx, start_addr);
+		pt3_dma_reset(dma);
+		writel(1 << 1, base + PT3_REG_DMA_CTL);	/* stop DMA */
+		writel(start_addr         & 0xffffffff, base + PT3_REG_DMA_D_L);
+		writel((start_addr >> 32) & 0xffffffff, base + PT3_REG_DMA_D_H);
+		pr_debug("set descriptor address low %llx\n",  start_addr         & 0xffffffff);
+		pr_debug("set descriptor address high %llx\n", (start_addr >> 32) & 0xffffffff);
+		writel(1 << 0, base + PT3_REG_DMA_CTL);	/* start DMA */
+	} else {
+		pr_debug("#%d DMA disable\n", dma->adap->idx);
+		writel(1 << 1, base + PT3_REG_DMA_CTL);	/* stop DMA */
+		while (1) {
+			if (!(readl(base + PT3_REG_STATUS) & 1))
+				break;
+			msleep_interruptible(1);
+		}
+	}
+	dma->enabled = enabled;
+}
+
+/* convert Gray code to binary, e.g. 1001 -> 1110 */
+static u32 pt3_dma_gray2binary(u32 gray, u32 bit)
+{
+	u32 binary = 0, i, j, k;
+
+	for (i = 0; i < bit; i++) {
+		k = 0;
+		for (j = i; j < bit; j++)
+			k ^= (gray >> j) & 1;
+		binary |= k << i;
+	}
+	return binary;
+}
+
+u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma)
+{
+	return pt3_dma_gray2binary(readl(pt3_dma_get_base_addr(dma) + PT3_REG_TS_ERR), 32);
+}
+
+void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval)
+{
+	void __iomem *base = pt3_dma_get_base_addr(dma);
+	u32 data = mode | initval;
+	pr_debug("#%d %s base=%p data=0x%04x\n", dma->adap->idx, __func__, base, data);
+	writel(data, base + PT3_REG_TS_CTL);
+}
+
+bool pt3_dma_ready(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *ts;
+	u8 *p;
+
+	u32 next = dma->ts_pos + 1;
+	if (next >= dma->ts_count)
+		next = 0;
+	ts = &dma->ts_info[next];
+	p = &ts->data[ts->data_pos];
+
+	if (*p == PT3_DMA_TS_SYNC)
+		return true;
+	if (*p == PT3_DMA_TS_NOT_SYNC)
+		return false;
+
+	pr_debug("#%d invalid sync byte value=0x%02x ts_pos=%d data_pos=%d curr=0x%02x\n",
+		dma->adap->idx, *p, next, ts->data_pos, dma->ts_info[dma->ts_pos].data[0]);
+	return false;
+}
+
+ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux)
+{
+	bool ready;
+	struct pt3_dma_page *ts;
+	u32 i, prev;
+	size_t csize, remain = dma->ts_info[dma->ts_pos].size;
+
+	mutex_lock(&dma->lock);
+	pr_debug("#%d dma_copy ts_pos=0x%x data_pos=0x%x\n",
+		   dma->adap->idx, dma->ts_pos, dma->ts_info[dma->ts_pos].data_pos);
+	for (;;) {
+		for (i = 0; i < 20; i++) {
+			ready = pt3_dma_ready(dma);
+			if (ready)
+				break;
+			msleep_interruptible(30);
+		}
+		if (!ready) {
+			pr_debug("#%d dma_copy NOT READY\n", dma->adap->idx);
+			goto last;
+		}
+		prev = dma->ts_pos - 1;
+		if (prev < 0 || dma->ts_count <= prev)
+			prev = dma->ts_count - 1;
+		if (dma->ts_info[prev].data[0] != PT3_DMA_TS_NOT_SYNC)
+			pr_debug("#%d DMA buffer overflow. prev=%d data=0x%x\n",
+					dma->adap->idx, prev, dma->ts_info[prev].data[0]);
+		ts = &dma->ts_info[dma->ts_pos];
+		for (;;) {
+			csize = (remain < (ts->size - ts->data_pos)) ?
+				 remain : (ts->size - ts->data_pos);
+			dvb_dmx_swfilter(demux, &ts->data[ts->data_pos], csize);
+			remain -= csize;
+			ts->data_pos += csize;
+			if (ts->data_pos >= ts->size) {
+				ts->data_pos = 0;
+				ts->data[ts->data_pos] = PT3_DMA_TS_NOT_SYNC;
+				dma->ts_pos++;
+				if (dma->ts_pos >= dma->ts_count)
+					dma->ts_pos = 0;
+				break;
+			}
+			if (remain <= 0)
+				goto last;
+		}
+	}
+last:
+	mutex_unlock(&dma->lock);
+	return dma->ts_info[dma->ts_pos].size - remain;
+}
+
+u32 pt3_dma_get_status(struct pt3_dma *dma)
+{
+	return readl(pt3_dma_get_base_addr(dma) + PT3_REG_STATUS);
+}
+
diff --git a/drivers/media/pci/pt3/pt3_dma.h b/drivers/media/pci/pt3/pt3_dma.h
new file mode 100644
index 0000000..934c222
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_dma.h
@@ -0,0 +1,50 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_DMA_H__
+#define	__PT3_DMA_H__
+
+#include "pt3_common.h"
+
+struct pt3_dma_page {
+	dma_addr_t addr;
+	u8 *data;
+	u32 size, data_pos;
+};
+
+enum pt3_dma_mode {
+	USE_LFSR = 1 << 16,
+	REVERSE  = 1 << 17,
+	RESET    = 1 << 18,
+};
+
+struct pt3_dma {
+	struct pt3_adapter *adap;
+	bool enabled;
+	u32 ts_pos, ts_count, desc_count;
+	struct pt3_dma_page *ts_info, *desc_info;
+	struct mutex lock;
+};
+
+ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux);
+struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap);
+void pt3_dma_free(struct pt3_dma *dma);
+u32 pt3_dma_get_status(struct pt3_dma *dma);
+u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma);
+void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled);
+void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval);
+
+#endif
diff --git a/drivers/media/pci/pt3/pt3_i2c.c b/drivers/media/pci/pt3/pt3_i2c.c
new file mode 100644
index 0000000..e872ae1
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_i2c.c
@@ -0,0 +1,189 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_i2c.h"
+
+#define PT3_I2C_DATA_OFFSET	2048
+#define PT3_I2C_START_ADDR	0x17fa
+
+enum pt3_i2c_cmd {
+	I_END,
+	I_ADDRESS,
+	I_CLOCK_L,
+	I_CLOCK_H,
+	I_DATA_L,
+	I_DATA_H,
+	I_RESET,
+	I_SLEEP,
+	I_DATA_L_NOP  = 0x08,
+	I_DATA_H_NOP  = 0x0c,
+	I_DATA_H_READ = 0x0d,
+	I_DATA_H_ACK0 = 0x0e,
+	I_DATA_H_ACK1 = 0x0f,
+};
+
+bool pt3_i2c_is_clean(struct pt3_board *pt3)
+{
+	return (readl(pt3->bar_reg + PT3_REG_I2C_R) >> 3) & 1;
+}
+
+void pt3_i2c_reset(struct pt3_board *pt3)
+{
+	writel(1 << 17, pt3->bar_reg + PT3_REG_I2C_W);	/* 0x00020000 */
+}
+
+void pt3_i2c_wait(struct pt3_board *pt3, u32 *status)
+{
+	u32 val;
+
+	while (1) {
+		val = readl(pt3->bar_reg + PT3_REG_I2C_R);
+		if (!(val & 1))				/* sequence stopped */
+			break;
+		msleep_interruptible(1);
+	}
+	if (status)
+		*status = val;				/* I2C register status */
+}
+
+void pt3_i2c_mem_write(struct pt3_board *pt3, u8 data)
+{
+	void __iomem *dst = pt3->bar_mem + PT3_I2C_DATA_OFFSET + pt3->i2c_addr;
+
+	if (pt3->i2c_filled) {
+		pt3->i2c_buf |= data << 4;
+		writeb(pt3->i2c_buf, dst);
+		pt3->i2c_addr++;
+	} else
+		pt3->i2c_buf = data;
+	pt3->i2c_filled ^= true;
+}
+
+void pt3_i2c_start(struct pt3_board *pt3)
+{
+	pt3_i2c_mem_write(pt3, I_DATA_H);
+	pt3_i2c_mem_write(pt3, I_CLOCK_H);
+	pt3_i2c_mem_write(pt3, I_DATA_L);
+	pt3_i2c_mem_write(pt3, I_CLOCK_L);
+}
+
+void pt3_i2c_cmd_write(struct pt3_board *pt3, const u8 *data, u32 size)
+{
+	u32 i, j;
+	u8 byte;
+
+	for (i = 0; i < size; i++) {
+		byte = data[i];
+		for (j = 0; j < 8; j++)
+			pt3_i2c_mem_write(pt3, (byte >> (7 - j)) & 1 ? I_DATA_H_NOP : I_DATA_L_NOP);
+		pt3_i2c_mem_write(pt3, I_DATA_H_ACK0);
+	}
+}
+
+void pt3_i2c_cmd_read(struct pt3_board *pt3, u8 *data, u32 size)
+{
+	u32 i, j;
+
+	for (i = 0; i < size; i++) {
+		for (j = 0; j < 8; j++)
+			pt3_i2c_mem_write(pt3, I_DATA_H_READ);
+		if (i == (size - 1))
+			pt3_i2c_mem_write(pt3, I_DATA_H_NOP);
+		else
+			pt3_i2c_mem_write(pt3, I_DATA_L_NOP);
+	}
+}
+
+void pt3_i2c_stop(struct pt3_board *pt3)
+{
+	pt3_i2c_mem_write(pt3, I_DATA_L);
+	pt3_i2c_mem_write(pt3, I_CLOCK_H);
+	pt3_i2c_mem_write(pt3, I_DATA_H);
+}
+
+int pt3_i2c_flush(struct pt3_board *pt3, bool end, u32 start_addr)
+{
+	u32 status;
+
+	if (end) {
+		pt3_i2c_mem_write(pt3, I_END);
+		if (pt3->i2c_filled)
+			pt3_i2c_mem_write(pt3, I_END);
+	}
+	pt3_i2c_wait(pt3, &status);
+	writel(1 << 16 | start_addr, pt3->bar_reg + PT3_REG_I2C_W);	/* 0x00010000 start sequence */
+	pt3_i2c_wait(pt3, &status);
+	if (status & 0b0110) {						/* ACK status */
+		pr_err("%s failed, status=0x%x\n", __func__, status);
+		return -EIO;
+	}
+	return 0;
+}
+
+u32 pt3_i2c_func(struct i2c_adapter *i2c)
+{
+	return I2C_FUNC_I2C;
+}
+
+int pt3_i2c_xfer(struct i2c_adapter *i2c, struct i2c_msg *msg, int num)
+{
+	struct pt3_board *pt3 = i2c_get_adapdata(i2c);
+	int i, j;
+
+	if (!num)
+		return pt3_i2c_flush(pt3, false, PT3_I2C_START_ADDR);
+	if ((num < 1) || (num > 3) || !msg || msg[0].flags)	/* always write first */
+		return -ENOTSUPP;
+	mutex_lock(&pt3->lock);
+	pt3->i2c_addr = 0;
+	for (i = 0; i < num; i++) {
+		u8 byte = (msg[i].addr << 1) | (msg[i].flags & 1);
+		pt3_i2c_start(pt3);
+		pt3_i2c_cmd_write(pt3, &byte, 1);
+		if (msg[i].flags == I2C_M_RD)
+			pt3_i2c_cmd_read(pt3, msg[i].buf, msg[i].len);
+		else
+			pt3_i2c_cmd_write(pt3, msg[i].buf, msg[i].len);
+	}
+	pt3_i2c_stop(pt3);
+	if (pt3_i2c_flush(pt3, true, 0))
+		num = -EIO;
+	else
+		for (i = 1; i < num; i++)
+			if (msg[i].flags == I2C_M_RD)
+				for (j = 0; j < msg[i].len; j++)
+					msg[i].buf[j] = readb(pt3->bar_mem + PT3_I2C_DATA_OFFSET + j);
+	mutex_unlock(&pt3->lock);
+	return num;
+}
+
+static const struct i2c_algorithm pt3_i2c_algo = {
+	.functionality = pt3_i2c_func,
+	.master_xfer = pt3_i2c_xfer,
+};
+
+int pt3_i2c_add_adapter(struct pt3_board *pt3)
+{
+	struct i2c_adapter *i2c = &pt3->i2c;
+	i2c->algo = &pt3_i2c_algo;
+	i2c->algo_data = NULL;
+	i2c->dev.parent = &pt3->pdev->dev;
+	strcpy(i2c->name, DRV_NAME);
+	i2c_set_adapdata(i2c, pt3);
+	return	i2c_add_adapter(i2c) ||
+		(!pt3_i2c_is_clean(pt3) && pt3_i2c_flush(pt3, false, 0));
+}
+
diff --git a/drivers/media/pci/pt3/pt3_i2c.h b/drivers/media/pci/pt3/pt3_i2c.h
new file mode 100644
index 0000000..8424fd5
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_i2c.h
@@ -0,0 +1,25 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_I2C_H__
+#define	__PT3_I2C_H__
+
+#include "pt3_common.h"
+
+void pt3_i2c_reset(struct pt3_board *pt3);
+int pt3_i2c_add_adapter(struct pt3_board *pt3);
+
+#endif
diff --git a/drivers/media/pci/pt3/pt3_pci.c b/drivers/media/pci/pt3/pt3_pci.c
new file mode 100644
index 0000000..858c30d
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_pci.c
@@ -0,0 +1,397 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCIE bridge Altera Cyclone IV FPGA EP4CGX15BF14C8N
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_dma.h"
+#include "pt3_i2c.h"
+#include "tc90522.h"
+#include "qm1d1c0042.h"
+#include "mxl301rf.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 DVB Driver");
+MODULE_LICENSE("GPL");
+
+static struct pci_device_id pt3_id_table[] = {
+	{ PCI_DEVICE(0x1172, 0x4c15) },
+	{ },
+};
+MODULE_DEVICE_TABLE(pci, pt3_id_table);
+
+static int lnb = 2;
+module_param(lnb, int, 0);
+MODULE_PARM_DESC(lnb, "LNB level (0:OFF 1:+11V 2:+15V)");
+
+struct pt3_lnb {
+	u32 bits;
+	char *str;
+};
+
+static const struct pt3_lnb pt3_lnb[] = {
+	{0b1100,  "0V"},
+	{0b1101, "11V"},
+	{0b1111, "15V"},
+};
+
+struct pt3_cfg {
+	fe_delivery_system_t type;
+	u8 addr_tuner, addr_demod;
+};
+
+static const struct pt3_cfg pt3_cfg[] = {
+	{SYS_ISDBS, 0x63, 0b00010001},
+	{SYS_ISDBS, 0x60, 0b00010011},
+	{SYS_ISDBT, 0x62, 0b00010000},
+	{SYS_ISDBT, 0x61, 0b00010010},
+};
+#define PT3_ADAPN ARRAY_SIZE(pt3_cfg)
+
+int pt3_update_lnb(struct pt3_board *pt3)
+{
+	u8 i, lnb_eff = 0;
+
+	if (pt3->reset) {
+		writel(pt3_lnb[0].bits, pt3->bar_reg + PT3_REG_SYS_W);
+		pt3->reset = false;
+		pt3->lnb = 0;
+	} else {
+		struct pt3_adapter *adap;
+		for (i = 0; i < PT3_ADAPN; i++) {
+			adap = pt3->adap[i];
+			pr_debug("#%d sleep %d\n", adap->idx, adap->sleep);
+			if ((pt3_cfg[i].type == SYS_ISDBS) && (!adap->sleep))
+				lnb_eff |= adap->voltage ? adap->voltage : lnb;
+		}
+		if (unlikely(lnb_eff < 0 || 2 < lnb_eff)) {
+			pr_err("Inconsistent LNB settings\n");
+			return -EINVAL;
+		}
+		if (pt3->lnb != lnb_eff) {
+			writel(pt3_lnb[lnb_eff].bits, pt3->bar_reg + PT3_REG_SYS_W);
+			pt3->lnb = lnb_eff;
+		}
+	}
+	pr_debug("LNB=%s\n", pt3_lnb[lnb_eff].str);
+	return 0;
+}
+
+int pt3_thread(void *data)
+{
+	size_t ret;
+	struct pt3_adapter *adap = data;
+
+	pr_debug("#%d %s sleep %d\n", adap->idx, __func__, adap->sleep);
+	set_freezable();
+	while (!kthread_should_stop()) {
+		try_to_freeze();
+		while ((ret = pt3_dma_copy(adap->dma, &adap->demux)) > 0)
+			;
+		if (ret < 0) {
+			pr_debug("#%d fail dma_copy\n", adap->idx);
+			msleep_interruptible(1);
+		}
+	}
+	return 0;
+}
+
+int pt3_start_feed(struct dvb_demux_feed *feed)
+{
+	int ret = 0;
+	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
+	pr_debug("#%d %s sleep %d\n", adap->idx, __func__, adap->sleep);
+	if (!adap->users++) {
+		pr_debug("#%d %s selected, DMA %s\n",
+			adap->idx, (pt3_cfg[adap->idx].type == SYS_ISDBS) ? "S" : "T",
+			pt3_dma_get_status(adap->dma) & 1 ? "ON" : "OFF");
+		mutex_lock(&adap->lock);
+		if (!adap->kthread) {
+			adap->kthread = kthread_run(pt3_thread, adap, DRV_NAME "_%d", adap->idx);
+			if (IS_ERR(adap->kthread)) {
+				ret = PTR_ERR(adap->kthread);
+				adap->kthread = NULL;
+			} else {
+				pt3_dma_set_test_mode(adap->dma, RESET, 0);	/* reset error count */
+				pt3_dma_set_enabled(adap->dma, true);
+			}
+		}
+		mutex_unlock(&adap->lock);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+int pt3_stop_feed(struct dvb_demux_feed *feed)
+{
+	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
+	pr_debug("#%d %s sleep %d\n", adap->idx, __func__, adap->sleep);
+	if (!--adap->users) {
+		mutex_lock(&adap->lock);
+		if (adap->kthread) {
+			pt3_dma_set_enabled(adap->dma, false);
+			pr_debug("#%d DMA ts_err packet cnt %d\n",
+				adap->idx, pt3_dma_get_ts_error_packet_count(adap->dma));
+			kthread_stop(adap->kthread);
+			adap->kthread = NULL;
+		}
+		mutex_unlock(&adap->lock);
+		msleep_interruptible(40);
+	}
+	return 0;
+}
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+struct pt3_adapter *pt3_dvb_register_adapter(struct pt3_board *pt3)
+{
+	int ret;
+	struct dvb_adapter *dvb;
+	struct dvb_demux *demux;
+	struct dmxdev *dmxdev;
+	struct pt3_adapter *adap = kzalloc(sizeof(struct pt3_adapter), GFP_KERNEL);
+	if (!adap)
+		return ERR_PTR(-ENOMEM);
+
+	adap->pt3 = pt3;
+	adap->sleep = true;
+
+	dvb = &adap->dvb;
+	dvb->priv = adap;
+	ret = dvb_register_adapter(dvb, DRV_NAME, THIS_MODULE, &pt3->pdev->dev, adapter_nr);
+	pr_debug("adapter%d registered\n", ret);
+	if (ret >= 0) {
+		demux = &adap->demux;
+		demux->dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING;
+		demux->priv = adap;
+		demux->feednum = 256;
+		demux->filternum = 256;
+		demux->start_feed = pt3_start_feed;
+		demux->stop_feed = pt3_stop_feed;
+		demux->write_to_decoder = NULL;
+		ret = dvb_dmx_init(demux);
+		if (ret >= 0) {
+			dmxdev = &adap->dmxdev;
+			dmxdev->filternum = 256;
+			dmxdev->demux = &demux->dmx;
+			dmxdev->capabilities = 0;
+			ret = dvb_dmxdev_init(dmxdev, dvb);
+			if (ret >= 0)
+				return adap;
+			dvb_dmx_release(demux);
+		}
+		dvb_unregister_adapter(dvb);
+	}
+	kfree(adap);
+	return ERR_PTR(ret);
+}
+
+int pt3_sleep(struct dvb_frontend *fe)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	pr_debug("#%d %s orig %p\n", adap->idx, __func__, adap->orig_sleep);
+	adap->sleep = true;
+	pt3_update_lnb(adap->pt3);
+	return (adap->orig_sleep) ? adap->orig_sleep(fe) : 0;
+}
+
+int pt3_wakeup(struct dvb_frontend *fe)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	pr_debug("#%d %s orig %p\n", adap->idx, __func__, adap->orig_init);
+	adap->sleep = false;
+	pt3_update_lnb(adap->pt3);
+	return (adap->orig_init) ? adap->orig_init(fe) : 0;
+}
+
+int pt3_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	adap->voltage = voltage == SEC_VOLTAGE_18 ? 2 : voltage == SEC_VOLTAGE_13 ? 1 : 0;
+	return (adap->orig_voltage) ? adap->orig_voltage(fe, voltage) : 0;
+}
+
+void pt3_cleanup_adapter(struct pt3_adapter *adap)
+{
+	if (!adap)
+		return;
+	if (adap->kthread)
+		kthread_stop(adap->kthread);
+	if (adap->fe) {
+		dvb_unregister_frontend(adap->fe);
+		adap->fe->ops.release(adap->fe);
+	}
+	if (adap->dma) {
+		if (adap->dma->enabled)
+			pt3_dma_set_enabled(adap->dma, false);
+		pt3_dma_free(adap->dma);
+	}
+	adap->demux.dmx.close(&adap->demux.dmx);
+	dvb_dmxdev_release(&adap->dmxdev);
+	dvb_dmx_release(&adap->demux);
+	dvb_unregister_adapter(&adap->dvb);
+	kfree(adap);
+}
+
+void pt3_remove(struct pci_dev *pdev)
+{
+	int i;
+	struct pt3_board *pt3 = pci_get_drvdata(pdev);
+
+	if (pt3) {
+		pt3->reset = true;
+		pt3_update_lnb(pt3);
+		for (i = 0; i < PT3_ADAPN; i++)
+			pt3_cleanup_adapter(pt3->adap[i]);
+		pt3_i2c_reset(pt3);
+		i2c_del_adapter(&pt3->i2c);
+		if (pt3->bar_mem)
+			iounmap(pt3->bar_mem);
+		if (pt3->bar_reg)
+			iounmap(pt3->bar_reg);
+		pci_release_selected_regions(pdev, pt3->bars);
+		kfree(pt3->adap);
+		kfree(pt3);
+	}
+	pci_disable_device(pdev);
+}
+
+int pt3_abort(struct pci_dev *pdev, int ret, char *fmt, ...)
+{
+	va_list ap;
+	char *s = NULL;
+	int slen;
+
+	va_start(ap, fmt);
+	slen = vsnprintf(s, 0, fmt, ap);
+	s = vzalloc(slen);
+	if (slen > 0 && s) {
+		vsnprintf(s, slen, fmt, ap);
+		dev_err(&pdev->dev, "%s", s);
+		vfree(s);
+	}
+	va_end(ap);
+	pt3_remove(pdev);
+	return ret;
+}
+
+int pt3_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+	struct pt3_board *pt3;
+	struct pt3_adapter *adap;
+	const struct pt3_cfg *cfg = pt3_cfg;
+	struct dvb_frontend *fe[PT3_ADAPN];
+	int i, err, bars = pci_select_bars(pdev, IORESOURCE_MEM);
+
+	err = pci_enable_device(pdev)					||
+		pci_set_dma_mask(pdev, DMA_BIT_MASK(64))		||
+		pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64))	||
+		pci_read_config_dword(pdev, PCI_CLASS_REVISION, &i)	||
+		pci_request_selected_regions(pdev, bars, DRV_NAME);
+	if (err)
+		return pt3_abort(pdev, err, "PCI/DMA error\n");
+	if ((i & 0xFF) != 1)
+		return pt3_abort(pdev, -EINVAL, "Revision 0x%x is not supported\n", i & 0xFF);
+
+	pci_set_master(pdev);
+	err = pci_save_state(pdev);
+	if (err)
+		return pt3_abort(pdev, err, "Failed pci_save_state\n");
+	pt3 = kzalloc(sizeof(struct pt3_board), GFP_KERNEL);
+	if (!pt3)
+		return pt3_abort(pdev, -ENOMEM, "struct pt3_board out of memory\n");
+	pt3->adap = kzalloc(PT3_ADAPN * sizeof(struct pt3_adapter *), GFP_KERNEL);
+	if (!pt3->adap)
+		return pt3_abort(pdev, -ENOMEM, "No memory for *adap\n");
+
+	pt3->bars = bars;
+	pt3->pdev = pdev;
+	pci_set_drvdata(pdev, pt3);
+	pt3->bar_reg = pci_ioremap_bar(pdev, 0);
+	pt3->bar_mem = pci_ioremap_bar(pdev, 2);
+	if (!pt3->bar_reg || !pt3->bar_mem)
+		return pt3_abort(pdev, -EIO, "Failed pci_ioremap_bar\n");
+
+	err = readl(pt3->bar_reg + PT3_REG_VERSION);
+	i = ((err >> 24) & 0xFF);
+	if (i != 3)
+		return pt3_abort(pdev, -EIO, "ID=0x%x, not a PT3\n", i);
+	i = ((err >>  8) & 0xFF);
+	if (i != 4)
+		return pt3_abort(pdev, -EIO, "FPGA version 0x%x is not supported\n", i);
+	err = pt3_i2c_add_adapter(pt3);
+	if (err < 0)
+		return pt3_abort(pdev, err, "Cannot add I2C\n");
+	mutex_init(&pt3->lock);
+
+	for (i = 0; i < PT3_ADAPN; i++) {
+		adap = pt3_dvb_register_adapter(pt3);
+		if (IS_ERR(adap))
+			return pt3_abort(pdev, PTR_ERR(adap), "Failed pt3_dvb_register_adapter\n");
+		adap->idx = i;
+		adap->dma = pt3_dma_create(adap);
+		if (!adap->dma)
+			return pt3_abort(pdev, -ENOMEM, "Failed pt3_dma_create\n");
+		pt3->adap[i] = adap;
+		adap->sleep = true;
+		mutex_init(&adap->lock);
+	}
+
+	for (i = 0; i < PT3_ADAPN; i++) {
+		fe[i] = tc90522_attach(&pt3->i2c, i, cfg[i].type, cfg[i].addr_demod);
+		if (!fe[i] || (cfg[i].type == SYS_ISDBS ?
+			qm1d1c0042_attach(fe[i], i, cfg[i].addr_tuner) : mxl301rf_attach(fe[i], i, cfg[i].addr_tuner))) {
+			while (i--)
+				fe[i]->ops.release(fe[i]);
+			return pt3_abort(pdev, -ENOMEM, "Cannot attach frontend\n");
+		}
+	}
+	fe[i-1]->ops.init(fe[i-1]);	/* power on tuner & amp */
+
+	for (i = 0; i < PT3_ADAPN; i++) {
+		struct pt3_adapter *adap = pt3->adap[i];
+		pr_debug("#%d %s\n", i, __func__);
+
+		adap->orig_voltage     = fe[i]->ops.set_voltage;
+		adap->orig_sleep       = fe[i]->ops.sleep;
+		adap->orig_init        = fe[i]->ops.init;
+		fe[i]->ops.set_voltage = pt3_set_voltage;
+		fe[i]->ops.sleep       = pt3_sleep;
+		fe[i]->ops.init        = pt3_wakeup;
+		if ((adap->orig_init(fe[i]) && adap->orig_init(fe[i]) && adap->orig_init(fe[i])) ||
+			adap->orig_sleep(fe[i]) || dvb_register_frontend(&adap->dvb, fe[i])) {
+			while (i--)
+				dvb_unregister_frontend(fe[i]);
+			for (i = 0; i < PT3_ADAPN; i++) {
+				fe[i]->ops.release(fe[i]);
+				adap->fe = NULL;
+			}
+			return pt3_abort(pdev, -EREMOTEIO, "Cannot register frontend\n");
+		}
+		adap->fe = fe[i];
+	}
+	pt3->reset = true;
+	pt3_update_lnb(pt3);
+	return 0;
+}
+
+static struct pci_driver pt3_driver = {
+	.name		= DRV_NAME,
+	.probe		= pt3_probe,
+	.remove		= pt3_remove,
+	.id_table	= pt3_id_table,
+};
+
+module_pci_driver(pt3_driver);
+
diff --git a/drivers/media/tuners/Kconfig b/drivers/media/tuners/Kconfig
index a128488..8ca8ed7 100644
--- a/drivers/media/tuners/Kconfig
+++ b/drivers/media/tuners/Kconfig
@@ -243,4 +243,19 @@ config MEDIA_TUNER_R820T
 	default m if !MEDIA_SUBDRV_AUTOSELECT
 	help
 	  Rafael Micro R820T silicon tuner driver.
+
+config MEDIA_TUNER_MXL301RF
+	tristate "MaxLinear MXL301RF ISDB-T tuner"
+	depends on MEDIA_SUPPORT && I2C
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  MaxLinear MxL301RF ISDB-T tuner driver for PT3.
+
+config MEDIA_TUNER_QM1D1C0042
+	tristate "Sharp QM1D1C0042 ISDB-S tuner"
+	depends on MEDIA_SUPPORT && I2C
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Sharp QM1D1C0042 ISDB-S tuner driver for PT3 PCIE card.
+
 endmenu
diff --git a/drivers/media/tuners/Makefile b/drivers/media/tuners/Makefile
index efe82a9..b98c988 100644
--- a/drivers/media/tuners/Makefile
+++ b/drivers/media/tuners/Makefile
@@ -37,6 +37,8 @@ obj-$(CONFIG_MEDIA_TUNER_FC0012) += fc0012.o
 obj-$(CONFIG_MEDIA_TUNER_FC0013) += fc0013.o
 obj-$(CONFIG_MEDIA_TUNER_IT913X) += tuner_it913x.o
 obj-$(CONFIG_MEDIA_TUNER_R820T) += r820t.o
+obj-$(CONFIG_MEDIA_TUNER_MXL301RF) += mxl301rf.o
+obj-$(CONFIG_MEDIA_TUNER_QM1D1C0042) += qm1d1c0042.o
 
 ccflags-y += -I$(srctree)/drivers/media/dvb-core
 ccflags-y += -I$(srctree)/drivers/media/dvb-frontends
diff --git a/drivers/media/tuners/mxl301rf.c b/drivers/media/tuners/mxl301rf.c
new file mode 100644
index 0000000..cc60831
--- /dev/null
+++ b/drivers/media/tuners/mxl301rf.c
@@ -0,0 +1,390 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-T tuner MaxLinear CMOS Hybrid TV MxL301RF
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "mxl301rf.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 MxL301RF MaxLinear CMOS Hybrid TV ISDB-T tuner driver");
+MODULE_LICENSE("GPL");
+
+struct mxl301rf {
+	struct dvb_frontend *fe;
+	u8 addr_tuner, idx;
+	u32 freq;
+};
+
+struct shf_dvbt {
+	u32	freq,		/* Channel center frequency @ kHz	*/
+		freq_th;	/* Offset frequency threshold @ kHz	*/
+	u8	shf_val,	/* Spur shift value			*/
+		shf_dir;	/* Spur shift direction			*/
+};
+
+static const struct shf_dvbt shf_dvbt_tab[] = {
+	{  64500, 500, 0x92, 0x07 },
+	{ 191500, 300, 0xe2, 0x07 },
+	{ 205500, 500, 0x2c, 0x04 },
+	{ 212500, 500, 0x1e, 0x04 },
+	{ 226500, 500, 0xd4, 0x07 },
+	{  99143, 500, 0x9c, 0x07 },
+	{ 173143, 500, 0xd4, 0x07 },
+	{ 191143, 300, 0xd4, 0x07 },
+	{ 207143, 500, 0xce, 0x07 },
+	{ 225143, 500, 0xce, 0x07 },
+	{ 243143, 500, 0xd4, 0x07 },
+	{ 261143, 500, 0xd4, 0x07 },
+	{ 291143, 500, 0xd4, 0x07 },
+	{ 339143, 500, 0x2c, 0x04 },
+	{ 117143, 500, 0x7a, 0x07 },
+	{ 135143, 300, 0x7a, 0x07 },
+	{ 153143, 500, 0x01, 0x07 }
+};
+
+static const u32 mxl301rf_rf_tab[112] = {
+	0x058d3f49, 0x05e8ccc9, 0x06445a49, 0x069fe7c9, 0x06fb7549,
+	0x075702c9, 0x07b29049, 0x080e1dc9, 0x0869ab49, 0x08c538c9,
+	0x0920c649, 0x097c53c9, 0x09f665c9, 0x0a51f349, 0x0aad80c9,
+	0x0b090e49, 0x0b649bc9, 0x0ba1a4c9, 0x0bfd3249, 0x0c58bfc9,
+	0x0cb44d49, 0x0d0fdac9, 0x0d6b6849, 0x0dc6f5c9, 0x0e228349,
+	0x0e7e10c9, 0x0ed99e49, 0x0f352bc9, 0x0f90b949, 0x0fec46c9,
+	0x1047d449, 0x10a361c9, 0x10feef49, 0x115a7cc9, 0x11b60a49,
+	0x121197c9, 0x126d2549, 0x12c8b2c9, 0x13244049, 0x137fcdc9,
+	0x13db5b49, 0x1436e8c9, 0x14927649, 0x14ee03c9, 0x15499149,
+	0x15a51ec9, 0x1600ac49, 0x165c39c9, 0x16b7c749, 0x171354c9,
+	0x176ee249, 0x17ca6fc9, 0x1825fd49, 0x18818ac9, 0x18dd1849,
+	0x1938a5c9, 0x19943349, 0x19efc0c9, 0x1a4b4e49, 0x1aa6dbc9,
+	0x1b026949, 0x1b5df6c9, 0x1bb98449, 0x1c339649, 0x1c8f23c9,
+	0x1ceab149, 0x1d463ec9, 0x1da1cc49, 0x1dfd59c9, 0x1e58e749,
+	0x1eb474c9, 0x1f100249, 0x1f6b8fc9, 0x1fc71d49, 0x2022aac9,
+	0x207e3849, 0x20d9c5c9, 0x21355349, 0x2190e0c9, 0x21ec6e49,
+	0x2247fbc9, 0x22a38949, 0x22ff16c9, 0x235aa449, 0x23b631c9,
+	0x2411bf49, 0x246d4cc9, 0x24c8da49, 0x252467c9, 0x257ff549,
+	0x25db82c9, 0x26371049, 0x26929dc9, 0x26ee2b49, 0x2749b8c9,
+	0x27a54649, 0x2800d3c9, 0x285c6149, 0x28b7eec9, 0x29137c49,
+	0x296f09c9, 0x29ca9749, 0x2a2624c9, 0x2a81b249, 0x2add3fc9,
+	0x2b38cd49, 0x2b945ac9, 0x2befe849, 0x2c4b75c9, 0x2ca70349,
+	0x2d0290c9, 0x2d5e1e49,
+};
+#define MXL301RF_NHK (mxl301rf_rf_tab[77])	/* 日本放送協会 Nippon Hōsō Kyōkai, Japan Broadcasting Corporation */
+
+int mxl301rf_freq(int freq)
+{
+	if (freq >= 90000000)
+		return freq;					/* real_freq Hz	*/
+	if (freq > 255)
+		return MXL301RF_NHK;
+	if (freq > 127)
+		return mxl301rf_rf_tab[freq - 128];		/* freqno (IO#)	*/
+	if (freq > 63) {					/* CATV		*/
+		freq -= 64;
+		if (freq > 22)
+			return mxl301rf_rf_tab[freq - 1];	/* C23-C62	*/
+		if (freq > 12)
+			return mxl301rf_rf_tab[freq - 10];	/* C13-C22	*/
+		return MXL301RF_NHK;
+	}
+	if (freq > 62)
+		return MXL301RF_NHK;
+	if (freq > 12)
+		return mxl301rf_rf_tab[freq + 50];		/* 13-62	*/
+	if (freq >  3)
+		return mxl301rf_rf_tab[freq +  9];		/*  4-12	*/
+	if (freq)
+		return mxl301rf_rf_tab[freq -  1];		/*  1-3		*/
+	return MXL301RF_NHK;
+}
+
+void mxl301rf_rftune(u8 *data, u32 *size, u32 freq)
+{
+	u32 dig_rf_freq, tmp, frac_divider, kHz, MHz, i;
+	u8 rf_data[] = {
+		0x13, 0x00,	/* abort tune			*/
+		0x3b, 0xc0,
+		0x3b, 0x80,
+		0x10, 0x95,	/* BW				*/
+		0x1a, 0x05,
+		0x61, 0x00,
+		0x62, 0xa0,
+		0x11, 0x40,	/* 2 bytes to store RF freq.	*/
+		0x12, 0x0e,	/* 2 bytes to store RF freq.	*/
+		0x13, 0x01	/* start tune			*/
+	};
+
+	freq = mxl301rf_freq(freq);
+	dig_rf_freq = 0;
+	tmp = 0;
+	frac_divider = 1000000;
+	kHz = 1000;
+	MHz = 1000000;
+
+	dig_rf_freq = freq / MHz;
+	tmp = freq % MHz;
+
+	for (i = 0; i < 6; i++) {
+		dig_rf_freq <<= 1;
+		frac_divider /= 2;
+		if (tmp > frac_divider) {
+			tmp -= frac_divider;
+			dig_rf_freq++;
+		}
+	}
+	if (tmp > 7812)
+		dig_rf_freq++;
+
+	rf_data[2 * (7) + 1] = (u8)(dig_rf_freq);
+	rf_data[2 * (8) + 1] = (u8)(dig_rf_freq >> 8);
+
+	for (i = 0; i < ARRAY_SIZE(shf_dvbt_tab); i++) {
+		if ((freq >= (shf_dvbt_tab[i].freq - shf_dvbt_tab[i].freq_th) * kHz) &&
+				(freq <= (shf_dvbt_tab[i].freq + shf_dvbt_tab[i].freq_th) * kHz)) {
+			rf_data[2 * (5) + 1] = shf_dvbt_tab[i].shf_val;
+			rf_data[2 * (6) + 1] = 0xa0 | shf_dvbt_tab[i].shf_dir;
+			break;
+		}
+	}
+	memcpy(data, rf_data, sizeof(rf_data));
+	*size = sizeof(rf_data);
+
+	pr_debug("mx_rftune freq=%d\n", freq);
+}
+
+/* write via demod */
+int mxl301rf_fe_write_data(struct dvb_frontend *fe, u8 addr_data, const u8 *data, int len)
+{
+	u8 buf[len + 1];
+
+	buf[0] = addr_data;
+	memcpy(buf + 1, data, len);
+	return fe->ops.write(fe, buf, len + 1);
+}
+
+#define MXL301RF_FE_PASSTHROUGH 0xfe
+
+int mxl301rf_fe_write_tuner(struct dvb_frontend *fe, const u8 *data, int len)
+{
+	u8 buf[len + 2];
+
+	buf[0] = MXL301RF_FE_PASSTHROUGH;
+	buf[1] = ((struct mxl301rf *)fe->tuner_priv)->addr_tuner << 1;
+	memcpy(buf + 2, data, len);
+	return fe->ops.write(fe, buf, len + 2);
+}
+
+/* read via demod */
+void mxl301rf_fe_read(struct dvb_frontend *fe, u8 addr, u8 *data)
+{
+	const u8 wbuf[2] = {0xfb, addr};
+	int ret;
+
+	mxl301rf_fe_write_tuner(fe, wbuf, sizeof(wbuf));
+	ret = fe->ops.write(fe, NULL, (1 << 16) | (((struct mxl301rf *)fe->tuner_priv)->addr_tuner << 8) | addr);
+	if (ret >= 0)
+		*data = ret;
+}
+
+void mxl301rf_idac_setting(struct dvb_frontend *fe)
+{
+	const u8 idac[] = {
+		0x0d, 0x00,
+		0x0c, 0x67,
+		0x6f, 0x89,
+		0x70, 0x0c,
+		0x6f, 0x8a,
+		0x70, 0x0e,
+		0x6f, 0x8b,
+		0x70, 0x10+12,
+	};
+	mxl301rf_fe_write_tuner(fe, idac, sizeof(idac));
+}
+
+void mxl301rf_set_register(struct dvb_frontend *fe, u8 addr, u8 value)
+{
+	const u8 data[2] = {addr, value};
+	mxl301rf_fe_write_tuner(fe, data, sizeof(data));
+}
+
+int mxl301rf_write_imsrst(struct dvb_frontend *fe)
+{
+	u8 data = 0x01 << 6;
+	return mxl301rf_fe_write_data(fe, 0x01, &data, 1);
+}
+
+enum mxl301rf_agc {
+	MXL301RF_AGC_AUTO,
+	MXL301RF_AGC_MANUAL,
+};
+
+int mxl301rf_set_agc(struct dvb_frontend *fe, enum mxl301rf_agc agc)
+{
+	u8 data = (agc == MXL301RF_AGC_AUTO) ? 0x40 : 0x00;
+	int ret = mxl301rf_fe_write_data(fe, 0x25, &data, 1);
+	if (ret)
+		return ret;
+
+	data = 0x4c | ((agc == MXL301RF_AGC_AUTO) ? 0x00 : 0x01);
+	return	mxl301rf_fe_write_data(fe, 0x23, &data, 1) ||
+		mxl301rf_write_imsrst(fe);
+}
+
+int mxl301rf_sleep(struct dvb_frontend *fe)
+{
+	u8 buf = (1 << 7) | (1 << 4);
+	const u8 data[4] = {0x01, 0x00, 0x13, 0x00};
+	int err = mxl301rf_set_agc(fe, MXL301RF_AGC_MANUAL);
+	if (err)
+		return err;
+	mxl301rf_fe_write_tuner(fe, data, sizeof(data));
+	return mxl301rf_fe_write_data(fe, 0x03, &buf, 1);
+}
+
+static const u8 mxl301rf_freq_tab[][3] = {
+	{   2, 0,  3 },
+	{  12, 1, 22 },
+	{  21, 0, 12 },
+	{  62, 1, 63 },
+	{ 112, 0, 62 }
+};
+
+bool mxl301rf_rfsynth_locked(struct dvb_frontend *fe)
+{
+	u8 data;
+
+	mxl301rf_fe_read(fe, 0x16, &data);
+	return (data & 0x0c) == 0x0c;
+}
+
+bool mxl301rf_refsynth_locked(struct dvb_frontend *fe)
+{
+	u8 data;
+
+	mxl301rf_fe_read(fe, 0x16, &data);
+	return (data & 0x03) == 0x03;
+}
+
+bool mxl301rf_locked(struct dvb_frontend *fe)
+{
+	bool locked1 = false, locked2 = false;
+	unsigned long timeout = jiffies + msecs_to_jiffies(100);
+
+	while (time_before(jiffies, timeout)) {
+		locked1 = mxl301rf_rfsynth_locked(fe);
+		locked2 = mxl301rf_refsynth_locked(fe);
+		if (locked1 && locked2)
+			break;
+		msleep_interruptible(1);
+	}
+	pr_debug("#%d %s lock1=%d lock2=%d\n", ((struct mxl301rf *)fe->tuner_priv)->idx, __func__, locked1, locked2);
+	return locked1 && locked2 ? !mxl301rf_set_agc(fe, MXL301RF_AGC_AUTO) : false;
+}
+
+int mxl301rf_tuner_rftune(struct dvb_frontend *fe, u32 freq)
+{
+	struct mxl301rf *mx = fe->tuner_priv;
+	u8 data[100];
+	u32 size = 0;
+	int err = mxl301rf_set_agc(fe, MXL301RF_AGC_MANUAL);
+	if (err)
+		return err;
+
+	mx->freq = freq;
+	mxl301rf_rftune(data, &size, freq);
+	if (size != 20) {
+		pr_debug("fail mx_rftune size = %d\n", size);
+		return -EINVAL;
+	}
+	mxl301rf_fe_write_tuner(fe, data, 14);
+	msleep_interruptible(1);
+	mxl301rf_fe_write_tuner(fe, data + 14, 6);
+	msleep_interruptible(1);
+	mxl301rf_set_register(fe, 0x1a, 0x0d);
+	mxl301rf_idac_setting(fe);
+
+	return mxl301rf_locked(fe) ? 0 : -ETIMEDOUT;
+}
+
+int mxl301rf_wakeup(struct dvb_frontend *fe)
+{
+	struct mxl301rf *mx = fe->tuner_priv;
+	int err;
+	u8 buf = (1 << 7) | (0 << 4);
+	const u8 data[2] = {0x01, 0x01};
+
+	err = mxl301rf_fe_write_data(fe, 0x03, &buf, 1);
+	if (err)
+		return err;
+	mxl301rf_fe_write_tuner(fe, data, sizeof(data));
+	mxl301rf_tuner_rftune(fe, mx->freq);
+	return 0;
+}
+
+void mxl301rf_ch2freq(u32 channel, bool *catv, u32 *number, u32 *freq)
+{
+	u32 i;
+	s32 freq_offset = 0;
+
+	if (12 <= channel)
+		freq_offset += 2;
+	if (17 <= channel)
+		freq_offset -= 2;
+	if (63 <= channel)
+		freq_offset += 2;
+	*freq = 93 + channel * 6 + freq_offset;
+
+	for (i = 0; i < ARRAY_SIZE(mxl301rf_freq_tab); i++) {
+		if (channel <= mxl301rf_freq_tab[i][0]) {
+			*catv = mxl301rf_freq_tab[i][1] ? true : false;
+			*number = channel + mxl301rf_freq_tab[i][2] - mxl301rf_freq_tab[i][0];
+			break;
+		}
+	}
+}
+
+int mxl301rf_release(struct dvb_frontend *fe)
+{
+	kfree(fe->tuner_priv);
+	fe->tuner_priv = NULL;
+	return 0;
+}
+
+static struct dvb_tuner_ops mxl301rf_ops = {
+	.info = {
+		.frequency_min	= 1,		/* actually 90 MHz, freq below that is handled as ch */
+		.frequency_max	= 770000000,	/* Hz */
+		.frequency_step	= 142857,
+	},
+	.set_frequency = mxl301rf_tuner_rftune,
+	.sleep = mxl301rf_sleep,
+	.init = mxl301rf_wakeup,
+	.release = mxl301rf_release,
+};
+
+int mxl301rf_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
+{
+	u8 d[] = { 0x10, 0x01 };
+	struct mxl301rf *mx = kzalloc(sizeof(struct mxl301rf), GFP_KERNEL);
+	if (!mx)
+		return -ENOMEM;
+	fe->tuner_priv = mx;
+	mx->fe = fe;
+	mx->idx = idx;
+	mx->addr_tuner = addr_tuner;
+	memcpy(&fe->ops.tuner_ops, &mxl301rf_ops, sizeof(struct dvb_tuner_ops));
+
+	return	mxl301rf_fe_write_data(fe, 0x1c, d, 1)	||
+		mxl301rf_fe_write_data(fe, 0x1d, d+1, 1);
+}
+EXPORT_SYMBOL(mxl301rf_attach);
+
diff --git a/drivers/media/tuners/mxl301rf.h b/drivers/media/tuners/mxl301rf.h
new file mode 100644
index 0000000..22599b9
--- /dev/null
+++ b/drivers/media/tuners/mxl301rf.h
@@ -0,0 +1,36 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-T tuner MaxLinear CMOS Hybrid TV MxL301RF
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 __MXL301RF_H__
+#define __MXL301RF_H__
+
+#ifndef pr_fmt
+ #define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+#endif
+#include "dvb_frontend.h"
+
+#if IS_ENABLED(CONFIG_MEDIA_TUNER_MXL301RF)
+extern int mxl301rf_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner);
+#else
+static inline int mxl301rf_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
+{
+	pr_warn("%s: driver disabled by Kconfig\n", __func__);
+	return 0;
+}
+#endif
+
+#endif
+
diff --git a/drivers/media/tuners/qm1d1c0042.c b/drivers/media/tuners/qm1d1c0042.c
new file mode 100644
index 0000000..3be552f
--- /dev/null
+++ b/drivers/media/tuners/qm1d1c0042.c
@@ -0,0 +1,382 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-S tuner driver QM1D1C0042
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "qm1d1c0042.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 QM1D1C0042 ISDB-S tuner driver");
+MODULE_LICENSE("GPL");
+
+struct qm1d1c0042 {
+	struct dvb_frontend *fe;
+	u8 addr_tuner, idx, reg[32];
+	u32 freq;
+};
+
+static const u8 qm1d1c0042_reg_rw[] = {
+	0x48, 0x1c, 0xa0, 0x10, 0xbc, 0xc5, 0x20, 0x33,
+	0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+	0x00, 0xff, 0xf3, 0x00, 0x2a, 0x64, 0xa6, 0x86,
+	0x8c, 0xcf, 0xb8, 0xf1, 0xa8, 0xf2, 0x89, 0x00,
+};
+
+/* read via demodulator */
+int qm1d1c0042_fe_read(struct dvb_frontend *fe, u8 addr, u8 *data)
+{
+	int ret;
+	if ((addr != 0x00) && (addr != 0x0d))
+		return -EFAULT;
+	ret = fe->ops.write(fe, NULL, (((struct qm1d1c0042 *)fe->tuner_priv)->addr_tuner << 8) | addr);
+	if (ret < 0)
+		return ret;
+	*data = ret;
+	return 0;
+}
+
+/* write via demodulator */
+int qm1d1c0042_fe_write_data(struct dvb_frontend *fe, u8 addr_data, u8 *data, int len)
+{
+	u8 buf[len + 1];
+
+	buf[0] = addr_data;
+	memcpy(buf + 1, data, len);
+	return fe->ops.write(fe, buf, len + 1);
+}
+
+#define QM1D1C0042_FE_PASSTHROUGH 0xfe
+
+int qm1d1c0042_fe_write_tuner(struct dvb_frontend *fe, u8 *data, int len)
+{
+	u8 buf[len + 2];
+
+	buf[0] = QM1D1C0042_FE_PASSTHROUGH;
+	buf[1] = ((struct qm1d1c0042 *)fe->tuner_priv)->addr_tuner << 1;
+	memcpy(buf + 2, data, len);
+	return fe->ops.write(fe, buf, len + 2);
+}
+
+int qm1d1c0042_write(struct dvb_frontend *fe, u8 addr, u8 data)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	u8 buf[] = { addr, data };
+	int err = qm1d1c0042_fe_write_tuner(fe, buf, sizeof(buf));
+	qm->reg[addr] = buf[1];
+	return err;
+}
+
+static const u8 qm1d1c0042_flag[32] = {
+	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
+	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+};
+
+int qm1d1c0042_write_pskmsrst(struct dvb_frontend *fe)
+{
+	u8 data = 0x01;
+	return qm1d1c0042_fe_write_data(fe, 0x03, &data, 1);
+}
+
+enum qm1d1c0042_agc {
+	QM1D1C0042_AGC_AUTO,
+	QM1D1C0042_AGC_MANUAL,
+};
+
+int qm1d1c0042_set_agc(struct dvb_frontend *fe, enum qm1d1c0042_agc agc)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	static u8 agc_data_s[2] = { 0xb0, 0x30 };
+	u8 data = (agc == QM1D1C0042_AGC_AUTO) ? 0xff : 0x00;
+	int ret = qm1d1c0042_fe_write_data(fe, 0x0a, &data, 1);
+	if (ret)
+		return ret;
+
+	data = agc_data_s[(qm->idx >> 1) & 1];
+	data |= (agc == QM1D1C0042_AGC_AUTO) ? 0x01 : 0x00;
+	ret = qm1d1c0042_fe_write_data(fe, 0x10, &data, 1);
+	if (ret)
+		return ret;
+
+	data = (agc == QM1D1C0042_AGC_AUTO) ? 0x40 : 0x00;
+	return (ret = qm1d1c0042_fe_write_data(fe, 0x11, &data, 1)) ? ret : qm1d1c0042_write_pskmsrst(fe);
+}
+
+int qm1d1c0042_sleep(struct dvb_frontend *fe)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	u8 buf = 1;
+	pr_debug("#%d %s\n", qm->idx, __func__);
+
+	qm->reg[0x01] &= (~(1 << 3)) & 0xff;
+	qm->reg[0x01] |= 1 << 0;
+	qm->reg[0x05] |= 1 << 3;
+	return	qm1d1c0042_set_agc(fe, QM1D1C0042_AGC_MANUAL)	||
+		qm1d1c0042_write(fe, 0x05, qm->reg[0x05])	||
+		qm1d1c0042_write(fe, 0x01, qm->reg[0x01])	||
+		qm1d1c0042_fe_write_data(fe, 0x17, &buf, 1);
+}
+
+int qm1d1c0042_wakeup(struct dvb_frontend *fe)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	u8 buf = 0;
+	pr_debug("#%d %s\n", qm->idx, __func__);
+
+	qm->reg[0x01] |= 1 << 3;
+	qm->reg[0x01] &= (~(1 << 0)) & 0xff;
+	qm->reg[0x05] &= (~(1 << 3)) & 0xff;
+	return	qm1d1c0042_fe_write_data(fe, 0x17, &buf, 1)	||
+		qm1d1c0042_write(fe, 0x01, qm->reg[0x01])	||
+		qm1d1c0042_write(fe, 0x05, qm->reg[0x05]);
+}
+
+void qm1d1c0042_get_channel_freq(u32 channel, u32 *number, u32 *freq)
+{
+	if (channel < 12) {
+		*number = 1 + 2 * channel;
+		*freq = 104948 + 3836 * channel;
+	} else if (channel < 24) {
+		channel -= 12;
+		*number = 2 + 2 * channel;
+		*freq = 161300 + 4000 * channel;
+	} else {
+		channel -= 24;
+		*number = 1 + 2 * channel;
+		*freq = 159300 + 4000 * channel;
+	}
+}
+
+static const u32 qm1d1c0042_freq_tab[9][3] = {
+	{ 2151000, 1, 7 },
+	{ 1950000, 1, 6 },
+	{ 1800000, 1, 5 },
+	{ 1600000, 1, 4 },
+	{ 1450000, 1, 3 },
+	{ 1250000, 1, 2 },
+	{ 1200000, 0, 7 },
+	{  975000, 0, 6 },
+	{  950000, 0, 0 }
+};
+
+static const u32 qm1d1c0042_sd_tab[24][2][3] = {
+	{{0x38fae1, 0x0d, 0x5}, {0x39fae1, 0x0d, 0x5},},
+	{{0x3f570a, 0x0e, 0x3}, {0x00570a, 0x0e, 0x3},},
+	{{0x05b333, 0x0e, 0x5}, {0x06b333, 0x0e, 0x5},},
+	{{0x3c0f5c, 0x0f, 0x4}, {0x3d0f5c, 0x0f, 0x4},},
+	{{0x026b85, 0x0f, 0x6}, {0x036b85, 0x0f, 0x6},},
+	{{0x38c7ae, 0x10, 0x5}, {0x39c7ae, 0x10, 0x5},},
+	{{0x3f23d7, 0x11, 0x3}, {0x0023d7, 0x11, 0x3},},
+	{{0x058000, 0x11, 0x5}, {0x068000, 0x11, 0x5},},
+	{{0x3bdc28, 0x12, 0x4}, {0x3cdc28, 0x12, 0x4},},
+	{{0x023851, 0x12, 0x6}, {0x033851, 0x12, 0x6},},
+	{{0x38947a, 0x13, 0x5}, {0x39947a, 0x13, 0x5},},
+	{{0x3ef0a3, 0x14, 0x3}, {0x3ff0a3, 0x14, 0x3},},
+	{{0x3c8000, 0x16, 0x4}, {0x3d8000, 0x16, 0x4},},
+	{{0x048000, 0x16, 0x6}, {0x058000, 0x16, 0x6},},
+	{{0x3c8000, 0x17, 0x5}, {0x3d8000, 0x17, 0x5},},
+	{{0x048000, 0x18, 0x3}, {0x058000, 0x18, 0x3},},
+	{{0x3c8000, 0x18, 0x6}, {0x3d8000, 0x18, 0x6},},
+	{{0x048000, 0x19, 0x4}, {0x058000, 0x19, 0x4},},
+	{{0x3c8000, 0x1a, 0x3}, {0x3d8000, 0x1a, 0x3},},
+	{{0x048000, 0x1a, 0x5}, {0x058000, 0x1a, 0x5},},
+	{{0x3c8000, 0x1b, 0x4}, {0x3d8000, 0x1b, 0x4},},
+	{{0x048000, 0x1b, 0x6}, {0x058000, 0x1b, 0x6},},
+	{{0x3c8000, 0x1c, 0x5}, {0x3d8000, 0x1c, 0x5},},
+	{{0x048000, 0x1d, 0x3}, {0x058000, 0x1d, 0x3},},
+};
+
+static int qm1d1c0042_tuning(struct qm1d1c0042 *qm, u32 *sd, u32 channel)
+{
+	int ret;
+	struct dvb_frontend *fe = qm->fe;
+	u8 i_data;
+	u32 i, N, A, index = (qm->idx >> 1) & 1;
+
+	qm->reg[0x08] &= 0xf0;
+	qm->reg[0x08] |= 0x09;
+
+	qm->reg[0x13] &= 0x9f;
+	qm->reg[0x13] |= 0x20;
+
+	for (i = 0; i < 8; i++) {
+		if ((qm1d1c0042_freq_tab[i+1][0] <= qm->freq) && (qm->freq < qm1d1c0042_freq_tab[i][0])) {
+			i_data = qm->reg[0x02];
+			i_data &= 0x0f;
+			i_data |= qm1d1c0042_freq_tab[i][1] << 7;
+			i_data |= qm1d1c0042_freq_tab[i][2] << 4;
+			qm1d1c0042_write(fe, 0x02, i_data);
+		}
+	}
+
+	*sd = qm1d1c0042_sd_tab[channel][index][0];
+	N = qm1d1c0042_sd_tab[channel][index][1];
+	A = qm1d1c0042_sd_tab[channel][index][2];
+
+	qm->reg[0x06] &= 0x40;
+	qm->reg[0x06] |= N;
+	ret = qm1d1c0042_write(fe, 0x06, qm->reg[0x06]);
+	if (ret)
+		return ret;
+
+	qm->reg[0x07] &= 0xf0;
+	qm->reg[0x07] |= A & 0x0f;
+	return qm1d1c0042_write(fe, 0x07, qm->reg[0x07]);
+}
+
+static int qm1d1c0042_local_lpf_tuning(struct qm1d1c0042 *qm, u32 channel)
+{
+	struct dvb_frontend *fe = qm->fe;
+	u8 i_data;
+	u32 sd = 0;
+	int ret = qm1d1c0042_tuning(qm, &sd, channel);
+	if (ret)
+		return ret;
+
+	i_data = qm->reg[0x08] & 0xf0;
+	i_data |= 2;
+	ret = qm1d1c0042_write(fe, 0x08, i_data);
+	if (ret)
+		return ret;
+
+	qm->reg[0x09] &= 0xc0;
+	qm->reg[0x09] |= (sd >> 16) & 0x3f;
+	qm->reg[0x0a] = (sd >> 8) & 0xff;
+	qm->reg[0x0b] = (sd >> 0) & 0xff;
+	ret =	qm1d1c0042_write(fe, 0x09, qm->reg[0x09])	||
+		qm1d1c0042_write(fe, 0x0a, qm->reg[0x0a])	||
+		qm1d1c0042_write(qm->fe, 0x0b, qm->reg[0x0b]);
+	if (ret)
+		return ret;
+
+	i_data = qm->reg[0x0c];
+	i_data &= 0x3f;
+	ret = qm1d1c0042_write(fe, 0x0c, i_data);
+	if (ret)
+		return ret;
+	msleep_interruptible(1);
+
+	i_data = qm->reg[0x0c];
+	i_data |= 0xc0;
+	return	qm1d1c0042_write(fe, 0x0c, i_data)	||
+		qm1d1c0042_write(fe, 0x08, 0x09)	||
+		qm1d1c0042_write(fe, 0x13, qm->reg[0x13]);
+}
+
+int qm1d1c0042_get_locked(struct qm1d1c0042 *qm, bool *locked)
+{
+	int ret = qm1d1c0042_fe_read(qm->fe, 0x0d, &qm->reg[0x0d]);
+	if (ret)
+		return ret;
+	if (qm->reg[0x0d] & 0x40)
+		*locked = true;
+	else
+		*locked = false;
+	return ret;
+}
+
+u32 qm1d1c0042_freq2ch(u32 frequency)
+{
+	u32 freq = frequency / 10,
+	    ch0 = (freq - 104948) / 3836, diff0 = freq - (104948 + 3836 * ch0),
+	    ch1 = (freq - 161300) / 4000, diff1 = freq - (161300 + 4000 * ch1),
+	    ch2 = (freq - 159300) / 4000, diff2 = freq - (159300 + 4000 * ch2),
+	    min = diff0 < diff1 ? diff0 : diff1;
+
+	if (frequency < 1024)
+		return frequency;	/* consider as channel ID if low */
+	if (diff2 < min)
+		return ch2 + 24;
+	else if (min == diff1)
+		return ch1 + 12;
+	else
+		return ch0;
+}
+
+int qm1d1c0042_set_freq(struct dvb_frontend *fe, u32 frequency)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	u32 channel = qm1d1c0042_freq2ch(frequency);
+	u32 number, freq, freq_kHz;
+	bool locked = false;
+	unsigned long timeout;
+	int err = qm1d1c0042_set_agc(fe, QM1D1C0042_AGC_MANUAL);
+	if (err)
+		return err;
+
+	qm1d1c0042_get_channel_freq(channel, &number, &freq);
+	freq_kHz = freq * 10;
+	if (((qm->idx >> 1) & 1) == 0)
+		freq_kHz -= 500;
+	else
+		freq_kHz += 500;
+	qm->freq = freq_kHz;
+	pr_debug("#%d ch %d freq %d kHz\n", qm->idx, channel, freq_kHz);
+
+	err = qm1d1c0042_local_lpf_tuning(qm, channel);
+	if (err)
+		return err;
+
+	timeout = jiffies + msecs_to_jiffies(1000);	/* 1s */
+	while (time_before(jiffies, timeout)) {
+		err = qm1d1c0042_get_locked(qm, &locked);
+		if (err)
+			return err;
+		if (locked)
+			break;
+		msleep_interruptible(1);
+	}
+	pr_debug("#%d %s %s\n", qm->idx, __func__, locked ? "LOCKED" : "TIMEOUT");
+	return locked ? qm1d1c0042_set_agc(fe, QM1D1C0042_AGC_AUTO) : -ETIMEDOUT;
+}
+
+int qm1d1c0042_release(struct dvb_frontend *fe)
+{
+	kfree(fe->tuner_priv);
+	fe->tuner_priv = NULL;
+	return 0;
+}
+
+static struct dvb_tuner_ops qm1d1c0042_ops = {
+	.info = {
+		.frequency_min	= 1,		/* actually 1024 kHz, freq below that is handled as ch */
+		.frequency_max	= 2150000,	/* kHz */
+		.frequency_step	= 1000,		/* = 1 MHz */
+	},
+	.set_frequency = qm1d1c0042_set_freq,
+	.sleep = qm1d1c0042_sleep,
+	.init = qm1d1c0042_wakeup,
+	.release = qm1d1c0042_release,
+};
+
+int qm1d1c0042_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
+{
+	u8 d[] = { 0x10, 0x15, 0x04 };
+	struct qm1d1c0042 *qm = kzalloc(sizeof(struct qm1d1c0042), GFP_KERNEL);
+	if (!qm)
+		return -ENOMEM;
+	fe->tuner_priv = qm;
+	qm->fe = fe;
+	qm->idx = idx;
+	qm->addr_tuner = addr_tuner;
+	memcpy(&fe->ops.tuner_ops, &qm1d1c0042_ops, sizeof(struct dvb_tuner_ops));
+
+	memcpy(qm->reg, qm1d1c0042_reg_rw, sizeof(qm1d1c0042_reg_rw));
+	qm->freq = 0;
+
+	return	qm1d1c0042_fe_write_data(fe, 0x1e, d,   1)	||
+		qm1d1c0042_fe_write_data(fe, 0x1c, d+1, 1)	||
+		qm1d1c0042_fe_write_data(fe, 0x1f, d+2, 1);
+}
+EXPORT_SYMBOL(qm1d1c0042_attach);
+
diff --git a/drivers/media/tuners/qm1d1c0042.h b/drivers/media/tuners/qm1d1c0042.h
new file mode 100644
index 0000000..2b14b70
--- /dev/null
+++ b/drivers/media/tuners/qm1d1c0042.h
@@ -0,0 +1,36 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-S tuner driver QM1D1C0042
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 __QM1D1C0042_H__
+#define __QM1D1C0042_H__
+
+#ifndef pr_fmt
+ #define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+#endif
+#include "dvb_frontend.h"
+
+#if IS_ENABLED(CONFIG_MEDIA_TUNER_QM1D1C0042)
+extern int qm1d1c0042_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner);
+#else
+static inline int qm1d1c0042_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
+{
+	pr_warn("%s: driver disabled by Kconfig\n", __func__);
+	return 0;
+}
+#endif
+
+#endif
+
-- 
1.8.4.5


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH] Full DVB driver package for Earthsoft PT3 (ISDB-S/T) cards
  2014-04-22 17:26 [PATCH] Full DVB driver package for Earthsoft PT3 (ISDB-S/T) cards Буди Романто, AreMa Inc
@ 2014-05-17 19:03 ` Antti Palosaari
  2014-05-19 22:19   ` ほち
  0 siblings, 1 reply; 21+ messages in thread
From: Antti Palosaari @ 2014-05-17 19:03 UTC (permalink / raw)
  To: "Буди
	Романто, AreMa Inc",
	linux-media
  Cc: "Буди
	Романто, AreMa Inc",
	m.chehab, hdegoede, hverkuil, laurent.pinchart, mkrufky,
	sylvester.nawrocki, g.liakhovetski, peter.senna

Hello
I reviewed only tc90522. There was a tons of issues and I am even a bit 
surprised that works.

For the next round you must split that patchset as one patch per driver. 
I don't care myself review more than that tc90522 demod driver - it is 
more than enough work for me as I have wasted now whole workday for 
reviewing two new drivers.

Overall tc90522 driver looks very complex and there was multiple issues. 
One reason of complexiness is that HW algo used. I cannot see any reason 
why it is used, just change default SW algo and implement things more 
likely others are doing.

A lot of comments inlined to code.

regards
Antti



On 04/22/2014 08:26 PM, Буди Романто, AreMa Inc wrote:
> From: Буди Роман то, AreMa Inc <knightrider@are.ma>
>
> DVB driver for Earthsoft PT3 (ISDB-S/T) receiver PCI Express cards.
> It behaves similarly as PT1 DVB, plus some tuning enhancements:
> 1. in addition to the real frequency:
> 	ISDB-S : freq. channel ID
> 	ISDB-T : freq# (I/O# +128), ch#, ch# +64 for CATV
> 2. in addition to TSID:
> 	ISDB-S : slot#
>
> Feature changes:
> - dropped DKMS & standalone compile
> - dropped verbosity (debug levels), use single level -DDEBUG instead
> - moved demodulator FE to drivers/media/dvb-frontends
> - moved tuners to drivers/media/tuners
> - translated to standard (?) I2C protocol
> - dropped unused features
> - added DVBv5 CNR support
>
> The full package (buildable as standalone, DKMS or tree embedded module) is available at
> https://github.com/knight-rider/ptx/tree/master/pt3_dvb
>
> Signed-off-by: Буди Романто, AreMa Inc <knightrider@are.ma>
> ---
>   drivers/media/dvb-frontends/Kconfig   |  11 +-
>   drivers/media/dvb-frontends/Makefile  |   1 +
>   drivers/media/dvb-frontends/tc90522.c | 539 ++++++++++++++++++++++++++++++++++
>   drivers/media/dvb-frontends/tc90522.h |  36 +++
>   drivers/media/pci/Kconfig             |   2 +-
>   drivers/media/pci/Makefile            |   1 +
>   drivers/media/pci/pt3/Kconfig         |  11 +
>   drivers/media/pci/pt3/Makefile        |   6 +
>   drivers/media/pci/pt3/pt3_common.h    |  86 ++++++
>   drivers/media/pci/pt3/pt3_dma.c       | 333 +++++++++++++++++++++
>   drivers/media/pci/pt3/pt3_dma.h       |  50 ++++
>   drivers/media/pci/pt3/pt3_i2c.c       | 189 ++++++++++++
>   drivers/media/pci/pt3/pt3_i2c.h       |  25 ++
>   drivers/media/pci/pt3/pt3_pci.c       | 397 +++++++++++++++++++++++++
>   drivers/media/tuners/Kconfig          |  15 +
>   drivers/media/tuners/Makefile         |   2 +
>   drivers/media/tuners/mxl301rf.c       | 390 ++++++++++++++++++++++++
>   drivers/media/tuners/mxl301rf.h       |  36 +++
>   drivers/media/tuners/qm1d1c0042.c     | 382 ++++++++++++++++++++++++
>   drivers/media/tuners/qm1d1c0042.h     |  36 +++
>   20 files changed, 2546 insertions(+), 2 deletions(-)
>   create mode 100644 drivers/media/dvb-frontends/tc90522.c
>   create mode 100644 drivers/media/dvb-frontends/tc90522.h
>   create mode 100644 drivers/media/pci/pt3/Kconfig
>   create mode 100644 drivers/media/pci/pt3/Makefile
>   create mode 100644 drivers/media/pci/pt3/pt3_common.h
>   create mode 100644 drivers/media/pci/pt3/pt3_dma.c
>   create mode 100644 drivers/media/pci/pt3/pt3_dma.h
>   create mode 100644 drivers/media/pci/pt3/pt3_i2c.c
>   create mode 100644 drivers/media/pci/pt3/pt3_i2c.h
>   create mode 100644 drivers/media/pci/pt3/pt3_pci.c
>   create mode 100644 drivers/media/tuners/mxl301rf.c
>   create mode 100644 drivers/media/tuners/mxl301rf.h
>   create mode 100644 drivers/media/tuners/qm1d1c0042.c
>   create mode 100644 drivers/media/tuners/qm1d1c0042.h
>
> diff --git a/drivers/media/dvb-frontends/Kconfig b/drivers/media/dvb-frontends/Kconfig
> index 025fc54..0047b3f 100644
> --- a/drivers/media/dvb-frontends/Kconfig
> +++ b/drivers/media/dvb-frontends/Kconfig
> @@ -591,7 +591,7 @@ config DVB_S5H1411
>   	  An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want
>   	  to support this frontend.
>
> -comment "ISDB-T (terrestrial) frontends"
> +comment "ISDB-S (satellite) & ISDB-T (terrestrial) frontends"
>   	depends on DVB_CORE
>
>   config DVB_S921
> @@ -618,6 +618,15 @@ config DVB_MB86A20S
>   	  A driver for Fujitsu mb86a20s ISDB-T/ISDB-Tsb demodulator.
>   	  Say Y when you want to support this frontend.
>
> +config DVB_TC90522
> +	tristate "Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S)"
> +	depends on DVB_CORE && I2C
> +	default m if !MEDIA_SUBDRV_AUTOSELECT
> +	help
> +	  Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S) DVB demodulator
> +	  frontend for Earthsoft PT3 PCIE cards.
> +	  Say Y when you want to support this frontend.
> +
>   comment "Digital terrestrial only tuners/PLL"
>   	depends on DVB_CORE
>
> diff --git a/drivers/media/dvb-frontends/Makefile b/drivers/media/dvb-frontends/Makefile
> index 282aba2..a80d212 100644
> --- a/drivers/media/dvb-frontends/Makefile
> +++ b/drivers/media/dvb-frontends/Makefile
> @@ -105,4 +105,5 @@ obj-$(CONFIG_DVB_RTL2830) += rtl2830.o
>   obj-$(CONFIG_DVB_RTL2832) += rtl2832.o
>   obj-$(CONFIG_DVB_M88RS2000) += m88rs2000.o
>   obj-$(CONFIG_DVB_AF9033) += af9033.o
> +obj-$(CONFIG_DVB_TC90522) += tc90522.o
>
> diff --git a/drivers/media/dvb-frontends/tc90522.c b/drivers/media/dvb-frontends/tc90522.c
> new file mode 100644
> index 0000000..a767600
> --- /dev/null
> +++ b/drivers/media/dvb-frontends/tc90522.c
> @@ -0,0 +1,539 @@
> +/*
> + * Earthsoft PT3 demodulator frontend Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S)

That is, or at least should be, general DTV demod driver. So lets call 
it Toshiba TC90522 or whatever the chipset name is.

> + *
> + * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 "dvb_math.h"
> +#include "tc90522.h"
> +
> +MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
> +MODULE_DESCRIPTION("Earthsoft PT3 Toshiba TC90522 OFDM(ISDB-T)/8PSK(ISDB-S) demodulator");
> +MODULE_LICENSE("GPL");
> +
> +#define TC90522_PASSTHROUGH 0xfe
> +
> +enum tc90522_state {
> +	TC90522_IDLE,
> +	TC90522_SET_FREQUENCY,
> +	TC90522_SET_MODULATION,
> +	TC90522_TRACK,
> +	TC90522_ABORT,
> +};
> +
> +struct tc90522 {
> +	struct dvb_frontend fe;
> +	struct i2c_adapter *i2c;
> +	fe_delivery_system_t type;
> +	u8 idx, addr_demod;
> +	s32 offset;
> +	enum tc90522_state state;
> +};
> +
> +int tc90522_write(struct dvb_frontend *fe, const u8 *data, int len)
> +{
> +	struct tc90522 *demod = fe->demodulator_priv;
> +	struct i2c_msg msg[3];
> +	u8 buf[6];
> +
> +	if (data) {
> +		msg[0].addr = demod->addr_demod;
> +		msg[0].buf = (u8 *)data;
> +		msg[0].flags = 0;			/* write */
> +		msg[0].len = len;
> +
> +		return i2c_transfer(demod->i2c, msg, 1) == 1 ? 0 : -EREMOTEIO;
> +	} else {
> +		u8 addr_tuner = (len >> 8) & 0xff,
> +		   addr_data = len & 0xff;
> +		if (len >> 16) {			/* read tuner without address */
> +			buf[0] = TC90522_PASSTHROUGH;
> +			buf[1] = (addr_tuner << 1) | 1;
> +			msg[0].buf = buf;
> +			msg[0].len = 2;
> +			msg[0].addr = demod->addr_demod;
> +			msg[0].flags = 0;		/* write */
> +
> +			msg[1].buf = buf + 2;
> +			msg[1].len = 1;
> +			msg[1].addr = demod->addr_demod;
> +			msg[1].flags = I2C_M_RD;	/* read */
> +
> +			return i2c_transfer(demod->i2c, msg, 2) == 2 ? buf[2] : -EREMOTEIO;
> +		} else {				/* read tuner */
> +			buf[0] = TC90522_PASSTHROUGH;
> +			buf[1] = addr_tuner << 1;
> +			buf[2] = addr_data;
> +			msg[0].buf = buf;
> +			msg[0].len = 3;
> +			msg[0].addr = demod->addr_demod;
> +			msg[0].flags = 0;		/* write */
> +
> +			buf[3] = TC90522_PASSTHROUGH;
> +			buf[4] = (addr_tuner << 1) | 1;
> +			msg[1].buf = buf + 3;
> +			msg[1].len = 2;
> +			msg[1].addr = demod->addr_demod;
> +			msg[1].flags = 0;		/* write */
> +
> +			msg[2].buf = buf + 5;
> +			msg[2].len = 1;
> +			msg[2].addr = demod->addr_demod;
> +			msg[2].flags = I2C_M_RD;	/* read */
> +
> +			return i2c_transfer(demod->i2c, msg, 3) == 3 ? buf[5] : -EREMOTEIO;
> +		}
> +	}
> +}

That routine is mess. I read it many times without understanding what it 
does, when and why. It is not register write over I2C as expected. For 
example parameter named "len" is abused for tuner I2C even that is demod 
driver...

> +
> +int tc90522_write_data(struct dvb_frontend *fe, u8 addr_data, u8 *data, u8 len)
> +{
> +	u8 buf[len + 1];
> +	buf[0] = addr_data;
> +	memcpy(buf + 1, data, len);
> +	return tc90522_write(fe, buf, len + 1);
> +}
> +
> +int tc90522_read(struct tc90522 *demod, u8 addr, u8 *buf, u8 buflen)
> +{
> +	struct i2c_msg msg[2];
> +	if (!buf || !buflen)
> +		return -EINVAL;
> +
> +	buf[0] = addr;
....
> +	msg[0].addr = demod->addr_demod;
> +	msg[0].flags = 0;			/* write */
> +	msg[0].buf = buf;

just give a addr pointer, no need to store it to buf first.


> +	msg[0].len = 1;
> +
> +	msg[1].addr = demod->addr_demod;
> +	msg[1].flags = I2C_M_RD;		/* read */
> +	msg[1].buf = buf;
> +	msg[1].len = buflen;
> +
> +	return i2c_transfer(demod->i2c, msg, 2) == 2 ? 0 : -EREMOTEIO;
> +}

All in all, it looks like demod is using just most typical register 
access for both register write and read, where first byte is register 
address and value(s) are after that. Register read is done using 
repeated START.

I encourage you to use RegMap API as it covers all that boilerplate 
stuff - and forces you implement things correctly (no such hack possible 
done in tc90522_write()).

> +
> +u32 tc90522_byten(const u8 *data, u32 n)
> +{
> +	u32 i, val = 0;
> +
> +	for (i = 0; i < n; i++) {
> +		val <<= 8;
> +		val |= data[i];
> +	}
> +	return val;
> +}

What is that? Kinda bit reverse? Look from existing bitops if there is 
such a solution already and if not, add comments what that is for.

> +
> +int tc90522_read_id_s(struct tc90522 *demod, u16 *id)
> +{
> +	u8 buf[2];
> +	int ret = tc90522_read(demod, 0xe6, buf, 2);
> +	if (!ret)
> +		*id = tc90522_byten(buf, 2);
> +	return ret;
> +}
> +
> +struct tmcc_s {			/* Transmission and Multiplexing Configuration Control */
> +	u32 mode[4];
> +	u32 slot[4];
> +	u32 id[8];
> +};
> +
> +int tc90522_read_tmcc_s(struct tc90522 *demod, struct tmcc_s *tmcc)
> +{
> +	enum {
> +		BASE = 0xc5,
> +		SIZE = 0xe5 - BASE + 1
> +	};
> +	u8 data[SIZE];
> +	u32 i, byte_offset, bit_offset;
> +
> +	int err = tc90522_read(demod, 0xc3, data, 1)	||

You used ret earlier, now err. ret fits very well here too. I think ret 
is more commonly used than err, but if you like to use err, then use it 
everywhere and vice versa.


> +		((data[0] >> 4) & 1)			||
> +		tc90522_read(demod, 0xce, data, 2)	||
> +		(tc90522_byten(data, 2) == 0)		||
> +		tc90522_read(demod, 0xc3, data, 1)	||
> +		tc90522_read(demod, 0xc5, data, SIZE);

Masking return statuses like that does not look good nor clear.

> +	if (err)
> +		return err;
> +	for (i = 0; i < 4; i++) {
> +		byte_offset = i >> 1;
> +		bit_offset = (i & 1) ? 0 : 4;
> +		tmcc->mode[i] = (data[0xc8 + byte_offset - BASE] >> bit_offset) & 0b00001111;
> +		tmcc->slot[i] = (data[0xca + i           - BASE] >>          0) & 0b00111111;
> +	}
> +	for (i = 0; i < 8; i++)
> +		tmcc->id[i] = tc90522_byten(data + 0xce + i * 2 - BASE, 2);
> +	return 0;
> +}
> +
> +enum tc90522_pwr {
> +	TC90522_PWR_OFF		= 0x00,
> +	TC90522_PWR_AMP_ON	= 0x04,
> +	TC90522_PWR_TUNER_ON	= 0x40,
> +};
> +
> +static enum tc90522_pwr tc90522_pwr = TC90522_PWR_OFF;

Global static variable for device power management..? That looks very 
bad. Those variables are shared between all driver instances. That will 
not work if you have multiple devices having that demod driver!

> +
> +int tc90522_set_powers(struct tc90522 *demod, enum tc90522_pwr pwr)
> +{
> +	u8 data = pwr | 0b10011001;
> +	pr_debug("#%d tuner %s amp %s\n", demod->idx, pwr & TC90522_PWR_TUNER_ON ? "ON" : "OFF", pwr & TC90522_PWR_AMP_ON ? "ON" : "OFF");
> +	tc90522_pwr = pwr;
> +	return tc90522_write_data(&demod->fe, 0x1e, &data, 1);
> +}
> +
> +/* dvb_frontend_ops */
> +int tc90522_get_frontend_algo(struct dvb_frontend *fe)
> +{
> +	return DVBFE_ALGO_HW;
> +}
> +
> +int tc90522_sleep(struct dvb_frontend *fe)
> +{
> +	struct tc90522 *demod = fe->demodulator_priv;
> +	pr_debug("#%d %s %s\n", demod->idx, __func__, demod->type == SYS_ISDBS ? "S" : "T");
> +	return fe->ops.tuner_ops.sleep(fe);

:-@ You are simply not allowed to hard code tuner power-management to 
demod driver, it is no, no, no. Demod driver can have only get IF and 
set parameters reference to tuner and nothing more.

You should sleep only demod in that demod sleep().

It is DVB core who is responsible of runtime power-management.

> +}
> +
> +int tc90522_wakeup(struct dvb_frontend *fe)
> +{
> +	struct tc90522 *demod = fe->demodulator_priv;
> +	pr_debug("#%d %s %s 0x%x\n", demod->idx, __func__, demod->type == SYS_ISDBS ? "S" : "T", tc90522_pwr);
> +
> +	if (!tc90522_pwr)
> +		return	tc90522_set_powers(demod, TC90522_PWR_TUNER_ON)	||
> +			i2c_transfer(demod->i2c, NULL, 0)		||
> +			tc90522_set_powers(demod, TC90522_PWR_TUNER_ON | TC90522_PWR_AMP_ON);
> +	demod->state = TC90522_IDLE;
> +	return fe->ops.tuner_ops.init(fe);
> +}

power-management is totally wrong here too

> +
> +void tc90522_release(struct dvb_frontend *fe)
> +{
> +	struct tc90522 *demod = fe->demodulator_priv;
> +	pr_debug("#%d %s\n", demod->idx, __func__);
> +
> +	if (tc90522_pwr)
> +		tc90522_set_powers(demod, TC90522_PWR_OFF);

That belongs to demod driver power-management callback sleep()

> +	tc90522_sleep(fe);

hmm? PM...

> +	fe->ops.tuner_ops.release(fe);
> +	kfree(demod);
> +}
> +
> +s64 tc90522_get_cn_raw(struct tc90522 *demod)
> +{
> +	u8 buf[3], buflen = demod->type == SYS_ISDBS ? 2 : 3, addr = demod->type == SYS_ISDBS ? 0xbc : 0x8b;
> +	int err = tc90522_read(demod, addr, buf, buflen);
> +	return err < 0 ? err : tc90522_byten(buf, buflen);
> +}
> +
> +s64 tc90522_get_cn_s(s64 raw)	/* @ .0001 dB */
> +{
> +	s64 x, y;
> +
> +	raw -= 3000;
> +	if (raw < 0)
> +		raw = 0;
> +	x = int_sqrt(raw << 20);
> +	y = 16346ll * x - (143410ll << 16);
> +	y = ((x * y) >> 16) + (502590ll << 16);
> +	y = ((x * y) >> 16) - (889770ll << 16);
> +	y = ((x * y) >> 16) + (895650ll << 16);
> +	y = (588570ll << 16) - ((x * y) >> 16);
> +	return y < 0 ? 0 : y >> 16;
> +}
> +
> +s64 tc90522_get_cn_t(s64 raw)	/* @ .0001 dB */
> +{
> +	s64 x, y;
> +	if (!raw)
> +		return 0;
> +	x = (1130911733ll - 10ll * intlog10(raw)) >> 2;
> +	y = (6ll * x / 25ll) - (16ll << 22);
> +	y = ((x * y) >> 22) + (398ll << 22);
> +	y = ((x * y) >> 22) + (5491ll << 22);
> +	y = ((x * y) >> 22) + (30965ll << 22);
> +	return y >> 22;
> +}
> +
> +int tc90522_read_signal_strength(struct dvb_frontend *fe, u16 *cn)	/* raw C/N */
> +{
> +	struct tc90522 *demod = fe->demodulator_priv;
> +	s64 ret = tc90522_get_cn_raw(demod);
> +	*cn = ret < 0 ? 0 : ret;
> +	pr_debug("v3 CN %d (%lld dB)\n", (int)*cn, demod->type == SYS_ISDBS ? (long long int)tc90522_get_cn_s(*cn) : (long long int)tc90522_get_cn_t(*cn));
> +	return ret < 0 ? ret : 0;
> +}

We have API who supports both CNR and signal strenght. Do not abuse 
signal strenght for CNR, instead implement as it should.

> +
> +int tc90522_read_status(struct dvb_frontend *fe, fe_status_t *status)
> +{
> +	struct tc90522 *demod = fe->demodulator_priv;
> +	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
> +	s64 ret = tc90522_get_cn_raw(demod),
> +	    raw = ret < 0 ? 0 : ret;
> +
> +	switch (demod->state) {
> +	case TC90522_IDLE:
> +	case TC90522_SET_FREQUENCY:
> +		*status = 0;
> +		break;
> +
> +	case TC90522_SET_MODULATION:
> +	case TC90522_ABORT:
> +		*status |= FE_HAS_SIGNAL;
> +		break;
> +
> +	case TC90522_TRACK:
> +		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
> +		break;
> +	}
> +
> +	c->cnr.len = 1;
> +	c->cnr.stat[0].svalue = demod->type == SYS_ISDBS ? tc90522_get_cn_s(raw) : tc90522_get_cn_t(raw);
> +	c->cnr.stat[0].scale = FE_SCALE_DECIBEL;
> +	pr_debug("v5 CN %lld (%lld dB)\n", raw, c->cnr.stat[0].svalue);
> +	return ret < 0 ? ret : 0;
> +}

So you have decided to add some statistics logic here too. It is good 
place to update stistics counters, but only on case where SW algo used 
and DVB core is polling. However, you used HW algo which means that is 
not polled automatically. Maybe it does not work as it should.

> +
> +/**** ISDB-S ****/
> +int tc90522_write_id_s(struct dvb_frontend *fe, u16 id)
> +{
> +	u8 data[2] = { id >> 8, (u8)id };
> +	return tc90522_write_data(fe, 0x8f, data, sizeof(data));
> +}

Rather useless oneliner function called only from one place. This makes 
only few lines of code more and bigger binary.

> +
> +int tc90522_tune_s(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
> +{
> +	struct tc90522 *demod = fe->demodulator_priv;
> +	struct tmcc_s tmcc;
> +	int i, ret,
> +	    freq = fe->dtv_property_cache.frequency,
> +	    tsid = fe->dtv_property_cache.stream_id;

Adding more ints here does not cost anything.

> +
> +	if (re_tune)
> +		demod->state = TC90522_SET_FREQUENCY;
> +
> +	switch (demod->state) {
> +	case TC90522_IDLE:
> +		*delay = msecs_to_jiffies(3000);
> +		*status = 0;
> +		return 0;
> +
> +	case TC90522_SET_FREQUENCY:
> +		pr_debug("#%d tsid 0x%x freq %d\n", demod->idx, tsid, freq);

You must use dev_ functions for logging.

> +		ret = fe->ops.tuner_ops.set_frequency(fe, freq);
> +		if (ret)
> +			return ret;
> +		demod->offset = 0;
> +		demod->state = TC90522_SET_MODULATION;
> +		*delay = 0;
> +		*status = FE_HAS_SIGNAL;
> +		return 0;
> +
> +	case TC90522_SET_MODULATION:
> +		for (i = 0; i < 1000; i++) {
> +			ret = tc90522_read_tmcc_s(demod, &tmcc);
> +			if (!ret)
> +				break;
> +			msleep_interruptible(1);
> +		}
> +		if (ret) {
> +			pr_debug("fail tc_read_tmcc_s ret=0x%x\n", ret);
> +			demod->state = TC90522_ABORT;
> +			*delay = msecs_to_jiffies(1000);
> +			return ret;
> +		}
> +		pr_debug("slots=%d,%d,%d,%d mode=%d,%d,%d,%d tmcc.id=0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x\n",
> +				tmcc.slot[0], tmcc.slot[1], tmcc.slot[2], tmcc.slot[3],
> +				tmcc.mode[0], tmcc.mode[1], tmcc.mode[2], tmcc.mode[3],
> +				tmcc.id[0], tmcc.id[1], tmcc.id[2], tmcc.id[3],
> +				tmcc.id[4], tmcc.id[5], tmcc.id[6], tmcc.id[7]);
> +		for (i = 0; i < ARRAY_SIZE(tmcc.id); i++) {
> +			pr_debug("tsid %x i %d tmcc.id %x\n", tsid, i, tmcc.id[i]);
> +			if (tmcc.id[i] == tsid)
> +				break;
> +		}
> +		if (tsid < ARRAY_SIZE(tmcc.id))		/* treat as slot# */
> +			i = tsid;
> +		if (i == ARRAY_SIZE(tmcc.id)) {
> +			pr_debug("#%d i%d tsid 0x%x not found\n", demod->idx, i, tsid);
> +			return -EINVAL;
> +		}
> +		demod->offset = i;
> +		pr_debug("#%d found tsid 0x%x on slot %d\n", demod->idx, tsid, i);
> +		ret = tc90522_write_id_s(fe, (u16)tmcc.id[demod->offset]);
> +		if (ret) {
> +			pr_debug("fail set_tmcc_s ret=%d\n", ret);
> +			return ret;
> +		}
> +		for (i = 0; i < 1000; i++) {
> +			u16 short_id;
> +			ret = tc90522_read_id_s(demod, &short_id);
> +			if (ret) {
> +				pr_debug("fail get_id_s ret=%d\n", ret);
> +				return ret;
> +			}
> +			tsid = short_id;
> +			pr_debug("#%d tsid=0x%x\n", demod->idx, tsid);
> +			if ((tsid & 0xffff) == tmcc.id[demod->offset])
> +				break;
> +			msleep_interruptible(1);
> +		}
> +		demod->state = TC90522_TRACK;
> +		/* fallthrough */
> +
> +	case TC90522_TRACK:
> +		*delay = msecs_to_jiffies(3000);
> +		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
> +		return 0;
> +
> +	case TC90522_ABORT:
> +		*delay = msecs_to_jiffies(3000);
> +		*status = FE_HAS_SIGNAL;
> +		return 0;
> +	}
> +	return -ERANGE;
> +}

That didnt look very correct and I didnt even understand it very well. 
Basically it is callback which dvb core uses to tune device. However, 
thee is complex state machine implemented. State machine state is 
updated by read_status() callback, which is *not* ran by DVB core when 
that HW aldo is used. How that can work? You need to call FE status from 
userspace in order to update state machine? If your app does not call 
status, that does not work at all?

And those 3 sec timers are here in order to leave some time for app to 
read FE status => updates state machine?

uh.

> +
> +static struct dvb_frontend_ops tc90522_ops_s = {
> +	.delsys = { SYS_ISDBS },
> +	.info = {
> +		.name = "TC90522 ISDB-S",
> +		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO | FE_CAN_MULTISTREAM |
> +			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
> +	},
> +	.init = tc90522_wakeup,
> +	.sleep = tc90522_sleep,
> +	.release = tc90522_release,
> +	.write = tc90522_write,
> +	.get_frontend_algo = tc90522_get_frontend_algo,
> +	.read_signal_strength = tc90522_read_signal_strength,
> +	.read_status = tc90522_read_status,
> +	.tune = tc90522_tune_s,
> +};
> +
> +/**** ISDB-T ****/
> +int tc90522_get_tmcc_t(struct tc90522 *demod)
> +{
> +	u8 buf;
> +	bool b = false, retryov, fulock;
> +
> +	while (1) {
> +		if (tc90522_read(demod, 0x80, &buf, 1))
> +			return -EBADMSG;
> +		retryov = buf & 0b10000000 ? true : false;
> +		fulock  = buf & 0b00001000 ? true : false;
> +		if (!fulock) {
> +			b = true;
> +			break;
> +		} else {
> +			if (retryov)
> +				break;
> +		}
> +		msleep_interruptible(1);

Weird looking sleep, I have never earlier seen that. Have you looked 
timers howto from kernel documentation?

Also, it looks a bit scary what goes to potential infinity loop. If you 
need some upper limit per time you should use loop implemented by 
jiffies. Otherwise just use for loop which surely ends.

First time I also see bitmaps used in kernel. I have used those many 
times in embedded projects and I was thinking those are not defined in 
kernel. Nothing wrong, I will also start using those as are nowadays 
possible.


> +	}
> +	return b ? 0 : -EBADMSG;
> +}
> +
> +int tc90522_tune_t(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
> +{
> +	struct tc90522 *demod = fe->demodulator_priv;
> +	int ret, i;
> +
> +	if (re_tune)
> +		demod->state = TC90522_SET_FREQUENCY;
> +
> +	switch (demod->state) {
> +	case TC90522_IDLE:
> +		*delay = msecs_to_jiffies(3000);
> +		*status = 0;
> +		return 0;
> +
> +	case TC90522_SET_FREQUENCY:
> +		if (fe->ops.tuner_ops.set_frequency(fe, fe->dtv_property_cache.frequency)) {
> +			*delay = msecs_to_jiffies(1000);
> +			*status = 0;
> +			return 0;
> +		}
> +		demod->state = TC90522_SET_MODULATION;
> +		*delay = 0;
> +		*status = FE_HAS_SIGNAL;
> +		return 0;
> +
> +	case TC90522_SET_MODULATION:
> +		for (i = 0; i < 1000; i++) {
> +			ret = tc90522_get_tmcc_t(demod);
> +			if (!ret)
> +				break;
> +			msleep_interruptible(2);
> +		}
> +		if (ret) {
> +			pr_debug("#%d fail get_tmcc_t ret=%d\n", demod->idx, ret);
> +				demod->state = TC90522_ABORT;
> +				*delay = msecs_to_jiffies(1000);
> +				return 0;
> +		}
> +		demod->state = TC90522_TRACK;
> +		/* fallthrough */
> +
> +	case TC90522_TRACK:
> +		*delay = msecs_to_jiffies(3000);
> +		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
> +		return 0;
> +
> +	case TC90522_ABORT:
> +		*delay = msecs_to_jiffies(3000);
> +		*status = FE_HAS_SIGNAL;
> +		return 0;
> +	}
> +	return -ERANGE;
> +}
> +
> +static struct dvb_frontend_ops tc90522_ops_t = {
> +	.delsys = { SYS_ISDBT },
> +	.info = {
> +		.name = "TC90522 ISDB-T",
> +		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO |
> +			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
> +	},
> +	.init = tc90522_wakeup,
> +	.sleep = tc90522_sleep,
> +	.release = tc90522_release,
> +	.write = tc90522_write,
> +	.get_frontend_algo = tc90522_get_frontend_algo,
> +	.read_signal_strength = tc90522_read_signal_strength,
> +	.read_status = tc90522_read_status,
> +	.tune = tc90522_tune_t,
> +};
> +
> +/**** Common ****/
> +struct dvb_frontend *tc90522_attach(struct i2c_adapter *i2c, u8 idx, fe_delivery_system_t type, u8 addr_demod)
> +{
> +	struct dvb_frontend *fe;
> +	struct tc90522 *demod = kzalloc(sizeof(struct tc90522), GFP_KERNEL);
> +	if (!demod)
> +		return NULL;
> +
> +	demod->i2c	= i2c;
> +	demod->idx	= idx;

Driver should not need index at all. It could be found from the frontend 
pointer after registration, but still not needed.

> +	demod->type	= type;
> +	demod->addr_demod = addr_demod;
> +	fe = &demod->fe;
> +	memcpy(&fe->ops, (demod->type == SYS_ISDBS) ? &tc90522_ops_s : &tc90522_ops_t, sizeof(struct dvb_frontend_ops));
> +	fe->demodulator_priv = demod;
> +	return fe;
> +}

There is some issues as T and S mode driver instances registered to same 
chip. What happens if you are wathing T and try tune S at same time? 
(probably T breaks). I am not still sure if it is something that should 
be fixed.


> +EXPORT_SYMBOL(tc90522_attach);
> +
> diff --git a/drivers/media/dvb-frontends/tc90522.h b/drivers/media/dvb-frontends/tc90522.h
> new file mode 100644
> index 0000000..78c5298
> --- /dev/null
> +++ b/drivers/media/dvb-frontends/tc90522.h
> @@ -0,0 +1,36 @@
> +/*
> + * Earthsoft PT3 demodulator frontend Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S)
> + *
> + * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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	__TC90522_H__
> +#define	__TC90522_H__
> +
> +#ifndef pr_fmt
> + #define pr_fmt(fmt) KBUILD_MODNAME " " fmt
> +#endif
> +#include "dvb_frontend.h"
> +
> +#if IS_ENABLED(CONFIG_DVB_TC90522)
> +extern struct dvb_frontend *tc90522_attach(struct i2c_adapter *i2c, u8 idx, fe_delivery_system_t type, u8 addr_demod);
> +#else
> +static inline struct dvb_frontend *tc90522_attach(struct i2c_adapter *i2c, u8 idx, fe_delivery_system_t type, u8 addr_demod)
> +{
> +	pr_warn("%s: driver disabled by Kconfig\n", __func__);
> +	return NULL;
> +}
> +#endif
> +
> +#endif
> +
> diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
> index 53196f1..87018c8 100644
> --- a/drivers/media/pci/Kconfig
> +++ b/drivers/media/pci/Kconfig
> @@ -30,7 +30,6 @@ source "drivers/media/pci/cx88/Kconfig"
>   source "drivers/media/pci/bt8xx/Kconfig"
>   source "drivers/media/pci/saa7134/Kconfig"
>   source "drivers/media/pci/saa7164/Kconfig"
> -
>   endif
>
>   if MEDIA_DIGITAL_TV_SUPPORT
> @@ -40,6 +39,7 @@ source "drivers/media/pci/b2c2/Kconfig"
>   source "drivers/media/pci/pluto2/Kconfig"
>   source "drivers/media/pci/dm1105/Kconfig"
>   source "drivers/media/pci/pt1/Kconfig"
> +source "drivers/media/pci/pt3/Kconfig"
>   source "drivers/media/pci/mantis/Kconfig"
>   source "drivers/media/pci/ngene/Kconfig"
>   source "drivers/media/pci/ddbridge/Kconfig"
> diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
> index 35cc578..f7be6bc 100644
> --- a/drivers/media/pci/Makefile
> +++ b/drivers/media/pci/Makefile
> @@ -7,6 +7,7 @@ obj-y        +=	ttpci/		\
>   		pluto2/		\
>   		dm1105/		\
>   		pt1/		\
> +		pt3/		\
>   		mantis/		\
>   		ngene/		\
>   		ddbridge/	\
> diff --git a/drivers/media/pci/pt3/Kconfig b/drivers/media/pci/pt3/Kconfig
> new file mode 100644
> index 0000000..0d866a0
> --- /dev/null
> +++ b/drivers/media/pci/pt3/Kconfig
> @@ -0,0 +1,11 @@
> +config PT3_DVB
> +	tristate "Earthsoft PT3 ISDB-S/T cards"
> +	depends on DVB_CORE && PCI
> +	select DVB_TC90522 if MEDIA_SUBDRV_AUTOSELECT
> +	select MEDIA_TUNER_QM1D1C0042 if MEDIA_SUBDRV_AUTOSELECT
> +	select MEDIA_TUNER_MXL301RF if MEDIA_SUBDRV_AUTOSELECT
> +	help
> +	  Support for Earthsoft PT3 PCI-Express cards.
> +	  You need to enable frontend (TC90522) & tuners (QM1D1C0042, MXL301RF)
> +	  Say Y or M if you own such a device and want to use it.
> +
> diff --git a/drivers/media/pci/pt3/Makefile b/drivers/media/pci/pt3/Makefile
> new file mode 100644
> index 0000000..ede00e1
> --- /dev/null
> +++ b/drivers/media/pci/pt3/Makefile
> @@ -0,0 +1,6 @@
> +pt3_dvb-objs := pt3_pci.o pt3_dma.o pt3_i2c.o
> +
> +obj-$(CONFIG_PT3_DVB) += pt3_dvb.o
> +
> +ccflags-y += -Idrivers/media/dvb-core -Idrivers/media/dvb-frontends -Idrivers/media/tuners
> +
> diff --git a/drivers/media/pci/pt3/pt3_common.h b/drivers/media/pci/pt3/pt3_common.h
> new file mode 100644
> index 0000000..21b36f5
> --- /dev/null
> +++ b/drivers/media/pci/pt3/pt3_common.h
> @@ -0,0 +1,86 @@
> +/*
> + * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
> + *
> + * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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	__PT3_COMMON_H__
> +#define	__PT3_COMMON_H__
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME " " fmt
> +
> +#include <linux/pci.h>
> +#include <linux/kthread.h>
> +#include <linux/freezer.h>
> +#include "dvb_demux.h"
> +#include "dmxdev.h"
> +#include "dvb_frontend.h"
> +
> +#define DRV_NAME "pt3_dvb"
> +
> +/* register idx */
> +#define PT3_REG_VERSION	0x00	/*	R	Version		*/
> +#define PT3_REG_BUS	0x04	/*	R	Bus		*/
> +#define PT3_REG_SYS_W	0x08	/*	W	System		*/
> +#define PT3_REG_SYS_R	0x0c	/*	R	System		*/
> +#define PT3_REG_I2C_W	0x10	/*	W	I2C		*/
> +#define PT3_REG_I2C_R	0x14	/*	R	I2C		*/
> +#define PT3_REG_RAM_W	0x18	/*	W	RAM		*/
> +#define PT3_REG_RAM_R	0x1c	/*	R	RAM		*/
> +#define PT3_REG_BASE	0x40	/* + 0x18*idx			*/
> +#define PT3_REG_DMA_D_L	0x00	/*	W	DMA descriptor	*/
> +#define PT3_REG_DMA_D_H	0x04	/*	W	DMA descriptor	*/
> +#define PT3_REG_DMA_CTL	0x08	/*	W	DMA		*/
> +#define PT3_REG_TS_CTL	0x0c	/*	W	TS		*/
> +#define PT3_REG_STATUS	0x10	/*	R	DMA/FIFO/TS	*/
> +#define PT3_REG_TS_ERR	0x14	/*	R	TS		*/
> +
> +struct pt3_adapter;
> +
> +struct pt3_board {
> +	struct mutex lock;
> +	int lnb;
> +	bool reset;
> +
> +	struct pci_dev *pdev;
> +	int bars;
> +	void __iomem *bar_reg, *bar_mem;
> +	struct i2c_adapter i2c;
> +	u8 i2c_buf;
> +	u32 i2c_addr;
> +	bool i2c_filled;
> +
> +	struct pt3_adapter **adap;
> +};
> +
> +struct pt3_adapter {
> +	struct mutex lock;
> +	struct pt3_board *pt3;
> +
> +	u8 idx;
> +	bool sleep;
> +	struct pt3_dma *dma;
> +	struct task_struct *kthread;
> +	struct dvb_adapter dvb;
> +	struct dvb_demux demux;
> +	struct dmxdev dmxdev;
> +	int users, voltage;
> +
> +	struct dvb_frontend *fe;
> +	int (*orig_voltage)(struct dvb_frontend *fe, fe_sec_voltage_t voltage);
> +	int (*orig_sleep)(struct dvb_frontend *fe);
> +	int (*orig_init)(struct dvb_frontend *fe);
> +};
> +
> +#endif
> +
> diff --git a/drivers/media/pci/pt3/pt3_dma.c b/drivers/media/pci/pt3/pt3_dma.c
> new file mode 100644
> index 0000000..a1fb82e
> --- /dev/null
> +++ b/drivers/media/pci/pt3/pt3_dma.c
> @@ -0,0 +1,333 @@
> +/*
> + * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 "pt3_dma.h"
> +
> +#define PT3_DMA_MAX_DESCS	204
> +#define PT3_DMA_PAGE_SIZE	(PT3_DMA_MAX_DESCS * sizeof(struct pt3_dma_desc))
> +#define PT3_DMA_BLOCK_COUNT	17
> +#define PT3_DMA_BLOCK_SIZE	(PT3_DMA_PAGE_SIZE * 47)
> +#define PT3_DMA_TS_BUF_SIZE	(PT3_DMA_BLOCK_SIZE * PT3_DMA_BLOCK_COUNT)
> +#define PT3_DMA_TS_SYNC		0x47
> +#define PT3_DMA_TS_NOT_SYNC	0x74
> +
> +void pt3_dma_free(struct pt3_dma *dma)
> +{
> +	struct pt3_dma_page *page;
> +	u32 i;
> +
> +	if (dma->ts_info) {
> +		for (i = 0; i < dma->ts_count; i++) {
> +			page = &dma->ts_info[i];
> +			if (page->data)
> +				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
> +		}
> +		kfree(dma->ts_info);
> +	}
> +	if (dma->desc_info) {
> +		for (i = 0; i < dma->desc_count; i++) {
> +			page = &dma->desc_info[i];
> +			if (page->data)
> +				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
> +		}
> +		kfree(dma->desc_info);
> +	}
> +	kfree(dma);
> +}
> +
> +struct pt3_dma_desc {
> +	u64 page_addr;
> +	u32 page_size;
> +	u64 next_desc;
> +} __packed;
> +
> +void pt3_dma_build_page_descriptor(struct pt3_dma *dma)
> +{
> +	struct pt3_dma_page *desc_info, *ts_info;
> +	u64 ts_addr, desc_addr;
> +	u32 i, j, ts_size, desc_remain, ts_info_pos, desc_info_pos;
> +	struct pt3_dma_desc *prev, *curr;
> +
> +	pr_debug("#%d %s ts_count=%d ts_size=%d desc_count=%d desc_size=%d\n",
> +		dma->adap->idx, __func__, dma->ts_count, dma->ts_info[0].size, dma->desc_count, dma->desc_info[0].size);
> +	desc_info_pos = ts_info_pos = 0;
> +	desc_info = &dma->desc_info[desc_info_pos];
> +	desc_addr   = desc_info->addr;
> +	desc_remain = desc_info->size;
> +	desc_info->data_pos = 0;
> +	prev = NULL;
> +	curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
> +	desc_info_pos++;
> +
> +	for (i = 0; i < dma->ts_count; i++) {
> +		if (unlikely(ts_info_pos >= dma->ts_count)) {
> +			pr_debug("#%d ts_info overflow max=%d curr=%d\n", dma->adap->idx, dma->ts_count, ts_info_pos);
> +			return;
> +		}
> +		ts_info = &dma->ts_info[ts_info_pos];
> +		ts_addr = ts_info->addr;
> +		ts_size = ts_info->size;
> +		ts_info_pos++;
> +		pr_debug("#%d i=%d, ts_info addr=0x%llx ts_size=%d\n", dma->adap->idx, i, ts_addr, ts_size);
> +		for (j = 0; j < ts_size / PT3_DMA_PAGE_SIZE; j++) {
> +			if (desc_remain < sizeof(struct pt3_dma_desc)) {
> +				if (unlikely(desc_info_pos >= dma->desc_count)) {
> +					pr_debug("#%d desc_info overflow max=%d curr=%d\n",
> +						dma->adap->idx, dma->desc_count, desc_info_pos);
> +					return;
> +				}
> +				desc_info = &dma->desc_info[desc_info_pos];
> +				desc_info->data_pos = 0;
> +				curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
> +				pr_debug("#%d desc_info_pos=%d ts_addr=0x%llx remain=%d\n",
> +					dma->adap->idx, desc_info_pos, ts_addr, desc_remain);
> +				desc_addr = desc_info->addr;
> +				desc_remain = desc_info->size;
> +				desc_info_pos++;
> +			}
> +			if (prev)
> +				prev->next_desc = desc_addr | 0b10;
> +			curr->page_addr = ts_addr           | 0b111;
> +			curr->page_size = PT3_DMA_PAGE_SIZE | 0b111;
> +			curr->next_desc = 0b10;
> +			pr_debug("#%d j=%d dma write desc ts_addr=0x%llx desc_info_pos=%d desc_remain=%d\n",
> +				dma->adap->idx, j, ts_addr, desc_info_pos, desc_remain);
> +			ts_addr += PT3_DMA_PAGE_SIZE;
> +
> +			prev = curr;
> +			desc_info->data_pos += sizeof(struct pt3_dma_desc);
> +			if (unlikely(desc_info->data_pos > desc_info->size)) {
> +				pr_debug("#%d dma desc_info data overflow max=%d curr=%d\n",
> +					dma->adap->idx, desc_info->size, desc_info->data_pos);
> +				return;
> +			}
> +			curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
> +			desc_addr += sizeof(struct pt3_dma_desc);
> +			desc_remain -= sizeof(struct pt3_dma_desc);
> +		}
> +	}
> +	if (prev)
> +		prev->next_desc = dma->desc_info->addr | 0b10;
> +}
> +
> +struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap)
> +{
> +	struct pt3_dma_page *page;
> +	u32 i;
> +
> +	struct pt3_dma *dma = kzalloc(sizeof(struct pt3_dma), GFP_KERNEL);
> +	if (!dma) {
> +		pr_debug("#%d fail allocate PT3_DMA\n", adap->idx);
> +		goto fail;
> +	}
> +	dma->adap = adap;
> +	dma->enabled = false;
> +	mutex_init(&dma->lock);
> +
> +	dma->ts_count = PT3_DMA_BLOCK_COUNT;
> +	dma->ts_info = kzalloc(sizeof(struct pt3_dma_page) * dma->ts_count, GFP_KERNEL);
> +	if (!dma->ts_info) {
> +		pr_debug("#%d fail allocate TS DMA page\n", adap->idx);
> +		goto fail;
> +	}
> +	pr_debug("#%d Alloc TS buf (ts_count %d)\n", adap->idx, dma->ts_count);
> +	for (i = 0; i < dma->ts_count; i++) {
> +		page = &dma->ts_info[i];
> +		page->size = PT3_DMA_BLOCK_SIZE;
> +		page->data_pos = 0;
> +		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
> +		if (!page->data) {
> +			pr_debug("#%d fail alloc_consistent. %d\n", adap->idx, i);
> +			goto fail;
> +		}
> +	}
> +
> +	dma->desc_count = 1 + (PT3_DMA_TS_BUF_SIZE / PT3_DMA_PAGE_SIZE - 1) / PT3_DMA_MAX_DESCS;
> +	dma->desc_info = kzalloc(sizeof(struct pt3_dma_page) * dma->desc_count, GFP_KERNEL);
> +	if (!dma->desc_info) {
> +		pr_debug("#%d fail allocate Desc DMA page\n", adap->idx);
> +		goto fail;
> +	}
> +	pr_debug("#%d Alloc Descriptor buf (desc_count %d)\n", adap->idx, dma->desc_count);
> +	for (i = 0; i < dma->desc_count; i++) {
> +		page = &dma->desc_info[i];
> +		page->size = PT3_DMA_PAGE_SIZE;
> +		page->data_pos = 0;
> +		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
> +		if (!page->data) {
> +			pr_debug("#%d fail alloc_consistent %d\n", adap->idx, i);
> +			goto fail;
> +		}
> +	}
> +
> +	pr_debug("#%d build page descriptor\n", adap->idx);
> +	pt3_dma_build_page_descriptor(dma);
> +	return dma;
> +fail:
> +	if (dma)
> +		pt3_dma_free(dma);
> +	return NULL;
> +}
> +
> +void __iomem *pt3_dma_get_base_addr(struct pt3_dma *dma)
> +{
> +	return dma->adap->pt3->bar_reg + PT3_REG_BASE + (0x18 * dma->adap->idx);
> +}
> +
> +void pt3_dma_reset(struct pt3_dma *dma)
> +{
> +	struct pt3_dma_page *ts;
> +	u32 i;
> +
> +	for (i = 0; i < dma->ts_count; i++) {
> +		ts = &dma->ts_info[i];
> +		memset(ts->data, 0, ts->size);
> +		ts->data_pos = 0;
> +		*ts->data = PT3_DMA_TS_NOT_SYNC;
> +	}
> +	dma->ts_pos = 0;
> +}
> +
> +void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled)
> +{
> +	void __iomem *base = pt3_dma_get_base_addr(dma);
> +	u64 start_addr = dma->desc_info->addr;
> +
> +	if (enabled) {
> +		pr_debug("#%d DMA enable start_addr=%llx\n", dma->adap->idx, start_addr);
> +		pt3_dma_reset(dma);
> +		writel(1 << 1, base + PT3_REG_DMA_CTL);	/* stop DMA */
> +		writel(start_addr         & 0xffffffff, base + PT3_REG_DMA_D_L);
> +		writel((start_addr >> 32) & 0xffffffff, base + PT3_REG_DMA_D_H);
> +		pr_debug("set descriptor address low %llx\n",  start_addr         & 0xffffffff);
> +		pr_debug("set descriptor address high %llx\n", (start_addr >> 32) & 0xffffffff);
> +		writel(1 << 0, base + PT3_REG_DMA_CTL);	/* start DMA */
> +	} else {
> +		pr_debug("#%d DMA disable\n", dma->adap->idx);
> +		writel(1 << 1, base + PT3_REG_DMA_CTL);	/* stop DMA */
> +		while (1) {
> +			if (!(readl(base + PT3_REG_STATUS) & 1))
> +				break;
> +			msleep_interruptible(1);
> +		}
> +	}
> +	dma->enabled = enabled;
> +}
> +
> +/* convert Gray code to binary, e.g. 1001 -> 1110 */
> +static u32 pt3_dma_gray2binary(u32 gray, u32 bit)
> +{
> +	u32 binary = 0, i, j, k;
> +
> +	for (i = 0; i < bit; i++) {
> +		k = 0;
> +		for (j = i; j < bit; j++)
> +			k ^= (gray >> j) & 1;
> +		binary |= k << i;
> +	}
> +	return binary;
> +}
> +
> +u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma)
> +{
> +	return pt3_dma_gray2binary(readl(pt3_dma_get_base_addr(dma) + PT3_REG_TS_ERR), 32);
> +}
> +
> +void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval)
> +{
> +	void __iomem *base = pt3_dma_get_base_addr(dma);
> +	u32 data = mode | initval;
> +	pr_debug("#%d %s base=%p data=0x%04x\n", dma->adap->idx, __func__, base, data);
> +	writel(data, base + PT3_REG_TS_CTL);
> +}
> +
> +bool pt3_dma_ready(struct pt3_dma *dma)
> +{
> +	struct pt3_dma_page *ts;
> +	u8 *p;
> +
> +	u32 next = dma->ts_pos + 1;
> +	if (next >= dma->ts_count)
> +		next = 0;
> +	ts = &dma->ts_info[next];
> +	p = &ts->data[ts->data_pos];
> +
> +	if (*p == PT3_DMA_TS_SYNC)
> +		return true;
> +	if (*p == PT3_DMA_TS_NOT_SYNC)
> +		return false;
> +
> +	pr_debug("#%d invalid sync byte value=0x%02x ts_pos=%d data_pos=%d curr=0x%02x\n",
> +		dma->adap->idx, *p, next, ts->data_pos, dma->ts_info[dma->ts_pos].data[0]);
> +	return false;
> +}
> +
> +ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux)
> +{
> +	bool ready;
> +	struct pt3_dma_page *ts;
> +	u32 i, prev;
> +	size_t csize, remain = dma->ts_info[dma->ts_pos].size;
> +
> +	mutex_lock(&dma->lock);
> +	pr_debug("#%d dma_copy ts_pos=0x%x data_pos=0x%x\n",
> +		   dma->adap->idx, dma->ts_pos, dma->ts_info[dma->ts_pos].data_pos);
> +	for (;;) {
> +		for (i = 0; i < 20; i++) {
> +			ready = pt3_dma_ready(dma);
> +			if (ready)
> +				break;
> +			msleep_interruptible(30);
> +		}
> +		if (!ready) {
> +			pr_debug("#%d dma_copy NOT READY\n", dma->adap->idx);
> +			goto last;
> +		}
> +		prev = dma->ts_pos - 1;
> +		if (prev < 0 || dma->ts_count <= prev)
> +			prev = dma->ts_count - 1;
> +		if (dma->ts_info[prev].data[0] != PT3_DMA_TS_NOT_SYNC)
> +			pr_debug("#%d DMA buffer overflow. prev=%d data=0x%x\n",
> +					dma->adap->idx, prev, dma->ts_info[prev].data[0]);
> +		ts = &dma->ts_info[dma->ts_pos];
> +		for (;;) {
> +			csize = (remain < (ts->size - ts->data_pos)) ?
> +				 remain : (ts->size - ts->data_pos);
> +			dvb_dmx_swfilter(demux, &ts->data[ts->data_pos], csize);
> +			remain -= csize;
> +			ts->data_pos += csize;
> +			if (ts->data_pos >= ts->size) {
> +				ts->data_pos = 0;
> +				ts->data[ts->data_pos] = PT3_DMA_TS_NOT_SYNC;
> +				dma->ts_pos++;
> +				if (dma->ts_pos >= dma->ts_count)
> +					dma->ts_pos = 0;
> +				break;
> +			}
> +			if (remain <= 0)
> +				goto last;
> +		}
> +	}
> +last:
> +	mutex_unlock(&dma->lock);
> +	return dma->ts_info[dma->ts_pos].size - remain;
> +}
> +
> +u32 pt3_dma_get_status(struct pt3_dma *dma)
> +{
> +	return readl(pt3_dma_get_base_addr(dma) + PT3_REG_STATUS);
> +}
> +
> diff --git a/drivers/media/pci/pt3/pt3_dma.h b/drivers/media/pci/pt3/pt3_dma.h
> new file mode 100644
> index 0000000..934c222
> --- /dev/null
> +++ b/drivers/media/pci/pt3/pt3_dma.h
> @@ -0,0 +1,50 @@
> +/*
> + * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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	__PT3_DMA_H__
> +#define	__PT3_DMA_H__
> +
> +#include "pt3_common.h"
> +
> +struct pt3_dma_page {
> +	dma_addr_t addr;
> +	u8 *data;
> +	u32 size, data_pos;
> +};
> +
> +enum pt3_dma_mode {
> +	USE_LFSR = 1 << 16,
> +	REVERSE  = 1 << 17,
> +	RESET    = 1 << 18,
> +};
> +
> +struct pt3_dma {
> +	struct pt3_adapter *adap;
> +	bool enabled;
> +	u32 ts_pos, ts_count, desc_count;
> +	struct pt3_dma_page *ts_info, *desc_info;
> +	struct mutex lock;
> +};
> +
> +ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux);
> +struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap);
> +void pt3_dma_free(struct pt3_dma *dma);
> +u32 pt3_dma_get_status(struct pt3_dma *dma);
> +u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma);
> +void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled);
> +void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval);
> +
> +#endif
> diff --git a/drivers/media/pci/pt3/pt3_i2c.c b/drivers/media/pci/pt3/pt3_i2c.c
> new file mode 100644
> index 0000000..e872ae1
> --- /dev/null
> +++ b/drivers/media/pci/pt3/pt3_i2c.c
> @@ -0,0 +1,189 @@
> +/*
> + * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 "pt3_i2c.h"
> +
> +#define PT3_I2C_DATA_OFFSET	2048
> +#define PT3_I2C_START_ADDR	0x17fa
> +
> +enum pt3_i2c_cmd {
> +	I_END,
> +	I_ADDRESS,
> +	I_CLOCK_L,
> +	I_CLOCK_H,
> +	I_DATA_L,
> +	I_DATA_H,
> +	I_RESET,
> +	I_SLEEP,
> +	I_DATA_L_NOP  = 0x08,
> +	I_DATA_H_NOP  = 0x0c,
> +	I_DATA_H_READ = 0x0d,
> +	I_DATA_H_ACK0 = 0x0e,
> +	I_DATA_H_ACK1 = 0x0f,
> +};
> +
> +bool pt3_i2c_is_clean(struct pt3_board *pt3)
> +{
> +	return (readl(pt3->bar_reg + PT3_REG_I2C_R) >> 3) & 1;
> +}
> +
> +void pt3_i2c_reset(struct pt3_board *pt3)
> +{
> +	writel(1 << 17, pt3->bar_reg + PT3_REG_I2C_W);	/* 0x00020000 */
> +}
> +
> +void pt3_i2c_wait(struct pt3_board *pt3, u32 *status)
> +{
> +	u32 val;
> +
> +	while (1) {
> +		val = readl(pt3->bar_reg + PT3_REG_I2C_R);
> +		if (!(val & 1))				/* sequence stopped */
> +			break;
> +		msleep_interruptible(1);
> +	}
> +	if (status)
> +		*status = val;				/* I2C register status */
> +}
> +
> +void pt3_i2c_mem_write(struct pt3_board *pt3, u8 data)
> +{
> +	void __iomem *dst = pt3->bar_mem + PT3_I2C_DATA_OFFSET + pt3->i2c_addr;
> +
> +	if (pt3->i2c_filled) {
> +		pt3->i2c_buf |= data << 4;
> +		writeb(pt3->i2c_buf, dst);
> +		pt3->i2c_addr++;
> +	} else
> +		pt3->i2c_buf = data;
> +	pt3->i2c_filled ^= true;
> +}
> +
> +void pt3_i2c_start(struct pt3_board *pt3)
> +{
> +	pt3_i2c_mem_write(pt3, I_DATA_H);
> +	pt3_i2c_mem_write(pt3, I_CLOCK_H);
> +	pt3_i2c_mem_write(pt3, I_DATA_L);
> +	pt3_i2c_mem_write(pt3, I_CLOCK_L);
> +}
> +
> +void pt3_i2c_cmd_write(struct pt3_board *pt3, const u8 *data, u32 size)
> +{
> +	u32 i, j;
> +	u8 byte;
> +
> +	for (i = 0; i < size; i++) {
> +		byte = data[i];
> +		for (j = 0; j < 8; j++)
> +			pt3_i2c_mem_write(pt3, (byte >> (7 - j)) & 1 ? I_DATA_H_NOP : I_DATA_L_NOP);
> +		pt3_i2c_mem_write(pt3, I_DATA_H_ACK0);
> +	}
> +}
> +
> +void pt3_i2c_cmd_read(struct pt3_board *pt3, u8 *data, u32 size)
> +{
> +	u32 i, j;
> +
> +	for (i = 0; i < size; i++) {
> +		for (j = 0; j < 8; j++)
> +			pt3_i2c_mem_write(pt3, I_DATA_H_READ);
> +		if (i == (size - 1))
> +			pt3_i2c_mem_write(pt3, I_DATA_H_NOP);
> +		else
> +			pt3_i2c_mem_write(pt3, I_DATA_L_NOP);
> +	}
> +}
> +
> +void pt3_i2c_stop(struct pt3_board *pt3)
> +{
> +	pt3_i2c_mem_write(pt3, I_DATA_L);
> +	pt3_i2c_mem_write(pt3, I_CLOCK_H);
> +	pt3_i2c_mem_write(pt3, I_DATA_H);
> +}
> +
> +int pt3_i2c_flush(struct pt3_board *pt3, bool end, u32 start_addr)
> +{
> +	u32 status;
> +
> +	if (end) {
> +		pt3_i2c_mem_write(pt3, I_END);
> +		if (pt3->i2c_filled)
> +			pt3_i2c_mem_write(pt3, I_END);
> +	}
> +	pt3_i2c_wait(pt3, &status);
> +	writel(1 << 16 | start_addr, pt3->bar_reg + PT3_REG_I2C_W);	/* 0x00010000 start sequence */
> +	pt3_i2c_wait(pt3, &status);
> +	if (status & 0b0110) {						/* ACK status */
> +		pr_err("%s failed, status=0x%x\n", __func__, status);
> +		return -EIO;
> +	}
> +	return 0;
> +}
> +
> +u32 pt3_i2c_func(struct i2c_adapter *i2c)
> +{
> +	return I2C_FUNC_I2C;
> +}
> +
> +int pt3_i2c_xfer(struct i2c_adapter *i2c, struct i2c_msg *msg, int num)
> +{
> +	struct pt3_board *pt3 = i2c_get_adapdata(i2c);
> +	int i, j;
> +
> +	if (!num)
> +		return pt3_i2c_flush(pt3, false, PT3_I2C_START_ADDR);
> +	if ((num < 1) || (num > 3) || !msg || msg[0].flags)	/* always write first */
> +		return -ENOTSUPP;
> +	mutex_lock(&pt3->lock);
> +	pt3->i2c_addr = 0;
> +	for (i = 0; i < num; i++) {
> +		u8 byte = (msg[i].addr << 1) | (msg[i].flags & 1);
> +		pt3_i2c_start(pt3);
> +		pt3_i2c_cmd_write(pt3, &byte, 1);
> +		if (msg[i].flags == I2C_M_RD)
> +			pt3_i2c_cmd_read(pt3, msg[i].buf, msg[i].len);
> +		else
> +			pt3_i2c_cmd_write(pt3, msg[i].buf, msg[i].len);
> +	}
> +	pt3_i2c_stop(pt3);
> +	if (pt3_i2c_flush(pt3, true, 0))
> +		num = -EIO;
> +	else
> +		for (i = 1; i < num; i++)
> +			if (msg[i].flags == I2C_M_RD)
> +				for (j = 0; j < msg[i].len; j++)
> +					msg[i].buf[j] = readb(pt3->bar_mem + PT3_I2C_DATA_OFFSET + j);
> +	mutex_unlock(&pt3->lock);
> +	return num;
> +}
> +
> +static const struct i2c_algorithm pt3_i2c_algo = {
> +	.functionality = pt3_i2c_func,
> +	.master_xfer = pt3_i2c_xfer,
> +};
> +
> +int pt3_i2c_add_adapter(struct pt3_board *pt3)
> +{
> +	struct i2c_adapter *i2c = &pt3->i2c;
> +	i2c->algo = &pt3_i2c_algo;
> +	i2c->algo_data = NULL;
> +	i2c->dev.parent = &pt3->pdev->dev;
> +	strcpy(i2c->name, DRV_NAME);
> +	i2c_set_adapdata(i2c, pt3);
> +	return	i2c_add_adapter(i2c) ||
> +		(!pt3_i2c_is_clean(pt3) && pt3_i2c_flush(pt3, false, 0));
> +}
> +
> diff --git a/drivers/media/pci/pt3/pt3_i2c.h b/drivers/media/pci/pt3/pt3_i2c.h
> new file mode 100644
> index 0000000..8424fd5
> --- /dev/null
> +++ b/drivers/media/pci/pt3/pt3_i2c.h
> @@ -0,0 +1,25 @@
> +/*
> + * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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	__PT3_I2C_H__
> +#define	__PT3_I2C_H__
> +
> +#include "pt3_common.h"
> +
> +void pt3_i2c_reset(struct pt3_board *pt3);
> +int pt3_i2c_add_adapter(struct pt3_board *pt3);
> +
> +#endif
> diff --git a/drivers/media/pci/pt3/pt3_pci.c b/drivers/media/pci/pt3/pt3_pci.c
> new file mode 100644
> index 0000000..858c30d
> --- /dev/null
> +++ b/drivers/media/pci/pt3/pt3_pci.c
> @@ -0,0 +1,397 @@
> +/*
> + * DVB driver for Earthsoft PT3 ISDB-S/T PCIE bridge Altera Cyclone IV FPGA EP4CGX15BF14C8N
> + *
> + * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 "pt3_dma.h"
> +#include "pt3_i2c.h"
> +#include "tc90522.h"
> +#include "qm1d1c0042.h"
> +#include "mxl301rf.h"
> +
> +MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
> +MODULE_DESCRIPTION("Earthsoft PT3 DVB Driver");
> +MODULE_LICENSE("GPL");
> +
> +static struct pci_device_id pt3_id_table[] = {
> +	{ PCI_DEVICE(0x1172, 0x4c15) },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(pci, pt3_id_table);
> +
> +static int lnb = 2;
> +module_param(lnb, int, 0);
> +MODULE_PARM_DESC(lnb, "LNB level (0:OFF 1:+11V 2:+15V)");
> +
> +struct pt3_lnb {
> +	u32 bits;
> +	char *str;
> +};
> +
> +static const struct pt3_lnb pt3_lnb[] = {
> +	{0b1100,  "0V"},
> +	{0b1101, "11V"},
> +	{0b1111, "15V"},
> +};
> +
> +struct pt3_cfg {
> +	fe_delivery_system_t type;
> +	u8 addr_tuner, addr_demod;
> +};
> +
> +static const struct pt3_cfg pt3_cfg[] = {
> +	{SYS_ISDBS, 0x63, 0b00010001},
> +	{SYS_ISDBS, 0x60, 0b00010011},
> +	{SYS_ISDBT, 0x62, 0b00010000},
> +	{SYS_ISDBT, 0x61, 0b00010010},
> +};
> +#define PT3_ADAPN ARRAY_SIZE(pt3_cfg)
> +
> +int pt3_update_lnb(struct pt3_board *pt3)
> +{
> +	u8 i, lnb_eff = 0;
> +
> +	if (pt3->reset) {
> +		writel(pt3_lnb[0].bits, pt3->bar_reg + PT3_REG_SYS_W);
> +		pt3->reset = false;
> +		pt3->lnb = 0;
> +	} else {
> +		struct pt3_adapter *adap;
> +		for (i = 0; i < PT3_ADAPN; i++) {
> +			adap = pt3->adap[i];
> +			pr_debug("#%d sleep %d\n", adap->idx, adap->sleep);
> +			if ((pt3_cfg[i].type == SYS_ISDBS) && (!adap->sleep))
> +				lnb_eff |= adap->voltage ? adap->voltage : lnb;
> +		}
> +		if (unlikely(lnb_eff < 0 || 2 < lnb_eff)) {
> +			pr_err("Inconsistent LNB settings\n");
> +			return -EINVAL;
> +		}
> +		if (pt3->lnb != lnb_eff) {
> +			writel(pt3_lnb[lnb_eff].bits, pt3->bar_reg + PT3_REG_SYS_W);
> +			pt3->lnb = lnb_eff;
> +		}
> +	}
> +	pr_debug("LNB=%s\n", pt3_lnb[lnb_eff].str);
> +	return 0;
> +}
> +
> +int pt3_thread(void *data)
> +{
> +	size_t ret;
> +	struct pt3_adapter *adap = data;
> +
> +	pr_debug("#%d %s sleep %d\n", adap->idx, __func__, adap->sleep);
> +	set_freezable();
> +	while (!kthread_should_stop()) {
> +		try_to_freeze();
> +		while ((ret = pt3_dma_copy(adap->dma, &adap->demux)) > 0)
> +			;
> +		if (ret < 0) {
> +			pr_debug("#%d fail dma_copy\n", adap->idx);
> +			msleep_interruptible(1);
> +		}
> +	}
> +	return 0;
> +}
> +
> +int pt3_start_feed(struct dvb_demux_feed *feed)
> +{
> +	int ret = 0;
> +	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
> +	pr_debug("#%d %s sleep %d\n", adap->idx, __func__, adap->sleep);
> +	if (!adap->users++) {
> +		pr_debug("#%d %s selected, DMA %s\n",
> +			adap->idx, (pt3_cfg[adap->idx].type == SYS_ISDBS) ? "S" : "T",
> +			pt3_dma_get_status(adap->dma) & 1 ? "ON" : "OFF");
> +		mutex_lock(&adap->lock);
> +		if (!adap->kthread) {
> +			adap->kthread = kthread_run(pt3_thread, adap, DRV_NAME "_%d", adap->idx);
> +			if (IS_ERR(adap->kthread)) {
> +				ret = PTR_ERR(adap->kthread);
> +				adap->kthread = NULL;
> +			} else {
> +				pt3_dma_set_test_mode(adap->dma, RESET, 0);	/* reset error count */
> +				pt3_dma_set_enabled(adap->dma, true);
> +			}
> +		}
> +		mutex_unlock(&adap->lock);
> +		if (ret)
> +			return ret;
> +	}
> +	return 0;
> +}
> +
> +int pt3_stop_feed(struct dvb_demux_feed *feed)
> +{
> +	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
> +	pr_debug("#%d %s sleep %d\n", adap->idx, __func__, adap->sleep);
> +	if (!--adap->users) {
> +		mutex_lock(&adap->lock);
> +		if (adap->kthread) {
> +			pt3_dma_set_enabled(adap->dma, false);
> +			pr_debug("#%d DMA ts_err packet cnt %d\n",
> +				adap->idx, pt3_dma_get_ts_error_packet_count(adap->dma));
> +			kthread_stop(adap->kthread);
> +			adap->kthread = NULL;
> +		}
> +		mutex_unlock(&adap->lock);
> +		msleep_interruptible(40);
> +	}
> +	return 0;
> +}
> +
> +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
> +
> +struct pt3_adapter *pt3_dvb_register_adapter(struct pt3_board *pt3)
> +{
> +	int ret;
> +	struct dvb_adapter *dvb;
> +	struct dvb_demux *demux;
> +	struct dmxdev *dmxdev;
> +	struct pt3_adapter *adap = kzalloc(sizeof(struct pt3_adapter), GFP_KERNEL);
> +	if (!adap)
> +		return ERR_PTR(-ENOMEM);
> +
> +	adap->pt3 = pt3;
> +	adap->sleep = true;
> +
> +	dvb = &adap->dvb;
> +	dvb->priv = adap;
> +	ret = dvb_register_adapter(dvb, DRV_NAME, THIS_MODULE, &pt3->pdev->dev, adapter_nr);
> +	pr_debug("adapter%d registered\n", ret);
> +	if (ret >= 0) {
> +		demux = &adap->demux;
> +		demux->dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING;
> +		demux->priv = adap;
> +		demux->feednum = 256;
> +		demux->filternum = 256;
> +		demux->start_feed = pt3_start_feed;
> +		demux->stop_feed = pt3_stop_feed;
> +		demux->write_to_decoder = NULL;
> +		ret = dvb_dmx_init(demux);
> +		if (ret >= 0) {
> +			dmxdev = &adap->dmxdev;
> +			dmxdev->filternum = 256;
> +			dmxdev->demux = &demux->dmx;
> +			dmxdev->capabilities = 0;
> +			ret = dvb_dmxdev_init(dmxdev, dvb);
> +			if (ret >= 0)
> +				return adap;
> +			dvb_dmx_release(demux);
> +		}
> +		dvb_unregister_adapter(dvb);
> +	}
> +	kfree(adap);
> +	return ERR_PTR(ret);
> +}
> +
> +int pt3_sleep(struct dvb_frontend *fe)
> +{
> +	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
> +	pr_debug("#%d %s orig %p\n", adap->idx, __func__, adap->orig_sleep);
> +	adap->sleep = true;
> +	pt3_update_lnb(adap->pt3);
> +	return (adap->orig_sleep) ? adap->orig_sleep(fe) : 0;
> +}
> +
> +int pt3_wakeup(struct dvb_frontend *fe)
> +{
> +	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
> +	pr_debug("#%d %s orig %p\n", adap->idx, __func__, adap->orig_init);
> +	adap->sleep = false;
> +	pt3_update_lnb(adap->pt3);
> +	return (adap->orig_init) ? adap->orig_init(fe) : 0;
> +}
> +
> +int pt3_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage)
> +{
> +	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
> +	adap->voltage = voltage == SEC_VOLTAGE_18 ? 2 : voltage == SEC_VOLTAGE_13 ? 1 : 0;
> +	return (adap->orig_voltage) ? adap->orig_voltage(fe, voltage) : 0;
> +}
> +
> +void pt3_cleanup_adapter(struct pt3_adapter *adap)
> +{
> +	if (!adap)
> +		return;
> +	if (adap->kthread)
> +		kthread_stop(adap->kthread);
> +	if (adap->fe) {
> +		dvb_unregister_frontend(adap->fe);
> +		adap->fe->ops.release(adap->fe);
> +	}
> +	if (adap->dma) {
> +		if (adap->dma->enabled)
> +			pt3_dma_set_enabled(adap->dma, false);
> +		pt3_dma_free(adap->dma);
> +	}
> +	adap->demux.dmx.close(&adap->demux.dmx);
> +	dvb_dmxdev_release(&adap->dmxdev);
> +	dvb_dmx_release(&adap->demux);
> +	dvb_unregister_adapter(&adap->dvb);
> +	kfree(adap);
> +}
> +
> +void pt3_remove(struct pci_dev *pdev)
> +{
> +	int i;
> +	struct pt3_board *pt3 = pci_get_drvdata(pdev);
> +
> +	if (pt3) {
> +		pt3->reset = true;
> +		pt3_update_lnb(pt3);
> +		for (i = 0; i < PT3_ADAPN; i++)
> +			pt3_cleanup_adapter(pt3->adap[i]);
> +		pt3_i2c_reset(pt3);
> +		i2c_del_adapter(&pt3->i2c);
> +		if (pt3->bar_mem)
> +			iounmap(pt3->bar_mem);
> +		if (pt3->bar_reg)
> +			iounmap(pt3->bar_reg);
> +		pci_release_selected_regions(pdev, pt3->bars);
> +		kfree(pt3->adap);
> +		kfree(pt3);
> +	}
> +	pci_disable_device(pdev);
> +}
> +
> +int pt3_abort(struct pci_dev *pdev, int ret, char *fmt, ...)
> +{
> +	va_list ap;
> +	char *s = NULL;
> +	int slen;
> +
> +	va_start(ap, fmt);
> +	slen = vsnprintf(s, 0, fmt, ap);
> +	s = vzalloc(slen);
> +	if (slen > 0 && s) {
> +		vsnprintf(s, slen, fmt, ap);
> +		dev_err(&pdev->dev, "%s", s);
> +		vfree(s);
> +	}
> +	va_end(ap);
> +	pt3_remove(pdev);
> +	return ret;
> +}
> +
> +int pt3_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
> +{
> +	struct pt3_board *pt3;
> +	struct pt3_adapter *adap;
> +	const struct pt3_cfg *cfg = pt3_cfg;
> +	struct dvb_frontend *fe[PT3_ADAPN];
> +	int i, err, bars = pci_select_bars(pdev, IORESOURCE_MEM);
> +
> +	err = pci_enable_device(pdev)					||
> +		pci_set_dma_mask(pdev, DMA_BIT_MASK(64))		||
> +		pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64))	||
> +		pci_read_config_dword(pdev, PCI_CLASS_REVISION, &i)	||
> +		pci_request_selected_regions(pdev, bars, DRV_NAME);
> +	if (err)
> +		return pt3_abort(pdev, err, "PCI/DMA error\n");
> +	if ((i & 0xFF) != 1)
> +		return pt3_abort(pdev, -EINVAL, "Revision 0x%x is not supported\n", i & 0xFF);
> +
> +	pci_set_master(pdev);
> +	err = pci_save_state(pdev);
> +	if (err)
> +		return pt3_abort(pdev, err, "Failed pci_save_state\n");
> +	pt3 = kzalloc(sizeof(struct pt3_board), GFP_KERNEL);
> +	if (!pt3)
> +		return pt3_abort(pdev, -ENOMEM, "struct pt3_board out of memory\n");
> +	pt3->adap = kzalloc(PT3_ADAPN * sizeof(struct pt3_adapter *), GFP_KERNEL);
> +	if (!pt3->adap)
> +		return pt3_abort(pdev, -ENOMEM, "No memory for *adap\n");
> +
> +	pt3->bars = bars;
> +	pt3->pdev = pdev;
> +	pci_set_drvdata(pdev, pt3);
> +	pt3->bar_reg = pci_ioremap_bar(pdev, 0);
> +	pt3->bar_mem = pci_ioremap_bar(pdev, 2);
> +	if (!pt3->bar_reg || !pt3->bar_mem)
> +		return pt3_abort(pdev, -EIO, "Failed pci_ioremap_bar\n");
> +
> +	err = readl(pt3->bar_reg + PT3_REG_VERSION);
> +	i = ((err >> 24) & 0xFF);
> +	if (i != 3)
> +		return pt3_abort(pdev, -EIO, "ID=0x%x, not a PT3\n", i);
> +	i = ((err >>  8) & 0xFF);
> +	if (i != 4)
> +		return pt3_abort(pdev, -EIO, "FPGA version 0x%x is not supported\n", i);
> +	err = pt3_i2c_add_adapter(pt3);
> +	if (err < 0)
> +		return pt3_abort(pdev, err, "Cannot add I2C\n");
> +	mutex_init(&pt3->lock);
> +
> +	for (i = 0; i < PT3_ADAPN; i++) {
> +		adap = pt3_dvb_register_adapter(pt3);
> +		if (IS_ERR(adap))
> +			return pt3_abort(pdev, PTR_ERR(adap), "Failed pt3_dvb_register_adapter\n");
> +		adap->idx = i;
> +		adap->dma = pt3_dma_create(adap);
> +		if (!adap->dma)
> +			return pt3_abort(pdev, -ENOMEM, "Failed pt3_dma_create\n");
> +		pt3->adap[i] = adap;
> +		adap->sleep = true;
> +		mutex_init(&adap->lock);
> +	}
> +
> +	for (i = 0; i < PT3_ADAPN; i++) {
> +		fe[i] = tc90522_attach(&pt3->i2c, i, cfg[i].type, cfg[i].addr_demod);
> +		if (!fe[i] || (cfg[i].type == SYS_ISDBS ?
> +			qm1d1c0042_attach(fe[i], i, cfg[i].addr_tuner) : mxl301rf_attach(fe[i], i, cfg[i].addr_tuner))) {
> +			while (i--)
> +				fe[i]->ops.release(fe[i]);
> +			return pt3_abort(pdev, -ENOMEM, "Cannot attach frontend\n");
> +		}
> +	}
> +	fe[i-1]->ops.init(fe[i-1]);	/* power on tuner & amp */
> +
> +	for (i = 0; i < PT3_ADAPN; i++) {
> +		struct pt3_adapter *adap = pt3->adap[i];
> +		pr_debug("#%d %s\n", i, __func__);
> +
> +		adap->orig_voltage     = fe[i]->ops.set_voltage;
> +		adap->orig_sleep       = fe[i]->ops.sleep;
> +		adap->orig_init        = fe[i]->ops.init;
> +		fe[i]->ops.set_voltage = pt3_set_voltage;
> +		fe[i]->ops.sleep       = pt3_sleep;
> +		fe[i]->ops.init        = pt3_wakeup;
> +		if ((adap->orig_init(fe[i]) && adap->orig_init(fe[i]) && adap->orig_init(fe[i])) ||
> +			adap->orig_sleep(fe[i]) || dvb_register_frontend(&adap->dvb, fe[i])) {
> +			while (i--)
> +				dvb_unregister_frontend(fe[i]);
> +			for (i = 0; i < PT3_ADAPN; i++) {
> +				fe[i]->ops.release(fe[i]);
> +				adap->fe = NULL;
> +			}
> +			return pt3_abort(pdev, -EREMOTEIO, "Cannot register frontend\n");
> +		}
> +		adap->fe = fe[i];
> +	}
> +	pt3->reset = true;
> +	pt3_update_lnb(pt3);
> +	return 0;
> +}
> +
> +static struct pci_driver pt3_driver = {
> +	.name		= DRV_NAME,
> +	.probe		= pt3_probe,
> +	.remove		= pt3_remove,
> +	.id_table	= pt3_id_table,
> +};
> +
> +module_pci_driver(pt3_driver);
> +
> diff --git a/drivers/media/tuners/Kconfig b/drivers/media/tuners/Kconfig
> index a128488..8ca8ed7 100644
> --- a/drivers/media/tuners/Kconfig
> +++ b/drivers/media/tuners/Kconfig
> @@ -243,4 +243,19 @@ config MEDIA_TUNER_R820T
>   	default m if !MEDIA_SUBDRV_AUTOSELECT
>   	help
>   	  Rafael Micro R820T silicon tuner driver.
> +
> +config MEDIA_TUNER_MXL301RF
> +	tristate "MaxLinear MXL301RF ISDB-T tuner"
> +	depends on MEDIA_SUPPORT && I2C
> +	default m if !MEDIA_SUBDRV_AUTOSELECT
> +	help
> +	  MaxLinear MxL301RF ISDB-T tuner driver for PT3.
> +
> +config MEDIA_TUNER_QM1D1C0042
> +	tristate "Sharp QM1D1C0042 ISDB-S tuner"
> +	depends on MEDIA_SUPPORT && I2C
> +	default m if !MEDIA_SUBDRV_AUTOSELECT
> +	help
> +	  Sharp QM1D1C0042 ISDB-S tuner driver for PT3 PCIE card.
> +
>   endmenu
> diff --git a/drivers/media/tuners/Makefile b/drivers/media/tuners/Makefile
> index efe82a9..b98c988 100644
> --- a/drivers/media/tuners/Makefile
> +++ b/drivers/media/tuners/Makefile
> @@ -37,6 +37,8 @@ obj-$(CONFIG_MEDIA_TUNER_FC0012) += fc0012.o
>   obj-$(CONFIG_MEDIA_TUNER_FC0013) += fc0013.o
>   obj-$(CONFIG_MEDIA_TUNER_IT913X) += tuner_it913x.o
>   obj-$(CONFIG_MEDIA_TUNER_R820T) += r820t.o
> +obj-$(CONFIG_MEDIA_TUNER_MXL301RF) += mxl301rf.o
> +obj-$(CONFIG_MEDIA_TUNER_QM1D1C0042) += qm1d1c0042.o
>
>   ccflags-y += -I$(srctree)/drivers/media/dvb-core
>   ccflags-y += -I$(srctree)/drivers/media/dvb-frontends
> diff --git a/drivers/media/tuners/mxl301rf.c b/drivers/media/tuners/mxl301rf.c
> new file mode 100644
> index 0000000..cc60831
> --- /dev/null
> +++ b/drivers/media/tuners/mxl301rf.c
> @@ -0,0 +1,390 @@
> +/*
> + * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-T tuner MaxLinear CMOS Hybrid TV MxL301RF
> + *
> + * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 "mxl301rf.h"
> +
> +MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
> +MODULE_DESCRIPTION("Earthsoft PT3 MxL301RF MaxLinear CMOS Hybrid TV ISDB-T tuner driver");
> +MODULE_LICENSE("GPL");
> +
> +struct mxl301rf {
> +	struct dvb_frontend *fe;
> +	u8 addr_tuner, idx;
> +	u32 freq;
> +};
> +
> +struct shf_dvbt {
> +	u32	freq,		/* Channel center frequency @ kHz	*/
> +		freq_th;	/* Offset frequency threshold @ kHz	*/
> +	u8	shf_val,	/* Spur shift value			*/
> +		shf_dir;	/* Spur shift direction			*/
> +};
> +
> +static const struct shf_dvbt shf_dvbt_tab[] = {
> +	{  64500, 500, 0x92, 0x07 },
> +	{ 191500, 300, 0xe2, 0x07 },
> +	{ 205500, 500, 0x2c, 0x04 },
> +	{ 212500, 500, 0x1e, 0x04 },
> +	{ 226500, 500, 0xd4, 0x07 },
> +	{  99143, 500, 0x9c, 0x07 },
> +	{ 173143, 500, 0xd4, 0x07 },
> +	{ 191143, 300, 0xd4, 0x07 },
> +	{ 207143, 500, 0xce, 0x07 },
> +	{ 225143, 500, 0xce, 0x07 },
> +	{ 243143, 500, 0xd4, 0x07 },
> +	{ 261143, 500, 0xd4, 0x07 },
> +	{ 291143, 500, 0xd4, 0x07 },
> +	{ 339143, 500, 0x2c, 0x04 },
> +	{ 117143, 500, 0x7a, 0x07 },
> +	{ 135143, 300, 0x7a, 0x07 },
> +	{ 153143, 500, 0x01, 0x07 }
> +};
> +
> +static const u32 mxl301rf_rf_tab[112] = {
> +	0x058d3f49, 0x05e8ccc9, 0x06445a49, 0x069fe7c9, 0x06fb7549,
> +	0x075702c9, 0x07b29049, 0x080e1dc9, 0x0869ab49, 0x08c538c9,
> +	0x0920c649, 0x097c53c9, 0x09f665c9, 0x0a51f349, 0x0aad80c9,
> +	0x0b090e49, 0x0b649bc9, 0x0ba1a4c9, 0x0bfd3249, 0x0c58bfc9,
> +	0x0cb44d49, 0x0d0fdac9, 0x0d6b6849, 0x0dc6f5c9, 0x0e228349,
> +	0x0e7e10c9, 0x0ed99e49, 0x0f352bc9, 0x0f90b949, 0x0fec46c9,
> +	0x1047d449, 0x10a361c9, 0x10feef49, 0x115a7cc9, 0x11b60a49,
> +	0x121197c9, 0x126d2549, 0x12c8b2c9, 0x13244049, 0x137fcdc9,
> +	0x13db5b49, 0x1436e8c9, 0x14927649, 0x14ee03c9, 0x15499149,
> +	0x15a51ec9, 0x1600ac49, 0x165c39c9, 0x16b7c749, 0x171354c9,
> +	0x176ee249, 0x17ca6fc9, 0x1825fd49, 0x18818ac9, 0x18dd1849,
> +	0x1938a5c9, 0x19943349, 0x19efc0c9, 0x1a4b4e49, 0x1aa6dbc9,
> +	0x1b026949, 0x1b5df6c9, 0x1bb98449, 0x1c339649, 0x1c8f23c9,
> +	0x1ceab149, 0x1d463ec9, 0x1da1cc49, 0x1dfd59c9, 0x1e58e749,
> +	0x1eb474c9, 0x1f100249, 0x1f6b8fc9, 0x1fc71d49, 0x2022aac9,
> +	0x207e3849, 0x20d9c5c9, 0x21355349, 0x2190e0c9, 0x21ec6e49,
> +	0x2247fbc9, 0x22a38949, 0x22ff16c9, 0x235aa449, 0x23b631c9,
> +	0x2411bf49, 0x246d4cc9, 0x24c8da49, 0x252467c9, 0x257ff549,
> +	0x25db82c9, 0x26371049, 0x26929dc9, 0x26ee2b49, 0x2749b8c9,
> +	0x27a54649, 0x2800d3c9, 0x285c6149, 0x28b7eec9, 0x29137c49,
> +	0x296f09c9, 0x29ca9749, 0x2a2624c9, 0x2a81b249, 0x2add3fc9,
> +	0x2b38cd49, 0x2b945ac9, 0x2befe849, 0x2c4b75c9, 0x2ca70349,
> +	0x2d0290c9, 0x2d5e1e49,
> +};
> +#define MXL301RF_NHK (mxl301rf_rf_tab[77])	/* 日本放送協会 Nippon Hōsō Kyōkai, Japan Broadcasting Corporation */
> +
> +int mxl301rf_freq(int freq)
> +{
> +	if (freq >= 90000000)
> +		return freq;					/* real_freq Hz	*/
> +	if (freq > 255)
> +		return MXL301RF_NHK;
> +	if (freq > 127)
> +		return mxl301rf_rf_tab[freq - 128];		/* freqno (IO#)	*/
> +	if (freq > 63) {					/* CATV		*/
> +		freq -= 64;
> +		if (freq > 22)
> +			return mxl301rf_rf_tab[freq - 1];	/* C23-C62	*/
> +		if (freq > 12)
> +			return mxl301rf_rf_tab[freq - 10];	/* C13-C22	*/
> +		return MXL301RF_NHK;
> +	}
> +	if (freq > 62)
> +		return MXL301RF_NHK;
> +	if (freq > 12)
> +		return mxl301rf_rf_tab[freq + 50];		/* 13-62	*/
> +	if (freq >  3)
> +		return mxl301rf_rf_tab[freq +  9];		/*  4-12	*/
> +	if (freq)
> +		return mxl301rf_rf_tab[freq -  1];		/*  1-3		*/
> +	return MXL301RF_NHK;
> +}
> +
> +void mxl301rf_rftune(u8 *data, u32 *size, u32 freq)
> +{
> +	u32 dig_rf_freq, tmp, frac_divider, kHz, MHz, i;
> +	u8 rf_data[] = {
> +		0x13, 0x00,	/* abort tune			*/
> +		0x3b, 0xc0,
> +		0x3b, 0x80,
> +		0x10, 0x95,	/* BW				*/
> +		0x1a, 0x05,
> +		0x61, 0x00,
> +		0x62, 0xa0,
> +		0x11, 0x40,	/* 2 bytes to store RF freq.	*/
> +		0x12, 0x0e,	/* 2 bytes to store RF freq.	*/
> +		0x13, 0x01	/* start tune			*/
> +	};
> +
> +	freq = mxl301rf_freq(freq);
> +	dig_rf_freq = 0;
> +	tmp = 0;
> +	frac_divider = 1000000;
> +	kHz = 1000;
> +	MHz = 1000000;
> +
> +	dig_rf_freq = freq / MHz;
> +	tmp = freq % MHz;
> +
> +	for (i = 0; i < 6; i++) {
> +		dig_rf_freq <<= 1;
> +		frac_divider /= 2;
> +		if (tmp > frac_divider) {
> +			tmp -= frac_divider;
> +			dig_rf_freq++;
> +		}
> +	}
> +	if (tmp > 7812)
> +		dig_rf_freq++;
> +
> +	rf_data[2 * (7) + 1] = (u8)(dig_rf_freq);
> +	rf_data[2 * (8) + 1] = (u8)(dig_rf_freq >> 8);
> +
> +	for (i = 0; i < ARRAY_SIZE(shf_dvbt_tab); i++) {
> +		if ((freq >= (shf_dvbt_tab[i].freq - shf_dvbt_tab[i].freq_th) * kHz) &&
> +				(freq <= (shf_dvbt_tab[i].freq + shf_dvbt_tab[i].freq_th) * kHz)) {
> +			rf_data[2 * (5) + 1] = shf_dvbt_tab[i].shf_val;
> +			rf_data[2 * (6) + 1] = 0xa0 | shf_dvbt_tab[i].shf_dir;
> +			break;
> +		}
> +	}
> +	memcpy(data, rf_data, sizeof(rf_data));
> +	*size = sizeof(rf_data);
> +
> +	pr_debug("mx_rftune freq=%d\n", freq);
> +}
> +
> +/* write via demod */
> +int mxl301rf_fe_write_data(struct dvb_frontend *fe, u8 addr_data, const u8 *data, int len)
> +{
> +	u8 buf[len + 1];
> +
> +	buf[0] = addr_data;
> +	memcpy(buf + 1, data, len);
> +	return fe->ops.write(fe, buf, len + 1);
> +}
> +
> +#define MXL301RF_FE_PASSTHROUGH 0xfe
> +
> +int mxl301rf_fe_write_tuner(struct dvb_frontend *fe, const u8 *data, int len)
> +{
> +	u8 buf[len + 2];
> +
> +	buf[0] = MXL301RF_FE_PASSTHROUGH;
> +	buf[1] = ((struct mxl301rf *)fe->tuner_priv)->addr_tuner << 1;
> +	memcpy(buf + 2, data, len);
> +	return fe->ops.write(fe, buf, len + 2);
> +}
> +
> +/* read via demod */
> +void mxl301rf_fe_read(struct dvb_frontend *fe, u8 addr, u8 *data)
> +{
> +	const u8 wbuf[2] = {0xfb, addr};
> +	int ret;
> +
> +	mxl301rf_fe_write_tuner(fe, wbuf, sizeof(wbuf));
> +	ret = fe->ops.write(fe, NULL, (1 << 16) | (((struct mxl301rf *)fe->tuner_priv)->addr_tuner << 8) | addr);
> +	if (ret >= 0)
> +		*data = ret;
> +}
> +
> +void mxl301rf_idac_setting(struct dvb_frontend *fe)
> +{
> +	const u8 idac[] = {
> +		0x0d, 0x00,
> +		0x0c, 0x67,
> +		0x6f, 0x89,
> +		0x70, 0x0c,
> +		0x6f, 0x8a,
> +		0x70, 0x0e,
> +		0x6f, 0x8b,
> +		0x70, 0x10+12,
> +	};
> +	mxl301rf_fe_write_tuner(fe, idac, sizeof(idac));
> +}
> +
> +void mxl301rf_set_register(struct dvb_frontend *fe, u8 addr, u8 value)
> +{
> +	const u8 data[2] = {addr, value};
> +	mxl301rf_fe_write_tuner(fe, data, sizeof(data));
> +}
> +
> +int mxl301rf_write_imsrst(struct dvb_frontend *fe)
> +{
> +	u8 data = 0x01 << 6;
> +	return mxl301rf_fe_write_data(fe, 0x01, &data, 1);
> +}
> +
> +enum mxl301rf_agc {
> +	MXL301RF_AGC_AUTO,
> +	MXL301RF_AGC_MANUAL,
> +};
> +
> +int mxl301rf_set_agc(struct dvb_frontend *fe, enum mxl301rf_agc agc)
> +{
> +	u8 data = (agc == MXL301RF_AGC_AUTO) ? 0x40 : 0x00;
> +	int ret = mxl301rf_fe_write_data(fe, 0x25, &data, 1);
> +	if (ret)
> +		return ret;
> +
> +	data = 0x4c | ((agc == MXL301RF_AGC_AUTO) ? 0x00 : 0x01);
> +	return	mxl301rf_fe_write_data(fe, 0x23, &data, 1) ||
> +		mxl301rf_write_imsrst(fe);
> +}
> +
> +int mxl301rf_sleep(struct dvb_frontend *fe)
> +{
> +	u8 buf = (1 << 7) | (1 << 4);
> +	const u8 data[4] = {0x01, 0x00, 0x13, 0x00};
> +	int err = mxl301rf_set_agc(fe, MXL301RF_AGC_MANUAL);
> +	if (err)
> +		return err;
> +	mxl301rf_fe_write_tuner(fe, data, sizeof(data));
> +	return mxl301rf_fe_write_data(fe, 0x03, &buf, 1);
> +}
> +
> +static const u8 mxl301rf_freq_tab[][3] = {
> +	{   2, 0,  3 },
> +	{  12, 1, 22 },
> +	{  21, 0, 12 },
> +	{  62, 1, 63 },
> +	{ 112, 0, 62 }
> +};
> +
> +bool mxl301rf_rfsynth_locked(struct dvb_frontend *fe)
> +{
> +	u8 data;
> +
> +	mxl301rf_fe_read(fe, 0x16, &data);
> +	return (data & 0x0c) == 0x0c;
> +}
> +
> +bool mxl301rf_refsynth_locked(struct dvb_frontend *fe)
> +{
> +	u8 data;
> +
> +	mxl301rf_fe_read(fe, 0x16, &data);
> +	return (data & 0x03) == 0x03;
> +}
> +
> +bool mxl301rf_locked(struct dvb_frontend *fe)
> +{
> +	bool locked1 = false, locked2 = false;
> +	unsigned long timeout = jiffies + msecs_to_jiffies(100);
> +
> +	while (time_before(jiffies, timeout)) {
> +		locked1 = mxl301rf_rfsynth_locked(fe);
> +		locked2 = mxl301rf_refsynth_locked(fe);
> +		if (locked1 && locked2)
> +			break;
> +		msleep_interruptible(1);
> +	}
> +	pr_debug("#%d %s lock1=%d lock2=%d\n", ((struct mxl301rf *)fe->tuner_priv)->idx, __func__, locked1, locked2);
> +	return locked1 && locked2 ? !mxl301rf_set_agc(fe, MXL301RF_AGC_AUTO) : false;
> +}
> +
> +int mxl301rf_tuner_rftune(struct dvb_frontend *fe, u32 freq)
> +{
> +	struct mxl301rf *mx = fe->tuner_priv;
> +	u8 data[100];
> +	u32 size = 0;
> +	int err = mxl301rf_set_agc(fe, MXL301RF_AGC_MANUAL);
> +	if (err)
> +		return err;
> +
> +	mx->freq = freq;
> +	mxl301rf_rftune(data, &size, freq);
> +	if (size != 20) {
> +		pr_debug("fail mx_rftune size = %d\n", size);
> +		return -EINVAL;
> +	}
> +	mxl301rf_fe_write_tuner(fe, data, 14);
> +	msleep_interruptible(1);
> +	mxl301rf_fe_write_tuner(fe, data + 14, 6);
> +	msleep_interruptible(1);
> +	mxl301rf_set_register(fe, 0x1a, 0x0d);
> +	mxl301rf_idac_setting(fe);
> +
> +	return mxl301rf_locked(fe) ? 0 : -ETIMEDOUT;
> +}
> +
> +int mxl301rf_wakeup(struct dvb_frontend *fe)
> +{
> +	struct mxl301rf *mx = fe->tuner_priv;
> +	int err;
> +	u8 buf = (1 << 7) | (0 << 4);
> +	const u8 data[2] = {0x01, 0x01};
> +
> +	err = mxl301rf_fe_write_data(fe, 0x03, &buf, 1);
> +	if (err)
> +		return err;
> +	mxl301rf_fe_write_tuner(fe, data, sizeof(data));
> +	mxl301rf_tuner_rftune(fe, mx->freq);
> +	return 0;
> +}
> +
> +void mxl301rf_ch2freq(u32 channel, bool *catv, u32 *number, u32 *freq)
> +{
> +	u32 i;
> +	s32 freq_offset = 0;
> +
> +	if (12 <= channel)
> +		freq_offset += 2;
> +	if (17 <= channel)
> +		freq_offset -= 2;
> +	if (63 <= channel)
> +		freq_offset += 2;
> +	*freq = 93 + channel * 6 + freq_offset;
> +
> +	for (i = 0; i < ARRAY_SIZE(mxl301rf_freq_tab); i++) {
> +		if (channel <= mxl301rf_freq_tab[i][0]) {
> +			*catv = mxl301rf_freq_tab[i][1] ? true : false;
> +			*number = channel + mxl301rf_freq_tab[i][2] - mxl301rf_freq_tab[i][0];
> +			break;
> +		}
> +	}
> +}
> +
> +int mxl301rf_release(struct dvb_frontend *fe)
> +{
> +	kfree(fe->tuner_priv);
> +	fe->tuner_priv = NULL;
> +	return 0;
> +}
> +
> +static struct dvb_tuner_ops mxl301rf_ops = {
> +	.info = {
> +		.frequency_min	= 1,		/* actually 90 MHz, freq below that is handled as ch */
> +		.frequency_max	= 770000000,	/* Hz */
> +		.frequency_step	= 142857,
> +	},
> +	.set_frequency = mxl301rf_tuner_rftune,
> +	.sleep = mxl301rf_sleep,
> +	.init = mxl301rf_wakeup,
> +	.release = mxl301rf_release,
> +};
> +
> +int mxl301rf_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
> +{
> +	u8 d[] = { 0x10, 0x01 };
> +	struct mxl301rf *mx = kzalloc(sizeof(struct mxl301rf), GFP_KERNEL);
> +	if (!mx)
> +		return -ENOMEM;
> +	fe->tuner_priv = mx;
> +	mx->fe = fe;
> +	mx->idx = idx;
> +	mx->addr_tuner = addr_tuner;
> +	memcpy(&fe->ops.tuner_ops, &mxl301rf_ops, sizeof(struct dvb_tuner_ops));
> +
> +	return	mxl301rf_fe_write_data(fe, 0x1c, d, 1)	||
> +		mxl301rf_fe_write_data(fe, 0x1d, d+1, 1);
> +}
> +EXPORT_SYMBOL(mxl301rf_attach);
> +
> diff --git a/drivers/media/tuners/mxl301rf.h b/drivers/media/tuners/mxl301rf.h
> new file mode 100644
> index 0000000..22599b9
> --- /dev/null
> +++ b/drivers/media/tuners/mxl301rf.h
> @@ -0,0 +1,36 @@
> +/*
> + * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-T tuner MaxLinear CMOS Hybrid TV MxL301RF
> + *
> + * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 __MXL301RF_H__
> +#define __MXL301RF_H__
> +
> +#ifndef pr_fmt
> + #define pr_fmt(fmt) KBUILD_MODNAME " " fmt
> +#endif
> +#include "dvb_frontend.h"
> +
> +#if IS_ENABLED(CONFIG_MEDIA_TUNER_MXL301RF)
> +extern int mxl301rf_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner);
> +#else
> +static inline int mxl301rf_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
> +{
> +	pr_warn("%s: driver disabled by Kconfig\n", __func__);
> +	return 0;
> +}
> +#endif
> +
> +#endif
> +
> diff --git a/drivers/media/tuners/qm1d1c0042.c b/drivers/media/tuners/qm1d1c0042.c
> new file mode 100644
> index 0000000..3be552f
> --- /dev/null
> +++ b/drivers/media/tuners/qm1d1c0042.c
> @@ -0,0 +1,382 @@
> +/*
> + * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-S tuner driver QM1D1C0042
> + *
> + * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 "qm1d1c0042.h"
> +
> +MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
> +MODULE_DESCRIPTION("Earthsoft PT3 QM1D1C0042 ISDB-S tuner driver");
> +MODULE_LICENSE("GPL");
> +
> +struct qm1d1c0042 {
> +	struct dvb_frontend *fe;
> +	u8 addr_tuner, idx, reg[32];
> +	u32 freq;
> +};
> +
> +static const u8 qm1d1c0042_reg_rw[] = {
> +	0x48, 0x1c, 0xa0, 0x10, 0xbc, 0xc5, 0x20, 0x33,
> +	0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
> +	0x00, 0xff, 0xf3, 0x00, 0x2a, 0x64, 0xa6, 0x86,
> +	0x8c, 0xcf, 0xb8, 0xf1, 0xa8, 0xf2, 0x89, 0x00,
> +};
> +
> +/* read via demodulator */
> +int qm1d1c0042_fe_read(struct dvb_frontend *fe, u8 addr, u8 *data)
> +{
> +	int ret;
> +	if ((addr != 0x00) && (addr != 0x0d))
> +		return -EFAULT;
> +	ret = fe->ops.write(fe, NULL, (((struct qm1d1c0042 *)fe->tuner_priv)->addr_tuner << 8) | addr);
> +	if (ret < 0)
> +		return ret;
> +	*data = ret;
> +	return 0;
> +}
> +
> +/* write via demodulator */
> +int qm1d1c0042_fe_write_data(struct dvb_frontend *fe, u8 addr_data, u8 *data, int len)
> +{
> +	u8 buf[len + 1];
> +
> +	buf[0] = addr_data;
> +	memcpy(buf + 1, data, len);
> +	return fe->ops.write(fe, buf, len + 1);
> +}
> +
> +#define QM1D1C0042_FE_PASSTHROUGH 0xfe
> +
> +int qm1d1c0042_fe_write_tuner(struct dvb_frontend *fe, u8 *data, int len)
> +{
> +	u8 buf[len + 2];
> +
> +	buf[0] = QM1D1C0042_FE_PASSTHROUGH;
> +	buf[1] = ((struct qm1d1c0042 *)fe->tuner_priv)->addr_tuner << 1;
> +	memcpy(buf + 2, data, len);
> +	return fe->ops.write(fe, buf, len + 2);
> +}
> +
> +int qm1d1c0042_write(struct dvb_frontend *fe, u8 addr, u8 data)
> +{
> +	struct qm1d1c0042 *qm = fe->tuner_priv;
> +	u8 buf[] = { addr, data };
> +	int err = qm1d1c0042_fe_write_tuner(fe, buf, sizeof(buf));
> +	qm->reg[addr] = buf[1];
> +	return err;
> +}
> +
> +static const u8 qm1d1c0042_flag[32] = {
> +	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
> +	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
> +};
> +
> +int qm1d1c0042_write_pskmsrst(struct dvb_frontend *fe)
> +{
> +	u8 data = 0x01;
> +	return qm1d1c0042_fe_write_data(fe, 0x03, &data, 1);
> +}
> +
> +enum qm1d1c0042_agc {
> +	QM1D1C0042_AGC_AUTO,
> +	QM1D1C0042_AGC_MANUAL,
> +};
> +
> +int qm1d1c0042_set_agc(struct dvb_frontend *fe, enum qm1d1c0042_agc agc)
> +{
> +	struct qm1d1c0042 *qm = fe->tuner_priv;
> +	static u8 agc_data_s[2] = { 0xb0, 0x30 };
> +	u8 data = (agc == QM1D1C0042_AGC_AUTO) ? 0xff : 0x00;
> +	int ret = qm1d1c0042_fe_write_data(fe, 0x0a, &data, 1);
> +	if (ret)
> +		return ret;
> +
> +	data = agc_data_s[(qm->idx >> 1) & 1];
> +	data |= (agc == QM1D1C0042_AGC_AUTO) ? 0x01 : 0x00;
> +	ret = qm1d1c0042_fe_write_data(fe, 0x10, &data, 1);
> +	if (ret)
> +		return ret;
> +
> +	data = (agc == QM1D1C0042_AGC_AUTO) ? 0x40 : 0x00;
> +	return (ret = qm1d1c0042_fe_write_data(fe, 0x11, &data, 1)) ? ret : qm1d1c0042_write_pskmsrst(fe);
> +}
> +
> +int qm1d1c0042_sleep(struct dvb_frontend *fe)
> +{
> +	struct qm1d1c0042 *qm = fe->tuner_priv;
> +	u8 buf = 1;
> +	pr_debug("#%d %s\n", qm->idx, __func__);
> +
> +	qm->reg[0x01] &= (~(1 << 3)) & 0xff;
> +	qm->reg[0x01] |= 1 << 0;
> +	qm->reg[0x05] |= 1 << 3;
> +	return	qm1d1c0042_set_agc(fe, QM1D1C0042_AGC_MANUAL)	||
> +		qm1d1c0042_write(fe, 0x05, qm->reg[0x05])	||
> +		qm1d1c0042_write(fe, 0x01, qm->reg[0x01])	||
> +		qm1d1c0042_fe_write_data(fe, 0x17, &buf, 1);
> +}
> +
> +int qm1d1c0042_wakeup(struct dvb_frontend *fe)
> +{
> +	struct qm1d1c0042 *qm = fe->tuner_priv;
> +	u8 buf = 0;
> +	pr_debug("#%d %s\n", qm->idx, __func__);
> +
> +	qm->reg[0x01] |= 1 << 3;
> +	qm->reg[0x01] &= (~(1 << 0)) & 0xff;
> +	qm->reg[0x05] &= (~(1 << 3)) & 0xff;
> +	return	qm1d1c0042_fe_write_data(fe, 0x17, &buf, 1)	||
> +		qm1d1c0042_write(fe, 0x01, qm->reg[0x01])	||
> +		qm1d1c0042_write(fe, 0x05, qm->reg[0x05]);
> +}
> +
> +void qm1d1c0042_get_channel_freq(u32 channel, u32 *number, u32 *freq)
> +{
> +	if (channel < 12) {
> +		*number = 1 + 2 * channel;
> +		*freq = 104948 + 3836 * channel;
> +	} else if (channel < 24) {
> +		channel -= 12;
> +		*number = 2 + 2 * channel;
> +		*freq = 161300 + 4000 * channel;
> +	} else {
> +		channel -= 24;
> +		*number = 1 + 2 * channel;
> +		*freq = 159300 + 4000 * channel;
> +	}
> +}
> +
> +static const u32 qm1d1c0042_freq_tab[9][3] = {
> +	{ 2151000, 1, 7 },
> +	{ 1950000, 1, 6 },
> +	{ 1800000, 1, 5 },
> +	{ 1600000, 1, 4 },
> +	{ 1450000, 1, 3 },
> +	{ 1250000, 1, 2 },
> +	{ 1200000, 0, 7 },
> +	{  975000, 0, 6 },
> +	{  950000, 0, 0 }
> +};
> +
> +static const u32 qm1d1c0042_sd_tab[24][2][3] = {
> +	{{0x38fae1, 0x0d, 0x5}, {0x39fae1, 0x0d, 0x5},},
> +	{{0x3f570a, 0x0e, 0x3}, {0x00570a, 0x0e, 0x3},},
> +	{{0x05b333, 0x0e, 0x5}, {0x06b333, 0x0e, 0x5},},
> +	{{0x3c0f5c, 0x0f, 0x4}, {0x3d0f5c, 0x0f, 0x4},},
> +	{{0x026b85, 0x0f, 0x6}, {0x036b85, 0x0f, 0x6},},
> +	{{0x38c7ae, 0x10, 0x5}, {0x39c7ae, 0x10, 0x5},},
> +	{{0x3f23d7, 0x11, 0x3}, {0x0023d7, 0x11, 0x3},},
> +	{{0x058000, 0x11, 0x5}, {0x068000, 0x11, 0x5},},
> +	{{0x3bdc28, 0x12, 0x4}, {0x3cdc28, 0x12, 0x4},},
> +	{{0x023851, 0x12, 0x6}, {0x033851, 0x12, 0x6},},
> +	{{0x38947a, 0x13, 0x5}, {0x39947a, 0x13, 0x5},},
> +	{{0x3ef0a3, 0x14, 0x3}, {0x3ff0a3, 0x14, 0x3},},
> +	{{0x3c8000, 0x16, 0x4}, {0x3d8000, 0x16, 0x4},},
> +	{{0x048000, 0x16, 0x6}, {0x058000, 0x16, 0x6},},
> +	{{0x3c8000, 0x17, 0x5}, {0x3d8000, 0x17, 0x5},},
> +	{{0x048000, 0x18, 0x3}, {0x058000, 0x18, 0x3},},
> +	{{0x3c8000, 0x18, 0x6}, {0x3d8000, 0x18, 0x6},},
> +	{{0x048000, 0x19, 0x4}, {0x058000, 0x19, 0x4},},
> +	{{0x3c8000, 0x1a, 0x3}, {0x3d8000, 0x1a, 0x3},},
> +	{{0x048000, 0x1a, 0x5}, {0x058000, 0x1a, 0x5},},
> +	{{0x3c8000, 0x1b, 0x4}, {0x3d8000, 0x1b, 0x4},},
> +	{{0x048000, 0x1b, 0x6}, {0x058000, 0x1b, 0x6},},
> +	{{0x3c8000, 0x1c, 0x5}, {0x3d8000, 0x1c, 0x5},},
> +	{{0x048000, 0x1d, 0x3}, {0x058000, 0x1d, 0x3},},
> +};
> +
> +static int qm1d1c0042_tuning(struct qm1d1c0042 *qm, u32 *sd, u32 channel)
> +{
> +	int ret;
> +	struct dvb_frontend *fe = qm->fe;
> +	u8 i_data;
> +	u32 i, N, A, index = (qm->idx >> 1) & 1;
> +
> +	qm->reg[0x08] &= 0xf0;
> +	qm->reg[0x08] |= 0x09;
> +
> +	qm->reg[0x13] &= 0x9f;
> +	qm->reg[0x13] |= 0x20;
> +
> +	for (i = 0; i < 8; i++) {
> +		if ((qm1d1c0042_freq_tab[i+1][0] <= qm->freq) && (qm->freq < qm1d1c0042_freq_tab[i][0])) {
> +			i_data = qm->reg[0x02];
> +			i_data &= 0x0f;
> +			i_data |= qm1d1c0042_freq_tab[i][1] << 7;
> +			i_data |= qm1d1c0042_freq_tab[i][2] << 4;
> +			qm1d1c0042_write(fe, 0x02, i_data);
> +		}
> +	}
> +
> +	*sd = qm1d1c0042_sd_tab[channel][index][0];
> +	N = qm1d1c0042_sd_tab[channel][index][1];
> +	A = qm1d1c0042_sd_tab[channel][index][2];
> +
> +	qm->reg[0x06] &= 0x40;
> +	qm->reg[0x06] |= N;
> +	ret = qm1d1c0042_write(fe, 0x06, qm->reg[0x06]);
> +	if (ret)
> +		return ret;
> +
> +	qm->reg[0x07] &= 0xf0;
> +	qm->reg[0x07] |= A & 0x0f;
> +	return qm1d1c0042_write(fe, 0x07, qm->reg[0x07]);
> +}
> +
> +static int qm1d1c0042_local_lpf_tuning(struct qm1d1c0042 *qm, u32 channel)
> +{
> +	struct dvb_frontend *fe = qm->fe;
> +	u8 i_data;
> +	u32 sd = 0;
> +	int ret = qm1d1c0042_tuning(qm, &sd, channel);
> +	if (ret)
> +		return ret;
> +
> +	i_data = qm->reg[0x08] & 0xf0;
> +	i_data |= 2;
> +	ret = qm1d1c0042_write(fe, 0x08, i_data);
> +	if (ret)
> +		return ret;
> +
> +	qm->reg[0x09] &= 0xc0;
> +	qm->reg[0x09] |= (sd >> 16) & 0x3f;
> +	qm->reg[0x0a] = (sd >> 8) & 0xff;
> +	qm->reg[0x0b] = (sd >> 0) & 0xff;
> +	ret =	qm1d1c0042_write(fe, 0x09, qm->reg[0x09])	||
> +		qm1d1c0042_write(fe, 0x0a, qm->reg[0x0a])	||
> +		qm1d1c0042_write(qm->fe, 0x0b, qm->reg[0x0b]);
> +	if (ret)
> +		return ret;
> +
> +	i_data = qm->reg[0x0c];
> +	i_data &= 0x3f;
> +	ret = qm1d1c0042_write(fe, 0x0c, i_data);
> +	if (ret)
> +		return ret;
> +	msleep_interruptible(1);
> +
> +	i_data = qm->reg[0x0c];
> +	i_data |= 0xc0;
> +	return	qm1d1c0042_write(fe, 0x0c, i_data)	||
> +		qm1d1c0042_write(fe, 0x08, 0x09)	||
> +		qm1d1c0042_write(fe, 0x13, qm->reg[0x13]);
> +}
> +
> +int qm1d1c0042_get_locked(struct qm1d1c0042 *qm, bool *locked)
> +{
> +	int ret = qm1d1c0042_fe_read(qm->fe, 0x0d, &qm->reg[0x0d]);
> +	if (ret)
> +		return ret;
> +	if (qm->reg[0x0d] & 0x40)
> +		*locked = true;
> +	else
> +		*locked = false;
> +	return ret;
> +}
> +
> +u32 qm1d1c0042_freq2ch(u32 frequency)
> +{
> +	u32 freq = frequency / 10,
> +	    ch0 = (freq - 104948) / 3836, diff0 = freq - (104948 + 3836 * ch0),
> +	    ch1 = (freq - 161300) / 4000, diff1 = freq - (161300 + 4000 * ch1),
> +	    ch2 = (freq - 159300) / 4000, diff2 = freq - (159300 + 4000 * ch2),
> +	    min = diff0 < diff1 ? diff0 : diff1;
> +
> +	if (frequency < 1024)
> +		return frequency;	/* consider as channel ID if low */
> +	if (diff2 < min)
> +		return ch2 + 24;
> +	else if (min == diff1)
> +		return ch1 + 12;
> +	else
> +		return ch0;
> +}
> +
> +int qm1d1c0042_set_freq(struct dvb_frontend *fe, u32 frequency)
> +{
> +	struct qm1d1c0042 *qm = fe->tuner_priv;
> +	u32 channel = qm1d1c0042_freq2ch(frequency);
> +	u32 number, freq, freq_kHz;
> +	bool locked = false;
> +	unsigned long timeout;
> +	int err = qm1d1c0042_set_agc(fe, QM1D1C0042_AGC_MANUAL);
> +	if (err)
> +		return err;
> +
> +	qm1d1c0042_get_channel_freq(channel, &number, &freq);
> +	freq_kHz = freq * 10;
> +	if (((qm->idx >> 1) & 1) == 0)
> +		freq_kHz -= 500;
> +	else
> +		freq_kHz += 500;
> +	qm->freq = freq_kHz;
> +	pr_debug("#%d ch %d freq %d kHz\n", qm->idx, channel, freq_kHz);
> +
> +	err = qm1d1c0042_local_lpf_tuning(qm, channel);
> +	if (err)
> +		return err;
> +
> +	timeout = jiffies + msecs_to_jiffies(1000);	/* 1s */
> +	while (time_before(jiffies, timeout)) {
> +		err = qm1d1c0042_get_locked(qm, &locked);
> +		if (err)
> +			return err;
> +		if (locked)
> +			break;
> +		msleep_interruptible(1);
> +	}
> +	pr_debug("#%d %s %s\n", qm->idx, __func__, locked ? "LOCKED" : "TIMEOUT");
> +	return locked ? qm1d1c0042_set_agc(fe, QM1D1C0042_AGC_AUTO) : -ETIMEDOUT;
> +}
> +
> +int qm1d1c0042_release(struct dvb_frontend *fe)
> +{
> +	kfree(fe->tuner_priv);
> +	fe->tuner_priv = NULL;
> +	return 0;
> +}
> +
> +static struct dvb_tuner_ops qm1d1c0042_ops = {
> +	.info = {
> +		.frequency_min	= 1,		/* actually 1024 kHz, freq below that is handled as ch */
> +		.frequency_max	= 2150000,	/* kHz */
> +		.frequency_step	= 1000,		/* = 1 MHz */
> +	},
> +	.set_frequency = qm1d1c0042_set_freq,
> +	.sleep = qm1d1c0042_sleep,
> +	.init = qm1d1c0042_wakeup,
> +	.release = qm1d1c0042_release,
> +};
> +
> +int qm1d1c0042_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
> +{
> +	u8 d[] = { 0x10, 0x15, 0x04 };
> +	struct qm1d1c0042 *qm = kzalloc(sizeof(struct qm1d1c0042), GFP_KERNEL);
> +	if (!qm)
> +		return -ENOMEM;
> +	fe->tuner_priv = qm;
> +	qm->fe = fe;
> +	qm->idx = idx;
> +	qm->addr_tuner = addr_tuner;
> +	memcpy(&fe->ops.tuner_ops, &qm1d1c0042_ops, sizeof(struct dvb_tuner_ops));
> +
> +	memcpy(qm->reg, qm1d1c0042_reg_rw, sizeof(qm1d1c0042_reg_rw));
> +	qm->freq = 0;
> +
> +	return	qm1d1c0042_fe_write_data(fe, 0x1e, d,   1)	||
> +		qm1d1c0042_fe_write_data(fe, 0x1c, d+1, 1)	||
> +		qm1d1c0042_fe_write_data(fe, 0x1f, d+2, 1);
> +}
> +EXPORT_SYMBOL(qm1d1c0042_attach);
> +
> diff --git a/drivers/media/tuners/qm1d1c0042.h b/drivers/media/tuners/qm1d1c0042.h
> new file mode 100644
> index 0000000..2b14b70
> --- /dev/null
> +++ b/drivers/media/tuners/qm1d1c0042.h
> @@ -0,0 +1,36 @@
> +/*
> + * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-S tuner driver QM1D1C0042
> + *
> + * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 __QM1D1C0042_H__
> +#define __QM1D1C0042_H__
> +
> +#ifndef pr_fmt
> + #define pr_fmt(fmt) KBUILD_MODNAME " " fmt
> +#endif
> +#include "dvb_frontend.h"
> +
> +#if IS_ENABLED(CONFIG_MEDIA_TUNER_QM1D1C0042)
> +extern int qm1d1c0042_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner);
> +#else
> +static inline int qm1d1c0042_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
> +{
> +	pr_warn("%s: driver disabled by Kconfig\n", __func__);
> +	return 0;
> +}
> +#endif
> +
> +#endif
> +
>


-- 
http://palosaari.fi/

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Full DVB driver package for Earthsoft PT3 (ISDB-S/T) cards
  2014-05-17 19:03 ` Antti Palosaari
@ 2014-05-19 22:19   ` ほち
  2014-05-19 23:23     ` Antti Palosaari
  0 siblings, 1 reply; 21+ messages in thread
From: ほち @ 2014-05-19 22:19 UTC (permalink / raw)
  To: Antti Palosaari
  Cc: linux-media, m.chehab, Hans De Goede, Hans Verkuil,
	Laurent Pinchart, Michael Krufky, Sylwester Nawrocki,
	Guennadi Liakhovetski, Peter Senna Tschudin

Thanks for the review.
Inlined questions before going further.
Best Regards
-Bud

2014-05-18 4:03 GMT+09:00 Antti Palosaari <crope@iki.fi>:
> Overall tc90522 driver looks very complex and there was multiple issues. One
> reason of complexiness is that HW algo used. I cannot see any reason why it
> is used, just change default SW algo and implement things more likely others
> are doing.
>
>> diff --git a/drivers/media/dvb-frontends/tc90522.c
>> b/drivers/media/dvb-frontends/tc90522.c
>> new file mode 100644
>> index 0000000..a767600
>> --- /dev/null
>> +++ b/drivers/media/dvb-frontends/tc90522.c
>> @@ -0,0 +1,539 @@
>> +/*
>> + * Earthsoft PT3 demodulator frontend Toshiba TC90522XBG
>> OFDM(ISDB-T)/8PSK(ISDB-S)
>
> That is, or at least should be, general DTV demod driver. So lets call it
> Toshiba TC90522 or whatever the chipset name is.

FYI, the only document available is SDK from PT3 card maker, Earthsoft.
No guarantee this driver works in other cards.

>> +int tc90522_write(struct dvb_frontend *fe, const u8 *data, int len)
>> +{
>> +       struct tc90522 *demod = fe->demodulator_priv;
>> +       struct i2c_msg msg[3];
>> +       u8 buf[6];
>> +
>> +       if (data) {
>> +               msg[0].addr = demod->addr_demod;
>> +               msg[0].buf = (u8 *)data;
>> +               msg[0].flags = 0;                       /* write */
>> +               msg[0].len = len;
>> +
>> +               return i2c_transfer(demod->i2c, msg, 1) == 1 ? 0 :
>> -EREMOTEIO;
>> +       } else {
>> +               u8 addr_tuner = (len >> 8) & 0xff,
>> +                  addr_data = len & 0xff;
>> +               if (len >> 16) {                        /* read tuner
>> without address */
>> +                       buf[0] = TC90522_PASSTHROUGH;
>> +                       buf[1] = (addr_tuner << 1) | 1;
>> +                       msg[0].buf = buf;
>> +                       msg[0].len = 2;
>> +                       msg[0].addr = demod->addr_demod;
>> +                       msg[0].flags = 0;               /* write */
>> +
>> +                       msg[1].buf = buf + 2;
>> +                       msg[1].len = 1;
>> +                       msg[1].addr = demod->addr_demod;
>> +                       msg[1].flags = I2C_M_RD;        /* read */
>> +
>> +                       return i2c_transfer(demod->i2c, msg, 2) == 2 ?
>> buf[2] : -EREMOTEIO;
>> +               } else {                                /* read tuner */
>> +                       buf[0] = TC90522_PASSTHROUGH;
>> +                       buf[1] = addr_tuner << 1;
>> +                       buf[2] = addr_data;
>> +                       msg[0].buf = buf;
>> +                       msg[0].len = 3;
>> +                       msg[0].addr = demod->addr_demod;
>> +                       msg[0].flags = 0;               /* write */
>> +
>> +                       buf[3] = TC90522_PASSTHROUGH;
>> +                       buf[4] = (addr_tuner << 1) | 1;
>> +                       msg[1].buf = buf + 3;
>> +                       msg[1].len = 2;
>> +                       msg[1].addr = demod->addr_demod;
>> +                       msg[1].flags = 0;               /* write */
>> +
>> +                       msg[2].buf = buf + 5;
>> +                       msg[2].len = 1;
>> +                       msg[2].addr = demod->addr_demod;
>> +                       msg[2].flags = I2C_M_RD;        /* read */
>> +
>> +                       return i2c_transfer(demod->i2c, msg, 3) == 3 ?
>> buf[5] : -EREMOTEIO;
>> +               }
>> +       }
>> +}
>
> That routine is mess. I read it many times without understanding what it
> does, when and why. It is not register write over I2C as expected. For
> example parameter named "len" is abused for tuner I2C even that is demod
> driver...

Tuners need to read data through demod, and there is no read callback available
in dvb_frontend.h (only write is provided).
The above routine provides R/W access.

>> +int tc90522_write_data(struct dvb_frontend *fe, u8 addr_data, u8 *data,
>> u8 len)
>> +{
>> +       u8 buf[len + 1];
>> +       buf[0] = addr_data;
>> +       memcpy(buf + 1, data, len);
>> +       return tc90522_write(fe, buf, len + 1);
>> +}
>> +
>> +int tc90522_read(struct tc90522 *demod, u8 addr, u8 *buf, u8 buflen)
>> +{
>> +       struct i2c_msg msg[2];
>> +       if (!buf || !buflen)
>> +               return -EINVAL;
>> +
>> +       buf[0] = addr;
>
> ....
>>
>> +       msg[0].addr = demod->addr_demod;
>> +       msg[0].flags = 0;                       /* write */
>> +       msg[0].buf = buf;
>
> just give a addr pointer, no need to store it to buf first.
>
>> +       msg[0].len = 1;
>> +
>> +       msg[1].addr = demod->addr_demod;
>> +       msg[1].flags = I2C_M_RD;                /* read */
>> +       msg[1].buf = buf;
>> +       msg[1].len = buflen;
>> +
>> +       return i2c_transfer(demod->i2c, msg, 2) == 2 ? 0 : -EREMOTEIO;
>> +}
>
> All in all, it looks like demod is using just most typical register access
> for both register write and read, where first byte is register address and
> value(s) are after that. Register read is done using repeated START.
>
> I encourage you to use RegMap API as it covers all that boilerplate stuff -
> and forces you implement things correctly (no such hack possible done in
> tc90522_write()).

Good recommendation. I'll take a look.

>> +u32 tc90522_byten(const u8 *data, u32 n)
>> +{
>> +       u32 i, val = 0;
>> +
>> +       for (i = 0; i < n; i++) {
>> +               val <<= 8;
>> +               val |= data[i];
>> +       }
>> +       return val;
>> +}
>
> What is that? Kinda bit reverse? Look from existing bitops if there is such
> a solution already and if not, add comments what that is for.

changed to:
u64 tc90522_ntoint(const u8 *data, u8 n)    /* convert n_bytes data
from stream (network byte order) to integer */
{                        /* can't use <arpa/inet.h>'s ntoh*() as
sometimes n = 3,5,... */
...

>> +               ((data[0] >> 4) & 1)                    ||
>> +               tc90522_read(demod, 0xce, data, 2)      ||
>> +               (tc90522_byten(data, 2) == 0)           ||
>> +               tc90522_read(demod, 0xc3, data, 1)      ||
>> +               tc90522_read(demod, 0xc5, data, SIZE);
>
> Masking return statuses like that does not look good nor clear.

Well, the statuses are not so important here.
We only want to know & stop if there was an error occured.
That is enough.

>> +enum tc90522_pwr {
>> +       TC90522_PWR_OFF         = 0x00,
>> +       TC90522_PWR_AMP_ON      = 0x04,
>> +       TC90522_PWR_TUNER_ON    = 0x40,
>> +};
>> +
>> +static enum tc90522_pwr tc90522_pwr = TC90522_PWR_OFF;
>
> Global static variable for device power management..? That looks very bad.
> Those variables are shared between all driver instances. That will not work
> if you have multiple devices having that demod driver!

OK, removed.
In the next release pt3_pci will instruct when to power on the demod chip.

>> +int tc90522_set_powers(struct tc90522 *demod, enum tc90522_pwr pwr)
>> +{
>> +       u8 data = pwr | 0b10011001;
>> +       pr_debug("#%d tuner %s amp %s\n", demod->idx, pwr &
>> TC90522_PWR_TUNER_ON ? "ON" : "OFF", pwr & TC90522_PWR_AMP_ON ? "ON" :
>> "OFF");
>> +       tc90522_pwr = pwr;
>> +       return tc90522_write_data(&demod->fe, 0x1e, &data, 1);
>> +}
>> +
>> +/* dvb_frontend_ops */
>> +int tc90522_get_frontend_algo(struct dvb_frontend *fe)
>> +{
>> +       return DVBFE_ALGO_HW;
>> +}
>> +
>> +int tc90522_sleep(struct dvb_frontend *fe)
>> +{
>> +       struct tc90522 *demod = fe->demodulator_priv;
>> +       pr_debug("#%d %s %s\n", demod->idx, __func__, demod->type ==
>> SYS_ISDBS ? "S" : "T");
>> +       return fe->ops.tuner_ops.sleep(fe);
>
> :-@ You are simply not allowed to hard code tuner power-management to demod
> driver, it is no, no, no. Demod driver can have only get IF and set
> parameters reference to tuner and nothing more.
>
> You should sleep only demod in that demod sleep().
> It is DVB core who is responsible of runtime power-management.
>
>> +}
>> +
>> +int tc90522_wakeup(struct dvb_frontend *fe)
>> +{
>> +       struct tc90522 *demod = fe->demodulator_priv;
>> +       pr_debug("#%d %s %s 0x%x\n", demod->idx, __func__, demod->type ==
>> SYS_ISDBS ? "S" : "T", tc90522_pwr);
>> +
>> +       if (!tc90522_pwr)
>> +               return  tc90522_set_powers(demod, TC90522_PWR_TUNER_ON) ||
>> +                       i2c_transfer(demod->i2c, NULL, 0)               ||
>> +                       tc90522_set_powers(demod, TC90522_PWR_TUNER_ON |
>> TC90522_PWR_AMP_ON);
>> +       demod->state = TC90522_IDLE;
>> +       return fe->ops.tuner_ops.init(fe);
>> +}
>
> power-management is totally wrong here too
>
>> +void tc90522_release(struct dvb_frontend *fe)
>> +{
>> +       struct tc90522 *demod = fe->demodulator_priv;
>> +       pr_debug("#%d %s\n", demod->idx, __func__);
>> +
>> +       if (tc90522_pwr)
>> +               tc90522_set_powers(demod, TC90522_PWR_OFF);
>
> That belongs to demod driver power-management callback sleep()
>
>> +       tc90522_sleep(fe);
>
> hmm? PM...
>
>> +       fe->ops.tuner_ops.release(fe);
>> +       kfree(demod);
>> +}

Sounds like your perception is TOTALLY WRONG.
Remember this chip has 4 independent demods sharing the same power.
There is only one power available for all 4!
Turning off 1 demod will shutdown the other 3.

Secondly, in PT3, there is no direct access to the tuners.
Every control / data is done via demod, including power.

>> +int tc90522_read_signal_strength(struct dvb_frontend *fe, u16 *cn)     /*
>> raw C/N */
>> +{
>> +       struct tc90522 *demod = fe->demodulator_priv;
>> +       s64 ret = tc90522_get_cn_raw(demod);
>> +       *cn = ret < 0 ? 0 : ret;
>> +       pr_debug("v3 CN %d (%lld dB)\n", (int)*cn, demod->type ==
>> SYS_ISDBS ? (long long int)tc90522_get_cn_s(*cn) : (long long
>> int)tc90522_get_cn_t(*cn));
>> +       return ret < 0 ? ret : 0;
>> +}
>
> We have API who supports both CNR and signal strenght. Do not abuse signal
> strenght for CNR, instead implement as it should.

OK, on DVBv3 stat I changed back .read_signal_strength to .read_snr
for CNR (digitally modulated SNR)
though AFAIK (in a strict mean) SNR != CNR.

>> +int tc90522_read_status(struct dvb_frontend *fe, fe_status_t *status)
>> +{
>> +       struct tc90522 *demod = fe->demodulator_priv;
>> +       struct dtv_frontend_properties *c = &fe->dtv_property_cache;
>> +       s64 ret = tc90522_get_cn_raw(demod),
>> +           raw = ret < 0 ? 0 : ret;
>> +
>> +       switch (demod->state) {
>> +       case TC90522_IDLE:
>> +       case TC90522_SET_FREQUENCY:
>> +               *status = 0;
>> +               break;
>> +
>> +       case TC90522_SET_MODULATION:
>> +       case TC90522_ABORT:
>> +               *status |= FE_HAS_SIGNAL;
>> +               break;
>> +
>> +       case TC90522_TRACK:
>> +               *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
>> +               break;
>> +       }
>> +
>> +       c->cnr.len = 1;
>> +       c->cnr.stat[0].svalue = demod->type == SYS_ISDBS ?
>> tc90522_get_cn_s(raw) : tc90522_get_cn_t(raw);
>> +       c->cnr.stat[0].scale = FE_SCALE_DECIBEL;
>> +       pr_debug("v5 CN %lld (%lld dB)\n", raw, c->cnr.stat[0].svalue);
>> +       return ret < 0 ? ret : 0;
>> +}
>
> So you have decided to add some statistics logic here too. It is good place
> to update stistics counters, but only on case where SW algo used and DVB
> core is polling. However, you used HW algo which means that is not polled
> automatically. Maybe it does not work as it should.

So sorry but so far this works perfectly.

>> +/**** ISDB-S ****/
>> +int tc90522_write_id_s(struct dvb_frontend *fe, u16 id)
>> +{
>> +       u8 data[2] = { id >> 8, (u8)id };
>> +       return tc90522_write_data(fe, 0x8f, data, sizeof(data));
>> +}
>
> Rather useless oneliner function called only from one place. This makes only
> few lines of code more and bigger binary.

OK, merged to parent function.

>> +int tc90522_tune_s(struct dvb_frontend *fe, bool re_tune, unsigned int
>> mode_flags, unsigned int *delay, fe_status_t *status)
>> +{
>> +       struct tc90522 *demod = fe->demodulator_priv;
>> +       struct tmcc_s tmcc;
>> +       int i, ret,
>> +           freq = fe->dtv_property_cache.frequency,
>> +           tsid = fe->dtv_property_cache.stream_id;
>
> Adding more ints here does not cost anything.

Well, this looks cleaner & smarter on my editor, and checkpatch.pl did
not complain...

>> +
>> +       if (re_tune)
>> +               demod->state = TC90522_SET_FREQUENCY;
>> +
>> +       switch (demod->state) {
>> +       case TC90522_IDLE:
>> +               *delay = msecs_to_jiffies(3000);
>> +               *status = 0;
>> +               return 0;
>> +
>> +       case TC90522_SET_FREQUENCY:
>> +               pr_debug("#%d tsid 0x%x freq %d\n", demod->idx, tsid,
>> freq);
>
> You must use dev_ functions for logging.

Some maintainers (I forgot their names, maybe you also?) asked to use pr_*.
And I agreed with them. dev_* is used only in pt3_pci, the PCI bridge driver.
IMHO pr_* is more suitable. We can change to dev_* if it is a must.

... skip ...

>> +               demod->state = TC90522_TRACK;
>> +               /* fallthrough */
>> +
>> +       case TC90522_TRACK:
>> +               *delay = msecs_to_jiffies(3000);
>> +               *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
>> +               return 0;
>> +
>> +       case TC90522_ABORT:
>> +               *delay = msecs_to_jiffies(3000);
>> +               *status = FE_HAS_SIGNAL;
>> +               return 0;
>> +       }
>> +       return -ERANGE;
>> +}
>
> That didnt look very correct and I didnt even understand it very well.
> Basically it is callback which dvb core uses to tune device. However, thee
> is complex state machine implemented. State machine state is updated by
> read_status() callback, which is *not* ran by DVB core when that HW aldo is
> used. How that can work? You need to call FE status from userspace in order
> to update state machine? If your app does not call status, that does not
> work at all?

You are WRONG.
It is dvb-core who runs the iteration.

> And those 3 sec timers are here in order to leave some time for app to read
> FE status => updates state machine?

User is recommended to do FE_READ_STATUS and check FE_HAS_LOCK status
to make sure it is tuned correctly.

>> +int tc90522_get_tmcc_t(struct tc90522 *demod)
>> +{
>> +       u8 buf;
>> +       bool b = false, retryov, fulock;
>> +
>> +       while (1) {
>> +               if (tc90522_read(demod, 0x80, &buf, 1))
>> +                       return -EBADMSG;
>> +               retryov = buf & 0b10000000 ? true : false;
>> +               fulock  = buf & 0b00001000 ? true : false;
>> +               if (!fulock) {
>> +                       b = true;
>> +                       break;
>> +               } else {
>> +                       if (retryov)
>> +                               break;
>> +               }
>> +               msleep_interruptible(1);
>
> Weird looking sleep, I have never earlier seen that. Have you looked timers
> howto from kernel documentation?
>
> Also, it looks a bit scary what goes to potential infinity loop. If you need
> some upper limit per time you should use loop implemented by jiffies.
> Otherwise just use for loop which surely ends.

So far never fails. But OK I will set an upper limit.

>> +/**** Common ****/
>> +struct dvb_frontend *tc90522_attach(struct i2c_adapter *i2c, u8 idx,
>> fe_delivery_system_t type, u8 addr_demod)
>> +{
>> +       struct dvb_frontend *fe;
>> +       struct tc90522 *demod = kzalloc(sizeof(struct tc90522),
>> GFP_KERNEL);
>> +       if (!demod)
>> +               return NULL;
>> +
>> +       demod->i2c      = i2c;
>> +       demod->idx      = idx;
>
> Driver should not need index at all. It could be found from the frontend
> pointer after registration, but still not needed.

If you read the source thoroughly, you will find that idx is used only
for debugging.
We can remove if it is prohibited.

>> +       demod->type     = type;
>> +       demod->addr_demod = addr_demod;
>> +       fe = &demod->fe;
>> +       memcpy(&fe->ops, (demod->type == SYS_ISDBS) ? &tc90522_ops_s :
>> &tc90522_ops_t, sizeof(struct dvb_frontend_ops));
>> +       fe->demodulator_priv = demod;
>> +       return fe;
>> +}
>
> There is some issues as T and S mode driver instances registered to same
> chip. What happens if you are wathing T and try tune S at same time?
> (probably T breaks). I am not still sure if it is something that should be
> fixed.

Nothing wrong. The chip can handle all 2ch T + 2ch S simultaneously.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Full DVB driver package for Earthsoft PT3 (ISDB-S/T) cards
  2014-05-19 22:19   ` ほち
@ 2014-05-19 23:23     ` Antti Palosaari
  0 siblings, 0 replies; 21+ messages in thread
From: Antti Palosaari @ 2014-05-19 23:23 UTC (permalink / raw)
  To: ほち
  Cc: linux-media, m.chehab, Hans De Goede, Hans Verkuil,
	Laurent Pinchart, Michael Krufky, Sylwester Nawrocki,
	Guennadi Liakhovetski, Peter Senna Tschudin

Moikka!

On 05/20/2014 01:19 AM, ほち wrote:
> Thanks for the review.
> Inlined questions before going further.

Lets see, I will answer my best. But one thing, that is very big 
driver/patch, containing one PCI driver, one (or two?) demod drivers and 
two tuner drivers. So it could take a some time to get reviewed those 
all. I cannot even review PCI driver as I have simply no knowledge, but 
I will review that demod (and hope someone else could take care of those 
tuner drivers).

> Best Regards
> -Bud
>
> 2014-05-18 4:03 GMT+09:00 Antti Palosaari <crope@iki.fi>:
>> Overall tc90522 driver looks very complex and there was multiple issues. One
>> reason of complexiness is that HW algo used. I cannot see any reason why it
>> is used, just change default SW algo and implement things more likely others
>> are doing.
>>
>>> diff --git a/drivers/media/dvb-frontends/tc90522.c
>>> b/drivers/media/dvb-frontends/tc90522.c
>>> new file mode 100644
>>> index 0000000..a767600
>>> --- /dev/null
>>> +++ b/drivers/media/dvb-frontends/tc90522.c
>>> @@ -0,0 +1,539 @@
>>> +/*
>>> + * Earthsoft PT3 demodulator frontend Toshiba TC90522XBG
>>> OFDM(ISDB-T)/8PSK(ISDB-S)
>>
>> That is, or at least should be, general DTV demod driver. So lets call it
>> Toshiba TC90522 or whatever the chipset name is.
>
> FYI, the only document available is SDK from PT3 card maker, Earthsoft.
> No guarantee this driver works in other cards.
>
>>> +int tc90522_write(struct dvb_frontend *fe, const u8 *data, int len)
>>> +{
>>> +       struct tc90522 *demod = fe->demodulator_priv;
>>> +       struct i2c_msg msg[3];
>>> +       u8 buf[6];
>>> +
>>> +       if (data) {
>>> +               msg[0].addr = demod->addr_demod;
>>> +               msg[0].buf = (u8 *)data;
>>> +               msg[0].flags = 0;                       /* write */
>>> +               msg[0].len = len;
>>> +
>>> +               return i2c_transfer(demod->i2c, msg, 1) == 1 ? 0 :
>>> -EREMOTEIO;
>>> +       } else {
>>> +               u8 addr_tuner = (len >> 8) & 0xff,
>>> +                  addr_data = len & 0xff;
>>> +               if (len >> 16) {                        /* read tuner
>>> without address */
>>> +                       buf[0] = TC90522_PASSTHROUGH;
>>> +                       buf[1] = (addr_tuner << 1) | 1;
>>> +                       msg[0].buf = buf;
>>> +                       msg[0].len = 2;
>>> +                       msg[0].addr = demod->addr_demod;
>>> +                       msg[0].flags = 0;               /* write */
>>> +
>>> +                       msg[1].buf = buf + 2;
>>> +                       msg[1].len = 1;
>>> +                       msg[1].addr = demod->addr_demod;
>>> +                       msg[1].flags = I2C_M_RD;        /* read */
>>> +
>>> +                       return i2c_transfer(demod->i2c, msg, 2) == 2 ?
>>> buf[2] : -EREMOTEIO;
>>> +               } else {                                /* read tuner */
>>> +                       buf[0] = TC90522_PASSTHROUGH;
>>> +                       buf[1] = addr_tuner << 1;
>>> +                       buf[2] = addr_data;
>>> +                       msg[0].buf = buf;
>>> +                       msg[0].len = 3;
>>> +                       msg[0].addr = demod->addr_demod;
>>> +                       msg[0].flags = 0;               /* write */
>>> +
>>> +                       buf[3] = TC90522_PASSTHROUGH;
>>> +                       buf[4] = (addr_tuner << 1) | 1;
>>> +                       msg[1].buf = buf + 3;
>>> +                       msg[1].len = 2;
>>> +                       msg[1].addr = demod->addr_demod;
>>> +                       msg[1].flags = 0;               /* write */
>>> +
>>> +                       msg[2].buf = buf + 5;
>>> +                       msg[2].len = 1;
>>> +                       msg[2].addr = demod->addr_demod;
>>> +                       msg[2].flags = I2C_M_RD;        /* read */
>>> +
>>> +                       return i2c_transfer(demod->i2c, msg, 3) == 3 ?
>>> buf[5] : -EREMOTEIO;
>>> +               }
>>> +       }
>>> +}
>>
>> That routine is mess. I read it many times without understanding what it
>> does, when and why. It is not register write over I2C as expected. For
>> example parameter named "len" is abused for tuner I2C even that is demod
>> driver...
>
> Tuners need to read data through demod, and there is no read callback available
> in dvb_frontend.h (only write is provided).
> The above routine provides R/W access.

That is called I2C-gate. I2C bus is wired through demodulator to tuner 
and there is gate which is controlled/owned by demod. That's very norm 
coupling. It is to prevent digital noise traveling to tuner IC and harm 
performance.

IMHO, the most correct implementation solution is use of I2C mux.
https://i2c.wiki.kernel.org/index.php/I2C_bus_multiplexing

See implementation example from rtl2832 and si2168 drivers.

>
>>> +int tc90522_write_data(struct dvb_frontend *fe, u8 addr_data, u8 *data,
>>> u8 len)
>>> +{
>>> +       u8 buf[len + 1];
>>> +       buf[0] = addr_data;
>>> +       memcpy(buf + 1, data, len);
>>> +       return tc90522_write(fe, buf, len + 1);
>>> +}
>>> +
>>> +int tc90522_read(struct tc90522 *demod, u8 addr, u8 *buf, u8 buflen)
>>> +{
>>> +       struct i2c_msg msg[2];
>>> +       if (!buf || !buflen)
>>> +               return -EINVAL;
>>> +
>>> +       buf[0] = addr;
>>
>> ....
>>>
>>> +       msg[0].addr = demod->addr_demod;
>>> +       msg[0].flags = 0;                       /* write */
>>> +       msg[0].buf = buf;
>>
>> just give a addr pointer, no need to store it to buf first.
>>
>>> +       msg[0].len = 1;
>>> +
>>> +       msg[1].addr = demod->addr_demod;
>>> +       msg[1].flags = I2C_M_RD;                /* read */
>>> +       msg[1].buf = buf;
>>> +       msg[1].len = buflen;
>>> +
>>> +       return i2c_transfer(demod->i2c, msg, 2) == 2 ? 0 : -EREMOTEIO;
>>> +}
>>
>> All in all, it looks like demod is using just most typical register access
>> for both register write and read, where first byte is register address and
>> value(s) are after that. Register read is done using repeated START.
>>
>> I encourage you to use RegMap API as it covers all that boilerplate stuff -
>> and forces you implement things correctly (no such hack possible done in
>> tc90522_write()).
>
> Good recommendation. I'll take a look.

Implementation example could be found from e4000 driver at least.

>
>>> +u32 tc90522_byten(const u8 *data, u32 n)
>>> +{
>>> +       u32 i, val = 0;
>>> +
>>> +       for (i = 0; i < n; i++) {
>>> +               val <<= 8;
>>> +               val |= data[i];
>>> +       }
>>> +       return val;
>>> +}
>>
>> What is that? Kinda bit reverse? Look from existing bitops if there is such
>> a solution already and if not, add comments what that is for.
>
> changed to:
> u64 tc90522_ntoint(const u8 *data, u8 n)    /* convert n_bytes data
> from stream (network byte order) to integer */
> {                        /* can't use <arpa/inet.h>'s ntoh*() as
> sometimes n = 3,5,... */
> ...
>

hmmm, I cannot understand just now. Lets check it later.


>>> +               ((data[0] >> 4) & 1)                    ||
>>> +               tc90522_read(demod, 0xce, data, 2)      ||
>>> +               (tc90522_byten(data, 2) == 0)           ||
>>> +               tc90522_read(demod, 0xc3, data, 1)      ||
>>> +               tc90522_read(demod, 0xc5, data, SIZE);
>>
>> Masking return statuses like that does not look good nor clear.
>
> Well, the statuses are not so important here.
> We only want to know & stop if there was an error occured.
> That is enough.

Maybe most common implementation technique in kernel is following:

ret = foo();
if (ret)
   goto err;

ret = bar();
if (ret)
   goto err;

>
>>> +enum tc90522_pwr {
>>> +       TC90522_PWR_OFF         = 0x00,
>>> +       TC90522_PWR_AMP_ON      = 0x04,
>>> +       TC90522_PWR_TUNER_ON    = 0x40,
>>> +};
>>> +
>>> +static enum tc90522_pwr tc90522_pwr = TC90522_PWR_OFF;
>>
>> Global static variable for device power management..? That looks very bad.
>> Those variables are shared between all driver instances. That will not work
>> if you have multiple devices having that demod driver!
>
> OK, removed.
> In the next release pt3_pci will instruct when to power on the demod chip.

Usually pci driver will not need to care at all about demod nor tuner 
power-management. DVB core does it all. There still could be some corner 
cases... but those are really corner cases and need some careful thinking.

>
>>> +int tc90522_set_powers(struct tc90522 *demod, enum tc90522_pwr pwr)
>>> +{
>>> +       u8 data = pwr | 0b10011001;
>>> +       pr_debug("#%d tuner %s amp %s\n", demod->idx, pwr &
>>> TC90522_PWR_TUNER_ON ? "ON" : "OFF", pwr & TC90522_PWR_AMP_ON ? "ON" :
>>> "OFF");
>>> +       tc90522_pwr = pwr;
>>> +       return tc90522_write_data(&demod->fe, 0x1e, &data, 1);
>>> +}
>>> +
>>> +/* dvb_frontend_ops */
>>> +int tc90522_get_frontend_algo(struct dvb_frontend *fe)
>>> +{
>>> +       return DVBFE_ALGO_HW;
>>> +}
>>> +
>>> +int tc90522_sleep(struct dvb_frontend *fe)
>>> +{
>>> +       struct tc90522 *demod = fe->demodulator_priv;
>>> +       pr_debug("#%d %s %s\n", demod->idx, __func__, demod->type ==
>>> SYS_ISDBS ? "S" : "T");
>>> +       return fe->ops.tuner_ops.sleep(fe);
>>
>> :-@ You are simply not allowed to hard code tuner power-management to demod
>> driver, it is no, no, no. Demod driver can have only get IF and set
>> parameters reference to tuner and nothing more.
>>
>> You should sleep only demod in that demod sleep().
>> It is DVB core who is responsible of runtime power-management.
>>
>>> +}
>>> +
>>> +int tc90522_wakeup(struct dvb_frontend *fe)
>>> +{
>>> +       struct tc90522 *demod = fe->demodulator_priv;
>>> +       pr_debug("#%d %s %s 0x%x\n", demod->idx, __func__, demod->type ==
>>> SYS_ISDBS ? "S" : "T", tc90522_pwr);
>>> +
>>> +       if (!tc90522_pwr)
>>> +               return  tc90522_set_powers(demod, TC90522_PWR_TUNER_ON) ||
>>> +                       i2c_transfer(demod->i2c, NULL, 0)               ||
>>> +                       tc90522_set_powers(demod, TC90522_PWR_TUNER_ON |
>>> TC90522_PWR_AMP_ON);
>>> +       demod->state = TC90522_IDLE;
>>> +       return fe->ops.tuner_ops.init(fe);
>>> +}
>>
>> power-management is totally wrong here too
>>
>>> +void tc90522_release(struct dvb_frontend *fe)
>>> +{
>>> +       struct tc90522 *demod = fe->demodulator_priv;
>>> +       pr_debug("#%d %s\n", demod->idx, __func__);
>>> +
>>> +       if (tc90522_pwr)
>>> +               tc90522_set_powers(demod, TC90522_PWR_OFF);
>>
>> That belongs to demod driver power-management callback sleep()
>>
>>> +       tc90522_sleep(fe);
>>
>> hmm? PM...
>>
>>> +       fe->ops.tuner_ops.release(fe);
>>> +       kfree(demod);
>>> +}
>
> Sounds like your perception is TOTALLY WRONG.
> Remember this chip has 4 independent demods sharing the same power.
> There is only one power available for all 4!
> Turning off 1 demod will shutdown the other 3.

... and if you put two of these cards to your computer it will not work. 
It will be shared across all 8 demods (2 x 4 integrated demods).

You cannot use global static variable to share "power state" variable 
like that. Maybe better to use one shared state for all 4 (?) integrated 
demods.

Lets take a simple example:
global static bool power_on;

you have 2 different devices, each having 1 demod (both physical and 
logical).

start device #0: => power_on = true;
start device #1: => power_on = true;
stop device #1: => power_on = false;
*** now your device #0 stops too, even you are using it.


>
> Secondly, in PT3, there is no direct access to the tuners.
> Every control / data is done via demod, including power.

That is just typical case. See I2C mux adapter.

>
>>> +int tc90522_read_signal_strength(struct dvb_frontend *fe, u16 *cn)     /*
>>> raw C/N */
>>> +{
>>> +       struct tc90522 *demod = fe->demodulator_priv;
>>> +       s64 ret = tc90522_get_cn_raw(demod);
>>> +       *cn = ret < 0 ? 0 : ret;
>>> +       pr_debug("v3 CN %d (%lld dB)\n", (int)*cn, demod->type ==
>>> SYS_ISDBS ? (long long int)tc90522_get_cn_s(*cn) : (long long
>>> int)tc90522_get_cn_t(*cn));
>>> +       return ret < 0 ? ret : 0;
>>> +}
>>
>> We have API who supports both CNR and signal strenght. Do not abuse signal
>> strenght for CNR, instead implement as it should.
>
> OK, on DVBv3 stat I changed back .read_signal_strength to .read_snr
> for CNR (digitally modulated SNR)
> though AFAIK (in a strict mean) SNR != CNR.

Yeah, strictly speaking. Nowadays DVBv5 statistics API known it as a 
CNR. See DVB v5 API command DTV_STAT_CNR. Grep that and you will find 
some implementation examples.

>
>>> +int tc90522_read_status(struct dvb_frontend *fe, fe_status_t *status)
>>> +{
>>> +       struct tc90522 *demod = fe->demodulator_priv;
>>> +       struct dtv_frontend_properties *c = &fe->dtv_property_cache;
>>> +       s64 ret = tc90522_get_cn_raw(demod),
>>> +           raw = ret < 0 ? 0 : ret;
>>> +
>>> +       switch (demod->state) {
>>> +       case TC90522_IDLE:
>>> +       case TC90522_SET_FREQUENCY:
>>> +               *status = 0;
>>> +               break;
>>> +
>>> +       case TC90522_SET_MODULATION:
>>> +       case TC90522_ABORT:
>>> +               *status |= FE_HAS_SIGNAL;
>>> +               break;
>>> +
>>> +       case TC90522_TRACK:
>>> +               *status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
>>> +               break;
>>> +       }
>>> +
>>> +       c->cnr.len = 1;
>>> +       c->cnr.stat[0].svalue = demod->type == SYS_ISDBS ?
>>> tc90522_get_cn_s(raw) : tc90522_get_cn_t(raw);
>>> +       c->cnr.stat[0].scale = FE_SCALE_DECIBEL;
>>> +       pr_debug("v5 CN %lld (%lld dB)\n", raw, c->cnr.stat[0].svalue);
>>> +       return ret < 0 ? ret : 0;
>>> +}
>>
>> So you have decided to add some statistics logic here too. It is good place
>> to update stistics counters, but only on case where SW algo used and DVB
>> core is polling. However, you used HW algo which means that is not polled
>> automatically. Maybe it does not work as it should.
>
> So sorry but so far this works perfectly.

Could work for your case, but it looks very abnormal. And when somehing 
looks abnormal it is not likely very correct.

>
>>> +/**** ISDB-S ****/
>>> +int tc90522_write_id_s(struct dvb_frontend *fe, u16 id)
>>> +{
>>> +       u8 data[2] = { id >> 8, (u8)id };
>>> +       return tc90522_write_data(fe, 0x8f, data, sizeof(data));
>>> +}
>>
>> Rather useless oneliner function called only from one place. This makes only
>> few lines of code more and bigger binary.
>
> OK, merged to parent function.
>
>>> +int tc90522_tune_s(struct dvb_frontend *fe, bool re_tune, unsigned int
>>> mode_flags, unsigned int *delay, fe_status_t *status)
>>> +{
>>> +       struct tc90522 *demod = fe->demodulator_priv;
>>> +       struct tmcc_s tmcc;
>>> +       int i, ret,
>>> +           freq = fe->dtv_property_cache.frequency,
>>> +           tsid = fe->dtv_property_cache.stream_id;
>>
>> Adding more ints here does not cost anything.
>
> Well, this looks cleaner & smarter on my editor, and checkpatch.pl did
> not complain...
>
>>> +
>>> +       if (re_tune)
>>> +               demod->state = TC90522_SET_FREQUENCY;
>>> +
>>> +       switch (demod->state) {
>>> +       case TC90522_IDLE:
>>> +               *delay = msecs_to_jiffies(3000);
>>> +               *status = 0;
>>> +               return 0;
>>> +
>>> +       case TC90522_SET_FREQUENCY:
>>> +               pr_debug("#%d tsid 0x%x freq %d\n", demod->idx, tsid,
>>> freq);
>>
>> You must use dev_ functions for logging.
>
> Some maintainers (I forgot their names, maybe you also?) asked to use pr_*.
> And I agreed with them. dev_* is used only in pt3_pci, the PCI bridge driver.
> IMHO pr_* is more suitable. We can change to dev_* if it is a must.

dev_ is really must for device drivers where you have a pointer to 
device (it is I2C in that case). pr_ is simply wrong. The one who has 
said you have to use pr_ is simply wrong and maybe didn't know existence 
of dev_.

And if you implement i2c client correctly, dev_ will print nicely driver 
name and device address beginning of each log entry.

>
> ... skip ...
>
>>> +               demod->state = TC90522_TRACK;
>>> +               /* fallthrough */
>>> +
>>> +       case TC90522_TRACK:
>>> +               *delay = msecs_to_jiffies(3000);
>>> +               *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
>>> +               return 0;
>>> +
>>> +       case TC90522_ABORT:
>>> +               *delay = msecs_to_jiffies(3000);
>>> +               *status = FE_HAS_SIGNAL;
>>> +               return 0;
>>> +       }
>>> +       return -ERANGE;
>>> +}
>>
>> That didnt look very correct and I didnt even understand it very well.
>> Basically it is callback which dvb core uses to tune device. However, thee
>> is complex state machine implemented. State machine state is updated by
>> read_status() callback, which is *not* ran by DVB core when that HW aldo is
>> used. How that can work? You need to call FE status from userspace in order
>> to update state machine? If your app does not call status, that does not
>> work at all?
>
> You are WRONG.
> It is dvb-core who runs the iteration.
>
>> And those 3 sec timers are here in order to leave some time for app to read
>> FE status => updates state machine?
>
> User is recommended to do FE_READ_STATUS and check FE_HAS_LOCK status
> to make sure it is tuned correctly.


There is still big question: what is the reason you select that exotic 
implementation? What is reason you cannot use default SW algo?


>
>>> +int tc90522_get_tmcc_t(struct tc90522 *demod)
>>> +{
>>> +       u8 buf;
>>> +       bool b = false, retryov, fulock;
>>> +
>>> +       while (1) {
>>> +               if (tc90522_read(demod, 0x80, &buf, 1))
>>> +                       return -EBADMSG;
>>> +               retryov = buf & 0b10000000 ? true : false;
>>> +               fulock  = buf & 0b00001000 ? true : false;
>>> +               if (!fulock) {
>>> +                       b = true;
>>> +                       break;
>>> +               } else {
>>> +                       if (retryov)
>>> +                               break;
>>> +               }
>>> +               msleep_interruptible(1);
>>
>> Weird looking sleep, I have never earlier seen that. Have you looked timers
>> howto from kernel documentation?
>>
>> Also, it looks a bit scary what goes to potential infinity loop. If you need
>> some upper limit per time you should use loop implemented by jiffies.
>> Otherwise just use for loop which surely ends.
>
> So far never fails. But OK I will set an upper limit.
>
>>> +/**** Common ****/
>>> +struct dvb_frontend *tc90522_attach(struct i2c_adapter *i2c, u8 idx,
>>> fe_delivery_system_t type, u8 addr_demod)
>>> +{
>>> +       struct dvb_frontend *fe;
>>> +       struct tc90522 *demod = kzalloc(sizeof(struct tc90522),
>>> GFP_KERNEL);
>>> +       if (!demod)
>>> +               return NULL;
>>> +
>>> +       demod->i2c      = i2c;
>>> +       demod->idx      = idx;
>>
>> Driver should not need index at all. It could be found from the frontend
>> pointer after registration, but still not needed.
>
> If you read the source thoroughly, you will find that idx is used only
> for debugging.
> We can remove if it is prohibited.

hmm, maybe you could use adapter ID then (if it is really needed whole ID).

>
>>> +       demod->type     = type;
>>> +       demod->addr_demod = addr_demod;
>>> +       fe = &demod->fe;
>>> +       memcpy(&fe->ops, (demod->type == SYS_ISDBS) ? &tc90522_ops_s :
>>> &tc90522_ops_t, sizeof(struct dvb_frontend_ops));
>>> +       fe->demodulator_priv = demod;
>>> +       return fe;
>>> +}
>>
>> There is some issues as T and S mode driver instances registered to same
>> chip. What happens if you are wathing T and try tune S at same time?
>> (probably T breaks). I am not still sure if it is something that should be
>> fixed.
>
> Nothing wrong. The chip can handle all 2ch T + 2ch S simultaneously.
>

Seems to be quite powerful chip. So there is only 2 RF tuners still, 
which limits some use?

So you have to register it as a 4 separate frontends (adapters too as 
those could be used same time). My cxd2832r driver uses pointer to carry 
state from frontend to another, when it registers originally 2 frontends 
- one for DVB-T/T2 and one for DVB-C. No global static variables needed 
at all. Just carry pointer from attach to another.

regards
Antti

-- 
http://palosaari.fi/

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH] Full DVB driver package for Earthsoft PT3 (ISDB-S/T) cards
@ 2014-05-20 23:37 Буди Романто, AreMa Inc
  0 siblings, 0 replies; 21+ messages in thread
From: Буди Романто, AreMa Inc @ 2014-05-20 23:37 UTC (permalink / raw)
  To: linux-media
  Cc: Буди
	Романто,
	AreMa Inc, crope, m.chehab, hdegoede, laurent.pinchart, mkrufky,
	sylvester.nawrocki, g.liakhovetski, peter.senna

DVB driver for Earthsoft PT3 (ISDB-S/T) receiver PCI Express cards.
It behaves similarly as PT1 DVB, plus some tuning enhancements:
1. in addition to the real frequency:
	ISDB-S : freq. channel ID
	ISDB-T : freq# (I/O# +128), ch#, ch# +64 for CATV
2. in addition to TSID:
	ISDB-S : slot#

Feature changes:
- dropped DKMS & standalone compile
- dropped verbosity (debug levels), use single level -DDEBUG instead
- moved demodulator FE to drivers/media/dvb-frontends
- moved tuners to drivers/media/tuners
- translated to standard (?) I2C protocol
- dropped FE's LNB control & other unused features
- DVBv3: changed back .read_signal_strength to .read_snr for raw CNR (digitally modulated SNR)
- added DVBv5 CNR support
- improved CNR precision

Main components:
1. Sharp	VA4M6JC2103	: contains 2 ISDB-S + 2 ISDB-T (customized?) tuners
	ISDB-S : Sharp QM1D1C0042 RF-IC
	ISDB-T : MaxLinear CMOS Hybrid TV MxL301RF
2. Toshiba	TC90522XBG	: (customized?) 2ch OFDM + 2ch 8PSK demodulator
3. Altera	EP4CGX15BF14C8N	: customized FPGA PCI bridge
4. ESMT		M12L16161A-7TG	: on-board memory

The full package (buildable as standalone, DKMS or tree embedded module) is available at
https://github.com/knight-rider/ptx/tree/master/pt3_dvb

Signed-off-by: Буди Романто, AreMa Inc <knightrider@are.ma>
---
 drivers/media/dvb-frontends/Kconfig   |  11 +-
 drivers/media/dvb-frontends/Makefile  |   1 +
 drivers/media/dvb-frontends/tc90522.c | 512 ++++++++++++++++++++++++++++++++++
 drivers/media/dvb-frontends/tc90522.h |  36 +++
 drivers/media/pci/Kconfig             |   2 +-
 drivers/media/pci/Makefile            |   1 +
 drivers/media/pci/pt3/Kconfig         |  11 +
 drivers/media/pci/pt3/Makefile        |   6 +
 drivers/media/pci/pt3/pt3_common.h    |  86 ++++++
 drivers/media/pci/pt3/pt3_dma.c       | 333 ++++++++++++++++++++++
 drivers/media/pci/pt3/pt3_dma.h       |  50 ++++
 drivers/media/pci/pt3/pt3_i2c.c       | 189 +++++++++++++
 drivers/media/pci/pt3/pt3_i2c.h       |  25 ++
 drivers/media/pci/pt3/pt3_pci.c       | 396 ++++++++++++++++++++++++++
 drivers/media/tuners/Kconfig          |  14 +
 drivers/media/tuners/Makefile         |   2 +
 drivers/media/tuners/mxl301rf.c       | 390 ++++++++++++++++++++++++++
 drivers/media/tuners/mxl301rf.h       |  36 +++
 drivers/media/tuners/qm1d1c0042.c     | 382 +++++++++++++++++++++++++
 drivers/media/tuners/qm1d1c0042.h     |  36 +++
 20 files changed, 2517 insertions(+), 2 deletions(-)
 create mode 100644 drivers/media/dvb-frontends/tc90522.c
 create mode 100644 drivers/media/dvb-frontends/tc90522.h
 create mode 100644 drivers/media/pci/pt3/Kconfig
 create mode 100644 drivers/media/pci/pt3/Makefile
 create mode 100644 drivers/media/pci/pt3/pt3_common.h
 create mode 100644 drivers/media/pci/pt3/pt3_dma.c
 create mode 100644 drivers/media/pci/pt3/pt3_dma.h
 create mode 100644 drivers/media/pci/pt3/pt3_i2c.c
 create mode 100644 drivers/media/pci/pt3/pt3_i2c.h
 create mode 100644 drivers/media/pci/pt3/pt3_pci.c
 create mode 100644 drivers/media/tuners/mxl301rf.c
 create mode 100644 drivers/media/tuners/mxl301rf.h
 create mode 100644 drivers/media/tuners/qm1d1c0042.c
 create mode 100644 drivers/media/tuners/qm1d1c0042.h

diff --git a/drivers/media/dvb-frontends/Kconfig b/drivers/media/dvb-frontends/Kconfig
index 1469d44..edc45bd 100644
--- a/drivers/media/dvb-frontends/Kconfig
+++ b/drivers/media/dvb-frontends/Kconfig
@@ -598,7 +598,7 @@ config DVB_S5H1411
 	  An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want
 	  to support this frontend.
 
-comment "ISDB-T (terrestrial) frontends"
+comment "ISDB-S (satellite) & ISDB-T (terrestrial) frontends"
 	depends on DVB_CORE
 
 config DVB_S921
@@ -625,6 +625,15 @@ config DVB_MB86A20S
 	  A driver for Fujitsu mb86a20s ISDB-T/ISDB-Tsb demodulator.
 	  Say Y when you want to support this frontend.
 
+config DVB_TC90522
+	tristate "Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S)"
+	depends on DVB_CORE && I2C
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S) DVB demodulator
+	  frontend for Earthsoft PT3 PCIE cards.
+	  Say Y when you want to support this frontend.
+
 comment "Digital terrestrial only tuners/PLL"
 	depends on DVB_CORE
 
diff --git a/drivers/media/dvb-frontends/Makefile b/drivers/media/dvb-frontends/Makefile
index dda0bee..5da7a25 100644
--- a/drivers/media/dvb-frontends/Makefile
+++ b/drivers/media/dvb-frontends/Makefile
@@ -106,4 +106,5 @@ obj-$(CONFIG_DVB_RTL2830) += rtl2830.o
 obj-$(CONFIG_DVB_RTL2832) += rtl2832.o
 obj-$(CONFIG_DVB_M88RS2000) += m88rs2000.o
 obj-$(CONFIG_DVB_AF9033) += af9033.o
+obj-$(CONFIG_DVB_TC90522) += tc90522.o
 
diff --git a/drivers/media/dvb-frontends/tc90522.c b/drivers/media/dvb-frontends/tc90522.c
new file mode 100644
index 0000000..6ad3ab0
--- /dev/null
+++ b/drivers/media/dvb-frontends/tc90522.c
@@ -0,0 +1,512 @@
+/*
+ * Toshiba TC90522XBG 2ch OFDM(ISDB-T) + 2ch 8PSK(ISDB-S) demodulator frontend for Earthsoft PT3
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "dvb_math.h"
+#include "tc90522.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Toshiba TC90522 OFDM(ISDB-T)/8PSK(ISDB-S) demodulator [Earthsoft PT3]");
+MODULE_LICENSE("GPL");
+
+#define TC90522_PASSTHROUGH 0xfe
+
+enum tc90522_state {
+	TC90522_IDLE,
+	TC90522_SET_FREQUENCY,
+	TC90522_SET_MODULATION,
+	TC90522_TRACK,
+	TC90522_ABORT,
+};
+
+struct tc90522 {
+	struct dvb_frontend fe;
+	struct i2c_adapter *i2c;
+	fe_delivery_system_t type;
+	u8 idx, addr_demod;
+	s32 offset;
+	enum tc90522_state state;
+};
+
+int tc90522_read_tuner_wo_addr(struct tc90522 *demod, u8 addr_tuner)
+{
+	u8 buf[] = { TC90522_PASSTHROUGH, (addr_tuner << 1) | 1, 0 };
+	struct i2c_msg msg[] = {
+		{ .addr = demod->addr_demod, .flags = 0,	.buf = buf,	.len = 2, },
+		{ .addr = demod->addr_demod, .flags = I2C_M_RD,	.buf = buf + 2,	.len = 1, },
+	};
+	return i2c_transfer(demod->i2c, msg, 2) == 2 ? buf[2] : -EREMOTEIO;
+}
+
+int tc90522_read_tuner(struct tc90522 *demod, int addrs)
+{
+	u8 addr_tuner = (addrs >> 8) & 0xff,
+	   addr_data = addrs & 0xff;
+	u8 buf[] = { TC90522_PASSTHROUGH, addr_tuner << 1, addr_data, TC90522_PASSTHROUGH, (addr_tuner << 1) | 1, 0 };
+	struct i2c_msg msg[] = {
+		{ .addr = demod->addr_demod, .flags = 0,	.buf = buf,	.len = 3, },
+		{ .addr = demod->addr_demod, .flags = 0,	.buf = buf + 3,	.len = 2, },
+		{ .addr = demod->addr_demod, .flags = I2C_M_RD,	.buf = buf + 5,	.len = 1, },
+	};
+
+	if (addrs >> 16)
+		return tc90522_read_tuner_wo_addr(demod, addr_tuner);
+	return i2c_transfer(demod->i2c, msg, 3) == 3 ? buf[5] : -EREMOTEIO;
+}
+
+int tc90522_write(struct dvb_frontend *fe, const u8 *data, int len)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	struct i2c_msg msg[] = {
+		{ .addr = demod->addr_demod, .flags = 0, .buf = (u8 *)data, .len = len, },
+	};
+	if (!data)
+		return tc90522_read_tuner(demod, len);
+	return i2c_transfer(demod->i2c, msg, 1) == 1 ? 0 : -EREMOTEIO;
+}
+
+int tc90522_write_data(struct dvb_frontend *fe, u8 addr_data, u8 *data, u8 len)
+{
+	u8 buf[len + 1];
+	buf[0] = addr_data;
+	memcpy(buf + 1, data, len);
+	return tc90522_write(fe, buf, len + 1);
+}
+
+int tc90522_read(struct tc90522 *demod, u8 addr, u8 *buf, u8 len)
+{
+	struct i2c_msg msg[] = {
+		{ .addr = demod->addr_demod, .flags = 0,	.buf = buf, .len = 1,	},
+		{ .addr = demod->addr_demod, .flags = I2C_M_RD,	.buf = buf, .len = len,	},
+	};
+	if (!buf || !len)
+		return -EINVAL;
+	buf[0] = addr;
+	return i2c_transfer(demod->i2c, msg, 2) == 2 ? 0 : -EREMOTEIO;
+}
+
+u64 tc90522_n2int(const u8 *data, u8 n)		/* convert n_bytes data from stream (network byte order) to integer */
+{						/* can't use <arpa/inet.h>'s ntoh*() as sometimes n = 3,5,...       */
+	u32 i, val = 0;
+
+	for (i = 0; i < n; i++) {
+		val <<= 8;
+		val |= data[i];
+	}
+	return val;
+}
+
+int tc90522_read_id_s(struct tc90522 *demod, u16 *id)
+{
+	u8 buf[2];
+	int err = tc90522_read(demod, 0xe6, buf, 2);
+	if (!err)
+		*id = tc90522_n2int(buf, 2);
+	return err;
+}
+
+struct tmcc_s {			/* Transmission and Multiplexing Configuration Control */
+	u32 mode[4];
+	u32 slot[4];
+	u32 id[8];
+};
+
+int tc90522_read_tmcc_s(struct tc90522 *demod, struct tmcc_s *tmcc)
+{
+	enum {
+		BASE = 0xc5,
+		SIZE = 0xe5 - BASE + 1
+	};
+	u8 data[SIZE];
+	u32 i, byte_offset, bit_offset;
+
+	int err = tc90522_read(demod, 0xc3, data, 1)	||
+		((data[0] >> 4) & 1)			||
+		tc90522_read(demod, 0xce, data, 2)	||
+		(tc90522_n2int(data, 2) == 0)		||
+		tc90522_read(demod, 0xc3, data, 1)	||
+		tc90522_read(demod, 0xc5, data, SIZE);
+	if (err)
+		return err;
+	for (i = 0; i < 4; i++) {
+		byte_offset = i >> 1;
+		bit_offset = (i & 1) ? 0 : 4;
+		tmcc->mode[i] = (data[0xc8 + byte_offset - BASE] >> bit_offset) & 0b00001111;
+		tmcc->slot[i] = (data[0xca + i           - BASE] >>          0) & 0b00111111;
+	}
+	for (i = 0; i < 8; i++)
+		tmcc->id[i] = tc90522_n2int(data + 0xce + i * 2 - BASE, 2);
+	return 0;
+}
+
+enum tc90522_pwr {
+	TC90522_PWR_OFF		= 0x00,
+	TC90522_PWR_AMP_ON	= 0x04,
+	TC90522_PWR_TUNER_ON	= 0x40,
+};
+
+int tc90522_set_powers(struct tc90522 *demod, enum tc90522_pwr pwr)
+{
+	u8 data = pwr | 0b10011001;
+	pr_debug("#%d %s tuner %s amp %s\n", demod->idx, __func__, pwr & TC90522_PWR_TUNER_ON ?
+		"ON" : "OFF", pwr & TC90522_PWR_AMP_ON ? "ON" : "OFF");
+	return tc90522_write_data(&demod->fe, 0x1e, &data, 1);
+}
+
+/* dvb_frontend_ops */
+int tc90522_get_frontend_algo(struct dvb_frontend *fe)
+{
+	return DVBFE_ALGO_HW;
+}
+
+int tc90522_sleep(struct dvb_frontend *fe)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	pr_debug("#%d %s %s\n", demod->idx, __func__, demod->type == SYS_ISDBS ? "S" : "T");
+	return fe->ops.tuner_ops.sleep(fe);
+}
+
+int tc90522_wakeup(struct dvb_frontend *fe)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	pr_debug("#%d %s %s\n", demod->idx, __func__, demod->type == SYS_ISDBS ? "S" : "T");
+
+	demod->state = TC90522_IDLE;
+	return fe->ops.tuner_ops.init(fe);
+}
+
+void tc90522_release(struct dvb_frontend *fe)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	pr_debug("#%d %s\n", demod->idx, __func__);
+
+	tc90522_set_powers(demod, TC90522_PWR_OFF);
+	tc90522_sleep(fe);
+	fe->ops.tuner_ops.release(fe);
+	kfree(demod);
+}
+
+s64 tc90522_get_cn_raw(struct tc90522 *demod)
+{
+	u8 buf[3], buflen = demod->type == SYS_ISDBS ? 2 : 3, addr = demod->type == SYS_ISDBS ? 0xbc : 0x8b;
+	int err = tc90522_read(demod, addr, buf, buflen);
+	return err < 0 ? err : tc90522_n2int(buf, buflen);
+}
+
+s64 tc90522_get_cn_s(s64 raw)	/* @ .0001 dB */
+{
+	s64 x, y;
+
+	raw -= 3000;
+	if (raw < 0)
+		raw = 0;
+	x = int_sqrt(raw << 20);
+	y = 16346ll * x - (143410ll << 16);
+	y = ((x * y) >> 16) + (502590ll << 16);
+	y = ((x * y) >> 16) - (889770ll << 16);
+	y = ((x * y) >> 16) + (895650ll << 16);
+	y = (588570ll << 16) - ((x * y) >> 16);
+	return y < 0 ? 0 : y >> 16;
+}
+
+s64 tc90522_get_cn_t(s64 raw)	/* @ .0001 dB */
+{
+	s64 x, y;
+	if (!raw)
+		return 0;
+	x = (1130911733ll - 10ll * intlog10(raw)) >> 2;
+	y = (6ll * x / 25ll) - (16ll << 22);
+	y = ((x * y) >> 22) + (398ll << 22);
+	y = ((x * y) >> 22) + (5491ll << 22);
+	y = ((x * y) >> 22) + (30965ll << 22);
+	return y >> 22;
+}
+
+int tc90522_read_snr(struct dvb_frontend *fe, u16 *cn)	/* raw C/N, digitally modulated S/N ratio */
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	s64 err = tc90522_get_cn_raw(demod);
+	*cn = err < 0 ? 0 : err;
+	pr_debug("v3 CN %d (%lld dB)\n", (int)*cn, demod->type == SYS_ISDBS ?
+		(int64_t)tc90522_get_cn_s(*cn) : (int64_t)tc90522_get_cn_t(*cn));
+	return err < 0 ? err : 0;
+}
+
+int tc90522_read_status(struct dvb_frontend *fe, fe_status_t *status)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+	s64 err = tc90522_get_cn_raw(demod),
+	    raw = err < 0 ? 0 : err;
+
+	switch (demod->state) {
+	case TC90522_IDLE:
+	case TC90522_SET_FREQUENCY:
+		*status = 0;
+		break;
+
+	case TC90522_SET_MODULATION:
+	case TC90522_ABORT:
+		*status |= FE_HAS_SIGNAL;
+		break;
+
+	case TC90522_TRACK:
+		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		break;
+	}
+
+	c->cnr.len = 1;
+	c->cnr.stat[0].svalue = demod->type == SYS_ISDBS ? tc90522_get_cn_s(raw) : tc90522_get_cn_t(raw);
+	c->cnr.stat[0].scale = FE_SCALE_DECIBEL;
+	pr_debug("v5 CN %lld (%lld dB)\n", raw, c->cnr.stat[0].svalue);
+	return err < 0 ? err : 0;
+}
+
+/**** ISDB-S ****/
+int tc90522_tune_s(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	struct tmcc_s tmcc;
+	int i, err,
+	    freq = fe->dtv_property_cache.frequency,
+	    tsid = fe->dtv_property_cache.stream_id;
+	u8 id_s[2];
+
+	if (re_tune)
+		demod->state = TC90522_SET_FREQUENCY;
+
+	switch (demod->state) {
+	case TC90522_IDLE:
+		*delay = msecs_to_jiffies(2000);
+		*status = 0;
+		return 0;
+
+	case TC90522_SET_FREQUENCY:
+		pr_debug("#%d tsid 0x%x freq %d\n", demod->idx, tsid, freq);
+		err = fe->ops.tuner_ops.set_frequency(fe, freq);
+		if (err)
+			return err;
+		demod->offset = 0;
+		demod->state = TC90522_SET_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case TC90522_SET_MODULATION:
+		for (i = 0; i < 1000; i++) {
+			err = tc90522_read_tmcc_s(demod, &tmcc);
+			if (!err)
+				break;
+			msleep_interruptible(1);
+		}
+		if (err) {
+			pr_debug("fail tc_read_tmcc_s err=0x%x\n", err);
+			demod->state = TC90522_ABORT;
+			*delay = msecs_to_jiffies(1000);
+			return err;
+		}
+		pr_debug("slots=%d,%d,%d,%d mode=%d,%d,%d,%d tmcc.id=0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x\n",
+				tmcc.slot[0], tmcc.slot[1], tmcc.slot[2], tmcc.slot[3],
+				tmcc.mode[0], tmcc.mode[1], tmcc.mode[2], tmcc.mode[3],
+				tmcc.id[0], tmcc.id[1], tmcc.id[2], tmcc.id[3],
+				tmcc.id[4], tmcc.id[5], tmcc.id[6], tmcc.id[7]);
+		for (i = 0; i < ARRAY_SIZE(tmcc.id); i++) {
+			pr_debug("tsid %x i %d tmcc.id %x\n", tsid, i, tmcc.id[i]);
+			if (tmcc.id[i] == tsid)
+				break;
+		}
+		if (tsid < ARRAY_SIZE(tmcc.id))		/* treat as slot# */
+			i = tsid;
+		if (i == ARRAY_SIZE(tmcc.id)) {
+			pr_debug("#%d i%d tsid 0x%x not found\n", demod->idx, i, tsid);
+			return -EINVAL;
+		}
+		demod->offset = i;
+		pr_debug("#%d found tsid 0x%x on slot %d\n", demod->idx, tsid, i);
+
+		id_s[0] = (tmcc.id[demod->offset] >> 8)	& 0xff;
+		id_s[1] = tmcc.id[demod->offset]	& 0xff;
+		err = tc90522_write_data(fe, 0x8f, id_s, sizeof(id_s));
+		if (err) {
+			pr_debug("fail set_tmcc_s err=%d\n", err);
+			return err;
+		}
+		for (i = 0; i < 1000; i++) {
+			u16 short_id;
+			err = tc90522_read_id_s(demod, &short_id);
+			if (err) {
+				pr_debug("fail get_id_s err=%d\n", err);
+				return err;
+			}
+			tsid = short_id;
+			pr_debug("#%d tsid=0x%x\n", demod->idx, tsid);
+			if ((tsid & 0xffff) == tmcc.id[demod->offset])
+				break;
+			msleep_interruptible(1);
+		}
+		demod->state = TC90522_TRACK;
+		/* fallthrough */
+
+	case TC90522_TRACK:
+		*delay = msecs_to_jiffies(2000);
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+
+	case TC90522_ABORT:
+		*delay = msecs_to_jiffies(2000);
+		*status = FE_HAS_SIGNAL;
+		return 0;
+	}
+	return -ERANGE;
+}
+
+static struct dvb_frontend_ops tc90522_ops_s = {
+	.delsys = { SYS_ISDBS },
+	.info = {
+		.name = "TC90522 ISDB-S",
+		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO | FE_CAN_MULTISTREAM |
+			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
+	},
+	.init = tc90522_wakeup,
+	.sleep = tc90522_sleep,
+	.release = tc90522_release,
+	.write = tc90522_write,
+	.get_frontend_algo = tc90522_get_frontend_algo,
+	.read_snr = tc90522_read_snr,
+	.read_status = tc90522_read_status,
+	.tune = tc90522_tune_s,
+};
+
+/**** ISDB-T ****/
+int tc90522_get_tmcc_t(struct tc90522 *demod)
+{
+	u8 buf;
+	u16 i = 65535;
+	bool b = false, retryov, fulock;
+
+	while (i--) {
+		if (tc90522_read(demod, 0x80, &buf, 1))
+			return -EBADMSG;
+		retryov = buf & 0b10000000 ? true : false;
+		fulock  = buf & 0b00001000 ? true : false;
+		if (!fulock) {
+			b = true;
+			break;
+		} else {
+			if (retryov)
+				break;
+		}
+		msleep_interruptible(1);
+	}
+	return b ? 0 : -EBADMSG;
+}
+
+int tc90522_tune_t(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	int err, i;
+
+	if (re_tune)
+		demod->state = TC90522_SET_FREQUENCY;
+
+	switch (demod->state) {
+	case TC90522_IDLE:
+		*delay = msecs_to_jiffies(2000);
+		*status = 0;
+		return 0;
+
+	case TC90522_SET_FREQUENCY:
+		if (fe->ops.tuner_ops.set_frequency(fe, fe->dtv_property_cache.frequency)) {
+			*delay = msecs_to_jiffies(1000);
+			*status = 0;
+			return 0;
+		}
+		demod->state = TC90522_SET_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case TC90522_SET_MODULATION:
+		for (i = 0; i < 1000; i++) {
+			err = tc90522_get_tmcc_t(demod);
+			if (!err)
+				break;
+			msleep_interruptible(2);
+		}
+		if (err) {
+			pr_debug("#%d fail get_tmcc_t err=%d\n", demod->idx, err);
+				demod->state = TC90522_ABORT;
+				*delay = msecs_to_jiffies(1000);
+				return 0;
+		}
+		demod->state = TC90522_TRACK;
+		/* fallthrough */
+
+	case TC90522_TRACK:
+		*delay = msecs_to_jiffies(2000);
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+
+	case TC90522_ABORT:
+		*delay = msecs_to_jiffies(2000);
+		*status = FE_HAS_SIGNAL;
+		return 0;
+	}
+	return -ERANGE;
+}
+
+static struct dvb_frontend_ops tc90522_ops_t = {
+	.delsys = { SYS_ISDBT },
+	.info = {
+		.name = "TC90522 ISDB-T",
+		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO |
+			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
+	},
+	.init = tc90522_wakeup,
+	.sleep = tc90522_sleep,
+	.release = tc90522_release,
+	.write = tc90522_write,
+	.get_frontend_algo = tc90522_get_frontend_algo,
+	.read_snr = tc90522_read_snr,
+	.read_status = tc90522_read_status,
+	.tune = tc90522_tune_t,
+};
+
+/**** Common ****/
+struct dvb_frontend *tc90522_attach(struct i2c_adapter *i2c, u8 idx, fe_delivery_system_t type, u8 addr_demod, bool pwr_on)
+{
+	struct dvb_frontend *fe;
+	struct tc90522 *demod = kzalloc(sizeof(struct tc90522), GFP_KERNEL);
+	if (!demod)
+		return NULL;
+
+	demod->i2c	= i2c;
+	demod->idx	= idx;
+	demod->type	= type;
+	demod->addr_demod = addr_demod;
+	fe = &demod->fe;
+	memcpy(&fe->ops, (demod->type == SYS_ISDBS) ? &tc90522_ops_s : &tc90522_ops_t, sizeof(struct dvb_frontend_ops));
+	fe->demodulator_priv = demod;
+
+	if (pwr_on && (tc90522_set_powers(demod, TC90522_PWR_TUNER_ON)	||
+			i2c_transfer(demod->i2c, NULL, 0)		||
+			tc90522_set_powers(demod, TC90522_PWR_TUNER_ON | TC90522_PWR_AMP_ON))) {
+		tc90522_release(fe);
+		return NULL;
+	}
+	return fe;
+}
+EXPORT_SYMBOL(tc90522_attach);
+
diff --git a/drivers/media/dvb-frontends/tc90522.h b/drivers/media/dvb-frontends/tc90522.h
new file mode 100644
index 0000000..5b1cda1
--- /dev/null
+++ b/drivers/media/dvb-frontends/tc90522.h
@@ -0,0 +1,36 @@
+/*
+ * Earthsoft PT3 demodulator frontend Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S)
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__TC90522_H__
+#define	__TC90522_H__
+
+#ifndef pr_fmt
+ #define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+#endif
+#include "dvb_frontend.h"
+
+#if IS_ENABLED(CONFIG_DVB_TC90522)
+extern struct dvb_frontend *tc90522_attach(struct i2c_adapter *i2c, u8 idx, fe_delivery_system_t type, u8 addr_demod, bool pwr);
+#else
+static inline struct dvb_frontend *tc90522_attach(struct i2c_adapter *i2c, u8 idx, fe_delivery_system_t type, u8 addr_demod, bool pwr)
+{
+	pr_warn("%s: driver disabled by Kconfig\n", __func__);
+	return NULL;
+}
+#endif
+
+#endif
+
diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
index 53196f1..87018c8 100644
--- a/drivers/media/pci/Kconfig
+++ b/drivers/media/pci/Kconfig
@@ -30,7 +30,6 @@ source "drivers/media/pci/cx88/Kconfig"
 source "drivers/media/pci/bt8xx/Kconfig"
 source "drivers/media/pci/saa7134/Kconfig"
 source "drivers/media/pci/saa7164/Kconfig"
-
 endif
 
 if MEDIA_DIGITAL_TV_SUPPORT
@@ -40,6 +39,7 @@ source "drivers/media/pci/b2c2/Kconfig"
 source "drivers/media/pci/pluto2/Kconfig"
 source "drivers/media/pci/dm1105/Kconfig"
 source "drivers/media/pci/pt1/Kconfig"
+source "drivers/media/pci/pt3/Kconfig"
 source "drivers/media/pci/mantis/Kconfig"
 source "drivers/media/pci/ngene/Kconfig"
 source "drivers/media/pci/ddbridge/Kconfig"
diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
index 35cc578..f7be6bc 100644
--- a/drivers/media/pci/Makefile
+++ b/drivers/media/pci/Makefile
@@ -7,6 +7,7 @@ obj-y        +=	ttpci/		\
 		pluto2/		\
 		dm1105/		\
 		pt1/		\
+		pt3/		\
 		mantis/		\
 		ngene/		\
 		ddbridge/	\
diff --git a/drivers/media/pci/pt3/Kconfig b/drivers/media/pci/pt3/Kconfig
new file mode 100644
index 0000000..0d866a0
--- /dev/null
+++ b/drivers/media/pci/pt3/Kconfig
@@ -0,0 +1,11 @@
+config PT3_DVB
+	tristate "Earthsoft PT3 ISDB-S/T cards"
+	depends on DVB_CORE && PCI
+	select DVB_TC90522 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_QM1D1C0042 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MXL301RF if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Support for Earthsoft PT3 PCI-Express cards.
+	  You need to enable frontend (TC90522) & tuners (QM1D1C0042, MXL301RF)
+	  Say Y or M if you own such a device and want to use it.
+
diff --git a/drivers/media/pci/pt3/Makefile b/drivers/media/pci/pt3/Makefile
new file mode 100644
index 0000000..ede00e1
--- /dev/null
+++ b/drivers/media/pci/pt3/Makefile
@@ -0,0 +1,6 @@
+pt3_dvb-objs := pt3_pci.o pt3_dma.o pt3_i2c.o
+
+obj-$(CONFIG_PT3_DVB) += pt3_dvb.o
+
+ccflags-y += -Idrivers/media/dvb-core -Idrivers/media/dvb-frontends -Idrivers/media/tuners
+
diff --git a/drivers/media/pci/pt3/pt3_common.h b/drivers/media/pci/pt3/pt3_common.h
new file mode 100644
index 0000000..21b36f5
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_common.h
@@ -0,0 +1,86 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_COMMON_H__
+#define	__PT3_COMMON_H__
+
+#define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+
+#include <linux/pci.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include "dvb_demux.h"
+#include "dmxdev.h"
+#include "dvb_frontend.h"
+
+#define DRV_NAME "pt3_dvb"
+
+/* register idx */
+#define PT3_REG_VERSION	0x00	/*	R	Version		*/
+#define PT3_REG_BUS	0x04	/*	R	Bus		*/
+#define PT3_REG_SYS_W	0x08	/*	W	System		*/
+#define PT3_REG_SYS_R	0x0c	/*	R	System		*/
+#define PT3_REG_I2C_W	0x10	/*	W	I2C		*/
+#define PT3_REG_I2C_R	0x14	/*	R	I2C		*/
+#define PT3_REG_RAM_W	0x18	/*	W	RAM		*/
+#define PT3_REG_RAM_R	0x1c	/*	R	RAM		*/
+#define PT3_REG_BASE	0x40	/* + 0x18*idx			*/
+#define PT3_REG_DMA_D_L	0x00	/*	W	DMA descriptor	*/
+#define PT3_REG_DMA_D_H	0x04	/*	W	DMA descriptor	*/
+#define PT3_REG_DMA_CTL	0x08	/*	W	DMA		*/
+#define PT3_REG_TS_CTL	0x0c	/*	W	TS		*/
+#define PT3_REG_STATUS	0x10	/*	R	DMA/FIFO/TS	*/
+#define PT3_REG_TS_ERR	0x14	/*	R	TS		*/
+
+struct pt3_adapter;
+
+struct pt3_board {
+	struct mutex lock;
+	int lnb;
+	bool reset;
+
+	struct pci_dev *pdev;
+	int bars;
+	void __iomem *bar_reg, *bar_mem;
+	struct i2c_adapter i2c;
+	u8 i2c_buf;
+	u32 i2c_addr;
+	bool i2c_filled;
+
+	struct pt3_adapter **adap;
+};
+
+struct pt3_adapter {
+	struct mutex lock;
+	struct pt3_board *pt3;
+
+	u8 idx;
+	bool sleep;
+	struct pt3_dma *dma;
+	struct task_struct *kthread;
+	struct dvb_adapter dvb;
+	struct dvb_demux demux;
+	struct dmxdev dmxdev;
+	int users, voltage;
+
+	struct dvb_frontend *fe;
+	int (*orig_voltage)(struct dvb_frontend *fe, fe_sec_voltage_t voltage);
+	int (*orig_sleep)(struct dvb_frontend *fe);
+	int (*orig_init)(struct dvb_frontend *fe);
+};
+
+#endif
+
diff --git a/drivers/media/pci/pt3/pt3_dma.c b/drivers/media/pci/pt3/pt3_dma.c
new file mode 100644
index 0000000..a1fb82e
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_dma.c
@@ -0,0 +1,333 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_dma.h"
+
+#define PT3_DMA_MAX_DESCS	204
+#define PT3_DMA_PAGE_SIZE	(PT3_DMA_MAX_DESCS * sizeof(struct pt3_dma_desc))
+#define PT3_DMA_BLOCK_COUNT	17
+#define PT3_DMA_BLOCK_SIZE	(PT3_DMA_PAGE_SIZE * 47)
+#define PT3_DMA_TS_BUF_SIZE	(PT3_DMA_BLOCK_SIZE * PT3_DMA_BLOCK_COUNT)
+#define PT3_DMA_TS_SYNC		0x47
+#define PT3_DMA_TS_NOT_SYNC	0x74
+
+void pt3_dma_free(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *page;
+	u32 i;
+
+	if (dma->ts_info) {
+		for (i = 0; i < dma->ts_count; i++) {
+			page = &dma->ts_info[i];
+			if (page->data)
+				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
+		}
+		kfree(dma->ts_info);
+	}
+	if (dma->desc_info) {
+		for (i = 0; i < dma->desc_count; i++) {
+			page = &dma->desc_info[i];
+			if (page->data)
+				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
+		}
+		kfree(dma->desc_info);
+	}
+	kfree(dma);
+}
+
+struct pt3_dma_desc {
+	u64 page_addr;
+	u32 page_size;
+	u64 next_desc;
+} __packed;
+
+void pt3_dma_build_page_descriptor(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *desc_info, *ts_info;
+	u64 ts_addr, desc_addr;
+	u32 i, j, ts_size, desc_remain, ts_info_pos, desc_info_pos;
+	struct pt3_dma_desc *prev, *curr;
+
+	pr_debug("#%d %s ts_count=%d ts_size=%d desc_count=%d desc_size=%d\n",
+		dma->adap->idx, __func__, dma->ts_count, dma->ts_info[0].size, dma->desc_count, dma->desc_info[0].size);
+	desc_info_pos = ts_info_pos = 0;
+	desc_info = &dma->desc_info[desc_info_pos];
+	desc_addr   = desc_info->addr;
+	desc_remain = desc_info->size;
+	desc_info->data_pos = 0;
+	prev = NULL;
+	curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+	desc_info_pos++;
+
+	for (i = 0; i < dma->ts_count; i++) {
+		if (unlikely(ts_info_pos >= dma->ts_count)) {
+			pr_debug("#%d ts_info overflow max=%d curr=%d\n", dma->adap->idx, dma->ts_count, ts_info_pos);
+			return;
+		}
+		ts_info = &dma->ts_info[ts_info_pos];
+		ts_addr = ts_info->addr;
+		ts_size = ts_info->size;
+		ts_info_pos++;
+		pr_debug("#%d i=%d, ts_info addr=0x%llx ts_size=%d\n", dma->adap->idx, i, ts_addr, ts_size);
+		for (j = 0; j < ts_size / PT3_DMA_PAGE_SIZE; j++) {
+			if (desc_remain < sizeof(struct pt3_dma_desc)) {
+				if (unlikely(desc_info_pos >= dma->desc_count)) {
+					pr_debug("#%d desc_info overflow max=%d curr=%d\n",
+						dma->adap->idx, dma->desc_count, desc_info_pos);
+					return;
+				}
+				desc_info = &dma->desc_info[desc_info_pos];
+				desc_info->data_pos = 0;
+				curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+				pr_debug("#%d desc_info_pos=%d ts_addr=0x%llx remain=%d\n",
+					dma->adap->idx, desc_info_pos, ts_addr, desc_remain);
+				desc_addr = desc_info->addr;
+				desc_remain = desc_info->size;
+				desc_info_pos++;
+			}
+			if (prev)
+				prev->next_desc = desc_addr | 0b10;
+			curr->page_addr = ts_addr           | 0b111;
+			curr->page_size = PT3_DMA_PAGE_SIZE | 0b111;
+			curr->next_desc = 0b10;
+			pr_debug("#%d j=%d dma write desc ts_addr=0x%llx desc_info_pos=%d desc_remain=%d\n",
+				dma->adap->idx, j, ts_addr, desc_info_pos, desc_remain);
+			ts_addr += PT3_DMA_PAGE_SIZE;
+
+			prev = curr;
+			desc_info->data_pos += sizeof(struct pt3_dma_desc);
+			if (unlikely(desc_info->data_pos > desc_info->size)) {
+				pr_debug("#%d dma desc_info data overflow max=%d curr=%d\n",
+					dma->adap->idx, desc_info->size, desc_info->data_pos);
+				return;
+			}
+			curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+			desc_addr += sizeof(struct pt3_dma_desc);
+			desc_remain -= sizeof(struct pt3_dma_desc);
+		}
+	}
+	if (prev)
+		prev->next_desc = dma->desc_info->addr | 0b10;
+}
+
+struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap)
+{
+	struct pt3_dma_page *page;
+	u32 i;
+
+	struct pt3_dma *dma = kzalloc(sizeof(struct pt3_dma), GFP_KERNEL);
+	if (!dma) {
+		pr_debug("#%d fail allocate PT3_DMA\n", adap->idx);
+		goto fail;
+	}
+	dma->adap = adap;
+	dma->enabled = false;
+	mutex_init(&dma->lock);
+
+	dma->ts_count = PT3_DMA_BLOCK_COUNT;
+	dma->ts_info = kzalloc(sizeof(struct pt3_dma_page) * dma->ts_count, GFP_KERNEL);
+	if (!dma->ts_info) {
+		pr_debug("#%d fail allocate TS DMA page\n", adap->idx);
+		goto fail;
+	}
+	pr_debug("#%d Alloc TS buf (ts_count %d)\n", adap->idx, dma->ts_count);
+	for (i = 0; i < dma->ts_count; i++) {
+		page = &dma->ts_info[i];
+		page->size = PT3_DMA_BLOCK_SIZE;
+		page->data_pos = 0;
+		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
+		if (!page->data) {
+			pr_debug("#%d fail alloc_consistent. %d\n", adap->idx, i);
+			goto fail;
+		}
+	}
+
+	dma->desc_count = 1 + (PT3_DMA_TS_BUF_SIZE / PT3_DMA_PAGE_SIZE - 1) / PT3_DMA_MAX_DESCS;
+	dma->desc_info = kzalloc(sizeof(struct pt3_dma_page) * dma->desc_count, GFP_KERNEL);
+	if (!dma->desc_info) {
+		pr_debug("#%d fail allocate Desc DMA page\n", adap->idx);
+		goto fail;
+	}
+	pr_debug("#%d Alloc Descriptor buf (desc_count %d)\n", adap->idx, dma->desc_count);
+	for (i = 0; i < dma->desc_count; i++) {
+		page = &dma->desc_info[i];
+		page->size = PT3_DMA_PAGE_SIZE;
+		page->data_pos = 0;
+		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
+		if (!page->data) {
+			pr_debug("#%d fail alloc_consistent %d\n", adap->idx, i);
+			goto fail;
+		}
+	}
+
+	pr_debug("#%d build page descriptor\n", adap->idx);
+	pt3_dma_build_page_descriptor(dma);
+	return dma;
+fail:
+	if (dma)
+		pt3_dma_free(dma);
+	return NULL;
+}
+
+void __iomem *pt3_dma_get_base_addr(struct pt3_dma *dma)
+{
+	return dma->adap->pt3->bar_reg + PT3_REG_BASE + (0x18 * dma->adap->idx);
+}
+
+void pt3_dma_reset(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *ts;
+	u32 i;
+
+	for (i = 0; i < dma->ts_count; i++) {
+		ts = &dma->ts_info[i];
+		memset(ts->data, 0, ts->size);
+		ts->data_pos = 0;
+		*ts->data = PT3_DMA_TS_NOT_SYNC;
+	}
+	dma->ts_pos = 0;
+}
+
+void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled)
+{
+	void __iomem *base = pt3_dma_get_base_addr(dma);
+	u64 start_addr = dma->desc_info->addr;
+
+	if (enabled) {
+		pr_debug("#%d DMA enable start_addr=%llx\n", dma->adap->idx, start_addr);
+		pt3_dma_reset(dma);
+		writel(1 << 1, base + PT3_REG_DMA_CTL);	/* stop DMA */
+		writel(start_addr         & 0xffffffff, base + PT3_REG_DMA_D_L);
+		writel((start_addr >> 32) & 0xffffffff, base + PT3_REG_DMA_D_H);
+		pr_debug("set descriptor address low %llx\n",  start_addr         & 0xffffffff);
+		pr_debug("set descriptor address high %llx\n", (start_addr >> 32) & 0xffffffff);
+		writel(1 << 0, base + PT3_REG_DMA_CTL);	/* start DMA */
+	} else {
+		pr_debug("#%d DMA disable\n", dma->adap->idx);
+		writel(1 << 1, base + PT3_REG_DMA_CTL);	/* stop DMA */
+		while (1) {
+			if (!(readl(base + PT3_REG_STATUS) & 1))
+				break;
+			msleep_interruptible(1);
+		}
+	}
+	dma->enabled = enabled;
+}
+
+/* convert Gray code to binary, e.g. 1001 -> 1110 */
+static u32 pt3_dma_gray2binary(u32 gray, u32 bit)
+{
+	u32 binary = 0, i, j, k;
+
+	for (i = 0; i < bit; i++) {
+		k = 0;
+		for (j = i; j < bit; j++)
+			k ^= (gray >> j) & 1;
+		binary |= k << i;
+	}
+	return binary;
+}
+
+u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma)
+{
+	return pt3_dma_gray2binary(readl(pt3_dma_get_base_addr(dma) + PT3_REG_TS_ERR), 32);
+}
+
+void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval)
+{
+	void __iomem *base = pt3_dma_get_base_addr(dma);
+	u32 data = mode | initval;
+	pr_debug("#%d %s base=%p data=0x%04x\n", dma->adap->idx, __func__, base, data);
+	writel(data, base + PT3_REG_TS_CTL);
+}
+
+bool pt3_dma_ready(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *ts;
+	u8 *p;
+
+	u32 next = dma->ts_pos + 1;
+	if (next >= dma->ts_count)
+		next = 0;
+	ts = &dma->ts_info[next];
+	p = &ts->data[ts->data_pos];
+
+	if (*p == PT3_DMA_TS_SYNC)
+		return true;
+	if (*p == PT3_DMA_TS_NOT_SYNC)
+		return false;
+
+	pr_debug("#%d invalid sync byte value=0x%02x ts_pos=%d data_pos=%d curr=0x%02x\n",
+		dma->adap->idx, *p, next, ts->data_pos, dma->ts_info[dma->ts_pos].data[0]);
+	return false;
+}
+
+ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux)
+{
+	bool ready;
+	struct pt3_dma_page *ts;
+	u32 i, prev;
+	size_t csize, remain = dma->ts_info[dma->ts_pos].size;
+
+	mutex_lock(&dma->lock);
+	pr_debug("#%d dma_copy ts_pos=0x%x data_pos=0x%x\n",
+		   dma->adap->idx, dma->ts_pos, dma->ts_info[dma->ts_pos].data_pos);
+	for (;;) {
+		for (i = 0; i < 20; i++) {
+			ready = pt3_dma_ready(dma);
+			if (ready)
+				break;
+			msleep_interruptible(30);
+		}
+		if (!ready) {
+			pr_debug("#%d dma_copy NOT READY\n", dma->adap->idx);
+			goto last;
+		}
+		prev = dma->ts_pos - 1;
+		if (prev < 0 || dma->ts_count <= prev)
+			prev = dma->ts_count - 1;
+		if (dma->ts_info[prev].data[0] != PT3_DMA_TS_NOT_SYNC)
+			pr_debug("#%d DMA buffer overflow. prev=%d data=0x%x\n",
+					dma->adap->idx, prev, dma->ts_info[prev].data[0]);
+		ts = &dma->ts_info[dma->ts_pos];
+		for (;;) {
+			csize = (remain < (ts->size - ts->data_pos)) ?
+				 remain : (ts->size - ts->data_pos);
+			dvb_dmx_swfilter(demux, &ts->data[ts->data_pos], csize);
+			remain -= csize;
+			ts->data_pos += csize;
+			if (ts->data_pos >= ts->size) {
+				ts->data_pos = 0;
+				ts->data[ts->data_pos] = PT3_DMA_TS_NOT_SYNC;
+				dma->ts_pos++;
+				if (dma->ts_pos >= dma->ts_count)
+					dma->ts_pos = 0;
+				break;
+			}
+			if (remain <= 0)
+				goto last;
+		}
+	}
+last:
+	mutex_unlock(&dma->lock);
+	return dma->ts_info[dma->ts_pos].size - remain;
+}
+
+u32 pt3_dma_get_status(struct pt3_dma *dma)
+{
+	return readl(pt3_dma_get_base_addr(dma) + PT3_REG_STATUS);
+}
+
diff --git a/drivers/media/pci/pt3/pt3_dma.h b/drivers/media/pci/pt3/pt3_dma.h
new file mode 100644
index 0000000..934c222
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_dma.h
@@ -0,0 +1,50 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_DMA_H__
+#define	__PT3_DMA_H__
+
+#include "pt3_common.h"
+
+struct pt3_dma_page {
+	dma_addr_t addr;
+	u8 *data;
+	u32 size, data_pos;
+};
+
+enum pt3_dma_mode {
+	USE_LFSR = 1 << 16,
+	REVERSE  = 1 << 17,
+	RESET    = 1 << 18,
+};
+
+struct pt3_dma {
+	struct pt3_adapter *adap;
+	bool enabled;
+	u32 ts_pos, ts_count, desc_count;
+	struct pt3_dma_page *ts_info, *desc_info;
+	struct mutex lock;
+};
+
+ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux);
+struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap);
+void pt3_dma_free(struct pt3_dma *dma);
+u32 pt3_dma_get_status(struct pt3_dma *dma);
+u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma);
+void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled);
+void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval);
+
+#endif
diff --git a/drivers/media/pci/pt3/pt3_i2c.c b/drivers/media/pci/pt3/pt3_i2c.c
new file mode 100644
index 0000000..e872ae1
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_i2c.c
@@ -0,0 +1,189 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_i2c.h"
+
+#define PT3_I2C_DATA_OFFSET	2048
+#define PT3_I2C_START_ADDR	0x17fa
+
+enum pt3_i2c_cmd {
+	I_END,
+	I_ADDRESS,
+	I_CLOCK_L,
+	I_CLOCK_H,
+	I_DATA_L,
+	I_DATA_H,
+	I_RESET,
+	I_SLEEP,
+	I_DATA_L_NOP  = 0x08,
+	I_DATA_H_NOP  = 0x0c,
+	I_DATA_H_READ = 0x0d,
+	I_DATA_H_ACK0 = 0x0e,
+	I_DATA_H_ACK1 = 0x0f,
+};
+
+bool pt3_i2c_is_clean(struct pt3_board *pt3)
+{
+	return (readl(pt3->bar_reg + PT3_REG_I2C_R) >> 3) & 1;
+}
+
+void pt3_i2c_reset(struct pt3_board *pt3)
+{
+	writel(1 << 17, pt3->bar_reg + PT3_REG_I2C_W);	/* 0x00020000 */
+}
+
+void pt3_i2c_wait(struct pt3_board *pt3, u32 *status)
+{
+	u32 val;
+
+	while (1) {
+		val = readl(pt3->bar_reg + PT3_REG_I2C_R);
+		if (!(val & 1))				/* sequence stopped */
+			break;
+		msleep_interruptible(1);
+	}
+	if (status)
+		*status = val;				/* I2C register status */
+}
+
+void pt3_i2c_mem_write(struct pt3_board *pt3, u8 data)
+{
+	void __iomem *dst = pt3->bar_mem + PT3_I2C_DATA_OFFSET + pt3->i2c_addr;
+
+	if (pt3->i2c_filled) {
+		pt3->i2c_buf |= data << 4;
+		writeb(pt3->i2c_buf, dst);
+		pt3->i2c_addr++;
+	} else
+		pt3->i2c_buf = data;
+	pt3->i2c_filled ^= true;
+}
+
+void pt3_i2c_start(struct pt3_board *pt3)
+{
+	pt3_i2c_mem_write(pt3, I_DATA_H);
+	pt3_i2c_mem_write(pt3, I_CLOCK_H);
+	pt3_i2c_mem_write(pt3, I_DATA_L);
+	pt3_i2c_mem_write(pt3, I_CLOCK_L);
+}
+
+void pt3_i2c_cmd_write(struct pt3_board *pt3, const u8 *data, u32 size)
+{
+	u32 i, j;
+	u8 byte;
+
+	for (i = 0; i < size; i++) {
+		byte = data[i];
+		for (j = 0; j < 8; j++)
+			pt3_i2c_mem_write(pt3, (byte >> (7 - j)) & 1 ? I_DATA_H_NOP : I_DATA_L_NOP);
+		pt3_i2c_mem_write(pt3, I_DATA_H_ACK0);
+	}
+}
+
+void pt3_i2c_cmd_read(struct pt3_board *pt3, u8 *data, u32 size)
+{
+	u32 i, j;
+
+	for (i = 0; i < size; i++) {
+		for (j = 0; j < 8; j++)
+			pt3_i2c_mem_write(pt3, I_DATA_H_READ);
+		if (i == (size - 1))
+			pt3_i2c_mem_write(pt3, I_DATA_H_NOP);
+		else
+			pt3_i2c_mem_write(pt3, I_DATA_L_NOP);
+	}
+}
+
+void pt3_i2c_stop(struct pt3_board *pt3)
+{
+	pt3_i2c_mem_write(pt3, I_DATA_L);
+	pt3_i2c_mem_write(pt3, I_CLOCK_H);
+	pt3_i2c_mem_write(pt3, I_DATA_H);
+}
+
+int pt3_i2c_flush(struct pt3_board *pt3, bool end, u32 start_addr)
+{
+	u32 status;
+
+	if (end) {
+		pt3_i2c_mem_write(pt3, I_END);
+		if (pt3->i2c_filled)
+			pt3_i2c_mem_write(pt3, I_END);
+	}
+	pt3_i2c_wait(pt3, &status);
+	writel(1 << 16 | start_addr, pt3->bar_reg + PT3_REG_I2C_W);	/* 0x00010000 start sequence */
+	pt3_i2c_wait(pt3, &status);
+	if (status & 0b0110) {						/* ACK status */
+		pr_err("%s failed, status=0x%x\n", __func__, status);
+		return -EIO;
+	}
+	return 0;
+}
+
+u32 pt3_i2c_func(struct i2c_adapter *i2c)
+{
+	return I2C_FUNC_I2C;
+}
+
+int pt3_i2c_xfer(struct i2c_adapter *i2c, struct i2c_msg *msg, int num)
+{
+	struct pt3_board *pt3 = i2c_get_adapdata(i2c);
+	int i, j;
+
+	if (!num)
+		return pt3_i2c_flush(pt3, false, PT3_I2C_START_ADDR);
+	if ((num < 1) || (num > 3) || !msg || msg[0].flags)	/* always write first */
+		return -ENOTSUPP;
+	mutex_lock(&pt3->lock);
+	pt3->i2c_addr = 0;
+	for (i = 0; i < num; i++) {
+		u8 byte = (msg[i].addr << 1) | (msg[i].flags & 1);
+		pt3_i2c_start(pt3);
+		pt3_i2c_cmd_write(pt3, &byte, 1);
+		if (msg[i].flags == I2C_M_RD)
+			pt3_i2c_cmd_read(pt3, msg[i].buf, msg[i].len);
+		else
+			pt3_i2c_cmd_write(pt3, msg[i].buf, msg[i].len);
+	}
+	pt3_i2c_stop(pt3);
+	if (pt3_i2c_flush(pt3, true, 0))
+		num = -EIO;
+	else
+		for (i = 1; i < num; i++)
+			if (msg[i].flags == I2C_M_RD)
+				for (j = 0; j < msg[i].len; j++)
+					msg[i].buf[j] = readb(pt3->bar_mem + PT3_I2C_DATA_OFFSET + j);
+	mutex_unlock(&pt3->lock);
+	return num;
+}
+
+static const struct i2c_algorithm pt3_i2c_algo = {
+	.functionality = pt3_i2c_func,
+	.master_xfer = pt3_i2c_xfer,
+};
+
+int pt3_i2c_add_adapter(struct pt3_board *pt3)
+{
+	struct i2c_adapter *i2c = &pt3->i2c;
+	i2c->algo = &pt3_i2c_algo;
+	i2c->algo_data = NULL;
+	i2c->dev.parent = &pt3->pdev->dev;
+	strcpy(i2c->name, DRV_NAME);
+	i2c_set_adapdata(i2c, pt3);
+	return	i2c_add_adapter(i2c) ||
+		(!pt3_i2c_is_clean(pt3) && pt3_i2c_flush(pt3, false, 0));
+}
+
diff --git a/drivers/media/pci/pt3/pt3_i2c.h b/drivers/media/pci/pt3/pt3_i2c.h
new file mode 100644
index 0000000..8424fd5
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_i2c.h
@@ -0,0 +1,25 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_I2C_H__
+#define	__PT3_I2C_H__
+
+#include "pt3_common.h"
+
+void pt3_i2c_reset(struct pt3_board *pt3);
+int pt3_i2c_add_adapter(struct pt3_board *pt3);
+
+#endif
diff --git a/drivers/media/pci/pt3/pt3_pci.c b/drivers/media/pci/pt3/pt3_pci.c
new file mode 100644
index 0000000..114beb3
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_pci.c
@@ -0,0 +1,396 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCIE bridge Altera Cyclone IV FPGA EP4CGX15BF14C8N
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_dma.h"
+#include "pt3_i2c.h"
+#include "tc90522.h"
+#include "qm1d1c0042.h"
+#include "mxl301rf.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 DVB Driver");
+MODULE_LICENSE("GPL");
+
+static struct pci_device_id pt3_id_table[] = {
+	{ PCI_DEVICE(0x1172, 0x4c15) },
+	{ },
+};
+MODULE_DEVICE_TABLE(pci, pt3_id_table);
+
+static int lnb = 2;
+module_param(lnb, int, 0);
+MODULE_PARM_DESC(lnb, "LNB level (0:OFF 1:+11V 2:+15V)");
+
+struct pt3_lnb {
+	u32 bits;
+	char *str;
+};
+
+static const struct pt3_lnb pt3_lnb[] = {
+	{0b1100,  "0V"},
+	{0b1101, "11V"},
+	{0b1111, "15V"},
+};
+
+struct pt3_cfg {
+	fe_delivery_system_t type;
+	u8 addr_tuner, addr_demod;
+};
+
+static const struct pt3_cfg pt3_cfg[] = {
+	{SYS_ISDBS, 0x63, 0b00010001},
+	{SYS_ISDBS, 0x60, 0b00010011},
+	{SYS_ISDBT, 0x62, 0b00010000},
+	{SYS_ISDBT, 0x61, 0b00010010},
+};
+#define PT3_ADAPN ARRAY_SIZE(pt3_cfg)
+
+int pt3_update_lnb(struct pt3_board *pt3)
+{
+	u8 i, lnb_eff = 0;
+
+	if (pt3->reset) {
+		writel(pt3_lnb[0].bits, pt3->bar_reg + PT3_REG_SYS_W);
+		pt3->reset = false;
+		pt3->lnb = 0;
+	} else {
+		struct pt3_adapter *adap;
+		for (i = 0; i < PT3_ADAPN; i++) {
+			adap = pt3->adap[i];
+			pr_debug("#%d sleep %d\n", adap->idx, adap->sleep);
+			if ((pt3_cfg[i].type == SYS_ISDBS) && (!adap->sleep))
+				lnb_eff |= adap->voltage ? adap->voltage : lnb;
+		}
+		if (unlikely(lnb_eff < 0 || 2 < lnb_eff)) {
+			pr_err("Inconsistent LNB settings\n");
+			return -EINVAL;
+		}
+		if (pt3->lnb != lnb_eff) {
+			writel(pt3_lnb[lnb_eff].bits, pt3->bar_reg + PT3_REG_SYS_W);
+			pt3->lnb = lnb_eff;
+		}
+	}
+	pr_debug("LNB=%s\n", pt3_lnb[lnb_eff].str);
+	return 0;
+}
+
+int pt3_thread(void *data)
+{
+	size_t ret;
+	struct pt3_adapter *adap = data;
+
+	pr_debug("#%d %s sleep %d\n", adap->idx, __func__, adap->sleep);
+	set_freezable();
+	while (!kthread_should_stop()) {
+		try_to_freeze();
+		while ((ret = pt3_dma_copy(adap->dma, &adap->demux)) > 0)
+			;
+		if (ret < 0) {
+			pr_debug("#%d fail dma_copy\n", adap->idx);
+			msleep_interruptible(1);
+		}
+	}
+	return 0;
+}
+
+int pt3_start_feed(struct dvb_demux_feed *feed)
+{
+	int ret = 0;
+	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
+	pr_debug("#%d %s sleep %d\n", adap->idx, __func__, adap->sleep);
+	if (!adap->users++) {
+		pr_debug("#%d %s selected, DMA %s\n",
+			adap->idx, (pt3_cfg[adap->idx].type == SYS_ISDBS) ? "S" : "T",
+			pt3_dma_get_status(adap->dma) & 1 ? "ON" : "OFF");
+		mutex_lock(&adap->lock);
+		if (!adap->kthread) {
+			adap->kthread = kthread_run(pt3_thread, adap, DRV_NAME "_%d", adap->idx);
+			if (IS_ERR(adap->kthread)) {
+				ret = PTR_ERR(adap->kthread);
+				adap->kthread = NULL;
+			} else {
+				pt3_dma_set_test_mode(adap->dma, RESET, 0);	/* reset error count */
+				pt3_dma_set_enabled(adap->dma, true);
+			}
+		}
+		mutex_unlock(&adap->lock);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+int pt3_stop_feed(struct dvb_demux_feed *feed)
+{
+	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
+	pr_debug("#%d %s sleep %d\n", adap->idx, __func__, adap->sleep);
+	if (!--adap->users) {
+		mutex_lock(&adap->lock);
+		if (adap->kthread) {
+			pt3_dma_set_enabled(adap->dma, false);
+			pr_debug("#%d DMA ts_err packet cnt %d\n",
+				adap->idx, pt3_dma_get_ts_error_packet_count(adap->dma));
+			kthread_stop(adap->kthread);
+			adap->kthread = NULL;
+		}
+		mutex_unlock(&adap->lock);
+		msleep_interruptible(40);
+	}
+	return 0;
+}
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+struct pt3_adapter *pt3_dvb_register_adapter(struct pt3_board *pt3)
+{
+	int ret;
+	struct dvb_adapter *dvb;
+	struct dvb_demux *demux;
+	struct dmxdev *dmxdev;
+	struct pt3_adapter *adap = kzalloc(sizeof(struct pt3_adapter), GFP_KERNEL);
+	if (!adap)
+		return ERR_PTR(-ENOMEM);
+
+	adap->pt3 = pt3;
+	adap->sleep = true;
+
+	dvb = &adap->dvb;
+	dvb->priv = adap;
+	ret = dvb_register_adapter(dvb, DRV_NAME, THIS_MODULE, &pt3->pdev->dev, adapter_nr);
+	pr_debug("adapter%d registered\n", ret);
+	if (ret >= 0) {
+		demux = &adap->demux;
+		demux->dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING;
+		demux->priv = adap;
+		demux->feednum = 256;
+		demux->filternum = 256;
+		demux->start_feed = pt3_start_feed;
+		demux->stop_feed = pt3_stop_feed;
+		demux->write_to_decoder = NULL;
+		ret = dvb_dmx_init(demux);
+		if (ret >= 0) {
+			dmxdev = &adap->dmxdev;
+			dmxdev->filternum = 256;
+			dmxdev->demux = &demux->dmx;
+			dmxdev->capabilities = 0;
+			ret = dvb_dmxdev_init(dmxdev, dvb);
+			if (ret >= 0)
+				return adap;
+			dvb_dmx_release(demux);
+		}
+		dvb_unregister_adapter(dvb);
+	}
+	kfree(adap);
+	return ERR_PTR(ret);
+}
+
+int pt3_sleep(struct dvb_frontend *fe)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	pr_debug("#%d %s orig %p\n", adap->idx, __func__, adap->orig_sleep);
+	adap->sleep = true;
+	pt3_update_lnb(adap->pt3);
+	return (adap->orig_sleep) ? adap->orig_sleep(fe) : 0;
+}
+
+int pt3_wakeup(struct dvb_frontend *fe)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	pr_debug("#%d %s orig %p\n", adap->idx, __func__, adap->orig_init);
+	adap->sleep = false;
+	pt3_update_lnb(adap->pt3);
+	return (adap->orig_init) ? adap->orig_init(fe) : 0;
+}
+
+int pt3_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	adap->voltage = voltage == SEC_VOLTAGE_18 ? 2 : voltage == SEC_VOLTAGE_13 ? 1 : 0;
+	return (adap->orig_voltage) ? adap->orig_voltage(fe, voltage) : 0;
+}
+
+void pt3_cleanup_adapter(struct pt3_adapter *adap)
+{
+	if (!adap)
+		return;
+	if (adap->kthread)
+		kthread_stop(adap->kthread);
+	if (adap->fe) {
+		dvb_unregister_frontend(adap->fe);
+		adap->fe->ops.release(adap->fe);
+	}
+	if (adap->dma) {
+		if (adap->dma->enabled)
+			pt3_dma_set_enabled(adap->dma, false);
+		pt3_dma_free(adap->dma);
+	}
+	adap->demux.dmx.close(&adap->demux.dmx);
+	dvb_dmxdev_release(&adap->dmxdev);
+	dvb_dmx_release(&adap->demux);
+	dvb_unregister_adapter(&adap->dvb);
+	kfree(adap);
+}
+
+void pt3_remove(struct pci_dev *pdev)
+{
+	int i;
+	struct pt3_board *pt3 = pci_get_drvdata(pdev);
+
+	if (pt3) {
+		pt3->reset = true;
+		pt3_update_lnb(pt3);
+		for (i = 0; i < PT3_ADAPN; i++)
+			pt3_cleanup_adapter(pt3->adap[i]);
+		pt3_i2c_reset(pt3);
+		i2c_del_adapter(&pt3->i2c);
+		if (pt3->bar_mem)
+			iounmap(pt3->bar_mem);
+		if (pt3->bar_reg)
+			iounmap(pt3->bar_reg);
+		pci_release_selected_regions(pdev, pt3->bars);
+		kfree(pt3->adap);
+		kfree(pt3);
+	}
+	pci_disable_device(pdev);
+}
+
+int pt3_abort(struct pci_dev *pdev, int ret, char *fmt, ...)
+{
+	va_list ap;
+	char *s = NULL;
+	int slen;
+
+	va_start(ap, fmt);
+	slen = vsnprintf(s, 0, fmt, ap);
+	s = vzalloc(slen);
+	if (slen > 0 && s) {
+		vsnprintf(s, slen, fmt, ap);
+		dev_err(&pdev->dev, "%s", s);
+		vfree(s);
+	}
+	va_end(ap);
+	pt3_remove(pdev);
+	return ret;
+}
+
+int pt3_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+	struct pt3_board *pt3;
+	struct pt3_adapter *adap;
+	const struct pt3_cfg *cfg = pt3_cfg;
+	struct dvb_frontend *fe[PT3_ADAPN];
+	int i, err, bars = pci_select_bars(pdev, IORESOURCE_MEM);
+
+	err = pci_enable_device(pdev)					||
+		pci_set_dma_mask(pdev, DMA_BIT_MASK(64))		||
+		pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64))	||
+		pci_read_config_dword(pdev, PCI_CLASS_REVISION, &i)	||
+		pci_request_selected_regions(pdev, bars, DRV_NAME);
+	if (err)
+		return pt3_abort(pdev, err, "PCI/DMA error\n");
+	if ((i & 0xFF) != 1)
+		return pt3_abort(pdev, -EINVAL, "Revision 0x%x is not supported\n", i & 0xFF);
+
+	pci_set_master(pdev);
+	err = pci_save_state(pdev);
+	if (err)
+		return pt3_abort(pdev, err, "Failed pci_save_state\n");
+	pt3 = kzalloc(sizeof(struct pt3_board), GFP_KERNEL);
+	if (!pt3)
+		return pt3_abort(pdev, -ENOMEM, "struct pt3_board out of memory\n");
+	pt3->adap = kzalloc(PT3_ADAPN * sizeof(struct pt3_adapter *), GFP_KERNEL);
+	if (!pt3->adap)
+		return pt3_abort(pdev, -ENOMEM, "No memory for *adap\n");
+
+	pt3->bars = bars;
+	pt3->pdev = pdev;
+	pci_set_drvdata(pdev, pt3);
+	pt3->bar_reg = pci_ioremap_bar(pdev, 0);
+	pt3->bar_mem = pci_ioremap_bar(pdev, 2);
+	if (!pt3->bar_reg || !pt3->bar_mem)
+		return pt3_abort(pdev, -EIO, "Failed pci_ioremap_bar\n");
+
+	err = readl(pt3->bar_reg + PT3_REG_VERSION);
+	i = ((err >> 24) & 0xFF);
+	if (i != 3)
+		return pt3_abort(pdev, -EIO, "ID=0x%x, not a PT3\n", i);
+	i = ((err >>  8) & 0xFF);
+	if (i != 4)
+		return pt3_abort(pdev, -EIO, "FPGA version 0x%x is not supported\n", i);
+	err = pt3_i2c_add_adapter(pt3);
+	if (err < 0)
+		return pt3_abort(pdev, err, "Cannot add I2C\n");
+	mutex_init(&pt3->lock);
+
+	for (i = 0; i < PT3_ADAPN; i++) {
+		adap = pt3_dvb_register_adapter(pt3);
+		if (IS_ERR(adap))
+			return pt3_abort(pdev, PTR_ERR(adap), "Failed pt3_dvb_register_adapter\n");
+		adap->idx = i;
+		adap->dma = pt3_dma_create(adap);
+		if (!adap->dma)
+			return pt3_abort(pdev, -ENOMEM, "Failed pt3_dma_create\n");
+		pt3->adap[i] = adap;
+		adap->sleep = true;
+		mutex_init(&adap->lock);
+	}
+
+	for (i = 0; i < PT3_ADAPN; i++) {
+		fe[i] = tc90522_attach(&pt3->i2c, i, cfg[i].type, cfg[i].addr_demod, i + 1 == PT3_ADAPN);
+		if (!fe[i] || (cfg[i].type == SYS_ISDBS ?
+			qm1d1c0042_attach(fe[i], i, cfg[i].addr_tuner) : mxl301rf_attach(fe[i], i, cfg[i].addr_tuner))) {
+			while (i--)
+				fe[i]->ops.release(fe[i]);
+			return pt3_abort(pdev, -ENOMEM, "Cannot attach frontend\n");
+		}
+	}
+
+	for (i = 0; i < PT3_ADAPN; i++) {
+		struct pt3_adapter *adap = pt3->adap[i];
+		pr_debug("#%d %s\n", i, __func__);
+
+		adap->orig_voltage     = fe[i]->ops.set_voltage;
+		adap->orig_sleep       = fe[i]->ops.sleep;
+		adap->orig_init        = fe[i]->ops.init;
+		fe[i]->ops.set_voltage = pt3_set_voltage;
+		fe[i]->ops.sleep       = pt3_sleep;
+		fe[i]->ops.init        = pt3_wakeup;
+		if ((adap->orig_init(fe[i]) && adap->orig_init(fe[i]) && adap->orig_init(fe[i])) ||
+			adap->orig_sleep(fe[i]) || dvb_register_frontend(&adap->dvb, fe[i])) {
+			while (i--)
+				dvb_unregister_frontend(fe[i]);
+			for (i = 0; i < PT3_ADAPN; i++) {
+				fe[i]->ops.release(fe[i]);
+				adap->fe = NULL;
+			}
+			return pt3_abort(pdev, -EREMOTEIO, "Cannot register frontend\n");
+		}
+		adap->fe = fe[i];
+	}
+	pt3->reset = true;
+	pt3_update_lnb(pt3);
+	return 0;
+}
+
+static struct pci_driver pt3_driver = {
+	.name		= DRV_NAME,
+	.probe		= pt3_probe,
+	.remove		= pt3_remove,
+	.id_table	= pt3_id_table,
+};
+
+module_pci_driver(pt3_driver);
+
diff --git a/drivers/media/tuners/Kconfig b/drivers/media/tuners/Kconfig
index 22b6b8b..b7b993e 100644
--- a/drivers/media/tuners/Kconfig
+++ b/drivers/media/tuners/Kconfig
@@ -250,4 +250,18 @@ config MEDIA_TUNER_R820T
 	default m if !MEDIA_SUBDRV_AUTOSELECT
 	help
 	  Rafael Micro R820T silicon tuner driver.
+
+config MEDIA_TUNER_MXL301RF
+	tristate "MaxLinear MXL301RF ISDB-T tuner"
+	depends on MEDIA_SUPPORT && I2C
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  MaxLinear MxL301RF ISDB-T tuner driver for PT3.
+
+config MEDIA_TUNER_QM1D1C0042
+	tristate "Sharp QM1D1C0042 ISDB-S tuner"
+	depends on MEDIA_SUPPORT && I2C
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Sharp QM1D1C0042 ISDB-S tuner driver for PT3.
 endmenu
diff --git a/drivers/media/tuners/Makefile b/drivers/media/tuners/Makefile
index a6ff0c6..42c0226 100644
--- a/drivers/media/tuners/Makefile
+++ b/drivers/media/tuners/Makefile
@@ -38,6 +38,8 @@ obj-$(CONFIG_MEDIA_TUNER_FC0012) += fc0012.o
 obj-$(CONFIG_MEDIA_TUNER_FC0013) += fc0013.o
 obj-$(CONFIG_MEDIA_TUNER_IT913X) += tuner_it913x.o
 obj-$(CONFIG_MEDIA_TUNER_R820T) += r820t.o
+obj-$(CONFIG_MEDIA_TUNER_MXL301RF) += mxl301rf.o
+obj-$(CONFIG_MEDIA_TUNER_QM1D1C0042) += qm1d1c0042.o
 
 ccflags-y += -I$(srctree)/drivers/media/dvb-core
 ccflags-y += -I$(srctree)/drivers/media/dvb-frontends
diff --git a/drivers/media/tuners/mxl301rf.c b/drivers/media/tuners/mxl301rf.c
new file mode 100644
index 0000000..cc60831
--- /dev/null
+++ b/drivers/media/tuners/mxl301rf.c
@@ -0,0 +1,390 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-T tuner MaxLinear CMOS Hybrid TV MxL301RF
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "mxl301rf.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 MxL301RF MaxLinear CMOS Hybrid TV ISDB-T tuner driver");
+MODULE_LICENSE("GPL");
+
+struct mxl301rf {
+	struct dvb_frontend *fe;
+	u8 addr_tuner, idx;
+	u32 freq;
+};
+
+struct shf_dvbt {
+	u32	freq,		/* Channel center frequency @ kHz	*/
+		freq_th;	/* Offset frequency threshold @ kHz	*/
+	u8	shf_val,	/* Spur shift value			*/
+		shf_dir;	/* Spur shift direction			*/
+};
+
+static const struct shf_dvbt shf_dvbt_tab[] = {
+	{  64500, 500, 0x92, 0x07 },
+	{ 191500, 300, 0xe2, 0x07 },
+	{ 205500, 500, 0x2c, 0x04 },
+	{ 212500, 500, 0x1e, 0x04 },
+	{ 226500, 500, 0xd4, 0x07 },
+	{  99143, 500, 0x9c, 0x07 },
+	{ 173143, 500, 0xd4, 0x07 },
+	{ 191143, 300, 0xd4, 0x07 },
+	{ 207143, 500, 0xce, 0x07 },
+	{ 225143, 500, 0xce, 0x07 },
+	{ 243143, 500, 0xd4, 0x07 },
+	{ 261143, 500, 0xd4, 0x07 },
+	{ 291143, 500, 0xd4, 0x07 },
+	{ 339143, 500, 0x2c, 0x04 },
+	{ 117143, 500, 0x7a, 0x07 },
+	{ 135143, 300, 0x7a, 0x07 },
+	{ 153143, 500, 0x01, 0x07 }
+};
+
+static const u32 mxl301rf_rf_tab[112] = {
+	0x058d3f49, 0x05e8ccc9, 0x06445a49, 0x069fe7c9, 0x06fb7549,
+	0x075702c9, 0x07b29049, 0x080e1dc9, 0x0869ab49, 0x08c538c9,
+	0x0920c649, 0x097c53c9, 0x09f665c9, 0x0a51f349, 0x0aad80c9,
+	0x0b090e49, 0x0b649bc9, 0x0ba1a4c9, 0x0bfd3249, 0x0c58bfc9,
+	0x0cb44d49, 0x0d0fdac9, 0x0d6b6849, 0x0dc6f5c9, 0x0e228349,
+	0x0e7e10c9, 0x0ed99e49, 0x0f352bc9, 0x0f90b949, 0x0fec46c9,
+	0x1047d449, 0x10a361c9, 0x10feef49, 0x115a7cc9, 0x11b60a49,
+	0x121197c9, 0x126d2549, 0x12c8b2c9, 0x13244049, 0x137fcdc9,
+	0x13db5b49, 0x1436e8c9, 0x14927649, 0x14ee03c9, 0x15499149,
+	0x15a51ec9, 0x1600ac49, 0x165c39c9, 0x16b7c749, 0x171354c9,
+	0x176ee249, 0x17ca6fc9, 0x1825fd49, 0x18818ac9, 0x18dd1849,
+	0x1938a5c9, 0x19943349, 0x19efc0c9, 0x1a4b4e49, 0x1aa6dbc9,
+	0x1b026949, 0x1b5df6c9, 0x1bb98449, 0x1c339649, 0x1c8f23c9,
+	0x1ceab149, 0x1d463ec9, 0x1da1cc49, 0x1dfd59c9, 0x1e58e749,
+	0x1eb474c9, 0x1f100249, 0x1f6b8fc9, 0x1fc71d49, 0x2022aac9,
+	0x207e3849, 0x20d9c5c9, 0x21355349, 0x2190e0c9, 0x21ec6e49,
+	0x2247fbc9, 0x22a38949, 0x22ff16c9, 0x235aa449, 0x23b631c9,
+	0x2411bf49, 0x246d4cc9, 0x24c8da49, 0x252467c9, 0x257ff549,
+	0x25db82c9, 0x26371049, 0x26929dc9, 0x26ee2b49, 0x2749b8c9,
+	0x27a54649, 0x2800d3c9, 0x285c6149, 0x28b7eec9, 0x29137c49,
+	0x296f09c9, 0x29ca9749, 0x2a2624c9, 0x2a81b249, 0x2add3fc9,
+	0x2b38cd49, 0x2b945ac9, 0x2befe849, 0x2c4b75c9, 0x2ca70349,
+	0x2d0290c9, 0x2d5e1e49,
+};
+#define MXL301RF_NHK (mxl301rf_rf_tab[77])	/* 日本放送協会 Nippon Hōsō Kyōkai, Japan Broadcasting Corporation */
+
+int mxl301rf_freq(int freq)
+{
+	if (freq >= 90000000)
+		return freq;					/* real_freq Hz	*/
+	if (freq > 255)
+		return MXL301RF_NHK;
+	if (freq > 127)
+		return mxl301rf_rf_tab[freq - 128];		/* freqno (IO#)	*/
+	if (freq > 63) {					/* CATV		*/
+		freq -= 64;
+		if (freq > 22)
+			return mxl301rf_rf_tab[freq - 1];	/* C23-C62	*/
+		if (freq > 12)
+			return mxl301rf_rf_tab[freq - 10];	/* C13-C22	*/
+		return MXL301RF_NHK;
+	}
+	if (freq > 62)
+		return MXL301RF_NHK;
+	if (freq > 12)
+		return mxl301rf_rf_tab[freq + 50];		/* 13-62	*/
+	if (freq >  3)
+		return mxl301rf_rf_tab[freq +  9];		/*  4-12	*/
+	if (freq)
+		return mxl301rf_rf_tab[freq -  1];		/*  1-3		*/
+	return MXL301RF_NHK;
+}
+
+void mxl301rf_rftune(u8 *data, u32 *size, u32 freq)
+{
+	u32 dig_rf_freq, tmp, frac_divider, kHz, MHz, i;
+	u8 rf_data[] = {
+		0x13, 0x00,	/* abort tune			*/
+		0x3b, 0xc0,
+		0x3b, 0x80,
+		0x10, 0x95,	/* BW				*/
+		0x1a, 0x05,
+		0x61, 0x00,
+		0x62, 0xa0,
+		0x11, 0x40,	/* 2 bytes to store RF freq.	*/
+		0x12, 0x0e,	/* 2 bytes to store RF freq.	*/
+		0x13, 0x01	/* start tune			*/
+	};
+
+	freq = mxl301rf_freq(freq);
+	dig_rf_freq = 0;
+	tmp = 0;
+	frac_divider = 1000000;
+	kHz = 1000;
+	MHz = 1000000;
+
+	dig_rf_freq = freq / MHz;
+	tmp = freq % MHz;
+
+	for (i = 0; i < 6; i++) {
+		dig_rf_freq <<= 1;
+		frac_divider /= 2;
+		if (tmp > frac_divider) {
+			tmp -= frac_divider;
+			dig_rf_freq++;
+		}
+	}
+	if (tmp > 7812)
+		dig_rf_freq++;
+
+	rf_data[2 * (7) + 1] = (u8)(dig_rf_freq);
+	rf_data[2 * (8) + 1] = (u8)(dig_rf_freq >> 8);
+
+	for (i = 0; i < ARRAY_SIZE(shf_dvbt_tab); i++) {
+		if ((freq >= (shf_dvbt_tab[i].freq - shf_dvbt_tab[i].freq_th) * kHz) &&
+				(freq <= (shf_dvbt_tab[i].freq + shf_dvbt_tab[i].freq_th) * kHz)) {
+			rf_data[2 * (5) + 1] = shf_dvbt_tab[i].shf_val;
+			rf_data[2 * (6) + 1] = 0xa0 | shf_dvbt_tab[i].shf_dir;
+			break;
+		}
+	}
+	memcpy(data, rf_data, sizeof(rf_data));
+	*size = sizeof(rf_data);
+
+	pr_debug("mx_rftune freq=%d\n", freq);
+}
+
+/* write via demod */
+int mxl301rf_fe_write_data(struct dvb_frontend *fe, u8 addr_data, const u8 *data, int len)
+{
+	u8 buf[len + 1];
+
+	buf[0] = addr_data;
+	memcpy(buf + 1, data, len);
+	return fe->ops.write(fe, buf, len + 1);
+}
+
+#define MXL301RF_FE_PASSTHROUGH 0xfe
+
+int mxl301rf_fe_write_tuner(struct dvb_frontend *fe, const u8 *data, int len)
+{
+	u8 buf[len + 2];
+
+	buf[0] = MXL301RF_FE_PASSTHROUGH;
+	buf[1] = ((struct mxl301rf *)fe->tuner_priv)->addr_tuner << 1;
+	memcpy(buf + 2, data, len);
+	return fe->ops.write(fe, buf, len + 2);
+}
+
+/* read via demod */
+void mxl301rf_fe_read(struct dvb_frontend *fe, u8 addr, u8 *data)
+{
+	const u8 wbuf[2] = {0xfb, addr};
+	int ret;
+
+	mxl301rf_fe_write_tuner(fe, wbuf, sizeof(wbuf));
+	ret = fe->ops.write(fe, NULL, (1 << 16) | (((struct mxl301rf *)fe->tuner_priv)->addr_tuner << 8) | addr);
+	if (ret >= 0)
+		*data = ret;
+}
+
+void mxl301rf_idac_setting(struct dvb_frontend *fe)
+{
+	const u8 idac[] = {
+		0x0d, 0x00,
+		0x0c, 0x67,
+		0x6f, 0x89,
+		0x70, 0x0c,
+		0x6f, 0x8a,
+		0x70, 0x0e,
+		0x6f, 0x8b,
+		0x70, 0x10+12,
+	};
+	mxl301rf_fe_write_tuner(fe, idac, sizeof(idac));
+}
+
+void mxl301rf_set_register(struct dvb_frontend *fe, u8 addr, u8 value)
+{
+	const u8 data[2] = {addr, value};
+	mxl301rf_fe_write_tuner(fe, data, sizeof(data));
+}
+
+int mxl301rf_write_imsrst(struct dvb_frontend *fe)
+{
+	u8 data = 0x01 << 6;
+	return mxl301rf_fe_write_data(fe, 0x01, &data, 1);
+}
+
+enum mxl301rf_agc {
+	MXL301RF_AGC_AUTO,
+	MXL301RF_AGC_MANUAL,
+};
+
+int mxl301rf_set_agc(struct dvb_frontend *fe, enum mxl301rf_agc agc)
+{
+	u8 data = (agc == MXL301RF_AGC_AUTO) ? 0x40 : 0x00;
+	int ret = mxl301rf_fe_write_data(fe, 0x25, &data, 1);
+	if (ret)
+		return ret;
+
+	data = 0x4c | ((agc == MXL301RF_AGC_AUTO) ? 0x00 : 0x01);
+	return	mxl301rf_fe_write_data(fe, 0x23, &data, 1) ||
+		mxl301rf_write_imsrst(fe);
+}
+
+int mxl301rf_sleep(struct dvb_frontend *fe)
+{
+	u8 buf = (1 << 7) | (1 << 4);
+	const u8 data[4] = {0x01, 0x00, 0x13, 0x00};
+	int err = mxl301rf_set_agc(fe, MXL301RF_AGC_MANUAL);
+	if (err)
+		return err;
+	mxl301rf_fe_write_tuner(fe, data, sizeof(data));
+	return mxl301rf_fe_write_data(fe, 0x03, &buf, 1);
+}
+
+static const u8 mxl301rf_freq_tab[][3] = {
+	{   2, 0,  3 },
+	{  12, 1, 22 },
+	{  21, 0, 12 },
+	{  62, 1, 63 },
+	{ 112, 0, 62 }
+};
+
+bool mxl301rf_rfsynth_locked(struct dvb_frontend *fe)
+{
+	u8 data;
+
+	mxl301rf_fe_read(fe, 0x16, &data);
+	return (data & 0x0c) == 0x0c;
+}
+
+bool mxl301rf_refsynth_locked(struct dvb_frontend *fe)
+{
+	u8 data;
+
+	mxl301rf_fe_read(fe, 0x16, &data);
+	return (data & 0x03) == 0x03;
+}
+
+bool mxl301rf_locked(struct dvb_frontend *fe)
+{
+	bool locked1 = false, locked2 = false;
+	unsigned long timeout = jiffies + msecs_to_jiffies(100);
+
+	while (time_before(jiffies, timeout)) {
+		locked1 = mxl301rf_rfsynth_locked(fe);
+		locked2 = mxl301rf_refsynth_locked(fe);
+		if (locked1 && locked2)
+			break;
+		msleep_interruptible(1);
+	}
+	pr_debug("#%d %s lock1=%d lock2=%d\n", ((struct mxl301rf *)fe->tuner_priv)->idx, __func__, locked1, locked2);
+	return locked1 && locked2 ? !mxl301rf_set_agc(fe, MXL301RF_AGC_AUTO) : false;
+}
+
+int mxl301rf_tuner_rftune(struct dvb_frontend *fe, u32 freq)
+{
+	struct mxl301rf *mx = fe->tuner_priv;
+	u8 data[100];
+	u32 size = 0;
+	int err = mxl301rf_set_agc(fe, MXL301RF_AGC_MANUAL);
+	if (err)
+		return err;
+
+	mx->freq = freq;
+	mxl301rf_rftune(data, &size, freq);
+	if (size != 20) {
+		pr_debug("fail mx_rftune size = %d\n", size);
+		return -EINVAL;
+	}
+	mxl301rf_fe_write_tuner(fe, data, 14);
+	msleep_interruptible(1);
+	mxl301rf_fe_write_tuner(fe, data + 14, 6);
+	msleep_interruptible(1);
+	mxl301rf_set_register(fe, 0x1a, 0x0d);
+	mxl301rf_idac_setting(fe);
+
+	return mxl301rf_locked(fe) ? 0 : -ETIMEDOUT;
+}
+
+int mxl301rf_wakeup(struct dvb_frontend *fe)
+{
+	struct mxl301rf *mx = fe->tuner_priv;
+	int err;
+	u8 buf = (1 << 7) | (0 << 4);
+	const u8 data[2] = {0x01, 0x01};
+
+	err = mxl301rf_fe_write_data(fe, 0x03, &buf, 1);
+	if (err)
+		return err;
+	mxl301rf_fe_write_tuner(fe, data, sizeof(data));
+	mxl301rf_tuner_rftune(fe, mx->freq);
+	return 0;
+}
+
+void mxl301rf_ch2freq(u32 channel, bool *catv, u32 *number, u32 *freq)
+{
+	u32 i;
+	s32 freq_offset = 0;
+
+	if (12 <= channel)
+		freq_offset += 2;
+	if (17 <= channel)
+		freq_offset -= 2;
+	if (63 <= channel)
+		freq_offset += 2;
+	*freq = 93 + channel * 6 + freq_offset;
+
+	for (i = 0; i < ARRAY_SIZE(mxl301rf_freq_tab); i++) {
+		if (channel <= mxl301rf_freq_tab[i][0]) {
+			*catv = mxl301rf_freq_tab[i][1] ? true : false;
+			*number = channel + mxl301rf_freq_tab[i][2] - mxl301rf_freq_tab[i][0];
+			break;
+		}
+	}
+}
+
+int mxl301rf_release(struct dvb_frontend *fe)
+{
+	kfree(fe->tuner_priv);
+	fe->tuner_priv = NULL;
+	return 0;
+}
+
+static struct dvb_tuner_ops mxl301rf_ops = {
+	.info = {
+		.frequency_min	= 1,		/* actually 90 MHz, freq below that is handled as ch */
+		.frequency_max	= 770000000,	/* Hz */
+		.frequency_step	= 142857,
+	},
+	.set_frequency = mxl301rf_tuner_rftune,
+	.sleep = mxl301rf_sleep,
+	.init = mxl301rf_wakeup,
+	.release = mxl301rf_release,
+};
+
+int mxl301rf_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
+{
+	u8 d[] = { 0x10, 0x01 };
+	struct mxl301rf *mx = kzalloc(sizeof(struct mxl301rf), GFP_KERNEL);
+	if (!mx)
+		return -ENOMEM;
+	fe->tuner_priv = mx;
+	mx->fe = fe;
+	mx->idx = idx;
+	mx->addr_tuner = addr_tuner;
+	memcpy(&fe->ops.tuner_ops, &mxl301rf_ops, sizeof(struct dvb_tuner_ops));
+
+	return	mxl301rf_fe_write_data(fe, 0x1c, d, 1)	||
+		mxl301rf_fe_write_data(fe, 0x1d, d+1, 1);
+}
+EXPORT_SYMBOL(mxl301rf_attach);
+
diff --git a/drivers/media/tuners/mxl301rf.h b/drivers/media/tuners/mxl301rf.h
new file mode 100644
index 0000000..22599b9
--- /dev/null
+++ b/drivers/media/tuners/mxl301rf.h
@@ -0,0 +1,36 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-T tuner MaxLinear CMOS Hybrid TV MxL301RF
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 __MXL301RF_H__
+#define __MXL301RF_H__
+
+#ifndef pr_fmt
+ #define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+#endif
+#include "dvb_frontend.h"
+
+#if IS_ENABLED(CONFIG_MEDIA_TUNER_MXL301RF)
+extern int mxl301rf_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner);
+#else
+static inline int mxl301rf_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
+{
+	pr_warn("%s: driver disabled by Kconfig\n", __func__);
+	return 0;
+}
+#endif
+
+#endif
+
diff --git a/drivers/media/tuners/qm1d1c0042.c b/drivers/media/tuners/qm1d1c0042.c
new file mode 100644
index 0000000..3be552f
--- /dev/null
+++ b/drivers/media/tuners/qm1d1c0042.c
@@ -0,0 +1,382 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-S tuner driver QM1D1C0042
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "qm1d1c0042.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 QM1D1C0042 ISDB-S tuner driver");
+MODULE_LICENSE("GPL");
+
+struct qm1d1c0042 {
+	struct dvb_frontend *fe;
+	u8 addr_tuner, idx, reg[32];
+	u32 freq;
+};
+
+static const u8 qm1d1c0042_reg_rw[] = {
+	0x48, 0x1c, 0xa0, 0x10, 0xbc, 0xc5, 0x20, 0x33,
+	0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+	0x00, 0xff, 0xf3, 0x00, 0x2a, 0x64, 0xa6, 0x86,
+	0x8c, 0xcf, 0xb8, 0xf1, 0xa8, 0xf2, 0x89, 0x00,
+};
+
+/* read via demodulator */
+int qm1d1c0042_fe_read(struct dvb_frontend *fe, u8 addr, u8 *data)
+{
+	int ret;
+	if ((addr != 0x00) && (addr != 0x0d))
+		return -EFAULT;
+	ret = fe->ops.write(fe, NULL, (((struct qm1d1c0042 *)fe->tuner_priv)->addr_tuner << 8) | addr);
+	if (ret < 0)
+		return ret;
+	*data = ret;
+	return 0;
+}
+
+/* write via demodulator */
+int qm1d1c0042_fe_write_data(struct dvb_frontend *fe, u8 addr_data, u8 *data, int len)
+{
+	u8 buf[len + 1];
+
+	buf[0] = addr_data;
+	memcpy(buf + 1, data, len);
+	return fe->ops.write(fe, buf, len + 1);
+}
+
+#define QM1D1C0042_FE_PASSTHROUGH 0xfe
+
+int qm1d1c0042_fe_write_tuner(struct dvb_frontend *fe, u8 *data, int len)
+{
+	u8 buf[len + 2];
+
+	buf[0] = QM1D1C0042_FE_PASSTHROUGH;
+	buf[1] = ((struct qm1d1c0042 *)fe->tuner_priv)->addr_tuner << 1;
+	memcpy(buf + 2, data, len);
+	return fe->ops.write(fe, buf, len + 2);
+}
+
+int qm1d1c0042_write(struct dvb_frontend *fe, u8 addr, u8 data)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	u8 buf[] = { addr, data };
+	int err = qm1d1c0042_fe_write_tuner(fe, buf, sizeof(buf));
+	qm->reg[addr] = buf[1];
+	return err;
+}
+
+static const u8 qm1d1c0042_flag[32] = {
+	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
+	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+};
+
+int qm1d1c0042_write_pskmsrst(struct dvb_frontend *fe)
+{
+	u8 data = 0x01;
+	return qm1d1c0042_fe_write_data(fe, 0x03, &data, 1);
+}
+
+enum qm1d1c0042_agc {
+	QM1D1C0042_AGC_AUTO,
+	QM1D1C0042_AGC_MANUAL,
+};
+
+int qm1d1c0042_set_agc(struct dvb_frontend *fe, enum qm1d1c0042_agc agc)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	static u8 agc_data_s[2] = { 0xb0, 0x30 };
+	u8 data = (agc == QM1D1C0042_AGC_AUTO) ? 0xff : 0x00;
+	int ret = qm1d1c0042_fe_write_data(fe, 0x0a, &data, 1);
+	if (ret)
+		return ret;
+
+	data = agc_data_s[(qm->idx >> 1) & 1];
+	data |= (agc == QM1D1C0042_AGC_AUTO) ? 0x01 : 0x00;
+	ret = qm1d1c0042_fe_write_data(fe, 0x10, &data, 1);
+	if (ret)
+		return ret;
+
+	data = (agc == QM1D1C0042_AGC_AUTO) ? 0x40 : 0x00;
+	return (ret = qm1d1c0042_fe_write_data(fe, 0x11, &data, 1)) ? ret : qm1d1c0042_write_pskmsrst(fe);
+}
+
+int qm1d1c0042_sleep(struct dvb_frontend *fe)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	u8 buf = 1;
+	pr_debug("#%d %s\n", qm->idx, __func__);
+
+	qm->reg[0x01] &= (~(1 << 3)) & 0xff;
+	qm->reg[0x01] |= 1 << 0;
+	qm->reg[0x05] |= 1 << 3;
+	return	qm1d1c0042_set_agc(fe, QM1D1C0042_AGC_MANUAL)	||
+		qm1d1c0042_write(fe, 0x05, qm->reg[0x05])	||
+		qm1d1c0042_write(fe, 0x01, qm->reg[0x01])	||
+		qm1d1c0042_fe_write_data(fe, 0x17, &buf, 1);
+}
+
+int qm1d1c0042_wakeup(struct dvb_frontend *fe)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	u8 buf = 0;
+	pr_debug("#%d %s\n", qm->idx, __func__);
+
+	qm->reg[0x01] |= 1 << 3;
+	qm->reg[0x01] &= (~(1 << 0)) & 0xff;
+	qm->reg[0x05] &= (~(1 << 3)) & 0xff;
+	return	qm1d1c0042_fe_write_data(fe, 0x17, &buf, 1)	||
+		qm1d1c0042_write(fe, 0x01, qm->reg[0x01])	||
+		qm1d1c0042_write(fe, 0x05, qm->reg[0x05]);
+}
+
+void qm1d1c0042_get_channel_freq(u32 channel, u32 *number, u32 *freq)
+{
+	if (channel < 12) {
+		*number = 1 + 2 * channel;
+		*freq = 104948 + 3836 * channel;
+	} else if (channel < 24) {
+		channel -= 12;
+		*number = 2 + 2 * channel;
+		*freq = 161300 + 4000 * channel;
+	} else {
+		channel -= 24;
+		*number = 1 + 2 * channel;
+		*freq = 159300 + 4000 * channel;
+	}
+}
+
+static const u32 qm1d1c0042_freq_tab[9][3] = {
+	{ 2151000, 1, 7 },
+	{ 1950000, 1, 6 },
+	{ 1800000, 1, 5 },
+	{ 1600000, 1, 4 },
+	{ 1450000, 1, 3 },
+	{ 1250000, 1, 2 },
+	{ 1200000, 0, 7 },
+	{  975000, 0, 6 },
+	{  950000, 0, 0 }
+};
+
+static const u32 qm1d1c0042_sd_tab[24][2][3] = {
+	{{0x38fae1, 0x0d, 0x5}, {0x39fae1, 0x0d, 0x5},},
+	{{0x3f570a, 0x0e, 0x3}, {0x00570a, 0x0e, 0x3},},
+	{{0x05b333, 0x0e, 0x5}, {0x06b333, 0x0e, 0x5},},
+	{{0x3c0f5c, 0x0f, 0x4}, {0x3d0f5c, 0x0f, 0x4},},
+	{{0x026b85, 0x0f, 0x6}, {0x036b85, 0x0f, 0x6},},
+	{{0x38c7ae, 0x10, 0x5}, {0x39c7ae, 0x10, 0x5},},
+	{{0x3f23d7, 0x11, 0x3}, {0x0023d7, 0x11, 0x3},},
+	{{0x058000, 0x11, 0x5}, {0x068000, 0x11, 0x5},},
+	{{0x3bdc28, 0x12, 0x4}, {0x3cdc28, 0x12, 0x4},},
+	{{0x023851, 0x12, 0x6}, {0x033851, 0x12, 0x6},},
+	{{0x38947a, 0x13, 0x5}, {0x39947a, 0x13, 0x5},},
+	{{0x3ef0a3, 0x14, 0x3}, {0x3ff0a3, 0x14, 0x3},},
+	{{0x3c8000, 0x16, 0x4}, {0x3d8000, 0x16, 0x4},},
+	{{0x048000, 0x16, 0x6}, {0x058000, 0x16, 0x6},},
+	{{0x3c8000, 0x17, 0x5}, {0x3d8000, 0x17, 0x5},},
+	{{0x048000, 0x18, 0x3}, {0x058000, 0x18, 0x3},},
+	{{0x3c8000, 0x18, 0x6}, {0x3d8000, 0x18, 0x6},},
+	{{0x048000, 0x19, 0x4}, {0x058000, 0x19, 0x4},},
+	{{0x3c8000, 0x1a, 0x3}, {0x3d8000, 0x1a, 0x3},},
+	{{0x048000, 0x1a, 0x5}, {0x058000, 0x1a, 0x5},},
+	{{0x3c8000, 0x1b, 0x4}, {0x3d8000, 0x1b, 0x4},},
+	{{0x048000, 0x1b, 0x6}, {0x058000, 0x1b, 0x6},},
+	{{0x3c8000, 0x1c, 0x5}, {0x3d8000, 0x1c, 0x5},},
+	{{0x048000, 0x1d, 0x3}, {0x058000, 0x1d, 0x3},},
+};
+
+static int qm1d1c0042_tuning(struct qm1d1c0042 *qm, u32 *sd, u32 channel)
+{
+	int ret;
+	struct dvb_frontend *fe = qm->fe;
+	u8 i_data;
+	u32 i, N, A, index = (qm->idx >> 1) & 1;
+
+	qm->reg[0x08] &= 0xf0;
+	qm->reg[0x08] |= 0x09;
+
+	qm->reg[0x13] &= 0x9f;
+	qm->reg[0x13] |= 0x20;
+
+	for (i = 0; i < 8; i++) {
+		if ((qm1d1c0042_freq_tab[i+1][0] <= qm->freq) && (qm->freq < qm1d1c0042_freq_tab[i][0])) {
+			i_data = qm->reg[0x02];
+			i_data &= 0x0f;
+			i_data |= qm1d1c0042_freq_tab[i][1] << 7;
+			i_data |= qm1d1c0042_freq_tab[i][2] << 4;
+			qm1d1c0042_write(fe, 0x02, i_data);
+		}
+	}
+
+	*sd = qm1d1c0042_sd_tab[channel][index][0];
+	N = qm1d1c0042_sd_tab[channel][index][1];
+	A = qm1d1c0042_sd_tab[channel][index][2];
+
+	qm->reg[0x06] &= 0x40;
+	qm->reg[0x06] |= N;
+	ret = qm1d1c0042_write(fe, 0x06, qm->reg[0x06]);
+	if (ret)
+		return ret;
+
+	qm->reg[0x07] &= 0xf0;
+	qm->reg[0x07] |= A & 0x0f;
+	return qm1d1c0042_write(fe, 0x07, qm->reg[0x07]);
+}
+
+static int qm1d1c0042_local_lpf_tuning(struct qm1d1c0042 *qm, u32 channel)
+{
+	struct dvb_frontend *fe = qm->fe;
+	u8 i_data;
+	u32 sd = 0;
+	int ret = qm1d1c0042_tuning(qm, &sd, channel);
+	if (ret)
+		return ret;
+
+	i_data = qm->reg[0x08] & 0xf0;
+	i_data |= 2;
+	ret = qm1d1c0042_write(fe, 0x08, i_data);
+	if (ret)
+		return ret;
+
+	qm->reg[0x09] &= 0xc0;
+	qm->reg[0x09] |= (sd >> 16) & 0x3f;
+	qm->reg[0x0a] = (sd >> 8) & 0xff;
+	qm->reg[0x0b] = (sd >> 0) & 0xff;
+	ret =	qm1d1c0042_write(fe, 0x09, qm->reg[0x09])	||
+		qm1d1c0042_write(fe, 0x0a, qm->reg[0x0a])	||
+		qm1d1c0042_write(qm->fe, 0x0b, qm->reg[0x0b]);
+	if (ret)
+		return ret;
+
+	i_data = qm->reg[0x0c];
+	i_data &= 0x3f;
+	ret = qm1d1c0042_write(fe, 0x0c, i_data);
+	if (ret)
+		return ret;
+	msleep_interruptible(1);
+
+	i_data = qm->reg[0x0c];
+	i_data |= 0xc0;
+	return	qm1d1c0042_write(fe, 0x0c, i_data)	||
+		qm1d1c0042_write(fe, 0x08, 0x09)	||
+		qm1d1c0042_write(fe, 0x13, qm->reg[0x13]);
+}
+
+int qm1d1c0042_get_locked(struct qm1d1c0042 *qm, bool *locked)
+{
+	int ret = qm1d1c0042_fe_read(qm->fe, 0x0d, &qm->reg[0x0d]);
+	if (ret)
+		return ret;
+	if (qm->reg[0x0d] & 0x40)
+		*locked = true;
+	else
+		*locked = false;
+	return ret;
+}
+
+u32 qm1d1c0042_freq2ch(u32 frequency)
+{
+	u32 freq = frequency / 10,
+	    ch0 = (freq - 104948) / 3836, diff0 = freq - (104948 + 3836 * ch0),
+	    ch1 = (freq - 161300) / 4000, diff1 = freq - (161300 + 4000 * ch1),
+	    ch2 = (freq - 159300) / 4000, diff2 = freq - (159300 + 4000 * ch2),
+	    min = diff0 < diff1 ? diff0 : diff1;
+
+	if (frequency < 1024)
+		return frequency;	/* consider as channel ID if low */
+	if (diff2 < min)
+		return ch2 + 24;
+	else if (min == diff1)
+		return ch1 + 12;
+	else
+		return ch0;
+}
+
+int qm1d1c0042_set_freq(struct dvb_frontend *fe, u32 frequency)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	u32 channel = qm1d1c0042_freq2ch(frequency);
+	u32 number, freq, freq_kHz;
+	bool locked = false;
+	unsigned long timeout;
+	int err = qm1d1c0042_set_agc(fe, QM1D1C0042_AGC_MANUAL);
+	if (err)
+		return err;
+
+	qm1d1c0042_get_channel_freq(channel, &number, &freq);
+	freq_kHz = freq * 10;
+	if (((qm->idx >> 1) & 1) == 0)
+		freq_kHz -= 500;
+	else
+		freq_kHz += 500;
+	qm->freq = freq_kHz;
+	pr_debug("#%d ch %d freq %d kHz\n", qm->idx, channel, freq_kHz);
+
+	err = qm1d1c0042_local_lpf_tuning(qm, channel);
+	if (err)
+		return err;
+
+	timeout = jiffies + msecs_to_jiffies(1000);	/* 1s */
+	while (time_before(jiffies, timeout)) {
+		err = qm1d1c0042_get_locked(qm, &locked);
+		if (err)
+			return err;
+		if (locked)
+			break;
+		msleep_interruptible(1);
+	}
+	pr_debug("#%d %s %s\n", qm->idx, __func__, locked ? "LOCKED" : "TIMEOUT");
+	return locked ? qm1d1c0042_set_agc(fe, QM1D1C0042_AGC_AUTO) : -ETIMEDOUT;
+}
+
+int qm1d1c0042_release(struct dvb_frontend *fe)
+{
+	kfree(fe->tuner_priv);
+	fe->tuner_priv = NULL;
+	return 0;
+}
+
+static struct dvb_tuner_ops qm1d1c0042_ops = {
+	.info = {
+		.frequency_min	= 1,		/* actually 1024 kHz, freq below that is handled as ch */
+		.frequency_max	= 2150000,	/* kHz */
+		.frequency_step	= 1000,		/* = 1 MHz */
+	},
+	.set_frequency = qm1d1c0042_set_freq,
+	.sleep = qm1d1c0042_sleep,
+	.init = qm1d1c0042_wakeup,
+	.release = qm1d1c0042_release,
+};
+
+int qm1d1c0042_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
+{
+	u8 d[] = { 0x10, 0x15, 0x04 };
+	struct qm1d1c0042 *qm = kzalloc(sizeof(struct qm1d1c0042), GFP_KERNEL);
+	if (!qm)
+		return -ENOMEM;
+	fe->tuner_priv = qm;
+	qm->fe = fe;
+	qm->idx = idx;
+	qm->addr_tuner = addr_tuner;
+	memcpy(&fe->ops.tuner_ops, &qm1d1c0042_ops, sizeof(struct dvb_tuner_ops));
+
+	memcpy(qm->reg, qm1d1c0042_reg_rw, sizeof(qm1d1c0042_reg_rw));
+	qm->freq = 0;
+
+	return	qm1d1c0042_fe_write_data(fe, 0x1e, d,   1)	||
+		qm1d1c0042_fe_write_data(fe, 0x1c, d+1, 1)	||
+		qm1d1c0042_fe_write_data(fe, 0x1f, d+2, 1);
+}
+EXPORT_SYMBOL(qm1d1c0042_attach);
+
diff --git a/drivers/media/tuners/qm1d1c0042.h b/drivers/media/tuners/qm1d1c0042.h
new file mode 100644
index 0000000..2b14b70
--- /dev/null
+++ b/drivers/media/tuners/qm1d1c0042.h
@@ -0,0 +1,36 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-S tuner driver QM1D1C0042
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 __QM1D1C0042_H__
+#define __QM1D1C0042_H__
+
+#ifndef pr_fmt
+ #define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+#endif
+#include "dvb_frontend.h"
+
+#if IS_ENABLED(CONFIG_MEDIA_TUNER_QM1D1C0042)
+extern int qm1d1c0042_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner);
+#else
+static inline int qm1d1c0042_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
+{
+	pr_warn("%s: driver disabled by Kconfig\n", __func__);
+	return 0;
+}
+#endif
+
+#endif
+
-- 
1.8.4.5


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH] Full DVB driver package for Earthsoft PT3 (ISDB-S/T) cards
@ 2014-04-21 15:20 Буди Романто, AreMa Inc
  0 siblings, 0 replies; 21+ messages in thread
From: Буди Романто, AreMa Inc @ 2014-04-21 15:20 UTC (permalink / raw)
  To: linux-media
  Cc: Буди
	Романто,
	AreMa Inc, crope, m.chehab, hdegoede, hverkuil, laurent.pinchart,
	mkrufky, sylvester.nawrocki, g.liakhovetski, peter.senna

From: Буди Роман то, AreMa Inc <knightrider@are.ma>

DVB driver for Earthsoft PT3 (ISDB-S/T) receiver PCI Express cards.
It behaves similarly as PT1 DVB, plus some tuning enhancements:
1. in addition to the real frequency:
	ISDB-S : freq. channel ID
	ISDB-T : freq# (I/O# +128), ch#, ch# +64 for CATV
2. in addition to TSID:
	ISDB-S : slot#

Feature changes:
- dropped DKMS & standalone compile
- dropped verbosity (debug levels), use single level -DDEBUG instead
- changed SNR (.read_snr) to CNR (.read_signal_strength)
- moved demodulator FE to drivers/media/dvb-frontends
- moved tuners to drivers/media/tuners
- translated to standard (?) I2C protocol
- dropped FE's LNB control & other unused features
- added DVBv5 CNR support
- improved CNR precision

The full package (buildable as standalone, DKMS or tree embedded module) is available at
https://github.com/knight-rider/ptx/tree/master/pt3_dvb

Signed-off-by: Буди Романто, AreMa Inc <knightrider@are.ma>
---
 drivers/media/dvb-frontends/Kconfig   |  11 +-
 drivers/media/dvb-frontends/Makefile  |   1 +
 drivers/media/dvb-frontends/tc90522.c | 537 ++++++++++++++++++++++++++++++++++
 drivers/media/dvb-frontends/tc90522.h |  36 +++
 drivers/media/pci/Kconfig             |   2 +-
 drivers/media/pci/Makefile            |   1 +
 drivers/media/pci/pt3/Kconfig         |  11 +
 drivers/media/pci/pt3/Makefile        |   6 +
 drivers/media/pci/pt3/pt3_common.h    |  85 ++++++
 drivers/media/pci/pt3/pt3_dma.c       | 333 +++++++++++++++++++++
 drivers/media/pci/pt3/pt3_dma.h       |  50 ++++
 drivers/media/pci/pt3/pt3_i2c.c       | 189 ++++++++++++
 drivers/media/pci/pt3/pt3_i2c.h       |  25 ++
 drivers/media/pci/pt3/pt3_pci.c       | 384 ++++++++++++++++++++++++
 drivers/media/tuners/Kconfig          |  15 +
 drivers/media/tuners/Makefile         |   2 +
 drivers/media/tuners/mxl301rf.c       | 390 ++++++++++++++++++++++++
 drivers/media/tuners/mxl301rf.h       |  36 +++
 drivers/media/tuners/qm1d1c0042.c     | 382 ++++++++++++++++++++++++
 drivers/media/tuners/qm1d1c0042.h     |  36 +++
 20 files changed, 2530 insertions(+), 2 deletions(-)
 create mode 100644 drivers/media/dvb-frontends/tc90522.c
 create mode 100644 drivers/media/dvb-frontends/tc90522.h
 create mode 100644 drivers/media/pci/pt3/Kconfig
 create mode 100644 drivers/media/pci/pt3/Makefile
 create mode 100644 drivers/media/pci/pt3/pt3_common.h
 create mode 100644 drivers/media/pci/pt3/pt3_dma.c
 create mode 100644 drivers/media/pci/pt3/pt3_dma.h
 create mode 100644 drivers/media/pci/pt3/pt3_i2c.c
 create mode 100644 drivers/media/pci/pt3/pt3_i2c.h
 create mode 100644 drivers/media/pci/pt3/pt3_pci.c
 create mode 100644 drivers/media/tuners/mxl301rf.c
 create mode 100644 drivers/media/tuners/mxl301rf.h
 create mode 100644 drivers/media/tuners/qm1d1c0042.c
 create mode 100644 drivers/media/tuners/qm1d1c0042.h

diff --git a/drivers/media/dvb-frontends/Kconfig b/drivers/media/dvb-frontends/Kconfig
index 025fc54..0047b3f 100644
--- a/drivers/media/dvb-frontends/Kconfig
+++ b/drivers/media/dvb-frontends/Kconfig
@@ -591,7 +591,7 @@ config DVB_S5H1411
 	  An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want
 	  to support this frontend.
 
-comment "ISDB-T (terrestrial) frontends"
+comment "ISDB-S (satellite) & ISDB-T (terrestrial) frontends"
 	depends on DVB_CORE
 
 config DVB_S921
@@ -618,6 +618,15 @@ config DVB_MB86A20S
 	  A driver for Fujitsu mb86a20s ISDB-T/ISDB-Tsb demodulator.
 	  Say Y when you want to support this frontend.
 
+config DVB_TC90522
+	tristate "Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S)"
+	depends on DVB_CORE && I2C
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S) DVB demodulator
+	  frontend for Earthsoft PT3 PCIE cards.
+	  Say Y when you want to support this frontend.
+
 comment "Digital terrestrial only tuners/PLL"
 	depends on DVB_CORE
 
diff --git a/drivers/media/dvb-frontends/Makefile b/drivers/media/dvb-frontends/Makefile
index 282aba2..a80d212 100644
--- a/drivers/media/dvb-frontends/Makefile
+++ b/drivers/media/dvb-frontends/Makefile
@@ -105,4 +105,5 @@ obj-$(CONFIG_DVB_RTL2830) += rtl2830.o
 obj-$(CONFIG_DVB_RTL2832) += rtl2832.o
 obj-$(CONFIG_DVB_M88RS2000) += m88rs2000.o
 obj-$(CONFIG_DVB_AF9033) += af9033.o
+obj-$(CONFIG_DVB_TC90522) += tc90522.o
 
diff --git a/drivers/media/dvb-frontends/tc90522.c b/drivers/media/dvb-frontends/tc90522.c
new file mode 100644
index 0000000..40229da
--- /dev/null
+++ b/drivers/media/dvb-frontends/tc90522.c
@@ -0,0 +1,537 @@
+/*
+ * Earthsoft PT3 demodulator frontend Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S)
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "dvb_math.h"
+#include "tc90522.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 Toshiba TC90522 OFDM(ISDB-T)/8PSK(ISDB-S) demodulator");
+MODULE_LICENSE("GPL");
+
+#define TC90522_PASSTHROUGH 0xfe
+
+enum tc90522_state {
+	TC90522_IDLE,
+	TC90522_SET_FREQUENCY,
+	TC90522_SET_MODULATION,
+	TC90522_TRACK,
+	TC90522_ABORT,
+};
+
+struct tc90522 {
+	struct dvb_frontend fe;
+	struct i2c_adapter *i2c;
+	fe_delivery_system_t type;
+	u8 idx, addr_demod;
+	s32 offset;
+	enum tc90522_state state;
+};
+
+int tc90522_write(struct dvb_frontend *fe, const u8 *data, int len)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	struct i2c_msg msg[3];
+	u8 buf[6];
+
+	if (data) {
+		msg[0].addr = demod->addr_demod;
+		msg[0].buf = (u8 *)data;
+		msg[0].flags = 0;			/* write */
+		msg[0].len = len;
+
+		return i2c_transfer(demod->i2c, msg, 1) == 1 ? 0 : -EREMOTEIO;
+	} else {
+		u8 addr_tuner = (len >> 8) & 0xff,
+		   addr_data = len & 0xff;
+		if (len >> 16) {			/* read tuner without address */
+			buf[0] = TC90522_PASSTHROUGH;
+			buf[1] = (addr_tuner << 1) | 1;
+			msg[0].buf = buf;
+			msg[0].len = 2;
+			msg[0].addr = demod->addr_demod;
+			msg[0].flags = 0;		/* write */
+
+			msg[1].buf = buf + 2;
+			msg[1].len = 1;
+			msg[1].addr = demod->addr_demod;
+			msg[1].flags = I2C_M_RD;	/* read */
+
+			return i2c_transfer(demod->i2c, msg, 2) == 2 ? buf[2] : -EREMOTEIO;
+		} else {				/* read tuner */
+			buf[0] = TC90522_PASSTHROUGH;
+			buf[1] = addr_tuner << 1;
+			buf[2] = addr_data;
+			msg[0].buf = buf;
+			msg[0].len = 3;
+			msg[0].addr = demod->addr_demod;
+			msg[0].flags = 0;		/* write */
+
+			buf[3] = TC90522_PASSTHROUGH;
+			buf[4] = (addr_tuner << 1) | 1;
+			msg[1].buf = buf + 3;
+			msg[1].len = 2;
+			msg[1].addr = demod->addr_demod;
+			msg[1].flags = 0;		/* write */
+
+			msg[2].buf = buf + 5;
+			msg[2].len = 1;
+			msg[2].addr = demod->addr_demod;
+			msg[2].flags = I2C_M_RD;	/* read */
+
+			return i2c_transfer(demod->i2c, msg, 3) == 3 ? buf[5] : -EREMOTEIO;
+		}
+	}
+}
+
+int tc90522_write_data(struct dvb_frontend *fe, u8 addr_data, u8 *data, u8 len)
+{
+	u8 buf[len + 1];
+	buf[0] = addr_data;
+	memcpy(buf + 1, data, len);
+	return tc90522_write(fe, buf, len + 1);
+}
+
+int tc90522_read(struct tc90522 *demod, u8 addr, u8 *buf, u8 buflen)
+{
+	struct i2c_msg msg[2];
+	if (!buf || !buflen)
+		return -EINVAL;
+
+	buf[0] = addr;
+	msg[0].addr = demod->addr_demod;
+	msg[0].flags = 0;			/* write */
+	msg[0].buf = buf;
+	msg[0].len = 1;
+
+	msg[1].addr = demod->addr_demod;
+	msg[1].flags = I2C_M_RD;		/* read */
+	msg[1].buf = buf;
+	msg[1].len = buflen;
+
+	return i2c_transfer(demod->i2c, msg, 2) == 2 ? 0 : -EREMOTEIO;
+}
+
+u32 tc90522_byten(const u8 *data, u32 n)
+{
+	u32 i, val = 0;
+
+	for (i = 0; i < n; i++) {
+		val <<= 8;
+		val |= data[i];
+	}
+	return val;
+}
+
+int tc90522_read_id_s(struct tc90522 *demod, u16 *id)
+{
+	u8 buf[2];
+	int ret = tc90522_read(demod, 0xe6, buf, 2);
+	if (!ret)
+		*id = tc90522_byten(buf, 2);
+	return ret;
+}
+
+struct tmcc_s {			/* Transmission and Multiplexing Configuration Control */
+	u32 mode[4];
+	u32 slot[4];
+	u32 id[8];
+};
+
+int tc90522_read_tmcc_s(struct tc90522 *demod, struct tmcc_s *tmcc)
+{
+	enum {
+		BASE = 0xc5,
+		SIZE = 0xe5 - BASE + 1
+	};
+	u8 data[SIZE];
+	u32 i, byte_offset, bit_offset;
+
+	int err = tc90522_read(demod, 0xc3, data, 1)	||
+		((data[0] >> 4) & 1)			||
+		tc90522_read(demod, 0xce, data, 2)	||
+		(tc90522_byten(data, 2) == 0)		||
+		tc90522_read(demod, 0xc3, data, 1)	||
+		tc90522_read(demod, 0xc5, data, SIZE);
+	if (err)
+		return err;
+	for (i = 0; i < 4; i++) {
+		byte_offset = i >> 1;
+		bit_offset = (i & 1) ? 0 : 4;
+		tmcc->mode[i] = (data[0xc8 + byte_offset - BASE] >> bit_offset) & 0b00001111;
+		tmcc->slot[i] = (data[0xca + i           - BASE] >>          0) & 0b00111111;
+	}
+	for (i = 0; i < 8; i++)
+		tmcc->id[i] = tc90522_byten(data + 0xce + i * 2 - BASE, 2);
+	return 0;
+}
+
+enum tc90522_pwr {
+	TC90522_PWR_OFF		= 0x00,
+	TC90522_PWR_AMP_ON	= 0x04,
+	TC90522_PWR_TUNER_ON	= 0x40,
+};
+
+static enum tc90522_pwr tc90522_pwr = TC90522_PWR_OFF;
+
+int tc90522_set_powers(struct tc90522 *demod, enum tc90522_pwr pwr)
+{
+	u8 data = pwr | 0b10011001;
+	pr_debug("#%d tuner %s amp %s\n", demod->idx, pwr & TC90522_PWR_TUNER_ON ? "ON" : "OFF", pwr & TC90522_PWR_AMP_ON ? "ON" : "OFF");
+	tc90522_pwr = pwr;
+	return tc90522_write_data(&demod->fe, 0x1e, &data, 1);
+}
+
+/* dvb_frontend_ops */
+int tc90522_get_frontend_algo(struct dvb_frontend *fe)
+{
+	return DVBFE_ALGO_HW;
+}
+
+int tc90522_sleep(struct dvb_frontend *fe)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	pr_debug("#%d %s %s\n", demod->idx, __func__, demod->type == SYS_ISDBS ? "S" : "T");
+	return fe->ops.tuner_ops.sleep(fe);
+}
+
+int tc90522_wakeup(struct dvb_frontend *fe)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	pr_debug("#%d %s %s 0x%x\n", demod->idx, __func__, demod->type == SYS_ISDBS ? "S" : "T", tc90522_pwr);
+
+	if (!tc90522_pwr)
+		return	tc90522_set_powers(demod, TC90522_PWR_TUNER_ON)	||
+			i2c_transfer(demod->i2c, NULL, 0)		||
+			tc90522_set_powers(demod, TC90522_PWR_TUNER_ON | TC90522_PWR_AMP_ON);
+	demod->state = TC90522_IDLE;
+	return fe->ops.tuner_ops.init(fe);
+}
+
+void tc90522_release(struct dvb_frontend *fe)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	pr_debug("#%d %s\n", demod->idx, __func__);
+
+	if (tc90522_pwr)
+		tc90522_set_powers(demod, TC90522_PWR_OFF);
+	tc90522_sleep(fe);
+	fe->ops.tuner_ops.release(fe);
+	kfree(demod);
+}
+
+s64 tc90522_get_cn_raw(struct tc90522 *demod)
+{
+	u8 buf[3], buflen = demod->type == SYS_ISDBS ? 2 : 3, addr = demod->type == SYS_ISDBS ? 0xbc : 0x8b;
+	int err = tc90522_read(demod, addr, buf, buflen);
+	return err < 0 ? err : tc90522_byten(buf, buflen);
+}
+
+s64 tc90522_get_cn_s(s64 raw)	/* @ .0001 dB */
+{
+	s64 x, y;
+
+	raw -= 3000;
+	if (raw < 0)
+		raw = 0;
+	x = int_sqrt(raw << 20);
+	y = 16346ll * x - (143410ll << 16);
+	y = ((x * y) >> 16) + (502590ll << 16);
+	y = ((x * y) >> 16) - (889770ll << 16);
+	y = ((x * y) >> 16) + (895650ll << 16);
+	y = (588570ll << 16) - ((x * y) >> 16);
+	return y < 0 ? 0 : y >> 16;
+}
+
+s64 tc90522_get_cn_t(s64 raw)	/* @ .0001 dB */
+{
+	s64 x, y;
+	if (!raw)
+		return 0;
+	x = (1130911733ll - 10ll * intlog10(raw)) >> 2;
+	y = (6ll * x / 25ll) - (16ll << 22);
+	y = ((x * y) >> 22) + (398ll << 22);
+	y = ((x * y) >> 22) + (5491ll << 22);
+	y = ((x * y) >> 22) + (30965ll << 22);
+	return y >> 22;
+}
+
+int tc90522_read_signal_strength(struct dvb_frontend *fe, u16 *cn)	/* raw C/N */
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	s64 ret = tc90522_get_cn_raw(demod);
+	*cn = ret < 0 ? 0 : ret;
+	pr_debug("CN %d (%lld dB)\n", (int)*cn, demod->type == SYS_ISDBS ? (long long int)tc90522_get_cn_s(*cn) : (long long int)tc90522_get_cn_t(*cn));
+	return ret < 0 ? ret : 0;
+}
+
+int tc90522_read_status(struct dvb_frontend *fe, fe_status_t *status)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+	s64 ret = tc90522_get_cn_raw(demod),
+	    raw = ret < 0 ? 0 : ret;
+
+	switch (demod->state) {
+	case TC90522_IDLE:
+	case TC90522_SET_FREQUENCY:
+		*status = 0;
+		break;
+
+	case TC90522_SET_MODULATION:
+	case TC90522_ABORT:
+		*status |= FE_HAS_SIGNAL;
+		break;
+
+	case TC90522_TRACK:
+		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		break;
+	}
+
+	c->cnr.stat[0].svalue = demod->type == SYS_ISDBS ? tc90522_get_cn_s(raw) : tc90522_get_cn_t(raw);
+	c->cnr.stat[0].scale = FE_SCALE_DECIBEL;
+	return ret < 0 ? ret : 0;
+}
+
+/**** ISDB-S ****/
+int tc90522_write_id_s(struct dvb_frontend *fe, u16 id)
+{
+	u8 data[2] = { id >> 8, (u8)id };
+	return tc90522_write_data(fe, 0x8f, data, sizeof(data));
+}
+
+int tc90522_tune_s(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	struct tmcc_s tmcc;
+	int i, ret,
+	    freq = fe->dtv_property_cache.frequency,
+	    tsid = fe->dtv_property_cache.stream_id;
+
+	if (re_tune)
+		demod->state = TC90522_SET_FREQUENCY;
+
+	switch (demod->state) {
+	case TC90522_IDLE:
+		*delay = msecs_to_jiffies(3000);
+		*status = 0;
+		return 0;
+
+	case TC90522_SET_FREQUENCY:
+		pr_debug("#%d tsid 0x%x freq %d\n", demod->idx, tsid, freq);
+		ret = fe->ops.tuner_ops.set_frequency(fe, freq);
+		if (ret)
+			return ret;
+		demod->offset = 0;
+		demod->state = TC90522_SET_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case TC90522_SET_MODULATION:
+		for (i = 0; i < 1000; i++) {
+			ret = tc90522_read_tmcc_s(demod, &tmcc);
+			if (!ret)
+				break;
+			msleep_interruptible(1);
+		}
+		if (ret) {
+			pr_debug("fail tc_read_tmcc_s ret=0x%x\n", ret);
+			demod->state = TC90522_ABORT;
+			*delay = msecs_to_jiffies(1000);
+			return ret;
+		}
+		pr_debug("slots=%d,%d,%d,%d mode=%d,%d,%d,%d tmcc.id=0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x\n",
+				tmcc.slot[0], tmcc.slot[1], tmcc.slot[2], tmcc.slot[3],
+				tmcc.mode[0], tmcc.mode[1], tmcc.mode[2], tmcc.mode[3],
+				tmcc.id[0], tmcc.id[1], tmcc.id[2], tmcc.id[3],
+				tmcc.id[4], tmcc.id[5], tmcc.id[6], tmcc.id[7]);
+		for (i = 0; i < ARRAY_SIZE(tmcc.id); i++) {
+			pr_debug("tsid %x i %d tmcc.id %x\n", tsid, i, tmcc.id[i]);
+			if (tmcc.id[i] == tsid)
+				break;
+		}
+		if (tsid < ARRAY_SIZE(tmcc.id))		/* treat as slot# */
+			i = tsid;
+		if (i == ARRAY_SIZE(tmcc.id)) {
+			pr_debug("#%d i%d tsid 0x%x not found\n", demod->idx, i, tsid);
+			return -EINVAL;
+		}
+		demod->offset = i;
+		pr_debug("#%d found tsid 0x%x on slot %d\n", demod->idx, tsid, i);
+		ret = tc90522_write_id_s(fe, (u16)tmcc.id[demod->offset]);
+		if (ret) {
+			pr_debug("fail set_tmcc_s ret=%d\n", ret);
+			return ret;
+		}
+		for (i = 0; i < 1000; i++) {
+			u16 short_id;
+			ret = tc90522_read_id_s(demod, &short_id);
+			if (ret) {
+				pr_debug("fail get_id_s ret=%d\n", ret);
+				return ret;
+			}
+			tsid = short_id;
+			pr_debug("#%d tsid=0x%x\n", demod->idx, tsid);
+			if ((tsid & 0xffff) == tmcc.id[demod->offset])
+				break;
+			msleep_interruptible(1);
+		}
+		demod->state = TC90522_TRACK;
+		/* fallthrough */
+
+	case TC90522_TRACK:
+		*delay = msecs_to_jiffies(3000);
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+
+	case TC90522_ABORT:
+		*delay = msecs_to_jiffies(3000);
+		*status = FE_HAS_SIGNAL;
+		return 0;
+	}
+	return -ERANGE;
+}
+
+static struct dvb_frontend_ops tc90522_ops_s = {
+	.delsys = { SYS_ISDBS },
+	.info = {
+		.name = "TC90522 ISDB-S",
+		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO | FE_CAN_MULTISTREAM |
+			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
+	},
+	.init = tc90522_wakeup,
+	.sleep = tc90522_sleep,
+	.release = tc90522_release,
+	.write = tc90522_write,
+	.get_frontend_algo = tc90522_get_frontend_algo,
+	.read_signal_strength = tc90522_read_signal_strength,
+	.read_status = tc90522_read_status,
+	.tune = tc90522_tune_s,
+};
+
+/**** ISDB-T ****/
+int tc90522_get_tmcc_t(struct tc90522 *demod)
+{
+	u8 buf;
+	bool b = false, retryov, fulock;
+
+	while (1) {
+		if (tc90522_read(demod, 0x80, &buf, 1))
+			return -EBADMSG;
+		retryov = buf & 0b10000000 ? true : false;
+		fulock  = buf & 0b00001000 ? true : false;
+		if (!fulock) {
+			b = true;
+			break;
+		} else {
+			if (retryov)
+				break;
+		}
+		msleep_interruptible(1);
+	}
+	return b ? 0 : -EBADMSG;
+}
+
+int tc90522_tune_t(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	int ret, i;
+
+	if (re_tune)
+		demod->state = TC90522_SET_FREQUENCY;
+
+	switch (demod->state) {
+	case TC90522_IDLE:
+		*delay = msecs_to_jiffies(3000);
+		*status = 0;
+		return 0;
+
+	case TC90522_SET_FREQUENCY:
+		if (fe->ops.tuner_ops.set_frequency(fe, fe->dtv_property_cache.frequency)) {
+			*delay = msecs_to_jiffies(1000);
+			*status = 0;
+			return 0;
+		}
+		demod->state = TC90522_SET_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case TC90522_SET_MODULATION:
+		for (i = 0; i < 1000; i++) {
+			ret = tc90522_get_tmcc_t(demod);
+			if (!ret)
+				break;
+			msleep_interruptible(2);
+		}
+		if (ret) {
+			pr_debug("#%d fail get_tmcc_t ret=%d\n", demod->idx, ret);
+				demod->state = TC90522_ABORT;
+				*delay = msecs_to_jiffies(1000);
+				return 0;
+		}
+		demod->state = TC90522_TRACK;
+		/* fallthrough */
+
+	case TC90522_TRACK:
+		*delay = msecs_to_jiffies(3000);
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+
+	case TC90522_ABORT:
+		*delay = msecs_to_jiffies(3000);
+		*status = FE_HAS_SIGNAL;
+		return 0;
+	}
+	return -ERANGE;
+}
+
+static struct dvb_frontend_ops tc90522_ops_t = {
+	.delsys = { SYS_ISDBT },
+	.info = {
+		.name = "TC90522 ISDB-T",
+		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO |
+			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
+	},
+	.init = tc90522_wakeup,
+	.sleep = tc90522_sleep,
+	.release = tc90522_release,
+	.write = tc90522_write,
+	.get_frontend_algo = tc90522_get_frontend_algo,
+	.read_signal_strength = tc90522_read_signal_strength,
+	.read_status = tc90522_read_status,
+	.tune = tc90522_tune_t,
+};
+
+/**** Common ****/
+struct dvb_frontend *tc90522_attach(struct i2c_adapter *i2c, u8 idx, fe_delivery_system_t type, u8 addr_demod)
+{
+	struct dvb_frontend *fe;
+	struct tc90522 *demod = kzalloc(sizeof(struct tc90522), GFP_KERNEL);
+	if (!demod)
+		return NULL;
+
+	demod->i2c	= i2c;
+	demod->idx	= idx;
+	demod->type	= type;
+	demod->addr_demod = addr_demod;
+	fe = &demod->fe;
+	memcpy(&fe->ops, (demod->type == SYS_ISDBS) ? &tc90522_ops_s : &tc90522_ops_t, sizeof(struct dvb_frontend_ops));
+	fe->demodulator_priv = demod;
+	return fe;
+}
+EXPORT_SYMBOL(tc90522_attach);
+
diff --git a/drivers/media/dvb-frontends/tc90522.h b/drivers/media/dvb-frontends/tc90522.h
new file mode 100644
index 0000000..78c5298
--- /dev/null
+++ b/drivers/media/dvb-frontends/tc90522.h
@@ -0,0 +1,36 @@
+/*
+ * Earthsoft PT3 demodulator frontend Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S)
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__TC90522_H__
+#define	__TC90522_H__
+
+#ifndef pr_fmt
+ #define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+#endif
+#include "dvb_frontend.h"
+
+#if IS_ENABLED(CONFIG_DVB_TC90522)
+extern struct dvb_frontend *tc90522_attach(struct i2c_adapter *i2c, u8 idx, fe_delivery_system_t type, u8 addr_demod);
+#else
+static inline struct dvb_frontend *tc90522_attach(struct i2c_adapter *i2c, u8 idx, fe_delivery_system_t type, u8 addr_demod)
+{
+	pr_warn("%s: driver disabled by Kconfig\n", __func__);
+	return NULL;
+}
+#endif
+
+#endif
+
diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
index 53196f1..87018c8 100644
--- a/drivers/media/pci/Kconfig
+++ b/drivers/media/pci/Kconfig
@@ -30,7 +30,6 @@ source "drivers/media/pci/cx88/Kconfig"
 source "drivers/media/pci/bt8xx/Kconfig"
 source "drivers/media/pci/saa7134/Kconfig"
 source "drivers/media/pci/saa7164/Kconfig"
-
 endif
 
 if MEDIA_DIGITAL_TV_SUPPORT
@@ -40,6 +39,7 @@ source "drivers/media/pci/b2c2/Kconfig"
 source "drivers/media/pci/pluto2/Kconfig"
 source "drivers/media/pci/dm1105/Kconfig"
 source "drivers/media/pci/pt1/Kconfig"
+source "drivers/media/pci/pt3/Kconfig"
 source "drivers/media/pci/mantis/Kconfig"
 source "drivers/media/pci/ngene/Kconfig"
 source "drivers/media/pci/ddbridge/Kconfig"
diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
index 35cc578..f7be6bc 100644
--- a/drivers/media/pci/Makefile
+++ b/drivers/media/pci/Makefile
@@ -7,6 +7,7 @@ obj-y        +=	ttpci/		\
 		pluto2/		\
 		dm1105/		\
 		pt1/		\
+		pt3/		\
 		mantis/		\
 		ngene/		\
 		ddbridge/	\
diff --git a/drivers/media/pci/pt3/Kconfig b/drivers/media/pci/pt3/Kconfig
new file mode 100644
index 0000000..0d866a0
--- /dev/null
+++ b/drivers/media/pci/pt3/Kconfig
@@ -0,0 +1,11 @@
+config PT3_DVB
+	tristate "Earthsoft PT3 ISDB-S/T cards"
+	depends on DVB_CORE && PCI
+	select DVB_TC90522 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_QM1D1C0042 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MXL301RF if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Support for Earthsoft PT3 PCI-Express cards.
+	  You need to enable frontend (TC90522) & tuners (QM1D1C0042, MXL301RF)
+	  Say Y or M if you own such a device and want to use it.
+
diff --git a/drivers/media/pci/pt3/Makefile b/drivers/media/pci/pt3/Makefile
new file mode 100644
index 0000000..ede00e1
--- /dev/null
+++ b/drivers/media/pci/pt3/Makefile
@@ -0,0 +1,6 @@
+pt3_dvb-objs := pt3_pci.o pt3_dma.o pt3_i2c.o
+
+obj-$(CONFIG_PT3_DVB) += pt3_dvb.o
+
+ccflags-y += -Idrivers/media/dvb-core -Idrivers/media/dvb-frontends -Idrivers/media/tuners
+
diff --git a/drivers/media/pci/pt3/pt3_common.h b/drivers/media/pci/pt3/pt3_common.h
new file mode 100644
index 0000000..7c1089f
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_common.h
@@ -0,0 +1,85 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_COMMON_H__
+#define	__PT3_COMMON_H__
+
+#define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+
+#include <linux/pci.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include "dvb_demux.h"
+#include "dmxdev.h"
+#include "dvb_frontend.h"
+
+#define DRV_NAME "pt3_dvb"
+
+/* register idx */
+#define PT3_REG_VERSION	0x00	/*	R	Version		*/
+#define PT3_REG_BUS	0x04	/*	R	Bus		*/
+#define PT3_REG_SYS_W	0x08	/*	W	System		*/
+#define PT3_REG_SYS_R	0x0c	/*	R	System		*/
+#define PT3_REG_I2C_W	0x10	/*	W	I2C		*/
+#define PT3_REG_I2C_R	0x14	/*	R	I2C		*/
+#define PT3_REG_RAM_W	0x18	/*	W	RAM		*/
+#define PT3_REG_RAM_R	0x1c	/*	R	RAM		*/
+#define PT3_REG_BASE	0x40	/* + 0x18*idx			*/
+#define PT3_REG_DMA_D_L	0x00	/*	W	DMA descriptor	*/
+#define PT3_REG_DMA_D_H	0x04	/*	W	DMA descriptor	*/
+#define PT3_REG_DMA_CTL	0x08	/*	W	DMA		*/
+#define PT3_REG_TS_CTL	0x0c	/*	W	TS		*/
+#define PT3_REG_STATUS	0x10	/*	R	DMA/FIFO/TS	*/
+#define PT3_REG_TS_ERR	0x14	/*	R	TS		*/
+
+struct pt3_adapter;
+
+struct pt3_board {
+	struct mutex lock;
+	int lnb;
+	bool reset;
+
+	struct pci_dev *pdev;
+	int bars;
+	void __iomem *bar_reg, *bar_mem;
+	struct i2c_adapter i2c;
+	u8 i2c_buf;
+	u32 i2c_addr;
+	bool i2c_filled;
+
+	struct pt3_adapter **adap;
+};
+
+struct pt3_adapter {
+	struct mutex lock;
+	struct pt3_board *pt3;
+
+	u8 idx;
+	bool sleep;
+	struct pt3_dma *dma;
+	struct task_struct *kthread;
+	struct dvb_adapter dvb;
+	struct dvb_demux demux;
+	struct dmxdev dmxdev;
+	int users;
+
+	struct dvb_frontend *fe;
+	int (*orig_sleep)(struct dvb_frontend *fe);
+	int (*orig_init)(struct dvb_frontend *fe);
+};
+
+#endif
+
diff --git a/drivers/media/pci/pt3/pt3_dma.c b/drivers/media/pci/pt3/pt3_dma.c
new file mode 100644
index 0000000..a1fb82e
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_dma.c
@@ -0,0 +1,333 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_dma.h"
+
+#define PT3_DMA_MAX_DESCS	204
+#define PT3_DMA_PAGE_SIZE	(PT3_DMA_MAX_DESCS * sizeof(struct pt3_dma_desc))
+#define PT3_DMA_BLOCK_COUNT	17
+#define PT3_DMA_BLOCK_SIZE	(PT3_DMA_PAGE_SIZE * 47)
+#define PT3_DMA_TS_BUF_SIZE	(PT3_DMA_BLOCK_SIZE * PT3_DMA_BLOCK_COUNT)
+#define PT3_DMA_TS_SYNC		0x47
+#define PT3_DMA_TS_NOT_SYNC	0x74
+
+void pt3_dma_free(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *page;
+	u32 i;
+
+	if (dma->ts_info) {
+		for (i = 0; i < dma->ts_count; i++) {
+			page = &dma->ts_info[i];
+			if (page->data)
+				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
+		}
+		kfree(dma->ts_info);
+	}
+	if (dma->desc_info) {
+		for (i = 0; i < dma->desc_count; i++) {
+			page = &dma->desc_info[i];
+			if (page->data)
+				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
+		}
+		kfree(dma->desc_info);
+	}
+	kfree(dma);
+}
+
+struct pt3_dma_desc {
+	u64 page_addr;
+	u32 page_size;
+	u64 next_desc;
+} __packed;
+
+void pt3_dma_build_page_descriptor(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *desc_info, *ts_info;
+	u64 ts_addr, desc_addr;
+	u32 i, j, ts_size, desc_remain, ts_info_pos, desc_info_pos;
+	struct pt3_dma_desc *prev, *curr;
+
+	pr_debug("#%d %s ts_count=%d ts_size=%d desc_count=%d desc_size=%d\n",
+		dma->adap->idx, __func__, dma->ts_count, dma->ts_info[0].size, dma->desc_count, dma->desc_info[0].size);
+	desc_info_pos = ts_info_pos = 0;
+	desc_info = &dma->desc_info[desc_info_pos];
+	desc_addr   = desc_info->addr;
+	desc_remain = desc_info->size;
+	desc_info->data_pos = 0;
+	prev = NULL;
+	curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+	desc_info_pos++;
+
+	for (i = 0; i < dma->ts_count; i++) {
+		if (unlikely(ts_info_pos >= dma->ts_count)) {
+			pr_debug("#%d ts_info overflow max=%d curr=%d\n", dma->adap->idx, dma->ts_count, ts_info_pos);
+			return;
+		}
+		ts_info = &dma->ts_info[ts_info_pos];
+		ts_addr = ts_info->addr;
+		ts_size = ts_info->size;
+		ts_info_pos++;
+		pr_debug("#%d i=%d, ts_info addr=0x%llx ts_size=%d\n", dma->adap->idx, i, ts_addr, ts_size);
+		for (j = 0; j < ts_size / PT3_DMA_PAGE_SIZE; j++) {
+			if (desc_remain < sizeof(struct pt3_dma_desc)) {
+				if (unlikely(desc_info_pos >= dma->desc_count)) {
+					pr_debug("#%d desc_info overflow max=%d curr=%d\n",
+						dma->adap->idx, dma->desc_count, desc_info_pos);
+					return;
+				}
+				desc_info = &dma->desc_info[desc_info_pos];
+				desc_info->data_pos = 0;
+				curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+				pr_debug("#%d desc_info_pos=%d ts_addr=0x%llx remain=%d\n",
+					dma->adap->idx, desc_info_pos, ts_addr, desc_remain);
+				desc_addr = desc_info->addr;
+				desc_remain = desc_info->size;
+				desc_info_pos++;
+			}
+			if (prev)
+				prev->next_desc = desc_addr | 0b10;
+			curr->page_addr = ts_addr           | 0b111;
+			curr->page_size = PT3_DMA_PAGE_SIZE | 0b111;
+			curr->next_desc = 0b10;
+			pr_debug("#%d j=%d dma write desc ts_addr=0x%llx desc_info_pos=%d desc_remain=%d\n",
+				dma->adap->idx, j, ts_addr, desc_info_pos, desc_remain);
+			ts_addr += PT3_DMA_PAGE_SIZE;
+
+			prev = curr;
+			desc_info->data_pos += sizeof(struct pt3_dma_desc);
+			if (unlikely(desc_info->data_pos > desc_info->size)) {
+				pr_debug("#%d dma desc_info data overflow max=%d curr=%d\n",
+					dma->adap->idx, desc_info->size, desc_info->data_pos);
+				return;
+			}
+			curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+			desc_addr += sizeof(struct pt3_dma_desc);
+			desc_remain -= sizeof(struct pt3_dma_desc);
+		}
+	}
+	if (prev)
+		prev->next_desc = dma->desc_info->addr | 0b10;
+}
+
+struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap)
+{
+	struct pt3_dma_page *page;
+	u32 i;
+
+	struct pt3_dma *dma = kzalloc(sizeof(struct pt3_dma), GFP_KERNEL);
+	if (!dma) {
+		pr_debug("#%d fail allocate PT3_DMA\n", adap->idx);
+		goto fail;
+	}
+	dma->adap = adap;
+	dma->enabled = false;
+	mutex_init(&dma->lock);
+
+	dma->ts_count = PT3_DMA_BLOCK_COUNT;
+	dma->ts_info = kzalloc(sizeof(struct pt3_dma_page) * dma->ts_count, GFP_KERNEL);
+	if (!dma->ts_info) {
+		pr_debug("#%d fail allocate TS DMA page\n", adap->idx);
+		goto fail;
+	}
+	pr_debug("#%d Alloc TS buf (ts_count %d)\n", adap->idx, dma->ts_count);
+	for (i = 0; i < dma->ts_count; i++) {
+		page = &dma->ts_info[i];
+		page->size = PT3_DMA_BLOCK_SIZE;
+		page->data_pos = 0;
+		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
+		if (!page->data) {
+			pr_debug("#%d fail alloc_consistent. %d\n", adap->idx, i);
+			goto fail;
+		}
+	}
+
+	dma->desc_count = 1 + (PT3_DMA_TS_BUF_SIZE / PT3_DMA_PAGE_SIZE - 1) / PT3_DMA_MAX_DESCS;
+	dma->desc_info = kzalloc(sizeof(struct pt3_dma_page) * dma->desc_count, GFP_KERNEL);
+	if (!dma->desc_info) {
+		pr_debug("#%d fail allocate Desc DMA page\n", adap->idx);
+		goto fail;
+	}
+	pr_debug("#%d Alloc Descriptor buf (desc_count %d)\n", adap->idx, dma->desc_count);
+	for (i = 0; i < dma->desc_count; i++) {
+		page = &dma->desc_info[i];
+		page->size = PT3_DMA_PAGE_SIZE;
+		page->data_pos = 0;
+		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
+		if (!page->data) {
+			pr_debug("#%d fail alloc_consistent %d\n", adap->idx, i);
+			goto fail;
+		}
+	}
+
+	pr_debug("#%d build page descriptor\n", adap->idx);
+	pt3_dma_build_page_descriptor(dma);
+	return dma;
+fail:
+	if (dma)
+		pt3_dma_free(dma);
+	return NULL;
+}
+
+void __iomem *pt3_dma_get_base_addr(struct pt3_dma *dma)
+{
+	return dma->adap->pt3->bar_reg + PT3_REG_BASE + (0x18 * dma->adap->idx);
+}
+
+void pt3_dma_reset(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *ts;
+	u32 i;
+
+	for (i = 0; i < dma->ts_count; i++) {
+		ts = &dma->ts_info[i];
+		memset(ts->data, 0, ts->size);
+		ts->data_pos = 0;
+		*ts->data = PT3_DMA_TS_NOT_SYNC;
+	}
+	dma->ts_pos = 0;
+}
+
+void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled)
+{
+	void __iomem *base = pt3_dma_get_base_addr(dma);
+	u64 start_addr = dma->desc_info->addr;
+
+	if (enabled) {
+		pr_debug("#%d DMA enable start_addr=%llx\n", dma->adap->idx, start_addr);
+		pt3_dma_reset(dma);
+		writel(1 << 1, base + PT3_REG_DMA_CTL);	/* stop DMA */
+		writel(start_addr         & 0xffffffff, base + PT3_REG_DMA_D_L);
+		writel((start_addr >> 32) & 0xffffffff, base + PT3_REG_DMA_D_H);
+		pr_debug("set descriptor address low %llx\n",  start_addr         & 0xffffffff);
+		pr_debug("set descriptor address high %llx\n", (start_addr >> 32) & 0xffffffff);
+		writel(1 << 0, base + PT3_REG_DMA_CTL);	/* start DMA */
+	} else {
+		pr_debug("#%d DMA disable\n", dma->adap->idx);
+		writel(1 << 1, base + PT3_REG_DMA_CTL);	/* stop DMA */
+		while (1) {
+			if (!(readl(base + PT3_REG_STATUS) & 1))
+				break;
+			msleep_interruptible(1);
+		}
+	}
+	dma->enabled = enabled;
+}
+
+/* convert Gray code to binary, e.g. 1001 -> 1110 */
+static u32 pt3_dma_gray2binary(u32 gray, u32 bit)
+{
+	u32 binary = 0, i, j, k;
+
+	for (i = 0; i < bit; i++) {
+		k = 0;
+		for (j = i; j < bit; j++)
+			k ^= (gray >> j) & 1;
+		binary |= k << i;
+	}
+	return binary;
+}
+
+u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma)
+{
+	return pt3_dma_gray2binary(readl(pt3_dma_get_base_addr(dma) + PT3_REG_TS_ERR), 32);
+}
+
+void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval)
+{
+	void __iomem *base = pt3_dma_get_base_addr(dma);
+	u32 data = mode | initval;
+	pr_debug("#%d %s base=%p data=0x%04x\n", dma->adap->idx, __func__, base, data);
+	writel(data, base + PT3_REG_TS_CTL);
+}
+
+bool pt3_dma_ready(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *ts;
+	u8 *p;
+
+	u32 next = dma->ts_pos + 1;
+	if (next >= dma->ts_count)
+		next = 0;
+	ts = &dma->ts_info[next];
+	p = &ts->data[ts->data_pos];
+
+	if (*p == PT3_DMA_TS_SYNC)
+		return true;
+	if (*p == PT3_DMA_TS_NOT_SYNC)
+		return false;
+
+	pr_debug("#%d invalid sync byte value=0x%02x ts_pos=%d data_pos=%d curr=0x%02x\n",
+		dma->adap->idx, *p, next, ts->data_pos, dma->ts_info[dma->ts_pos].data[0]);
+	return false;
+}
+
+ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux)
+{
+	bool ready;
+	struct pt3_dma_page *ts;
+	u32 i, prev;
+	size_t csize, remain = dma->ts_info[dma->ts_pos].size;
+
+	mutex_lock(&dma->lock);
+	pr_debug("#%d dma_copy ts_pos=0x%x data_pos=0x%x\n",
+		   dma->adap->idx, dma->ts_pos, dma->ts_info[dma->ts_pos].data_pos);
+	for (;;) {
+		for (i = 0; i < 20; i++) {
+			ready = pt3_dma_ready(dma);
+			if (ready)
+				break;
+			msleep_interruptible(30);
+		}
+		if (!ready) {
+			pr_debug("#%d dma_copy NOT READY\n", dma->adap->idx);
+			goto last;
+		}
+		prev = dma->ts_pos - 1;
+		if (prev < 0 || dma->ts_count <= prev)
+			prev = dma->ts_count - 1;
+		if (dma->ts_info[prev].data[0] != PT3_DMA_TS_NOT_SYNC)
+			pr_debug("#%d DMA buffer overflow. prev=%d data=0x%x\n",
+					dma->adap->idx, prev, dma->ts_info[prev].data[0]);
+		ts = &dma->ts_info[dma->ts_pos];
+		for (;;) {
+			csize = (remain < (ts->size - ts->data_pos)) ?
+				 remain : (ts->size - ts->data_pos);
+			dvb_dmx_swfilter(demux, &ts->data[ts->data_pos], csize);
+			remain -= csize;
+			ts->data_pos += csize;
+			if (ts->data_pos >= ts->size) {
+				ts->data_pos = 0;
+				ts->data[ts->data_pos] = PT3_DMA_TS_NOT_SYNC;
+				dma->ts_pos++;
+				if (dma->ts_pos >= dma->ts_count)
+					dma->ts_pos = 0;
+				break;
+			}
+			if (remain <= 0)
+				goto last;
+		}
+	}
+last:
+	mutex_unlock(&dma->lock);
+	return dma->ts_info[dma->ts_pos].size - remain;
+}
+
+u32 pt3_dma_get_status(struct pt3_dma *dma)
+{
+	return readl(pt3_dma_get_base_addr(dma) + PT3_REG_STATUS);
+}
+
diff --git a/drivers/media/pci/pt3/pt3_dma.h b/drivers/media/pci/pt3/pt3_dma.h
new file mode 100644
index 0000000..934c222
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_dma.h
@@ -0,0 +1,50 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_DMA_H__
+#define	__PT3_DMA_H__
+
+#include "pt3_common.h"
+
+struct pt3_dma_page {
+	dma_addr_t addr;
+	u8 *data;
+	u32 size, data_pos;
+};
+
+enum pt3_dma_mode {
+	USE_LFSR = 1 << 16,
+	REVERSE  = 1 << 17,
+	RESET    = 1 << 18,
+};
+
+struct pt3_dma {
+	struct pt3_adapter *adap;
+	bool enabled;
+	u32 ts_pos, ts_count, desc_count;
+	struct pt3_dma_page *ts_info, *desc_info;
+	struct mutex lock;
+};
+
+ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux);
+struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap);
+void pt3_dma_free(struct pt3_dma *dma);
+u32 pt3_dma_get_status(struct pt3_dma *dma);
+u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma);
+void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled);
+void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval);
+
+#endif
diff --git a/drivers/media/pci/pt3/pt3_i2c.c b/drivers/media/pci/pt3/pt3_i2c.c
new file mode 100644
index 0000000..e872ae1
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_i2c.c
@@ -0,0 +1,189 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_i2c.h"
+
+#define PT3_I2C_DATA_OFFSET	2048
+#define PT3_I2C_START_ADDR	0x17fa
+
+enum pt3_i2c_cmd {
+	I_END,
+	I_ADDRESS,
+	I_CLOCK_L,
+	I_CLOCK_H,
+	I_DATA_L,
+	I_DATA_H,
+	I_RESET,
+	I_SLEEP,
+	I_DATA_L_NOP  = 0x08,
+	I_DATA_H_NOP  = 0x0c,
+	I_DATA_H_READ = 0x0d,
+	I_DATA_H_ACK0 = 0x0e,
+	I_DATA_H_ACK1 = 0x0f,
+};
+
+bool pt3_i2c_is_clean(struct pt3_board *pt3)
+{
+	return (readl(pt3->bar_reg + PT3_REG_I2C_R) >> 3) & 1;
+}
+
+void pt3_i2c_reset(struct pt3_board *pt3)
+{
+	writel(1 << 17, pt3->bar_reg + PT3_REG_I2C_W);	/* 0x00020000 */
+}
+
+void pt3_i2c_wait(struct pt3_board *pt3, u32 *status)
+{
+	u32 val;
+
+	while (1) {
+		val = readl(pt3->bar_reg + PT3_REG_I2C_R);
+		if (!(val & 1))				/* sequence stopped */
+			break;
+		msleep_interruptible(1);
+	}
+	if (status)
+		*status = val;				/* I2C register status */
+}
+
+void pt3_i2c_mem_write(struct pt3_board *pt3, u8 data)
+{
+	void __iomem *dst = pt3->bar_mem + PT3_I2C_DATA_OFFSET + pt3->i2c_addr;
+
+	if (pt3->i2c_filled) {
+		pt3->i2c_buf |= data << 4;
+		writeb(pt3->i2c_buf, dst);
+		pt3->i2c_addr++;
+	} else
+		pt3->i2c_buf = data;
+	pt3->i2c_filled ^= true;
+}
+
+void pt3_i2c_start(struct pt3_board *pt3)
+{
+	pt3_i2c_mem_write(pt3, I_DATA_H);
+	pt3_i2c_mem_write(pt3, I_CLOCK_H);
+	pt3_i2c_mem_write(pt3, I_DATA_L);
+	pt3_i2c_mem_write(pt3, I_CLOCK_L);
+}
+
+void pt3_i2c_cmd_write(struct pt3_board *pt3, const u8 *data, u32 size)
+{
+	u32 i, j;
+	u8 byte;
+
+	for (i = 0; i < size; i++) {
+		byte = data[i];
+		for (j = 0; j < 8; j++)
+			pt3_i2c_mem_write(pt3, (byte >> (7 - j)) & 1 ? I_DATA_H_NOP : I_DATA_L_NOP);
+		pt3_i2c_mem_write(pt3, I_DATA_H_ACK0);
+	}
+}
+
+void pt3_i2c_cmd_read(struct pt3_board *pt3, u8 *data, u32 size)
+{
+	u32 i, j;
+
+	for (i = 0; i < size; i++) {
+		for (j = 0; j < 8; j++)
+			pt3_i2c_mem_write(pt3, I_DATA_H_READ);
+		if (i == (size - 1))
+			pt3_i2c_mem_write(pt3, I_DATA_H_NOP);
+		else
+			pt3_i2c_mem_write(pt3, I_DATA_L_NOP);
+	}
+}
+
+void pt3_i2c_stop(struct pt3_board *pt3)
+{
+	pt3_i2c_mem_write(pt3, I_DATA_L);
+	pt3_i2c_mem_write(pt3, I_CLOCK_H);
+	pt3_i2c_mem_write(pt3, I_DATA_H);
+}
+
+int pt3_i2c_flush(struct pt3_board *pt3, bool end, u32 start_addr)
+{
+	u32 status;
+
+	if (end) {
+		pt3_i2c_mem_write(pt3, I_END);
+		if (pt3->i2c_filled)
+			pt3_i2c_mem_write(pt3, I_END);
+	}
+	pt3_i2c_wait(pt3, &status);
+	writel(1 << 16 | start_addr, pt3->bar_reg + PT3_REG_I2C_W);	/* 0x00010000 start sequence */
+	pt3_i2c_wait(pt3, &status);
+	if (status & 0b0110) {						/* ACK status */
+		pr_err("%s failed, status=0x%x\n", __func__, status);
+		return -EIO;
+	}
+	return 0;
+}
+
+u32 pt3_i2c_func(struct i2c_adapter *i2c)
+{
+	return I2C_FUNC_I2C;
+}
+
+int pt3_i2c_xfer(struct i2c_adapter *i2c, struct i2c_msg *msg, int num)
+{
+	struct pt3_board *pt3 = i2c_get_adapdata(i2c);
+	int i, j;
+
+	if (!num)
+		return pt3_i2c_flush(pt3, false, PT3_I2C_START_ADDR);
+	if ((num < 1) || (num > 3) || !msg || msg[0].flags)	/* always write first */
+		return -ENOTSUPP;
+	mutex_lock(&pt3->lock);
+	pt3->i2c_addr = 0;
+	for (i = 0; i < num; i++) {
+		u8 byte = (msg[i].addr << 1) | (msg[i].flags & 1);
+		pt3_i2c_start(pt3);
+		pt3_i2c_cmd_write(pt3, &byte, 1);
+		if (msg[i].flags == I2C_M_RD)
+			pt3_i2c_cmd_read(pt3, msg[i].buf, msg[i].len);
+		else
+			pt3_i2c_cmd_write(pt3, msg[i].buf, msg[i].len);
+	}
+	pt3_i2c_stop(pt3);
+	if (pt3_i2c_flush(pt3, true, 0))
+		num = -EIO;
+	else
+		for (i = 1; i < num; i++)
+			if (msg[i].flags == I2C_M_RD)
+				for (j = 0; j < msg[i].len; j++)
+					msg[i].buf[j] = readb(pt3->bar_mem + PT3_I2C_DATA_OFFSET + j);
+	mutex_unlock(&pt3->lock);
+	return num;
+}
+
+static const struct i2c_algorithm pt3_i2c_algo = {
+	.functionality = pt3_i2c_func,
+	.master_xfer = pt3_i2c_xfer,
+};
+
+int pt3_i2c_add_adapter(struct pt3_board *pt3)
+{
+	struct i2c_adapter *i2c = &pt3->i2c;
+	i2c->algo = &pt3_i2c_algo;
+	i2c->algo_data = NULL;
+	i2c->dev.parent = &pt3->pdev->dev;
+	strcpy(i2c->name, DRV_NAME);
+	i2c_set_adapdata(i2c, pt3);
+	return	i2c_add_adapter(i2c) ||
+		(!pt3_i2c_is_clean(pt3) && pt3_i2c_flush(pt3, false, 0));
+}
+
diff --git a/drivers/media/pci/pt3/pt3_i2c.h b/drivers/media/pci/pt3/pt3_i2c.h
new file mode 100644
index 0000000..8424fd5
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_i2c.h
@@ -0,0 +1,25 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_I2C_H__
+#define	__PT3_I2C_H__
+
+#include "pt3_common.h"
+
+void pt3_i2c_reset(struct pt3_board *pt3);
+int pt3_i2c_add_adapter(struct pt3_board *pt3);
+
+#endif
diff --git a/drivers/media/pci/pt3/pt3_pci.c b/drivers/media/pci/pt3/pt3_pci.c
new file mode 100644
index 0000000..0fb5d0f
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_pci.c
@@ -0,0 +1,384 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCIE bridge Altera Cyclone IV FPGA EP4CGX15BF14C8N
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_dma.h"
+#include "pt3_i2c.h"
+#include "tc90522.h"
+#include "qm1d1c0042.h"
+#include "mxl301rf.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 DVB Driver");
+MODULE_LICENSE("GPL");
+
+static struct pci_device_id pt3_id_table[] = {
+	{ PCI_DEVICE(0x1172, 0x4c15) },
+	{ },
+};
+MODULE_DEVICE_TABLE(pci, pt3_id_table);
+
+static int lnb = 2;
+module_param(lnb, int, 0);
+MODULE_PARM_DESC(lnb, "LNB level (0:OFF 1:+11V 2:+15V)");
+
+struct pt3_lnb {
+	u32 bits;
+	char *str;
+};
+
+static const struct pt3_lnb pt3_lnb[] = {
+	{0b1100,  "0V"},
+	{0b1101, "11V"},
+	{0b1111, "15V"},
+};
+
+struct pt3_cfg {
+	fe_delivery_system_t type;
+	u8 addr_tuner, addr_demod;
+};
+
+static const struct pt3_cfg pt3_cfg[] = {
+	{SYS_ISDBS, 0x63, 0b00010001},
+	{SYS_ISDBS, 0x60, 0b00010011},
+	{SYS_ISDBT, 0x62, 0b00010000},
+	{SYS_ISDBT, 0x61, 0b00010010},
+};
+#define PT3_ADAPN ARRAY_SIZE(pt3_cfg)
+
+int pt3_update_lnb(struct pt3_board *pt3)
+{
+	u8 i, lnb_eff = 0;
+
+	if (pt3->reset) {
+		writel(pt3_lnb[0].bits, pt3->bar_reg + PT3_REG_SYS_W);
+		pt3->reset = false;
+		pt3->lnb = 0;
+	} else {
+		struct pt3_adapter *adap;
+		for (i = 0; i < PT3_ADAPN; i++) {
+			adap = pt3->adap[i];
+			pr_debug("#%d sleep %d\n", adap->idx, adap->sleep);
+			if ((pt3_cfg[i].type == SYS_ISDBS) && (!adap->sleep))
+				lnb_eff |= lnb;
+		}
+		if (pt3->lnb != lnb_eff) {
+			writel(pt3_lnb[lnb_eff].bits, pt3->bar_reg + PT3_REG_SYS_W);
+			pt3->lnb = lnb_eff;
+		}
+	}
+	pr_debug("LNB=%s\n", pt3_lnb[lnb_eff].str);
+	return 0;
+}
+
+int pt3_thread(void *data)
+{
+	size_t ret;
+	struct pt3_adapter *adap = data;
+
+	pr_debug("#%d %s sleep %d\n", adap->idx, __func__, adap->sleep);
+	set_freezable();
+	while (!kthread_should_stop()) {
+		try_to_freeze();
+		while ((ret = pt3_dma_copy(adap->dma, &adap->demux)) > 0)
+			;
+		if (ret < 0) {
+			pr_debug("#%d fail dma_copy\n", adap->idx);
+			msleep_interruptible(1);
+		}
+	}
+	return 0;
+}
+
+int pt3_start_feed(struct dvb_demux_feed *feed)
+{
+	int ret = 0;
+	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
+	pr_debug("#%d %s sleep %d\n", adap->idx, __func__, adap->sleep);
+	if (!adap->users++) {
+		pr_debug("#%d %s selected, DMA %s\n",
+			adap->idx, (pt3_cfg[adap->idx].type == SYS_ISDBS) ? "S" : "T",
+			pt3_dma_get_status(adap->dma) & 1 ? "ON" : "OFF");
+		mutex_lock(&adap->lock);
+		if (!adap->kthread) {
+			adap->kthread = kthread_run(pt3_thread, adap, DRV_NAME "_%d", adap->idx);
+			if (IS_ERR(adap->kthread)) {
+				ret = PTR_ERR(adap->kthread);
+				adap->kthread = NULL;
+			} else {
+				pt3_dma_set_test_mode(adap->dma, RESET, 0);	/* reset error count */
+				pt3_dma_set_enabled(adap->dma, true);
+			}
+		}
+		mutex_unlock(&adap->lock);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+int pt3_stop_feed(struct dvb_demux_feed *feed)
+{
+	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
+	pr_debug("#%d %s sleep %d\n", adap->idx, __func__, adap->sleep);
+	if (!--adap->users) {
+		mutex_lock(&adap->lock);
+		if (adap->kthread) {
+			pt3_dma_set_enabled(adap->dma, false);
+			pr_debug("#%d DMA ts_err packet cnt %d\n",
+				adap->idx, pt3_dma_get_ts_error_packet_count(adap->dma));
+			kthread_stop(adap->kthread);
+			adap->kthread = NULL;
+		}
+		mutex_unlock(&adap->lock);
+		msleep_interruptible(40);
+	}
+	return 0;
+}
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+struct pt3_adapter *pt3_dvb_register_adapter(struct pt3_board *pt3)
+{
+	int ret;
+	struct dvb_adapter *dvb;
+	struct dvb_demux *demux;
+	struct dmxdev *dmxdev;
+	struct pt3_adapter *adap = kzalloc(sizeof(struct pt3_adapter), GFP_KERNEL);
+	if (!adap)
+		return ERR_PTR(-ENOMEM);
+
+	adap->pt3 = pt3;
+	adap->sleep = true;
+
+	dvb = &adap->dvb;
+	dvb->priv = adap;
+	ret = dvb_register_adapter(dvb, DRV_NAME, THIS_MODULE, &pt3->pdev->dev, adapter_nr);
+	pr_debug("adapter%d registered\n", ret);
+	if (ret >= 0) {
+		demux = &adap->demux;
+		demux->dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING;
+		demux->priv = adap;
+		demux->feednum = 256;
+		demux->filternum = 256;
+		demux->start_feed = pt3_start_feed;
+		demux->stop_feed = pt3_stop_feed;
+		demux->write_to_decoder = NULL;
+		ret = dvb_dmx_init(demux);
+		if (ret >= 0) {
+			dmxdev = &adap->dmxdev;
+			dmxdev->filternum = 256;
+			dmxdev->demux = &demux->dmx;
+			dmxdev->capabilities = 0;
+			ret = dvb_dmxdev_init(dmxdev, dvb);
+			if (ret >= 0)
+				return adap;
+			dvb_dmx_release(demux);
+		}
+		dvb_unregister_adapter(dvb);
+	}
+	kfree(adap);
+	return ERR_PTR(ret);
+}
+
+int pt3_sleep(struct dvb_frontend *fe)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	pr_debug("#%d %s orig %p\n", adap->idx, __func__, adap->orig_sleep);
+	adap->sleep = true;
+	pt3_update_lnb(adap->pt3);
+	return (adap->orig_sleep) ? adap->orig_sleep(fe) : 0;
+}
+
+int pt3_wakeup(struct dvb_frontend *fe)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	pr_debug("#%d %s orig %p\n", adap->idx, __func__, adap->orig_init);
+	adap->sleep = false;
+	pt3_update_lnb(adap->pt3);
+	return (adap->orig_init) ? adap->orig_init(fe) : 0;
+}
+
+void pt3_cleanup_adapter(struct pt3_adapter *adap)
+{
+	if (!adap)
+		return;
+	if (adap->kthread)
+		kthread_stop(adap->kthread);
+	if (adap->fe) {
+		dvb_unregister_frontend(adap->fe);
+		adap->fe->ops.release(adap->fe);
+	}
+	if (adap->dma) {
+		if (adap->dma->enabled)
+			pt3_dma_set_enabled(adap->dma, false);
+		pt3_dma_free(adap->dma);
+	}
+	adap->demux.dmx.close(&adap->demux.dmx);
+	dvb_dmxdev_release(&adap->dmxdev);
+	dvb_dmx_release(&adap->demux);
+	dvb_unregister_adapter(&adap->dvb);
+	kfree(adap);
+}
+
+void pt3_remove(struct pci_dev *pdev)
+{
+	int i;
+	struct pt3_board *pt3 = pci_get_drvdata(pdev);
+
+	if (pt3) {
+		pt3->reset = true;
+		pt3_update_lnb(pt3);
+		for (i = 0; i < PT3_ADAPN; i++)
+			pt3_cleanup_adapter(pt3->adap[i]);
+		pt3_i2c_reset(pt3);
+		i2c_del_adapter(&pt3->i2c);
+		if (pt3->bar_mem)
+			iounmap(pt3->bar_mem);
+		if (pt3->bar_reg)
+			iounmap(pt3->bar_reg);
+		pci_release_selected_regions(pdev, pt3->bars);
+		kfree(pt3->adap);
+		kfree(pt3);
+	}
+	pci_disable_device(pdev);
+}
+
+int pt3_abort(struct pci_dev *pdev, int ret, char *fmt, ...)
+{
+	va_list ap;
+	char *s = NULL;
+	int slen;
+
+	va_start(ap, fmt);
+	slen = vsnprintf(s, 0, fmt, ap);
+	s = vzalloc(slen);
+	if (slen > 0 && s) {
+		vsnprintf(s, slen, fmt, ap);
+		dev_err(&pdev->dev, "%s", s);
+		vfree(s);
+	}
+	va_end(ap);
+	pt3_remove(pdev);
+	return ret;
+}
+
+int pt3_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+	struct pt3_board *pt3;
+	struct pt3_adapter *adap;
+	const struct pt3_cfg *cfg = pt3_cfg;
+	struct dvb_frontend *fe[PT3_ADAPN];
+	int i, err, bars = pci_select_bars(pdev, IORESOURCE_MEM);
+
+	err = pci_enable_device(pdev)					||
+		pci_set_dma_mask(pdev, DMA_BIT_MASK(64))		||
+		pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64))	||
+		pci_read_config_dword(pdev, PCI_CLASS_REVISION, &i)	||
+		pci_request_selected_regions(pdev, bars, DRV_NAME);
+	if (err)
+		return pt3_abort(pdev, err, "PCI/DMA error\n");
+	if ((i & 0xFF) != 1)
+		return pt3_abort(pdev, -EINVAL, "Revision 0x%x is not supported\n", i & 0xFF);
+
+	pci_set_master(pdev);
+	err = pci_save_state(pdev);
+	if (err)
+		return pt3_abort(pdev, err, "Failed pci_save_state\n");
+	pt3 = kzalloc(sizeof(struct pt3_board), GFP_KERNEL);
+	if (!pt3)
+		return pt3_abort(pdev, -ENOMEM, "struct pt3_board out of memory\n");
+	pt3->adap = kzalloc(PT3_ADAPN * sizeof(struct pt3_adapter *), GFP_KERNEL);
+	if (!pt3->adap)
+		return pt3_abort(pdev, -ENOMEM, "No memory for *adap\n");
+
+	pt3->bars = bars;
+	pt3->pdev = pdev;
+	pci_set_drvdata(pdev, pt3);
+	pt3->bar_reg = pci_ioremap_bar(pdev, 0);
+	pt3->bar_mem = pci_ioremap_bar(pdev, 2);
+	if (!pt3->bar_reg || !pt3->bar_mem)
+		return pt3_abort(pdev, -EIO, "Failed pci_ioremap_bar\n");
+
+	err = readl(pt3->bar_reg + PT3_REG_VERSION);
+	i = ((err >> 24) & 0xFF);
+	if (i != 3)
+		return pt3_abort(pdev, -EIO, "ID=0x%x, not a PT3\n", i);
+	i = ((err >>  8) & 0xFF);
+	if (i != 4)
+		return pt3_abort(pdev, -EIO, "FPGA version 0x%x is not supported\n", i);
+	err = pt3_i2c_add_adapter(pt3);
+	if (err < 0)
+		return pt3_abort(pdev, err, "Cannot add I2C\n");
+	mutex_init(&pt3->lock);
+
+	for (i = 0; i < PT3_ADAPN; i++) {
+		adap = pt3_dvb_register_adapter(pt3);
+		if (IS_ERR(adap))
+			return pt3_abort(pdev, PTR_ERR(adap), "Failed pt3_dvb_register_adapter\n");
+		adap->idx = i;
+		adap->dma = pt3_dma_create(adap);
+		if (!adap->dma)
+			return pt3_abort(pdev, -ENOMEM, "Failed pt3_dma_create\n");
+		pt3->adap[i] = adap;
+		adap->sleep = true;
+		mutex_init(&adap->lock);
+	}
+
+	for (i = 0; i < PT3_ADAPN; i++) {
+		fe[i] = tc90522_attach(&pt3->i2c, i, cfg[i].type, cfg[i].addr_demod);
+		if (!fe[i] || (cfg[i].type == SYS_ISDBS ?
+			qm1d1c0042_attach(fe[i], i, cfg[i].addr_tuner) : mxl301rf_attach(fe[i], i, cfg[i].addr_tuner))) {
+			while (i--)
+				fe[i]->ops.release(fe[i]);
+			return pt3_abort(pdev, -ENOMEM, "Cannot attach frontend\n");
+		}
+	}
+	fe[i-1]->ops.init(fe[i-1]);	/* power on tuner & amp */
+
+	for (i = 0; i < PT3_ADAPN; i++) {
+		struct pt3_adapter *adap = pt3->adap[i];
+		pr_debug("#%d %s\n", i, __func__);
+
+		adap->orig_sleep       = fe[i]->ops.sleep;
+		adap->orig_init        = fe[i]->ops.init;
+		fe[i]->ops.sleep       = pt3_sleep;
+		fe[i]->ops.init        = pt3_wakeup;
+		if ((adap->orig_init(fe[i]) && adap->orig_init(fe[i]) && adap->orig_init(fe[i])) ||
+			adap->orig_sleep(fe[i]) || dvb_register_frontend(&adap->dvb, fe[i])) {
+			while (i--)
+				dvb_unregister_frontend(fe[i]);
+			for (i = 0; i < PT3_ADAPN; i++) {
+				fe[i]->ops.release(fe[i]);
+				adap->fe = NULL;
+			}
+			return pt3_abort(pdev, -EREMOTEIO, "Cannot register frontend\n");
+		}
+		adap->fe = fe[i];
+	}
+	pt3->reset = true;
+	pt3_update_lnb(pt3);
+	return 0;
+}
+
+static struct pci_driver pt3_driver = {
+	.name		= DRV_NAME,
+	.probe		= pt3_probe,
+	.remove		= pt3_remove,
+	.id_table	= pt3_id_table,
+};
+
+module_pci_driver(pt3_driver);
+
diff --git a/drivers/media/tuners/Kconfig b/drivers/media/tuners/Kconfig
index a128488..8ca8ed7 100644
--- a/drivers/media/tuners/Kconfig
+++ b/drivers/media/tuners/Kconfig
@@ -243,4 +243,19 @@ config MEDIA_TUNER_R820T
 	default m if !MEDIA_SUBDRV_AUTOSELECT
 	help
 	  Rafael Micro R820T silicon tuner driver.
+
+config MEDIA_TUNER_MXL301RF
+	tristate "MaxLinear MXL301RF ISDB-T tuner"
+	depends on MEDIA_SUPPORT && I2C
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  MaxLinear MxL301RF ISDB-T tuner driver for PT3.
+
+config MEDIA_TUNER_QM1D1C0042
+	tristate "Sharp QM1D1C0042 ISDB-S tuner"
+	depends on MEDIA_SUPPORT && I2C
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Sharp QM1D1C0042 ISDB-S tuner driver for PT3 PCIE card.
+
 endmenu
diff --git a/drivers/media/tuners/Makefile b/drivers/media/tuners/Makefile
index efe82a9..b98c988 100644
--- a/drivers/media/tuners/Makefile
+++ b/drivers/media/tuners/Makefile
@@ -37,6 +37,8 @@ obj-$(CONFIG_MEDIA_TUNER_FC0012) += fc0012.o
 obj-$(CONFIG_MEDIA_TUNER_FC0013) += fc0013.o
 obj-$(CONFIG_MEDIA_TUNER_IT913X) += tuner_it913x.o
 obj-$(CONFIG_MEDIA_TUNER_R820T) += r820t.o
+obj-$(CONFIG_MEDIA_TUNER_MXL301RF) += mxl301rf.o
+obj-$(CONFIG_MEDIA_TUNER_QM1D1C0042) += qm1d1c0042.o
 
 ccflags-y += -I$(srctree)/drivers/media/dvb-core
 ccflags-y += -I$(srctree)/drivers/media/dvb-frontends
diff --git a/drivers/media/tuners/mxl301rf.c b/drivers/media/tuners/mxl301rf.c
new file mode 100644
index 0000000..cc60831
--- /dev/null
+++ b/drivers/media/tuners/mxl301rf.c
@@ -0,0 +1,390 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-T tuner MaxLinear CMOS Hybrid TV MxL301RF
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "mxl301rf.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 MxL301RF MaxLinear CMOS Hybrid TV ISDB-T tuner driver");
+MODULE_LICENSE("GPL");
+
+struct mxl301rf {
+	struct dvb_frontend *fe;
+	u8 addr_tuner, idx;
+	u32 freq;
+};
+
+struct shf_dvbt {
+	u32	freq,		/* Channel center frequency @ kHz	*/
+		freq_th;	/* Offset frequency threshold @ kHz	*/
+	u8	shf_val,	/* Spur shift value			*/
+		shf_dir;	/* Spur shift direction			*/
+};
+
+static const struct shf_dvbt shf_dvbt_tab[] = {
+	{  64500, 500, 0x92, 0x07 },
+	{ 191500, 300, 0xe2, 0x07 },
+	{ 205500, 500, 0x2c, 0x04 },
+	{ 212500, 500, 0x1e, 0x04 },
+	{ 226500, 500, 0xd4, 0x07 },
+	{  99143, 500, 0x9c, 0x07 },
+	{ 173143, 500, 0xd4, 0x07 },
+	{ 191143, 300, 0xd4, 0x07 },
+	{ 207143, 500, 0xce, 0x07 },
+	{ 225143, 500, 0xce, 0x07 },
+	{ 243143, 500, 0xd4, 0x07 },
+	{ 261143, 500, 0xd4, 0x07 },
+	{ 291143, 500, 0xd4, 0x07 },
+	{ 339143, 500, 0x2c, 0x04 },
+	{ 117143, 500, 0x7a, 0x07 },
+	{ 135143, 300, 0x7a, 0x07 },
+	{ 153143, 500, 0x01, 0x07 }
+};
+
+static const u32 mxl301rf_rf_tab[112] = {
+	0x058d3f49, 0x05e8ccc9, 0x06445a49, 0x069fe7c9, 0x06fb7549,
+	0x075702c9, 0x07b29049, 0x080e1dc9, 0x0869ab49, 0x08c538c9,
+	0x0920c649, 0x097c53c9, 0x09f665c9, 0x0a51f349, 0x0aad80c9,
+	0x0b090e49, 0x0b649bc9, 0x0ba1a4c9, 0x0bfd3249, 0x0c58bfc9,
+	0x0cb44d49, 0x0d0fdac9, 0x0d6b6849, 0x0dc6f5c9, 0x0e228349,
+	0x0e7e10c9, 0x0ed99e49, 0x0f352bc9, 0x0f90b949, 0x0fec46c9,
+	0x1047d449, 0x10a361c9, 0x10feef49, 0x115a7cc9, 0x11b60a49,
+	0x121197c9, 0x126d2549, 0x12c8b2c9, 0x13244049, 0x137fcdc9,
+	0x13db5b49, 0x1436e8c9, 0x14927649, 0x14ee03c9, 0x15499149,
+	0x15a51ec9, 0x1600ac49, 0x165c39c9, 0x16b7c749, 0x171354c9,
+	0x176ee249, 0x17ca6fc9, 0x1825fd49, 0x18818ac9, 0x18dd1849,
+	0x1938a5c9, 0x19943349, 0x19efc0c9, 0x1a4b4e49, 0x1aa6dbc9,
+	0x1b026949, 0x1b5df6c9, 0x1bb98449, 0x1c339649, 0x1c8f23c9,
+	0x1ceab149, 0x1d463ec9, 0x1da1cc49, 0x1dfd59c9, 0x1e58e749,
+	0x1eb474c9, 0x1f100249, 0x1f6b8fc9, 0x1fc71d49, 0x2022aac9,
+	0x207e3849, 0x20d9c5c9, 0x21355349, 0x2190e0c9, 0x21ec6e49,
+	0x2247fbc9, 0x22a38949, 0x22ff16c9, 0x235aa449, 0x23b631c9,
+	0x2411bf49, 0x246d4cc9, 0x24c8da49, 0x252467c9, 0x257ff549,
+	0x25db82c9, 0x26371049, 0x26929dc9, 0x26ee2b49, 0x2749b8c9,
+	0x27a54649, 0x2800d3c9, 0x285c6149, 0x28b7eec9, 0x29137c49,
+	0x296f09c9, 0x29ca9749, 0x2a2624c9, 0x2a81b249, 0x2add3fc9,
+	0x2b38cd49, 0x2b945ac9, 0x2befe849, 0x2c4b75c9, 0x2ca70349,
+	0x2d0290c9, 0x2d5e1e49,
+};
+#define MXL301RF_NHK (mxl301rf_rf_tab[77])	/* 日本放送協会 Nippon Hōsō Kyōkai, Japan Broadcasting Corporation */
+
+int mxl301rf_freq(int freq)
+{
+	if (freq >= 90000000)
+		return freq;					/* real_freq Hz	*/
+	if (freq > 255)
+		return MXL301RF_NHK;
+	if (freq > 127)
+		return mxl301rf_rf_tab[freq - 128];		/* freqno (IO#)	*/
+	if (freq > 63) {					/* CATV		*/
+		freq -= 64;
+		if (freq > 22)
+			return mxl301rf_rf_tab[freq - 1];	/* C23-C62	*/
+		if (freq > 12)
+			return mxl301rf_rf_tab[freq - 10];	/* C13-C22	*/
+		return MXL301RF_NHK;
+	}
+	if (freq > 62)
+		return MXL301RF_NHK;
+	if (freq > 12)
+		return mxl301rf_rf_tab[freq + 50];		/* 13-62	*/
+	if (freq >  3)
+		return mxl301rf_rf_tab[freq +  9];		/*  4-12	*/
+	if (freq)
+		return mxl301rf_rf_tab[freq -  1];		/*  1-3		*/
+	return MXL301RF_NHK;
+}
+
+void mxl301rf_rftune(u8 *data, u32 *size, u32 freq)
+{
+	u32 dig_rf_freq, tmp, frac_divider, kHz, MHz, i;
+	u8 rf_data[] = {
+		0x13, 0x00,	/* abort tune			*/
+		0x3b, 0xc0,
+		0x3b, 0x80,
+		0x10, 0x95,	/* BW				*/
+		0x1a, 0x05,
+		0x61, 0x00,
+		0x62, 0xa0,
+		0x11, 0x40,	/* 2 bytes to store RF freq.	*/
+		0x12, 0x0e,	/* 2 bytes to store RF freq.	*/
+		0x13, 0x01	/* start tune			*/
+	};
+
+	freq = mxl301rf_freq(freq);
+	dig_rf_freq = 0;
+	tmp = 0;
+	frac_divider = 1000000;
+	kHz = 1000;
+	MHz = 1000000;
+
+	dig_rf_freq = freq / MHz;
+	tmp = freq % MHz;
+
+	for (i = 0; i < 6; i++) {
+		dig_rf_freq <<= 1;
+		frac_divider /= 2;
+		if (tmp > frac_divider) {
+			tmp -= frac_divider;
+			dig_rf_freq++;
+		}
+	}
+	if (tmp > 7812)
+		dig_rf_freq++;
+
+	rf_data[2 * (7) + 1] = (u8)(dig_rf_freq);
+	rf_data[2 * (8) + 1] = (u8)(dig_rf_freq >> 8);
+
+	for (i = 0; i < ARRAY_SIZE(shf_dvbt_tab); i++) {
+		if ((freq >= (shf_dvbt_tab[i].freq - shf_dvbt_tab[i].freq_th) * kHz) &&
+				(freq <= (shf_dvbt_tab[i].freq + shf_dvbt_tab[i].freq_th) * kHz)) {
+			rf_data[2 * (5) + 1] = shf_dvbt_tab[i].shf_val;
+			rf_data[2 * (6) + 1] = 0xa0 | shf_dvbt_tab[i].shf_dir;
+			break;
+		}
+	}
+	memcpy(data, rf_data, sizeof(rf_data));
+	*size = sizeof(rf_data);
+
+	pr_debug("mx_rftune freq=%d\n", freq);
+}
+
+/* write via demod */
+int mxl301rf_fe_write_data(struct dvb_frontend *fe, u8 addr_data, const u8 *data, int len)
+{
+	u8 buf[len + 1];
+
+	buf[0] = addr_data;
+	memcpy(buf + 1, data, len);
+	return fe->ops.write(fe, buf, len + 1);
+}
+
+#define MXL301RF_FE_PASSTHROUGH 0xfe
+
+int mxl301rf_fe_write_tuner(struct dvb_frontend *fe, const u8 *data, int len)
+{
+	u8 buf[len + 2];
+
+	buf[0] = MXL301RF_FE_PASSTHROUGH;
+	buf[1] = ((struct mxl301rf *)fe->tuner_priv)->addr_tuner << 1;
+	memcpy(buf + 2, data, len);
+	return fe->ops.write(fe, buf, len + 2);
+}
+
+/* read via demod */
+void mxl301rf_fe_read(struct dvb_frontend *fe, u8 addr, u8 *data)
+{
+	const u8 wbuf[2] = {0xfb, addr};
+	int ret;
+
+	mxl301rf_fe_write_tuner(fe, wbuf, sizeof(wbuf));
+	ret = fe->ops.write(fe, NULL, (1 << 16) | (((struct mxl301rf *)fe->tuner_priv)->addr_tuner << 8) | addr);
+	if (ret >= 0)
+		*data = ret;
+}
+
+void mxl301rf_idac_setting(struct dvb_frontend *fe)
+{
+	const u8 idac[] = {
+		0x0d, 0x00,
+		0x0c, 0x67,
+		0x6f, 0x89,
+		0x70, 0x0c,
+		0x6f, 0x8a,
+		0x70, 0x0e,
+		0x6f, 0x8b,
+		0x70, 0x10+12,
+	};
+	mxl301rf_fe_write_tuner(fe, idac, sizeof(idac));
+}
+
+void mxl301rf_set_register(struct dvb_frontend *fe, u8 addr, u8 value)
+{
+	const u8 data[2] = {addr, value};
+	mxl301rf_fe_write_tuner(fe, data, sizeof(data));
+}
+
+int mxl301rf_write_imsrst(struct dvb_frontend *fe)
+{
+	u8 data = 0x01 << 6;
+	return mxl301rf_fe_write_data(fe, 0x01, &data, 1);
+}
+
+enum mxl301rf_agc {
+	MXL301RF_AGC_AUTO,
+	MXL301RF_AGC_MANUAL,
+};
+
+int mxl301rf_set_agc(struct dvb_frontend *fe, enum mxl301rf_agc agc)
+{
+	u8 data = (agc == MXL301RF_AGC_AUTO) ? 0x40 : 0x00;
+	int ret = mxl301rf_fe_write_data(fe, 0x25, &data, 1);
+	if (ret)
+		return ret;
+
+	data = 0x4c | ((agc == MXL301RF_AGC_AUTO) ? 0x00 : 0x01);
+	return	mxl301rf_fe_write_data(fe, 0x23, &data, 1) ||
+		mxl301rf_write_imsrst(fe);
+}
+
+int mxl301rf_sleep(struct dvb_frontend *fe)
+{
+	u8 buf = (1 << 7) | (1 << 4);
+	const u8 data[4] = {0x01, 0x00, 0x13, 0x00};
+	int err = mxl301rf_set_agc(fe, MXL301RF_AGC_MANUAL);
+	if (err)
+		return err;
+	mxl301rf_fe_write_tuner(fe, data, sizeof(data));
+	return mxl301rf_fe_write_data(fe, 0x03, &buf, 1);
+}
+
+static const u8 mxl301rf_freq_tab[][3] = {
+	{   2, 0,  3 },
+	{  12, 1, 22 },
+	{  21, 0, 12 },
+	{  62, 1, 63 },
+	{ 112, 0, 62 }
+};
+
+bool mxl301rf_rfsynth_locked(struct dvb_frontend *fe)
+{
+	u8 data;
+
+	mxl301rf_fe_read(fe, 0x16, &data);
+	return (data & 0x0c) == 0x0c;
+}
+
+bool mxl301rf_refsynth_locked(struct dvb_frontend *fe)
+{
+	u8 data;
+
+	mxl301rf_fe_read(fe, 0x16, &data);
+	return (data & 0x03) == 0x03;
+}
+
+bool mxl301rf_locked(struct dvb_frontend *fe)
+{
+	bool locked1 = false, locked2 = false;
+	unsigned long timeout = jiffies + msecs_to_jiffies(100);
+
+	while (time_before(jiffies, timeout)) {
+		locked1 = mxl301rf_rfsynth_locked(fe);
+		locked2 = mxl301rf_refsynth_locked(fe);
+		if (locked1 && locked2)
+			break;
+		msleep_interruptible(1);
+	}
+	pr_debug("#%d %s lock1=%d lock2=%d\n", ((struct mxl301rf *)fe->tuner_priv)->idx, __func__, locked1, locked2);
+	return locked1 && locked2 ? !mxl301rf_set_agc(fe, MXL301RF_AGC_AUTO) : false;
+}
+
+int mxl301rf_tuner_rftune(struct dvb_frontend *fe, u32 freq)
+{
+	struct mxl301rf *mx = fe->tuner_priv;
+	u8 data[100];
+	u32 size = 0;
+	int err = mxl301rf_set_agc(fe, MXL301RF_AGC_MANUAL);
+	if (err)
+		return err;
+
+	mx->freq = freq;
+	mxl301rf_rftune(data, &size, freq);
+	if (size != 20) {
+		pr_debug("fail mx_rftune size = %d\n", size);
+		return -EINVAL;
+	}
+	mxl301rf_fe_write_tuner(fe, data, 14);
+	msleep_interruptible(1);
+	mxl301rf_fe_write_tuner(fe, data + 14, 6);
+	msleep_interruptible(1);
+	mxl301rf_set_register(fe, 0x1a, 0x0d);
+	mxl301rf_idac_setting(fe);
+
+	return mxl301rf_locked(fe) ? 0 : -ETIMEDOUT;
+}
+
+int mxl301rf_wakeup(struct dvb_frontend *fe)
+{
+	struct mxl301rf *mx = fe->tuner_priv;
+	int err;
+	u8 buf = (1 << 7) | (0 << 4);
+	const u8 data[2] = {0x01, 0x01};
+
+	err = mxl301rf_fe_write_data(fe, 0x03, &buf, 1);
+	if (err)
+		return err;
+	mxl301rf_fe_write_tuner(fe, data, sizeof(data));
+	mxl301rf_tuner_rftune(fe, mx->freq);
+	return 0;
+}
+
+void mxl301rf_ch2freq(u32 channel, bool *catv, u32 *number, u32 *freq)
+{
+	u32 i;
+	s32 freq_offset = 0;
+
+	if (12 <= channel)
+		freq_offset += 2;
+	if (17 <= channel)
+		freq_offset -= 2;
+	if (63 <= channel)
+		freq_offset += 2;
+	*freq = 93 + channel * 6 + freq_offset;
+
+	for (i = 0; i < ARRAY_SIZE(mxl301rf_freq_tab); i++) {
+		if (channel <= mxl301rf_freq_tab[i][0]) {
+			*catv = mxl301rf_freq_tab[i][1] ? true : false;
+			*number = channel + mxl301rf_freq_tab[i][2] - mxl301rf_freq_tab[i][0];
+			break;
+		}
+	}
+}
+
+int mxl301rf_release(struct dvb_frontend *fe)
+{
+	kfree(fe->tuner_priv);
+	fe->tuner_priv = NULL;
+	return 0;
+}
+
+static struct dvb_tuner_ops mxl301rf_ops = {
+	.info = {
+		.frequency_min	= 1,		/* actually 90 MHz, freq below that is handled as ch */
+		.frequency_max	= 770000000,	/* Hz */
+		.frequency_step	= 142857,
+	},
+	.set_frequency = mxl301rf_tuner_rftune,
+	.sleep = mxl301rf_sleep,
+	.init = mxl301rf_wakeup,
+	.release = mxl301rf_release,
+};
+
+int mxl301rf_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
+{
+	u8 d[] = { 0x10, 0x01 };
+	struct mxl301rf *mx = kzalloc(sizeof(struct mxl301rf), GFP_KERNEL);
+	if (!mx)
+		return -ENOMEM;
+	fe->tuner_priv = mx;
+	mx->fe = fe;
+	mx->idx = idx;
+	mx->addr_tuner = addr_tuner;
+	memcpy(&fe->ops.tuner_ops, &mxl301rf_ops, sizeof(struct dvb_tuner_ops));
+
+	return	mxl301rf_fe_write_data(fe, 0x1c, d, 1)	||
+		mxl301rf_fe_write_data(fe, 0x1d, d+1, 1);
+}
+EXPORT_SYMBOL(mxl301rf_attach);
+
diff --git a/drivers/media/tuners/mxl301rf.h b/drivers/media/tuners/mxl301rf.h
new file mode 100644
index 0000000..22599b9
--- /dev/null
+++ b/drivers/media/tuners/mxl301rf.h
@@ -0,0 +1,36 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-T tuner MaxLinear CMOS Hybrid TV MxL301RF
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 __MXL301RF_H__
+#define __MXL301RF_H__
+
+#ifndef pr_fmt
+ #define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+#endif
+#include "dvb_frontend.h"
+
+#if IS_ENABLED(CONFIG_MEDIA_TUNER_MXL301RF)
+extern int mxl301rf_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner);
+#else
+static inline int mxl301rf_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
+{
+	pr_warn("%s: driver disabled by Kconfig\n", __func__);
+	return 0;
+}
+#endif
+
+#endif
+
diff --git a/drivers/media/tuners/qm1d1c0042.c b/drivers/media/tuners/qm1d1c0042.c
new file mode 100644
index 0000000..3be552f
--- /dev/null
+++ b/drivers/media/tuners/qm1d1c0042.c
@@ -0,0 +1,382 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-S tuner driver QM1D1C0042
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "qm1d1c0042.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 QM1D1C0042 ISDB-S tuner driver");
+MODULE_LICENSE("GPL");
+
+struct qm1d1c0042 {
+	struct dvb_frontend *fe;
+	u8 addr_tuner, idx, reg[32];
+	u32 freq;
+};
+
+static const u8 qm1d1c0042_reg_rw[] = {
+	0x48, 0x1c, 0xa0, 0x10, 0xbc, 0xc5, 0x20, 0x33,
+	0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+	0x00, 0xff, 0xf3, 0x00, 0x2a, 0x64, 0xa6, 0x86,
+	0x8c, 0xcf, 0xb8, 0xf1, 0xa8, 0xf2, 0x89, 0x00,
+};
+
+/* read via demodulator */
+int qm1d1c0042_fe_read(struct dvb_frontend *fe, u8 addr, u8 *data)
+{
+	int ret;
+	if ((addr != 0x00) && (addr != 0x0d))
+		return -EFAULT;
+	ret = fe->ops.write(fe, NULL, (((struct qm1d1c0042 *)fe->tuner_priv)->addr_tuner << 8) | addr);
+	if (ret < 0)
+		return ret;
+	*data = ret;
+	return 0;
+}
+
+/* write via demodulator */
+int qm1d1c0042_fe_write_data(struct dvb_frontend *fe, u8 addr_data, u8 *data, int len)
+{
+	u8 buf[len + 1];
+
+	buf[0] = addr_data;
+	memcpy(buf + 1, data, len);
+	return fe->ops.write(fe, buf, len + 1);
+}
+
+#define QM1D1C0042_FE_PASSTHROUGH 0xfe
+
+int qm1d1c0042_fe_write_tuner(struct dvb_frontend *fe, u8 *data, int len)
+{
+	u8 buf[len + 2];
+
+	buf[0] = QM1D1C0042_FE_PASSTHROUGH;
+	buf[1] = ((struct qm1d1c0042 *)fe->tuner_priv)->addr_tuner << 1;
+	memcpy(buf + 2, data, len);
+	return fe->ops.write(fe, buf, len + 2);
+}
+
+int qm1d1c0042_write(struct dvb_frontend *fe, u8 addr, u8 data)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	u8 buf[] = { addr, data };
+	int err = qm1d1c0042_fe_write_tuner(fe, buf, sizeof(buf));
+	qm->reg[addr] = buf[1];
+	return err;
+}
+
+static const u8 qm1d1c0042_flag[32] = {
+	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
+	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+};
+
+int qm1d1c0042_write_pskmsrst(struct dvb_frontend *fe)
+{
+	u8 data = 0x01;
+	return qm1d1c0042_fe_write_data(fe, 0x03, &data, 1);
+}
+
+enum qm1d1c0042_agc {
+	QM1D1C0042_AGC_AUTO,
+	QM1D1C0042_AGC_MANUAL,
+};
+
+int qm1d1c0042_set_agc(struct dvb_frontend *fe, enum qm1d1c0042_agc agc)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	static u8 agc_data_s[2] = { 0xb0, 0x30 };
+	u8 data = (agc == QM1D1C0042_AGC_AUTO) ? 0xff : 0x00;
+	int ret = qm1d1c0042_fe_write_data(fe, 0x0a, &data, 1);
+	if (ret)
+		return ret;
+
+	data = agc_data_s[(qm->idx >> 1) & 1];
+	data |= (agc == QM1D1C0042_AGC_AUTO) ? 0x01 : 0x00;
+	ret = qm1d1c0042_fe_write_data(fe, 0x10, &data, 1);
+	if (ret)
+		return ret;
+
+	data = (agc == QM1D1C0042_AGC_AUTO) ? 0x40 : 0x00;
+	return (ret = qm1d1c0042_fe_write_data(fe, 0x11, &data, 1)) ? ret : qm1d1c0042_write_pskmsrst(fe);
+}
+
+int qm1d1c0042_sleep(struct dvb_frontend *fe)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	u8 buf = 1;
+	pr_debug("#%d %s\n", qm->idx, __func__);
+
+	qm->reg[0x01] &= (~(1 << 3)) & 0xff;
+	qm->reg[0x01] |= 1 << 0;
+	qm->reg[0x05] |= 1 << 3;
+	return	qm1d1c0042_set_agc(fe, QM1D1C0042_AGC_MANUAL)	||
+		qm1d1c0042_write(fe, 0x05, qm->reg[0x05])	||
+		qm1d1c0042_write(fe, 0x01, qm->reg[0x01])	||
+		qm1d1c0042_fe_write_data(fe, 0x17, &buf, 1);
+}
+
+int qm1d1c0042_wakeup(struct dvb_frontend *fe)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	u8 buf = 0;
+	pr_debug("#%d %s\n", qm->idx, __func__);
+
+	qm->reg[0x01] |= 1 << 3;
+	qm->reg[0x01] &= (~(1 << 0)) & 0xff;
+	qm->reg[0x05] &= (~(1 << 3)) & 0xff;
+	return	qm1d1c0042_fe_write_data(fe, 0x17, &buf, 1)	||
+		qm1d1c0042_write(fe, 0x01, qm->reg[0x01])	||
+		qm1d1c0042_write(fe, 0x05, qm->reg[0x05]);
+}
+
+void qm1d1c0042_get_channel_freq(u32 channel, u32 *number, u32 *freq)
+{
+	if (channel < 12) {
+		*number = 1 + 2 * channel;
+		*freq = 104948 + 3836 * channel;
+	} else if (channel < 24) {
+		channel -= 12;
+		*number = 2 + 2 * channel;
+		*freq = 161300 + 4000 * channel;
+	} else {
+		channel -= 24;
+		*number = 1 + 2 * channel;
+		*freq = 159300 + 4000 * channel;
+	}
+}
+
+static const u32 qm1d1c0042_freq_tab[9][3] = {
+	{ 2151000, 1, 7 },
+	{ 1950000, 1, 6 },
+	{ 1800000, 1, 5 },
+	{ 1600000, 1, 4 },
+	{ 1450000, 1, 3 },
+	{ 1250000, 1, 2 },
+	{ 1200000, 0, 7 },
+	{  975000, 0, 6 },
+	{  950000, 0, 0 }
+};
+
+static const u32 qm1d1c0042_sd_tab[24][2][3] = {
+	{{0x38fae1, 0x0d, 0x5}, {0x39fae1, 0x0d, 0x5},},
+	{{0x3f570a, 0x0e, 0x3}, {0x00570a, 0x0e, 0x3},},
+	{{0x05b333, 0x0e, 0x5}, {0x06b333, 0x0e, 0x5},},
+	{{0x3c0f5c, 0x0f, 0x4}, {0x3d0f5c, 0x0f, 0x4},},
+	{{0x026b85, 0x0f, 0x6}, {0x036b85, 0x0f, 0x6},},
+	{{0x38c7ae, 0x10, 0x5}, {0x39c7ae, 0x10, 0x5},},
+	{{0x3f23d7, 0x11, 0x3}, {0x0023d7, 0x11, 0x3},},
+	{{0x058000, 0x11, 0x5}, {0x068000, 0x11, 0x5},},
+	{{0x3bdc28, 0x12, 0x4}, {0x3cdc28, 0x12, 0x4},},
+	{{0x023851, 0x12, 0x6}, {0x033851, 0x12, 0x6},},
+	{{0x38947a, 0x13, 0x5}, {0x39947a, 0x13, 0x5},},
+	{{0x3ef0a3, 0x14, 0x3}, {0x3ff0a3, 0x14, 0x3},},
+	{{0x3c8000, 0x16, 0x4}, {0x3d8000, 0x16, 0x4},},
+	{{0x048000, 0x16, 0x6}, {0x058000, 0x16, 0x6},},
+	{{0x3c8000, 0x17, 0x5}, {0x3d8000, 0x17, 0x5},},
+	{{0x048000, 0x18, 0x3}, {0x058000, 0x18, 0x3},},
+	{{0x3c8000, 0x18, 0x6}, {0x3d8000, 0x18, 0x6},},
+	{{0x048000, 0x19, 0x4}, {0x058000, 0x19, 0x4},},
+	{{0x3c8000, 0x1a, 0x3}, {0x3d8000, 0x1a, 0x3},},
+	{{0x048000, 0x1a, 0x5}, {0x058000, 0x1a, 0x5},},
+	{{0x3c8000, 0x1b, 0x4}, {0x3d8000, 0x1b, 0x4},},
+	{{0x048000, 0x1b, 0x6}, {0x058000, 0x1b, 0x6},},
+	{{0x3c8000, 0x1c, 0x5}, {0x3d8000, 0x1c, 0x5},},
+	{{0x048000, 0x1d, 0x3}, {0x058000, 0x1d, 0x3},},
+};
+
+static int qm1d1c0042_tuning(struct qm1d1c0042 *qm, u32 *sd, u32 channel)
+{
+	int ret;
+	struct dvb_frontend *fe = qm->fe;
+	u8 i_data;
+	u32 i, N, A, index = (qm->idx >> 1) & 1;
+
+	qm->reg[0x08] &= 0xf0;
+	qm->reg[0x08] |= 0x09;
+
+	qm->reg[0x13] &= 0x9f;
+	qm->reg[0x13] |= 0x20;
+
+	for (i = 0; i < 8; i++) {
+		if ((qm1d1c0042_freq_tab[i+1][0] <= qm->freq) && (qm->freq < qm1d1c0042_freq_tab[i][0])) {
+			i_data = qm->reg[0x02];
+			i_data &= 0x0f;
+			i_data |= qm1d1c0042_freq_tab[i][1] << 7;
+			i_data |= qm1d1c0042_freq_tab[i][2] << 4;
+			qm1d1c0042_write(fe, 0x02, i_data);
+		}
+	}
+
+	*sd = qm1d1c0042_sd_tab[channel][index][0];
+	N = qm1d1c0042_sd_tab[channel][index][1];
+	A = qm1d1c0042_sd_tab[channel][index][2];
+
+	qm->reg[0x06] &= 0x40;
+	qm->reg[0x06] |= N;
+	ret = qm1d1c0042_write(fe, 0x06, qm->reg[0x06]);
+	if (ret)
+		return ret;
+
+	qm->reg[0x07] &= 0xf0;
+	qm->reg[0x07] |= A & 0x0f;
+	return qm1d1c0042_write(fe, 0x07, qm->reg[0x07]);
+}
+
+static int qm1d1c0042_local_lpf_tuning(struct qm1d1c0042 *qm, u32 channel)
+{
+	struct dvb_frontend *fe = qm->fe;
+	u8 i_data;
+	u32 sd = 0;
+	int ret = qm1d1c0042_tuning(qm, &sd, channel);
+	if (ret)
+		return ret;
+
+	i_data = qm->reg[0x08] & 0xf0;
+	i_data |= 2;
+	ret = qm1d1c0042_write(fe, 0x08, i_data);
+	if (ret)
+		return ret;
+
+	qm->reg[0x09] &= 0xc0;
+	qm->reg[0x09] |= (sd >> 16) & 0x3f;
+	qm->reg[0x0a] = (sd >> 8) & 0xff;
+	qm->reg[0x0b] = (sd >> 0) & 0xff;
+	ret =	qm1d1c0042_write(fe, 0x09, qm->reg[0x09])	||
+		qm1d1c0042_write(fe, 0x0a, qm->reg[0x0a])	||
+		qm1d1c0042_write(qm->fe, 0x0b, qm->reg[0x0b]);
+	if (ret)
+		return ret;
+
+	i_data = qm->reg[0x0c];
+	i_data &= 0x3f;
+	ret = qm1d1c0042_write(fe, 0x0c, i_data);
+	if (ret)
+		return ret;
+	msleep_interruptible(1);
+
+	i_data = qm->reg[0x0c];
+	i_data |= 0xc0;
+	return	qm1d1c0042_write(fe, 0x0c, i_data)	||
+		qm1d1c0042_write(fe, 0x08, 0x09)	||
+		qm1d1c0042_write(fe, 0x13, qm->reg[0x13]);
+}
+
+int qm1d1c0042_get_locked(struct qm1d1c0042 *qm, bool *locked)
+{
+	int ret = qm1d1c0042_fe_read(qm->fe, 0x0d, &qm->reg[0x0d]);
+	if (ret)
+		return ret;
+	if (qm->reg[0x0d] & 0x40)
+		*locked = true;
+	else
+		*locked = false;
+	return ret;
+}
+
+u32 qm1d1c0042_freq2ch(u32 frequency)
+{
+	u32 freq = frequency / 10,
+	    ch0 = (freq - 104948) / 3836, diff0 = freq - (104948 + 3836 * ch0),
+	    ch1 = (freq - 161300) / 4000, diff1 = freq - (161300 + 4000 * ch1),
+	    ch2 = (freq - 159300) / 4000, diff2 = freq - (159300 + 4000 * ch2),
+	    min = diff0 < diff1 ? diff0 : diff1;
+
+	if (frequency < 1024)
+		return frequency;	/* consider as channel ID if low */
+	if (diff2 < min)
+		return ch2 + 24;
+	else if (min == diff1)
+		return ch1 + 12;
+	else
+		return ch0;
+}
+
+int qm1d1c0042_set_freq(struct dvb_frontend *fe, u32 frequency)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	u32 channel = qm1d1c0042_freq2ch(frequency);
+	u32 number, freq, freq_kHz;
+	bool locked = false;
+	unsigned long timeout;
+	int err = qm1d1c0042_set_agc(fe, QM1D1C0042_AGC_MANUAL);
+	if (err)
+		return err;
+
+	qm1d1c0042_get_channel_freq(channel, &number, &freq);
+	freq_kHz = freq * 10;
+	if (((qm->idx >> 1) & 1) == 0)
+		freq_kHz -= 500;
+	else
+		freq_kHz += 500;
+	qm->freq = freq_kHz;
+	pr_debug("#%d ch %d freq %d kHz\n", qm->idx, channel, freq_kHz);
+
+	err = qm1d1c0042_local_lpf_tuning(qm, channel);
+	if (err)
+		return err;
+
+	timeout = jiffies + msecs_to_jiffies(1000);	/* 1s */
+	while (time_before(jiffies, timeout)) {
+		err = qm1d1c0042_get_locked(qm, &locked);
+		if (err)
+			return err;
+		if (locked)
+			break;
+		msleep_interruptible(1);
+	}
+	pr_debug("#%d %s %s\n", qm->idx, __func__, locked ? "LOCKED" : "TIMEOUT");
+	return locked ? qm1d1c0042_set_agc(fe, QM1D1C0042_AGC_AUTO) : -ETIMEDOUT;
+}
+
+int qm1d1c0042_release(struct dvb_frontend *fe)
+{
+	kfree(fe->tuner_priv);
+	fe->tuner_priv = NULL;
+	return 0;
+}
+
+static struct dvb_tuner_ops qm1d1c0042_ops = {
+	.info = {
+		.frequency_min	= 1,		/* actually 1024 kHz, freq below that is handled as ch */
+		.frequency_max	= 2150000,	/* kHz */
+		.frequency_step	= 1000,		/* = 1 MHz */
+	},
+	.set_frequency = qm1d1c0042_set_freq,
+	.sleep = qm1d1c0042_sleep,
+	.init = qm1d1c0042_wakeup,
+	.release = qm1d1c0042_release,
+};
+
+int qm1d1c0042_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
+{
+	u8 d[] = { 0x10, 0x15, 0x04 };
+	struct qm1d1c0042 *qm = kzalloc(sizeof(struct qm1d1c0042), GFP_KERNEL);
+	if (!qm)
+		return -ENOMEM;
+	fe->tuner_priv = qm;
+	qm->fe = fe;
+	qm->idx = idx;
+	qm->addr_tuner = addr_tuner;
+	memcpy(&fe->ops.tuner_ops, &qm1d1c0042_ops, sizeof(struct dvb_tuner_ops));
+
+	memcpy(qm->reg, qm1d1c0042_reg_rw, sizeof(qm1d1c0042_reg_rw));
+	qm->freq = 0;
+
+	return	qm1d1c0042_fe_write_data(fe, 0x1e, d,   1)	||
+		qm1d1c0042_fe_write_data(fe, 0x1c, d+1, 1)	||
+		qm1d1c0042_fe_write_data(fe, 0x1f, d+2, 1);
+}
+EXPORT_SYMBOL(qm1d1c0042_attach);
+
diff --git a/drivers/media/tuners/qm1d1c0042.h b/drivers/media/tuners/qm1d1c0042.h
new file mode 100644
index 0000000..2b14b70
--- /dev/null
+++ b/drivers/media/tuners/qm1d1c0042.h
@@ -0,0 +1,36 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-S tuner driver QM1D1C0042
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 __QM1D1C0042_H__
+#define __QM1D1C0042_H__
+
+#ifndef pr_fmt
+ #define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+#endif
+#include "dvb_frontend.h"
+
+#if IS_ENABLED(CONFIG_MEDIA_TUNER_QM1D1C0042)
+extern int qm1d1c0042_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner);
+#else
+static inline int qm1d1c0042_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
+{
+	pr_warn("%s: driver disabled by Kconfig\n", __func__);
+	return 0;
+}
+#endif
+
+#endif
+
-- 
1.8.4.5


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH] Full DVB driver package for Earthsoft PT3 (ISDB-S/T) cards
@ 2014-04-10 16:06 буди Романто
  0 siblings, 0 replies; 21+ messages in thread
From: буди Романто @ 2014-04-10 16:06 UTC (permalink / raw)
  To: linux-media
  Cc: буди
	Романто,
	crope, m.chehab, hdegoede, hverkuil, laurent.pinchart, mkrufky,
	sylvester.nawrocki, g.liakhovetski, peter.senna

From: буди Роман то <knightrider@are.ma>

DVB driver for Earthsoft PT3 (ISDB-S/T) receiver PCI Express cards.
It behaves similarly as PT1 DVB, plus some tuning enhancements:
1. in addition to the real frequency:
	ISDB-S : freq. channel ID
	ISDB-T : freq# (I/O# +128), ch#, ch# +64 for CATV
2. in addition to TSID:
	ISDB-S : slot#

Feature changes:
- dropped DKMS & standalone compile
- dropped verbosity (debug levels), use single level -DDEBUG instead
- changed SNR (.read_snr) to CNR (.read_signal_strength)
- moved demodulator FE to drivers/media/dvb-frontends
- moved tuners to drivers/media/tuners
- translated to standard (?) I2C protocol
- dropped FE's LNB control & other unused features
- added DVBv5 stat support for CNR

The full package (buildable as standalone, DKMS or tree embedded module) is available at
https://github.com/knight-rider/ptx/tree/master/pt3_dvb

Signed-off-by: буди Романто <knightrider@are.ma>
---
 drivers/media/dvb-frontends/Kconfig   |  11 +-
 drivers/media/dvb-frontends/Makefile  |   1 +
 drivers/media/dvb-frontends/tc90522.c | 546 ++++++++++++++++++++++++++++++++++
 drivers/media/dvb-frontends/tc90522.h |  36 +++
 drivers/media/pci/Kconfig             |   2 +-
 drivers/media/pci/Makefile            |   1 +
 drivers/media/pci/pt3/Kconfig         |  11 +
 drivers/media/pci/pt3/Makefile        |   6 +
 drivers/media/pci/pt3/pt3_common.h    |  85 ++++++
 drivers/media/pci/pt3/pt3_dma.c       | 348 ++++++++++++++++++++++
 drivers/media/pci/pt3/pt3_dma.h       |  50 ++++
 drivers/media/pci/pt3/pt3_i2c.c       | 189 ++++++++++++
 drivers/media/pci/pt3/pt3_i2c.h       |  25 ++
 drivers/media/pci/pt3/pt3_pci.c       | 388 ++++++++++++++++++++++++
 drivers/media/tuners/Kconfig          |  15 +
 drivers/media/tuners/Makefile         |   2 +
 drivers/media/tuners/mxl301rf.c       | 390 ++++++++++++++++++++++++
 drivers/media/tuners/mxl301rf.h       |  36 +++
 drivers/media/tuners/qm1d1c0042.c     | 382 ++++++++++++++++++++++++
 drivers/media/tuners/qm1d1c0042.h     |  36 +++
 20 files changed, 2558 insertions(+), 2 deletions(-)
 create mode 100644 drivers/media/dvb-frontends/tc90522.c
 create mode 100644 drivers/media/dvb-frontends/tc90522.h
 create mode 100644 drivers/media/pci/pt3/Kconfig
 create mode 100644 drivers/media/pci/pt3/Makefile
 create mode 100644 drivers/media/pci/pt3/pt3_common.h
 create mode 100644 drivers/media/pci/pt3/pt3_dma.c
 create mode 100644 drivers/media/pci/pt3/pt3_dma.h
 create mode 100644 drivers/media/pci/pt3/pt3_i2c.c
 create mode 100644 drivers/media/pci/pt3/pt3_i2c.h
 create mode 100644 drivers/media/pci/pt3/pt3_pci.c
 create mode 100644 drivers/media/tuners/mxl301rf.c
 create mode 100644 drivers/media/tuners/mxl301rf.h
 create mode 100644 drivers/media/tuners/qm1d1c0042.c
 create mode 100644 drivers/media/tuners/qm1d1c0042.h

diff --git a/drivers/media/dvb-frontends/Kconfig b/drivers/media/dvb-frontends/Kconfig
index 025fc54..0047b3f 100644
--- a/drivers/media/dvb-frontends/Kconfig
+++ b/drivers/media/dvb-frontends/Kconfig
@@ -591,7 +591,7 @@ config DVB_S5H1411
 	  An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want
 	  to support this frontend.
 
-comment "ISDB-T (terrestrial) frontends"
+comment "ISDB-S (satellite) & ISDB-T (terrestrial) frontends"
 	depends on DVB_CORE
 
 config DVB_S921
@@ -618,6 +618,15 @@ config DVB_MB86A20S
 	  A driver for Fujitsu mb86a20s ISDB-T/ISDB-Tsb demodulator.
 	  Say Y when you want to support this frontend.
 
+config DVB_TC90522
+	tristate "Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S)"
+	depends on DVB_CORE && I2C
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S) DVB demodulator
+	  frontend for Earthsoft PT3 PCIE cards.
+	  Say Y when you want to support this frontend.
+
 comment "Digital terrestrial only tuners/PLL"
 	depends on DVB_CORE
 
diff --git a/drivers/media/dvb-frontends/Makefile b/drivers/media/dvb-frontends/Makefile
index 282aba2..a80d212 100644
--- a/drivers/media/dvb-frontends/Makefile
+++ b/drivers/media/dvb-frontends/Makefile
@@ -105,4 +105,5 @@ obj-$(CONFIG_DVB_RTL2830) += rtl2830.o
 obj-$(CONFIG_DVB_RTL2832) += rtl2832.o
 obj-$(CONFIG_DVB_M88RS2000) += m88rs2000.o
 obj-$(CONFIG_DVB_AF9033) += af9033.o
+obj-$(CONFIG_DVB_TC90522) += tc90522.o
 
diff --git a/drivers/media/dvb-frontends/tc90522.c b/drivers/media/dvb-frontends/tc90522.c
new file mode 100644
index 0000000..1ed3eb3
--- /dev/null
+++ b/drivers/media/dvb-frontends/tc90522.c
@@ -0,0 +1,546 @@
+/*
+ * Earthsoft PT3 demodulator frontend Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S)
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "dvb_math.h"
+#include "tc90522.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 Toshiba TC90522 OFDM(ISDB-T)/8PSK(ISDB-S) demodulator");
+MODULE_LICENSE("GPL");
+
+#define TC90522_PASSTHROUGH 0xfe
+
+enum tc90522_state {
+	TC90522_IDLE,
+	TC90522_SET_FREQUENCY,
+	TC90522_SET_MODULATION,
+	TC90522_TRACK,
+	TC90522_ABORT,
+};
+
+struct tc90522 {
+	struct dvb_frontend fe;
+	struct i2c_adapter *i2c;
+	fe_delivery_system_t type;
+	u8 idx, addr_demod;
+	s32 offset;
+	enum tc90522_state state;
+};
+
+int tc90522_write(struct dvb_frontend *fe, const u8 *data, int len)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	struct i2c_msg msg[3];
+	u8 buf[6];
+
+	if (data) {
+		msg[0].addr = demod->addr_demod;
+		msg[0].buf = (u8 *)data;
+		msg[0].flags = 0;			/* write */
+		msg[0].len = len;
+
+		return i2c_transfer(demod->i2c, msg, 1) == 1 ? 0 : -EREMOTEIO;
+	} else {
+		u8 addr_tuner = (len >> 8) & 0xff,
+		   addr_data = len & 0xff;
+		if (len >> 16) {			/* read tuner without address */
+			buf[0] = TC90522_PASSTHROUGH;
+			buf[1] = (addr_tuner << 1) | 1;
+			msg[0].buf = buf;
+			msg[0].len = 2;
+			msg[0].addr = demod->addr_demod;
+			msg[0].flags = 0;		/* write */
+
+			msg[1].buf = buf + 2;
+			msg[1].len = 1;
+			msg[1].addr = demod->addr_demod;
+			msg[1].flags = I2C_M_RD;	/* read */
+
+			return i2c_transfer(demod->i2c, msg, 2) == 2 ? buf[2] : -EREMOTEIO;
+		} else {				/* read tuner */
+			buf[0] = TC90522_PASSTHROUGH;
+			buf[1] = addr_tuner << 1;
+			buf[2] = addr_data;
+			msg[0].buf = buf;
+			msg[0].len = 3;
+			msg[0].addr = demod->addr_demod;
+			msg[0].flags = 0;		/* write */
+
+			buf[3] = TC90522_PASSTHROUGH;
+			buf[4] = (addr_tuner << 1) | 1;
+			msg[1].buf = buf + 3;
+			msg[1].len = 2;
+			msg[1].addr = demod->addr_demod;
+			msg[1].flags = 0;		/* write */
+
+			msg[2].buf = buf + 5;
+			msg[2].len = 1;
+			msg[2].addr = demod->addr_demod;
+			msg[2].flags = I2C_M_RD;	/* read */
+
+			return i2c_transfer(demod->i2c, msg, 3) == 3 ? buf[5] : -EREMOTEIO;
+		}
+	}
+}
+
+int tc90522_write_data(struct dvb_frontend *fe, u8 addr_data, u8 *data, u8 len)
+{
+	u8 buf[len + 1];
+	buf[0] = addr_data;
+	memcpy(buf + 1, data, len);
+	return tc90522_write(fe, buf, len + 1);
+}
+
+int tc90522_read(struct tc90522 *demod, u8 addr, u8 *buf, u8 buflen)
+{
+	struct i2c_msg msg[2];
+	if (!buf || !buflen)
+		return -EINVAL;
+
+	buf[0] = addr;
+	msg[0].addr = demod->addr_demod;
+	msg[0].flags = 0;			/* write */
+	msg[0].buf = buf;
+	msg[0].len = 1;
+
+	msg[1].addr = demod->addr_demod;
+	msg[1].flags = I2C_M_RD;		/* read */
+	msg[1].buf = buf;
+	msg[1].len = buflen;
+
+	return i2c_transfer(demod->i2c, msg, 2) == 2 ? 0 : -EREMOTEIO;
+}
+
+u32 tc90522_byten(const u8 *data, u32 n)
+{
+	u32 i, val = 0;
+
+	for (i = 0; i < n; i++) {
+		val <<= 8;
+		val |= data[i];
+	}
+	return val;
+}
+
+int tc90522_read_id_s(struct tc90522 *demod, u16 *id)
+{
+	u8 buf[2];
+	int ret = tc90522_read(demod, 0xe6, buf, 2);
+	if (!ret)
+		*id = tc90522_byten(buf, 2);
+	return ret;
+}
+
+struct tmcc_s {			/* Transmission and Multiplexing Configuration Control */
+	u32 mode[4];
+	u32 slot[4];
+	u32 id[8];
+};
+
+int tc90522_read_tmcc_s(struct tc90522 *demod, struct tmcc_s *tmcc)
+{
+	enum {
+		BASE = 0xc5,
+		SIZE = 0xe5 - BASE + 1
+	};
+	u8 data[SIZE];
+	u32 i, byte_offset, bit_offset;
+
+	int err = tc90522_read(demod, 0xc3, data, 1)	||
+		((data[0] >> 4) & 1)			||
+		tc90522_read(demod, 0xce, data, 2)	||
+		(tc90522_byten(data, 2) == 0)		||
+		tc90522_read(demod, 0xc3, data, 1)	||
+		tc90522_read(demod, 0xc5, data, SIZE);
+	if (err)
+		return err;
+	for (i = 0; i < 4; i++) {
+		byte_offset = i >> 1;
+		bit_offset = (i & 1) ? 0 : 4;
+		tmcc->mode[i] = (data[0xc8 + byte_offset - BASE] >> bit_offset) & 0b00001111;
+		tmcc->slot[i] = (data[0xca + i           - BASE] >>          0) & 0b00111111;
+	}
+	for (i = 0; i < 8; i++)
+		tmcc->id[i] = tc90522_byten(data + 0xce + i * 2 - BASE, 2);
+	return 0;
+}
+
+enum tc90522_pwr {
+	TC90522_PWR_OFF		= 0x00,
+	TC90522_PWR_AMP_ON	= 0x04,
+	TC90522_PWR_TUNER_ON	= 0x40,
+};
+
+static enum tc90522_pwr tc90522_pwr = TC90522_PWR_OFF;
+
+int tc90522_set_powers(struct tc90522 *demod, enum tc90522_pwr pwr)
+{
+	u8 data = pwr | 0b10011001;
+	pr_debug("#%d tuner %s amp %s\n", demod->idx, pwr & TC90522_PWR_TUNER_ON ? "ON" : "OFF", pwr & TC90522_PWR_AMP_ON ? "ON" : "OFF");
+	tc90522_pwr = pwr;
+	return tc90522_write_data(&demod->fe, 0x1e, &data, 1);
+}
+
+/* dvb_frontend_ops */
+int tc90522_get_frontend_algo(struct dvb_frontend *fe)
+{
+	return DVBFE_ALGO_HW;
+}
+
+int tc90522_sleep(struct dvb_frontend *fe)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	pr_debug("#%d %s %s\n", demod->idx, __func__, demod->type == SYS_ISDBS ? "S" : "T");
+	return fe->ops.tuner_ops.sleep(fe);
+}
+
+int tc90522_wakeup(struct dvb_frontend *fe)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	pr_debug("#%d %s %s 0x%x\n", demod->idx, __func__, demod->type == SYS_ISDBS ? "S" : "T", tc90522_pwr);
+
+	if (!tc90522_pwr)
+		return	tc90522_set_powers(demod, TC90522_PWR_TUNER_ON)	||
+			i2c_transfer(demod->i2c, NULL, 0)		||
+			tc90522_set_powers(demod, TC90522_PWR_TUNER_ON | TC90522_PWR_AMP_ON);
+	demod->state = TC90522_IDLE;
+	return fe->ops.tuner_ops.init(fe);
+}
+
+void tc90522_release(struct dvb_frontend *fe)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	pr_debug("#%d %s\n", demod->idx, __func__);
+
+	if (tc90522_pwr)
+		tc90522_set_powers(demod, TC90522_PWR_OFF);
+	tc90522_sleep(fe);
+	fe->ops.tuner_ops.release(fe);
+	kfree(demod);
+}
+
+s64 tc90522_get_cn_raw(struct tc90522 *demod)
+{
+	u8 buf[3], buflen = demod->type == SYS_ISDBS ? 2 : 3, addr = demod->type == SYS_ISDBS ? 0xbc : 0x8b;
+	int err = tc90522_read(demod, addr, buf, buflen);
+	return err < 0 ? err : tc90522_byten(buf, buflen);
+}
+
+s64 tc90522_get_cn_s(s64 raw)	/* @ .0001 dB */
+{
+	s64 x1, x2, x3, x4, x5, y;
+
+	raw -= 3000;
+	if (raw < 0)
+		raw = 0;
+
+	x1 = int_sqrt(raw << 16) * ((12700ll << 21) / 1000000);
+	x2 = x1 * x1 >> 31;
+	x3 = x2 * x1 >> 31;
+	x4 = x2 * x2 >> 31;
+	x5 = x4 * x1 >> 31;
+
+	y = (58857ll << 23) / 1000;
+	y -= x1 * ((89565ll << 24) / 1000) >> 30;
+	y += x2 * ((88977ll << 24) / 1000) >> 28;
+	y -= x3 * ((50259ll << 25) / 1000) >> 27;
+	y += x4 * ((14341ll << 27) / 1000) >> 27;
+	y -= x5 * ((16346ll << 30) / 10000) >> 28;
+	return y < 0 ? 0 : y >> 10;
+}
+
+s64 tc90522_get_cn_t(s64 raw)	/* @ .0001 dB */
+{
+	s64 x, y;
+	if (!raw)
+		return 0;
+
+	x = 10 * (intlog10(0x69000000 / raw) - (2 << 24));
+	y = (24ll << 46) / 1000000;
+	y = (y * x >> 30) - (16ll << 40) / 10000;
+	y = (y * x >> 29) + (398ll << 35) / 10000;
+	y = (y * x >> 30) + (5491ll << 29) / 10000;
+	y = (y * x >> 30) + (30965ll << 23) / 10000;
+	return y >> 10;
+}
+
+int tc90522_read_signal_strength(struct dvb_frontend *fe, u16 *cn)	/* raw C/N */
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	s64 ret = tc90522_get_cn_raw(demod);
+	*cn = ret < 0 ? 0 : ret;
+	pr_debug("CN %d (%d dB)\n", (int)*cn, demod->type == SYS_ISDBS ? (int)tc90522_get_cn_s(*cn) : (int)tc90522_get_cn_t(*cn));
+	return ret < 0 ? ret : 0;
+}
+
+int tc90522_read_status(struct dvb_frontend *fe, fe_status_t *status)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+	s64 ret = tc90522_get_cn_raw(demod),
+	    raw = ret < 0 ? 0 : ret;
+
+	switch (demod->state) {
+	case TC90522_IDLE:
+	case TC90522_SET_FREQUENCY:
+		*status = 0;
+		break;
+
+	case TC90522_SET_MODULATION:
+	case TC90522_ABORT:
+		*status |= FE_HAS_SIGNAL;
+		break;
+
+	case TC90522_TRACK:
+		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		break;
+	}
+
+	c->cnr.stat[0].svalue = demod->type == SYS_ISDBS ? tc90522_get_cn_s(raw) : tc90522_get_cn_t(raw);
+	c->cnr.stat[0].scale = FE_SCALE_DECIBEL;
+	return ret < 0 ? ret : 0;
+}
+
+/**** ISDB-S ****/
+int tc90522_write_id_s(struct dvb_frontend *fe, u16 id)
+{
+	u8 data[2] = { id >> 8, (u8)id };
+	return tc90522_write_data(fe, 0x8f, data, sizeof(data));
+}
+
+int tc90522_tune_s(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	struct tmcc_s tmcc;
+	int i, ret,
+	    freq = fe->dtv_property_cache.frequency,
+	    tsid = fe->dtv_property_cache.stream_id;
+
+	if (re_tune)
+		demod->state = TC90522_SET_FREQUENCY;
+
+	switch (demod->state) {
+	case TC90522_IDLE:
+		*delay = msecs_to_jiffies(3000);
+		*status = 0;
+		return 0;
+
+	case TC90522_SET_FREQUENCY:
+		pr_debug("#%d tsid 0x%x freq %d\n", demod->idx, tsid, freq);
+		ret = fe->ops.tuner_ops.set_frequency(fe, freq);
+		if (ret)
+			return ret;
+		demod->offset = 0;
+		demod->state = TC90522_SET_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case TC90522_SET_MODULATION:
+		for (i = 0; i < 1000; i++) {
+			ret = tc90522_read_tmcc_s(demod, &tmcc);
+			if (!ret)
+				break;
+			msleep_interruptible(1);
+		}
+		if (ret) {
+			pr_debug("fail tc_read_tmcc_s ret=0x%x\n", ret);
+			demod->state = TC90522_ABORT;
+			*delay = msecs_to_jiffies(1000);
+			return ret;
+		}
+		pr_debug("slots=%d,%d,%d,%d mode=%d,%d,%d,%d tmcc.id=0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x\n",
+				tmcc.slot[0], tmcc.slot[1], tmcc.slot[2], tmcc.slot[3],
+				tmcc.mode[0], tmcc.mode[1], tmcc.mode[2], tmcc.mode[3],
+				tmcc.id[0], tmcc.id[1], tmcc.id[2], tmcc.id[3],
+				tmcc.id[4], tmcc.id[5], tmcc.id[6], tmcc.id[7]);
+		for (i = 0; i < sizeof(tmcc.id)/sizeof(tmcc.id[0]); i++) {
+			pr_debug("tsid %x i %d tmcc.id %x\n", tsid, i, tmcc.id[i]);
+			if (tmcc.id[i] == tsid)
+				break;
+		}
+		if (tsid < sizeof(tmcc.id)/sizeof(tmcc.id[0]))	/* consider as slot# */
+			i = tsid;
+		if (i == sizeof(tmcc.id)/sizeof(tmcc.id[0])) {
+			pr_debug("#%d i%d tsid 0x%x not found\n", demod->idx, i, tsid);
+			return -EINVAL;
+		}
+		demod->offset = i;
+		pr_debug("#%d found tsid 0x%x on slot %d\n", demod->idx, tsid, i);
+		ret = tc90522_write_id_s(fe, (u16)tmcc.id[demod->offset]);
+		if (ret) {
+			pr_debug("fail set_tmcc_s ret=%d\n", ret);
+			return ret;
+		}
+		for (i = 0; i < 1000; i++) {
+			u16 short_id;
+			ret = tc90522_read_id_s(demod, &short_id);
+			if (ret) {
+				pr_debug("fail get_id_s ret=%d\n", ret);
+				return ret;
+			}
+			tsid = short_id;
+			pr_debug("#%d tsid=0x%x\n", demod->idx, tsid);
+			if ((tsid & 0xffff) == tmcc.id[demod->offset])
+				break;
+			msleep_interruptible(1);
+		}
+		demod->state = TC90522_TRACK;
+		/* fallthrough */
+
+	case TC90522_TRACK:
+		*delay = msecs_to_jiffies(3000);
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+
+	case TC90522_ABORT:
+		*delay = msecs_to_jiffies(3000);
+		*status = FE_HAS_SIGNAL;
+		return 0;
+	}
+	return -ERANGE;
+}
+
+static struct dvb_frontend_ops tc90522_ops_s = {
+	.delsys = { SYS_ISDBS },
+	.info = {
+		.name = "TC90522 ISDB-S",
+		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO | FE_CAN_MULTISTREAM |
+			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
+	},
+	.init = tc90522_wakeup,
+	.sleep = tc90522_sleep,
+	.release = tc90522_release,
+	.write = tc90522_write,
+	.get_frontend_algo = tc90522_get_frontend_algo,
+	.read_signal_strength = tc90522_read_signal_strength,
+	.read_status = tc90522_read_status,
+	.tune = tc90522_tune_s,
+};
+
+/**** ISDB-T ****/
+int tc90522_get_tmcc_t(struct tc90522 *demod)
+{
+	u8 buf;
+	bool b = false, retryov, fulock;
+
+	while (1) {
+		if (tc90522_read(demod, 0x80, &buf, 1))
+			return -EBADMSG;
+		retryov = buf & 0b10000000 ? true : false;
+		fulock  = buf & 0b00001000 ? true : false;
+		if (!fulock) {
+			b = true;
+			break;
+		} else {
+			if (retryov)
+				break;
+		}
+		msleep_interruptible(1);
+	}
+	return b ? 0 : -EBADMSG;
+}
+
+int tc90522_tune_t(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	int ret, i;
+
+	if (re_tune)
+		demod->state = TC90522_SET_FREQUENCY;
+
+	switch (demod->state) {
+	case TC90522_IDLE:
+		*delay = msecs_to_jiffies(3000);
+		*status = 0;
+		return 0;
+
+	case TC90522_SET_FREQUENCY:
+		if (fe->ops.tuner_ops.set_frequency(fe, fe->dtv_property_cache.frequency)) {
+			*delay = msecs_to_jiffies(1000);
+			*status = 0;
+			return 0;
+		}
+		demod->state = TC90522_SET_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case TC90522_SET_MODULATION:
+		for (i = 0; i < 1000; i++) {
+			ret = tc90522_get_tmcc_t(demod);
+			if (!ret)
+				break;
+			msleep_interruptible(2);
+		}
+		if (ret) {
+			pr_debug("#%d fail get_tmcc_t ret=%d\n", demod->idx, ret);
+				demod->state = TC90522_ABORT;
+				*delay = msecs_to_jiffies(1000);
+				return 0;
+		}
+		demod->state = TC90522_TRACK;
+		/* fallthrough */
+
+	case TC90522_TRACK:
+		*delay = msecs_to_jiffies(3000);
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+
+	case TC90522_ABORT:
+		*delay = msecs_to_jiffies(3000);
+		*status = FE_HAS_SIGNAL;
+		return 0;
+	}
+	return -ERANGE;
+}
+
+static struct dvb_frontend_ops tc90522_ops_t = {
+	.delsys = { SYS_ISDBT },
+	.info = {
+		.name = "TC90522 ISDB-T",
+		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO |
+			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
+	},
+	.init = tc90522_wakeup,
+	.sleep = tc90522_sleep,
+	.release = tc90522_release,
+	.write = tc90522_write,
+	.get_frontend_algo = tc90522_get_frontend_algo,
+	.read_signal_strength = tc90522_read_signal_strength,
+	.read_status = tc90522_read_status,
+	.tune = tc90522_tune_t,
+};
+
+/**** Common ****/
+struct dvb_frontend *tc90522_attach(struct i2c_adapter *i2c, u8 idx, fe_delivery_system_t type, u8 addr_demod)
+{
+	struct dvb_frontend *fe;
+	struct tc90522 *demod = kzalloc(sizeof(struct tc90522), GFP_KERNEL);
+	if (!demod)
+		return NULL;
+
+	demod->i2c	= i2c;
+	demod->idx	= idx;
+	demod->type	= type;
+	demod->addr_demod = addr_demod;
+	fe = &demod->fe;
+	memcpy(&fe->ops, (demod->type == SYS_ISDBS) ? &tc90522_ops_s : &tc90522_ops_t, sizeof(struct dvb_frontend_ops));
+	fe->demodulator_priv = demod;
+	return fe;
+}
+EXPORT_SYMBOL(tc90522_attach);
+
diff --git a/drivers/media/dvb-frontends/tc90522.h b/drivers/media/dvb-frontends/tc90522.h
new file mode 100644
index 0000000..78c5298
--- /dev/null
+++ b/drivers/media/dvb-frontends/tc90522.h
@@ -0,0 +1,36 @@
+/*
+ * Earthsoft PT3 demodulator frontend Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S)
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__TC90522_H__
+#define	__TC90522_H__
+
+#ifndef pr_fmt
+ #define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+#endif
+#include "dvb_frontend.h"
+
+#if IS_ENABLED(CONFIG_DVB_TC90522)
+extern struct dvb_frontend *tc90522_attach(struct i2c_adapter *i2c, u8 idx, fe_delivery_system_t type, u8 addr_demod);
+#else
+static inline struct dvb_frontend *tc90522_attach(struct i2c_adapter *i2c, u8 idx, fe_delivery_system_t type, u8 addr_demod)
+{
+	pr_warn("%s: driver disabled by Kconfig\n", __func__);
+	return NULL;
+}
+#endif
+
+#endif
+
diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
index 53196f1..87018c8 100644
--- a/drivers/media/pci/Kconfig
+++ b/drivers/media/pci/Kconfig
@@ -30,7 +30,6 @@ source "drivers/media/pci/cx88/Kconfig"
 source "drivers/media/pci/bt8xx/Kconfig"
 source "drivers/media/pci/saa7134/Kconfig"
 source "drivers/media/pci/saa7164/Kconfig"
-
 endif
 
 if MEDIA_DIGITAL_TV_SUPPORT
@@ -40,6 +39,7 @@ source "drivers/media/pci/b2c2/Kconfig"
 source "drivers/media/pci/pluto2/Kconfig"
 source "drivers/media/pci/dm1105/Kconfig"
 source "drivers/media/pci/pt1/Kconfig"
+source "drivers/media/pci/pt3/Kconfig"
 source "drivers/media/pci/mantis/Kconfig"
 source "drivers/media/pci/ngene/Kconfig"
 source "drivers/media/pci/ddbridge/Kconfig"
diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
index 35cc578..f7be6bc 100644
--- a/drivers/media/pci/Makefile
+++ b/drivers/media/pci/Makefile
@@ -7,6 +7,7 @@ obj-y        +=	ttpci/		\
 		pluto2/		\
 		dm1105/		\
 		pt1/		\
+		pt3/		\
 		mantis/		\
 		ngene/		\
 		ddbridge/	\
diff --git a/drivers/media/pci/pt3/Kconfig b/drivers/media/pci/pt3/Kconfig
new file mode 100644
index 0000000..0d866a0
--- /dev/null
+++ b/drivers/media/pci/pt3/Kconfig
@@ -0,0 +1,11 @@
+config PT3_DVB
+	tristate "Earthsoft PT3 ISDB-S/T cards"
+	depends on DVB_CORE && PCI
+	select DVB_TC90522 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_QM1D1C0042 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MXL301RF if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Support for Earthsoft PT3 PCI-Express cards.
+	  You need to enable frontend (TC90522) & tuners (QM1D1C0042, MXL301RF)
+	  Say Y or M if you own such a device and want to use it.
+
diff --git a/drivers/media/pci/pt3/Makefile b/drivers/media/pci/pt3/Makefile
new file mode 100644
index 0000000..ede00e1
--- /dev/null
+++ b/drivers/media/pci/pt3/Makefile
@@ -0,0 +1,6 @@
+pt3_dvb-objs := pt3_pci.o pt3_dma.o pt3_i2c.o
+
+obj-$(CONFIG_PT3_DVB) += pt3_dvb.o
+
+ccflags-y += -Idrivers/media/dvb-core -Idrivers/media/dvb-frontends -Idrivers/media/tuners
+
diff --git a/drivers/media/pci/pt3/pt3_common.h b/drivers/media/pci/pt3/pt3_common.h
new file mode 100644
index 0000000..7c1089f
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_common.h
@@ -0,0 +1,85 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_COMMON_H__
+#define	__PT3_COMMON_H__
+
+#define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+
+#include <linux/pci.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include "dvb_demux.h"
+#include "dmxdev.h"
+#include "dvb_frontend.h"
+
+#define DRV_NAME "pt3_dvb"
+
+/* register idx */
+#define PT3_REG_VERSION	0x00	/*	R	Version		*/
+#define PT3_REG_BUS	0x04	/*	R	Bus		*/
+#define PT3_REG_SYS_W	0x08	/*	W	System		*/
+#define PT3_REG_SYS_R	0x0c	/*	R	System		*/
+#define PT3_REG_I2C_W	0x10	/*	W	I2C		*/
+#define PT3_REG_I2C_R	0x14	/*	R	I2C		*/
+#define PT3_REG_RAM_W	0x18	/*	W	RAM		*/
+#define PT3_REG_RAM_R	0x1c	/*	R	RAM		*/
+#define PT3_REG_BASE	0x40	/* + 0x18*idx			*/
+#define PT3_REG_DMA_D_L	0x00	/*	W	DMA descriptor	*/
+#define PT3_REG_DMA_D_H	0x04	/*	W	DMA descriptor	*/
+#define PT3_REG_DMA_CTL	0x08	/*	W	DMA		*/
+#define PT3_REG_TS_CTL	0x0c	/*	W	TS		*/
+#define PT3_REG_STATUS	0x10	/*	R	DMA/FIFO/TS	*/
+#define PT3_REG_TS_ERR	0x14	/*	R	TS		*/
+
+struct pt3_adapter;
+
+struct pt3_board {
+	struct mutex lock;
+	int lnb;
+	bool reset;
+
+	struct pci_dev *pdev;
+	int bars;
+	void __iomem *bar_reg, *bar_mem;
+	struct i2c_adapter i2c;
+	u8 i2c_buf;
+	u32 i2c_addr;
+	bool i2c_filled;
+
+	struct pt3_adapter **adap;
+};
+
+struct pt3_adapter {
+	struct mutex lock;
+	struct pt3_board *pt3;
+
+	u8 idx;
+	bool sleep;
+	struct pt3_dma *dma;
+	struct task_struct *kthread;
+	struct dvb_adapter dvb;
+	struct dvb_demux demux;
+	struct dmxdev dmxdev;
+	int users;
+
+	struct dvb_frontend *fe;
+	int (*orig_sleep)(struct dvb_frontend *fe);
+	int (*orig_init)(struct dvb_frontend *fe);
+};
+
+#endif
+
diff --git a/drivers/media/pci/pt3/pt3_dma.c b/drivers/media/pci/pt3/pt3_dma.c
new file mode 100644
index 0000000..cb892ea
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_dma.c
@@ -0,0 +1,348 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_dma.h"
+
+#define PT3_DMA_MAX_DESCS	204
+#define PT3_DMA_PAGE_SIZE	(PT3_DMA_MAX_DESCS * sizeof(struct pt3_dma_desc))
+#define PT3_DMA_BLOCK_COUNT	17
+#define PT3_DMA_BLOCK_SIZE	(PT3_DMA_PAGE_SIZE * 47)
+#define PT3_DMA_TS_BUF_SIZE	(PT3_DMA_BLOCK_SIZE * PT3_DMA_BLOCK_COUNT)
+#define PT3_DMA_TS_SYNC		0x47
+#define PT3_DMA_TS_NOT_SYNC	0x74
+
+void pt3_dma_free(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *page;
+	u32 i;
+
+	if (dma->ts_info) {
+		for (i = 0; i < dma->ts_count; i++) {
+			page = &dma->ts_info[i];
+			if (page->data)
+				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
+		}
+		kfree(dma->ts_info);
+	}
+	if (dma->desc_info) {
+		for (i = 0; i < dma->desc_count; i++) {
+			page = &dma->desc_info[i];
+			if (page->data)
+				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
+		}
+		kfree(dma->desc_info);
+	}
+	kfree(dma);
+}
+
+struct pt3_dma_desc {
+	u64 page_addr;
+	u32 page_size;
+	u64 next_desc;
+} __packed;
+
+void pt3_dma_build_page_descriptor(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *desc_info, *ts_info;
+	u64 ts_addr, desc_addr;
+	u32 i, j, ts_size, desc_remain, ts_info_pos, desc_info_pos;
+	struct pt3_dma_desc *prev, *curr;
+
+	pr_debug("#%d %s ts_count=%d ts_size=%d desc_count=%d desc_size=%d\n",
+		dma->adap->idx, __func__, dma->ts_count, dma->ts_info[0].size, dma->desc_count, dma->desc_info[0].size);
+	desc_info_pos = ts_info_pos = 0;
+	desc_info = &dma->desc_info[desc_info_pos];
+	desc_addr   = desc_info->addr;
+	desc_remain = desc_info->size;
+	desc_info->data_pos = 0;
+	prev = NULL;
+	curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+	desc_info_pos++;
+
+	for (i = 0; i < dma->ts_count; i++) {
+		if (unlikely(ts_info_pos >= dma->ts_count)) {
+			pr_debug("#%d ts_info overflow max=%d curr=%d\n", dma->adap->idx, dma->ts_count, ts_info_pos);
+			return;
+		}
+		ts_info = &dma->ts_info[ts_info_pos];
+		ts_addr = ts_info->addr;
+		ts_size = ts_info->size;
+		ts_info_pos++;
+		pr_debug("#%d i=%d, ts_info addr=0x%llx ts_size=%d\n", dma->adap->idx, i, ts_addr, ts_size);
+		for (j = 0; j < ts_size / PT3_DMA_PAGE_SIZE; j++) {
+			if (desc_remain < sizeof(struct pt3_dma_desc)) {
+				if (unlikely(desc_info_pos >= dma->desc_count)) {
+					pr_debug("#%d desc_info overflow max=%d curr=%d\n",
+						dma->adap->idx, dma->desc_count, desc_info_pos);
+					return;
+				}
+				desc_info = &dma->desc_info[desc_info_pos];
+				desc_info->data_pos = 0;
+				curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+				pr_debug("#%d desc_info_pos=%d ts_addr=0x%llx remain=%d\n",
+					dma->adap->idx, desc_info_pos, ts_addr, desc_remain);
+				desc_addr = desc_info->addr;
+				desc_remain = desc_info->size;
+				desc_info_pos++;
+			}
+			if (prev)
+				prev->next_desc = desc_addr | 0b10;
+			curr->page_addr = ts_addr           | 0b111;
+			curr->page_size = PT3_DMA_PAGE_SIZE | 0b111;
+			curr->next_desc = 0b10;
+			pr_debug("#%d j=%d dma write desc ts_addr=0x%llx desc_info_pos=%d desc_remain=%d\n",
+				dma->adap->idx, j, ts_addr, desc_info_pos, desc_remain);
+			ts_addr += PT3_DMA_PAGE_SIZE;
+
+			prev = curr;
+			desc_info->data_pos += sizeof(struct pt3_dma_desc);
+			if (unlikely(desc_info->data_pos > desc_info->size)) {
+				pr_debug("#%d dma desc_info data overflow max=%d curr=%d\n",
+					dma->adap->idx, desc_info->size, desc_info->data_pos);
+				return;
+			}
+			curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+			desc_addr += sizeof(struct pt3_dma_desc);
+			desc_remain -= sizeof(struct pt3_dma_desc);
+		}
+	}
+	if (prev)
+		prev->next_desc = dma->desc_info->addr | 0b10;
+}
+
+struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap)
+{
+	struct pt3_dma_page *page;
+	u32 i;
+
+	struct pt3_dma *dma = kzalloc(sizeof(struct pt3_dma), GFP_KERNEL);
+	if (!dma) {
+		pr_debug("#%d fail allocate PT3_DMA\n", adap->idx);
+		goto fail;
+	}
+	dma->adap = adap;
+	dma->enabled = false;
+	mutex_init(&dma->lock);
+
+	dma->ts_count = PT3_DMA_BLOCK_COUNT;
+	dma->ts_info = kzalloc(sizeof(struct pt3_dma_page) * dma->ts_count, GFP_KERNEL);
+	if (!dma->ts_info) {
+		pr_debug("#%d fail allocate TS DMA page\n", adap->idx);
+		goto fail;
+	}
+	pr_debug("#%d Alloc TS buf (ts_count %d)\n", adap->idx, dma->ts_count);
+	for (i = 0; i < dma->ts_count; i++) {
+		page = &dma->ts_info[i];
+		page->size = PT3_DMA_BLOCK_SIZE;
+		page->data_pos = 0;
+		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
+		if (!page->data) {
+			pr_debug("#%d fail alloc_consistent. %d\n", adap->idx, i);
+			goto fail;
+		}
+	}
+
+	dma->desc_count = 1 + (PT3_DMA_TS_BUF_SIZE / PT3_DMA_PAGE_SIZE - 1) / PT3_DMA_MAX_DESCS;
+	dma->desc_info = kzalloc(sizeof(struct pt3_dma_page) * dma->desc_count, GFP_KERNEL);
+	if (!dma->desc_info) {
+		pr_debug("#%d fail allocate Desc DMA page\n", adap->idx);
+		goto fail;
+	}
+	pr_debug("#%d Alloc Descriptor buf (desc_count %d)\n", adap->idx, dma->desc_count);
+	for (i = 0; i < dma->desc_count; i++) {
+		page = &dma->desc_info[i];
+		page->size = PT3_DMA_PAGE_SIZE;
+		page->data_pos = 0;
+		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
+		if (!page->data) {
+			pr_debug("#%d fail alloc_consistent %d\n", adap->idx, i);
+			goto fail;
+		}
+	}
+
+	pr_debug("#%d build page descriptor\n", adap->idx);
+	pt3_dma_build_page_descriptor(dma);
+	return dma;
+fail:
+	if (dma)
+		pt3_dma_free(dma);
+	return NULL;
+}
+
+void __iomem *pt3_dma_get_base_addr(struct pt3_dma *dma)
+{
+	return dma->adap->pt3->bar_reg + PT3_REG_BASE + (0x18 * dma->adap->idx);
+}
+
+void pt3_dma_reset(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *ts;
+	u32 i;
+
+	for (i = 0; i < dma->ts_count; i++) {
+		ts = &dma->ts_info[i];
+		memset(ts->data, 0, ts->size);
+		ts->data_pos = 0;
+		*ts->data = PT3_DMA_TS_NOT_SYNC;
+	}
+	dma->ts_pos = 0;
+}
+
+void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled)
+{
+	void __iomem *base = pt3_dma_get_base_addr(dma);
+	u64 start_addr = dma->desc_info->addr;
+
+	if (enabled) {
+		pr_debug("#%d DMA enable start_addr=%llx\n", dma->adap->idx, start_addr);
+		pt3_dma_reset(dma);
+		writel(1 << 1, base + PT3_REG_DMA_CTL);	/* stop DMA */
+		writel(start_addr         & 0xffffffff, base + PT3_REG_DMA_D_L);
+		writel((start_addr >> 32) & 0xffffffff, base + PT3_REG_DMA_D_H);
+		pr_debug("set descriptor address low %llx\n",  start_addr         & 0xffffffff);
+		pr_debug("set descriptor address high %llx\n", (start_addr >> 32) & 0xffffffff);
+		writel(1 << 0, base + PT3_REG_DMA_CTL);	/* start DMA */
+	} else {
+		pr_debug("#%d DMA disable\n", dma->adap->idx);
+		writel(1 << 1, base + PT3_REG_DMA_CTL);	/* stop DMA */
+		while (1) {
+			if (!(readl(base + PT3_REG_STATUS) & 1))
+				break;
+			msleep_interruptible(1);
+		}
+	}
+	dma->enabled = enabled;
+}
+
+/* convert Gray code to binary, e.g. 1001 -> 1110 */
+static u32 pt3_dma_gray2binary(u32 gray, u32 bit)
+{
+	u32 binary = 0, i, j, k;
+
+	for (i = 0; i < bit; i++) {
+		k = 0;
+		for (j = i; j < bit; j++)
+			k ^= (gray >> j) & 1;
+		binary |= k << i;
+	}
+	return binary;
+}
+
+u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma)
+{
+	return pt3_dma_gray2binary(readl(pt3_dma_get_base_addr(dma) + PT3_REG_TS_ERR), 32);
+}
+
+void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval)
+{
+	void __iomem *base = pt3_dma_get_base_addr(dma);
+	u32 data = mode | initval;
+	pr_debug("#%d %s base=%p data=0x%04x\n", dma->adap->idx, __func__, base, data);
+	writel(data, base + PT3_REG_TS_CTL);
+}
+
+void pt3_dma_tspat(u8 *p)
+{
+	u16 i, pid = ((p[1] & 0x1f) << 8) + p[2], section_length = (p[6] & 0x0f) + p[7];
+	if (pid)
+		return;		/* not a Program Association Table */
+	if (p[1] & 0x40)	/* Payload Unit Start Indicator */
+		p++;		/* add pointer field */
+	for (p += 12, i = 0; i < section_length - 9; i += 4, p += 4)
+		pr_debug("SID:PMT %04x:%04x\n", (*p << 8) + p[1], ((p[2] & 0x1f) << 8) + p[3]);
+}
+
+bool pt3_dma_ready(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *ts;
+	u8 *p;
+
+	u32 next = dma->ts_pos + 1;
+	if (next >= dma->ts_count)
+		next = 0;
+	ts = &dma->ts_info[next];
+	p = &ts->data[ts->data_pos];
+
+	if (*p == PT3_DMA_TS_SYNC) {
+#ifdef DEBUG
+		pt3_dma_tspat(p);
+#endif
+		return true;
+	}
+	if (*p == PT3_DMA_TS_NOT_SYNC)
+		return false;
+
+	pr_debug("#%d invalid sync byte value=0x%02x ts_pos=%d data_pos=%d curr=0x%02x\n",
+		dma->adap->idx, *p, next, ts->data_pos, dma->ts_info[dma->ts_pos].data[0]);
+	return false;
+}
+
+ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux)
+{
+	bool ready;
+	struct pt3_dma_page *ts;
+	u32 i, prev;
+	size_t csize, remain = dma->ts_info[dma->ts_pos].size;
+
+	mutex_lock(&dma->lock);
+	pr_debug("#%d dma_copy ts_pos=0x%x data_pos=0x%x\n",
+		   dma->adap->idx, dma->ts_pos, dma->ts_info[dma->ts_pos].data_pos);
+	for (;;) {
+		for (i = 0; i < 20; i++) {
+			ready = pt3_dma_ready(dma);
+			if (ready)
+				break;
+			msleep_interruptible(30);
+		}
+		if (!ready) {
+			pr_debug("#%d dma_copy NOT READY\n", dma->adap->idx);
+			goto last;
+		}
+		prev = dma->ts_pos - 1;
+		if (prev < 0 || dma->ts_count <= prev)
+			prev = dma->ts_count - 1;
+		if (dma->ts_info[prev].data[0] != PT3_DMA_TS_NOT_SYNC)
+			pr_debug("#%d DMA buffer overflow. prev=%d data=0x%x\n",
+					dma->adap->idx, prev, dma->ts_info[prev].data[0]);
+		ts = &dma->ts_info[dma->ts_pos];
+		for (;;) {
+			csize = (remain < (ts->size - ts->data_pos)) ?
+				 remain : (ts->size - ts->data_pos);
+			dvb_dmx_swfilter(demux, &ts->data[ts->data_pos], csize);
+			remain -= csize;
+			ts->data_pos += csize;
+			if (ts->data_pos >= ts->size) {
+				ts->data_pos = 0;
+				ts->data[ts->data_pos] = PT3_DMA_TS_NOT_SYNC;
+				dma->ts_pos++;
+				if (dma->ts_pos >= dma->ts_count)
+					dma->ts_pos = 0;
+				break;
+			}
+			if (remain <= 0)
+				goto last;
+		}
+	}
+last:
+	mutex_unlock(&dma->lock);
+	return dma->ts_info[dma->ts_pos].size - remain;
+}
+
+u32 pt3_dma_get_status(struct pt3_dma *dma)
+{
+	return readl(pt3_dma_get_base_addr(dma) + PT3_REG_STATUS);
+}
+
diff --git a/drivers/media/pci/pt3/pt3_dma.h b/drivers/media/pci/pt3/pt3_dma.h
new file mode 100644
index 0000000..934c222
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_dma.h
@@ -0,0 +1,50 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_DMA_H__
+#define	__PT3_DMA_H__
+
+#include "pt3_common.h"
+
+struct pt3_dma_page {
+	dma_addr_t addr;
+	u8 *data;
+	u32 size, data_pos;
+};
+
+enum pt3_dma_mode {
+	USE_LFSR = 1 << 16,
+	REVERSE  = 1 << 17,
+	RESET    = 1 << 18,
+};
+
+struct pt3_dma {
+	struct pt3_adapter *adap;
+	bool enabled;
+	u32 ts_pos, ts_count, desc_count;
+	struct pt3_dma_page *ts_info, *desc_info;
+	struct mutex lock;
+};
+
+ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux);
+struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap);
+void pt3_dma_free(struct pt3_dma *dma);
+u32 pt3_dma_get_status(struct pt3_dma *dma);
+u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma);
+void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled);
+void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval);
+
+#endif
diff --git a/drivers/media/pci/pt3/pt3_i2c.c b/drivers/media/pci/pt3/pt3_i2c.c
new file mode 100644
index 0000000..e872ae1
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_i2c.c
@@ -0,0 +1,189 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_i2c.h"
+
+#define PT3_I2C_DATA_OFFSET	2048
+#define PT3_I2C_START_ADDR	0x17fa
+
+enum pt3_i2c_cmd {
+	I_END,
+	I_ADDRESS,
+	I_CLOCK_L,
+	I_CLOCK_H,
+	I_DATA_L,
+	I_DATA_H,
+	I_RESET,
+	I_SLEEP,
+	I_DATA_L_NOP  = 0x08,
+	I_DATA_H_NOP  = 0x0c,
+	I_DATA_H_READ = 0x0d,
+	I_DATA_H_ACK0 = 0x0e,
+	I_DATA_H_ACK1 = 0x0f,
+};
+
+bool pt3_i2c_is_clean(struct pt3_board *pt3)
+{
+	return (readl(pt3->bar_reg + PT3_REG_I2C_R) >> 3) & 1;
+}
+
+void pt3_i2c_reset(struct pt3_board *pt3)
+{
+	writel(1 << 17, pt3->bar_reg + PT3_REG_I2C_W);	/* 0x00020000 */
+}
+
+void pt3_i2c_wait(struct pt3_board *pt3, u32 *status)
+{
+	u32 val;
+
+	while (1) {
+		val = readl(pt3->bar_reg + PT3_REG_I2C_R);
+		if (!(val & 1))				/* sequence stopped */
+			break;
+		msleep_interruptible(1);
+	}
+	if (status)
+		*status = val;				/* I2C register status */
+}
+
+void pt3_i2c_mem_write(struct pt3_board *pt3, u8 data)
+{
+	void __iomem *dst = pt3->bar_mem + PT3_I2C_DATA_OFFSET + pt3->i2c_addr;
+
+	if (pt3->i2c_filled) {
+		pt3->i2c_buf |= data << 4;
+		writeb(pt3->i2c_buf, dst);
+		pt3->i2c_addr++;
+	} else
+		pt3->i2c_buf = data;
+	pt3->i2c_filled ^= true;
+}
+
+void pt3_i2c_start(struct pt3_board *pt3)
+{
+	pt3_i2c_mem_write(pt3, I_DATA_H);
+	pt3_i2c_mem_write(pt3, I_CLOCK_H);
+	pt3_i2c_mem_write(pt3, I_DATA_L);
+	pt3_i2c_mem_write(pt3, I_CLOCK_L);
+}
+
+void pt3_i2c_cmd_write(struct pt3_board *pt3, const u8 *data, u32 size)
+{
+	u32 i, j;
+	u8 byte;
+
+	for (i = 0; i < size; i++) {
+		byte = data[i];
+		for (j = 0; j < 8; j++)
+			pt3_i2c_mem_write(pt3, (byte >> (7 - j)) & 1 ? I_DATA_H_NOP : I_DATA_L_NOP);
+		pt3_i2c_mem_write(pt3, I_DATA_H_ACK0);
+	}
+}
+
+void pt3_i2c_cmd_read(struct pt3_board *pt3, u8 *data, u32 size)
+{
+	u32 i, j;
+
+	for (i = 0; i < size; i++) {
+		for (j = 0; j < 8; j++)
+			pt3_i2c_mem_write(pt3, I_DATA_H_READ);
+		if (i == (size - 1))
+			pt3_i2c_mem_write(pt3, I_DATA_H_NOP);
+		else
+			pt3_i2c_mem_write(pt3, I_DATA_L_NOP);
+	}
+}
+
+void pt3_i2c_stop(struct pt3_board *pt3)
+{
+	pt3_i2c_mem_write(pt3, I_DATA_L);
+	pt3_i2c_mem_write(pt3, I_CLOCK_H);
+	pt3_i2c_mem_write(pt3, I_DATA_H);
+}
+
+int pt3_i2c_flush(struct pt3_board *pt3, bool end, u32 start_addr)
+{
+	u32 status;
+
+	if (end) {
+		pt3_i2c_mem_write(pt3, I_END);
+		if (pt3->i2c_filled)
+			pt3_i2c_mem_write(pt3, I_END);
+	}
+	pt3_i2c_wait(pt3, &status);
+	writel(1 << 16 | start_addr, pt3->bar_reg + PT3_REG_I2C_W);	/* 0x00010000 start sequence */
+	pt3_i2c_wait(pt3, &status);
+	if (status & 0b0110) {						/* ACK status */
+		pr_err("%s failed, status=0x%x\n", __func__, status);
+		return -EIO;
+	}
+	return 0;
+}
+
+u32 pt3_i2c_func(struct i2c_adapter *i2c)
+{
+	return I2C_FUNC_I2C;
+}
+
+int pt3_i2c_xfer(struct i2c_adapter *i2c, struct i2c_msg *msg, int num)
+{
+	struct pt3_board *pt3 = i2c_get_adapdata(i2c);
+	int i, j;
+
+	if (!num)
+		return pt3_i2c_flush(pt3, false, PT3_I2C_START_ADDR);
+	if ((num < 1) || (num > 3) || !msg || msg[0].flags)	/* always write first */
+		return -ENOTSUPP;
+	mutex_lock(&pt3->lock);
+	pt3->i2c_addr = 0;
+	for (i = 0; i < num; i++) {
+		u8 byte = (msg[i].addr << 1) | (msg[i].flags & 1);
+		pt3_i2c_start(pt3);
+		pt3_i2c_cmd_write(pt3, &byte, 1);
+		if (msg[i].flags == I2C_M_RD)
+			pt3_i2c_cmd_read(pt3, msg[i].buf, msg[i].len);
+		else
+			pt3_i2c_cmd_write(pt3, msg[i].buf, msg[i].len);
+	}
+	pt3_i2c_stop(pt3);
+	if (pt3_i2c_flush(pt3, true, 0))
+		num = -EIO;
+	else
+		for (i = 1; i < num; i++)
+			if (msg[i].flags == I2C_M_RD)
+				for (j = 0; j < msg[i].len; j++)
+					msg[i].buf[j] = readb(pt3->bar_mem + PT3_I2C_DATA_OFFSET + j);
+	mutex_unlock(&pt3->lock);
+	return num;
+}
+
+static const struct i2c_algorithm pt3_i2c_algo = {
+	.functionality = pt3_i2c_func,
+	.master_xfer = pt3_i2c_xfer,
+};
+
+int pt3_i2c_add_adapter(struct pt3_board *pt3)
+{
+	struct i2c_adapter *i2c = &pt3->i2c;
+	i2c->algo = &pt3_i2c_algo;
+	i2c->algo_data = NULL;
+	i2c->dev.parent = &pt3->pdev->dev;
+	strcpy(i2c->name, DRV_NAME);
+	i2c_set_adapdata(i2c, pt3);
+	return	i2c_add_adapter(i2c) ||
+		(!pt3_i2c_is_clean(pt3) && pt3_i2c_flush(pt3, false, 0));
+}
+
diff --git a/drivers/media/pci/pt3/pt3_i2c.h b/drivers/media/pci/pt3/pt3_i2c.h
new file mode 100644
index 0000000..8424fd5
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_i2c.h
@@ -0,0 +1,25 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_I2C_H__
+#define	__PT3_I2C_H__
+
+#include "pt3_common.h"
+
+void pt3_i2c_reset(struct pt3_board *pt3);
+int pt3_i2c_add_adapter(struct pt3_board *pt3);
+
+#endif
diff --git a/drivers/media/pci/pt3/pt3_pci.c b/drivers/media/pci/pt3/pt3_pci.c
new file mode 100644
index 0000000..275d83d
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_pci.c
@@ -0,0 +1,388 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCIE bridge Altera Cyclone IV FPGA EP4CGX15BF14C8N
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_dma.h"
+#include "pt3_i2c.h"
+#include "tc90522.h"
+#include "qm1d1c0042.h"
+#include "mxl301rf.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 DVB Driver");
+MODULE_LICENSE("GPL");
+
+static struct pci_device_id pt3_id_table[] = {
+	{ PCI_DEVICE(0x1172, 0x4c15) },
+	{ },
+};
+MODULE_DEVICE_TABLE(pci, pt3_id_table);
+
+static int lnb = 2;
+module_param(lnb, int, 0);
+MODULE_PARM_DESC(lnb, "LNB level (0:OFF 1:+11V 2:+15V)");
+
+struct pt3_lnb {
+	u32 bits;
+	char *str;
+};
+
+static const struct pt3_lnb pt3_lnb[] = {
+	{0b1100,  "0V"},
+	{0b1101, "11V"},
+	{0b1111, "15V"},
+};
+
+struct pt3_cfg {
+	fe_delivery_system_t type;
+	u8 addr_tuner, addr_demod;
+};
+
+static const struct pt3_cfg pt3_cfg[] = {
+	{SYS_ISDBS, 0x63, 0b00010001},
+	{SYS_ISDBS, 0x60, 0b00010011},
+	{SYS_ISDBT, 0x62, 0b00010000},
+	{SYS_ISDBT, 0x61, 0b00010010},
+};
+#define PT3_ADAPN ARRAY_SIZE(pt3_cfg)
+
+int pt3_update_lnb(struct pt3_board *pt3)
+{
+	u8 i, lnb_eff = 0;
+
+	if (pt3->reset) {
+		writel(pt3_lnb[0].bits, pt3->bar_reg + PT3_REG_SYS_W);
+		pt3->reset = false;
+		pt3->lnb = 0;
+	} else {
+		struct pt3_adapter *adap;
+		for (i = 0; i < PT3_ADAPN; i++) {
+			adap = pt3->adap[i];
+			pr_debug("#%d sleep %d\n", adap->idx, adap->sleep);
+			if ((pt3_cfg[i].type == SYS_ISDBS) && (!adap->sleep))
+				lnb_eff |= lnb;
+		}
+		if (pt3->lnb != lnb_eff) {
+			writel(pt3_lnb[lnb_eff].bits, pt3->bar_reg + PT3_REG_SYS_W);
+			pt3->lnb = lnb_eff;
+		}
+	}
+	pr_debug("LNB=%s\n", pt3_lnb[lnb_eff].str);
+	return 0;
+}
+
+int pt3_thread(void *data)
+{
+	size_t ret;
+	struct pt3_adapter *adap = data;
+
+	pr_debug("#%d %s sleep %d\n", adap->idx, __func__, adap->sleep);
+	set_freezable();
+	while (!kthread_should_stop()) {
+		try_to_freeze();
+		while ((ret = pt3_dma_copy(adap->dma, &adap->demux)) > 0)
+			;
+		if (ret < 0) {
+			pr_debug("#%d fail dma_copy\n", adap->idx);
+			msleep_interruptible(1);
+		}
+	}
+	return 0;
+}
+
+int pt3_start_feed(struct dvb_demux_feed *feed)
+{
+	int ret = 0;
+	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
+	pr_debug("#%d %s sleep %d\n", adap->idx, __func__, adap->sleep);
+	if (!adap->users++) {
+		pr_debug("#%d %s selected, DMA %s\n",
+			adap->idx, (pt3_cfg[adap->idx].type == SYS_ISDBS) ? "S" : "T", pt3_dma_get_status(adap->dma) & 1 ? "ON" : "OFF");
+		mutex_lock(&adap->lock);
+		if (!adap->kthread) {
+			adap->kthread = kthread_run(pt3_thread, adap, DRV_NAME "_%d", adap->idx);
+			if (IS_ERR(adap->kthread)) {
+				ret = PTR_ERR(adap->kthread);
+				adap->kthread = NULL;
+			} else {
+				pt3_dma_set_test_mode(adap->dma, RESET, 0);	/* reset error count */
+				pt3_dma_set_enabled(adap->dma, true);
+			}
+		}
+		mutex_unlock(&adap->lock);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+int pt3_stop_feed(struct dvb_demux_feed *feed)
+{
+	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
+	pr_debug("#%d %s sleep %d\n", adap->idx, __func__, adap->sleep);
+	if (!--adap->users) {
+		mutex_lock(&adap->lock);
+		if (adap->kthread) {
+			pt3_dma_set_enabled(adap->dma, false);
+			pr_debug("#%d DMA ts_err packet cnt %d\n",
+				adap->idx, pt3_dma_get_ts_error_packet_count(adap->dma));
+			kthread_stop(adap->kthread);
+			adap->kthread = NULL;
+		}
+		mutex_unlock(&adap->lock);
+		msleep_interruptible(40);
+	}
+	return 0;
+}
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+struct pt3_adapter *pt3_dvb_register_adapter(struct pt3_board *pt3)
+{
+	int ret;
+	struct dvb_adapter *dvb;
+	struct dvb_demux *demux;
+	struct dmxdev *dmxdev;
+	struct pt3_adapter *adap = kzalloc(sizeof(struct pt3_adapter), GFP_KERNEL);
+	if (!adap)
+		return ERR_PTR(-ENOMEM);
+
+	adap->pt3 = pt3;
+	adap->sleep = true;
+
+	dvb = &adap->dvb;
+	dvb->priv = adap;
+	ret = dvb_register_adapter(dvb, DRV_NAME, THIS_MODULE, &pt3->pdev->dev, adapter_nr);
+	pr_debug("adapter%d registered\n", ret);
+	if (ret >= 0) {
+		demux = &adap->demux;
+		demux->dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING;
+		demux->priv = adap;
+		demux->feednum = 256;
+		demux->filternum = 256;
+		demux->start_feed = pt3_start_feed;
+		demux->stop_feed = pt3_stop_feed;
+		demux->write_to_decoder = NULL;
+		ret = dvb_dmx_init(demux);
+		if (ret >= 0) {
+			dmxdev = &adap->dmxdev;
+			dmxdev->filternum = 256;
+			dmxdev->demux = &demux->dmx;
+			dmxdev->capabilities = 0;
+			ret = dvb_dmxdev_init(dmxdev, dvb);
+			if (ret >= 0)
+				return adap;
+			dvb_dmx_release(demux);
+		}
+		dvb_unregister_adapter(dvb);
+	}
+	kfree(adap);
+	return ERR_PTR(ret);
+}
+
+int pt3_sleep(struct dvb_frontend *fe)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	pr_debug("#%d %s orig %p\n", adap->idx, __func__, adap->orig_sleep);
+	adap->sleep = true;
+	pt3_update_lnb(adap->pt3);
+	return (adap->orig_sleep) ? adap->orig_sleep(fe) : 0;
+}
+
+int pt3_wakeup(struct dvb_frontend *fe)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	pr_debug("#%d %s orig %p\n", adap->idx, __func__, adap->orig_init);
+	adap->sleep = false;
+	pt3_update_lnb(adap->pt3);
+	return (adap->orig_init) ? adap->orig_init(fe) : 0;
+}
+
+void pt3_cleanup_adapter(struct pt3_adapter *adap)
+{
+	if (!adap)
+		return;
+	if (adap->kthread)
+		kthread_stop(adap->kthread);
+	if (adap->fe) {
+		dvb_unregister_frontend(adap->fe);
+		adap->fe->ops.release(adap->fe);
+	}
+	if (adap->dma) {
+		if (adap->dma->enabled)
+			pt3_dma_set_enabled(adap->dma, false);
+		pt3_dma_free(adap->dma);
+	}
+	adap->demux.dmx.close(&adap->demux.dmx);
+	dvb_dmxdev_release(&adap->dmxdev);
+	dvb_dmx_release(&adap->demux);
+	dvb_unregister_adapter(&adap->dvb);
+	kfree(adap);
+}
+
+void pt3_remove(struct pci_dev *pdev)
+{
+	int i;
+	struct pt3_board *pt3 = pci_get_drvdata(pdev);
+
+	if (pt3) {
+		pt3->reset = true;
+		pt3_update_lnb(pt3);
+		for (i = 0; i < PT3_ADAPN; i++)
+			pt3_cleanup_adapter(pt3->adap[i]);
+		pt3_i2c_reset(pt3);
+		i2c_del_adapter(&pt3->i2c);
+		if (pt3->bar_mem)
+			iounmap(pt3->bar_mem);
+		if (pt3->bar_reg)
+			iounmap(pt3->bar_reg);
+		pci_release_selected_regions(pdev, pt3->bars);
+		kfree(pt3->adap);
+		kfree(pt3);
+	}
+	pci_disable_device(pdev);
+}
+
+int pt3_abort(struct pci_dev *pdev, int ret, char *fmt, ...)
+{
+	va_list ap;
+	char *s = NULL;
+	int slen;
+
+	va_start(ap, fmt);
+	slen = vsnprintf(s, 0, fmt, ap);
+	s = vzalloc(slen);
+	if (slen > 0 && s) {
+		vsnprintf(s, slen, fmt, ap);
+		dev_err(&pdev->dev, "%s", s);
+		vfree(s);
+	}
+	va_end(ap);
+	pt3_remove(pdev);
+	return ret;
+}
+
+int pt3_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+	struct pt3_board *pt3;
+	struct pt3_adapter *adap;
+	const struct pt3_cfg *cfg = pt3_cfg;
+	struct dvb_frontend *fe[PT3_ADAPN];
+	int i, err, bars = pci_select_bars(pdev, IORESOURCE_MEM);
+
+	err = pci_enable_device(pdev);
+	if (err)
+		return pt3_abort(pdev, err, "PCI device unusable\n");
+	err = pci_set_dma_mask(pdev, DMA_BIT_MASK(64));
+	if (err)
+		return pt3_abort(pdev, err, "DMA mask error\n");
+	pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64));
+
+	pci_read_config_dword(pdev, PCI_CLASS_REVISION, &i);
+	if ((i & 0xFF) != 1)
+		return pt3_abort(pdev, err, "Revision 0x%x is not supported\n", i & 0xFF);
+	err = pci_request_selected_regions(pdev, bars, DRV_NAME);
+	if (err)
+		return pt3_abort(pdev, err, "Could not request regions\n");
+
+	pci_set_master(pdev);
+	err = pci_save_state(pdev);
+	if (err)
+		return pt3_abort(pdev, err, "Failed pci_save_state\n");
+	pt3 = kzalloc(sizeof(struct pt3_board), GFP_KERNEL);
+	if (!pt3)
+		return pt3_abort(pdev, -ENOMEM, "struct pt3_board out of memory\n");
+	pt3->adap = kzalloc(PT3_ADAPN * sizeof(struct pt3_adapter *), GFP_KERNEL);
+	if (!pt3->adap)
+		return pt3_abort(pdev, -ENOMEM, "No memory for *adap\n");
+
+	pt3->bars = bars;
+	pt3->pdev = pdev;
+	pci_set_drvdata(pdev, pt3);
+	pt3->bar_reg = pci_ioremap_bar(pdev, 0);
+	pt3->bar_mem = pci_ioremap_bar(pdev, 2);
+	if (!pt3->bar_reg || !pt3->bar_mem)
+		return pt3_abort(pdev, -EIO, "Failed pci_ioremap_bar\n");
+
+	err = readl(pt3->bar_reg + PT3_REG_VERSION);
+	i = ((err >> 24) & 0xFF);
+	if (i != 3)
+		return pt3_abort(pdev, -EIO, "ID=0x%x, not a PT3\n", i);
+	i = ((err >>  8) & 0xFF);
+	if (i != 4)
+		return pt3_abort(pdev, -EIO, "FPGA version 0x%x is not supported\n", i);
+	err = pt3_i2c_add_adapter(pt3);
+	if (err < 0)
+		return pt3_abort(pdev, err, "Cannot add I2C\n");
+	mutex_init(&pt3->lock);
+
+	for (i = 0; i < PT3_ADAPN; i++) {
+		adap = pt3_dvb_register_adapter(pt3);
+		if (IS_ERR(adap))
+			return pt3_abort(pdev, PTR_ERR(adap), "Failed pt3_dvb_register_adapter\n");
+		adap->idx = i;
+		adap->dma = pt3_dma_create(adap);
+		if (!adap->dma)
+			return pt3_abort(pdev, -ENOMEM, "Failed pt3_dma_create\n");
+		pt3->adap[i] = adap;
+		adap->sleep = true;
+		mutex_init(&adap->lock);
+	}
+
+	for (i = 0; i < PT3_ADAPN; i++) {
+		fe[i] = tc90522_attach(&pt3->i2c, i, cfg[i].type, cfg[i].addr_demod);
+		if (!fe[i] || (cfg[i].type == SYS_ISDBS ?
+			qm1d1c0042_attach(fe[i], i, cfg[i].addr_tuner) : mxl301rf_attach(fe[i], i, cfg[i].addr_tuner))) {
+			while (i--)
+				fe[i]->ops.release(fe[i]);
+			return pt3_abort(pdev, -ENOMEM, "Cannot attach frontend\n");
+		}
+	}
+	fe[i-1]->ops.init(fe[i-1]);	/* power on tuner & amp */
+
+	for (i = 0; i < PT3_ADAPN; i++) {
+		struct pt3_adapter *adap = pt3->adap[i];
+		pr_debug("#%d %s\n", i, __func__);
+
+		adap->orig_sleep       = fe[i]->ops.sleep;
+		adap->orig_init        = fe[i]->ops.init;
+		fe[i]->ops.sleep       = pt3_sleep;
+		fe[i]->ops.init        = pt3_wakeup;
+		if ((adap->orig_init(fe[i]) && adap->orig_init(fe[i]) && adap->orig_init(fe[i]) && adap->orig_init(fe[i])) ||
+			adap->orig_sleep(fe[i]) || dvb_register_frontend(&adap->dvb, fe[i])) {
+			while (i--)
+				dvb_unregister_frontend(fe[i]);
+			for (i = 0; i < PT3_ADAPN; i++) {
+				fe[i]->ops.release(fe[i]);
+				adap->fe = NULL;
+			}
+			return pt3_abort(pdev, -EREMOTEIO, "Cannot register frontend\n");
+		}
+		adap->fe = fe[i];
+	}
+	pt3->reset = true;
+	pt3_update_lnb(pt3);
+	return 0;
+}
+
+static struct pci_driver pt3_driver = {
+	.name		= DRV_NAME,
+	.probe		= pt3_probe,
+	.remove		= pt3_remove,
+	.id_table	= pt3_id_table,
+};
+
+module_pci_driver(pt3_driver);
+
diff --git a/drivers/media/tuners/Kconfig b/drivers/media/tuners/Kconfig
index a128488..8ca8ed7 100644
--- a/drivers/media/tuners/Kconfig
+++ b/drivers/media/tuners/Kconfig
@@ -243,4 +243,19 @@ config MEDIA_TUNER_R820T
 	default m if !MEDIA_SUBDRV_AUTOSELECT
 	help
 	  Rafael Micro R820T silicon tuner driver.
+
+config MEDIA_TUNER_MXL301RF
+	tristate "MaxLinear MXL301RF ISDB-T tuner"
+	depends on MEDIA_SUPPORT && I2C
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  MaxLinear MxL301RF ISDB-T tuner driver for PT3.
+
+config MEDIA_TUNER_QM1D1C0042
+	tristate "Sharp QM1D1C0042 ISDB-S tuner"
+	depends on MEDIA_SUPPORT && I2C
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Sharp QM1D1C0042 ISDB-S tuner driver for PT3 PCIE card.
+
 endmenu
diff --git a/drivers/media/tuners/Makefile b/drivers/media/tuners/Makefile
index efe82a9..b98c988 100644
--- a/drivers/media/tuners/Makefile
+++ b/drivers/media/tuners/Makefile
@@ -37,6 +37,8 @@ obj-$(CONFIG_MEDIA_TUNER_FC0012) += fc0012.o
 obj-$(CONFIG_MEDIA_TUNER_FC0013) += fc0013.o
 obj-$(CONFIG_MEDIA_TUNER_IT913X) += tuner_it913x.o
 obj-$(CONFIG_MEDIA_TUNER_R820T) += r820t.o
+obj-$(CONFIG_MEDIA_TUNER_MXL301RF) += mxl301rf.o
+obj-$(CONFIG_MEDIA_TUNER_QM1D1C0042) += qm1d1c0042.o
 
 ccflags-y += -I$(srctree)/drivers/media/dvb-core
 ccflags-y += -I$(srctree)/drivers/media/dvb-frontends
diff --git a/drivers/media/tuners/mxl301rf.c b/drivers/media/tuners/mxl301rf.c
new file mode 100644
index 0000000..cc60831
--- /dev/null
+++ b/drivers/media/tuners/mxl301rf.c
@@ -0,0 +1,390 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-T tuner MaxLinear CMOS Hybrid TV MxL301RF
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "mxl301rf.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 MxL301RF MaxLinear CMOS Hybrid TV ISDB-T tuner driver");
+MODULE_LICENSE("GPL");
+
+struct mxl301rf {
+	struct dvb_frontend *fe;
+	u8 addr_tuner, idx;
+	u32 freq;
+};
+
+struct shf_dvbt {
+	u32	freq,		/* Channel center frequency @ kHz	*/
+		freq_th;	/* Offset frequency threshold @ kHz	*/
+	u8	shf_val,	/* Spur shift value			*/
+		shf_dir;	/* Spur shift direction			*/
+};
+
+static const struct shf_dvbt shf_dvbt_tab[] = {
+	{  64500, 500, 0x92, 0x07 },
+	{ 191500, 300, 0xe2, 0x07 },
+	{ 205500, 500, 0x2c, 0x04 },
+	{ 212500, 500, 0x1e, 0x04 },
+	{ 226500, 500, 0xd4, 0x07 },
+	{  99143, 500, 0x9c, 0x07 },
+	{ 173143, 500, 0xd4, 0x07 },
+	{ 191143, 300, 0xd4, 0x07 },
+	{ 207143, 500, 0xce, 0x07 },
+	{ 225143, 500, 0xce, 0x07 },
+	{ 243143, 500, 0xd4, 0x07 },
+	{ 261143, 500, 0xd4, 0x07 },
+	{ 291143, 500, 0xd4, 0x07 },
+	{ 339143, 500, 0x2c, 0x04 },
+	{ 117143, 500, 0x7a, 0x07 },
+	{ 135143, 300, 0x7a, 0x07 },
+	{ 153143, 500, 0x01, 0x07 }
+};
+
+static const u32 mxl301rf_rf_tab[112] = {
+	0x058d3f49, 0x05e8ccc9, 0x06445a49, 0x069fe7c9, 0x06fb7549,
+	0x075702c9, 0x07b29049, 0x080e1dc9, 0x0869ab49, 0x08c538c9,
+	0x0920c649, 0x097c53c9, 0x09f665c9, 0x0a51f349, 0x0aad80c9,
+	0x0b090e49, 0x0b649bc9, 0x0ba1a4c9, 0x0bfd3249, 0x0c58bfc9,
+	0x0cb44d49, 0x0d0fdac9, 0x0d6b6849, 0x0dc6f5c9, 0x0e228349,
+	0x0e7e10c9, 0x0ed99e49, 0x0f352bc9, 0x0f90b949, 0x0fec46c9,
+	0x1047d449, 0x10a361c9, 0x10feef49, 0x115a7cc9, 0x11b60a49,
+	0x121197c9, 0x126d2549, 0x12c8b2c9, 0x13244049, 0x137fcdc9,
+	0x13db5b49, 0x1436e8c9, 0x14927649, 0x14ee03c9, 0x15499149,
+	0x15a51ec9, 0x1600ac49, 0x165c39c9, 0x16b7c749, 0x171354c9,
+	0x176ee249, 0x17ca6fc9, 0x1825fd49, 0x18818ac9, 0x18dd1849,
+	0x1938a5c9, 0x19943349, 0x19efc0c9, 0x1a4b4e49, 0x1aa6dbc9,
+	0x1b026949, 0x1b5df6c9, 0x1bb98449, 0x1c339649, 0x1c8f23c9,
+	0x1ceab149, 0x1d463ec9, 0x1da1cc49, 0x1dfd59c9, 0x1e58e749,
+	0x1eb474c9, 0x1f100249, 0x1f6b8fc9, 0x1fc71d49, 0x2022aac9,
+	0x207e3849, 0x20d9c5c9, 0x21355349, 0x2190e0c9, 0x21ec6e49,
+	0x2247fbc9, 0x22a38949, 0x22ff16c9, 0x235aa449, 0x23b631c9,
+	0x2411bf49, 0x246d4cc9, 0x24c8da49, 0x252467c9, 0x257ff549,
+	0x25db82c9, 0x26371049, 0x26929dc9, 0x26ee2b49, 0x2749b8c9,
+	0x27a54649, 0x2800d3c9, 0x285c6149, 0x28b7eec9, 0x29137c49,
+	0x296f09c9, 0x29ca9749, 0x2a2624c9, 0x2a81b249, 0x2add3fc9,
+	0x2b38cd49, 0x2b945ac9, 0x2befe849, 0x2c4b75c9, 0x2ca70349,
+	0x2d0290c9, 0x2d5e1e49,
+};
+#define MXL301RF_NHK (mxl301rf_rf_tab[77])	/* 日本放送協会 Nippon Hōsō Kyōkai, Japan Broadcasting Corporation */
+
+int mxl301rf_freq(int freq)
+{
+	if (freq >= 90000000)
+		return freq;					/* real_freq Hz	*/
+	if (freq > 255)
+		return MXL301RF_NHK;
+	if (freq > 127)
+		return mxl301rf_rf_tab[freq - 128];		/* freqno (IO#)	*/
+	if (freq > 63) {					/* CATV		*/
+		freq -= 64;
+		if (freq > 22)
+			return mxl301rf_rf_tab[freq - 1];	/* C23-C62	*/
+		if (freq > 12)
+			return mxl301rf_rf_tab[freq - 10];	/* C13-C22	*/
+		return MXL301RF_NHK;
+	}
+	if (freq > 62)
+		return MXL301RF_NHK;
+	if (freq > 12)
+		return mxl301rf_rf_tab[freq + 50];		/* 13-62	*/
+	if (freq >  3)
+		return mxl301rf_rf_tab[freq +  9];		/*  4-12	*/
+	if (freq)
+		return mxl301rf_rf_tab[freq -  1];		/*  1-3		*/
+	return MXL301RF_NHK;
+}
+
+void mxl301rf_rftune(u8 *data, u32 *size, u32 freq)
+{
+	u32 dig_rf_freq, tmp, frac_divider, kHz, MHz, i;
+	u8 rf_data[] = {
+		0x13, 0x00,	/* abort tune			*/
+		0x3b, 0xc0,
+		0x3b, 0x80,
+		0x10, 0x95,	/* BW				*/
+		0x1a, 0x05,
+		0x61, 0x00,
+		0x62, 0xa0,
+		0x11, 0x40,	/* 2 bytes to store RF freq.	*/
+		0x12, 0x0e,	/* 2 bytes to store RF freq.	*/
+		0x13, 0x01	/* start tune			*/
+	};
+
+	freq = mxl301rf_freq(freq);
+	dig_rf_freq = 0;
+	tmp = 0;
+	frac_divider = 1000000;
+	kHz = 1000;
+	MHz = 1000000;
+
+	dig_rf_freq = freq / MHz;
+	tmp = freq % MHz;
+
+	for (i = 0; i < 6; i++) {
+		dig_rf_freq <<= 1;
+		frac_divider /= 2;
+		if (tmp > frac_divider) {
+			tmp -= frac_divider;
+			dig_rf_freq++;
+		}
+	}
+	if (tmp > 7812)
+		dig_rf_freq++;
+
+	rf_data[2 * (7) + 1] = (u8)(dig_rf_freq);
+	rf_data[2 * (8) + 1] = (u8)(dig_rf_freq >> 8);
+
+	for (i = 0; i < ARRAY_SIZE(shf_dvbt_tab); i++) {
+		if ((freq >= (shf_dvbt_tab[i].freq - shf_dvbt_tab[i].freq_th) * kHz) &&
+				(freq <= (shf_dvbt_tab[i].freq + shf_dvbt_tab[i].freq_th) * kHz)) {
+			rf_data[2 * (5) + 1] = shf_dvbt_tab[i].shf_val;
+			rf_data[2 * (6) + 1] = 0xa0 | shf_dvbt_tab[i].shf_dir;
+			break;
+		}
+	}
+	memcpy(data, rf_data, sizeof(rf_data));
+	*size = sizeof(rf_data);
+
+	pr_debug("mx_rftune freq=%d\n", freq);
+}
+
+/* write via demod */
+int mxl301rf_fe_write_data(struct dvb_frontend *fe, u8 addr_data, const u8 *data, int len)
+{
+	u8 buf[len + 1];
+
+	buf[0] = addr_data;
+	memcpy(buf + 1, data, len);
+	return fe->ops.write(fe, buf, len + 1);
+}
+
+#define MXL301RF_FE_PASSTHROUGH 0xfe
+
+int mxl301rf_fe_write_tuner(struct dvb_frontend *fe, const u8 *data, int len)
+{
+	u8 buf[len + 2];
+
+	buf[0] = MXL301RF_FE_PASSTHROUGH;
+	buf[1] = ((struct mxl301rf *)fe->tuner_priv)->addr_tuner << 1;
+	memcpy(buf + 2, data, len);
+	return fe->ops.write(fe, buf, len + 2);
+}
+
+/* read via demod */
+void mxl301rf_fe_read(struct dvb_frontend *fe, u8 addr, u8 *data)
+{
+	const u8 wbuf[2] = {0xfb, addr};
+	int ret;
+
+	mxl301rf_fe_write_tuner(fe, wbuf, sizeof(wbuf));
+	ret = fe->ops.write(fe, NULL, (1 << 16) | (((struct mxl301rf *)fe->tuner_priv)->addr_tuner << 8) | addr);
+	if (ret >= 0)
+		*data = ret;
+}
+
+void mxl301rf_idac_setting(struct dvb_frontend *fe)
+{
+	const u8 idac[] = {
+		0x0d, 0x00,
+		0x0c, 0x67,
+		0x6f, 0x89,
+		0x70, 0x0c,
+		0x6f, 0x8a,
+		0x70, 0x0e,
+		0x6f, 0x8b,
+		0x70, 0x10+12,
+	};
+	mxl301rf_fe_write_tuner(fe, idac, sizeof(idac));
+}
+
+void mxl301rf_set_register(struct dvb_frontend *fe, u8 addr, u8 value)
+{
+	const u8 data[2] = {addr, value};
+	mxl301rf_fe_write_tuner(fe, data, sizeof(data));
+}
+
+int mxl301rf_write_imsrst(struct dvb_frontend *fe)
+{
+	u8 data = 0x01 << 6;
+	return mxl301rf_fe_write_data(fe, 0x01, &data, 1);
+}
+
+enum mxl301rf_agc {
+	MXL301RF_AGC_AUTO,
+	MXL301RF_AGC_MANUAL,
+};
+
+int mxl301rf_set_agc(struct dvb_frontend *fe, enum mxl301rf_agc agc)
+{
+	u8 data = (agc == MXL301RF_AGC_AUTO) ? 0x40 : 0x00;
+	int ret = mxl301rf_fe_write_data(fe, 0x25, &data, 1);
+	if (ret)
+		return ret;
+
+	data = 0x4c | ((agc == MXL301RF_AGC_AUTO) ? 0x00 : 0x01);
+	return	mxl301rf_fe_write_data(fe, 0x23, &data, 1) ||
+		mxl301rf_write_imsrst(fe);
+}
+
+int mxl301rf_sleep(struct dvb_frontend *fe)
+{
+	u8 buf = (1 << 7) | (1 << 4);
+	const u8 data[4] = {0x01, 0x00, 0x13, 0x00};
+	int err = mxl301rf_set_agc(fe, MXL301RF_AGC_MANUAL);
+	if (err)
+		return err;
+	mxl301rf_fe_write_tuner(fe, data, sizeof(data));
+	return mxl301rf_fe_write_data(fe, 0x03, &buf, 1);
+}
+
+static const u8 mxl301rf_freq_tab[][3] = {
+	{   2, 0,  3 },
+	{  12, 1, 22 },
+	{  21, 0, 12 },
+	{  62, 1, 63 },
+	{ 112, 0, 62 }
+};
+
+bool mxl301rf_rfsynth_locked(struct dvb_frontend *fe)
+{
+	u8 data;
+
+	mxl301rf_fe_read(fe, 0x16, &data);
+	return (data & 0x0c) == 0x0c;
+}
+
+bool mxl301rf_refsynth_locked(struct dvb_frontend *fe)
+{
+	u8 data;
+
+	mxl301rf_fe_read(fe, 0x16, &data);
+	return (data & 0x03) == 0x03;
+}
+
+bool mxl301rf_locked(struct dvb_frontend *fe)
+{
+	bool locked1 = false, locked2 = false;
+	unsigned long timeout = jiffies + msecs_to_jiffies(100);
+
+	while (time_before(jiffies, timeout)) {
+		locked1 = mxl301rf_rfsynth_locked(fe);
+		locked2 = mxl301rf_refsynth_locked(fe);
+		if (locked1 && locked2)
+			break;
+		msleep_interruptible(1);
+	}
+	pr_debug("#%d %s lock1=%d lock2=%d\n", ((struct mxl301rf *)fe->tuner_priv)->idx, __func__, locked1, locked2);
+	return locked1 && locked2 ? !mxl301rf_set_agc(fe, MXL301RF_AGC_AUTO) : false;
+}
+
+int mxl301rf_tuner_rftune(struct dvb_frontend *fe, u32 freq)
+{
+	struct mxl301rf *mx = fe->tuner_priv;
+	u8 data[100];
+	u32 size = 0;
+	int err = mxl301rf_set_agc(fe, MXL301RF_AGC_MANUAL);
+	if (err)
+		return err;
+
+	mx->freq = freq;
+	mxl301rf_rftune(data, &size, freq);
+	if (size != 20) {
+		pr_debug("fail mx_rftune size = %d\n", size);
+		return -EINVAL;
+	}
+	mxl301rf_fe_write_tuner(fe, data, 14);
+	msleep_interruptible(1);
+	mxl301rf_fe_write_tuner(fe, data + 14, 6);
+	msleep_interruptible(1);
+	mxl301rf_set_register(fe, 0x1a, 0x0d);
+	mxl301rf_idac_setting(fe);
+
+	return mxl301rf_locked(fe) ? 0 : -ETIMEDOUT;
+}
+
+int mxl301rf_wakeup(struct dvb_frontend *fe)
+{
+	struct mxl301rf *mx = fe->tuner_priv;
+	int err;
+	u8 buf = (1 << 7) | (0 << 4);
+	const u8 data[2] = {0x01, 0x01};
+
+	err = mxl301rf_fe_write_data(fe, 0x03, &buf, 1);
+	if (err)
+		return err;
+	mxl301rf_fe_write_tuner(fe, data, sizeof(data));
+	mxl301rf_tuner_rftune(fe, mx->freq);
+	return 0;
+}
+
+void mxl301rf_ch2freq(u32 channel, bool *catv, u32 *number, u32 *freq)
+{
+	u32 i;
+	s32 freq_offset = 0;
+
+	if (12 <= channel)
+		freq_offset += 2;
+	if (17 <= channel)
+		freq_offset -= 2;
+	if (63 <= channel)
+		freq_offset += 2;
+	*freq = 93 + channel * 6 + freq_offset;
+
+	for (i = 0; i < ARRAY_SIZE(mxl301rf_freq_tab); i++) {
+		if (channel <= mxl301rf_freq_tab[i][0]) {
+			*catv = mxl301rf_freq_tab[i][1] ? true : false;
+			*number = channel + mxl301rf_freq_tab[i][2] - mxl301rf_freq_tab[i][0];
+			break;
+		}
+	}
+}
+
+int mxl301rf_release(struct dvb_frontend *fe)
+{
+	kfree(fe->tuner_priv);
+	fe->tuner_priv = NULL;
+	return 0;
+}
+
+static struct dvb_tuner_ops mxl301rf_ops = {
+	.info = {
+		.frequency_min	= 1,		/* actually 90 MHz, freq below that is handled as ch */
+		.frequency_max	= 770000000,	/* Hz */
+		.frequency_step	= 142857,
+	},
+	.set_frequency = mxl301rf_tuner_rftune,
+	.sleep = mxl301rf_sleep,
+	.init = mxl301rf_wakeup,
+	.release = mxl301rf_release,
+};
+
+int mxl301rf_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
+{
+	u8 d[] = { 0x10, 0x01 };
+	struct mxl301rf *mx = kzalloc(sizeof(struct mxl301rf), GFP_KERNEL);
+	if (!mx)
+		return -ENOMEM;
+	fe->tuner_priv = mx;
+	mx->fe = fe;
+	mx->idx = idx;
+	mx->addr_tuner = addr_tuner;
+	memcpy(&fe->ops.tuner_ops, &mxl301rf_ops, sizeof(struct dvb_tuner_ops));
+
+	return	mxl301rf_fe_write_data(fe, 0x1c, d, 1)	||
+		mxl301rf_fe_write_data(fe, 0x1d, d+1, 1);
+}
+EXPORT_SYMBOL(mxl301rf_attach);
+
diff --git a/drivers/media/tuners/mxl301rf.h b/drivers/media/tuners/mxl301rf.h
new file mode 100644
index 0000000..22599b9
--- /dev/null
+++ b/drivers/media/tuners/mxl301rf.h
@@ -0,0 +1,36 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-T tuner MaxLinear CMOS Hybrid TV MxL301RF
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 __MXL301RF_H__
+#define __MXL301RF_H__
+
+#ifndef pr_fmt
+ #define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+#endif
+#include "dvb_frontend.h"
+
+#if IS_ENABLED(CONFIG_MEDIA_TUNER_MXL301RF)
+extern int mxl301rf_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner);
+#else
+static inline int mxl301rf_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
+{
+	pr_warn("%s: driver disabled by Kconfig\n", __func__);
+	return 0;
+}
+#endif
+
+#endif
+
diff --git a/drivers/media/tuners/qm1d1c0042.c b/drivers/media/tuners/qm1d1c0042.c
new file mode 100644
index 0000000..3be552f
--- /dev/null
+++ b/drivers/media/tuners/qm1d1c0042.c
@@ -0,0 +1,382 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-S tuner driver QM1D1C0042
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "qm1d1c0042.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 QM1D1C0042 ISDB-S tuner driver");
+MODULE_LICENSE("GPL");
+
+struct qm1d1c0042 {
+	struct dvb_frontend *fe;
+	u8 addr_tuner, idx, reg[32];
+	u32 freq;
+};
+
+static const u8 qm1d1c0042_reg_rw[] = {
+	0x48, 0x1c, 0xa0, 0x10, 0xbc, 0xc5, 0x20, 0x33,
+	0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+	0x00, 0xff, 0xf3, 0x00, 0x2a, 0x64, 0xa6, 0x86,
+	0x8c, 0xcf, 0xb8, 0xf1, 0xa8, 0xf2, 0x89, 0x00,
+};
+
+/* read via demodulator */
+int qm1d1c0042_fe_read(struct dvb_frontend *fe, u8 addr, u8 *data)
+{
+	int ret;
+	if ((addr != 0x00) && (addr != 0x0d))
+		return -EFAULT;
+	ret = fe->ops.write(fe, NULL, (((struct qm1d1c0042 *)fe->tuner_priv)->addr_tuner << 8) | addr);
+	if (ret < 0)
+		return ret;
+	*data = ret;
+	return 0;
+}
+
+/* write via demodulator */
+int qm1d1c0042_fe_write_data(struct dvb_frontend *fe, u8 addr_data, u8 *data, int len)
+{
+	u8 buf[len + 1];
+
+	buf[0] = addr_data;
+	memcpy(buf + 1, data, len);
+	return fe->ops.write(fe, buf, len + 1);
+}
+
+#define QM1D1C0042_FE_PASSTHROUGH 0xfe
+
+int qm1d1c0042_fe_write_tuner(struct dvb_frontend *fe, u8 *data, int len)
+{
+	u8 buf[len + 2];
+
+	buf[0] = QM1D1C0042_FE_PASSTHROUGH;
+	buf[1] = ((struct qm1d1c0042 *)fe->tuner_priv)->addr_tuner << 1;
+	memcpy(buf + 2, data, len);
+	return fe->ops.write(fe, buf, len + 2);
+}
+
+int qm1d1c0042_write(struct dvb_frontend *fe, u8 addr, u8 data)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	u8 buf[] = { addr, data };
+	int err = qm1d1c0042_fe_write_tuner(fe, buf, sizeof(buf));
+	qm->reg[addr] = buf[1];
+	return err;
+}
+
+static const u8 qm1d1c0042_flag[32] = {
+	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
+	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+};
+
+int qm1d1c0042_write_pskmsrst(struct dvb_frontend *fe)
+{
+	u8 data = 0x01;
+	return qm1d1c0042_fe_write_data(fe, 0x03, &data, 1);
+}
+
+enum qm1d1c0042_agc {
+	QM1D1C0042_AGC_AUTO,
+	QM1D1C0042_AGC_MANUAL,
+};
+
+int qm1d1c0042_set_agc(struct dvb_frontend *fe, enum qm1d1c0042_agc agc)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	static u8 agc_data_s[2] = { 0xb0, 0x30 };
+	u8 data = (agc == QM1D1C0042_AGC_AUTO) ? 0xff : 0x00;
+	int ret = qm1d1c0042_fe_write_data(fe, 0x0a, &data, 1);
+	if (ret)
+		return ret;
+
+	data = agc_data_s[(qm->idx >> 1) & 1];
+	data |= (agc == QM1D1C0042_AGC_AUTO) ? 0x01 : 0x00;
+	ret = qm1d1c0042_fe_write_data(fe, 0x10, &data, 1);
+	if (ret)
+		return ret;
+
+	data = (agc == QM1D1C0042_AGC_AUTO) ? 0x40 : 0x00;
+	return (ret = qm1d1c0042_fe_write_data(fe, 0x11, &data, 1)) ? ret : qm1d1c0042_write_pskmsrst(fe);
+}
+
+int qm1d1c0042_sleep(struct dvb_frontend *fe)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	u8 buf = 1;
+	pr_debug("#%d %s\n", qm->idx, __func__);
+
+	qm->reg[0x01] &= (~(1 << 3)) & 0xff;
+	qm->reg[0x01] |= 1 << 0;
+	qm->reg[0x05] |= 1 << 3;
+	return	qm1d1c0042_set_agc(fe, QM1D1C0042_AGC_MANUAL)	||
+		qm1d1c0042_write(fe, 0x05, qm->reg[0x05])	||
+		qm1d1c0042_write(fe, 0x01, qm->reg[0x01])	||
+		qm1d1c0042_fe_write_data(fe, 0x17, &buf, 1);
+}
+
+int qm1d1c0042_wakeup(struct dvb_frontend *fe)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	u8 buf = 0;
+	pr_debug("#%d %s\n", qm->idx, __func__);
+
+	qm->reg[0x01] |= 1 << 3;
+	qm->reg[0x01] &= (~(1 << 0)) & 0xff;
+	qm->reg[0x05] &= (~(1 << 3)) & 0xff;
+	return	qm1d1c0042_fe_write_data(fe, 0x17, &buf, 1)	||
+		qm1d1c0042_write(fe, 0x01, qm->reg[0x01])	||
+		qm1d1c0042_write(fe, 0x05, qm->reg[0x05]);
+}
+
+void qm1d1c0042_get_channel_freq(u32 channel, u32 *number, u32 *freq)
+{
+	if (channel < 12) {
+		*number = 1 + 2 * channel;
+		*freq = 104948 + 3836 * channel;
+	} else if (channel < 24) {
+		channel -= 12;
+		*number = 2 + 2 * channel;
+		*freq = 161300 + 4000 * channel;
+	} else {
+		channel -= 24;
+		*number = 1 + 2 * channel;
+		*freq = 159300 + 4000 * channel;
+	}
+}
+
+static const u32 qm1d1c0042_freq_tab[9][3] = {
+	{ 2151000, 1, 7 },
+	{ 1950000, 1, 6 },
+	{ 1800000, 1, 5 },
+	{ 1600000, 1, 4 },
+	{ 1450000, 1, 3 },
+	{ 1250000, 1, 2 },
+	{ 1200000, 0, 7 },
+	{  975000, 0, 6 },
+	{  950000, 0, 0 }
+};
+
+static const u32 qm1d1c0042_sd_tab[24][2][3] = {
+	{{0x38fae1, 0x0d, 0x5}, {0x39fae1, 0x0d, 0x5},},
+	{{0x3f570a, 0x0e, 0x3}, {0x00570a, 0x0e, 0x3},},
+	{{0x05b333, 0x0e, 0x5}, {0x06b333, 0x0e, 0x5},},
+	{{0x3c0f5c, 0x0f, 0x4}, {0x3d0f5c, 0x0f, 0x4},},
+	{{0x026b85, 0x0f, 0x6}, {0x036b85, 0x0f, 0x6},},
+	{{0x38c7ae, 0x10, 0x5}, {0x39c7ae, 0x10, 0x5},},
+	{{0x3f23d7, 0x11, 0x3}, {0x0023d7, 0x11, 0x3},},
+	{{0x058000, 0x11, 0x5}, {0x068000, 0x11, 0x5},},
+	{{0x3bdc28, 0x12, 0x4}, {0x3cdc28, 0x12, 0x4},},
+	{{0x023851, 0x12, 0x6}, {0x033851, 0x12, 0x6},},
+	{{0x38947a, 0x13, 0x5}, {0x39947a, 0x13, 0x5},},
+	{{0x3ef0a3, 0x14, 0x3}, {0x3ff0a3, 0x14, 0x3},},
+	{{0x3c8000, 0x16, 0x4}, {0x3d8000, 0x16, 0x4},},
+	{{0x048000, 0x16, 0x6}, {0x058000, 0x16, 0x6},},
+	{{0x3c8000, 0x17, 0x5}, {0x3d8000, 0x17, 0x5},},
+	{{0x048000, 0x18, 0x3}, {0x058000, 0x18, 0x3},},
+	{{0x3c8000, 0x18, 0x6}, {0x3d8000, 0x18, 0x6},},
+	{{0x048000, 0x19, 0x4}, {0x058000, 0x19, 0x4},},
+	{{0x3c8000, 0x1a, 0x3}, {0x3d8000, 0x1a, 0x3},},
+	{{0x048000, 0x1a, 0x5}, {0x058000, 0x1a, 0x5},},
+	{{0x3c8000, 0x1b, 0x4}, {0x3d8000, 0x1b, 0x4},},
+	{{0x048000, 0x1b, 0x6}, {0x058000, 0x1b, 0x6},},
+	{{0x3c8000, 0x1c, 0x5}, {0x3d8000, 0x1c, 0x5},},
+	{{0x048000, 0x1d, 0x3}, {0x058000, 0x1d, 0x3},},
+};
+
+static int qm1d1c0042_tuning(struct qm1d1c0042 *qm, u32 *sd, u32 channel)
+{
+	int ret;
+	struct dvb_frontend *fe = qm->fe;
+	u8 i_data;
+	u32 i, N, A, index = (qm->idx >> 1) & 1;
+
+	qm->reg[0x08] &= 0xf0;
+	qm->reg[0x08] |= 0x09;
+
+	qm->reg[0x13] &= 0x9f;
+	qm->reg[0x13] |= 0x20;
+
+	for (i = 0; i < 8; i++) {
+		if ((qm1d1c0042_freq_tab[i+1][0] <= qm->freq) && (qm->freq < qm1d1c0042_freq_tab[i][0])) {
+			i_data = qm->reg[0x02];
+			i_data &= 0x0f;
+			i_data |= qm1d1c0042_freq_tab[i][1] << 7;
+			i_data |= qm1d1c0042_freq_tab[i][2] << 4;
+			qm1d1c0042_write(fe, 0x02, i_data);
+		}
+	}
+
+	*sd = qm1d1c0042_sd_tab[channel][index][0];
+	N = qm1d1c0042_sd_tab[channel][index][1];
+	A = qm1d1c0042_sd_tab[channel][index][2];
+
+	qm->reg[0x06] &= 0x40;
+	qm->reg[0x06] |= N;
+	ret = qm1d1c0042_write(fe, 0x06, qm->reg[0x06]);
+	if (ret)
+		return ret;
+
+	qm->reg[0x07] &= 0xf0;
+	qm->reg[0x07] |= A & 0x0f;
+	return qm1d1c0042_write(fe, 0x07, qm->reg[0x07]);
+}
+
+static int qm1d1c0042_local_lpf_tuning(struct qm1d1c0042 *qm, u32 channel)
+{
+	struct dvb_frontend *fe = qm->fe;
+	u8 i_data;
+	u32 sd = 0;
+	int ret = qm1d1c0042_tuning(qm, &sd, channel);
+	if (ret)
+		return ret;
+
+	i_data = qm->reg[0x08] & 0xf0;
+	i_data |= 2;
+	ret = qm1d1c0042_write(fe, 0x08, i_data);
+	if (ret)
+		return ret;
+
+	qm->reg[0x09] &= 0xc0;
+	qm->reg[0x09] |= (sd >> 16) & 0x3f;
+	qm->reg[0x0a] = (sd >> 8) & 0xff;
+	qm->reg[0x0b] = (sd >> 0) & 0xff;
+	ret =	qm1d1c0042_write(fe, 0x09, qm->reg[0x09])	||
+		qm1d1c0042_write(fe, 0x0a, qm->reg[0x0a])	||
+		qm1d1c0042_write(qm->fe, 0x0b, qm->reg[0x0b]);
+	if (ret)
+		return ret;
+
+	i_data = qm->reg[0x0c];
+	i_data &= 0x3f;
+	ret = qm1d1c0042_write(fe, 0x0c, i_data);
+	if (ret)
+		return ret;
+	msleep_interruptible(1);
+
+	i_data = qm->reg[0x0c];
+	i_data |= 0xc0;
+	return	qm1d1c0042_write(fe, 0x0c, i_data)	||
+		qm1d1c0042_write(fe, 0x08, 0x09)	||
+		qm1d1c0042_write(fe, 0x13, qm->reg[0x13]);
+}
+
+int qm1d1c0042_get_locked(struct qm1d1c0042 *qm, bool *locked)
+{
+	int ret = qm1d1c0042_fe_read(qm->fe, 0x0d, &qm->reg[0x0d]);
+	if (ret)
+		return ret;
+	if (qm->reg[0x0d] & 0x40)
+		*locked = true;
+	else
+		*locked = false;
+	return ret;
+}
+
+u32 qm1d1c0042_freq2ch(u32 frequency)
+{
+	u32 freq = frequency / 10,
+	    ch0 = (freq - 104948) / 3836, diff0 = freq - (104948 + 3836 * ch0),
+	    ch1 = (freq - 161300) / 4000, diff1 = freq - (161300 + 4000 * ch1),
+	    ch2 = (freq - 159300) / 4000, diff2 = freq - (159300 + 4000 * ch2),
+	    min = diff0 < diff1 ? diff0 : diff1;
+
+	if (frequency < 1024)
+		return frequency;	/* consider as channel ID if low */
+	if (diff2 < min)
+		return ch2 + 24;
+	else if (min == diff1)
+		return ch1 + 12;
+	else
+		return ch0;
+}
+
+int qm1d1c0042_set_freq(struct dvb_frontend *fe, u32 frequency)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	u32 channel = qm1d1c0042_freq2ch(frequency);
+	u32 number, freq, freq_kHz;
+	bool locked = false;
+	unsigned long timeout;
+	int err = qm1d1c0042_set_agc(fe, QM1D1C0042_AGC_MANUAL);
+	if (err)
+		return err;
+
+	qm1d1c0042_get_channel_freq(channel, &number, &freq);
+	freq_kHz = freq * 10;
+	if (((qm->idx >> 1) & 1) == 0)
+		freq_kHz -= 500;
+	else
+		freq_kHz += 500;
+	qm->freq = freq_kHz;
+	pr_debug("#%d ch %d freq %d kHz\n", qm->idx, channel, freq_kHz);
+
+	err = qm1d1c0042_local_lpf_tuning(qm, channel);
+	if (err)
+		return err;
+
+	timeout = jiffies + msecs_to_jiffies(1000);	/* 1s */
+	while (time_before(jiffies, timeout)) {
+		err = qm1d1c0042_get_locked(qm, &locked);
+		if (err)
+			return err;
+		if (locked)
+			break;
+		msleep_interruptible(1);
+	}
+	pr_debug("#%d %s %s\n", qm->idx, __func__, locked ? "LOCKED" : "TIMEOUT");
+	return locked ? qm1d1c0042_set_agc(fe, QM1D1C0042_AGC_AUTO) : -ETIMEDOUT;
+}
+
+int qm1d1c0042_release(struct dvb_frontend *fe)
+{
+	kfree(fe->tuner_priv);
+	fe->tuner_priv = NULL;
+	return 0;
+}
+
+static struct dvb_tuner_ops qm1d1c0042_ops = {
+	.info = {
+		.frequency_min	= 1,		/* actually 1024 kHz, freq below that is handled as ch */
+		.frequency_max	= 2150000,	/* kHz */
+		.frequency_step	= 1000,		/* = 1 MHz */
+	},
+	.set_frequency = qm1d1c0042_set_freq,
+	.sleep = qm1d1c0042_sleep,
+	.init = qm1d1c0042_wakeup,
+	.release = qm1d1c0042_release,
+};
+
+int qm1d1c0042_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
+{
+	u8 d[] = { 0x10, 0x15, 0x04 };
+	struct qm1d1c0042 *qm = kzalloc(sizeof(struct qm1d1c0042), GFP_KERNEL);
+	if (!qm)
+		return -ENOMEM;
+	fe->tuner_priv = qm;
+	qm->fe = fe;
+	qm->idx = idx;
+	qm->addr_tuner = addr_tuner;
+	memcpy(&fe->ops.tuner_ops, &qm1d1c0042_ops, sizeof(struct dvb_tuner_ops));
+
+	memcpy(qm->reg, qm1d1c0042_reg_rw, sizeof(qm1d1c0042_reg_rw));
+	qm->freq = 0;
+
+	return	qm1d1c0042_fe_write_data(fe, 0x1e, d,   1)	||
+		qm1d1c0042_fe_write_data(fe, 0x1c, d+1, 1)	||
+		qm1d1c0042_fe_write_data(fe, 0x1f, d+2, 1);
+}
+EXPORT_SYMBOL(qm1d1c0042_attach);
+
diff --git a/drivers/media/tuners/qm1d1c0042.h b/drivers/media/tuners/qm1d1c0042.h
new file mode 100644
index 0000000..2b14b70
--- /dev/null
+++ b/drivers/media/tuners/qm1d1c0042.h
@@ -0,0 +1,36 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-S tuner driver QM1D1C0042
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 __QM1D1C0042_H__
+#define __QM1D1C0042_H__
+
+#ifndef pr_fmt
+ #define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+#endif
+#include "dvb_frontend.h"
+
+#if IS_ENABLED(CONFIG_MEDIA_TUNER_QM1D1C0042)
+extern int qm1d1c0042_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner);
+#else
+static inline int qm1d1c0042_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
+{
+	pr_warn("%s: driver disabled by Kconfig\n", __func__);
+	return 0;
+}
+#endif
+
+#endif
+
-- 
1.8.4.5


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH] Full DVB driver package for Earthsoft PT3 (ISDB-S/T) cards
  2014-04-02  7:44 Guest
@ 2014-04-05  4:34 ` Akihiro TSUKADA
  0 siblings, 0 replies; 21+ messages in thread
From: Akihiro TSUKADA @ 2014-04-05  4:34 UTC (permalink / raw)
  To: Guest; +Cc: knightrider, mchehab, linux-media

Hi Bud,

It seems that the tuner modules use fe->ops.write()
for i2c communications, which is set by parent module.
but I'm afraid fe->ops.write() is not for the purpose.
Shouldn't they use i2c_adapters passed from _attach() etc. instead?

regards,
akihiro


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH] Full DVB driver package for Earthsoft PT3 (ISDB-S/T) cards
@ 2014-04-02  7:44 Guest
  2014-04-05  4:34 ` Akihiro TSUKADA
  0 siblings, 1 reply; 21+ messages in thread
From: Guest @ 2014-04-02  7:44 UTC (permalink / raw)
  To: linux-media
  Cc: Bud R, crope, mchehab, hdegoede, hverkuil, laurent.pinchart,
	mkrufky, sylvester.nawrocki, g.liakhovetski, peter.senna

From: Bud R <knightrider@are.ma>

DVB driver for Earthsoft PT3 (ISDB-S/T) receiver PCI Express cards.
It behaves similarly as PT1 DVB, plus some tuning enhancements:
1. in addition to the real frequency:
	ISDB-S : freq. channel ID
	ISDB-T : freq# (I/O# +128), ch#, ch# +64 for CATV
2. in addition to TSID:
	ISDB-S : slot#

Feature changes:
- dropped DKMS & standalone compile
- dropped verbosity (debug levels), use single level -DDEBUG instead
- changed SNR (.read_snr) to CNR (.read_signal_strength)
- moved demodulator FE to drivers/media/dvb-frontends
- moved tuners to drivers/media/tuners
- translated to standard (?) I2C protocol
- dropped FE's LNB control & other unused features
- added DVBv5 stats support (only CNRs)

The full package (buildable as standalone, DKMS or tree embedded module) is available at
https://github.com/knight-rider/ptx/tree/master/pt3_dvb

--------------------------------------------------------------------------------

Mauro stated:
- We reserve uppercase for defines only.
- Please split the struct definition from the table itself
- Please put "const" on all tables, as otherwise gcc may not store it as a table, but, instead, to convert it into code.
- We generally use lowcases for hexadecimal values.
- You shouldn't use gettimeofday(), nor define your own timediff logic. Use jiffies and time_before/time_after instead.

MY WISHLIST: Can you add these rules to checkpatch.pl?

--------------------------------------------------------------------------------

Signed-off-by: Guest <knightrider@are.ma>
---
 drivers/media/dvb-frontends/Kconfig   |  11 +-
 drivers/media/dvb-frontends/Makefile  |   1 +
 drivers/media/dvb-frontends/tc90522.c | 546 ++++++++++++++++++++++++++++++++++
 drivers/media/dvb-frontends/tc90522.h |  36 +++
 drivers/media/pci/Kconfig             |   2 +-
 drivers/media/pci/Makefile            |   1 +
 drivers/media/pci/pt3/Kconfig         |  11 +
 drivers/media/pci/pt3/Makefile        |   6 +
 drivers/media/pci/pt3/pt3_common.h    |  85 ++++++
 drivers/media/pci/pt3/pt3_dma.c       | 333 +++++++++++++++++++++
 drivers/media/pci/pt3/pt3_dma.h       |  50 ++++
 drivers/media/pci/pt3/pt3_i2c.c       | 189 ++++++++++++
 drivers/media/pci/pt3/pt3_i2c.h       |  25 ++
 drivers/media/pci/pt3/pt3_pci.c       | 388 ++++++++++++++++++++++++
 drivers/media/tuners/Kconfig          |  15 +
 drivers/media/tuners/Makefile         |   2 +
 drivers/media/tuners/mxl301rf.c       | 390 ++++++++++++++++++++++++
 drivers/media/tuners/mxl301rf.h       |  36 +++
 drivers/media/tuners/qm1d1c0042.c     | 382 ++++++++++++++++++++++++
 drivers/media/tuners/qm1d1c0042.h     |  36 +++
 20 files changed, 2543 insertions(+), 2 deletions(-)
 create mode 100644 drivers/media/dvb-frontends/tc90522.c
 create mode 100644 drivers/media/dvb-frontends/tc90522.h
 create mode 100644 drivers/media/pci/pt3/Kconfig
 create mode 100644 drivers/media/pci/pt3/Makefile
 create mode 100644 drivers/media/pci/pt3/pt3_common.h
 create mode 100644 drivers/media/pci/pt3/pt3_dma.c
 create mode 100644 drivers/media/pci/pt3/pt3_dma.h
 create mode 100644 drivers/media/pci/pt3/pt3_i2c.c
 create mode 100644 drivers/media/pci/pt3/pt3_i2c.h
 create mode 100644 drivers/media/pci/pt3/pt3_pci.c
 create mode 100644 drivers/media/tuners/mxl301rf.c
 create mode 100644 drivers/media/tuners/mxl301rf.h
 create mode 100644 drivers/media/tuners/qm1d1c0042.c
 create mode 100644 drivers/media/tuners/qm1d1c0042.h

diff --git a/drivers/media/dvb-frontends/Kconfig b/drivers/media/dvb-frontends/Kconfig
index 025fc54..0047b3f 100644
--- a/drivers/media/dvb-frontends/Kconfig
+++ b/drivers/media/dvb-frontends/Kconfig
@@ -591,7 +591,7 @@ config DVB_S5H1411
 	  An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want
 	  to support this frontend.
 
-comment "ISDB-T (terrestrial) frontends"
+comment "ISDB-S (satellite) & ISDB-T (terrestrial) frontends"
 	depends on DVB_CORE
 
 config DVB_S921
@@ -618,6 +618,15 @@ config DVB_MB86A20S
 	  A driver for Fujitsu mb86a20s ISDB-T/ISDB-Tsb demodulator.
 	  Say Y when you want to support this frontend.
 
+config DVB_TC90522
+	tristate "Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S)"
+	depends on DVB_CORE && I2C
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S) DVB demodulator
+	  frontend for Earthsoft PT3 PCIE cards.
+	  Say Y when you want to support this frontend.
+
 comment "Digital terrestrial only tuners/PLL"
 	depends on DVB_CORE
 
diff --git a/drivers/media/dvb-frontends/Makefile b/drivers/media/dvb-frontends/Makefile
index 282aba2..a80d212 100644
--- a/drivers/media/dvb-frontends/Makefile
+++ b/drivers/media/dvb-frontends/Makefile
@@ -105,4 +105,5 @@ obj-$(CONFIG_DVB_RTL2830) += rtl2830.o
 obj-$(CONFIG_DVB_RTL2832) += rtl2832.o
 obj-$(CONFIG_DVB_M88RS2000) += m88rs2000.o
 obj-$(CONFIG_DVB_AF9033) += af9033.o
+obj-$(CONFIG_DVB_TC90522) += tc90522.o
 
diff --git a/drivers/media/dvb-frontends/tc90522.c b/drivers/media/dvb-frontends/tc90522.c
new file mode 100644
index 0000000..328f200
--- /dev/null
+++ b/drivers/media/dvb-frontends/tc90522.c
@@ -0,0 +1,546 @@
+/*
+ * Earthsoft PT3 demodulator frontend Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S)
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "dvb_math.h"
+#include "tc90522.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 Toshiba TC90522 OFDM(ISDB-T)/8PSK(ISDB-S) demodulator");
+MODULE_LICENSE("GPL");
+
+#define TC90522_PASSTHROUGH 0xfe
+
+enum tc90522_state {
+	TC90522_IDLE,
+	TC90522_SET_FREQUENCY,
+	TC90522_SET_MODULATION,
+	TC90522_TRACK,
+	TC90522_ABORT,
+};
+
+struct tc90522 {
+	struct dvb_frontend fe;
+	struct i2c_adapter *i2c;
+	fe_delivery_system_t type;
+	u8 idx, addr_demod;
+	s32 offset;
+	enum tc90522_state state;
+};
+
+int tc90522_write(struct dvb_frontend *fe, const u8 *data, int len)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	struct i2c_msg msg[3];
+	u8 buf[6];
+
+	if (data) {
+		msg[0].addr = demod->addr_demod;
+		msg[0].buf = (u8 *)data;
+		msg[0].flags = 0;			/* write */
+		msg[0].len = len;
+
+		return i2c_transfer(demod->i2c, msg, 1) == 1 ? 0 : -EREMOTEIO;
+	} else {
+		u8 addr_tuner = (len >> 8) & 0xff,
+		   addr_data = len & 0xff;
+		if (len >> 16) {			/* read tuner without address */
+			buf[0] = TC90522_PASSTHROUGH;
+			buf[1] = (addr_tuner << 1) | 1;
+			msg[0].buf = buf;
+			msg[0].len = 2;
+			msg[0].addr = demod->addr_demod;
+			msg[0].flags = 0;		/* write */
+
+			msg[1].buf = buf + 2;
+			msg[1].len = 1;
+			msg[1].addr = demod->addr_demod;
+			msg[1].flags = I2C_M_RD;	/* read */
+
+			return i2c_transfer(demod->i2c, msg, 2) == 2 ? buf[2] : -EREMOTEIO;
+		} else {				/* read tuner */
+			buf[0] = TC90522_PASSTHROUGH;
+			buf[1] = addr_tuner << 1;
+			buf[2] = addr_data;
+			msg[0].buf = buf;
+			msg[0].len = 3;
+			msg[0].addr = demod->addr_demod;
+			msg[0].flags = 0;		/* write */
+
+			buf[3] = TC90522_PASSTHROUGH;
+			buf[4] = (addr_tuner << 1) | 1;
+			msg[1].buf = buf + 3;
+			msg[1].len = 2;
+			msg[1].addr = demod->addr_demod;
+			msg[1].flags = 0;		/* write */
+
+			msg[2].buf = buf + 5;
+			msg[2].len = 1;
+			msg[2].addr = demod->addr_demod;
+			msg[2].flags = I2C_M_RD;	/* read */
+
+			return i2c_transfer(demod->i2c, msg, 3) == 3 ? buf[5] : -EREMOTEIO;
+		}
+	}
+}
+
+int tc90522_write_data(struct dvb_frontend *fe, u8 addr_data, u8 *data, u8 len)
+{
+	u8 buf[len + 1];
+	buf[0] = addr_data;
+	memcpy(buf + 1, data, len);
+	return tc90522_write(fe, buf, len + 1);
+}
+
+int tc90522_read(struct tc90522 *demod, u8 addr, u8 *buf, u8 buflen)
+{
+	struct i2c_msg msg[2];
+	if (!buf || !buflen)
+		return -EINVAL;
+
+	buf[0] = addr;
+	msg[0].addr = demod->addr_demod;
+	msg[0].flags = 0;			/* write */
+	msg[0].buf = buf;
+	msg[0].len = 1;
+
+	msg[1].addr = demod->addr_demod;
+	msg[1].flags = I2C_M_RD;		/* read */
+	msg[1].buf = buf;
+	msg[1].len = buflen;
+
+	return i2c_transfer(demod->i2c, msg, 2) == 2 ? 0 : -EREMOTEIO;
+}
+
+u32 tc90522_byten(const u8 *data, u32 n)
+{
+	u32 i, val = 0;
+
+	for (i = 0; i < n; i++) {
+		val <<= 8;
+		val |= data[i];
+	}
+	return val;
+}
+
+int tc90522_read_id_s(struct tc90522 *demod, u16 *id)
+{
+	u8 buf[2];
+	int ret = tc90522_read(demod, 0xe6, buf, 2);
+	if (!ret)
+		*id = tc90522_byten(buf, 2);
+	return ret;
+}
+
+struct tmcc_s {			/* Transmission and Multiplexing Configuration Control */
+	u32 mode[4];
+	u32 slot[4];
+	u32 id[8];
+};
+
+int tc90522_read_tmcc_s(struct tc90522 *demod, struct tmcc_s *tmcc)
+{
+	enum {
+		BASE = 0xc5,
+		SIZE = 0xe5 - BASE + 1
+	};
+	u8 data[SIZE];
+	u32 i, byte_offset, bit_offset;
+
+	int err = tc90522_read(demod, 0xc3, data, 1)	||
+		((data[0] >> 4) & 1)			||
+		tc90522_read(demod, 0xce, data, 2)	||
+		(tc90522_byten(data, 2) == 0)		||
+		tc90522_read(demod, 0xc3, data, 1)	||
+		tc90522_read(demod, 0xc5, data, SIZE);
+	if (err)
+		return err;
+	for (i = 0; i < 4; i++) {
+		byte_offset = i >> 1;
+		bit_offset = (i & 1) ? 0 : 4;
+		tmcc->mode[i] = (data[0xc8 + byte_offset - BASE] >> bit_offset) & 0b00001111;
+		tmcc->slot[i] = (data[0xca + i           - BASE] >>          0) & 0b00111111;
+	}
+	for (i = 0; i < 8; i++)
+		tmcc->id[i] = tc90522_byten(data + 0xce + i * 2 - BASE, 2);
+	return 0;
+}
+
+enum tc90522_pwr {
+	TC90522_PWR_OFF		= 0x00,
+	TC90522_PWR_AMP_ON	= 0x04,
+	TC90522_PWR_TUNER_ON	= 0x40,
+};
+
+static enum tc90522_pwr tc90522_pwr = TC90522_PWR_OFF;
+
+int tc90522_set_powers(struct tc90522 *demod, enum tc90522_pwr pwr)
+{
+	u8 data = pwr | 0b10011001;
+	pr_debug("#%d tuner %s amp %s\n", demod->idx, pwr & TC90522_PWR_TUNER_ON ? "ON" : "OFF", pwr & TC90522_PWR_AMP_ON ? "ON" : "OFF");
+	tc90522_pwr = pwr;
+	return tc90522_write_data(&demod->fe, 0x1e, &data, 1);
+}
+
+/* dvb_frontend_ops */
+int tc90522_get_frontend_algo(struct dvb_frontend *fe)
+{
+	return DVBFE_ALGO_HW;
+}
+
+int tc90522_sleep(struct dvb_frontend *fe)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	pr_debug("#%d %s %s\n", demod->idx, __func__, demod->type == SYS_ISDBS ? "S" : "T");
+	return fe->ops.tuner_ops.sleep(fe);
+}
+
+int tc90522_wakeup(struct dvb_frontend *fe)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	pr_debug("#%d %s %s 0x%x\n", demod->idx, __func__, demod->type == SYS_ISDBS ? "S" : "T", tc90522_pwr);
+
+	if (!tc90522_pwr)
+		return	tc90522_set_powers(demod, TC90522_PWR_TUNER_ON)	||
+			i2c_transfer(demod->i2c, NULL, 0)		||
+			tc90522_set_powers(demod, TC90522_PWR_TUNER_ON | TC90522_PWR_AMP_ON);
+	demod->state = TC90522_IDLE;
+	return fe->ops.tuner_ops.init(fe);
+}
+
+void tc90522_release(struct dvb_frontend *fe)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	pr_debug("#%d %s\n", demod->idx, __func__);
+
+	if (tc90522_pwr)
+		tc90522_set_powers(demod, TC90522_PWR_OFF);
+	tc90522_sleep(fe);
+	fe->ops.tuner_ops.release(fe);
+	kfree(demod);
+}
+
+s64 tc90522_get_cn_raw(struct tc90522 *demod)
+{
+	u8 buf[3], buflen = demod->type == SYS_ISDBS ? 2 : 3, addr = demod->type == SYS_ISDBS ? 0xbc : 0x8b;
+	int err = tc90522_read(demod, addr, buf, buflen);
+	return err < 0 ? err : tc90522_byten(buf, buflen);
+}
+
+s64 tc90522_get_cn_s(s64 raw)	/* @ .0001 dB */
+{
+	s64 x1, x2, x3, x4, x5, y;
+
+	raw -= 3000;
+	if (raw < 0)
+		raw = 0;
+
+	x1 = int_sqrt(raw << 16) * ((12700ll << 21) / 1000000);
+	x2 = (s64)x1 * x1 >> 31;
+	x3 = (s64)x2 * x1 >> 31;
+	x4 = (s64)x2 * x2 >> 31;
+	x5 = (s64)x4 * x1 >> 31;
+
+	y = (58857ll << 23) / 1000;
+	y -= (s64)x1 * ((89565ll << 24) / 1000) >> 30;
+	y += (s64)x2 * ((88977ll << 24) / 1000) >> 28;
+	y -= (s64)x3 * ((50259ll << 25) / 1000) >> 27;
+	y += (s64)x4 * ((14341ll << 27) / 1000) >> 27;
+	y -= (s64)x5 * ((16346ll << 30) / 10000) >> 28;
+	return y < 0 ? 0 : y >> 10;
+}
+
+s64 tc90522_get_cn_t(s64 raw)	/* @ .0001 dB */
+{
+	s64 x, y;
+	if (!raw)
+		return 0;
+
+	x = 10 * (intlog10(0x69000000 / raw) - (2 << 24));
+	y = (24ll << 46) / 1000000;
+	y = ((s64)y * x >> 30) - (16ll << 40) / 10000;
+	y = ((s64)y * x >> 29) + (398ll << 35) / 10000;
+	y = ((s64)y * x >> 30) + (5491ll << 29) / 10000;
+	y = ((s64)y * x >> 30) + (30965ll << 23) / 10000;
+	return y >> 10;
+}
+
+int tc90522_read_signal_strength(struct dvb_frontend *fe, u16 *cn)	/* raw C/N */
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	s64 ret = tc90522_get_cn_raw(demod);
+	*cn = ret < 0 ? 0 : ret;
+	pr_debug("CN %d (%d dB)\n", (int)*cn, demod->type == SYS_ISDBS ? (int)tc90522_get_cn_s(*cn) : (int)tc90522_get_cn_t(*cn));
+	return ret < 0 ? ret : 0;
+}
+
+int tc90522_read_status(struct dvb_frontend *fe, fe_status_t *status)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+	s64 ret = tc90522_get_cn_raw(demod),
+	    raw = ret < 0 ? 0 : ret;
+
+	switch (demod->state) {
+	case TC90522_IDLE:
+	case TC90522_SET_FREQUENCY:
+		*status = 0;
+		break;
+
+	case TC90522_SET_MODULATION:
+	case TC90522_ABORT:
+		*status |= FE_HAS_SIGNAL;
+		break;
+
+	case TC90522_TRACK:
+		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		break;
+	}
+
+	c->cnr.stat[0].svalue = demod->type == SYS_ISDBS ? tc90522_get_cn_s(raw) : tc90522_get_cn_t(raw);
+	c->cnr.stat[0].scale = FE_SCALE_DECIBEL;
+	return ret;
+}
+
+/**** ISDB-S ****/
+int tc90522_write_id_s(struct dvb_frontend *fe, u16 id)
+{
+	u8 data[2] = { id >> 8, (u8)id };
+	return tc90522_write_data(fe, 0x8f, data, sizeof(data));
+}
+
+int tc90522_tune_s(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	struct tmcc_s tmcc;
+	int i, ret,
+	    freq = fe->dtv_property_cache.frequency,
+	    tsid = fe->dtv_property_cache.stream_id;
+
+	if (re_tune)
+		demod->state = TC90522_SET_FREQUENCY;
+
+	switch (demod->state) {
+	case TC90522_IDLE:
+		*delay = msecs_to_jiffies(3000);
+		*status = 0;
+		return 0;
+
+	case TC90522_SET_FREQUENCY:
+		pr_debug("#%d tsid 0x%x freq %d\n", demod->idx, tsid, freq);
+		ret = fe->ops.tuner_ops.set_frequency(fe, freq);
+		if (ret)
+			return ret;
+		demod->offset = 0;
+		demod->state = TC90522_SET_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case TC90522_SET_MODULATION:
+		for (i = 0; i < 1000; i++) {
+			ret = tc90522_read_tmcc_s(demod, &tmcc);
+			if (!ret)
+				break;
+			msleep_interruptible(1);
+		}
+		if (ret) {
+			pr_debug("fail tc_read_tmcc_s ret=0x%x\n", ret);
+			demod->state = TC90522_ABORT;
+			*delay = msecs_to_jiffies(1000);
+			return ret;
+		}
+		pr_debug("slots=%d,%d,%d,%d mode=%d,%d,%d,%d tmcc.id=0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x\n",
+				tmcc.slot[0], tmcc.slot[1], tmcc.slot[2], tmcc.slot[3],
+				tmcc.mode[0], tmcc.mode[1], tmcc.mode[2], tmcc.mode[3],
+				tmcc.id[0], tmcc.id[1], tmcc.id[2], tmcc.id[3],
+				tmcc.id[4], tmcc.id[5], tmcc.id[6], tmcc.id[7]);
+		for (i = 0; i < sizeof(tmcc.id)/sizeof(tmcc.id[0]); i++) {
+			pr_debug("tsid %x i %d tmcc.id %x\n", tsid, i, tmcc.id[i]);
+			if (tmcc.id[i] == tsid)
+				break;
+		}
+		if (tsid < sizeof(tmcc.id)/sizeof(tmcc.id[0]))	/* consider as slot# */
+			i = tsid;
+		if (i == sizeof(tmcc.id)/sizeof(tmcc.id[0])) {
+			pr_debug("#%d i%d tsid 0x%x not found\n", demod->idx, i, tsid);
+			return -EINVAL;
+		}
+		demod->offset = i;
+		pr_debug("#%d found tsid 0x%x on slot %d\n", demod->idx, tsid, i);
+		ret = tc90522_write_id_s(fe, (u16)tmcc.id[demod->offset]);
+		if (ret) {
+			pr_debug("fail set_tmcc_s ret=%d\n", ret);
+			return ret;
+		}
+		for (i = 0; i < 1000; i++) {
+			u16 short_id;
+			ret = tc90522_read_id_s(demod, &short_id);
+			if (ret) {
+				pr_debug("fail get_id_s ret=%d\n", ret);
+				return ret;
+			}
+			tsid = short_id;
+			pr_debug("#%d tsid=0x%x\n", demod->idx, tsid);
+			if ((tsid & 0xffff) == tmcc.id[demod->offset])
+				break;
+			msleep_interruptible(1);
+		}
+		demod->state = TC90522_TRACK;
+		/* fallthrough */
+
+	case TC90522_TRACK:
+		*delay = msecs_to_jiffies(3000);
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+
+	case TC90522_ABORT:
+		*delay = msecs_to_jiffies(3000);
+		*status = FE_HAS_SIGNAL;
+		return 0;
+	}
+	return -ERANGE;
+}
+
+static struct dvb_frontend_ops tc90522_ops_s = {
+	.delsys = { SYS_ISDBS },
+	.info = {
+		.name = "TC90522 ISDB-S",
+		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO | FE_CAN_MULTISTREAM |
+			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
+	},
+	.init = tc90522_wakeup,
+	.sleep = tc90522_sleep,
+	.release = tc90522_release,
+	.write = tc90522_write,
+	.get_frontend_algo = tc90522_get_frontend_algo,
+	.read_signal_strength = tc90522_read_signal_strength,
+	.read_status = tc90522_read_status,
+	.tune = tc90522_tune_s,
+};
+
+/**** ISDB-T ****/
+int tc90522_get_tmcc_t(struct tc90522 *demod)
+{
+	u8 buf;
+	bool b = false, retryov, fulock;
+
+	while (1) {
+		if (tc90522_read(demod, 0x80, &buf, 1))
+			return -EBADMSG;
+		retryov = buf & 0b10000000 ? true : false;
+		fulock  = buf & 0b00001000 ? true : false;
+		if (!fulock) {
+			b = true;
+			break;
+		} else {
+			if (retryov)
+				break;
+		}
+		msleep_interruptible(1);
+	}
+	return b ? 0 : -EBADMSG;
+}
+
+int tc90522_tune_t(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
+{
+	struct tc90522 *demod = fe->demodulator_priv;
+	int ret, i;
+
+	if (re_tune)
+		demod->state = TC90522_SET_FREQUENCY;
+
+	switch (demod->state) {
+	case TC90522_IDLE:
+		*delay = msecs_to_jiffies(3000);
+		*status = 0;
+		return 0;
+
+	case TC90522_SET_FREQUENCY:
+		if (fe->ops.tuner_ops.set_frequency(fe, fe->dtv_property_cache.frequency)) {
+			*delay = msecs_to_jiffies(1000);
+			*status = 0;
+			return 0;
+		}
+		demod->state = TC90522_SET_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case TC90522_SET_MODULATION:
+		for (i = 0; i < 1000; i++) {
+			ret = tc90522_get_tmcc_t(demod);
+			if (!ret)
+				break;
+			msleep_interruptible(2);
+		}
+		if (ret) {
+			pr_debug("#%d fail get_tmcc_t ret=%d\n", demod->idx, ret);
+				demod->state = TC90522_ABORT;
+				*delay = msecs_to_jiffies(1000);
+				return 0;
+		}
+		demod->state = TC90522_TRACK;
+		/* fallthrough */
+
+	case TC90522_TRACK:
+		*delay = msecs_to_jiffies(3000);
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+
+	case TC90522_ABORT:
+		*delay = msecs_to_jiffies(3000);
+		*status = FE_HAS_SIGNAL;
+		return 0;
+	}
+	return -ERANGE;
+}
+
+static struct dvb_frontend_ops tc90522_ops_t = {
+	.delsys = { SYS_ISDBT },
+	.info = {
+		.name = "TC90522 ISDB-T",
+		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO |
+			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
+	},
+	.init = tc90522_wakeup,
+	.sleep = tc90522_sleep,
+	.release = tc90522_release,
+	.write = tc90522_write,
+	.get_frontend_algo = tc90522_get_frontend_algo,
+	.read_signal_strength = tc90522_read_signal_strength,
+	.read_status = tc90522_read_status,
+	.tune = tc90522_tune_t,
+};
+
+/**** Common ****/
+struct dvb_frontend *tc90522_attach(struct i2c_adapter *i2c, u8 idx, fe_delivery_system_t type, u8 addr_demod)
+{
+	struct dvb_frontend *fe;
+	struct tc90522 *demod = kzalloc(sizeof(struct tc90522), GFP_KERNEL);
+	if (!demod)
+		return NULL;
+
+	demod->i2c	= i2c;
+	demod->idx	= idx;
+	demod->type	= type;
+	demod->addr_demod = addr_demod;
+	fe = &demod->fe;
+	memcpy(&fe->ops, (demod->type == SYS_ISDBS) ? &tc90522_ops_s : &tc90522_ops_t, sizeof(struct dvb_frontend_ops));
+	fe->demodulator_priv = demod;
+	return fe;
+}
+EXPORT_SYMBOL(tc90522_attach);
+
diff --git a/drivers/media/dvb-frontends/tc90522.h b/drivers/media/dvb-frontends/tc90522.h
new file mode 100644
index 0000000..78c5298
--- /dev/null
+++ b/drivers/media/dvb-frontends/tc90522.h
@@ -0,0 +1,36 @@
+/*
+ * Earthsoft PT3 demodulator frontend Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S)
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__TC90522_H__
+#define	__TC90522_H__
+
+#ifndef pr_fmt
+ #define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+#endif
+#include "dvb_frontend.h"
+
+#if IS_ENABLED(CONFIG_DVB_TC90522)
+extern struct dvb_frontend *tc90522_attach(struct i2c_adapter *i2c, u8 idx, fe_delivery_system_t type, u8 addr_demod);
+#else
+static inline struct dvb_frontend *tc90522_attach(struct i2c_adapter *i2c, u8 idx, fe_delivery_system_t type, u8 addr_demod)
+{
+	pr_warn("%s: driver disabled by Kconfig\n", __func__);
+	return NULL;
+}
+#endif
+
+#endif
+
diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
index 53196f1..87018c8 100644
--- a/drivers/media/pci/Kconfig
+++ b/drivers/media/pci/Kconfig
@@ -30,7 +30,6 @@ source "drivers/media/pci/cx88/Kconfig"
 source "drivers/media/pci/bt8xx/Kconfig"
 source "drivers/media/pci/saa7134/Kconfig"
 source "drivers/media/pci/saa7164/Kconfig"
-
 endif
 
 if MEDIA_DIGITAL_TV_SUPPORT
@@ -40,6 +39,7 @@ source "drivers/media/pci/b2c2/Kconfig"
 source "drivers/media/pci/pluto2/Kconfig"
 source "drivers/media/pci/dm1105/Kconfig"
 source "drivers/media/pci/pt1/Kconfig"
+source "drivers/media/pci/pt3/Kconfig"
 source "drivers/media/pci/mantis/Kconfig"
 source "drivers/media/pci/ngene/Kconfig"
 source "drivers/media/pci/ddbridge/Kconfig"
diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
index 35cc578..f7be6bc 100644
--- a/drivers/media/pci/Makefile
+++ b/drivers/media/pci/Makefile
@@ -7,6 +7,7 @@ obj-y        +=	ttpci/		\
 		pluto2/		\
 		dm1105/		\
 		pt1/		\
+		pt3/		\
 		mantis/		\
 		ngene/		\
 		ddbridge/	\
diff --git a/drivers/media/pci/pt3/Kconfig b/drivers/media/pci/pt3/Kconfig
new file mode 100644
index 0000000..0d866a0
--- /dev/null
+++ b/drivers/media/pci/pt3/Kconfig
@@ -0,0 +1,11 @@
+config PT3_DVB
+	tristate "Earthsoft PT3 ISDB-S/T cards"
+	depends on DVB_CORE && PCI
+	select DVB_TC90522 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_QM1D1C0042 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MXL301RF if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Support for Earthsoft PT3 PCI-Express cards.
+	  You need to enable frontend (TC90522) & tuners (QM1D1C0042, MXL301RF)
+	  Say Y or M if you own such a device and want to use it.
+
diff --git a/drivers/media/pci/pt3/Makefile b/drivers/media/pci/pt3/Makefile
new file mode 100644
index 0000000..ede00e1
--- /dev/null
+++ b/drivers/media/pci/pt3/Makefile
@@ -0,0 +1,6 @@
+pt3_dvb-objs := pt3_pci.o pt3_dma.o pt3_i2c.o
+
+obj-$(CONFIG_PT3_DVB) += pt3_dvb.o
+
+ccflags-y += -Idrivers/media/dvb-core -Idrivers/media/dvb-frontends -Idrivers/media/tuners
+
diff --git a/drivers/media/pci/pt3/pt3_common.h b/drivers/media/pci/pt3/pt3_common.h
new file mode 100644
index 0000000..7c1089f
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_common.h
@@ -0,0 +1,85 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_COMMON_H__
+#define	__PT3_COMMON_H__
+
+#define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+
+#include <linux/pci.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include "dvb_demux.h"
+#include "dmxdev.h"
+#include "dvb_frontend.h"
+
+#define DRV_NAME "pt3_dvb"
+
+/* register idx */
+#define PT3_REG_VERSION	0x00	/*	R	Version		*/
+#define PT3_REG_BUS	0x04	/*	R	Bus		*/
+#define PT3_REG_SYS_W	0x08	/*	W	System		*/
+#define PT3_REG_SYS_R	0x0c	/*	R	System		*/
+#define PT3_REG_I2C_W	0x10	/*	W	I2C		*/
+#define PT3_REG_I2C_R	0x14	/*	R	I2C		*/
+#define PT3_REG_RAM_W	0x18	/*	W	RAM		*/
+#define PT3_REG_RAM_R	0x1c	/*	R	RAM		*/
+#define PT3_REG_BASE	0x40	/* + 0x18*idx			*/
+#define PT3_REG_DMA_D_L	0x00	/*	W	DMA descriptor	*/
+#define PT3_REG_DMA_D_H	0x04	/*	W	DMA descriptor	*/
+#define PT3_REG_DMA_CTL	0x08	/*	W	DMA		*/
+#define PT3_REG_TS_CTL	0x0c	/*	W	TS		*/
+#define PT3_REG_STATUS	0x10	/*	R	DMA/FIFO/TS	*/
+#define PT3_REG_TS_ERR	0x14	/*	R	TS		*/
+
+struct pt3_adapter;
+
+struct pt3_board {
+	struct mutex lock;
+	int lnb;
+	bool reset;
+
+	struct pci_dev *pdev;
+	int bars;
+	void __iomem *bar_reg, *bar_mem;
+	struct i2c_adapter i2c;
+	u8 i2c_buf;
+	u32 i2c_addr;
+	bool i2c_filled;
+
+	struct pt3_adapter **adap;
+};
+
+struct pt3_adapter {
+	struct mutex lock;
+	struct pt3_board *pt3;
+
+	u8 idx;
+	bool sleep;
+	struct pt3_dma *dma;
+	struct task_struct *kthread;
+	struct dvb_adapter dvb;
+	struct dvb_demux demux;
+	struct dmxdev dmxdev;
+	int users;
+
+	struct dvb_frontend *fe;
+	int (*orig_sleep)(struct dvb_frontend *fe);
+	int (*orig_init)(struct dvb_frontend *fe);
+};
+
+#endif
+
diff --git a/drivers/media/pci/pt3/pt3_dma.c b/drivers/media/pci/pt3/pt3_dma.c
new file mode 100644
index 0000000..a1fb82e
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_dma.c
@@ -0,0 +1,333 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_dma.h"
+
+#define PT3_DMA_MAX_DESCS	204
+#define PT3_DMA_PAGE_SIZE	(PT3_DMA_MAX_DESCS * sizeof(struct pt3_dma_desc))
+#define PT3_DMA_BLOCK_COUNT	17
+#define PT3_DMA_BLOCK_SIZE	(PT3_DMA_PAGE_SIZE * 47)
+#define PT3_DMA_TS_BUF_SIZE	(PT3_DMA_BLOCK_SIZE * PT3_DMA_BLOCK_COUNT)
+#define PT3_DMA_TS_SYNC		0x47
+#define PT3_DMA_TS_NOT_SYNC	0x74
+
+void pt3_dma_free(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *page;
+	u32 i;
+
+	if (dma->ts_info) {
+		for (i = 0; i < dma->ts_count; i++) {
+			page = &dma->ts_info[i];
+			if (page->data)
+				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
+		}
+		kfree(dma->ts_info);
+	}
+	if (dma->desc_info) {
+		for (i = 0; i < dma->desc_count; i++) {
+			page = &dma->desc_info[i];
+			if (page->data)
+				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
+		}
+		kfree(dma->desc_info);
+	}
+	kfree(dma);
+}
+
+struct pt3_dma_desc {
+	u64 page_addr;
+	u32 page_size;
+	u64 next_desc;
+} __packed;
+
+void pt3_dma_build_page_descriptor(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *desc_info, *ts_info;
+	u64 ts_addr, desc_addr;
+	u32 i, j, ts_size, desc_remain, ts_info_pos, desc_info_pos;
+	struct pt3_dma_desc *prev, *curr;
+
+	pr_debug("#%d %s ts_count=%d ts_size=%d desc_count=%d desc_size=%d\n",
+		dma->adap->idx, __func__, dma->ts_count, dma->ts_info[0].size, dma->desc_count, dma->desc_info[0].size);
+	desc_info_pos = ts_info_pos = 0;
+	desc_info = &dma->desc_info[desc_info_pos];
+	desc_addr   = desc_info->addr;
+	desc_remain = desc_info->size;
+	desc_info->data_pos = 0;
+	prev = NULL;
+	curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+	desc_info_pos++;
+
+	for (i = 0; i < dma->ts_count; i++) {
+		if (unlikely(ts_info_pos >= dma->ts_count)) {
+			pr_debug("#%d ts_info overflow max=%d curr=%d\n", dma->adap->idx, dma->ts_count, ts_info_pos);
+			return;
+		}
+		ts_info = &dma->ts_info[ts_info_pos];
+		ts_addr = ts_info->addr;
+		ts_size = ts_info->size;
+		ts_info_pos++;
+		pr_debug("#%d i=%d, ts_info addr=0x%llx ts_size=%d\n", dma->adap->idx, i, ts_addr, ts_size);
+		for (j = 0; j < ts_size / PT3_DMA_PAGE_SIZE; j++) {
+			if (desc_remain < sizeof(struct pt3_dma_desc)) {
+				if (unlikely(desc_info_pos >= dma->desc_count)) {
+					pr_debug("#%d desc_info overflow max=%d curr=%d\n",
+						dma->adap->idx, dma->desc_count, desc_info_pos);
+					return;
+				}
+				desc_info = &dma->desc_info[desc_info_pos];
+				desc_info->data_pos = 0;
+				curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+				pr_debug("#%d desc_info_pos=%d ts_addr=0x%llx remain=%d\n",
+					dma->adap->idx, desc_info_pos, ts_addr, desc_remain);
+				desc_addr = desc_info->addr;
+				desc_remain = desc_info->size;
+				desc_info_pos++;
+			}
+			if (prev)
+				prev->next_desc = desc_addr | 0b10;
+			curr->page_addr = ts_addr           | 0b111;
+			curr->page_size = PT3_DMA_PAGE_SIZE | 0b111;
+			curr->next_desc = 0b10;
+			pr_debug("#%d j=%d dma write desc ts_addr=0x%llx desc_info_pos=%d desc_remain=%d\n",
+				dma->adap->idx, j, ts_addr, desc_info_pos, desc_remain);
+			ts_addr += PT3_DMA_PAGE_SIZE;
+
+			prev = curr;
+			desc_info->data_pos += sizeof(struct pt3_dma_desc);
+			if (unlikely(desc_info->data_pos > desc_info->size)) {
+				pr_debug("#%d dma desc_info data overflow max=%d curr=%d\n",
+					dma->adap->idx, desc_info->size, desc_info->data_pos);
+				return;
+			}
+			curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+			desc_addr += sizeof(struct pt3_dma_desc);
+			desc_remain -= sizeof(struct pt3_dma_desc);
+		}
+	}
+	if (prev)
+		prev->next_desc = dma->desc_info->addr | 0b10;
+}
+
+struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap)
+{
+	struct pt3_dma_page *page;
+	u32 i;
+
+	struct pt3_dma *dma = kzalloc(sizeof(struct pt3_dma), GFP_KERNEL);
+	if (!dma) {
+		pr_debug("#%d fail allocate PT3_DMA\n", adap->idx);
+		goto fail;
+	}
+	dma->adap = adap;
+	dma->enabled = false;
+	mutex_init(&dma->lock);
+
+	dma->ts_count = PT3_DMA_BLOCK_COUNT;
+	dma->ts_info = kzalloc(sizeof(struct pt3_dma_page) * dma->ts_count, GFP_KERNEL);
+	if (!dma->ts_info) {
+		pr_debug("#%d fail allocate TS DMA page\n", adap->idx);
+		goto fail;
+	}
+	pr_debug("#%d Alloc TS buf (ts_count %d)\n", adap->idx, dma->ts_count);
+	for (i = 0; i < dma->ts_count; i++) {
+		page = &dma->ts_info[i];
+		page->size = PT3_DMA_BLOCK_SIZE;
+		page->data_pos = 0;
+		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
+		if (!page->data) {
+			pr_debug("#%d fail alloc_consistent. %d\n", adap->idx, i);
+			goto fail;
+		}
+	}
+
+	dma->desc_count = 1 + (PT3_DMA_TS_BUF_SIZE / PT3_DMA_PAGE_SIZE - 1) / PT3_DMA_MAX_DESCS;
+	dma->desc_info = kzalloc(sizeof(struct pt3_dma_page) * dma->desc_count, GFP_KERNEL);
+	if (!dma->desc_info) {
+		pr_debug("#%d fail allocate Desc DMA page\n", adap->idx);
+		goto fail;
+	}
+	pr_debug("#%d Alloc Descriptor buf (desc_count %d)\n", adap->idx, dma->desc_count);
+	for (i = 0; i < dma->desc_count; i++) {
+		page = &dma->desc_info[i];
+		page->size = PT3_DMA_PAGE_SIZE;
+		page->data_pos = 0;
+		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
+		if (!page->data) {
+			pr_debug("#%d fail alloc_consistent %d\n", adap->idx, i);
+			goto fail;
+		}
+	}
+
+	pr_debug("#%d build page descriptor\n", adap->idx);
+	pt3_dma_build_page_descriptor(dma);
+	return dma;
+fail:
+	if (dma)
+		pt3_dma_free(dma);
+	return NULL;
+}
+
+void __iomem *pt3_dma_get_base_addr(struct pt3_dma *dma)
+{
+	return dma->adap->pt3->bar_reg + PT3_REG_BASE + (0x18 * dma->adap->idx);
+}
+
+void pt3_dma_reset(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *ts;
+	u32 i;
+
+	for (i = 0; i < dma->ts_count; i++) {
+		ts = &dma->ts_info[i];
+		memset(ts->data, 0, ts->size);
+		ts->data_pos = 0;
+		*ts->data = PT3_DMA_TS_NOT_SYNC;
+	}
+	dma->ts_pos = 0;
+}
+
+void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled)
+{
+	void __iomem *base = pt3_dma_get_base_addr(dma);
+	u64 start_addr = dma->desc_info->addr;
+
+	if (enabled) {
+		pr_debug("#%d DMA enable start_addr=%llx\n", dma->adap->idx, start_addr);
+		pt3_dma_reset(dma);
+		writel(1 << 1, base + PT3_REG_DMA_CTL);	/* stop DMA */
+		writel(start_addr         & 0xffffffff, base + PT3_REG_DMA_D_L);
+		writel((start_addr >> 32) & 0xffffffff, base + PT3_REG_DMA_D_H);
+		pr_debug("set descriptor address low %llx\n",  start_addr         & 0xffffffff);
+		pr_debug("set descriptor address high %llx\n", (start_addr >> 32) & 0xffffffff);
+		writel(1 << 0, base + PT3_REG_DMA_CTL);	/* start DMA */
+	} else {
+		pr_debug("#%d DMA disable\n", dma->adap->idx);
+		writel(1 << 1, base + PT3_REG_DMA_CTL);	/* stop DMA */
+		while (1) {
+			if (!(readl(base + PT3_REG_STATUS) & 1))
+				break;
+			msleep_interruptible(1);
+		}
+	}
+	dma->enabled = enabled;
+}
+
+/* convert Gray code to binary, e.g. 1001 -> 1110 */
+static u32 pt3_dma_gray2binary(u32 gray, u32 bit)
+{
+	u32 binary = 0, i, j, k;
+
+	for (i = 0; i < bit; i++) {
+		k = 0;
+		for (j = i; j < bit; j++)
+			k ^= (gray >> j) & 1;
+		binary |= k << i;
+	}
+	return binary;
+}
+
+u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma)
+{
+	return pt3_dma_gray2binary(readl(pt3_dma_get_base_addr(dma) + PT3_REG_TS_ERR), 32);
+}
+
+void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval)
+{
+	void __iomem *base = pt3_dma_get_base_addr(dma);
+	u32 data = mode | initval;
+	pr_debug("#%d %s base=%p data=0x%04x\n", dma->adap->idx, __func__, base, data);
+	writel(data, base + PT3_REG_TS_CTL);
+}
+
+bool pt3_dma_ready(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *ts;
+	u8 *p;
+
+	u32 next = dma->ts_pos + 1;
+	if (next >= dma->ts_count)
+		next = 0;
+	ts = &dma->ts_info[next];
+	p = &ts->data[ts->data_pos];
+
+	if (*p == PT3_DMA_TS_SYNC)
+		return true;
+	if (*p == PT3_DMA_TS_NOT_SYNC)
+		return false;
+
+	pr_debug("#%d invalid sync byte value=0x%02x ts_pos=%d data_pos=%d curr=0x%02x\n",
+		dma->adap->idx, *p, next, ts->data_pos, dma->ts_info[dma->ts_pos].data[0]);
+	return false;
+}
+
+ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux)
+{
+	bool ready;
+	struct pt3_dma_page *ts;
+	u32 i, prev;
+	size_t csize, remain = dma->ts_info[dma->ts_pos].size;
+
+	mutex_lock(&dma->lock);
+	pr_debug("#%d dma_copy ts_pos=0x%x data_pos=0x%x\n",
+		   dma->adap->idx, dma->ts_pos, dma->ts_info[dma->ts_pos].data_pos);
+	for (;;) {
+		for (i = 0; i < 20; i++) {
+			ready = pt3_dma_ready(dma);
+			if (ready)
+				break;
+			msleep_interruptible(30);
+		}
+		if (!ready) {
+			pr_debug("#%d dma_copy NOT READY\n", dma->adap->idx);
+			goto last;
+		}
+		prev = dma->ts_pos - 1;
+		if (prev < 0 || dma->ts_count <= prev)
+			prev = dma->ts_count - 1;
+		if (dma->ts_info[prev].data[0] != PT3_DMA_TS_NOT_SYNC)
+			pr_debug("#%d DMA buffer overflow. prev=%d data=0x%x\n",
+					dma->adap->idx, prev, dma->ts_info[prev].data[0]);
+		ts = &dma->ts_info[dma->ts_pos];
+		for (;;) {
+			csize = (remain < (ts->size - ts->data_pos)) ?
+				 remain : (ts->size - ts->data_pos);
+			dvb_dmx_swfilter(demux, &ts->data[ts->data_pos], csize);
+			remain -= csize;
+			ts->data_pos += csize;
+			if (ts->data_pos >= ts->size) {
+				ts->data_pos = 0;
+				ts->data[ts->data_pos] = PT3_DMA_TS_NOT_SYNC;
+				dma->ts_pos++;
+				if (dma->ts_pos >= dma->ts_count)
+					dma->ts_pos = 0;
+				break;
+			}
+			if (remain <= 0)
+				goto last;
+		}
+	}
+last:
+	mutex_unlock(&dma->lock);
+	return dma->ts_info[dma->ts_pos].size - remain;
+}
+
+u32 pt3_dma_get_status(struct pt3_dma *dma)
+{
+	return readl(pt3_dma_get_base_addr(dma) + PT3_REG_STATUS);
+}
+
diff --git a/drivers/media/pci/pt3/pt3_dma.h b/drivers/media/pci/pt3/pt3_dma.h
new file mode 100644
index 0000000..934c222
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_dma.h
@@ -0,0 +1,50 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_DMA_H__
+#define	__PT3_DMA_H__
+
+#include "pt3_common.h"
+
+struct pt3_dma_page {
+	dma_addr_t addr;
+	u8 *data;
+	u32 size, data_pos;
+};
+
+enum pt3_dma_mode {
+	USE_LFSR = 1 << 16,
+	REVERSE  = 1 << 17,
+	RESET    = 1 << 18,
+};
+
+struct pt3_dma {
+	struct pt3_adapter *adap;
+	bool enabled;
+	u32 ts_pos, ts_count, desc_count;
+	struct pt3_dma_page *ts_info, *desc_info;
+	struct mutex lock;
+};
+
+ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux);
+struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap);
+void pt3_dma_free(struct pt3_dma *dma);
+u32 pt3_dma_get_status(struct pt3_dma *dma);
+u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma);
+void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled);
+void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval);
+
+#endif
diff --git a/drivers/media/pci/pt3/pt3_i2c.c b/drivers/media/pci/pt3/pt3_i2c.c
new file mode 100644
index 0000000..e872ae1
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_i2c.c
@@ -0,0 +1,189 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_i2c.h"
+
+#define PT3_I2C_DATA_OFFSET	2048
+#define PT3_I2C_START_ADDR	0x17fa
+
+enum pt3_i2c_cmd {
+	I_END,
+	I_ADDRESS,
+	I_CLOCK_L,
+	I_CLOCK_H,
+	I_DATA_L,
+	I_DATA_H,
+	I_RESET,
+	I_SLEEP,
+	I_DATA_L_NOP  = 0x08,
+	I_DATA_H_NOP  = 0x0c,
+	I_DATA_H_READ = 0x0d,
+	I_DATA_H_ACK0 = 0x0e,
+	I_DATA_H_ACK1 = 0x0f,
+};
+
+bool pt3_i2c_is_clean(struct pt3_board *pt3)
+{
+	return (readl(pt3->bar_reg + PT3_REG_I2C_R) >> 3) & 1;
+}
+
+void pt3_i2c_reset(struct pt3_board *pt3)
+{
+	writel(1 << 17, pt3->bar_reg + PT3_REG_I2C_W);	/* 0x00020000 */
+}
+
+void pt3_i2c_wait(struct pt3_board *pt3, u32 *status)
+{
+	u32 val;
+
+	while (1) {
+		val = readl(pt3->bar_reg + PT3_REG_I2C_R);
+		if (!(val & 1))				/* sequence stopped */
+			break;
+		msleep_interruptible(1);
+	}
+	if (status)
+		*status = val;				/* I2C register status */
+}
+
+void pt3_i2c_mem_write(struct pt3_board *pt3, u8 data)
+{
+	void __iomem *dst = pt3->bar_mem + PT3_I2C_DATA_OFFSET + pt3->i2c_addr;
+
+	if (pt3->i2c_filled) {
+		pt3->i2c_buf |= data << 4;
+		writeb(pt3->i2c_buf, dst);
+		pt3->i2c_addr++;
+	} else
+		pt3->i2c_buf = data;
+	pt3->i2c_filled ^= true;
+}
+
+void pt3_i2c_start(struct pt3_board *pt3)
+{
+	pt3_i2c_mem_write(pt3, I_DATA_H);
+	pt3_i2c_mem_write(pt3, I_CLOCK_H);
+	pt3_i2c_mem_write(pt3, I_DATA_L);
+	pt3_i2c_mem_write(pt3, I_CLOCK_L);
+}
+
+void pt3_i2c_cmd_write(struct pt3_board *pt3, const u8 *data, u32 size)
+{
+	u32 i, j;
+	u8 byte;
+
+	for (i = 0; i < size; i++) {
+		byte = data[i];
+		for (j = 0; j < 8; j++)
+			pt3_i2c_mem_write(pt3, (byte >> (7 - j)) & 1 ? I_DATA_H_NOP : I_DATA_L_NOP);
+		pt3_i2c_mem_write(pt3, I_DATA_H_ACK0);
+	}
+}
+
+void pt3_i2c_cmd_read(struct pt3_board *pt3, u8 *data, u32 size)
+{
+	u32 i, j;
+
+	for (i = 0; i < size; i++) {
+		for (j = 0; j < 8; j++)
+			pt3_i2c_mem_write(pt3, I_DATA_H_READ);
+		if (i == (size - 1))
+			pt3_i2c_mem_write(pt3, I_DATA_H_NOP);
+		else
+			pt3_i2c_mem_write(pt3, I_DATA_L_NOP);
+	}
+}
+
+void pt3_i2c_stop(struct pt3_board *pt3)
+{
+	pt3_i2c_mem_write(pt3, I_DATA_L);
+	pt3_i2c_mem_write(pt3, I_CLOCK_H);
+	pt3_i2c_mem_write(pt3, I_DATA_H);
+}
+
+int pt3_i2c_flush(struct pt3_board *pt3, bool end, u32 start_addr)
+{
+	u32 status;
+
+	if (end) {
+		pt3_i2c_mem_write(pt3, I_END);
+		if (pt3->i2c_filled)
+			pt3_i2c_mem_write(pt3, I_END);
+	}
+	pt3_i2c_wait(pt3, &status);
+	writel(1 << 16 | start_addr, pt3->bar_reg + PT3_REG_I2C_W);	/* 0x00010000 start sequence */
+	pt3_i2c_wait(pt3, &status);
+	if (status & 0b0110) {						/* ACK status */
+		pr_err("%s failed, status=0x%x\n", __func__, status);
+		return -EIO;
+	}
+	return 0;
+}
+
+u32 pt3_i2c_func(struct i2c_adapter *i2c)
+{
+	return I2C_FUNC_I2C;
+}
+
+int pt3_i2c_xfer(struct i2c_adapter *i2c, struct i2c_msg *msg, int num)
+{
+	struct pt3_board *pt3 = i2c_get_adapdata(i2c);
+	int i, j;
+
+	if (!num)
+		return pt3_i2c_flush(pt3, false, PT3_I2C_START_ADDR);
+	if ((num < 1) || (num > 3) || !msg || msg[0].flags)	/* always write first */
+		return -ENOTSUPP;
+	mutex_lock(&pt3->lock);
+	pt3->i2c_addr = 0;
+	for (i = 0; i < num; i++) {
+		u8 byte = (msg[i].addr << 1) | (msg[i].flags & 1);
+		pt3_i2c_start(pt3);
+		pt3_i2c_cmd_write(pt3, &byte, 1);
+		if (msg[i].flags == I2C_M_RD)
+			pt3_i2c_cmd_read(pt3, msg[i].buf, msg[i].len);
+		else
+			pt3_i2c_cmd_write(pt3, msg[i].buf, msg[i].len);
+	}
+	pt3_i2c_stop(pt3);
+	if (pt3_i2c_flush(pt3, true, 0))
+		num = -EIO;
+	else
+		for (i = 1; i < num; i++)
+			if (msg[i].flags == I2C_M_RD)
+				for (j = 0; j < msg[i].len; j++)
+					msg[i].buf[j] = readb(pt3->bar_mem + PT3_I2C_DATA_OFFSET + j);
+	mutex_unlock(&pt3->lock);
+	return num;
+}
+
+static const struct i2c_algorithm pt3_i2c_algo = {
+	.functionality = pt3_i2c_func,
+	.master_xfer = pt3_i2c_xfer,
+};
+
+int pt3_i2c_add_adapter(struct pt3_board *pt3)
+{
+	struct i2c_adapter *i2c = &pt3->i2c;
+	i2c->algo = &pt3_i2c_algo;
+	i2c->algo_data = NULL;
+	i2c->dev.parent = &pt3->pdev->dev;
+	strcpy(i2c->name, DRV_NAME);
+	i2c_set_adapdata(i2c, pt3);
+	return	i2c_add_adapter(i2c) ||
+		(!pt3_i2c_is_clean(pt3) && pt3_i2c_flush(pt3, false, 0));
+}
+
diff --git a/drivers/media/pci/pt3/pt3_i2c.h b/drivers/media/pci/pt3/pt3_i2c.h
new file mode 100644
index 0000000..8424fd5
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_i2c.h
@@ -0,0 +1,25 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_I2C_H__
+#define	__PT3_I2C_H__
+
+#include "pt3_common.h"
+
+void pt3_i2c_reset(struct pt3_board *pt3);
+int pt3_i2c_add_adapter(struct pt3_board *pt3);
+
+#endif
diff --git a/drivers/media/pci/pt3/pt3_pci.c b/drivers/media/pci/pt3/pt3_pci.c
new file mode 100644
index 0000000..275d83d
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_pci.c
@@ -0,0 +1,388 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCIE bridge Altera Cyclone IV FPGA EP4CGX15BF14C8N
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_dma.h"
+#include "pt3_i2c.h"
+#include "tc90522.h"
+#include "qm1d1c0042.h"
+#include "mxl301rf.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 DVB Driver");
+MODULE_LICENSE("GPL");
+
+static struct pci_device_id pt3_id_table[] = {
+	{ PCI_DEVICE(0x1172, 0x4c15) },
+	{ },
+};
+MODULE_DEVICE_TABLE(pci, pt3_id_table);
+
+static int lnb = 2;
+module_param(lnb, int, 0);
+MODULE_PARM_DESC(lnb, "LNB level (0:OFF 1:+11V 2:+15V)");
+
+struct pt3_lnb {
+	u32 bits;
+	char *str;
+};
+
+static const struct pt3_lnb pt3_lnb[] = {
+	{0b1100,  "0V"},
+	{0b1101, "11V"},
+	{0b1111, "15V"},
+};
+
+struct pt3_cfg {
+	fe_delivery_system_t type;
+	u8 addr_tuner, addr_demod;
+};
+
+static const struct pt3_cfg pt3_cfg[] = {
+	{SYS_ISDBS, 0x63, 0b00010001},
+	{SYS_ISDBS, 0x60, 0b00010011},
+	{SYS_ISDBT, 0x62, 0b00010000},
+	{SYS_ISDBT, 0x61, 0b00010010},
+};
+#define PT3_ADAPN ARRAY_SIZE(pt3_cfg)
+
+int pt3_update_lnb(struct pt3_board *pt3)
+{
+	u8 i, lnb_eff = 0;
+
+	if (pt3->reset) {
+		writel(pt3_lnb[0].bits, pt3->bar_reg + PT3_REG_SYS_W);
+		pt3->reset = false;
+		pt3->lnb = 0;
+	} else {
+		struct pt3_adapter *adap;
+		for (i = 0; i < PT3_ADAPN; i++) {
+			adap = pt3->adap[i];
+			pr_debug("#%d sleep %d\n", adap->idx, adap->sleep);
+			if ((pt3_cfg[i].type == SYS_ISDBS) && (!adap->sleep))
+				lnb_eff |= lnb;
+		}
+		if (pt3->lnb != lnb_eff) {
+			writel(pt3_lnb[lnb_eff].bits, pt3->bar_reg + PT3_REG_SYS_W);
+			pt3->lnb = lnb_eff;
+		}
+	}
+	pr_debug("LNB=%s\n", pt3_lnb[lnb_eff].str);
+	return 0;
+}
+
+int pt3_thread(void *data)
+{
+	size_t ret;
+	struct pt3_adapter *adap = data;
+
+	pr_debug("#%d %s sleep %d\n", adap->idx, __func__, adap->sleep);
+	set_freezable();
+	while (!kthread_should_stop()) {
+		try_to_freeze();
+		while ((ret = pt3_dma_copy(adap->dma, &adap->demux)) > 0)
+			;
+		if (ret < 0) {
+			pr_debug("#%d fail dma_copy\n", adap->idx);
+			msleep_interruptible(1);
+		}
+	}
+	return 0;
+}
+
+int pt3_start_feed(struct dvb_demux_feed *feed)
+{
+	int ret = 0;
+	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
+	pr_debug("#%d %s sleep %d\n", adap->idx, __func__, adap->sleep);
+	if (!adap->users++) {
+		pr_debug("#%d %s selected, DMA %s\n",
+			adap->idx, (pt3_cfg[adap->idx].type == SYS_ISDBS) ? "S" : "T", pt3_dma_get_status(adap->dma) & 1 ? "ON" : "OFF");
+		mutex_lock(&adap->lock);
+		if (!adap->kthread) {
+			adap->kthread = kthread_run(pt3_thread, adap, DRV_NAME "_%d", adap->idx);
+			if (IS_ERR(adap->kthread)) {
+				ret = PTR_ERR(adap->kthread);
+				adap->kthread = NULL;
+			} else {
+				pt3_dma_set_test_mode(adap->dma, RESET, 0);	/* reset error count */
+				pt3_dma_set_enabled(adap->dma, true);
+			}
+		}
+		mutex_unlock(&adap->lock);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+int pt3_stop_feed(struct dvb_demux_feed *feed)
+{
+	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
+	pr_debug("#%d %s sleep %d\n", adap->idx, __func__, adap->sleep);
+	if (!--adap->users) {
+		mutex_lock(&adap->lock);
+		if (adap->kthread) {
+			pt3_dma_set_enabled(adap->dma, false);
+			pr_debug("#%d DMA ts_err packet cnt %d\n",
+				adap->idx, pt3_dma_get_ts_error_packet_count(adap->dma));
+			kthread_stop(adap->kthread);
+			adap->kthread = NULL;
+		}
+		mutex_unlock(&adap->lock);
+		msleep_interruptible(40);
+	}
+	return 0;
+}
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+struct pt3_adapter *pt3_dvb_register_adapter(struct pt3_board *pt3)
+{
+	int ret;
+	struct dvb_adapter *dvb;
+	struct dvb_demux *demux;
+	struct dmxdev *dmxdev;
+	struct pt3_adapter *adap = kzalloc(sizeof(struct pt3_adapter), GFP_KERNEL);
+	if (!adap)
+		return ERR_PTR(-ENOMEM);
+
+	adap->pt3 = pt3;
+	adap->sleep = true;
+
+	dvb = &adap->dvb;
+	dvb->priv = adap;
+	ret = dvb_register_adapter(dvb, DRV_NAME, THIS_MODULE, &pt3->pdev->dev, adapter_nr);
+	pr_debug("adapter%d registered\n", ret);
+	if (ret >= 0) {
+		demux = &adap->demux;
+		demux->dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING;
+		demux->priv = adap;
+		demux->feednum = 256;
+		demux->filternum = 256;
+		demux->start_feed = pt3_start_feed;
+		demux->stop_feed = pt3_stop_feed;
+		demux->write_to_decoder = NULL;
+		ret = dvb_dmx_init(demux);
+		if (ret >= 0) {
+			dmxdev = &adap->dmxdev;
+			dmxdev->filternum = 256;
+			dmxdev->demux = &demux->dmx;
+			dmxdev->capabilities = 0;
+			ret = dvb_dmxdev_init(dmxdev, dvb);
+			if (ret >= 0)
+				return adap;
+			dvb_dmx_release(demux);
+		}
+		dvb_unregister_adapter(dvb);
+	}
+	kfree(adap);
+	return ERR_PTR(ret);
+}
+
+int pt3_sleep(struct dvb_frontend *fe)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	pr_debug("#%d %s orig %p\n", adap->idx, __func__, adap->orig_sleep);
+	adap->sleep = true;
+	pt3_update_lnb(adap->pt3);
+	return (adap->orig_sleep) ? adap->orig_sleep(fe) : 0;
+}
+
+int pt3_wakeup(struct dvb_frontend *fe)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	pr_debug("#%d %s orig %p\n", adap->idx, __func__, adap->orig_init);
+	adap->sleep = false;
+	pt3_update_lnb(adap->pt3);
+	return (adap->orig_init) ? adap->orig_init(fe) : 0;
+}
+
+void pt3_cleanup_adapter(struct pt3_adapter *adap)
+{
+	if (!adap)
+		return;
+	if (adap->kthread)
+		kthread_stop(adap->kthread);
+	if (adap->fe) {
+		dvb_unregister_frontend(adap->fe);
+		adap->fe->ops.release(adap->fe);
+	}
+	if (adap->dma) {
+		if (adap->dma->enabled)
+			pt3_dma_set_enabled(adap->dma, false);
+		pt3_dma_free(adap->dma);
+	}
+	adap->demux.dmx.close(&adap->demux.dmx);
+	dvb_dmxdev_release(&adap->dmxdev);
+	dvb_dmx_release(&adap->demux);
+	dvb_unregister_adapter(&adap->dvb);
+	kfree(adap);
+}
+
+void pt3_remove(struct pci_dev *pdev)
+{
+	int i;
+	struct pt3_board *pt3 = pci_get_drvdata(pdev);
+
+	if (pt3) {
+		pt3->reset = true;
+		pt3_update_lnb(pt3);
+		for (i = 0; i < PT3_ADAPN; i++)
+			pt3_cleanup_adapter(pt3->adap[i]);
+		pt3_i2c_reset(pt3);
+		i2c_del_adapter(&pt3->i2c);
+		if (pt3->bar_mem)
+			iounmap(pt3->bar_mem);
+		if (pt3->bar_reg)
+			iounmap(pt3->bar_reg);
+		pci_release_selected_regions(pdev, pt3->bars);
+		kfree(pt3->adap);
+		kfree(pt3);
+	}
+	pci_disable_device(pdev);
+}
+
+int pt3_abort(struct pci_dev *pdev, int ret, char *fmt, ...)
+{
+	va_list ap;
+	char *s = NULL;
+	int slen;
+
+	va_start(ap, fmt);
+	slen = vsnprintf(s, 0, fmt, ap);
+	s = vzalloc(slen);
+	if (slen > 0 && s) {
+		vsnprintf(s, slen, fmt, ap);
+		dev_err(&pdev->dev, "%s", s);
+		vfree(s);
+	}
+	va_end(ap);
+	pt3_remove(pdev);
+	return ret;
+}
+
+int pt3_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+	struct pt3_board *pt3;
+	struct pt3_adapter *adap;
+	const struct pt3_cfg *cfg = pt3_cfg;
+	struct dvb_frontend *fe[PT3_ADAPN];
+	int i, err, bars = pci_select_bars(pdev, IORESOURCE_MEM);
+
+	err = pci_enable_device(pdev);
+	if (err)
+		return pt3_abort(pdev, err, "PCI device unusable\n");
+	err = pci_set_dma_mask(pdev, DMA_BIT_MASK(64));
+	if (err)
+		return pt3_abort(pdev, err, "DMA mask error\n");
+	pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64));
+
+	pci_read_config_dword(pdev, PCI_CLASS_REVISION, &i);
+	if ((i & 0xFF) != 1)
+		return pt3_abort(pdev, err, "Revision 0x%x is not supported\n", i & 0xFF);
+	err = pci_request_selected_regions(pdev, bars, DRV_NAME);
+	if (err)
+		return pt3_abort(pdev, err, "Could not request regions\n");
+
+	pci_set_master(pdev);
+	err = pci_save_state(pdev);
+	if (err)
+		return pt3_abort(pdev, err, "Failed pci_save_state\n");
+	pt3 = kzalloc(sizeof(struct pt3_board), GFP_KERNEL);
+	if (!pt3)
+		return pt3_abort(pdev, -ENOMEM, "struct pt3_board out of memory\n");
+	pt3->adap = kzalloc(PT3_ADAPN * sizeof(struct pt3_adapter *), GFP_KERNEL);
+	if (!pt3->adap)
+		return pt3_abort(pdev, -ENOMEM, "No memory for *adap\n");
+
+	pt3->bars = bars;
+	pt3->pdev = pdev;
+	pci_set_drvdata(pdev, pt3);
+	pt3->bar_reg = pci_ioremap_bar(pdev, 0);
+	pt3->bar_mem = pci_ioremap_bar(pdev, 2);
+	if (!pt3->bar_reg || !pt3->bar_mem)
+		return pt3_abort(pdev, -EIO, "Failed pci_ioremap_bar\n");
+
+	err = readl(pt3->bar_reg + PT3_REG_VERSION);
+	i = ((err >> 24) & 0xFF);
+	if (i != 3)
+		return pt3_abort(pdev, -EIO, "ID=0x%x, not a PT3\n", i);
+	i = ((err >>  8) & 0xFF);
+	if (i != 4)
+		return pt3_abort(pdev, -EIO, "FPGA version 0x%x is not supported\n", i);
+	err = pt3_i2c_add_adapter(pt3);
+	if (err < 0)
+		return pt3_abort(pdev, err, "Cannot add I2C\n");
+	mutex_init(&pt3->lock);
+
+	for (i = 0; i < PT3_ADAPN; i++) {
+		adap = pt3_dvb_register_adapter(pt3);
+		if (IS_ERR(adap))
+			return pt3_abort(pdev, PTR_ERR(adap), "Failed pt3_dvb_register_adapter\n");
+		adap->idx = i;
+		adap->dma = pt3_dma_create(adap);
+		if (!adap->dma)
+			return pt3_abort(pdev, -ENOMEM, "Failed pt3_dma_create\n");
+		pt3->adap[i] = adap;
+		adap->sleep = true;
+		mutex_init(&adap->lock);
+	}
+
+	for (i = 0; i < PT3_ADAPN; i++) {
+		fe[i] = tc90522_attach(&pt3->i2c, i, cfg[i].type, cfg[i].addr_demod);
+		if (!fe[i] || (cfg[i].type == SYS_ISDBS ?
+			qm1d1c0042_attach(fe[i], i, cfg[i].addr_tuner) : mxl301rf_attach(fe[i], i, cfg[i].addr_tuner))) {
+			while (i--)
+				fe[i]->ops.release(fe[i]);
+			return pt3_abort(pdev, -ENOMEM, "Cannot attach frontend\n");
+		}
+	}
+	fe[i-1]->ops.init(fe[i-1]);	/* power on tuner & amp */
+
+	for (i = 0; i < PT3_ADAPN; i++) {
+		struct pt3_adapter *adap = pt3->adap[i];
+		pr_debug("#%d %s\n", i, __func__);
+
+		adap->orig_sleep       = fe[i]->ops.sleep;
+		adap->orig_init        = fe[i]->ops.init;
+		fe[i]->ops.sleep       = pt3_sleep;
+		fe[i]->ops.init        = pt3_wakeup;
+		if ((adap->orig_init(fe[i]) && adap->orig_init(fe[i]) && adap->orig_init(fe[i]) && adap->orig_init(fe[i])) ||
+			adap->orig_sleep(fe[i]) || dvb_register_frontend(&adap->dvb, fe[i])) {
+			while (i--)
+				dvb_unregister_frontend(fe[i]);
+			for (i = 0; i < PT3_ADAPN; i++) {
+				fe[i]->ops.release(fe[i]);
+				adap->fe = NULL;
+			}
+			return pt3_abort(pdev, -EREMOTEIO, "Cannot register frontend\n");
+		}
+		adap->fe = fe[i];
+	}
+	pt3->reset = true;
+	pt3_update_lnb(pt3);
+	return 0;
+}
+
+static struct pci_driver pt3_driver = {
+	.name		= DRV_NAME,
+	.probe		= pt3_probe,
+	.remove		= pt3_remove,
+	.id_table	= pt3_id_table,
+};
+
+module_pci_driver(pt3_driver);
+
diff --git a/drivers/media/tuners/Kconfig b/drivers/media/tuners/Kconfig
index a128488..8ca8ed7 100644
--- a/drivers/media/tuners/Kconfig
+++ b/drivers/media/tuners/Kconfig
@@ -243,4 +243,19 @@ config MEDIA_TUNER_R820T
 	default m if !MEDIA_SUBDRV_AUTOSELECT
 	help
 	  Rafael Micro R820T silicon tuner driver.
+
+config MEDIA_TUNER_MXL301RF
+	tristate "MaxLinear MXL301RF ISDB-T tuner"
+	depends on MEDIA_SUPPORT && I2C
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  MaxLinear MxL301RF ISDB-T tuner driver for PT3.
+
+config MEDIA_TUNER_QM1D1C0042
+	tristate "Sharp QM1D1C0042 ISDB-S tuner"
+	depends on MEDIA_SUPPORT && I2C
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Sharp QM1D1C0042 ISDB-S tuner driver for PT3 PCIE card.
+
 endmenu
diff --git a/drivers/media/tuners/Makefile b/drivers/media/tuners/Makefile
index efe82a9..0c60caa 100644
--- a/drivers/media/tuners/Makefile
+++ b/drivers/media/tuners/Makefile
@@ -37,6 +37,8 @@ obj-$(CONFIG_MEDIA_TUNER_FC0012) += fc0012.o
 obj-$(CONFIG_MEDIA_TUNER_FC0013) += fc0013.o
 obj-$(CONFIG_MEDIA_TUNER_IT913X) += tuner_it913x.o
 obj-$(CONFIG_MEDIA_TUNER_R820T) += r820t.o
+obj-$(CONFIG_MEDIA_TUNER_QM1D1C0042) += qm1d1c0042.o
+obj-$(CONFIG_MEDIA_TUNER_MXL301RF) += mxl301rf.o
 
 ccflags-y += -I$(srctree)/drivers/media/dvb-core
 ccflags-y += -I$(srctree)/drivers/media/dvb-frontends
diff --git a/drivers/media/tuners/mxl301rf.c b/drivers/media/tuners/mxl301rf.c
new file mode 100644
index 0000000..cc60831
--- /dev/null
+++ b/drivers/media/tuners/mxl301rf.c
@@ -0,0 +1,390 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-T tuner MaxLinear CMOS Hybrid TV MxL301RF
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "mxl301rf.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 MxL301RF MaxLinear CMOS Hybrid TV ISDB-T tuner driver");
+MODULE_LICENSE("GPL");
+
+struct mxl301rf {
+	struct dvb_frontend *fe;
+	u8 addr_tuner, idx;
+	u32 freq;
+};
+
+struct shf_dvbt {
+	u32	freq,		/* Channel center frequency @ kHz	*/
+		freq_th;	/* Offset frequency threshold @ kHz	*/
+	u8	shf_val,	/* Spur shift value			*/
+		shf_dir;	/* Spur shift direction			*/
+};
+
+static const struct shf_dvbt shf_dvbt_tab[] = {
+	{  64500, 500, 0x92, 0x07 },
+	{ 191500, 300, 0xe2, 0x07 },
+	{ 205500, 500, 0x2c, 0x04 },
+	{ 212500, 500, 0x1e, 0x04 },
+	{ 226500, 500, 0xd4, 0x07 },
+	{  99143, 500, 0x9c, 0x07 },
+	{ 173143, 500, 0xd4, 0x07 },
+	{ 191143, 300, 0xd4, 0x07 },
+	{ 207143, 500, 0xce, 0x07 },
+	{ 225143, 500, 0xce, 0x07 },
+	{ 243143, 500, 0xd4, 0x07 },
+	{ 261143, 500, 0xd4, 0x07 },
+	{ 291143, 500, 0xd4, 0x07 },
+	{ 339143, 500, 0x2c, 0x04 },
+	{ 117143, 500, 0x7a, 0x07 },
+	{ 135143, 300, 0x7a, 0x07 },
+	{ 153143, 500, 0x01, 0x07 }
+};
+
+static const u32 mxl301rf_rf_tab[112] = {
+	0x058d3f49, 0x05e8ccc9, 0x06445a49, 0x069fe7c9, 0x06fb7549,
+	0x075702c9, 0x07b29049, 0x080e1dc9, 0x0869ab49, 0x08c538c9,
+	0x0920c649, 0x097c53c9, 0x09f665c9, 0x0a51f349, 0x0aad80c9,
+	0x0b090e49, 0x0b649bc9, 0x0ba1a4c9, 0x0bfd3249, 0x0c58bfc9,
+	0x0cb44d49, 0x0d0fdac9, 0x0d6b6849, 0x0dc6f5c9, 0x0e228349,
+	0x0e7e10c9, 0x0ed99e49, 0x0f352bc9, 0x0f90b949, 0x0fec46c9,
+	0x1047d449, 0x10a361c9, 0x10feef49, 0x115a7cc9, 0x11b60a49,
+	0x121197c9, 0x126d2549, 0x12c8b2c9, 0x13244049, 0x137fcdc9,
+	0x13db5b49, 0x1436e8c9, 0x14927649, 0x14ee03c9, 0x15499149,
+	0x15a51ec9, 0x1600ac49, 0x165c39c9, 0x16b7c749, 0x171354c9,
+	0x176ee249, 0x17ca6fc9, 0x1825fd49, 0x18818ac9, 0x18dd1849,
+	0x1938a5c9, 0x19943349, 0x19efc0c9, 0x1a4b4e49, 0x1aa6dbc9,
+	0x1b026949, 0x1b5df6c9, 0x1bb98449, 0x1c339649, 0x1c8f23c9,
+	0x1ceab149, 0x1d463ec9, 0x1da1cc49, 0x1dfd59c9, 0x1e58e749,
+	0x1eb474c9, 0x1f100249, 0x1f6b8fc9, 0x1fc71d49, 0x2022aac9,
+	0x207e3849, 0x20d9c5c9, 0x21355349, 0x2190e0c9, 0x21ec6e49,
+	0x2247fbc9, 0x22a38949, 0x22ff16c9, 0x235aa449, 0x23b631c9,
+	0x2411bf49, 0x246d4cc9, 0x24c8da49, 0x252467c9, 0x257ff549,
+	0x25db82c9, 0x26371049, 0x26929dc9, 0x26ee2b49, 0x2749b8c9,
+	0x27a54649, 0x2800d3c9, 0x285c6149, 0x28b7eec9, 0x29137c49,
+	0x296f09c9, 0x29ca9749, 0x2a2624c9, 0x2a81b249, 0x2add3fc9,
+	0x2b38cd49, 0x2b945ac9, 0x2befe849, 0x2c4b75c9, 0x2ca70349,
+	0x2d0290c9, 0x2d5e1e49,
+};
+#define MXL301RF_NHK (mxl301rf_rf_tab[77])	/* 日本放送協会 Nippon Hōsō Kyōkai, Japan Broadcasting Corporation */
+
+int mxl301rf_freq(int freq)
+{
+	if (freq >= 90000000)
+		return freq;					/* real_freq Hz	*/
+	if (freq > 255)
+		return MXL301RF_NHK;
+	if (freq > 127)
+		return mxl301rf_rf_tab[freq - 128];		/* freqno (IO#)	*/
+	if (freq > 63) {					/* CATV		*/
+		freq -= 64;
+		if (freq > 22)
+			return mxl301rf_rf_tab[freq - 1];	/* C23-C62	*/
+		if (freq > 12)
+			return mxl301rf_rf_tab[freq - 10];	/* C13-C22	*/
+		return MXL301RF_NHK;
+	}
+	if (freq > 62)
+		return MXL301RF_NHK;
+	if (freq > 12)
+		return mxl301rf_rf_tab[freq + 50];		/* 13-62	*/
+	if (freq >  3)
+		return mxl301rf_rf_tab[freq +  9];		/*  4-12	*/
+	if (freq)
+		return mxl301rf_rf_tab[freq -  1];		/*  1-3		*/
+	return MXL301RF_NHK;
+}
+
+void mxl301rf_rftune(u8 *data, u32 *size, u32 freq)
+{
+	u32 dig_rf_freq, tmp, frac_divider, kHz, MHz, i;
+	u8 rf_data[] = {
+		0x13, 0x00,	/* abort tune			*/
+		0x3b, 0xc0,
+		0x3b, 0x80,
+		0x10, 0x95,	/* BW				*/
+		0x1a, 0x05,
+		0x61, 0x00,
+		0x62, 0xa0,
+		0x11, 0x40,	/* 2 bytes to store RF freq.	*/
+		0x12, 0x0e,	/* 2 bytes to store RF freq.	*/
+		0x13, 0x01	/* start tune			*/
+	};
+
+	freq = mxl301rf_freq(freq);
+	dig_rf_freq = 0;
+	tmp = 0;
+	frac_divider = 1000000;
+	kHz = 1000;
+	MHz = 1000000;
+
+	dig_rf_freq = freq / MHz;
+	tmp = freq % MHz;
+
+	for (i = 0; i < 6; i++) {
+		dig_rf_freq <<= 1;
+		frac_divider /= 2;
+		if (tmp > frac_divider) {
+			tmp -= frac_divider;
+			dig_rf_freq++;
+		}
+	}
+	if (tmp > 7812)
+		dig_rf_freq++;
+
+	rf_data[2 * (7) + 1] = (u8)(dig_rf_freq);
+	rf_data[2 * (8) + 1] = (u8)(dig_rf_freq >> 8);
+
+	for (i = 0; i < ARRAY_SIZE(shf_dvbt_tab); i++) {
+		if ((freq >= (shf_dvbt_tab[i].freq - shf_dvbt_tab[i].freq_th) * kHz) &&
+				(freq <= (shf_dvbt_tab[i].freq + shf_dvbt_tab[i].freq_th) * kHz)) {
+			rf_data[2 * (5) + 1] = shf_dvbt_tab[i].shf_val;
+			rf_data[2 * (6) + 1] = 0xa0 | shf_dvbt_tab[i].shf_dir;
+			break;
+		}
+	}
+	memcpy(data, rf_data, sizeof(rf_data));
+	*size = sizeof(rf_data);
+
+	pr_debug("mx_rftune freq=%d\n", freq);
+}
+
+/* write via demod */
+int mxl301rf_fe_write_data(struct dvb_frontend *fe, u8 addr_data, const u8 *data, int len)
+{
+	u8 buf[len + 1];
+
+	buf[0] = addr_data;
+	memcpy(buf + 1, data, len);
+	return fe->ops.write(fe, buf, len + 1);
+}
+
+#define MXL301RF_FE_PASSTHROUGH 0xfe
+
+int mxl301rf_fe_write_tuner(struct dvb_frontend *fe, const u8 *data, int len)
+{
+	u8 buf[len + 2];
+
+	buf[0] = MXL301RF_FE_PASSTHROUGH;
+	buf[1] = ((struct mxl301rf *)fe->tuner_priv)->addr_tuner << 1;
+	memcpy(buf + 2, data, len);
+	return fe->ops.write(fe, buf, len + 2);
+}
+
+/* read via demod */
+void mxl301rf_fe_read(struct dvb_frontend *fe, u8 addr, u8 *data)
+{
+	const u8 wbuf[2] = {0xfb, addr};
+	int ret;
+
+	mxl301rf_fe_write_tuner(fe, wbuf, sizeof(wbuf));
+	ret = fe->ops.write(fe, NULL, (1 << 16) | (((struct mxl301rf *)fe->tuner_priv)->addr_tuner << 8) | addr);
+	if (ret >= 0)
+		*data = ret;
+}
+
+void mxl301rf_idac_setting(struct dvb_frontend *fe)
+{
+	const u8 idac[] = {
+		0x0d, 0x00,
+		0x0c, 0x67,
+		0x6f, 0x89,
+		0x70, 0x0c,
+		0x6f, 0x8a,
+		0x70, 0x0e,
+		0x6f, 0x8b,
+		0x70, 0x10+12,
+	};
+	mxl301rf_fe_write_tuner(fe, idac, sizeof(idac));
+}
+
+void mxl301rf_set_register(struct dvb_frontend *fe, u8 addr, u8 value)
+{
+	const u8 data[2] = {addr, value};
+	mxl301rf_fe_write_tuner(fe, data, sizeof(data));
+}
+
+int mxl301rf_write_imsrst(struct dvb_frontend *fe)
+{
+	u8 data = 0x01 << 6;
+	return mxl301rf_fe_write_data(fe, 0x01, &data, 1);
+}
+
+enum mxl301rf_agc {
+	MXL301RF_AGC_AUTO,
+	MXL301RF_AGC_MANUAL,
+};
+
+int mxl301rf_set_agc(struct dvb_frontend *fe, enum mxl301rf_agc agc)
+{
+	u8 data = (agc == MXL301RF_AGC_AUTO) ? 0x40 : 0x00;
+	int ret = mxl301rf_fe_write_data(fe, 0x25, &data, 1);
+	if (ret)
+		return ret;
+
+	data = 0x4c | ((agc == MXL301RF_AGC_AUTO) ? 0x00 : 0x01);
+	return	mxl301rf_fe_write_data(fe, 0x23, &data, 1) ||
+		mxl301rf_write_imsrst(fe);
+}
+
+int mxl301rf_sleep(struct dvb_frontend *fe)
+{
+	u8 buf = (1 << 7) | (1 << 4);
+	const u8 data[4] = {0x01, 0x00, 0x13, 0x00};
+	int err = mxl301rf_set_agc(fe, MXL301RF_AGC_MANUAL);
+	if (err)
+		return err;
+	mxl301rf_fe_write_tuner(fe, data, sizeof(data));
+	return mxl301rf_fe_write_data(fe, 0x03, &buf, 1);
+}
+
+static const u8 mxl301rf_freq_tab[][3] = {
+	{   2, 0,  3 },
+	{  12, 1, 22 },
+	{  21, 0, 12 },
+	{  62, 1, 63 },
+	{ 112, 0, 62 }
+};
+
+bool mxl301rf_rfsynth_locked(struct dvb_frontend *fe)
+{
+	u8 data;
+
+	mxl301rf_fe_read(fe, 0x16, &data);
+	return (data & 0x0c) == 0x0c;
+}
+
+bool mxl301rf_refsynth_locked(struct dvb_frontend *fe)
+{
+	u8 data;
+
+	mxl301rf_fe_read(fe, 0x16, &data);
+	return (data & 0x03) == 0x03;
+}
+
+bool mxl301rf_locked(struct dvb_frontend *fe)
+{
+	bool locked1 = false, locked2 = false;
+	unsigned long timeout = jiffies + msecs_to_jiffies(100);
+
+	while (time_before(jiffies, timeout)) {
+		locked1 = mxl301rf_rfsynth_locked(fe);
+		locked2 = mxl301rf_refsynth_locked(fe);
+		if (locked1 && locked2)
+			break;
+		msleep_interruptible(1);
+	}
+	pr_debug("#%d %s lock1=%d lock2=%d\n", ((struct mxl301rf *)fe->tuner_priv)->idx, __func__, locked1, locked2);
+	return locked1 && locked2 ? !mxl301rf_set_agc(fe, MXL301RF_AGC_AUTO) : false;
+}
+
+int mxl301rf_tuner_rftune(struct dvb_frontend *fe, u32 freq)
+{
+	struct mxl301rf *mx = fe->tuner_priv;
+	u8 data[100];
+	u32 size = 0;
+	int err = mxl301rf_set_agc(fe, MXL301RF_AGC_MANUAL);
+	if (err)
+		return err;
+
+	mx->freq = freq;
+	mxl301rf_rftune(data, &size, freq);
+	if (size != 20) {
+		pr_debug("fail mx_rftune size = %d\n", size);
+		return -EINVAL;
+	}
+	mxl301rf_fe_write_tuner(fe, data, 14);
+	msleep_interruptible(1);
+	mxl301rf_fe_write_tuner(fe, data + 14, 6);
+	msleep_interruptible(1);
+	mxl301rf_set_register(fe, 0x1a, 0x0d);
+	mxl301rf_idac_setting(fe);
+
+	return mxl301rf_locked(fe) ? 0 : -ETIMEDOUT;
+}
+
+int mxl301rf_wakeup(struct dvb_frontend *fe)
+{
+	struct mxl301rf *mx = fe->tuner_priv;
+	int err;
+	u8 buf = (1 << 7) | (0 << 4);
+	const u8 data[2] = {0x01, 0x01};
+
+	err = mxl301rf_fe_write_data(fe, 0x03, &buf, 1);
+	if (err)
+		return err;
+	mxl301rf_fe_write_tuner(fe, data, sizeof(data));
+	mxl301rf_tuner_rftune(fe, mx->freq);
+	return 0;
+}
+
+void mxl301rf_ch2freq(u32 channel, bool *catv, u32 *number, u32 *freq)
+{
+	u32 i;
+	s32 freq_offset = 0;
+
+	if (12 <= channel)
+		freq_offset += 2;
+	if (17 <= channel)
+		freq_offset -= 2;
+	if (63 <= channel)
+		freq_offset += 2;
+	*freq = 93 + channel * 6 + freq_offset;
+
+	for (i = 0; i < ARRAY_SIZE(mxl301rf_freq_tab); i++) {
+		if (channel <= mxl301rf_freq_tab[i][0]) {
+			*catv = mxl301rf_freq_tab[i][1] ? true : false;
+			*number = channel + mxl301rf_freq_tab[i][2] - mxl301rf_freq_tab[i][0];
+			break;
+		}
+	}
+}
+
+int mxl301rf_release(struct dvb_frontend *fe)
+{
+	kfree(fe->tuner_priv);
+	fe->tuner_priv = NULL;
+	return 0;
+}
+
+static struct dvb_tuner_ops mxl301rf_ops = {
+	.info = {
+		.frequency_min	= 1,		/* actually 90 MHz, freq below that is handled as ch */
+		.frequency_max	= 770000000,	/* Hz */
+		.frequency_step	= 142857,
+	},
+	.set_frequency = mxl301rf_tuner_rftune,
+	.sleep = mxl301rf_sleep,
+	.init = mxl301rf_wakeup,
+	.release = mxl301rf_release,
+};
+
+int mxl301rf_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
+{
+	u8 d[] = { 0x10, 0x01 };
+	struct mxl301rf *mx = kzalloc(sizeof(struct mxl301rf), GFP_KERNEL);
+	if (!mx)
+		return -ENOMEM;
+	fe->tuner_priv = mx;
+	mx->fe = fe;
+	mx->idx = idx;
+	mx->addr_tuner = addr_tuner;
+	memcpy(&fe->ops.tuner_ops, &mxl301rf_ops, sizeof(struct dvb_tuner_ops));
+
+	return	mxl301rf_fe_write_data(fe, 0x1c, d, 1)	||
+		mxl301rf_fe_write_data(fe, 0x1d, d+1, 1);
+}
+EXPORT_SYMBOL(mxl301rf_attach);
+
diff --git a/drivers/media/tuners/mxl301rf.h b/drivers/media/tuners/mxl301rf.h
new file mode 100644
index 0000000..22599b9
--- /dev/null
+++ b/drivers/media/tuners/mxl301rf.h
@@ -0,0 +1,36 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-T tuner MaxLinear CMOS Hybrid TV MxL301RF
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 __MXL301RF_H__
+#define __MXL301RF_H__
+
+#ifndef pr_fmt
+ #define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+#endif
+#include "dvb_frontend.h"
+
+#if IS_ENABLED(CONFIG_MEDIA_TUNER_MXL301RF)
+extern int mxl301rf_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner);
+#else
+static inline int mxl301rf_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
+{
+	pr_warn("%s: driver disabled by Kconfig\n", __func__);
+	return 0;
+}
+#endif
+
+#endif
+
diff --git a/drivers/media/tuners/qm1d1c0042.c b/drivers/media/tuners/qm1d1c0042.c
new file mode 100644
index 0000000..3be552f
--- /dev/null
+++ b/drivers/media/tuners/qm1d1c0042.c
@@ -0,0 +1,382 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-S tuner driver QM1D1C0042
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "qm1d1c0042.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 QM1D1C0042 ISDB-S tuner driver");
+MODULE_LICENSE("GPL");
+
+struct qm1d1c0042 {
+	struct dvb_frontend *fe;
+	u8 addr_tuner, idx, reg[32];
+	u32 freq;
+};
+
+static const u8 qm1d1c0042_reg_rw[] = {
+	0x48, 0x1c, 0xa0, 0x10, 0xbc, 0xc5, 0x20, 0x33,
+	0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+	0x00, 0xff, 0xf3, 0x00, 0x2a, 0x64, 0xa6, 0x86,
+	0x8c, 0xcf, 0xb8, 0xf1, 0xa8, 0xf2, 0x89, 0x00,
+};
+
+/* read via demodulator */
+int qm1d1c0042_fe_read(struct dvb_frontend *fe, u8 addr, u8 *data)
+{
+	int ret;
+	if ((addr != 0x00) && (addr != 0x0d))
+		return -EFAULT;
+	ret = fe->ops.write(fe, NULL, (((struct qm1d1c0042 *)fe->tuner_priv)->addr_tuner << 8) | addr);
+	if (ret < 0)
+		return ret;
+	*data = ret;
+	return 0;
+}
+
+/* write via demodulator */
+int qm1d1c0042_fe_write_data(struct dvb_frontend *fe, u8 addr_data, u8 *data, int len)
+{
+	u8 buf[len + 1];
+
+	buf[0] = addr_data;
+	memcpy(buf + 1, data, len);
+	return fe->ops.write(fe, buf, len + 1);
+}
+
+#define QM1D1C0042_FE_PASSTHROUGH 0xfe
+
+int qm1d1c0042_fe_write_tuner(struct dvb_frontend *fe, u8 *data, int len)
+{
+	u8 buf[len + 2];
+
+	buf[0] = QM1D1C0042_FE_PASSTHROUGH;
+	buf[1] = ((struct qm1d1c0042 *)fe->tuner_priv)->addr_tuner << 1;
+	memcpy(buf + 2, data, len);
+	return fe->ops.write(fe, buf, len + 2);
+}
+
+int qm1d1c0042_write(struct dvb_frontend *fe, u8 addr, u8 data)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	u8 buf[] = { addr, data };
+	int err = qm1d1c0042_fe_write_tuner(fe, buf, sizeof(buf));
+	qm->reg[addr] = buf[1];
+	return err;
+}
+
+static const u8 qm1d1c0042_flag[32] = {
+	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
+	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+};
+
+int qm1d1c0042_write_pskmsrst(struct dvb_frontend *fe)
+{
+	u8 data = 0x01;
+	return qm1d1c0042_fe_write_data(fe, 0x03, &data, 1);
+}
+
+enum qm1d1c0042_agc {
+	QM1D1C0042_AGC_AUTO,
+	QM1D1C0042_AGC_MANUAL,
+};
+
+int qm1d1c0042_set_agc(struct dvb_frontend *fe, enum qm1d1c0042_agc agc)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	static u8 agc_data_s[2] = { 0xb0, 0x30 };
+	u8 data = (agc == QM1D1C0042_AGC_AUTO) ? 0xff : 0x00;
+	int ret = qm1d1c0042_fe_write_data(fe, 0x0a, &data, 1);
+	if (ret)
+		return ret;
+
+	data = agc_data_s[(qm->idx >> 1) & 1];
+	data |= (agc == QM1D1C0042_AGC_AUTO) ? 0x01 : 0x00;
+	ret = qm1d1c0042_fe_write_data(fe, 0x10, &data, 1);
+	if (ret)
+		return ret;
+
+	data = (agc == QM1D1C0042_AGC_AUTO) ? 0x40 : 0x00;
+	return (ret = qm1d1c0042_fe_write_data(fe, 0x11, &data, 1)) ? ret : qm1d1c0042_write_pskmsrst(fe);
+}
+
+int qm1d1c0042_sleep(struct dvb_frontend *fe)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	u8 buf = 1;
+	pr_debug("#%d %s\n", qm->idx, __func__);
+
+	qm->reg[0x01] &= (~(1 << 3)) & 0xff;
+	qm->reg[0x01] |= 1 << 0;
+	qm->reg[0x05] |= 1 << 3;
+	return	qm1d1c0042_set_agc(fe, QM1D1C0042_AGC_MANUAL)	||
+		qm1d1c0042_write(fe, 0x05, qm->reg[0x05])	||
+		qm1d1c0042_write(fe, 0x01, qm->reg[0x01])	||
+		qm1d1c0042_fe_write_data(fe, 0x17, &buf, 1);
+}
+
+int qm1d1c0042_wakeup(struct dvb_frontend *fe)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	u8 buf = 0;
+	pr_debug("#%d %s\n", qm->idx, __func__);
+
+	qm->reg[0x01] |= 1 << 3;
+	qm->reg[0x01] &= (~(1 << 0)) & 0xff;
+	qm->reg[0x05] &= (~(1 << 3)) & 0xff;
+	return	qm1d1c0042_fe_write_data(fe, 0x17, &buf, 1)	||
+		qm1d1c0042_write(fe, 0x01, qm->reg[0x01])	||
+		qm1d1c0042_write(fe, 0x05, qm->reg[0x05]);
+}
+
+void qm1d1c0042_get_channel_freq(u32 channel, u32 *number, u32 *freq)
+{
+	if (channel < 12) {
+		*number = 1 + 2 * channel;
+		*freq = 104948 + 3836 * channel;
+	} else if (channel < 24) {
+		channel -= 12;
+		*number = 2 + 2 * channel;
+		*freq = 161300 + 4000 * channel;
+	} else {
+		channel -= 24;
+		*number = 1 + 2 * channel;
+		*freq = 159300 + 4000 * channel;
+	}
+}
+
+static const u32 qm1d1c0042_freq_tab[9][3] = {
+	{ 2151000, 1, 7 },
+	{ 1950000, 1, 6 },
+	{ 1800000, 1, 5 },
+	{ 1600000, 1, 4 },
+	{ 1450000, 1, 3 },
+	{ 1250000, 1, 2 },
+	{ 1200000, 0, 7 },
+	{  975000, 0, 6 },
+	{  950000, 0, 0 }
+};
+
+static const u32 qm1d1c0042_sd_tab[24][2][3] = {
+	{{0x38fae1, 0x0d, 0x5}, {0x39fae1, 0x0d, 0x5},},
+	{{0x3f570a, 0x0e, 0x3}, {0x00570a, 0x0e, 0x3},},
+	{{0x05b333, 0x0e, 0x5}, {0x06b333, 0x0e, 0x5},},
+	{{0x3c0f5c, 0x0f, 0x4}, {0x3d0f5c, 0x0f, 0x4},},
+	{{0x026b85, 0x0f, 0x6}, {0x036b85, 0x0f, 0x6},},
+	{{0x38c7ae, 0x10, 0x5}, {0x39c7ae, 0x10, 0x5},},
+	{{0x3f23d7, 0x11, 0x3}, {0x0023d7, 0x11, 0x3},},
+	{{0x058000, 0x11, 0x5}, {0x068000, 0x11, 0x5},},
+	{{0x3bdc28, 0x12, 0x4}, {0x3cdc28, 0x12, 0x4},},
+	{{0x023851, 0x12, 0x6}, {0x033851, 0x12, 0x6},},
+	{{0x38947a, 0x13, 0x5}, {0x39947a, 0x13, 0x5},},
+	{{0x3ef0a3, 0x14, 0x3}, {0x3ff0a3, 0x14, 0x3},},
+	{{0x3c8000, 0x16, 0x4}, {0x3d8000, 0x16, 0x4},},
+	{{0x048000, 0x16, 0x6}, {0x058000, 0x16, 0x6},},
+	{{0x3c8000, 0x17, 0x5}, {0x3d8000, 0x17, 0x5},},
+	{{0x048000, 0x18, 0x3}, {0x058000, 0x18, 0x3},},
+	{{0x3c8000, 0x18, 0x6}, {0x3d8000, 0x18, 0x6},},
+	{{0x048000, 0x19, 0x4}, {0x058000, 0x19, 0x4},},
+	{{0x3c8000, 0x1a, 0x3}, {0x3d8000, 0x1a, 0x3},},
+	{{0x048000, 0x1a, 0x5}, {0x058000, 0x1a, 0x5},},
+	{{0x3c8000, 0x1b, 0x4}, {0x3d8000, 0x1b, 0x4},},
+	{{0x048000, 0x1b, 0x6}, {0x058000, 0x1b, 0x6},},
+	{{0x3c8000, 0x1c, 0x5}, {0x3d8000, 0x1c, 0x5},},
+	{{0x048000, 0x1d, 0x3}, {0x058000, 0x1d, 0x3},},
+};
+
+static int qm1d1c0042_tuning(struct qm1d1c0042 *qm, u32 *sd, u32 channel)
+{
+	int ret;
+	struct dvb_frontend *fe = qm->fe;
+	u8 i_data;
+	u32 i, N, A, index = (qm->idx >> 1) & 1;
+
+	qm->reg[0x08] &= 0xf0;
+	qm->reg[0x08] |= 0x09;
+
+	qm->reg[0x13] &= 0x9f;
+	qm->reg[0x13] |= 0x20;
+
+	for (i = 0; i < 8; i++) {
+		if ((qm1d1c0042_freq_tab[i+1][0] <= qm->freq) && (qm->freq < qm1d1c0042_freq_tab[i][0])) {
+			i_data = qm->reg[0x02];
+			i_data &= 0x0f;
+			i_data |= qm1d1c0042_freq_tab[i][1] << 7;
+			i_data |= qm1d1c0042_freq_tab[i][2] << 4;
+			qm1d1c0042_write(fe, 0x02, i_data);
+		}
+	}
+
+	*sd = qm1d1c0042_sd_tab[channel][index][0];
+	N = qm1d1c0042_sd_tab[channel][index][1];
+	A = qm1d1c0042_sd_tab[channel][index][2];
+
+	qm->reg[0x06] &= 0x40;
+	qm->reg[0x06] |= N;
+	ret = qm1d1c0042_write(fe, 0x06, qm->reg[0x06]);
+	if (ret)
+		return ret;
+
+	qm->reg[0x07] &= 0xf0;
+	qm->reg[0x07] |= A & 0x0f;
+	return qm1d1c0042_write(fe, 0x07, qm->reg[0x07]);
+}
+
+static int qm1d1c0042_local_lpf_tuning(struct qm1d1c0042 *qm, u32 channel)
+{
+	struct dvb_frontend *fe = qm->fe;
+	u8 i_data;
+	u32 sd = 0;
+	int ret = qm1d1c0042_tuning(qm, &sd, channel);
+	if (ret)
+		return ret;
+
+	i_data = qm->reg[0x08] & 0xf0;
+	i_data |= 2;
+	ret = qm1d1c0042_write(fe, 0x08, i_data);
+	if (ret)
+		return ret;
+
+	qm->reg[0x09] &= 0xc0;
+	qm->reg[0x09] |= (sd >> 16) & 0x3f;
+	qm->reg[0x0a] = (sd >> 8) & 0xff;
+	qm->reg[0x0b] = (sd >> 0) & 0xff;
+	ret =	qm1d1c0042_write(fe, 0x09, qm->reg[0x09])	||
+		qm1d1c0042_write(fe, 0x0a, qm->reg[0x0a])	||
+		qm1d1c0042_write(qm->fe, 0x0b, qm->reg[0x0b]);
+	if (ret)
+		return ret;
+
+	i_data = qm->reg[0x0c];
+	i_data &= 0x3f;
+	ret = qm1d1c0042_write(fe, 0x0c, i_data);
+	if (ret)
+		return ret;
+	msleep_interruptible(1);
+
+	i_data = qm->reg[0x0c];
+	i_data |= 0xc0;
+	return	qm1d1c0042_write(fe, 0x0c, i_data)	||
+		qm1d1c0042_write(fe, 0x08, 0x09)	||
+		qm1d1c0042_write(fe, 0x13, qm->reg[0x13]);
+}
+
+int qm1d1c0042_get_locked(struct qm1d1c0042 *qm, bool *locked)
+{
+	int ret = qm1d1c0042_fe_read(qm->fe, 0x0d, &qm->reg[0x0d]);
+	if (ret)
+		return ret;
+	if (qm->reg[0x0d] & 0x40)
+		*locked = true;
+	else
+		*locked = false;
+	return ret;
+}
+
+u32 qm1d1c0042_freq2ch(u32 frequency)
+{
+	u32 freq = frequency / 10,
+	    ch0 = (freq - 104948) / 3836, diff0 = freq - (104948 + 3836 * ch0),
+	    ch1 = (freq - 161300) / 4000, diff1 = freq - (161300 + 4000 * ch1),
+	    ch2 = (freq - 159300) / 4000, diff2 = freq - (159300 + 4000 * ch2),
+	    min = diff0 < diff1 ? diff0 : diff1;
+
+	if (frequency < 1024)
+		return frequency;	/* consider as channel ID if low */
+	if (diff2 < min)
+		return ch2 + 24;
+	else if (min == diff1)
+		return ch1 + 12;
+	else
+		return ch0;
+}
+
+int qm1d1c0042_set_freq(struct dvb_frontend *fe, u32 frequency)
+{
+	struct qm1d1c0042 *qm = fe->tuner_priv;
+	u32 channel = qm1d1c0042_freq2ch(frequency);
+	u32 number, freq, freq_kHz;
+	bool locked = false;
+	unsigned long timeout;
+	int err = qm1d1c0042_set_agc(fe, QM1D1C0042_AGC_MANUAL);
+	if (err)
+		return err;
+
+	qm1d1c0042_get_channel_freq(channel, &number, &freq);
+	freq_kHz = freq * 10;
+	if (((qm->idx >> 1) & 1) == 0)
+		freq_kHz -= 500;
+	else
+		freq_kHz += 500;
+	qm->freq = freq_kHz;
+	pr_debug("#%d ch %d freq %d kHz\n", qm->idx, channel, freq_kHz);
+
+	err = qm1d1c0042_local_lpf_tuning(qm, channel);
+	if (err)
+		return err;
+
+	timeout = jiffies + msecs_to_jiffies(1000);	/* 1s */
+	while (time_before(jiffies, timeout)) {
+		err = qm1d1c0042_get_locked(qm, &locked);
+		if (err)
+			return err;
+		if (locked)
+			break;
+		msleep_interruptible(1);
+	}
+	pr_debug("#%d %s %s\n", qm->idx, __func__, locked ? "LOCKED" : "TIMEOUT");
+	return locked ? qm1d1c0042_set_agc(fe, QM1D1C0042_AGC_AUTO) : -ETIMEDOUT;
+}
+
+int qm1d1c0042_release(struct dvb_frontend *fe)
+{
+	kfree(fe->tuner_priv);
+	fe->tuner_priv = NULL;
+	return 0;
+}
+
+static struct dvb_tuner_ops qm1d1c0042_ops = {
+	.info = {
+		.frequency_min	= 1,		/* actually 1024 kHz, freq below that is handled as ch */
+		.frequency_max	= 2150000,	/* kHz */
+		.frequency_step	= 1000,		/* = 1 MHz */
+	},
+	.set_frequency = qm1d1c0042_set_freq,
+	.sleep = qm1d1c0042_sleep,
+	.init = qm1d1c0042_wakeup,
+	.release = qm1d1c0042_release,
+};
+
+int qm1d1c0042_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
+{
+	u8 d[] = { 0x10, 0x15, 0x04 };
+	struct qm1d1c0042 *qm = kzalloc(sizeof(struct qm1d1c0042), GFP_KERNEL);
+	if (!qm)
+		return -ENOMEM;
+	fe->tuner_priv = qm;
+	qm->fe = fe;
+	qm->idx = idx;
+	qm->addr_tuner = addr_tuner;
+	memcpy(&fe->ops.tuner_ops, &qm1d1c0042_ops, sizeof(struct dvb_tuner_ops));
+
+	memcpy(qm->reg, qm1d1c0042_reg_rw, sizeof(qm1d1c0042_reg_rw));
+	qm->freq = 0;
+
+	return	qm1d1c0042_fe_write_data(fe, 0x1e, d,   1)	||
+		qm1d1c0042_fe_write_data(fe, 0x1c, d+1, 1)	||
+		qm1d1c0042_fe_write_data(fe, 0x1f, d+2, 1);
+}
+EXPORT_SYMBOL(qm1d1c0042_attach);
+
diff --git a/drivers/media/tuners/qm1d1c0042.h b/drivers/media/tuners/qm1d1c0042.h
new file mode 100644
index 0000000..2b14b70
--- /dev/null
+++ b/drivers/media/tuners/qm1d1c0042.h
@@ -0,0 +1,36 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-S tuner driver QM1D1C0042
+ *
+ * Copyright (C) 2014 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 __QM1D1C0042_H__
+#define __QM1D1C0042_H__
+
+#ifndef pr_fmt
+ #define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+#endif
+#include "dvb_frontend.h"
+
+#if IS_ENABLED(CONFIG_MEDIA_TUNER_QM1D1C0042)
+extern int qm1d1c0042_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner);
+#else
+static inline int qm1d1c0042_attach(struct dvb_frontend *fe, u8 idx, u8 addr_tuner)
+{
+	pr_warn("%s: driver disabled by Kconfig\n", __func__);
+	return 0;
+}
+#endif
+
+#endif
+
-- 
1.8.4.5


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH] Full DVB driver package for Earthsoft PT3 (ISDB-S/T) cards
  2013-12-19 23:14 Guest
  2013-12-21 13:24 ` Mauro Carvalho Chehab
@ 2013-12-21 16:06 ` Antti Palosaari
  1 sibling, 0 replies; 21+ messages in thread
From: Antti Palosaari @ 2013-12-21 16:06 UTC (permalink / raw)
  To: Guest, linux-media
  Cc: Bud R, mchehab, hdegoede, hverkuil, laurent.pinchart, mkrufky,
	sylvester.nawrocki, g.liakhovetski, peter.senna

On 20.12.2013 01:14, Guest wrote:
> From: Bud R <knightrider@are.ma>
>
> *** Is this okay? ***

No, that is huge patch bomb with a lot of things that should be 
implement differently.

First of all lets take a look of hardware in a level what chips there is 
and how those are connected.

MaxLinear MxL301RF multimode silicon RF tuner
Sharp QM1D1C0042 satellite silicon RF tuner
Toshiba TC90522 ISDB-S/T demodulator
Altera Cyclone IV FPGA, PCI-bridge

* Cyclone IV is FPGA, runs custom device vendor specific logic.
* TC90522 can stream multiple TS, ISDB-S and ISDB-T, at same time. I am 
not sure if that device could do it, but it should be taken into account 
when implementing demod driver.


>
> A DVB driver for Earthsoft PT3 (ISDB-S/T) receiver PCI Express cards, based on
> 1. PT3 chardev driver
> 	https://github.com/knight-rider/ptx/tree/master/pt3_drv
> 	https://github.com/m-tsudo/pt3
> 2. PT1/PT2 DVB driver
> 	drivers/media/pci/pt1
>
> It behaves similarly as PT1 DVB, plus some tuning enhancements:
> 1. in addition to the real frequency:
> 	ISDB-S : freq. channel ID
> 	ISDB-T : freq# (I/O# +128), ch#, ch# +64 for CATV
> 2. in addition to TSID:
> 	ISDB-S : slot#
>
> Feature changes:
> - dropped DKMS & standalone compile
> - dropped verbosity (debug levels), use single level -DDEBUG instead
> - changed SNR (.read_snr) to CNR (.read_signal_strength)
> - moved FE to drivers/media/dvb-frontends
> - moved demodulator & tuners to drivers/media/tuners

Those are not moved.

> - translated to standard (?) I2C protocol
> - dropped unused features
>
> The full package (buildable as standalone, DKMS or tree embedded module) is available at
> https://github.com/knight-rider/ptx/tree/master/pt3_dvb
>
> Signed-off-by: Bud R <knightrider@are.ma>
>
> ---
>   drivers/media/dvb-frontends/Kconfig      |  10 +-
>   drivers/media/dvb-frontends/Makefile     |   2 +
>   drivers/media/dvb-frontends/mxl301rf.c   | 332 ++++++++++++++
>   drivers/media/dvb-frontends/mxl301rf.h   |  27 ++

drivers/media/tuners/

>   drivers/media/dvb-frontends/pt3_common.h |  95 ++++
>   drivers/media/dvb-frontends/qm1d1c0042.c | 413 ++++++++++++++++++
>   drivers/media/dvb-frontends/qm1d1c0042.h |  34 ++

drivers/media/tuners/

>   drivers/media/dvb-frontends/tc90522.c    | 724 +++++++++++++++++++++++++++++++
>   drivers/media/dvb-frontends/tc90522.h    |  48 ++
>   drivers/media/pci/Kconfig                |   2 +-
>   drivers/media/pci/Makefile               |   1 +
>   drivers/media/pci/pt3/Kconfig            |  10 +
>   drivers/media/pci/pt3/Makefile           |   6 +
>   drivers/media/pci/pt3/pt3.c              | 543 +++++++++++++++++++++++
>   drivers/media/pci/pt3/pt3.h              |  23 +
>   drivers/media/pci/pt3/pt3_dma.c          | 335 ++++++++++++++
>   drivers/media/pci/pt3/pt3_dma.h          |  48 ++
>   drivers/media/pci/pt3/pt3_i2c.c          | 183 ++++++++
>   drivers/media/pci/pt3/pt3_i2c.h          |  30 ++


> +EXPORT_SYMBOL(mxl301rf_set_freq);
> +EXPORT_SYMBOL(mxl301rf_set_sleep);

You should bind "attach" tuner directly to the DVB frontend.


> +EXPORT_SYMBOL(qm1d1c0042_set_freq);
> +EXPORT_SYMBOL(qm1d1c0042_set_sleep);
> +EXPORT_SYMBOL(qm1d1c0042_tuner_init);


> +EXPORT_SYMBOL(tc90522_attach);
> +EXPORT_SYMBOL(tc90522_init);
> +EXPORT_SYMBOL(tc90522_set_powers);


First of all that driver should be converted to Kernel DVB driver model. 
It works something like:
You have a PCI driver (pt3). Then you call from attach(TC90522) from pt3 
in order to get frontend. After that you attach tuner to frontend 
calling attach(MxL301RF) or/and attach(QM1D1C0042).

In that case it is a little bit tricky as you have a *physically* single 
demod and 2 RF tuners. But what I looked that demod has itself 2 demods 
integrated to one package which could even operate same time. So, it 
means you have to register 2 frontends, one for ISDB-S and one for 
ISDB-T and attach correct tuner per frontend.

I know some developers may prefer to registering 2 multimode frontends 
"as a newer single frontend model" and then select operating mode using 
delivery-system command. Anyhow, that makes some extra headache as you 
should switch RF tuner per selected frontend standard. IMHO better to 
forgot fuss about single frontend model in that case and switch to older 
model where is two different standard frontends registered.


regards
Antti

-- 
http://palosaari.fi/

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Full DVB driver package for Earthsoft PT3 (ISDB-S/T) cards
  2013-12-19 23:14 Guest
@ 2013-12-21 13:24 ` Mauro Carvalho Chehab
  2013-12-21 16:06 ` Antti Palosaari
  1 sibling, 0 replies; 21+ messages in thread
From: Mauro Carvalho Chehab @ 2013-12-21 13:24 UTC (permalink / raw)
  To: Guest
  Cc: linux-media, Bud R, crope, mchehab, hdegoede, hverkuil,
	laurent.pinchart, mkrufky, sylvester.nawrocki, g.liakhovetski,
	peter.senna

Em Fri, 20 Dec 2013 08:14:11 +0900
Guest <info@are.ma> escreveu:

> From: Bud R <knightrider@are.ma>
> 
> *** Is this okay? ***
> 
> A DVB driver for Earthsoft PT3 (ISDB-S/T) receiver PCI Express cards, based on
> 1. PT3 chardev driver
> 	https://github.com/knight-rider/ptx/tree/master/pt3_drv
> 	https://github.com/m-tsudo/pt3
> 2. PT1/PT2 DVB driver
> 	drivers/media/pci/pt1
> 
> It behaves similarly as PT1 DVB, plus some tuning enhancements:
> 1. in addition to the real frequency:
> 	ISDB-S : freq. channel ID
> 	ISDB-T : freq# (I/O# +128), ch#, ch# +64 for CATV
> 2. in addition to TSID:
> 	ISDB-S : slot#
> 
> Feature changes:
> - dropped DKMS & standalone compile
> - dropped verbosity (debug levels), use single level -DDEBUG instead
> - changed SNR (.read_snr) to CNR (.read_signal_strength)
> - moved FE to drivers/media/dvb-frontends
> - moved demodulator & tuners to drivers/media/tuners
> - translated to standard (?) I2C protocol
> - dropped unused features
> 
> The full package (buildable as standalone, DKMS or tree embedded module) is available at
> https://github.com/knight-rider/ptx/tree/master/pt3_dvb
> 
> Signed-off-by: Bud R <knightrider@are.ma>
> 
> ---
>  drivers/media/dvb-frontends/Kconfig      |  10 +-
>  drivers/media/dvb-frontends/Makefile     |   2 +
>  drivers/media/dvb-frontends/mxl301rf.c   | 332 ++++++++++++++
>  drivers/media/dvb-frontends/mxl301rf.h   |  27 ++
>  drivers/media/dvb-frontends/pt3_common.h |  95 ++++
>  drivers/media/dvb-frontends/qm1d1c0042.c | 413 ++++++++++++++++++
>  drivers/media/dvb-frontends/qm1d1c0042.h |  34 ++
>  drivers/media/dvb-frontends/tc90522.c    | 724 +++++++++++++++++++++++++++++++
>  drivers/media/dvb-frontends/tc90522.h    |  48 ++
>  drivers/media/pci/Kconfig                |   2 +-
>  drivers/media/pci/Makefile               |   1 +
>  drivers/media/pci/pt3/Kconfig            |  10 +
>  drivers/media/pci/pt3/Makefile           |   6 +
>  drivers/media/pci/pt3/pt3.c              | 543 +++++++++++++++++++++++
>  drivers/media/pci/pt3/pt3.h              |  23 +
>  drivers/media/pci/pt3/pt3_dma.c          | 335 ++++++++++++++
>  drivers/media/pci/pt3/pt3_dma.h          |  48 ++
>  drivers/media/pci/pt3/pt3_i2c.c          | 183 ++++++++
>  drivers/media/pci/pt3/pt3_i2c.h          |  30 ++
>  19 files changed, 2864 insertions(+), 2 deletions(-)
>  create mode 100644 drivers/media/dvb-frontends/mxl301rf.c
>  create mode 100644 drivers/media/dvb-frontends/mxl301rf.h
>  create mode 100644 drivers/media/dvb-frontends/pt3_common.h
>  create mode 100644 drivers/media/dvb-frontends/qm1d1c0042.c
>  create mode 100644 drivers/media/dvb-frontends/qm1d1c0042.h
>  create mode 100644 drivers/media/dvb-frontends/tc90522.c
>  create mode 100644 drivers/media/dvb-frontends/tc90522.h
>  create mode 100644 drivers/media/pci/pt3/Kconfig
>  create mode 100644 drivers/media/pci/pt3/Makefile
>  create mode 100644 drivers/media/pci/pt3/pt3.c
>  create mode 100644 drivers/media/pci/pt3/pt3.h
>  create mode 100644 drivers/media/pci/pt3/pt3_dma.c
>  create mode 100644 drivers/media/pci/pt3/pt3_dma.h
>  create mode 100644 drivers/media/pci/pt3/pt3_i2c.c
>  create mode 100644 drivers/media/pci/pt3/pt3_i2c.h
> 
> diff --git a/drivers/media/dvb-frontends/Kconfig b/drivers/media/dvb-frontends/Kconfig
> index dd12a1e..44dec85 100644
> --- a/drivers/media/dvb-frontends/Kconfig
> +++ b/drivers/media/dvb-frontends/Kconfig
> @@ -591,7 +591,7 @@ config DVB_S5H1411
>  	  An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want
>  	  to support this frontend.
>  
> -comment "ISDB-T (terrestrial) frontends"
> +comment "ISDB-S (satellite) & ISDB-T (terrestrial) frontends"
>  	depends on DVB_CORE
>  
>  config DVB_S921
> @@ -618,6 +618,14 @@ config DVB_MB86A20S
>  	  A driver for Fujitsu mb86a20s ISDB-T/ISDB-Tsb demodulator.
>  	  Say Y when you want to support this frontend.
>  
> +config DVB_PT3_FE
> +	tristate "Earthsoft PT3 ISDB-S/T demodulator/tuner frontends"
> +	depends on DVB_CORE && I2C
> +	default m if !MEDIA_SUBDRV_AUTOSELECT
> +	help
> +	  DVB driver frontend for Earthsoft PT3 ISDB-S/ISDB-T PCIE cards.
> +	  Say Y when you want to support this frontend.
> +
>  comment "Digital terrestrial only tuners/PLL"
>  	depends on DVB_CORE
>  
> diff --git a/drivers/media/dvb-frontends/Makefile b/drivers/media/dvb-frontends/Makefile
> index 0c75a6a..0e8f029 100644
> --- a/drivers/media/dvb-frontends/Makefile
> +++ b/drivers/media/dvb-frontends/Makefile
> @@ -10,6 +10,7 @@ stv0900-objs := stv0900_core.o stv0900_sw.o
>  drxd-objs := drxd_firm.o drxd_hard.o
>  cxd2820r-objs := cxd2820r_core.o cxd2820r_c.o cxd2820r_t.o cxd2820r_t2.o
>  drxk-objs := drxk_hard.o
> +pt3_fe-objs := tc90522.o mxl301rf.o qm1d1c0042.o
>  
>  obj-$(CONFIG_DVB_PLL) += dvb-pll.o
>  obj-$(CONFIG_DVB_STV0299) += stv0299.o
> @@ -105,4 +106,5 @@ obj-$(CONFIG_DVB_RTL2830) += rtl2830.o
>  obj-$(CONFIG_DVB_RTL2832) += rtl2832.o
>  obj-$(CONFIG_DVB_M88RS2000) += m88rs2000.o
>  obj-$(CONFIG_DVB_AF9033) += af9033.o
> +obj-$(CONFIG_DVB_PT3_FE) += pt3_fe.o
>  
> diff --git a/drivers/media/dvb-frontends/mxl301rf.c b/drivers/media/dvb-frontends/mxl301rf.c
> new file mode 100644
> index 0000000..78310aa
> --- /dev/null
> +++ b/drivers/media/dvb-frontends/mxl301rf.c
> @@ -0,0 +1,332 @@
> +/*
> + * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-T tuner MaxLinear CMOS Hybrid TV MxL301RF
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 "pt3_common.h"

No. Frontends/tuners should not include board-specific includes. if you need
to pass configuration from PT3 into it, you should pass it as parameters to
the attach function.

> +#include "tc90522.h"
> +
> +MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
> +MODULE_DESCRIPTION("Earthsoft PT3 MxL301RF MaxLinear CMOS Hybrid TV ISDB-T tuner driver");
> +MODULE_LICENSE("GPL");
> +
> +static struct {
> +	u32	freq,		/* Channel center frequency @ kHz	*/
> +		freq_th;	/* Offset frequency threshold @ kHz	*/
> +	u8	shf_val,	/* Spur shift value			*/
> +		shf_dir;	/* Spur shift direction			*/
> +} SHF_DVBT_TAB[] = {

We reserve uppercase for defines only. Also, for the sake of a better
read, please split the struct definition from the table itself, like:

struct shf_frequency_tab {
	u32	freq,		/* Channel center frequency @ kHz	*/
		freq_th;	/* Offset frequency threshold @ kHz	*/
	u8	shf_val,	/* Spur shift value			*/
		shf_dir;	/* Spur shift direction			*/
};

static const struct shf_frequency_tab shf_dvbt_tab [] = {
...
};

Please put "const" on all tables, as otherwise gcc may not store it
as a table, but, instead, to convert it into code.

> +	{  64500, 500, 0x92, 0x07 },
> +	{ 191500, 300, 0xE2, 0x07 },
> +	{ 205500, 500, 0x2C, 0x04 },
> +	{ 212500, 500, 0x1E, 0x04 },
> +	{ 226500, 500, 0xD4, 0x07 },
> +	{  99143, 500, 0x9C, 0x07 },
> +	{ 173143, 500, 0xD4, 0x07 },
> +	{ 191143, 300, 0xD4, 0x07 },
> +	{ 207143, 500, 0xCE, 0x07 },
> +	{ 225143, 500, 0xCE, 0x07 },
> +	{ 243143, 500, 0xD4, 0x07 },
> +	{ 261143, 500, 0xD4, 0x07 },
> +	{ 291143, 500, 0xD4, 0x07 },
> +	{ 339143, 500, 0x2C, 0x04 },
> +	{ 117143, 500, 0x7A, 0x07 },
> +	{ 135143, 300, 0x7A, 0x07 },
> +	{ 153143, 500, 0x01, 0x07 }
> +};

We generally use lowcases for hexadecimal values.

> +
> +static void mxl301rf_rftune(u8 *data, u32 *size, u32 freq)
> +{
> +	u32 dig_rf_freq, tmp, frac_divider, kHz, MHz, i;
> +	u8 rf_data[] = {
> +		0x13, 0x00,	/* abort tune			*/
> +		0x3B, 0xC0,
> +		0x3B, 0x80,
> +		0x10, 0x95,	/* BW				*/
> +		0x1A, 0x05,
> +		0x61, 0x00,
> +		0x62, 0xA0,
> +		0x11, 0x40,	/* 2 bytes to store RF freq.	*/
> +		0x12, 0x0E,	/* 2 bytes to store RF freq.	*/
> +		0x13, 0x01	/* start tune			*/
> +	};
> +
> +	dig_rf_freq = 0;
> +	tmp = 0;
> +	frac_divider = 1000000;
> +	kHz = 1000;
> +	MHz = 1000000;
> +
> +	dig_rf_freq = freq / MHz;
> +	tmp = freq % MHz;
> +
> +	for (i = 0; i < 6; i++) {
> +		dig_rf_freq <<= 1;
> +		frac_divider /= 2;
> +		if (tmp > frac_divider) {
> +			tmp -= frac_divider;
> +			dig_rf_freq++;
> +		}
> +	}
> +	if (tmp > 7812)
> +		dig_rf_freq++;
> +
> +	rf_data[2 * (7) + 1] = (u8)(dig_rf_freq);
> +	rf_data[2 * (8) + 1] = (u8)(dig_rf_freq >> 8);
> +
> +	for (i = 0; i < sizeof(SHF_DVBT_TAB)/sizeof(*SHF_DVBT_TAB); i++) {
> +		if ((freq >= (SHF_DVBT_TAB[i].freq - SHF_DVBT_TAB[i].freq_th) * kHz) &&
> +				(freq <= (SHF_DVBT_TAB[i].freq + SHF_DVBT_TAB[i].freq_th) * kHz)) {
> +			rf_data[2 * (5) + 1] = SHF_DVBT_TAB[i].shf_val;
> +			rf_data[2 * (6) + 1] = 0xa0 | SHF_DVBT_TAB[i].shf_dir;
> +			break;
> +		}
> +	}
> +	memcpy(data, rf_data, sizeof(rf_data));
> +	*size = sizeof(rf_data);
> +
> +	pr_debug("mx_rftune freq=%d\n", freq);
> +}
> +
> +static void mxl301rf_standby(struct pt3_adapter *adap)
> +{
> +	u8 data[4] = {0x01, 0x00, 0x13, 0x00};
> +	tc90522_write_tuner_without_addr(adap, data, sizeof(data));
> +}
> +
> +static void mxl301rf_set_register(struct pt3_adapter *adap, u8 addr, u8 value)
> +{
> +	u8 data[2] = {addr, value};
> +	tc90522_write_tuner_without_addr(adap, data, sizeof(data));
> +}
> +
> +static void mxl301rf_idac_setting(struct pt3_adapter *adap)
> +{
> +	u8 data[] = {
> +		0x0D, 0x00,
> +		0x0C, 0x67,
> +		0x6F, 0x89,
> +		0x70, 0x0C,
> +		0x6F, 0x8A,
> +		0x70, 0x0E,
> +		0x6F, 0x8B,
> +		0x70, 0x10+12,
> +	};
> +	tc90522_write_tuner_without_addr(adap, data, sizeof(data));
> +}
> +
> +void mxl301rf_tuner_rftune(struct pt3_adapter *adap, u32 freq)
> +{
> +	u8 data[100];
> +	u32 size;
> +
> +	size = 0;
> +	adap->freq = freq;
> +	mxl301rf_rftune(data, &size, freq);
> +	if (size != 20) {
> +		pr_debug("fail mx_rftune size = %d\n", size);
> +		return;
> +	}
> +	tc90522_write_tuner_without_addr(adap, data, 14);
> +	msleep_interruptible(1);

Why to use msleep_interruptible() instead of just msleep()?

Btw, are you aware that this can actually sleep for up to 10 ms,
depending on how CONFIG_HZ is set?

> +	tc90522_write_tuner_without_addr(adap, data + 14, 6);
> +	msleep_interruptible(1);
> +	mxl301rf_set_register(adap, 0x1a, 0x0d);
> +	mxl301rf_idac_setting(adap);
> +}
> +
> +static void mxl301rf_wakeup(struct pt3_adapter *adap)
> +{
> +	u8 data[2] = {0x01, 0x01};
> +
> +	tc90522_write_tuner_without_addr(adap, data, sizeof(data));
> +	mxl301rf_tuner_rftune(adap, adap->freq);
> +}
> +
> +static void mxl301rf_set_sleep_mode(struct pt3_adapter *adap, bool sleep)
> +{
> +	if (sleep)
> +		mxl301rf_standby(adap);
> +	else
> +		mxl301rf_wakeup(adap);
> +}
> +
> +int mxl301rf_set_sleep(struct pt3_adapter *adap, bool sleep)
> +{
> +	int ret;
> +
> +	if (sleep) {
> +		ret = tc90522_set_agc(adap, TC90522_AGC_MANUAL);
> +		if (ret)
> +			return ret;
> +		mxl301rf_set_sleep_mode(adap, sleep);
> +		tc90522_write_sleep_time(adap, sleep);
> +	} else {
> +		tc90522_write_sleep_time(adap, sleep);
> +		mxl301rf_set_sleep_mode(adap, sleep);
> +	}
> +	adap->sleep = sleep;
> +	return 0;
> +}
> +
> +static u8 MXL301RF_FREQ_TABLE[][3] = {

Again, lowercase for tables, and use "const".

> +	{   2, 0,  3 },
> +	{  12, 1, 22 },
> +	{  21, 0, 12 },
> +	{  62, 1, 63 },
> +	{ 112, 0, 62 }
> +};
> +
> +void mxl301rf_get_channel_frequency(struct pt3_adapter *adap, u32 channel, bool *catv, u32 *number, u32 *freq)
> +{
> +	u32 i;
> +	s32 freq_offset = 0;
> +
> +	if (12 <= channel)
> +		freq_offset += 2;
> +	if (17 <= channel)
> +		freq_offset -= 2;
> +	if (63 <= channel)
> +		freq_offset += 2;
> +	*freq = 93 + channel * 6 + freq_offset;
> +
> +	for (i = 0; i < sizeof(MXL301RF_FREQ_TABLE) / sizeof(*MXL301RF_FREQ_TABLE); i++) {

Use ARRAY_SIZE(mxl301rf_freq_table) instead of those two sizeofs.

> +		if (channel <= MXL301RF_FREQ_TABLE[i][0]) {
> +			*catv = MXL301RF_FREQ_TABLE[i][1] ? true : false;
> +			*number = channel + MXL301RF_FREQ_TABLE[i][2] - MXL301RF_FREQ_TABLE[i][0];
> +			break;
> +		}
> +	}
> +}
> +
> +static u32 MXL301RF_RF_TABLE[112] = {
> +	0x058d3f49, 0x05e8ccc9, 0x06445a49, 0x069fe7c9, 0x06fb7549,
> +	0x075702c9, 0x07b29049, 0x080e1dc9, 0x0869ab49, 0x08c538c9,
> +	0x0920c649, 0x097c53c9, 0x09f665c9, 0x0a51f349, 0x0aad80c9,
> +	0x0b090e49, 0x0b649bc9, 0x0ba1a4c9, 0x0bfd3249, 0x0c58bfc9,
> +	0x0cb44d49, 0x0d0fdac9, 0x0d6b6849, 0x0dc6f5c9, 0x0e228349,
> +	0x0e7e10c9, 0x0ed99e49, 0x0f352bc9, 0x0f90b949, 0x0fec46c9,
> +	0x1047d449, 0x10a361c9, 0x10feef49, 0x115a7cc9, 0x11b60a49,
> +	0x121197c9, 0x126d2549, 0x12c8b2c9, 0x13244049, 0x137fcdc9,
> +	0x13db5b49, 0x1436e8c9, 0x14927649, 0x14ee03c9, 0x15499149,
> +	0x15a51ec9, 0x1600ac49, 0x165c39c9, 0x16b7c749, 0x171354c9,
> +	0x176ee249, 0x17ca6fc9, 0x1825fd49, 0x18818ac9, 0x18dd1849,
> +	0x1938a5c9, 0x19943349, 0x19efc0c9, 0x1a4b4e49, 0x1aa6dbc9,
> +	0x1b026949, 0x1b5df6c9, 0x1bb98449, 0x1c339649, 0x1c8f23c9,
> +	0x1ceab149, 0x1d463ec9, 0x1da1cc49, 0x1dfd59c9, 0x1e58e749,
> +	0x1eb474c9, 0x1f100249, 0x1f6b8fc9, 0x1fc71d49, 0x2022aac9,
> +	0x207e3849, 0x20d9c5c9, 0x21355349, 0x2190e0c9, 0x21ec6e49,
> +	0x2247fbc9, 0x22a38949, 0x22ff16c9, 0x235aa449, 0x23b631c9,
> +	0x2411bf49, 0x246d4cc9, 0x24c8da49, 0x252467c9, 0x257ff549,
> +	0x25db82c9, 0x26371049, 0x26929dc9, 0x26ee2b49, 0x2749b8c9,
> +	0x27a54649, 0x2800d3c9, 0x285c6149, 0x28b7eec9, 0x29137c49,
> +	0x296f09c9, 0x29ca9749, 0x2a2624c9, 0x2a81b249, 0x2add3fc9,
> +	0x2b38cd49, 0x2b945ac9, 0x2befe849, 0x2c4b75c9, 0x2ca70349,
> +	0x2d0290c9, 0x2d5e1e49,
> +};
> +
> +/* read via demodulator */
> +static void mxl301rf_read(struct pt3_adapter *adap, u8 addr, u8 *data)
> +{
> +	u8 write[2] = {0xfb, addr};
> +
> +	tc90522_write_tuner_without_addr(adap, write, sizeof(write));
> +	tc90522_read_tuner_without_addr(adap, data);
> +}
> +
> +bool mxl301rf_rfsynth_locked(struct pt3_adapter *adap)
> +{
> +	u8 data;
> +
> +	mxl301rf_read(adap, 0x16, &data);
> +	return (data & 0x0c) == 0x0c ? true : false;
> +}
> +
> +bool mxl301rf_refsynth_locked(struct pt3_adapter *adap)
> +{
> +	u8 data;
> +
> +	mxl301rf_read(adap, 0x16, &data);
> +	return (data & 0x03) == 0x03 ? true : false;
> +}
> +
> +bool mxl301rf_locked(struct pt3_adapter *adap)
> +{
> +	bool locked1, locked2;
> +	struct timeval begin, now;
> +
> +	do_gettimeofday(&begin);
> +	while (1) {
> +		do_gettimeofday(&now);
> +		locked1 = mxl301rf_rfsynth_locked(adap);
> +		locked2 = mxl301rf_refsynth_locked(adap);
> +		if (locked1 && locked2)
> +			break;
> +		if (tc90522_time_diff(&begin, &now) > 1000)
> +			break;
> +		msleep_interruptible(1);
> +	}

No. You shouldn't use gettimeofday(), nor define your own timediff logic.
gettimeofday is to expensive, and can cause troubles, if the clock
gets updated while the loop is being executed.

Instead, use jiffies and time_before/time_after. For example, 
something like:

	while (time_before(jiffies, jiffies + msecs_to_jiffies(1000)) {
		locked1 = mxl301rf_rfsynth_locked(adap);
		locked2 = mxl301rf_refsynth_locked(adap);
		if (locked1 && locked2)
			break;
		msleep(1);
	}

> +	pr_debug("#%d mx locked1=%d locked2=%d\n", adap->idx, locked1, locked2);
> +	return locked1 && locked2;
> +}
> +
> +int mxl301rf_set_freq(struct pt3_adapter *adap, u32 channel, s32 offset)
> +{
> +	bool catv;
> +	u32 number, freq, real_freq;
> +	int ret = tc90522_set_agc(adap, TC90522_AGC_MANUAL);
> +
> +	if (ret)
> +		return ret;
> +	mxl301rf_get_channel_frequency(adap, channel, &catv, &number, &freq);
> +	pr_debug("#%d ch%d%s no%d %dHz\n", adap->idx, channel, catv ? " CATV" : "", number, freq);
> +	/* real_freq = (7 * freq + 1 + offset) * 1000000.0/7.0; */
> +	real_freq = MXL301RF_RF_TABLE[channel];
> +
> +	mxl301rf_tuner_rftune(adap, real_freq);
> +
> +	return (!mxl301rf_locked(adap)) ? -ETIMEDOUT : tc90522_set_agc(adap, TC90522_AGC_AUTO);
> +}
> +
> +#define MXL301RF_NHK (MXL301RF_RF_TABLE[77])

I don't like those magic numbers. Why 77? Please add a comment.

Also, please put this define just after the table declaration.

> +int mxl301rf_freq(int freq)
> +{
> +	if (freq >= 90000000)
> +		return freq;					/* real_freq	*/
> +	if (freq > 255)
> +		return MXL301RF_NHK;
> +	if (freq > 127)
> +		return MXL301RF_RF_TABLE[freq - 128];		/* freqno (IO#)	*/
> +	if (freq > 63) {					/* CATV		*/
> +		freq -= 64;
> +		if (freq > 22)
> +			return MXL301RF_RF_TABLE[freq - 1];	/* C23-C62	*/
> +		if (freq > 12)
> +			return MXL301RF_RF_TABLE[freq - 10];	/* C13-C22	*/
> +		return MXL301RF_NHK;
> +	}
> +	if (freq > 62)
> +		return MXL301RF_NHK;
> +	if (freq > 12)
> +		return MXL301RF_RF_TABLE[freq + 50];		/* 13-62	*/
> +	if (freq >  3)
> +		return MXL301RF_RF_TABLE[freq +  9];		/*  4-12	*/
> +	if (freq)
> +		return MXL301RF_RF_TABLE[freq -  1];		/*  1-3		*/
> +	return MXL301RF_NHK;
> +}

The entire logic above seems wrong to me. Frequencies are always in HZ.

It should be up to userspace to convert channel numbers into frequencies.

> +
> +EXPORT_SYMBOL(mxl301rf_set_freq);
> +EXPORT_SYMBOL(mxl301rf_set_sleep);

Why to export those symbols? The only symbol that generally need to be exported
is the one that attaches the frontend.

Btw, scripts/checkpatch.pl complains about those two:

WARNING: EXPORT_SYMBOL(foo); should immediately follow its function/variable
#465: FILE: drivers/media/dvb-frontends/mxl301rf.c:330:
+EXPORT_SYMBOL(mxl301rf_set_freq);

WARNING: EXPORT_SYMBOL(foo); should immediately follow its function/variable
#466: FILE: drivers/media/dvb-frontends/mxl301rf.c:331:
+EXPORT_SYMBOL(mxl301rf_set_sleep);


> +
> diff --git a/drivers/media/dvb-frontends/mxl301rf.h b/drivers/media/dvb-frontends/mxl301rf.h
> new file mode 100644
> index 0000000..6ed58a6
> --- /dev/null
> +++ b/drivers/media/dvb-frontends/mxl301rf.h
> @@ -0,0 +1,27 @@
> +/*
> + * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-T tuner MaxLinear CMOS Hybrid TV MxL301RF
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 __MXL301RF_H__
> +#define __MXL301RF_H__
> +
> +int mxl301rf_set_freq(struct pt3_adapter *adap, u32 channel, s32 offset);
> +int mxl301rf_set_sleep(struct pt3_adapter *adap, bool sleep);
> +int mxl301rf_freq(int freq);
> +void mxl301rf_tuner_rftune(struct pt3_adapter *adap, u32 freq);
> +bool mxl301rf_locked(struct pt3_adapter *adap);
> +
> +#endif
> +
> diff --git a/drivers/media/dvb-frontends/pt3_common.h b/drivers/media/dvb-frontends/pt3_common.h
> new file mode 100644
> index 0000000..6ad1ecd
> --- /dev/null
> +++ b/drivers/media/dvb-frontends/pt3_common.h
> @@ -0,0 +1,95 @@
> +/*
> + * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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	__PT3_COMMON_H__
> +#define	__PT3_COMMON_H__
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME " " fmt
> +
> +#include <linux/pci.h>
> +#include <linux/kthread.h>
> +#include <linux/freezer.h>
> +#include "dvb_demux.h"
> +#include "dmxdev.h"
> +#include "dvb_frontend.h"
> +
> +#define PT3_NR_ADAPS 4
> +#define DRV_NAME "pt3_dvb"
> +
> +/* register idx */
> +#define REG_VERSION	0x00	/*	R	Version		*/
> +#define REG_BUS		0x04	/*	R	Bus		*/
> +#define REG_SYSTEM_W	0x08	/*	W	System		*/
> +#define REG_SYSTEM_R	0x0c	/*	R	System		*/
> +#define REG_I2C_W	0x10	/*	W	I2C		*/
> +#define REG_I2C_R	0x14	/*	R	I2C		*/
> +#define REG_RAM_W	0x18	/*	W	RAM		*/
> +#define REG_RAM_R	0x1c	/*	R	RAM		*/
> +#define REG_BASE	0x40	/* + 0x18*idx			*/
> +#define REG_DMA_DESC_L	0x00	/*	W	DMA		*/
> +#define REG_DMA_DESC_H	0x04	/*	W	DMA		*/
> +#define REG_DMA_CTL	0x08	/*	W	DMA		*/
> +#define REG_TS_CTL	0x0c	/*	W	TS		*/
> +#define REG_STATUS	0x10	/*	R	DMA/FIFO/TS	*/
> +#define REG_TS_ERR	0x14	/*	R	TS		*/
> +
> +struct pt3_adapter;
> +
> +struct pt3_board {
> +	struct mutex lock;
> +	int lnb;
> +	bool reset;
> +
> +	struct pci_dev *pdev;
> +	int bars;
> +	void __iomem *bar_reg, *bar_mem;
> +	struct i2c_adapter i2c;
> +	u8 i2c_buf;
> +	u32 i2c_addr;
> +	bool i2c_filled;
> +
> +	struct pt3_adapter *adap[PT3_NR_ADAPS];
> +};
> +
> +struct pt3_adapter {
> +	struct mutex lock;
> +	struct pt3_board *pt3;
> +
> +	int idx, init_ch;
> +	char *str;
> +	fe_delivery_system_t type;
> +	bool in_use, sleep;
> +	u32 channel;
> +	s32 offset;
> +	u8 addr_demod, addr_tuner;
> +	u32 freq;
> +	struct qm1d1c0042 *qm;
> +	struct pt3_dma *dma;
> +	struct task_struct *kthread;
> +	int *dec;
> +	struct dvb_adapter dvb;
> +	struct dvb_demux demux;
> +	int users;
> +	struct dmxdev dmxdev;
> +	struct dvb_frontend *fe;
> +	int (*orig_voltage)(struct dvb_frontend *fe, fe_sec_voltage_t voltage);
> +	int (*orig_sleep)  (struct dvb_frontend *fe);
> +	int (*orig_init)   (struct dvb_frontend *fe);
> +	fe_sec_voltage_t voltage;
> +};
> +
> +#endif
> +
> diff --git a/drivers/media/dvb-frontends/qm1d1c0042.c b/drivers/media/dvb-frontends/qm1d1c0042.c
> new file mode 100644
> index 0000000..2972bcf
> --- /dev/null
> +++ b/drivers/media/dvb-frontends/qm1d1c0042.c
> @@ -0,0 +1,413 @@
> +/*
> + * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-S tuner driver QM1D1C0042
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 "pt3_common.h"

Again, don't include PT3 specifics here.

> +#include "tc90522.h"
> +#include "qm1d1c0042.h"
> +
> +MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
> +MODULE_DESCRIPTION("Earthsoft PT3 QM1D1C0042 ISDB-S tuner driver");
> +MODULE_LICENSE("GPL");
> +
> +static u8 qm1d1c0042_reg_rw[] = {
> +	0x48, 0x1c, 0xa0, 0x10, 0xbc, 0xc5, 0x20, 0x33,
> +	0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
> +	0x00, 0xff, 0xf3, 0x00, 0x2a, 0x64, 0xa6, 0x86,
> +	0x8c, 0xcf, 0xb8, 0xf1, 0xa8, 0xf2, 0x89, 0x00,
> +};
> +
> +void qm1d1c0042_init_reg_param(struct qm1d1c0042 *qm)
> +{
> +	memcpy(qm->reg, qm1d1c0042_reg_rw, sizeof(qm1d1c0042_reg_rw));
> +
> +	qm->adap->freq = 0;
> +	qm->standby = false;
> +	qm->wait_time_lpf = 20;
> +	qm->wait_time_search_fast = 4;
> +	qm->wait_time_search_normal = 15;
> +}
> +
> +/* read via demodulator */
> +static int qm1d1c0042_read(struct qm1d1c0042 *qm, u8 addr, u8 *data)
> +{
> +	int ret = 0;
> +	if ((addr == 0x00) || (addr == 0x0d))
> +		ret = tc90522_read_tuner(qm->adap, addr, data);
> +	return ret;
> +}
> +
> +/* write via demodulator */
> +static int qm1d1c0042_write(struct qm1d1c0042 *qm, u8 addr, u8 data)
> +{
> +	int ret = tc90522_write_tuner(qm->adap, addr, &data, sizeof(data));
> +	qm->reg[addr] = data;
> +	return ret;
> +}
> +
> +#define QM1D1C0042_INIT_DUMMY_RESET 0x0c
> +
> +void qm1d1c0042_dummy_reset(struct qm1d1c0042 *qm)
> +{
> +	qm1d1c0042_write(qm, 0x01, QM1D1C0042_INIT_DUMMY_RESET);
> +	qm1d1c0042_write(qm, 0x01, QM1D1C0042_INIT_DUMMY_RESET);
> +}
> +
> +static u8 qm1d1c0042_flag[32] = {
> +	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
> +	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
> +};
> +
> +static int qm1d1c0042_set_sleep_mode(struct qm1d1c0042 *qm)
> +{
> +	int ret;
> +
> +	if (qm->standby) {
> +		qm->reg[0x01] &= (~(1 << 3)) & 0xff;
> +		qm->reg[0x01] |= 1 << 0;
> +		qm->reg[0x05] |= 1 << 3;
> +
> +		ret = qm1d1c0042_write(qm, 0x05, qm->reg[0x05]);
> +		if (ret)
> +			return ret;
> +		ret = qm1d1c0042_write(qm, 0x01, qm->reg[0x01]);
> +		if (ret)
> +			return ret;
> +	} else {
> +		qm->reg[0x01] |= 1 << 3;
> +		qm->reg[0x01] &= (~(1 << 0)) & 0xff;
> +		qm->reg[0x05] &= (~(1 << 3)) & 0xff;
> +
> +		ret = qm1d1c0042_write(qm, 0x01, qm->reg[0x01]);
> +		if (ret)
> +			return ret;
> +		ret = qm1d1c0042_write(qm, 0x05, qm->reg[0x05]);
> +		if (ret)
> +			return ret;
> +	}
> +	return ret;
> +}
> +
> +static int qm1d1c0042_set_search_mode(struct qm1d1c0042 *qm)
> +{
> +	qm->reg[3] &= 0xfe;
> +	return qm1d1c0042_write(qm, 0x03, qm->reg[3]);
> +}
> +
> +int qm1d1c0042_init(struct qm1d1c0042 *qm)
> +{
> +	u8 i_data;
> +	u32 i;
> +	int ret;
> +
> +	/* soft reset on */
> +	ret = qm1d1c0042_write(qm, 0x01, QM1D1C0042_INIT_DUMMY_RESET);
> +	if (ret)
> +		return ret;
> +
> +	msleep_interruptible(1);
> +
> +	/* soft reset off */
> +	i_data = qm->reg[0x01] | 0x10;
> +	ret = qm1d1c0042_write(qm, 0x01, i_data);
> +	if (ret)
> +		return ret;
> +
> +	/* ID check */
> +	ret = qm1d1c0042_read(qm, 0x00, &i_data);
> +	if (ret)
> +		return ret;
> +	if (i_data != 0x48)
> +		return -EINVAL;
> +
> +	/* LPF tuning on */
> +	msleep_interruptible(1);
> +	qm->reg[0x0c] |= 0x40;
> +	ret = qm1d1c0042_write(qm, 0x0c, qm->reg[0x0c]);
> +	if (ret)
> +		return ret;
> +	msleep_interruptible(qm->wait_time_lpf);
> +
> +	for (i = 0; i < sizeof(qm1d1c0042_flag); i++)
> +		if (qm1d1c0042_flag[i] == 1) {
> +			ret = qm1d1c0042_write(qm, i, qm->reg[i]);
> +			if (ret)
> +				return ret;
> +		}
> +	ret = qm1d1c0042_set_sleep_mode(qm);
> +	if (ret)
> +		return ret;
> +	return qm1d1c0042_set_search_mode(qm);
> +}
> +
> +int qm1d1c0042_set_sleep(struct qm1d1c0042 *qm, bool sleep)
> +{
> +	qm->standby = sleep;
> +	if (sleep) {
> +		int ret = tc90522_set_agc(qm->adap, TC90522_AGC_MANUAL);
> +		if (ret)
> +			return ret;
> +		qm1d1c0042_set_sleep_mode(qm);
> +		tc90522_set_sleep_s(qm->adap, sleep);
> +	} else {
> +		tc90522_set_sleep_s(qm->adap, sleep);
> +		qm1d1c0042_set_sleep_mode(qm);
> +	}
> +	qm->adap->sleep = sleep;
> +	return 0;
> +}
> +
> +void qm1d1c0042_get_channel_freq(u32 channel, u32 *number, u32 *freq)
> +{
> +	if (channel < 12) {
> +		*number = 1 + 2 * channel;
> +		*freq = 104948 + 3836 * channel;
> +	} else if (channel < 24) {
> +		channel -= 12;
> +		*number = 2 + 2 * channel;
> +		*freq = 161300 + 4000 * channel;
> +	} else {
> +		channel -= 24;
> +		*number = 1 + 2 * channel;
> +		*freq = 159300 + 4000 * channel;
> +	}
> +}

Again, frequencies are always specified in Hz, not in channels.

> +
> +static u32 QM1D1C0042_FREQ_TABLE[9][3] = {
> +	{ 2151000, 1, 7 },
> +	{ 1950000, 1, 6 },
> +	{ 1800000, 1, 5 },
> +	{ 1600000, 1, 4 },
> +	{ 1450000, 1, 3 },
> +	{ 1250000, 1, 2 },
> +	{ 1200000, 0, 7 },
> +	{  975000, 0, 6 },
> +	{  950000, 0, 0 }
> +};
> +
> +static u32 SD_TABLE[24][2][3] = {
> +	{{0x38fae1, 0x0d, 0x5}, {0x39fae1, 0x0d, 0x5},},
> +	{{0x3f570a, 0x0e, 0x3}, {0x00570a, 0x0e, 0x3},},
> +	{{0x05b333, 0x0e, 0x5}, {0x06b333, 0x0e, 0x5},},
> +	{{0x3c0f5c, 0x0f, 0x4}, {0x3d0f5c, 0x0f, 0x4},},
> +	{{0x026b85, 0x0f, 0x6}, {0x036b85, 0x0f, 0x6},},
> +	{{0x38c7ae, 0x10, 0x5}, {0x39c7ae, 0x10, 0x5},},
> +	{{0x3f23d7, 0x11, 0x3}, {0x0023d7, 0x11, 0x3},},
> +	{{0x058000, 0x11, 0x5}, {0x068000, 0x11, 0x5},},
> +	{{0x3bdc28, 0x12, 0x4}, {0x3cdc28, 0x12, 0x4},},
> +	{{0x023851, 0x12, 0x6}, {0x033851, 0x12, 0x6},},
> +	{{0x38947a, 0x13, 0x5}, {0x39947a, 0x13, 0x5},},
> +	{{0x3ef0a3, 0x14, 0x3}, {0x3ff0a3, 0x14, 0x3},},
> +	{{0x3c8000, 0x16, 0x4}, {0x3d8000, 0x16, 0x4},},
> +	{{0x048000, 0x16, 0x6}, {0x058000, 0x16, 0x6},},
> +	{{0x3c8000, 0x17, 0x5}, {0x3d8000, 0x17, 0x5},},
> +	{{0x048000, 0x18, 0x3}, {0x058000, 0x18, 0x3},},
> +	{{0x3c8000, 0x18, 0x6}, {0x3d8000, 0x18, 0x6},},
> +	{{0x048000, 0x19, 0x4}, {0x058000, 0x19, 0x4},},
> +	{{0x3c8000, 0x1a, 0x3}, {0x3d8000, 0x1a, 0x3},},
> +	{{0x048000, 0x1a, 0x5}, {0x058000, 0x1a, 0x5},},
> +	{{0x3c8000, 0x1b, 0x4}, {0x3d8000, 0x1b, 0x4},},
> +	{{0x048000, 0x1b, 0x6}, {0x058000, 0x1b, 0x6},},
> +	{{0x3c8000, 0x1c, 0x5}, {0x3d8000, 0x1c, 0x5},},
> +	{{0x048000, 0x1d, 0x3}, {0x058000, 0x1d, 0x3},},
> +};
> +
> +static int qm1d1c0042_tuning(struct qm1d1c0042 *qm, u32 *sd, u32 channel)
> +{
> +	int ret;
> +	struct pt3_adapter *adap = qm->adap;
> +	u8 i_data;
> +	u32 index, i, N, A;
> +
> +	qm->reg[0x08] &= 0xf0;
> +	qm->reg[0x08] |= 0x09;
> +
> +	qm->reg[0x13] &= 0x9f;
> +	qm->reg[0x13] |= 0x20;
> +
> +	for (i = 0; i < 8; i++) {
> +		if ((QM1D1C0042_FREQ_TABLE[i+1][0] <= adap->freq) && (adap->freq < QM1D1C0042_FREQ_TABLE[i][0])) {
> +			i_data = qm->reg[0x02];
> +			i_data &= 0x0f;
> +			i_data |= QM1D1C0042_FREQ_TABLE[i][1] << 7;
> +			i_data |= QM1D1C0042_FREQ_TABLE[i][2] << 4;
> +			qm1d1c0042_write(qm, 0x02, i_data);
> +		}
> +	}
> +
> +	index = tc90522_index(qm->adap);
> +	*sd = SD_TABLE[channel][index][0];
> +	N = SD_TABLE[channel][index][1];
> +	A = SD_TABLE[channel][index][2];
> +
> +	qm->reg[0x06] &= 0x40;
> +	qm->reg[0x06] |= N;
> +	ret = qm1d1c0042_write(qm, 0x06, qm->reg[0x06]);
> +	if (ret)
> +		return ret;
> +
> +	qm->reg[0x07] &= 0xf0;
> +	qm->reg[0x07] |= A & 0x0f;
> +	return qm1d1c0042_write(qm, 0x07, qm->reg[0x07]);
> +}
> +
> +static int qm1d1c0042_local_lpf_tuning(struct qm1d1c0042 *qm, int lpf, u32 channel)
> +{
> +	u8 i_data;
> +	u32 sd = 0;
> +	int ret = qm1d1c0042_tuning(qm, &sd, channel);
> +
> +	if (ret)
> +		return ret;
> +	if (lpf) {
> +		i_data = qm->reg[0x08] & 0xf0;
> +		i_data |= 2;
> +		ret = qm1d1c0042_write(qm, 0x08, i_data);
> +	} else
> +		ret = qm1d1c0042_write(qm, 0x08, qm->reg[0x08]);
> +	if (ret)
> +		return ret;
> +
> +	qm->reg[0x09] &= 0xc0;
> +	qm->reg[0x09] |= (sd >> 16) & 0x3f;
> +	qm->reg[0x0a] = (sd >> 8) & 0xff;
> +	qm->reg[0x0b] = (sd >> 0) & 0xff;
> +	ret = qm1d1c0042_write(qm, 0x09, qm->reg[0x09]);
> +	if (ret)
> +		return ret;
> +	ret = qm1d1c0042_write(qm, 0x0a, qm->reg[0x0a]);
> +	if (ret)
> +		return ret;
> +	ret = qm1d1c0042_write(qm, 0x0b, qm->reg[0x0b]);
> +	if (ret)
> +		return ret;
> +
> +	if (lpf) {
> +		i_data = qm->reg[0x0c];
> +		i_data &= 0x3f;
> +		ret = qm1d1c0042_write(qm, 0x0c, i_data);
> +		if (ret)
> +			return ret;
> +		msleep_interruptible(1);
> +
> +		i_data = qm->reg[0x0c];
> +		i_data |= 0xc0;
> +		ret = qm1d1c0042_write(qm, 0x0c, i_data);
> +		if (ret)
> +			return ret;
> +		msleep_interruptible(qm->wait_time_lpf);
> +		ret = qm1d1c0042_write(qm, 0x08, 0x09);
> +		if (ret)
> +			return ret;
> +		ret = qm1d1c0042_write(qm, 0x13, qm->reg[0x13]);
> +		if (ret)
> +			return ret;
> +	} else {
> +		ret = qm1d1c0042_write(qm, 0x13, qm->reg[0x13]);
> +		if (ret)
> +			return ret;
> +		i_data = qm->reg[0x0c];
> +		i_data &= 0x7f;
> +		ret = qm1d1c0042_write(qm, 0x0c, i_data);
> +		if (ret)
> +			return ret;
> +		msleep_interruptible(2);
> +
> +		i_data = qm->reg[0x0c];
> +		i_data |= 0x80;
> +		ret = qm1d1c0042_write(qm, 0x0c, i_data);
> +		if (ret)
> +			return ret;
> +		if (qm->reg[0x03] & 0x01)
> +			msleep_interruptible(qm->wait_time_search_fast);
> +		else
> +			msleep_interruptible(qm->wait_time_search_normal);
> +	}
> +	return ret;
> +}
> +
> +int qm1d1c0042_get_locked(struct qm1d1c0042 *qm, bool *locked)
> +{
> +	int ret = qm1d1c0042_read(qm, 0x0d, &qm->reg[0x0d]);
> +	if (ret)
> +		return ret;
> +	if (qm->reg[0x0d] & 0x40)
> +		*locked = true;
> +	else
> +		*locked = false;
> +	return ret;
> +}
> +
> +int qm1d1c0042_set_freq(struct qm1d1c0042 *qm, u32 channel)
> +{
> +	u32 number, freq, freq_kHz;
> +	struct timeval begin, now;
> +	bool locked;
> +	int ret = tc90522_set_agc(qm->adap, TC90522_AGC_MANUAL);
> +	if (ret)
> +		return ret;
> +
> +	qm1d1c0042_get_channel_freq(channel, &number, &freq);
> +	freq_kHz = freq * 10;
> +	if (tc90522_index(qm->adap) == 0)
> +		freq_kHz -= 500;
> +	else
> +		freq_kHz += 500;
> +	qm->adap->freq = freq_kHz;
> +	pr_debug("#%d ch %d freq %d kHz\n", qm->adap->idx, channel, freq_kHz);
> +
> +	ret = qm1d1c0042_local_lpf_tuning(qm, 1, channel);
> +	if (ret)
> +		return ret;
> +	do_gettimeofday(&begin);

Again, don't use gettimeofday(). This is only for userspace interfaces.
Use a jiffies-based time_before/time_after approach.

> +	while (1) {
> +		do_gettimeofday(&now);
> +		ret = qm1d1c0042_get_locked(qm, &locked);
> +		if (ret)
> +			return ret;
> +		if (locked)
> +			break;
> +		if (tc90522_time_diff(&begin, &now) >= 100)
> +			break;
> +		msleep_interruptible(1);
> +	}
> +	pr_debug("#%d qm_get_locked %d ret=0x%x\n", qm->adap->idx, locked, ret);
> +	if (!locked)
> +		return -ETIMEDOUT;
> +
> +	ret = tc90522_set_agc(qm->adap, TC90522_AGC_AUTO);
> +	if (!ret) {
> +		qm->adap->channel = channel;
> +		qm->adap->offset = 0;
> +	}
> +	return ret;
> +}
> +
> +int qm1d1c0042_tuner_init(struct qm1d1c0042 *qm)
> +{
> +	if (*qm->reg) {
> +		if (qm1d1c0042_init(qm))
> +			return -EIO;
> +	} else {
> +		qm1d1c0042_init_reg_param(qm);
> +		qm1d1c0042_dummy_reset(qm);
> +	}
> +	return 0;
> +}
> +
> +EXPORT_SYMBOL(qm1d1c0042_set_freq);
> +EXPORT_SYMBOL(qm1d1c0042_set_sleep);
> +EXPORT_SYMBOL(qm1d1c0042_tuner_init);

Again, don't export things here. Drivers should use the frontend
callbacks.

> +
> diff --git a/drivers/media/dvb-frontends/qm1d1c0042.h b/drivers/media/dvb-frontends/qm1d1c0042.h
> new file mode 100644
> index 0000000..079e846
> --- /dev/null
> +++ b/drivers/media/dvb-frontends/qm1d1c0042.h
> @@ -0,0 +1,34 @@
> +/*
> + * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-S tuner driver QM1D1C0042
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 __QM1D1C0042_H__
> +#define __QM1D1C0042_H__
> +
> +struct qm1d1c0042 {
> +	struct pt3_adapter *adap;
> +	u8 reg[32];
> +
> +	bool standby;
> +	u32 wait_time_lpf, wait_time_search_fast, wait_time_search_normal;
> +	struct tmcc_s tmcc;
> +};
> +
> +int qm1d1c0042_set_freq(struct qm1d1c0042 *qm, u32 channel);
> +int qm1d1c0042_set_sleep(struct qm1d1c0042 *qm, bool sleep);
> +int qm1d1c0042_tuner_init(struct qm1d1c0042 *qm);
> +
> +#endif
> +
> diff --git a/drivers/media/dvb-frontends/tc90522.c b/drivers/media/dvb-frontends/tc90522.c
> new file mode 100644
> index 0000000..bd162b7
> --- /dev/null
> +++ b/drivers/media/dvb-frontends/tc90522.c
> @@ -0,0 +1,724 @@
> +/*
> + * Earthsoft PT3 demodulator frontend Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S)
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 "pt3_common.h"
> +#include "tc90522.h"
> +#include "qm1d1c0042.h"
> +#include "mxl301rf.h"
> +
> +MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
> +MODULE_DESCRIPTION("Earthsoft PT3 Toshiba TC90522 OFDM(ISDB-T)/8PSK(ISDB-S) demodulator");
> +MODULE_LICENSE("GPL");
> +
> +int tc90522_write(struct pt3_adapter *adap, u8 addr, u8 *data, u8 len)
> +{
> +	struct i2c_msg msg[1];
> +	u8 buf[len + 1];
> +	buf[0] = addr;
> +	memcpy(buf + 1, data, len);
> +
> +	msg[0].addr = adap->addr_demod;
> +	msg[0].flags = 0;			/* write */
> +	msg[0].buf = buf;
> +	msg[0].len = len + 1;
> +
> +	return i2c_transfer(&adap->pt3->i2c, msg, 1) == 1 ? 0 : -EREMOTEIO;
> +}
> +
> +static int tc90522_write_pskmsrst(struct pt3_adapter *adap)
> +{
> +	u8 data = 0x01;
> +	return tc90522_write(adap, 0x03, &data, 1);
> +}
> +
> +static int tc90522_write_imsrst(struct pt3_adapter *adap)
> +{
> +	u8 data = 0x01 << 6;
> +	return tc90522_write(adap, 0x01, &data, 1);
> +}
> +
> +int tc90522_init(struct pt3_adapter *adap)
> +{
> +	u8 data = 0x10;
> +
> +	pr_debug("#%d %s tuner=0x%x demod=0x%x\n", adap->idx, adap->str, adap->addr_tuner, adap->addr_demod);
> +	if (adap->type == SYS_ISDBS) {
> +		u8 d[] = { 0x15, 0x04 };
> +		int ret = tc90522_write_pskmsrst(adap);
> +		if (ret)
> +			return ret;
> +		ret = tc90522_write(adap, 0x1e, &data, 1);
> +		if (ret)
> +			return ret;
> +		return (ret = tc90522_write(adap, 0x1c, &d[0], 1)) ?
> +			ret : tc90522_write(adap, 0x1f, &d[1], 1);
> +	} else {
> +		int ret = tc90522_write_imsrst(adap);
> +		if (ret)
> +			return ret;
> +		ret = tc90522_write(adap, 0x1c, &data, 1);
> +		if (ret)
> +			return ret;
> +		data = 0x01;
> +		return tc90522_write(adap, 0x1d, &data, 1);
> +	}
> +}
> +
> +int tc90522_set_powers(struct pt3_adapter *adap, bool tuner, bool amp)
> +{
> +	u8	tuner_power = tuner ? 0x03 : 0x02,
> +		amp_power = amp ? 0x03 : 0x02,
> +		data = (tuner_power << 6) | (0x01 << 4) | (amp_power << 2) | 0x01 << 0;
> +	pr_debug("#%d tuner %s amp %s\n", adap->idx, tuner ? "ON" : "OFF", amp ? "ON" : "OFF");
> +	return tc90522_write(adap, 0x1e, &data, 1);
> +}
> +
> +#define TC90522_THROUGH 0xfe

Better to put those register definitions either at the beginning of the
file or at a .h file.

> +
> +int tc90522_write_tuner(struct pt3_adapter *adap, u8 addr, const u8 *data, u32 len)
> +{
> +	struct i2c_msg msg[1];
> +	u8 buf[len + 3];
> +
> +	buf[0] = TC90522_THROUGH;
> +	buf[1] = adap->addr_tuner << 1;
> +	buf[2] = addr;
> +	memcpy(buf + 3, data, len);
> +	msg[0].buf = buf;
> +	msg[0].len = len + 3;
> +	msg[0].addr = adap->addr_demod;
> +	msg[0].flags = 0;			/* write */
> +
> +	return i2c_transfer(&adap->pt3->i2c, msg, 1) == 1 ? 0 : -EREMOTEIO;
> +}
> +
> +int tc90522_read_tuner(struct pt3_adapter *adap, u8 addr, u8 *data)
> +{
> +	struct i2c_msg msg[3];
> +	u8 buf[5];
> +
> +	buf[0] = TC90522_THROUGH;
> +	buf[1] = adap->addr_tuner << 1;
> +	buf[2] = addr;
> +	msg[0].buf = buf;
> +	msg[0].len = 3;
> +	msg[0].addr = adap->addr_demod;
> +	msg[0].flags = 0;			/* write */
> +
> +	buf[3] = TC90522_THROUGH;
> +	buf[4] = (adap->addr_tuner << 1) | 1;
> +	msg[1].buf = buf + 3;
> +	msg[1].len = 2;
> +	msg[1].addr = adap->addr_demod;
> +	msg[1].flags = 0;			/* write */
> +
> +	msg[2].buf = data;
> +	msg[2].len = 1;
> +	msg[2].addr = adap->addr_demod;
> +	msg[2].flags = I2C_M_RD;		/* read */
> +
> +	return i2c_transfer(&adap->pt3->i2c, msg, 3) == 3 ? 0 : -EREMOTEIO;
> +}
> +
> +static u8 agc_data_s[2] = { 0xb0, 0x30 };
> +
> +u32 tc90522_index(struct pt3_adapter *adap)
> +{
> +	return (adap->addr_demod >> 1) & 1;
> +}
> +
> +int tc90522_set_agc_s(struct pt3_adapter *adap, enum tc90522_agc agc)
> +{
> +	u8 data = (agc == TC90522_AGC_AUTO) ? 0xff : 0x00;
> +	int ret = tc90522_write(adap, 0x0a, &data, 1);
> +	if (ret)
> +		return ret;
> +
> +	data = agc_data_s[tc90522_index(adap)];
> +	data |= (agc == TC90522_AGC_AUTO) ? 0x01 : 0x00;
> +	ret = tc90522_write(adap, 0x10, &data, 1);
> +	if (ret)
> +		return ret;
> +
> +	data = (agc == TC90522_AGC_AUTO) ? 0x40 : 0x00;
> +	return (ret = tc90522_write(adap, 0x11, &data, 1)) ? ret : tc90522_write_pskmsrst(adap);
> +}
> +
> +int tc90522_set_sleep_s(struct pt3_adapter *adap, bool sleep)
> +{
> +	u8 buf = sleep ? 1 : 0;
> +	return tc90522_write(adap, 0x17, &buf, 1);
> +}
> +
> +int tc90522_set_agc_t(struct pt3_adapter *adap, enum tc90522_agc agc)
> +{
> +	u8 data = (agc == TC90522_AGC_AUTO) ? 0x40 : 0x00;
> +	int ret = tc90522_write(adap, 0x25, &data, 1);
> +	if (ret)
> +		return ret;
> +
> +	data = 0x4c | ((agc == TC90522_AGC_AUTO) ? 0x00 : 0x01);
> +	return (ret = tc90522_write(adap, 0x23, &data, 1)) ? ret : tc90522_write_imsrst(adap);
> +}
> +
> +int tc90522_set_agc(struct pt3_adapter *adap, enum tc90522_agc agc)
> +{
> +	if (adap->type == SYS_ISDBS)
> +		return tc90522_set_agc_s(adap, agc);
> +	else
> +		return tc90522_set_agc_t(adap, agc);
> +}
> +
> +int tc90522_write_tuner_without_addr(struct pt3_adapter *adap, const u8 *data, u32 len)
> +{
> +	struct i2c_msg msg[1];
> +	u8 buf[len + 2];
> +
> +	buf[0] = TC90522_THROUGH;
> +	buf[1] = adap->addr_tuner << 1;
> +	memcpy(buf + 2, data, len);
> +	msg[0].buf = buf;
> +	msg[0].len = len + 2;
> +	msg[0].addr = adap->addr_demod;
> +	msg[0].flags = 0;			/* write */
> +
> +	return i2c_transfer(&adap->pt3->i2c, msg, 1) == 1 ? 0 : -EREMOTEIO;
> +}
> +
> +int tc90522_write_sleep_time(struct pt3_adapter *adap, int sleep)
> +{
> +	u8 data = (1 << 7) | ((sleep ? 1 : 0) << 4);
> +	return tc90522_write(adap, 0x03, &data, 1);
> +}
> +
> +u32 tc90522_time_diff(struct timeval *st, struct timeval *et)
> +{
> +	u32 diff = ((et->tv_sec - st->tv_sec) * 1000000 + (et->tv_usec - st->tv_usec)) / 1000;
> +	pr_debug("time diff = %d\n", diff);
> +	return diff;
> +}
> +
> +int tc90522_read_tuner_without_addr(struct pt3_adapter *adap, u8 *data)
> +{
> +	struct i2c_msg msg[2];
> +	u8 buf[2];
> +
> +	buf[0] = TC90522_THROUGH;
> +	buf[1] = (adap->addr_tuner << 1) | 1;
> +	msg[0].buf = buf;
> +	msg[0].len = 2;
> +	msg[0].addr = adap->addr_demod;
> +	msg[0].flags = 0;			/* write */
> +
> +	msg[1].buf = data;
> +	msg[1].len = 1;
> +	msg[1].addr = adap->addr_demod;
> +	msg[1].flags = I2C_M_RD;		/* read */
> +
> +	return i2c_transfer(&adap->pt3->i2c, msg, 2) == 2 ? 0 : -EREMOTEIO;
> +}
> +
> +static u32 tc90522_byten(const u8 *data, u32 n)
> +{
> +	u32 i, val = 0;
> +
> +	for (i = 0; i < n; i++) {
> +		val <<= 8;
> +		val |= data[i];
> +	}
> +	return val;
> +}
> +
> +int tc90522_read(struct pt3_adapter *adap, u8 addr, u8 *buf, u8 buflen)
> +{
> +	struct i2c_msg msg[2];
> +	buf[0] = addr;
> +
> +	msg[0].addr = adap->addr_demod;
> +	msg[0].flags = 0;			/* write */
> +	msg[0].buf = buf;
> +	msg[0].len = 1;
> +
> +	msg[1].addr = adap->addr_demod;
> +	msg[1].flags = I2C_M_RD;		/* read */
> +	msg[1].buf = buf;
> +	msg[1].len = buflen;
> +
> +	return i2c_transfer(&adap->pt3->i2c, msg, 2) == 2 ? 0 : -EREMOTEIO;
> +}
> +
> +int tc90522_read_retryov_fulock(struct pt3_adapter *adap, bool *retryov, bool *fulock)
> +{
> +	u8 buf;
> +	int ret = tc90522_read(adap, 0x80, &buf, 1);
> +	if (!ret) {
> +		*retryov = buf & 0b10000000 ? true : false;
> +		*fulock  = buf & 0b00001000 ? true : false;
> +	}
> +	return ret;
> +}
> +
> +int tc90522_read_cn(struct pt3_adapter *adap, u32 *cn)
> +{
> +	u8 buf[3], buflen = (adap->type == SYS_ISDBS) ? 2 : 3, addr = (adap->type == SYS_ISDBS) ? 0xbc : 0x8b;
> +	int ret = tc90522_read(adap, addr, buf, buflen);
> +	if (!ret)
> +		*cn =  tc90522_byten(buf, buflen);
> +	return ret;
> +}
> +
> +int tc90522_read_id_s(struct pt3_adapter *adap, u16 *id)
> +{
> +	u8 buf[2];
> +	int ret = tc90522_read(adap, 0xe6, buf, 2);
> +	if (!ret)
> +		*id = tc90522_byten(buf, 2);
> +	return ret;
> +}
> +
> +int tc90522_read_tmcc_s(struct pt3_adapter *adap, struct tmcc_s *tmcc)
> +{
> +	enum {
> +		BASE = 0xc5,
> +		SIZE = 0xe5 - BASE + 1
> +	};
> +	u8 data[SIZE];
> +	u32 i, byte_offset, bit_offset;
> +
> +	int ret = tc90522_read(adap, 0xc3, data, 1);
> +	if (ret)
> +		return ret;
> +	if ((data[0] >> 4) & 1)
> +		return -EBADMSG;
> +
> +	ret = tc90522_read(adap, 0xce, data, 2);
> +	if (ret)
> +		return ret;
> +	if (tc90522_byten(data, 2) == 0)
> +		return -EBADMSG;
> +
> +	ret = tc90522_read(adap, 0xc3, data, 1);
> +	if (ret)
> +		return ret;
> +
> +	ret = tc90522_read(adap, 0xc5, data, SIZE);
> +	if (ret)
> +		return ret;
> +	for (i = 0; i < 4; i++) {
> +		byte_offset = i >> 1;
> +		bit_offset = (i & 1) ? 0 : 4;
> +		tmcc->mode[i] = (data[0xc8 + byte_offset - BASE] >> bit_offset) & 0b00001111;
> +		tmcc->slot[i] = (data[0xca + i           - BASE] >>          0) & 0b00111111;
> +	}
> +	for (i = 0; i < 8; i++)
> +		tmcc->id[i] = tc90522_byten(data + 0xce + i * 2 - BASE, 2);
> +	return ret;
> +}
> +
> +int tc90522_write_id_s(struct pt3_adapter *adap, u16 id)
> +{
> +	u8 data[2] = { id >> 8, (u8)id };
> +	return tc90522_write(adap, 0x8f, data, sizeof(data));
> +}
> +
> +/*
> + * DVB Frontend driver for Earthsoft PT3 ISDB-S/T PCI-E card
> + */
> +
> +/**** Common ****/
> +enum tc90522_tune_state {
> +	PT3_TUNE_IDLE,
> +	PT3_TUNE_SET_FREQUENCY,
> +	PT3_TUNE_CHECK_FREQUENCY,
> +	PT3_TUNE_SET_MODULATION,
> +	PT3_TUNE_CHECK_MODULATION,
> +	PT3_TUNE_SET_TS_ID,
> +	PT3_TUNE_CHECK_TS_ID,
> +	PT3_TUNE_TRACK,
> +	PT3_TUNE_ABORT,
> +};
> +
> +struct tc90522_state {
> +	struct pt3_adapter *adap;
> +	struct dvb_frontend fe;
> +	enum tc90522_tune_state tune_state;
> +};
> +
> +static int tc90522_read_signal_strength(struct dvb_frontend *fe, u16 *cn)
> +{
> +	struct tc90522_state *state = fe->demodulator_priv;
> +	return tc90522_read_cn(state->adap, (u32 *)cn);
> +}


What kind of stats this frontend provides? Only Signal strength? 
What scale?

Please use DVBv5 stats, in order to provide the proper scale for it.
See the dib8000 patches I posted this week if you need more info
about how to add support for it.

> +
> +static int tc90522_get_frontend_algo(struct dvb_frontend *fe)
> +{
> +	return DVBFE_ALGO_HW;
> +}
> +
> +static void tc90522_release(struct dvb_frontend *fe)
> +{
> +	kfree(fe->demodulator_priv);
> +}
> +
> +static int tc90522_fe_init(struct dvb_frontend *fe)
> +{
> +	struct tc90522_state *state = fe->demodulator_priv;
> +	state->tune_state = PT3_TUNE_IDLE;
> +	return (state->adap->type == SYS_ISDBS) ?
> +		qm1d1c0042_set_sleep(state->adap->qm, false) : mxl301rf_set_sleep(state->adap, false);
> +}
> +
> +static int tc90522_sleep(struct dvb_frontend *fe)
> +{
> +	struct tc90522_state *state = fe->demodulator_priv;
> +	return (state->adap->type == SYS_ISDBS) ?
> +		qm1d1c0042_set_sleep(state->adap->qm, true) : mxl301rf_set_sleep(state->adap, true);
> +}
> +
> +/**** ISDB-S ****/
> +u32 tc90522_s_get_channel(u32 frequency)
> +{
> +	u32 freq = frequency / 10,
> +	    ch0 = (freq - 104948) / 3836, diff0 = freq - (104948 + 3836 * ch0),
> +	    ch1 = (freq - 161300) / 4000, diff1 = freq - (161300 + 4000 * ch1),
> +	    ch2 = (freq - 159300) / 4000, diff2 = freq - (159300 + 4000 * ch2),
> +	    min = diff0 < diff1 ? diff0 : diff1;
> +
> +	if (diff2 < min)
> +		return ch2 + 24;
> +	else if (min == diff1)
> +		return ch1 + 12;
> +	else
> +		return ch0;
> +}
> +
> +static int tc90522_s_read_status(struct dvb_frontend *fe, fe_status_t *status)
> +{
> +	struct tc90522_state *state = fe->demodulator_priv;
> +
> +	switch (state->tune_state) {
> +	case PT3_TUNE_IDLE:
> +	case PT3_TUNE_SET_FREQUENCY:
> +	case PT3_TUNE_CHECK_FREQUENCY:
> +		*status = 0;
> +		return 0;
> +
> +	case PT3_TUNE_SET_MODULATION:
> +	case PT3_TUNE_CHECK_MODULATION:
> +	case PT3_TUNE_ABORT:
> +		*status |= FE_HAS_SIGNAL;
> +		return 0;
> +
> +	case PT3_TUNE_SET_TS_ID:
> +	case PT3_TUNE_CHECK_TS_ID:
> +		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER;
> +		return 0;
> +
> +	case PT3_TUNE_TRACK:
> +		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
> +		return 0;
> +	}
> +	BUG();
> +}
> +
> +static int tc90522_s_tune(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
> +{
> +	struct tc90522_state *state = fe->demodulator_priv;
> +	struct pt3_adapter *adap = state->adap;
> +	struct tmcc_s *tmcc = &adap->qm->tmcc;
> +	int i, ret,
> +	    freq = state->fe.dtv_property_cache.frequency,
> +	    tsid = state->fe.dtv_property_cache.stream_id,
> +	    ch = (freq < 1024) ? freq : tc90522_s_get_channel(freq);	/* consider as channel ID if low */
> +
> +	if (re_tune)
> +		state->tune_state = PT3_TUNE_SET_FREQUENCY;
> +
> +	switch (state->tune_state) {
> +	case PT3_TUNE_IDLE:
> +		*delay = 3 * HZ;
> +		*status = 0;
> +		return 0;
> +
> +	case PT3_TUNE_SET_FREQUENCY:
> +	case PT3_TUNE_CHECK_FREQUENCY:
> +		pr_debug("#%d freq %d tsid 0x%x ch %d\n", adap->idx, freq, tsid, ch);
> +		ret = qm1d1c0042_set_freq(adap->qm, ch);
> +		if (ret)
> +			return ret;
> +		adap->channel = ch;
> +		state->tune_state = PT3_TUNE_SET_MODULATION;
> +		*delay = 0;
> +		*status = FE_HAS_SIGNAL;
> +		return 0;
> +
> +	case PT3_TUNE_SET_MODULATION:
> +		for (i = 0; i < 1000; i++) {
> +			ret = tc90522_read_tmcc_s(adap, tmcc);
> +			if (!ret)
> +				break;
> +			msleep_interruptible(1);
> +		}
> +		if (ret) {
> +			pr_debug("fail tc_read_tmcc_s ret=0x%x\n", ret);
> +			state->tune_state = PT3_TUNE_ABORT;
> +			*delay = HZ;
> +			return ret;
> +		}
> +		pr_debug("slots=%d,%d,%d,%d mode=%d,%d,%d,%d\n",
> +				tmcc->slot[0], tmcc->slot[1], tmcc->slot[2], tmcc->slot[3],
> +				tmcc->mode[0], tmcc->mode[1], tmcc->mode[2], tmcc->mode[3]);
> +		state->tune_state = PT3_TUNE_CHECK_MODULATION;
> +		*delay = 0;
> +		*status = FE_HAS_SIGNAL;
> +		return 0;
> +
> +	case PT3_TUNE_CHECK_MODULATION:
> +		pr_debug("tmcc->id=0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x\n",
> +				tmcc->id[0], tmcc->id[1], tmcc->id[2], tmcc->id[3],
> +				tmcc->id[4], tmcc->id[5], tmcc->id[6], tmcc->id[7]);
> +		for (i = 0; i < sizeof(tmcc->id)/sizeof(tmcc->id[0]); i++) {
> +			pr_debug("tsid %x i %d tmcc->id %x\n", tsid, i, tmcc->id[i]);
> +			if (tmcc->id[i] == tsid)
> +				break;
> +		}
> +		if (tsid < sizeof(tmcc->id)/sizeof(tmcc->id[0]))	/* consider as slot# */

Please use ARRAY_SIZE().

> +			i = tsid;
> +		if (i == sizeof(tmcc->id)/sizeof(tmcc->id[0])) {

Please use ARRAY_SIZE().

> +			pr_debug("#%d i%d tsid 0x%x not found\n", adap->idx, i, tsid);
> +			return -EINVAL;
> +		}
> +		adap->offset = i;
> +		pr_debug("#%d found tsid 0x%x on slot %d\n", adap->idx, tsid, i);
> +		state->tune_state = PT3_TUNE_SET_TS_ID;
> +		*delay = 0;
> +		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER;
> +		return 0;
> +
> +	case PT3_TUNE_SET_TS_ID:
> +		ret = tc90522_write_id_s(adap, (u16)tmcc->id[adap->offset]);
> +		if (ret) {
> +			pr_debug("fail set_tmcc_s ret=%d\n", ret);
> +			return ret;
> +		}
> +		state->tune_state = PT3_TUNE_CHECK_TS_ID;
> +		return 0;
> +
> +	case PT3_TUNE_CHECK_TS_ID:
> +		for (i = 0; i < 1000; i++) {
> +			u16 short_id;
> +			ret = tc90522_read_id_s(adap, &short_id);
> +			if (ret) {
> +				pr_debug("fail get_id_s ret=%d\n", ret);
> +				return ret;
> +			}
> +			tsid = short_id;
> +			pr_debug("#%d tsid=0x%x\n", adap->idx, tsid);
> +			if ((tsid & 0xffff) == tmcc->id[adap->offset])
> +				break;
> +			msleep_interruptible(1);
> +		}
> +		state->tune_state = PT3_TUNE_TRACK;
> +
> +	case PT3_TUNE_TRACK:
> +		*delay = 3 * HZ;

Please use msecs_to_jiffies() on all those *delay settings.

> +		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
> +		return 0;
> +
> +	case PT3_TUNE_ABORT:
> +		*delay = 3 * HZ;
> +		*status = FE_HAS_SIGNAL;
> +		return 0;
> +	}
> +	BUG();
> +}
> +
> +static struct dvb_frontend_ops tc90522_s_ops = {
> +	.delsys = { SYS_ISDBS },

Can it be used simultaneously in ISDB-S and ISDB-T mode? If not, then
we may have a problem at the internal DVB API, as it should be just
one entry for both (but freqs for Satellite are in kHz).

> +	.info = {
> +		.name = "PT3 ISDB-S",
> +		.frequency_min = 1,

Seriously? I doubt it can use 1Hz as min freq.

> +		.frequency_max = 2150000,
> +		.frequency_stepsize = 1000,
> +		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO | FE_CAN_MULTISTREAM |
> +			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
> +	},
> +	.init = tc90522_fe_init,
> +	.sleep = tc90522_sleep,
> +	.release = tc90522_release,
> +	.get_frontend_algo = tc90522_get_frontend_algo,
> +	.read_signal_strength = tc90522_read_signal_strength,
> +	.read_status = tc90522_s_read_status,
> +	.tune = tc90522_s_tune,
> +};
> +
> +/**** ISDB-T ****/
> +static int tc90522_t_get_tmcc(struct pt3_adapter *adap)
> +{
> +	bool b = false, retryov, fulock;
> +
> +	while (1) {
> +		tc90522_read_retryov_fulock(adap, &retryov, &fulock);
> +		if (!fulock) {
> +			b = true;
> +			break;
> +		} else {
> +			if (retryov)
> +				break;
> +		}
> +		msleep_interruptible(1);
> +	}
> +	return b ? 0 : -EBADMSG;
> +}
> +
> +static int tc90522_t_read_status(struct dvb_frontend *fe, fe_status_t *status)
> +{
> +	struct tc90522_state *state = fe->demodulator_priv;
> +
> +	switch (state->tune_state) {
> +	case PT3_TUNE_IDLE:
> +	case PT3_TUNE_SET_FREQUENCY:
> +	case PT3_TUNE_CHECK_FREQUENCY:
> +		*status = 0;
> +		return 0;
> +
> +	case PT3_TUNE_SET_MODULATION:
> +	case PT3_TUNE_CHECK_MODULATION:
> +	case PT3_TUNE_SET_TS_ID:
> +	case PT3_TUNE_CHECK_TS_ID:
> +	case PT3_TUNE_ABORT:
> +		*status |= FE_HAS_SIGNAL;
> +		return 0;
> +
> +	case PT3_TUNE_TRACK:
> +		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
> +		return 0;
> +	}
> +	BUG();

Why do you need to produce an OOPS here? instead, just return an
error if the state is not valid.

> +}
> +
> +static int tc90522_t_tune(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
> +{
> +	struct tc90522_state *state = fe->demodulator_priv;
> +	int ret, i;
> +
> +	if (re_tune)
> +		state->tune_state = PT3_TUNE_SET_FREQUENCY;
> +
> +	switch (state->tune_state) {
> +	case PT3_TUNE_IDLE:
> +		*delay = 3 * HZ;
> +		*status = 0;
> +		return 0;
> +
> +	case PT3_TUNE_SET_FREQUENCY:
> +		ret = tc90522_set_agc(state->adap, TC90522_AGC_MANUAL);
> +		if (ret)
> +			return ret;
> +		mxl301rf_tuner_rftune(state->adap, mxl301rf_freq(state->fe.dtv_property_cache.frequency));
> +		state->tune_state = PT3_TUNE_CHECK_FREQUENCY;
> +		*delay = 0;
> +		*status = 0;
> +		return 0;
> +
> +	case PT3_TUNE_CHECK_FREQUENCY:
> +		if (!mxl301rf_locked(state->adap)) {
> +			*delay = HZ;
> +			*status = 0;
> +			return 0;
> +		}
> +		state->tune_state = PT3_TUNE_SET_MODULATION;
> +		*delay = 0;
> +		*status = FE_HAS_SIGNAL;
> +		return 0;
> +
> +	case PT3_TUNE_SET_MODULATION:
> +		ret = tc90522_set_agc(state->adap, TC90522_AGC_AUTO);
> +		if (ret)
> +			return ret;
> +		state->tune_state = PT3_TUNE_CHECK_MODULATION;
> +		*delay = 0;
> +		*status = FE_HAS_SIGNAL;
> +		return 0;
> +
> +	case PT3_TUNE_CHECK_MODULATION:
> +	case PT3_TUNE_SET_TS_ID:
> +	case PT3_TUNE_CHECK_TS_ID:
> +		for (i = 0; i < 1000; i++) {
> +			ret = tc90522_t_get_tmcc(state->adap);
> +			if (!ret)
> +				break;
> +			msleep_interruptible(2);
> +		}
> +		if (ret) {
> +			pr_debug("#%d fail get_tmcc_t ret=%d\n", state->adap->idx, ret);
> +				state->tune_state = PT3_TUNE_ABORT;
> +				*delay = HZ;
> +				return 0;
> +		}
> +		state->tune_state = PT3_TUNE_TRACK;
> +
> +	case PT3_TUNE_TRACK:
> +		*delay = 3 * HZ;
> +		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
> +		return 0;
> +
> +	case PT3_TUNE_ABORT:
> +		*delay = 3 * HZ;
> +		*status = FE_HAS_SIGNAL;
> +		return 0;
> +	}
> +	BUG();
> +}
> +
> +static struct dvb_frontend_ops tc90522_t_ops = {
> +	.delsys = { SYS_ISDBT },
> +	.info = {
> +		.name = "PT3 ISDB-T",
> +		.frequency_min = 1,
> +		.frequency_max = 770000000,
> +		.frequency_stepsize = 142857,
> +		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO |
> +			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
> +	},
> +	.init = tc90522_fe_init,
> +	.sleep = tc90522_sleep,
> +	.release = tc90522_release,
> +	.get_frontend_algo = tc90522_get_frontend_algo,
> +	.read_signal_strength = tc90522_read_signal_strength,
> +	.read_status = tc90522_t_read_status,
> +	.tune = tc90522_t_tune,
> +};

The same notes I did for ISDB-S applies for ISDB-T.

> +
> +/**** Common ****/
> +struct dvb_frontend *tc90522_attach(struct pt3_adapter *adap)
> +{
> +	struct dvb_frontend *fe;
> +	struct tc90522_state *state = kzalloc(sizeof(struct tc90522_state), GFP_KERNEL);
> +
> +	if (!state)
> +		return NULL;
> +	state->adap = adap;
> +	fe = &state->fe;
> +	memcpy(&fe->ops, (adap->type == SYS_ISDBS) ? &tc90522_s_ops : &tc90522_t_ops, sizeof(struct dvb_frontend_ops));
> +	fe->demodulator_priv = state;
> +	return fe;
> +}
> +
> +EXPORT_SYMBOL(tc90522_attach);
> +EXPORT_SYMBOL(tc90522_init);
> +EXPORT_SYMBOL(tc90522_set_powers);

Why not only attach? Please check the patch with checkpatch.pl.

> +
> diff --git a/drivers/media/dvb-frontends/tc90522.h b/drivers/media/dvb-frontends/tc90522.h
> new file mode 100644
> index 0000000..6a8b618
> --- /dev/null
> +++ b/drivers/media/dvb-frontends/tc90522.h
> @@ -0,0 +1,48 @@
> +/*
> + * Earthsoft PT3 demodulator frontend Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S)
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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	__TC90522_H__
> +#define	__TC90522_H__
> +
> +/* Transmission and Multiplexing Configuration Control */
> +
> +struct tmcc_s {
> +	u32 mode[4];
> +	u32 slot[4];
> +	u32 id[8];
> +};
> +
> +enum tc90522_agc {
> +	TC90522_AGC_AUTO,
> +	TC90522_AGC_MANUAL,
> +};
> +
> +u32 tc90522_index(struct pt3_adapter *adap);
> +int tc90522_init(struct pt3_adapter *adap);
> +int tc90522_read_tuner(struct pt3_adapter *adap, u8 addr, u8 *data);
> +int tc90522_read_tuner_without_addr(struct pt3_adapter *adap, u8 *data);
> +int tc90522_set_agc(struct pt3_adapter *adap, enum tc90522_agc agc);
> +int tc90522_set_powers(struct pt3_adapter *adap, bool tuner, bool amp);
> +int tc90522_set_sleep_s(struct pt3_adapter *adap, bool sleep);
> +u32 tc90522_time_diff(struct timeval *st, struct timeval *et);
> +int tc90522_write_sleep_time(struct pt3_adapter *adap, int sleep);
> +int tc90522_write_tuner(struct pt3_adapter *adap, u8 addr, const u8 *data, u32 size);
> +int tc90522_write_tuner_without_addr(struct pt3_adapter *adap, const u8 *data, u32 size);

Why so many exported functions? That seems wrong.

> +
> +struct dvb_frontend *tc90522_attach(struct pt3_adapter *adap);
> +
> +#endif
> +
> diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
> index 53196f1..87018c8 100644
> --- a/drivers/media/pci/Kconfig
> +++ b/drivers/media/pci/Kconfig
> @@ -30,7 +30,6 @@ source "drivers/media/pci/cx88/Kconfig"
>  source "drivers/media/pci/bt8xx/Kconfig"
>  source "drivers/media/pci/saa7134/Kconfig"
>  source "drivers/media/pci/saa7164/Kconfig"
> -
>  endif
>  
>  if MEDIA_DIGITAL_TV_SUPPORT
> @@ -40,6 +39,7 @@ source "drivers/media/pci/b2c2/Kconfig"
>  source "drivers/media/pci/pluto2/Kconfig"
>  source "drivers/media/pci/dm1105/Kconfig"
>  source "drivers/media/pci/pt1/Kconfig"
> +source "drivers/media/pci/pt3/Kconfig"
>  source "drivers/media/pci/mantis/Kconfig"
>  source "drivers/media/pci/ngene/Kconfig"
>  source "drivers/media/pci/ddbridge/Kconfig"
> diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
> index 35cc578..f7be6bc 100644
> --- a/drivers/media/pci/Makefile
> +++ b/drivers/media/pci/Makefile
> @@ -7,6 +7,7 @@ obj-y        +=	ttpci/		\
>  		pluto2/		\
>  		dm1105/		\
>  		pt1/		\
> +		pt3/		\
>  		mantis/		\
>  		ngene/		\
>  		ddbridge/	\
> diff --git a/drivers/media/pci/pt3/Kconfig b/drivers/media/pci/pt3/Kconfig
> new file mode 100644
> index 0000000..519fdaa
> --- /dev/null
> +++ b/drivers/media/pci/pt3/Kconfig
> @@ -0,0 +1,10 @@
> +config PT3_DVB
> +	tristate "Earthsoft PT3 ISDB-S/T cards"
> +	depends on DVB_CORE && PCI
> +	select DVB_PT3_FE if MEDIA_SUBDRV_AUTOSELECT
> +	select MEDIA_TUNER_PT3 if MEDIA_SUBDRV_AUTOSELECT
> +	help
> +	  Support for Earthsoft PT3 PCI-Express cards.
> +	  You may also need to enable PT3 DVB frontend & tuner.
> +	  Say Y or M if you own such a device and want to use it.
> +
> diff --git a/drivers/media/pci/pt3/Makefile b/drivers/media/pci/pt3/Makefile
> new file mode 100644
> index 0000000..b030927
> --- /dev/null
> +++ b/drivers/media/pci/pt3/Makefile
> @@ -0,0 +1,6 @@
> +pt3_dvb-objs := pt3.o pt3_dma.o pt3_i2c.o
> +
> +obj-$(CONFIG_PT3_DVB) += pt3_dvb.o
> +
> +ccflags-y += -Idrivers/media/dvb-core -Idrivers/media/dvb-frontends -Idrivers/media/tuners
> +
> diff --git a/drivers/media/pci/pt3/pt3.c b/drivers/media/pci/pt3/pt3.c
> new file mode 100644
> index 0000000..9c186cb
> --- /dev/null
> +++ b/drivers/media/pci/pt3/pt3.c
> @@ -0,0 +1,543 @@
> +/*
> + * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card - Altera FPGA EP4CGX15BF14C8N
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 "pt3_common.h"
> +#include "pt3_dma.h"
> +#include "pt3_i2c.h"
> +#include "tc90522.h"
> +#include "qm1d1c0042.h"
> +#include "mxl301rf.h"
> +
> +MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
> +MODULE_DESCRIPTION("Earthsoft PT3 DVB Driver");
> +MODULE_LICENSE("GPL");
> +
> +static DEFINE_PCI_DEVICE_TABLE(pt3_id_table) = {
> +	{ PCI_DEVICE(0x1172, 0x4c15) },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(pci, pt3_id_table);
> +
> +static int lnb = 2;	/* used if not set by frontend or the value is invalid */
> +module_param(lnb, int, 0);
> +MODULE_PARM_DESC(lnb, "LNB level (0:OFF 1:+11V 2:+15V)");
> +
> +struct {
> +	u32 bits;
> +	char *str;
> +} pt3_lnb[] = {
> +	{0b1100,  "0V"},
> +	{0b1101, "11V"},
> +	{0b1111, "15V"},
> +};
> +
> +static int pt3_update_lnb(struct pt3_board *pt3)
> +{
> +	u8 i, lnb_eff = 0;
> +
> +	if (pt3->reset) {
> +		writel(pt3_lnb[0].bits, pt3->bar_reg + REG_SYSTEM_W);
> +		pt3->reset = false;
> +		pt3->lnb = 0;
> +	} else {
> +		struct pt3_adapter *adap;
> +		mutex_lock(&pt3->lock);
> +		for (i = 0; i < PT3_NR_ADAPS; i++) {
> +			adap = pt3->adap[i];
> +			pr_debug("#%d in_use %d sleep %d\n", adap->idx, adap->in_use, adap->sleep);
> +			if ((adap->type == SYS_ISDBS) && (!adap->sleep)) {
> +				lnb_eff |= adap->voltage == SEC_VOLTAGE_13 ? 1
> +					:  adap->voltage == SEC_VOLTAGE_18 ? 2
> +					:  lnb;
> +			}
> +		}
> +		mutex_unlock(&pt3->lock);
> +		if (unlikely(lnb_eff < 0 || 2 < lnb_eff)) {
> +			pr_err("Inconsistent LNB settings\n");
> +			return -EINVAL;
> +		}
> +		if (pt3->lnb != lnb_eff) {
> +			writel(pt3_lnb[lnb_eff].bits, pt3->bar_reg + REG_SYSTEM_W);
> +			pt3->lnb = lnb_eff;
> +		}
> +	}
> +	pr_debug("LNB=%s\n", pt3_lnb[lnb_eff].str);
> +	return 0;
> +}

LNB settings should likely be inside the dvb-frontend, and not here.

> +
> +void pt3_filter(struct pt3_adapter *adap, struct dvb_demux *demux, u8 *buf, size_t count)
> +{
> +	dvb_dmx_swfilter(demux, buf, count);
> +}
> +
> +int pt3_thread(void *data)
> +{
> +	size_t ret;
> +	struct pt3_adapter *adap = data;
> +
> +	set_freezable();
> +	while (!kthread_should_stop()) {
> +		try_to_freeze();
> +		while ((ret = pt3_dma_copy(adap->dma, &adap->demux)) > 0)
> +			;
> +		if (ret < 0) {
> +			pr_debug("#%d fail dma_copy\n", adap->idx);
> +			msleep_interruptible(1);
> +		}
> +	}
> +	return 0;
> +}

Why do you need a thread on this driver?

> +
> +static int pt3_start_polling(struct pt3_adapter *adap)
> +{
> +	int ret = 0;
> +
> +	mutex_lock(&adap->lock);
> +	if (!adap->kthread) {
> +		adap->kthread = kthread_run(pt3_thread, adap, DRV_NAME "_%d", adap->idx);
> +		if (IS_ERR(adap->kthread)) {
> +			ret = PTR_ERR(adap->kthread);
> +			adap->kthread = NULL;
> +		} else {
> +			pt3_dma_set_test_mode(adap->dma, RESET, 0);	/* reset_error_count */
> +			pt3_dma_set_enabled(adap->dma, true);
> +		}
> +	}
> +	mutex_unlock(&adap->lock);
> +	return ret;
> +}
> +
> +static void pt3_stop_polling(struct pt3_adapter *adap)
> +{
> +	mutex_lock(&adap->lock);
> +	if (adap->kthread) {
> +		pt3_dma_set_enabled(adap->dma, false);
> +		pr_debug("#%d DMA ts_err packet cnt %d\n",
> +			adap->idx, pt3_dma_get_ts_error_packet_count(adap->dma));
> +		kthread_stop(adap->kthread);
> +		adap->kthread = NULL;
> +	}
> +	mutex_unlock(&adap->lock);
> +}
> +
> +static int pt3_start_feed(struct dvb_demux_feed *feed)
> +{
> +	int ret;
> +	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
> +	if (!adap->users++) {
> +		if (adap->in_use) {
> +			pr_err("#%d device is already used\n", adap->idx);
> +			return -EIO;
> +		}
> +		pr_debug("#%d %s selected, DMA %s\n",
> +			adap->idx, adap->str, pt3_dma_get_status(adap->dma) & 1 ? "ON" : "OFF");
> +		adap->in_use = true;
> +		ret = pt3_start_polling(adap);
> +		if (ret)
> +			return ret;
> +	}
> +	return 0;
> +}
> +
> +static int pt3_stop_feed(struct dvb_demux_feed *feed)
> +{
> +	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
> +	if (!--adap->users) {
> +		pt3_stop_polling(adap);
> +		adap->in_use = false;
> +		msleep_interruptible(40);
> +	}
> +	return 0;
> +}
> +
> +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
> +
> +static struct pt3_adapter *pt3_alloc_adapter(struct pt3_board *pt3)
> +{
> +	int ret;
> +	struct dvb_adapter *dvb;
> +	struct dvb_demux *demux;
> +	struct dmxdev *dmxdev;
> +	struct pt3_adapter *adap = kzalloc(sizeof(struct pt3_adapter), GFP_KERNEL);
> +
> +	if (!adap) {
> +		ret = -ENOMEM;
> +		goto err;
> +	}
> +	adap->pt3 = pt3;
> +	adap->voltage = SEC_VOLTAGE_OFF;
> +	adap->sleep = true;
> +
> +	dvb = &adap->dvb;
> +	dvb->priv = adap;
> +	ret = dvb_register_adapter(dvb, DRV_NAME, THIS_MODULE, &pt3->pdev->dev, adapter_nr);
> +	if (ret < 0)
> +		goto err_kfree;
> +
> +	demux = &adap->demux;
> +	demux->dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING;
> +	demux->priv = adap;
> +	demux->feednum = 256;
> +	demux->filternum = 256;
> +	demux->start_feed = pt3_start_feed;
> +	demux->stop_feed = pt3_stop_feed;
> +	demux->write_to_decoder = NULL;
> +	ret = dvb_dmx_init(demux);
> +	if (ret < 0)
> +		goto err_unregister_adapter;
> +
> +	dmxdev = &adap->dmxdev;
> +	dmxdev->filternum = 256;
> +	dmxdev->demux = &demux->dmx;
> +	dmxdev->capabilities = 0;
> +	ret = dvb_dmxdev_init(dmxdev, dvb);
> +	if (ret < 0)
> +		goto err_dmx_release;
> +
> +	return adap;
> +
> +err_dmx_release:
> +	dvb_dmx_release(demux);
> +err_unregister_adapter:
> +	dvb_unregister_adapter(dvb);
> +err_kfree:
> +	kfree(adap);
> +err:
> +	return ERR_PTR(ret);
> +}
> +
> +int pt3_tuner_set_sleep(struct pt3_adapter *adap, bool sleep)
> +{
> +	int ret;
> +	pr_debug("#%d %p %s %s\n", adap->idx, adap, adap->str, sleep ? "Sleep" : "Wakeup");
> +
> +	if (adap->type == SYS_ISDBS)
> +		ret = qm1d1c0042_set_sleep(adap->qm, sleep);
> +	else
> +		ret = mxl301rf_set_sleep(adap, sleep);
> +	msleep_interruptible(10);
> +	return ret;
> +}
> +
> +static void pt3_cleanup_adapter(struct pt3_adapter *adap)
> +{
> +	if (!adap)
> +		return;
> +	if (adap->kthread)
> +		kthread_stop(adap->kthread);
> +	if (adap->fe)
> +		dvb_unregister_frontend(adap->fe);
> +	if (!adap->sleep)
> +		pt3_tuner_set_sleep(adap, true);
> +	if (adap->qm)
> +		vfree(adap->qm);
> +	if (adap->dma) {
> +		if (adap->dma->enabled)
> +			pt3_dma_set_enabled(adap->dma, false);
> +		pt3_dma_free(adap->dma);
> +	}
> +	adap->demux.dmx.close(&adap->demux.dmx);
> +	dvb_dmxdev_release(&adap->dmxdev);
> +	dvb_dmx_release(&adap->demux);
> +	dvb_unregister_adapter(&adap->dvb);
> +	kfree(adap);
> +}
> +
> +static int pt3_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage)
> +{
> +	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
> +	adap->voltage = voltage;
> +	return (adap->orig_voltage) ? adap->orig_voltage(fe, voltage) : 0;
> +}
> +
> +static int pt3_sleep(struct dvb_frontend *fe)
> +{
> +	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
> +	adap->sleep = true;
> +	pt3_update_lnb(adap->pt3);
> +	return (adap->orig_sleep) ? adap->orig_sleep(fe) : 0;
> +}
> +
> +static int pt3_wakeup(struct dvb_frontend *fe)
> +{
> +	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
> +	adap->sleep = false;
> +	pt3_update_lnb(adap->pt3);
> +	return (adap->orig_init) ? adap->orig_init(fe) : 0;
> +}
> +
> +static int pt3_init_frontends(struct pt3_board *pt3)
> +{
> +	struct dvb_frontend *fe[PT3_NR_ADAPS];
> +	int i, ret;
> +
> +	for (i = 0; i < PT3_NR_ADAPS; i++) {
> +		fe[i] = tc90522_attach(pt3->adap[i]);
> +		if (!fe[i]) {
> +			while (i--)
> +				fe[i]->ops.release(fe[i]);
> +			return -ENOMEM;
> +		}
> +	}
> +	for (i = 0; i < PT3_NR_ADAPS; i++) {
> +		struct pt3_adapter *adap = pt3->adap[i];
> +
> +		adap->orig_voltage     = fe[i]->ops.set_voltage;
> +		adap->orig_sleep       = fe[i]->ops.sleep;
> +		adap->orig_init        = fe[i]->ops.init;
> +		fe[i]->ops.set_voltage = pt3_set_voltage;
> +		fe[i]->ops.sleep       = pt3_sleep;
> +		fe[i]->ops.init        = pt3_wakeup;
> +
> +		ret = dvb_register_frontend(&adap->dvb, fe[i]);
> +		if (ret >= 0)
> +			adap->fe = fe[i];
> +		else {
> +			while (i--)
> +				dvb_unregister_frontend(fe[i]);
> +			for (i = 0; i < PT3_NR_ADAPS; i++)
> +				fe[i]->ops.release(fe[i]);
> +			return ret;
> +		}
> +	}
> +	return 0;
> +}
> +
> +static void pt3_remove(struct pci_dev *pdev)
> +{
> +	int i;
> +	struct pt3_board *pt3 = pci_get_drvdata(pdev);
> +
> +	if (pt3) {
> +		pt3->reset = true;
> +		pt3_update_lnb(pt3);
> +		if (pt3->adap && pt3->adap[PT3_NR_ADAPS-1])
> +			tc90522_set_powers(pt3->adap[PT3_NR_ADAPS-1], false, false);
> +		for (i = 0; i < PT3_NR_ADAPS; i++)
> +			pt3_cleanup_adapter(pt3->adap[i]);
> +		pt3_i2c_reset(pt3);
> +		i2c_del_adapter(&pt3->i2c);
> +		if (pt3->bar_mem)
> +			iounmap(pt3->bar_mem);
> +		if (pt3->bar_reg)
> +			iounmap(pt3->bar_reg);
> +		pci_release_selected_regions(pdev, pt3->bars);
> +		kfree(pt3);
> +	}
> +	pci_disable_device(pdev);
> +}
> +
> +static int pt3_abort(struct pci_dev *pdev, int ret, char *fmt, ...)
> +{
> +	va_list ap;
> +	char *s = NULL;
> +	int slen;
> +
> +	va_start(ap, fmt);
> +	slen = vsnprintf(s, 0, fmt, ap);
> +	s = vzalloc(slen);
> +	if (slen > 0 && s) {
> +		vsnprintf(s, slen, fmt, ap);
> +		dev_alert(&pdev->dev, "%s", s);
> +		vfree(s);
> +	}
> +	va_end(ap);

The above looks weird. Why don't you just use dev_err()?

> +	pt3_remove(pdev);
> +	return ret;
> +}
> +
> +struct {
> +	fe_delivery_system_t type;
> +	u8 addr_tuner, addr_demod;
> +	int init_ch;
> +	char *str;
> +} pt3_config[] = {
> +	{SYS_ISDBS, 0x63, 0b00010001,  0, "ISDB-S"},
> +	{SYS_ISDBS, 0x60, 0b00010011,  0, "ISDB-S"},
> +	{SYS_ISDBT, 0x62, 0b00010000, 70, "ISDB-T"},
> +	{SYS_ISDBT, 0x61, 0b00010010, 71, "ISDB-T"},
> +};
> +
> +int pt3_tuner_init(struct pt3_adapter *adap)
> +{
> +	qm1d1c0042_tuner_init(adap->qm);
> +	adap->pt3->i2c_addr = 0;
> +	if (pt3_i2c_flush(adap->pt3, true, 0))
> +		return -EIO;
> +
> +	if (qm1d1c0042_tuner_init(adap->qm))
> +		return -EIO;
> +	adap->pt3->i2c_addr = 0;
> +	if (pt3_i2c_flush(adap->pt3, true, 0))
> +		return -EIO;
> +	return 0;
> +}
> +
> +static int pt3_tuner_init_all(struct pt3_board *pt3)
> +{
> +	int ret, i, j;
> +
> +	if (!pt3_i2c_is_clean(pt3)) {
> +		pr_debug("cleanup I2C\n");
> +		ret = pt3_i2c_flush(pt3, false, 0);
> +		if (ret)
> +			goto last;
> +		msleep_interruptible(10);
> +	}
> +	for (i = 0; i < PT3_NR_ADAPS; i++) {
> +		ret = tc90522_init(pt3->adap[i]);
> +		pr_debug("#%d tc_init ret=%d\n", i, ret);
> +	}
> +	ret = tc90522_set_powers(pt3->adap[PT3_NR_ADAPS-1], true, false);
> +	if (ret) {
> +		pr_debug("fail set powers.\n");
> +		goto last;
> +	}
> +	msleep_interruptible(1);
> +
> +	for (i = 0; i < PT3_NR_ADAPS; i++)
> +		if (pt3->adap[i]->type == SYS_ISDBS) {
> +			for (j = 0; j < 10; j++) {
> +				if (j)
> +					pr_debug("retry pt3_tuner_init\n");
> +				ret = pt3_tuner_init(pt3->adap[i]);
> +				if (!ret)
> +					break;
> +				msleep_interruptible(1);
> +			}
> +			if (ret) {
> +				pr_debug("#%d fail pt3_tuner_init ret=0x%x\n", i, ret);
> +				goto last;
> +			}
> +		}
> +	ret = pt3_i2c_flush(pt3, false, PT3_I2C_START_ADDR);
> +	if (ret)
> +		goto last;
> +	ret = tc90522_set_powers(pt3->adap[PT3_NR_ADAPS-1], true, true);
> +	if (ret) {
> +		pr_debug("fail tc_set_powers,\n");
> +		goto last;
> +	}
> +	for (i = 0; i < PT3_NR_ADAPS; i++) {
> +		struct pt3_adapter *adap = pt3->adap[i];
> +		ret = pt3_tuner_set_sleep(adap, false);
> +		if (ret)
> +			goto last;
> +		ret = (adap->type == SYS_ISDBS) ?
> +			qm1d1c0042_set_freq(adap->qm, adap->init_ch) :
> +			mxl301rf_set_freq(adap, adap->init_ch, 0);
> +		if (ret)
> +			pr_debug("fail set_frequency, ret=%d\n", ret);
> +		ret = pt3_tuner_set_sleep(adap, true);
> +		if (ret)
> +			goto last;
> +	}
> +last:
> +	return ret;
> +}
> +
> +static int pt3_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
> +{
> +	struct pt3_board *pt3;
> +	struct pt3_adapter *adap;
> +	int i, ret, bars = pci_select_bars(pdev, IORESOURCE_MEM);
> +
> +	ret = pci_enable_device(pdev);
> +	if (ret < 0)
> +		return pt3_abort(pdev, ret, "PCI device unusable\n");
> +	ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(64));
> +	if (ret)
> +		return pt3_abort(pdev, ret, "DMA mask error\n");
> +	pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64));
> +
> +	pci_read_config_dword(pdev, PCI_CLASS_REVISION, &i);
> +	if ((i & 0xFF) != 1)
> +		return pt3_abort(pdev, ret, "Revision 0x%x is not supported\n", i & 0xFF);
> +	ret = pci_request_selected_regions(pdev, bars, DRV_NAME);
> +	if (ret < 0)
> +		return pt3_abort(pdev, ret, "Could not request regions\n");
> +
> +	pci_set_master(pdev);
> +	ret = pci_save_state(pdev);
> +	if (ret)
> +		return pt3_abort(pdev, ret, "Failed pci_save_state\n");
> +	pt3 = kzalloc(sizeof(struct pt3_board), GFP_KERNEL);
> +	if (!pt3)
> +		return pt3_abort(pdev, -ENOMEM, "struct pt3_board out of memory\n");
> +
> +	pt3->bars = bars;
> +	pt3->pdev = pdev;
> +	pci_set_drvdata(pdev, pt3);
> +	pt3->bar_reg = pci_ioremap_bar(pdev, 0);
> +	pt3->bar_mem = pci_ioremap_bar(pdev, 2);
> +	if (!pt3->bar_reg || !pt3->bar_mem)
> +		return pt3_abort(pdev, -EIO, "Failed pci_ioremap_bar\n");
> +
> +	ret = readl(pt3->bar_reg + REG_VERSION);
> +	i = ((ret >> 24) & 0xFF);
> +	if (i != 3)
> +		return pt3_abort(pdev, -EIO, "ID=0x%x, not a PT3\n", i);
> +	i = ((ret >>  8) & 0xFF);
> +	if (i != 4)
> +		return pt3_abort(pdev, -EIO, "FPGA version 0x%x is not supported\n", i);
> +	mutex_init(&pt3->lock);
> +
> +	for (i = 0; i < PT3_NR_ADAPS; i++) {
> +		pt3->adap[i] = NULL;
> +		adap = pt3_alloc_adapter(pt3);
> +		if (IS_ERR(adap))
> +			return pt3_abort(pdev, PTR_ERR(adap), "Failed pt3_alloc_adapter\n");
> +		adap->idx = i;
> +		adap->dma = pt3_dma_create(adap);
> +		if (!adap->dma)
> +			return pt3_abort(pdev, -ENOMEM, "Failed pt3_dma_create\n");
> +		mutex_init(&adap->lock);
> +		pt3->adap[i] = adap;
> +		adap->type       = pt3_config[i].type;
> +		adap->addr_tuner = pt3_config[i].addr_tuner;
> +		adap->addr_demod = pt3_config[i].addr_demod;
> +		adap->init_ch    = pt3_config[i].init_ch;
> +		adap->str        = pt3_config[i].str;
> +		if (adap->type == SYS_ISDBS) {
> +			adap->qm = vzalloc(sizeof(struct qm1d1c0042));
> +			if (!adap->qm)
> +				return pt3_abort(pdev, -ENOMEM, "QM out of memory\n");
> +			adap->qm->adap = adap;
> +		}
> +		adap->sleep = true;
> +	}
> +	pt3->reset = true;
> +	pt3_update_lnb(pt3);
> +
> +	ret = pt3_i2c_add_adapter(pt3);
> +	if (ret < 0)
> +		return pt3_abort(pdev, ret, "Cannot add I2C\n");
> +
> +	if (pt3_tuner_init_all(pt3))
> +		return pt3_abort(pdev, ret, "Failed pt3_tuner_init_all\n");
> +	ret = pt3_init_frontends(pt3);
> +	return (ret >= 0) ? ret : pt3_abort(pdev, ret, "Failed pt3_init_frontends\n");
> +}
> +
> +static struct pci_driver pt3_driver = {
> +	.name		= DRV_NAME,
> +	.probe		= pt3_probe,
> +	.remove		= pt3_remove,
> +	.id_table	= pt3_id_table,
> +};
> +
> +module_pci_driver(pt3_driver);

I'll need to revisit this driver, after you do the changes requested by
tuner/frontend proper API usage.

> +
> diff --git a/drivers/media/pci/pt3/pt3.h b/drivers/media/pci/pt3/pt3.h
> new file mode 100644
> index 0000000..057d1b4
> --- /dev/null
> +++ b/drivers/media/pci/pt3/pt3.h
> @@ -0,0 +1,23 @@
> +/*
> + * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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	__PT3_H__
> +#define	__PT3_H__
> +
> +void pt3_filter(struct pt3_adapter *adap, struct dvb_demux *demux, u8 *buf, size_t count);
> +
> +#endif
> +
> diff --git a/drivers/media/pci/pt3/pt3_dma.c b/drivers/media/pci/pt3/pt3_dma.c
> new file mode 100644
> index 0000000..e7448d6
> --- /dev/null
> +++ b/drivers/media/pci/pt3/pt3_dma.c
> @@ -0,0 +1,335 @@
> +/*
> + * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 "pt3_common.h"
> +#include "pt3_dma.h"
> +#include "pt3.h"
> +
> +#define PT3_DMA_MAX_DESCS	204
> +#define PT3_DMA_PAGE_SIZE	(PT3_DMA_MAX_DESCS * sizeof(struct pt3_dma_desc))
> +#define PT3_DMA_BLOCK_COUNT	17
> +#define PT3_DMA_BLOCK_SIZE	(PT3_DMA_PAGE_SIZE * 47)
> +#define PT3_DMA_TS_BUF_SIZE	(PT3_DMA_BLOCK_SIZE * PT3_DMA_BLOCK_COUNT)
> +#define PT3_DMA_TS_SYNC		0x47
> +#define PT3_DMA_TS_NOT_SYNC	0x74
> +
> +void pt3_dma_free(struct pt3_dma *dma)
> +{
> +	struct pt3_dma_page *page;
> +	u32 i;
> +
> +	if (dma->ts_info) {
> +		for (i = 0; i < dma->ts_count; i++) {
> +			page = &dma->ts_info[i];
> +			if (page->data)
> +				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
> +		}
> +		kfree(dma->ts_info);
> +	}
> +	if (dma->desc_info) {
> +		for (i = 0; i < dma->desc_count; i++) {
> +			page = &dma->desc_info[i];
> +			if (page->data)
> +				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
> +		}
> +		kfree(dma->desc_info);
> +	}
> +	kfree(dma);
> +}
> +
> +struct pt3_dma_desc {
> +	u64 page_addr;
> +	u32 page_size;
> +	u64 next_desc;
> +} __packed;
> +
> +void pt3_dma_build_page_descriptor(struct pt3_dma *dma)
> +{
> +	struct pt3_dma_page *desc_info, *ts_info;
> +	u64 ts_addr, desc_addr;
> +	u32 i, j, ts_size, desc_remain, ts_info_pos, desc_info_pos;
> +	struct pt3_dma_desc *prev, *curr;
> +
> +	pr_debug("#%d build page descriptor ts_count=%d ts_size=%d desc_count=%d desc_size=%d\n",
> +		dma->adap->idx, dma->ts_count, dma->ts_info[0].size, dma->desc_count, dma->desc_info[0].size);
> +	desc_info_pos = ts_info_pos = 0;
> +	desc_info = &dma->desc_info[desc_info_pos];
> +	desc_addr   = desc_info->addr;
> +	desc_remain = desc_info->size;
> +	desc_info->data_pos = 0;
> +	prev = NULL;
> +	curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
> +	desc_info_pos++;
> +
> +	for (i = 0; i < dma->ts_count; i++) {
> +		if (unlikely(ts_info_pos >= dma->ts_count)) {
> +			pr_debug("#%d ts_info overflow max=%d curr=%d\n", dma->adap->idx, dma->ts_count, ts_info_pos);
> +			return;
> +		}
> +		ts_info = &dma->ts_info[ts_info_pos];
> +		ts_addr = ts_info->addr;
> +		ts_size = ts_info->size;
> +		ts_info_pos++;
> +		pr_debug("#%d i=%d, ts_info addr=0x%llx ts_size=%d\n", dma->adap->idx, i, ts_addr, ts_size);
> +		for (j = 0; j < ts_size / PT3_DMA_PAGE_SIZE; j++) {
> +			if (desc_remain < sizeof(struct pt3_dma_desc)) {
> +				if (unlikely(desc_info_pos >= dma->desc_count)) {
> +					pr_debug("#%d desc_info overflow max=%d curr=%d\n",
> +						dma->adap->idx, dma->desc_count, desc_info_pos);
> +					return;
> +				}
> +				desc_info = &dma->desc_info[desc_info_pos];
> +				desc_info->data_pos = 0;
> +				curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
> +				pr_debug("#%d desc_info_pos=%d ts_addr=0x%llx remain=%d\n",
> +					dma->adap->idx, desc_info_pos, ts_addr, desc_remain);
> +				desc_addr = desc_info->addr;
> +				desc_remain = desc_info->size;
> +				desc_info_pos++;
> +			}
> +			if (prev)
> +				prev->next_desc = desc_addr | 0b10;
> +			curr->page_addr = ts_addr           | 0b111;
> +			curr->page_size = PT3_DMA_PAGE_SIZE | 0b111;
> +			curr->next_desc = 0b10;
> +			pr_debug("#%d j=%d dma write desc ts_addr=0x%llx desc_info_pos=%d desc_remain=%d\n",
> +				dma->adap->idx, j, ts_addr, desc_info_pos, desc_remain);
> +			ts_addr += PT3_DMA_PAGE_SIZE;
> +
> +			prev = curr;
> +			desc_info->data_pos += sizeof(struct pt3_dma_desc);
> +			if (unlikely(desc_info->data_pos > desc_info->size)) {
> +				pr_debug("#%d dma desc_info data overflow max=%d curr=%d\n",
> +					dma->adap->idx, desc_info->size, desc_info->data_pos);
> +				return;
> +			}
> +			curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
> +			desc_addr += sizeof(struct pt3_dma_desc);
> +			desc_remain -= sizeof(struct pt3_dma_desc);
> +		}
> +	}
> +	if (prev)
> +		prev->next_desc = dma->desc_info->addr | 0b10;
> +}
> +
> +struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap)
> +{
> +	struct pt3_dma_page *page;
> +	u32 i;
> +
> +	struct pt3_dma *dma = kzalloc(sizeof(struct pt3_dma), GFP_KERNEL);
> +	if (!dma) {
> +		pr_debug("#%d fail allocate PT3_DMA\n", adap->idx);
> +		goto fail;
> +	}
> +	dma->adap = adap;
> +	dma->enabled = false;
> +	mutex_init(&dma->lock);
> +
> +	dma->ts_count = PT3_DMA_BLOCK_COUNT;
> +	dma->ts_info = kzalloc(sizeof(struct pt3_dma_page) * dma->ts_count, GFP_KERNEL);
> +	if (!dma->ts_info) {
> +		pr_debug("#%d fail allocate TS DMA page\n", adap->idx);
> +		goto fail;
> +	}
> +	pr_debug("#%d Alloc TS buf (ts_count %d)\n", adap->idx, dma->ts_count);
> +	for (i = 0; i < dma->ts_count; i++) {
> +		page = &dma->ts_info[i];
> +		page->size = PT3_DMA_BLOCK_SIZE;
> +		page->data_pos = 0;
> +		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
> +		if (!page->data) {
> +			pr_debug("#%d fail alloc_consistent. %d\n", adap->idx, i);
> +			goto fail;
> +		}
> +	}
> +
> +	dma->desc_count = 1 + (PT3_DMA_TS_BUF_SIZE / PT3_DMA_PAGE_SIZE - 1) / PT3_DMA_MAX_DESCS;
> +	dma->desc_info = kzalloc(sizeof(struct pt3_dma_page) * dma->desc_count, GFP_KERNEL);
> +	if (!dma->desc_info) {
> +		pr_debug("#%d fail allocate Desc DMA page\n", adap->idx);
> +		goto fail;
> +	}
> +	pr_debug("#%d Alloc Descriptor buf (desc_count %d)\n", adap->idx, dma->desc_count);
> +	for (i = 0; i < dma->desc_count; i++) {
> +		page = &dma->desc_info[i];
> +		page->size = PT3_DMA_PAGE_SIZE;
> +		page->data_pos = 0;
> +		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
> +		if (!page->data) {
> +			pr_debug("#%d fail alloc_consistent %d\n", adap->idx, i);
> +			goto fail;
> +		}
> +	}
> +
> +	pr_debug("#%d build page descriptor\n", adap->idx);
> +	pt3_dma_build_page_descriptor(dma);
> +	return dma;
> +fail:
> +	if (dma)
> +		pt3_dma_free(dma);
> +	return NULL;
> +}
> +
> +void __iomem *pt3_dma_get_base_addr(struct pt3_dma *dma)
> +{
> +	return dma->adap->pt3->bar_reg + REG_BASE + (0x18 * dma->adap->idx);
> +}
> +
> +void pt3_dma_reset(struct pt3_dma *dma)
> +{
> +	struct pt3_dma_page *ts;
> +	u32 i;
> +
> +	for (i = 0; i < dma->ts_count; i++) {
> +		ts = &dma->ts_info[i];
> +		memset(ts->data, 0, ts->size);
> +		ts->data_pos = 0;
> +		*ts->data = PT3_DMA_TS_NOT_SYNC;
> +	}
> +	dma->ts_pos = 0;
> +}
> +
> +void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled)
> +{
> +	void __iomem *base = pt3_dma_get_base_addr(dma);
> +	u64 start_addr = dma->desc_info->addr;
> +
> +	if (enabled) {
> +		pr_debug("#%d DMA enable start_addr=%llx\n", dma->adap->idx, start_addr);
> +		pt3_dma_reset(dma);
> +		writel(1 << 1, base + REG_DMA_CTL);	/* stop DMA */
> +		writel(start_addr         & 0xffffffff, base + REG_DMA_DESC_L);
> +		writel((start_addr >> 32) & 0xffffffff, base + REG_DMA_DESC_H);
> +		pr_debug("set descriptor address low %llx\n",  start_addr         & 0xffffffff);
> +		pr_debug("set descriptor address high %llx\n", (start_addr >> 32) & 0xffffffff);
> +		writel(1 << 0, base + REG_DMA_CTL);	/* start DMA */
> +	} else {
> +		pr_debug("#%d DMA disable\n", dma->adap->idx);
> +		writel(1 << 1, base + REG_DMA_CTL);	/* stop DMA */
> +		while (1) {
> +			if (!(readl(base + REG_STATUS) & 1))
> +				break;
> +			msleep_interruptible(1);
> +		}
> +	}
> +	dma->enabled = enabled;
> +}
> +
> +/* convert Gray code to binary, e.g. 1001 -> 1110 */
> +static u32 pt3_dma_gray2binary(u32 gray, u32 bit)
> +{
> +	u32 binary = 0, i, j, k;
> +
> +	for (i = 0; i < bit; i++) {
> +		k = 0;
> +		for (j = i; j < bit; j++)
> +			k ^= (gray >> j) & 1;
> +		binary |= k << i;
> +	}
> +	return binary;
> +}
> +
> +u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma)
> +{
> +	return pt3_dma_gray2binary(readl(pt3_dma_get_base_addr(dma) + REG_TS_ERR), 32);
> +}
> +
> +void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval)
> +{
> +	void __iomem *base = pt3_dma_get_base_addr(dma);
> +	u32 data = mode | initval;
> +	pr_debug("set_test_mode base=%p data=0x%04x\n", base, data);
> +	writel(data, base + REG_TS_CTL);
> +}
> +
> +bool pt3_dma_ready(struct pt3_dma *dma)
> +{
> +	struct pt3_dma_page *ts;
> +	u8 *p;
> +
> +	u32 next = dma->ts_pos + 1;
> +	if (next >= dma->ts_count)
> +		next = 0;
> +	ts = &dma->ts_info[next];
> +	p = &ts->data[ts->data_pos];
> +
> +	if (*p == PT3_DMA_TS_SYNC)
> +		return true;
> +	if (*p == PT3_DMA_TS_NOT_SYNC)
> +		return false;
> +
> +	pr_debug("#%d invalid sync byte value=0x%02x ts_pos=%d data_pos=%d curr=0x%02x\n",
> +		dma->adap->idx, *p, next, ts->data_pos, dma->ts_info[dma->ts_pos].data[0]);
> +	return false;
> +}
> +
> +ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux)
> +{
> +	bool ready;
> +	struct pt3_dma_page *ts;
> +	u32 i, prev;
> +	size_t csize, remain = dma->ts_info[dma->ts_pos].size;
> +
> +	mutex_lock(&dma->lock);
> +	pr_debug("#%d dma_copy ts_pos=0x%x data_pos=0x%x\n",
> +		   dma->adap->idx, dma->ts_pos, dma->ts_info[dma->ts_pos].data_pos);
> +	for (;;) {
> +		for (i = 0; i < 20; i++) {
> +			ready = pt3_dma_ready(dma);
> +			if (ready)
> +				break;
> +			msleep_interruptible(30);
> +		}
> +		if (!ready) {
> +			pr_debug("#%d dma_copy NOT READY\n", dma->adap->idx);
> +			goto last;
> +		}
> +		prev = dma->ts_pos - 1;
> +		if (prev < 0 || dma->ts_count <= prev)
> +			prev = dma->ts_count - 1;
> +		if (dma->ts_info[prev].data[0] != PT3_DMA_TS_NOT_SYNC)
> +			pr_debug("#%d DMA buffer overflow. prev=%d data=0x%x\n",
> +					dma->adap->idx, prev, dma->ts_info[prev].data[0]);
> +		ts = &dma->ts_info[dma->ts_pos];
> +		for (;;) {
> +			csize = (remain < (ts->size - ts->data_pos)) ?
> +				 remain : (ts->size - ts->data_pos);
> +			pt3_filter(dma->adap, demux, &ts->data[ts->data_pos], csize);
> +			remain -= csize;
> +			ts->data_pos += csize;
> +			if (ts->data_pos >= ts->size) {
> +				ts->data_pos = 0;
> +				ts->data[ts->data_pos] = PT3_DMA_TS_NOT_SYNC;
> +				dma->ts_pos++;
> +				if (dma->ts_pos >= dma->ts_count)
> +					dma->ts_pos = 0;
> +				break;
> +			}
> +			if (remain <= 0)
> +				goto last;
> +		}
> +	}
> +last:
> +	mutex_unlock(&dma->lock);
> +	return dma->ts_info[dma->ts_pos].size - remain;
> +}
> +
> +u32 pt3_dma_get_status(struct pt3_dma *dma)
> +{
> +	return readl(pt3_dma_get_base_addr(dma) + REG_STATUS);
> +}
> +
> diff --git a/drivers/media/pci/pt3/pt3_dma.h b/drivers/media/pci/pt3/pt3_dma.h
> new file mode 100644
> index 0000000..ecae4c1
> --- /dev/null
> +++ b/drivers/media/pci/pt3/pt3_dma.h
> @@ -0,0 +1,48 @@
> +/*
> + * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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	__PT3_DMA_H__
> +#define	__PT3_DMA_H__
> +
> +struct pt3_dma_page {
> +	dma_addr_t addr;
> +	u8 *data;
> +	u32 size, data_pos;
> +};
> +
> +enum pt3_dma_mode {
> +	USE_LFSR = 1 << 16,
> +	REVERSE  = 1 << 17,
> +	RESET    = 1 << 18,
> +};
> +
> +struct pt3_dma {
> +	struct pt3_adapter *adap;
> +	bool enabled;
> +	u32 ts_pos, ts_count, desc_count;
> +	struct pt3_dma_page *ts_info, *desc_info;
> +	struct mutex lock;
> +};
> +
> +ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux);
> +struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap);
> +void pt3_dma_free(struct pt3_dma *dma);
> +u32 pt3_dma_get_status(struct pt3_dma *dma);
> +u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma);
> +void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled);
> +void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval);
> +
> +#endif
> diff --git a/drivers/media/pci/pt3/pt3_i2c.c b/drivers/media/pci/pt3/pt3_i2c.c
> new file mode 100644
> index 0000000..3e28b8f
> --- /dev/null
> +++ b/drivers/media/pci/pt3/pt3_i2c.c
> @@ -0,0 +1,183 @@
> +/*
> + * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 "pt3_i2c.h"
> +
> +enum pt3_i2c_cmd {
> +	I_END,
> +	I_ADDRESS,
> +	I_CLOCK_L,
> +	I_CLOCK_H,
> +	I_DATA_L,
> +	I_DATA_H,
> +	I_RESET,
> +	I_SLEEP,
> +	I_DATA_L_NOP  = 0x08,
> +	I_DATA_H_NOP  = 0x0c,
> +	I_DATA_H_READ = 0x0d,
> +	I_DATA_H_ACK0 = 0x0e,
> +	I_DATA_H_ACK1 = 0x0f,
> +};
> +
> +bool pt3_i2c_is_clean(struct pt3_board *pt3)
> +{
> +	return (readl(pt3->bar_reg + REG_I2C_R) >> 3) & 1;
> +}
> +
> +void pt3_i2c_reset(struct pt3_board *pt3)
> +{
> +	writel(1 << 17, pt3->bar_reg + REG_I2C_W);	/* 0x00020000 */
> +}
> +
> +void pt3_i2c_wait(struct pt3_board *pt3, u32 *status)
> +{
> +	u32 val;
> +
> +	while (1) {
> +		val = readl(pt3->bar_reg + REG_I2C_R);
> +		if (!(val & 1))				/* sequence stopped */
> +			break;
> +		msleep_interruptible(1);
> +	}
> +	if (status)
> +		*status = val;				/* I2C register status */
> +}
> +
> +void pt3_i2c_mem_write(struct pt3_board *pt3, u8 data)
> +{
> +	void __iomem *dst = pt3->bar_mem + PT3_I2C_DATA_OFFSET + pt3->i2c_addr;
> +
> +	if (pt3->i2c_filled) {
> +		pt3->i2c_buf |= data << 4;
> +		writeb(pt3->i2c_buf, dst);
> +		pt3->i2c_addr++;
> +	} else
> +		pt3->i2c_buf = data;
> +	pt3->i2c_filled ^= true;
> +}
> +
> +void pt3_i2c_start(struct pt3_board *pt3)
> +{
> +	pt3_i2c_mem_write(pt3, I_DATA_H);
> +	pt3_i2c_mem_write(pt3, I_CLOCK_H);
> +	pt3_i2c_mem_write(pt3, I_DATA_L);
> +	pt3_i2c_mem_write(pt3, I_CLOCK_L);
> +}
> +
> +void pt3_i2c_cmd_write(struct pt3_board *pt3, const u8 *data, u32 size)
> +{
> +	u32 i, j;
> +	u8 byte;
> +
> +	for (i = 0; i < size; i++) {
> +		byte = data[i];
> +		for (j = 0; j < 8; j++)
> +			pt3_i2c_mem_write(pt3, (byte >> (7 - j)) & 1 ? I_DATA_H_NOP : I_DATA_L_NOP);
> +		pt3_i2c_mem_write(pt3, I_DATA_H_ACK0);
> +	}
> +}
> +
> +void pt3_i2c_cmd_read(struct pt3_board *pt3, u8 *data, u32 size)
> +{
> +	u32 i, j;
> +
> +	for (i = 0; i < size; i++) {
> +		for (j = 0; j < 8; j++)
> +			pt3_i2c_mem_write(pt3, I_DATA_H_READ);
> +		if (i == (size - 1))
> +			pt3_i2c_mem_write(pt3, I_DATA_H_NOP);
> +		else
> +			pt3_i2c_mem_write(pt3, I_DATA_L_NOP);
> +	}
> +}
> +
> +void pt3_i2c_stop(struct pt3_board *pt3)
> +{
> +	pt3_i2c_mem_write(pt3, I_DATA_L);
> +	pt3_i2c_mem_write(pt3, I_CLOCK_H);
> +	pt3_i2c_mem_write(pt3, I_DATA_H);
> +}
> +
> +int pt3_i2c_flush(struct pt3_board *pt3, bool end, u32 start_addr)
> +{
> +	u32 status;
> +
> +	if (end) {
> +		pt3_i2c_mem_write(pt3, I_END);
> +		if (pt3->i2c_filled)
> +			pt3_i2c_mem_write(pt3, I_END);
> +	}
> +	pt3_i2c_wait(pt3, &status);
> +	writel(1 << 16 | start_addr, pt3->bar_reg + REG_I2C_W);	/* 0x00010000 start sequence */
> +	pt3_i2c_wait(pt3, &status);
> +	if (status & 0b0110) {					/* ACK status */
> +		pr_err("%s failed, status=0x%x\n", __func__, status);
> +		return -EIO;
> +	}
> +	return 0;
> +}
> +
> +u32 pt3_i2c_func(struct i2c_adapter *adap)
> +{
> +	return I2C_FUNC_I2C;
> +}
> +
> +int pt3_i2c_xfer(struct i2c_adapter *i2c, struct i2c_msg *msg, int num)
> +{
> +	struct pt3_board *pt3 = i2c_get_adapdata(i2c);
> +	int i, j;
> +
> +	if ((num < 1) || (num > 3) || !msg || msg[0].flags)	/* always write first */
> +		return -ENOTSUPP;
> +	mutex_lock(&pt3->lock);
> +	pt3->i2c_addr = 0;
> +	for (i = 0; i < num; i++) {
> +		u8 byte = (msg[i].addr << 1) | (msg[i].flags & 1);
> +		pt3_i2c_start(pt3);
> +		pt3_i2c_cmd_write(pt3, &byte, 1);
> +		if (msg[i].flags == I2C_M_RD)
> +			pt3_i2c_cmd_read(pt3, msg[i].buf, msg[i].len);
> +		else
> +			pt3_i2c_cmd_write(pt3, msg[i].buf, msg[i].len);
> +	}
> +	pt3_i2c_stop(pt3);
> +	if (pt3_i2c_flush(pt3, true, 0))
> +		num = -EIO;
> +	else
> +		for (i = 1; i < num; i++)
> +			if (msg[i].flags == I2C_M_RD)
> +				for (j = 0; j < msg[i].len; j++)
> +					msg[i].buf[j] = readb(pt3->bar_mem + PT3_I2C_DATA_OFFSET + j);
> +	mutex_unlock(&pt3->lock);
> +	return num;
> +}
> +
> +static const struct i2c_algorithm pt3_i2c_algo = {
> +	.functionality = pt3_i2c_func,
> +	.master_xfer = pt3_i2c_xfer,
> +};
> +
> +int pt3_i2c_add_adapter(struct pt3_board *pt3)
> +{
> +	struct i2c_adapter *i2c = &pt3->i2c;
> +	i2c->algo = &pt3_i2c_algo;
> +	i2c->algo_data = NULL;
> +	i2c->dev.parent = &pt3->pdev->dev;
> +	strcpy(i2c->name, DRV_NAME);
> +	i2c_set_adapdata(i2c, pt3);
> +	return i2c_add_adapter(i2c);
> +}
> +
> diff --git a/drivers/media/pci/pt3/pt3_i2c.h b/drivers/media/pci/pt3/pt3_i2c.h
> new file mode 100644
> index 0000000..acf8053
> --- /dev/null
> +++ b/drivers/media/pci/pt3/pt3_i2c.h
> @@ -0,0 +1,30 @@
> +/*
> + * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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	__PT3_I2C_H__
> +#define	__PT3_I2C_H__
> +
> +#include "pt3_common.h"
> +
> +#define PT3_I2C_DATA_OFFSET	2048
> +#define PT3_I2C_START_ADDR	0x17fa
> +
> +int pt3_i2c_flush(struct pt3_board *pt3, bool end, u32 start_addr);
> +bool pt3_i2c_is_clean(struct pt3_board *pt3);
> +void pt3_i2c_reset(struct pt3_board *pt3);
> +int pt3_i2c_add_adapter(struct pt3_board *pt3);
> +
> +#endif


-- 

Cheers,
Mauro

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH] Full DVB driver package for Earthsoft PT3 (ISDB-S/T) cards
@ 2013-12-19 23:14 Guest
  2013-12-21 13:24 ` Mauro Carvalho Chehab
  2013-12-21 16:06 ` Antti Palosaari
  0 siblings, 2 replies; 21+ messages in thread
From: Guest @ 2013-12-19 23:14 UTC (permalink / raw)
  To: linux-media
  Cc: Bud R, crope, mchehab, hdegoede, hverkuil, laurent.pinchart,
	mkrufky, sylvester.nawrocki, g.liakhovetski, peter.senna

From: Bud R <knightrider@are.ma>

*** Is this okay? ***

A DVB driver for Earthsoft PT3 (ISDB-S/T) receiver PCI Express cards, based on
1. PT3 chardev driver
	https://github.com/knight-rider/ptx/tree/master/pt3_drv
	https://github.com/m-tsudo/pt3
2. PT1/PT2 DVB driver
	drivers/media/pci/pt1

It behaves similarly as PT1 DVB, plus some tuning enhancements:
1. in addition to the real frequency:
	ISDB-S : freq. channel ID
	ISDB-T : freq# (I/O# +128), ch#, ch# +64 for CATV
2. in addition to TSID:
	ISDB-S : slot#

Feature changes:
- dropped DKMS & standalone compile
- dropped verbosity (debug levels), use single level -DDEBUG instead
- changed SNR (.read_snr) to CNR (.read_signal_strength)
- moved FE to drivers/media/dvb-frontends
- moved demodulator & tuners to drivers/media/tuners
- translated to standard (?) I2C protocol
- dropped unused features

The full package (buildable as standalone, DKMS or tree embedded module) is available at
https://github.com/knight-rider/ptx/tree/master/pt3_dvb

Signed-off-by: Bud R <knightrider@are.ma>

---
 drivers/media/dvb-frontends/Kconfig      |  10 +-
 drivers/media/dvb-frontends/Makefile     |   2 +
 drivers/media/dvb-frontends/mxl301rf.c   | 332 ++++++++++++++
 drivers/media/dvb-frontends/mxl301rf.h   |  27 ++
 drivers/media/dvb-frontends/pt3_common.h |  95 ++++
 drivers/media/dvb-frontends/qm1d1c0042.c | 413 ++++++++++++++++++
 drivers/media/dvb-frontends/qm1d1c0042.h |  34 ++
 drivers/media/dvb-frontends/tc90522.c    | 724 +++++++++++++++++++++++++++++++
 drivers/media/dvb-frontends/tc90522.h    |  48 ++
 drivers/media/pci/Kconfig                |   2 +-
 drivers/media/pci/Makefile               |   1 +
 drivers/media/pci/pt3/Kconfig            |  10 +
 drivers/media/pci/pt3/Makefile           |   6 +
 drivers/media/pci/pt3/pt3.c              | 543 +++++++++++++++++++++++
 drivers/media/pci/pt3/pt3.h              |  23 +
 drivers/media/pci/pt3/pt3_dma.c          | 335 ++++++++++++++
 drivers/media/pci/pt3/pt3_dma.h          |  48 ++
 drivers/media/pci/pt3/pt3_i2c.c          | 183 ++++++++
 drivers/media/pci/pt3/pt3_i2c.h          |  30 ++
 19 files changed, 2864 insertions(+), 2 deletions(-)
 create mode 100644 drivers/media/dvb-frontends/mxl301rf.c
 create mode 100644 drivers/media/dvb-frontends/mxl301rf.h
 create mode 100644 drivers/media/dvb-frontends/pt3_common.h
 create mode 100644 drivers/media/dvb-frontends/qm1d1c0042.c
 create mode 100644 drivers/media/dvb-frontends/qm1d1c0042.h
 create mode 100644 drivers/media/dvb-frontends/tc90522.c
 create mode 100644 drivers/media/dvb-frontends/tc90522.h
 create mode 100644 drivers/media/pci/pt3/Kconfig
 create mode 100644 drivers/media/pci/pt3/Makefile
 create mode 100644 drivers/media/pci/pt3/pt3.c
 create mode 100644 drivers/media/pci/pt3/pt3.h
 create mode 100644 drivers/media/pci/pt3/pt3_dma.c
 create mode 100644 drivers/media/pci/pt3/pt3_dma.h
 create mode 100644 drivers/media/pci/pt3/pt3_i2c.c
 create mode 100644 drivers/media/pci/pt3/pt3_i2c.h

diff --git a/drivers/media/dvb-frontends/Kconfig b/drivers/media/dvb-frontends/Kconfig
index dd12a1e..44dec85 100644
--- a/drivers/media/dvb-frontends/Kconfig
+++ b/drivers/media/dvb-frontends/Kconfig
@@ -591,7 +591,7 @@ config DVB_S5H1411
 	  An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want
 	  to support this frontend.
 
-comment "ISDB-T (terrestrial) frontends"
+comment "ISDB-S (satellite) & ISDB-T (terrestrial) frontends"
 	depends on DVB_CORE
 
 config DVB_S921
@@ -618,6 +618,14 @@ config DVB_MB86A20S
 	  A driver for Fujitsu mb86a20s ISDB-T/ISDB-Tsb demodulator.
 	  Say Y when you want to support this frontend.
 
+config DVB_PT3_FE
+	tristate "Earthsoft PT3 ISDB-S/T demodulator/tuner frontends"
+	depends on DVB_CORE && I2C
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  DVB driver frontend for Earthsoft PT3 ISDB-S/ISDB-T PCIE cards.
+	  Say Y when you want to support this frontend.
+
 comment "Digital terrestrial only tuners/PLL"
 	depends on DVB_CORE
 
diff --git a/drivers/media/dvb-frontends/Makefile b/drivers/media/dvb-frontends/Makefile
index 0c75a6a..0e8f029 100644
--- a/drivers/media/dvb-frontends/Makefile
+++ b/drivers/media/dvb-frontends/Makefile
@@ -10,6 +10,7 @@ stv0900-objs := stv0900_core.o stv0900_sw.o
 drxd-objs := drxd_firm.o drxd_hard.o
 cxd2820r-objs := cxd2820r_core.o cxd2820r_c.o cxd2820r_t.o cxd2820r_t2.o
 drxk-objs := drxk_hard.o
+pt3_fe-objs := tc90522.o mxl301rf.o qm1d1c0042.o
 
 obj-$(CONFIG_DVB_PLL) += dvb-pll.o
 obj-$(CONFIG_DVB_STV0299) += stv0299.o
@@ -105,4 +106,5 @@ obj-$(CONFIG_DVB_RTL2830) += rtl2830.o
 obj-$(CONFIG_DVB_RTL2832) += rtl2832.o
 obj-$(CONFIG_DVB_M88RS2000) += m88rs2000.o
 obj-$(CONFIG_DVB_AF9033) += af9033.o
+obj-$(CONFIG_DVB_PT3_FE) += pt3_fe.o
 
diff --git a/drivers/media/dvb-frontends/mxl301rf.c b/drivers/media/dvb-frontends/mxl301rf.c
new file mode 100644
index 0000000..78310aa
--- /dev/null
+++ b/drivers/media/dvb-frontends/mxl301rf.c
@@ -0,0 +1,332 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-T tuner MaxLinear CMOS Hybrid TV MxL301RF
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_common.h"
+#include "tc90522.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 MxL301RF MaxLinear CMOS Hybrid TV ISDB-T tuner driver");
+MODULE_LICENSE("GPL");
+
+static struct {
+	u32	freq,		/* Channel center frequency @ kHz	*/
+		freq_th;	/* Offset frequency threshold @ kHz	*/
+	u8	shf_val,	/* Spur shift value			*/
+		shf_dir;	/* Spur shift direction			*/
+} SHF_DVBT_TAB[] = {
+	{  64500, 500, 0x92, 0x07 },
+	{ 191500, 300, 0xE2, 0x07 },
+	{ 205500, 500, 0x2C, 0x04 },
+	{ 212500, 500, 0x1E, 0x04 },
+	{ 226500, 500, 0xD4, 0x07 },
+	{  99143, 500, 0x9C, 0x07 },
+	{ 173143, 500, 0xD4, 0x07 },
+	{ 191143, 300, 0xD4, 0x07 },
+	{ 207143, 500, 0xCE, 0x07 },
+	{ 225143, 500, 0xCE, 0x07 },
+	{ 243143, 500, 0xD4, 0x07 },
+	{ 261143, 500, 0xD4, 0x07 },
+	{ 291143, 500, 0xD4, 0x07 },
+	{ 339143, 500, 0x2C, 0x04 },
+	{ 117143, 500, 0x7A, 0x07 },
+	{ 135143, 300, 0x7A, 0x07 },
+	{ 153143, 500, 0x01, 0x07 }
+};
+
+static void mxl301rf_rftune(u8 *data, u32 *size, u32 freq)
+{
+	u32 dig_rf_freq, tmp, frac_divider, kHz, MHz, i;
+	u8 rf_data[] = {
+		0x13, 0x00,	/* abort tune			*/
+		0x3B, 0xC0,
+		0x3B, 0x80,
+		0x10, 0x95,	/* BW				*/
+		0x1A, 0x05,
+		0x61, 0x00,
+		0x62, 0xA0,
+		0x11, 0x40,	/* 2 bytes to store RF freq.	*/
+		0x12, 0x0E,	/* 2 bytes to store RF freq.	*/
+		0x13, 0x01	/* start tune			*/
+	};
+
+	dig_rf_freq = 0;
+	tmp = 0;
+	frac_divider = 1000000;
+	kHz = 1000;
+	MHz = 1000000;
+
+	dig_rf_freq = freq / MHz;
+	tmp = freq % MHz;
+
+	for (i = 0; i < 6; i++) {
+		dig_rf_freq <<= 1;
+		frac_divider /= 2;
+		if (tmp > frac_divider) {
+			tmp -= frac_divider;
+			dig_rf_freq++;
+		}
+	}
+	if (tmp > 7812)
+		dig_rf_freq++;
+
+	rf_data[2 * (7) + 1] = (u8)(dig_rf_freq);
+	rf_data[2 * (8) + 1] = (u8)(dig_rf_freq >> 8);
+
+	for (i = 0; i < sizeof(SHF_DVBT_TAB)/sizeof(*SHF_DVBT_TAB); i++) {
+		if ((freq >= (SHF_DVBT_TAB[i].freq - SHF_DVBT_TAB[i].freq_th) * kHz) &&
+				(freq <= (SHF_DVBT_TAB[i].freq + SHF_DVBT_TAB[i].freq_th) * kHz)) {
+			rf_data[2 * (5) + 1] = SHF_DVBT_TAB[i].shf_val;
+			rf_data[2 * (6) + 1] = 0xa0 | SHF_DVBT_TAB[i].shf_dir;
+			break;
+		}
+	}
+	memcpy(data, rf_data, sizeof(rf_data));
+	*size = sizeof(rf_data);
+
+	pr_debug("mx_rftune freq=%d\n", freq);
+}
+
+static void mxl301rf_standby(struct pt3_adapter *adap)
+{
+	u8 data[4] = {0x01, 0x00, 0x13, 0x00};
+	tc90522_write_tuner_without_addr(adap, data, sizeof(data));
+}
+
+static void mxl301rf_set_register(struct pt3_adapter *adap, u8 addr, u8 value)
+{
+	u8 data[2] = {addr, value};
+	tc90522_write_tuner_without_addr(adap, data, sizeof(data));
+}
+
+static void mxl301rf_idac_setting(struct pt3_adapter *adap)
+{
+	u8 data[] = {
+		0x0D, 0x00,
+		0x0C, 0x67,
+		0x6F, 0x89,
+		0x70, 0x0C,
+		0x6F, 0x8A,
+		0x70, 0x0E,
+		0x6F, 0x8B,
+		0x70, 0x10+12,
+	};
+	tc90522_write_tuner_without_addr(adap, data, sizeof(data));
+}
+
+void mxl301rf_tuner_rftune(struct pt3_adapter *adap, u32 freq)
+{
+	u8 data[100];
+	u32 size;
+
+	size = 0;
+	adap->freq = freq;
+	mxl301rf_rftune(data, &size, freq);
+	if (size != 20) {
+		pr_debug("fail mx_rftune size = %d\n", size);
+		return;
+	}
+	tc90522_write_tuner_without_addr(adap, data, 14);
+	msleep_interruptible(1);
+	tc90522_write_tuner_without_addr(adap, data + 14, 6);
+	msleep_interruptible(1);
+	mxl301rf_set_register(adap, 0x1a, 0x0d);
+	mxl301rf_idac_setting(adap);
+}
+
+static void mxl301rf_wakeup(struct pt3_adapter *adap)
+{
+	u8 data[2] = {0x01, 0x01};
+
+	tc90522_write_tuner_without_addr(adap, data, sizeof(data));
+	mxl301rf_tuner_rftune(adap, adap->freq);
+}
+
+static void mxl301rf_set_sleep_mode(struct pt3_adapter *adap, bool sleep)
+{
+	if (sleep)
+		mxl301rf_standby(adap);
+	else
+		mxl301rf_wakeup(adap);
+}
+
+int mxl301rf_set_sleep(struct pt3_adapter *adap, bool sleep)
+{
+	int ret;
+
+	if (sleep) {
+		ret = tc90522_set_agc(adap, TC90522_AGC_MANUAL);
+		if (ret)
+			return ret;
+		mxl301rf_set_sleep_mode(adap, sleep);
+		tc90522_write_sleep_time(adap, sleep);
+	} else {
+		tc90522_write_sleep_time(adap, sleep);
+		mxl301rf_set_sleep_mode(adap, sleep);
+	}
+	adap->sleep = sleep;
+	return 0;
+}
+
+static u8 MXL301RF_FREQ_TABLE[][3] = {
+	{   2, 0,  3 },
+	{  12, 1, 22 },
+	{  21, 0, 12 },
+	{  62, 1, 63 },
+	{ 112, 0, 62 }
+};
+
+void mxl301rf_get_channel_frequency(struct pt3_adapter *adap, u32 channel, bool *catv, u32 *number, u32 *freq)
+{
+	u32 i;
+	s32 freq_offset = 0;
+
+	if (12 <= channel)
+		freq_offset += 2;
+	if (17 <= channel)
+		freq_offset -= 2;
+	if (63 <= channel)
+		freq_offset += 2;
+	*freq = 93 + channel * 6 + freq_offset;
+
+	for (i = 0; i < sizeof(MXL301RF_FREQ_TABLE) / sizeof(*MXL301RF_FREQ_TABLE); i++) {
+		if (channel <= MXL301RF_FREQ_TABLE[i][0]) {
+			*catv = MXL301RF_FREQ_TABLE[i][1] ? true : false;
+			*number = channel + MXL301RF_FREQ_TABLE[i][2] - MXL301RF_FREQ_TABLE[i][0];
+			break;
+		}
+	}
+}
+
+static u32 MXL301RF_RF_TABLE[112] = {
+	0x058d3f49, 0x05e8ccc9, 0x06445a49, 0x069fe7c9, 0x06fb7549,
+	0x075702c9, 0x07b29049, 0x080e1dc9, 0x0869ab49, 0x08c538c9,
+	0x0920c649, 0x097c53c9, 0x09f665c9, 0x0a51f349, 0x0aad80c9,
+	0x0b090e49, 0x0b649bc9, 0x0ba1a4c9, 0x0bfd3249, 0x0c58bfc9,
+	0x0cb44d49, 0x0d0fdac9, 0x0d6b6849, 0x0dc6f5c9, 0x0e228349,
+	0x0e7e10c9, 0x0ed99e49, 0x0f352bc9, 0x0f90b949, 0x0fec46c9,
+	0x1047d449, 0x10a361c9, 0x10feef49, 0x115a7cc9, 0x11b60a49,
+	0x121197c9, 0x126d2549, 0x12c8b2c9, 0x13244049, 0x137fcdc9,
+	0x13db5b49, 0x1436e8c9, 0x14927649, 0x14ee03c9, 0x15499149,
+	0x15a51ec9, 0x1600ac49, 0x165c39c9, 0x16b7c749, 0x171354c9,
+	0x176ee249, 0x17ca6fc9, 0x1825fd49, 0x18818ac9, 0x18dd1849,
+	0x1938a5c9, 0x19943349, 0x19efc0c9, 0x1a4b4e49, 0x1aa6dbc9,
+	0x1b026949, 0x1b5df6c9, 0x1bb98449, 0x1c339649, 0x1c8f23c9,
+	0x1ceab149, 0x1d463ec9, 0x1da1cc49, 0x1dfd59c9, 0x1e58e749,
+	0x1eb474c9, 0x1f100249, 0x1f6b8fc9, 0x1fc71d49, 0x2022aac9,
+	0x207e3849, 0x20d9c5c9, 0x21355349, 0x2190e0c9, 0x21ec6e49,
+	0x2247fbc9, 0x22a38949, 0x22ff16c9, 0x235aa449, 0x23b631c9,
+	0x2411bf49, 0x246d4cc9, 0x24c8da49, 0x252467c9, 0x257ff549,
+	0x25db82c9, 0x26371049, 0x26929dc9, 0x26ee2b49, 0x2749b8c9,
+	0x27a54649, 0x2800d3c9, 0x285c6149, 0x28b7eec9, 0x29137c49,
+	0x296f09c9, 0x29ca9749, 0x2a2624c9, 0x2a81b249, 0x2add3fc9,
+	0x2b38cd49, 0x2b945ac9, 0x2befe849, 0x2c4b75c9, 0x2ca70349,
+	0x2d0290c9, 0x2d5e1e49,
+};
+
+/* read via demodulator */
+static void mxl301rf_read(struct pt3_adapter *adap, u8 addr, u8 *data)
+{
+	u8 write[2] = {0xfb, addr};
+
+	tc90522_write_tuner_without_addr(adap, write, sizeof(write));
+	tc90522_read_tuner_without_addr(adap, data);
+}
+
+bool mxl301rf_rfsynth_locked(struct pt3_adapter *adap)
+{
+	u8 data;
+
+	mxl301rf_read(adap, 0x16, &data);
+	return (data & 0x0c) == 0x0c ? true : false;
+}
+
+bool mxl301rf_refsynth_locked(struct pt3_adapter *adap)
+{
+	u8 data;
+
+	mxl301rf_read(adap, 0x16, &data);
+	return (data & 0x03) == 0x03 ? true : false;
+}
+
+bool mxl301rf_locked(struct pt3_adapter *adap)
+{
+	bool locked1, locked2;
+	struct timeval begin, now;
+
+	do_gettimeofday(&begin);
+	while (1) {
+		do_gettimeofday(&now);
+		locked1 = mxl301rf_rfsynth_locked(adap);
+		locked2 = mxl301rf_refsynth_locked(adap);
+		if (locked1 && locked2)
+			break;
+		if (tc90522_time_diff(&begin, &now) > 1000)
+			break;
+		msleep_interruptible(1);
+	}
+	pr_debug("#%d mx locked1=%d locked2=%d\n", adap->idx, locked1, locked2);
+	return locked1 && locked2;
+}
+
+int mxl301rf_set_freq(struct pt3_adapter *adap, u32 channel, s32 offset)
+{
+	bool catv;
+	u32 number, freq, real_freq;
+	int ret = tc90522_set_agc(adap, TC90522_AGC_MANUAL);
+
+	if (ret)
+		return ret;
+	mxl301rf_get_channel_frequency(adap, channel, &catv, &number, &freq);
+	pr_debug("#%d ch%d%s no%d %dHz\n", adap->idx, channel, catv ? " CATV" : "", number, freq);
+	/* real_freq = (7 * freq + 1 + offset) * 1000000.0/7.0; */
+	real_freq = MXL301RF_RF_TABLE[channel];
+
+	mxl301rf_tuner_rftune(adap, real_freq);
+
+	return (!mxl301rf_locked(adap)) ? -ETIMEDOUT : tc90522_set_agc(adap, TC90522_AGC_AUTO);
+}
+
+#define MXL301RF_NHK (MXL301RF_RF_TABLE[77])
+int mxl301rf_freq(int freq)
+{
+	if (freq >= 90000000)
+		return freq;					/* real_freq	*/
+	if (freq > 255)
+		return MXL301RF_NHK;
+	if (freq > 127)
+		return MXL301RF_RF_TABLE[freq - 128];		/* freqno (IO#)	*/
+	if (freq > 63) {					/* CATV		*/
+		freq -= 64;
+		if (freq > 22)
+			return MXL301RF_RF_TABLE[freq - 1];	/* C23-C62	*/
+		if (freq > 12)
+			return MXL301RF_RF_TABLE[freq - 10];	/* C13-C22	*/
+		return MXL301RF_NHK;
+	}
+	if (freq > 62)
+		return MXL301RF_NHK;
+	if (freq > 12)
+		return MXL301RF_RF_TABLE[freq + 50];		/* 13-62	*/
+	if (freq >  3)
+		return MXL301RF_RF_TABLE[freq +  9];		/*  4-12	*/
+	if (freq)
+		return MXL301RF_RF_TABLE[freq -  1];		/*  1-3		*/
+	return MXL301RF_NHK;
+}
+
+EXPORT_SYMBOL(mxl301rf_set_freq);
+EXPORT_SYMBOL(mxl301rf_set_sleep);
+
diff --git a/drivers/media/dvb-frontends/mxl301rf.h b/drivers/media/dvb-frontends/mxl301rf.h
new file mode 100644
index 0000000..6ed58a6
--- /dev/null
+++ b/drivers/media/dvb-frontends/mxl301rf.h
@@ -0,0 +1,27 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-T tuner MaxLinear CMOS Hybrid TV MxL301RF
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 __MXL301RF_H__
+#define __MXL301RF_H__
+
+int mxl301rf_set_freq(struct pt3_adapter *adap, u32 channel, s32 offset);
+int mxl301rf_set_sleep(struct pt3_adapter *adap, bool sleep);
+int mxl301rf_freq(int freq);
+void mxl301rf_tuner_rftune(struct pt3_adapter *adap, u32 freq);
+bool mxl301rf_locked(struct pt3_adapter *adap);
+
+#endif
+
diff --git a/drivers/media/dvb-frontends/pt3_common.h b/drivers/media/dvb-frontends/pt3_common.h
new file mode 100644
index 0000000..6ad1ecd
--- /dev/null
+++ b/drivers/media/dvb-frontends/pt3_common.h
@@ -0,0 +1,95 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_COMMON_H__
+#define	__PT3_COMMON_H__
+
+#define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+
+#include <linux/pci.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include "dvb_demux.h"
+#include "dmxdev.h"
+#include "dvb_frontend.h"
+
+#define PT3_NR_ADAPS 4
+#define DRV_NAME "pt3_dvb"
+
+/* register idx */
+#define REG_VERSION	0x00	/*	R	Version		*/
+#define REG_BUS		0x04	/*	R	Bus		*/
+#define REG_SYSTEM_W	0x08	/*	W	System		*/
+#define REG_SYSTEM_R	0x0c	/*	R	System		*/
+#define REG_I2C_W	0x10	/*	W	I2C		*/
+#define REG_I2C_R	0x14	/*	R	I2C		*/
+#define REG_RAM_W	0x18	/*	W	RAM		*/
+#define REG_RAM_R	0x1c	/*	R	RAM		*/
+#define REG_BASE	0x40	/* + 0x18*idx			*/
+#define REG_DMA_DESC_L	0x00	/*	W	DMA		*/
+#define REG_DMA_DESC_H	0x04	/*	W	DMA		*/
+#define REG_DMA_CTL	0x08	/*	W	DMA		*/
+#define REG_TS_CTL	0x0c	/*	W	TS		*/
+#define REG_STATUS	0x10	/*	R	DMA/FIFO/TS	*/
+#define REG_TS_ERR	0x14	/*	R	TS		*/
+
+struct pt3_adapter;
+
+struct pt3_board {
+	struct mutex lock;
+	int lnb;
+	bool reset;
+
+	struct pci_dev *pdev;
+	int bars;
+	void __iomem *bar_reg, *bar_mem;
+	struct i2c_adapter i2c;
+	u8 i2c_buf;
+	u32 i2c_addr;
+	bool i2c_filled;
+
+	struct pt3_adapter *adap[PT3_NR_ADAPS];
+};
+
+struct pt3_adapter {
+	struct mutex lock;
+	struct pt3_board *pt3;
+
+	int idx, init_ch;
+	char *str;
+	fe_delivery_system_t type;
+	bool in_use, sleep;
+	u32 channel;
+	s32 offset;
+	u8 addr_demod, addr_tuner;
+	u32 freq;
+	struct qm1d1c0042 *qm;
+	struct pt3_dma *dma;
+	struct task_struct *kthread;
+	int *dec;
+	struct dvb_adapter dvb;
+	struct dvb_demux demux;
+	int users;
+	struct dmxdev dmxdev;
+	struct dvb_frontend *fe;
+	int (*orig_voltage)(struct dvb_frontend *fe, fe_sec_voltage_t voltage);
+	int (*orig_sleep)  (struct dvb_frontend *fe);
+	int (*orig_init)   (struct dvb_frontend *fe);
+	fe_sec_voltage_t voltage;
+};
+
+#endif
+
diff --git a/drivers/media/dvb-frontends/qm1d1c0042.c b/drivers/media/dvb-frontends/qm1d1c0042.c
new file mode 100644
index 0000000..2972bcf
--- /dev/null
+++ b/drivers/media/dvb-frontends/qm1d1c0042.c
@@ -0,0 +1,413 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-S tuner driver QM1D1C0042
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_common.h"
+#include "tc90522.h"
+#include "qm1d1c0042.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 QM1D1C0042 ISDB-S tuner driver");
+MODULE_LICENSE("GPL");
+
+static u8 qm1d1c0042_reg_rw[] = {
+	0x48, 0x1c, 0xa0, 0x10, 0xbc, 0xc5, 0x20, 0x33,
+	0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+	0x00, 0xff, 0xf3, 0x00, 0x2a, 0x64, 0xa6, 0x86,
+	0x8c, 0xcf, 0xb8, 0xf1, 0xa8, 0xf2, 0x89, 0x00,
+};
+
+void qm1d1c0042_init_reg_param(struct qm1d1c0042 *qm)
+{
+	memcpy(qm->reg, qm1d1c0042_reg_rw, sizeof(qm1d1c0042_reg_rw));
+
+	qm->adap->freq = 0;
+	qm->standby = false;
+	qm->wait_time_lpf = 20;
+	qm->wait_time_search_fast = 4;
+	qm->wait_time_search_normal = 15;
+}
+
+/* read via demodulator */
+static int qm1d1c0042_read(struct qm1d1c0042 *qm, u8 addr, u8 *data)
+{
+	int ret = 0;
+	if ((addr == 0x00) || (addr == 0x0d))
+		ret = tc90522_read_tuner(qm->adap, addr, data);
+	return ret;
+}
+
+/* write via demodulator */
+static int qm1d1c0042_write(struct qm1d1c0042 *qm, u8 addr, u8 data)
+{
+	int ret = tc90522_write_tuner(qm->adap, addr, &data, sizeof(data));
+	qm->reg[addr] = data;
+	return ret;
+}
+
+#define QM1D1C0042_INIT_DUMMY_RESET 0x0c
+
+void qm1d1c0042_dummy_reset(struct qm1d1c0042 *qm)
+{
+	qm1d1c0042_write(qm, 0x01, QM1D1C0042_INIT_DUMMY_RESET);
+	qm1d1c0042_write(qm, 0x01, QM1D1C0042_INIT_DUMMY_RESET);
+}
+
+static u8 qm1d1c0042_flag[32] = {
+	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
+	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+};
+
+static int qm1d1c0042_set_sleep_mode(struct qm1d1c0042 *qm)
+{
+	int ret;
+
+	if (qm->standby) {
+		qm->reg[0x01] &= (~(1 << 3)) & 0xff;
+		qm->reg[0x01] |= 1 << 0;
+		qm->reg[0x05] |= 1 << 3;
+
+		ret = qm1d1c0042_write(qm, 0x05, qm->reg[0x05]);
+		if (ret)
+			return ret;
+		ret = qm1d1c0042_write(qm, 0x01, qm->reg[0x01]);
+		if (ret)
+			return ret;
+	} else {
+		qm->reg[0x01] |= 1 << 3;
+		qm->reg[0x01] &= (~(1 << 0)) & 0xff;
+		qm->reg[0x05] &= (~(1 << 3)) & 0xff;
+
+		ret = qm1d1c0042_write(qm, 0x01, qm->reg[0x01]);
+		if (ret)
+			return ret;
+		ret = qm1d1c0042_write(qm, 0x05, qm->reg[0x05]);
+		if (ret)
+			return ret;
+	}
+	return ret;
+}
+
+static int qm1d1c0042_set_search_mode(struct qm1d1c0042 *qm)
+{
+	qm->reg[3] &= 0xfe;
+	return qm1d1c0042_write(qm, 0x03, qm->reg[3]);
+}
+
+int qm1d1c0042_init(struct qm1d1c0042 *qm)
+{
+	u8 i_data;
+	u32 i;
+	int ret;
+
+	/* soft reset on */
+	ret = qm1d1c0042_write(qm, 0x01, QM1D1C0042_INIT_DUMMY_RESET);
+	if (ret)
+		return ret;
+
+	msleep_interruptible(1);
+
+	/* soft reset off */
+	i_data = qm->reg[0x01] | 0x10;
+	ret = qm1d1c0042_write(qm, 0x01, i_data);
+	if (ret)
+		return ret;
+
+	/* ID check */
+	ret = qm1d1c0042_read(qm, 0x00, &i_data);
+	if (ret)
+		return ret;
+	if (i_data != 0x48)
+		return -EINVAL;
+
+	/* LPF tuning on */
+	msleep_interruptible(1);
+	qm->reg[0x0c] |= 0x40;
+	ret = qm1d1c0042_write(qm, 0x0c, qm->reg[0x0c]);
+	if (ret)
+		return ret;
+	msleep_interruptible(qm->wait_time_lpf);
+
+	for (i = 0; i < sizeof(qm1d1c0042_flag); i++)
+		if (qm1d1c0042_flag[i] == 1) {
+			ret = qm1d1c0042_write(qm, i, qm->reg[i]);
+			if (ret)
+				return ret;
+		}
+	ret = qm1d1c0042_set_sleep_mode(qm);
+	if (ret)
+		return ret;
+	return qm1d1c0042_set_search_mode(qm);
+}
+
+int qm1d1c0042_set_sleep(struct qm1d1c0042 *qm, bool sleep)
+{
+	qm->standby = sleep;
+	if (sleep) {
+		int ret = tc90522_set_agc(qm->adap, TC90522_AGC_MANUAL);
+		if (ret)
+			return ret;
+		qm1d1c0042_set_sleep_mode(qm);
+		tc90522_set_sleep_s(qm->adap, sleep);
+	} else {
+		tc90522_set_sleep_s(qm->adap, sleep);
+		qm1d1c0042_set_sleep_mode(qm);
+	}
+	qm->adap->sleep = sleep;
+	return 0;
+}
+
+void qm1d1c0042_get_channel_freq(u32 channel, u32 *number, u32 *freq)
+{
+	if (channel < 12) {
+		*number = 1 + 2 * channel;
+		*freq = 104948 + 3836 * channel;
+	} else if (channel < 24) {
+		channel -= 12;
+		*number = 2 + 2 * channel;
+		*freq = 161300 + 4000 * channel;
+	} else {
+		channel -= 24;
+		*number = 1 + 2 * channel;
+		*freq = 159300 + 4000 * channel;
+	}
+}
+
+static u32 QM1D1C0042_FREQ_TABLE[9][3] = {
+	{ 2151000, 1, 7 },
+	{ 1950000, 1, 6 },
+	{ 1800000, 1, 5 },
+	{ 1600000, 1, 4 },
+	{ 1450000, 1, 3 },
+	{ 1250000, 1, 2 },
+	{ 1200000, 0, 7 },
+	{  975000, 0, 6 },
+	{  950000, 0, 0 }
+};
+
+static u32 SD_TABLE[24][2][3] = {
+	{{0x38fae1, 0x0d, 0x5}, {0x39fae1, 0x0d, 0x5},},
+	{{0x3f570a, 0x0e, 0x3}, {0x00570a, 0x0e, 0x3},},
+	{{0x05b333, 0x0e, 0x5}, {0x06b333, 0x0e, 0x5},},
+	{{0x3c0f5c, 0x0f, 0x4}, {0x3d0f5c, 0x0f, 0x4},},
+	{{0x026b85, 0x0f, 0x6}, {0x036b85, 0x0f, 0x6},},
+	{{0x38c7ae, 0x10, 0x5}, {0x39c7ae, 0x10, 0x5},},
+	{{0x3f23d7, 0x11, 0x3}, {0x0023d7, 0x11, 0x3},},
+	{{0x058000, 0x11, 0x5}, {0x068000, 0x11, 0x5},},
+	{{0x3bdc28, 0x12, 0x4}, {0x3cdc28, 0x12, 0x4},},
+	{{0x023851, 0x12, 0x6}, {0x033851, 0x12, 0x6},},
+	{{0x38947a, 0x13, 0x5}, {0x39947a, 0x13, 0x5},},
+	{{0x3ef0a3, 0x14, 0x3}, {0x3ff0a3, 0x14, 0x3},},
+	{{0x3c8000, 0x16, 0x4}, {0x3d8000, 0x16, 0x4},},
+	{{0x048000, 0x16, 0x6}, {0x058000, 0x16, 0x6},},
+	{{0x3c8000, 0x17, 0x5}, {0x3d8000, 0x17, 0x5},},
+	{{0x048000, 0x18, 0x3}, {0x058000, 0x18, 0x3},},
+	{{0x3c8000, 0x18, 0x6}, {0x3d8000, 0x18, 0x6},},
+	{{0x048000, 0x19, 0x4}, {0x058000, 0x19, 0x4},},
+	{{0x3c8000, 0x1a, 0x3}, {0x3d8000, 0x1a, 0x3},},
+	{{0x048000, 0x1a, 0x5}, {0x058000, 0x1a, 0x5},},
+	{{0x3c8000, 0x1b, 0x4}, {0x3d8000, 0x1b, 0x4},},
+	{{0x048000, 0x1b, 0x6}, {0x058000, 0x1b, 0x6},},
+	{{0x3c8000, 0x1c, 0x5}, {0x3d8000, 0x1c, 0x5},},
+	{{0x048000, 0x1d, 0x3}, {0x058000, 0x1d, 0x3},},
+};
+
+static int qm1d1c0042_tuning(struct qm1d1c0042 *qm, u32 *sd, u32 channel)
+{
+	int ret;
+	struct pt3_adapter *adap = qm->adap;
+	u8 i_data;
+	u32 index, i, N, A;
+
+	qm->reg[0x08] &= 0xf0;
+	qm->reg[0x08] |= 0x09;
+
+	qm->reg[0x13] &= 0x9f;
+	qm->reg[0x13] |= 0x20;
+
+	for (i = 0; i < 8; i++) {
+		if ((QM1D1C0042_FREQ_TABLE[i+1][0] <= adap->freq) && (adap->freq < QM1D1C0042_FREQ_TABLE[i][0])) {
+			i_data = qm->reg[0x02];
+			i_data &= 0x0f;
+			i_data |= QM1D1C0042_FREQ_TABLE[i][1] << 7;
+			i_data |= QM1D1C0042_FREQ_TABLE[i][2] << 4;
+			qm1d1c0042_write(qm, 0x02, i_data);
+		}
+	}
+
+	index = tc90522_index(qm->adap);
+	*sd = SD_TABLE[channel][index][0];
+	N = SD_TABLE[channel][index][1];
+	A = SD_TABLE[channel][index][2];
+
+	qm->reg[0x06] &= 0x40;
+	qm->reg[0x06] |= N;
+	ret = qm1d1c0042_write(qm, 0x06, qm->reg[0x06]);
+	if (ret)
+		return ret;
+
+	qm->reg[0x07] &= 0xf0;
+	qm->reg[0x07] |= A & 0x0f;
+	return qm1d1c0042_write(qm, 0x07, qm->reg[0x07]);
+}
+
+static int qm1d1c0042_local_lpf_tuning(struct qm1d1c0042 *qm, int lpf, u32 channel)
+{
+	u8 i_data;
+	u32 sd = 0;
+	int ret = qm1d1c0042_tuning(qm, &sd, channel);
+
+	if (ret)
+		return ret;
+	if (lpf) {
+		i_data = qm->reg[0x08] & 0xf0;
+		i_data |= 2;
+		ret = qm1d1c0042_write(qm, 0x08, i_data);
+	} else
+		ret = qm1d1c0042_write(qm, 0x08, qm->reg[0x08]);
+	if (ret)
+		return ret;
+
+	qm->reg[0x09] &= 0xc0;
+	qm->reg[0x09] |= (sd >> 16) & 0x3f;
+	qm->reg[0x0a] = (sd >> 8) & 0xff;
+	qm->reg[0x0b] = (sd >> 0) & 0xff;
+	ret = qm1d1c0042_write(qm, 0x09, qm->reg[0x09]);
+	if (ret)
+		return ret;
+	ret = qm1d1c0042_write(qm, 0x0a, qm->reg[0x0a]);
+	if (ret)
+		return ret;
+	ret = qm1d1c0042_write(qm, 0x0b, qm->reg[0x0b]);
+	if (ret)
+		return ret;
+
+	if (lpf) {
+		i_data = qm->reg[0x0c];
+		i_data &= 0x3f;
+		ret = qm1d1c0042_write(qm, 0x0c, i_data);
+		if (ret)
+			return ret;
+		msleep_interruptible(1);
+
+		i_data = qm->reg[0x0c];
+		i_data |= 0xc0;
+		ret = qm1d1c0042_write(qm, 0x0c, i_data);
+		if (ret)
+			return ret;
+		msleep_interruptible(qm->wait_time_lpf);
+		ret = qm1d1c0042_write(qm, 0x08, 0x09);
+		if (ret)
+			return ret;
+		ret = qm1d1c0042_write(qm, 0x13, qm->reg[0x13]);
+		if (ret)
+			return ret;
+	} else {
+		ret = qm1d1c0042_write(qm, 0x13, qm->reg[0x13]);
+		if (ret)
+			return ret;
+		i_data = qm->reg[0x0c];
+		i_data &= 0x7f;
+		ret = qm1d1c0042_write(qm, 0x0c, i_data);
+		if (ret)
+			return ret;
+		msleep_interruptible(2);
+
+		i_data = qm->reg[0x0c];
+		i_data |= 0x80;
+		ret = qm1d1c0042_write(qm, 0x0c, i_data);
+		if (ret)
+			return ret;
+		if (qm->reg[0x03] & 0x01)
+			msleep_interruptible(qm->wait_time_search_fast);
+		else
+			msleep_interruptible(qm->wait_time_search_normal);
+	}
+	return ret;
+}
+
+int qm1d1c0042_get_locked(struct qm1d1c0042 *qm, bool *locked)
+{
+	int ret = qm1d1c0042_read(qm, 0x0d, &qm->reg[0x0d]);
+	if (ret)
+		return ret;
+	if (qm->reg[0x0d] & 0x40)
+		*locked = true;
+	else
+		*locked = false;
+	return ret;
+}
+
+int qm1d1c0042_set_freq(struct qm1d1c0042 *qm, u32 channel)
+{
+	u32 number, freq, freq_kHz;
+	struct timeval begin, now;
+	bool locked;
+	int ret = tc90522_set_agc(qm->adap, TC90522_AGC_MANUAL);
+	if (ret)
+		return ret;
+
+	qm1d1c0042_get_channel_freq(channel, &number, &freq);
+	freq_kHz = freq * 10;
+	if (tc90522_index(qm->adap) == 0)
+		freq_kHz -= 500;
+	else
+		freq_kHz += 500;
+	qm->adap->freq = freq_kHz;
+	pr_debug("#%d ch %d freq %d kHz\n", qm->adap->idx, channel, freq_kHz);
+
+	ret = qm1d1c0042_local_lpf_tuning(qm, 1, channel);
+	if (ret)
+		return ret;
+	do_gettimeofday(&begin);
+	while (1) {
+		do_gettimeofday(&now);
+		ret = qm1d1c0042_get_locked(qm, &locked);
+		if (ret)
+			return ret;
+		if (locked)
+			break;
+		if (tc90522_time_diff(&begin, &now) >= 100)
+			break;
+		msleep_interruptible(1);
+	}
+	pr_debug("#%d qm_get_locked %d ret=0x%x\n", qm->adap->idx, locked, ret);
+	if (!locked)
+		return -ETIMEDOUT;
+
+	ret = tc90522_set_agc(qm->adap, TC90522_AGC_AUTO);
+	if (!ret) {
+		qm->adap->channel = channel;
+		qm->adap->offset = 0;
+	}
+	return ret;
+}
+
+int qm1d1c0042_tuner_init(struct qm1d1c0042 *qm)
+{
+	if (*qm->reg) {
+		if (qm1d1c0042_init(qm))
+			return -EIO;
+	} else {
+		qm1d1c0042_init_reg_param(qm);
+		qm1d1c0042_dummy_reset(qm);
+	}
+	return 0;
+}
+
+EXPORT_SYMBOL(qm1d1c0042_set_freq);
+EXPORT_SYMBOL(qm1d1c0042_set_sleep);
+EXPORT_SYMBOL(qm1d1c0042_tuner_init);
+
diff --git a/drivers/media/dvb-frontends/qm1d1c0042.h b/drivers/media/dvb-frontends/qm1d1c0042.h
new file mode 100644
index 0000000..079e846
--- /dev/null
+++ b/drivers/media/dvb-frontends/qm1d1c0042.h
@@ -0,0 +1,34 @@
+/*
+ * Sharp VA4M6JC2103 - Earthsoft PT3 ISDB-S tuner driver QM1D1C0042
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 __QM1D1C0042_H__
+#define __QM1D1C0042_H__
+
+struct qm1d1c0042 {
+	struct pt3_adapter *adap;
+	u8 reg[32];
+
+	bool standby;
+	u32 wait_time_lpf, wait_time_search_fast, wait_time_search_normal;
+	struct tmcc_s tmcc;
+};
+
+int qm1d1c0042_set_freq(struct qm1d1c0042 *qm, u32 channel);
+int qm1d1c0042_set_sleep(struct qm1d1c0042 *qm, bool sleep);
+int qm1d1c0042_tuner_init(struct qm1d1c0042 *qm);
+
+#endif
+
diff --git a/drivers/media/dvb-frontends/tc90522.c b/drivers/media/dvb-frontends/tc90522.c
new file mode 100644
index 0000000..bd162b7
--- /dev/null
+++ b/drivers/media/dvb-frontends/tc90522.c
@@ -0,0 +1,724 @@
+/*
+ * Earthsoft PT3 demodulator frontend Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S)
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_common.h"
+#include "tc90522.h"
+#include "qm1d1c0042.h"
+#include "mxl301rf.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 Toshiba TC90522 OFDM(ISDB-T)/8PSK(ISDB-S) demodulator");
+MODULE_LICENSE("GPL");
+
+int tc90522_write(struct pt3_adapter *adap, u8 addr, u8 *data, u8 len)
+{
+	struct i2c_msg msg[1];
+	u8 buf[len + 1];
+	buf[0] = addr;
+	memcpy(buf + 1, data, len);
+
+	msg[0].addr = adap->addr_demod;
+	msg[0].flags = 0;			/* write */
+	msg[0].buf = buf;
+	msg[0].len = len + 1;
+
+	return i2c_transfer(&adap->pt3->i2c, msg, 1) == 1 ? 0 : -EREMOTEIO;
+}
+
+static int tc90522_write_pskmsrst(struct pt3_adapter *adap)
+{
+	u8 data = 0x01;
+	return tc90522_write(adap, 0x03, &data, 1);
+}
+
+static int tc90522_write_imsrst(struct pt3_adapter *adap)
+{
+	u8 data = 0x01 << 6;
+	return tc90522_write(adap, 0x01, &data, 1);
+}
+
+int tc90522_init(struct pt3_adapter *adap)
+{
+	u8 data = 0x10;
+
+	pr_debug("#%d %s tuner=0x%x demod=0x%x\n", adap->idx, adap->str, adap->addr_tuner, adap->addr_demod);
+	if (adap->type == SYS_ISDBS) {
+		u8 d[] = { 0x15, 0x04 };
+		int ret = tc90522_write_pskmsrst(adap);
+		if (ret)
+			return ret;
+		ret = tc90522_write(adap, 0x1e, &data, 1);
+		if (ret)
+			return ret;
+		return (ret = tc90522_write(adap, 0x1c, &d[0], 1)) ?
+			ret : tc90522_write(adap, 0x1f, &d[1], 1);
+	} else {
+		int ret = tc90522_write_imsrst(adap);
+		if (ret)
+			return ret;
+		ret = tc90522_write(adap, 0x1c, &data, 1);
+		if (ret)
+			return ret;
+		data = 0x01;
+		return tc90522_write(adap, 0x1d, &data, 1);
+	}
+}
+
+int tc90522_set_powers(struct pt3_adapter *adap, bool tuner, bool amp)
+{
+	u8	tuner_power = tuner ? 0x03 : 0x02,
+		amp_power = amp ? 0x03 : 0x02,
+		data = (tuner_power << 6) | (0x01 << 4) | (amp_power << 2) | 0x01 << 0;
+	pr_debug("#%d tuner %s amp %s\n", adap->idx, tuner ? "ON" : "OFF", amp ? "ON" : "OFF");
+	return tc90522_write(adap, 0x1e, &data, 1);
+}
+
+#define TC90522_THROUGH 0xfe
+
+int tc90522_write_tuner(struct pt3_adapter *adap, u8 addr, const u8 *data, u32 len)
+{
+	struct i2c_msg msg[1];
+	u8 buf[len + 3];
+
+	buf[0] = TC90522_THROUGH;
+	buf[1] = adap->addr_tuner << 1;
+	buf[2] = addr;
+	memcpy(buf + 3, data, len);
+	msg[0].buf = buf;
+	msg[0].len = len + 3;
+	msg[0].addr = adap->addr_demod;
+	msg[0].flags = 0;			/* write */
+
+	return i2c_transfer(&adap->pt3->i2c, msg, 1) == 1 ? 0 : -EREMOTEIO;
+}
+
+int tc90522_read_tuner(struct pt3_adapter *adap, u8 addr, u8 *data)
+{
+	struct i2c_msg msg[3];
+	u8 buf[5];
+
+	buf[0] = TC90522_THROUGH;
+	buf[1] = adap->addr_tuner << 1;
+	buf[2] = addr;
+	msg[0].buf = buf;
+	msg[0].len = 3;
+	msg[0].addr = adap->addr_demod;
+	msg[0].flags = 0;			/* write */
+
+	buf[3] = TC90522_THROUGH;
+	buf[4] = (adap->addr_tuner << 1) | 1;
+	msg[1].buf = buf + 3;
+	msg[1].len = 2;
+	msg[1].addr = adap->addr_demod;
+	msg[1].flags = 0;			/* write */
+
+	msg[2].buf = data;
+	msg[2].len = 1;
+	msg[2].addr = adap->addr_demod;
+	msg[2].flags = I2C_M_RD;		/* read */
+
+	return i2c_transfer(&adap->pt3->i2c, msg, 3) == 3 ? 0 : -EREMOTEIO;
+}
+
+static u8 agc_data_s[2] = { 0xb0, 0x30 };
+
+u32 tc90522_index(struct pt3_adapter *adap)
+{
+	return (adap->addr_demod >> 1) & 1;
+}
+
+int tc90522_set_agc_s(struct pt3_adapter *adap, enum tc90522_agc agc)
+{
+	u8 data = (agc == TC90522_AGC_AUTO) ? 0xff : 0x00;
+	int ret = tc90522_write(adap, 0x0a, &data, 1);
+	if (ret)
+		return ret;
+
+	data = agc_data_s[tc90522_index(adap)];
+	data |= (agc == TC90522_AGC_AUTO) ? 0x01 : 0x00;
+	ret = tc90522_write(adap, 0x10, &data, 1);
+	if (ret)
+		return ret;
+
+	data = (agc == TC90522_AGC_AUTO) ? 0x40 : 0x00;
+	return (ret = tc90522_write(adap, 0x11, &data, 1)) ? ret : tc90522_write_pskmsrst(adap);
+}
+
+int tc90522_set_sleep_s(struct pt3_adapter *adap, bool sleep)
+{
+	u8 buf = sleep ? 1 : 0;
+	return tc90522_write(adap, 0x17, &buf, 1);
+}
+
+int tc90522_set_agc_t(struct pt3_adapter *adap, enum tc90522_agc agc)
+{
+	u8 data = (agc == TC90522_AGC_AUTO) ? 0x40 : 0x00;
+	int ret = tc90522_write(adap, 0x25, &data, 1);
+	if (ret)
+		return ret;
+
+	data = 0x4c | ((agc == TC90522_AGC_AUTO) ? 0x00 : 0x01);
+	return (ret = tc90522_write(adap, 0x23, &data, 1)) ? ret : tc90522_write_imsrst(adap);
+}
+
+int tc90522_set_agc(struct pt3_adapter *adap, enum tc90522_agc agc)
+{
+	if (adap->type == SYS_ISDBS)
+		return tc90522_set_agc_s(adap, agc);
+	else
+		return tc90522_set_agc_t(adap, agc);
+}
+
+int tc90522_write_tuner_without_addr(struct pt3_adapter *adap, const u8 *data, u32 len)
+{
+	struct i2c_msg msg[1];
+	u8 buf[len + 2];
+
+	buf[0] = TC90522_THROUGH;
+	buf[1] = adap->addr_tuner << 1;
+	memcpy(buf + 2, data, len);
+	msg[0].buf = buf;
+	msg[0].len = len + 2;
+	msg[0].addr = adap->addr_demod;
+	msg[0].flags = 0;			/* write */
+
+	return i2c_transfer(&adap->pt3->i2c, msg, 1) == 1 ? 0 : -EREMOTEIO;
+}
+
+int tc90522_write_sleep_time(struct pt3_adapter *adap, int sleep)
+{
+	u8 data = (1 << 7) | ((sleep ? 1 : 0) << 4);
+	return tc90522_write(adap, 0x03, &data, 1);
+}
+
+u32 tc90522_time_diff(struct timeval *st, struct timeval *et)
+{
+	u32 diff = ((et->tv_sec - st->tv_sec) * 1000000 + (et->tv_usec - st->tv_usec)) / 1000;
+	pr_debug("time diff = %d\n", diff);
+	return diff;
+}
+
+int tc90522_read_tuner_without_addr(struct pt3_adapter *adap, u8 *data)
+{
+	struct i2c_msg msg[2];
+	u8 buf[2];
+
+	buf[0] = TC90522_THROUGH;
+	buf[1] = (adap->addr_tuner << 1) | 1;
+	msg[0].buf = buf;
+	msg[0].len = 2;
+	msg[0].addr = adap->addr_demod;
+	msg[0].flags = 0;			/* write */
+
+	msg[1].buf = data;
+	msg[1].len = 1;
+	msg[1].addr = adap->addr_demod;
+	msg[1].flags = I2C_M_RD;		/* read */
+
+	return i2c_transfer(&adap->pt3->i2c, msg, 2) == 2 ? 0 : -EREMOTEIO;
+}
+
+static u32 tc90522_byten(const u8 *data, u32 n)
+{
+	u32 i, val = 0;
+
+	for (i = 0; i < n; i++) {
+		val <<= 8;
+		val |= data[i];
+	}
+	return val;
+}
+
+int tc90522_read(struct pt3_adapter *adap, u8 addr, u8 *buf, u8 buflen)
+{
+	struct i2c_msg msg[2];
+	buf[0] = addr;
+
+	msg[0].addr = adap->addr_demod;
+	msg[0].flags = 0;			/* write */
+	msg[0].buf = buf;
+	msg[0].len = 1;
+
+	msg[1].addr = adap->addr_demod;
+	msg[1].flags = I2C_M_RD;		/* read */
+	msg[1].buf = buf;
+	msg[1].len = buflen;
+
+	return i2c_transfer(&adap->pt3->i2c, msg, 2) == 2 ? 0 : -EREMOTEIO;
+}
+
+int tc90522_read_retryov_fulock(struct pt3_adapter *adap, bool *retryov, bool *fulock)
+{
+	u8 buf;
+	int ret = tc90522_read(adap, 0x80, &buf, 1);
+	if (!ret) {
+		*retryov = buf & 0b10000000 ? true : false;
+		*fulock  = buf & 0b00001000 ? true : false;
+	}
+	return ret;
+}
+
+int tc90522_read_cn(struct pt3_adapter *adap, u32 *cn)
+{
+	u8 buf[3], buflen = (adap->type == SYS_ISDBS) ? 2 : 3, addr = (adap->type == SYS_ISDBS) ? 0xbc : 0x8b;
+	int ret = tc90522_read(adap, addr, buf, buflen);
+	if (!ret)
+		*cn =  tc90522_byten(buf, buflen);
+	return ret;
+}
+
+int tc90522_read_id_s(struct pt3_adapter *adap, u16 *id)
+{
+	u8 buf[2];
+	int ret = tc90522_read(adap, 0xe6, buf, 2);
+	if (!ret)
+		*id = tc90522_byten(buf, 2);
+	return ret;
+}
+
+int tc90522_read_tmcc_s(struct pt3_adapter *adap, struct tmcc_s *tmcc)
+{
+	enum {
+		BASE = 0xc5,
+		SIZE = 0xe5 - BASE + 1
+	};
+	u8 data[SIZE];
+	u32 i, byte_offset, bit_offset;
+
+	int ret = tc90522_read(adap, 0xc3, data, 1);
+	if (ret)
+		return ret;
+	if ((data[0] >> 4) & 1)
+		return -EBADMSG;
+
+	ret = tc90522_read(adap, 0xce, data, 2);
+	if (ret)
+		return ret;
+	if (tc90522_byten(data, 2) == 0)
+		return -EBADMSG;
+
+	ret = tc90522_read(adap, 0xc3, data, 1);
+	if (ret)
+		return ret;
+
+	ret = tc90522_read(adap, 0xc5, data, SIZE);
+	if (ret)
+		return ret;
+	for (i = 0; i < 4; i++) {
+		byte_offset = i >> 1;
+		bit_offset = (i & 1) ? 0 : 4;
+		tmcc->mode[i] = (data[0xc8 + byte_offset - BASE] >> bit_offset) & 0b00001111;
+		tmcc->slot[i] = (data[0xca + i           - BASE] >>          0) & 0b00111111;
+	}
+	for (i = 0; i < 8; i++)
+		tmcc->id[i] = tc90522_byten(data + 0xce + i * 2 - BASE, 2);
+	return ret;
+}
+
+int tc90522_write_id_s(struct pt3_adapter *adap, u16 id)
+{
+	u8 data[2] = { id >> 8, (u8)id };
+	return tc90522_write(adap, 0x8f, data, sizeof(data));
+}
+
+/*
+ * DVB Frontend driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ */
+
+/**** Common ****/
+enum tc90522_tune_state {
+	PT3_TUNE_IDLE,
+	PT3_TUNE_SET_FREQUENCY,
+	PT3_TUNE_CHECK_FREQUENCY,
+	PT3_TUNE_SET_MODULATION,
+	PT3_TUNE_CHECK_MODULATION,
+	PT3_TUNE_SET_TS_ID,
+	PT3_TUNE_CHECK_TS_ID,
+	PT3_TUNE_TRACK,
+	PT3_TUNE_ABORT,
+};
+
+struct tc90522_state {
+	struct pt3_adapter *adap;
+	struct dvb_frontend fe;
+	enum tc90522_tune_state tune_state;
+};
+
+static int tc90522_read_signal_strength(struct dvb_frontend *fe, u16 *cn)
+{
+	struct tc90522_state *state = fe->demodulator_priv;
+	return tc90522_read_cn(state->adap, (u32 *)cn);
+}
+
+static int tc90522_get_frontend_algo(struct dvb_frontend *fe)
+{
+	return DVBFE_ALGO_HW;
+}
+
+static void tc90522_release(struct dvb_frontend *fe)
+{
+	kfree(fe->demodulator_priv);
+}
+
+static int tc90522_fe_init(struct dvb_frontend *fe)
+{
+	struct tc90522_state *state = fe->demodulator_priv;
+	state->tune_state = PT3_TUNE_IDLE;
+	return (state->adap->type == SYS_ISDBS) ?
+		qm1d1c0042_set_sleep(state->adap->qm, false) : mxl301rf_set_sleep(state->adap, false);
+}
+
+static int tc90522_sleep(struct dvb_frontend *fe)
+{
+	struct tc90522_state *state = fe->demodulator_priv;
+	return (state->adap->type == SYS_ISDBS) ?
+		qm1d1c0042_set_sleep(state->adap->qm, true) : mxl301rf_set_sleep(state->adap, true);
+}
+
+/**** ISDB-S ****/
+u32 tc90522_s_get_channel(u32 frequency)
+{
+	u32 freq = frequency / 10,
+	    ch0 = (freq - 104948) / 3836, diff0 = freq - (104948 + 3836 * ch0),
+	    ch1 = (freq - 161300) / 4000, diff1 = freq - (161300 + 4000 * ch1),
+	    ch2 = (freq - 159300) / 4000, diff2 = freq - (159300 + 4000 * ch2),
+	    min = diff0 < diff1 ? diff0 : diff1;
+
+	if (diff2 < min)
+		return ch2 + 24;
+	else if (min == diff1)
+		return ch1 + 12;
+	else
+		return ch0;
+}
+
+static int tc90522_s_read_status(struct dvb_frontend *fe, fe_status_t *status)
+{
+	struct tc90522_state *state = fe->demodulator_priv;
+
+	switch (state->tune_state) {
+	case PT3_TUNE_IDLE:
+	case PT3_TUNE_SET_FREQUENCY:
+	case PT3_TUNE_CHECK_FREQUENCY:
+		*status = 0;
+		return 0;
+
+	case PT3_TUNE_SET_MODULATION:
+	case PT3_TUNE_CHECK_MODULATION:
+	case PT3_TUNE_ABORT:
+		*status |= FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3_TUNE_SET_TS_ID:
+	case PT3_TUNE_CHECK_TS_ID:
+		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER;
+		return 0;
+
+	case PT3_TUNE_TRACK:
+		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+	}
+	BUG();
+}
+
+static int tc90522_s_tune(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
+{
+	struct tc90522_state *state = fe->demodulator_priv;
+	struct pt3_adapter *adap = state->adap;
+	struct tmcc_s *tmcc = &adap->qm->tmcc;
+	int i, ret,
+	    freq = state->fe.dtv_property_cache.frequency,
+	    tsid = state->fe.dtv_property_cache.stream_id,
+	    ch = (freq < 1024) ? freq : tc90522_s_get_channel(freq);	/* consider as channel ID if low */
+
+	if (re_tune)
+		state->tune_state = PT3_TUNE_SET_FREQUENCY;
+
+	switch (state->tune_state) {
+	case PT3_TUNE_IDLE:
+		*delay = 3 * HZ;
+		*status = 0;
+		return 0;
+
+	case PT3_TUNE_SET_FREQUENCY:
+	case PT3_TUNE_CHECK_FREQUENCY:
+		pr_debug("#%d freq %d tsid 0x%x ch %d\n", adap->idx, freq, tsid, ch);
+		ret = qm1d1c0042_set_freq(adap->qm, ch);
+		if (ret)
+			return ret;
+		adap->channel = ch;
+		state->tune_state = PT3_TUNE_SET_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3_TUNE_SET_MODULATION:
+		for (i = 0; i < 1000; i++) {
+			ret = tc90522_read_tmcc_s(adap, tmcc);
+			if (!ret)
+				break;
+			msleep_interruptible(1);
+		}
+		if (ret) {
+			pr_debug("fail tc_read_tmcc_s ret=0x%x\n", ret);
+			state->tune_state = PT3_TUNE_ABORT;
+			*delay = HZ;
+			return ret;
+		}
+		pr_debug("slots=%d,%d,%d,%d mode=%d,%d,%d,%d\n",
+				tmcc->slot[0], tmcc->slot[1], tmcc->slot[2], tmcc->slot[3],
+				tmcc->mode[0], tmcc->mode[1], tmcc->mode[2], tmcc->mode[3]);
+		state->tune_state = PT3_TUNE_CHECK_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3_TUNE_CHECK_MODULATION:
+		pr_debug("tmcc->id=0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x\n",
+				tmcc->id[0], tmcc->id[1], tmcc->id[2], tmcc->id[3],
+				tmcc->id[4], tmcc->id[5], tmcc->id[6], tmcc->id[7]);
+		for (i = 0; i < sizeof(tmcc->id)/sizeof(tmcc->id[0]); i++) {
+			pr_debug("tsid %x i %d tmcc->id %x\n", tsid, i, tmcc->id[i]);
+			if (tmcc->id[i] == tsid)
+				break;
+		}
+		if (tsid < sizeof(tmcc->id)/sizeof(tmcc->id[0]))	/* consider as slot# */
+			i = tsid;
+		if (i == sizeof(tmcc->id)/sizeof(tmcc->id[0])) {
+			pr_debug("#%d i%d tsid 0x%x not found\n", adap->idx, i, tsid);
+			return -EINVAL;
+		}
+		adap->offset = i;
+		pr_debug("#%d found tsid 0x%x on slot %d\n", adap->idx, tsid, i);
+		state->tune_state = PT3_TUNE_SET_TS_ID;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER;
+		return 0;
+
+	case PT3_TUNE_SET_TS_ID:
+		ret = tc90522_write_id_s(adap, (u16)tmcc->id[adap->offset]);
+		if (ret) {
+			pr_debug("fail set_tmcc_s ret=%d\n", ret);
+			return ret;
+		}
+		state->tune_state = PT3_TUNE_CHECK_TS_ID;
+		return 0;
+
+	case PT3_TUNE_CHECK_TS_ID:
+		for (i = 0; i < 1000; i++) {
+			u16 short_id;
+			ret = tc90522_read_id_s(adap, &short_id);
+			if (ret) {
+				pr_debug("fail get_id_s ret=%d\n", ret);
+				return ret;
+			}
+			tsid = short_id;
+			pr_debug("#%d tsid=0x%x\n", adap->idx, tsid);
+			if ((tsid & 0xffff) == tmcc->id[adap->offset])
+				break;
+			msleep_interruptible(1);
+		}
+		state->tune_state = PT3_TUNE_TRACK;
+
+	case PT3_TUNE_TRACK:
+		*delay = 3 * HZ;
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+
+	case PT3_TUNE_ABORT:
+		*delay = 3 * HZ;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+	}
+	BUG();
+}
+
+static struct dvb_frontend_ops tc90522_s_ops = {
+	.delsys = { SYS_ISDBS },
+	.info = {
+		.name = "PT3 ISDB-S",
+		.frequency_min = 1,
+		.frequency_max = 2150000,
+		.frequency_stepsize = 1000,
+		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO | FE_CAN_MULTISTREAM |
+			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
+	},
+	.init = tc90522_fe_init,
+	.sleep = tc90522_sleep,
+	.release = tc90522_release,
+	.get_frontend_algo = tc90522_get_frontend_algo,
+	.read_signal_strength = tc90522_read_signal_strength,
+	.read_status = tc90522_s_read_status,
+	.tune = tc90522_s_tune,
+};
+
+/**** ISDB-T ****/
+static int tc90522_t_get_tmcc(struct pt3_adapter *adap)
+{
+	bool b = false, retryov, fulock;
+
+	while (1) {
+		tc90522_read_retryov_fulock(adap, &retryov, &fulock);
+		if (!fulock) {
+			b = true;
+			break;
+		} else {
+			if (retryov)
+				break;
+		}
+		msleep_interruptible(1);
+	}
+	return b ? 0 : -EBADMSG;
+}
+
+static int tc90522_t_read_status(struct dvb_frontend *fe, fe_status_t *status)
+{
+	struct tc90522_state *state = fe->demodulator_priv;
+
+	switch (state->tune_state) {
+	case PT3_TUNE_IDLE:
+	case PT3_TUNE_SET_FREQUENCY:
+	case PT3_TUNE_CHECK_FREQUENCY:
+		*status = 0;
+		return 0;
+
+	case PT3_TUNE_SET_MODULATION:
+	case PT3_TUNE_CHECK_MODULATION:
+	case PT3_TUNE_SET_TS_ID:
+	case PT3_TUNE_CHECK_TS_ID:
+	case PT3_TUNE_ABORT:
+		*status |= FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3_TUNE_TRACK:
+		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+	}
+	BUG();
+}
+
+static int tc90522_t_tune(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
+{
+	struct tc90522_state *state = fe->demodulator_priv;
+	int ret, i;
+
+	if (re_tune)
+		state->tune_state = PT3_TUNE_SET_FREQUENCY;
+
+	switch (state->tune_state) {
+	case PT3_TUNE_IDLE:
+		*delay = 3 * HZ;
+		*status = 0;
+		return 0;
+
+	case PT3_TUNE_SET_FREQUENCY:
+		ret = tc90522_set_agc(state->adap, TC90522_AGC_MANUAL);
+		if (ret)
+			return ret;
+		mxl301rf_tuner_rftune(state->adap, mxl301rf_freq(state->fe.dtv_property_cache.frequency));
+		state->tune_state = PT3_TUNE_CHECK_FREQUENCY;
+		*delay = 0;
+		*status = 0;
+		return 0;
+
+	case PT3_TUNE_CHECK_FREQUENCY:
+		if (!mxl301rf_locked(state->adap)) {
+			*delay = HZ;
+			*status = 0;
+			return 0;
+		}
+		state->tune_state = PT3_TUNE_SET_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3_TUNE_SET_MODULATION:
+		ret = tc90522_set_agc(state->adap, TC90522_AGC_AUTO);
+		if (ret)
+			return ret;
+		state->tune_state = PT3_TUNE_CHECK_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3_TUNE_CHECK_MODULATION:
+	case PT3_TUNE_SET_TS_ID:
+	case PT3_TUNE_CHECK_TS_ID:
+		for (i = 0; i < 1000; i++) {
+			ret = tc90522_t_get_tmcc(state->adap);
+			if (!ret)
+				break;
+			msleep_interruptible(2);
+		}
+		if (ret) {
+			pr_debug("#%d fail get_tmcc_t ret=%d\n", state->adap->idx, ret);
+				state->tune_state = PT3_TUNE_ABORT;
+				*delay = HZ;
+				return 0;
+		}
+		state->tune_state = PT3_TUNE_TRACK;
+
+	case PT3_TUNE_TRACK:
+		*delay = 3 * HZ;
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+
+	case PT3_TUNE_ABORT:
+		*delay = 3 * HZ;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+	}
+	BUG();
+}
+
+static struct dvb_frontend_ops tc90522_t_ops = {
+	.delsys = { SYS_ISDBT },
+	.info = {
+		.name = "PT3 ISDB-T",
+		.frequency_min = 1,
+		.frequency_max = 770000000,
+		.frequency_stepsize = 142857,
+		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO |
+			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
+	},
+	.init = tc90522_fe_init,
+	.sleep = tc90522_sleep,
+	.release = tc90522_release,
+	.get_frontend_algo = tc90522_get_frontend_algo,
+	.read_signal_strength = tc90522_read_signal_strength,
+	.read_status = tc90522_t_read_status,
+	.tune = tc90522_t_tune,
+};
+
+/**** Common ****/
+struct dvb_frontend *tc90522_attach(struct pt3_adapter *adap)
+{
+	struct dvb_frontend *fe;
+	struct tc90522_state *state = kzalloc(sizeof(struct tc90522_state), GFP_KERNEL);
+
+	if (!state)
+		return NULL;
+	state->adap = adap;
+	fe = &state->fe;
+	memcpy(&fe->ops, (adap->type == SYS_ISDBS) ? &tc90522_s_ops : &tc90522_t_ops, sizeof(struct dvb_frontend_ops));
+	fe->demodulator_priv = state;
+	return fe;
+}
+
+EXPORT_SYMBOL(tc90522_attach);
+EXPORT_SYMBOL(tc90522_init);
+EXPORT_SYMBOL(tc90522_set_powers);
+
diff --git a/drivers/media/dvb-frontends/tc90522.h b/drivers/media/dvb-frontends/tc90522.h
new file mode 100644
index 0000000..6a8b618
--- /dev/null
+++ b/drivers/media/dvb-frontends/tc90522.h
@@ -0,0 +1,48 @@
+/*
+ * Earthsoft PT3 demodulator frontend Toshiba TC90522XBG OFDM(ISDB-T)/8PSK(ISDB-S)
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__TC90522_H__
+#define	__TC90522_H__
+
+/* Transmission and Multiplexing Configuration Control */
+
+struct tmcc_s {
+	u32 mode[4];
+	u32 slot[4];
+	u32 id[8];
+};
+
+enum tc90522_agc {
+	TC90522_AGC_AUTO,
+	TC90522_AGC_MANUAL,
+};
+
+u32 tc90522_index(struct pt3_adapter *adap);
+int tc90522_init(struct pt3_adapter *adap);
+int tc90522_read_tuner(struct pt3_adapter *adap, u8 addr, u8 *data);
+int tc90522_read_tuner_without_addr(struct pt3_adapter *adap, u8 *data);
+int tc90522_set_agc(struct pt3_adapter *adap, enum tc90522_agc agc);
+int tc90522_set_powers(struct pt3_adapter *adap, bool tuner, bool amp);
+int tc90522_set_sleep_s(struct pt3_adapter *adap, bool sleep);
+u32 tc90522_time_diff(struct timeval *st, struct timeval *et);
+int tc90522_write_sleep_time(struct pt3_adapter *adap, int sleep);
+int tc90522_write_tuner(struct pt3_adapter *adap, u8 addr, const u8 *data, u32 size);
+int tc90522_write_tuner_without_addr(struct pt3_adapter *adap, const u8 *data, u32 size);
+
+struct dvb_frontend *tc90522_attach(struct pt3_adapter *adap);
+
+#endif
+
diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
index 53196f1..87018c8 100644
--- a/drivers/media/pci/Kconfig
+++ b/drivers/media/pci/Kconfig
@@ -30,7 +30,6 @@ source "drivers/media/pci/cx88/Kconfig"
 source "drivers/media/pci/bt8xx/Kconfig"
 source "drivers/media/pci/saa7134/Kconfig"
 source "drivers/media/pci/saa7164/Kconfig"
-
 endif
 
 if MEDIA_DIGITAL_TV_SUPPORT
@@ -40,6 +39,7 @@ source "drivers/media/pci/b2c2/Kconfig"
 source "drivers/media/pci/pluto2/Kconfig"
 source "drivers/media/pci/dm1105/Kconfig"
 source "drivers/media/pci/pt1/Kconfig"
+source "drivers/media/pci/pt3/Kconfig"
 source "drivers/media/pci/mantis/Kconfig"
 source "drivers/media/pci/ngene/Kconfig"
 source "drivers/media/pci/ddbridge/Kconfig"
diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
index 35cc578..f7be6bc 100644
--- a/drivers/media/pci/Makefile
+++ b/drivers/media/pci/Makefile
@@ -7,6 +7,7 @@ obj-y        +=	ttpci/		\
 		pluto2/		\
 		dm1105/		\
 		pt1/		\
+		pt3/		\
 		mantis/		\
 		ngene/		\
 		ddbridge/	\
diff --git a/drivers/media/pci/pt3/Kconfig b/drivers/media/pci/pt3/Kconfig
new file mode 100644
index 0000000..519fdaa
--- /dev/null
+++ b/drivers/media/pci/pt3/Kconfig
@@ -0,0 +1,10 @@
+config PT3_DVB
+	tristate "Earthsoft PT3 ISDB-S/T cards"
+	depends on DVB_CORE && PCI
+	select DVB_PT3_FE if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_PT3 if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Support for Earthsoft PT3 PCI-Express cards.
+	  You may also need to enable PT3 DVB frontend & tuner.
+	  Say Y or M if you own such a device and want to use it.
+
diff --git a/drivers/media/pci/pt3/Makefile b/drivers/media/pci/pt3/Makefile
new file mode 100644
index 0000000..b030927
--- /dev/null
+++ b/drivers/media/pci/pt3/Makefile
@@ -0,0 +1,6 @@
+pt3_dvb-objs := pt3.o pt3_dma.o pt3_i2c.o
+
+obj-$(CONFIG_PT3_DVB) += pt3_dvb.o
+
+ccflags-y += -Idrivers/media/dvb-core -Idrivers/media/dvb-frontends -Idrivers/media/tuners
+
diff --git a/drivers/media/pci/pt3/pt3.c b/drivers/media/pci/pt3/pt3.c
new file mode 100644
index 0000000..9c186cb
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3.c
@@ -0,0 +1,543 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card - Altera FPGA EP4CGX15BF14C8N
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_common.h"
+#include "pt3_dma.h"
+#include "pt3_i2c.h"
+#include "tc90522.h"
+#include "qm1d1c0042.h"
+#include "mxl301rf.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 DVB Driver");
+MODULE_LICENSE("GPL");
+
+static DEFINE_PCI_DEVICE_TABLE(pt3_id_table) = {
+	{ PCI_DEVICE(0x1172, 0x4c15) },
+	{ },
+};
+MODULE_DEVICE_TABLE(pci, pt3_id_table);
+
+static int lnb = 2;	/* used if not set by frontend or the value is invalid */
+module_param(lnb, int, 0);
+MODULE_PARM_DESC(lnb, "LNB level (0:OFF 1:+11V 2:+15V)");
+
+struct {
+	u32 bits;
+	char *str;
+} pt3_lnb[] = {
+	{0b1100,  "0V"},
+	{0b1101, "11V"},
+	{0b1111, "15V"},
+};
+
+static int pt3_update_lnb(struct pt3_board *pt3)
+{
+	u8 i, lnb_eff = 0;
+
+	if (pt3->reset) {
+		writel(pt3_lnb[0].bits, pt3->bar_reg + REG_SYSTEM_W);
+		pt3->reset = false;
+		pt3->lnb = 0;
+	} else {
+		struct pt3_adapter *adap;
+		mutex_lock(&pt3->lock);
+		for (i = 0; i < PT3_NR_ADAPS; i++) {
+			adap = pt3->adap[i];
+			pr_debug("#%d in_use %d sleep %d\n", adap->idx, adap->in_use, adap->sleep);
+			if ((adap->type == SYS_ISDBS) && (!adap->sleep)) {
+				lnb_eff |= adap->voltage == SEC_VOLTAGE_13 ? 1
+					:  adap->voltage == SEC_VOLTAGE_18 ? 2
+					:  lnb;
+			}
+		}
+		mutex_unlock(&pt3->lock);
+		if (unlikely(lnb_eff < 0 || 2 < lnb_eff)) {
+			pr_err("Inconsistent LNB settings\n");
+			return -EINVAL;
+		}
+		if (pt3->lnb != lnb_eff) {
+			writel(pt3_lnb[lnb_eff].bits, pt3->bar_reg + REG_SYSTEM_W);
+			pt3->lnb = lnb_eff;
+		}
+	}
+	pr_debug("LNB=%s\n", pt3_lnb[lnb_eff].str);
+	return 0;
+}
+
+void pt3_filter(struct pt3_adapter *adap, struct dvb_demux *demux, u8 *buf, size_t count)
+{
+	dvb_dmx_swfilter(demux, buf, count);
+}
+
+int pt3_thread(void *data)
+{
+	size_t ret;
+	struct pt3_adapter *adap = data;
+
+	set_freezable();
+	while (!kthread_should_stop()) {
+		try_to_freeze();
+		while ((ret = pt3_dma_copy(adap->dma, &adap->demux)) > 0)
+			;
+		if (ret < 0) {
+			pr_debug("#%d fail dma_copy\n", adap->idx);
+			msleep_interruptible(1);
+		}
+	}
+	return 0;
+}
+
+static int pt3_start_polling(struct pt3_adapter *adap)
+{
+	int ret = 0;
+
+	mutex_lock(&adap->lock);
+	if (!adap->kthread) {
+		adap->kthread = kthread_run(pt3_thread, adap, DRV_NAME "_%d", adap->idx);
+		if (IS_ERR(adap->kthread)) {
+			ret = PTR_ERR(adap->kthread);
+			adap->kthread = NULL;
+		} else {
+			pt3_dma_set_test_mode(adap->dma, RESET, 0);	/* reset_error_count */
+			pt3_dma_set_enabled(adap->dma, true);
+		}
+	}
+	mutex_unlock(&adap->lock);
+	return ret;
+}
+
+static void pt3_stop_polling(struct pt3_adapter *adap)
+{
+	mutex_lock(&adap->lock);
+	if (adap->kthread) {
+		pt3_dma_set_enabled(adap->dma, false);
+		pr_debug("#%d DMA ts_err packet cnt %d\n",
+			adap->idx, pt3_dma_get_ts_error_packet_count(adap->dma));
+		kthread_stop(adap->kthread);
+		adap->kthread = NULL;
+	}
+	mutex_unlock(&adap->lock);
+}
+
+static int pt3_start_feed(struct dvb_demux_feed *feed)
+{
+	int ret;
+	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
+	if (!adap->users++) {
+		if (adap->in_use) {
+			pr_err("#%d device is already used\n", adap->idx);
+			return -EIO;
+		}
+		pr_debug("#%d %s selected, DMA %s\n",
+			adap->idx, adap->str, pt3_dma_get_status(adap->dma) & 1 ? "ON" : "OFF");
+		adap->in_use = true;
+		ret = pt3_start_polling(adap);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int pt3_stop_feed(struct dvb_demux_feed *feed)
+{
+	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
+	if (!--adap->users) {
+		pt3_stop_polling(adap);
+		adap->in_use = false;
+		msleep_interruptible(40);
+	}
+	return 0;
+}
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static struct pt3_adapter *pt3_alloc_adapter(struct pt3_board *pt3)
+{
+	int ret;
+	struct dvb_adapter *dvb;
+	struct dvb_demux *demux;
+	struct dmxdev *dmxdev;
+	struct pt3_adapter *adap = kzalloc(sizeof(struct pt3_adapter), GFP_KERNEL);
+
+	if (!adap) {
+		ret = -ENOMEM;
+		goto err;
+	}
+	adap->pt3 = pt3;
+	adap->voltage = SEC_VOLTAGE_OFF;
+	adap->sleep = true;
+
+	dvb = &adap->dvb;
+	dvb->priv = adap;
+	ret = dvb_register_adapter(dvb, DRV_NAME, THIS_MODULE, &pt3->pdev->dev, adapter_nr);
+	if (ret < 0)
+		goto err_kfree;
+
+	demux = &adap->demux;
+	demux->dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING;
+	demux->priv = adap;
+	demux->feednum = 256;
+	demux->filternum = 256;
+	demux->start_feed = pt3_start_feed;
+	demux->stop_feed = pt3_stop_feed;
+	demux->write_to_decoder = NULL;
+	ret = dvb_dmx_init(demux);
+	if (ret < 0)
+		goto err_unregister_adapter;
+
+	dmxdev = &adap->dmxdev;
+	dmxdev->filternum = 256;
+	dmxdev->demux = &demux->dmx;
+	dmxdev->capabilities = 0;
+	ret = dvb_dmxdev_init(dmxdev, dvb);
+	if (ret < 0)
+		goto err_dmx_release;
+
+	return adap;
+
+err_dmx_release:
+	dvb_dmx_release(demux);
+err_unregister_adapter:
+	dvb_unregister_adapter(dvb);
+err_kfree:
+	kfree(adap);
+err:
+	return ERR_PTR(ret);
+}
+
+int pt3_tuner_set_sleep(struct pt3_adapter *adap, bool sleep)
+{
+	int ret;
+	pr_debug("#%d %p %s %s\n", adap->idx, adap, adap->str, sleep ? "Sleep" : "Wakeup");
+
+	if (adap->type == SYS_ISDBS)
+		ret = qm1d1c0042_set_sleep(adap->qm, sleep);
+	else
+		ret = mxl301rf_set_sleep(adap, sleep);
+	msleep_interruptible(10);
+	return ret;
+}
+
+static void pt3_cleanup_adapter(struct pt3_adapter *adap)
+{
+	if (!adap)
+		return;
+	if (adap->kthread)
+		kthread_stop(adap->kthread);
+	if (adap->fe)
+		dvb_unregister_frontend(adap->fe);
+	if (!adap->sleep)
+		pt3_tuner_set_sleep(adap, true);
+	if (adap->qm)
+		vfree(adap->qm);
+	if (adap->dma) {
+		if (adap->dma->enabled)
+			pt3_dma_set_enabled(adap->dma, false);
+		pt3_dma_free(adap->dma);
+	}
+	adap->demux.dmx.close(&adap->demux.dmx);
+	dvb_dmxdev_release(&adap->dmxdev);
+	dvb_dmx_release(&adap->demux);
+	dvb_unregister_adapter(&adap->dvb);
+	kfree(adap);
+}
+
+static int pt3_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	adap->voltage = voltage;
+	return (adap->orig_voltage) ? adap->orig_voltage(fe, voltage) : 0;
+}
+
+static int pt3_sleep(struct dvb_frontend *fe)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	adap->sleep = true;
+	pt3_update_lnb(adap->pt3);
+	return (adap->orig_sleep) ? adap->orig_sleep(fe) : 0;
+}
+
+static int pt3_wakeup(struct dvb_frontend *fe)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	adap->sleep = false;
+	pt3_update_lnb(adap->pt3);
+	return (adap->orig_init) ? adap->orig_init(fe) : 0;
+}
+
+static int pt3_init_frontends(struct pt3_board *pt3)
+{
+	struct dvb_frontend *fe[PT3_NR_ADAPS];
+	int i, ret;
+
+	for (i = 0; i < PT3_NR_ADAPS; i++) {
+		fe[i] = tc90522_attach(pt3->adap[i]);
+		if (!fe[i]) {
+			while (i--)
+				fe[i]->ops.release(fe[i]);
+			return -ENOMEM;
+		}
+	}
+	for (i = 0; i < PT3_NR_ADAPS; i++) {
+		struct pt3_adapter *adap = pt3->adap[i];
+
+		adap->orig_voltage     = fe[i]->ops.set_voltage;
+		adap->orig_sleep       = fe[i]->ops.sleep;
+		adap->orig_init        = fe[i]->ops.init;
+		fe[i]->ops.set_voltage = pt3_set_voltage;
+		fe[i]->ops.sleep       = pt3_sleep;
+		fe[i]->ops.init        = pt3_wakeup;
+
+		ret = dvb_register_frontend(&adap->dvb, fe[i]);
+		if (ret >= 0)
+			adap->fe = fe[i];
+		else {
+			while (i--)
+				dvb_unregister_frontend(fe[i]);
+			for (i = 0; i < PT3_NR_ADAPS; i++)
+				fe[i]->ops.release(fe[i]);
+			return ret;
+		}
+	}
+	return 0;
+}
+
+static void pt3_remove(struct pci_dev *pdev)
+{
+	int i;
+	struct pt3_board *pt3 = pci_get_drvdata(pdev);
+
+	if (pt3) {
+		pt3->reset = true;
+		pt3_update_lnb(pt3);
+		if (pt3->adap && pt3->adap[PT3_NR_ADAPS-1])
+			tc90522_set_powers(pt3->adap[PT3_NR_ADAPS-1], false, false);
+		for (i = 0; i < PT3_NR_ADAPS; i++)
+			pt3_cleanup_adapter(pt3->adap[i]);
+		pt3_i2c_reset(pt3);
+		i2c_del_adapter(&pt3->i2c);
+		if (pt3->bar_mem)
+			iounmap(pt3->bar_mem);
+		if (pt3->bar_reg)
+			iounmap(pt3->bar_reg);
+		pci_release_selected_regions(pdev, pt3->bars);
+		kfree(pt3);
+	}
+	pci_disable_device(pdev);
+}
+
+static int pt3_abort(struct pci_dev *pdev, int ret, char *fmt, ...)
+{
+	va_list ap;
+	char *s = NULL;
+	int slen;
+
+	va_start(ap, fmt);
+	slen = vsnprintf(s, 0, fmt, ap);
+	s = vzalloc(slen);
+	if (slen > 0 && s) {
+		vsnprintf(s, slen, fmt, ap);
+		dev_alert(&pdev->dev, "%s", s);
+		vfree(s);
+	}
+	va_end(ap);
+	pt3_remove(pdev);
+	return ret;
+}
+
+struct {
+	fe_delivery_system_t type;
+	u8 addr_tuner, addr_demod;
+	int init_ch;
+	char *str;
+} pt3_config[] = {
+	{SYS_ISDBS, 0x63, 0b00010001,  0, "ISDB-S"},
+	{SYS_ISDBS, 0x60, 0b00010011,  0, "ISDB-S"},
+	{SYS_ISDBT, 0x62, 0b00010000, 70, "ISDB-T"},
+	{SYS_ISDBT, 0x61, 0b00010010, 71, "ISDB-T"},
+};
+
+int pt3_tuner_init(struct pt3_adapter *adap)
+{
+	qm1d1c0042_tuner_init(adap->qm);
+	adap->pt3->i2c_addr = 0;
+	if (pt3_i2c_flush(adap->pt3, true, 0))
+		return -EIO;
+
+	if (qm1d1c0042_tuner_init(adap->qm))
+		return -EIO;
+	adap->pt3->i2c_addr = 0;
+	if (pt3_i2c_flush(adap->pt3, true, 0))
+		return -EIO;
+	return 0;
+}
+
+static int pt3_tuner_init_all(struct pt3_board *pt3)
+{
+	int ret, i, j;
+
+	if (!pt3_i2c_is_clean(pt3)) {
+		pr_debug("cleanup I2C\n");
+		ret = pt3_i2c_flush(pt3, false, 0);
+		if (ret)
+			goto last;
+		msleep_interruptible(10);
+	}
+	for (i = 0; i < PT3_NR_ADAPS; i++) {
+		ret = tc90522_init(pt3->adap[i]);
+		pr_debug("#%d tc_init ret=%d\n", i, ret);
+	}
+	ret = tc90522_set_powers(pt3->adap[PT3_NR_ADAPS-1], true, false);
+	if (ret) {
+		pr_debug("fail set powers.\n");
+		goto last;
+	}
+	msleep_interruptible(1);
+
+	for (i = 0; i < PT3_NR_ADAPS; i++)
+		if (pt3->adap[i]->type == SYS_ISDBS) {
+			for (j = 0; j < 10; j++) {
+				if (j)
+					pr_debug("retry pt3_tuner_init\n");
+				ret = pt3_tuner_init(pt3->adap[i]);
+				if (!ret)
+					break;
+				msleep_interruptible(1);
+			}
+			if (ret) {
+				pr_debug("#%d fail pt3_tuner_init ret=0x%x\n", i, ret);
+				goto last;
+			}
+		}
+	ret = pt3_i2c_flush(pt3, false, PT3_I2C_START_ADDR);
+	if (ret)
+		goto last;
+	ret = tc90522_set_powers(pt3->adap[PT3_NR_ADAPS-1], true, true);
+	if (ret) {
+		pr_debug("fail tc_set_powers,\n");
+		goto last;
+	}
+	for (i = 0; i < PT3_NR_ADAPS; i++) {
+		struct pt3_adapter *adap = pt3->adap[i];
+		ret = pt3_tuner_set_sleep(adap, false);
+		if (ret)
+			goto last;
+		ret = (adap->type == SYS_ISDBS) ?
+			qm1d1c0042_set_freq(adap->qm, adap->init_ch) :
+			mxl301rf_set_freq(adap, adap->init_ch, 0);
+		if (ret)
+			pr_debug("fail set_frequency, ret=%d\n", ret);
+		ret = pt3_tuner_set_sleep(adap, true);
+		if (ret)
+			goto last;
+	}
+last:
+	return ret;
+}
+
+static int pt3_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+	struct pt3_board *pt3;
+	struct pt3_adapter *adap;
+	int i, ret, bars = pci_select_bars(pdev, IORESOURCE_MEM);
+
+	ret = pci_enable_device(pdev);
+	if (ret < 0)
+		return pt3_abort(pdev, ret, "PCI device unusable\n");
+	ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(64));
+	if (ret)
+		return pt3_abort(pdev, ret, "DMA mask error\n");
+	pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64));
+
+	pci_read_config_dword(pdev, PCI_CLASS_REVISION, &i);
+	if ((i & 0xFF) != 1)
+		return pt3_abort(pdev, ret, "Revision 0x%x is not supported\n", i & 0xFF);
+	ret = pci_request_selected_regions(pdev, bars, DRV_NAME);
+	if (ret < 0)
+		return pt3_abort(pdev, ret, "Could not request regions\n");
+
+	pci_set_master(pdev);
+	ret = pci_save_state(pdev);
+	if (ret)
+		return pt3_abort(pdev, ret, "Failed pci_save_state\n");
+	pt3 = kzalloc(sizeof(struct pt3_board), GFP_KERNEL);
+	if (!pt3)
+		return pt3_abort(pdev, -ENOMEM, "struct pt3_board out of memory\n");
+
+	pt3->bars = bars;
+	pt3->pdev = pdev;
+	pci_set_drvdata(pdev, pt3);
+	pt3->bar_reg = pci_ioremap_bar(pdev, 0);
+	pt3->bar_mem = pci_ioremap_bar(pdev, 2);
+	if (!pt3->bar_reg || !pt3->bar_mem)
+		return pt3_abort(pdev, -EIO, "Failed pci_ioremap_bar\n");
+
+	ret = readl(pt3->bar_reg + REG_VERSION);
+	i = ((ret >> 24) & 0xFF);
+	if (i != 3)
+		return pt3_abort(pdev, -EIO, "ID=0x%x, not a PT3\n", i);
+	i = ((ret >>  8) & 0xFF);
+	if (i != 4)
+		return pt3_abort(pdev, -EIO, "FPGA version 0x%x is not supported\n", i);
+	mutex_init(&pt3->lock);
+
+	for (i = 0; i < PT3_NR_ADAPS; i++) {
+		pt3->adap[i] = NULL;
+		adap = pt3_alloc_adapter(pt3);
+		if (IS_ERR(adap))
+			return pt3_abort(pdev, PTR_ERR(adap), "Failed pt3_alloc_adapter\n");
+		adap->idx = i;
+		adap->dma = pt3_dma_create(adap);
+		if (!adap->dma)
+			return pt3_abort(pdev, -ENOMEM, "Failed pt3_dma_create\n");
+		mutex_init(&adap->lock);
+		pt3->adap[i] = adap;
+		adap->type       = pt3_config[i].type;
+		adap->addr_tuner = pt3_config[i].addr_tuner;
+		adap->addr_demod = pt3_config[i].addr_demod;
+		adap->init_ch    = pt3_config[i].init_ch;
+		adap->str        = pt3_config[i].str;
+		if (adap->type == SYS_ISDBS) {
+			adap->qm = vzalloc(sizeof(struct qm1d1c0042));
+			if (!adap->qm)
+				return pt3_abort(pdev, -ENOMEM, "QM out of memory\n");
+			adap->qm->adap = adap;
+		}
+		adap->sleep = true;
+	}
+	pt3->reset = true;
+	pt3_update_lnb(pt3);
+
+	ret = pt3_i2c_add_adapter(pt3);
+	if (ret < 0)
+		return pt3_abort(pdev, ret, "Cannot add I2C\n");
+
+	if (pt3_tuner_init_all(pt3))
+		return pt3_abort(pdev, ret, "Failed pt3_tuner_init_all\n");
+	ret = pt3_init_frontends(pt3);
+	return (ret >= 0) ? ret : pt3_abort(pdev, ret, "Failed pt3_init_frontends\n");
+}
+
+static struct pci_driver pt3_driver = {
+	.name		= DRV_NAME,
+	.probe		= pt3_probe,
+	.remove		= pt3_remove,
+	.id_table	= pt3_id_table,
+};
+
+module_pci_driver(pt3_driver);
+
diff --git a/drivers/media/pci/pt3/pt3.h b/drivers/media/pci/pt3/pt3.h
new file mode 100644
index 0000000..057d1b4
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3.h
@@ -0,0 +1,23 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_H__
+#define	__PT3_H__
+
+void pt3_filter(struct pt3_adapter *adap, struct dvb_demux *demux, u8 *buf, size_t count);
+
+#endif
+
diff --git a/drivers/media/pci/pt3/pt3_dma.c b/drivers/media/pci/pt3/pt3_dma.c
new file mode 100644
index 0000000..e7448d6
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_dma.c
@@ -0,0 +1,335 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_common.h"
+#include "pt3_dma.h"
+#include "pt3.h"
+
+#define PT3_DMA_MAX_DESCS	204
+#define PT3_DMA_PAGE_SIZE	(PT3_DMA_MAX_DESCS * sizeof(struct pt3_dma_desc))
+#define PT3_DMA_BLOCK_COUNT	17
+#define PT3_DMA_BLOCK_SIZE	(PT3_DMA_PAGE_SIZE * 47)
+#define PT3_DMA_TS_BUF_SIZE	(PT3_DMA_BLOCK_SIZE * PT3_DMA_BLOCK_COUNT)
+#define PT3_DMA_TS_SYNC		0x47
+#define PT3_DMA_TS_NOT_SYNC	0x74
+
+void pt3_dma_free(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *page;
+	u32 i;
+
+	if (dma->ts_info) {
+		for (i = 0; i < dma->ts_count; i++) {
+			page = &dma->ts_info[i];
+			if (page->data)
+				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
+		}
+		kfree(dma->ts_info);
+	}
+	if (dma->desc_info) {
+		for (i = 0; i < dma->desc_count; i++) {
+			page = &dma->desc_info[i];
+			if (page->data)
+				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
+		}
+		kfree(dma->desc_info);
+	}
+	kfree(dma);
+}
+
+struct pt3_dma_desc {
+	u64 page_addr;
+	u32 page_size;
+	u64 next_desc;
+} __packed;
+
+void pt3_dma_build_page_descriptor(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *desc_info, *ts_info;
+	u64 ts_addr, desc_addr;
+	u32 i, j, ts_size, desc_remain, ts_info_pos, desc_info_pos;
+	struct pt3_dma_desc *prev, *curr;
+
+	pr_debug("#%d build page descriptor ts_count=%d ts_size=%d desc_count=%d desc_size=%d\n",
+		dma->adap->idx, dma->ts_count, dma->ts_info[0].size, dma->desc_count, dma->desc_info[0].size);
+	desc_info_pos = ts_info_pos = 0;
+	desc_info = &dma->desc_info[desc_info_pos];
+	desc_addr   = desc_info->addr;
+	desc_remain = desc_info->size;
+	desc_info->data_pos = 0;
+	prev = NULL;
+	curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+	desc_info_pos++;
+
+	for (i = 0; i < dma->ts_count; i++) {
+		if (unlikely(ts_info_pos >= dma->ts_count)) {
+			pr_debug("#%d ts_info overflow max=%d curr=%d\n", dma->adap->idx, dma->ts_count, ts_info_pos);
+			return;
+		}
+		ts_info = &dma->ts_info[ts_info_pos];
+		ts_addr = ts_info->addr;
+		ts_size = ts_info->size;
+		ts_info_pos++;
+		pr_debug("#%d i=%d, ts_info addr=0x%llx ts_size=%d\n", dma->adap->idx, i, ts_addr, ts_size);
+		for (j = 0; j < ts_size / PT3_DMA_PAGE_SIZE; j++) {
+			if (desc_remain < sizeof(struct pt3_dma_desc)) {
+				if (unlikely(desc_info_pos >= dma->desc_count)) {
+					pr_debug("#%d desc_info overflow max=%d curr=%d\n",
+						dma->adap->idx, dma->desc_count, desc_info_pos);
+					return;
+				}
+				desc_info = &dma->desc_info[desc_info_pos];
+				desc_info->data_pos = 0;
+				curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+				pr_debug("#%d desc_info_pos=%d ts_addr=0x%llx remain=%d\n",
+					dma->adap->idx, desc_info_pos, ts_addr, desc_remain);
+				desc_addr = desc_info->addr;
+				desc_remain = desc_info->size;
+				desc_info_pos++;
+			}
+			if (prev)
+				prev->next_desc = desc_addr | 0b10;
+			curr->page_addr = ts_addr           | 0b111;
+			curr->page_size = PT3_DMA_PAGE_SIZE | 0b111;
+			curr->next_desc = 0b10;
+			pr_debug("#%d j=%d dma write desc ts_addr=0x%llx desc_info_pos=%d desc_remain=%d\n",
+				dma->adap->idx, j, ts_addr, desc_info_pos, desc_remain);
+			ts_addr += PT3_DMA_PAGE_SIZE;
+
+			prev = curr;
+			desc_info->data_pos += sizeof(struct pt3_dma_desc);
+			if (unlikely(desc_info->data_pos > desc_info->size)) {
+				pr_debug("#%d dma desc_info data overflow max=%d curr=%d\n",
+					dma->adap->idx, desc_info->size, desc_info->data_pos);
+				return;
+			}
+			curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+			desc_addr += sizeof(struct pt3_dma_desc);
+			desc_remain -= sizeof(struct pt3_dma_desc);
+		}
+	}
+	if (prev)
+		prev->next_desc = dma->desc_info->addr | 0b10;
+}
+
+struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap)
+{
+	struct pt3_dma_page *page;
+	u32 i;
+
+	struct pt3_dma *dma = kzalloc(sizeof(struct pt3_dma), GFP_KERNEL);
+	if (!dma) {
+		pr_debug("#%d fail allocate PT3_DMA\n", adap->idx);
+		goto fail;
+	}
+	dma->adap = adap;
+	dma->enabled = false;
+	mutex_init(&dma->lock);
+
+	dma->ts_count = PT3_DMA_BLOCK_COUNT;
+	dma->ts_info = kzalloc(sizeof(struct pt3_dma_page) * dma->ts_count, GFP_KERNEL);
+	if (!dma->ts_info) {
+		pr_debug("#%d fail allocate TS DMA page\n", adap->idx);
+		goto fail;
+	}
+	pr_debug("#%d Alloc TS buf (ts_count %d)\n", adap->idx, dma->ts_count);
+	for (i = 0; i < dma->ts_count; i++) {
+		page = &dma->ts_info[i];
+		page->size = PT3_DMA_BLOCK_SIZE;
+		page->data_pos = 0;
+		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
+		if (!page->data) {
+			pr_debug("#%d fail alloc_consistent. %d\n", adap->idx, i);
+			goto fail;
+		}
+	}
+
+	dma->desc_count = 1 + (PT3_DMA_TS_BUF_SIZE / PT3_DMA_PAGE_SIZE - 1) / PT3_DMA_MAX_DESCS;
+	dma->desc_info = kzalloc(sizeof(struct pt3_dma_page) * dma->desc_count, GFP_KERNEL);
+	if (!dma->desc_info) {
+		pr_debug("#%d fail allocate Desc DMA page\n", adap->idx);
+		goto fail;
+	}
+	pr_debug("#%d Alloc Descriptor buf (desc_count %d)\n", adap->idx, dma->desc_count);
+	for (i = 0; i < dma->desc_count; i++) {
+		page = &dma->desc_info[i];
+		page->size = PT3_DMA_PAGE_SIZE;
+		page->data_pos = 0;
+		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
+		if (!page->data) {
+			pr_debug("#%d fail alloc_consistent %d\n", adap->idx, i);
+			goto fail;
+		}
+	}
+
+	pr_debug("#%d build page descriptor\n", adap->idx);
+	pt3_dma_build_page_descriptor(dma);
+	return dma;
+fail:
+	if (dma)
+		pt3_dma_free(dma);
+	return NULL;
+}
+
+void __iomem *pt3_dma_get_base_addr(struct pt3_dma *dma)
+{
+	return dma->adap->pt3->bar_reg + REG_BASE + (0x18 * dma->adap->idx);
+}
+
+void pt3_dma_reset(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *ts;
+	u32 i;
+
+	for (i = 0; i < dma->ts_count; i++) {
+		ts = &dma->ts_info[i];
+		memset(ts->data, 0, ts->size);
+		ts->data_pos = 0;
+		*ts->data = PT3_DMA_TS_NOT_SYNC;
+	}
+	dma->ts_pos = 0;
+}
+
+void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled)
+{
+	void __iomem *base = pt3_dma_get_base_addr(dma);
+	u64 start_addr = dma->desc_info->addr;
+
+	if (enabled) {
+		pr_debug("#%d DMA enable start_addr=%llx\n", dma->adap->idx, start_addr);
+		pt3_dma_reset(dma);
+		writel(1 << 1, base + REG_DMA_CTL);	/* stop DMA */
+		writel(start_addr         & 0xffffffff, base + REG_DMA_DESC_L);
+		writel((start_addr >> 32) & 0xffffffff, base + REG_DMA_DESC_H);
+		pr_debug("set descriptor address low %llx\n",  start_addr         & 0xffffffff);
+		pr_debug("set descriptor address high %llx\n", (start_addr >> 32) & 0xffffffff);
+		writel(1 << 0, base + REG_DMA_CTL);	/* start DMA */
+	} else {
+		pr_debug("#%d DMA disable\n", dma->adap->idx);
+		writel(1 << 1, base + REG_DMA_CTL);	/* stop DMA */
+		while (1) {
+			if (!(readl(base + REG_STATUS) & 1))
+				break;
+			msleep_interruptible(1);
+		}
+	}
+	dma->enabled = enabled;
+}
+
+/* convert Gray code to binary, e.g. 1001 -> 1110 */
+static u32 pt3_dma_gray2binary(u32 gray, u32 bit)
+{
+	u32 binary = 0, i, j, k;
+
+	for (i = 0; i < bit; i++) {
+		k = 0;
+		for (j = i; j < bit; j++)
+			k ^= (gray >> j) & 1;
+		binary |= k << i;
+	}
+	return binary;
+}
+
+u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma)
+{
+	return pt3_dma_gray2binary(readl(pt3_dma_get_base_addr(dma) + REG_TS_ERR), 32);
+}
+
+void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval)
+{
+	void __iomem *base = pt3_dma_get_base_addr(dma);
+	u32 data = mode | initval;
+	pr_debug("set_test_mode base=%p data=0x%04x\n", base, data);
+	writel(data, base + REG_TS_CTL);
+}
+
+bool pt3_dma_ready(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *ts;
+	u8 *p;
+
+	u32 next = dma->ts_pos + 1;
+	if (next >= dma->ts_count)
+		next = 0;
+	ts = &dma->ts_info[next];
+	p = &ts->data[ts->data_pos];
+
+	if (*p == PT3_DMA_TS_SYNC)
+		return true;
+	if (*p == PT3_DMA_TS_NOT_SYNC)
+		return false;
+
+	pr_debug("#%d invalid sync byte value=0x%02x ts_pos=%d data_pos=%d curr=0x%02x\n",
+		dma->adap->idx, *p, next, ts->data_pos, dma->ts_info[dma->ts_pos].data[0]);
+	return false;
+}
+
+ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux)
+{
+	bool ready;
+	struct pt3_dma_page *ts;
+	u32 i, prev;
+	size_t csize, remain = dma->ts_info[dma->ts_pos].size;
+
+	mutex_lock(&dma->lock);
+	pr_debug("#%d dma_copy ts_pos=0x%x data_pos=0x%x\n",
+		   dma->adap->idx, dma->ts_pos, dma->ts_info[dma->ts_pos].data_pos);
+	for (;;) {
+		for (i = 0; i < 20; i++) {
+			ready = pt3_dma_ready(dma);
+			if (ready)
+				break;
+			msleep_interruptible(30);
+		}
+		if (!ready) {
+			pr_debug("#%d dma_copy NOT READY\n", dma->adap->idx);
+			goto last;
+		}
+		prev = dma->ts_pos - 1;
+		if (prev < 0 || dma->ts_count <= prev)
+			prev = dma->ts_count - 1;
+		if (dma->ts_info[prev].data[0] != PT3_DMA_TS_NOT_SYNC)
+			pr_debug("#%d DMA buffer overflow. prev=%d data=0x%x\n",
+					dma->adap->idx, prev, dma->ts_info[prev].data[0]);
+		ts = &dma->ts_info[dma->ts_pos];
+		for (;;) {
+			csize = (remain < (ts->size - ts->data_pos)) ?
+				 remain : (ts->size - ts->data_pos);
+			pt3_filter(dma->adap, demux, &ts->data[ts->data_pos], csize);
+			remain -= csize;
+			ts->data_pos += csize;
+			if (ts->data_pos >= ts->size) {
+				ts->data_pos = 0;
+				ts->data[ts->data_pos] = PT3_DMA_TS_NOT_SYNC;
+				dma->ts_pos++;
+				if (dma->ts_pos >= dma->ts_count)
+					dma->ts_pos = 0;
+				break;
+			}
+			if (remain <= 0)
+				goto last;
+		}
+	}
+last:
+	mutex_unlock(&dma->lock);
+	return dma->ts_info[dma->ts_pos].size - remain;
+}
+
+u32 pt3_dma_get_status(struct pt3_dma *dma)
+{
+	return readl(pt3_dma_get_base_addr(dma) + REG_STATUS);
+}
+
diff --git a/drivers/media/pci/pt3/pt3_dma.h b/drivers/media/pci/pt3/pt3_dma.h
new file mode 100644
index 0000000..ecae4c1
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_dma.h
@@ -0,0 +1,48 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_DMA_H__
+#define	__PT3_DMA_H__
+
+struct pt3_dma_page {
+	dma_addr_t addr;
+	u8 *data;
+	u32 size, data_pos;
+};
+
+enum pt3_dma_mode {
+	USE_LFSR = 1 << 16,
+	REVERSE  = 1 << 17,
+	RESET    = 1 << 18,
+};
+
+struct pt3_dma {
+	struct pt3_adapter *adap;
+	bool enabled;
+	u32 ts_pos, ts_count, desc_count;
+	struct pt3_dma_page *ts_info, *desc_info;
+	struct mutex lock;
+};
+
+ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux);
+struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap);
+void pt3_dma_free(struct pt3_dma *dma);
+u32 pt3_dma_get_status(struct pt3_dma *dma);
+u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma);
+void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled);
+void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval);
+
+#endif
diff --git a/drivers/media/pci/pt3/pt3_i2c.c b/drivers/media/pci/pt3/pt3_i2c.c
new file mode 100644
index 0000000..3e28b8f
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_i2c.c
@@ -0,0 +1,183 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_i2c.h"
+
+enum pt3_i2c_cmd {
+	I_END,
+	I_ADDRESS,
+	I_CLOCK_L,
+	I_CLOCK_H,
+	I_DATA_L,
+	I_DATA_H,
+	I_RESET,
+	I_SLEEP,
+	I_DATA_L_NOP  = 0x08,
+	I_DATA_H_NOP  = 0x0c,
+	I_DATA_H_READ = 0x0d,
+	I_DATA_H_ACK0 = 0x0e,
+	I_DATA_H_ACK1 = 0x0f,
+};
+
+bool pt3_i2c_is_clean(struct pt3_board *pt3)
+{
+	return (readl(pt3->bar_reg + REG_I2C_R) >> 3) & 1;
+}
+
+void pt3_i2c_reset(struct pt3_board *pt3)
+{
+	writel(1 << 17, pt3->bar_reg + REG_I2C_W);	/* 0x00020000 */
+}
+
+void pt3_i2c_wait(struct pt3_board *pt3, u32 *status)
+{
+	u32 val;
+
+	while (1) {
+		val = readl(pt3->bar_reg + REG_I2C_R);
+		if (!(val & 1))				/* sequence stopped */
+			break;
+		msleep_interruptible(1);
+	}
+	if (status)
+		*status = val;				/* I2C register status */
+}
+
+void pt3_i2c_mem_write(struct pt3_board *pt3, u8 data)
+{
+	void __iomem *dst = pt3->bar_mem + PT3_I2C_DATA_OFFSET + pt3->i2c_addr;
+
+	if (pt3->i2c_filled) {
+		pt3->i2c_buf |= data << 4;
+		writeb(pt3->i2c_buf, dst);
+		pt3->i2c_addr++;
+	} else
+		pt3->i2c_buf = data;
+	pt3->i2c_filled ^= true;
+}
+
+void pt3_i2c_start(struct pt3_board *pt3)
+{
+	pt3_i2c_mem_write(pt3, I_DATA_H);
+	pt3_i2c_mem_write(pt3, I_CLOCK_H);
+	pt3_i2c_mem_write(pt3, I_DATA_L);
+	pt3_i2c_mem_write(pt3, I_CLOCK_L);
+}
+
+void pt3_i2c_cmd_write(struct pt3_board *pt3, const u8 *data, u32 size)
+{
+	u32 i, j;
+	u8 byte;
+
+	for (i = 0; i < size; i++) {
+		byte = data[i];
+		for (j = 0; j < 8; j++)
+			pt3_i2c_mem_write(pt3, (byte >> (7 - j)) & 1 ? I_DATA_H_NOP : I_DATA_L_NOP);
+		pt3_i2c_mem_write(pt3, I_DATA_H_ACK0);
+	}
+}
+
+void pt3_i2c_cmd_read(struct pt3_board *pt3, u8 *data, u32 size)
+{
+	u32 i, j;
+
+	for (i = 0; i < size; i++) {
+		for (j = 0; j < 8; j++)
+			pt3_i2c_mem_write(pt3, I_DATA_H_READ);
+		if (i == (size - 1))
+			pt3_i2c_mem_write(pt3, I_DATA_H_NOP);
+		else
+			pt3_i2c_mem_write(pt3, I_DATA_L_NOP);
+	}
+}
+
+void pt3_i2c_stop(struct pt3_board *pt3)
+{
+	pt3_i2c_mem_write(pt3, I_DATA_L);
+	pt3_i2c_mem_write(pt3, I_CLOCK_H);
+	pt3_i2c_mem_write(pt3, I_DATA_H);
+}
+
+int pt3_i2c_flush(struct pt3_board *pt3, bool end, u32 start_addr)
+{
+	u32 status;
+
+	if (end) {
+		pt3_i2c_mem_write(pt3, I_END);
+		if (pt3->i2c_filled)
+			pt3_i2c_mem_write(pt3, I_END);
+	}
+	pt3_i2c_wait(pt3, &status);
+	writel(1 << 16 | start_addr, pt3->bar_reg + REG_I2C_W);	/* 0x00010000 start sequence */
+	pt3_i2c_wait(pt3, &status);
+	if (status & 0b0110) {					/* ACK status */
+		pr_err("%s failed, status=0x%x\n", __func__, status);
+		return -EIO;
+	}
+	return 0;
+}
+
+u32 pt3_i2c_func(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C;
+}
+
+int pt3_i2c_xfer(struct i2c_adapter *i2c, struct i2c_msg *msg, int num)
+{
+	struct pt3_board *pt3 = i2c_get_adapdata(i2c);
+	int i, j;
+
+	if ((num < 1) || (num > 3) || !msg || msg[0].flags)	/* always write first */
+		return -ENOTSUPP;
+	mutex_lock(&pt3->lock);
+	pt3->i2c_addr = 0;
+	for (i = 0; i < num; i++) {
+		u8 byte = (msg[i].addr << 1) | (msg[i].flags & 1);
+		pt3_i2c_start(pt3);
+		pt3_i2c_cmd_write(pt3, &byte, 1);
+		if (msg[i].flags == I2C_M_RD)
+			pt3_i2c_cmd_read(pt3, msg[i].buf, msg[i].len);
+		else
+			pt3_i2c_cmd_write(pt3, msg[i].buf, msg[i].len);
+	}
+	pt3_i2c_stop(pt3);
+	if (pt3_i2c_flush(pt3, true, 0))
+		num = -EIO;
+	else
+		for (i = 1; i < num; i++)
+			if (msg[i].flags == I2C_M_RD)
+				for (j = 0; j < msg[i].len; j++)
+					msg[i].buf[j] = readb(pt3->bar_mem + PT3_I2C_DATA_OFFSET + j);
+	mutex_unlock(&pt3->lock);
+	return num;
+}
+
+static const struct i2c_algorithm pt3_i2c_algo = {
+	.functionality = pt3_i2c_func,
+	.master_xfer = pt3_i2c_xfer,
+};
+
+int pt3_i2c_add_adapter(struct pt3_board *pt3)
+{
+	struct i2c_adapter *i2c = &pt3->i2c;
+	i2c->algo = &pt3_i2c_algo;
+	i2c->algo_data = NULL;
+	i2c->dev.parent = &pt3->pdev->dev;
+	strcpy(i2c->name, DRV_NAME);
+	i2c_set_adapdata(i2c, pt3);
+	return i2c_add_adapter(i2c);
+}
+
diff --git a/drivers/media/pci/pt3/pt3_i2c.h b/drivers/media/pci/pt3/pt3_i2c.h
new file mode 100644
index 0000000..acf8053
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_i2c.h
@@ -0,0 +1,30 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_I2C_H__
+#define	__PT3_I2C_H__
+
+#include "pt3_common.h"
+
+#define PT3_I2C_DATA_OFFSET	2048
+#define PT3_I2C_START_ADDR	0x17fa
+
+int pt3_i2c_flush(struct pt3_board *pt3, bool end, u32 start_addr);
+bool pt3_i2c_is_clean(struct pt3_board *pt3);
+void pt3_i2c_reset(struct pt3_board *pt3);
+int pt3_i2c_add_adapter(struct pt3_board *pt3);
+
+#endif
-- 
1.8.4.5


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH] Full DVB driver package for Earthsoft PT3 (ISDB-S/T) cards
  2013-12-08  5:14 Guest
@ 2013-12-08 22:52 ` Antti Palosaari
  0 siblings, 0 replies; 21+ messages in thread
From: Antti Palosaari @ 2013-12-08 22:52 UTC (permalink / raw)
  To: Guest, linux-media
  Cc: Буди
	Романто,
	mkrufky

(V4L guys dropped from Cc)

Hello Guest

You didn't split that correctly.
Look for example tc90522 demodulator driver. It is in wrong directory. 
It does not implement frontend. Instead you have added frontend to pt3 
module and wrapped all functionality from tc90522 driver. You must 
implement standalone demodulator driver named tc90522, which implements 
frontend. There is maybe near 100 existing drivers to look example in 
drivers/media/dvb-frontends/ directory.

regards
Antti


On 08.12.2013 07:14, Guest wrote:
> From: Буди Романто <knightrider@are.ma>
>
> A DVB driver for Earthsoft PT3 (ISDB-S/T) receiver PCI Express cards, based on
> 1. PT3 chardev driver
> 	https://github.com/knight-rider/ptx/tree/master/pt3_drv
> 	https://github.com/m-tsudo/pt3
> 2. PT1/PT2 DVB driver
> 	drivers/media/pci/pt1
>
> It behaves similarly as PT1 DVB, plus some tuning enhancements:
> 1. in addition to the real frequency:
> 	ISDB-S : freq. channel ID
> 	ISDB-T : freq# (I/O# +128), ch#, ch# +64 for CATV
> 2. in addition to TSID:
> 	ISDB-S : slot#
>
> Feature changes:
> - dropped DKMS & standalone compile
> - dropped verbosity (debug levels), use single level -DDEBUG instead
> - changed SNR (.read_snr) to CNR (.read_signal_strength)
> - moved FE to drivers/media/dvb-frontends
> - moved demodulator & tuners to drivers/media/tuners
> - translated to standard (?) I2C protocol
>
> The full package (buildable as standalone, DKMS or tree embedded module) is available at
> https://github.com/knight-rider/ptx/tree/master/pt3_dvb
>
> Signed-off-by: Bud R <knightrider@are.ma>
>
> ---
>   drivers/media/dvb-frontends/Kconfig      |  10 +-
>   drivers/media/dvb-frontends/Makefile     |   1 +
>   drivers/media/dvb-frontends/pt3_common.h |  95 +++++
>   drivers/media/dvb-frontends/pt3_fe.c     | 412 ++++++++++++++++++++++
>   drivers/media/dvb-frontends/pt3_fe.h     |  22 ++
>   drivers/media/pci/Kconfig                |   2 +-
>   drivers/media/pci/Makefile               |   1 +
>   drivers/media/pci/pt3/Kconfig            |  10 +
>   drivers/media/pci/pt3/Makefile           |   6 +
>   drivers/media/pci/pt3/pt3.c              | 579 +++++++++++++++++++++++++++++++
>   drivers/media/pci/pt3/pt3.h              |  23 ++
>   drivers/media/pci/pt3/pt3_dma.c          | 335 ++++++++++++++++++
>   drivers/media/pci/pt3/pt3_dma.h          |  48 +++
>   drivers/media/pci/pt3/pt3_i2c.c          | 167 +++++++++
>   drivers/media/pci/pt3/pt3_i2c.h          |  29 ++
>   drivers/media/tuners/Kconfig             |   7 +
>   drivers/media/tuners/Makefile            |   2 +
>   drivers/media/tuners/mxl301rf.c          | 347 ++++++++++++++++++
>   drivers/media/tuners/mxl301rf.h          |  27 ++
>   drivers/media/tuners/qm1d1c0042.c        | 413 ++++++++++++++++++++++
>   drivers/media/tuners/qm1d1c0042.h        |  34 ++
>   drivers/media/tuners/tc90522.c           | 412 ++++++++++++++++++++++
>   drivers/media/tuners/tc90522.h           |  90 +++++
>   23 files changed, 3070 insertions(+), 2 deletions(-)
>   create mode 100644 drivers/media/dvb-frontends/pt3_common.h
>   create mode 100644 drivers/media/dvb-frontends/pt3_fe.c
>   create mode 100644 drivers/media/dvb-frontends/pt3_fe.h
>   create mode 100644 drivers/media/pci/pt3/Kconfig
>   create mode 100644 drivers/media/pci/pt3/Makefile
>   create mode 100644 drivers/media/pci/pt3/pt3.c
>   create mode 100644 drivers/media/pci/pt3/pt3.h
>   create mode 100644 drivers/media/pci/pt3/pt3_dma.c
>   create mode 100644 drivers/media/pci/pt3/pt3_dma.h
>   create mode 100644 drivers/media/pci/pt3/pt3_i2c.c
>   create mode 100644 drivers/media/pci/pt3/pt3_i2c.h
>   create mode 100644 drivers/media/tuners/mxl301rf.c
>   create mode 100644 drivers/media/tuners/mxl301rf.h
>   create mode 100644 drivers/media/tuners/qm1d1c0042.c
>   create mode 100644 drivers/media/tuners/qm1d1c0042.h
>   create mode 100644 drivers/media/tuners/tc90522.c
>   create mode 100644 drivers/media/tuners/tc90522.h
>
> diff --git a/drivers/media/dvb-frontends/Kconfig b/drivers/media/dvb-frontends/Kconfig
> index bddbab4..371c407 100644
> --- a/drivers/media/dvb-frontends/Kconfig
> +++ b/drivers/media/dvb-frontends/Kconfig
> @@ -584,7 +584,7 @@ config DVB_S5H1411
>   	  An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want
>   	  to support this frontend.
>
> -comment "ISDB-T (terrestrial) frontends"
> +comment "ISDB-S (satellite) & ISDB-T (terrestrial) frontends"
>   	depends on DVB_CORE
>
>   config DVB_S921
> @@ -611,6 +611,14 @@ config DVB_MB86A20S
>   	  A driver for Fujitsu mb86a20s ISDB-T/ISDB-Tsb demodulator.
>   	  Say Y when you want to support this frontend.
>
> +config DVB_PT3_FE
> +	tristate "Earthsoft PT3 ISDB-S/T frontends"
> +	depends on DVB_CORE && I2C
> +	default m if !MEDIA_SUBDRV_AUTOSELECT
> +	help
> +	  DVB driver frontend for Earthsoft PT3 ISDB-S/ISDB-T PCIE cards.
> +	  Say Y when you want to support this frontend.
> +
>   comment "Digital terrestrial only tuners/PLL"
>   	depends on DVB_CORE
>
> diff --git a/drivers/media/dvb-frontends/Makefile b/drivers/media/dvb-frontends/Makefile
> index f9cb43d..1b97ba1 100644
> --- a/drivers/media/dvb-frontends/Makefile
> +++ b/drivers/media/dvb-frontends/Makefile
> @@ -104,4 +104,5 @@ obj-$(CONFIG_DVB_RTL2830) += rtl2830.o
>   obj-$(CONFIG_DVB_RTL2832) += rtl2832.o
>   obj-$(CONFIG_DVB_M88RS2000) += m88rs2000.o
>   obj-$(CONFIG_DVB_AF9033) += af9033.o
> +obj-$(CONFIG_DVB_PT3_FE) += pt3_fe.o
>
> diff --git a/drivers/media/dvb-frontends/pt3_common.h b/drivers/media/dvb-frontends/pt3_common.h
> new file mode 100644
> index 0000000..8519f0e
> --- /dev/null
> +++ b/drivers/media/dvb-frontends/pt3_common.h
> @@ -0,0 +1,95 @@
> +/*
> + * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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	__PT3_COMMON_H__
> +#define	__PT3_COMMON_H__
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME " " fmt
> +
> +#include <linux/pci.h>
> +#include <linux/kthread.h>
> +#include <linux/freezer.h>
> +#include "dvb_demux.h"
> +#include "dmxdev.h"
> +#include "dvb_frontend.h"
> +
> +#define PT3_NR_ADAPS 4
> +#define PT3_SHIFT_MASK(val, shift, mask) (((val) >> (shift)) & (((u64)1<<(mask))-1))
> +
> +/* register idx */
> +#define REG_VERSION	0x00	/*	R	Version		*/
> +#define REG_BUS		0x04	/*	R	Bus		*/
> +#define REG_SYSTEM_W	0x08	/*	W	System		*/
> +#define REG_SYSTEM_R	0x0c	/*	R	System		*/
> +#define REG_I2C_W	0x10	/*	W	I2C		*/
> +#define REG_I2C_R	0x14	/*	R	I2C		*/
> +#define REG_RAM_W	0x18	/*	W	RAM		*/
> +#define REG_RAM_R	0x1c	/*	R	RAM		*/
> +#define REG_BASE	0x40	/* + 0x18*idx			*/
> +#define REG_DMA_DESC_L	0x00	/*	W	DMA		*/
> +#define REG_DMA_DESC_H	0x04	/*	W	DMA		*/
> +#define REG_DMA_CTL	0x08	/*	W	DMA		*/
> +#define REG_TS_CTL	0x0c	/*	W	TS		*/
> +#define REG_STATUS	0x10	/*	R	DMA/FIFO/TS	*/
> +#define REG_TS_ERR	0x14	/*	R	TS		*/
> +
> +struct pt3_adapter;
> +
> +struct pt3_board {
> +	struct mutex lock;
> +	int lnb;
> +	bool reset;
> +
> +	struct pci_dev *pdev;
> +	int bars;
> +	void __iomem *bar_reg, *bar_mem;
> +	struct i2c_adapter i2c;
> +	u8 i2c_buf;
> +	u32 i2c_addr;
> +	bool i2c_filled;
> +
> +	struct pt3_adapter *adap[PT3_NR_ADAPS];
> +};
> +
> +struct pt3_adapter {
> +	struct mutex lock;
> +	struct pt3_board *pt3;
> +
> +	int idx, init_ch;
> +	char *str;
> +	fe_delivery_system_t type;
> +	bool in_use, sleep;
> +	u32 channel;
> +	s32 offset;
> +	u8 addr_demod, addr_tuner;
> +	u32 freq;
> +	struct qm1d1c0042 *qm;
> +	struct pt3_dma *dma;
> +	struct task_struct *kthread;
> +	int *dec;
> +	struct dvb_adapter dvb;
> +	struct dvb_demux demux;
> +	int users;
> +	struct dmxdev dmxdev;
> +	struct dvb_frontend *fe;
> +	int (*orig_voltage)(struct dvb_frontend *fe, fe_sec_voltage_t voltage);
> +	int (*orig_sleep)  (struct dvb_frontend *fe);
> +	int (*orig_init)   (struct dvb_frontend *fe);
> +	fe_sec_voltage_t voltage;
> +};
> +
> +#endif
> +
> diff --git a/drivers/media/dvb-frontends/pt3_fe.c b/drivers/media/dvb-frontends/pt3_fe.c
> new file mode 100644
> index 0000000..00b5e06
> --- /dev/null
> +++ b/drivers/media/dvb-frontends/pt3_fe.c
> @@ -0,0 +1,412 @@
> +/*
> + * DVB Frontend driver for Earthsoft PT3 ISDB-S/T PCI-E card
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 "pt3_common.h"
> +#include "tc90522.h"
> +#include "qm1d1c0042.h"
> +#include "mxl301rf.h"
> +
> +MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
> +MODULE_DESCRIPTION("DVB Frontend Driver for Earthsoft PT3 cards");
> +MODULE_LICENSE("GPL");
> +
> +/**** Common ****/
> +enum pt3_fe_tune_state {
> +	PT3_TUNE_IDLE,
> +	PT3_TUNE_SET_FREQUENCY,
> +	PT3_TUNE_CHECK_FREQUENCY,
> +	PT3_TUNE_SET_MODULATION,
> +	PT3_TUNE_CHECK_MODULATION,
> +	PT3_TUNE_SET_TS_ID,
> +	PT3_TUNE_CHECK_TS_ID,
> +	PT3_TUNE_TRACK,
> +	PT3_TUNE_ABORT,
> +};
> +
> +struct pt3_fe_state {
> +	struct pt3_adapter *adap;
> +	struct dvb_frontend fe;
> +	enum pt3_fe_tune_state tune_state;
> +};
> +
> +static int pt3_fe_read_signal_strength(struct dvb_frontend *fe, u16 *cn)
> +{
> +	struct pt3_fe_state *state = fe->demodulator_priv;
> +	return tc90522_read_cn(state->adap, (u32 *)cn);
> +}
> +
> +static int pt3_fe_get_frontend_algo(struct dvb_frontend *fe)
> +{
> +	return DVBFE_ALGO_HW;
> +}
> +
> +static void pt3_fe_release(struct dvb_frontend *fe)
> +{
> +	kfree(fe->demodulator_priv);
> +}
> +
> +static int pt3_fe_init(struct dvb_frontend *fe)
> +{
> +	struct pt3_fe_state *state = fe->demodulator_priv;
> +	state->tune_state = PT3_TUNE_IDLE;
> +	return (state->adap->type == SYS_ISDBS) ?
> +		qm1d1c0042_set_sleep(state->adap->qm, false) : mxl301rf_set_sleep(state->adap, false);
> +}
> +
> +static int pt3_fe_sleep(struct dvb_frontend *fe)
> +{
> +	struct pt3_fe_state *state = fe->demodulator_priv;
> +	return (state->adap->type == SYS_ISDBS) ?
> +		qm1d1c0042_set_sleep(state->adap->qm, true) : mxl301rf_set_sleep(state->adap, true);
> +}
> +
> +/**** ISDB-S ****/
> +u32 pt3_fe_s_get_channel(u32 frequency)
> +{
> +	u32 freq = frequency / 10,
> +	    ch0 = (freq - 104948) / 3836, diff0 = freq - (104948 + 3836 * ch0),
> +	    ch1 = (freq - 161300) / 4000, diff1 = freq - (161300 + 4000 * ch1),
> +	    ch2 = (freq - 159300) / 4000, diff2 = freq - (159300 + 4000 * ch2),
> +	    min = diff0 < diff1 ? diff0 : diff1;
> +
> +	if (diff2 < min)
> +		return ch2 + 24;
> +	else if (min == diff1)
> +		return ch1 + 12;
> +	else
> +		return ch0;
> +}
> +
> +static int pt3_fe_s_read_status(struct dvb_frontend *fe, fe_status_t *status)
> +{
> +	struct pt3_fe_state *state = fe->demodulator_priv;
> +
> +	switch (state->tune_state) {
> +	case PT3_TUNE_IDLE:
> +	case PT3_TUNE_SET_FREQUENCY:
> +	case PT3_TUNE_CHECK_FREQUENCY:
> +		*status = 0;
> +		return 0;
> +
> +	case PT3_TUNE_SET_MODULATION:
> +	case PT3_TUNE_CHECK_MODULATION:
> +	case PT3_TUNE_ABORT:
> +		*status |= FE_HAS_SIGNAL;
> +		return 0;
> +
> +	case PT3_TUNE_SET_TS_ID:
> +	case PT3_TUNE_CHECK_TS_ID:
> +		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER;
> +		return 0;
> +
> +	case PT3_TUNE_TRACK:
> +		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
> +		return 0;
> +	}
> +	BUG();
> +}
> +
> +static int pt3_fe_s_tune(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
> +{
> +	struct pt3_fe_state *state = fe->demodulator_priv;
> +	struct pt3_adapter *adap = state->adap;
> +	struct tmcc_s *tmcc = &adap->qm->tmcc;
> +	int i, ret,
> +	    freq = state->fe.dtv_property_cache.frequency,
> +	    tsid = state->fe.dtv_property_cache.stream_id,
> +	    ch = (freq < 1024) ? freq : pt3_fe_s_get_channel(freq);	/* consider as channel ID if low */
> +
> +	if (re_tune)
> +		state->tune_state = PT3_TUNE_SET_FREQUENCY;
> +
> +	switch (state->tune_state) {
> +	case PT3_TUNE_IDLE:
> +		*delay = 3 * HZ;
> +		*status = 0;
> +		return 0;
> +
> +	case PT3_TUNE_SET_FREQUENCY:
> +	case PT3_TUNE_CHECK_FREQUENCY:
> +		pr_debug("#%d freq %d tsid 0x%x ch %d\n", adap->idx, freq, tsid, ch);
> +		ret = qm1d1c0042_set_frequency(adap->qm, ch);
> +		if (ret)
> +			return ret;
> +		adap->channel = ch;
> +		state->tune_state = PT3_TUNE_SET_MODULATION;
> +		*delay = 0;
> +		*status = FE_HAS_SIGNAL;
> +		return 0;
> +
> +	case PT3_TUNE_SET_MODULATION:
> +		for (i = 0; i < 1000; i++) {
> +			ret = tc90522_read_tmcc(adap, tmcc);
> +			if (!ret)
> +				break;
> +			msleep_interruptible(1);
> +		}
> +		if (ret) {
> +			pr_debug("fail tc_read_tmcc_s ret=0x%x\n", ret);
> +			state->tune_state = PT3_TUNE_ABORT;
> +			*delay = HZ;
> +			return ret;
> +		}
> +		pr_debug("slots=%d,%d,%d,%d mode=%d,%d,%d,%d\n",
> +				tmcc->slot[0], tmcc->slot[1], tmcc->slot[2], tmcc->slot[3],
> +				tmcc->mode[0], tmcc->mode[1], tmcc->mode[2], tmcc->mode[3]);
> +		state->tune_state = PT3_TUNE_CHECK_MODULATION;
> +		*delay = 0;
> +		*status = FE_HAS_SIGNAL;
> +		return 0;
> +
> +	case PT3_TUNE_CHECK_MODULATION:
> +		pr_debug("tmcc->id=0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x\n",
> +				tmcc->id[0], tmcc->id[1], tmcc->id[2], tmcc->id[3],
> +				tmcc->id[4], tmcc->id[5], tmcc->id[6], tmcc->id[7]);
> +		for (i = 0; i < sizeof(tmcc->id)/sizeof(tmcc->id[0]); i++) {
> +			pr_debug("tsid %x i %d tmcc->id %x\n", tsid, i, tmcc->id[i]);
> +			if (tmcc->id[i] == tsid)
> +				break;
> +		}
> +		if (tsid < sizeof(tmcc->id)/sizeof(tmcc->id[0]))	/* consider as slot# */
> +			i = tsid;
> +		if (i == sizeof(tmcc->id)/sizeof(tmcc->id[0])) {
> +			pr_debug("#%d i%d tsid 0x%x not found\n", adap->idx, i, tsid);
> +			return -EINVAL;
> +		}
> +		adap->offset = i;
> +		pr_debug("#%d found tsid 0x%x on slot %d\n", adap->idx, tsid, i);
> +		state->tune_state = PT3_TUNE_SET_TS_ID;
> +		*delay = 0;
> +		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER;
> +		return 0;
> +
> +	case PT3_TUNE_SET_TS_ID:
> +		ret = tc90522_write_id_s(adap, (u16)tmcc->id[adap->offset]);
> +		if (ret) {
> +			pr_debug("fail set_tmcc_s ret=%d\n", ret);
> +			return ret;
> +		}
> +		state->tune_state = PT3_TUNE_CHECK_TS_ID;
> +		return 0;
> +
> +	case PT3_TUNE_CHECK_TS_ID:
> +		for (i = 0; i < 1000; i++) {
> +			u16 short_id;
> +			ret = tc90522_read_id_s(adap, &short_id);
> +			if (ret) {
> +				pr_debug("fail get_id_s ret=%d\n", ret);
> +				return ret;
> +			}
> +			tsid = short_id;
> +			pr_debug("#%d tsid=0x%x\n", adap->idx, tsid);
> +			if ((tsid & 0xffff) == tmcc->id[adap->offset])
> +				break;
> +			msleep_interruptible(1);
> +		}
> +		state->tune_state = PT3_TUNE_TRACK;
> +
> +	case PT3_TUNE_TRACK:
> +		*delay = 3 * HZ;
> +		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
> +		return 0;
> +
> +	case PT3_TUNE_ABORT:
> +		*delay = 3 * HZ;
> +		*status = FE_HAS_SIGNAL;
> +		return 0;
> +	}
> +	BUG();
> +}
> +
> +static struct dvb_frontend_ops pt3_fe_s_ops = {
> +	.delsys = { SYS_ISDBS },
> +	.info = {
> +		.name = "PT3 ISDB-S",
> +		.frequency_min = 1,
> +		.frequency_max = 2150000,
> +		.frequency_stepsize = 1000,
> +		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO | FE_CAN_MULTISTREAM |
> +			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
> +	},
> +	.init = pt3_fe_init,
> +	.sleep = pt3_fe_sleep,
> +	.release = pt3_fe_release,
> +	.get_frontend_algo = pt3_fe_get_frontend_algo,
> +	.read_signal_strength = pt3_fe_read_signal_strength,
> +	.read_status = pt3_fe_s_read_status,
> +	.tune = pt3_fe_s_tune,
> +};
> +
> +/**** ISDB-T ****/
> +static int pt3_fe_t_get_tmcc(struct pt3_adapter *adap, struct tmcc_t *tmcc)
> +{
> +	bool b = false, retryov, tmunvld, fulock;
> +
> +	if (unlikely(!tmcc))
> +		return -EINVAL;
> +	while (1) {
> +		tc90522_read_retryov_tmunvld_fulock(adap, &retryov, &tmunvld, &fulock);
> +		if (!fulock) {
> +			b = true;
> +			break;
> +		} else {
> +			if (retryov)
> +				break;
> +		}
> +		msleep_interruptible(1);
> +	}
> +	if (likely(b))
> +		tc90522_read_tmcc(adap, tmcc);
> +	return b ? 0 : -EBADMSG;
> +}
> +
> +static int pt3_fe_t_read_status(struct dvb_frontend *fe, fe_status_t *status)
> +{
> +	struct pt3_fe_state *state = fe->demodulator_priv;
> +
> +	switch (state->tune_state) {
> +	case PT3_TUNE_IDLE:
> +	case PT3_TUNE_SET_FREQUENCY:
> +	case PT3_TUNE_CHECK_FREQUENCY:
> +		*status = 0;
> +		return 0;
> +
> +	case PT3_TUNE_SET_MODULATION:
> +	case PT3_TUNE_CHECK_MODULATION:
> +	case PT3_TUNE_SET_TS_ID:
> +	case PT3_TUNE_CHECK_TS_ID:
> +	case PT3_TUNE_ABORT:
> +		*status |= FE_HAS_SIGNAL;
> +		return 0;
> +
> +	case PT3_TUNE_TRACK:
> +		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
> +		return 0;
> +	}
> +	BUG();
> +}
> +
> +static int pt3_fe_t_tune(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
> +{
> +	struct pt3_fe_state *state = fe->demodulator_priv;
> +	struct tmcc_t tmcc_t;
> +	int ret, i;
> +
> +	if (re_tune)
> +		state->tune_state = PT3_TUNE_SET_FREQUENCY;
> +
> +	switch (state->tune_state) {
> +	case PT3_TUNE_IDLE:
> +		*delay = 3 * HZ;
> +		*status = 0;
> +		return 0;
> +
> +	case PT3_TUNE_SET_FREQUENCY:
> +		ret = tc90522_set_agc(state->adap, TC90522_AGC_MANUAL);
> +		if (ret)
> +			return ret;
> +		mxl301rf_tuner_rftune(state->adap, mxl301rf_freq(state->fe.dtv_property_cache.frequency));
> +		state->tune_state = PT3_TUNE_CHECK_FREQUENCY;
> +		*delay = 0;
> +		*status = 0;
> +		return 0;
> +
> +	case PT3_TUNE_CHECK_FREQUENCY:
> +		if (!mxl301rf_locked(state->adap)) {
> +			*delay = HZ;
> +			*status = 0;
> +			return 0;
> +		}
> +		state->tune_state = PT3_TUNE_SET_MODULATION;
> +		*delay = 0;
> +		*status = FE_HAS_SIGNAL;
> +		return 0;
> +
> +	case PT3_TUNE_SET_MODULATION:
> +		ret = tc90522_set_agc(state->adap, TC90522_AGC_AUTO);
> +		if (ret)
> +			return ret;
> +		state->tune_state = PT3_TUNE_CHECK_MODULATION;
> +		*delay = 0;
> +		*status = FE_HAS_SIGNAL;
> +		return 0;
> +
> +	case PT3_TUNE_CHECK_MODULATION:
> +	case PT3_TUNE_SET_TS_ID:
> +	case PT3_TUNE_CHECK_TS_ID:
> +		for (i = 0; i < 1000; i++) {
> +			ret = pt3_fe_t_get_tmcc(state->adap, &tmcc_t);
> +			if (!ret)
> +				break;
> +			msleep_interruptible(2);
> +		}
> +		if (ret) {
> +			pr_debug("#%d fail get_tmcc_t ret=%d\n", state->adap->idx, ret);
> +				state->tune_state = PT3_TUNE_ABORT;
> +				*delay = HZ;
> +				return 0;
> +		}
> +		state->tune_state = PT3_TUNE_TRACK;
> +
> +	case PT3_TUNE_TRACK:
> +		*delay = 3 * HZ;
> +		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
> +		return 0;
> +
> +	case PT3_TUNE_ABORT:
> +		*delay = 3 * HZ;
> +		*status = FE_HAS_SIGNAL;
> +		return 0;
> +	}
> +	BUG();
> +}
> +
> +static struct dvb_frontend_ops pt3_fe_t_ops = {
> +	.delsys = { SYS_ISDBT },
> +	.info = {
> +		.name = "PT3 ISDB-T",
> +		.frequency_min = 1,
> +		.frequency_max = 770000000,
> +		.frequency_stepsize = 142857,
> +		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO |
> +			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
> +	},
> +	.init = pt3_fe_init,
> +	.sleep = pt3_fe_sleep,
> +	.release = pt3_fe_release,
> +	.get_frontend_algo = pt3_fe_get_frontend_algo,
> +	.read_signal_strength = pt3_fe_read_signal_strength,
> +	.read_status = pt3_fe_t_read_status,
> +	.tune = pt3_fe_t_tune,
> +};
> +
> +/**** Common ****/
> +struct dvb_frontend *pt3_fe_attach(struct pt3_adapter *adap)
> +{
> +	struct dvb_frontend *fe;
> +	struct pt3_fe_state *state = kzalloc(sizeof(struct pt3_fe_state), GFP_KERNEL);
> +
> +	if (!state)
> +		return NULL;
> +	state->adap = adap;
> +	fe = &state->fe;
> +	memcpy(&fe->ops, (adap->type == SYS_ISDBS) ? &pt3_fe_s_ops : &pt3_fe_t_ops, sizeof(struct dvb_frontend_ops));
> +	fe->demodulator_priv = state;
> +	return fe;
> +}
> +
> +EXPORT_SYMBOL(pt3_fe_attach);
> +
> diff --git a/drivers/media/dvb-frontends/pt3_fe.h b/drivers/media/dvb-frontends/pt3_fe.h
> new file mode 100644
> index 0000000..0d614f3
> --- /dev/null
> +++ b/drivers/media/dvb-frontends/pt3_fe.h
> @@ -0,0 +1,22 @@
> +/*
> + * DVB Frontend driver for Earthsoft PT3 ISDB-S/T PCI-E card
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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	__PT3_FE_H__
> +#define	__PT3_FE_H__
> +
> +struct dvb_frontend *pt3_fe_attach(struct pt3_adapter *adap);
> +
> +#endif
> diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
> index 53196f1..87018c8 100644
> --- a/drivers/media/pci/Kconfig
> +++ b/drivers/media/pci/Kconfig
> @@ -30,7 +30,6 @@ source "drivers/media/pci/cx88/Kconfig"
>   source "drivers/media/pci/bt8xx/Kconfig"
>   source "drivers/media/pci/saa7134/Kconfig"
>   source "drivers/media/pci/saa7164/Kconfig"
> -
>   endif
>
>   if MEDIA_DIGITAL_TV_SUPPORT
> @@ -40,6 +39,7 @@ source "drivers/media/pci/b2c2/Kconfig"
>   source "drivers/media/pci/pluto2/Kconfig"
>   source "drivers/media/pci/dm1105/Kconfig"
>   source "drivers/media/pci/pt1/Kconfig"
> +source "drivers/media/pci/pt3/Kconfig"
>   source "drivers/media/pci/mantis/Kconfig"
>   source "drivers/media/pci/ngene/Kconfig"
>   source "drivers/media/pci/ddbridge/Kconfig"
> diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
> index 35cc578..f7be6bc 100644
> --- a/drivers/media/pci/Makefile
> +++ b/drivers/media/pci/Makefile
> @@ -7,6 +7,7 @@ obj-y        +=	ttpci/		\
>   		pluto2/		\
>   		dm1105/		\
>   		pt1/		\
> +		pt3/		\
>   		mantis/		\
>   		ngene/		\
>   		ddbridge/	\
> diff --git a/drivers/media/pci/pt3/Kconfig b/drivers/media/pci/pt3/Kconfig
> new file mode 100644
> index 0000000..519fdaa
> --- /dev/null
> +++ b/drivers/media/pci/pt3/Kconfig
> @@ -0,0 +1,10 @@
> +config PT3_DVB
> +	tristate "Earthsoft PT3 ISDB-S/T cards"
> +	depends on DVB_CORE && PCI
> +	select DVB_PT3_FE if MEDIA_SUBDRV_AUTOSELECT
> +	select MEDIA_TUNER_PT3 if MEDIA_SUBDRV_AUTOSELECT
> +	help
> +	  Support for Earthsoft PT3 PCI-Express cards.
> +	  You may also need to enable PT3 DVB frontend & tuner.
> +	  Say Y or M if you own such a device and want to use it.
> +
> diff --git a/drivers/media/pci/pt3/Makefile b/drivers/media/pci/pt3/Makefile
> new file mode 100644
> index 0000000..b030927
> --- /dev/null
> +++ b/drivers/media/pci/pt3/Makefile
> @@ -0,0 +1,6 @@
> +pt3_dvb-objs := pt3.o pt3_dma.o pt3_i2c.o
> +
> +obj-$(CONFIG_PT3_DVB) += pt3_dvb.o
> +
> +ccflags-y += -Idrivers/media/dvb-core -Idrivers/media/dvb-frontends -Idrivers/media/tuners
> +
> diff --git a/drivers/media/pci/pt3/pt3.c b/drivers/media/pci/pt3/pt3.c
> new file mode 100644
> index 0000000..d1b1952
> --- /dev/null
> +++ b/drivers/media/pci/pt3/pt3.c
> @@ -0,0 +1,579 @@
> +/*
> + * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 "pt3_common.h"
> +#include "pt3_dma.h"
> +#include "pt3_i2c.h"
> +#include "tc90522.h"
> +#include "qm1d1c0042.h"
> +#include "mxl301rf.h"
> +#include "pt3_fe.h"
> +
> +#define DRV_NAME "pt3_dvb"
> +
> +MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
> +MODULE_DESCRIPTION("Earthsoft PT3 DVB Driver");
> +MODULE_LICENSE("GPL");
> +
> +static DEFINE_PCI_DEVICE_TABLE(pt3_id_table) = {
> +	{ PCI_DEVICE(0x1172, 0x4c15) },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(pci, pt3_id_table);
> +
> +static int lnb = 2;	/* used if not set by frontend or the value is invalid */
> +module_param(lnb, int, 0);
> +MODULE_PARM_DESC(lnb, "LNB level (0:OFF 1:+11V 2:+15V)");
> +
> +struct {
> +	u32 bits;
> +	char *str;
> +} pt3_lnb[] = {
> +	{0b1100,  "0V"},
> +	{0b1101, "11V"},
> +	{0b1111, "15V"},
> +};
> +
> +static int pt3_update_lnb(struct pt3_board *pt3)
> +{
> +	u8 i, lnb_eff = 0;
> +
> +	if (pt3->reset) {
> +		writel(pt3_lnb[0].bits, pt3->bar_reg + REG_SYSTEM_W);
> +		pt3->reset = false;
> +		pt3->lnb = 0;
> +	} else {
> +		struct pt3_adapter *adap;
> +		mutex_lock(&pt3->lock);
> +		for (i = 0; i < PT3_NR_ADAPS; i++) {
> +			adap = pt3->adap[i];
> +			pr_debug("#%d in_use %d sleep %d\n", adap->idx, adap->in_use, adap->sleep);
> +			if ((adap->type == SYS_ISDBS) && (!adap->sleep)) {
> +				lnb_eff |= adap->voltage == SEC_VOLTAGE_13 ? 1
> +					:  adap->voltage == SEC_VOLTAGE_18 ? 2
> +					:  lnb;
> +			}
> +		}
> +		mutex_unlock(&pt3->lock);
> +		if (unlikely(lnb_eff < 0 || 2 < lnb_eff)) {
> +			pr_err("Inconsistent LNB settings\n");
> +			return -EINVAL;
> +		}
> +		if (pt3->lnb != lnb_eff) {
> +			writel(pt3_lnb[lnb_eff].bits, pt3->bar_reg + REG_SYSTEM_W);
> +			pt3->lnb = lnb_eff;
> +		}
> +	}
> +	pr_debug("LNB=%s\n", pt3_lnb[lnb_eff].str);
> +	return 0;
> +}
> +
> +void pt3_filter(struct pt3_adapter *adap, struct dvb_demux *demux, u8 *buf, size_t count)
> +{
> +	dvb_dmx_swfilter(demux, buf, count);
> +}
> +
> +int pt3_thread(void *data)
> +{
> +	size_t ret;
> +	struct pt3_adapter *adap = data;
> +
> +	set_freezable();
> +	while (!kthread_should_stop()) {
> +		try_to_freeze();
> +		while ((ret = pt3_dma_copy(adap->dma, &adap->demux)) > 0)
> +			;
> +		if (ret < 0) {
> +			pr_debug("#%d fail dma_copy\n", adap->idx);
> +			msleep_interruptible(1);
> +		}
> +	}
> +	return 0;
> +}
> +
> +static int pt3_start_polling(struct pt3_adapter *adap)
> +{
> +	int ret = 0;
> +
> +	mutex_lock(&adap->lock);
> +	if (!adap->kthread) {
> +		adap->kthread = kthread_run(pt3_thread, adap, DRV_NAME "_%d", adap->idx);
> +		if (IS_ERR(adap->kthread)) {
> +			ret = PTR_ERR(adap->kthread);
> +			adap->kthread = NULL;
> +		} else {
> +			pt3_dma_set_test_mode(adap->dma, RESET, 0);	/* reset_error_count */
> +			pt3_dma_set_enabled(adap->dma, true);
> +		}
> +	}
> +	mutex_unlock(&adap->lock);
> +	return ret;
> +}
> +
> +static void pt3_stop_polling(struct pt3_adapter *adap)
> +{
> +	mutex_lock(&adap->lock);
> +	if (adap->kthread) {
> +		pt3_dma_set_enabled(adap->dma, false);
> +		pr_debug("#%d DMA ts_err packet cnt %d\n",
> +			adap->idx, pt3_dma_get_ts_error_packet_count(adap->dma));
> +		kthread_stop(adap->kthread);
> +		adap->kthread = NULL;
> +	}
> +	mutex_unlock(&adap->lock);
> +}
> +
> +static int pt3_start_feed(struct dvb_demux_feed *feed)
> +{
> +	int ret;
> +	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
> +	if (!adap->users++) {
> +		if (adap->in_use) {
> +			pr_err("#%d device is already used\n", adap->idx);
> +			return -EIO;
> +		}
> +		pr_debug("#%d %s selected, DMA %s\n",
> +			adap->idx, adap->str, pt3_dma_get_status(adap->dma) & 1 ? "ON" : "OFF");
> +		adap->in_use = true;
> +		ret = pt3_start_polling(adap);
> +		if (ret)
> +			return ret;
> +	}
> +	return 0;
> +}
> +
> +static int pt3_stop_feed(struct dvb_demux_feed *feed)
> +{
> +	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
> +	if (!--adap->users) {
> +		pt3_stop_polling(adap);
> +		adap->in_use = false;
> +		msleep_interruptible(40);
> +	}
> +	return 0;
> +}
> +
> +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
> +
> +static struct pt3_adapter *pt3_alloc_adapter(struct pt3_board *pt3)
> +{
> +	int ret;
> +	struct dvb_adapter *dvb;
> +	struct dvb_demux *demux;
> +	struct dmxdev *dmxdev;
> +	struct pt3_adapter *adap = kzalloc(sizeof(struct pt3_adapter), GFP_KERNEL);
> +
> +	if (!adap) {
> +		ret = -ENOMEM;
> +		goto err;
> +	}
> +	adap->pt3 = pt3;
> +	adap->voltage = SEC_VOLTAGE_OFF;
> +	adap->sleep = true;
> +
> +	dvb = &adap->dvb;
> +	dvb->priv = adap;
> +	ret = dvb_register_adapter(dvb, DRV_NAME, THIS_MODULE, &pt3->pdev->dev, adapter_nr);
> +	if (ret < 0)
> +		goto err_kfree;
> +
> +	demux = &adap->demux;
> +	demux->dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING;
> +	demux->priv = adap;
> +	demux->feednum = 256;
> +	demux->filternum = 256;
> +	demux->start_feed = pt3_start_feed;
> +	demux->stop_feed = pt3_stop_feed;
> +	demux->write_to_decoder = NULL;
> +	ret = dvb_dmx_init(demux);
> +	if (ret < 0)
> +		goto err_unregister_adapter;
> +
> +	dmxdev = &adap->dmxdev;
> +	dmxdev->filternum = 256;
> +	dmxdev->demux = &demux->dmx;
> +	dmxdev->capabilities = 0;
> +	ret = dvb_dmxdev_init(dmxdev, dvb);
> +	if (ret < 0)
> +		goto err_dmx_release;
> +
> +	return adap;
> +
> +err_dmx_release:
> +	dvb_dmx_release(demux);
> +err_unregister_adapter:
> +	dvb_unregister_adapter(dvb);
> +err_kfree:
> +	kfree(adap);
> +err:
> +	return ERR_PTR(ret);
> +}
> +
> +int pt3_set_freq(struct pt3_adapter *adap, u32 ch)
> +{
> +	int ret;
> +
> +	pr_debug("#%d %s set_freq channel=%d\n", adap->idx, adap->str, ch);
> +
> +	if (adap->type == SYS_ISDBS)
> +		ret = qm1d1c0042_set_frequency(adap->qm, ch);
> +	else
> +		ret = mxl301rf_set_frequency(adap, ch, 0);
> +	return ret;
> +}
> +
> +int pt3_tuner_set_sleep(struct pt3_adapter *adap, bool sleep)
> +{
> +	int ret;
> +	pr_debug("#%d %p %s %s\n", adap->idx, adap, adap->str, sleep ? "Sleep" : "Wakeup");
> +
> +	if (adap->type == SYS_ISDBS)
> +		ret = qm1d1c0042_set_sleep(adap->qm, sleep);
> +	else
> +		ret = mxl301rf_set_sleep(adap, sleep);
> +	msleep_interruptible(10);
> +	return ret;
> +}
> +
> +static void pt3_cleanup_adapter(struct pt3_adapter *adap)
> +{
> +	if (!adap)
> +		return;
> +	if (adap->kthread)
> +		kthread_stop(adap->kthread);
> +	if (adap->fe)
> +		dvb_unregister_frontend(adap->fe);
> +	if (!adap->sleep)
> +		pt3_tuner_set_sleep(adap, true);
> +	if (adap->qm)
> +		vfree(adap->qm);
> +	if (adap->dma) {
> +		if (adap->dma->enabled)
> +			pt3_dma_set_enabled(adap->dma, false);
> +		pt3_dma_free(adap->dma);
> +	}
> +	adap->demux.dmx.close(&adap->demux.dmx);
> +	dvb_dmxdev_release(&adap->dmxdev);
> +	dvb_dmx_release(&adap->demux);
> +	dvb_unregister_adapter(&adap->dvb);
> +	kfree(adap);
> +}
> +
> +static int pt3_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage)
> +{
> +	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
> +	adap->voltage = voltage;
> +	return (adap->orig_voltage) ? adap->orig_voltage(fe, voltage) : 0;
> +}
> +
> +static int pt3_sleep(struct dvb_frontend *fe)
> +{
> +	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
> +	adap->sleep = true;
> +	pt3_update_lnb(adap->pt3);
> +	return (adap->orig_sleep) ? adap->orig_sleep(fe) : 0;
> +}
> +
> +static int pt3_wakeup(struct dvb_frontend *fe)
> +{
> +	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
> +	adap->sleep = false;
> +	pt3_update_lnb(adap->pt3);
> +	return (adap->orig_init) ? adap->orig_init(fe) : 0;
> +}
> +
> +static int pt3_init_frontends(struct pt3_board *pt3)
> +{
> +	struct dvb_frontend *fe[PT3_NR_ADAPS];
> +	int i, ret;
> +
> +	for (i = 0; i < PT3_NR_ADAPS; i++) {
> +		fe[i] = pt3_fe_attach(pt3->adap[i]);
> +		if (!fe[i]) {
> +			while (i--)
> +				fe[i]->ops.release(fe[i]);
> +			return -ENOMEM;
> +		}
> +	}
> +	for (i = 0; i < PT3_NR_ADAPS; i++) {
> +		struct pt3_adapter *adap = pt3->adap[i];
> +
> +		adap->orig_voltage     = fe[i]->ops.set_voltage;
> +		adap->orig_sleep       = fe[i]->ops.sleep;
> +		adap->orig_init        = fe[i]->ops.init;
> +		fe[i]->ops.set_voltage = pt3_set_voltage;
> +		fe[i]->ops.sleep       = pt3_sleep;
> +		fe[i]->ops.init        = pt3_wakeup;
> +
> +		ret = dvb_register_frontend(&adap->dvb, fe[i]);
> +		if (ret >= 0)
> +			adap->fe = fe[i];
> +		else {
> +			while (i--)
> +				dvb_unregister_frontend(fe[i]);
> +			for (i = 0; i < PT3_NR_ADAPS; i++)
> +				fe[i]->ops.release(fe[i]);
> +			return ret;
> +		}
> +	}
> +	return 0;
> +}
> +
> +static void pt3_remove(struct pci_dev *pdev)
> +{
> +	int i;
> +	struct pt3_board *pt3 = pci_get_drvdata(pdev);
> +
> +	if (pt3) {
> +		pt3->reset = true;
> +		pt3_update_lnb(pt3);
> +		if (pt3->adap && pt3->adap[PT3_NR_ADAPS-1])
> +			tc90522_set_powers(pt3->adap[PT3_NR_ADAPS-1], false, false);
> +		for (i = 0; i < PT3_NR_ADAPS; i++)
> +			pt3_cleanup_adapter(pt3->adap[i]);
> +		pt3_i2c_reset(pt3);
> +		i2c_del_adapter(&pt3->i2c);
> +		if (pt3->bar_mem)
> +			iounmap(pt3->bar_mem);
> +		if (pt3->bar_reg)
> +			iounmap(pt3->bar_reg);
> +		pci_release_selected_regions(pdev, pt3->bars);
> +		kfree(pt3);
> +	}
> +	pci_disable_device(pdev);
> +}
> +
> +static int pt3_abort(struct pci_dev *pdev, int ret, char *fmt, ...)
> +{
> +	va_list ap;
> +	char *s = NULL;
> +	int slen;
> +
> +	va_start(ap, fmt);
> +	slen = vsnprintf(s, 0, fmt, ap);
> +	s = vzalloc(slen);
> +	if (slen > 0 && s) {
> +		vsnprintf(s, slen, fmt, ap);
> +		dev_alert(&pdev->dev, "%s", s);
> +		vfree(s);
> +	}
> +	va_end(ap);
> +	pt3_remove(pdev);
> +	return ret;
> +}
> +
> +struct {
> +	fe_delivery_system_t type;
> +	u8 addr_tuner, addr_demod;
> +	int init_ch;
> +	char *str;
> +} pt3_config[] = {
> +	{SYS_ISDBS, 0x63, 0b00010001,  0, "ISDB-S"},
> +	{SYS_ISDBS, 0x60, 0b00010011,  0, "ISDB-S"},
> +	{SYS_ISDBT, 0x62, 0b00010000, 70, "ISDB-T"},
> +	{SYS_ISDBT, 0x61, 0b00010010, 71, "ISDB-T"},
> +};
> +
> +int pt3_tuner_init(struct pt3_adapter *adap)
> +{
> +	qm1d1c0042_tuner_init(adap->qm);
> +	adap->pt3->i2c_addr = 0;
> +	if (pt3_i2c_flush(adap->pt3, true, 0))
> +		return -EIO;
> +
> +	if (qm1d1c0042_tuner_init(adap->qm))
> +		return -EIO;
> +	adap->pt3->i2c_addr = 0;
> +	if (pt3_i2c_flush(adap->pt3, true, 0))
> +		return -EIO;
> +	return 0;
> +}
> +
> +static int pt3_tuner_init_all(struct pt3_board *pt3)
> +{
> +	int ret, i, j;
> +	struct pt3_ts_pins_mode pins;
> +
> +	if (!pt3_i2c_is_clean(pt3)) {
> +		pr_debug("cleanup I2C\n");
> +		ret = pt3_i2c_flush(pt3, false, 0);
> +		if (ret)
> +			goto last;
> +		msleep_interruptible(10);
> +	}
> +	for (i = 0; i < PT3_NR_ADAPS; i++) {
> +		ret = tc90522_init(pt3->adap[i]);
> +		pr_debug("#%d tc_init ret=%d\n", i, ret);
> +	}
> +	ret = tc90522_set_powers(pt3->adap[PT3_NR_ADAPS-1], true, false);
> +	if (ret) {
> +		pr_debug("fail set powers.\n");
> +		goto last;
> +	}
> +
> +	pins.clock_data = PT3_TS_PIN_MODE_NORMAL;
> +	pins.byte       = PT3_TS_PIN_MODE_NORMAL;
> +	pins.valid      = PT3_TS_PIN_MODE_NORMAL;
> +	for (i = 0; i < PT3_NR_ADAPS; i++) {
> +		ret = tc90522_set_ts_pins_mode(pt3->adap[i], &pins);
> +		if (ret)
> +			pr_debug("#%d %s fail set ts pins mode ret=%d\n", i, pt3->adap[i]->str, ret);
> +	}
> +	msleep_interruptible(1);
> +
> +	for (i = 0; i < PT3_NR_ADAPS; i++)
> +		if (pt3->adap[i]->type == SYS_ISDBS) {
> +			for (j = 0; j < 10; j++) {
> +				if (j)
> +					pr_debug("retry pt3_tuner_init\n");
> +				ret = pt3_tuner_init(pt3->adap[i]);
> +				if (!ret)
> +					break;
> +				msleep_interruptible(1);
> +			}
> +			if (ret) {
> +				pr_debug("#%d fail pt3_tuner_init ret=0x%x\n", i, ret);
> +				goto last;
> +			}
> +		}
> +	ret = pt3_i2c_flush(pt3, false, PT3_I2C_START_ADDR);
> +	if (ret)
> +		goto last;
> +	ret = tc90522_set_powers(pt3->adap[PT3_NR_ADAPS-1], true, true);
> +	if (ret) {
> +		pr_debug("fail tc_set_powers,\n");
> +		goto last;
> +	}
> +	for (i = 0; i < PT3_NR_ADAPS; i++) {
> +		struct pt3_adapter *adap = pt3->adap[i];
> +		ret = pt3_tuner_set_sleep(adap, false);
> +		if (ret)
> +			goto last;
> +		ret = pt3_set_freq(adap, adap->init_ch);
> +		if (ret)
> +			pr_debug("fail set_frequency, ret=%d\n", ret);
> +		ret = pt3_tuner_set_sleep(adap, true);
> +		if (ret)
> +			goto last;
> +	}
> +last:
> +	return ret;
> +}
> +
> +static const struct i2c_algorithm pt3_i2c_algo = {
> +	.functionality = pt3_i2c_func,
> +	.master_xfer = pt3_i2c_xfer,
> +};
> +
> +static int pt3_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
> +{
> +	struct pt3_board *pt3;
> +	struct pt3_adapter *adap;
> +	struct i2c_adapter *i2c;
> +	int i, ret, bars = pci_select_bars(pdev, IORESOURCE_MEM);
> +
> +	ret = pci_enable_device(pdev);
> +	if (ret < 0)
> +		return pt3_abort(pdev, ret, "PCI device unusable\n");
> +	ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(64));
> +	if (ret)
> +		return pt3_abort(pdev, ret, "DMA mask error\n");
> +	pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64));
> +
> +	pci_read_config_dword(pdev, PCI_CLASS_REVISION, &i);
> +	if ((i & 0xFF) != 1)
> +		return pt3_abort(pdev, ret, "Revision 0x%x is not supported\n", i & 0xFF);
> +	ret = pci_request_selected_regions(pdev, bars, DRV_NAME);
> +	if (ret < 0)
> +		return pt3_abort(pdev, ret, "Could not request regions\n");
> +
> +	pci_set_master(pdev);
> +	ret = pci_save_state(pdev);
> +	if (ret)
> +		return pt3_abort(pdev, ret, "Failed pci_save_state\n");
> +	pt3 = kzalloc(sizeof(struct pt3_board), GFP_KERNEL);
> +	if (!pt3)
> +		return pt3_abort(pdev, -ENOMEM, "struct pt3_board out of memory\n");
> +
> +	pt3->bars = bars;
> +	pt3->pdev = pdev;
> +	pci_set_drvdata(pdev, pt3);
> +	pt3->bar_reg = pci_ioremap_bar(pdev, 0);
> +	pt3->bar_mem = pci_ioremap_bar(pdev, 2);
> +	if (!pt3->bar_reg || !pt3->bar_mem)
> +		return pt3_abort(pdev, -EIO, "Failed pci_ioremap_bar\n");
> +
> +	ret = readl(pt3->bar_reg + REG_VERSION);
> +	i = ((ret >> 24) & 0xFF);
> +	if (i != 3)
> +		return pt3_abort(pdev, -EIO, "ID=0x%x, not a PT3\n", i);
> +	i = ((ret >>  8) & 0xFF);
> +	if (i != 4)
> +		return pt3_abort(pdev, -EIO, "FPGA version 0x%x is not supported\n", i);
> +	mutex_init(&pt3->lock);
> +
> +	for (i = 0; i < PT3_NR_ADAPS; i++) {
> +		pt3->adap[i] = NULL;
> +		adap = pt3_alloc_adapter(pt3);
> +		if (IS_ERR(adap))
> +			return pt3_abort(pdev, PTR_ERR(adap), "Failed pt3_alloc_adapter\n");
> +		adap->idx = i;
> +		adap->dma = pt3_dma_create(adap);
> +		if (!adap->dma)
> +			return pt3_abort(pdev, -ENOMEM, "Failed pt3_dma_create\n");
> +		mutex_init(&adap->lock);
> +		pt3->adap[i] = adap;
> +		adap->type       = pt3_config[i].type;
> +		adap->addr_tuner = pt3_config[i].addr_tuner;
> +		adap->addr_demod    = pt3_config[i].addr_demod;
> +		adap->init_ch    = pt3_config[i].init_ch;
> +		adap->str        = pt3_config[i].str;
> +		if (adap->type == SYS_ISDBS) {
> +			adap->qm = vzalloc(sizeof(struct qm1d1c0042));
> +			if (!adap->qm)
> +				return pt3_abort(pdev, -ENOMEM, "QM out of memory\n");
> +			adap->qm->adap = adap;
> +		}
> +		adap->sleep = true;
> +	}
> +	pt3->reset = true;
> +	pt3_update_lnb(pt3);
> +
> +	i2c = &pt3->i2c;
> +	i2c->algo = &pt3_i2c_algo;
> +	i2c->algo_data = NULL;
> +	i2c->dev.parent = &pdev->dev;
> +	strcpy(i2c->name, DRV_NAME);
> +	i2c_set_adapdata(i2c, pt3);
> +	ret = i2c_add_adapter(i2c);
> +	if (ret < 0)
> +		return pt3_abort(pdev, ret, "Cannot add I2C\n");
> +
> +	if (pt3_tuner_init_all(pt3))
> +		return pt3_abort(pdev, ret, "Failed pt3_tuner_init_all\n");
> +	ret = pt3_init_frontends(pt3);
> +	return (ret >= 0) ? ret : pt3_abort(pdev, ret, "Failed pt3_init_frontends\n");
> +}
> +
> +static struct pci_driver pt3_driver = {
> +	.name		= DRV_NAME,
> +	.probe		= pt3_probe,
> +	.remove		= pt3_remove,
> +	.id_table	= pt3_id_table,
> +};
> +
> +module_pci_driver(pt3_driver);
> +
> diff --git a/drivers/media/pci/pt3/pt3.h b/drivers/media/pci/pt3/pt3.h
> new file mode 100644
> index 0000000..057d1b4
> --- /dev/null
> +++ b/drivers/media/pci/pt3/pt3.h
> @@ -0,0 +1,23 @@
> +/*
> + * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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	__PT3_H__
> +#define	__PT3_H__
> +
> +void pt3_filter(struct pt3_adapter *adap, struct dvb_demux *demux, u8 *buf, size_t count);
> +
> +#endif
> +
> diff --git a/drivers/media/pci/pt3/pt3_dma.c b/drivers/media/pci/pt3/pt3_dma.c
> new file mode 100644
> index 0000000..33b1a2e
> --- /dev/null
> +++ b/drivers/media/pci/pt3/pt3_dma.c
> @@ -0,0 +1,335 @@
> +/*
> + * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 "pt3_common.h"
> +#include "pt3_dma.h"
> +#include "pt3.h"
> +
> +#define PT3_DMA_MAX_DESCS	204
> +#define PT3_DMA_PAGE_SIZE	(PT3_DMA_MAX_DESCS * sizeof(struct pt3_dma_desc))
> +#define PT3_DMA_BLOCK_COUNT	17
> +#define PT3_DMA_BLOCK_SIZE	(PT3_DMA_PAGE_SIZE * 47)
> +#define PT3_DMA_TS_BUF_SIZE	(PT3_DMA_BLOCK_SIZE * PT3_DMA_BLOCK_COUNT)
> +#define PT3_DMA_TS_SYNC		0x47
> +#define PT3_DMA_TS_NOT_SYNC	0x74
> +
> +void pt3_dma_free(struct pt3_dma *dma)
> +{
> +	struct pt3_dma_page *page;
> +	u32 i;
> +
> +	if (dma->ts_info) {
> +		for (i = 0; i < dma->ts_count; i++) {
> +			page = &dma->ts_info[i];
> +			if (page->data)
> +				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
> +		}
> +		kfree(dma->ts_info);
> +	}
> +	if (dma->desc_info) {
> +		for (i = 0; i < dma->desc_count; i++) {
> +			page = &dma->desc_info[i];
> +			if (page->data)
> +				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
> +		}
> +		kfree(dma->desc_info);
> +	}
> +	kfree(dma);
> +}
> +
> +struct pt3_dma_desc {
> +	u64 page_addr;
> +	u32 page_size;
> +	u64 next_desc;
> +} __packed;
> +
> +void pt3_dma_build_page_descriptor(struct pt3_dma *dma)
> +{
> +	struct pt3_dma_page *desc_info, *ts_info;
> +	u64 ts_addr, desc_addr;
> +	u32 i, j, ts_size, desc_remain, ts_info_pos, desc_info_pos;
> +	struct pt3_dma_desc *prev, *curr;
> +
> +	pr_debug("#%d build page descriptor ts_count=%d ts_size=%d desc_count=%d desc_size=%d\n",
> +		dma->adap->idx, dma->ts_count, dma->ts_info[0].size, dma->desc_count, dma->desc_info[0].size);
> +	desc_info_pos = ts_info_pos = 0;
> +	desc_info = &dma->desc_info[desc_info_pos];
> +	desc_addr   = desc_info->addr;
> +	desc_remain = desc_info->size;
> +	desc_info->data_pos = 0;
> +	prev = NULL;
> +	curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
> +	desc_info_pos++;
> +
> +	for (i = 0; i < dma->ts_count; i++) {
> +		if (unlikely(ts_info_pos >= dma->ts_count)) {
> +			pr_debug("#%d ts_info overflow max=%d curr=%d\n", dma->adap->idx, dma->ts_count, ts_info_pos);
> +			return;
> +		}
> +		ts_info = &dma->ts_info[ts_info_pos];
> +		ts_addr = ts_info->addr;
> +		ts_size = ts_info->size;
> +		ts_info_pos++;
> +		pr_debug("#%d i=%d, ts_info addr=0x%llx ts_size=%d\n", dma->adap->idx, i, ts_addr, ts_size);
> +		for (j = 0; j < ts_size / PT3_DMA_PAGE_SIZE; j++) {
> +			if (desc_remain < sizeof(struct pt3_dma_desc)) {
> +				if (unlikely(desc_info_pos >= dma->desc_count)) {
> +					pr_debug("#%d desc_info overflow max=%d curr=%d\n",
> +						dma->adap->idx, dma->desc_count, desc_info_pos);
> +					return;
> +				}
> +				desc_info = &dma->desc_info[desc_info_pos];
> +				desc_info->data_pos = 0;
> +				curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
> +				pr_debug("#%d desc_info_pos=%d ts_addr=0x%llx remain=%d\n",
> +					dma->adap->idx, desc_info_pos, ts_addr, desc_remain);
> +				desc_addr = desc_info->addr;
> +				desc_remain = desc_info->size;
> +				desc_info_pos++;
> +			}
> +			if (prev)
> +				prev->next_desc = desc_addr | 0b10;
> +			curr->page_addr = ts_addr           | 0b111;
> +			curr->page_size = PT3_DMA_PAGE_SIZE | 0b111;
> +			curr->next_desc = 0b10;
> +			pr_debug("#%d j=%d dma write desc ts_addr=0x%llx desc_info_pos=%d desc_remain=%d\n",
> +				dma->adap->idx, j, ts_addr, desc_info_pos, desc_remain);
> +			ts_addr += PT3_DMA_PAGE_SIZE;
> +
> +			prev = curr;
> +			desc_info->data_pos += sizeof(struct pt3_dma_desc);
> +			if (unlikely(desc_info->data_pos > desc_info->size)) {
> +				pr_debug("#%d dma desc_info data overflow max=%d curr=%d\n",
> +					dma->adap->idx, desc_info->size, desc_info->data_pos);
> +				return;
> +			}
> +			curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
> +			desc_addr += sizeof(struct pt3_dma_desc);
> +			desc_remain -= sizeof(struct pt3_dma_desc);
> +		}
> +	}
> +	if (prev)
> +		prev->next_desc = dma->desc_info->addr | 0b10;
> +}
> +
> +struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap)
> +{
> +	struct pt3_dma_page *page;
> +	u32 i;
> +
> +	struct pt3_dma *dma = kzalloc(sizeof(struct pt3_dma), GFP_KERNEL);
> +	if (!dma) {
> +		pr_debug("#%d fail allocate PT3_DMA\n", adap->idx);
> +		goto fail;
> +	}
> +	dma->adap = adap;
> +	dma->enabled = false;
> +	mutex_init(&dma->lock);
> +
> +	dma->ts_count = PT3_DMA_BLOCK_COUNT;
> +	dma->ts_info = kzalloc(sizeof(struct pt3_dma_page) * dma->ts_count, GFP_KERNEL);
> +	if (!dma->ts_info) {
> +		pr_debug("#%d fail allocate TS DMA page\n", adap->idx);
> +		goto fail;
> +	}
> +	pr_debug("#%d Alloc TS buf (ts_count %d)\n", adap->idx, dma->ts_count);
> +	for (i = 0; i < dma->ts_count; i++) {
> +		page = &dma->ts_info[i];
> +		page->size = PT3_DMA_BLOCK_SIZE;
> +		page->data_pos = 0;
> +		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
> +		if (!page->data) {
> +			pr_debug("#%d fail alloc_consistent. %d\n", adap->idx, i);
> +			goto fail;
> +		}
> +	}
> +
> +	dma->desc_count = 1 + (PT3_DMA_TS_BUF_SIZE / PT3_DMA_PAGE_SIZE - 1) / PT3_DMA_MAX_DESCS;
> +	dma->desc_info = kzalloc(sizeof(struct pt3_dma_page) * dma->desc_count, GFP_KERNEL);
> +	if (!dma->desc_info) {
> +		pr_debug("#%d fail allocate Desc DMA page\n", adap->idx);
> +		goto fail;
> +	}
> +	pr_debug("#%d Alloc Descriptor buf (desc_count %d)\n", adap->idx, dma->desc_count);
> +	for (i = 0; i < dma->desc_count; i++) {
> +		page = &dma->desc_info[i];
> +		page->size = PT3_DMA_PAGE_SIZE;
> +		page->data_pos = 0;
> +		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
> +		if (!page->data) {
> +			pr_debug("#%d fail alloc_consistent %d\n", adap->idx, i);
> +			goto fail;
> +		}
> +	}
> +
> +	pr_debug("#%d build page descriptor\n", adap->idx);
> +	pt3_dma_build_page_descriptor(dma);
> +	return dma;
> +fail:
> +	if (dma)
> +		pt3_dma_free(dma);
> +	return NULL;
> +}
> +
> +void __iomem *pt3_dma_get_base_addr(struct pt3_dma *dma)
> +{
> +	return dma->adap->pt3->bar_reg + REG_BASE + (0x18 * dma->adap->idx);
> +}
> +
> +void pt3_dma_reset(struct pt3_dma *dma)
> +{
> +	struct pt3_dma_page *ts;
> +	u32 i;
> +
> +	for (i = 0; i < dma->ts_count; i++) {
> +		ts = &dma->ts_info[i];
> +		memset(ts->data, 0, ts->size);
> +		ts->data_pos = 0;
> +		*ts->data = PT3_DMA_TS_NOT_SYNC;
> +	}
> +	dma->ts_pos = 0;
> +}
> +
> +void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled)
> +{
> +	void __iomem *base = pt3_dma_get_base_addr(dma);
> +	u64 start_addr = dma->desc_info->addr;
> +
> +	if (enabled) {
> +		pr_debug("#%d DMA enable start_addr=%llx\n", dma->adap->idx, start_addr);
> +		pt3_dma_reset(dma);
> +		writel(1 << 1, base + REG_DMA_CTL);	/* stop DMA */
> +		writel(PT3_SHIFT_MASK(start_addr,  0, 32), base + REG_DMA_DESC_L);
> +		writel(PT3_SHIFT_MASK(start_addr, 32, 32), base + REG_DMA_DESC_H);
> +		pr_debug("set descriptor address low %llx\n",  PT3_SHIFT_MASK(start_addr,  0, 32));
> +		pr_debug("set descriptor address high %llx\n", PT3_SHIFT_MASK(start_addr, 32, 32));
> +		writel(1 << 0, base + REG_DMA_CTL);	/* start DMA */
> +	} else {
> +		pr_debug("#%d DMA disable\n", dma->adap->idx);
> +		writel(1 << 1, base + REG_DMA_CTL);	/* stop DMA */
> +		while (1) {
> +			if (!PT3_SHIFT_MASK(readl(base + REG_STATUS), 0, 1))
> +				break;
> +			msleep_interruptible(1);
> +		}
> +	}
> +	dma->enabled = enabled;
> +}
> +
> +/* convert Gray code to binary, e.g. 1001 -> 1110 */
> +static u32 pt3_dma_gray2binary(u32 gray, u32 bit)
> +{
> +	u32 binary = 0, i, j, k;
> +
> +	for (i = 0; i < bit; i++) {
> +		k = 0;
> +		for (j = i; j < bit; j++)
> +			k = k ^ PT3_SHIFT_MASK(gray, j, 1);
> +		binary |= k << i;
> +	}
> +	return binary;
> +}
> +
> +u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma)
> +{
> +	return pt3_dma_gray2binary(readl(pt3_dma_get_base_addr(dma) + REG_TS_ERR), 32);
> +}
> +
> +void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval)
> +{
> +	void __iomem *base = pt3_dma_get_base_addr(dma);
> +	u32 data = mode | initval;
> +	pr_debug("set_test_mode base=%p data=0x%04x\n", base, data);
> +	writel(data, base + REG_TS_CTL);
> +}
> +
> +bool pt3_dma_ready(struct pt3_dma *dma)
> +{
> +	struct pt3_dma_page *ts;
> +	u8 *p;
> +
> +	u32 next = dma->ts_pos + 1;
> +	if (next >= dma->ts_count)
> +		next = 0;
> +	ts = &dma->ts_info[next];
> +	p = &ts->data[ts->data_pos];
> +
> +	if (*p == PT3_DMA_TS_SYNC)
> +		return true;
> +	if (*p == PT3_DMA_TS_NOT_SYNC)
> +		return false;
> +
> +	pr_debug("#%d invalid sync byte value=0x%02x ts_pos=%d data_pos=%d curr=0x%02x\n",
> +		dma->adap->idx, *p, next, ts->data_pos, dma->ts_info[dma->ts_pos].data[0]);
> +	return false;
> +}
> +
> +ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux)
> +{
> +	bool ready;
> +	struct pt3_dma_page *ts;
> +	u32 i, prev;
> +	size_t csize, remain = dma->ts_info[dma->ts_pos].size;
> +
> +	mutex_lock(&dma->lock);
> +	pr_debug("#%d dma_copy ts_pos=0x%x data_pos=0x%x\n",
> +		   dma->adap->idx, dma->ts_pos, dma->ts_info[dma->ts_pos].data_pos);
> +	for (;;) {
> +		for (i = 0; i < 20; i++) {
> +			ready = pt3_dma_ready(dma);
> +			if (ready)
> +				break;
> +			msleep_interruptible(30);
> +		}
> +		if (!ready) {
> +			pr_debug("#%d dma_copy NOT READY\n", dma->adap->idx);
> +			goto last;
> +		}
> +		prev = dma->ts_pos - 1;
> +		if (prev < 0 || dma->ts_count <= prev)
> +			prev = dma->ts_count - 1;
> +		if (dma->ts_info[prev].data[0] != PT3_DMA_TS_NOT_SYNC)
> +			pr_debug("#%d DMA buffer overflow. prev=%d data=0x%x\n",
> +					dma->adap->idx, prev, dma->ts_info[prev].data[0]);
> +		ts = &dma->ts_info[dma->ts_pos];
> +		for (;;) {
> +			csize = (remain < (ts->size - ts->data_pos)) ?
> +				 remain : (ts->size - ts->data_pos);
> +			pt3_filter(dma->adap, demux, &ts->data[ts->data_pos], csize);
> +			remain -= csize;
> +			ts->data_pos += csize;
> +			if (ts->data_pos >= ts->size) {
> +				ts->data_pos = 0;
> +				ts->data[ts->data_pos] = PT3_DMA_TS_NOT_SYNC;
> +				dma->ts_pos++;
> +				if (dma->ts_pos >= dma->ts_count)
> +					dma->ts_pos = 0;
> +				break;
> +			}
> +			if (remain <= 0)
> +				goto last;
> +		}
> +	}
> +last:
> +	mutex_unlock(&dma->lock);
> +	return dma->ts_info[dma->ts_pos].size - remain;
> +}
> +
> +u32 pt3_dma_get_status(struct pt3_dma *dma)
> +{
> +	return readl(pt3_dma_get_base_addr(dma) + REG_STATUS);
> +}
> +
> diff --git a/drivers/media/pci/pt3/pt3_dma.h b/drivers/media/pci/pt3/pt3_dma.h
> new file mode 100644
> index 0000000..ecae4c1
> --- /dev/null
> +++ b/drivers/media/pci/pt3/pt3_dma.h
> @@ -0,0 +1,48 @@
> +/*
> + * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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	__PT3_DMA_H__
> +#define	__PT3_DMA_H__
> +
> +struct pt3_dma_page {
> +	dma_addr_t addr;
> +	u8 *data;
> +	u32 size, data_pos;
> +};
> +
> +enum pt3_dma_mode {
> +	USE_LFSR = 1 << 16,
> +	REVERSE  = 1 << 17,
> +	RESET    = 1 << 18,
> +};
> +
> +struct pt3_dma {
> +	struct pt3_adapter *adap;
> +	bool enabled;
> +	u32 ts_pos, ts_count, desc_count;
> +	struct pt3_dma_page *ts_info, *desc_info;
> +	struct mutex lock;
> +};
> +
> +ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux);
> +struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap);
> +void pt3_dma_free(struct pt3_dma *dma);
> +u32 pt3_dma_get_status(struct pt3_dma *dma);
> +u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma);
> +void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled);
> +void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval);
> +
> +#endif
> diff --git a/drivers/media/pci/pt3/pt3_i2c.c b/drivers/media/pci/pt3/pt3_i2c.c
> new file mode 100644
> index 0000000..7f0eb7c
> --- /dev/null
> +++ b/drivers/media/pci/pt3/pt3_i2c.c
> @@ -0,0 +1,167 @@
> +/*
> + * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 "pt3_common.h"
> +#include "pt3_i2c.h"
> +
> +enum pt3_i2c_cmd {
> +	I_END,
> +	I_ADDRESS,
> +	I_CLOCK_L,
> +	I_CLOCK_H,
> +	I_DATA_L,
> +	I_DATA_H,
> +	I_RESET,
> +	I_SLEEP,
> +	I_DATA_L_NOP  = 0x08,
> +	I_DATA_H_NOP  = 0x0c,
> +	I_DATA_H_READ = 0x0d,
> +	I_DATA_H_ACK0 = 0x0e,
> +	I_DATA_H_ACK1 = 0x0f,
> +};
> +
> +bool pt3_i2c_is_clean(struct pt3_board *pt3)
> +{
> +	return PT3_SHIFT_MASK(readl(pt3->bar_reg + REG_I2C_R), 3, 1);
> +}
> +
> +void pt3_i2c_reset(struct pt3_board *pt3)
> +{
> +	writel(1 << 17, pt3->bar_reg + REG_I2C_W);	/* 0x00020000 */
> +}
> +
> +void pt3_i2c_wait(struct pt3_board *pt3, u32 *status)
> +{
> +	u32 val;
> +
> +	while (1) {
> +		val = readl(pt3->bar_reg + REG_I2C_R);
> +		if (!PT3_SHIFT_MASK(val, 0, 1))		/* sequence stopped */
> +			break;
> +		msleep_interruptible(1);
> +	}
> +	if (status)
> +		*status = val;				/* I2C register status */
> +}
> +
> +void pt3_i2c_mem_write(struct pt3_board *pt3, u8 data)
> +{
> +	void __iomem *dst = pt3->bar_mem + PT3_I2C_DATA_OFFSET + pt3->i2c_addr;
> +
> +	if (pt3->i2c_filled) {
> +		pt3->i2c_buf |= data << 4;
> +		writeb(pt3->i2c_buf, dst);
> +		pt3->i2c_addr++;
> +	} else
> +		pt3->i2c_buf = data;
> +	pt3->i2c_filled ^= true;
> +}
> +
> +void pt3_i2c_start(struct pt3_board *pt3)
> +{
> +	pt3_i2c_mem_write(pt3, I_DATA_H);
> +	pt3_i2c_mem_write(pt3, I_CLOCK_H);
> +	pt3_i2c_mem_write(pt3, I_DATA_L);
> +	pt3_i2c_mem_write(pt3, I_CLOCK_L);
> +}
> +
> +void pt3_i2c_cmd_write(struct pt3_board *pt3, const u8 *data, u32 size)
> +{
> +	u32 i, j;
> +	u8 byte;
> +
> +	for (i = 0; i < size; i++) {
> +		byte = data[i];
> +		for (j = 0; j < 8; j++)
> +			pt3_i2c_mem_write(pt3, PT3_SHIFT_MASK(byte, 7 - j, 1) ? I_DATA_H_NOP : I_DATA_L_NOP);
> +		pt3_i2c_mem_write(pt3, I_DATA_H_ACK0);
> +	}
> +}
> +
> +void pt3_i2c_cmd_read(struct pt3_board *pt3, u8 *data, u32 size)
> +{
> +	u32 i, j;
> +
> +	for (i = 0; i < size; i++) {
> +		for (j = 0; j < 8; j++)
> +			pt3_i2c_mem_write(pt3, I_DATA_H_READ);
> +		if (i == (size - 1))
> +			pt3_i2c_mem_write(pt3, I_DATA_H_NOP);
> +		else
> +			pt3_i2c_mem_write(pt3, I_DATA_L_NOP);
> +	}
> +}
> +
> +void pt3_i2c_stop(struct pt3_board *pt3)
> +{
> +	pt3_i2c_mem_write(pt3, I_DATA_L);
> +	pt3_i2c_mem_write(pt3, I_CLOCK_H);
> +	pt3_i2c_mem_write(pt3, I_DATA_H);
> +}
> +
> +int pt3_i2c_flush(struct pt3_board *pt3, bool end, u32 start_addr)
> +{
> +	u32 status;
> +
> +	if (end) {
> +		pt3_i2c_mem_write(pt3, I_END);
> +		if (pt3->i2c_filled)
> +			pt3_i2c_mem_write(pt3, I_END);
> +	}
> +	pt3_i2c_wait(pt3, &status);
> +	writel(1 << 16 | start_addr, pt3->bar_reg + REG_I2C_W);	/* 0x00010000 start sequence */
> +	pt3_i2c_wait(pt3, &status);
> +	if (PT3_SHIFT_MASK(status, 1, 2)) {			/* ACK status */
> +		pr_err("%s failed, status=0x%x\n", __func__, status);
> +		return -EIO;
> +	}
> +	return 0;
> +}
> +
> +u32 pt3_i2c_func(struct i2c_adapter *adap)
> +{
> +	return I2C_FUNC_I2C;
> +}
> +
> +int pt3_i2c_xfer(struct i2c_adapter *i2c, struct i2c_msg *msg, int num)
> +{
> +	struct pt3_board *pt3 = i2c_get_adapdata(i2c);
> +	int i, j;
> +
> +	if ((num < 1) || (num > 3) || !msg || msg[0].flags)	/* always write first */
> +		return -ENOTSUPP;
> +	mutex_lock(&pt3->lock);
> +	pt3->i2c_addr = 0;
> +	for (i = 0; i < num; i++) {
> +		u8 byte = (msg[i].addr << 1) | (msg[i].flags & 1);
> +		pt3_i2c_start(pt3);
> +		pt3_i2c_cmd_write(pt3, &byte, 1);
> +		if (msg[i].flags == I2C_M_RD)
> +			pt3_i2c_cmd_read(pt3, msg[i].buf, msg[i].len);
> +		else
> +			pt3_i2c_cmd_write(pt3, msg[i].buf, msg[i].len);
> +	}
> +	pt3_i2c_stop(pt3);
> +	if (pt3_i2c_flush(pt3, true, 0))
> +		return -EIO;
> +	for (i = 1; i < num; i++)
> +		if (msg[i].flags == I2C_M_RD)
> +			for (j = 0; j < msg[i].len; j++)
> +				msg[i].buf[j] = readb(pt3->bar_mem + PT3_I2C_DATA_OFFSET + j);
> +	mutex_unlock(&pt3->lock);
> +	return num;
> +}
> +
> diff --git a/drivers/media/pci/pt3/pt3_i2c.h b/drivers/media/pci/pt3/pt3_i2c.h
> new file mode 100644
> index 0000000..5e61f17
> --- /dev/null
> +++ b/drivers/media/pci/pt3/pt3_i2c.h
> @@ -0,0 +1,29 @@
> +/*
> + * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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	__PT3_I2C_H__
> +#define	__PT3_I2C_H__
> +
> +#define PT3_I2C_DATA_OFFSET	2048
> +#define PT3_I2C_START_ADDR	0x17fa
> +
> +int pt3_i2c_flush(struct pt3_board *pt3, bool end, u32 start_addr);
> +bool pt3_i2c_is_clean(struct pt3_board *pt3);
> +void pt3_i2c_reset(struct pt3_board *pt3);
> +u32 pt3_i2c_func(struct i2c_adapter *adap);
> +int pt3_i2c_xfer(struct i2c_adapter *i2c, struct i2c_msg *msg, int num);
> +
> +#endif
> diff --git a/drivers/media/tuners/Kconfig b/drivers/media/tuners/Kconfig
> index 15665de..999e70c 100644
> --- a/drivers/media/tuners/Kconfig
> +++ b/drivers/media/tuners/Kconfig
> @@ -235,4 +235,11 @@ config MEDIA_TUNER_R820T
>   	default m if !MEDIA_SUBDRV_AUTOSELECT
>   	help
>   	  Rafael Micro R820T silicon tuner driver.
> +
> +config MEDIA_TUNER_PT3
> +	tristate "Demodulator/Tuners for Earthsoft PT3 ISDB-S/T"
> +	depends on MEDIA_SUPPORT && I2C
> +	default m if !MEDIA_SUBDRV_AUTOSELECT
> +	help
> +	  Earthsoft PT3 demodulator/tuner driver: ISDB-S QM1D1C0042, ISDB-T MxL301RF, TC90522
>   endmenu
> diff --git a/drivers/media/tuners/Makefile b/drivers/media/tuners/Makefile
> index 308f108..de6a87e 100644
> --- a/drivers/media/tuners/Makefile
> +++ b/drivers/media/tuners/Makefile
> @@ -3,6 +3,7 @@
>   #
>
>   tda18271-objs := tda18271-maps.o tda18271-common.o tda18271-fe.o
> +pt3_tuner-objs := mxl301rf.o qm1d1c0042.o tc90522.o
>
>   obj-$(CONFIG_MEDIA_TUNER_XC2028) += tuner-xc2028.o
>   obj-$(CONFIG_MEDIA_TUNER_SIMPLE) += tuner-simple.o
> @@ -36,6 +37,7 @@ obj-$(CONFIG_MEDIA_TUNER_FC0012) += fc0012.o
>   obj-$(CONFIG_MEDIA_TUNER_FC0013) += fc0013.o
>   obj-$(CONFIG_MEDIA_TUNER_IT913X) += tuner_it913x.o
>   obj-$(CONFIG_MEDIA_TUNER_R820T) += r820t.o
> +obj-$(CONFIG_MEDIA_TUNER_PT3) += pt3_tuner.o
>
>   ccflags-y += -I$(srctree)/drivers/media/dvb-core
>   ccflags-y += -I$(srctree)/drivers/media/dvb-frontends
> diff --git a/drivers/media/tuners/mxl301rf.c b/drivers/media/tuners/mxl301rf.c
> new file mode 100644
> index 0000000..8ecf263
> --- /dev/null
> +++ b/drivers/media/tuners/mxl301rf.c
> @@ -0,0 +1,347 @@
> +/*
> + * MxL301RF MaxLinear CMOS Hybrid TV ISDB-T tuner driver for Earthsoft PT3
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 "pt3_common.h"
> +#include "tc90522.h"
> +
> +MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
> +MODULE_DESCRIPTION("Earthsoft PT3 MxL301RF MaxLinear CMOS Hybrid TV ISDB-T tuner driver");
> +MODULE_LICENSE("GPL");
> +
> +static struct {
> +	u32	freq,		/* Channel center frequency @ kHz	*/
> +		freq_th;	/* Offset frequency threshold @ kHz	*/
> +	u8	shf_val,	/* Spur shift value			*/
> +		shf_dir;	/* Spur shift direction			*/
> +} SHF_DVBT_TAB[] = {
> +	{  64500, 500, 0x92, 0x07 },
> +	{ 191500, 300, 0xE2, 0x07 },
> +	{ 205500, 500, 0x2C, 0x04 },
> +	{ 212500, 500, 0x1E, 0x04 },
> +	{ 226500, 500, 0xD4, 0x07 },
> +	{  99143, 500, 0x9C, 0x07 },
> +	{ 173143, 500, 0xD4, 0x07 },
> +	{ 191143, 300, 0xD4, 0x07 },
> +	{ 207143, 500, 0xCE, 0x07 },
> +	{ 225143, 500, 0xCE, 0x07 },
> +	{ 243143, 500, 0xD4, 0x07 },
> +	{ 261143, 500, 0xD4, 0x07 },
> +	{ 291143, 500, 0xD4, 0x07 },
> +	{ 339143, 500, 0x2C, 0x04 },
> +	{ 117143, 500, 0x7A, 0x07 },
> +	{ 135143, 300, 0x7A, 0x07 },
> +	{ 153143, 500, 0x01, 0x07 }
> +};
> +
> +static void mxl301rf_rftune(u8 *data, u32 *size, u32 freq)
> +{
> +	u32 dig_rf_freq, tmp, frac_divider, kHz, MHz, i;
> +	u8 rf_data[] = {
> +		0x13, 0x00,	/* abort tune			*/
> +		0x3B, 0xC0,
> +		0x3B, 0x80,
> +		0x10, 0x95,	/* BW				*/
> +		0x1A, 0x05,
> +		0x61, 0x00,
> +		0x62, 0xA0,
> +		0x11, 0x40,	/* 2 bytes to store RF freq.	*/
> +		0x12, 0x0E,	/* 2 bytes to store RF freq.	*/
> +		0x13, 0x01	/* start tune			*/
> +	};
> +
> +	dig_rf_freq = 0;
> +	tmp = 0;
> +	frac_divider = 1000000;
> +	kHz = 1000;
> +	MHz = 1000000;
> +
> +	dig_rf_freq = freq / MHz;
> +	tmp = freq % MHz;
> +
> +	for (i = 0; i < 6; i++) {
> +		dig_rf_freq <<= 1;
> +		frac_divider /= 2;
> +		if (tmp > frac_divider) {
> +			tmp -= frac_divider;
> +			dig_rf_freq++;
> +		}
> +	}
> +	if (tmp > 7812)
> +		dig_rf_freq++;
> +
> +	rf_data[2 * (7) + 1] = (u8)(dig_rf_freq);
> +	rf_data[2 * (8) + 1] = (u8)(dig_rf_freq >> 8);
> +
> +	for (i = 0; i < sizeof(SHF_DVBT_TAB)/sizeof(*SHF_DVBT_TAB); i++) {
> +		if ((freq >= (SHF_DVBT_TAB[i].freq - SHF_DVBT_TAB[i].freq_th) * kHz) &&
> +				(freq <= (SHF_DVBT_TAB[i].freq + SHF_DVBT_TAB[i].freq_th) * kHz)) {
> +			rf_data[2 * (5) + 1] = SHF_DVBT_TAB[i].shf_val;
> +			rf_data[2 * (6) + 1] = 0xa0 | SHF_DVBT_TAB[i].shf_dir;
> +			break;
> +		}
> +	}
> +	memcpy(data, rf_data, sizeof(rf_data));
> +	*size = sizeof(rf_data);
> +
> +	pr_debug("mx_rftune freq=%d\n", freq);
> +}
> +
> +/* write via demodulator */
> +static void mxl301rf_write(struct pt3_adapter *adap, u8 *data, size_t size)
> +{
> +	tc90522_write_tuner_without_addr(adap, data, size);
> +}
> +
> +static void mxl301rf_standby(struct pt3_adapter *adap)
> +{
> +	u8 data[4] = {0x01, 0x00, 0x13, 0x00};
> +	mxl301rf_write(adap, data, sizeof(data));
> +}
> +
> +static void mxl301rf_set_register(struct pt3_adapter *adap, u8 addr, u8 value)
> +{
> +	u8 data[2] = {addr, value};
> +	mxl301rf_write(adap, data, sizeof(data));
> +}
> +
> +static void mxl301rf_idac_setting(struct pt3_adapter *adap)
> +{
> +	u8 data[] = {
> +		0x0D, 0x00,
> +		0x0C, 0x67,
> +		0x6F, 0x89,
> +		0x70, 0x0C,
> +		0x6F, 0x8A,
> +		0x70, 0x0E,
> +		0x6F, 0x8B,
> +		0x70, 0x10+12,
> +	};
> +	mxl301rf_write(adap, data, sizeof(data));
> +}
> +
> +void mxl301rf_tuner_rftune(struct pt3_adapter *adap, u32 freq)
> +{
> +	u8 data[100];
> +	u32 size;
> +
> +	size = 0;
> +	adap->freq = freq;
> +	mxl301rf_rftune(data, &size, freq);
> +	if (size != 20) {
> +		pr_debug("fail mx_rftune size = %d\n", size);
> +		return;
> +	}
> +	mxl301rf_write(adap, data, 14);
> +	msleep_interruptible(1);
> +	mxl301rf_write(adap, data + 14, 6);
> +	msleep_interruptible(1);
> +	mxl301rf_set_register(adap, 0x1a, 0x0d);
> +	mxl301rf_idac_setting(adap);
> +}
> +
> +static void mxl301rf_wakeup(struct pt3_adapter *adap)
> +{
> +	u8 data[2] = {0x01, 0x01};
> +
> +	mxl301rf_write(adap, data, sizeof(data));
> +	mxl301rf_tuner_rftune(adap, adap->freq);
> +}
> +
> +static void mxl301rf_set_sleep_mode(struct pt3_adapter *adap, bool sleep)
> +{
> +	if (sleep)
> +		mxl301rf_standby(adap);
> +	else
> +		mxl301rf_wakeup(adap);
> +}
> +
> +int mxl301rf_set_sleep(struct pt3_adapter *adap, bool sleep)
> +{
> +	int ret;
> +
> +	if (sleep) {
> +		ret = tc90522_set_agc(adap, TC90522_AGC_MANUAL);
> +		if (ret)
> +			return ret;
> +		mxl301rf_set_sleep_mode(adap, sleep);
> +		tc90522_write_sleep_time(adap, sleep);
> +	} else {
> +		tc90522_write_sleep_time(adap, sleep);
> +		mxl301rf_set_sleep_mode(adap, sleep);
> +	}
> +	adap->sleep = sleep;
> +	return 0;
> +}
> +
> +static u8 MXL301RF_FREQ_TABLE[][3] = {
> +	{   2, 0,  3 },
> +	{  12, 1, 22 },
> +	{  21, 0, 12 },
> +	{  62, 1, 63 },
> +	{ 112, 0, 62 }
> +};
> +
> +void mxl301rf_get_channel_frequency(struct pt3_adapter *adap, u32 channel, bool *catv, u32 *number, u32 *freq)
> +{
> +	u32 i;
> +	s32 freq_offset = 0;
> +
> +	if (12 <= channel)
> +		freq_offset += 2;
> +	if (17 <= channel)
> +		freq_offset -= 2;
> +	if (63 <= channel)
> +		freq_offset += 2;
> +	*freq = 93 + channel * 6 + freq_offset;
> +
> +	for (i = 0; i < sizeof(MXL301RF_FREQ_TABLE) / sizeof(*MXL301RF_FREQ_TABLE); i++) {
> +		if (channel <= MXL301RF_FREQ_TABLE[i][0]) {
> +			*catv = MXL301RF_FREQ_TABLE[i][1] ? true : false;
> +			*number = channel + MXL301RF_FREQ_TABLE[i][2] - MXL301RF_FREQ_TABLE[i][0];
> +			break;
> +		}
> +	}
> +}
> +
> +static u32 MXL301RF_RF_TABLE[112] = {
> +	0x058d3f49, 0x05e8ccc9, 0x06445a49, 0x069fe7c9, 0x06fb7549,
> +	0x075702c9, 0x07b29049, 0x080e1dc9, 0x0869ab49, 0x08c538c9,
> +	0x0920c649, 0x097c53c9, 0x09f665c9, 0x0a51f349, 0x0aad80c9,
> +	0x0b090e49, 0x0b649bc9, 0x0ba1a4c9, 0x0bfd3249, 0x0c58bfc9,
> +	0x0cb44d49, 0x0d0fdac9, 0x0d6b6849, 0x0dc6f5c9, 0x0e228349,
> +	0x0e7e10c9, 0x0ed99e49, 0x0f352bc9, 0x0f90b949, 0x0fec46c9,
> +	0x1047d449, 0x10a361c9, 0x10feef49, 0x115a7cc9, 0x11b60a49,
> +	0x121197c9, 0x126d2549, 0x12c8b2c9, 0x13244049, 0x137fcdc9,
> +	0x13db5b49, 0x1436e8c9, 0x14927649, 0x14ee03c9, 0x15499149,
> +	0x15a51ec9, 0x1600ac49, 0x165c39c9, 0x16b7c749, 0x171354c9,
> +	0x176ee249, 0x17ca6fc9, 0x1825fd49, 0x18818ac9, 0x18dd1849,
> +	0x1938a5c9, 0x19943349, 0x19efc0c9, 0x1a4b4e49, 0x1aa6dbc9,
> +	0x1b026949, 0x1b5df6c9, 0x1bb98449, 0x1c339649, 0x1c8f23c9,
> +	0x1ceab149, 0x1d463ec9, 0x1da1cc49, 0x1dfd59c9, 0x1e58e749,
> +	0x1eb474c9, 0x1f100249, 0x1f6b8fc9, 0x1fc71d49, 0x2022aac9,
> +	0x207e3849, 0x20d9c5c9, 0x21355349, 0x2190e0c9, 0x21ec6e49,
> +	0x2247fbc9, 0x22a38949, 0x22ff16c9, 0x235aa449, 0x23b631c9,
> +	0x2411bf49, 0x246d4cc9, 0x24c8da49, 0x252467c9, 0x257ff549,
> +	0x25db82c9, 0x26371049, 0x26929dc9, 0x26ee2b49, 0x2749b8c9,
> +	0x27a54649, 0x2800d3c9, 0x285c6149, 0x28b7eec9, 0x29137c49,
> +	0x296f09c9, 0x29ca9749, 0x2a2624c9, 0x2a81b249, 0x2add3fc9,
> +	0x2b38cd49, 0x2b945ac9, 0x2befe849, 0x2c4b75c9, 0x2ca70349,
> +	0x2d0290c9, 0x2d5e1e49,
> +};
> +
> +/* read via demodulator */
> +static void mxl301rf_read(struct pt3_adapter *adap, u8 addr, u8 *data)
> +{
> +	u8 write[2] = {0xfb, addr};
> +
> +	tc90522_write_tuner_without_addr(adap, write, sizeof(write));
> +	tc90522_read_tuner_without_addr(adap, data);
> +}
> +
> +static void mxl301rf_rfsynth_lock_status(struct pt3_adapter *adap, bool *locked)
> +{
> +	u8 data;
> +
> +	*locked = false;
> +	mxl301rf_read(adap, 0x16, &data);
> +	data &= 0x0c;
> +	if (data == 0x0c)
> +		*locked = true;
> +}
> +
> +static void mxl301rf_refsynth_lock_status(struct pt3_adapter *adap, bool *locked)
> +{
> +	u8 data;
> +
> +	*locked = false;
> +	mxl301rf_read(adap, 0x16, &data);
> +	data &= 0x03;
> +	if (data == 0x03)
> +		*locked = true;
> +}
> +
> +bool mxl301rf_locked(struct pt3_adapter *adap)
> +{
> +	bool locked1 = false, locked2 = false;
> +	struct timeval begin, now;
> +
> +	do_gettimeofday(&begin);
> +	while (1) {
> +		do_gettimeofday(&now);
> +		mxl301rf_rfsynth_lock_status(adap, &locked1);
> +		mxl301rf_refsynth_lock_status(adap, &locked2);
> +		if (locked1 && locked2)
> +			break;
> +		if (tc90522_time_diff(&begin, &now) > 1000)
> +			break;
> +		msleep_interruptible(1);
> +	}
> +	pr_debug("#%d mx locked1=%d locked2=%d\n", adap->idx, locked1, locked2);
> +	return locked1 && locked2;
> +}
> +
> +int mxl301rf_set_frequency(struct pt3_adapter *adap, u32 channel, s32 offset)
> +{
> +	bool catv;
> +	u32 number, freq, real_freq;
> +	int ret = tc90522_set_agc(adap, TC90522_AGC_MANUAL);
> +
> +	if (ret)
> +		return ret;
> +	mxl301rf_get_channel_frequency(adap, channel, &catv, &number, &freq);
> +	pr_debug("#%d ch%d%s no%d %dHz\n", adap->idx, channel, catv ? " CATV" : "", number, freq);
> +	/* real_freq = (7 * freq + 1 + offset) * 1000000.0/7.0; */
> +	real_freq = MXL301RF_RF_TABLE[channel];
> +
> +	mxl301rf_tuner_rftune(adap, real_freq);
> +
> +	return (!mxl301rf_locked(adap)) ? -ETIMEDOUT : tc90522_set_agc(adap, TC90522_AGC_AUTO);
> +}
> +
> +#define MXL301RF_NHK (MXL301RF_RF_TABLE[77])
> +int mxl301rf_freq(int freq)
> +{
> +	if (freq >= 90000000)
> +		return freq;					/* real_freq	*/
> +	if (freq > 255)
> +		return MXL301RF_NHK;
> +	if (freq > 127)
> +		return MXL301RF_RF_TABLE[freq - 128];		/* freqno (IO#)	*/
> +	if (freq > 63) {					/* CATV		*/
> +		freq -= 64;
> +		if (freq > 22)
> +			return MXL301RF_RF_TABLE[freq - 1];	/* C23-C62	*/
> +		if (freq > 12)
> +			return MXL301RF_RF_TABLE[freq - 10];	/* C13-C22	*/
> +		return MXL301RF_NHK;
> +	}
> +	if (freq > 62)
> +		return MXL301RF_NHK;
> +	if (freq > 12)
> +		return MXL301RF_RF_TABLE[freq + 50];		/* 13-62	*/
> +	if (freq >  3)
> +		return MXL301RF_RF_TABLE[freq +  9];		/*  4-12	*/
> +	if (freq)
> +		return MXL301RF_RF_TABLE[freq -  1];		/*  1-3		*/
> +	return MXL301RF_NHK;
> +}
> +
> +EXPORT_SYMBOL(mxl301rf_set_frequency);
> +EXPORT_SYMBOL(mxl301rf_set_sleep);
> +EXPORT_SYMBOL(mxl301rf_freq);
> +EXPORT_SYMBOL(mxl301rf_tuner_rftune);
> +EXPORT_SYMBOL(mxl301rf_locked);
> +
> diff --git a/drivers/media/tuners/mxl301rf.h b/drivers/media/tuners/mxl301rf.h
> new file mode 100644
> index 0000000..a025494
> --- /dev/null
> +++ b/drivers/media/tuners/mxl301rf.h
> @@ -0,0 +1,27 @@
> +/*
> + * MxL301RF MaxLinear CMOS Hybrid TV ISDB-T tuner driver for Earthsoft PT3
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 __MXL301RF_H__
> +#define __MXL301RF_H__
> +
> +int mxl301rf_set_frequency(struct pt3_adapter *adap, u32 channel, s32 offset);
> +int mxl301rf_set_sleep(struct pt3_adapter *adap, bool sleep);
> +int mxl301rf_freq(int freq);
> +void mxl301rf_tuner_rftune(struct pt3_adapter *adap, u32 freq);
> +bool mxl301rf_locked(struct pt3_adapter *adap);
> +
> +#endif
> +
> diff --git a/drivers/media/tuners/qm1d1c0042.c b/drivers/media/tuners/qm1d1c0042.c
> new file mode 100644
> index 0000000..eb7c869
> --- /dev/null
> +++ b/drivers/media/tuners/qm1d1c0042.c
> @@ -0,0 +1,413 @@
> +/*
> + * QM1D1C0042 ISDB-S tuner driver for Earthsoft PT3
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 "pt3_common.h"
> +#include "tc90522.h"
> +#include "qm1d1c0042.h"
> +
> +MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
> +MODULE_DESCRIPTION("Earthsoft PT3 QM1D1C0042 ISDB-S tuner driver");
> +MODULE_LICENSE("GPL");
> +
> +static u8 qm1d1c0042_reg_rw[] = {
> +	0x48, 0x1c, 0xa0, 0x10, 0xbc, 0xc5, 0x20, 0x33,
> +	0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
> +	0x00, 0xff, 0xf3, 0x00, 0x2a, 0x64, 0xa6, 0x86,
> +	0x8c, 0xcf, 0xb8, 0xf1, 0xa8, 0xf2, 0x89, 0x00,
> +};
> +
> +void qm1d1c0042_init_reg_param(struct qm1d1c0042 *qm)
> +{
> +	memcpy(qm->reg, qm1d1c0042_reg_rw, sizeof(qm1d1c0042_reg_rw));
> +
> +	qm->adap->freq = 0;
> +	qm->standby = false;
> +	qm->wait_time_lpf = 20;
> +	qm->wait_time_search_fast = 4;
> +	qm->wait_time_search_normal = 15;
> +}
> +
> +/* read via demodulator */
> +static int qm1d1c0042_read(struct qm1d1c0042 *qm, u8 addr, u8 *data)
> +{
> +	int ret = 0;
> +	if ((addr == 0x00) || (addr == 0x0d))
> +		ret = tc90522_read_tuner(qm->adap, addr, data);
> +	return ret;
> +}
> +
> +/* write via demodulator */
> +static int qm1d1c0042_write(struct qm1d1c0042 *qm, u8 addr, u8 data)
> +{
> +	int ret = tc90522_write_tuner(qm->adap, addr, &data, sizeof(data));
> +	qm->reg[addr] = data;
> +	return ret;
> +}
> +
> +#define QM1D1C0042_INIT_DUMMY_RESET 0x0c
> +
> +void qm1d1c0042_dummy_reset(struct qm1d1c0042 *qm)
> +{
> +	qm1d1c0042_write(qm, 0x01, QM1D1C0042_INIT_DUMMY_RESET);
> +	qm1d1c0042_write(qm, 0x01, QM1D1C0042_INIT_DUMMY_RESET);
> +}
> +
> +static u8 qm1d1c0042_flag[32] = {
> +	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
> +	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
> +};
> +
> +static int qm1d1c0042_set_sleep_mode(struct qm1d1c0042 *qm)
> +{
> +	int ret;
> +
> +	if (qm->standby) {
> +		qm->reg[0x01] &= (~(1 << 3)) & 0xff;
> +		qm->reg[0x01] |= 1 << 0;
> +		qm->reg[0x05] |= 1 << 3;
> +
> +		ret = qm1d1c0042_write(qm, 0x05, qm->reg[0x05]);
> +		if (ret)
> +			return ret;
> +		ret = qm1d1c0042_write(qm, 0x01, qm->reg[0x01]);
> +		if (ret)
> +			return ret;
> +	} else {
> +		qm->reg[0x01] |= 1 << 3;
> +		qm->reg[0x01] &= (~(1 << 0)) & 0xff;
> +		qm->reg[0x05] &= (~(1 << 3)) & 0xff;
> +
> +		ret = qm1d1c0042_write(qm, 0x01, qm->reg[0x01]);
> +		if (ret)
> +			return ret;
> +		ret = qm1d1c0042_write(qm, 0x05, qm->reg[0x05]);
> +		if (ret)
> +			return ret;
> +	}
> +	return ret;
> +}
> +
> +static int qm1d1c0042_set_search_mode(struct qm1d1c0042 *qm)
> +{
> +	qm->reg[3] &= 0xfe;
> +	return qm1d1c0042_write(qm, 0x03, qm->reg[3]);
> +}
> +
> +int qm1d1c0042_init(struct qm1d1c0042 *qm)
> +{
> +	u8 i_data;
> +	u32 i;
> +	int ret;
> +
> +	/* soft reset on */
> +	ret = qm1d1c0042_write(qm, 0x01, QM1D1C0042_INIT_DUMMY_RESET);
> +	if (ret)
> +		return ret;
> +
> +	msleep_interruptible(1);
> +
> +	/* soft reset off */
> +	i_data = qm->reg[0x01] | 0x10;
> +	ret = qm1d1c0042_write(qm, 0x01, i_data);
> +	if (ret)
> +		return ret;
> +
> +	/* ID check */
> +	ret = qm1d1c0042_read(qm, 0x00, &i_data);
> +	if (ret)
> +		return ret;
> +	if (i_data != 0x48)
> +		return -EINVAL;
> +
> +	/* LPF tuning on */
> +	msleep_interruptible(1);
> +	qm->reg[0x0c] |= 0x40;
> +	ret = qm1d1c0042_write(qm, 0x0c, qm->reg[0x0c]);
> +	if (ret)
> +		return ret;
> +	msleep_interruptible(qm->wait_time_lpf);
> +
> +	for (i = 0; i < sizeof(qm1d1c0042_flag); i++)
> +		if (qm1d1c0042_flag[i] == 1) {
> +			ret = qm1d1c0042_write(qm, i, qm->reg[i]);
> +			if (ret)
> +				return ret;
> +		}
> +	ret = qm1d1c0042_set_sleep_mode(qm);
> +	if (ret)
> +		return ret;
> +	return qm1d1c0042_set_search_mode(qm);
> +}
> +
> +int qm1d1c0042_set_sleep(struct qm1d1c0042 *qm, bool sleep)
> +{
> +	qm->standby = sleep;
> +	if (sleep) {
> +		int ret = tc90522_set_agc(qm->adap, TC90522_AGC_MANUAL);
> +		if (ret)
> +			return ret;
> +		qm1d1c0042_set_sleep_mode(qm);
> +		tc90522_set_sleep_s(qm->adap, sleep);
> +	} else {
> +		tc90522_set_sleep_s(qm->adap, sleep);
> +		qm1d1c0042_set_sleep_mode(qm);
> +	}
> +	qm->adap->sleep = sleep;
> +	return 0;
> +}
> +
> +void qm1d1c0042_get_channel_freq(u32 channel, u32 *number, u32 *freq)
> +{
> +	if (channel < 12) {
> +		*number = 1 + 2 * channel;
> +		*freq = 104948 + 3836 * channel;
> +	} else if (channel < 24) {
> +		channel -= 12;
> +		*number = 2 + 2 * channel;
> +		*freq = 161300 + 4000 * channel;
> +	} else {
> +		channel -= 24;
> +		*number = 1 + 2 * channel;
> +		*freq = 159300 + 4000 * channel;
> +	}
> +}
> +
> +static u32 QM1D1C0042_FREQ_TABLE[9][3] = {
> +	{ 2151000, 1, 7 },
> +	{ 1950000, 1, 6 },
> +	{ 1800000, 1, 5 },
> +	{ 1600000, 1, 4 },
> +	{ 1450000, 1, 3 },
> +	{ 1250000, 1, 2 },
> +	{ 1200000, 0, 7 },
> +	{  975000, 0, 6 },
> +	{  950000, 0, 0 }
> +};
> +
> +static u32 SD_TABLE[24][2][3] = {
> +	{{0x38fae1, 0x0d, 0x5}, {0x39fae1, 0x0d, 0x5},},
> +	{{0x3f570a, 0x0e, 0x3}, {0x00570a, 0x0e, 0x3},},
> +	{{0x05b333, 0x0e, 0x5}, {0x06b333, 0x0e, 0x5},},
> +	{{0x3c0f5c, 0x0f, 0x4}, {0x3d0f5c, 0x0f, 0x4},},
> +	{{0x026b85, 0x0f, 0x6}, {0x036b85, 0x0f, 0x6},},
> +	{{0x38c7ae, 0x10, 0x5}, {0x39c7ae, 0x10, 0x5},},
> +	{{0x3f23d7, 0x11, 0x3}, {0x0023d7, 0x11, 0x3},},
> +	{{0x058000, 0x11, 0x5}, {0x068000, 0x11, 0x5},},
> +	{{0x3bdc28, 0x12, 0x4}, {0x3cdc28, 0x12, 0x4},},
> +	{{0x023851, 0x12, 0x6}, {0x033851, 0x12, 0x6},},
> +	{{0x38947a, 0x13, 0x5}, {0x39947a, 0x13, 0x5},},
> +	{{0x3ef0a3, 0x14, 0x3}, {0x3ff0a3, 0x14, 0x3},},
> +	{{0x3c8000, 0x16, 0x4}, {0x3d8000, 0x16, 0x4},},
> +	{{0x048000, 0x16, 0x6}, {0x058000, 0x16, 0x6},},
> +	{{0x3c8000, 0x17, 0x5}, {0x3d8000, 0x17, 0x5},},
> +	{{0x048000, 0x18, 0x3}, {0x058000, 0x18, 0x3},},
> +	{{0x3c8000, 0x18, 0x6}, {0x3d8000, 0x18, 0x6},},
> +	{{0x048000, 0x19, 0x4}, {0x058000, 0x19, 0x4},},
> +	{{0x3c8000, 0x1a, 0x3}, {0x3d8000, 0x1a, 0x3},},
> +	{{0x048000, 0x1a, 0x5}, {0x058000, 0x1a, 0x5},},
> +	{{0x3c8000, 0x1b, 0x4}, {0x3d8000, 0x1b, 0x4},},
> +	{{0x048000, 0x1b, 0x6}, {0x058000, 0x1b, 0x6},},
> +	{{0x3c8000, 0x1c, 0x5}, {0x3d8000, 0x1c, 0x5},},
> +	{{0x048000, 0x1d, 0x3}, {0x058000, 0x1d, 0x3},},
> +};
> +
> +static int qm1d1c0042_tuning(struct qm1d1c0042 *qm, u32 *sd, u32 channel)
> +{
> +	int ret;
> +	struct pt3_adapter *adap = qm->adap;
> +	u8 i_data;
> +	u32 index, i, N, A;
> +
> +	qm->reg[0x08] &= 0xf0;
> +	qm->reg[0x08] |= 0x09;
> +
> +	qm->reg[0x13] &= 0x9f;
> +	qm->reg[0x13] |= 0x20;
> +
> +	for (i = 0; i < 8; i++) {
> +		if ((QM1D1C0042_FREQ_TABLE[i+1][0] <= adap->freq) && (adap->freq < QM1D1C0042_FREQ_TABLE[i][0])) {
> +			i_data = qm->reg[0x02];
> +			i_data &= 0x0f;
> +			i_data |= QM1D1C0042_FREQ_TABLE[i][1] << 7;
> +			i_data |= QM1D1C0042_FREQ_TABLE[i][2] << 4;
> +			qm1d1c0042_write(qm, 0x02, i_data);
> +		}
> +	}
> +
> +	index = tc90522_index(qm->adap);
> +	*sd = SD_TABLE[channel][index][0];
> +	N = SD_TABLE[channel][index][1];
> +	A = SD_TABLE[channel][index][2];
> +
> +	qm->reg[0x06] &= 0x40;
> +	qm->reg[0x06] |= N;
> +	ret = qm1d1c0042_write(qm, 0x06, qm->reg[0x06]);
> +	if (ret)
> +		return ret;
> +
> +	qm->reg[0x07] &= 0xf0;
> +	qm->reg[0x07] |= A & 0x0f;
> +	return qm1d1c0042_write(qm, 0x07, qm->reg[0x07]);
> +}
> +
> +static int qm1d1c0042_local_lpf_tuning(struct qm1d1c0042 *qm, int lpf, u32 channel)
> +{
> +	u8 i_data;
> +	u32 sd = 0;
> +	int ret = qm1d1c0042_tuning(qm, &sd, channel);
> +
> +	if (ret)
> +		return ret;
> +	if (lpf) {
> +		i_data = qm->reg[0x08] & 0xf0;
> +		i_data |= 2;
> +		ret = qm1d1c0042_write(qm, 0x08, i_data);
> +	} else
> +		ret = qm1d1c0042_write(qm, 0x08, qm->reg[0x08]);
> +	if (ret)
> +		return ret;
> +
> +	qm->reg[0x09] &= 0xc0;
> +	qm->reg[0x09] |= (sd >> 16) & 0x3f;
> +	qm->reg[0x0a] = (sd >> 8) & 0xff;
> +	qm->reg[0x0b] = (sd >> 0) & 0xff;
> +	ret = qm1d1c0042_write(qm, 0x09, qm->reg[0x09]);
> +	if (ret)
> +		return ret;
> +	ret = qm1d1c0042_write(qm, 0x0a, qm->reg[0x0a]);
> +	if (ret)
> +		return ret;
> +	ret = qm1d1c0042_write(qm, 0x0b, qm->reg[0x0b]);
> +	if (ret)
> +		return ret;
> +
> +	if (lpf) {
> +		i_data = qm->reg[0x0c];
> +		i_data &= 0x3f;
> +		ret = qm1d1c0042_write(qm, 0x0c, i_data);
> +		if (ret)
> +			return ret;
> +		msleep_interruptible(1);
> +
> +		i_data = qm->reg[0x0c];
> +		i_data |= 0xc0;
> +		ret = qm1d1c0042_write(qm, 0x0c, i_data);
> +		if (ret)
> +			return ret;
> +		msleep_interruptible(qm->wait_time_lpf);
> +		ret = qm1d1c0042_write(qm, 0x08, 0x09);
> +		if (ret)
> +			return ret;
> +		ret = qm1d1c0042_write(qm, 0x13, qm->reg[0x13]);
> +		if (ret)
> +			return ret;
> +	} else {
> +		ret = qm1d1c0042_write(qm, 0x13, qm->reg[0x13]);
> +		if (ret)
> +			return ret;
> +		i_data = qm->reg[0x0c];
> +		i_data &= 0x7f;
> +		ret = qm1d1c0042_write(qm, 0x0c, i_data);
> +		if (ret)
> +			return ret;
> +		msleep_interruptible(2);
> +
> +		i_data = qm->reg[0x0c];
> +		i_data |= 0x80;
> +		ret = qm1d1c0042_write(qm, 0x0c, i_data);
> +		if (ret)
> +			return ret;
> +		if (qm->reg[0x03] & 0x01)
> +			msleep_interruptible(qm->wait_time_search_fast);
> +		else
> +			msleep_interruptible(qm->wait_time_search_normal);
> +	}
> +	return ret;
> +}
> +
> +int qm1d1c0042_get_locked(struct qm1d1c0042 *qm, bool *locked)
> +{
> +	int ret = qm1d1c0042_read(qm, 0x0d, &qm->reg[0x0d]);
> +	if (ret)
> +		return ret;
> +	if (qm->reg[0x0d] & 0x40)
> +		*locked = true;
> +	else
> +		*locked = false;
> +	return ret;
> +}
> +
> +int qm1d1c0042_set_frequency(struct qm1d1c0042 *qm, u32 channel)
> +{
> +	u32 number, freq, freq_kHz;
> +	struct timeval begin, now;
> +	bool locked;
> +	int ret = tc90522_set_agc(qm->adap, TC90522_AGC_MANUAL);
> +	if (ret)
> +		return ret;
> +
> +	qm1d1c0042_get_channel_freq(channel, &number, &freq);
> +	freq_kHz = freq * 10;
> +	if (tc90522_index(qm->adap) == 0)
> +		freq_kHz -= 500;
> +	else
> +		freq_kHz += 500;
> +	qm->adap->freq = freq_kHz;
> +	pr_debug("#%d ch %d freq %d kHz\n", qm->adap->idx, channel, freq_kHz);
> +
> +	ret = qm1d1c0042_local_lpf_tuning(qm, 1, channel);
> +	if (ret)
> +		return ret;
> +	do_gettimeofday(&begin);
> +	while (1) {
> +		do_gettimeofday(&now);
> +		ret = qm1d1c0042_get_locked(qm, &locked);
> +		if (ret)
> +			return ret;
> +		if (locked)
> +			break;
> +		if (tc90522_time_diff(&begin, &now) >= 100)
> +			break;
> +		msleep_interruptible(1);
> +	}
> +	pr_debug("#%d qm_get_locked %d ret=0x%x\n", qm->adap->idx, locked, ret);
> +	if (!locked)
> +		return -ETIMEDOUT;
> +
> +	ret = tc90522_set_agc(qm->adap, TC90522_AGC_AUTO);
> +	if (!ret) {
> +		qm->adap->channel = channel;
> +		qm->adap->offset = 0;
> +	}
> +	return ret;
> +}
> +
> +int qm1d1c0042_tuner_init(struct qm1d1c0042 *qm)
> +{
> +	if (*qm->reg) {
> +		if (qm1d1c0042_init(qm))
> +			return -EIO;
> +	} else {
> +		qm1d1c0042_init_reg_param(qm);
> +		qm1d1c0042_dummy_reset(qm);
> +	}
> +	return 0;
> +}
> +
> +EXPORT_SYMBOL(qm1d1c0042_set_frequency);
> +EXPORT_SYMBOL(qm1d1c0042_set_sleep);
> +EXPORT_SYMBOL(qm1d1c0042_tuner_init);
> +
> diff --git a/drivers/media/tuners/qm1d1c0042.h b/drivers/media/tuners/qm1d1c0042.h
> new file mode 100644
> index 0000000..ed3246d
> --- /dev/null
> +++ b/drivers/media/tuners/qm1d1c0042.h
> @@ -0,0 +1,34 @@
> +/*
> + * QM1D1C0042 ISDB-S tuner driver for Earthsoft PT3
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 __QM1D1C0042_H__
> +#define __QM1D1C0042_H__
> +
> +struct qm1d1c0042 {
> +	struct pt3_adapter *adap;
> +	u8 reg[32];
> +
> +	bool standby;
> +	u32 wait_time_lpf, wait_time_search_fast, wait_time_search_normal;
> +	struct tmcc_s tmcc;
> +};
> +
> +int qm1d1c0042_set_frequency(struct qm1d1c0042 *qm, u32 channel);
> +int qm1d1c0042_set_sleep(struct qm1d1c0042 *qm, bool sleep);
> +int qm1d1c0042_tuner_init(struct qm1d1c0042 *qm);
> +
> +#endif
> +
> diff --git a/drivers/media/tuners/tc90522.c b/drivers/media/tuners/tc90522.c
> new file mode 100644
> index 0000000..bce4532
> --- /dev/null
> +++ b/drivers/media/tuners/tc90522.c
> @@ -0,0 +1,412 @@
> +/*
> + * Earthsoft PT3 demodulator driver TC90522 Toshiba OFDM(ISDB-T)/8PSK(ISDB-S)
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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 "pt3_common.h"
> +#include "tc90522.h"
> +
> +MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
> +MODULE_DESCRIPTION("Earthsoft PT3 TC90522 OFDM(ISDB-T)/8PSK(ISDB-S) Toshiba demodulator driver");
> +MODULE_LICENSE("GPL");
> +
> +int tc90522_write(struct pt3_adapter *adap, u8 addr, u8 *data, u8 len)
> +{
> +	struct i2c_msg msg[1];
> +	u8 buf[len + 1];
> +	buf[0] = addr;
> +	memcpy(buf + 1, data, len);
> +
> +	msg[0].addr = adap->addr_demod;
> +	msg[0].flags = 0;			/* write */
> +	msg[0].buf = buf;
> +	msg[0].len = len + 1;
> +
> +	return i2c_transfer(&adap->pt3->i2c, msg, 1) == 1 ? 0 : -EREMOTEIO;
> +}
> +
> +static int tc90522_write_pskmsrst(struct pt3_adapter *adap)
> +{
> +	u8 data = 0x01;
> +	return tc90522_write(adap, 0x03, &data, 1);
> +}
> +
> +static int tc90522_write_imsrst(struct pt3_adapter *adap)
> +{
> +	u8 data = 0x01 << 6;
> +	return tc90522_write(adap, 0x01, &data, 1);
> +}
> +
> +int tc90522_init(struct pt3_adapter *adap)
> +{
> +	u8 data = 0x10;
> +
> +	pr_debug("#%d %s tuner=0x%x demod=0x%x\n", adap->idx, adap->str, adap->addr_tuner, adap->addr_demod);
> +	if (adap->type == SYS_ISDBS) {
> +		int ret = tc90522_write_pskmsrst(adap);
> +		return ret ? ret : tc90522_write(adap, 0x1e, &data, 1);
> +	} else {
> +		int ret = tc90522_write_imsrst(adap);
> +		return ret ? ret : tc90522_write(adap, 0x1c, &data, 1);
> +	}
> +}
> +
> +int tc90522_set_powers(struct pt3_adapter *adap, bool tuner, bool amp)
> +{
> +	u8	tuner_power = tuner ? 0x03 : 0x02,
> +		amp_power = amp ? 0x03 : 0x02,
> +		data = (tuner_power << 6) | (0x01 << 4) | (amp_power << 2) | 0x01 << 0;
> +	pr_debug("#%d tuner %s amp %s\n", adap->idx, tuner ? "ON" : "OFF", amp ? "ON" : "OFF");
> +	return tc90522_write(adap, 0x1e, &data, 1);
> +}
> +
> +int tc90522_set_ts_pins_mode(struct pt3_adapter *adap, struct pt3_ts_pins_mode *mode)
> +{
> +	u32	clock_data = mode->clock_data,
> +		byte = mode->byte,
> +		valid = mode->valid;
> +
> +	if (clock_data)
> +		clock_data++;
> +	if (byte)
> +		byte++;
> +	if (valid)
> +		valid++;
> +	if (adap->type == SYS_ISDBS) {
> +		u8 data[2];
> +		int ret;
> +		data[0] = 0x15 | (valid << 6);
> +		data[1] = 0x04 | (clock_data << 4) | byte;
> +		return (ret = tc90522_write(adap, 0x1c, &data[0], 1)) ?
> +			ret : tc90522_write(adap, 0x1f, &data[1], 1);
> +	} else {
> +		u8 data = (u8)(0x01 | (clock_data << 6) | (byte << 4) | (valid << 2));
> +		return tc90522_write(adap, 0x1d, &data, 1);
> +	}
> +}
> +
> +#define TC90522_THROUGH 0xfe
> +
> +int tc90522_write_tuner(struct pt3_adapter *adap, u8 addr, const u8 *data, u32 len)
> +{
> +	struct i2c_msg msg[1];
> +	u8 buf[len + 3];
> +
> +	buf[0] = TC90522_THROUGH;
> +	buf[1] = adap->addr_tuner << 1;
> +	buf[2] = addr;
> +	memcpy(buf + 3, data, len);
> +	msg[0].buf = buf;
> +	msg[0].len = len + 3;
> +	msg[0].addr = adap->addr_demod;
> +	msg[0].flags = 0;			/* write */
> +
> +	return i2c_transfer(&adap->pt3->i2c, msg, 1) == 1 ? 0 : -EREMOTEIO;
> +}
> +
> +int tc90522_read_tuner(struct pt3_adapter *adap, u8 addr, u8 *data)
> +{
> +	struct i2c_msg msg[3];
> +	u8 buf[5];
> +
> +	buf[0] = TC90522_THROUGH;
> +	buf[1] = adap->addr_tuner << 1;
> +	buf[2] = addr;
> +	msg[0].buf = buf;
> +	msg[0].len = 3;
> +	msg[0].addr = adap->addr_demod;
> +	msg[0].flags = 0;			/* write */
> +
> +	buf[3] = TC90522_THROUGH;
> +	buf[4] = (adap->addr_tuner << 1) | 1;
> +	msg[1].buf = buf + 3;
> +	msg[1].len = 2;
> +	msg[1].addr = adap->addr_demod;
> +	msg[1].flags = 0;			/* write */
> +
> +	msg[2].buf = data;
> +	msg[2].len = 1;
> +	msg[2].addr = adap->addr_demod;
> +	msg[2].flags = I2C_M_RD;		/* read */
> +
> +	return i2c_transfer(&adap->pt3->i2c, msg, 3) == 3 ? 0 : -EREMOTEIO;
> +}
> +
> +static u8 agc_data_s[2] = { 0xb0, 0x30 };
> +
> +u32 tc90522_index(struct pt3_adapter *adap)
> +{
> +	return PT3_SHIFT_MASK(adap->addr_demod, 1, 1);
> +}
> +
> +int tc90522_set_agc_s(struct pt3_adapter *adap, enum tc90522_agc agc)
> +{
> +	u8 data = (agc == TC90522_AGC_AUTO) ? 0xff : 0x00;
> +	int ret = tc90522_write(adap, 0x0a, &data, 1);
> +	if (ret)
> +		return ret;
> +
> +	data = agc_data_s[tc90522_index(adap)];
> +	data |= (agc == TC90522_AGC_AUTO) ? 0x01 : 0x00;
> +	ret = tc90522_write(adap, 0x10, &data, 1);
> +	if (ret)
> +		return ret;
> +
> +	data = (agc == TC90522_AGC_AUTO) ? 0x40 : 0x00;
> +	return (ret = tc90522_write(adap, 0x11, &data, 1)) ? ret : tc90522_write_pskmsrst(adap);
> +}
> +
> +int tc90522_set_sleep_s(struct pt3_adapter *adap, bool sleep)
> +{
> +	u8 buf = sleep ? 1 : 0;
> +	return tc90522_write(adap, 0x17, &buf, 1);
> +}
> +
> +int tc90522_set_agc_t(struct pt3_adapter *adap, enum tc90522_agc agc)
> +{
> +	u8 data = (agc == TC90522_AGC_AUTO) ? 0x40 : 0x00;
> +	int ret = tc90522_write(adap, 0x25, &data, 1);
> +	if (ret)
> +		return ret;
> +
> +	data = 0x4c | ((agc == TC90522_AGC_AUTO) ? 0x00 : 0x01);
> +	return (ret = tc90522_write(adap, 0x23, &data, 1)) ? ret : tc90522_write_imsrst(adap);
> +}
> +
> +int tc90522_set_agc(struct pt3_adapter *adap, enum tc90522_agc agc)
> +{
> +	if (adap->type == SYS_ISDBS)
> +		return tc90522_set_agc_s(adap, agc);
> +	else
> +		return tc90522_set_agc_t(adap, agc);
> +}
> +
> +int tc90522_write_tuner_without_addr(struct pt3_adapter *adap, const u8 *data, u32 len)
> +{
> +	struct i2c_msg msg[1];
> +	u8 buf[len + 2];
> +
> +	buf[0] = TC90522_THROUGH;
> +	buf[1] = adap->addr_tuner << 1;
> +	memcpy(buf + 2, data, len);
> +	msg[0].buf = buf;
> +	msg[0].len = len + 2;
> +	msg[0].addr = adap->addr_demod;
> +	msg[0].flags = 0;			/* write */
> +
> +	return i2c_transfer(&adap->pt3->i2c, msg, 1) == 1 ? 0 : -EREMOTEIO;
> +}
> +
> +int tc90522_write_sleep_time(struct pt3_adapter *adap, int sleep)
> +{
> +	u8 data = (1 << 7) | ((sleep ? 1 : 0) << 4);
> +	return tc90522_write(adap, 0x03, &data, 1);
> +}
> +
> +u32 tc90522_time_diff(struct timeval *st, struct timeval *et)
> +{
> +	u32 diff = ((et->tv_sec - st->tv_sec) * 1000000 + (et->tv_usec - st->tv_usec)) / 1000;
> +	pr_debug("time diff = %d\n", diff);
> +	return diff;
> +}
> +
> +int tc90522_read_tuner_without_addr(struct pt3_adapter *adap, u8 *data)
> +{
> +	struct i2c_msg msg[2];
> +	u8 buf[2];
> +
> +	buf[0] = TC90522_THROUGH;
> +	buf[1] = (adap->addr_tuner << 1) | 1;
> +	msg[0].buf = buf;
> +	msg[0].len = 2;
> +	msg[0].addr = adap->addr_demod;
> +	msg[0].flags = 0;			/* write */
> +
> +	msg[1].buf = data;
> +	msg[1].len = 1;
> +	msg[1].addr = adap->addr_demod;
> +	msg[1].flags = I2C_M_RD;		/* read */
> +
> +	return i2c_transfer(&adap->pt3->i2c, msg, 2) == 2 ? 0 : -EREMOTEIO;
> +}
> +
> +static u32 tc90522_byten(const u8 *data, u32 n)
> +{
> +	u32 i, val = 0;
> +
> +	for (i = 0; i < n; i++) {
> +		val <<= 8;
> +		val |= data[i];
> +	}
> +	return val;
> +}
> +
> +int tc90522_read(struct pt3_adapter *adap, u8 addr, u8 *buf, u8 buflen)
> +{
> +	struct i2c_msg msg[2];
> +	buf[0] = addr;
> +
> +	msg[0].addr = adap->addr_demod;
> +	msg[0].flags = 0;			/* write */
> +	msg[0].buf = buf;
> +	msg[0].len = 1;
> +
> +	msg[1].addr = adap->addr_demod;
> +	msg[1].flags = I2C_M_RD;		/* read */
> +	msg[1].buf = buf;
> +	msg[1].len = buflen;
> +
> +	return i2c_transfer(&adap->pt3->i2c, msg, 2) == 2 ? 0 : -EREMOTEIO;
> +}
> +
> +int tc90522_read_retryov_tmunvld_fulock(struct pt3_adapter *adap, bool *retryov, bool *tmunvld, bool *fulock)
> +{
> +	u8 buf;
> +	int ret = tc90522_read(adap, 0x80, &buf, 1);
> +	if (!ret) {
> +		*retryov = PT3_SHIFT_MASK(buf, 7, 1) ? true : false;
> +		*tmunvld = PT3_SHIFT_MASK(buf, 5, 1) ? true : false;
> +		*fulock  = PT3_SHIFT_MASK(buf, 3, 1) ? true : false;
> +	}
> +	return ret;
> +}
> +
> +int tc90522_read_cn(struct pt3_adapter *adap, u32 *cn)
> +{
> +	u8 buf[3], buflen = (adap->type == SYS_ISDBS) ? 2 : 3, addr = (adap->type == SYS_ISDBS) ? 0xbc : 0x8b;
> +	int ret = tc90522_read(adap, addr, buf, buflen);
> +	if (!ret)
> +		*cn =  tc90522_byten(buf, buflen);
> +	return ret;
> +}
> +
> +int tc90522_read_id_s(struct pt3_adapter *adap, u16 *id)
> +{
> +	u8 buf[2];
> +	int ret = tc90522_read(adap, 0xe6, buf, 2);
> +	if (!ret)
> +		*id = tc90522_byten(buf, 2);
> +	return ret;
> +}
> +
> +int tc90522_read_tmcc_t(struct pt3_adapter *adap, struct tmcc_t *tmcc)
> +{
> +	u32 interleave0h, interleave0l, segment1h, segment1l;
> +	u8 data[8];
> +
> +	int ret = tc90522_read(adap, 0xb2+0, data, 4);
> +	if (ret)
> +		return ret;
> +	ret = tc90522_read(adap, 0xb2+4, data+4, 4);
> +	if (ret)
> +		return ret;
> +
> +	tmcc->system    = PT3_SHIFT_MASK(data[0], 6, 2);
> +	tmcc->indicator = PT3_SHIFT_MASK(data[0], 2, 4);
> +	tmcc->emergency = PT3_SHIFT_MASK(data[0], 1, 1);
> +	tmcc->partial   = PT3_SHIFT_MASK(data[0], 0, 1);
> +
> +	tmcc->mode[0] = PT3_SHIFT_MASK(data[1], 5, 3);
> +	tmcc->mode[1] = PT3_SHIFT_MASK(data[2], 0, 3);
> +	tmcc->mode[2] = PT3_SHIFT_MASK(data[4], 3, 3);
> +
> +	tmcc->rate[0] = PT3_SHIFT_MASK(data[1], 2, 3);
> +	tmcc->rate[1] = PT3_SHIFT_MASK(data[3], 5, 3);
> +	tmcc->rate[2] = PT3_SHIFT_MASK(data[4], 0, 3);
> +
> +	interleave0h = PT3_SHIFT_MASK(data[1], 0, 2);
> +	interleave0l = PT3_SHIFT_MASK(data[2], 7, 1);
> +
> +	tmcc->interleave[0] = interleave0h << 1 | interleave0l << 0;
> +	tmcc->interleave[1] = PT3_SHIFT_MASK(data[3], 2, 3);
> +	tmcc->interleave[2] = PT3_SHIFT_MASK(data[5], 5, 3);
> +
> +	segment1h = PT3_SHIFT_MASK(data[3], 0, 2);
> +	segment1l = PT3_SHIFT_MASK(data[4], 6, 2);
> +
> +	tmcc->segment[0] = PT3_SHIFT_MASK(data[2], 3, 4);
> +	tmcc->segment[1] = segment1h << 2 | segment1l << 0;
> +	tmcc->segment[2] = PT3_SHIFT_MASK(data[5], 1, 4);
> +
> +	return ret;
> +}
> +
> +int tc90522_read_tmcc_s(struct pt3_adapter *adap, struct tmcc_s *tmcc)
> +{
> +	enum {
> +		BASE = 0xc5,
> +		SIZE = 0xe5 - BASE + 1
> +	};
> +	u8 data[SIZE];
> +	u32 i, byte_offset, bit_offset;
> +
> +	int ret = tc90522_read(adap, 0xc3, data, 1);
> +	if (ret)
> +		return ret;
> +	if (PT3_SHIFT_MASK(data[0], 4, 1))
> +		return -EBADMSG;
> +
> +	ret = tc90522_read(adap, 0xce, data, 2);
> +	if (ret)
> +		return ret;
> +	if (tc90522_byten(data, 2) == 0)
> +		return -EBADMSG;
> +
> +	ret = tc90522_read(adap, 0xc3, data, 1);
> +	if (ret)
> +		return ret;
> +	tmcc->emergency = PT3_SHIFT_MASK(data[0], 2, 1);
> +	tmcc->extflag   = PT3_SHIFT_MASK(data[0], 1, 1);
> +
> +	ret = tc90522_read(adap, 0xc5, data, SIZE);
> +	if (ret)
> +		return ret;
> +	tmcc->indicator = PT3_SHIFT_MASK(data[0xc5 - BASE], 3, 5);
> +	tmcc->uplink    = PT3_SHIFT_MASK(data[0xc7 - BASE], 0, 4);
> +
> +	for (i = 0; i < 4; i++) {
> +		byte_offset = i >> 1;
> +		bit_offset = (i & 1) ? 0 : 4;
> +		tmcc->mode[i] = PT3_SHIFT_MASK(data[0xc8 + byte_offset - BASE], bit_offset, 4);
> +		tmcc->slot[i] = PT3_SHIFT_MASK(data[0xca + i - BASE], 0, 6);
> +	}
> +	for (i = 0; i < 8; i++)
> +		tmcc->id[i] = tc90522_byten(data + 0xce + i * 2 - BASE, 2);
> +	return ret;
> +}
> +
> +int tc90522_read_tmcc(struct pt3_adapter *adap, void *tmcc)
> +{
> +	if (adap->type == SYS_ISDBS)
> +		return tc90522_read_tmcc_s(adap, (struct tmcc_s *)tmcc);
> +	else
> +		return tc90522_read_tmcc_t(adap, (struct tmcc_t *)tmcc);
> +}
> +
> +int tc90522_write_id_s(struct pt3_adapter *adap, u16 id)
> +{
> +	u8 data[2] = { id >> 8, (u8)id };
> +	return tc90522_write(adap, 0x8f, data, sizeof(data));
> +}
> +
> +EXPORT_SYMBOL(tc90522_init);
> +EXPORT_SYMBOL(tc90522_read_cn);
> +EXPORT_SYMBOL(tc90522_read_id_s);
> +EXPORT_SYMBOL(tc90522_read_retryov_tmunvld_fulock);
> +EXPORT_SYMBOL(tc90522_read_tmcc);
> +EXPORT_SYMBOL(tc90522_set_agc);
> +EXPORT_SYMBOL(tc90522_set_powers);
> +EXPORT_SYMBOL(tc90522_set_ts_pins_mode);
> +EXPORT_SYMBOL(tc90522_write_id_s);
> +
> diff --git a/drivers/media/tuners/tc90522.h b/drivers/media/tuners/tc90522.h
> new file mode 100644
> index 0000000..9d33ec5
> --- /dev/null
> +++ b/drivers/media/tuners/tc90522.h
> @@ -0,0 +1,90 @@
> +/*
> + * Earthsoft PT3 demodulator driver TC90522 Toshiba OFDM(ISDB-T)/8PSK(ISDB-S)
> + *
> + * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
> + *
> + * 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	__TC90522_H__
> +#define	__TC90522_H__
> +
> +/* Transmission and Multiplexing Configuration Control */
> +
> +enum {
> +	LAYER_INDEX_L = 0,
> +	LAYER_INDEX_H,
> +
> +	LAYER_INDEX_A = 0,
> +	LAYER_INDEX_B,
> +	LAYER_INDEX_C
> +};
> +
> +enum {
> +	LAYER_COUNT_S = LAYER_INDEX_H + 1,
> +	LAYER_COUNT_T = LAYER_INDEX_C + 1,
> +};
> +
> +struct tmcc_s {
> +	u32 indicator;
> +	u32 mode[4];
> +	u32 slot[4];
> +	u32 id[8];
> +	u32 emergency;
> +	u32 uplink;
> +	u32 extflag;
> +};
> +
> +struct tmcc_t {
> +	u32 system;
> +	u32 indicator;
> +	u32 emergency;
> +	u32 partial;
> +	u32 mode[LAYER_COUNT_T];
> +	u32 rate[LAYER_COUNT_T];
> +	u32 interleave[LAYER_COUNT_T];
> +	u32 segment[LAYER_COUNT_T];
> +};
> +
> +enum tc90522_agc {
> +	TC90522_AGC_AUTO,
> +	TC90522_AGC_MANUAL,
> +};
> +
> +enum pt3_ts_pin_mode {
> +	PT3_TS_PIN_MODE_NORMAL,
> +	PT3_TS_PIN_MODE_LOW,
> +	PT3_TS_PIN_MODE_HIGH,
> +};
> +
> +struct pt3_ts_pins_mode {
> +	enum pt3_ts_pin_mode clock_data, byte, valid;
> +};
> +
> +u32 tc90522_index(struct pt3_adapter *adap);
> +int tc90522_init(struct pt3_adapter *adap);
> +int tc90522_read_cn(struct pt3_adapter *adap, u32 *cn);
> +int tc90522_read_id_s(struct pt3_adapter *adap, u16 *id);
> +int tc90522_read_retryov_tmunvld_fulock(struct pt3_adapter *adap, bool *retryov, bool *tmunvld, bool *fulock);
> +int tc90522_read_tmcc(struct pt3_adapter *adap, void *tmcc);
> +int tc90522_read_tuner(struct pt3_adapter *adap, u8 addr, u8 *data);
> +int tc90522_read_tuner_without_addr(struct pt3_adapter *adap, u8 *data);
> +int tc90522_set_agc(struct pt3_adapter *adap, enum tc90522_agc agc);
> +int tc90522_set_powers(struct pt3_adapter *adap, bool tuner, bool amp);
> +int tc90522_set_sleep_s(struct pt3_adapter *adap, bool sleep);
> +int tc90522_set_ts_pins_mode(struct pt3_adapter *adap, struct pt3_ts_pins_mode *mode);
> +u32 tc90522_time_diff(struct timeval *st, struct timeval *et);
> +int tc90522_write_id_s(struct pt3_adapter *adap, u16 id);
> +int tc90522_write_sleep_time(struct pt3_adapter *adap, int sleep);
> +int tc90522_write_tuner(struct pt3_adapter *adap, u8 addr, const u8 *data, u32 size);
> +int tc90522_write_tuner_without_addr(struct pt3_adapter *adap, const u8 *data, u32 size);
> +
> +#endif
>


-- 
http://palosaari.fi/

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH] Full DVB driver package for Earthsoft PT3 (ISDB-S/T) cards
@ 2013-12-08  5:14 Guest
  2013-12-08 22:52 ` Antti Palosaari
  0 siblings, 1 reply; 21+ messages in thread
From: Guest @ 2013-12-08  5:14 UTC (permalink / raw)
  To: linux-media
  Cc: Буди
	Романто,
	mchehab, hdegoede, hverkuil, laurent.pinchart, mkrufky,
	sylvester.nawrocki, g.liakhovetski, peter.senna

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain, Size: 95225 bytes --]

From: Буди Романто <knightrider@are.ma>

A DVB driver for Earthsoft PT3 (ISDB-S/T) receiver PCI Express cards, based on
1. PT3 chardev driver
	https://github.com/knight-rider/ptx/tree/master/pt3_drv
	https://github.com/m-tsudo/pt3
2. PT1/PT2 DVB driver
	drivers/media/pci/pt1

It behaves similarly as PT1 DVB, plus some tuning enhancements:
1. in addition to the real frequency:
	ISDB-S : freq. channel ID
	ISDB-T : freq# (I/O# +128), ch#, ch# +64 for CATV
2. in addition to TSID:
	ISDB-S : slot#

Feature changes:
- dropped DKMS & standalone compile
- dropped verbosity (debug levels), use single level -DDEBUG instead
- changed SNR (.read_snr) to CNR (.read_signal_strength)
- moved FE to drivers/media/dvb-frontends
- moved demodulator & tuners to drivers/media/tuners
- translated to standard (?) I2C protocol

The full package (buildable as standalone, DKMS or tree embedded module) is available at
https://github.com/knight-rider/ptx/tree/master/pt3_dvb

Signed-off-by: Bud R <knightrider@are.ma>

---
 drivers/media/dvb-frontends/Kconfig      |  10 +-
 drivers/media/dvb-frontends/Makefile     |   1 +
 drivers/media/dvb-frontends/pt3_common.h |  95 +++++
 drivers/media/dvb-frontends/pt3_fe.c     | 412 ++++++++++++++++++++++
 drivers/media/dvb-frontends/pt3_fe.h     |  22 ++
 drivers/media/pci/Kconfig                |   2 +-
 drivers/media/pci/Makefile               |   1 +
 drivers/media/pci/pt3/Kconfig            |  10 +
 drivers/media/pci/pt3/Makefile           |   6 +
 drivers/media/pci/pt3/pt3.c              | 579 +++++++++++++++++++++++++++++++
 drivers/media/pci/pt3/pt3.h              |  23 ++
 drivers/media/pci/pt3/pt3_dma.c          | 335 ++++++++++++++++++
 drivers/media/pci/pt3/pt3_dma.h          |  48 +++
 drivers/media/pci/pt3/pt3_i2c.c          | 167 +++++++++
 drivers/media/pci/pt3/pt3_i2c.h          |  29 ++
 drivers/media/tuners/Kconfig             |   7 +
 drivers/media/tuners/Makefile            |   2 +
 drivers/media/tuners/mxl301rf.c          | 347 ++++++++++++++++++
 drivers/media/tuners/mxl301rf.h          |  27 ++
 drivers/media/tuners/qm1d1c0042.c        | 413 ++++++++++++++++++++++
 drivers/media/tuners/qm1d1c0042.h        |  34 ++
 drivers/media/tuners/tc90522.c           | 412 ++++++++++++++++++++++
 drivers/media/tuners/tc90522.h           |  90 +++++
 23 files changed, 3070 insertions(+), 2 deletions(-)
 create mode 100644 drivers/media/dvb-frontends/pt3_common.h
 create mode 100644 drivers/media/dvb-frontends/pt3_fe.c
 create mode 100644 drivers/media/dvb-frontends/pt3_fe.h
 create mode 100644 drivers/media/pci/pt3/Kconfig
 create mode 100644 drivers/media/pci/pt3/Makefile
 create mode 100644 drivers/media/pci/pt3/pt3.c
 create mode 100644 drivers/media/pci/pt3/pt3.h
 create mode 100644 drivers/media/pci/pt3/pt3_dma.c
 create mode 100644 drivers/media/pci/pt3/pt3_dma.h
 create mode 100644 drivers/media/pci/pt3/pt3_i2c.c
 create mode 100644 drivers/media/pci/pt3/pt3_i2c.h
 create mode 100644 drivers/media/tuners/mxl301rf.c
 create mode 100644 drivers/media/tuners/mxl301rf.h
 create mode 100644 drivers/media/tuners/qm1d1c0042.c
 create mode 100644 drivers/media/tuners/qm1d1c0042.h
 create mode 100644 drivers/media/tuners/tc90522.c
 create mode 100644 drivers/media/tuners/tc90522.h

diff --git a/drivers/media/dvb-frontends/Kconfig b/drivers/media/dvb-frontends/Kconfig
index bddbab4..371c407 100644
--- a/drivers/media/dvb-frontends/Kconfig
+++ b/drivers/media/dvb-frontends/Kconfig
@@ -584,7 +584,7 @@ config DVB_S5H1411
 	  An ATSC 8VSB and QAM64/256 tuner module. Say Y when you want
 	  to support this frontend.
 
-comment "ISDB-T (terrestrial) frontends"
+comment "ISDB-S (satellite) & ISDB-T (terrestrial) frontends"
 	depends on DVB_CORE
 
 config DVB_S921
@@ -611,6 +611,14 @@ config DVB_MB86A20S
 	  A driver for Fujitsu mb86a20s ISDB-T/ISDB-Tsb demodulator.
 	  Say Y when you want to support this frontend.
 
+config DVB_PT3_FE
+	tristate "Earthsoft PT3 ISDB-S/T frontends"
+	depends on DVB_CORE && I2C
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  DVB driver frontend for Earthsoft PT3 ISDB-S/ISDB-T PCIE cards.
+	  Say Y when you want to support this frontend.
+
 comment "Digital terrestrial only tuners/PLL"
 	depends on DVB_CORE
 
diff --git a/drivers/media/dvb-frontends/Makefile b/drivers/media/dvb-frontends/Makefile
index f9cb43d..1b97ba1 100644
--- a/drivers/media/dvb-frontends/Makefile
+++ b/drivers/media/dvb-frontends/Makefile
@@ -104,4 +104,5 @@ obj-$(CONFIG_DVB_RTL2830) += rtl2830.o
 obj-$(CONFIG_DVB_RTL2832) += rtl2832.o
 obj-$(CONFIG_DVB_M88RS2000) += m88rs2000.o
 obj-$(CONFIG_DVB_AF9033) += af9033.o
+obj-$(CONFIG_DVB_PT3_FE) += pt3_fe.o
 
diff --git a/drivers/media/dvb-frontends/pt3_common.h b/drivers/media/dvb-frontends/pt3_common.h
new file mode 100644
index 0000000..8519f0e
--- /dev/null
+++ b/drivers/media/dvb-frontends/pt3_common.h
@@ -0,0 +1,95 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_COMMON_H__
+#define	__PT3_COMMON_H__
+
+#define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+
+#include <linux/pci.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include "dvb_demux.h"
+#include "dmxdev.h"
+#include "dvb_frontend.h"
+
+#define PT3_NR_ADAPS 4
+#define PT3_SHIFT_MASK(val, shift, mask) (((val) >> (shift)) & (((u64)1<<(mask))-1))
+
+/* register idx */
+#define REG_VERSION	0x00	/*	R	Version		*/
+#define REG_BUS		0x04	/*	R	Bus		*/
+#define REG_SYSTEM_W	0x08	/*	W	System		*/
+#define REG_SYSTEM_R	0x0c	/*	R	System		*/
+#define REG_I2C_W	0x10	/*	W	I2C		*/
+#define REG_I2C_R	0x14	/*	R	I2C		*/
+#define REG_RAM_W	0x18	/*	W	RAM		*/
+#define REG_RAM_R	0x1c	/*	R	RAM		*/
+#define REG_BASE	0x40	/* + 0x18*idx			*/
+#define REG_DMA_DESC_L	0x00	/*	W	DMA		*/
+#define REG_DMA_DESC_H	0x04	/*	W	DMA		*/
+#define REG_DMA_CTL	0x08	/*	W	DMA		*/
+#define REG_TS_CTL	0x0c	/*	W	TS		*/
+#define REG_STATUS	0x10	/*	R	DMA/FIFO/TS	*/
+#define REG_TS_ERR	0x14	/*	R	TS		*/
+
+struct pt3_adapter;
+
+struct pt3_board {
+	struct mutex lock;
+	int lnb;
+	bool reset;
+
+	struct pci_dev *pdev;
+	int bars;
+	void __iomem *bar_reg, *bar_mem;
+	struct i2c_adapter i2c;
+	u8 i2c_buf;
+	u32 i2c_addr;
+	bool i2c_filled;
+
+	struct pt3_adapter *adap[PT3_NR_ADAPS];
+};
+
+struct pt3_adapter {
+	struct mutex lock;
+	struct pt3_board *pt3;
+
+	int idx, init_ch;
+	char *str;
+	fe_delivery_system_t type;
+	bool in_use, sleep;
+	u32 channel;
+	s32 offset;
+	u8 addr_demod, addr_tuner;
+	u32 freq;
+	struct qm1d1c0042 *qm;
+	struct pt3_dma *dma;
+	struct task_struct *kthread;
+	int *dec;
+	struct dvb_adapter dvb;
+	struct dvb_demux demux;
+	int users;
+	struct dmxdev dmxdev;
+	struct dvb_frontend *fe;
+	int (*orig_voltage)(struct dvb_frontend *fe, fe_sec_voltage_t voltage);
+	int (*orig_sleep)  (struct dvb_frontend *fe);
+	int (*orig_init)   (struct dvb_frontend *fe);
+	fe_sec_voltage_t voltage;
+};
+
+#endif
+
diff --git a/drivers/media/dvb-frontends/pt3_fe.c b/drivers/media/dvb-frontends/pt3_fe.c
new file mode 100644
index 0000000..00b5e06
--- /dev/null
+++ b/drivers/media/dvb-frontends/pt3_fe.c
@@ -0,0 +1,412 @@
+/*
+ * DVB Frontend driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_common.h"
+#include "tc90522.h"
+#include "qm1d1c0042.h"
+#include "mxl301rf.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("DVB Frontend Driver for Earthsoft PT3 cards");
+MODULE_LICENSE("GPL");
+
+/**** Common ****/
+enum pt3_fe_tune_state {
+	PT3_TUNE_IDLE,
+	PT3_TUNE_SET_FREQUENCY,
+	PT3_TUNE_CHECK_FREQUENCY,
+	PT3_TUNE_SET_MODULATION,
+	PT3_TUNE_CHECK_MODULATION,
+	PT3_TUNE_SET_TS_ID,
+	PT3_TUNE_CHECK_TS_ID,
+	PT3_TUNE_TRACK,
+	PT3_TUNE_ABORT,
+};
+
+struct pt3_fe_state {
+	struct pt3_adapter *adap;
+	struct dvb_frontend fe;
+	enum pt3_fe_tune_state tune_state;
+};
+
+static int pt3_fe_read_signal_strength(struct dvb_frontend *fe, u16 *cn)
+{
+	struct pt3_fe_state *state = fe->demodulator_priv;
+	return tc90522_read_cn(state->adap, (u32 *)cn);
+}
+
+static int pt3_fe_get_frontend_algo(struct dvb_frontend *fe)
+{
+	return DVBFE_ALGO_HW;
+}
+
+static void pt3_fe_release(struct dvb_frontend *fe)
+{
+	kfree(fe->demodulator_priv);
+}
+
+static int pt3_fe_init(struct dvb_frontend *fe)
+{
+	struct pt3_fe_state *state = fe->demodulator_priv;
+	state->tune_state = PT3_TUNE_IDLE;
+	return (state->adap->type == SYS_ISDBS) ?
+		qm1d1c0042_set_sleep(state->adap->qm, false) : mxl301rf_set_sleep(state->adap, false);
+}
+
+static int pt3_fe_sleep(struct dvb_frontend *fe)
+{
+	struct pt3_fe_state *state = fe->demodulator_priv;
+	return (state->adap->type == SYS_ISDBS) ?
+		qm1d1c0042_set_sleep(state->adap->qm, true) : mxl301rf_set_sleep(state->adap, true);
+}
+
+/**** ISDB-S ****/
+u32 pt3_fe_s_get_channel(u32 frequency)
+{
+	u32 freq = frequency / 10,
+	    ch0 = (freq - 104948) / 3836, diff0 = freq - (104948 + 3836 * ch0),
+	    ch1 = (freq - 161300) / 4000, diff1 = freq - (161300 + 4000 * ch1),
+	    ch2 = (freq - 159300) / 4000, diff2 = freq - (159300 + 4000 * ch2),
+	    min = diff0 < diff1 ? diff0 : diff1;
+
+	if (diff2 < min)
+		return ch2 + 24;
+	else if (min == diff1)
+		return ch1 + 12;
+	else
+		return ch0;
+}
+
+static int pt3_fe_s_read_status(struct dvb_frontend *fe, fe_status_t *status)
+{
+	struct pt3_fe_state *state = fe->demodulator_priv;
+
+	switch (state->tune_state) {
+	case PT3_TUNE_IDLE:
+	case PT3_TUNE_SET_FREQUENCY:
+	case PT3_TUNE_CHECK_FREQUENCY:
+		*status = 0;
+		return 0;
+
+	case PT3_TUNE_SET_MODULATION:
+	case PT3_TUNE_CHECK_MODULATION:
+	case PT3_TUNE_ABORT:
+		*status |= FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3_TUNE_SET_TS_ID:
+	case PT3_TUNE_CHECK_TS_ID:
+		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER;
+		return 0;
+
+	case PT3_TUNE_TRACK:
+		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+	}
+	BUG();
+}
+
+static int pt3_fe_s_tune(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
+{
+	struct pt3_fe_state *state = fe->demodulator_priv;
+	struct pt3_adapter *adap = state->adap;
+	struct tmcc_s *tmcc = &adap->qm->tmcc;
+	int i, ret,
+	    freq = state->fe.dtv_property_cache.frequency,
+	    tsid = state->fe.dtv_property_cache.stream_id,
+	    ch = (freq < 1024) ? freq : pt3_fe_s_get_channel(freq);	/* consider as channel ID if low */
+
+	if (re_tune)
+		state->tune_state = PT3_TUNE_SET_FREQUENCY;
+
+	switch (state->tune_state) {
+	case PT3_TUNE_IDLE:
+		*delay = 3 * HZ;
+		*status = 0;
+		return 0;
+
+	case PT3_TUNE_SET_FREQUENCY:
+	case PT3_TUNE_CHECK_FREQUENCY:
+		pr_debug("#%d freq %d tsid 0x%x ch %d\n", adap->idx, freq, tsid, ch);
+		ret = qm1d1c0042_set_frequency(adap->qm, ch);
+		if (ret)
+			return ret;
+		adap->channel = ch;
+		state->tune_state = PT3_TUNE_SET_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3_TUNE_SET_MODULATION:
+		for (i = 0; i < 1000; i++) {
+			ret = tc90522_read_tmcc(adap, tmcc);
+			if (!ret)
+				break;
+			msleep_interruptible(1);
+		}
+		if (ret) {
+			pr_debug("fail tc_read_tmcc_s ret=0x%x\n", ret);
+			state->tune_state = PT3_TUNE_ABORT;
+			*delay = HZ;
+			return ret;
+		}
+		pr_debug("slots=%d,%d,%d,%d mode=%d,%d,%d,%d\n",
+				tmcc->slot[0], tmcc->slot[1], tmcc->slot[2], tmcc->slot[3],
+				tmcc->mode[0], tmcc->mode[1], tmcc->mode[2], tmcc->mode[3]);
+		state->tune_state = PT3_TUNE_CHECK_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3_TUNE_CHECK_MODULATION:
+		pr_debug("tmcc->id=0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x\n",
+				tmcc->id[0], tmcc->id[1], tmcc->id[2], tmcc->id[3],
+				tmcc->id[4], tmcc->id[5], tmcc->id[6], tmcc->id[7]);
+		for (i = 0; i < sizeof(tmcc->id)/sizeof(tmcc->id[0]); i++) {
+			pr_debug("tsid %x i %d tmcc->id %x\n", tsid, i, tmcc->id[i]);
+			if (tmcc->id[i] == tsid)
+				break;
+		}
+		if (tsid < sizeof(tmcc->id)/sizeof(tmcc->id[0]))	/* consider as slot# */
+			i = tsid;
+		if (i == sizeof(tmcc->id)/sizeof(tmcc->id[0])) {
+			pr_debug("#%d i%d tsid 0x%x not found\n", adap->idx, i, tsid);
+			return -EINVAL;
+		}
+		adap->offset = i;
+		pr_debug("#%d found tsid 0x%x on slot %d\n", adap->idx, tsid, i);
+		state->tune_state = PT3_TUNE_SET_TS_ID;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER;
+		return 0;
+
+	case PT3_TUNE_SET_TS_ID:
+		ret = tc90522_write_id_s(adap, (u16)tmcc->id[adap->offset]);
+		if (ret) {
+			pr_debug("fail set_tmcc_s ret=%d\n", ret);
+			return ret;
+		}
+		state->tune_state = PT3_TUNE_CHECK_TS_ID;
+		return 0;
+
+	case PT3_TUNE_CHECK_TS_ID:
+		for (i = 0; i < 1000; i++) {
+			u16 short_id;
+			ret = tc90522_read_id_s(adap, &short_id);
+			if (ret) {
+				pr_debug("fail get_id_s ret=%d\n", ret);
+				return ret;
+			}
+			tsid = short_id;
+			pr_debug("#%d tsid=0x%x\n", adap->idx, tsid);
+			if ((tsid & 0xffff) == tmcc->id[adap->offset])
+				break;
+			msleep_interruptible(1);
+		}
+		state->tune_state = PT3_TUNE_TRACK;
+
+	case PT3_TUNE_TRACK:
+		*delay = 3 * HZ;
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+
+	case PT3_TUNE_ABORT:
+		*delay = 3 * HZ;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+	}
+	BUG();
+}
+
+static struct dvb_frontend_ops pt3_fe_s_ops = {
+	.delsys = { SYS_ISDBS },
+	.info = {
+		.name = "PT3 ISDB-S",
+		.frequency_min = 1,
+		.frequency_max = 2150000,
+		.frequency_stepsize = 1000,
+		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO | FE_CAN_MULTISTREAM |
+			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
+	},
+	.init = pt3_fe_init,
+	.sleep = pt3_fe_sleep,
+	.release = pt3_fe_release,
+	.get_frontend_algo = pt3_fe_get_frontend_algo,
+	.read_signal_strength = pt3_fe_read_signal_strength,
+	.read_status = pt3_fe_s_read_status,
+	.tune = pt3_fe_s_tune,
+};
+
+/**** ISDB-T ****/
+static int pt3_fe_t_get_tmcc(struct pt3_adapter *adap, struct tmcc_t *tmcc)
+{
+	bool b = false, retryov, tmunvld, fulock;
+
+	if (unlikely(!tmcc))
+		return -EINVAL;
+	while (1) {
+		tc90522_read_retryov_tmunvld_fulock(adap, &retryov, &tmunvld, &fulock);
+		if (!fulock) {
+			b = true;
+			break;
+		} else {
+			if (retryov)
+				break;
+		}
+		msleep_interruptible(1);
+	}
+	if (likely(b))
+		tc90522_read_tmcc(adap, tmcc);
+	return b ? 0 : -EBADMSG;
+}
+
+static int pt3_fe_t_read_status(struct dvb_frontend *fe, fe_status_t *status)
+{
+	struct pt3_fe_state *state = fe->demodulator_priv;
+
+	switch (state->tune_state) {
+	case PT3_TUNE_IDLE:
+	case PT3_TUNE_SET_FREQUENCY:
+	case PT3_TUNE_CHECK_FREQUENCY:
+		*status = 0;
+		return 0;
+
+	case PT3_TUNE_SET_MODULATION:
+	case PT3_TUNE_CHECK_MODULATION:
+	case PT3_TUNE_SET_TS_ID:
+	case PT3_TUNE_CHECK_TS_ID:
+	case PT3_TUNE_ABORT:
+		*status |= FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3_TUNE_TRACK:
+		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+	}
+	BUG();
+}
+
+static int pt3_fe_t_tune(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
+{
+	struct pt3_fe_state *state = fe->demodulator_priv;
+	struct tmcc_t tmcc_t;
+	int ret, i;
+
+	if (re_tune)
+		state->tune_state = PT3_TUNE_SET_FREQUENCY;
+
+	switch (state->tune_state) {
+	case PT3_TUNE_IDLE:
+		*delay = 3 * HZ;
+		*status = 0;
+		return 0;
+
+	case PT3_TUNE_SET_FREQUENCY:
+		ret = tc90522_set_agc(state->adap, TC90522_AGC_MANUAL);
+		if (ret)
+			return ret;
+		mxl301rf_tuner_rftune(state->adap, mxl301rf_freq(state->fe.dtv_property_cache.frequency));
+		state->tune_state = PT3_TUNE_CHECK_FREQUENCY;
+		*delay = 0;
+		*status = 0;
+		return 0;
+
+	case PT3_TUNE_CHECK_FREQUENCY:
+		if (!mxl301rf_locked(state->adap)) {
+			*delay = HZ;
+			*status = 0;
+			return 0;
+		}
+		state->tune_state = PT3_TUNE_SET_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3_TUNE_SET_MODULATION:
+		ret = tc90522_set_agc(state->adap, TC90522_AGC_AUTO);
+		if (ret)
+			return ret;
+		state->tune_state = PT3_TUNE_CHECK_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3_TUNE_CHECK_MODULATION:
+	case PT3_TUNE_SET_TS_ID:
+	case PT3_TUNE_CHECK_TS_ID:
+		for (i = 0; i < 1000; i++) {
+			ret = pt3_fe_t_get_tmcc(state->adap, &tmcc_t);
+			if (!ret)
+				break;
+			msleep_interruptible(2);
+		}
+		if (ret) {
+			pr_debug("#%d fail get_tmcc_t ret=%d\n", state->adap->idx, ret);
+				state->tune_state = PT3_TUNE_ABORT;
+				*delay = HZ;
+				return 0;
+		}
+		state->tune_state = PT3_TUNE_TRACK;
+
+	case PT3_TUNE_TRACK:
+		*delay = 3 * HZ;
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+
+	case PT3_TUNE_ABORT:
+		*delay = 3 * HZ;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+	}
+	BUG();
+}
+
+static struct dvb_frontend_ops pt3_fe_t_ops = {
+	.delsys = { SYS_ISDBT },
+	.info = {
+		.name = "PT3 ISDB-T",
+		.frequency_min = 1,
+		.frequency_max = 770000000,
+		.frequency_stepsize = 142857,
+		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO |
+			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
+	},
+	.init = pt3_fe_init,
+	.sleep = pt3_fe_sleep,
+	.release = pt3_fe_release,
+	.get_frontend_algo = pt3_fe_get_frontend_algo,
+	.read_signal_strength = pt3_fe_read_signal_strength,
+	.read_status = pt3_fe_t_read_status,
+	.tune = pt3_fe_t_tune,
+};
+
+/**** Common ****/
+struct dvb_frontend *pt3_fe_attach(struct pt3_adapter *adap)
+{
+	struct dvb_frontend *fe;
+	struct pt3_fe_state *state = kzalloc(sizeof(struct pt3_fe_state), GFP_KERNEL);
+
+	if (!state)
+		return NULL;
+	state->adap = adap;
+	fe = &state->fe;
+	memcpy(&fe->ops, (adap->type == SYS_ISDBS) ? &pt3_fe_s_ops : &pt3_fe_t_ops, sizeof(struct dvb_frontend_ops));
+	fe->demodulator_priv = state;
+	return fe;
+}
+
+EXPORT_SYMBOL(pt3_fe_attach);
+
diff --git a/drivers/media/dvb-frontends/pt3_fe.h b/drivers/media/dvb-frontends/pt3_fe.h
new file mode 100644
index 0000000..0d614f3
--- /dev/null
+++ b/drivers/media/dvb-frontends/pt3_fe.h
@@ -0,0 +1,22 @@
+/*
+ * DVB Frontend driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_FE_H__
+#define	__PT3_FE_H__
+
+struct dvb_frontend *pt3_fe_attach(struct pt3_adapter *adap);
+
+#endif
diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
index 53196f1..87018c8 100644
--- a/drivers/media/pci/Kconfig
+++ b/drivers/media/pci/Kconfig
@@ -30,7 +30,6 @@ source "drivers/media/pci/cx88/Kconfig"
 source "drivers/media/pci/bt8xx/Kconfig"
 source "drivers/media/pci/saa7134/Kconfig"
 source "drivers/media/pci/saa7164/Kconfig"
-
 endif
 
 if MEDIA_DIGITAL_TV_SUPPORT
@@ -40,6 +39,7 @@ source "drivers/media/pci/b2c2/Kconfig"
 source "drivers/media/pci/pluto2/Kconfig"
 source "drivers/media/pci/dm1105/Kconfig"
 source "drivers/media/pci/pt1/Kconfig"
+source "drivers/media/pci/pt3/Kconfig"
 source "drivers/media/pci/mantis/Kconfig"
 source "drivers/media/pci/ngene/Kconfig"
 source "drivers/media/pci/ddbridge/Kconfig"
diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
index 35cc578..f7be6bc 100644
--- a/drivers/media/pci/Makefile
+++ b/drivers/media/pci/Makefile
@@ -7,6 +7,7 @@ obj-y        +=	ttpci/		\
 		pluto2/		\
 		dm1105/		\
 		pt1/		\
+		pt3/		\
 		mantis/		\
 		ngene/		\
 		ddbridge/	\
diff --git a/drivers/media/pci/pt3/Kconfig b/drivers/media/pci/pt3/Kconfig
new file mode 100644
index 0000000..519fdaa
--- /dev/null
+++ b/drivers/media/pci/pt3/Kconfig
@@ -0,0 +1,10 @@
+config PT3_DVB
+	tristate "Earthsoft PT3 ISDB-S/T cards"
+	depends on DVB_CORE && PCI
+	select DVB_PT3_FE if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_PT3 if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Support for Earthsoft PT3 PCI-Express cards.
+	  You may also need to enable PT3 DVB frontend & tuner.
+	  Say Y or M if you own such a device and want to use it.
+
diff --git a/drivers/media/pci/pt3/Makefile b/drivers/media/pci/pt3/Makefile
new file mode 100644
index 0000000..b030927
--- /dev/null
+++ b/drivers/media/pci/pt3/Makefile
@@ -0,0 +1,6 @@
+pt3_dvb-objs := pt3.o pt3_dma.o pt3_i2c.o
+
+obj-$(CONFIG_PT3_DVB) += pt3_dvb.o
+
+ccflags-y += -Idrivers/media/dvb-core -Idrivers/media/dvb-frontends -Idrivers/media/tuners
+
diff --git a/drivers/media/pci/pt3/pt3.c b/drivers/media/pci/pt3/pt3.c
new file mode 100644
index 0000000..d1b1952
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3.c
@@ -0,0 +1,579 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_common.h"
+#include "pt3_dma.h"
+#include "pt3_i2c.h"
+#include "tc90522.h"
+#include "qm1d1c0042.h"
+#include "mxl301rf.h"
+#include "pt3_fe.h"
+
+#define DRV_NAME "pt3_dvb"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 DVB Driver");
+MODULE_LICENSE("GPL");
+
+static DEFINE_PCI_DEVICE_TABLE(pt3_id_table) = {
+	{ PCI_DEVICE(0x1172, 0x4c15) },
+	{ },
+};
+MODULE_DEVICE_TABLE(pci, pt3_id_table);
+
+static int lnb = 2;	/* used if not set by frontend or the value is invalid */
+module_param(lnb, int, 0);
+MODULE_PARM_DESC(lnb, "LNB level (0:OFF 1:+11V 2:+15V)");
+
+struct {
+	u32 bits;
+	char *str;
+} pt3_lnb[] = {
+	{0b1100,  "0V"},
+	{0b1101, "11V"},
+	{0b1111, "15V"},
+};
+
+static int pt3_update_lnb(struct pt3_board *pt3)
+{
+	u8 i, lnb_eff = 0;
+
+	if (pt3->reset) {
+		writel(pt3_lnb[0].bits, pt3->bar_reg + REG_SYSTEM_W);
+		pt3->reset = false;
+		pt3->lnb = 0;
+	} else {
+		struct pt3_adapter *adap;
+		mutex_lock(&pt3->lock);
+		for (i = 0; i < PT3_NR_ADAPS; i++) {
+			adap = pt3->adap[i];
+			pr_debug("#%d in_use %d sleep %d\n", adap->idx, adap->in_use, adap->sleep);
+			if ((adap->type == SYS_ISDBS) && (!adap->sleep)) {
+				lnb_eff |= adap->voltage == SEC_VOLTAGE_13 ? 1
+					:  adap->voltage == SEC_VOLTAGE_18 ? 2
+					:  lnb;
+			}
+		}
+		mutex_unlock(&pt3->lock);
+		if (unlikely(lnb_eff < 0 || 2 < lnb_eff)) {
+			pr_err("Inconsistent LNB settings\n");
+			return -EINVAL;
+		}
+		if (pt3->lnb != lnb_eff) {
+			writel(pt3_lnb[lnb_eff].bits, pt3->bar_reg + REG_SYSTEM_W);
+			pt3->lnb = lnb_eff;
+		}
+	}
+	pr_debug("LNB=%s\n", pt3_lnb[lnb_eff].str);
+	return 0;
+}
+
+void pt3_filter(struct pt3_adapter *adap, struct dvb_demux *demux, u8 *buf, size_t count)
+{
+	dvb_dmx_swfilter(demux, buf, count);
+}
+
+int pt3_thread(void *data)
+{
+	size_t ret;
+	struct pt3_adapter *adap = data;
+
+	set_freezable();
+	while (!kthread_should_stop()) {
+		try_to_freeze();
+		while ((ret = pt3_dma_copy(adap->dma, &adap->demux)) > 0)
+			;
+		if (ret < 0) {
+			pr_debug("#%d fail dma_copy\n", adap->idx);
+			msleep_interruptible(1);
+		}
+	}
+	return 0;
+}
+
+static int pt3_start_polling(struct pt3_adapter *adap)
+{
+	int ret = 0;
+
+	mutex_lock(&adap->lock);
+	if (!adap->kthread) {
+		adap->kthread = kthread_run(pt3_thread, adap, DRV_NAME "_%d", adap->idx);
+		if (IS_ERR(adap->kthread)) {
+			ret = PTR_ERR(adap->kthread);
+			adap->kthread = NULL;
+		} else {
+			pt3_dma_set_test_mode(adap->dma, RESET, 0);	/* reset_error_count */
+			pt3_dma_set_enabled(adap->dma, true);
+		}
+	}
+	mutex_unlock(&adap->lock);
+	return ret;
+}
+
+static void pt3_stop_polling(struct pt3_adapter *adap)
+{
+	mutex_lock(&adap->lock);
+	if (adap->kthread) {
+		pt3_dma_set_enabled(adap->dma, false);
+		pr_debug("#%d DMA ts_err packet cnt %d\n",
+			adap->idx, pt3_dma_get_ts_error_packet_count(adap->dma));
+		kthread_stop(adap->kthread);
+		adap->kthread = NULL;
+	}
+	mutex_unlock(&adap->lock);
+}
+
+static int pt3_start_feed(struct dvb_demux_feed *feed)
+{
+	int ret;
+	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
+	if (!adap->users++) {
+		if (adap->in_use) {
+			pr_err("#%d device is already used\n", adap->idx);
+			return -EIO;
+		}
+		pr_debug("#%d %s selected, DMA %s\n",
+			adap->idx, adap->str, pt3_dma_get_status(adap->dma) & 1 ? "ON" : "OFF");
+		adap->in_use = true;
+		ret = pt3_start_polling(adap);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int pt3_stop_feed(struct dvb_demux_feed *feed)
+{
+	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
+	if (!--adap->users) {
+		pt3_stop_polling(adap);
+		adap->in_use = false;
+		msleep_interruptible(40);
+	}
+	return 0;
+}
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static struct pt3_adapter *pt3_alloc_adapter(struct pt3_board *pt3)
+{
+	int ret;
+	struct dvb_adapter *dvb;
+	struct dvb_demux *demux;
+	struct dmxdev *dmxdev;
+	struct pt3_adapter *adap = kzalloc(sizeof(struct pt3_adapter), GFP_KERNEL);
+
+	if (!adap) {
+		ret = -ENOMEM;
+		goto err;
+	}
+	adap->pt3 = pt3;
+	adap->voltage = SEC_VOLTAGE_OFF;
+	adap->sleep = true;
+
+	dvb = &adap->dvb;
+	dvb->priv = adap;
+	ret = dvb_register_adapter(dvb, DRV_NAME, THIS_MODULE, &pt3->pdev->dev, adapter_nr);
+	if (ret < 0)
+		goto err_kfree;
+
+	demux = &adap->demux;
+	demux->dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING;
+	demux->priv = adap;
+	demux->feednum = 256;
+	demux->filternum = 256;
+	demux->start_feed = pt3_start_feed;
+	demux->stop_feed = pt3_stop_feed;
+	demux->write_to_decoder = NULL;
+	ret = dvb_dmx_init(demux);
+	if (ret < 0)
+		goto err_unregister_adapter;
+
+	dmxdev = &adap->dmxdev;
+	dmxdev->filternum = 256;
+	dmxdev->demux = &demux->dmx;
+	dmxdev->capabilities = 0;
+	ret = dvb_dmxdev_init(dmxdev, dvb);
+	if (ret < 0)
+		goto err_dmx_release;
+
+	return adap;
+
+err_dmx_release:
+	dvb_dmx_release(demux);
+err_unregister_adapter:
+	dvb_unregister_adapter(dvb);
+err_kfree:
+	kfree(adap);
+err:
+	return ERR_PTR(ret);
+}
+
+int pt3_set_freq(struct pt3_adapter *adap, u32 ch)
+{
+	int ret;
+
+	pr_debug("#%d %s set_freq channel=%d\n", adap->idx, adap->str, ch);
+
+	if (adap->type == SYS_ISDBS)
+		ret = qm1d1c0042_set_frequency(adap->qm, ch);
+	else
+		ret = mxl301rf_set_frequency(adap, ch, 0);
+	return ret;
+}
+
+int pt3_tuner_set_sleep(struct pt3_adapter *adap, bool sleep)
+{
+	int ret;
+	pr_debug("#%d %p %s %s\n", adap->idx, adap, adap->str, sleep ? "Sleep" : "Wakeup");
+
+	if (adap->type == SYS_ISDBS)
+		ret = qm1d1c0042_set_sleep(adap->qm, sleep);
+	else
+		ret = mxl301rf_set_sleep(adap, sleep);
+	msleep_interruptible(10);
+	return ret;
+}
+
+static void pt3_cleanup_adapter(struct pt3_adapter *adap)
+{
+	if (!adap)
+		return;
+	if (adap->kthread)
+		kthread_stop(adap->kthread);
+	if (adap->fe)
+		dvb_unregister_frontend(adap->fe);
+	if (!adap->sleep)
+		pt3_tuner_set_sleep(adap, true);
+	if (adap->qm)
+		vfree(adap->qm);
+	if (adap->dma) {
+		if (adap->dma->enabled)
+			pt3_dma_set_enabled(adap->dma, false);
+		pt3_dma_free(adap->dma);
+	}
+	adap->demux.dmx.close(&adap->demux.dmx);
+	dvb_dmxdev_release(&adap->dmxdev);
+	dvb_dmx_release(&adap->demux);
+	dvb_unregister_adapter(&adap->dvb);
+	kfree(adap);
+}
+
+static int pt3_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	adap->voltage = voltage;
+	return (adap->orig_voltage) ? adap->orig_voltage(fe, voltage) : 0;
+}
+
+static int pt3_sleep(struct dvb_frontend *fe)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	adap->sleep = true;
+	pt3_update_lnb(adap->pt3);
+	return (adap->orig_sleep) ? adap->orig_sleep(fe) : 0;
+}
+
+static int pt3_wakeup(struct dvb_frontend *fe)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	adap->sleep = false;
+	pt3_update_lnb(adap->pt3);
+	return (adap->orig_init) ? adap->orig_init(fe) : 0;
+}
+
+static int pt3_init_frontends(struct pt3_board *pt3)
+{
+	struct dvb_frontend *fe[PT3_NR_ADAPS];
+	int i, ret;
+
+	for (i = 0; i < PT3_NR_ADAPS; i++) {
+		fe[i] = pt3_fe_attach(pt3->adap[i]);
+		if (!fe[i]) {
+			while (i--)
+				fe[i]->ops.release(fe[i]);
+			return -ENOMEM;
+		}
+	}
+	for (i = 0; i < PT3_NR_ADAPS; i++) {
+		struct pt3_adapter *adap = pt3->adap[i];
+
+		adap->orig_voltage     = fe[i]->ops.set_voltage;
+		adap->orig_sleep       = fe[i]->ops.sleep;
+		adap->orig_init        = fe[i]->ops.init;
+		fe[i]->ops.set_voltage = pt3_set_voltage;
+		fe[i]->ops.sleep       = pt3_sleep;
+		fe[i]->ops.init        = pt3_wakeup;
+
+		ret = dvb_register_frontend(&adap->dvb, fe[i]);
+		if (ret >= 0)
+			adap->fe = fe[i];
+		else {
+			while (i--)
+				dvb_unregister_frontend(fe[i]);
+			for (i = 0; i < PT3_NR_ADAPS; i++)
+				fe[i]->ops.release(fe[i]);
+			return ret;
+		}
+	}
+	return 0;
+}
+
+static void pt3_remove(struct pci_dev *pdev)
+{
+	int i;
+	struct pt3_board *pt3 = pci_get_drvdata(pdev);
+
+	if (pt3) {
+		pt3->reset = true;
+		pt3_update_lnb(pt3);
+		if (pt3->adap && pt3->adap[PT3_NR_ADAPS-1])
+			tc90522_set_powers(pt3->adap[PT3_NR_ADAPS-1], false, false);
+		for (i = 0; i < PT3_NR_ADAPS; i++)
+			pt3_cleanup_adapter(pt3->adap[i]);
+		pt3_i2c_reset(pt3);
+		i2c_del_adapter(&pt3->i2c);
+		if (pt3->bar_mem)
+			iounmap(pt3->bar_mem);
+		if (pt3->bar_reg)
+			iounmap(pt3->bar_reg);
+		pci_release_selected_regions(pdev, pt3->bars);
+		kfree(pt3);
+	}
+	pci_disable_device(pdev);
+}
+
+static int pt3_abort(struct pci_dev *pdev, int ret, char *fmt, ...)
+{
+	va_list ap;
+	char *s = NULL;
+	int slen;
+
+	va_start(ap, fmt);
+	slen = vsnprintf(s, 0, fmt, ap);
+	s = vzalloc(slen);
+	if (slen > 0 && s) {
+		vsnprintf(s, slen, fmt, ap);
+		dev_alert(&pdev->dev, "%s", s);
+		vfree(s);
+	}
+	va_end(ap);
+	pt3_remove(pdev);
+	return ret;
+}
+
+struct {
+	fe_delivery_system_t type;
+	u8 addr_tuner, addr_demod;
+	int init_ch;
+	char *str;
+} pt3_config[] = {
+	{SYS_ISDBS, 0x63, 0b00010001,  0, "ISDB-S"},
+	{SYS_ISDBS, 0x60, 0b00010011,  0, "ISDB-S"},
+	{SYS_ISDBT, 0x62, 0b00010000, 70, "ISDB-T"},
+	{SYS_ISDBT, 0x61, 0b00010010, 71, "ISDB-T"},
+};
+
+int pt3_tuner_init(struct pt3_adapter *adap)
+{
+	qm1d1c0042_tuner_init(adap->qm);
+	adap->pt3->i2c_addr = 0;
+	if (pt3_i2c_flush(adap->pt3, true, 0))
+		return -EIO;
+
+	if (qm1d1c0042_tuner_init(adap->qm))
+		return -EIO;
+	adap->pt3->i2c_addr = 0;
+	if (pt3_i2c_flush(adap->pt3, true, 0))
+		return -EIO;
+	return 0;
+}
+
+static int pt3_tuner_init_all(struct pt3_board *pt3)
+{
+	int ret, i, j;
+	struct pt3_ts_pins_mode pins;
+
+	if (!pt3_i2c_is_clean(pt3)) {
+		pr_debug("cleanup I2C\n");
+		ret = pt3_i2c_flush(pt3, false, 0);
+		if (ret)
+			goto last;
+		msleep_interruptible(10);
+	}
+	for (i = 0; i < PT3_NR_ADAPS; i++) {
+		ret = tc90522_init(pt3->adap[i]);
+		pr_debug("#%d tc_init ret=%d\n", i, ret);
+	}
+	ret = tc90522_set_powers(pt3->adap[PT3_NR_ADAPS-1], true, false);
+	if (ret) {
+		pr_debug("fail set powers.\n");
+		goto last;
+	}
+
+	pins.clock_data = PT3_TS_PIN_MODE_NORMAL;
+	pins.byte       = PT3_TS_PIN_MODE_NORMAL;
+	pins.valid      = PT3_TS_PIN_MODE_NORMAL;
+	for (i = 0; i < PT3_NR_ADAPS; i++) {
+		ret = tc90522_set_ts_pins_mode(pt3->adap[i], &pins);
+		if (ret)
+			pr_debug("#%d %s fail set ts pins mode ret=%d\n", i, pt3->adap[i]->str, ret);
+	}
+	msleep_interruptible(1);
+
+	for (i = 0; i < PT3_NR_ADAPS; i++)
+		if (pt3->adap[i]->type == SYS_ISDBS) {
+			for (j = 0; j < 10; j++) {
+				if (j)
+					pr_debug("retry pt3_tuner_init\n");
+				ret = pt3_tuner_init(pt3->adap[i]);
+				if (!ret)
+					break;
+				msleep_interruptible(1);
+			}
+			if (ret) {
+				pr_debug("#%d fail pt3_tuner_init ret=0x%x\n", i, ret);
+				goto last;
+			}
+		}
+	ret = pt3_i2c_flush(pt3, false, PT3_I2C_START_ADDR);
+	if (ret)
+		goto last;
+	ret = tc90522_set_powers(pt3->adap[PT3_NR_ADAPS-1], true, true);
+	if (ret) {
+		pr_debug("fail tc_set_powers,\n");
+		goto last;
+	}
+	for (i = 0; i < PT3_NR_ADAPS; i++) {
+		struct pt3_adapter *adap = pt3->adap[i];
+		ret = pt3_tuner_set_sleep(adap, false);
+		if (ret)
+			goto last;
+		ret = pt3_set_freq(adap, adap->init_ch);
+		if (ret)
+			pr_debug("fail set_frequency, ret=%d\n", ret);
+		ret = pt3_tuner_set_sleep(adap, true);
+		if (ret)
+			goto last;
+	}
+last:
+	return ret;
+}
+
+static const struct i2c_algorithm pt3_i2c_algo = {
+	.functionality = pt3_i2c_func,
+	.master_xfer = pt3_i2c_xfer,
+};
+
+static int pt3_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+	struct pt3_board *pt3;
+	struct pt3_adapter *adap;
+	struct i2c_adapter *i2c;
+	int i, ret, bars = pci_select_bars(pdev, IORESOURCE_MEM);
+
+	ret = pci_enable_device(pdev);
+	if (ret < 0)
+		return pt3_abort(pdev, ret, "PCI device unusable\n");
+	ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(64));
+	if (ret)
+		return pt3_abort(pdev, ret, "DMA mask error\n");
+	pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64));
+
+	pci_read_config_dword(pdev, PCI_CLASS_REVISION, &i);
+	if ((i & 0xFF) != 1)
+		return pt3_abort(pdev, ret, "Revision 0x%x is not supported\n", i & 0xFF);
+	ret = pci_request_selected_regions(pdev, bars, DRV_NAME);
+	if (ret < 0)
+		return pt3_abort(pdev, ret, "Could not request regions\n");
+
+	pci_set_master(pdev);
+	ret = pci_save_state(pdev);
+	if (ret)
+		return pt3_abort(pdev, ret, "Failed pci_save_state\n");
+	pt3 = kzalloc(sizeof(struct pt3_board), GFP_KERNEL);
+	if (!pt3)
+		return pt3_abort(pdev, -ENOMEM, "struct pt3_board out of memory\n");
+
+	pt3->bars = bars;
+	pt3->pdev = pdev;
+	pci_set_drvdata(pdev, pt3);
+	pt3->bar_reg = pci_ioremap_bar(pdev, 0);
+	pt3->bar_mem = pci_ioremap_bar(pdev, 2);
+	if (!pt3->bar_reg || !pt3->bar_mem)
+		return pt3_abort(pdev, -EIO, "Failed pci_ioremap_bar\n");
+
+	ret = readl(pt3->bar_reg + REG_VERSION);
+	i = ((ret >> 24) & 0xFF);
+	if (i != 3)
+		return pt3_abort(pdev, -EIO, "ID=0x%x, not a PT3\n", i);
+	i = ((ret >>  8) & 0xFF);
+	if (i != 4)
+		return pt3_abort(pdev, -EIO, "FPGA version 0x%x is not supported\n", i);
+	mutex_init(&pt3->lock);
+
+	for (i = 0; i < PT3_NR_ADAPS; i++) {
+		pt3->adap[i] = NULL;
+		adap = pt3_alloc_adapter(pt3);
+		if (IS_ERR(adap))
+			return pt3_abort(pdev, PTR_ERR(adap), "Failed pt3_alloc_adapter\n");
+		adap->idx = i;
+		adap->dma = pt3_dma_create(adap);
+		if (!adap->dma)
+			return pt3_abort(pdev, -ENOMEM, "Failed pt3_dma_create\n");
+		mutex_init(&adap->lock);
+		pt3->adap[i] = adap;
+		adap->type       = pt3_config[i].type;
+		adap->addr_tuner = pt3_config[i].addr_tuner;
+		adap->addr_demod    = pt3_config[i].addr_demod;
+		adap->init_ch    = pt3_config[i].init_ch;
+		adap->str        = pt3_config[i].str;
+		if (adap->type == SYS_ISDBS) {
+			adap->qm = vzalloc(sizeof(struct qm1d1c0042));
+			if (!adap->qm)
+				return pt3_abort(pdev, -ENOMEM, "QM out of memory\n");
+			adap->qm->adap = adap;
+		}
+		adap->sleep = true;
+	}
+	pt3->reset = true;
+	pt3_update_lnb(pt3);
+
+	i2c = &pt3->i2c;
+	i2c->algo = &pt3_i2c_algo;
+	i2c->algo_data = NULL;
+	i2c->dev.parent = &pdev->dev;
+	strcpy(i2c->name, DRV_NAME);
+	i2c_set_adapdata(i2c, pt3);
+	ret = i2c_add_adapter(i2c);
+	if (ret < 0)
+		return pt3_abort(pdev, ret, "Cannot add I2C\n");
+
+	if (pt3_tuner_init_all(pt3))
+		return pt3_abort(pdev, ret, "Failed pt3_tuner_init_all\n");
+	ret = pt3_init_frontends(pt3);
+	return (ret >= 0) ? ret : pt3_abort(pdev, ret, "Failed pt3_init_frontends\n");
+}
+
+static struct pci_driver pt3_driver = {
+	.name		= DRV_NAME,
+	.probe		= pt3_probe,
+	.remove		= pt3_remove,
+	.id_table	= pt3_id_table,
+};
+
+module_pci_driver(pt3_driver);
+
diff --git a/drivers/media/pci/pt3/pt3.h b/drivers/media/pci/pt3/pt3.h
new file mode 100644
index 0000000..057d1b4
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3.h
@@ -0,0 +1,23 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_H__
+#define	__PT3_H__
+
+void pt3_filter(struct pt3_adapter *adap, struct dvb_demux *demux, u8 *buf, size_t count);
+
+#endif
+
diff --git a/drivers/media/pci/pt3/pt3_dma.c b/drivers/media/pci/pt3/pt3_dma.c
new file mode 100644
index 0000000..33b1a2e
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_dma.c
@@ -0,0 +1,335 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_common.h"
+#include "pt3_dma.h"
+#include "pt3.h"
+
+#define PT3_DMA_MAX_DESCS	204
+#define PT3_DMA_PAGE_SIZE	(PT3_DMA_MAX_DESCS * sizeof(struct pt3_dma_desc))
+#define PT3_DMA_BLOCK_COUNT	17
+#define PT3_DMA_BLOCK_SIZE	(PT3_DMA_PAGE_SIZE * 47)
+#define PT3_DMA_TS_BUF_SIZE	(PT3_DMA_BLOCK_SIZE * PT3_DMA_BLOCK_COUNT)
+#define PT3_DMA_TS_SYNC		0x47
+#define PT3_DMA_TS_NOT_SYNC	0x74
+
+void pt3_dma_free(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *page;
+	u32 i;
+
+	if (dma->ts_info) {
+		for (i = 0; i < dma->ts_count; i++) {
+			page = &dma->ts_info[i];
+			if (page->data)
+				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
+		}
+		kfree(dma->ts_info);
+	}
+	if (dma->desc_info) {
+		for (i = 0; i < dma->desc_count; i++) {
+			page = &dma->desc_info[i];
+			if (page->data)
+				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
+		}
+		kfree(dma->desc_info);
+	}
+	kfree(dma);
+}
+
+struct pt3_dma_desc {
+	u64 page_addr;
+	u32 page_size;
+	u64 next_desc;
+} __packed;
+
+void pt3_dma_build_page_descriptor(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *desc_info, *ts_info;
+	u64 ts_addr, desc_addr;
+	u32 i, j, ts_size, desc_remain, ts_info_pos, desc_info_pos;
+	struct pt3_dma_desc *prev, *curr;
+
+	pr_debug("#%d build page descriptor ts_count=%d ts_size=%d desc_count=%d desc_size=%d\n",
+		dma->adap->idx, dma->ts_count, dma->ts_info[0].size, dma->desc_count, dma->desc_info[0].size);
+	desc_info_pos = ts_info_pos = 0;
+	desc_info = &dma->desc_info[desc_info_pos];
+	desc_addr   = desc_info->addr;
+	desc_remain = desc_info->size;
+	desc_info->data_pos = 0;
+	prev = NULL;
+	curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+	desc_info_pos++;
+
+	for (i = 0; i < dma->ts_count; i++) {
+		if (unlikely(ts_info_pos >= dma->ts_count)) {
+			pr_debug("#%d ts_info overflow max=%d curr=%d\n", dma->adap->idx, dma->ts_count, ts_info_pos);
+			return;
+		}
+		ts_info = &dma->ts_info[ts_info_pos];
+		ts_addr = ts_info->addr;
+		ts_size = ts_info->size;
+		ts_info_pos++;
+		pr_debug("#%d i=%d, ts_info addr=0x%llx ts_size=%d\n", dma->adap->idx, i, ts_addr, ts_size);
+		for (j = 0; j < ts_size / PT3_DMA_PAGE_SIZE; j++) {
+			if (desc_remain < sizeof(struct pt3_dma_desc)) {
+				if (unlikely(desc_info_pos >= dma->desc_count)) {
+					pr_debug("#%d desc_info overflow max=%d curr=%d\n",
+						dma->adap->idx, dma->desc_count, desc_info_pos);
+					return;
+				}
+				desc_info = &dma->desc_info[desc_info_pos];
+				desc_info->data_pos = 0;
+				curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+				pr_debug("#%d desc_info_pos=%d ts_addr=0x%llx remain=%d\n",
+					dma->adap->idx, desc_info_pos, ts_addr, desc_remain);
+				desc_addr = desc_info->addr;
+				desc_remain = desc_info->size;
+				desc_info_pos++;
+			}
+			if (prev)
+				prev->next_desc = desc_addr | 0b10;
+			curr->page_addr = ts_addr           | 0b111;
+			curr->page_size = PT3_DMA_PAGE_SIZE | 0b111;
+			curr->next_desc = 0b10;
+			pr_debug("#%d j=%d dma write desc ts_addr=0x%llx desc_info_pos=%d desc_remain=%d\n",
+				dma->adap->idx, j, ts_addr, desc_info_pos, desc_remain);
+			ts_addr += PT3_DMA_PAGE_SIZE;
+
+			prev = curr;
+			desc_info->data_pos += sizeof(struct pt3_dma_desc);
+			if (unlikely(desc_info->data_pos > desc_info->size)) {
+				pr_debug("#%d dma desc_info data overflow max=%d curr=%d\n",
+					dma->adap->idx, desc_info->size, desc_info->data_pos);
+				return;
+			}
+			curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+			desc_addr += sizeof(struct pt3_dma_desc);
+			desc_remain -= sizeof(struct pt3_dma_desc);
+		}
+	}
+	if (prev)
+		prev->next_desc = dma->desc_info->addr | 0b10;
+}
+
+struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap)
+{
+	struct pt3_dma_page *page;
+	u32 i;
+
+	struct pt3_dma *dma = kzalloc(sizeof(struct pt3_dma), GFP_KERNEL);
+	if (!dma) {
+		pr_debug("#%d fail allocate PT3_DMA\n", adap->idx);
+		goto fail;
+	}
+	dma->adap = adap;
+	dma->enabled = false;
+	mutex_init(&dma->lock);
+
+	dma->ts_count = PT3_DMA_BLOCK_COUNT;
+	dma->ts_info = kzalloc(sizeof(struct pt3_dma_page) * dma->ts_count, GFP_KERNEL);
+	if (!dma->ts_info) {
+		pr_debug("#%d fail allocate TS DMA page\n", adap->idx);
+		goto fail;
+	}
+	pr_debug("#%d Alloc TS buf (ts_count %d)\n", adap->idx, dma->ts_count);
+	for (i = 0; i < dma->ts_count; i++) {
+		page = &dma->ts_info[i];
+		page->size = PT3_DMA_BLOCK_SIZE;
+		page->data_pos = 0;
+		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
+		if (!page->data) {
+			pr_debug("#%d fail alloc_consistent. %d\n", adap->idx, i);
+			goto fail;
+		}
+	}
+
+	dma->desc_count = 1 + (PT3_DMA_TS_BUF_SIZE / PT3_DMA_PAGE_SIZE - 1) / PT3_DMA_MAX_DESCS;
+	dma->desc_info = kzalloc(sizeof(struct pt3_dma_page) * dma->desc_count, GFP_KERNEL);
+	if (!dma->desc_info) {
+		pr_debug("#%d fail allocate Desc DMA page\n", adap->idx);
+		goto fail;
+	}
+	pr_debug("#%d Alloc Descriptor buf (desc_count %d)\n", adap->idx, dma->desc_count);
+	for (i = 0; i < dma->desc_count; i++) {
+		page = &dma->desc_info[i];
+		page->size = PT3_DMA_PAGE_SIZE;
+		page->data_pos = 0;
+		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
+		if (!page->data) {
+			pr_debug("#%d fail alloc_consistent %d\n", adap->idx, i);
+			goto fail;
+		}
+	}
+
+	pr_debug("#%d build page descriptor\n", adap->idx);
+	pt3_dma_build_page_descriptor(dma);
+	return dma;
+fail:
+	if (dma)
+		pt3_dma_free(dma);
+	return NULL;
+}
+
+void __iomem *pt3_dma_get_base_addr(struct pt3_dma *dma)
+{
+	return dma->adap->pt3->bar_reg + REG_BASE + (0x18 * dma->adap->idx);
+}
+
+void pt3_dma_reset(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *ts;
+	u32 i;
+
+	for (i = 0; i < dma->ts_count; i++) {
+		ts = &dma->ts_info[i];
+		memset(ts->data, 0, ts->size);
+		ts->data_pos = 0;
+		*ts->data = PT3_DMA_TS_NOT_SYNC;
+	}
+	dma->ts_pos = 0;
+}
+
+void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled)
+{
+	void __iomem *base = pt3_dma_get_base_addr(dma);
+	u64 start_addr = dma->desc_info->addr;
+
+	if (enabled) {
+		pr_debug("#%d DMA enable start_addr=%llx\n", dma->adap->idx, start_addr);
+		pt3_dma_reset(dma);
+		writel(1 << 1, base + REG_DMA_CTL);	/* stop DMA */
+		writel(PT3_SHIFT_MASK(start_addr,  0, 32), base + REG_DMA_DESC_L);
+		writel(PT3_SHIFT_MASK(start_addr, 32, 32), base + REG_DMA_DESC_H);
+		pr_debug("set descriptor address low %llx\n",  PT3_SHIFT_MASK(start_addr,  0, 32));
+		pr_debug("set descriptor address high %llx\n", PT3_SHIFT_MASK(start_addr, 32, 32));
+		writel(1 << 0, base + REG_DMA_CTL);	/* start DMA */
+	} else {
+		pr_debug("#%d DMA disable\n", dma->adap->idx);
+		writel(1 << 1, base + REG_DMA_CTL);	/* stop DMA */
+		while (1) {
+			if (!PT3_SHIFT_MASK(readl(base + REG_STATUS), 0, 1))
+				break;
+			msleep_interruptible(1);
+		}
+	}
+	dma->enabled = enabled;
+}
+
+/* convert Gray code to binary, e.g. 1001 -> 1110 */
+static u32 pt3_dma_gray2binary(u32 gray, u32 bit)
+{
+	u32 binary = 0, i, j, k;
+
+	for (i = 0; i < bit; i++) {
+		k = 0;
+		for (j = i; j < bit; j++)
+			k = k ^ PT3_SHIFT_MASK(gray, j, 1);
+		binary |= k << i;
+	}
+	return binary;
+}
+
+u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma)
+{
+	return pt3_dma_gray2binary(readl(pt3_dma_get_base_addr(dma) + REG_TS_ERR), 32);
+}
+
+void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval)
+{
+	void __iomem *base = pt3_dma_get_base_addr(dma);
+	u32 data = mode | initval;
+	pr_debug("set_test_mode base=%p data=0x%04x\n", base, data);
+	writel(data, base + REG_TS_CTL);
+}
+
+bool pt3_dma_ready(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *ts;
+	u8 *p;
+
+	u32 next = dma->ts_pos + 1;
+	if (next >= dma->ts_count)
+		next = 0;
+	ts = &dma->ts_info[next];
+	p = &ts->data[ts->data_pos];
+
+	if (*p == PT3_DMA_TS_SYNC)
+		return true;
+	if (*p == PT3_DMA_TS_NOT_SYNC)
+		return false;
+
+	pr_debug("#%d invalid sync byte value=0x%02x ts_pos=%d data_pos=%d curr=0x%02x\n",
+		dma->adap->idx, *p, next, ts->data_pos, dma->ts_info[dma->ts_pos].data[0]);
+	return false;
+}
+
+ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux)
+{
+	bool ready;
+	struct pt3_dma_page *ts;
+	u32 i, prev;
+	size_t csize, remain = dma->ts_info[dma->ts_pos].size;
+
+	mutex_lock(&dma->lock);
+	pr_debug("#%d dma_copy ts_pos=0x%x data_pos=0x%x\n",
+		   dma->adap->idx, dma->ts_pos, dma->ts_info[dma->ts_pos].data_pos);
+	for (;;) {
+		for (i = 0; i < 20; i++) {
+			ready = pt3_dma_ready(dma);
+			if (ready)
+				break;
+			msleep_interruptible(30);
+		}
+		if (!ready) {
+			pr_debug("#%d dma_copy NOT READY\n", dma->adap->idx);
+			goto last;
+		}
+		prev = dma->ts_pos - 1;
+		if (prev < 0 || dma->ts_count <= prev)
+			prev = dma->ts_count - 1;
+		if (dma->ts_info[prev].data[0] != PT3_DMA_TS_NOT_SYNC)
+			pr_debug("#%d DMA buffer overflow. prev=%d data=0x%x\n",
+					dma->adap->idx, prev, dma->ts_info[prev].data[0]);
+		ts = &dma->ts_info[dma->ts_pos];
+		for (;;) {
+			csize = (remain < (ts->size - ts->data_pos)) ?
+				 remain : (ts->size - ts->data_pos);
+			pt3_filter(dma->adap, demux, &ts->data[ts->data_pos], csize);
+			remain -= csize;
+			ts->data_pos += csize;
+			if (ts->data_pos >= ts->size) {
+				ts->data_pos = 0;
+				ts->data[ts->data_pos] = PT3_DMA_TS_NOT_SYNC;
+				dma->ts_pos++;
+				if (dma->ts_pos >= dma->ts_count)
+					dma->ts_pos = 0;
+				break;
+			}
+			if (remain <= 0)
+				goto last;
+		}
+	}
+last:
+	mutex_unlock(&dma->lock);
+	return dma->ts_info[dma->ts_pos].size - remain;
+}
+
+u32 pt3_dma_get_status(struct pt3_dma *dma)
+{
+	return readl(pt3_dma_get_base_addr(dma) + REG_STATUS);
+}
+
diff --git a/drivers/media/pci/pt3/pt3_dma.h b/drivers/media/pci/pt3/pt3_dma.h
new file mode 100644
index 0000000..ecae4c1
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_dma.h
@@ -0,0 +1,48 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_DMA_H__
+#define	__PT3_DMA_H__
+
+struct pt3_dma_page {
+	dma_addr_t addr;
+	u8 *data;
+	u32 size, data_pos;
+};
+
+enum pt3_dma_mode {
+	USE_LFSR = 1 << 16,
+	REVERSE  = 1 << 17,
+	RESET    = 1 << 18,
+};
+
+struct pt3_dma {
+	struct pt3_adapter *adap;
+	bool enabled;
+	u32 ts_pos, ts_count, desc_count;
+	struct pt3_dma_page *ts_info, *desc_info;
+	struct mutex lock;
+};
+
+ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux);
+struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap);
+void pt3_dma_free(struct pt3_dma *dma);
+u32 pt3_dma_get_status(struct pt3_dma *dma);
+u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma);
+void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled);
+void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval);
+
+#endif
diff --git a/drivers/media/pci/pt3/pt3_i2c.c b/drivers/media/pci/pt3/pt3_i2c.c
new file mode 100644
index 0000000..7f0eb7c
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_i2c.c
@@ -0,0 +1,167 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_common.h"
+#include "pt3_i2c.h"
+
+enum pt3_i2c_cmd {
+	I_END,
+	I_ADDRESS,
+	I_CLOCK_L,
+	I_CLOCK_H,
+	I_DATA_L,
+	I_DATA_H,
+	I_RESET,
+	I_SLEEP,
+	I_DATA_L_NOP  = 0x08,
+	I_DATA_H_NOP  = 0x0c,
+	I_DATA_H_READ = 0x0d,
+	I_DATA_H_ACK0 = 0x0e,
+	I_DATA_H_ACK1 = 0x0f,
+};
+
+bool pt3_i2c_is_clean(struct pt3_board *pt3)
+{
+	return PT3_SHIFT_MASK(readl(pt3->bar_reg + REG_I2C_R), 3, 1);
+}
+
+void pt3_i2c_reset(struct pt3_board *pt3)
+{
+	writel(1 << 17, pt3->bar_reg + REG_I2C_W);	/* 0x00020000 */
+}
+
+void pt3_i2c_wait(struct pt3_board *pt3, u32 *status)
+{
+	u32 val;
+
+	while (1) {
+		val = readl(pt3->bar_reg + REG_I2C_R);
+		if (!PT3_SHIFT_MASK(val, 0, 1))		/* sequence stopped */
+			break;
+		msleep_interruptible(1);
+	}
+	if (status)
+		*status = val;				/* I2C register status */
+}
+
+void pt3_i2c_mem_write(struct pt3_board *pt3, u8 data)
+{
+	void __iomem *dst = pt3->bar_mem + PT3_I2C_DATA_OFFSET + pt3->i2c_addr;
+
+	if (pt3->i2c_filled) {
+		pt3->i2c_buf |= data << 4;
+		writeb(pt3->i2c_buf, dst);
+		pt3->i2c_addr++;
+	} else
+		pt3->i2c_buf = data;
+	pt3->i2c_filled ^= true;
+}
+
+void pt3_i2c_start(struct pt3_board *pt3)
+{
+	pt3_i2c_mem_write(pt3, I_DATA_H);
+	pt3_i2c_mem_write(pt3, I_CLOCK_H);
+	pt3_i2c_mem_write(pt3, I_DATA_L);
+	pt3_i2c_mem_write(pt3, I_CLOCK_L);
+}
+
+void pt3_i2c_cmd_write(struct pt3_board *pt3, const u8 *data, u32 size)
+{
+	u32 i, j;
+	u8 byte;
+
+	for (i = 0; i < size; i++) {
+		byte = data[i];
+		for (j = 0; j < 8; j++)
+			pt3_i2c_mem_write(pt3, PT3_SHIFT_MASK(byte, 7 - j, 1) ? I_DATA_H_NOP : I_DATA_L_NOP);
+		pt3_i2c_mem_write(pt3, I_DATA_H_ACK0);
+	}
+}
+
+void pt3_i2c_cmd_read(struct pt3_board *pt3, u8 *data, u32 size)
+{
+	u32 i, j;
+
+	for (i = 0; i < size; i++) {
+		for (j = 0; j < 8; j++)
+			pt3_i2c_mem_write(pt3, I_DATA_H_READ);
+		if (i == (size - 1))
+			pt3_i2c_mem_write(pt3, I_DATA_H_NOP);
+		else
+			pt3_i2c_mem_write(pt3, I_DATA_L_NOP);
+	}
+}
+
+void pt3_i2c_stop(struct pt3_board *pt3)
+{
+	pt3_i2c_mem_write(pt3, I_DATA_L);
+	pt3_i2c_mem_write(pt3, I_CLOCK_H);
+	pt3_i2c_mem_write(pt3, I_DATA_H);
+}
+
+int pt3_i2c_flush(struct pt3_board *pt3, bool end, u32 start_addr)
+{
+	u32 status;
+
+	if (end) {
+		pt3_i2c_mem_write(pt3, I_END);
+		if (pt3->i2c_filled)
+			pt3_i2c_mem_write(pt3, I_END);
+	}
+	pt3_i2c_wait(pt3, &status);
+	writel(1 << 16 | start_addr, pt3->bar_reg + REG_I2C_W);	/* 0x00010000 start sequence */
+	pt3_i2c_wait(pt3, &status);
+	if (PT3_SHIFT_MASK(status, 1, 2)) {			/* ACK status */
+		pr_err("%s failed, status=0x%x\n", __func__, status);
+		return -EIO;
+	}
+	return 0;
+}
+
+u32 pt3_i2c_func(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C;
+}
+
+int pt3_i2c_xfer(struct i2c_adapter *i2c, struct i2c_msg *msg, int num)
+{
+	struct pt3_board *pt3 = i2c_get_adapdata(i2c);
+	int i, j;
+
+	if ((num < 1) || (num > 3) || !msg || msg[0].flags)	/* always write first */
+		return -ENOTSUPP;
+	mutex_lock(&pt3->lock);
+	pt3->i2c_addr = 0;
+	for (i = 0; i < num; i++) {
+		u8 byte = (msg[i].addr << 1) | (msg[i].flags & 1);
+		pt3_i2c_start(pt3);
+		pt3_i2c_cmd_write(pt3, &byte, 1);
+		if (msg[i].flags == I2C_M_RD)
+			pt3_i2c_cmd_read(pt3, msg[i].buf, msg[i].len);
+		else
+			pt3_i2c_cmd_write(pt3, msg[i].buf, msg[i].len);
+	}
+	pt3_i2c_stop(pt3);
+	if (pt3_i2c_flush(pt3, true, 0))
+		return -EIO;
+	for (i = 1; i < num; i++)
+		if (msg[i].flags == I2C_M_RD)
+			for (j = 0; j < msg[i].len; j++)
+				msg[i].buf[j] = readb(pt3->bar_mem + PT3_I2C_DATA_OFFSET + j);
+	mutex_unlock(&pt3->lock);
+	return num;
+}
+
diff --git a/drivers/media/pci/pt3/pt3_i2c.h b/drivers/media/pci/pt3/pt3_i2c.h
new file mode 100644
index 0000000..5e61f17
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_i2c.h
@@ -0,0 +1,29 @@
+/*
+ * DVB driver for Earthsoft PT3 ISDB-S/T PCI-E card
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__PT3_I2C_H__
+#define	__PT3_I2C_H__
+
+#define PT3_I2C_DATA_OFFSET	2048
+#define PT3_I2C_START_ADDR	0x17fa
+
+int pt3_i2c_flush(struct pt3_board *pt3, bool end, u32 start_addr);
+bool pt3_i2c_is_clean(struct pt3_board *pt3);
+void pt3_i2c_reset(struct pt3_board *pt3);
+u32 pt3_i2c_func(struct i2c_adapter *adap);
+int pt3_i2c_xfer(struct i2c_adapter *i2c, struct i2c_msg *msg, int num);
+
+#endif
diff --git a/drivers/media/tuners/Kconfig b/drivers/media/tuners/Kconfig
index 15665de..999e70c 100644
--- a/drivers/media/tuners/Kconfig
+++ b/drivers/media/tuners/Kconfig
@@ -235,4 +235,11 @@ config MEDIA_TUNER_R820T
 	default m if !MEDIA_SUBDRV_AUTOSELECT
 	help
 	  Rafael Micro R820T silicon tuner driver.
+
+config MEDIA_TUNER_PT3
+	tristate "Demodulator/Tuners for Earthsoft PT3 ISDB-S/T"
+	depends on MEDIA_SUPPORT && I2C
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Earthsoft PT3 demodulator/tuner driver: ISDB-S QM1D1C0042, ISDB-T MxL301RF, TC90522
 endmenu
diff --git a/drivers/media/tuners/Makefile b/drivers/media/tuners/Makefile
index 308f108..de6a87e 100644
--- a/drivers/media/tuners/Makefile
+++ b/drivers/media/tuners/Makefile
@@ -3,6 +3,7 @@
 #
 
 tda18271-objs := tda18271-maps.o tda18271-common.o tda18271-fe.o
+pt3_tuner-objs := mxl301rf.o qm1d1c0042.o tc90522.o
 
 obj-$(CONFIG_MEDIA_TUNER_XC2028) += tuner-xc2028.o
 obj-$(CONFIG_MEDIA_TUNER_SIMPLE) += tuner-simple.o
@@ -36,6 +37,7 @@ obj-$(CONFIG_MEDIA_TUNER_FC0012) += fc0012.o
 obj-$(CONFIG_MEDIA_TUNER_FC0013) += fc0013.o
 obj-$(CONFIG_MEDIA_TUNER_IT913X) += tuner_it913x.o
 obj-$(CONFIG_MEDIA_TUNER_R820T) += r820t.o
+obj-$(CONFIG_MEDIA_TUNER_PT3) += pt3_tuner.o
 
 ccflags-y += -I$(srctree)/drivers/media/dvb-core
 ccflags-y += -I$(srctree)/drivers/media/dvb-frontends
diff --git a/drivers/media/tuners/mxl301rf.c b/drivers/media/tuners/mxl301rf.c
new file mode 100644
index 0000000..8ecf263
--- /dev/null
+++ b/drivers/media/tuners/mxl301rf.c
@@ -0,0 +1,347 @@
+/*
+ * MxL301RF MaxLinear CMOS Hybrid TV ISDB-T tuner driver for Earthsoft PT3
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_common.h"
+#include "tc90522.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 MxL301RF MaxLinear CMOS Hybrid TV ISDB-T tuner driver");
+MODULE_LICENSE("GPL");
+
+static struct {
+	u32	freq,		/* Channel center frequency @ kHz	*/
+		freq_th;	/* Offset frequency threshold @ kHz	*/
+	u8	shf_val,	/* Spur shift value			*/
+		shf_dir;	/* Spur shift direction			*/
+} SHF_DVBT_TAB[] = {
+	{  64500, 500, 0x92, 0x07 },
+	{ 191500, 300, 0xE2, 0x07 },
+	{ 205500, 500, 0x2C, 0x04 },
+	{ 212500, 500, 0x1E, 0x04 },
+	{ 226500, 500, 0xD4, 0x07 },
+	{  99143, 500, 0x9C, 0x07 },
+	{ 173143, 500, 0xD4, 0x07 },
+	{ 191143, 300, 0xD4, 0x07 },
+	{ 207143, 500, 0xCE, 0x07 },
+	{ 225143, 500, 0xCE, 0x07 },
+	{ 243143, 500, 0xD4, 0x07 },
+	{ 261143, 500, 0xD4, 0x07 },
+	{ 291143, 500, 0xD4, 0x07 },
+	{ 339143, 500, 0x2C, 0x04 },
+	{ 117143, 500, 0x7A, 0x07 },
+	{ 135143, 300, 0x7A, 0x07 },
+	{ 153143, 500, 0x01, 0x07 }
+};
+
+static void mxl301rf_rftune(u8 *data, u32 *size, u32 freq)
+{
+	u32 dig_rf_freq, tmp, frac_divider, kHz, MHz, i;
+	u8 rf_data[] = {
+		0x13, 0x00,	/* abort tune			*/
+		0x3B, 0xC0,
+		0x3B, 0x80,
+		0x10, 0x95,	/* BW				*/
+		0x1A, 0x05,
+		0x61, 0x00,
+		0x62, 0xA0,
+		0x11, 0x40,	/* 2 bytes to store RF freq.	*/
+		0x12, 0x0E,	/* 2 bytes to store RF freq.	*/
+		0x13, 0x01	/* start tune			*/
+	};
+
+	dig_rf_freq = 0;
+	tmp = 0;
+	frac_divider = 1000000;
+	kHz = 1000;
+	MHz = 1000000;
+
+	dig_rf_freq = freq / MHz;
+	tmp = freq % MHz;
+
+	for (i = 0; i < 6; i++) {
+		dig_rf_freq <<= 1;
+		frac_divider /= 2;
+		if (tmp > frac_divider) {
+			tmp -= frac_divider;
+			dig_rf_freq++;
+		}
+	}
+	if (tmp > 7812)
+		dig_rf_freq++;
+
+	rf_data[2 * (7) + 1] = (u8)(dig_rf_freq);
+	rf_data[2 * (8) + 1] = (u8)(dig_rf_freq >> 8);
+
+	for (i = 0; i < sizeof(SHF_DVBT_TAB)/sizeof(*SHF_DVBT_TAB); i++) {
+		if ((freq >= (SHF_DVBT_TAB[i].freq - SHF_DVBT_TAB[i].freq_th) * kHz) &&
+				(freq <= (SHF_DVBT_TAB[i].freq + SHF_DVBT_TAB[i].freq_th) * kHz)) {
+			rf_data[2 * (5) + 1] = SHF_DVBT_TAB[i].shf_val;
+			rf_data[2 * (6) + 1] = 0xa0 | SHF_DVBT_TAB[i].shf_dir;
+			break;
+		}
+	}
+	memcpy(data, rf_data, sizeof(rf_data));
+	*size = sizeof(rf_data);
+
+	pr_debug("mx_rftune freq=%d\n", freq);
+}
+
+/* write via demodulator */
+static void mxl301rf_write(struct pt3_adapter *adap, u8 *data, size_t size)
+{
+	tc90522_write_tuner_without_addr(adap, data, size);
+}
+
+static void mxl301rf_standby(struct pt3_adapter *adap)
+{
+	u8 data[4] = {0x01, 0x00, 0x13, 0x00};
+	mxl301rf_write(adap, data, sizeof(data));
+}
+
+static void mxl301rf_set_register(struct pt3_adapter *adap, u8 addr, u8 value)
+{
+	u8 data[2] = {addr, value};
+	mxl301rf_write(adap, data, sizeof(data));
+}
+
+static void mxl301rf_idac_setting(struct pt3_adapter *adap)
+{
+	u8 data[] = {
+		0x0D, 0x00,
+		0x0C, 0x67,
+		0x6F, 0x89,
+		0x70, 0x0C,
+		0x6F, 0x8A,
+		0x70, 0x0E,
+		0x6F, 0x8B,
+		0x70, 0x10+12,
+	};
+	mxl301rf_write(adap, data, sizeof(data));
+}
+
+void mxl301rf_tuner_rftune(struct pt3_adapter *adap, u32 freq)
+{
+	u8 data[100];
+	u32 size;
+
+	size = 0;
+	adap->freq = freq;
+	mxl301rf_rftune(data, &size, freq);
+	if (size != 20) {
+		pr_debug("fail mx_rftune size = %d\n", size);
+		return;
+	}
+	mxl301rf_write(adap, data, 14);
+	msleep_interruptible(1);
+	mxl301rf_write(adap, data + 14, 6);
+	msleep_interruptible(1);
+	mxl301rf_set_register(adap, 0x1a, 0x0d);
+	mxl301rf_idac_setting(adap);
+}
+
+static void mxl301rf_wakeup(struct pt3_adapter *adap)
+{
+	u8 data[2] = {0x01, 0x01};
+
+	mxl301rf_write(adap, data, sizeof(data));
+	mxl301rf_tuner_rftune(adap, adap->freq);
+}
+
+static void mxl301rf_set_sleep_mode(struct pt3_adapter *adap, bool sleep)
+{
+	if (sleep)
+		mxl301rf_standby(adap);
+	else
+		mxl301rf_wakeup(adap);
+}
+
+int mxl301rf_set_sleep(struct pt3_adapter *adap, bool sleep)
+{
+	int ret;
+
+	if (sleep) {
+		ret = tc90522_set_agc(adap, TC90522_AGC_MANUAL);
+		if (ret)
+			return ret;
+		mxl301rf_set_sleep_mode(adap, sleep);
+		tc90522_write_sleep_time(adap, sleep);
+	} else {
+		tc90522_write_sleep_time(adap, sleep);
+		mxl301rf_set_sleep_mode(adap, sleep);
+	}
+	adap->sleep = sleep;
+	return 0;
+}
+
+static u8 MXL301RF_FREQ_TABLE[][3] = {
+	{   2, 0,  3 },
+	{  12, 1, 22 },
+	{  21, 0, 12 },
+	{  62, 1, 63 },
+	{ 112, 0, 62 }
+};
+
+void mxl301rf_get_channel_frequency(struct pt3_adapter *adap, u32 channel, bool *catv, u32 *number, u32 *freq)
+{
+	u32 i;
+	s32 freq_offset = 0;
+
+	if (12 <= channel)
+		freq_offset += 2;
+	if (17 <= channel)
+		freq_offset -= 2;
+	if (63 <= channel)
+		freq_offset += 2;
+	*freq = 93 + channel * 6 + freq_offset;
+
+	for (i = 0; i < sizeof(MXL301RF_FREQ_TABLE) / sizeof(*MXL301RF_FREQ_TABLE); i++) {
+		if (channel <= MXL301RF_FREQ_TABLE[i][0]) {
+			*catv = MXL301RF_FREQ_TABLE[i][1] ? true : false;
+			*number = channel + MXL301RF_FREQ_TABLE[i][2] - MXL301RF_FREQ_TABLE[i][0];
+			break;
+		}
+	}
+}
+
+static u32 MXL301RF_RF_TABLE[112] = {
+	0x058d3f49, 0x05e8ccc9, 0x06445a49, 0x069fe7c9, 0x06fb7549,
+	0x075702c9, 0x07b29049, 0x080e1dc9, 0x0869ab49, 0x08c538c9,
+	0x0920c649, 0x097c53c9, 0x09f665c9, 0x0a51f349, 0x0aad80c9,
+	0x0b090e49, 0x0b649bc9, 0x0ba1a4c9, 0x0bfd3249, 0x0c58bfc9,
+	0x0cb44d49, 0x0d0fdac9, 0x0d6b6849, 0x0dc6f5c9, 0x0e228349,
+	0x0e7e10c9, 0x0ed99e49, 0x0f352bc9, 0x0f90b949, 0x0fec46c9,
+	0x1047d449, 0x10a361c9, 0x10feef49, 0x115a7cc9, 0x11b60a49,
+	0x121197c9, 0x126d2549, 0x12c8b2c9, 0x13244049, 0x137fcdc9,
+	0x13db5b49, 0x1436e8c9, 0x14927649, 0x14ee03c9, 0x15499149,
+	0x15a51ec9, 0x1600ac49, 0x165c39c9, 0x16b7c749, 0x171354c9,
+	0x176ee249, 0x17ca6fc9, 0x1825fd49, 0x18818ac9, 0x18dd1849,
+	0x1938a5c9, 0x19943349, 0x19efc0c9, 0x1a4b4e49, 0x1aa6dbc9,
+	0x1b026949, 0x1b5df6c9, 0x1bb98449, 0x1c339649, 0x1c8f23c9,
+	0x1ceab149, 0x1d463ec9, 0x1da1cc49, 0x1dfd59c9, 0x1e58e749,
+	0x1eb474c9, 0x1f100249, 0x1f6b8fc9, 0x1fc71d49, 0x2022aac9,
+	0x207e3849, 0x20d9c5c9, 0x21355349, 0x2190e0c9, 0x21ec6e49,
+	0x2247fbc9, 0x22a38949, 0x22ff16c9, 0x235aa449, 0x23b631c9,
+	0x2411bf49, 0x246d4cc9, 0x24c8da49, 0x252467c9, 0x257ff549,
+	0x25db82c9, 0x26371049, 0x26929dc9, 0x26ee2b49, 0x2749b8c9,
+	0x27a54649, 0x2800d3c9, 0x285c6149, 0x28b7eec9, 0x29137c49,
+	0x296f09c9, 0x29ca9749, 0x2a2624c9, 0x2a81b249, 0x2add3fc9,
+	0x2b38cd49, 0x2b945ac9, 0x2befe849, 0x2c4b75c9, 0x2ca70349,
+	0x2d0290c9, 0x2d5e1e49,
+};
+
+/* read via demodulator */
+static void mxl301rf_read(struct pt3_adapter *adap, u8 addr, u8 *data)
+{
+	u8 write[2] = {0xfb, addr};
+
+	tc90522_write_tuner_without_addr(adap, write, sizeof(write));
+	tc90522_read_tuner_without_addr(adap, data);
+}
+
+static void mxl301rf_rfsynth_lock_status(struct pt3_adapter *adap, bool *locked)
+{
+	u8 data;
+
+	*locked = false;
+	mxl301rf_read(adap, 0x16, &data);
+	data &= 0x0c;
+	if (data == 0x0c)
+		*locked = true;
+}
+
+static void mxl301rf_refsynth_lock_status(struct pt3_adapter *adap, bool *locked)
+{
+	u8 data;
+
+	*locked = false;
+	mxl301rf_read(adap, 0x16, &data);
+	data &= 0x03;
+	if (data == 0x03)
+		*locked = true;
+}
+
+bool mxl301rf_locked(struct pt3_adapter *adap)
+{
+	bool locked1 = false, locked2 = false;
+	struct timeval begin, now;
+
+	do_gettimeofday(&begin);
+	while (1) {
+		do_gettimeofday(&now);
+		mxl301rf_rfsynth_lock_status(adap, &locked1);
+		mxl301rf_refsynth_lock_status(adap, &locked2);
+		if (locked1 && locked2)
+			break;
+		if (tc90522_time_diff(&begin, &now) > 1000)
+			break;
+		msleep_interruptible(1);
+	}
+	pr_debug("#%d mx locked1=%d locked2=%d\n", adap->idx, locked1, locked2);
+	return locked1 && locked2;
+}
+
+int mxl301rf_set_frequency(struct pt3_adapter *adap, u32 channel, s32 offset)
+{
+	bool catv;
+	u32 number, freq, real_freq;
+	int ret = tc90522_set_agc(adap, TC90522_AGC_MANUAL);
+
+	if (ret)
+		return ret;
+	mxl301rf_get_channel_frequency(adap, channel, &catv, &number, &freq);
+	pr_debug("#%d ch%d%s no%d %dHz\n", adap->idx, channel, catv ? " CATV" : "", number, freq);
+	/* real_freq = (7 * freq + 1 + offset) * 1000000.0/7.0; */
+	real_freq = MXL301RF_RF_TABLE[channel];
+
+	mxl301rf_tuner_rftune(adap, real_freq);
+
+	return (!mxl301rf_locked(adap)) ? -ETIMEDOUT : tc90522_set_agc(adap, TC90522_AGC_AUTO);
+}
+
+#define MXL301RF_NHK (MXL301RF_RF_TABLE[77])
+int mxl301rf_freq(int freq)
+{
+	if (freq >= 90000000)
+		return freq;					/* real_freq	*/
+	if (freq > 255)
+		return MXL301RF_NHK;
+	if (freq > 127)
+		return MXL301RF_RF_TABLE[freq - 128];		/* freqno (IO#)	*/
+	if (freq > 63) {					/* CATV		*/
+		freq -= 64;
+		if (freq > 22)
+			return MXL301RF_RF_TABLE[freq - 1];	/* C23-C62	*/
+		if (freq > 12)
+			return MXL301RF_RF_TABLE[freq - 10];	/* C13-C22	*/
+		return MXL301RF_NHK;
+	}
+	if (freq > 62)
+		return MXL301RF_NHK;
+	if (freq > 12)
+		return MXL301RF_RF_TABLE[freq + 50];		/* 13-62	*/
+	if (freq >  3)
+		return MXL301RF_RF_TABLE[freq +  9];		/*  4-12	*/
+	if (freq)
+		return MXL301RF_RF_TABLE[freq -  1];		/*  1-3		*/
+	return MXL301RF_NHK;
+}
+
+EXPORT_SYMBOL(mxl301rf_set_frequency);
+EXPORT_SYMBOL(mxl301rf_set_sleep);
+EXPORT_SYMBOL(mxl301rf_freq);
+EXPORT_SYMBOL(mxl301rf_tuner_rftune);
+EXPORT_SYMBOL(mxl301rf_locked);
+
diff --git a/drivers/media/tuners/mxl301rf.h b/drivers/media/tuners/mxl301rf.h
new file mode 100644
index 0000000..a025494
--- /dev/null
+++ b/drivers/media/tuners/mxl301rf.h
@@ -0,0 +1,27 @@
+/*
+ * MxL301RF MaxLinear CMOS Hybrid TV ISDB-T tuner driver for Earthsoft PT3
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 __MXL301RF_H__
+#define __MXL301RF_H__
+
+int mxl301rf_set_frequency(struct pt3_adapter *adap, u32 channel, s32 offset);
+int mxl301rf_set_sleep(struct pt3_adapter *adap, bool sleep);
+int mxl301rf_freq(int freq);
+void mxl301rf_tuner_rftune(struct pt3_adapter *adap, u32 freq);
+bool mxl301rf_locked(struct pt3_adapter *adap);
+
+#endif
+
diff --git a/drivers/media/tuners/qm1d1c0042.c b/drivers/media/tuners/qm1d1c0042.c
new file mode 100644
index 0000000..eb7c869
--- /dev/null
+++ b/drivers/media/tuners/qm1d1c0042.c
@@ -0,0 +1,413 @@
+/*
+ * QM1D1C0042 ISDB-S tuner driver for Earthsoft PT3
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_common.h"
+#include "tc90522.h"
+#include "qm1d1c0042.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 QM1D1C0042 ISDB-S tuner driver");
+MODULE_LICENSE("GPL");
+
+static u8 qm1d1c0042_reg_rw[] = {
+	0x48, 0x1c, 0xa0, 0x10, 0xbc, 0xc5, 0x20, 0x33,
+	0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+	0x00, 0xff, 0xf3, 0x00, 0x2a, 0x64, 0xa6, 0x86,
+	0x8c, 0xcf, 0xb8, 0xf1, 0xa8, 0xf2, 0x89, 0x00,
+};
+
+void qm1d1c0042_init_reg_param(struct qm1d1c0042 *qm)
+{
+	memcpy(qm->reg, qm1d1c0042_reg_rw, sizeof(qm1d1c0042_reg_rw));
+
+	qm->adap->freq = 0;
+	qm->standby = false;
+	qm->wait_time_lpf = 20;
+	qm->wait_time_search_fast = 4;
+	qm->wait_time_search_normal = 15;
+}
+
+/* read via demodulator */
+static int qm1d1c0042_read(struct qm1d1c0042 *qm, u8 addr, u8 *data)
+{
+	int ret = 0;
+	if ((addr == 0x00) || (addr == 0x0d))
+		ret = tc90522_read_tuner(qm->adap, addr, data);
+	return ret;
+}
+
+/* write via demodulator */
+static int qm1d1c0042_write(struct qm1d1c0042 *qm, u8 addr, u8 data)
+{
+	int ret = tc90522_write_tuner(qm->adap, addr, &data, sizeof(data));
+	qm->reg[addr] = data;
+	return ret;
+}
+
+#define QM1D1C0042_INIT_DUMMY_RESET 0x0c
+
+void qm1d1c0042_dummy_reset(struct qm1d1c0042 *qm)
+{
+	qm1d1c0042_write(qm, 0x01, QM1D1C0042_INIT_DUMMY_RESET);
+	qm1d1c0042_write(qm, 0x01, QM1D1C0042_INIT_DUMMY_RESET);
+}
+
+static u8 qm1d1c0042_flag[32] = {
+	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
+	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+};
+
+static int qm1d1c0042_set_sleep_mode(struct qm1d1c0042 *qm)
+{
+	int ret;
+
+	if (qm->standby) {
+		qm->reg[0x01] &= (~(1 << 3)) & 0xff;
+		qm->reg[0x01] |= 1 << 0;
+		qm->reg[0x05] |= 1 << 3;
+
+		ret = qm1d1c0042_write(qm, 0x05, qm->reg[0x05]);
+		if (ret)
+			return ret;
+		ret = qm1d1c0042_write(qm, 0x01, qm->reg[0x01]);
+		if (ret)
+			return ret;
+	} else {
+		qm->reg[0x01] |= 1 << 3;
+		qm->reg[0x01] &= (~(1 << 0)) & 0xff;
+		qm->reg[0x05] &= (~(1 << 3)) & 0xff;
+
+		ret = qm1d1c0042_write(qm, 0x01, qm->reg[0x01]);
+		if (ret)
+			return ret;
+		ret = qm1d1c0042_write(qm, 0x05, qm->reg[0x05]);
+		if (ret)
+			return ret;
+	}
+	return ret;
+}
+
+static int qm1d1c0042_set_search_mode(struct qm1d1c0042 *qm)
+{
+	qm->reg[3] &= 0xfe;
+	return qm1d1c0042_write(qm, 0x03, qm->reg[3]);
+}
+
+int qm1d1c0042_init(struct qm1d1c0042 *qm)
+{
+	u8 i_data;
+	u32 i;
+	int ret;
+
+	/* soft reset on */
+	ret = qm1d1c0042_write(qm, 0x01, QM1D1C0042_INIT_DUMMY_RESET);
+	if (ret)
+		return ret;
+
+	msleep_interruptible(1);
+
+	/* soft reset off */
+	i_data = qm->reg[0x01] | 0x10;
+	ret = qm1d1c0042_write(qm, 0x01, i_data);
+	if (ret)
+		return ret;
+
+	/* ID check */
+	ret = qm1d1c0042_read(qm, 0x00, &i_data);
+	if (ret)
+		return ret;
+	if (i_data != 0x48)
+		return -EINVAL;
+
+	/* LPF tuning on */
+	msleep_interruptible(1);
+	qm->reg[0x0c] |= 0x40;
+	ret = qm1d1c0042_write(qm, 0x0c, qm->reg[0x0c]);
+	if (ret)
+		return ret;
+	msleep_interruptible(qm->wait_time_lpf);
+
+	for (i = 0; i < sizeof(qm1d1c0042_flag); i++)
+		if (qm1d1c0042_flag[i] == 1) {
+			ret = qm1d1c0042_write(qm, i, qm->reg[i]);
+			if (ret)
+				return ret;
+		}
+	ret = qm1d1c0042_set_sleep_mode(qm);
+	if (ret)
+		return ret;
+	return qm1d1c0042_set_search_mode(qm);
+}
+
+int qm1d1c0042_set_sleep(struct qm1d1c0042 *qm, bool sleep)
+{
+	qm->standby = sleep;
+	if (sleep) {
+		int ret = tc90522_set_agc(qm->adap, TC90522_AGC_MANUAL);
+		if (ret)
+			return ret;
+		qm1d1c0042_set_sleep_mode(qm);
+		tc90522_set_sleep_s(qm->adap, sleep);
+	} else {
+		tc90522_set_sleep_s(qm->adap, sleep);
+		qm1d1c0042_set_sleep_mode(qm);
+	}
+	qm->adap->sleep = sleep;
+	return 0;
+}
+
+void qm1d1c0042_get_channel_freq(u32 channel, u32 *number, u32 *freq)
+{
+	if (channel < 12) {
+		*number = 1 + 2 * channel;
+		*freq = 104948 + 3836 * channel;
+	} else if (channel < 24) {
+		channel -= 12;
+		*number = 2 + 2 * channel;
+		*freq = 161300 + 4000 * channel;
+	} else {
+		channel -= 24;
+		*number = 1 + 2 * channel;
+		*freq = 159300 + 4000 * channel;
+	}
+}
+
+static u32 QM1D1C0042_FREQ_TABLE[9][3] = {
+	{ 2151000, 1, 7 },
+	{ 1950000, 1, 6 },
+	{ 1800000, 1, 5 },
+	{ 1600000, 1, 4 },
+	{ 1450000, 1, 3 },
+	{ 1250000, 1, 2 },
+	{ 1200000, 0, 7 },
+	{  975000, 0, 6 },
+	{  950000, 0, 0 }
+};
+
+static u32 SD_TABLE[24][2][3] = {
+	{{0x38fae1, 0x0d, 0x5}, {0x39fae1, 0x0d, 0x5},},
+	{{0x3f570a, 0x0e, 0x3}, {0x00570a, 0x0e, 0x3},},
+	{{0x05b333, 0x0e, 0x5}, {0x06b333, 0x0e, 0x5},},
+	{{0x3c0f5c, 0x0f, 0x4}, {0x3d0f5c, 0x0f, 0x4},},
+	{{0x026b85, 0x0f, 0x6}, {0x036b85, 0x0f, 0x6},},
+	{{0x38c7ae, 0x10, 0x5}, {0x39c7ae, 0x10, 0x5},},
+	{{0x3f23d7, 0x11, 0x3}, {0x0023d7, 0x11, 0x3},},
+	{{0x058000, 0x11, 0x5}, {0x068000, 0x11, 0x5},},
+	{{0x3bdc28, 0x12, 0x4}, {0x3cdc28, 0x12, 0x4},},
+	{{0x023851, 0x12, 0x6}, {0x033851, 0x12, 0x6},},
+	{{0x38947a, 0x13, 0x5}, {0x39947a, 0x13, 0x5},},
+	{{0x3ef0a3, 0x14, 0x3}, {0x3ff0a3, 0x14, 0x3},},
+	{{0x3c8000, 0x16, 0x4}, {0x3d8000, 0x16, 0x4},},
+	{{0x048000, 0x16, 0x6}, {0x058000, 0x16, 0x6},},
+	{{0x3c8000, 0x17, 0x5}, {0x3d8000, 0x17, 0x5},},
+	{{0x048000, 0x18, 0x3}, {0x058000, 0x18, 0x3},},
+	{{0x3c8000, 0x18, 0x6}, {0x3d8000, 0x18, 0x6},},
+	{{0x048000, 0x19, 0x4}, {0x058000, 0x19, 0x4},},
+	{{0x3c8000, 0x1a, 0x3}, {0x3d8000, 0x1a, 0x3},},
+	{{0x048000, 0x1a, 0x5}, {0x058000, 0x1a, 0x5},},
+	{{0x3c8000, 0x1b, 0x4}, {0x3d8000, 0x1b, 0x4},},
+	{{0x048000, 0x1b, 0x6}, {0x058000, 0x1b, 0x6},},
+	{{0x3c8000, 0x1c, 0x5}, {0x3d8000, 0x1c, 0x5},},
+	{{0x048000, 0x1d, 0x3}, {0x058000, 0x1d, 0x3},},
+};
+
+static int qm1d1c0042_tuning(struct qm1d1c0042 *qm, u32 *sd, u32 channel)
+{
+	int ret;
+	struct pt3_adapter *adap = qm->adap;
+	u8 i_data;
+	u32 index, i, N, A;
+
+	qm->reg[0x08] &= 0xf0;
+	qm->reg[0x08] |= 0x09;
+
+	qm->reg[0x13] &= 0x9f;
+	qm->reg[0x13] |= 0x20;
+
+	for (i = 0; i < 8; i++) {
+		if ((QM1D1C0042_FREQ_TABLE[i+1][0] <= adap->freq) && (adap->freq < QM1D1C0042_FREQ_TABLE[i][0])) {
+			i_data = qm->reg[0x02];
+			i_data &= 0x0f;
+			i_data |= QM1D1C0042_FREQ_TABLE[i][1] << 7;
+			i_data |= QM1D1C0042_FREQ_TABLE[i][2] << 4;
+			qm1d1c0042_write(qm, 0x02, i_data);
+		}
+	}
+
+	index = tc90522_index(qm->adap);
+	*sd = SD_TABLE[channel][index][0];
+	N = SD_TABLE[channel][index][1];
+	A = SD_TABLE[channel][index][2];
+
+	qm->reg[0x06] &= 0x40;
+	qm->reg[0x06] |= N;
+	ret = qm1d1c0042_write(qm, 0x06, qm->reg[0x06]);
+	if (ret)
+		return ret;
+
+	qm->reg[0x07] &= 0xf0;
+	qm->reg[0x07] |= A & 0x0f;
+	return qm1d1c0042_write(qm, 0x07, qm->reg[0x07]);
+}
+
+static int qm1d1c0042_local_lpf_tuning(struct qm1d1c0042 *qm, int lpf, u32 channel)
+{
+	u8 i_data;
+	u32 sd = 0;
+	int ret = qm1d1c0042_tuning(qm, &sd, channel);
+
+	if (ret)
+		return ret;
+	if (lpf) {
+		i_data = qm->reg[0x08] & 0xf0;
+		i_data |= 2;
+		ret = qm1d1c0042_write(qm, 0x08, i_data);
+	} else
+		ret = qm1d1c0042_write(qm, 0x08, qm->reg[0x08]);
+	if (ret)
+		return ret;
+
+	qm->reg[0x09] &= 0xc0;
+	qm->reg[0x09] |= (sd >> 16) & 0x3f;
+	qm->reg[0x0a] = (sd >> 8) & 0xff;
+	qm->reg[0x0b] = (sd >> 0) & 0xff;
+	ret = qm1d1c0042_write(qm, 0x09, qm->reg[0x09]);
+	if (ret)
+		return ret;
+	ret = qm1d1c0042_write(qm, 0x0a, qm->reg[0x0a]);
+	if (ret)
+		return ret;
+	ret = qm1d1c0042_write(qm, 0x0b, qm->reg[0x0b]);
+	if (ret)
+		return ret;
+
+	if (lpf) {
+		i_data = qm->reg[0x0c];
+		i_data &= 0x3f;
+		ret = qm1d1c0042_write(qm, 0x0c, i_data);
+		if (ret)
+			return ret;
+		msleep_interruptible(1);
+
+		i_data = qm->reg[0x0c];
+		i_data |= 0xc0;
+		ret = qm1d1c0042_write(qm, 0x0c, i_data);
+		if (ret)
+			return ret;
+		msleep_interruptible(qm->wait_time_lpf);
+		ret = qm1d1c0042_write(qm, 0x08, 0x09);
+		if (ret)
+			return ret;
+		ret = qm1d1c0042_write(qm, 0x13, qm->reg[0x13]);
+		if (ret)
+			return ret;
+	} else {
+		ret = qm1d1c0042_write(qm, 0x13, qm->reg[0x13]);
+		if (ret)
+			return ret;
+		i_data = qm->reg[0x0c];
+		i_data &= 0x7f;
+		ret = qm1d1c0042_write(qm, 0x0c, i_data);
+		if (ret)
+			return ret;
+		msleep_interruptible(2);
+
+		i_data = qm->reg[0x0c];
+		i_data |= 0x80;
+		ret = qm1d1c0042_write(qm, 0x0c, i_data);
+		if (ret)
+			return ret;
+		if (qm->reg[0x03] & 0x01)
+			msleep_interruptible(qm->wait_time_search_fast);
+		else
+			msleep_interruptible(qm->wait_time_search_normal);
+	}
+	return ret;
+}
+
+int qm1d1c0042_get_locked(struct qm1d1c0042 *qm, bool *locked)
+{
+	int ret = qm1d1c0042_read(qm, 0x0d, &qm->reg[0x0d]);
+	if (ret)
+		return ret;
+	if (qm->reg[0x0d] & 0x40)
+		*locked = true;
+	else
+		*locked = false;
+	return ret;
+}
+
+int qm1d1c0042_set_frequency(struct qm1d1c0042 *qm, u32 channel)
+{
+	u32 number, freq, freq_kHz;
+	struct timeval begin, now;
+	bool locked;
+	int ret = tc90522_set_agc(qm->adap, TC90522_AGC_MANUAL);
+	if (ret)
+		return ret;
+
+	qm1d1c0042_get_channel_freq(channel, &number, &freq);
+	freq_kHz = freq * 10;
+	if (tc90522_index(qm->adap) == 0)
+		freq_kHz -= 500;
+	else
+		freq_kHz += 500;
+	qm->adap->freq = freq_kHz;
+	pr_debug("#%d ch %d freq %d kHz\n", qm->adap->idx, channel, freq_kHz);
+
+	ret = qm1d1c0042_local_lpf_tuning(qm, 1, channel);
+	if (ret)
+		return ret;
+	do_gettimeofday(&begin);
+	while (1) {
+		do_gettimeofday(&now);
+		ret = qm1d1c0042_get_locked(qm, &locked);
+		if (ret)
+			return ret;
+		if (locked)
+			break;
+		if (tc90522_time_diff(&begin, &now) >= 100)
+			break;
+		msleep_interruptible(1);
+	}
+	pr_debug("#%d qm_get_locked %d ret=0x%x\n", qm->adap->idx, locked, ret);
+	if (!locked)
+		return -ETIMEDOUT;
+
+	ret = tc90522_set_agc(qm->adap, TC90522_AGC_AUTO);
+	if (!ret) {
+		qm->adap->channel = channel;
+		qm->adap->offset = 0;
+	}
+	return ret;
+}
+
+int qm1d1c0042_tuner_init(struct qm1d1c0042 *qm)
+{
+	if (*qm->reg) {
+		if (qm1d1c0042_init(qm))
+			return -EIO;
+	} else {
+		qm1d1c0042_init_reg_param(qm);
+		qm1d1c0042_dummy_reset(qm);
+	}
+	return 0;
+}
+
+EXPORT_SYMBOL(qm1d1c0042_set_frequency);
+EXPORT_SYMBOL(qm1d1c0042_set_sleep);
+EXPORT_SYMBOL(qm1d1c0042_tuner_init);
+
diff --git a/drivers/media/tuners/qm1d1c0042.h b/drivers/media/tuners/qm1d1c0042.h
new file mode 100644
index 0000000..ed3246d
--- /dev/null
+++ b/drivers/media/tuners/qm1d1c0042.h
@@ -0,0 +1,34 @@
+/*
+ * QM1D1C0042 ISDB-S tuner driver for Earthsoft PT3
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 __QM1D1C0042_H__
+#define __QM1D1C0042_H__
+
+struct qm1d1c0042 {
+	struct pt3_adapter *adap;
+	u8 reg[32];
+
+	bool standby;
+	u32 wait_time_lpf, wait_time_search_fast, wait_time_search_normal;
+	struct tmcc_s tmcc;
+};
+
+int qm1d1c0042_set_frequency(struct qm1d1c0042 *qm, u32 channel);
+int qm1d1c0042_set_sleep(struct qm1d1c0042 *qm, bool sleep);
+int qm1d1c0042_tuner_init(struct qm1d1c0042 *qm);
+
+#endif
+
diff --git a/drivers/media/tuners/tc90522.c b/drivers/media/tuners/tc90522.c
new file mode 100644
index 0000000..bce4532
--- /dev/null
+++ b/drivers/media/tuners/tc90522.c
@@ -0,0 +1,412 @@
+/*
+ * Earthsoft PT3 demodulator driver TC90522 Toshiba OFDM(ISDB-T)/8PSK(ISDB-S)
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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 "pt3_common.h"
+#include "tc90522.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 TC90522 OFDM(ISDB-T)/8PSK(ISDB-S) Toshiba demodulator driver");
+MODULE_LICENSE("GPL");
+
+int tc90522_write(struct pt3_adapter *adap, u8 addr, u8 *data, u8 len)
+{
+	struct i2c_msg msg[1];
+	u8 buf[len + 1];
+	buf[0] = addr;
+	memcpy(buf + 1, data, len);
+
+	msg[0].addr = adap->addr_demod;
+	msg[0].flags = 0;			/* write */
+	msg[0].buf = buf;
+	msg[0].len = len + 1;
+
+	return i2c_transfer(&adap->pt3->i2c, msg, 1) == 1 ? 0 : -EREMOTEIO;
+}
+
+static int tc90522_write_pskmsrst(struct pt3_adapter *adap)
+{
+	u8 data = 0x01;
+	return tc90522_write(adap, 0x03, &data, 1);
+}
+
+static int tc90522_write_imsrst(struct pt3_adapter *adap)
+{
+	u8 data = 0x01 << 6;
+	return tc90522_write(adap, 0x01, &data, 1);
+}
+
+int tc90522_init(struct pt3_adapter *adap)
+{
+	u8 data = 0x10;
+
+	pr_debug("#%d %s tuner=0x%x demod=0x%x\n", adap->idx, adap->str, adap->addr_tuner, adap->addr_demod);
+	if (adap->type == SYS_ISDBS) {
+		int ret = tc90522_write_pskmsrst(adap);
+		return ret ? ret : tc90522_write(adap, 0x1e, &data, 1);
+	} else {
+		int ret = tc90522_write_imsrst(adap);
+		return ret ? ret : tc90522_write(adap, 0x1c, &data, 1);
+	}
+}
+
+int tc90522_set_powers(struct pt3_adapter *adap, bool tuner, bool amp)
+{
+	u8	tuner_power = tuner ? 0x03 : 0x02,
+		amp_power = amp ? 0x03 : 0x02,
+		data = (tuner_power << 6) | (0x01 << 4) | (amp_power << 2) | 0x01 << 0;
+	pr_debug("#%d tuner %s amp %s\n", adap->idx, tuner ? "ON" : "OFF", amp ? "ON" : "OFF");
+	return tc90522_write(adap, 0x1e, &data, 1);
+}
+
+int tc90522_set_ts_pins_mode(struct pt3_adapter *adap, struct pt3_ts_pins_mode *mode)
+{
+	u32	clock_data = mode->clock_data,
+		byte = mode->byte,
+		valid = mode->valid;
+
+	if (clock_data)
+		clock_data++;
+	if (byte)
+		byte++;
+	if (valid)
+		valid++;
+	if (adap->type == SYS_ISDBS) {
+		u8 data[2];
+		int ret;
+		data[0] = 0x15 | (valid << 6);
+		data[1] = 0x04 | (clock_data << 4) | byte;
+		return (ret = tc90522_write(adap, 0x1c, &data[0], 1)) ?
+			ret : tc90522_write(adap, 0x1f, &data[1], 1);
+	} else {
+		u8 data = (u8)(0x01 | (clock_data << 6) | (byte << 4) | (valid << 2));
+		return tc90522_write(adap, 0x1d, &data, 1);
+	}
+}
+
+#define TC90522_THROUGH 0xfe
+
+int tc90522_write_tuner(struct pt3_adapter *adap, u8 addr, const u8 *data, u32 len)
+{
+	struct i2c_msg msg[1];
+	u8 buf[len + 3];
+
+	buf[0] = TC90522_THROUGH;
+	buf[1] = adap->addr_tuner << 1;
+	buf[2] = addr;
+	memcpy(buf + 3, data, len);
+	msg[0].buf = buf;
+	msg[0].len = len + 3;
+	msg[0].addr = adap->addr_demod;
+	msg[0].flags = 0;			/* write */
+
+	return i2c_transfer(&adap->pt3->i2c, msg, 1) == 1 ? 0 : -EREMOTEIO;
+}
+
+int tc90522_read_tuner(struct pt3_adapter *adap, u8 addr, u8 *data)
+{
+	struct i2c_msg msg[3];
+	u8 buf[5];
+
+	buf[0] = TC90522_THROUGH;
+	buf[1] = adap->addr_tuner << 1;
+	buf[2] = addr;
+	msg[0].buf = buf;
+	msg[0].len = 3;
+	msg[0].addr = adap->addr_demod;
+	msg[0].flags = 0;			/* write */
+
+	buf[3] = TC90522_THROUGH;
+	buf[4] = (adap->addr_tuner << 1) | 1;
+	msg[1].buf = buf + 3;
+	msg[1].len = 2;
+	msg[1].addr = adap->addr_demod;
+	msg[1].flags = 0;			/* write */
+
+	msg[2].buf = data;
+	msg[2].len = 1;
+	msg[2].addr = adap->addr_demod;
+	msg[2].flags = I2C_M_RD;		/* read */
+
+	return i2c_transfer(&adap->pt3->i2c, msg, 3) == 3 ? 0 : -EREMOTEIO;
+}
+
+static u8 agc_data_s[2] = { 0xb0, 0x30 };
+
+u32 tc90522_index(struct pt3_adapter *adap)
+{
+	return PT3_SHIFT_MASK(adap->addr_demod, 1, 1);
+}
+
+int tc90522_set_agc_s(struct pt3_adapter *adap, enum tc90522_agc agc)
+{
+	u8 data = (agc == TC90522_AGC_AUTO) ? 0xff : 0x00;
+	int ret = tc90522_write(adap, 0x0a, &data, 1);
+	if (ret)
+		return ret;
+
+	data = agc_data_s[tc90522_index(adap)];
+	data |= (agc == TC90522_AGC_AUTO) ? 0x01 : 0x00;
+	ret = tc90522_write(adap, 0x10, &data, 1);
+	if (ret)
+		return ret;
+
+	data = (agc == TC90522_AGC_AUTO) ? 0x40 : 0x00;
+	return (ret = tc90522_write(adap, 0x11, &data, 1)) ? ret : tc90522_write_pskmsrst(adap);
+}
+
+int tc90522_set_sleep_s(struct pt3_adapter *adap, bool sleep)
+{
+	u8 buf = sleep ? 1 : 0;
+	return tc90522_write(adap, 0x17, &buf, 1);
+}
+
+int tc90522_set_agc_t(struct pt3_adapter *adap, enum tc90522_agc agc)
+{
+	u8 data = (agc == TC90522_AGC_AUTO) ? 0x40 : 0x00;
+	int ret = tc90522_write(adap, 0x25, &data, 1);
+	if (ret)
+		return ret;
+
+	data = 0x4c | ((agc == TC90522_AGC_AUTO) ? 0x00 : 0x01);
+	return (ret = tc90522_write(adap, 0x23, &data, 1)) ? ret : tc90522_write_imsrst(adap);
+}
+
+int tc90522_set_agc(struct pt3_adapter *adap, enum tc90522_agc agc)
+{
+	if (adap->type == SYS_ISDBS)
+		return tc90522_set_agc_s(adap, agc);
+	else
+		return tc90522_set_agc_t(adap, agc);
+}
+
+int tc90522_write_tuner_without_addr(struct pt3_adapter *adap, const u8 *data, u32 len)
+{
+	struct i2c_msg msg[1];
+	u8 buf[len + 2];
+
+	buf[0] = TC90522_THROUGH;
+	buf[1] = adap->addr_tuner << 1;
+	memcpy(buf + 2, data, len);
+	msg[0].buf = buf;
+	msg[0].len = len + 2;
+	msg[0].addr = adap->addr_demod;
+	msg[0].flags = 0;			/* write */
+
+	return i2c_transfer(&adap->pt3->i2c, msg, 1) == 1 ? 0 : -EREMOTEIO;
+}
+
+int tc90522_write_sleep_time(struct pt3_adapter *adap, int sleep)
+{
+	u8 data = (1 << 7) | ((sleep ? 1 : 0) << 4);
+	return tc90522_write(adap, 0x03, &data, 1);
+}
+
+u32 tc90522_time_diff(struct timeval *st, struct timeval *et)
+{
+	u32 diff = ((et->tv_sec - st->tv_sec) * 1000000 + (et->tv_usec - st->tv_usec)) / 1000;
+	pr_debug("time diff = %d\n", diff);
+	return diff;
+}
+
+int tc90522_read_tuner_without_addr(struct pt3_adapter *adap, u8 *data)
+{
+	struct i2c_msg msg[2];
+	u8 buf[2];
+
+	buf[0] = TC90522_THROUGH;
+	buf[1] = (adap->addr_tuner << 1) | 1;
+	msg[0].buf = buf;
+	msg[0].len = 2;
+	msg[0].addr = adap->addr_demod;
+	msg[0].flags = 0;			/* write */
+
+	msg[1].buf = data;
+	msg[1].len = 1;
+	msg[1].addr = adap->addr_demod;
+	msg[1].flags = I2C_M_RD;		/* read */
+
+	return i2c_transfer(&adap->pt3->i2c, msg, 2) == 2 ? 0 : -EREMOTEIO;
+}
+
+static u32 tc90522_byten(const u8 *data, u32 n)
+{
+	u32 i, val = 0;
+
+	for (i = 0; i < n; i++) {
+		val <<= 8;
+		val |= data[i];
+	}
+	return val;
+}
+
+int tc90522_read(struct pt3_adapter *adap, u8 addr, u8 *buf, u8 buflen)
+{
+	struct i2c_msg msg[2];
+	buf[0] = addr;
+
+	msg[0].addr = adap->addr_demod;
+	msg[0].flags = 0;			/* write */
+	msg[0].buf = buf;
+	msg[0].len = 1;
+
+	msg[1].addr = adap->addr_demod;
+	msg[1].flags = I2C_M_RD;		/* read */
+	msg[1].buf = buf;
+	msg[1].len = buflen;
+
+	return i2c_transfer(&adap->pt3->i2c, msg, 2) == 2 ? 0 : -EREMOTEIO;
+}
+
+int tc90522_read_retryov_tmunvld_fulock(struct pt3_adapter *adap, bool *retryov, bool *tmunvld, bool *fulock)
+{
+	u8 buf;
+	int ret = tc90522_read(adap, 0x80, &buf, 1);
+	if (!ret) {
+		*retryov = PT3_SHIFT_MASK(buf, 7, 1) ? true : false;
+		*tmunvld = PT3_SHIFT_MASK(buf, 5, 1) ? true : false;
+		*fulock  = PT3_SHIFT_MASK(buf, 3, 1) ? true : false;
+	}
+	return ret;
+}
+
+int tc90522_read_cn(struct pt3_adapter *adap, u32 *cn)
+{
+	u8 buf[3], buflen = (adap->type == SYS_ISDBS) ? 2 : 3, addr = (adap->type == SYS_ISDBS) ? 0xbc : 0x8b;
+	int ret = tc90522_read(adap, addr, buf, buflen);
+	if (!ret)
+		*cn =  tc90522_byten(buf, buflen);
+	return ret;
+}
+
+int tc90522_read_id_s(struct pt3_adapter *adap, u16 *id)
+{
+	u8 buf[2];
+	int ret = tc90522_read(adap, 0xe6, buf, 2);
+	if (!ret)
+		*id = tc90522_byten(buf, 2);
+	return ret;
+}
+
+int tc90522_read_tmcc_t(struct pt3_adapter *adap, struct tmcc_t *tmcc)
+{
+	u32 interleave0h, interleave0l, segment1h, segment1l;
+	u8 data[8];
+
+	int ret = tc90522_read(adap, 0xb2+0, data, 4);
+	if (ret)
+		return ret;
+	ret = tc90522_read(adap, 0xb2+4, data+4, 4);
+	if (ret)
+		return ret;
+
+	tmcc->system    = PT3_SHIFT_MASK(data[0], 6, 2);
+	tmcc->indicator = PT3_SHIFT_MASK(data[0], 2, 4);
+	tmcc->emergency = PT3_SHIFT_MASK(data[0], 1, 1);
+	tmcc->partial   = PT3_SHIFT_MASK(data[0], 0, 1);
+
+	tmcc->mode[0] = PT3_SHIFT_MASK(data[1], 5, 3);
+	tmcc->mode[1] = PT3_SHIFT_MASK(data[2], 0, 3);
+	tmcc->mode[2] = PT3_SHIFT_MASK(data[4], 3, 3);
+
+	tmcc->rate[0] = PT3_SHIFT_MASK(data[1], 2, 3);
+	tmcc->rate[1] = PT3_SHIFT_MASK(data[3], 5, 3);
+	tmcc->rate[2] = PT3_SHIFT_MASK(data[4], 0, 3);
+
+	interleave0h = PT3_SHIFT_MASK(data[1], 0, 2);
+	interleave0l = PT3_SHIFT_MASK(data[2], 7, 1);
+
+	tmcc->interleave[0] = interleave0h << 1 | interleave0l << 0;
+	tmcc->interleave[1] = PT3_SHIFT_MASK(data[3], 2, 3);
+	tmcc->interleave[2] = PT3_SHIFT_MASK(data[5], 5, 3);
+
+	segment1h = PT3_SHIFT_MASK(data[3], 0, 2);
+	segment1l = PT3_SHIFT_MASK(data[4], 6, 2);
+
+	tmcc->segment[0] = PT3_SHIFT_MASK(data[2], 3, 4);
+	tmcc->segment[1] = segment1h << 2 | segment1l << 0;
+	tmcc->segment[2] = PT3_SHIFT_MASK(data[5], 1, 4);
+
+	return ret;
+}
+
+int tc90522_read_tmcc_s(struct pt3_adapter *adap, struct tmcc_s *tmcc)
+{
+	enum {
+		BASE = 0xc5,
+		SIZE = 0xe5 - BASE + 1
+	};
+	u8 data[SIZE];
+	u32 i, byte_offset, bit_offset;
+
+	int ret = tc90522_read(adap, 0xc3, data, 1);
+	if (ret)
+		return ret;
+	if (PT3_SHIFT_MASK(data[0], 4, 1))
+		return -EBADMSG;
+
+	ret = tc90522_read(adap, 0xce, data, 2);
+	if (ret)
+		return ret;
+	if (tc90522_byten(data, 2) == 0)
+		return -EBADMSG;
+
+	ret = tc90522_read(adap, 0xc3, data, 1);
+	if (ret)
+		return ret;
+	tmcc->emergency = PT3_SHIFT_MASK(data[0], 2, 1);
+	tmcc->extflag   = PT3_SHIFT_MASK(data[0], 1, 1);
+
+	ret = tc90522_read(adap, 0xc5, data, SIZE);
+	if (ret)
+		return ret;
+	tmcc->indicator = PT3_SHIFT_MASK(data[0xc5 - BASE], 3, 5);
+	tmcc->uplink    = PT3_SHIFT_MASK(data[0xc7 - BASE], 0, 4);
+
+	for (i = 0; i < 4; i++) {
+		byte_offset = i >> 1;
+		bit_offset = (i & 1) ? 0 : 4;
+		tmcc->mode[i] = PT3_SHIFT_MASK(data[0xc8 + byte_offset - BASE], bit_offset, 4);
+		tmcc->slot[i] = PT3_SHIFT_MASK(data[0xca + i - BASE], 0, 6);
+	}
+	for (i = 0; i < 8; i++)
+		tmcc->id[i] = tc90522_byten(data + 0xce + i * 2 - BASE, 2);
+	return ret;
+}
+
+int tc90522_read_tmcc(struct pt3_adapter *adap, void *tmcc)
+{
+	if (adap->type == SYS_ISDBS)
+		return tc90522_read_tmcc_s(adap, (struct tmcc_s *)tmcc);
+	else
+		return tc90522_read_tmcc_t(adap, (struct tmcc_t *)tmcc);
+}
+
+int tc90522_write_id_s(struct pt3_adapter *adap, u16 id)
+{
+	u8 data[2] = { id >> 8, (u8)id };
+	return tc90522_write(adap, 0x8f, data, sizeof(data));
+}
+
+EXPORT_SYMBOL(tc90522_init);
+EXPORT_SYMBOL(tc90522_read_cn);
+EXPORT_SYMBOL(tc90522_read_id_s);
+EXPORT_SYMBOL(tc90522_read_retryov_tmunvld_fulock);
+EXPORT_SYMBOL(tc90522_read_tmcc);
+EXPORT_SYMBOL(tc90522_set_agc);
+EXPORT_SYMBOL(tc90522_set_powers);
+EXPORT_SYMBOL(tc90522_set_ts_pins_mode);
+EXPORT_SYMBOL(tc90522_write_id_s);
+
diff --git a/drivers/media/tuners/tc90522.h b/drivers/media/tuners/tc90522.h
new file mode 100644
index 0000000..9d33ec5
--- /dev/null
+++ b/drivers/media/tuners/tc90522.h
@@ -0,0 +1,90 @@
+/*
+ * Earthsoft PT3 demodulator driver TC90522 Toshiba OFDM(ISDB-T)/8PSK(ISDB-S)
+ *
+ * Copyright (C) 2013 Budi Rachmanto, AreMa Inc. <info@are.ma>
+ *
+ * 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	__TC90522_H__
+#define	__TC90522_H__
+
+/* Transmission and Multiplexing Configuration Control */
+
+enum {
+	LAYER_INDEX_L = 0,
+	LAYER_INDEX_H,
+
+	LAYER_INDEX_A = 0,
+	LAYER_INDEX_B,
+	LAYER_INDEX_C
+};
+
+enum {
+	LAYER_COUNT_S = LAYER_INDEX_H + 1,
+	LAYER_COUNT_T = LAYER_INDEX_C + 1,
+};
+
+struct tmcc_s {
+	u32 indicator;
+	u32 mode[4];
+	u32 slot[4];
+	u32 id[8];
+	u32 emergency;
+	u32 uplink;
+	u32 extflag;
+};
+
+struct tmcc_t {
+	u32 system;
+	u32 indicator;
+	u32 emergency;
+	u32 partial;
+	u32 mode[LAYER_COUNT_T];
+	u32 rate[LAYER_COUNT_T];
+	u32 interleave[LAYER_COUNT_T];
+	u32 segment[LAYER_COUNT_T];
+};
+
+enum tc90522_agc {
+	TC90522_AGC_AUTO,
+	TC90522_AGC_MANUAL,
+};
+
+enum pt3_ts_pin_mode {
+	PT3_TS_PIN_MODE_NORMAL,
+	PT3_TS_PIN_MODE_LOW,
+	PT3_TS_PIN_MODE_HIGH,
+};
+
+struct pt3_ts_pins_mode {
+	enum pt3_ts_pin_mode clock_data, byte, valid;
+};
+
+u32 tc90522_index(struct pt3_adapter *adap);
+int tc90522_init(struct pt3_adapter *adap);
+int tc90522_read_cn(struct pt3_adapter *adap, u32 *cn);
+int tc90522_read_id_s(struct pt3_adapter *adap, u16 *id);
+int tc90522_read_retryov_tmunvld_fulock(struct pt3_adapter *adap, bool *retryov, bool *tmunvld, bool *fulock);
+int tc90522_read_tmcc(struct pt3_adapter *adap, void *tmcc);
+int tc90522_read_tuner(struct pt3_adapter *adap, u8 addr, u8 *data);
+int tc90522_read_tuner_without_addr(struct pt3_adapter *adap, u8 *data);
+int tc90522_set_agc(struct pt3_adapter *adap, enum tc90522_agc agc);
+int tc90522_set_powers(struct pt3_adapter *adap, bool tuner, bool amp);
+int tc90522_set_sleep_s(struct pt3_adapter *adap, bool sleep);
+int tc90522_set_ts_pins_mode(struct pt3_adapter *adap, struct pt3_ts_pins_mode *mode);
+u32 tc90522_time_diff(struct timeval *st, struct timeval *et);
+int tc90522_write_id_s(struct pt3_adapter *adap, u16 id);
+int tc90522_write_sleep_time(struct pt3_adapter *adap, int sleep);
+int tc90522_write_tuner(struct pt3_adapter *adap, u8 addr, const u8 *data, u32 size);
+int tc90522_write_tuner_without_addr(struct pt3_adapter *adap, const u8 *data, u32 size);
+
+#endif
-- 
1.8.4.5


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH] Full DVB driver package for Earthsoft PT3 (ISDB-S/T) cards
  2013-11-06 13:14       ` Michael Krufky
@ 2013-11-06 16:13         ` Antti Palosaari
  0 siblings, 0 replies; 21+ messages in thread
From: Antti Palosaari @ 2013-11-06 16:13 UTC (permalink / raw)
  To: Michael Krufky, ほち
  Cc: linux-media, Hans De Goede, Hans Verkuil, Laurent Pinchart,
	Sylwester Nawrocki, Guennadi Liakhovetski, Peter Senna Tschudin

On 06.11.2013 15:14, Michael Krufky wrote:
> On Tue, Nov 5, 2013 at 5:30 PM, ほち <knightrider@are.ma> wrote:
>> Michael Krufky <mkrufky <at> linuxtv.org> writes:
>>
>>> As the DVB maintainer, I am telling you that I won't merge this as a
>>> monolithic driver.  The standard is to separate the driver into
>>> modules where possible, unless there is a valid reason for doing
>>> otherwise.
>>>
>>> I understand that you used the PT1 driver as a reference, but we're
>>> trying to enforce a standard of codingstyle within the kernel.  I
>>> recommend looking at the other DVB drivers as well.
>>
>> OK Sir. Any good / latest examples?
>
> There are plenty of DVB drivers to look at under drivers/media/  ...
> you may notice that there are v4l and dvb device drivers together
> under this hierarchy.  It's easy to tell which drivers support DVB
> when you look at the source.
>
> I could name a few specific ones, but i'd really recommend for you to
> take a look at a bunch of them.  No single driver should be considered
> a 'prefect example' as they are all under constant maintenance.
>
> Also, many of these drivers are for devices that support both v4l and
> DVB interfaces.  One example is the cx23885 driver.  Still, please try
> to look over the entire media tree, as that would give a better idea
> of how the drivers are structured.

I will also try explain that modular chipset driver architecture what I 
could :)

If you look normal digital television device there is always 3 chips, 
usually those exists in physically, but some cases multiple chips are 
integrated to same packet.
Those chips are:
1) bus interface (USB/PCIe/firewire "bridge")
2) demodulator
3) RF tuner (we call it usually just tuner)

There has been multiple cases where people has submitted one big driver 
and afterwards some new devices appeared having same chips. It is almost 
impossible to separate those drivers afterwards as you will need 
original hardware and so. That has led to situation we have some 
overlapping drivers.

To avoid these problems, we have specified some rules to new drivers:
RFCv2: Second draft of guidelines for submitting patches to linux-media
http://lwn.net/Articles/529490/

I search some pictures from that device to see what are used chips. Here 
is blog having some pictures:
http://hidepod.blog.shinobi.jp/iyh-/%E5%98%98%E3%81%A0%EF%BC%81

What I see:
1) PCI-bridge. Custom Altera Cyclone IV FPGA. (heh, that is familiar 
chip for me. I have used older Cyclone II for some digital technique 
course exercises).
2) Toshiba demodulator
3) Sharp tuner module (there is some tuner chip inside, which needs driver)

So those are the parts and each one needs own driver.

regards
Antti

-- 
http://palosaari.fi/

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Full DVB driver package for Earthsoft PT3 (ISDB-S/T) cards
  2013-11-05 22:30     ` ほち
@ 2013-11-06 13:14       ` Michael Krufky
  2013-11-06 16:13         ` Antti Palosaari
  0 siblings, 1 reply; 21+ messages in thread
From: Michael Krufky @ 2013-11-06 13:14 UTC (permalink / raw)
  To: ほち
  Cc: linux-media, Hans De Goede, Hans Verkuil, Laurent Pinchart,
	Sylwester Nawrocki, Guennadi Liakhovetski, Peter Senna Tschudin

On Tue, Nov 5, 2013 at 5:30 PM, ほち <knightrider@are.ma> wrote:
> Michael Krufky <mkrufky <at> linuxtv.org> writes:
>
>> As the DVB maintainer, I am telling you that I won't merge this as a
>> monolithic driver.  The standard is to separate the driver into
>> modules where possible, unless there is a valid reason for doing
>> otherwise.
>>
>> I understand that you used the PT1 driver as a reference, but we're
>> trying to enforce a standard of codingstyle within the kernel.  I
>> recommend looking at the other DVB drivers as well.
>
> OK Sir. Any good / latest examples?

There are plenty of DVB drivers to look at under drivers/media/  ...
you may notice that there are v4l and dvb device drivers together
under this hierarchy.  It's easy to tell which drivers support DVB
when you look at the source.

I could name a few specific ones, but i'd really recommend for you to
take a look at a bunch of them.  No single driver should be considered
a 'prefect example' as they are all under constant maintenance.

Also, many of these drivers are for devices that support both v4l and
DVB interfaces.  One example is the cx23885 driver.  Still, please try
to look over the entire media tree, as that would give a better idea
of how the drivers are structured.

Best regards,

Mike Krufky

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Full DVB driver package for Earthsoft PT3 (ISDB-S/T) cards
  2013-11-05 20:56   ` ほち
  2013-11-05 21:09     ` Michael Krufky
@ 2013-11-05 22:30     ` ほち
  2013-11-06 13:14       ` Michael Krufky
  1 sibling, 1 reply; 21+ messages in thread
From: ほち @ 2013-11-05 22:30 UTC (permalink / raw)
  To: Michael Krufky
  Cc: linux-media, Hans De Goede, Hans Verkuil, Laurent Pinchart,
	Sylwester Nawrocki, Guennadi Liakhovetski, Peter Senna Tschudin

Michael Krufky <mkrufky <at> linuxtv.org> writes:

> As the DVB maintainer, I am telling you that I won't merge this as a
> monolithic driver.  The standard is to separate the driver into
> modules where possible, unless there is a valid reason for doing
> otherwise.
>
> I understand that you used the PT1 driver as a reference, but we're
> trying to enforce a standard of codingstyle within the kernel.  I
> recommend looking at the other DVB drivers as well.

OK Sir. Any good / latest examples?

> Please shorten it to something more along the lines of:
>
> Support for Earthsoft PT3 PCI-Express cards.
>
> Say Y or M to enable support for this device.

Roger

> > FYI, there is another version of PT3 driver, named pt3_drv.ko, that
> > utilize character devices as the I/O. I'd rather use pt3_dvb.ko to
> > distinguish.
>
> we're not interested in multiple drivers for the same hardware.  Only
> one will be merged into the kernel, if any at all, and users need not
> think about the names of these drivers.  One of the beauties of
> merging a driver into the kernel is that users gain automatic support
> for the hardware without having to think or care about the name of the
> driver.

pt3_drv.ko is a public domain (old-fashioned) chardev driver for PT3,
and does not conform to standard DVB platform.
It doesn't seem to be merged into the mainstreem kernel tree.

> > Maybe I'd like to change the dirname:
> > drivers/media/pci/pt3_dvb => drivers/media/pci/pt3
>
> not a bad idea

> >> every source file and header file should include GPLv2 license headers.
> >
> > Roger: not very crucial though...
>
> entirely crucial if you're looking to merge into the kernel.  ...or
> did we misunderstand your request?

I meant: not a big task... is the following enough?

/*
 * DVB driver for Earthsoft PT3 PCI-E ISDB-S/T card
 *
 * Copyright (C) 2013 xxxx xxxx <xxxxxx@zng.info>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

> >>> +#define PT3_QM_INIT_DUMMY_RESET 0x0c
> >>
> >> it's nicer when these macros are defined in one place, but its not a
> >> requirement.  It's OK to leave it here if you really want to, but I
> >> suggest instead to create a _reg.h file containing all register
> >> #defines
> >
> > Will consider...

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Full DVB driver package for Earthsoft PT3 (ISDB-S/T) cards
  2013-11-05 20:56   ` ほち
@ 2013-11-05 21:09     ` Michael Krufky
  2013-11-05 22:30     ` ほち
  1 sibling, 0 replies; 21+ messages in thread
From: Michael Krufky @ 2013-11-05 21:09 UTC (permalink / raw)
  To: ほち
  Cc: linux-media, Hans De Goede, Hans Verkuil, Laurent Pinchart,
	Sylwester Nawrocki, Guennadi Liakhovetski, Peter Senna Tschudin

On Tue, Nov 5, 2013 at 3:56 PM, ほち <knightrider@are.ma> wrote:
> see inline
>
> 2013/11/6 Michael Krufky <mkrufky@linuxtv.org>:
>> responding inline:
>>
>>> Mauro Carvalho Chehab asked to put tuner code as an I2C driver, under drivers/media/tuners, frontends at drivers/media/dvb-
>>> However, to keep package integrity & compatibility with PT1/PT2 user apps, FE etc. are still placed in the same directory.
>>
>> A userspace application doesn't care  where file are places within the
>> kernel tree.  You are to use standard linux-dvb api's and the
>> codingstyle of the driver shall comply with that of the linux kernel's
>> DVB subsystem, including the proper placement of files.
>
> As stated in my (previous) mails, I took PT1 module as a reference.
> Everything is located in single directory ...pt1/, including the frontends.
> They are built as a single integrated module, earth-pt1.ko
>
> Sure I can split the FEs out to .../dvb-frontends/, build there as a separated
> (FE only) module that would be hot-linked with the main module. However
> I'm afraid (& sure) this will only make people confused with complex
> dependencies leading to annoying bugs... The simpler the better...
>
> Guys I need more opinions from other people before splitting the module.
> IMHO even Linus won't like this...
>

As the DVB maintainer, I am telling you that I won't merge this as a
monolithic driver.  The standard is to separate the driver into
modules where possible, unless there is a valid reason for doing
otherwise.

I understand that you used the PT1 driver as a reference, but we're
trying to enforce a standard of codingstyle within the kernel.  I
recommend looking at the other DVB drivers as well.

>>> diff --git a/drivers/media/pci/pt3_dvb/Kconfig b/drivers/media/pci/pt3_dvb/Kconfig
>>> new file mode 100644
>>> index 0000000..f9ba00d
>>> --- /dev/null
>>> +++ b/drivers/media/pci/pt3_dvb/Kconfig
>>> @@ -0,0 +1,12 @@
>>> +config PT3_DVB
>>> +       tristate "Earthsoft PT3 cards"
>>> +       depends on DVB_CORE && PCI
>>> +       help
>>> +         Support for Earthsoft PT3 PCI-Express cards.
>>> +
>>> +         Since these cards have no MPEG decoder onboard, they transmit
>>> +         only compressed MPEG data over the PCI bus, so you need
>>> +         an external software decoder to watch TV on your computer.
>>> +
>>> +         Say Y or M if you own such a device and want to use it.
>>
>> Very few of these digital tuner boards have onboard mpeg decoders.  We
>> can do without this extra information here.
>
> ok, will change to:
> These cards transmit only compressed MPEG data over the PCI bus.
> You need external software decoder to watch TV on your computer.

This is superfluous information.  Please look at the Kconfig
description for the many other DVB drivers supported in the kernel.
There are very few that contain their own hardware decoders, and
almosy all of them require a software decoder for playback on a PC.
Please shorten it to something more along the lines of:

Support for Earthsoft PT3 PCI-Express cards.

Say Y or M to enable support for this device.

>
>>> diff --git a/drivers/media/pci/pt3_dvb/Makefile b/drivers/media/pci/pt3_dvb/Makefile
>>> new file mode 100644
>>> index 0000000..7087c90
>>> --- /dev/null
>>> +++ b/drivers/media/pci/pt3_dvb/Makefile
>>> @@ -0,0 +1,6 @@
>>> +pt3_dvb-objs := pt3.o pt3_fe.o pt3_dma.o pt3_tc.o pt3_i2c.o pt3_bus.o
>>> +
>>> +obj-$(CONFIG_PT3_DVB) += pt3_dvb.o
>>> +
>>> +ccflags-y += -Idrivers/media/dvb-core -Idrivers/media/dvb-frontends -Idrivers/media/tuners
>>> +
>>
>> Ususally, the driver would be named 'pt3.ko' and the object file that
>> handles the DVB api would be called pt3_dvb.o
>>
>> This isn't any rule, but the way that you've named them seems a bit
>> awkward to me.  I don't require that you change this, just stating my
>> awkward feeling on the matter.
>
> FYI, there is another version of PT3 driver, named pt3_drv.ko, that
> utilize character devices as the I/O. I'd rather use pt3_dvb.ko to
> distinguish.
>

we're not interested in multiple drivers for the same hardware.  Only
one will be merged into the kernel, if any at all, and users need not
think about the names of these drivers.  One of the beauties of
merging a driver into the kernel is that users gain automatic support
for the hardware without having to think or care about the name of the
driver.

> Maybe I'd like to change the dirname:
> drivers/media/pci/pt3_dvb => drivers/media/pci/pt3

not a bad idea ;-)

>
>>> +static int lnb = 2;    /* used if not set by frontend / the value is invalid */
>>> +module_param(lnb, int, 0);
>>> +MODULE_PARM_DESC(lnb, "LNB level (0:OFF 1:+11V 2:+15V)");
>>
>> Take these above three statements out of the header file and move them
>> into a .c file
>
> OK Sir
>
>>> +struct dvb_frontend *pt3_fe_s_attach(struct pt3_adapter *adap);
>>> +struct dvb_frontend *pt3_fe_t_attach(struct pt3_adapter *adap);
>>
>> Please create separate headers corresponding to the .c file that
>> contains the function.  Don't put them all in one, as the tuner and
>> demodulator should be separate modules
>
> Splitting the protos? Well I will consider...

No need to consider it for very long - it absolutely needs to be done.
 Once the tuner / demod are broken out into separated modules, this
will no longer be appropriate at all.

>
>> every source file and header file should include GPLv2 license headers.
>
> Roger: not very crucial though...

entirely crucial if you're looking to merge into the kernel.  ...or
did we misunderstand your request?

>
>>> +#define PT3_QM_INIT_DUMMY_RESET 0x0c
>>
>> it's nicer when these macros are defined in one place, but its not a
>> requirement.  It's OK to leave it here if you really want to, but I
>> suggest instead to create a _reg.h file containing all register
>> #defines
>
> Will consider...

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Full DVB driver package for Earthsoft PT3 (ISDB-S/T) cards
       [not found] ` <CAOcJUbxCjEWk47MkJP15QBAuGd3ePYS3ZRMduqdMCrVT362-8Q@mail.gmail.com>
@ 2013-11-05 20:56   ` ほち
  2013-11-05 21:09     ` Michael Krufky
  2013-11-05 22:30     ` ほち
  0 siblings, 2 replies; 21+ messages in thread
From: ほち @ 2013-11-05 20:56 UTC (permalink / raw)
  To: Michael Krufky
  Cc: linux-media, Hans De Goede, Hans Verkuil, Laurent Pinchart,
	Sylwester Nawrocki, Guennadi Liakhovetski, Peter Senna Tschudin

see inline

2013/11/6 Michael Krufky <mkrufky@linuxtv.org>:
> responding inline:
>
>> Mauro Carvalho Chehab asked to put tuner code as an I2C driver, under drivers/media/tuners, frontends at drivers/media/dvb-
>> However, to keep package integrity & compatibility with PT1/PT2 user apps, FE etc. are still placed in the same directory.
>
> A userspace application doesn't care  where file are places within the
> kernel tree.  You are to use standard linux-dvb api's and the
> codingstyle of the driver shall comply with that of the linux kernel's
> DVB subsystem, including the proper placement of files.

As stated in my (previous) mails, I took PT1 module as a reference.
Everything is located in single directory ...pt1/, including the frontends.
They are built as a single integrated module, earth-pt1.ko

Sure I can split the FEs out to .../dvb-frontends/, build there as a separated
(FE only) module that would be hot-linked with the main module. However
I'm afraid (& sure) this will only make people confused with complex
dependencies leading to annoying bugs... The simpler the better...

Guys I need more opinions from other people before splitting the module.
IMHO even Linus won't like this...

>> diff --git a/drivers/media/pci/pt3_dvb/Kconfig b/drivers/media/pci/pt3_dvb/Kconfig
>> new file mode 100644
>> index 0000000..f9ba00d
>> --- /dev/null
>> +++ b/drivers/media/pci/pt3_dvb/Kconfig
>> @@ -0,0 +1,12 @@
>> +config PT3_DVB
>> +       tristate "Earthsoft PT3 cards"
>> +       depends on DVB_CORE && PCI
>> +       help
>> +         Support for Earthsoft PT3 PCI-Express cards.
>> +
>> +         Since these cards have no MPEG decoder onboard, they transmit
>> +         only compressed MPEG data over the PCI bus, so you need
>> +         an external software decoder to watch TV on your computer.
>> +
>> +         Say Y or M if you own such a device and want to use it.
>
> Very few of these digital tuner boards have onboard mpeg decoders.  We
> can do without this extra information here.

ok, will change to:
These cards transmit only compressed MPEG data over the PCI bus.
You need external software decoder to watch TV on your computer.

>> diff --git a/drivers/media/pci/pt3_dvb/Makefile b/drivers/media/pci/pt3_dvb/Makefile
>> new file mode 100644
>> index 0000000..7087c90
>> --- /dev/null
>> +++ b/drivers/media/pci/pt3_dvb/Makefile
>> @@ -0,0 +1,6 @@
>> +pt3_dvb-objs := pt3.o pt3_fe.o pt3_dma.o pt3_tc.o pt3_i2c.o pt3_bus.o
>> +
>> +obj-$(CONFIG_PT3_DVB) += pt3_dvb.o
>> +
>> +ccflags-y += -Idrivers/media/dvb-core -Idrivers/media/dvb-frontends -Idrivers/media/tuners
>> +
>
> Ususally, the driver would be named 'pt3.ko' and the object file that
> handles the DVB api would be called pt3_dvb.o
>
> This isn't any rule, but the way that you've named them seems a bit
> awkward to me.  I don't require that you change this, just stating my
> awkward feeling on the matter.

FYI, there is another version of PT3 driver, named pt3_drv.ko, that
utilize character devices as the I/O. I'd rather use pt3_dvb.ko to
distinguish.

Maybe I'd like to change the dirname:
drivers/media/pci/pt3_dvb => drivers/media/pci/pt3

>> +static int lnb = 2;    /* used if not set by frontend / the value is invalid */
>> +module_param(lnb, int, 0);
>> +MODULE_PARM_DESC(lnb, "LNB level (0:OFF 1:+11V 2:+15V)");
>
> Take these above three statements out of the header file and move them
> into a .c file

OK Sir

>> +struct dvb_frontend *pt3_fe_s_attach(struct pt3_adapter *adap);
>> +struct dvb_frontend *pt3_fe_t_attach(struct pt3_adapter *adap);
>
> Please create separate headers corresponding to the .c file that
> contains the function.  Don't put them all in one, as the tuner and
> demodulator should be separate modules

Splitting the protos? Well I will consider...

> every source file and header file should include GPLv2 license headers.

Roger: not very crucial though...

>> +#define PT3_QM_INIT_DUMMY_RESET 0x0c
>
> it's nicer when these macros are defined in one place, but its not a
> requirement.  It's OK to leave it here if you really want to, but I
> suggest instead to create a _reg.h file containing all register
> #defines

Will consider...

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH] Full DVB driver package for Earthsoft PT3 (ISDB-S/T) cards
@ 2013-11-05 16:36 буди Романто
  0 siblings, 0 replies; 21+ messages in thread
From: буди Романто @ 2013-11-05 16:36 UTC (permalink / raw)
  To: linux-media
  Cc: hdegoede, hverkuil, laurent.pinchart, mkrufky,
	sylvester.nawrocki, g.liakhovetski, peter.senna,
	Буди
	Романто

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain, Size: 81837 bytes --]

From: Буди Романто <knightrider@are.ma>

A DVB driver for Earthsoft PT3 (ISDB-S/T) receiver PCI Express cards, based on
1. PT3 chardev driver
	https://github.com/knight-rider/ptx/tree/master/pt3_drv
	https://github.com/m-tsudo/pt3
2. PT1/PT2 DVB driver
	./drivers/media/pci/pt1

It behaves similarly as PT1 DVB, plus some tuning enhancements:
1. in addition to the real frequency:
	ISDB-S : freq. channel ID
	ISDB-T : freq# (I/O# +128), ch#, ch# +64 for CATV
2. in addition to TSID:
	ISDB-S : slot#

As requested, the following features have been removed:
- DKMS & standalone compile
- verbosity (debug levels), use single level -DDEBUG instead
- FE: SNR (.read_snr), use CNR (.read_signal_strength) instead

The full package (buildable as standalone, DKMS or mainstream embedded module) is available at
https://github.com/knight-rider/ptx/tree/master/pt3_dvb

Mauro Carvalho Chehab asked to put tuner code as an I2C driver, under drivers/media/tuners, frontends at drivers/media/dvb-
However, to keep package integrity & compatibility with PT1/PT2 user apps, FE etc. are still placed in the same directory.

Signed-off-by: Bud R <knightrider@are.ma>

--------------------------------------------------------------------

diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
index 53196f1..2c90c34 100644
--- a/drivers/media/pci/Kconfig
+++ b/drivers/media/pci/Kconfig
@@ -30,7 +30,6 @@ source "drivers/media/pci/cx88/Kconfig"
 source "drivers/media/pci/bt8xx/Kconfig"
 source "drivers/media/pci/saa7134/Kconfig"
 source "drivers/media/pci/saa7164/Kconfig"
-
 endif
 
 if MEDIA_DIGITAL_TV_SUPPORT
@@ -40,6 +39,7 @@ source "drivers/media/pci/b2c2/Kconfig"
 source "drivers/media/pci/pluto2/Kconfig"
 source "drivers/media/pci/dm1105/Kconfig"
 source "drivers/media/pci/pt1/Kconfig"
+source "drivers/media/pci/pt3_dvb/Kconfig"
 source "drivers/media/pci/mantis/Kconfig"
 source "drivers/media/pci/ngene/Kconfig"
 source "drivers/media/pci/ddbridge/Kconfig"
diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
index 35cc578..02c6857 100644
--- a/drivers/media/pci/Makefile
+++ b/drivers/media/pci/Makefile
@@ -7,6 +7,7 @@ obj-y        +=	ttpci/		\
 		pluto2/		\
 		dm1105/		\
 		pt1/		\
+		pt3_dvb/	\
 		mantis/		\
 		ngene/		\
 		ddbridge/	\
diff --git a/drivers/media/pci/pt3_dvb/Kconfig b/drivers/media/pci/pt3_dvb/Kconfig
new file mode 100644
index 0000000..f9ba00d
--- /dev/null
+++ b/drivers/media/pci/pt3_dvb/Kconfig
@@ -0,0 +1,12 @@
+config PT3_DVB
+	tristate "Earthsoft PT3 cards"
+	depends on DVB_CORE && PCI
+	help
+	  Support for Earthsoft PT3 PCI-Express cards.
+
+	  Since these cards have no MPEG decoder onboard, they transmit
+	  only compressed MPEG data over the PCI bus, so you need
+	  an external software decoder to watch TV on your computer.
+
+	  Say Y or M if you own such a device and want to use it.
+
diff --git a/drivers/media/pci/pt3_dvb/Makefile b/drivers/media/pci/pt3_dvb/Makefile
new file mode 100644
index 0000000..7087c90
--- /dev/null
+++ b/drivers/media/pci/pt3_dvb/Makefile
@@ -0,0 +1,6 @@
+pt3_dvb-objs := pt3.o pt3_fe.o pt3_dma.o pt3_tc.o pt3_i2c.o pt3_bus.o
+
+obj-$(CONFIG_PT3_DVB) += pt3_dvb.o
+
+ccflags-y += -Idrivers/media/dvb-core -Idrivers/media/dvb-frontends -Idrivers/media/tuners
+
diff --git a/drivers/media/pci/pt3_dvb/pt3.c b/drivers/media/pci/pt3_dvb/pt3.c
new file mode 100644
index 0000000..11c1035
--- /dev/null
+++ b/drivers/media/pci/pt3_dvb/pt3.c
@@ -0,0 +1,540 @@
+#include "pt3.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 DVB Driver");
+MODULE_LICENSE("GPL");
+
+static DEFINE_PCI_DEVICE_TABLE(pt3_id_table) = {
+	{ PCI_DEVICE(0x1172, 0x4c15) },
+	{ },
+};
+MODULE_DEVICE_TABLE(pci, pt3_id_table);
+
+#define DRV_NAME "pt3_dvb"
+
+struct {
+	u32 bits;
+	char *str;
+} pt3_lnb[] = {
+	{0b1100,  "0V"},
+	{0b1101, "11V"},
+	{0b1111, "15V"},
+};
+
+static int pt3_update_lnb(struct pt3_board *pt3)
+{
+	u8 i, lnb_eff = 0;
+
+	if (pt3->reset) {
+		writel(pt3_lnb[0].bits, pt3->reg[0] + REG_SYSTEM_W);
+		pt3->reset = false;
+		pt3->lnb = 0;
+	} else {
+		struct pt3_adapter *adap;
+		mutex_lock(&pt3->lock);
+		for (i = 0; i < PT3_NR_ADAPS; i++) {
+			adap = pt3->adap[i];
+			pr_debug("#%d in_use %d sleep %d\n", adap->idx, adap->in_use, adap->sleep);
+			if ((adap->type == SYS_ISDBS) && (!adap->sleep)) {
+				lnb_eff |= adap->voltage == SEC_VOLTAGE_13 ? 1
+					:  adap->voltage == SEC_VOLTAGE_18 ? 2
+					:  lnb;
+			}
+		}
+		mutex_unlock(&pt3->lock);
+		if (unlikely(lnb_eff < 0 || 2 < lnb_eff)) {
+			pr_err("Inconsistent LNB settings\n");
+			return -EINVAL;
+		}
+		if (pt3->lnb != lnb_eff) {
+			writel(pt3_lnb[lnb_eff].bits, pt3->reg[0] + REG_SYSTEM_W);
+			pt3->lnb = lnb_eff;
+		}
+	}
+	pr_debug("LNB=%s\n", pt3_lnb[lnb_eff].str);
+	return 0;
+}
+
+void pt3_filter(struct pt3_adapter *adap, struct dvb_demux *demux, u8 *buf, size_t count)
+{
+	dvb_dmx_swfilter(demux, buf, count);
+}
+
+int pt3_thread(void *data)
+{
+	size_t ret;
+	struct pt3_adapter *adap = data;
+
+	set_freezable();
+	while (!kthread_should_stop()) {
+		try_to_freeze();
+		while ((ret = pt3_dma_copy(adap->dma, &adap->demux)) > 0)
+			;
+		if (ret < 0) {
+			pr_debug("#%d fail dma_copy\n", adap->idx);
+			msleep_interruptible(1);
+		}
+	}
+	return 0;
+}
+
+static int pt3_start_polling(struct pt3_adapter *adap)
+{
+	int ret = 0;
+
+	mutex_lock(&adap->lock);
+	if (!adap->kthread) {
+		adap->kthread = kthread_run(pt3_thread, adap, DRV_NAME "_%d", adap->idx);
+		if (IS_ERR(adap->kthread)) {
+			ret = PTR_ERR(adap->kthread);
+			adap->kthread = NULL;
+		} else {
+			pt3_dma_set_test_mode(adap->dma, RESET, 0);	/* reset_error_count */
+			pt3_dma_set_enabled(adap->dma, true);
+		}
+	}
+	mutex_unlock(&adap->lock);
+	return ret;
+}
+
+static void pt3_stop_polling(struct pt3_adapter *adap)
+{
+	mutex_lock(&adap->lock);
+	if (adap->kthread) {
+		pt3_dma_set_enabled(adap->dma, false);
+		pr_debug("#%d DMA ts_err packet cnt %d\n",
+			adap->idx, pt3_dma_get_ts_error_packet_count(adap->dma));
+		kthread_stop(adap->kthread);
+		adap->kthread = NULL;
+	}
+	mutex_unlock(&adap->lock);
+}
+
+static int pt3_start_feed(struct dvb_demux_feed *feed)
+{
+	int ret;
+	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
+	if (!adap->users++) {
+		if (adap->in_use) {
+			pr_err("#%d device is already used\n", adap->idx);
+			return -EIO;
+		}
+		pr_debug("#%d %s selected, DMA %s\n",
+			adap->idx, adap->str, pt3_dma_get_status(adap->dma) & 1 ? "ON" : "OFF");
+		adap->in_use = true;
+		ret = pt3_start_polling(adap);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int pt3_stop_feed(struct dvb_demux_feed *feed)
+{
+	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
+	if (!--adap->users) {
+		pt3_stop_polling(adap);
+		adap->in_use = false;
+		msleep_interruptible(40);
+	}
+	return 0;
+}
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static struct pt3_adapter *pt3_alloc_adapter(struct pt3_board *pt3)
+{
+	int ret;
+	struct dvb_adapter *dvb;
+	struct dvb_demux *demux;
+	struct dmxdev *dmxdev;
+	struct pt3_adapter *adap = kzalloc(sizeof(struct pt3_adapter), GFP_KERNEL);
+
+	if (!adap) {
+		ret = -ENOMEM;
+		goto err;
+	}
+	adap->pt3 = pt3;
+	adap->voltage = SEC_VOLTAGE_OFF;
+	adap->sleep = true;
+
+	dvb = &adap->dvb;
+	dvb->priv = adap;
+	ret = dvb_register_adapter(dvb, DRV_NAME, THIS_MODULE, &pt3->pdev->dev, adapter_nr);
+	if (ret < 0)
+		goto err_kfree;
+
+	demux = &adap->demux;
+	demux->dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING;
+	demux->priv = adap;
+	demux->feednum = 256;
+	demux->filternum = 256;
+	demux->start_feed = pt3_start_feed;
+	demux->stop_feed = pt3_stop_feed;
+	demux->write_to_decoder = NULL;
+	ret = dvb_dmx_init(demux);
+	if (ret < 0)
+		goto err_unregister_adapter;
+
+	dmxdev = &adap->dmxdev;
+	dmxdev->filternum = 256;
+	dmxdev->demux = &demux->dmx;
+	dmxdev->capabilities = 0;
+	ret = dvb_dmxdev_init(dmxdev, dvb);
+	if (ret < 0)
+		goto err_dmx_release;
+
+	return adap;
+
+err_dmx_release:
+	dvb_dmx_release(demux);
+err_unregister_adapter:
+	dvb_unregister_adapter(dvb);
+err_kfree:
+	kfree(adap);
+err:
+	return ERR_PTR(ret);
+}
+
+static int pt3_tuner_power_on(struct pt3_board *pt3, struct pt3_bus *bus)
+{
+	int ret, i, j;
+	struct pt3_ts_pins_mode pins;
+
+	for (i = 0; i < PT3_NR_ADAPS; i++) {
+		ret = pt3_tc_init(pt3->adap[i]);
+		pr_debug("#%d tc_init ret=%d\n", i, ret);
+	}
+	ret = pt3_tc_set_powers(pt3->adap[PT3_NR_ADAPS-1], NULL, true, false);
+	if (ret) {
+		pr_debug("fail set powers.\n");
+		goto last;
+	}
+
+	pins.clock_data = PT3_TS_PIN_MODE_NORMAL;
+	pins.byte       = PT3_TS_PIN_MODE_NORMAL;
+	pins.valid      = PT3_TS_PIN_MODE_NORMAL;
+
+	for (i = 0; i < PT3_NR_ADAPS; i++) {
+		ret = pt3_tc_set_ts_pins_mode(pt3->adap[i], &pins);
+		if (ret)
+			pr_debug("#%d %s fail set ts pins mode ret=%d\n", i, pt3->adap[i]->str, ret);
+	}
+	msleep_interruptible(1);
+
+	for (i = 0; i < PT3_NR_ADAPS; i++)
+		if (pt3->adap[i]->type == SYS_ISDBS) {
+			for (j = 0; j < 10; j++) {
+				if (j)
+					pr_debug("retry pt3_qm_tuner_init\n");
+				ret = pt3_qm_tuner_init(pt3->i2c, pt3->adap[i]);
+				if (!ret)
+					break;
+				msleep_interruptible(1);
+			}
+			if (ret) {
+				pr_debug("#%d fail pt3_qm_tuner_init ret=0x%x\n", i, ret);
+				goto last;
+			}
+		}
+	if (unlikely(bus->cmd_addr < 4096))
+		pt3_i2c_copy(pt3->i2c, bus);
+
+	bus->cmd_addr = PT3_BUS_CMD_ADDR1;
+	ret = pt3_i2c_run(pt3->i2c, bus, false);
+	if (ret) {
+		pr_debug("failed cmd_addr=0x%x ret=0x%x\n", PT3_BUS_CMD_ADDR1, ret);
+		goto last;
+	}
+	ret = pt3_tc_set_powers(pt3->adap[PT3_NR_ADAPS-1], NULL, true, true);
+	if (ret) {
+		pr_debug("fail tc_set_powers,\n");
+		goto last;
+	}
+last:
+	return ret;
+}
+
+static int pt3_tuner_init_all(struct pt3_board *pt3)
+{
+	int ret, i;
+	struct pt3_i2c *i2c = pt3->i2c;
+	struct pt3_bus *bus = vzalloc(sizeof(struct pt3_bus));
+
+	if (!bus)
+		return -ENOMEM;
+	pt3_bus_end(bus);
+	bus->cmd_addr = PT3_BUS_CMD_ADDR0;
+
+	if (!pt3_i2c_is_clean(i2c)) {
+		pr_debug("cleanup I2C bus\n");
+		ret = pt3_i2c_run(i2c, bus, false);
+		if (ret)
+			goto last;
+		msleep_interruptible(10);
+	}
+	ret = pt3_tuner_power_on(pt3, bus);
+	if (ret)
+		goto last;
+	pr_debug("tuner_power_on\n");
+
+	for (i = 0; i < PT3_NR_ADAPS; i++) {
+		struct pt3_adapter *adap = pt3->adap[i];
+		ret = pt3_fe_tuner_sleep(adap, false);
+		if (ret)
+			goto last;
+		ret = pt3_fe_set_freq(adap, adap->init_ch, 0);
+		if (ret)
+			pr_debug("fail set_frequency, ret=%d\n", ret);
+		ret = pt3_fe_tuner_sleep(adap, true);
+		if (ret)
+			goto last;
+	}
+last:
+	vfree(bus);
+	return ret;
+}
+
+static void pt3_cleanup_adapter(struct pt3_adapter *adap)
+{
+	if (!adap)
+		return;
+	if (adap->kthread)
+		kthread_stop(adap->kthread);
+	if (adap->fe)
+		dvb_unregister_frontend(adap->fe);
+	if (!adap->sleep)
+		pt3_fe_tuner_sleep(adap, true);
+	if (adap->qm)
+		vfree(adap->qm);
+	if (adap->dma) {
+		if (adap->dma->enabled)
+			pt3_dma_set_enabled(adap->dma, false);
+		pt3_dma_free(adap->dma);
+	}
+	adap->demux.dmx.close(&adap->demux.dmx);
+	dvb_dmxdev_release(&adap->dmxdev);
+	dvb_dmx_release(&adap->demux);
+	dvb_unregister_adapter(&adap->dvb);
+	kfree(adap);
+}
+
+static int pt3_fe_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	adap->voltage = voltage;
+	return (adap->orig_voltage) ? adap->orig_voltage(fe, voltage) : 0;
+}
+
+static int pt3_fe_sleep(struct dvb_frontend *fe)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	adap->sleep = true;
+	pt3_update_lnb(adap->pt3);
+	return (adap->orig_sleep) ? adap->orig_sleep(fe) : 0;
+}
+
+static int pt3_fe_wakeup(struct dvb_frontend *fe)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	adap->sleep = false;
+	pt3_update_lnb(adap->pt3);
+	return (adap->orig_init) ? adap->orig_init(fe) : 0;
+}
+
+static int pt3_init_frontends(struct pt3_board *pt3)
+{
+	struct dvb_frontend *fe[PT3_NR_ADAPS];
+	int i, ret;
+
+	for (i = 0; i < PT3_NR_ADAPS; i++)
+		if (pt3->adap[i]->type == SYS_ISDBS) {
+			fe[i] = pt3_fe_s_attach(pt3->adap[i]);
+			if (!fe[i])
+				break;
+		} else {
+			fe[i] = pt3_fe_t_attach(pt3->adap[i]);
+			if (!fe[i])
+				break;
+		}
+	if (i < PT3_NR_ADAPS) {
+		while (i--)
+			fe[i]->ops.release(fe[i]);
+		return -ENOMEM;
+	}
+	for (i = 0; i < PT3_NR_ADAPS; i++) {
+		struct pt3_adapter *adap = pt3->adap[i];
+
+		adap->orig_voltage     = fe[i]->ops.set_voltage;
+		adap->orig_sleep       = fe[i]->ops.sleep;
+		adap->orig_init        = fe[i]->ops.init;
+		fe[i]->ops.set_voltage = pt3_fe_set_voltage;
+		fe[i]->ops.sleep       = pt3_fe_sleep;
+		fe[i]->ops.init        = pt3_fe_wakeup;
+
+		ret = dvb_register_frontend(&adap->dvb, fe[i]);
+		if (ret >= 0)
+			adap->fe = fe[i];
+		else {
+			while (i--)
+				dvb_unregister_frontend(fe[i]);
+			for (i = 0; i < PT3_NR_ADAPS; i++)
+				fe[i]->ops.release(fe[i]);
+			return ret;
+		}
+	}
+	return 0;
+}
+
+static void pt3_remove(struct pci_dev *pdev)
+{
+	int i;
+	struct pt3_board *pt3 = pci_get_drvdata(pdev);
+
+	if (pt3) {
+		pt3->reset = true;
+		pt3_update_lnb(pt3);
+		if (pt3->i2c) {
+			if (pt3->adap[PT3_NR_ADAPS-1])
+				pt3_tc_set_powers(pt3->adap[PT3_NR_ADAPS-1], NULL, false, false);
+			pt3_i2c_reset(pt3->i2c);
+			vfree(pt3->i2c);
+		}
+		for (i = 0; i < PT3_NR_ADAPS; i++)
+			pt3_cleanup_adapter(pt3->adap[i]);
+		if (pt3->reg[1])
+			iounmap(pt3->reg[1]);
+		if (pt3->reg[0])
+			iounmap(pt3->reg[0]);
+		pci_release_selected_regions(pdev, pt3->bars);
+		kfree(pt3);
+	}
+	pci_disable_device(pdev);
+}
+
+static int pt3_abort(struct pci_dev *pdev, int ret, char *fmt, ...)
+{
+	va_list ap;
+	char *s = NULL;
+	int slen;
+
+	va_start(ap, fmt);
+	slen = vsnprintf(s, 0, fmt, ap);
+	s = vzalloc(slen);
+	if (slen > 0 && s) {
+		vsnprintf(s, slen, fmt, ap);
+		dev_alert(&pdev->dev, "%s", s);
+		vfree(s);
+	}
+	va_end(ap);
+	pt3_remove(pdev);
+	return ret;
+}
+
+struct {
+	fe_delivery_system_t type;
+	u8 addr_tuner, addr_tc;
+	int init_ch;
+	char *str;
+} pt3_config[] = {
+	{SYS_ISDBS, 0x63, 0b00010001,  0, "ISDB-S"},
+	{SYS_ISDBS, 0x60, 0b00010011,  0, "ISDB-S"},
+	{SYS_ISDBT, 0x62, 0b00010000, 70, "ISDB-T"},
+	{SYS_ISDBT, 0x61, 0b00010010, 71, "ISDB-T"},
+};
+
+static int pt3_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+	struct pt3_board *pt3;
+	struct pt3_adapter *adap;
+	int i, ret, bars = pci_select_bars(pdev, IORESOURCE_MEM);
+
+	ret = pci_enable_device(pdev);
+	if (ret < 0)
+		return pt3_abort(pdev, ret, "PCI device unusable\n");
+	ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(64));
+	if (ret)
+		return pt3_abort(pdev, ret, "DMA mask error\n");
+	pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64));
+
+	pci_read_config_dword(pdev, PCI_CLASS_REVISION, &i);
+	if ((i & 0xFF) != 1)
+		return pt3_abort(pdev, ret, "Revision 0x%x is not supported\n", i & 0xFF);
+	ret = pci_request_selected_regions(pdev, bars, DRV_NAME);
+	if (ret < 0)
+		return pt3_abort(pdev, ret, "Could not request regions\n");
+
+	pci_set_master(pdev);
+	ret = pci_save_state(pdev);
+	if (ret)
+		return pt3_abort(pdev, ret, "Failed pci_save_state\n");
+	pt3 = kzalloc(sizeof(struct pt3_board), GFP_KERNEL);
+	if (!pt3)
+		return pt3_abort(pdev, -ENOMEM, "struct pt3_board out of memory\n");
+
+	pt3->bars = bars;
+	pt3->pdev = pdev;
+	pci_set_drvdata(pdev, pt3);
+	pt3->reg[0] = pci_ioremap_bar(pdev, 0);
+	pt3->reg[1] = pci_ioremap_bar(pdev, 2);
+	if (!pt3->reg[0] || !pt3->reg[1])
+		return pt3_abort(pdev, -EIO, "Failed pci_ioremap_bar\n");
+
+	ret = readl(pt3->reg[0] + REG_VERSION);
+	i = ((ret >> 24) & 0xFF);
+	if (i != 3)
+		return pt3_abort(pdev, -EIO, "ID=0x%x, not a PT3\n", i);
+	i = ((ret >>  8) & 0xFF);
+	if (i != 4)
+		return pt3_abort(pdev, -EIO, "FPGA version 0x%x is not supported\n", i);
+	mutex_init(&pt3->lock);
+
+	for (i = 0; i < PT3_NR_ADAPS; i++) {
+		pt3->adap[i] = NULL;
+		adap = pt3_alloc_adapter(pt3);
+		if (IS_ERR(adap))
+			return pt3_abort(pdev, PTR_ERR(adap), "Failed pt3_alloc_adapter\n");
+		adap->idx = i;
+		adap->dma = pt3_dma_create(adap);
+		if (!adap->dma)
+			return pt3_abort(pdev, -ENOMEM, "Failed pt3_dma_create\n");
+		mutex_init(&adap->lock);
+		pt3->adap[i] = adap;
+		adap->type       = pt3_config[i].type;
+		adap->addr_tuner = pt3_config[i].addr_tuner;
+		adap->addr_tc    = pt3_config[i].addr_tc;
+		adap->init_ch    = pt3_config[i].init_ch;
+		adap->str        = pt3_config[i].str;
+		if (adap->type == SYS_ISDBS) {
+			adap->qm = vzalloc(sizeof(struct pt3_qm));
+			if (!adap->qm)
+				return pt3_abort(pdev, -ENOMEM, "QM out of memory\n");
+			adap->qm->adap = adap;
+		}
+		adap->sleep = true;
+	}
+	pt3->reset = true;
+	pt3_update_lnb(pt3);
+
+	pt3->i2c = vzalloc(sizeof(struct pt3_i2c));
+	if (!pt3->i2c)
+		return pt3_abort(pdev, -ENOMEM, "Cannot allocate I2C\n");
+	mutex_init(&pt3->i2c->lock);
+	pt3->i2c->reg[0] = pt3->reg[0];
+	pt3->i2c->reg[1] = pt3->reg[1];
+
+	if (pt3_tuner_init_all(pt3))
+		return pt3_abort(pdev, ret, "Failed pt3_tuner_init_all\n");
+	ret = pt3_init_frontends(pt3);
+	return (ret >= 0) ? ret : pt3_abort(pdev, ret, "Failed pt3_init_frontends\n");
+}
+
+static struct pci_driver pt3_driver = {
+	.name		= DRV_NAME,
+	.probe		= pt3_probe,
+	.remove		= pt3_remove,
+	.id_table	= pt3_id_table,
+};
+
+module_pci_driver(pt3_driver);
+
diff --git a/drivers/media/pci/pt3_dvb/pt3.h b/drivers/media/pci/pt3_dvb/pt3.h
new file mode 100644
index 0000000..c810905
--- /dev/null
+++ b/drivers/media/pci/pt3_dvb/pt3.h
@@ -0,0 +1,222 @@
+#ifndef	__PT3_H__
+#define	__PT3_H__
+
+#define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+
+#include <linux/pci.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include "dvb_demux.h"
+#include "dmxdev.h"
+#include "dvb_frontend.h"
+
+#define PT3_NR_ADAPS 4
+#define PT3_SHIFT_MASK(val, shift, mask) (((val) >> (shift)) & (((u64)1<<(mask))-1))
+
+/* register idx */
+#define REG_VERSION	0x00	/*	R	Version		*/
+#define REG_BUS		0x04	/*	R	Bus		*/
+#define REG_SYSTEM_W	0x08	/*	W	System		*/
+#define REG_SYSTEM_R	0x0c	/*	R	System		*/
+#define REG_I2C_W	0x10	/*	W	I2C		*/
+#define REG_I2C_R	0x14	/*	R	I2C		*/
+#define REG_RAM_W	0x18	/*	W	RAM		*/
+#define REG_RAM_R	0x1c	/*	R	RAM		*/
+#define REG_BASE	0x40	/* + 0x18*idx			*/
+#define REG_DMA_DESC_L	0x00	/*	W	DMA		*/
+#define REG_DMA_DESC_H	0x04	/*	W	DMA		*/
+#define REG_DMA_CTL	0x08	/*	W	DMA		*/
+#define REG_TS_CTL	0x0c	/*	W	TS		*/
+#define REG_STATUS	0x10	/*	R	DMA/FIFO/TS	*/
+#define REG_TS_ERR	0x14	/*	R	TS		*/
+
+static int lnb = 2;	/* used if not set by frontend / the value is invalid */
+module_param(lnb, int, 0);
+MODULE_PARM_DESC(lnb, "LNB level (0:OFF 1:+11V 2:+15V)");
+
+/* Transmission and Multiplexing Configuration Control */
+
+enum {
+	LAYER_INDEX_L = 0,
+	LAYER_INDEX_H,
+
+	LAYER_INDEX_A = 0,
+	LAYER_INDEX_B,
+	LAYER_INDEX_C
+};
+
+enum {
+	LAYER_COUNT_S = LAYER_INDEX_H + 1,
+	LAYER_COUNT_T = LAYER_INDEX_C + 1,
+};
+
+struct tmcc_s {
+	u32 indicator;
+	u32 mode[4];
+	u32 slot[4];
+	u32 id[8];
+	u32 emergency;
+	u32 uplink;
+	u32 extflag;
+};
+
+struct tmcc_t {
+	u32 system;
+	u32 indicator;
+	u32 emergency;
+	u32 partial;
+	u32 mode[LAYER_COUNT_T];
+	u32 rate[LAYER_COUNT_T];
+	u32 interleave[LAYER_COUNT_T];
+	u32 segment[LAYER_COUNT_T];
+};
+
+struct pt3_i2c {
+	u8 __iomem *reg[2];
+	struct mutex lock;
+};
+
+struct pt3_dma_page {
+	dma_addr_t addr;
+	u8 *data;
+	u32 size, data_pos;
+};
+
+struct pt3_adapter;
+
+struct pt3_dma {
+	struct pt3_adapter *adap;
+	bool enabled;
+	u32 ts_pos, ts_count, desc_count;
+	struct pt3_dma_page *ts_info, *desc_info;
+	struct mutex lock;
+};
+
+struct pt3_qm {
+	struct pt3_adapter *adap;
+	u8 reg[32];
+
+	bool standby;
+	u32 wait_time_lpf, wait_time_search_fast, wait_time_search_normal;
+	struct tmcc_s tmcc;
+};
+
+struct pt3_board {
+	struct mutex lock;
+	bool reset;
+	int lnb;
+
+	struct pci_dev *pdev;
+	void __iomem *reg[2];
+	int bars;
+	struct pt3_i2c *i2c;
+
+	struct pt3_adapter *adap[PT3_NR_ADAPS];
+};
+
+struct pt3_adapter {
+	struct mutex lock;
+	struct pt3_board *pt3;
+
+	int idx, init_ch;
+	char *str;
+	fe_delivery_system_t type;
+	bool in_use, sleep;
+	u32 channel;
+	s32 offset;
+	u8 addr_tc, addr_tuner;
+	u32 freq;
+	struct pt3_qm *qm;
+	struct pt3_dma *dma;
+	struct task_struct *kthread;
+	int *dec;
+	struct dvb_adapter dvb;
+	struct dvb_demux demux;
+	int users;
+	struct dmxdev dmxdev;
+	struct dvb_frontend *fe;
+	int (*orig_voltage)(struct dvb_frontend *fe, fe_sec_voltage_t voltage);
+	int (*orig_sleep)  (struct dvb_frontend *fe);
+	int (*orig_init)   (struct dvb_frontend *fe);
+	fe_sec_voltage_t voltage;
+};
+
+enum pt3_ts_pin_mode {
+	PT3_TS_PIN_MODE_NORMAL,
+	PT3_TS_PIN_MODE_LOW,
+	PT3_TS_PIN_MODE_HIGH,
+};
+
+struct pt3_ts_pins_mode {
+	enum pt3_ts_pin_mode clock_data, byte, valid;
+};
+
+#define PT3_BUS_CMD_MAX   4096
+#define PT3_BUS_CMD_ADDR0 4096
+#define PT3_BUS_CMD_ADDR1 (4096 + 2042)
+
+struct pt3_bus {
+	u32 read_addr, cmd_addr, cmd_count, cmd_pos, buf_pos, buf_size;
+	u8 cmd_tmp, cmds[PT3_BUS_CMD_MAX], *buf;
+};
+
+enum pt3_tc_agc {
+	PT3_TC_AGC_AUTO,
+	PT3_TC_AGC_MANUAL,
+};
+
+enum pt3_dma_mode {
+	USE_LFSR = 1 << 16,
+	REVERSE  = 1 << 17,
+	RESET    = 1 << 18,
+};
+
+/* protos */
+u8 pt3_bus_data1(struct pt3_bus *bus, u32 index);
+void pt3_bus_end(struct pt3_bus *bus);
+void pt3_bus_push_read_data(struct pt3_bus *bus, u8 data);
+u32 pt3_bus_read(struct pt3_bus *bus, u8 *data, u32 size);
+void pt3_bus_sleep(struct pt3_bus *bus, u32 ms);
+void pt3_bus_start(struct pt3_bus *bus);
+void pt3_bus_stop(struct pt3_bus *bus);
+void pt3_bus_write(struct pt3_bus *bus, const u8 *data, u32 size);
+ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux);
+struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap);
+void pt3_dma_free(struct pt3_dma *dma);
+u32 pt3_dma_get_status(struct pt3_dma *dma);
+u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma);
+void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled);
+void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval);
+int pt3_fe_set_freq(struct pt3_adapter *adap, u32 channel, s32 offset);
+int pt3_fe_tuner_sleep(struct pt3_adapter *adap, bool sleep);
+void pt3_filter(struct pt3_adapter *adap, struct dvb_demux *demux, u8 *buf, size_t count);
+void pt3_i2c_copy(struct pt3_i2c *i2c, struct pt3_bus *bus);
+bool pt3_i2c_is_clean(struct pt3_i2c *i2c);
+void pt3_i2c_reset(struct pt3_i2c *i2c);
+int pt3_i2c_run(struct pt3_i2c *i2c, struct pt3_bus *bus, bool copy);
+u32 pt3_tc_index(struct pt3_adapter *adap);
+int pt3_tc_init(struct pt3_adapter *adap);
+int pt3_tc_read_cn_s(struct pt3_adapter *adap, struct pt3_bus *bus, u32 *cn);
+int pt3_tc_read_cndat_t(struct pt3_adapter *adap, struct pt3_bus *bus, u32 *cn);
+int pt3_tc_read_id_s(struct pt3_adapter *adap, struct pt3_bus *bus, u16 *id);
+int pt3_tc_read_retryov_tmunvld_fulock(struct pt3_adapter *adap, struct pt3_bus *bus, int *retryov, int *tmunvld, int *fulock);
+int pt3_tc_read_tmcc_s(struct pt3_adapter *adap, struct pt3_bus *bus, struct tmcc_s *tmcc);
+int pt3_tc_read_tmcc_t(struct pt3_adapter *adap, struct pt3_bus *bus, struct tmcc_t *tmcc);
+int pt3_tc_read_tuner(struct pt3_adapter *adap, struct pt3_bus *bus, u8 addr, u8 *data);
+int pt3_tc_read_tuner_without_addr(struct pt3_adapter *adap, struct pt3_bus *bus, u8 *data);
+int pt3_tc_set_agc_s(struct pt3_adapter *adap, enum pt3_tc_agc agc);
+int pt3_tc_set_agc_t(struct pt3_adapter *adap, enum pt3_tc_agc agc);
+int pt3_tc_set_powers(struct pt3_adapter *adap, struct pt3_bus *bus, bool tuner, bool amp);
+int pt3_tc_set_sleep_s(struct pt3_adapter *adap, struct pt3_bus *bus, bool sleep);
+int pt3_tc_set_ts_pins_mode(struct pt3_adapter *adap, struct pt3_ts_pins_mode *mode);
+u32 pt3_tc_time_diff(struct timeval *st, struct timeval *et);
+int pt3_tc_write_id_s(struct pt3_adapter *adap, struct pt3_bus *bus, u16 id);
+int pt3_tc_write_sleep_time(struct pt3_adapter *adap, int sleep);
+int pt3_tc_write_tuner(struct pt3_adapter *adap, struct pt3_bus *bus, u8 addr, const u8 *data, u32 size);
+int pt3_tc_write_tuner_without_addr(struct pt3_adapter *adap, struct pt3_bus *bus, const u8 *data, u32 size);
+int pt3_qm_tuner_init(struct pt3_i2c *i2c, struct pt3_adapter *adap);
+struct dvb_frontend *pt3_fe_s_attach(struct pt3_adapter *adap);
+struct dvb_frontend *pt3_fe_t_attach(struct pt3_adapter *adap);
+
+#endif
+
diff --git a/drivers/media/pci/pt3_dvb/pt3_bus.c b/drivers/media/pci/pt3_dvb/pt3_bus.c
new file mode 100644
index 0000000..5db7874
--- /dev/null
+++ b/drivers/media/pci/pt3_dvb/pt3_bus.c
@@ -0,0 +1,134 @@
+#include "pt3.h"
+
+enum pt3_bus_cmd {
+	I_END,
+	I_ADDRESS,
+	I_CLOCK_L,
+	I_CLOCK_H,
+	I_DATA_L,
+	I_DATA_H,
+	I_RESET,
+	I_SLEEP,
+	I_DATA_L_NOP  = 0x08,
+	I_DATA_H_NOP  = 0x0c,
+	I_DATA_H_READ = 0x0d,
+	I_DATA_H_ACK0 = 0x0e,
+	I_DATA_H_ACK1 = 0x0f,
+};
+
+static void pt3_bus_add_cmd(struct pt3_bus *bus, enum pt3_bus_cmd cmd)
+{
+	if ((bus->cmd_count % 2) == 0)
+		bus->cmd_tmp = cmd;
+	else
+		bus->cmd_tmp |= cmd << 4;
+
+	if (bus->cmd_count % 2) {
+		bus->cmds[bus->cmd_pos] = bus->cmd_tmp;
+		bus->cmd_pos++;
+		if (bus->cmd_pos >= sizeof(bus->cmds)) {
+			pr_debug("bus->cmds is overflow\n");
+			bus->cmd_pos = 0;
+		}
+	}
+	bus->cmd_count++;
+}
+
+u8 pt3_bus_data1(struct pt3_bus *bus, u32 index)
+{
+	if (unlikely(!bus->buf)) {
+		pr_debug("buf is not ready.\n");
+		return 0;
+	}
+	if (unlikely(bus->buf_size < index + 1)) {
+		pr_debug("buf does not have enough size. buf_size=%d\n",
+				bus->buf_size);
+		return 0;
+	}
+	return bus->buf[index];
+}
+
+void pt3_bus_start(struct pt3_bus *bus)
+{
+	pt3_bus_add_cmd(bus, I_DATA_H);
+	pt3_bus_add_cmd(bus, I_CLOCK_H);
+	pt3_bus_add_cmd(bus, I_DATA_L);
+	pt3_bus_add_cmd(bus, I_CLOCK_L);
+}
+
+void pt3_bus_stop(struct pt3_bus *bus)
+{
+	pt3_bus_add_cmd(bus, I_DATA_L);
+	pt3_bus_add_cmd(bus, I_CLOCK_H);
+	pt3_bus_add_cmd(bus, I_DATA_H);
+}
+
+void pt3_bus_write(struct pt3_bus *bus, const u8 *data, u32 size)
+{
+	u32 i, j;
+	u8 byte;
+
+	for (i = 0; i < size; i++) {
+		byte = data[i];
+		for (j = 0; j < 8; j++)
+			pt3_bus_add_cmd(bus, PT3_SHIFT_MASK(byte, 7 - j, 1) ? I_DATA_H_NOP : I_DATA_L_NOP);
+		pt3_bus_add_cmd(bus, I_DATA_H_ACK0);
+	}
+}
+
+u32 pt3_bus_read(struct pt3_bus *bus, u8 *data, u32 size)
+{
+	u32 i, j;
+	u32 index;
+
+	for (i = 0; i < size; i++) {
+		for (j = 0; j < 8; j++)
+			pt3_bus_add_cmd(bus, I_DATA_H_READ);
+		if (i == (size - 1))
+			pt3_bus_add_cmd(bus, I_DATA_H_NOP);
+		else
+			pt3_bus_add_cmd(bus, I_DATA_L_NOP);
+	}
+	index = bus->read_addr;
+	bus->read_addr += size;
+	if (likely(!bus->buf)) {
+		bus->buf = data;
+		bus->buf_pos = 0;
+		bus->buf_size = size;
+	} else
+		pr_debug("bus read buf already exists.\n");
+
+	return index;
+}
+
+void pt3_bus_push_read_data(struct pt3_bus *bus, u8 data)
+{
+	if (unlikely(bus->buf)) {
+		if (bus->buf_pos >= bus->buf_size) {
+			pr_debug("buffer over run. pos=%d\n", bus->buf_pos);
+			bus->buf_pos = 0;
+		}
+		bus->buf[bus->buf_pos] = data;
+		bus->buf_pos++;
+	}
+}
+
+void pt3_bus_sleep(struct pt3_bus *bus, u32 ms)
+{
+	u32 i;
+	for (i = 0; i < ms; i++)
+		pt3_bus_add_cmd(bus, I_SLEEP);
+}
+
+void pt3_bus_end(struct pt3_bus *bus)
+{
+	pt3_bus_add_cmd(bus, I_END);
+	if (bus->cmd_count % 2)
+		pt3_bus_add_cmd(bus, I_END);
+}
+
+void pt3_bus_reset(struct pt3_bus *bus)
+{
+	pt3_bus_add_cmd(bus, I_RESET);
+}
+
diff --git a/drivers/media/pci/pt3_dvb/pt3_dma.c b/drivers/media/pci/pt3_dvb/pt3_dma.c
new file mode 100644
index 0000000..bc4c67b
--- /dev/null
+++ b/drivers/media/pci/pt3_dvb/pt3_dma.c
@@ -0,0 +1,316 @@
+#include "pt3.h"
+
+#define PT3_DMA_MAX_DESCS	204
+#define PT3_DMA_PAGE_SIZE	(PT3_DMA_MAX_DESCS * sizeof(struct pt3_dma_desc))
+#define PT3_DMA_BLOCK_COUNT	17
+#define PT3_DMA_BLOCK_SIZE	(PT3_DMA_PAGE_SIZE * 47)
+#define PT3_DMA_TS_BUF_SIZE	(PT3_DMA_BLOCK_SIZE * PT3_DMA_BLOCK_COUNT)
+#define PT3_DMA_NOT_SYNC_BYTE	0x74
+
+struct pt3_dma_desc {
+	u64 page_addr;
+	u32 page_size;
+	u64 next_desc;
+} __packed;
+
+void pt3_dma_build_page_descriptor(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *desc_info, *ts_info;
+	u64 ts_addr, desc_addr;
+	u32 i, j, ts_size, desc_remain, ts_info_pos, desc_info_pos;
+	struct pt3_dma_desc *prev, *curr;
+
+	pr_debug("#%d build page descriptor ts_count=%d ts_size=%d desc_count=%d desc_size=%d\n",
+		dma->adap->idx, dma->ts_count, dma->ts_info[0].size, dma->desc_count, dma->desc_info[0].size);
+	desc_info_pos = ts_info_pos = 0;
+	desc_info = &dma->desc_info[desc_info_pos];
+	desc_addr   = desc_info->addr;
+	desc_remain = desc_info->size;
+	desc_info->data_pos = 0;
+	prev = NULL;
+	curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+	desc_info_pos++;
+
+	for (i = 0; i < dma->ts_count; i++) {
+		if (unlikely(ts_info_pos >= dma->ts_count)) {
+			pr_debug("#%d ts_info overflow max=%d curr=%d\n", dma->adap->idx, dma->ts_count, ts_info_pos);
+			return;
+		}
+		ts_info = &dma->ts_info[ts_info_pos];
+		ts_addr = ts_info->addr;
+		ts_size = ts_info->size;
+		ts_info_pos++;
+		pr_debug("#%d i=%d, ts_info addr=0x%llx ts_size=%d\n", dma->adap->idx, i, ts_addr, ts_size);
+		for (j = 0; j < ts_size / PT3_DMA_PAGE_SIZE; j++) {
+			if (desc_remain < sizeof(struct pt3_dma_desc)) {
+				if (unlikely(desc_info_pos >= dma->desc_count)) {
+					pr_debug("#%d desc_info overflow max=%d curr=%d\n",
+						dma->adap->idx, dma->desc_count, desc_info_pos);
+					return;
+				}
+				desc_info = &dma->desc_info[desc_info_pos];
+				desc_info->data_pos = 0;
+				curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+				pr_debug("#%d desc_info_pos=%d ts_addr=0x%llx remain=%d\n",
+					dma->adap->idx, desc_info_pos, ts_addr, desc_remain);
+				desc_addr = desc_info->addr;
+				desc_remain = desc_info->size;
+				desc_info_pos++;
+			}
+			if (prev)
+				prev->next_desc = desc_addr | 0b10;
+			curr->page_addr = ts_addr           | 0b111;
+			curr->page_size = PT3_DMA_PAGE_SIZE | 0b111;
+			curr->next_desc = 0b10;
+			pr_debug("#%d j=%d dma write desc ts_addr=0x%llx desc_info_pos=%d desc_remain=%d\n",
+				dma->adap->idx, j, ts_addr, desc_info_pos, desc_remain);
+			ts_addr += PT3_DMA_PAGE_SIZE;
+
+			prev = curr;
+			desc_info->data_pos += sizeof(struct pt3_dma_desc);
+			if (unlikely(desc_info->data_pos > desc_info->size)) {
+				pr_debug("#%d dma desc_info data overflow max=%d curr=%d\n",
+					dma->adap->idx, desc_info->size, desc_info->data_pos);
+				return;
+			}
+			curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+			desc_addr += sizeof(struct pt3_dma_desc);
+			desc_remain -= sizeof(struct pt3_dma_desc);
+		}
+	}
+	if (prev)
+		prev->next_desc = dma->desc_info->addr | 0b10;
+}
+
+struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap)
+{
+	struct pt3_dma_page *page;
+	u32 i;
+
+	struct pt3_dma *dma = kzalloc(sizeof(struct pt3_dma), GFP_KERNEL);
+	if (!dma) {
+		pr_debug("#%d fail allocate PT3_DMA\n", adap->idx);
+		goto fail;
+	}
+	dma->adap = adap;
+	dma->enabled = false;
+	mutex_init(&dma->lock);
+
+	dma->ts_count = PT3_DMA_BLOCK_COUNT;
+	dma->ts_info = kzalloc(sizeof(struct pt3_dma_page) * dma->ts_count, GFP_KERNEL);
+	if (!dma->ts_info) {
+		pr_debug("#%d fail allocate TS DMA page\n", adap->idx);
+		goto fail;
+	}
+	pr_debug("#%d Alloc TS buf (ts_count %d)\n", adap->idx, dma->ts_count);
+	for (i = 0; i < dma->ts_count; i++) {
+		page = &dma->ts_info[i];
+		page->size = PT3_DMA_BLOCK_SIZE;
+		page->data_pos = 0;
+		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
+		if (!page->data) {
+			pr_debug("#%d fail alloc_consistent. %d\n", adap->idx, i);
+			goto fail;
+		}
+	}
+
+	dma->desc_count = 1 + (PT3_DMA_TS_BUF_SIZE / PT3_DMA_PAGE_SIZE - 1) / PT3_DMA_MAX_DESCS;
+	dma->desc_info = kzalloc(sizeof(struct pt3_dma_page) * dma->desc_count, GFP_KERNEL);
+	if (!dma->desc_info) {
+		pr_debug("#%d fail allocate Desc DMA page\n", adap->idx);
+		goto fail;
+	}
+	pr_debug("#%d Alloc Descriptor buf (desc_count %d)\n", adap->idx, dma->desc_count);
+	for (i = 0; i < dma->desc_count; i++) {
+		page = &dma->desc_info[i];
+		page->size = PT3_DMA_PAGE_SIZE;
+		page->data_pos = 0;
+		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
+		if (!page->data) {
+			pr_debug("#%d fail alloc_consistent %d\n", adap->idx, i);
+			goto fail;
+		}
+	}
+
+	pr_debug("#%d build page descriptor\n", adap->idx);
+	pt3_dma_build_page_descriptor(dma);
+	return dma;
+fail:
+	if (dma)
+		pt3_dma_free(dma);
+	return NULL;
+}
+
+void pt3_dma_free(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *page;
+	u32 i;
+
+	if (dma->ts_info) {
+		for (i = 0; i < dma->ts_count; i++) {
+			page = &dma->ts_info[i];
+			if (page->data)
+				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
+		}
+		kfree(dma->ts_info);
+	}
+	if (dma->desc_info) {
+		for (i = 0; i < dma->desc_count; i++) {
+			page = &dma->desc_info[i];
+			if (page->data)
+				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
+		}
+		kfree(dma->desc_info);
+	}
+	kfree(dma);
+}
+
+void __iomem *pt3_dma_get_base_addr(struct pt3_dma *dma)
+{
+	return dma->adap->pt3->i2c->reg[0] + REG_BASE + (0x18 * dma->adap->idx);
+}
+
+void pt3_dma_reset(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *ts;
+	u32 i;
+
+	for (i = 0; i < dma->ts_count; i++) {
+		ts = &dma->ts_info[i];
+		memset(ts->data, 0, ts->size);
+		ts->data_pos = 0;
+		*ts->data = PT3_DMA_NOT_SYNC_BYTE;
+	}
+	dma->ts_pos = 0;
+}
+
+void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled)
+{
+	void __iomem *base = pt3_dma_get_base_addr(dma);
+	u64 start_addr = dma->desc_info->addr;
+
+	if (enabled) {
+		pr_debug("#%d DMA enable start_addr=%llx\n", dma->adap->idx, start_addr);
+		pt3_dma_reset(dma);
+		writel(1 << 1, base + REG_DMA_CTL);	/* stop DMA */
+		writel(PT3_SHIFT_MASK(start_addr,  0, 32), base + REG_DMA_DESC_L);
+		writel(PT3_SHIFT_MASK(start_addr, 32, 32), base + REG_DMA_DESC_H);
+		pr_debug("set descriptor address low %llx\n",  PT3_SHIFT_MASK(start_addr,  0, 32));
+		pr_debug("set descriptor address high %llx\n", PT3_SHIFT_MASK(start_addr, 32, 32));
+		writel(1 << 0, base + REG_DMA_CTL);	/* start DMA */
+	} else {
+		pr_debug("#%d DMA disable\n", dma->adap->idx);
+		writel(1 << 1, base + REG_DMA_CTL);	/* stop DMA */
+		while (1) {
+			if (!PT3_SHIFT_MASK(readl(base + REG_STATUS), 0, 1))
+				break;
+			msleep_interruptible(1);
+		}
+	}
+	dma->enabled = enabled;
+}
+
+/* convert Gray code to binary, e.g. 1001 -> 1110 */
+static u32 pt3_dma_gray2binary(u32 gray, u32 bit)
+{
+	u32 binary = 0, i, j, k;
+
+	for (i = 0; i < bit; i++) {
+		k = 0;
+		for (j = i; j < bit; j++)
+			k = k ^ PT3_SHIFT_MASK(gray, j, 1);
+		binary |= k << i;
+	}
+	return binary;
+}
+
+u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma)
+{
+	return pt3_dma_gray2binary(readl(pt3_dma_get_base_addr(dma) + REG_TS_ERR), 32);
+}
+
+void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval)
+{
+	void __iomem *base = pt3_dma_get_base_addr(dma);
+	u32 data = mode | initval;
+	pr_debug("set_test_mode base=%p data=0x%04x\n", base, data);
+	writel(data, base + REG_TS_CTL);
+}
+
+bool pt3_dma_ready(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *ts;
+	u8 *p;
+
+	u32 next = dma->ts_pos + 1;
+	if (next >= dma->ts_count)
+		next = 0;
+	ts = &dma->ts_info[next];
+	p = &ts->data[ts->data_pos];
+
+	if (*p == 0x47)
+		return true;
+	if (*p == PT3_DMA_NOT_SYNC_BYTE)
+		return false;
+
+	pr_debug("#%d invalid sync byte value=0x%02x ts_pos=%d data_pos=%d curr=0x%02x\n",
+		dma->adap->idx, *p, next, ts->data_pos, dma->ts_info[dma->ts_pos].data[0]);
+	return false;
+}
+
+ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux)
+{
+	bool ready;
+	struct pt3_dma_page *ts;
+	u32 i, prev;
+	size_t csize, remain = dma->ts_info[dma->ts_pos].size;
+
+	mutex_lock(&dma->lock);
+	pr_debug("#%d dma_copy ts_pos=0x%x data_pos=0x%x\n",
+		   dma->adap->idx, dma->ts_pos, dma->ts_info[dma->ts_pos].data_pos);
+	for (;;) {
+		for (i = 0; i < 20; i++) {
+			ready = pt3_dma_ready(dma);
+			if (ready)
+				break;
+			msleep_interruptible(30);
+		}
+		if (!ready) {
+			pr_debug("#%d dma_copy NOT READY\n", dma->adap->idx);
+			goto last;
+		}
+		prev = dma->ts_pos - 1;
+		if (prev < 0 || dma->ts_count <= prev)
+			prev = dma->ts_count - 1;
+		if (dma->ts_info[prev].data[0] != PT3_DMA_NOT_SYNC_BYTE)
+			pr_debug("#%d DMA buffer overflow. prev=%d data=0x%x\n",
+					dma->adap->idx, prev, dma->ts_info[prev].data[0]);
+		ts = &dma->ts_info[dma->ts_pos];
+		for (;;) {
+			csize = (remain < (ts->size - ts->data_pos)) ?
+				 remain : (ts->size - ts->data_pos);
+			pt3_filter(dma->adap, demux, &ts->data[ts->data_pos], csize);
+			remain -= csize;
+			ts->data_pos += csize;
+			if (ts->data_pos >= ts->size) {
+				ts->data_pos = 0;
+				ts->data[ts->data_pos] = PT3_DMA_NOT_SYNC_BYTE;
+				dma->ts_pos++;
+				if (dma->ts_pos >= dma->ts_count)
+					dma->ts_pos = 0;
+				break;
+			}
+			if (remain <= 0)
+				goto last;
+		}
+	}
+last:
+	mutex_unlock(&dma->lock);
+	return dma->ts_info[dma->ts_pos].size - remain;
+}
+
+u32 pt3_dma_get_status(struct pt3_dma *dma)
+{
+	return readl(pt3_dma_get_base_addr(dma) + REG_STATUS);
+}
+
diff --git a/drivers/media/pci/pt3_dvb/pt3_fe.c b/drivers/media/pci/pt3_dvb/pt3_fe.c
new file mode 100644
index 0000000..111a696
--- /dev/null
+++ b/drivers/media/pci/pt3_dvb/pt3_fe.c
@@ -0,0 +1,1186 @@
+#include "dvb_math.h"
+#include "pt3.h"
+
+static u8 pt3_qm_reg_rw[] = {
+	0x48, 0x1c, 0xa0, 0x10, 0xbc, 0xc5, 0x20, 0x33,
+	0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+	0x00, 0xff, 0xf3, 0x00, 0x2a, 0x64, 0xa6, 0x86,
+	0x8c, 0xcf, 0xb8, 0xf1, 0xa8, 0xf2, 0x89, 0x00,
+};
+
+void pt3_qm_init_reg_param(struct pt3_qm *qm)
+{
+	memcpy(qm->reg, pt3_qm_reg_rw, sizeof(pt3_qm_reg_rw));
+
+	qm->adap->freq = 0;
+	qm->standby = false;
+	qm->wait_time_lpf = 20;
+	qm->wait_time_search_fast = 4;
+	qm->wait_time_search_normal = 15;
+}
+
+static int pt3_qm_write(struct pt3_qm *qm, struct pt3_bus *bus, u8 addr, u8 data)
+{
+	int ret = pt3_tc_write_tuner(qm->adap, bus, addr, &data, sizeof(data));
+	qm->reg[addr] = data;
+	return ret;
+}
+
+#define PT3_QM_INIT_DUMMY_RESET 0x0c
+
+void pt3_qm_dummy_reset(struct pt3_qm *qm, struct pt3_bus *bus)
+{
+	pt3_qm_write(qm, bus, 0x01, PT3_QM_INIT_DUMMY_RESET);
+	pt3_qm_write(qm, bus, 0x01, PT3_QM_INIT_DUMMY_RESET);
+}
+
+static void pt3_qm_sleep(struct pt3_bus *bus, u32 ms)
+{
+	if (bus)
+		pt3_bus_sleep(bus, ms);
+	else
+		msleep_interruptible(ms);
+}
+
+static int pt3_qm_read(struct pt3_qm *qm, struct pt3_bus *bus, u8 addr, u8 *data)
+{
+	int ret = 0;
+	if ((addr == 0x00) || (addr == 0x0d))
+		ret = pt3_tc_read_tuner(qm->adap, bus, addr, data);
+	return ret;
+}
+
+static u8 pt3_qm_flag[32] = {
+	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
+	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+};
+
+static int pt3_qm_set_sleep_mode(struct pt3_qm *qm, struct pt3_bus *bus)
+{
+	int ret;
+
+	if (qm->standby) {
+		qm->reg[0x01] &= (~(1 << 3)) & 0xff;
+		qm->reg[0x01] |= 1 << 0;
+		qm->reg[0x05] |= 1 << 3;
+
+		ret = pt3_qm_write(qm, bus, 0x05, qm->reg[0x05]);
+		if (ret)
+			return ret;
+		ret = pt3_qm_write(qm, bus, 0x01, qm->reg[0x01]);
+		if (ret)
+			return ret;
+	} else {
+		qm->reg[0x01] |= 1 << 3;
+		qm->reg[0x01] &= (~(1 << 0)) & 0xff;
+		qm->reg[0x05] &= (~(1 << 3)) & 0xff;
+
+		ret = pt3_qm_write(qm, bus, 0x01, qm->reg[0x01]);
+		if (ret)
+			return ret;
+		ret = pt3_qm_write(qm, bus, 0x05, qm->reg[0x05]);
+		if (ret)
+			return ret;
+	}
+	return ret;
+}
+
+static int pt3_qm_set_search_mode(struct pt3_qm *qm, struct pt3_bus *bus)
+{
+	qm->reg[3] &= 0xfe;
+	return pt3_qm_write(qm, bus, 0x03, qm->reg[3]);
+}
+
+int pt3_qm_init(struct pt3_qm *qm, struct pt3_bus *bus)
+{
+	u8 i_data;
+	u32 i;
+	int ret;
+
+	/* soft reset on */
+	ret = pt3_qm_write(qm, bus, 0x01, PT3_QM_INIT_DUMMY_RESET);
+	if (ret)
+		return ret;
+
+	pt3_qm_sleep(bus, 1);
+
+	/* soft reset off */
+	i_data = qm->reg[0x01] | 0x10;
+	ret = pt3_qm_write(qm, bus, 0x01, i_data);
+	if (ret)
+		return ret;
+
+	/* ID check */
+	ret = pt3_qm_read(qm, bus, 0x00, &i_data);
+	if (ret)
+		return ret;
+
+	if ((bus == NULL) && (i_data != 0x48))
+		return -EINVAL;
+
+	/* LPF tuning on */
+	pt3_qm_sleep(bus, 1);
+	qm->reg[0x0c] |= 0x40;
+	ret = pt3_qm_write(qm, bus, 0x0c, qm->reg[0x0c]);
+	if (ret)
+		return ret;
+	pt3_qm_sleep(bus, qm->wait_time_lpf);
+
+	for (i = 0; i < sizeof(pt3_qm_flag); i++)
+		if (pt3_qm_flag[i] == 1) {
+			ret = pt3_qm_write(qm, bus, i, qm->reg[i]);
+			if (ret)
+				return ret;
+		}
+	ret = pt3_qm_set_sleep_mode(qm, bus);
+	if (ret)
+		return ret;
+	return pt3_qm_set_search_mode(qm, bus);
+}
+
+int pt3_qm_tuner_init(struct pt3_i2c *i2c, struct pt3_adapter *adap)
+{
+	int ret;
+	struct pt3_bus *bus = vzalloc(sizeof(struct pt3_bus));
+
+	if (!bus)
+		return -ENOMEM;
+	pt3_qm_init_reg_param(adap->qm);
+	pt3_qm_dummy_reset(adap->qm, bus);
+	pt3_bus_end(bus);
+	ret = pt3_i2c_run(i2c, bus, true);
+	vfree(bus);
+	if (ret) {
+		pr_debug("fail pt3_qm_tuner_init dummy reset ret=%d\n", ret);
+		return ret;
+	}
+
+	bus = vzalloc(sizeof(struct pt3_bus));
+	if (!bus)
+		return -ENOMEM;
+	ret = pt3_qm_init(adap->qm, bus);
+	if (ret) {
+		vfree(bus);
+		return ret;
+	}
+	pt3_bus_end(bus);
+	ret = pt3_i2c_run(i2c, bus, true);
+	vfree(bus);
+	if (ret) {
+		pr_debug("fail pt3_qm_tuner_init qm init ret=%d\n", ret);
+		return ret;
+	}
+	return ret;
+}
+
+int pt3_qm_set_sleep(struct pt3_qm *qm, bool sleep)
+{
+	qm->standby = sleep;
+	if (sleep) {
+		int ret = pt3_tc_set_agc_s(qm->adap, PT3_TC_AGC_MANUAL);
+		if (ret)
+			return ret;
+		pt3_qm_set_sleep_mode(qm, NULL);
+		pt3_tc_set_sleep_s(qm->adap, NULL, sleep);
+	} else {
+		pt3_tc_set_sleep_s(qm->adap, NULL, sleep);
+		pt3_qm_set_sleep_mode(qm, NULL);
+	}
+	qm->adap->sleep = sleep;
+	return 0;
+}
+
+void pt3_qm_get_channel_freq(u32 channel, u32 *number, u32 *freq)
+{
+	if (channel < 12) {
+		*number = 1 + 2 * channel;
+		*freq = 104948 + 3836 * channel;
+	} else if (channel < 24) {
+		channel -= 12;
+		*number = 2 + 2 * channel;
+		*freq = 161300 + 4000 * channel;
+	} else {
+		channel -= 24;
+		*number = 1 + 2 * channel;
+		*freq = 159300 + 4000 * channel;
+	}
+}
+
+static u32 PT3_QM_FREQ_TABLE[9][3] = {
+	{ 2151000, 1, 7 },
+	{ 1950000, 1, 6 },
+	{ 1800000, 1, 5 },
+	{ 1600000, 1, 4 },
+	{ 1450000, 1, 3 },
+	{ 1250000, 1, 2 },
+	{ 1200000, 0, 7 },
+	{  975000, 0, 6 },
+	{  950000, 0, 0 }
+};
+
+static u32 SD_TABLE[24][2][3] = {
+	{{0x38fae1, 0x0d, 0x5}, {0x39fae1, 0x0d, 0x5},},
+	{{0x3f570a, 0x0e, 0x3}, {0x00570a, 0x0e, 0x3},},
+	{{0x05b333, 0x0e, 0x5}, {0x06b333, 0x0e, 0x5},},
+	{{0x3c0f5c, 0x0f, 0x4}, {0x3d0f5c, 0x0f, 0x4},},
+	{{0x026b85, 0x0f, 0x6}, {0x036b85, 0x0f, 0x6},},
+	{{0x38c7ae, 0x10, 0x5}, {0x39c7ae, 0x10, 0x5},},
+	{{0x3f23d7, 0x11, 0x3}, {0x0023d7, 0x11, 0x3},},
+	{{0x058000, 0x11, 0x5}, {0x068000, 0x11, 0x5},},
+	{{0x3bdc28, 0x12, 0x4}, {0x3cdc28, 0x12, 0x4},},
+	{{0x023851, 0x12, 0x6}, {0x033851, 0x12, 0x6},},
+	{{0x38947a, 0x13, 0x5}, {0x39947a, 0x13, 0x5},},
+	{{0x3ef0a3, 0x14, 0x3}, {0x3ff0a3, 0x14, 0x3},},
+	{{0x3c8000, 0x16, 0x4}, {0x3d8000, 0x16, 0x4},},
+	{{0x048000, 0x16, 0x6}, {0x058000, 0x16, 0x6},},
+	{{0x3c8000, 0x17, 0x5}, {0x3d8000, 0x17, 0x5},},
+	{{0x048000, 0x18, 0x3}, {0x058000, 0x18, 0x3},},
+	{{0x3c8000, 0x18, 0x6}, {0x3d8000, 0x18, 0x6},},
+	{{0x048000, 0x19, 0x4}, {0x058000, 0x19, 0x4},},
+	{{0x3c8000, 0x1a, 0x3}, {0x3d8000, 0x1a, 0x3},},
+	{{0x048000, 0x1a, 0x5}, {0x058000, 0x1a, 0x5},},
+	{{0x3c8000, 0x1b, 0x4}, {0x3d8000, 0x1b, 0x4},},
+	{{0x048000, 0x1b, 0x6}, {0x058000, 0x1b, 0x6},},
+	{{0x3c8000, 0x1c, 0x5}, {0x3d8000, 0x1c, 0x5},},
+	{{0x048000, 0x1d, 0x3}, {0x058000, 0x1d, 0x3},},
+};
+
+static int pt3_qm_tuning(struct pt3_qm *qm, struct pt3_bus *bus, u32 *sd, u32 channel)
+{
+	int ret;
+	struct pt3_adapter *adap = qm->adap;
+	u8 i_data;
+	u32 index, i, N, A;
+
+	qm->reg[0x08] &= 0xf0;
+	qm->reg[0x08] |= 0x09;
+
+	qm->reg[0x13] &= 0x9f;
+	qm->reg[0x13] |= 0x20;
+
+	for (i = 0; i < 8; i++) {
+		if ((PT3_QM_FREQ_TABLE[i+1][0] <= adap->freq) && (adap->freq < PT3_QM_FREQ_TABLE[i][0])) {
+			i_data = qm->reg[0x02];
+			i_data &= 0x0f;
+			i_data |= PT3_QM_FREQ_TABLE[i][1] << 7;
+			i_data |= PT3_QM_FREQ_TABLE[i][2] << 4;
+			pt3_qm_write(qm, bus, 0x02, i_data);
+		}
+	}
+
+	index = pt3_tc_index(qm->adap);
+	*sd = SD_TABLE[channel][index][0];
+	N = SD_TABLE[channel][index][1];
+	A = SD_TABLE[channel][index][2];
+
+	qm->reg[0x06] &= 0x40;
+	qm->reg[0x06] |= N;
+	ret = pt3_qm_write(qm, bus, 0x06, qm->reg[0x06]);
+	if (ret)
+		return ret;
+
+	qm->reg[0x07] &= 0xf0;
+	qm->reg[0x07] |= A & 0x0f;
+	return pt3_qm_write(qm, bus, 0x07, qm->reg[0x07]);
+}
+
+static int pt3_qm_local_lpf_tuning(struct pt3_qm *qm, struct pt3_bus *bus, int lpf, u32 channel)
+{
+	u8 i_data;
+	u32 sd = 0;
+	int ret = pt3_qm_tuning(qm, bus, &sd, channel);
+
+	if (ret)
+		return ret;
+	if (lpf) {
+		i_data = qm->reg[0x08] & 0xf0;
+		i_data |= 2;
+		ret = pt3_qm_write(qm, bus, 0x08, i_data);
+	} else
+		ret = pt3_qm_write(qm, bus, 0x08, qm->reg[0x08]);
+	if (ret)
+		return ret;
+
+	qm->reg[0x09] &= 0xc0;
+	qm->reg[0x09] |= (sd >> 16) & 0x3f;
+	qm->reg[0x0a] = (sd >> 8) & 0xff;
+	qm->reg[0x0b] = (sd >> 0) & 0xff;
+	ret = pt3_qm_write(qm, bus, 0x09, qm->reg[0x09]);
+	if (ret)
+		return ret;
+	ret = pt3_qm_write(qm, bus, 0x0a, qm->reg[0x0a]);
+	if (ret)
+		return ret;
+	ret = pt3_qm_write(qm, bus, 0x0b, qm->reg[0x0b]);
+	if (ret)
+		return ret;
+
+	if (lpf) {
+		i_data = qm->reg[0x0c];
+		i_data &= 0x3f;
+		ret = pt3_qm_write(qm, bus, 0x0c, i_data);
+		if (ret)
+			return ret;
+		pt3_qm_sleep(bus, 1);
+
+		i_data = qm->reg[0x0c];
+		i_data |= 0xc0;
+		ret = pt3_qm_write(qm, bus, 0x0c, i_data);
+		if (ret)
+			return ret;
+		pt3_qm_sleep(bus, qm->wait_time_lpf);
+		ret = pt3_qm_write(qm, bus, 0x08, 0x09);
+		if (ret)
+			return ret;
+		ret = pt3_qm_write(qm, bus, 0x13, qm->reg[0x13]);
+		if (ret)
+			return ret;
+	} else {
+		ret = pt3_qm_write(qm, bus, 0x13, qm->reg[0x13]);
+		if (ret)
+			return ret;
+		i_data = qm->reg[0x0c];
+		i_data &= 0x7f;
+		ret = pt3_qm_write(qm, bus, 0x0c, i_data);
+		if (ret)
+			return ret;
+		pt3_qm_sleep(bus, 2);
+
+		i_data = qm->reg[0x0c];
+		i_data |= 0x80;
+		ret = pt3_qm_write(qm, bus, 0x0c, i_data);
+		if (ret)
+			return ret;
+		if (qm->reg[0x03] & 0x01)
+			pt3_qm_sleep(bus, qm->wait_time_search_fast);
+		else
+			pt3_qm_sleep(bus, qm->wait_time_search_normal);
+	}
+	return ret;
+}
+
+int pt3_qm_get_locked(struct pt3_qm *qm, bool *locked)
+{
+	int ret = pt3_qm_read(qm, NULL, 0x0d, &qm->reg[0x0d]);
+	if (ret)
+		return ret;
+	if (qm->reg[0x0d] & 0x40)
+		*locked = true;
+	else
+		*locked = false;
+	return ret;
+}
+
+int pt3_qm_set_frequency(struct pt3_qm *qm, u32 channel)
+{
+	u32 number, freq, freq_kHz;
+	struct timeval begin, now;
+	bool locked;
+	int ret = pt3_tc_set_agc_s(qm->adap, PT3_TC_AGC_MANUAL);
+	if (ret)
+		return ret;
+
+	pt3_qm_get_channel_freq(channel, &number, &freq);
+	freq_kHz = freq * 10;
+	if (pt3_tc_index(qm->adap) == 0)
+		freq_kHz -= 500;
+	else
+		freq_kHz += 500;
+	qm->adap->freq = freq_kHz;
+	pr_debug("#%d ch %d freq %d kHz\n", qm->adap->idx, channel, freq_kHz);
+
+	ret = pt3_qm_local_lpf_tuning(qm, NULL, 1, channel);
+	if (ret)
+		return ret;
+	do_gettimeofday(&begin);
+	while (1) {
+		do_gettimeofday(&now);
+		ret = pt3_qm_get_locked(qm, &locked);
+		if (ret)
+			return ret;
+		if (locked)
+			break;
+		if (pt3_tc_time_diff(&begin, &now) >= 100)
+			break;
+		msleep_interruptible(1);
+	}
+	pr_debug("#%d qm_get_locked %d ret=0x%x\n", qm->adap->idx, locked, ret);
+	if (!locked)
+		return -ETIMEDOUT;
+
+	ret = pt3_tc_set_agc_s(qm->adap, PT3_TC_AGC_AUTO);
+	if (!ret) {
+		qm->adap->channel = channel;
+		qm->adap->offset = 0;
+	}
+	return ret;
+}
+
+static struct {
+	u32	freq;		/* Channel center frequency @ kHz	*/
+	u32	freq_th;	/* Offset frequency threshold @ kHz	*/
+	u8	shf_val;	/* Spur shift value			*/
+	u8	shf_dir;	/* Spur shift direction			*/
+} SHF_DVBT_TAB[] = {
+	{  64500, 500, 0x92, 0x07 },
+	{ 191500, 300, 0xE2, 0x07 },
+	{ 205500, 500, 0x2C, 0x04 },
+	{ 212500, 500, 0x1E, 0x04 },
+	{ 226500, 500, 0xD4, 0x07 },
+	{  99143, 500, 0x9C, 0x07 },
+	{ 173143, 500, 0xD4, 0x07 },
+	{ 191143, 300, 0xD4, 0x07 },
+	{ 207143, 500, 0xCE, 0x07 },
+	{ 225143, 500, 0xCE, 0x07 },
+	{ 243143, 500, 0xD4, 0x07 },
+	{ 261143, 500, 0xD4, 0x07 },
+	{ 291143, 500, 0xD4, 0x07 },
+	{ 339143, 500, 0x2C, 0x04 },
+	{ 117143, 500, 0x7A, 0x07 },
+	{ 135143, 300, 0x7A, 0x07 },
+	{ 153143, 500, 0x01, 0x07 }
+};
+
+static void pt3_mx_rftune(u8 *data, u32 *size, u32 freq)
+{
+	u32 dig_rf_freq, tmp, frac_divider, kHz, MHz, i;
+	u8 rf_data[] = {
+		0x13, 0x00,	/* abort tune			*/
+		0x3B, 0xC0,
+		0x3B, 0x80,
+		0x10, 0x95,	/* BW				*/
+		0x1A, 0x05,
+		0x61, 0x00,
+		0x62, 0xA0,
+		0x11, 0x40,	/* 2 bytes to store RF freq.	*/
+		0x12, 0x0E,	/* 2 bytes to store RF freq.	*/
+		0x13, 0x01	/* start tune			*/
+	};
+
+	dig_rf_freq = 0;
+	tmp = 0;
+	frac_divider = 1000000;
+	kHz = 1000;
+	MHz = 1000000;
+
+	dig_rf_freq = freq / MHz;
+	tmp = freq % MHz;
+
+	for (i = 0; i < 6; i++) {
+		dig_rf_freq <<= 1;
+		frac_divider /= 2;
+		if (tmp > frac_divider) {
+			tmp -= frac_divider;
+			dig_rf_freq++;
+		}
+	}
+	if (tmp > 7812)
+		dig_rf_freq++;
+
+	rf_data[2 * (7) + 1] = (u8)(dig_rf_freq);
+	rf_data[2 * (8) + 1] = (u8)(dig_rf_freq >> 8);
+
+	for (i = 0; i < sizeof(SHF_DVBT_TAB)/sizeof(*SHF_DVBT_TAB); i++) {
+		if ((freq >= (SHF_DVBT_TAB[i].freq - SHF_DVBT_TAB[i].freq_th) * kHz) &&
+				(freq <= (SHF_DVBT_TAB[i].freq + SHF_DVBT_TAB[i].freq_th) * kHz)) {
+			rf_data[2 * (5) + 1] = SHF_DVBT_TAB[i].shf_val;
+			rf_data[2 * (6) + 1] = 0xa0 | SHF_DVBT_TAB[i].shf_dir;
+			break;
+		}
+	}
+	memcpy(data, rf_data, sizeof(rf_data));
+	*size = sizeof(rf_data);
+
+	pr_debug("mx_rftune freq=%d\n", freq);
+}
+
+static void pt3_mx_write(struct pt3_adapter *adap, struct pt3_bus *bus, u8 *data, size_t size)
+{
+	pt3_tc_write_tuner_without_addr(adap, bus, data, size);
+}
+
+static void pt3_mx_standby(struct pt3_adapter *adap)
+{
+	u8 data[4] = {0x01, 0x00, 0x13, 0x00};
+	pt3_mx_write(adap, NULL, data, sizeof(data));
+}
+
+static void pt3_mx_set_register(struct pt3_adapter *adap, struct pt3_bus *bus, u8 addr, u8 value)
+{
+	u8 data[2] = {addr, value};
+	pt3_mx_write(adap, bus, data, sizeof(data));
+}
+
+static void pt3_mx_idac_setting(struct pt3_adapter *adap, struct pt3_bus *bus)
+{
+	u8 data[] = {
+		0x0D, 0x00,
+		0x0C, 0x67,
+		0x6F, 0x89,
+		0x70, 0x0C,
+		0x6F, 0x8A,
+		0x70, 0x0E,
+		0x6F, 0x8B,
+		0x70, 0x10+12,
+	};
+	pt3_mx_write(adap, bus, data, sizeof(data));
+}
+
+static void pt3_mx_tuner_rftune(struct pt3_adapter *adap, struct pt3_bus *bus, u32 freq)
+{
+	u8 data[100];
+	u32 size;
+
+	size = 0;
+	adap->freq = freq;
+	pt3_mx_rftune(data, &size, freq);
+	if (size != 20) {
+		pr_debug("fail mx_rftune size = %d\n", size);
+		return;
+	}
+	pt3_mx_write(adap, bus, data, 14);
+	msleep_interruptible(1);
+	pt3_mx_write(adap, bus, data + 14, 6);
+	msleep_interruptible(1);
+	pt3_mx_set_register(adap, bus, 0x1a, 0x0d);
+	pt3_mx_idac_setting(adap, bus);
+}
+
+static void pt3_mx_wakeup(struct pt3_adapter *adap)
+{
+	u8 data[2] = {0x01, 0x01};
+
+	pt3_mx_write(adap, NULL, data, sizeof(data));
+	pt3_mx_tuner_rftune(adap, NULL, adap->freq);
+}
+
+static void pt3_mx_set_sleep_mode(struct pt3_adapter *adap, bool sleep)
+{
+	if (sleep)
+		pt3_mx_standby(adap);
+	else
+		pt3_mx_wakeup(adap);
+}
+
+int pt3_mx_set_sleep(struct pt3_adapter *adap, bool sleep)
+{
+	int ret;
+
+	if (sleep) {
+		ret = pt3_tc_set_agc_t(adap, PT3_TC_AGC_MANUAL);
+		if (ret)
+			return ret;
+		pt3_mx_set_sleep_mode(adap, sleep);
+		pt3_tc_write_sleep_time(adap, sleep);
+	} else {
+		pt3_tc_write_sleep_time(adap, sleep);
+		pt3_mx_set_sleep_mode(adap, sleep);
+	}
+	adap->sleep = sleep;
+	return 0;
+}
+
+static u8 PT3_MX_FREQ_TABLE[][3] = {
+	{   2, 0,  3 },
+	{  12, 1, 22 },
+	{  21, 0, 12 },
+	{  62, 1, 63 },
+	{ 112, 0, 62 }
+};
+
+void pt3_mx_get_channel_frequency(struct pt3_adapter *adap, u32 channel, bool *catv, u32 *number, u32 *freq)
+{
+	u32 i;
+	s32 freq_offset = 0;
+
+	if (12 <= channel)
+		freq_offset += 2;
+	if (17 <= channel)
+		freq_offset -= 2;
+	if (63 <= channel)
+		freq_offset += 2;
+	*freq = 93 + channel * 6 + freq_offset;
+
+	for (i = 0; i < sizeof(PT3_MX_FREQ_TABLE) / sizeof(*PT3_MX_FREQ_TABLE); i++) {
+		if (channel <= PT3_MX_FREQ_TABLE[i][0]) {
+			*catv = PT3_MX_FREQ_TABLE[i][1] ? true : false;
+			*number = channel + PT3_MX_FREQ_TABLE[i][2] - PT3_MX_FREQ_TABLE[i][0];
+			break;
+		}
+	}
+}
+
+static u32 RF_TABLE[112] = {
+	0x058d3f49, 0x05e8ccc9, 0x06445a49, 0x069fe7c9, 0x06fb7549,
+	0x075702c9, 0x07b29049, 0x080e1dc9, 0x0869ab49, 0x08c538c9,
+	0x0920c649, 0x097c53c9, 0x09f665c9, 0x0a51f349, 0x0aad80c9,
+	0x0b090e49, 0x0b649bc9, 0x0ba1a4c9, 0x0bfd3249, 0x0c58bfc9,
+	0x0cb44d49, 0x0d0fdac9, 0x0d6b6849, 0x0dc6f5c9, 0x0e228349,
+	0x0e7e10c9, 0x0ed99e49, 0x0f352bc9, 0x0f90b949, 0x0fec46c9,
+	0x1047d449, 0x10a361c9, 0x10feef49, 0x115a7cc9, 0x11b60a49,
+	0x121197c9, 0x126d2549, 0x12c8b2c9, 0x13244049, 0x137fcdc9,
+	0x13db5b49, 0x1436e8c9, 0x14927649, 0x14ee03c9, 0x15499149,
+	0x15a51ec9, 0x1600ac49, 0x165c39c9, 0x16b7c749, 0x171354c9,
+	0x176ee249, 0x17ca6fc9, 0x1825fd49, 0x18818ac9, 0x18dd1849,
+	0x1938a5c9, 0x19943349, 0x19efc0c9, 0x1a4b4e49, 0x1aa6dbc9,
+	0x1b026949, 0x1b5df6c9, 0x1bb98449, 0x1c339649, 0x1c8f23c9,
+	0x1ceab149, 0x1d463ec9, 0x1da1cc49, 0x1dfd59c9, 0x1e58e749,
+	0x1eb474c9, 0x1f100249, 0x1f6b8fc9, 0x1fc71d49, 0x2022aac9,
+	0x207e3849, 0x20d9c5c9, 0x21355349, 0x2190e0c9, 0x21ec6e49,
+	0x2247fbc9, 0x22a38949, 0x22ff16c9, 0x235aa449, 0x23b631c9,
+	0x2411bf49, 0x246d4cc9, 0x24c8da49, 0x252467c9, 0x257ff549,
+	0x25db82c9, 0x26371049, 0x26929dc9, 0x26ee2b49, 0x2749b8c9,
+	0x27a54649, 0x2800d3c9, 0x285c6149, 0x28b7eec9, 0x29137c49,
+	0x296f09c9, 0x29ca9749, 0x2a2624c9, 0x2a81b249, 0x2add3fc9,
+	0x2b38cd49, 0x2b945ac9, 0x2befe849, 0x2c4b75c9, 0x2ca70349,
+	0x2d0290c9, 0x2d5e1e49,
+};
+
+static void pt3_mx_read(struct pt3_adapter *adap, struct pt3_bus *bus, u8 addr, u8 *data)
+{
+	u8 write[2] = {0xfb, addr};
+
+	pt3_tc_write_tuner_without_addr(adap, bus, write, sizeof(write));
+	pt3_tc_read_tuner_without_addr(adap, bus, data);
+}
+
+static void pt3_mx_rfsynth_lock_status(struct pt3_adapter *adap, struct pt3_bus *bus, bool *locked)
+{
+	u8 data;
+
+	*locked = false;
+	pt3_mx_read(adap, bus, 0x16, &data);
+	data &= 0x0c;
+	if (data == 0x0c)
+		*locked = true;
+}
+
+static void pt3_mx_refsynth_lock_status(struct pt3_adapter *adap, struct pt3_bus *bus, bool *locked)
+{
+	u8 data;
+
+	*locked = false;
+	pt3_mx_read(adap, bus, 0x16, &data);
+	data &= 0x03;
+	if (data == 0x03)
+		*locked = true;
+}
+
+bool pt3_mx_locked(struct pt3_adapter *adap)
+{
+	bool locked1 = false, locked2 = false;
+	struct timeval begin, now;
+
+	do_gettimeofday(&begin);
+	while (1) {
+		do_gettimeofday(&now);
+		pt3_mx_rfsynth_lock_status(adap, NULL, &locked1);
+		pt3_mx_refsynth_lock_status(adap, NULL, &locked2);
+		if (locked1 && locked2)
+			break;
+		if (pt3_tc_time_diff(&begin, &now) > 1000)
+			break;
+		msleep_interruptible(1);
+	}
+	pr_debug("#%d mx locked1=%d locked2=%d\n", adap->idx, locked1, locked2);
+	return locked1 && locked2;
+}
+
+int pt3_mx_set_frequency(struct pt3_adapter *adap, u32 channel, s32 offset)
+{
+	bool catv;
+	u32 number, freq, real_freq;
+	int ret = pt3_tc_set_agc_t(adap, PT3_TC_AGC_MANUAL);
+
+	if (ret)
+		return ret;
+	pt3_mx_get_channel_frequency(adap, channel, &catv, &number, &freq);
+	pr_debug("#%d ch%d%s no%d %dHz\n", adap->idx, channel, catv ? " CATV" : "", number, freq);
+	/* real_freq = (7 * freq + 1 + offset) * 1000000.0/7.0; */
+	real_freq = RF_TABLE[channel];
+
+	pt3_mx_tuner_rftune(adap, NULL, real_freq);
+
+	return (!pt3_mx_locked(adap)) ? -ETIMEDOUT : pt3_tc_set_agc_t(adap, PT3_TC_AGC_AUTO);
+}
+
+int pt3_fe_set_freq(struct pt3_adapter *adap, u32 channel, s32 offset)
+{
+	int ret;
+
+	pr_debug("#%d %s set_freq channel=%d offset=%d\n", adap->idx, adap->str, channel, offset);
+
+	if (adap->type == SYS_ISDBS)
+		ret = pt3_qm_set_frequency(adap->qm, channel);
+	else
+		ret = pt3_mx_set_frequency(adap, channel, offset);
+	return ret;
+}
+
+int pt3_fe_tuner_sleep(struct pt3_adapter *adap, bool sleep)
+{
+	int ret;
+
+	pr_debug("#%d %p %s %s\n", adap->idx, adap, adap->str, sleep ? "Sleep" : "Wakeup");
+
+	if (adap->type == SYS_ISDBS)
+		ret = pt3_qm_set_sleep(adap->qm, sleep);
+	else
+		ret = pt3_mx_set_sleep(adap, sleep);
+	msleep_interruptible(10);
+	return ret;
+}
+
+/**** ISDB-S ****/
+
+enum pt3_fe_s_tune_state {
+	PT3S_IDLE,
+	PT3S_SET_FREQUENCY,
+	PT3S_SET_MODULATION,
+	PT3S_CHECK_MODULATION,
+	PT3S_SET_TS_ID,
+	PT3S_CHECK_TS_ID,
+	PT3S_TRACK,
+};
+
+struct pt3_fe_s_state {
+	struct pt3_adapter *adap;
+	struct dvb_frontend fe;
+	enum pt3_fe_s_tune_state tune_state;
+};
+
+static int pt3_fe_s_read_signal_strength(struct dvb_frontend *fe, u16 *cn)
+{
+	struct pt3_fe_s_state *state = fe->demodulator_priv;
+	return pt3_tc_read_cn_s(state->adap, NULL, (u32 *)cn);
+}
+
+static int pt3_fe_s_get_frontend_algo(struct dvb_frontend *fe)
+{
+	return DVBFE_ALGO_HW;
+}
+
+static void pt3_fe_s_release(struct dvb_frontend *fe)
+{
+	kfree(fe->demodulator_priv);
+}
+
+static int pt3_fe_s_init(struct dvb_frontend *fe)
+{
+	struct pt3_fe_s_state *state = fe->demodulator_priv;
+	state->tune_state = PT3S_IDLE;
+	return pt3_qm_set_sleep(state->adap->qm, false);
+}
+
+static int pt3_fe_s_sleep(struct dvb_frontend *fe)
+{
+	struct pt3_fe_s_state *state = fe->demodulator_priv;
+	return pt3_qm_set_sleep(state->adap->qm, true);
+}
+
+u32 pt3_fe_s_get_channel(u32 frequency)
+{
+	u32 freq = frequency / 10,
+	    ch0 = (freq - 104948) / 3836, diff0 = freq - (104948 + 3836 * ch0),
+	    ch1 = (freq - 161300) / 4000, diff1 = freq - (161300 + 4000 * ch1),
+	    ch2 = (freq - 159300) / 4000, diff2 = freq - (159300 + 4000 * ch2),
+	    min = diff0 < diff1 ? diff0 : diff1;
+
+	if (diff2 < min)
+		return ch2 + 24;
+	else if (min == diff1)
+		return ch1 + 12;
+	else
+		return ch0;
+}
+
+static int pt3_fe_s_read_status(struct dvb_frontend *fe, fe_status_t *status)
+{
+	struct pt3_fe_s_state *state = fe->demodulator_priv;
+
+	switch (state->tune_state) {
+	case PT3S_IDLE:
+	case PT3S_SET_FREQUENCY:
+		*status = 0;
+		return 0;
+
+	case PT3S_SET_MODULATION:
+	case PT3S_CHECK_MODULATION:
+		*status |= FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3S_SET_TS_ID:
+	case PT3S_CHECK_TS_ID:
+		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER;
+		return 0;
+
+	case PT3S_TRACK:
+		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+	}
+	BUG();
+}
+
+static int pt3_fe_s_tune(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
+{
+	struct pt3_fe_s_state *state = fe->demodulator_priv;
+	struct pt3_adapter *adap = state->adap;
+	struct tmcc_s *tmcc = &adap->qm->tmcc;
+	int i, ret,
+	    freq = state->fe.dtv_property_cache.frequency,
+	    tsid = state->fe.dtv_property_cache.stream_id,
+	    ch = (freq < 1024) ? freq : pt3_fe_s_get_channel(freq);	/* consider as channel ID if low */
+
+	if (re_tune)
+		state->tune_state = PT3S_SET_FREQUENCY;
+
+	switch (state->tune_state) {
+	case PT3S_IDLE:
+		*delay = 3 * HZ;
+		*status = 0;
+		return 0;
+
+	case PT3S_SET_FREQUENCY:
+		pr_debug("#%d freq %d tsid 0x%x ch %d\n", adap->idx, freq, tsid, ch);
+		ret = pt3_qm_set_frequency(adap->qm, ch);
+		if (ret)
+			return ret;
+		adap->channel = ch;
+		state->tune_state = PT3S_SET_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3S_SET_MODULATION:
+		for (i = 0; i < 1000; i++) {
+			ret = pt3_tc_read_tmcc_s(adap, NULL, tmcc);
+			if (!ret)
+				break;
+			msleep_interruptible(1);
+		}
+		if (ret) {
+			pr_debug("fail tc_read_tmcc_s ret=0x%x\n", ret);
+			return ret;
+		}
+		pr_debug("slots=%d,%d,%d,%d mode=%d,%d,%d,%d\n",
+				tmcc->slot[0], tmcc->slot[1], tmcc->slot[2], tmcc->slot[3],
+				tmcc->mode[0], tmcc->mode[1], tmcc->mode[2], tmcc->mode[3]);
+		state->tune_state = PT3S_CHECK_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3S_CHECK_MODULATION:
+		pr_debug("tmcc->id=0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x\n",
+				tmcc->id[0], tmcc->id[1], tmcc->id[2], tmcc->id[3],
+				tmcc->id[4], tmcc->id[5], tmcc->id[6], tmcc->id[7]);
+		for (i = 0; i < sizeof(tmcc->id)/sizeof(tmcc->id[0]); i++) {
+			pr_debug("tsid %x i %d tmcc->id %x\n", tsid, i, tmcc->id[i]);
+			if (tmcc->id[i] == tsid)
+				break;
+		}
+		if (tsid < sizeof(tmcc->id)/sizeof(tmcc->id[0]))	/* consider as slot# */
+			i = tsid;
+		if (i == sizeof(tmcc->id)/sizeof(tmcc->id[0])) {
+			pr_debug("#%d i%d tsid 0x%x not found\n", adap->idx, i, tsid);
+			return -EINVAL;
+		}
+		adap->offset = i;
+		pr_debug("#%d found tsid 0x%x on slot %d\n", adap->idx, tsid, i);
+		state->tune_state = PT3S_SET_TS_ID;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER;
+		return 0;
+
+	case PT3S_SET_TS_ID:
+		ret = pt3_tc_write_id_s(adap, NULL, (u16)tmcc->id[adap->offset]);
+		if (ret) {
+			pr_debug("fail set_tmcc_s ret=%d\n", ret);
+			return ret;
+		}
+		state->tune_state = PT3S_CHECK_TS_ID;
+		return 0;
+
+	case PT3S_CHECK_TS_ID:
+		for (i = 0; i < 1000; i++) {
+			u16 short_id;
+			ret = pt3_tc_read_id_s(adap, NULL, &short_id);
+			if (ret) {
+				pr_debug("fail get_id_s ret=%d\n", ret);
+				return ret;
+			}
+			tsid = short_id;
+			pr_debug("#%d tsid=0x%x\n", adap->idx, tsid);
+			if ((tsid & 0xffff) == tmcc->id[adap->offset])
+				break;
+			msleep_interruptible(1);
+		}
+		state->tune_state = PT3S_TRACK;
+
+	case PT3S_TRACK:
+		*delay = 3 * HZ;
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+	}
+	BUG();
+}
+
+static struct dvb_frontend_ops pt3_fe_s_ops = {
+	.delsys = { SYS_ISDBS },
+	.info = {
+		.name = "PT3 ISDB-S",
+		.frequency_min = 1,
+		.frequency_max = 2150000,
+		.frequency_stepsize = 1000,
+		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO | FE_CAN_MULTISTREAM |
+			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
+	},
+	.read_signal_strength = pt3_fe_s_read_signal_strength,
+	.read_status = pt3_fe_s_read_status,
+	.get_frontend_algo = pt3_fe_s_get_frontend_algo,
+	.release = pt3_fe_s_release,
+	.init = pt3_fe_s_init,
+	.sleep = pt3_fe_s_sleep,
+	.tune = pt3_fe_s_tune,
+};
+
+struct dvb_frontend *pt3_fe_s_attach(struct pt3_adapter *adap)
+{
+	struct dvb_frontend *fe;
+	struct pt3_fe_s_state *state = kzalloc(sizeof(struct pt3_fe_s_state), GFP_KERNEL);
+
+	if (!state)
+		return NULL;
+	state->adap = adap;
+	fe = &state->fe;
+	memcpy(&fe->ops, &pt3_fe_s_ops, sizeof(struct dvb_frontend_ops));
+	fe->demodulator_priv = state;
+	return fe;
+}
+
+/**** ISDB-T ****/
+
+enum pt3_fe_t_tune_state {
+	PT3T_IDLE,
+	PT3T_SET_FREQUENCY,
+	PT3T_CHECK_FREQUENCY,
+	PT3T_SET_MODULATION,
+	PT3T_CHECK_MODULATION,
+	PT3T_TRACK,
+	PT3T_ABORT,
+};
+
+struct pt3_fe_t_state {
+	struct pt3_adapter *adap;
+	struct dvb_frontend fe;
+	enum pt3_fe_t_tune_state tune_state;
+};
+
+static int pt3_fe_t_read_signal_strength(struct dvb_frontend *fe, u16 *cn)
+{
+	struct pt3_fe_t_state *state = fe->demodulator_priv;
+	return pt3_tc_read_cndat_t(state->adap, NULL, (u32 *)cn);
+}
+
+static int pt3_fe_t_get_frontend_algo(struct dvb_frontend *fe)
+{
+	return DVBFE_ALGO_HW;
+}
+
+static void pt3_fe_t_release(struct dvb_frontend *fe)
+{
+	kfree(fe->demodulator_priv);
+}
+
+static int pt3_fe_t_init(struct dvb_frontend *fe)
+{
+	struct pt3_fe_t_state *state = fe->demodulator_priv;
+	state->tune_state = PT3T_IDLE;
+	return pt3_mx_set_sleep(state->adap, false);
+}
+
+static int pt3_fe_t_sleep(struct dvb_frontend *fe)
+{
+	struct pt3_fe_t_state *state = fe->demodulator_priv;
+	return pt3_mx_set_sleep(state->adap, true);
+}
+
+static int pt3_fe_t_get_tmcc(struct pt3_adapter *adap, struct tmcc_t *tmcc)
+{
+	int b = 0, retryov, tmunvld, fulock;
+
+	if (unlikely(!tmcc))
+		return -EINVAL;
+	while (1) {
+		pt3_tc_read_retryov_tmunvld_fulock(adap, NULL, &retryov, &tmunvld, &fulock);
+		if (!fulock) {
+			b = 1;
+			break;
+		} else {
+			if (retryov)
+				break;
+		}
+		msleep_interruptible(1);
+	}
+	if (likely(b))
+		pt3_tc_read_tmcc_t(adap, NULL, tmcc);
+	return b ? 0 : -EBADMSG;
+}
+
+static int pt3_fe_t_read_status(struct dvb_frontend *fe, fe_status_t *status)
+{
+	struct pt3_fe_t_state *state = fe->demodulator_priv;
+
+	switch (state->tune_state) {
+	case PT3T_IDLE:
+	case PT3T_SET_FREQUENCY:
+	case PT3T_CHECK_FREQUENCY:
+		*status = 0;
+		return 0;
+
+	case PT3T_SET_MODULATION:
+	case PT3T_CHECK_MODULATION:
+	case PT3T_ABORT:
+		*status |= FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3T_TRACK:
+		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+	}
+	BUG();
+}
+
+#define NHK (RF_TABLE[77])
+int pt3_fe_t_freq(int freq)
+{
+	if (freq >= 90000000)
+		return freq;				/* real_freq	*/
+	if (freq > 255)
+		return NHK;
+	if (freq > 127)
+		return RF_TABLE[freq - 128];		/* freqno (IO#)	*/
+	if (freq > 63) {				/* CATV		*/
+		freq -= 64;
+		if (freq > 22)
+			return RF_TABLE[freq - 1];	/* C23-C62	*/
+		if (freq > 12)
+			return RF_TABLE[freq - 10];	/* C13-C22	*/
+		return NHK;
+	}
+	if (freq > 62)
+		return NHK;
+	if (freq > 12)
+		return RF_TABLE[freq + 50];		/* 13-62	*/
+	if (freq >  3)
+		return RF_TABLE[freq +  9];		/*  4-12	*/
+	if (freq)
+		return RF_TABLE[freq -  1];		/*  1-3		*/
+	return NHK;
+}
+
+static int pt3_fe_t_tune(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
+{
+	struct pt3_fe_t_state *state = fe->demodulator_priv;
+	struct tmcc_t tmcc_t;
+	int ret, i;
+
+	if (re_tune)
+		state->tune_state = PT3T_SET_FREQUENCY;
+
+	switch (state->tune_state) {
+	case PT3T_IDLE:
+		*delay = 3 * HZ;
+		*status = 0;
+		return 0;
+
+	case PT3T_SET_FREQUENCY:
+		ret = pt3_tc_set_agc_t(state->adap, PT3_TC_AGC_MANUAL);
+		if (ret)
+			return ret;
+		pt3_mx_tuner_rftune(state->adap, NULL, pt3_fe_t_freq(state->fe.dtv_property_cache.frequency));
+		state->tune_state = PT3T_CHECK_FREQUENCY;
+		*delay = 0;
+		*status = 0;
+		return 0;
+
+	case PT3T_CHECK_FREQUENCY:
+		if (!pt3_mx_locked(state->adap)) {
+			*delay = HZ;
+			*status = 0;
+			return 0;
+		}
+		state->tune_state = PT3T_SET_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3T_SET_MODULATION:
+		ret = pt3_tc_set_agc_t(state->adap, PT3_TC_AGC_AUTO);
+		if (ret)
+			return ret;
+		state->tune_state = PT3T_CHECK_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3T_CHECK_MODULATION:
+		for (i = 0; i < 1000; i++) {
+			ret = pt3_fe_t_get_tmcc(state->adap, &tmcc_t);
+			if (!ret)
+				break;
+			msleep_interruptible(2);
+		}
+		if (ret) {
+			pr_debug("#%d fail get_tmcc_t ret=%d\n", state->adap->idx, ret);
+				state->tune_state = PT3T_ABORT;
+				*delay = HZ;
+				return 0;
+		}
+		state->tune_state = PT3T_TRACK;
+
+	case PT3T_TRACK:
+		*delay = 3 * HZ;
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+
+	case PT3T_ABORT:
+		*delay = 3 * HZ;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+	}
+	BUG();
+}
+
+static struct dvb_frontend_ops pt3_fe_t_ops = {
+	.delsys = { SYS_ISDBT },
+	.info = {
+		.name = "PT3 ISDB-T",
+		.frequency_min = 1,
+		.frequency_max = 770000000,
+		.frequency_stepsize = 142857,
+		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO |
+			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
+	},
+	.read_signal_strength = pt3_fe_t_read_signal_strength,
+	.read_status = pt3_fe_t_read_status,
+	.get_frontend_algo = pt3_fe_t_get_frontend_algo,
+	.release = pt3_fe_t_release,
+	.init = pt3_fe_t_init,
+	.sleep = pt3_fe_t_sleep,
+	.tune = pt3_fe_t_tune,
+};
+
+struct dvb_frontend *pt3_fe_t_attach(struct pt3_adapter *adap)
+{
+	struct dvb_frontend *fe;
+	struct pt3_fe_t_state *state = kzalloc(sizeof(struct pt3_fe_t_state), GFP_KERNEL);
+	if (!state)
+		return NULL;
+	state->adap = adap;
+	fe = &state->fe;
+	memcpy(&fe->ops, &pt3_fe_t_ops, sizeof(struct dvb_frontend_ops));
+	fe->demodulator_priv = state;
+	return fe;
+}
+
diff --git a/drivers/media/pci/pt3_dvb/pt3_i2c.c b/drivers/media/pci/pt3_dvb/pt3_i2c.c
new file mode 100644
index 0000000..d320b70
--- /dev/null
+++ b/drivers/media/pci/pt3_dvb/pt3_i2c.c
@@ -0,0 +1,64 @@
+#include "pt3.h"
+
+#define PT3_I2C_DATA_OFFSET 2048
+
+bool pt3_i2c_is_clean(struct pt3_i2c *i2c)
+{
+	return PT3_SHIFT_MASK(readl(i2c->reg[0] + REG_I2C_R), 3, 1);
+}
+
+void pt3_i2c_reset(struct pt3_i2c *i2c)
+{
+	writel(1 << 17, i2c->reg[0] + REG_I2C_W);	/* 0x00020000 */
+}
+
+static void pt3_i2c_wait(struct pt3_i2c *i2c, u32 *data)
+{
+	u32 val;
+
+	while (1) {
+		val = readl(i2c->reg[0] + REG_I2C_R);
+		if (!PT3_SHIFT_MASK(val, 0, 1))
+			break;
+		msleep_interruptible(1);
+	}
+	if (data)
+		*data = val;
+}
+
+void pt3_i2c_copy(struct pt3_i2c *i2c, struct pt3_bus *bus)
+{
+	u32 i;
+	u8 *src = &bus->cmds[0];
+	void __iomem *dst = i2c->reg[1] + PT3_I2C_DATA_OFFSET + (bus->cmd_addr / 2);
+
+	for (i = 0; i < bus->cmd_pos; i++)
+		writeb(src[i], dst + i);
+}
+
+int pt3_i2c_run(struct pt3_i2c *i2c, struct pt3_bus *bus, bool copy)
+{
+	int ret = 0;
+	u32 data, a, i, start_addr = bus->cmd_addr;
+
+	mutex_lock(&i2c->lock);
+	if (copy)
+		pt3_i2c_copy(i2c, bus);
+
+	pt3_i2c_wait(i2c, &data);
+	if (unlikely(start_addr >= (1 << 13)))
+		pr_debug("start address is over.\n");
+	writel(1 << 16 | start_addr, i2c->reg[0] + REG_I2C_W);
+	pt3_i2c_wait(i2c, &data);
+
+	a = PT3_SHIFT_MASK(data, 1, 2);
+	if (a) {
+		pr_debug("fail i2c run_code ret=0x%x\n", data);
+		ret = -EIO;
+	}
+	for (i = 0; i < bus->read_addr; i++)
+		pt3_bus_push_read_data(bus, readb(i2c->reg[1] + PT3_I2C_DATA_OFFSET + i));
+	mutex_unlock(&i2c->lock);
+	return ret;
+}
+
diff --git a/drivers/media/pci/pt3_dvb/pt3_tc.c b/drivers/media/pci/pt3_dvb/pt3_tc.c
new file mode 100644
index 0000000..a5fddd9
--- /dev/null
+++ b/drivers/media/pci/pt3_dvb/pt3_tc.c
@@ -0,0 +1,458 @@
+#include "pt3.h"
+
+int pt3_tc_write(struct pt3_adapter *adap, struct pt3_bus *bus, u8 addr, const u8 *data, u32 size)
+{
+	int ret = 0;
+	u8 buf;
+	struct pt3_bus *p = bus ? bus : vzalloc(sizeof(struct pt3_bus));
+
+	if (!p) {
+		pr_debug("out of memory.\n");
+		return -ENOMEM;
+	}
+
+	pt3_bus_start(p);
+	buf = adap->addr_tc << 1;
+	pt3_bus_write(p, &buf, 1);
+	pt3_bus_write(p, &addr, 1);
+	pt3_bus_write(p, data, size);
+	pt3_bus_stop(p);
+
+	if (!bus) {
+		pt3_bus_end(p);
+		ret = pt3_i2c_run(adap->pt3->i2c, p, true);
+		vfree(p);
+	}
+	return ret;
+}
+
+static int pt3_tc_write_pskmsrst(struct pt3_adapter *adap)
+{
+	u8 buf = 0x01;
+	return pt3_tc_write(adap, NULL, 0x03, &buf, 1);
+}
+
+static int pt3_tc_write_imsrst(struct pt3_adapter *adap)
+{
+	u8 buf = 0x01 << 6;
+	return pt3_tc_write(adap, NULL, 0x01, &buf, 1);
+}
+
+int pt3_tc_init(struct pt3_adapter *adap)
+{
+	u8 buf = 0x10;
+
+	pr_debug("#%d %s tuner=0x%x tc=0x%x\n", adap->idx, adap->str, adap->addr_tuner, adap->addr_tc);
+	if (adap->type == SYS_ISDBS) {
+		int ret = pt3_tc_write_pskmsrst(adap);
+		return ret ? ret : pt3_tc_write(adap, NULL, 0x1e, &buf, 1);
+	} else {
+		int ret = pt3_tc_write_imsrst(adap);
+		return ret ? ret : pt3_tc_write(adap, NULL, 0x1c, &buf, 1);
+	}
+}
+
+int pt3_tc_set_powers(struct pt3_adapter *adap, struct pt3_bus *bus, bool tuner, bool amp)
+{
+	u8	tuner_power = tuner ? 0x03 : 0x02,
+		amp_power = amp ? 0x03 : 0x02,
+		data = (tuner_power << 6) | (0x01 << 4) | (amp_power << 2) | 0x01 << 0;
+	pr_debug("#%d tuner %s amp %s\n", adap->idx, tuner ? "ON" : "OFF", amp ? "ON" : "OFF");
+	return pt3_tc_write(adap, bus, 0x1e, &data, 1);
+}
+
+int pt3_tc_set_ts_pins_mode(struct pt3_adapter *adap, struct pt3_ts_pins_mode *mode)
+{
+	u32	clock_data = mode->clock_data,
+		byte = mode->byte,
+		valid = mode->valid;
+
+	if (clock_data)
+		clock_data++;
+	if (byte)
+		byte++;
+	if (valid)
+		valid++;
+	if (adap->type == SYS_ISDBS) {
+		u8 data[2];
+		int ret;
+		data[0] = 0x15 | (valid << 6);
+		data[1] = 0x04 | (clock_data << 4) | byte;
+		return (ret = pt3_tc_write(adap, NULL, 0x1c, &data[0], 1)) ?
+			ret : pt3_tc_write(adap, NULL, 0x1f, &data[1], 1);
+	} else {
+		u8 data = (u8)(0x01 | (clock_data << 6) | (byte << 4) | (valid << 2));
+		return pt3_tc_write(adap, NULL, 0x1d, &data, 1);
+	}
+}
+
+#define PT3_TC_THROUGH 0xfe
+int pt3_tc_write_tuner(struct pt3_adapter *adap, struct pt3_bus *bus, u8 addr, const u8 *data, u32 size)
+{
+	int ret = 0;
+	u8 buf;
+	struct pt3_bus *p = bus ? bus : vzalloc(sizeof(struct pt3_bus));
+
+	if (!p) {
+		pr_debug("out of memory.\n");
+		return -ENOMEM;
+	}
+
+	pt3_bus_start(p);
+	buf = adap->addr_tc << 1;
+	pt3_bus_write(p, &buf, 1);
+	buf = PT3_TC_THROUGH;
+	pt3_bus_write(p, &buf, 1);
+	buf = adap->addr_tuner << 1;
+	pt3_bus_write(p, &buf, 1);
+	pt3_bus_write(p, &addr, 1);
+	pt3_bus_write(p, data, size);
+	pt3_bus_stop(p);
+
+	if (!bus) {
+		pt3_bus_end(p);
+		ret = pt3_i2c_run(adap->pt3->i2c, p, true);
+		vfree(p);
+	}
+	return ret;
+}
+
+int pt3_tc_read_tuner(struct pt3_adapter *adap, struct pt3_bus *bus, u8 addr, u8 *data)
+{
+	int ret = 0;
+	u8 buf;
+	size_t rindex;
+
+	struct pt3_bus *p = bus ? bus : vzalloc(sizeof(struct pt3_bus));
+	if (!p) {
+		pr_debug("#%d tc_read_tuner out of memory\n", adap->idx);
+		return -ENOMEM;
+	}
+
+	pt3_bus_start(p);
+	buf = adap->addr_tc << 1;
+	pt3_bus_write(p, &buf, 1);
+	buf = PT3_TC_THROUGH;
+	pt3_bus_write(p, &buf, 1);
+	buf = adap->addr_tuner << 1;
+	pt3_bus_write(p, &buf, 1);
+	pt3_bus_write(p, &addr, 1);
+
+	pt3_bus_start(p);
+	buf = adap->addr_tc << 1;
+	pt3_bus_write(p, &buf, 1);
+	buf = PT3_TC_THROUGH;
+	pt3_bus_write(p, &buf, 1);
+	buf = (adap->addr_tuner << 1) | 1;
+	pt3_bus_write(p, &buf, 1);
+
+	pt3_bus_start(p);
+	buf = (adap->addr_tc << 1) | 1;
+	pt3_bus_write(p, &buf, 1);
+	rindex = pt3_bus_read(p, &buf, 1);
+	pt3_bus_stop(p);
+
+	if (!bus) {
+		pt3_bus_end(p);
+		ret = pt3_i2c_run(adap->pt3->i2c, p, true);
+		data[0] = pt3_bus_data1(p, rindex);
+		vfree(p);
+	}
+	pr_debug("#%d read_tuner addr_tc=0x%x addr_tuner=0x%x\n",
+		   adap->idx, adap->addr_tc, adap->addr_tuner);
+	return ret;
+}
+
+static u8 agc_data_s[2] = { 0xb0, 0x30 };
+
+u32 pt3_tc_index(struct pt3_adapter *adap)
+{
+	return PT3_SHIFT_MASK(adap->addr_tc, 1, 1);
+}
+
+int pt3_tc_set_agc_s(struct pt3_adapter *adap, enum pt3_tc_agc agc)
+{
+	u8 data = (agc == PT3_TC_AGC_AUTO) ? 0xff : 0x00;
+	int ret = pt3_tc_write(adap, NULL, 0x0a, &data, 1);
+	if (ret)
+		return ret;
+
+	data = agc_data_s[pt3_tc_index(adap)];
+	data |= (agc == PT3_TC_AGC_AUTO) ? 0x01 : 0x00;
+	ret = pt3_tc_write(adap, NULL, 0x10, &data, 1);
+	if (ret)
+		return ret;
+
+	data = (agc == PT3_TC_AGC_AUTO) ? 0x40 : 0x00;
+	return (ret = pt3_tc_write(adap, NULL, 0x11, &data, 1)) ? ret : pt3_tc_write_pskmsrst(adap);
+}
+
+int pt3_tc_set_sleep_s(struct pt3_adapter *adap, struct pt3_bus *bus, bool sleep)
+{
+	u8 buf = sleep ? 1 : 0;
+	return pt3_tc_write(adap, bus, 0x17, &buf, 1);
+}
+
+int pt3_tc_set_agc_t(struct pt3_adapter *adap, enum pt3_tc_agc agc)
+{
+	u8 data = (agc == PT3_TC_AGC_AUTO) ? 0x40 : 0x00;
+	int ret = pt3_tc_write(adap, NULL, 0x25, &data, 1);
+	if (ret)
+		return ret;
+
+	data = 0x4c | ((agc == PT3_TC_AGC_AUTO) ? 0x00 : 0x01);
+	return (ret = pt3_tc_write(adap, NULL, 0x23, &data, 1)) ? ret : pt3_tc_write_imsrst(adap);
+}
+
+int pt3_tc_write_tuner_without_addr(struct pt3_adapter *adap, struct pt3_bus *bus, const u8 *data, u32 size)
+{
+	int ret = 0;
+	u8 buf;
+	struct pt3_bus *p = bus ? bus : vzalloc(sizeof(struct pt3_bus));
+	if (!p) {
+		pr_debug("out of memory.\n");
+		return -ENOMEM;
+	}
+
+	pt3_bus_start(p);
+	buf = adap->addr_tc << 1;
+	pt3_bus_write(p, &buf, 1);
+	buf = PT3_TC_THROUGH;
+	pt3_bus_write(p, &buf, 1);
+	buf = adap->addr_tuner << 1;
+	pt3_bus_write(p, &buf, 1);
+	pt3_bus_write(p, data, size);
+	pt3_bus_stop(p);
+
+	if (!bus) {
+		pt3_bus_end(p);
+		ret = pt3_i2c_run(adap->pt3->i2c, p, true);
+		vfree(p);
+	}
+	return ret;
+}
+
+int pt3_tc_write_sleep_time(struct pt3_adapter *adap, int sleep)
+{
+	u8 data = (1 << 7) | ((sleep ? 1 : 0) << 4);
+	return pt3_tc_write(adap, NULL, 0x03, &data, 1);
+}
+
+u32 pt3_tc_time_diff(struct timeval *st, struct timeval *et)
+{
+	u32 diff = ((et->tv_sec - st->tv_sec) * 1000000 + (et->tv_usec - st->tv_usec)) / 1000;
+	pr_debug("time diff = %d\n", diff);
+	return diff;
+}
+
+int pt3_tc_read_tuner_without_addr(struct pt3_adapter *adap, struct pt3_bus *bus, u8 *data)
+{
+	int ret = 0;
+	u8 buf;
+	u32 rindex;
+	struct pt3_bus *p = bus ? bus : vzalloc(sizeof(struct pt3_bus));
+
+	if (!p) {
+		pr_debug("out of memory.\n");
+		return -ENOMEM;
+	}
+
+	pt3_bus_start(p);
+	buf = adap->addr_tc << 1;
+	pt3_bus_write(p, &buf, 1);
+	buf = PT3_TC_THROUGH;
+	pt3_bus_write(p, &buf, 1);
+	buf = (adap->addr_tuner << 1) | 0x01;
+	pt3_bus_write(p, &buf, 1);
+
+	pt3_bus_start(p);
+	buf = (adap->addr_tc << 1) | 0x01;
+	pt3_bus_write(p, &buf, 1);
+	rindex = pt3_bus_read(p, &buf, 1);
+	pt3_bus_stop(p);
+
+	if (!bus) {
+		pt3_bus_end(p);
+		ret = pt3_i2c_run(adap->pt3->i2c, p, true);
+		data[0] = pt3_bus_data1(p, rindex);
+		vfree(p);
+	}
+	pr_debug("#%d read_tuner_without addr_tc=0x%x addr_tuner=0x%x\n",
+		adap->idx, adap->addr_tc, adap->addr_tuner);
+	return ret;
+}
+
+static int pt3_tc_read(struct pt3_adapter *adap, struct pt3_bus *bus, u8 addr, u8 *data, u32 size)
+{
+	int ret = 0;
+	u8 buf[size];
+	u32 i, rindex;
+	struct pt3_bus *p = bus ? bus : vzalloc(sizeof(struct pt3_bus));
+	if (!p) {
+		pr_debug("out of memory.\n");
+		return -ENOMEM;
+	}
+
+	pt3_bus_start(p);
+	buf[0] = adap->addr_tc << 1;
+	pt3_bus_write(p, &buf[0], 1);
+	pt3_bus_write(p, &addr, 1);
+
+	pt3_bus_start(p);
+	buf[0] = adap->addr_tc << 1 | 1;
+	pt3_bus_write(p, &buf[0], 1);
+	rindex = pt3_bus_read(p, &buf[0], size);
+	pt3_bus_stop(p);
+
+	if (!bus) {
+		pt3_bus_end(p);
+		ret = pt3_i2c_run(adap->pt3->i2c, p, true);
+		for (i = 0; i < size; i++)
+			data[i] = pt3_bus_data1(p, rindex + i);
+		vfree(p);
+	}
+	return ret;
+}
+
+static u32 pt3_tc_byten(const u8 *data, u32 n)
+{
+	u32 i, value = 0;
+
+	for (i = 0; i < n; i++) {
+		value <<= 8;
+		value |= data[i];
+	}
+	return value;
+}
+
+int pt3_tc_read_cn_s(struct pt3_adapter *adap, struct pt3_bus *bus, u32 *cn)
+{
+	u8 data[2];
+	int ret = pt3_tc_read(adap, bus, 0xbc, data, sizeof(data));
+	if (!ret)
+		*cn = pt3_tc_byten(data, 2);
+	return ret;
+}
+
+int pt3_tc_read_cndat_t(struct pt3_adapter *adap, struct pt3_bus *bus, u32 *cn)
+{
+	u8 data[3];
+	int ret = pt3_tc_read(adap, bus, 0x8b, data, sizeof(data));
+	if (!ret)
+		*cn = pt3_tc_byten(data, 3);
+	return ret;
+}
+
+int pt3_tc_read_retryov_tmunvld_fulock(struct pt3_adapter *adap, struct pt3_bus *bus, int *retryov, int *tmunvld, int *fulock)
+{
+	u8 data;
+	int ret = pt3_tc_read(adap, bus, 0x80, &data, 1);
+	if (!ret) {
+		*retryov = PT3_SHIFT_MASK(data, 7, 1) ? 1 : 0;
+		*tmunvld = PT3_SHIFT_MASK(data, 5, 1) ? 1 : 0;
+		*fulock  = PT3_SHIFT_MASK(data, 3, 1) ? 1 : 0;
+	}
+	return ret;
+}
+
+int pt3_tc_read_tmcc_t(struct pt3_adapter *adap, struct pt3_bus *bus, struct tmcc_t *tmcc)
+{
+	u32 interleave0h, interleave0l, segment1h, segment1l;
+	u8 data[8];
+
+	int ret = pt3_tc_read(adap, bus, 0xb2+0, &data[0], 4);
+	if (ret)
+		return ret;
+	ret = pt3_tc_read(adap, bus, 0xb2+4, &data[4], 4);
+	if (ret)
+		return ret;
+
+	tmcc->system    = PT3_SHIFT_MASK(data[0], 6, 2);
+	tmcc->indicator = PT3_SHIFT_MASK(data[0], 2, 4);
+	tmcc->emergency = PT3_SHIFT_MASK(data[0], 1, 1);
+	tmcc->partial   = PT3_SHIFT_MASK(data[0], 0, 1);
+
+	tmcc->mode[0] = PT3_SHIFT_MASK(data[1], 5, 3);
+	tmcc->mode[1] = PT3_SHIFT_MASK(data[2], 0, 3);
+	tmcc->mode[2] = PT3_SHIFT_MASK(data[4], 3, 3);
+
+	tmcc->rate[0] = PT3_SHIFT_MASK(data[1], 2, 3);
+	tmcc->rate[1] = PT3_SHIFT_MASK(data[3], 5, 3);
+	tmcc->rate[2] = PT3_SHIFT_MASK(data[4], 0, 3);
+
+	interleave0h = PT3_SHIFT_MASK(data[1], 0, 2);
+	interleave0l = PT3_SHIFT_MASK(data[2], 7, 1);
+
+	tmcc->interleave[0] = interleave0h << 1 | interleave0l << 0;
+	tmcc->interleave[1] = PT3_SHIFT_MASK(data[3], 2, 3);
+	tmcc->interleave[2] = PT3_SHIFT_MASK(data[5], 5, 3);
+
+	segment1h = PT3_SHIFT_MASK(data[3], 0, 2);
+	segment1l = PT3_SHIFT_MASK(data[4], 6, 2);
+
+	tmcc->segment[0] = PT3_SHIFT_MASK(data[2], 3, 4);
+	tmcc->segment[1] = segment1h << 2 | segment1l << 0;
+	tmcc->segment[2] = PT3_SHIFT_MASK(data[5], 1, 4);
+
+	return ret;
+}
+
+int pt3_tc_read_tmcc_s(struct pt3_adapter *adap, struct pt3_bus *bus, struct tmcc_s *tmcc)
+{
+	enum {
+		BASE = 0xc5,
+		SIZE = 0xe5 - BASE + 1
+	};
+	u8 data[SIZE];
+	u32 i, byte_offset, bit_offset;
+
+	int ret = pt3_tc_read(adap, bus, 0xc3, data, 1);
+	if (ret)
+		return ret;
+	if (PT3_SHIFT_MASK(data[0], 4, 1))
+		return -EBADMSG;
+
+	ret = pt3_tc_read(adap, bus, 0xce, data, 2);
+	if (ret)
+		return ret;
+	if (pt3_tc_byten(data, 2) == 0)
+		return -EBADMSG;
+
+	ret = pt3_tc_read(adap, bus, 0xc3, data, 1);
+	if (ret)
+		return ret;
+	tmcc->emergency = PT3_SHIFT_MASK(data[0], 2, 1);
+	tmcc->extflag   = PT3_SHIFT_MASK(data[0], 1, 1);
+
+	ret = pt3_tc_read(adap, bus, 0xc5, data, SIZE);
+	if (ret)
+		return ret;
+	tmcc->indicator = PT3_SHIFT_MASK(data[0xc5 - BASE], 3, 5);
+	tmcc->uplink    = PT3_SHIFT_MASK(data[0xc7 - BASE], 0, 4);
+
+	for (i = 0; i < 4; i++) {
+		byte_offset = i / 2;
+		bit_offset = (i % 2) ? 0 : 4;
+		tmcc->mode[i] = PT3_SHIFT_MASK(data[0xc8 + byte_offset - BASE], bit_offset, 4);
+		tmcc->slot[i] = PT3_SHIFT_MASK(data[0xca + i - BASE], 0, 6);
+	}
+	for (i = 0; i < 8; i++)
+		tmcc->id[i] = pt3_tc_byten(data + 0xce + i * 2 - BASE, 2);
+	return ret;
+}
+
+int pt3_tc_write_id_s(struct pt3_adapter *adap, struct pt3_bus *bus, u16 id)
+{
+	u8 data[2] = { id >> 8, (u8)id };
+	return pt3_tc_write(adap, bus, 0x8f, data, sizeof(data));
+}
+
+int pt3_tc_read_id_s(struct pt3_adapter *adap, struct pt3_bus *bus, u16 *id)
+{
+	u8 data[2];
+	int ret = pt3_tc_read(adap, bus, 0xe6, data, sizeof(data));
+	if (!ret)
+		*id = pt3_tc_byten(data, 2);
+	return ret;
+}
+

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH] Full DVB driver package for Earthsoft PT3 (ISDB-S/T) cards
@ 2013-11-05 15:43 буди Романто
       [not found] ` <CAOcJUbxCjEWk47MkJP15QBAuGd3ePYS3ZRMduqdMCrVT362-8Q@mail.gmail.com>
  0 siblings, 1 reply; 21+ messages in thread
From: буди Романто @ 2013-11-05 15:43 UTC (permalink / raw)
  To: linux-media
  Cc: hdegoede, hverkuil, laurent.pinchart, mkrufky,
	sylvester.nawrocki, g.liakhovetski, peter.senna,
	Буди
	Романто

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain, Size: 81794 bytes --]

From: Буди Романто <knightrider@are.ma>

A DVB driver for Earthsoft PT3 (ISDB-S/T) receiver PCI Express cards, based on
1. PT3 chardev driver
	https://github.com/knight-rider/ptx/tree/master/pt3_drv
	https://github.com/m-tsudo/pt3
2. PT1/PT2 DVB driver
	./drivers/media/pci/pt1

It behaves similarly as PT1 DVB, plus some tuning enhancements:
1. in addition to the real frequency:
	ISDB-S : freq. channel ID
	ISDB-T : freq# (I/O# +128), ch#, ch# +64 for CATV
2. in addition to TSID:
	ISDB-S : slot#

As requested, the following features have been removed:
- DKMS & standalone compile
- verbosity (debug levels), use single level -DDEBUG instead
- FE: SNR (.read_snr), use CNR (.read_signal_strength) instead

The full package (buildable as standalone, DKMS or mainstream embedded module) is available at
https://github.com/knight-rider/ptx/tree/master/pt3_dvb

Mauro Carvalho Chehab asked to put tuner code as an I2C driver, under drivers/media/tuners, frontends at drivers/media/dvb-
However, to keep package integrity & compatibility with PT1/PT2 user apps, FE etc. are still placed in the same directory.

--------------------------------------------------------------------

diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
index 53196f1..2c90c34 100644
--- a/drivers/media/pci/Kconfig
+++ b/drivers/media/pci/Kconfig
@@ -30,7 +30,6 @@ source "drivers/media/pci/cx88/Kconfig"
 source "drivers/media/pci/bt8xx/Kconfig"
 source "drivers/media/pci/saa7134/Kconfig"
 source "drivers/media/pci/saa7164/Kconfig"
-
 endif
 
 if MEDIA_DIGITAL_TV_SUPPORT
@@ -40,6 +39,7 @@ source "drivers/media/pci/b2c2/Kconfig"
 source "drivers/media/pci/pluto2/Kconfig"
 source "drivers/media/pci/dm1105/Kconfig"
 source "drivers/media/pci/pt1/Kconfig"
+source "drivers/media/pci/pt3_dvb/Kconfig"
 source "drivers/media/pci/mantis/Kconfig"
 source "drivers/media/pci/ngene/Kconfig"
 source "drivers/media/pci/ddbridge/Kconfig"
diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
index 35cc578..02c6857 100644
--- a/drivers/media/pci/Makefile
+++ b/drivers/media/pci/Makefile
@@ -7,6 +7,7 @@ obj-y        +=	ttpci/		\
 		pluto2/		\
 		dm1105/		\
 		pt1/		\
+		pt3_dvb/	\
 		mantis/		\
 		ngene/		\
 		ddbridge/	\
diff --git a/drivers/media/pci/pt3_dvb/Kconfig b/drivers/media/pci/pt3_dvb/Kconfig
new file mode 100644
index 0000000..f9ba00d
--- /dev/null
+++ b/drivers/media/pci/pt3_dvb/Kconfig
@@ -0,0 +1,12 @@
+config PT3_DVB
+	tristate "Earthsoft PT3 cards"
+	depends on DVB_CORE && PCI
+	help
+	  Support for Earthsoft PT3 PCI-Express cards.
+
+	  Since these cards have no MPEG decoder onboard, they transmit
+	  only compressed MPEG data over the PCI bus, so you need
+	  an external software decoder to watch TV on your computer.
+
+	  Say Y or M if you own such a device and want to use it.
+
diff --git a/drivers/media/pci/pt3_dvb/Makefile b/drivers/media/pci/pt3_dvb/Makefile
new file mode 100644
index 0000000..7087c90
--- /dev/null
+++ b/drivers/media/pci/pt3_dvb/Makefile
@@ -0,0 +1,6 @@
+pt3_dvb-objs := pt3.o pt3_fe.o pt3_dma.o pt3_tc.o pt3_i2c.o pt3_bus.o
+
+obj-$(CONFIG_PT3_DVB) += pt3_dvb.o
+
+ccflags-y += -Idrivers/media/dvb-core -Idrivers/media/dvb-frontends -Idrivers/media/tuners
+
diff --git a/drivers/media/pci/pt3_dvb/pt3.c b/drivers/media/pci/pt3_dvb/pt3.c
new file mode 100644
index 0000000..11c1035
--- /dev/null
+++ b/drivers/media/pci/pt3_dvb/pt3.c
@@ -0,0 +1,540 @@
+#include "pt3.h"
+
+MODULE_AUTHOR("Budi Rachmanto, AreMa Inc. <knightrider(@)are.ma>");
+MODULE_DESCRIPTION("Earthsoft PT3 DVB Driver");
+MODULE_LICENSE("GPL");
+
+static DEFINE_PCI_DEVICE_TABLE(pt3_id_table) = {
+	{ PCI_DEVICE(0x1172, 0x4c15) },
+	{ },
+};
+MODULE_DEVICE_TABLE(pci, pt3_id_table);
+
+#define DRV_NAME "pt3_dvb"
+
+struct {
+	u32 bits;
+	char *str;
+} pt3_lnb[] = {
+	{0b1100,  "0V"},
+	{0b1101, "11V"},
+	{0b1111, "15V"},
+};
+
+static int pt3_update_lnb(struct pt3_board *pt3)
+{
+	u8 i, lnb_eff = 0;
+
+	if (pt3->reset) {
+		writel(pt3_lnb[0].bits, pt3->reg[0] + REG_SYSTEM_W);
+		pt3->reset = false;
+		pt3->lnb = 0;
+	} else {
+		struct pt3_adapter *adap;
+		mutex_lock(&pt3->lock);
+		for (i = 0; i < PT3_NR_ADAPS; i++) {
+			adap = pt3->adap[i];
+			pr_debug("#%d in_use %d sleep %d\n", adap->idx, adap->in_use, adap->sleep);
+			if ((adap->type == SYS_ISDBS) && (!adap->sleep)) {
+				lnb_eff |= adap->voltage == SEC_VOLTAGE_13 ? 1
+					:  adap->voltage == SEC_VOLTAGE_18 ? 2
+					:  lnb;
+			}
+		}
+		mutex_unlock(&pt3->lock);
+		if (unlikely(lnb_eff < 0 || 2 < lnb_eff)) {
+			pr_err("Inconsistent LNB settings\n");
+			return -EINVAL;
+		}
+		if (pt3->lnb != lnb_eff) {
+			writel(pt3_lnb[lnb_eff].bits, pt3->reg[0] + REG_SYSTEM_W);
+			pt3->lnb = lnb_eff;
+		}
+	}
+	pr_debug("LNB=%s\n", pt3_lnb[lnb_eff].str);
+	return 0;
+}
+
+void pt3_filter(struct pt3_adapter *adap, struct dvb_demux *demux, u8 *buf, size_t count)
+{
+	dvb_dmx_swfilter(demux, buf, count);
+}
+
+int pt3_thread(void *data)
+{
+	size_t ret;
+	struct pt3_adapter *adap = data;
+
+	set_freezable();
+	while (!kthread_should_stop()) {
+		try_to_freeze();
+		while ((ret = pt3_dma_copy(adap->dma, &adap->demux)) > 0)
+			;
+		if (ret < 0) {
+			pr_debug("#%d fail dma_copy\n", adap->idx);
+			msleep_interruptible(1);
+		}
+	}
+	return 0;
+}
+
+static int pt3_start_polling(struct pt3_adapter *adap)
+{
+	int ret = 0;
+
+	mutex_lock(&adap->lock);
+	if (!adap->kthread) {
+		adap->kthread = kthread_run(pt3_thread, adap, DRV_NAME "_%d", adap->idx);
+		if (IS_ERR(adap->kthread)) {
+			ret = PTR_ERR(adap->kthread);
+			adap->kthread = NULL;
+		} else {
+			pt3_dma_set_test_mode(adap->dma, RESET, 0);	/* reset_error_count */
+			pt3_dma_set_enabled(adap->dma, true);
+		}
+	}
+	mutex_unlock(&adap->lock);
+	return ret;
+}
+
+static void pt3_stop_polling(struct pt3_adapter *adap)
+{
+	mutex_lock(&adap->lock);
+	if (adap->kthread) {
+		pt3_dma_set_enabled(adap->dma, false);
+		pr_debug("#%d DMA ts_err packet cnt %d\n",
+			adap->idx, pt3_dma_get_ts_error_packet_count(adap->dma));
+		kthread_stop(adap->kthread);
+		adap->kthread = NULL;
+	}
+	mutex_unlock(&adap->lock);
+}
+
+static int pt3_start_feed(struct dvb_demux_feed *feed)
+{
+	int ret;
+	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
+	if (!adap->users++) {
+		if (adap->in_use) {
+			pr_err("#%d device is already used\n", adap->idx);
+			return -EIO;
+		}
+		pr_debug("#%d %s selected, DMA %s\n",
+			adap->idx, adap->str, pt3_dma_get_status(adap->dma) & 1 ? "ON" : "OFF");
+		adap->in_use = true;
+		ret = pt3_start_polling(adap);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int pt3_stop_feed(struct dvb_demux_feed *feed)
+{
+	struct pt3_adapter *adap = container_of(feed->demux, struct pt3_adapter, demux);
+	if (!--adap->users) {
+		pt3_stop_polling(adap);
+		adap->in_use = false;
+		msleep_interruptible(40);
+	}
+	return 0;
+}
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static struct pt3_adapter *pt3_alloc_adapter(struct pt3_board *pt3)
+{
+	int ret;
+	struct dvb_adapter *dvb;
+	struct dvb_demux *demux;
+	struct dmxdev *dmxdev;
+	struct pt3_adapter *adap = kzalloc(sizeof(struct pt3_adapter), GFP_KERNEL);
+
+	if (!adap) {
+		ret = -ENOMEM;
+		goto err;
+	}
+	adap->pt3 = pt3;
+	adap->voltage = SEC_VOLTAGE_OFF;
+	adap->sleep = true;
+
+	dvb = &adap->dvb;
+	dvb->priv = adap;
+	ret = dvb_register_adapter(dvb, DRV_NAME, THIS_MODULE, &pt3->pdev->dev, adapter_nr);
+	if (ret < 0)
+		goto err_kfree;
+
+	demux = &adap->demux;
+	demux->dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING;
+	demux->priv = adap;
+	demux->feednum = 256;
+	demux->filternum = 256;
+	demux->start_feed = pt3_start_feed;
+	demux->stop_feed = pt3_stop_feed;
+	demux->write_to_decoder = NULL;
+	ret = dvb_dmx_init(demux);
+	if (ret < 0)
+		goto err_unregister_adapter;
+
+	dmxdev = &adap->dmxdev;
+	dmxdev->filternum = 256;
+	dmxdev->demux = &demux->dmx;
+	dmxdev->capabilities = 0;
+	ret = dvb_dmxdev_init(dmxdev, dvb);
+	if (ret < 0)
+		goto err_dmx_release;
+
+	return adap;
+
+err_dmx_release:
+	dvb_dmx_release(demux);
+err_unregister_adapter:
+	dvb_unregister_adapter(dvb);
+err_kfree:
+	kfree(adap);
+err:
+	return ERR_PTR(ret);
+}
+
+static int pt3_tuner_power_on(struct pt3_board *pt3, struct pt3_bus *bus)
+{
+	int ret, i, j;
+	struct pt3_ts_pins_mode pins;
+
+	for (i = 0; i < PT3_NR_ADAPS; i++) {
+		ret = pt3_tc_init(pt3->adap[i]);
+		pr_debug("#%d tc_init ret=%d\n", i, ret);
+	}
+	ret = pt3_tc_set_powers(pt3->adap[PT3_NR_ADAPS-1], NULL, true, false);
+	if (ret) {
+		pr_debug("fail set powers.\n");
+		goto last;
+	}
+
+	pins.clock_data = PT3_TS_PIN_MODE_NORMAL;
+	pins.byte       = PT3_TS_PIN_MODE_NORMAL;
+	pins.valid      = PT3_TS_PIN_MODE_NORMAL;
+
+	for (i = 0; i < PT3_NR_ADAPS; i++) {
+		ret = pt3_tc_set_ts_pins_mode(pt3->adap[i], &pins);
+		if (ret)
+			pr_debug("#%d %s fail set ts pins mode ret=%d\n", i, pt3->adap[i]->str, ret);
+	}
+	msleep_interruptible(1);
+
+	for (i = 0; i < PT3_NR_ADAPS; i++)
+		if (pt3->adap[i]->type == SYS_ISDBS) {
+			for (j = 0; j < 10; j++) {
+				if (j)
+					pr_debug("retry pt3_qm_tuner_init\n");
+				ret = pt3_qm_tuner_init(pt3->i2c, pt3->adap[i]);
+				if (!ret)
+					break;
+				msleep_interruptible(1);
+			}
+			if (ret) {
+				pr_debug("#%d fail pt3_qm_tuner_init ret=0x%x\n", i, ret);
+				goto last;
+			}
+		}
+	if (unlikely(bus->cmd_addr < 4096))
+		pt3_i2c_copy(pt3->i2c, bus);
+
+	bus->cmd_addr = PT3_BUS_CMD_ADDR1;
+	ret = pt3_i2c_run(pt3->i2c, bus, false);
+	if (ret) {
+		pr_debug("failed cmd_addr=0x%x ret=0x%x\n", PT3_BUS_CMD_ADDR1, ret);
+		goto last;
+	}
+	ret = pt3_tc_set_powers(pt3->adap[PT3_NR_ADAPS-1], NULL, true, true);
+	if (ret) {
+		pr_debug("fail tc_set_powers,\n");
+		goto last;
+	}
+last:
+	return ret;
+}
+
+static int pt3_tuner_init_all(struct pt3_board *pt3)
+{
+	int ret, i;
+	struct pt3_i2c *i2c = pt3->i2c;
+	struct pt3_bus *bus = vzalloc(sizeof(struct pt3_bus));
+
+	if (!bus)
+		return -ENOMEM;
+	pt3_bus_end(bus);
+	bus->cmd_addr = PT3_BUS_CMD_ADDR0;
+
+	if (!pt3_i2c_is_clean(i2c)) {
+		pr_debug("cleanup I2C bus\n");
+		ret = pt3_i2c_run(i2c, bus, false);
+		if (ret)
+			goto last;
+		msleep_interruptible(10);
+	}
+	ret = pt3_tuner_power_on(pt3, bus);
+	if (ret)
+		goto last;
+	pr_debug("tuner_power_on\n");
+
+	for (i = 0; i < PT3_NR_ADAPS; i++) {
+		struct pt3_adapter *adap = pt3->adap[i];
+		ret = pt3_fe_tuner_sleep(adap, false);
+		if (ret)
+			goto last;
+		ret = pt3_fe_set_freq(adap, adap->init_ch, 0);
+		if (ret)
+			pr_debug("fail set_frequency, ret=%d\n", ret);
+		ret = pt3_fe_tuner_sleep(adap, true);
+		if (ret)
+			goto last;
+	}
+last:
+	vfree(bus);
+	return ret;
+}
+
+static void pt3_cleanup_adapter(struct pt3_adapter *adap)
+{
+	if (!adap)
+		return;
+	if (adap->kthread)
+		kthread_stop(adap->kthread);
+	if (adap->fe)
+		dvb_unregister_frontend(adap->fe);
+	if (!adap->sleep)
+		pt3_fe_tuner_sleep(adap, true);
+	if (adap->qm)
+		vfree(adap->qm);
+	if (adap->dma) {
+		if (adap->dma->enabled)
+			pt3_dma_set_enabled(adap->dma, false);
+		pt3_dma_free(adap->dma);
+	}
+	adap->demux.dmx.close(&adap->demux.dmx);
+	dvb_dmxdev_release(&adap->dmxdev);
+	dvb_dmx_release(&adap->demux);
+	dvb_unregister_adapter(&adap->dvb);
+	kfree(adap);
+}
+
+static int pt3_fe_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t voltage)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	adap->voltage = voltage;
+	return (adap->orig_voltage) ? adap->orig_voltage(fe, voltage) : 0;
+}
+
+static int pt3_fe_sleep(struct dvb_frontend *fe)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	adap->sleep = true;
+	pt3_update_lnb(adap->pt3);
+	return (adap->orig_sleep) ? adap->orig_sleep(fe) : 0;
+}
+
+static int pt3_fe_wakeup(struct dvb_frontend *fe)
+{
+	struct pt3_adapter *adap = container_of(fe->dvb, struct pt3_adapter, dvb);
+	adap->sleep = false;
+	pt3_update_lnb(adap->pt3);
+	return (adap->orig_init) ? adap->orig_init(fe) : 0;
+}
+
+static int pt3_init_frontends(struct pt3_board *pt3)
+{
+	struct dvb_frontend *fe[PT3_NR_ADAPS];
+	int i, ret;
+
+	for (i = 0; i < PT3_NR_ADAPS; i++)
+		if (pt3->adap[i]->type == SYS_ISDBS) {
+			fe[i] = pt3_fe_s_attach(pt3->adap[i]);
+			if (!fe[i])
+				break;
+		} else {
+			fe[i] = pt3_fe_t_attach(pt3->adap[i]);
+			if (!fe[i])
+				break;
+		}
+	if (i < PT3_NR_ADAPS) {
+		while (i--)
+			fe[i]->ops.release(fe[i]);
+		return -ENOMEM;
+	}
+	for (i = 0; i < PT3_NR_ADAPS; i++) {
+		struct pt3_adapter *adap = pt3->adap[i];
+
+		adap->orig_voltage     = fe[i]->ops.set_voltage;
+		adap->orig_sleep       = fe[i]->ops.sleep;
+		adap->orig_init        = fe[i]->ops.init;
+		fe[i]->ops.set_voltage = pt3_fe_set_voltage;
+		fe[i]->ops.sleep       = pt3_fe_sleep;
+		fe[i]->ops.init        = pt3_fe_wakeup;
+
+		ret = dvb_register_frontend(&adap->dvb, fe[i]);
+		if (ret >= 0)
+			adap->fe = fe[i];
+		else {
+			while (i--)
+				dvb_unregister_frontend(fe[i]);
+			for (i = 0; i < PT3_NR_ADAPS; i++)
+				fe[i]->ops.release(fe[i]);
+			return ret;
+		}
+	}
+	return 0;
+}
+
+static void pt3_remove(struct pci_dev *pdev)
+{
+	int i;
+	struct pt3_board *pt3 = pci_get_drvdata(pdev);
+
+	if (pt3) {
+		pt3->reset = true;
+		pt3_update_lnb(pt3);
+		if (pt3->i2c) {
+			if (pt3->adap[PT3_NR_ADAPS-1])
+				pt3_tc_set_powers(pt3->adap[PT3_NR_ADAPS-1], NULL, false, false);
+			pt3_i2c_reset(pt3->i2c);
+			vfree(pt3->i2c);
+		}
+		for (i = 0; i < PT3_NR_ADAPS; i++)
+			pt3_cleanup_adapter(pt3->adap[i]);
+		if (pt3->reg[1])
+			iounmap(pt3->reg[1]);
+		if (pt3->reg[0])
+			iounmap(pt3->reg[0]);
+		pci_release_selected_regions(pdev, pt3->bars);
+		kfree(pt3);
+	}
+	pci_disable_device(pdev);
+}
+
+static int pt3_abort(struct pci_dev *pdev, int ret, char *fmt, ...)
+{
+	va_list ap;
+	char *s = NULL;
+	int slen;
+
+	va_start(ap, fmt);
+	slen = vsnprintf(s, 0, fmt, ap);
+	s = vzalloc(slen);
+	if (slen > 0 && s) {
+		vsnprintf(s, slen, fmt, ap);
+		dev_alert(&pdev->dev, "%s", s);
+		vfree(s);
+	}
+	va_end(ap);
+	pt3_remove(pdev);
+	return ret;
+}
+
+struct {
+	fe_delivery_system_t type;
+	u8 addr_tuner, addr_tc;
+	int init_ch;
+	char *str;
+} pt3_config[] = {
+	{SYS_ISDBS, 0x63, 0b00010001,  0, "ISDB-S"},
+	{SYS_ISDBS, 0x60, 0b00010011,  0, "ISDB-S"},
+	{SYS_ISDBT, 0x62, 0b00010000, 70, "ISDB-T"},
+	{SYS_ISDBT, 0x61, 0b00010010, 71, "ISDB-T"},
+};
+
+static int pt3_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+	struct pt3_board *pt3;
+	struct pt3_adapter *adap;
+	int i, ret, bars = pci_select_bars(pdev, IORESOURCE_MEM);
+
+	ret = pci_enable_device(pdev);
+	if (ret < 0)
+		return pt3_abort(pdev, ret, "PCI device unusable\n");
+	ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(64));
+	if (ret)
+		return pt3_abort(pdev, ret, "DMA mask error\n");
+	pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64));
+
+	pci_read_config_dword(pdev, PCI_CLASS_REVISION, &i);
+	if ((i & 0xFF) != 1)
+		return pt3_abort(pdev, ret, "Revision 0x%x is not supported\n", i & 0xFF);
+	ret = pci_request_selected_regions(pdev, bars, DRV_NAME);
+	if (ret < 0)
+		return pt3_abort(pdev, ret, "Could not request regions\n");
+
+	pci_set_master(pdev);
+	ret = pci_save_state(pdev);
+	if (ret)
+		return pt3_abort(pdev, ret, "Failed pci_save_state\n");
+	pt3 = kzalloc(sizeof(struct pt3_board), GFP_KERNEL);
+	if (!pt3)
+		return pt3_abort(pdev, -ENOMEM, "struct pt3_board out of memory\n");
+
+	pt3->bars = bars;
+	pt3->pdev = pdev;
+	pci_set_drvdata(pdev, pt3);
+	pt3->reg[0] = pci_ioremap_bar(pdev, 0);
+	pt3->reg[1] = pci_ioremap_bar(pdev, 2);
+	if (!pt3->reg[0] || !pt3->reg[1])
+		return pt3_abort(pdev, -EIO, "Failed pci_ioremap_bar\n");
+
+	ret = readl(pt3->reg[0] + REG_VERSION);
+	i = ((ret >> 24) & 0xFF);
+	if (i != 3)
+		return pt3_abort(pdev, -EIO, "ID=0x%x, not a PT3\n", i);
+	i = ((ret >>  8) & 0xFF);
+	if (i != 4)
+		return pt3_abort(pdev, -EIO, "FPGA version 0x%x is not supported\n", i);
+	mutex_init(&pt3->lock);
+
+	for (i = 0; i < PT3_NR_ADAPS; i++) {
+		pt3->adap[i] = NULL;
+		adap = pt3_alloc_adapter(pt3);
+		if (IS_ERR(adap))
+			return pt3_abort(pdev, PTR_ERR(adap), "Failed pt3_alloc_adapter\n");
+		adap->idx = i;
+		adap->dma = pt3_dma_create(adap);
+		if (!adap->dma)
+			return pt3_abort(pdev, -ENOMEM, "Failed pt3_dma_create\n");
+		mutex_init(&adap->lock);
+		pt3->adap[i] = adap;
+		adap->type       = pt3_config[i].type;
+		adap->addr_tuner = pt3_config[i].addr_tuner;
+		adap->addr_tc    = pt3_config[i].addr_tc;
+		adap->init_ch    = pt3_config[i].init_ch;
+		adap->str        = pt3_config[i].str;
+		if (adap->type == SYS_ISDBS) {
+			adap->qm = vzalloc(sizeof(struct pt3_qm));
+			if (!adap->qm)
+				return pt3_abort(pdev, -ENOMEM, "QM out of memory\n");
+			adap->qm->adap = adap;
+		}
+		adap->sleep = true;
+	}
+	pt3->reset = true;
+	pt3_update_lnb(pt3);
+
+	pt3->i2c = vzalloc(sizeof(struct pt3_i2c));
+	if (!pt3->i2c)
+		return pt3_abort(pdev, -ENOMEM, "Cannot allocate I2C\n");
+	mutex_init(&pt3->i2c->lock);
+	pt3->i2c->reg[0] = pt3->reg[0];
+	pt3->i2c->reg[1] = pt3->reg[1];
+
+	if (pt3_tuner_init_all(pt3))
+		return pt3_abort(pdev, ret, "Failed pt3_tuner_init_all\n");
+	ret = pt3_init_frontends(pt3);
+	return (ret >= 0) ? ret : pt3_abort(pdev, ret, "Failed pt3_init_frontends\n");
+}
+
+static struct pci_driver pt3_driver = {
+	.name		= DRV_NAME,
+	.probe		= pt3_probe,
+	.remove		= pt3_remove,
+	.id_table	= pt3_id_table,
+};
+
+module_pci_driver(pt3_driver);
+
diff --git a/drivers/media/pci/pt3_dvb/pt3.h b/drivers/media/pci/pt3_dvb/pt3.h
new file mode 100644
index 0000000..c810905
--- /dev/null
+++ b/drivers/media/pci/pt3_dvb/pt3.h
@@ -0,0 +1,222 @@
+#ifndef	__PT3_H__
+#define	__PT3_H__
+
+#define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+
+#include <linux/pci.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include "dvb_demux.h"
+#include "dmxdev.h"
+#include "dvb_frontend.h"
+
+#define PT3_NR_ADAPS 4
+#define PT3_SHIFT_MASK(val, shift, mask) (((val) >> (shift)) & (((u64)1<<(mask))-1))
+
+/* register idx */
+#define REG_VERSION	0x00	/*	R	Version		*/
+#define REG_BUS		0x04	/*	R	Bus		*/
+#define REG_SYSTEM_W	0x08	/*	W	System		*/
+#define REG_SYSTEM_R	0x0c	/*	R	System		*/
+#define REG_I2C_W	0x10	/*	W	I2C		*/
+#define REG_I2C_R	0x14	/*	R	I2C		*/
+#define REG_RAM_W	0x18	/*	W	RAM		*/
+#define REG_RAM_R	0x1c	/*	R	RAM		*/
+#define REG_BASE	0x40	/* + 0x18*idx			*/
+#define REG_DMA_DESC_L	0x00	/*	W	DMA		*/
+#define REG_DMA_DESC_H	0x04	/*	W	DMA		*/
+#define REG_DMA_CTL	0x08	/*	W	DMA		*/
+#define REG_TS_CTL	0x0c	/*	W	TS		*/
+#define REG_STATUS	0x10	/*	R	DMA/FIFO/TS	*/
+#define REG_TS_ERR	0x14	/*	R	TS		*/
+
+static int lnb = 2;	/* used if not set by frontend / the value is invalid */
+module_param(lnb, int, 0);
+MODULE_PARM_DESC(lnb, "LNB level (0:OFF 1:+11V 2:+15V)");
+
+/* Transmission and Multiplexing Configuration Control */
+
+enum {
+	LAYER_INDEX_L = 0,
+	LAYER_INDEX_H,
+
+	LAYER_INDEX_A = 0,
+	LAYER_INDEX_B,
+	LAYER_INDEX_C
+};
+
+enum {
+	LAYER_COUNT_S = LAYER_INDEX_H + 1,
+	LAYER_COUNT_T = LAYER_INDEX_C + 1,
+};
+
+struct tmcc_s {
+	u32 indicator;
+	u32 mode[4];
+	u32 slot[4];
+	u32 id[8];
+	u32 emergency;
+	u32 uplink;
+	u32 extflag;
+};
+
+struct tmcc_t {
+	u32 system;
+	u32 indicator;
+	u32 emergency;
+	u32 partial;
+	u32 mode[LAYER_COUNT_T];
+	u32 rate[LAYER_COUNT_T];
+	u32 interleave[LAYER_COUNT_T];
+	u32 segment[LAYER_COUNT_T];
+};
+
+struct pt3_i2c {
+	u8 __iomem *reg[2];
+	struct mutex lock;
+};
+
+struct pt3_dma_page {
+	dma_addr_t addr;
+	u8 *data;
+	u32 size, data_pos;
+};
+
+struct pt3_adapter;
+
+struct pt3_dma {
+	struct pt3_adapter *adap;
+	bool enabled;
+	u32 ts_pos, ts_count, desc_count;
+	struct pt3_dma_page *ts_info, *desc_info;
+	struct mutex lock;
+};
+
+struct pt3_qm {
+	struct pt3_adapter *adap;
+	u8 reg[32];
+
+	bool standby;
+	u32 wait_time_lpf, wait_time_search_fast, wait_time_search_normal;
+	struct tmcc_s tmcc;
+};
+
+struct pt3_board {
+	struct mutex lock;
+	bool reset;
+	int lnb;
+
+	struct pci_dev *pdev;
+	void __iomem *reg[2];
+	int bars;
+	struct pt3_i2c *i2c;
+
+	struct pt3_adapter *adap[PT3_NR_ADAPS];
+};
+
+struct pt3_adapter {
+	struct mutex lock;
+	struct pt3_board *pt3;
+
+	int idx, init_ch;
+	char *str;
+	fe_delivery_system_t type;
+	bool in_use, sleep;
+	u32 channel;
+	s32 offset;
+	u8 addr_tc, addr_tuner;
+	u32 freq;
+	struct pt3_qm *qm;
+	struct pt3_dma *dma;
+	struct task_struct *kthread;
+	int *dec;
+	struct dvb_adapter dvb;
+	struct dvb_demux demux;
+	int users;
+	struct dmxdev dmxdev;
+	struct dvb_frontend *fe;
+	int (*orig_voltage)(struct dvb_frontend *fe, fe_sec_voltage_t voltage);
+	int (*orig_sleep)  (struct dvb_frontend *fe);
+	int (*orig_init)   (struct dvb_frontend *fe);
+	fe_sec_voltage_t voltage;
+};
+
+enum pt3_ts_pin_mode {
+	PT3_TS_PIN_MODE_NORMAL,
+	PT3_TS_PIN_MODE_LOW,
+	PT3_TS_PIN_MODE_HIGH,
+};
+
+struct pt3_ts_pins_mode {
+	enum pt3_ts_pin_mode clock_data, byte, valid;
+};
+
+#define PT3_BUS_CMD_MAX   4096
+#define PT3_BUS_CMD_ADDR0 4096
+#define PT3_BUS_CMD_ADDR1 (4096 + 2042)
+
+struct pt3_bus {
+	u32 read_addr, cmd_addr, cmd_count, cmd_pos, buf_pos, buf_size;
+	u8 cmd_tmp, cmds[PT3_BUS_CMD_MAX], *buf;
+};
+
+enum pt3_tc_agc {
+	PT3_TC_AGC_AUTO,
+	PT3_TC_AGC_MANUAL,
+};
+
+enum pt3_dma_mode {
+	USE_LFSR = 1 << 16,
+	REVERSE  = 1 << 17,
+	RESET    = 1 << 18,
+};
+
+/* protos */
+u8 pt3_bus_data1(struct pt3_bus *bus, u32 index);
+void pt3_bus_end(struct pt3_bus *bus);
+void pt3_bus_push_read_data(struct pt3_bus *bus, u8 data);
+u32 pt3_bus_read(struct pt3_bus *bus, u8 *data, u32 size);
+void pt3_bus_sleep(struct pt3_bus *bus, u32 ms);
+void pt3_bus_start(struct pt3_bus *bus);
+void pt3_bus_stop(struct pt3_bus *bus);
+void pt3_bus_write(struct pt3_bus *bus, const u8 *data, u32 size);
+ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux);
+struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap);
+void pt3_dma_free(struct pt3_dma *dma);
+u32 pt3_dma_get_status(struct pt3_dma *dma);
+u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma);
+void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled);
+void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval);
+int pt3_fe_set_freq(struct pt3_adapter *adap, u32 channel, s32 offset);
+int pt3_fe_tuner_sleep(struct pt3_adapter *adap, bool sleep);
+void pt3_filter(struct pt3_adapter *adap, struct dvb_demux *demux, u8 *buf, size_t count);
+void pt3_i2c_copy(struct pt3_i2c *i2c, struct pt3_bus *bus);
+bool pt3_i2c_is_clean(struct pt3_i2c *i2c);
+void pt3_i2c_reset(struct pt3_i2c *i2c);
+int pt3_i2c_run(struct pt3_i2c *i2c, struct pt3_bus *bus, bool copy);
+u32 pt3_tc_index(struct pt3_adapter *adap);
+int pt3_tc_init(struct pt3_adapter *adap);
+int pt3_tc_read_cn_s(struct pt3_adapter *adap, struct pt3_bus *bus, u32 *cn);
+int pt3_tc_read_cndat_t(struct pt3_adapter *adap, struct pt3_bus *bus, u32 *cn);
+int pt3_tc_read_id_s(struct pt3_adapter *adap, struct pt3_bus *bus, u16 *id);
+int pt3_tc_read_retryov_tmunvld_fulock(struct pt3_adapter *adap, struct pt3_bus *bus, int *retryov, int *tmunvld, int *fulock);
+int pt3_tc_read_tmcc_s(struct pt3_adapter *adap, struct pt3_bus *bus, struct tmcc_s *tmcc);
+int pt3_tc_read_tmcc_t(struct pt3_adapter *adap, struct pt3_bus *bus, struct tmcc_t *tmcc);
+int pt3_tc_read_tuner(struct pt3_adapter *adap, struct pt3_bus *bus, u8 addr, u8 *data);
+int pt3_tc_read_tuner_without_addr(struct pt3_adapter *adap, struct pt3_bus *bus, u8 *data);
+int pt3_tc_set_agc_s(struct pt3_adapter *adap, enum pt3_tc_agc agc);
+int pt3_tc_set_agc_t(struct pt3_adapter *adap, enum pt3_tc_agc agc);
+int pt3_tc_set_powers(struct pt3_adapter *adap, struct pt3_bus *bus, bool tuner, bool amp);
+int pt3_tc_set_sleep_s(struct pt3_adapter *adap, struct pt3_bus *bus, bool sleep);
+int pt3_tc_set_ts_pins_mode(struct pt3_adapter *adap, struct pt3_ts_pins_mode *mode);
+u32 pt3_tc_time_diff(struct timeval *st, struct timeval *et);
+int pt3_tc_write_id_s(struct pt3_adapter *adap, struct pt3_bus *bus, u16 id);
+int pt3_tc_write_sleep_time(struct pt3_adapter *adap, int sleep);
+int pt3_tc_write_tuner(struct pt3_adapter *adap, struct pt3_bus *bus, u8 addr, const u8 *data, u32 size);
+int pt3_tc_write_tuner_without_addr(struct pt3_adapter *adap, struct pt3_bus *bus, const u8 *data, u32 size);
+int pt3_qm_tuner_init(struct pt3_i2c *i2c, struct pt3_adapter *adap);
+struct dvb_frontend *pt3_fe_s_attach(struct pt3_adapter *adap);
+struct dvb_frontend *pt3_fe_t_attach(struct pt3_adapter *adap);
+
+#endif
+
diff --git a/drivers/media/pci/pt3_dvb/pt3_bus.c b/drivers/media/pci/pt3_dvb/pt3_bus.c
new file mode 100644
index 0000000..5db7874
--- /dev/null
+++ b/drivers/media/pci/pt3_dvb/pt3_bus.c
@@ -0,0 +1,134 @@
+#include "pt3.h"
+
+enum pt3_bus_cmd {
+	I_END,
+	I_ADDRESS,
+	I_CLOCK_L,
+	I_CLOCK_H,
+	I_DATA_L,
+	I_DATA_H,
+	I_RESET,
+	I_SLEEP,
+	I_DATA_L_NOP  = 0x08,
+	I_DATA_H_NOP  = 0x0c,
+	I_DATA_H_READ = 0x0d,
+	I_DATA_H_ACK0 = 0x0e,
+	I_DATA_H_ACK1 = 0x0f,
+};
+
+static void pt3_bus_add_cmd(struct pt3_bus *bus, enum pt3_bus_cmd cmd)
+{
+	if ((bus->cmd_count % 2) == 0)
+		bus->cmd_tmp = cmd;
+	else
+		bus->cmd_tmp |= cmd << 4;
+
+	if (bus->cmd_count % 2) {
+		bus->cmds[bus->cmd_pos] = bus->cmd_tmp;
+		bus->cmd_pos++;
+		if (bus->cmd_pos >= sizeof(bus->cmds)) {
+			pr_debug("bus->cmds is overflow\n");
+			bus->cmd_pos = 0;
+		}
+	}
+	bus->cmd_count++;
+}
+
+u8 pt3_bus_data1(struct pt3_bus *bus, u32 index)
+{
+	if (unlikely(!bus->buf)) {
+		pr_debug("buf is not ready.\n");
+		return 0;
+	}
+	if (unlikely(bus->buf_size < index + 1)) {
+		pr_debug("buf does not have enough size. buf_size=%d\n",
+				bus->buf_size);
+		return 0;
+	}
+	return bus->buf[index];
+}
+
+void pt3_bus_start(struct pt3_bus *bus)
+{
+	pt3_bus_add_cmd(bus, I_DATA_H);
+	pt3_bus_add_cmd(bus, I_CLOCK_H);
+	pt3_bus_add_cmd(bus, I_DATA_L);
+	pt3_bus_add_cmd(bus, I_CLOCK_L);
+}
+
+void pt3_bus_stop(struct pt3_bus *bus)
+{
+	pt3_bus_add_cmd(bus, I_DATA_L);
+	pt3_bus_add_cmd(bus, I_CLOCK_H);
+	pt3_bus_add_cmd(bus, I_DATA_H);
+}
+
+void pt3_bus_write(struct pt3_bus *bus, const u8 *data, u32 size)
+{
+	u32 i, j;
+	u8 byte;
+
+	for (i = 0; i < size; i++) {
+		byte = data[i];
+		for (j = 0; j < 8; j++)
+			pt3_bus_add_cmd(bus, PT3_SHIFT_MASK(byte, 7 - j, 1) ? I_DATA_H_NOP : I_DATA_L_NOP);
+		pt3_bus_add_cmd(bus, I_DATA_H_ACK0);
+	}
+}
+
+u32 pt3_bus_read(struct pt3_bus *bus, u8 *data, u32 size)
+{
+	u32 i, j;
+	u32 index;
+
+	for (i = 0; i < size; i++) {
+		for (j = 0; j < 8; j++)
+			pt3_bus_add_cmd(bus, I_DATA_H_READ);
+		if (i == (size - 1))
+			pt3_bus_add_cmd(bus, I_DATA_H_NOP);
+		else
+			pt3_bus_add_cmd(bus, I_DATA_L_NOP);
+	}
+	index = bus->read_addr;
+	bus->read_addr += size;
+	if (likely(!bus->buf)) {
+		bus->buf = data;
+		bus->buf_pos = 0;
+		bus->buf_size = size;
+	} else
+		pr_debug("bus read buf already exists.\n");
+
+	return index;
+}
+
+void pt3_bus_push_read_data(struct pt3_bus *bus, u8 data)
+{
+	if (unlikely(bus->buf)) {
+		if (bus->buf_pos >= bus->buf_size) {
+			pr_debug("buffer over run. pos=%d\n", bus->buf_pos);
+			bus->buf_pos = 0;
+		}
+		bus->buf[bus->buf_pos] = data;
+		bus->buf_pos++;
+	}
+}
+
+void pt3_bus_sleep(struct pt3_bus *bus, u32 ms)
+{
+	u32 i;
+	for (i = 0; i < ms; i++)
+		pt3_bus_add_cmd(bus, I_SLEEP);
+}
+
+void pt3_bus_end(struct pt3_bus *bus)
+{
+	pt3_bus_add_cmd(bus, I_END);
+	if (bus->cmd_count % 2)
+		pt3_bus_add_cmd(bus, I_END);
+}
+
+void pt3_bus_reset(struct pt3_bus *bus)
+{
+	pt3_bus_add_cmd(bus, I_RESET);
+}
+
diff --git a/drivers/media/pci/pt3_dvb/pt3_dma.c b/drivers/media/pci/pt3_dvb/pt3_dma.c
new file mode 100644
index 0000000..bc4c67b
--- /dev/null
+++ b/drivers/media/pci/pt3_dvb/pt3_dma.c
@@ -0,0 +1,316 @@
+#include "pt3.h"
+
+#define PT3_DMA_MAX_DESCS	204
+#define PT3_DMA_PAGE_SIZE	(PT3_DMA_MAX_DESCS * sizeof(struct pt3_dma_desc))
+#define PT3_DMA_BLOCK_COUNT	17
+#define PT3_DMA_BLOCK_SIZE	(PT3_DMA_PAGE_SIZE * 47)
+#define PT3_DMA_TS_BUF_SIZE	(PT3_DMA_BLOCK_SIZE * PT3_DMA_BLOCK_COUNT)
+#define PT3_DMA_NOT_SYNC_BYTE	0x74
+
+struct pt3_dma_desc {
+	u64 page_addr;
+	u32 page_size;
+	u64 next_desc;
+} __packed;
+
+void pt3_dma_build_page_descriptor(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *desc_info, *ts_info;
+	u64 ts_addr, desc_addr;
+	u32 i, j, ts_size, desc_remain, ts_info_pos, desc_info_pos;
+	struct pt3_dma_desc *prev, *curr;
+
+	pr_debug("#%d build page descriptor ts_count=%d ts_size=%d desc_count=%d desc_size=%d\n",
+		dma->adap->idx, dma->ts_count, dma->ts_info[0].size, dma->desc_count, dma->desc_info[0].size);
+	desc_info_pos = ts_info_pos = 0;
+	desc_info = &dma->desc_info[desc_info_pos];
+	desc_addr   = desc_info->addr;
+	desc_remain = desc_info->size;
+	desc_info->data_pos = 0;
+	prev = NULL;
+	curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+	desc_info_pos++;
+
+	for (i = 0; i < dma->ts_count; i++) {
+		if (unlikely(ts_info_pos >= dma->ts_count)) {
+			pr_debug("#%d ts_info overflow max=%d curr=%d\n", dma->adap->idx, dma->ts_count, ts_info_pos);
+			return;
+		}
+		ts_info = &dma->ts_info[ts_info_pos];
+		ts_addr = ts_info->addr;
+		ts_size = ts_info->size;
+		ts_info_pos++;
+		pr_debug("#%d i=%d, ts_info addr=0x%llx ts_size=%d\n", dma->adap->idx, i, ts_addr, ts_size);
+		for (j = 0; j < ts_size / PT3_DMA_PAGE_SIZE; j++) {
+			if (desc_remain < sizeof(struct pt3_dma_desc)) {
+				if (unlikely(desc_info_pos >= dma->desc_count)) {
+					pr_debug("#%d desc_info overflow max=%d curr=%d\n",
+						dma->adap->idx, dma->desc_count, desc_info_pos);
+					return;
+				}
+				desc_info = &dma->desc_info[desc_info_pos];
+				desc_info->data_pos = 0;
+				curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+				pr_debug("#%d desc_info_pos=%d ts_addr=0x%llx remain=%d\n",
+					dma->adap->idx, desc_info_pos, ts_addr, desc_remain);
+				desc_addr = desc_info->addr;
+				desc_remain = desc_info->size;
+				desc_info_pos++;
+			}
+			if (prev)
+				prev->next_desc = desc_addr | 0b10;
+			curr->page_addr = ts_addr           | 0b111;
+			curr->page_size = PT3_DMA_PAGE_SIZE | 0b111;
+			curr->next_desc = 0b10;
+			pr_debug("#%d j=%d dma write desc ts_addr=0x%llx desc_info_pos=%d desc_remain=%d\n",
+				dma->adap->idx, j, ts_addr, desc_info_pos, desc_remain);
+			ts_addr += PT3_DMA_PAGE_SIZE;
+
+			prev = curr;
+			desc_info->data_pos += sizeof(struct pt3_dma_desc);
+			if (unlikely(desc_info->data_pos > desc_info->size)) {
+				pr_debug("#%d dma desc_info data overflow max=%d curr=%d\n",
+					dma->adap->idx, desc_info->size, desc_info->data_pos);
+				return;
+			}
+			curr = (struct pt3_dma_desc *)&desc_info->data[desc_info->data_pos];
+			desc_addr += sizeof(struct pt3_dma_desc);
+			desc_remain -= sizeof(struct pt3_dma_desc);
+		}
+	}
+	if (prev)
+		prev->next_desc = dma->desc_info->addr | 0b10;
+}
+
+struct pt3_dma *pt3_dma_create(struct pt3_adapter *adap)
+{
+	struct pt3_dma_page *page;
+	u32 i;
+
+	struct pt3_dma *dma = kzalloc(sizeof(struct pt3_dma), GFP_KERNEL);
+	if (!dma) {
+		pr_debug("#%d fail allocate PT3_DMA\n", adap->idx);
+		goto fail;
+	}
+	dma->adap = adap;
+	dma->enabled = false;
+	mutex_init(&dma->lock);
+
+	dma->ts_count = PT3_DMA_BLOCK_COUNT;
+	dma->ts_info = kzalloc(sizeof(struct pt3_dma_page) * dma->ts_count, GFP_KERNEL);
+	if (!dma->ts_info) {
+		pr_debug("#%d fail allocate TS DMA page\n", adap->idx);
+		goto fail;
+	}
+	pr_debug("#%d Alloc TS buf (ts_count %d)\n", adap->idx, dma->ts_count);
+	for (i = 0; i < dma->ts_count; i++) {
+		page = &dma->ts_info[i];
+		page->size = PT3_DMA_BLOCK_SIZE;
+		page->data_pos = 0;
+		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
+		if (!page->data) {
+			pr_debug("#%d fail alloc_consistent. %d\n", adap->idx, i);
+			goto fail;
+		}
+	}
+
+	dma->desc_count = 1 + (PT3_DMA_TS_BUF_SIZE / PT3_DMA_PAGE_SIZE - 1) / PT3_DMA_MAX_DESCS;
+	dma->desc_info = kzalloc(sizeof(struct pt3_dma_page) * dma->desc_count, GFP_KERNEL);
+	if (!dma->desc_info) {
+		pr_debug("#%d fail allocate Desc DMA page\n", adap->idx);
+		goto fail;
+	}
+	pr_debug("#%d Alloc Descriptor buf (desc_count %d)\n", adap->idx, dma->desc_count);
+	for (i = 0; i < dma->desc_count; i++) {
+		page = &dma->desc_info[i];
+		page->size = PT3_DMA_PAGE_SIZE;
+		page->data_pos = 0;
+		page->data = pci_alloc_consistent(adap->pt3->pdev, page->size, &page->addr);
+		if (!page->data) {
+			pr_debug("#%d fail alloc_consistent %d\n", adap->idx, i);
+			goto fail;
+		}
+	}
+
+	pr_debug("#%d build page descriptor\n", adap->idx);
+	pt3_dma_build_page_descriptor(dma);
+	return dma;
+fail:
+	if (dma)
+		pt3_dma_free(dma);
+	return NULL;
+}
+
+void pt3_dma_free(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *page;
+	u32 i;
+
+	if (dma->ts_info) {
+		for (i = 0; i < dma->ts_count; i++) {
+			page = &dma->ts_info[i];
+			if (page->data)
+				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
+		}
+		kfree(dma->ts_info);
+	}
+	if (dma->desc_info) {
+		for (i = 0; i < dma->desc_count; i++) {
+			page = &dma->desc_info[i];
+			if (page->data)
+				pci_free_consistent(dma->adap->pt3->pdev, page->size, page->data, page->addr);
+		}
+		kfree(dma->desc_info);
+	}
+	kfree(dma);
+}
+
+void __iomem *pt3_dma_get_base_addr(struct pt3_dma *dma)
+{
+	return dma->adap->pt3->i2c->reg[0] + REG_BASE + (0x18 * dma->adap->idx);
+}
+
+void pt3_dma_reset(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *ts;
+	u32 i;
+
+	for (i = 0; i < dma->ts_count; i++) {
+		ts = &dma->ts_info[i];
+		memset(ts->data, 0, ts->size);
+		ts->data_pos = 0;
+		*ts->data = PT3_DMA_NOT_SYNC_BYTE;
+	}
+	dma->ts_pos = 0;
+}
+
+void pt3_dma_set_enabled(struct pt3_dma *dma, bool enabled)
+{
+	void __iomem *base = pt3_dma_get_base_addr(dma);
+	u64 start_addr = dma->desc_info->addr;
+
+	if (enabled) {
+		pr_debug("#%d DMA enable start_addr=%llx\n", dma->adap->idx, start_addr);
+		pt3_dma_reset(dma);
+		writel(1 << 1, base + REG_DMA_CTL);	/* stop DMA */
+		writel(PT3_SHIFT_MASK(start_addr,  0, 32), base + REG_DMA_DESC_L);
+		writel(PT3_SHIFT_MASK(start_addr, 32, 32), base + REG_DMA_DESC_H);
+		pr_debug("set descriptor address low %llx\n",  PT3_SHIFT_MASK(start_addr,  0, 32));
+		pr_debug("set descriptor address high %llx\n", PT3_SHIFT_MASK(start_addr, 32, 32));
+		writel(1 << 0, base + REG_DMA_CTL);	/* start DMA */
+	} else {
+		pr_debug("#%d DMA disable\n", dma->adap->idx);
+		writel(1 << 1, base + REG_DMA_CTL);	/* stop DMA */
+		while (1) {
+			if (!PT3_SHIFT_MASK(readl(base + REG_STATUS), 0, 1))
+				break;
+			msleep_interruptible(1);
+		}
+	}
+	dma->enabled = enabled;
+}
+
+/* convert Gray code to binary, e.g. 1001 -> 1110 */
+static u32 pt3_dma_gray2binary(u32 gray, u32 bit)
+{
+	u32 binary = 0, i, j, k;
+
+	for (i = 0; i < bit; i++) {
+		k = 0;
+		for (j = i; j < bit; j++)
+			k = k ^ PT3_SHIFT_MASK(gray, j, 1);
+		binary |= k << i;
+	}
+	return binary;
+}
+
+u32 pt3_dma_get_ts_error_packet_count(struct pt3_dma *dma)
+{
+	return pt3_dma_gray2binary(readl(pt3_dma_get_base_addr(dma) + REG_TS_ERR), 32);
+}
+
+void pt3_dma_set_test_mode(struct pt3_dma *dma, enum pt3_dma_mode mode, u16 initval)
+{
+	void __iomem *base = pt3_dma_get_base_addr(dma);
+	u32 data = mode | initval;
+	pr_debug("set_test_mode base=%p data=0x%04x\n", base, data);
+	writel(data, base + REG_TS_CTL);
+}
+
+bool pt3_dma_ready(struct pt3_dma *dma)
+{
+	struct pt3_dma_page *ts;
+	u8 *p;
+
+	u32 next = dma->ts_pos + 1;
+	if (next >= dma->ts_count)
+		next = 0;
+	ts = &dma->ts_info[next];
+	p = &ts->data[ts->data_pos];
+
+	if (*p == 0x47)
+		return true;
+	if (*p == PT3_DMA_NOT_SYNC_BYTE)
+		return false;
+
+	pr_debug("#%d invalid sync byte value=0x%02x ts_pos=%d data_pos=%d curr=0x%02x\n",
+		dma->adap->idx, *p, next, ts->data_pos, dma->ts_info[dma->ts_pos].data[0]);
+	return false;
+}
+
+ssize_t pt3_dma_copy(struct pt3_dma *dma, struct dvb_demux *demux)
+{
+	bool ready;
+	struct pt3_dma_page *ts;
+	u32 i, prev;
+	size_t csize, remain = dma->ts_info[dma->ts_pos].size;
+
+	mutex_lock(&dma->lock);
+	pr_debug("#%d dma_copy ts_pos=0x%x data_pos=0x%x\n",
+		   dma->adap->idx, dma->ts_pos, dma->ts_info[dma->ts_pos].data_pos);
+	for (;;) {
+		for (i = 0; i < 20; i++) {
+			ready = pt3_dma_ready(dma);
+			if (ready)
+				break;
+			msleep_interruptible(30);
+		}
+		if (!ready) {
+			pr_debug("#%d dma_copy NOT READY\n", dma->adap->idx);
+			goto last;
+		}
+		prev = dma->ts_pos - 1;
+		if (prev < 0 || dma->ts_count <= prev)
+			prev = dma->ts_count - 1;
+		if (dma->ts_info[prev].data[0] != PT3_DMA_NOT_SYNC_BYTE)
+			pr_debug("#%d DMA buffer overflow. prev=%d data=0x%x\n",
+					dma->adap->idx, prev, dma->ts_info[prev].data[0]);
+		ts = &dma->ts_info[dma->ts_pos];
+		for (;;) {
+			csize = (remain < (ts->size - ts->data_pos)) ?
+				 remain : (ts->size - ts->data_pos);
+			pt3_filter(dma->adap, demux, &ts->data[ts->data_pos], csize);
+			remain -= csize;
+			ts->data_pos += csize;
+			if (ts->data_pos >= ts->size) {
+				ts->data_pos = 0;
+				ts->data[ts->data_pos] = PT3_DMA_NOT_SYNC_BYTE;
+				dma->ts_pos++;
+				if (dma->ts_pos >= dma->ts_count)
+					dma->ts_pos = 0;
+				break;
+			}
+			if (remain <= 0)
+				goto last;
+		}
+	}
+last:
+	mutex_unlock(&dma->lock);
+	return dma->ts_info[dma->ts_pos].size - remain;
+}
+
+u32 pt3_dma_get_status(struct pt3_dma *dma)
+{
+	return readl(pt3_dma_get_base_addr(dma) + REG_STATUS);
+}
+
diff --git a/drivers/media/pci/pt3_dvb/pt3_fe.c b/drivers/media/pci/pt3_dvb/pt3_fe.c
new file mode 100644
index 0000000..111a696
--- /dev/null
+++ b/drivers/media/pci/pt3_dvb/pt3_fe.c
@@ -0,0 +1,1186 @@
+#include "dvb_math.h"
+#include "pt3.h"
+
+static u8 pt3_qm_reg_rw[] = {
+	0x48, 0x1c, 0xa0, 0x10, 0xbc, 0xc5, 0x20, 0x33,
+	0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+	0x00, 0xff, 0xf3, 0x00, 0x2a, 0x64, 0xa6, 0x86,
+	0x8c, 0xcf, 0xb8, 0xf1, 0xa8, 0xf2, 0x89, 0x00,
+};
+
+void pt3_qm_init_reg_param(struct pt3_qm *qm)
+{
+	memcpy(qm->reg, pt3_qm_reg_rw, sizeof(pt3_qm_reg_rw));
+
+	qm->adap->freq = 0;
+	qm->standby = false;
+	qm->wait_time_lpf = 20;
+	qm->wait_time_search_fast = 4;
+	qm->wait_time_search_normal = 15;
+}
+
+static int pt3_qm_write(struct pt3_qm *qm, struct pt3_bus *bus, u8 addr, u8 data)
+{
+	int ret = pt3_tc_write_tuner(qm->adap, bus, addr, &data, sizeof(data));
+	qm->reg[addr] = data;
+	return ret;
+}
+
+#define PT3_QM_INIT_DUMMY_RESET 0x0c
+
+void pt3_qm_dummy_reset(struct pt3_qm *qm, struct pt3_bus *bus)
+{
+	pt3_qm_write(qm, bus, 0x01, PT3_QM_INIT_DUMMY_RESET);
+	pt3_qm_write(qm, bus, 0x01, PT3_QM_INIT_DUMMY_RESET);
+}
+
+static void pt3_qm_sleep(struct pt3_bus *bus, u32 ms)
+{
+	if (bus)
+		pt3_bus_sleep(bus, ms);
+	else
+		msleep_interruptible(ms);
+}
+
+static int pt3_qm_read(struct pt3_qm *qm, struct pt3_bus *bus, u8 addr, u8 *data)
+{
+	int ret = 0;
+	if ((addr == 0x00) || (addr == 0x0d))
+		ret = pt3_tc_read_tuner(qm->adap, bus, addr, data);
+	return ret;
+}
+
+static u8 pt3_qm_flag[32] = {
+	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
+	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+};
+
+static int pt3_qm_set_sleep_mode(struct pt3_qm *qm, struct pt3_bus *bus)
+{
+	int ret;
+
+	if (qm->standby) {
+		qm->reg[0x01] &= (~(1 << 3)) & 0xff;
+		qm->reg[0x01] |= 1 << 0;
+		qm->reg[0x05] |= 1 << 3;
+
+		ret = pt3_qm_write(qm, bus, 0x05, qm->reg[0x05]);
+		if (ret)
+			return ret;
+		ret = pt3_qm_write(qm, bus, 0x01, qm->reg[0x01]);
+		if (ret)
+			return ret;
+	} else {
+		qm->reg[0x01] |= 1 << 3;
+		qm->reg[0x01] &= (~(1 << 0)) & 0xff;
+		qm->reg[0x05] &= (~(1 << 3)) & 0xff;
+
+		ret = pt3_qm_write(qm, bus, 0x01, qm->reg[0x01]);
+		if (ret)
+			return ret;
+		ret = pt3_qm_write(qm, bus, 0x05, qm->reg[0x05]);
+		if (ret)
+			return ret;
+	}
+	return ret;
+}
+
+static int pt3_qm_set_search_mode(struct pt3_qm *qm, struct pt3_bus *bus)
+{
+	qm->reg[3] &= 0xfe;
+	return pt3_qm_write(qm, bus, 0x03, qm->reg[3]);
+}
+
+int pt3_qm_init(struct pt3_qm *qm, struct pt3_bus *bus)
+{
+	u8 i_data;
+	u32 i;
+	int ret;
+
+	/* soft reset on */
+	ret = pt3_qm_write(qm, bus, 0x01, PT3_QM_INIT_DUMMY_RESET);
+	if (ret)
+		return ret;
+
+	pt3_qm_sleep(bus, 1);
+
+	/* soft reset off */
+	i_data = qm->reg[0x01] | 0x10;
+	ret = pt3_qm_write(qm, bus, 0x01, i_data);
+	if (ret)
+		return ret;
+
+	/* ID check */
+	ret = pt3_qm_read(qm, bus, 0x00, &i_data);
+	if (ret)
+		return ret;
+
+	if ((bus == NULL) && (i_data != 0x48))
+		return -EINVAL;
+
+	/* LPF tuning on */
+	pt3_qm_sleep(bus, 1);
+	qm->reg[0x0c] |= 0x40;
+	ret = pt3_qm_write(qm, bus, 0x0c, qm->reg[0x0c]);
+	if (ret)
+		return ret;
+	pt3_qm_sleep(bus, qm->wait_time_lpf);
+
+	for (i = 0; i < sizeof(pt3_qm_flag); i++)
+		if (pt3_qm_flag[i] == 1) {
+			ret = pt3_qm_write(qm, bus, i, qm->reg[i]);
+			if (ret)
+				return ret;
+		}
+	ret = pt3_qm_set_sleep_mode(qm, bus);
+	if (ret)
+		return ret;
+	return pt3_qm_set_search_mode(qm, bus);
+}
+
+int pt3_qm_tuner_init(struct pt3_i2c *i2c, struct pt3_adapter *adap)
+{
+	int ret;
+	struct pt3_bus *bus = vzalloc(sizeof(struct pt3_bus));
+
+	if (!bus)
+		return -ENOMEM;
+	pt3_qm_init_reg_param(adap->qm);
+	pt3_qm_dummy_reset(adap->qm, bus);
+	pt3_bus_end(bus);
+	ret = pt3_i2c_run(i2c, bus, true);
+	vfree(bus);
+	if (ret) {
+		pr_debug("fail pt3_qm_tuner_init dummy reset ret=%d\n", ret);
+		return ret;
+	}
+
+	bus = vzalloc(sizeof(struct pt3_bus));
+	if (!bus)
+		return -ENOMEM;
+	ret = pt3_qm_init(adap->qm, bus);
+	if (ret) {
+		vfree(bus);
+		return ret;
+	}
+	pt3_bus_end(bus);
+	ret = pt3_i2c_run(i2c, bus, true);
+	vfree(bus);
+	if (ret) {
+		pr_debug("fail pt3_qm_tuner_init qm init ret=%d\n", ret);
+		return ret;
+	}
+	return ret;
+}
+
+int pt3_qm_set_sleep(struct pt3_qm *qm, bool sleep)
+{
+	qm->standby = sleep;
+	if (sleep) {
+		int ret = pt3_tc_set_agc_s(qm->adap, PT3_TC_AGC_MANUAL);
+		if (ret)
+			return ret;
+		pt3_qm_set_sleep_mode(qm, NULL);
+		pt3_tc_set_sleep_s(qm->adap, NULL, sleep);
+	} else {
+		pt3_tc_set_sleep_s(qm->adap, NULL, sleep);
+		pt3_qm_set_sleep_mode(qm, NULL);
+	}
+	qm->adap->sleep = sleep;
+	return 0;
+}
+
+void pt3_qm_get_channel_freq(u32 channel, u32 *number, u32 *freq)
+{
+	if (channel < 12) {
+		*number = 1 + 2 * channel;
+		*freq = 104948 + 3836 * channel;
+	} else if (channel < 24) {
+		channel -= 12;
+		*number = 2 + 2 * channel;
+		*freq = 161300 + 4000 * channel;
+	} else {
+		channel -= 24;
+		*number = 1 + 2 * channel;
+		*freq = 159300 + 4000 * channel;
+	}
+}
+
+static u32 PT3_QM_FREQ_TABLE[9][3] = {
+	{ 2151000, 1, 7 },
+	{ 1950000, 1, 6 },
+	{ 1800000, 1, 5 },
+	{ 1600000, 1, 4 },
+	{ 1450000, 1, 3 },
+	{ 1250000, 1, 2 },
+	{ 1200000, 0, 7 },
+	{  975000, 0, 6 },
+	{  950000, 0, 0 }
+};
+
+static u32 SD_TABLE[24][2][3] = {
+	{{0x38fae1, 0x0d, 0x5}, {0x39fae1, 0x0d, 0x5},},
+	{{0x3f570a, 0x0e, 0x3}, {0x00570a, 0x0e, 0x3},},
+	{{0x05b333, 0x0e, 0x5}, {0x06b333, 0x0e, 0x5},},
+	{{0x3c0f5c, 0x0f, 0x4}, {0x3d0f5c, 0x0f, 0x4},},
+	{{0x026b85, 0x0f, 0x6}, {0x036b85, 0x0f, 0x6},},
+	{{0x38c7ae, 0x10, 0x5}, {0x39c7ae, 0x10, 0x5},},
+	{{0x3f23d7, 0x11, 0x3}, {0x0023d7, 0x11, 0x3},},
+	{{0x058000, 0x11, 0x5}, {0x068000, 0x11, 0x5},},
+	{{0x3bdc28, 0x12, 0x4}, {0x3cdc28, 0x12, 0x4},},
+	{{0x023851, 0x12, 0x6}, {0x033851, 0x12, 0x6},},
+	{{0x38947a, 0x13, 0x5}, {0x39947a, 0x13, 0x5},},
+	{{0x3ef0a3, 0x14, 0x3}, {0x3ff0a3, 0x14, 0x3},},
+	{{0x3c8000, 0x16, 0x4}, {0x3d8000, 0x16, 0x4},},
+	{{0x048000, 0x16, 0x6}, {0x058000, 0x16, 0x6},},
+	{{0x3c8000, 0x17, 0x5}, {0x3d8000, 0x17, 0x5},},
+	{{0x048000, 0x18, 0x3}, {0x058000, 0x18, 0x3},},
+	{{0x3c8000, 0x18, 0x6}, {0x3d8000, 0x18, 0x6},},
+	{{0x048000, 0x19, 0x4}, {0x058000, 0x19, 0x4},},
+	{{0x3c8000, 0x1a, 0x3}, {0x3d8000, 0x1a, 0x3},},
+	{{0x048000, 0x1a, 0x5}, {0x058000, 0x1a, 0x5},},
+	{{0x3c8000, 0x1b, 0x4}, {0x3d8000, 0x1b, 0x4},},
+	{{0x048000, 0x1b, 0x6}, {0x058000, 0x1b, 0x6},},
+	{{0x3c8000, 0x1c, 0x5}, {0x3d8000, 0x1c, 0x5},},
+	{{0x048000, 0x1d, 0x3}, {0x058000, 0x1d, 0x3},},
+};
+
+static int pt3_qm_tuning(struct pt3_qm *qm, struct pt3_bus *bus, u32 *sd, u32 channel)
+{
+	int ret;
+	struct pt3_adapter *adap = qm->adap;
+	u8 i_data;
+	u32 index, i, N, A;
+
+	qm->reg[0x08] &= 0xf0;
+	qm->reg[0x08] |= 0x09;
+
+	qm->reg[0x13] &= 0x9f;
+	qm->reg[0x13] |= 0x20;
+
+	for (i = 0; i < 8; i++) {
+		if ((PT3_QM_FREQ_TABLE[i+1][0] <= adap->freq) && (adap->freq < PT3_QM_FREQ_TABLE[i][0])) {
+			i_data = qm->reg[0x02];
+			i_data &= 0x0f;
+			i_data |= PT3_QM_FREQ_TABLE[i][1] << 7;
+			i_data |= PT3_QM_FREQ_TABLE[i][2] << 4;
+			pt3_qm_write(qm, bus, 0x02, i_data);
+		}
+	}
+
+	index = pt3_tc_index(qm->adap);
+	*sd = SD_TABLE[channel][index][0];
+	N = SD_TABLE[channel][index][1];
+	A = SD_TABLE[channel][index][2];
+
+	qm->reg[0x06] &= 0x40;
+	qm->reg[0x06] |= N;
+	ret = pt3_qm_write(qm, bus, 0x06, qm->reg[0x06]);
+	if (ret)
+		return ret;
+
+	qm->reg[0x07] &= 0xf0;
+	qm->reg[0x07] |= A & 0x0f;
+	return pt3_qm_write(qm, bus, 0x07, qm->reg[0x07]);
+}
+
+static int pt3_qm_local_lpf_tuning(struct pt3_qm *qm, struct pt3_bus *bus, int lpf, u32 channel)
+{
+	u8 i_data;
+	u32 sd = 0;
+	int ret = pt3_qm_tuning(qm, bus, &sd, channel);
+
+	if (ret)
+		return ret;
+	if (lpf) {
+		i_data = qm->reg[0x08] & 0xf0;
+		i_data |= 2;
+		ret = pt3_qm_write(qm, bus, 0x08, i_data);
+	} else
+		ret = pt3_qm_write(qm, bus, 0x08, qm->reg[0x08]);
+	if (ret)
+		return ret;
+
+	qm->reg[0x09] &= 0xc0;
+	qm->reg[0x09] |= (sd >> 16) & 0x3f;
+	qm->reg[0x0a] = (sd >> 8) & 0xff;
+	qm->reg[0x0b] = (sd >> 0) & 0xff;
+	ret = pt3_qm_write(qm, bus, 0x09, qm->reg[0x09]);
+	if (ret)
+		return ret;
+	ret = pt3_qm_write(qm, bus, 0x0a, qm->reg[0x0a]);
+	if (ret)
+		return ret;
+	ret = pt3_qm_write(qm, bus, 0x0b, qm->reg[0x0b]);
+	if (ret)
+		return ret;
+
+	if (lpf) {
+		i_data = qm->reg[0x0c];
+		i_data &= 0x3f;
+		ret = pt3_qm_write(qm, bus, 0x0c, i_data);
+		if (ret)
+			return ret;
+		pt3_qm_sleep(bus, 1);
+
+		i_data = qm->reg[0x0c];
+		i_data |= 0xc0;
+		ret = pt3_qm_write(qm, bus, 0x0c, i_data);
+		if (ret)
+			return ret;
+		pt3_qm_sleep(bus, qm->wait_time_lpf);
+		ret = pt3_qm_write(qm, bus, 0x08, 0x09);
+		if (ret)
+			return ret;
+		ret = pt3_qm_write(qm, bus, 0x13, qm->reg[0x13]);
+		if (ret)
+			return ret;
+	} else {
+		ret = pt3_qm_write(qm, bus, 0x13, qm->reg[0x13]);
+		if (ret)
+			return ret;
+		i_data = qm->reg[0x0c];
+		i_data &= 0x7f;
+		ret = pt3_qm_write(qm, bus, 0x0c, i_data);
+		if (ret)
+			return ret;
+		pt3_qm_sleep(bus, 2);
+
+		i_data = qm->reg[0x0c];
+		i_data |= 0x80;
+		ret = pt3_qm_write(qm, bus, 0x0c, i_data);
+		if (ret)
+			return ret;
+		if (qm->reg[0x03] & 0x01)
+			pt3_qm_sleep(bus, qm->wait_time_search_fast);
+		else
+			pt3_qm_sleep(bus, qm->wait_time_search_normal);
+	}
+	return ret;
+}
+
+int pt3_qm_get_locked(struct pt3_qm *qm, bool *locked)
+{
+	int ret = pt3_qm_read(qm, NULL, 0x0d, &qm->reg[0x0d]);
+	if (ret)
+		return ret;
+	if (qm->reg[0x0d] & 0x40)
+		*locked = true;
+	else
+		*locked = false;
+	return ret;
+}
+
+int pt3_qm_set_frequency(struct pt3_qm *qm, u32 channel)
+{
+	u32 number, freq, freq_kHz;
+	struct timeval begin, now;
+	bool locked;
+	int ret = pt3_tc_set_agc_s(qm->adap, PT3_TC_AGC_MANUAL);
+	if (ret)
+		return ret;
+
+	pt3_qm_get_channel_freq(channel, &number, &freq);
+	freq_kHz = freq * 10;
+	if (pt3_tc_index(qm->adap) == 0)
+		freq_kHz -= 500;
+	else
+		freq_kHz += 500;
+	qm->adap->freq = freq_kHz;
+	pr_debug("#%d ch %d freq %d kHz\n", qm->adap->idx, channel, freq_kHz);
+
+	ret = pt3_qm_local_lpf_tuning(qm, NULL, 1, channel);
+	if (ret)
+		return ret;
+	do_gettimeofday(&begin);
+	while (1) {
+		do_gettimeofday(&now);
+		ret = pt3_qm_get_locked(qm, &locked);
+		if (ret)
+			return ret;
+		if (locked)
+			break;
+		if (pt3_tc_time_diff(&begin, &now) >= 100)
+			break;
+		msleep_interruptible(1);
+	}
+	pr_debug("#%d qm_get_locked %d ret=0x%x\n", qm->adap->idx, locked, ret);
+	if (!locked)
+		return -ETIMEDOUT;
+
+	ret = pt3_tc_set_agc_s(qm->adap, PT3_TC_AGC_AUTO);
+	if (!ret) {
+		qm->adap->channel = channel;
+		qm->adap->offset = 0;
+	}
+	return ret;
+}
+
+static struct {
+	u32	freq;		/* Channel center frequency @ kHz	*/
+	u32	freq_th;	/* Offset frequency threshold @ kHz	*/
+	u8	shf_val;	/* Spur shift value			*/
+	u8	shf_dir;	/* Spur shift direction			*/
+} SHF_DVBT_TAB[] = {
+	{  64500, 500, 0x92, 0x07 },
+	{ 191500, 300, 0xE2, 0x07 },
+	{ 205500, 500, 0x2C, 0x04 },
+	{ 212500, 500, 0x1E, 0x04 },
+	{ 226500, 500, 0xD4, 0x07 },
+	{  99143, 500, 0x9C, 0x07 },
+	{ 173143, 500, 0xD4, 0x07 },
+	{ 191143, 300, 0xD4, 0x07 },
+	{ 207143, 500, 0xCE, 0x07 },
+	{ 225143, 500, 0xCE, 0x07 },
+	{ 243143, 500, 0xD4, 0x07 },
+	{ 261143, 500, 0xD4, 0x07 },
+	{ 291143, 500, 0xD4, 0x07 },
+	{ 339143, 500, 0x2C, 0x04 },
+	{ 117143, 500, 0x7A, 0x07 },
+	{ 135143, 300, 0x7A, 0x07 },
+	{ 153143, 500, 0x01, 0x07 }
+};
+
+static void pt3_mx_rftune(u8 *data, u32 *size, u32 freq)
+{
+	u32 dig_rf_freq, tmp, frac_divider, kHz, MHz, i;
+	u8 rf_data[] = {
+		0x13, 0x00,	/* abort tune			*/
+		0x3B, 0xC0,
+		0x3B, 0x80,
+		0x10, 0x95,	/* BW				*/
+		0x1A, 0x05,
+		0x61, 0x00,
+		0x62, 0xA0,
+		0x11, 0x40,	/* 2 bytes to store RF freq.	*/
+		0x12, 0x0E,	/* 2 bytes to store RF freq.	*/
+		0x13, 0x01	/* start tune			*/
+	};
+
+	dig_rf_freq = 0;
+	tmp = 0;
+	frac_divider = 1000000;
+	kHz = 1000;
+	MHz = 1000000;
+
+	dig_rf_freq = freq / MHz;
+	tmp = freq % MHz;
+
+	for (i = 0; i < 6; i++) {
+		dig_rf_freq <<= 1;
+		frac_divider /= 2;
+		if (tmp > frac_divider) {
+			tmp -= frac_divider;
+			dig_rf_freq++;
+		}
+	}
+	if (tmp > 7812)
+		dig_rf_freq++;
+
+	rf_data[2 * (7) + 1] = (u8)(dig_rf_freq);
+	rf_data[2 * (8) + 1] = (u8)(dig_rf_freq >> 8);
+
+	for (i = 0; i < sizeof(SHF_DVBT_TAB)/sizeof(*SHF_DVBT_TAB); i++) {
+		if ((freq >= (SHF_DVBT_TAB[i].freq - SHF_DVBT_TAB[i].freq_th) * kHz) &&
+				(freq <= (SHF_DVBT_TAB[i].freq + SHF_DVBT_TAB[i].freq_th) * kHz)) {
+			rf_data[2 * (5) + 1] = SHF_DVBT_TAB[i].shf_val;
+			rf_data[2 * (6) + 1] = 0xa0 | SHF_DVBT_TAB[i].shf_dir;
+			break;
+		}
+	}
+	memcpy(data, rf_data, sizeof(rf_data));
+	*size = sizeof(rf_data);
+
+	pr_debug("mx_rftune freq=%d\n", freq);
+}
+
+static void pt3_mx_write(struct pt3_adapter *adap, struct pt3_bus *bus, u8 *data, size_t size)
+{
+	pt3_tc_write_tuner_without_addr(adap, bus, data, size);
+}
+
+static void pt3_mx_standby(struct pt3_adapter *adap)
+{
+	u8 data[4] = {0x01, 0x00, 0x13, 0x00};
+	pt3_mx_write(adap, NULL, data, sizeof(data));
+}
+
+static void pt3_mx_set_register(struct pt3_adapter *adap, struct pt3_bus *bus, u8 addr, u8 value)
+{
+	u8 data[2] = {addr, value};
+	pt3_mx_write(adap, bus, data, sizeof(data));
+}
+
+static void pt3_mx_idac_setting(struct pt3_adapter *adap, struct pt3_bus *bus)
+{
+	u8 data[] = {
+		0x0D, 0x00,
+		0x0C, 0x67,
+		0x6F, 0x89,
+		0x70, 0x0C,
+		0x6F, 0x8A,
+		0x70, 0x0E,
+		0x6F, 0x8B,
+		0x70, 0x10+12,
+	};
+	pt3_mx_write(adap, bus, data, sizeof(data));
+}
+
+static void pt3_mx_tuner_rftune(struct pt3_adapter *adap, struct pt3_bus *bus, u32 freq)
+{
+	u8 data[100];
+	u32 size;
+
+	size = 0;
+	adap->freq = freq;
+	pt3_mx_rftune(data, &size, freq);
+	if (size != 20) {
+		pr_debug("fail mx_rftune size = %d\n", size);
+		return;
+	}
+	pt3_mx_write(adap, bus, data, 14);
+	msleep_interruptible(1);
+	pt3_mx_write(adap, bus, data + 14, 6);
+	msleep_interruptible(1);
+	pt3_mx_set_register(adap, bus, 0x1a, 0x0d);
+	pt3_mx_idac_setting(adap, bus);
+}
+
+static void pt3_mx_wakeup(struct pt3_adapter *adap)
+{
+	u8 data[2] = {0x01, 0x01};
+
+	pt3_mx_write(adap, NULL, data, sizeof(data));
+	pt3_mx_tuner_rftune(adap, NULL, adap->freq);
+}
+
+static void pt3_mx_set_sleep_mode(struct pt3_adapter *adap, bool sleep)
+{
+	if (sleep)
+		pt3_mx_standby(adap);
+	else
+		pt3_mx_wakeup(adap);
+}
+
+int pt3_mx_set_sleep(struct pt3_adapter *adap, bool sleep)
+{
+	int ret;
+
+	if (sleep) {
+		ret = pt3_tc_set_agc_t(adap, PT3_TC_AGC_MANUAL);
+		if (ret)
+			return ret;
+		pt3_mx_set_sleep_mode(adap, sleep);
+		pt3_tc_write_sleep_time(adap, sleep);
+	} else {
+		pt3_tc_write_sleep_time(adap, sleep);
+		pt3_mx_set_sleep_mode(adap, sleep);
+	}
+	adap->sleep = sleep;
+	return 0;
+}
+
+static u8 PT3_MX_FREQ_TABLE[][3] = {
+	{   2, 0,  3 },
+	{  12, 1, 22 },
+	{  21, 0, 12 },
+	{  62, 1, 63 },
+	{ 112, 0, 62 }
+};
+
+void pt3_mx_get_channel_frequency(struct pt3_adapter *adap, u32 channel, bool *catv, u32 *number, u32 *freq)
+{
+	u32 i;
+	s32 freq_offset = 0;
+
+	if (12 <= channel)
+		freq_offset += 2;
+	if (17 <= channel)
+		freq_offset -= 2;
+	if (63 <= channel)
+		freq_offset += 2;
+	*freq = 93 + channel * 6 + freq_offset;
+
+	for (i = 0; i < sizeof(PT3_MX_FREQ_TABLE) / sizeof(*PT3_MX_FREQ_TABLE); i++) {
+		if (channel <= PT3_MX_FREQ_TABLE[i][0]) {
+			*catv = PT3_MX_FREQ_TABLE[i][1] ? true : false;
+			*number = channel + PT3_MX_FREQ_TABLE[i][2] - PT3_MX_FREQ_TABLE[i][0];
+			break;
+		}
+	}
+}
+
+static u32 RF_TABLE[112] = {
+	0x058d3f49, 0x05e8ccc9, 0x06445a49, 0x069fe7c9, 0x06fb7549,
+	0x075702c9, 0x07b29049, 0x080e1dc9, 0x0869ab49, 0x08c538c9,
+	0x0920c649, 0x097c53c9, 0x09f665c9, 0x0a51f349, 0x0aad80c9,
+	0x0b090e49, 0x0b649bc9, 0x0ba1a4c9, 0x0bfd3249, 0x0c58bfc9,
+	0x0cb44d49, 0x0d0fdac9, 0x0d6b6849, 0x0dc6f5c9, 0x0e228349,
+	0x0e7e10c9, 0x0ed99e49, 0x0f352bc9, 0x0f90b949, 0x0fec46c9,
+	0x1047d449, 0x10a361c9, 0x10feef49, 0x115a7cc9, 0x11b60a49,
+	0x121197c9, 0x126d2549, 0x12c8b2c9, 0x13244049, 0x137fcdc9,
+	0x13db5b49, 0x1436e8c9, 0x14927649, 0x14ee03c9, 0x15499149,
+	0x15a51ec9, 0x1600ac49, 0x165c39c9, 0x16b7c749, 0x171354c9,
+	0x176ee249, 0x17ca6fc9, 0x1825fd49, 0x18818ac9, 0x18dd1849,
+	0x1938a5c9, 0x19943349, 0x19efc0c9, 0x1a4b4e49, 0x1aa6dbc9,
+	0x1b026949, 0x1b5df6c9, 0x1bb98449, 0x1c339649, 0x1c8f23c9,
+	0x1ceab149, 0x1d463ec9, 0x1da1cc49, 0x1dfd59c9, 0x1e58e749,
+	0x1eb474c9, 0x1f100249, 0x1f6b8fc9, 0x1fc71d49, 0x2022aac9,
+	0x207e3849, 0x20d9c5c9, 0x21355349, 0x2190e0c9, 0x21ec6e49,
+	0x2247fbc9, 0x22a38949, 0x22ff16c9, 0x235aa449, 0x23b631c9,
+	0x2411bf49, 0x246d4cc9, 0x24c8da49, 0x252467c9, 0x257ff549,
+	0x25db82c9, 0x26371049, 0x26929dc9, 0x26ee2b49, 0x2749b8c9,
+	0x27a54649, 0x2800d3c9, 0x285c6149, 0x28b7eec9, 0x29137c49,
+	0x296f09c9, 0x29ca9749, 0x2a2624c9, 0x2a81b249, 0x2add3fc9,
+	0x2b38cd49, 0x2b945ac9, 0x2befe849, 0x2c4b75c9, 0x2ca70349,
+	0x2d0290c9, 0x2d5e1e49,
+};
+
+static void pt3_mx_read(struct pt3_adapter *adap, struct pt3_bus *bus, u8 addr, u8 *data)
+{
+	u8 write[2] = {0xfb, addr};
+
+	pt3_tc_write_tuner_without_addr(adap, bus, write, sizeof(write));
+	pt3_tc_read_tuner_without_addr(adap, bus, data);
+}
+
+static void pt3_mx_rfsynth_lock_status(struct pt3_adapter *adap, struct pt3_bus *bus, bool *locked)
+{
+	u8 data;
+
+	*locked = false;
+	pt3_mx_read(adap, bus, 0x16, &data);
+	data &= 0x0c;
+	if (data == 0x0c)
+		*locked = true;
+}
+
+static void pt3_mx_refsynth_lock_status(struct pt3_adapter *adap, struct pt3_bus *bus, bool *locked)
+{
+	u8 data;
+
+	*locked = false;
+	pt3_mx_read(adap, bus, 0x16, &data);
+	data &= 0x03;
+	if (data == 0x03)
+		*locked = true;
+}
+
+bool pt3_mx_locked(struct pt3_adapter *adap)
+{
+	bool locked1 = false, locked2 = false;
+	struct timeval begin, now;
+
+	do_gettimeofday(&begin);
+	while (1) {
+		do_gettimeofday(&now);
+		pt3_mx_rfsynth_lock_status(adap, NULL, &locked1);
+		pt3_mx_refsynth_lock_status(adap, NULL, &locked2);
+		if (locked1 && locked2)
+			break;
+		if (pt3_tc_time_diff(&begin, &now) > 1000)
+			break;
+		msleep_interruptible(1);
+	}
+	pr_debug("#%d mx locked1=%d locked2=%d\n", adap->idx, locked1, locked2);
+	return locked1 && locked2;
+}
+
+int pt3_mx_set_frequency(struct pt3_adapter *adap, u32 channel, s32 offset)
+{
+	bool catv;
+	u32 number, freq, real_freq;
+	int ret = pt3_tc_set_agc_t(adap, PT3_TC_AGC_MANUAL);
+
+	if (ret)
+		return ret;
+	pt3_mx_get_channel_frequency(adap, channel, &catv, &number, &freq);
+	pr_debug("#%d ch%d%s no%d %dHz\n", adap->idx, channel, catv ? " CATV" : "", number, freq);
+	/* real_freq = (7 * freq + 1 + offset) * 1000000.0/7.0; */
+	real_freq = RF_TABLE[channel];
+
+	pt3_mx_tuner_rftune(adap, NULL, real_freq);
+
+	return (!pt3_mx_locked(adap)) ? -ETIMEDOUT : pt3_tc_set_agc_t(adap, PT3_TC_AGC_AUTO);
+}
+
+int pt3_fe_set_freq(struct pt3_adapter *adap, u32 channel, s32 offset)
+{
+	int ret;
+
+	pr_debug("#%d %s set_freq channel=%d offset=%d\n", adap->idx, adap->str, channel, offset);
+
+	if (adap->type == SYS_ISDBS)
+		ret = pt3_qm_set_frequency(adap->qm, channel);
+	else
+		ret = pt3_mx_set_frequency(adap, channel, offset);
+	return ret;
+}
+
+int pt3_fe_tuner_sleep(struct pt3_adapter *adap, bool sleep)
+{
+	int ret;
+
+	pr_debug("#%d %p %s %s\n", adap->idx, adap, adap->str, sleep ? "Sleep" : "Wakeup");
+
+	if (adap->type == SYS_ISDBS)
+		ret = pt3_qm_set_sleep(adap->qm, sleep);
+	else
+		ret = pt3_mx_set_sleep(adap, sleep);
+	msleep_interruptible(10);
+	return ret;
+}
+
+/**** ISDB-S ****/
+
+enum pt3_fe_s_tune_state {
+	PT3S_IDLE,
+	PT3S_SET_FREQUENCY,
+	PT3S_SET_MODULATION,
+	PT3S_CHECK_MODULATION,
+	PT3S_SET_TS_ID,
+	PT3S_CHECK_TS_ID,
+	PT3S_TRACK,
+};
+
+struct pt3_fe_s_state {
+	struct pt3_adapter *adap;
+	struct dvb_frontend fe;
+	enum pt3_fe_s_tune_state tune_state;
+};
+
+static int pt3_fe_s_read_signal_strength(struct dvb_frontend *fe, u16 *cn)
+{
+	struct pt3_fe_s_state *state = fe->demodulator_priv;
+	return pt3_tc_read_cn_s(state->adap, NULL, (u32 *)cn);
+}
+
+static int pt3_fe_s_get_frontend_algo(struct dvb_frontend *fe)
+{
+	return DVBFE_ALGO_HW;
+}
+
+static void pt3_fe_s_release(struct dvb_frontend *fe)
+{
+	kfree(fe->demodulator_priv);
+}
+
+static int pt3_fe_s_init(struct dvb_frontend *fe)
+{
+	struct pt3_fe_s_state *state = fe->demodulator_priv;
+	state->tune_state = PT3S_IDLE;
+	return pt3_qm_set_sleep(state->adap->qm, false);
+}
+
+static int pt3_fe_s_sleep(struct dvb_frontend *fe)
+{
+	struct pt3_fe_s_state *state = fe->demodulator_priv;
+	return pt3_qm_set_sleep(state->adap->qm, true);
+}
+
+u32 pt3_fe_s_get_channel(u32 frequency)
+{
+	u32 freq = frequency / 10,
+	    ch0 = (freq - 104948) / 3836, diff0 = freq - (104948 + 3836 * ch0),
+	    ch1 = (freq - 161300) / 4000, diff1 = freq - (161300 + 4000 * ch1),
+	    ch2 = (freq - 159300) / 4000, diff2 = freq - (159300 + 4000 * ch2),
+	    min = diff0 < diff1 ? diff0 : diff1;
+
+	if (diff2 < min)
+		return ch2 + 24;
+	else if (min == diff1)
+		return ch1 + 12;
+	else
+		return ch0;
+}
+
+static int pt3_fe_s_read_status(struct dvb_frontend *fe, fe_status_t *status)
+{
+	struct pt3_fe_s_state *state = fe->demodulator_priv;
+
+	switch (state->tune_state) {
+	case PT3S_IDLE:
+	case PT3S_SET_FREQUENCY:
+		*status = 0;
+		return 0;
+
+	case PT3S_SET_MODULATION:
+	case PT3S_CHECK_MODULATION:
+		*status |= FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3S_SET_TS_ID:
+	case PT3S_CHECK_TS_ID:
+		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER;
+		return 0;
+
+	case PT3S_TRACK:
+		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+	}
+	BUG();
+}
+
+static int pt3_fe_s_tune(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
+{
+	struct pt3_fe_s_state *state = fe->demodulator_priv;
+	struct pt3_adapter *adap = state->adap;
+	struct tmcc_s *tmcc = &adap->qm->tmcc;
+	int i, ret,
+	    freq = state->fe.dtv_property_cache.frequency,
+	    tsid = state->fe.dtv_property_cache.stream_id,
+	    ch = (freq < 1024) ? freq : pt3_fe_s_get_channel(freq);	/* consider as channel ID if low */
+
+	if (re_tune)
+		state->tune_state = PT3S_SET_FREQUENCY;
+
+	switch (state->tune_state) {
+	case PT3S_IDLE:
+		*delay = 3 * HZ;
+		*status = 0;
+		return 0;
+
+	case PT3S_SET_FREQUENCY:
+		pr_debug("#%d freq %d tsid 0x%x ch %d\n", adap->idx, freq, tsid, ch);
+		ret = pt3_qm_set_frequency(adap->qm, ch);
+		if (ret)
+			return ret;
+		adap->channel = ch;
+		state->tune_state = PT3S_SET_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3S_SET_MODULATION:
+		for (i = 0; i < 1000; i++) {
+			ret = pt3_tc_read_tmcc_s(adap, NULL, tmcc);
+			if (!ret)
+				break;
+			msleep_interruptible(1);
+		}
+		if (ret) {
+			pr_debug("fail tc_read_tmcc_s ret=0x%x\n", ret);
+			return ret;
+		}
+		pr_debug("slots=%d,%d,%d,%d mode=%d,%d,%d,%d\n",
+				tmcc->slot[0], tmcc->slot[1], tmcc->slot[2], tmcc->slot[3],
+				tmcc->mode[0], tmcc->mode[1], tmcc->mode[2], tmcc->mode[3]);
+		state->tune_state = PT3S_CHECK_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3S_CHECK_MODULATION:
+		pr_debug("tmcc->id=0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x\n",
+				tmcc->id[0], tmcc->id[1], tmcc->id[2], tmcc->id[3],
+				tmcc->id[4], tmcc->id[5], tmcc->id[6], tmcc->id[7]);
+		for (i = 0; i < sizeof(tmcc->id)/sizeof(tmcc->id[0]); i++) {
+			pr_debug("tsid %x i %d tmcc->id %x\n", tsid, i, tmcc->id[i]);
+			if (tmcc->id[i] == tsid)
+				break;
+		}
+		if (tsid < sizeof(tmcc->id)/sizeof(tmcc->id[0]))	/* consider as slot# */
+			i = tsid;
+		if (i == sizeof(tmcc->id)/sizeof(tmcc->id[0])) {
+			pr_debug("#%d i%d tsid 0x%x not found\n", adap->idx, i, tsid);
+			return -EINVAL;
+		}
+		adap->offset = i;
+		pr_debug("#%d found tsid 0x%x on slot %d\n", adap->idx, tsid, i);
+		state->tune_state = PT3S_SET_TS_ID;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER;
+		return 0;
+
+	case PT3S_SET_TS_ID:
+		ret = pt3_tc_write_id_s(adap, NULL, (u16)tmcc->id[adap->offset]);
+		if (ret) {
+			pr_debug("fail set_tmcc_s ret=%d\n", ret);
+			return ret;
+		}
+		state->tune_state = PT3S_CHECK_TS_ID;
+		return 0;
+
+	case PT3S_CHECK_TS_ID:
+		for (i = 0; i < 1000; i++) {
+			u16 short_id;
+			ret = pt3_tc_read_id_s(adap, NULL, &short_id);
+			if (ret) {
+				pr_debug("fail get_id_s ret=%d\n", ret);
+				return ret;
+			}
+			tsid = short_id;
+			pr_debug("#%d tsid=0x%x\n", adap->idx, tsid);
+			if ((tsid & 0xffff) == tmcc->id[adap->offset])
+				break;
+			msleep_interruptible(1);
+		}
+		state->tune_state = PT3S_TRACK;
+
+	case PT3S_TRACK:
+		*delay = 3 * HZ;
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+	}
+	BUG();
+}
+
+static struct dvb_frontend_ops pt3_fe_s_ops = {
+	.delsys = { SYS_ISDBS },
+	.info = {
+		.name = "PT3 ISDB-S",
+		.frequency_min = 1,
+		.frequency_max = 2150000,
+		.frequency_stepsize = 1000,
+		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO | FE_CAN_MULTISTREAM |
+			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
+	},
+	.read_signal_strength = pt3_fe_s_read_signal_strength,
+	.read_status = pt3_fe_s_read_status,
+	.get_frontend_algo = pt3_fe_s_get_frontend_algo,
+	.release = pt3_fe_s_release,
+	.init = pt3_fe_s_init,
+	.sleep = pt3_fe_s_sleep,
+	.tune = pt3_fe_s_tune,
+};
+
+struct dvb_frontend *pt3_fe_s_attach(struct pt3_adapter *adap)
+{
+	struct dvb_frontend *fe;
+	struct pt3_fe_s_state *state = kzalloc(sizeof(struct pt3_fe_s_state), GFP_KERNEL);
+
+	if (!state)
+		return NULL;
+	state->adap = adap;
+	fe = &state->fe;
+	memcpy(&fe->ops, &pt3_fe_s_ops, sizeof(struct dvb_frontend_ops));
+	fe->demodulator_priv = state;
+	return fe;
+}
+
+/**** ISDB-T ****/
+
+enum pt3_fe_t_tune_state {
+	PT3T_IDLE,
+	PT3T_SET_FREQUENCY,
+	PT3T_CHECK_FREQUENCY,
+	PT3T_SET_MODULATION,
+	PT3T_CHECK_MODULATION,
+	PT3T_TRACK,
+	PT3T_ABORT,
+};
+
+struct pt3_fe_t_state {
+	struct pt3_adapter *adap;
+	struct dvb_frontend fe;
+	enum pt3_fe_t_tune_state tune_state;
+};
+
+static int pt3_fe_t_read_signal_strength(struct dvb_frontend *fe, u16 *cn)
+{
+	struct pt3_fe_t_state *state = fe->demodulator_priv;
+	return pt3_tc_read_cndat_t(state->adap, NULL, (u32 *)cn);
+}
+
+static int pt3_fe_t_get_frontend_algo(struct dvb_frontend *fe)
+{
+	return DVBFE_ALGO_HW;
+}
+
+static void pt3_fe_t_release(struct dvb_frontend *fe)
+{
+	kfree(fe->demodulator_priv);
+}
+
+static int pt3_fe_t_init(struct dvb_frontend *fe)
+{
+	struct pt3_fe_t_state *state = fe->demodulator_priv;
+	state->tune_state = PT3T_IDLE;
+	return pt3_mx_set_sleep(state->adap, false);
+}
+
+static int pt3_fe_t_sleep(struct dvb_frontend *fe)
+{
+	struct pt3_fe_t_state *state = fe->demodulator_priv;
+	return pt3_mx_set_sleep(state->adap, true);
+}
+
+static int pt3_fe_t_get_tmcc(struct pt3_adapter *adap, struct tmcc_t *tmcc)
+{
+	int b = 0, retryov, tmunvld, fulock;
+
+	if (unlikely(!tmcc))
+		return -EINVAL;
+	while (1) {
+		pt3_tc_read_retryov_tmunvld_fulock(adap, NULL, &retryov, &tmunvld, &fulock);
+		if (!fulock) {
+			b = 1;
+			break;
+		} else {
+			if (retryov)
+				break;
+		}
+		msleep_interruptible(1);
+	}
+	if (likely(b))
+		pt3_tc_read_tmcc_t(adap, NULL, tmcc);
+	return b ? 0 : -EBADMSG;
+}
+
+static int pt3_fe_t_read_status(struct dvb_frontend *fe, fe_status_t *status)
+{
+	struct pt3_fe_t_state *state = fe->demodulator_priv;
+
+	switch (state->tune_state) {
+	case PT3T_IDLE:
+	case PT3T_SET_FREQUENCY:
+	case PT3T_CHECK_FREQUENCY:
+		*status = 0;
+		return 0;
+
+	case PT3T_SET_MODULATION:
+	case PT3T_CHECK_MODULATION:
+	case PT3T_ABORT:
+		*status |= FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3T_TRACK:
+		*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+	}
+	BUG();
+}
+
+#define NHK (RF_TABLE[77])
+int pt3_fe_t_freq(int freq)
+{
+	if (freq >= 90000000)
+		return freq;				/* real_freq	*/
+	if (freq > 255)
+		return NHK;
+	if (freq > 127)
+		return RF_TABLE[freq - 128];		/* freqno (IO#)	*/
+	if (freq > 63) {				/* CATV		*/
+		freq -= 64;
+		if (freq > 22)
+			return RF_TABLE[freq - 1];	/* C23-C62	*/
+		if (freq > 12)
+			return RF_TABLE[freq - 10];	/* C13-C22	*/
+		return NHK;
+	}
+	if (freq > 62)
+		return NHK;
+	if (freq > 12)
+		return RF_TABLE[freq + 50];		/* 13-62	*/
+	if (freq >  3)
+		return RF_TABLE[freq +  9];		/*  4-12	*/
+	if (freq)
+		return RF_TABLE[freq -  1];		/*  1-3		*/
+	return NHK;
+}
+
+static int pt3_fe_t_tune(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
+{
+	struct pt3_fe_t_state *state = fe->demodulator_priv;
+	struct tmcc_t tmcc_t;
+	int ret, i;
+
+	if (re_tune)
+		state->tune_state = PT3T_SET_FREQUENCY;
+
+	switch (state->tune_state) {
+	case PT3T_IDLE:
+		*delay = 3 * HZ;
+		*status = 0;
+		return 0;
+
+	case PT3T_SET_FREQUENCY:
+		ret = pt3_tc_set_agc_t(state->adap, PT3_TC_AGC_MANUAL);
+		if (ret)
+			return ret;
+		pt3_mx_tuner_rftune(state->adap, NULL, pt3_fe_t_freq(state->fe.dtv_property_cache.frequency));
+		state->tune_state = PT3T_CHECK_FREQUENCY;
+		*delay = 0;
+		*status = 0;
+		return 0;
+
+	case PT3T_CHECK_FREQUENCY:
+		if (!pt3_mx_locked(state->adap)) {
+			*delay = HZ;
+			*status = 0;
+			return 0;
+		}
+		state->tune_state = PT3T_SET_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3T_SET_MODULATION:
+		ret = pt3_tc_set_agc_t(state->adap, PT3_TC_AGC_AUTO);
+		if (ret)
+			return ret;
+		state->tune_state = PT3T_CHECK_MODULATION;
+		*delay = 0;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+
+	case PT3T_CHECK_MODULATION:
+		for (i = 0; i < 1000; i++) {
+			ret = pt3_fe_t_get_tmcc(state->adap, &tmcc_t);
+			if (!ret)
+				break;
+			msleep_interruptible(2);
+		}
+		if (ret) {
+			pr_debug("#%d fail get_tmcc_t ret=%d\n", state->adap->idx, ret);
+				state->tune_state = PT3T_ABORT;
+				*delay = HZ;
+				return 0;
+		}
+		state->tune_state = PT3T_TRACK;
+
+	case PT3T_TRACK:
+		*delay = 3 * HZ;
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_LOCK;
+		return 0;
+
+	case PT3T_ABORT:
+		*delay = 3 * HZ;
+		*status = FE_HAS_SIGNAL;
+		return 0;
+	}
+	BUG();
+}
+
+static struct dvb_frontend_ops pt3_fe_t_ops = {
+	.delsys = { SYS_ISDBT },
+	.info = {
+		.name = "PT3 ISDB-T",
+		.frequency_min = 1,
+		.frequency_max = 770000000,
+		.frequency_stepsize = 142857,
+		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO | FE_CAN_QAM_AUTO |
+			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
+	},
+	.read_signal_strength = pt3_fe_t_read_signal_strength,
+	.read_status = pt3_fe_t_read_status,
+	.get_frontend_algo = pt3_fe_t_get_frontend_algo,
+	.release = pt3_fe_t_release,
+	.init = pt3_fe_t_init,
+	.sleep = pt3_fe_t_sleep,
+	.tune = pt3_fe_t_tune,
+};
+
+struct dvb_frontend *pt3_fe_t_attach(struct pt3_adapter *adap)
+{
+	struct dvb_frontend *fe;
+	struct pt3_fe_t_state *state = kzalloc(sizeof(struct pt3_fe_t_state), GFP_KERNEL);
+	if (!state)
+		return NULL;
+	state->adap = adap;
+	fe = &state->fe;
+	memcpy(&fe->ops, &pt3_fe_t_ops, sizeof(struct dvb_frontend_ops));
+	fe->demodulator_priv = state;
+	return fe;
+}
+
diff --git a/drivers/media/pci/pt3_dvb/pt3_i2c.c b/drivers/media/pci/pt3_dvb/pt3_i2c.c
new file mode 100644
index 0000000..d320b70
--- /dev/null
+++ b/drivers/media/pci/pt3_dvb/pt3_i2c.c
@@ -0,0 +1,64 @@
+#include "pt3.h"
+
+#define PT3_I2C_DATA_OFFSET 2048
+
+bool pt3_i2c_is_clean(struct pt3_i2c *i2c)
+{
+	return PT3_SHIFT_MASK(readl(i2c->reg[0] + REG_I2C_R), 3, 1);
+}
+
+void pt3_i2c_reset(struct pt3_i2c *i2c)
+{
+	writel(1 << 17, i2c->reg[0] + REG_I2C_W);	/* 0x00020000 */
+}
+
+static void pt3_i2c_wait(struct pt3_i2c *i2c, u32 *data)
+{
+	u32 val;
+
+	while (1) {
+		val = readl(i2c->reg[0] + REG_I2C_R);
+		if (!PT3_SHIFT_MASK(val, 0, 1))
+			break;
+		msleep_interruptible(1);
+	}
+	if (data)
+		*data = val;
+}
+
+void pt3_i2c_copy(struct pt3_i2c *i2c, struct pt3_bus *bus)
+{
+	u32 i;
+	u8 *src = &bus->cmds[0];
+	void __iomem *dst = i2c->reg[1] + PT3_I2C_DATA_OFFSET + (bus->cmd_addr / 2);
+
+	for (i = 0; i < bus->cmd_pos; i++)
+		writeb(src[i], dst + i);
+}
+
+int pt3_i2c_run(struct pt3_i2c *i2c, struct pt3_bus *bus, bool copy)
+{
+	int ret = 0;
+	u32 data, a, i, start_addr = bus->cmd_addr;
+
+	mutex_lock(&i2c->lock);
+	if (copy)
+		pt3_i2c_copy(i2c, bus);
+
+	pt3_i2c_wait(i2c, &data);
+	if (unlikely(start_addr >= (1 << 13)))
+		pr_debug("start address is over.\n");
+	writel(1 << 16 | start_addr, i2c->reg[0] + REG_I2C_W);
+	pt3_i2c_wait(i2c, &data);
+
+	a = PT3_SHIFT_MASK(data, 1, 2);
+	if (a) {
+		pr_debug("fail i2c run_code ret=0x%x\n", data);
+		ret = -EIO;
+	}
+	for (i = 0; i < bus->read_addr; i++)
+		pt3_bus_push_read_data(bus, readb(i2c->reg[1] + PT3_I2C_DATA_OFFSET + i));
+	mutex_unlock(&i2c->lock);
+	return ret;
+}
+
diff --git a/drivers/media/pci/pt3_dvb/pt3_tc.c b/drivers/media/pci/pt3_dvb/pt3_tc.c
new file mode 100644
index 0000000..a5fddd9
--- /dev/null
+++ b/drivers/media/pci/pt3_dvb/pt3_tc.c
@@ -0,0 +1,458 @@
+#include "pt3.h"
+
+int pt3_tc_write(struct pt3_adapter *adap, struct pt3_bus *bus, u8 addr, const u8 *data, u32 size)
+{
+	int ret = 0;
+	u8 buf;
+	struct pt3_bus *p = bus ? bus : vzalloc(sizeof(struct pt3_bus));
+
+	if (!p) {
+		pr_debug("out of memory.\n");
+		return -ENOMEM;
+	}
+
+	pt3_bus_start(p);
+	buf = adap->addr_tc << 1;
+	pt3_bus_write(p, &buf, 1);
+	pt3_bus_write(p, &addr, 1);
+	pt3_bus_write(p, data, size);
+	pt3_bus_stop(p);
+
+	if (!bus) {
+		pt3_bus_end(p);
+		ret = pt3_i2c_run(adap->pt3->i2c, p, true);
+		vfree(p);
+	}
+	return ret;
+}
+
+static int pt3_tc_write_pskmsrst(struct pt3_adapter *adap)
+{
+	u8 buf = 0x01;
+	return pt3_tc_write(adap, NULL, 0x03, &buf, 1);
+}
+
+static int pt3_tc_write_imsrst(struct pt3_adapter *adap)
+{
+	u8 buf = 0x01 << 6;
+	return pt3_tc_write(adap, NULL, 0x01, &buf, 1);
+}
+
+int pt3_tc_init(struct pt3_adapter *adap)
+{
+	u8 buf = 0x10;
+
+	pr_debug("#%d %s tuner=0x%x tc=0x%x\n", adap->idx, adap->str, adap->addr_tuner, adap->addr_tc);
+	if (adap->type == SYS_ISDBS) {
+		int ret = pt3_tc_write_pskmsrst(adap);
+		return ret ? ret : pt3_tc_write(adap, NULL, 0x1e, &buf, 1);
+	} else {
+		int ret = pt3_tc_write_imsrst(adap);
+		return ret ? ret : pt3_tc_write(adap, NULL, 0x1c, &buf, 1);
+	}
+}
+
+int pt3_tc_set_powers(struct pt3_adapter *adap, struct pt3_bus *bus, bool tuner, bool amp)
+{
+	u8	tuner_power = tuner ? 0x03 : 0x02,
+		amp_power = amp ? 0x03 : 0x02,
+		data = (tuner_power << 6) | (0x01 << 4) | (amp_power << 2) | 0x01 << 0;
+	pr_debug("#%d tuner %s amp %s\n", adap->idx, tuner ? "ON" : "OFF", amp ? "ON" : "OFF");
+	return pt3_tc_write(adap, bus, 0x1e, &data, 1);
+}
+
+int pt3_tc_set_ts_pins_mode(struct pt3_adapter *adap, struct pt3_ts_pins_mode *mode)
+{
+	u32	clock_data = mode->clock_data,
+		byte = mode->byte,
+		valid = mode->valid;
+
+	if (clock_data)
+		clock_data++;
+	if (byte)
+		byte++;
+	if (valid)
+		valid++;
+	if (adap->type == SYS_ISDBS) {
+		u8 data[2];
+		int ret;
+		data[0] = 0x15 | (valid << 6);
+		data[1] = 0x04 | (clock_data << 4) | byte;
+		return (ret = pt3_tc_write(adap, NULL, 0x1c, &data[0], 1)) ?
+			ret : pt3_tc_write(adap, NULL, 0x1f, &data[1], 1);
+	} else {
+		u8 data = (u8)(0x01 | (clock_data << 6) | (byte << 4) | (valid << 2));
+		return pt3_tc_write(adap, NULL, 0x1d, &data, 1);
+	}
+}
+
+#define PT3_TC_THROUGH 0xfe
+int pt3_tc_write_tuner(struct pt3_adapter *adap, struct pt3_bus *bus, u8 addr, const u8 *data, u32 size)
+{
+	int ret = 0;
+	u8 buf;
+	struct pt3_bus *p = bus ? bus : vzalloc(sizeof(struct pt3_bus));
+
+	if (!p) {
+		pr_debug("out of memory.\n");
+		return -ENOMEM;
+	}
+
+	pt3_bus_start(p);
+	buf = adap->addr_tc << 1;
+	pt3_bus_write(p, &buf, 1);
+	buf = PT3_TC_THROUGH;
+	pt3_bus_write(p, &buf, 1);
+	buf = adap->addr_tuner << 1;
+	pt3_bus_write(p, &buf, 1);
+	pt3_bus_write(p, &addr, 1);
+	pt3_bus_write(p, data, size);
+	pt3_bus_stop(p);
+
+	if (!bus) {
+		pt3_bus_end(p);
+		ret = pt3_i2c_run(adap->pt3->i2c, p, true);
+		vfree(p);
+	}
+	return ret;
+}
+
+int pt3_tc_read_tuner(struct pt3_adapter *adap, struct pt3_bus *bus, u8 addr, u8 *data)
+{
+	int ret = 0;
+	u8 buf;
+	size_t rindex;
+
+	struct pt3_bus *p = bus ? bus : vzalloc(sizeof(struct pt3_bus));
+	if (!p) {
+		pr_debug("#%d tc_read_tuner out of memory\n", adap->idx);
+		return -ENOMEM;
+	}
+
+	pt3_bus_start(p);
+	buf = adap->addr_tc << 1;
+	pt3_bus_write(p, &buf, 1);
+	buf = PT3_TC_THROUGH;
+	pt3_bus_write(p, &buf, 1);
+	buf = adap->addr_tuner << 1;
+	pt3_bus_write(p, &buf, 1);
+	pt3_bus_write(p, &addr, 1);
+
+	pt3_bus_start(p);
+	buf = adap->addr_tc << 1;
+	pt3_bus_write(p, &buf, 1);
+	buf = PT3_TC_THROUGH;
+	pt3_bus_write(p, &buf, 1);
+	buf = (adap->addr_tuner << 1) | 1;
+	pt3_bus_write(p, &buf, 1);
+
+	pt3_bus_start(p);
+	buf = (adap->addr_tc << 1) | 1;
+	pt3_bus_write(p, &buf, 1);
+	rindex = pt3_bus_read(p, &buf, 1);
+	pt3_bus_stop(p);
+
+	if (!bus) {
+		pt3_bus_end(p);
+		ret = pt3_i2c_run(adap->pt3->i2c, p, true);
+		data[0] = pt3_bus_data1(p, rindex);
+		vfree(p);
+	}
+	pr_debug("#%d read_tuner addr_tc=0x%x addr_tuner=0x%x\n",
+		   adap->idx, adap->addr_tc, adap->addr_tuner);
+	return ret;
+}
+
+static u8 agc_data_s[2] = { 0xb0, 0x30 };
+
+u32 pt3_tc_index(struct pt3_adapter *adap)
+{
+	return PT3_SHIFT_MASK(adap->addr_tc, 1, 1);
+}
+
+int pt3_tc_set_agc_s(struct pt3_adapter *adap, enum pt3_tc_agc agc)
+{
+	u8 data = (agc == PT3_TC_AGC_AUTO) ? 0xff : 0x00;
+	int ret = pt3_tc_write(adap, NULL, 0x0a, &data, 1);
+	if (ret)
+		return ret;
+
+	data = agc_data_s[pt3_tc_index(adap)];
+	data |= (agc == PT3_TC_AGC_AUTO) ? 0x01 : 0x00;
+	ret = pt3_tc_write(adap, NULL, 0x10, &data, 1);
+	if (ret)
+		return ret;
+
+	data = (agc == PT3_TC_AGC_AUTO) ? 0x40 : 0x00;
+	return (ret = pt3_tc_write(adap, NULL, 0x11, &data, 1)) ? ret : pt3_tc_write_pskmsrst(adap);
+}
+
+int pt3_tc_set_sleep_s(struct pt3_adapter *adap, struct pt3_bus *bus, bool sleep)
+{
+	u8 buf = sleep ? 1 : 0;
+	return pt3_tc_write(adap, bus, 0x17, &buf, 1);
+}
+
+int pt3_tc_set_agc_t(struct pt3_adapter *adap, enum pt3_tc_agc agc)
+{
+	u8 data = (agc == PT3_TC_AGC_AUTO) ? 0x40 : 0x00;
+	int ret = pt3_tc_write(adap, NULL, 0x25, &data, 1);
+	if (ret)
+		return ret;
+
+	data = 0x4c | ((agc == PT3_TC_AGC_AUTO) ? 0x00 : 0x01);
+	return (ret = pt3_tc_write(adap, NULL, 0x23, &data, 1)) ? ret : pt3_tc_write_imsrst(adap);
+}
+
+int pt3_tc_write_tuner_without_addr(struct pt3_adapter *adap, struct pt3_bus *bus, const u8 *data, u32 size)
+{
+	int ret = 0;
+	u8 buf;
+	struct pt3_bus *p = bus ? bus : vzalloc(sizeof(struct pt3_bus));
+	if (!p) {
+		pr_debug("out of memory.\n");
+		return -ENOMEM;
+	}
+
+	pt3_bus_start(p);
+	buf = adap->addr_tc << 1;
+	pt3_bus_write(p, &buf, 1);
+	buf = PT3_TC_THROUGH;
+	pt3_bus_write(p, &buf, 1);
+	buf = adap->addr_tuner << 1;
+	pt3_bus_write(p, &buf, 1);
+	pt3_bus_write(p, data, size);
+	pt3_bus_stop(p);
+
+	if (!bus) {
+		pt3_bus_end(p);
+		ret = pt3_i2c_run(adap->pt3->i2c, p, true);
+		vfree(p);
+	}
+	return ret;
+}
+
+int pt3_tc_write_sleep_time(struct pt3_adapter *adap, int sleep)
+{
+	u8 data = (1 << 7) | ((sleep ? 1 : 0) << 4);
+	return pt3_tc_write(adap, NULL, 0x03, &data, 1);
+}
+
+u32 pt3_tc_time_diff(struct timeval *st, struct timeval *et)
+{
+	u32 diff = ((et->tv_sec - st->tv_sec) * 1000000 + (et->tv_usec - st->tv_usec)) / 1000;
+	pr_debug("time diff = %d\n", diff);
+	return diff;
+}
+
+int pt3_tc_read_tuner_without_addr(struct pt3_adapter *adap, struct pt3_bus *bus, u8 *data)
+{
+	int ret = 0;
+	u8 buf;
+	u32 rindex;
+	struct pt3_bus *p = bus ? bus : vzalloc(sizeof(struct pt3_bus));
+
+	if (!p) {
+		pr_debug("out of memory.\n");
+		return -ENOMEM;
+	}
+
+	pt3_bus_start(p);
+	buf = adap->addr_tc << 1;
+	pt3_bus_write(p, &buf, 1);
+	buf = PT3_TC_THROUGH;
+	pt3_bus_write(p, &buf, 1);
+	buf = (adap->addr_tuner << 1) | 0x01;
+	pt3_bus_write(p, &buf, 1);
+
+	pt3_bus_start(p);
+	buf = (adap->addr_tc << 1) | 0x01;
+	pt3_bus_write(p, &buf, 1);
+	rindex = pt3_bus_read(p, &buf, 1);
+	pt3_bus_stop(p);
+
+	if (!bus) {
+		pt3_bus_end(p);
+		ret = pt3_i2c_run(adap->pt3->i2c, p, true);
+		data[0] = pt3_bus_data1(p, rindex);
+		vfree(p);
+	}
+	pr_debug("#%d read_tuner_without addr_tc=0x%x addr_tuner=0x%x\n",
+		adap->idx, adap->addr_tc, adap->addr_tuner);
+	return ret;
+}
+
+static int pt3_tc_read(struct pt3_adapter *adap, struct pt3_bus *bus, u8 addr, u8 *data, u32 size)
+{
+	int ret = 0;
+	u8 buf[size];
+	u32 i, rindex;
+	struct pt3_bus *p = bus ? bus : vzalloc(sizeof(struct pt3_bus));
+	if (!p) {
+		pr_debug("out of memory.\n");
+		return -ENOMEM;
+	}
+
+	pt3_bus_start(p);
+	buf[0] = adap->addr_tc << 1;
+	pt3_bus_write(p, &buf[0], 1);
+	pt3_bus_write(p, &addr, 1);
+
+	pt3_bus_start(p);
+	buf[0] = adap->addr_tc << 1 | 1;
+	pt3_bus_write(p, &buf[0], 1);
+	rindex = pt3_bus_read(p, &buf[0], size);
+	pt3_bus_stop(p);
+
+	if (!bus) {
+		pt3_bus_end(p);
+		ret = pt3_i2c_run(adap->pt3->i2c, p, true);
+		for (i = 0; i < size; i++)
+			data[i] = pt3_bus_data1(p, rindex + i);
+		vfree(p);
+	}
+	return ret;
+}
+
+static u32 pt3_tc_byten(const u8 *data, u32 n)
+{
+	u32 i, value = 0;
+
+	for (i = 0; i < n; i++) {
+		value <<= 8;
+		value |= data[i];
+	}
+	return value;
+}
+
+int pt3_tc_read_cn_s(struct pt3_adapter *adap, struct pt3_bus *bus, u32 *cn)
+{
+	u8 data[2];
+	int ret = pt3_tc_read(adap, bus, 0xbc, data, sizeof(data));
+	if (!ret)
+		*cn = pt3_tc_byten(data, 2);
+	return ret;
+}
+
+int pt3_tc_read_cndat_t(struct pt3_adapter *adap, struct pt3_bus *bus, u32 *cn)
+{
+	u8 data[3];
+	int ret = pt3_tc_read(adap, bus, 0x8b, data, sizeof(data));
+	if (!ret)
+		*cn = pt3_tc_byten(data, 3);
+	return ret;
+}
+
+int pt3_tc_read_retryov_tmunvld_fulock(struct pt3_adapter *adap, struct pt3_bus *bus, int *retryov, int *tmunvld, int *fulock)
+{
+	u8 data;
+	int ret = pt3_tc_read(adap, bus, 0x80, &data, 1);
+	if (!ret) {
+		*retryov = PT3_SHIFT_MASK(data, 7, 1) ? 1 : 0;
+		*tmunvld = PT3_SHIFT_MASK(data, 5, 1) ? 1 : 0;
+		*fulock  = PT3_SHIFT_MASK(data, 3, 1) ? 1 : 0;
+	}
+	return ret;
+}
+
+int pt3_tc_read_tmcc_t(struct pt3_adapter *adap, struct pt3_bus *bus, struct tmcc_t *tmcc)
+{
+	u32 interleave0h, interleave0l, segment1h, segment1l;
+	u8 data[8];
+
+	int ret = pt3_tc_read(adap, bus, 0xb2+0, &data[0], 4);
+	if (ret)
+		return ret;
+	ret = pt3_tc_read(adap, bus, 0xb2+4, &data[4], 4);
+	if (ret)
+		return ret;
+
+	tmcc->system    = PT3_SHIFT_MASK(data[0], 6, 2);
+	tmcc->indicator = PT3_SHIFT_MASK(data[0], 2, 4);
+	tmcc->emergency = PT3_SHIFT_MASK(data[0], 1, 1);
+	tmcc->partial   = PT3_SHIFT_MASK(data[0], 0, 1);
+
+	tmcc->mode[0] = PT3_SHIFT_MASK(data[1], 5, 3);
+	tmcc->mode[1] = PT3_SHIFT_MASK(data[2], 0, 3);
+	tmcc->mode[2] = PT3_SHIFT_MASK(data[4], 3, 3);
+
+	tmcc->rate[0] = PT3_SHIFT_MASK(data[1], 2, 3);
+	tmcc->rate[1] = PT3_SHIFT_MASK(data[3], 5, 3);
+	tmcc->rate[2] = PT3_SHIFT_MASK(data[4], 0, 3);
+
+	interleave0h = PT3_SHIFT_MASK(data[1], 0, 2);
+	interleave0l = PT3_SHIFT_MASK(data[2], 7, 1);
+
+	tmcc->interleave[0] = interleave0h << 1 | interleave0l << 0;
+	tmcc->interleave[1] = PT3_SHIFT_MASK(data[3], 2, 3);
+	tmcc->interleave[2] = PT3_SHIFT_MASK(data[5], 5, 3);
+
+	segment1h = PT3_SHIFT_MASK(data[3], 0, 2);
+	segment1l = PT3_SHIFT_MASK(data[4], 6, 2);
+
+	tmcc->segment[0] = PT3_SHIFT_MASK(data[2], 3, 4);
+	tmcc->segment[1] = segment1h << 2 | segment1l << 0;
+	tmcc->segment[2] = PT3_SHIFT_MASK(data[5], 1, 4);
+
+	return ret;
+}
+
+int pt3_tc_read_tmcc_s(struct pt3_adapter *adap, struct pt3_bus *bus, struct tmcc_s *tmcc)
+{
+	enum {
+		BASE = 0xc5,
+		SIZE = 0xe5 - BASE + 1
+	};
+	u8 data[SIZE];
+	u32 i, byte_offset, bit_offset;
+
+	int ret = pt3_tc_read(adap, bus, 0xc3, data, 1);
+	if (ret)
+		return ret;
+	if (PT3_SHIFT_MASK(data[0], 4, 1))
+		return -EBADMSG;
+
+	ret = pt3_tc_read(adap, bus, 0xce, data, 2);
+	if (ret)
+		return ret;
+	if (pt3_tc_byten(data, 2) == 0)
+		return -EBADMSG;
+
+	ret = pt3_tc_read(adap, bus, 0xc3, data, 1);
+	if (ret)
+		return ret;
+	tmcc->emergency = PT3_SHIFT_MASK(data[0], 2, 1);
+	tmcc->extflag   = PT3_SHIFT_MASK(data[0], 1, 1);
+
+	ret = pt3_tc_read(adap, bus, 0xc5, data, SIZE);
+	if (ret)
+		return ret;
+	tmcc->indicator = PT3_SHIFT_MASK(data[0xc5 - BASE], 3, 5);
+	tmcc->uplink    = PT3_SHIFT_MASK(data[0xc7 - BASE], 0, 4);
+
+	for (i = 0; i < 4; i++) {
+		byte_offset = i / 2;
+		bit_offset = (i % 2) ? 0 : 4;
+		tmcc->mode[i] = PT3_SHIFT_MASK(data[0xc8 + byte_offset - BASE], bit_offset, 4);
+		tmcc->slot[i] = PT3_SHIFT_MASK(data[0xca + i - BASE], 0, 6);
+	}
+	for (i = 0; i < 8; i++)
+		tmcc->id[i] = pt3_tc_byten(data + 0xce + i * 2 - BASE, 2);
+	return ret;
+}
+
+int pt3_tc_write_id_s(struct pt3_adapter *adap, struct pt3_bus *bus, u16 id)
+{
+	u8 data[2] = { id >> 8, (u8)id };
+	return pt3_tc_write(adap, bus, 0x8f, data, sizeof(data));
+}
+
+int pt3_tc_read_id_s(struct pt3_adapter *adap, struct pt3_bus *bus, u16 *id)
+{
+	u8 data[2];
+	int ret = pt3_tc_read(adap, bus, 0xe6, data, sizeof(data));
+	if (!ret)
+		*id = pt3_tc_byten(data, 2);
+	return ret;
+}
+

^ permalink raw reply related	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2014-05-20 23:38 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-04-22 17:26 [PATCH] Full DVB driver package for Earthsoft PT3 (ISDB-S/T) cards Буди Романто, AreMa Inc
2014-05-17 19:03 ` Antti Palosaari
2014-05-19 22:19   ` ほち
2014-05-19 23:23     ` Antti Palosaari
  -- strict thread matches above, loose matches on Subject: below --
2014-05-20 23:37 Буди Романто, AreMa Inc
2014-04-21 15:20 Буди Романто, AreMa Inc
2014-04-10 16:06 буди Романто
2014-04-02  7:44 Guest
2014-04-05  4:34 ` Akihiro TSUKADA
2013-12-19 23:14 Guest
2013-12-21 13:24 ` Mauro Carvalho Chehab
2013-12-21 16:06 ` Antti Palosaari
2013-12-08  5:14 Guest
2013-12-08 22:52 ` Antti Palosaari
2013-11-05 16:36 буди Романто
2013-11-05 15:43 буди Романто
     [not found] ` <CAOcJUbxCjEWk47MkJP15QBAuGd3ePYS3ZRMduqdMCrVT362-8Q@mail.gmail.com>
2013-11-05 20:56   ` ほち
2013-11-05 21:09     ` Michael Krufky
2013-11-05 22:30     ` ほち
2013-11-06 13:14       ` Michael Krufky
2013-11-06 16:13         ` Antti Palosaari

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.