linux-media.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/8] PCTV DVB-S2 Stick (461e) [2013:0258] driver
@ 2013-11-06 17:57 Antti Palosaari
  2013-11-06 17:57 ` [PATCH 1/8] em28xx: add support for Empia EM28178 Antti Palosaari
                   ` (7 more replies)
  0 siblings, 8 replies; 12+ messages in thread
From: Antti Palosaari @ 2013-11-06 17:57 UTC (permalink / raw)
  To: linux-media; +Cc: Antti Palosaari

This patch serie adds support for PCTV Systems latest DVB-S/S2 USB
stick, model numbered as 461e.

I found only German version of product page [1], maybe English is
coming soon as this is quite young device. Device looks 100% similar
than older PCTV DVB-S2 Stick 460e, but internally demodulator and tuner
are different.

There is two new Linux drivers for demod and tuner.
Montage M88DS3103 DVB-S/S2 demodulator driver
Montage M88TS2022 silicon tuner driver

M88DS3103 requires firmware which is available my LinuxTV project page [2].

That driver could be downloaded from my LinuxTV.org Git tree [3].

Feel free to test!

[1] http://www.pctvsystems.com/Products/ProductsEuropeAsia/Satelliteproducts/PCTVDVBS2Stick/tabid/236/language/de-DE/Default.aspx
[2] http://palosaari.fi/linux/v4l-dvb/firmware/M88DS3103/
[3] http://git.linuxtv.org/anttip/media_tree.git/shortlog/refs/heads/pctv_461e


regards
Antti

Antti Palosaari (8):
  em28xx: add support for Empia EM28178
  a8293: add small sleep in order to settle LNB voltage
  Montage M88DS3103 DVB-S/S2 demodulator driver
  Montage M88TS2022 silicon tuner driver
  em28xx: add support for PCTV DVB-S2 Stick (461e) [2013:0258]
  m88ds3103: add parent for I2C adapter
  MAINTAINERS: add M88DS3103
  MAINTAINERS: add M88TS2022

 MAINTAINERS                                  |   20 +
 drivers/media/dvb-frontends/Kconfig          |    7 +
 drivers/media/dvb-frontends/Makefile         |    1 +
 drivers/media/dvb-frontends/a8293.c          |    2 +
 drivers/media/dvb-frontends/m88ds3103.c      | 1294 ++++++++++++++++++++++++++
 drivers/media/dvb-frontends/m88ds3103.h      |  108 +++
 drivers/media/dvb-frontends/m88ds3103_priv.h |  218 +++++
 drivers/media/tuners/Kconfig                 |    7 +
 drivers/media/tuners/Makefile                |    1 +
 drivers/media/tuners/m88ts2022.c             |  664 +++++++++++++
 drivers/media/tuners/m88ts2022.h             |   72 ++
 drivers/media/tuners/m88ts2022_priv.h        |   38 +
 drivers/media/usb/em28xx/Kconfig             |    2 +
 drivers/media/usb/em28xx/em28xx-cards.c      |   40 +
 drivers/media/usb/em28xx/em28xx-core.c       |    9 +-
 drivers/media/usb/em28xx/em28xx-dvb.c        |   49 +
 drivers/media/usb/em28xx/em28xx-input.c      |    2 +
 drivers/media/usb/em28xx/em28xx-reg.h        |    1 +
 drivers/media/usb/em28xx/em28xx.h            |    1 +
 19 files changed, 2533 insertions(+), 3 deletions(-)
 create mode 100644 drivers/media/dvb-frontends/m88ds3103.c
 create mode 100644 drivers/media/dvb-frontends/m88ds3103.h
 create mode 100644 drivers/media/dvb-frontends/m88ds3103_priv.h
 create mode 100644 drivers/media/tuners/m88ts2022.c
 create mode 100644 drivers/media/tuners/m88ts2022.h
 create mode 100644 drivers/media/tuners/m88ts2022_priv.h

-- 
1.8.4.2


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

* [PATCH 1/8] em28xx: add support for Empia EM28178
  2013-11-06 17:57 [PATCH 0/8] PCTV DVB-S2 Stick (461e) [2013:0258] driver Antti Palosaari
@ 2013-11-06 17:57 ` Antti Palosaari
  2013-11-06 17:57 ` [PATCH 2/8] a8293: add small sleep in order to settle LNB voltage Antti Palosaari
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 12+ messages in thread
From: Antti Palosaari @ 2013-11-06 17:57 UTC (permalink / raw)
  To: linux-media; +Cc: Antti Palosaari

Signed-off-by: Antti Palosaari <crope@iki.fi>
---
 drivers/media/usb/em28xx/em28xx-cards.c | 5 +++++
 drivers/media/usb/em28xx/em28xx-core.c  | 9 ++++++---
 drivers/media/usb/em28xx/em28xx-input.c | 2 ++
 drivers/media/usb/em28xx/em28xx-reg.h   | 1 +
 4 files changed, 14 insertions(+), 3 deletions(-)

diff --git a/drivers/media/usb/em28xx/em28xx-cards.c b/drivers/media/usb/em28xx/em28xx-cards.c
index a519669..62332a6 100644
--- a/drivers/media/usb/em28xx/em28xx-cards.c
+++ b/drivers/media/usb/em28xx/em28xx-cards.c
@@ -2968,6 +2968,11 @@ static int em28xx_init_dev(struct em28xx *dev, struct usb_device *udev,
 			dev->wait_after_write = 0;
 			dev->eeprom_addrwidth_16bit = 1;
 			break;
+		case CHIP_ID_EM28178:
+			chip_name = "em28178";
+			dev->wait_after_write = 0;
+			dev->eeprom_addrwidth_16bit = 1;
+			break;
 		case CHIP_ID_EM2883:
 			chip_name = "em2882/3";
 			dev->wait_after_write = 0;
diff --git a/drivers/media/usb/em28xx/em28xx-core.c b/drivers/media/usb/em28xx/em28xx-core.c
index fc157af..0c5fdba 100644
--- a/drivers/media/usb/em28xx/em28xx-core.c
+++ b/drivers/media/usb/em28xx/em28xx-core.c
@@ -482,8 +482,10 @@ int em28xx_audio_setup(struct em28xx *dev)
 	int vid1, vid2, feat, cfg;
 	u32 vid;
 
-	if (dev->chip_id == CHIP_ID_EM2870 || dev->chip_id == CHIP_ID_EM2874
-		|| dev->chip_id == CHIP_ID_EM28174) {
+	if (dev->chip_id == CHIP_ID_EM2870 ||
+	    dev->chip_id == CHIP_ID_EM2874 ||
+	    dev->chip_id == CHIP_ID_EM28174 ||
+	    dev->chip_id == CHIP_ID_EM28178) {
 		/* Digital only device - don't load any alsa module */
 		dev->audio_mode.has_audio = false;
 		dev->has_audio_class = false;
@@ -606,7 +608,8 @@ int em28xx_capture_start(struct em28xx *dev, int start)
 
 	if (dev->chip_id == CHIP_ID_EM2874 ||
 	    dev->chip_id == CHIP_ID_EM2884 ||
-	    dev->chip_id == CHIP_ID_EM28174) {
+	    dev->chip_id == CHIP_ID_EM28174 ||
+	    dev->chip_id == CHIP_ID_EM28178) {
 		/* The Transport Stream Enable Register moved in em2874 */
 		if (!start) {
 			rc = em28xx_write_reg_bits(dev, EM2874_R5F_TS_ENABLE,
diff --git a/drivers/media/usb/em28xx/em28xx-input.c b/drivers/media/usb/em28xx/em28xx-input.c
index ea181e4..d4a0a75 100644
--- a/drivers/media/usb/em28xx/em28xx-input.c
+++ b/drivers/media/usb/em28xx/em28xx-input.c
@@ -442,6 +442,7 @@ static int em28xx_ir_change_protocol(struct rc_dev *rc_dev, u64 *rc_type)
 	case CHIP_ID_EM2884:
 	case CHIP_ID_EM2874:
 	case CHIP_ID_EM28174:
+	case CHIP_ID_EM28178:
 		return em2874_ir_change_protocol(rc_dev, rc_type);
 	default:
 		printk("Unrecognized em28xx chip id 0x%02x: IR not supported\n",
@@ -633,6 +634,7 @@ static int em28xx_ir_init(struct em28xx *dev)
 		case CHIP_ID_EM2884:
 		case CHIP_ID_EM2874:
 		case CHIP_ID_EM28174:
+		case CHIP_ID_EM28178:
 			ir->get_key = em2874_polling_getkey;
 			rc->allowed_protos = RC_BIT_RC5 | RC_BIT_NEC |
 					     RC_BIT_RC6_0;
diff --git a/drivers/media/usb/em28xx/em28xx-reg.h b/drivers/media/usb/em28xx/em28xx-reg.h
index 0e04778..b769ceb 100644
--- a/drivers/media/usb/em28xx/em28xx-reg.h
+++ b/drivers/media/usb/em28xx/em28xx-reg.h
@@ -245,6 +245,7 @@ enum em28xx_chip_id {
 	CHIP_ID_EM2874 = 65,
 	CHIP_ID_EM2884 = 68,
 	CHIP_ID_EM28174 = 113,
+	CHIP_ID_EM28178 = 114,
 };
 
 /*
-- 
1.8.4.2


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

* [PATCH 2/8] a8293: add small sleep in order to settle LNB voltage
  2013-11-06 17:57 [PATCH 0/8] PCTV DVB-S2 Stick (461e) [2013:0258] driver Antti Palosaari
  2013-11-06 17:57 ` [PATCH 1/8] em28xx: add support for Empia EM28178 Antti Palosaari
@ 2013-11-06 17:57 ` Antti Palosaari
  2013-11-06 17:57 ` [PATCH 3/8] Montage M88DS3103 DVB-S/S2 demodulator driver Antti Palosaari
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 12+ messages in thread
From: Antti Palosaari @ 2013-11-06 17:57 UTC (permalink / raw)
  To: linux-media; +Cc: Antti Palosaari

Signed-off-by: Antti Palosaari <crope@iki.fi>
---
 drivers/media/dvb-frontends/a8293.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/media/dvb-frontends/a8293.c b/drivers/media/dvb-frontends/a8293.c
index 74fbb5d..780da58 100644
--- a/drivers/media/dvb-frontends/a8293.c
+++ b/drivers/media/dvb-frontends/a8293.c
@@ -96,6 +96,8 @@ static int a8293_set_voltage(struct dvb_frontend *fe,
 	if (ret)
 		goto err;
 
+	usleep_range(1500, 50000);
+
 	return ret;
 err:
 	dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
-- 
1.8.4.2


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

* [PATCH 3/8] Montage M88DS3103 DVB-S/S2 demodulator driver
  2013-11-06 17:57 [PATCH 0/8] PCTV DVB-S2 Stick (461e) [2013:0258] driver Antti Palosaari
  2013-11-06 17:57 ` [PATCH 1/8] em28xx: add support for Empia EM28178 Antti Palosaari
  2013-11-06 17:57 ` [PATCH 2/8] a8293: add small sleep in order to settle LNB voltage Antti Palosaari
@ 2013-11-06 17:57 ` Antti Palosaari
  2013-11-09  2:35   ` Manu Abraham
  2013-11-06 17:57 ` [PATCH 4/8] Montage M88TS2022 silicon tuner driver Antti Palosaari
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 12+ messages in thread
From: Antti Palosaari @ 2013-11-06 17:57 UTC (permalink / raw)
  To: linux-media; +Cc: Antti Palosaari

---
 drivers/media/dvb-frontends/Kconfig          |    7 +
 drivers/media/dvb-frontends/Makefile         |    1 +
 drivers/media/dvb-frontends/m88ds3103.c      | 1293 ++++++++++++++++++++++++++
 drivers/media/dvb-frontends/m88ds3103.h      |  108 +++
 drivers/media/dvb-frontends/m88ds3103_priv.h |  218 +++++
 5 files changed, 1627 insertions(+)
 create mode 100644 drivers/media/dvb-frontends/m88ds3103.c
 create mode 100644 drivers/media/dvb-frontends/m88ds3103.h
 create mode 100644 drivers/media/dvb-frontends/m88ds3103_priv.h

diff --git a/drivers/media/dvb-frontends/Kconfig b/drivers/media/dvb-frontends/Kconfig
index bddbab4..6c46caf 100644
--- a/drivers/media/dvb-frontends/Kconfig
+++ b/drivers/media/dvb-frontends/Kconfig
@@ -35,6 +35,13 @@ config DVB_STV6110x
 	help
 	  A Silicon tuner that supports DVB-S and DVB-S2 modes
 
+config DVB_M88DS3103
+	tristate "Montage M88DS3103"
+	depends on DVB_CORE && I2C
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y when you want to support this frontend.
+
 comment "Multistandard (cable + terrestrial) frontends"
 	depends on DVB_CORE
 
diff --git a/drivers/media/dvb-frontends/Makefile b/drivers/media/dvb-frontends/Makefile
index f9cb43d..0c75a6a 100644
--- a/drivers/media/dvb-frontends/Makefile
+++ b/drivers/media/dvb-frontends/Makefile
@@ -85,6 +85,7 @@ obj-$(CONFIG_DVB_STV6110) += stv6110.o
 obj-$(CONFIG_DVB_STV0900) += stv0900.o
 obj-$(CONFIG_DVB_STV090x) += stv090x.o
 obj-$(CONFIG_DVB_STV6110x) += stv6110x.o
+obj-$(CONFIG_DVB_M88DS3103) += m88ds3103.o
 obj-$(CONFIG_DVB_ISL6423) += isl6423.o
 obj-$(CONFIG_DVB_EC100) += ec100.o
 obj-$(CONFIG_DVB_HD29L2) += hd29l2.o
diff --git a/drivers/media/dvb-frontends/m88ds3103.c b/drivers/media/dvb-frontends/m88ds3103.c
new file mode 100644
index 0000000..91b3729
--- /dev/null
+++ b/drivers/media/dvb-frontends/m88ds3103.c
@@ -0,0 +1,1293 @@
+/*
+ * Montage M88DS3103 demodulator driver
+ *
+ * Copyright (C) 2013 Antti Palosaari <crope@iki.fi>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    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.,
+ *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "m88ds3103_priv.h"
+
+static struct dvb_frontend_ops m88ds3103_ops;
+
+/* write multiple registers */
+static int m88ds3103_wr_regs(struct m88ds3103_priv *priv,
+		u8 reg, const u8 *val, int len)
+{
+	int ret;
+	u8 buf[1 + len];
+	struct i2c_msg msg[1] = {
+		{
+			.addr = priv->cfg->i2c_addr,
+			.flags = 0,
+			.len = sizeof(buf),
+			.buf = buf,
+		}
+	};
+
+	buf[0] = reg;
+	memcpy(&buf[1], val, len);
+
+	mutex_lock(&priv->i2c_mutex);
+	ret = i2c_transfer(priv->i2c, msg, 1);
+	mutex_unlock(&priv->i2c_mutex);
+	if (ret == 1) {
+		ret = 0;
+	} else {
+		dev_warn(&priv->i2c->dev,
+				"%s: i2c wr failed=%d reg=%02x len=%d\n",
+				KBUILD_MODNAME, ret, reg, len);
+		ret = -EREMOTEIO;
+	}
+
+	return ret;
+}
+
+/* read multiple registers */
+static int m88ds3103_rd_regs(struct m88ds3103_priv *priv,
+		u8 reg, u8 *val, int len)
+{
+	int ret;
+	u8 buf[len];
+	struct i2c_msg msg[2] = {
+		{
+			.addr = priv->cfg->i2c_addr,
+			.flags = 0,
+			.len = 1,
+			.buf = &reg,
+		}, {
+			.addr = priv->cfg->i2c_addr,
+			.flags = I2C_M_RD,
+			.len = sizeof(buf),
+			.buf = buf,
+		}
+	};
+
+	mutex_lock(&priv->i2c_mutex);
+	ret = i2c_transfer(priv->i2c, msg, 2);
+	mutex_unlock(&priv->i2c_mutex);
+	if (ret == 2) {
+		memcpy(val, buf, len);
+		ret = 0;
+	} else {
+		dev_warn(&priv->i2c->dev,
+				"%s: i2c rd failed=%d reg=%02x len=%d\n",
+				KBUILD_MODNAME, ret, reg, len);
+		ret = -EREMOTEIO;
+	}
+
+	return ret;
+}
+
+/* write single register */
+static int m88ds3103_wr_reg(struct m88ds3103_priv *priv, u8 reg, u8 val)
+{
+	return m88ds3103_wr_regs(priv, reg, &val, 1);
+}
+
+/* read single register */
+static int m88ds3103_rd_reg(struct m88ds3103_priv *priv, u8 reg, u8 *val)
+{
+	return m88ds3103_rd_regs(priv, reg, val, 1);
+}
+
+/* write single register with mask */
+static int m88ds3103_wr_reg_mask(struct m88ds3103_priv *priv,
+		u8 reg, u8 val, u8 mask)
+{
+	int ret;
+	u8 u8tmp;
+
+	/* no need for read if whole reg is written */
+	if (mask != 0xff) {
+		ret = m88ds3103_rd_regs(priv, reg, &u8tmp, 1);
+		if (ret)
+			return ret;
+
+		val &= mask;
+		u8tmp &= ~mask;
+		val |= u8tmp;
+	}
+
+	return m88ds3103_wr_regs(priv, reg, &val, 1);
+}
+
+/* read single register with mask */
+static int m88ds3103_rd_reg_mask(struct m88ds3103_priv *priv,
+		u8 reg, u8 *val, u8 mask)
+{
+	int ret, i;
+	u8 u8tmp;
+
+	ret = m88ds3103_rd_regs(priv, reg, &u8tmp, 1);
+	if (ret)
+		return ret;
+
+	u8tmp &= mask;
+
+	/* find position of the first bit */
+	for (i = 0; i < 8; i++) {
+		if ((mask >> i) & 0x01)
+			break;
+	}
+	*val = u8tmp >> i;
+
+	return 0;
+}
+
+static int m88ds3103_read_status(struct dvb_frontend *fe, fe_status_t *status)
+{
+	struct m88ds3103_priv *priv = fe->demodulator_priv;
+	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+	int ret;
+	u8 u8tmp;
+
+	*status = 0;
+
+	if (!priv->warm) {
+		ret = -EAGAIN;
+		goto err;
+	}
+
+	switch (c->delivery_system) {
+	case SYS_DVBS:
+		ret = m88ds3103_rd_reg_mask(priv, 0xd1, &u8tmp, 0x07);
+		if (ret)
+			goto err;
+
+		if (u8tmp == 0x07)
+			*status = FE_HAS_SIGNAL | FE_HAS_CARRIER |
+					FE_HAS_VITERBI | FE_HAS_SYNC |
+					FE_HAS_LOCK;
+		break;
+	case SYS_DVBS2:
+		ret = m88ds3103_rd_reg_mask(priv, 0x0d, &u8tmp, 0x8f);
+		if (ret)
+			goto err;
+
+		if (u8tmp == 0x8f)
+			*status = FE_HAS_SIGNAL | FE_HAS_CARRIER |
+					FE_HAS_VITERBI | FE_HAS_SYNC |
+					FE_HAS_LOCK;
+		break;
+	default:
+		dev_dbg(&priv->i2c->dev, "%s: invalid delivery_system\n",
+				__func__);
+		ret = -EINVAL;
+		goto err;
+	}
+
+	priv->fe_status = *status;
+
+	dev_dbg(&priv->i2c->dev, "%s: lock=%02x status=%02x\n",
+			__func__, u8tmp, *status);
+
+	return 0;
+err:
+	dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+static int m88ds3103_set_frontend(struct dvb_frontend *fe)
+{
+	struct m88ds3103_priv *priv = fe->demodulator_priv;
+	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+	int ret, i, len;
+	const struct m88ds3103_reg_val *init;
+	u8 u8tmp, u8tmp1, u8tmp2;
+	u8 buf[2];
+	u16 u16tmp, divide_ratio;
+	u32 tuner_frequency, target_mclk, ts_clk;
+	s32 s32tmp;
+	dev_dbg(&priv->i2c->dev,
+			"%s: delivery_system=%d modulation=%d frequency=%d symbol_rate=%d inversion=%d pilot=%d rolloff=%d\n",
+			__func__, c->delivery_system,
+			c->modulation, c->frequency, c->symbol_rate,
+			c->inversion, c->pilot, c->rolloff);
+
+	if (!priv->warm) {
+		ret = -EAGAIN;
+		goto err;
+	}
+
+	/* program tuner */
+	if (fe->ops.tuner_ops.set_params) {
+		ret = fe->ops.tuner_ops.set_params(fe);
+		if (ret)
+			goto err;
+	}
+
+	if (fe->ops.tuner_ops.get_frequency) {
+		ret = fe->ops.tuner_ops.get_frequency(fe, &tuner_frequency);
+		if (ret)
+			goto err;
+	}
+
+	/* reset */
+	ret = m88ds3103_wr_reg(priv, 0x07, 0x80);
+	if (ret)
+		goto err;
+
+	ret = m88ds3103_wr_reg(priv, 0x07, 0x00);
+	if (ret)
+		goto err;
+
+	ret = m88ds3103_wr_reg(priv, 0xb2, 0x01);
+	if (ret)
+		goto err;
+
+	ret = m88ds3103_wr_reg(priv, 0x00, 0x01);
+	if (ret)
+		goto err;
+
+	switch (c->delivery_system) {
+	case SYS_DVBS:
+		len = ARRAY_SIZE(m88ds3103_dvbs_init_reg_vals);
+		init = m88ds3103_dvbs_init_reg_vals;
+		target_mclk = 96000;
+		break;
+	case SYS_DVBS2:
+		len = ARRAY_SIZE(m88ds3103_dvbs2_init_reg_vals);
+		init = m88ds3103_dvbs2_init_reg_vals;
+
+		switch (priv->cfg->ts_mode) {
+		case M88DS3103_TS_SERIAL:
+		case M88DS3103_TS_SERIAL_D7:
+			if (c->symbol_rate < 18000000)
+				target_mclk = 96000;
+			else
+				target_mclk = 144000;
+			break;
+		case M88DS3103_TS_PARALLEL:
+		case M88DS3103_TS_PARALLEL_12:
+		case M88DS3103_TS_PARALLEL_16:
+		case M88DS3103_TS_PARALLEL_19_2:
+		case M88DS3103_TS_CI:
+			if (c->symbol_rate < 18000000)
+				target_mclk = 96000;
+			else if (c->symbol_rate < 28000000)
+				target_mclk = 144000;
+			else
+				target_mclk = 192000;
+			break;
+		default:
+			dev_dbg(&priv->i2c->dev, "%s: invalid ts_mode\n",
+					__func__);
+			ret = -EINVAL;
+			goto err;
+		}
+		break;
+	default:
+		dev_dbg(&priv->i2c->dev, "%s: invalid delivery_system\n",
+				__func__);
+		ret = -EINVAL;
+		goto err;
+	}
+
+	/* program init table */
+	if (c->delivery_system != priv->delivery_system) {
+		dev_dbg(&priv->i2c->dev, "%s: program init\n", __func__);
+		for (i = 0; i < len; i++) {
+			ret = m88ds3103_wr_reg(priv, init[i].reg, init[i].val);
+			if (ret)
+				goto err;
+		}
+	}
+
+	u8tmp1 = 0; /* silence compiler warning */
+	switch (priv->cfg->ts_mode) {
+	case M88DS3103_TS_SERIAL:
+		u8tmp1 = 0x00;
+		ts_clk = 0;
+		u8tmp = 0x04;
+		break;
+	case M88DS3103_TS_SERIAL_D7:
+		u8tmp1 = 0x20;
+		ts_clk = 0;
+		u8tmp = 0x04;
+		break;
+	case M88DS3103_TS_PARALLEL:
+		ts_clk = 24000;
+		u8tmp = 0x00;
+		break;
+	case M88DS3103_TS_PARALLEL_12:
+		ts_clk = 12000;
+		u8tmp = 0x00;
+		break;
+	case M88DS3103_TS_PARALLEL_16:
+		ts_clk = 16000;
+		u8tmp = 0x00;
+		break;
+	case M88DS3103_TS_PARALLEL_19_2:
+		ts_clk = 19200;
+		u8tmp = 0x00;
+		break;
+	case M88DS3103_TS_CI:
+		ts_clk = 6000;
+		u8tmp = 0x01;
+		break;
+	default:
+		dev_dbg(&priv->i2c->dev, "%s: invalid ts_mode\n", __func__);
+		ret = -EINVAL;
+		goto err;
+	}
+
+	/* TS mode */
+	ret = m88ds3103_wr_reg_mask(priv, 0xfd, u8tmp, 0x05);
+	if (ret)
+		goto err;
+
+	switch (priv->cfg->ts_mode) {
+	case M88DS3103_TS_SERIAL:
+	case M88DS3103_TS_SERIAL_D7:
+		ret = m88ds3103_wr_reg_mask(priv, 0x29, u8tmp1, 0x20);
+		if (ret)
+			goto err;
+	}
+
+	if (ts_clk) {
+		divide_ratio = DIV_ROUND_UP(target_mclk, ts_clk);
+		u8tmp1 = divide_ratio / 2;
+		u8tmp2 = DIV_ROUND_UP(divide_ratio, 2);
+	} else {
+		divide_ratio = 0;
+		u8tmp1 = 0;
+		u8tmp2 = 0;
+	}
+
+	dev_dbg(&priv->i2c->dev,
+			"%s: target_mclk=%d ts_clk=%d divide_ratio=%d\n",
+			__func__, target_mclk, ts_clk, divide_ratio);
+
+	u8tmp1--;
+	u8tmp2--;
+	/* u8tmp1[5:2] => fe[3:0], u8tmp1[1:0] => ea[7:6] */
+	u8tmp1 &= 0x3f;
+	/* u8tmp2[5:0] => ea[5:0] */
+	u8tmp2 &= 0x3f;
+
+	ret = m88ds3103_rd_reg(priv, 0xfe, &u8tmp);
+	if (ret)
+		goto err;
+
+	u8tmp = ((u8tmp  & 0xf0) << 0) | u8tmp1 >> 2;
+	ret = m88ds3103_wr_reg(priv, 0xfe, u8tmp);
+	if (ret)
+		goto err;
+
+	u8tmp = ((u8tmp1 & 0x03) << 6) | u8tmp2 >> 0;
+	ret = m88ds3103_wr_reg(priv, 0xea, u8tmp);
+	if (ret)
+		goto err;
+
+	switch (target_mclk) {
+	case 72000:
+		u8tmp1 = 0x00; /* 0b00 */
+		u8tmp2 = 0x03; /* 0b11 */
+		break;
+	case 96000:
+		u8tmp1 = 0x02; /* 0b10 */
+		u8tmp2 = 0x01; /* 0b01 */
+		break;
+	case 115200:
+		u8tmp1 = 0x01; /* 0b01 */
+		u8tmp2 = 0x01; /* 0b01 */
+		break;
+	case 144000:
+		u8tmp1 = 0x00; /* 0b00 */
+		u8tmp2 = 0x01; /* 0b01 */
+		break;
+	case 192000:
+		u8tmp1 = 0x03; /* 0b11 */
+		u8tmp2 = 0x00; /* 0b00 */
+		break;
+	default:
+		dev_dbg(&priv->i2c->dev, "%s: invalid target_mclk\n", __func__);
+		ret = -EINVAL;
+		goto err;
+	}
+
+	ret = m88ds3103_wr_reg_mask(priv, 0x22, u8tmp1 << 6, 0xc0);
+	if (ret)
+		goto err;
+
+	ret = m88ds3103_wr_reg_mask(priv, 0x24, u8tmp2 << 6, 0xc0);
+	if (ret)
+		goto err;
+
+	if (c->symbol_rate <= 3000000)
+		u8tmp = 0x20;
+	else if (c->symbol_rate <= 10000000)
+		u8tmp = 0x10;
+	else
+		u8tmp = 0x06;
+
+	ret = m88ds3103_wr_reg(priv, 0xc3, 0x08);
+	if (ret)
+		goto err;
+
+	ret = m88ds3103_wr_reg(priv, 0xc8, u8tmp);
+	if (ret)
+		goto err;
+
+	ret = m88ds3103_wr_reg(priv, 0xc4, 0x08);
+	if (ret)
+		goto err;
+
+	ret = m88ds3103_wr_reg(priv, 0xc7, 0x00);
+	if (ret)
+		goto err;
+
+	u16tmp = (((c->symbol_rate / 1000) << 15) + (M88DS3103_MCLK_KHZ / 4)) /
+			(M88DS3103_MCLK_KHZ / 2);
+	buf[0] = (u16tmp >> 0) & 0xff;
+	buf[1] = (u16tmp >> 8) & 0xff;
+	ret = m88ds3103_wr_regs(priv, 0x61, buf, 2);
+	if (ret)
+		goto err;
+
+	ret = m88ds3103_wr_reg_mask(priv, 0x4d, priv->cfg->spec_inv << 1, 0x02);
+	if (ret)
+		goto err;
+
+	ret = m88ds3103_wr_reg_mask(priv, 0x30, priv->cfg->agc_inv << 4, 0x10);
+	if (ret)
+		goto err;
+
+	ret = m88ds3103_wr_reg(priv, 0x33, priv->cfg->agc);
+	if (ret)
+		goto err;
+
+	dev_dbg(&priv->i2c->dev, "%s: carrier offset=%d\n", __func__,
+			(tuner_frequency - c->frequency));
+
+	s32tmp = 0x10000 * (tuner_frequency - c->frequency);
+	s32tmp = (2 * s32tmp + M88DS3103_MCLK_KHZ) / (2 * M88DS3103_MCLK_KHZ);
+	if (s32tmp < 0)
+		s32tmp += 0x10000;
+
+	buf[0] = (s32tmp >> 0) & 0xff;
+	buf[1] = (s32tmp >> 8) & 0xff;
+	ret = m88ds3103_wr_regs(priv, 0x5e, buf, 2);
+	if (ret)
+		goto err;
+
+	ret = m88ds3103_wr_reg(priv, 0x00, 0x00);
+	if (ret)
+		goto err;
+
+	ret = m88ds3103_wr_reg(priv, 0xb2, 0x00);
+	if (ret)
+		goto err;
+
+	priv->delivery_system = c->delivery_system;
+
+	return 0;
+err:
+	dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+static int m88ds3103_init(struct dvb_frontend *fe)
+{
+	struct m88ds3103_priv *priv = fe->demodulator_priv;
+	int ret, len, remaining;
+	const struct firmware *fw = NULL;
+	u8 *fw_file = M88DS3103_FIRMWARE;
+	u8 u8tmp;
+	dev_dbg(&priv->i2c->dev, "%s:\n", __func__);
+
+	/* set cold state by default */
+	priv->warm = false;
+
+	/* wake up device from sleep */
+	ret = m88ds3103_wr_reg_mask(priv, 0x08, 0x01, 0x01);
+	if (ret)
+		goto err;
+
+	ret = m88ds3103_wr_reg_mask(priv, 0x04, 0x00, 0x01);
+	if (ret)
+		goto err;
+
+	ret = m88ds3103_wr_reg_mask(priv, 0x23, 0x00, 0x10);
+	if (ret)
+		goto err;
+
+	/* reset */
+	ret = m88ds3103_wr_reg(priv, 0x07, 0x60);
+	if (ret)
+		goto err;
+
+	ret = m88ds3103_wr_reg(priv, 0x07, 0x00);
+	if (ret)
+		goto err;
+
+	/* firmware status */
+	ret = m88ds3103_rd_reg(priv, 0xb9, &u8tmp);
+	if (ret)
+		goto err;
+
+	dev_dbg(&priv->i2c->dev, "%s: firmware=%02x\n", __func__, u8tmp);
+
+	if (u8tmp)
+		goto skip_fw_download;
+
+	/* cold state - try to download firmware */
+	dev_info(&priv->i2c->dev, "%s: found a '%s' in cold state\n",
+			KBUILD_MODNAME, m88ds3103_ops.info.name);
+
+	/* request the firmware, this will block and timeout */
+	ret = request_firmware(&fw, fw_file, priv->i2c->dev.parent);
+	if (ret) {
+		dev_err(&priv->i2c->dev, "%s: firmare file '%s' not found\n",
+				KBUILD_MODNAME, fw_file);
+		goto err;
+	}
+
+	dev_info(&priv->i2c->dev, "%s: downloading firmware from file '%s'\n",
+			KBUILD_MODNAME, fw_file);
+
+	ret = m88ds3103_wr_reg(priv, 0xb2, 0x01);
+	if (ret)
+		goto err;
+
+	for (remaining = fw->size; remaining > 0;
+			remaining -= (priv->cfg->i2c_wr_max - 1)) {
+		len = remaining;
+		if (len > (priv->cfg->i2c_wr_max - 1))
+			len = (priv->cfg->i2c_wr_max - 1);
+
+		ret = m88ds3103_wr_regs(priv, 0xb0,
+				&fw->data[fw->size - remaining], len);
+		if (ret) {
+			dev_err(&priv->i2c->dev,
+					"%s: firmware download failed=%d\n",
+					KBUILD_MODNAME, ret);
+			goto err;
+		}
+	}
+
+	ret = m88ds3103_wr_reg(priv, 0xb2, 0x00);
+	if (ret)
+		goto err;
+
+	release_firmware(fw);
+	fw = NULL;
+
+	ret = m88ds3103_rd_reg(priv, 0xb9, &u8tmp);
+	if (ret)
+		goto err;
+
+	if (!u8tmp) {
+		dev_info(&priv->i2c->dev, "%s: firmware did not run\n",
+				KBUILD_MODNAME);
+		ret = -EFAULT;
+		goto err;
+	}
+
+	dev_info(&priv->i2c->dev, "%s: found a '%s' in warm state\n",
+			KBUILD_MODNAME, m88ds3103_ops.info.name);
+	dev_info(&priv->i2c->dev, "%s: firmware version %X.%X\n",
+			KBUILD_MODNAME, (u8tmp >> 4) & 0xf, (u8tmp >> 0 & 0xf));
+
+skip_fw_download:
+	/* warm state */
+	priv->warm = true;
+
+	return 0;
+err:
+	if (fw)
+		release_firmware(fw);
+
+	dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+static int m88ds3103_sleep(struct dvb_frontend *fe)
+{
+	struct m88ds3103_priv *priv = fe->demodulator_priv;
+	int ret;
+	dev_dbg(&priv->i2c->dev, "%s:\n", __func__);
+
+	priv->delivery_system = SYS_UNDEFINED;
+
+	/* TS Hi-Z */
+	ret = m88ds3103_wr_reg_mask(priv, 0x27, 0x00, 0x01);
+	if (ret)
+		goto err;
+
+	/* sleep */
+	ret = m88ds3103_wr_reg_mask(priv, 0x08, 0x00, 0x01);
+	if (ret)
+		goto err;
+
+	ret = m88ds3103_wr_reg_mask(priv, 0x04, 0x01, 0x01);
+	if (ret)
+		goto err;
+
+	ret = m88ds3103_wr_reg_mask(priv, 0x23, 0x10, 0x10);
+	if (ret)
+		goto err;
+
+	return 0;
+err:
+	dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+static int m88ds3103_get_frontend(struct dvb_frontend *fe)
+{
+	struct m88ds3103_priv *priv = fe->demodulator_priv;
+	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+	int ret;
+	u8 buf[3];
+	dev_dbg(&priv->i2c->dev, "%s:\n", __func__);
+
+	if (!priv->warm || !(priv->fe_status & FE_HAS_LOCK)) {
+		ret = -EAGAIN;
+		goto err;
+	}
+
+	switch (c->delivery_system) {
+	case SYS_DVBS:
+		ret = m88ds3103_rd_reg(priv, 0xe0, &buf[0]);
+		if (ret)
+			goto err;
+
+		ret = m88ds3103_rd_reg(priv, 0xe6, &buf[1]);
+		if (ret)
+			goto err;
+
+		switch ((buf[0] >> 2) & 0x01) {
+		case 0:
+			c->inversion = INVERSION_OFF;
+			break;
+		case 1:
+			c->inversion = INVERSION_ON;
+			break;
+		default:
+			dev_dbg(&priv->i2c->dev, "%s: invalid inversion\n",
+					__func__);
+		}
+
+		switch ((buf[1] >> 5) & 0x07) {
+		case 0:
+			c->fec_inner = FEC_7_8;
+			break;
+		case 1:
+			c->fec_inner = FEC_5_6;
+			break;
+		case 2:
+			c->fec_inner = FEC_3_4;
+			break;
+		case 3:
+			c->fec_inner = FEC_2_3;
+			break;
+		case 4:
+			c->fec_inner = FEC_1_2;
+			break;
+		default:
+			dev_dbg(&priv->i2c->dev, "%s: invalid fec_inner\n",
+					__func__);
+		}
+
+		c->modulation = QPSK;
+
+		break;
+	case SYS_DVBS2:
+		ret = m88ds3103_rd_reg(priv, 0x7e, &buf[0]);
+		if (ret)
+			goto err;
+
+		ret = m88ds3103_rd_reg(priv, 0x89, &buf[1]);
+		if (ret)
+			goto err;
+
+		ret = m88ds3103_rd_reg(priv, 0xf2, &buf[2]);
+		if (ret)
+			goto err;
+
+		switch ((buf[0] >> 0) & 0x0f) {
+		case 2:
+			c->fec_inner = FEC_2_5;
+			break;
+		case 3:
+			c->fec_inner = FEC_1_2;
+			break;
+		case 4:
+			c->fec_inner = FEC_3_5;
+			break;
+		case 5:
+			c->fec_inner = FEC_2_3;
+			break;
+		case 6:
+			c->fec_inner = FEC_3_4;
+			break;
+		case 7:
+			c->fec_inner = FEC_4_5;
+			break;
+		case 8:
+			c->fec_inner = FEC_5_6;
+			break;
+		case 9:
+			c->fec_inner = FEC_8_9;
+			break;
+		case 10:
+			c->fec_inner = FEC_9_10;
+			break;
+		default:
+			dev_dbg(&priv->i2c->dev, "%s: invalid fec_inner\n",
+					__func__);
+		}
+
+		switch ((buf[0] >> 5) & 0x01) {
+		case 0:
+			c->pilot = PILOT_OFF;
+			break;
+		case 1:
+			c->pilot = PILOT_ON;
+			break;
+		default:
+			dev_dbg(&priv->i2c->dev, "%s: invalid pilot\n",
+					__func__);
+		}
+
+		switch ((buf[0] >> 6) & 0x07) {
+		case 0:
+			c->modulation = QPSK;
+			break;
+		case 1:
+			c->modulation = PSK_8;
+			break;
+		case 2:
+			c->modulation = APSK_16;
+			break;
+		case 3:
+			c->modulation = APSK_32;
+			break;
+		default:
+			dev_dbg(&priv->i2c->dev, "%s: invalid modulation\n",
+					__func__);
+		}
+
+		switch ((buf[1] >> 7) & 0x01) {
+		case 0:
+			c->inversion = INVERSION_OFF;
+			break;
+		case 1:
+			c->inversion = INVERSION_ON;
+			break;
+		default:
+			dev_dbg(&priv->i2c->dev, "%s: invalid inversion\n",
+					__func__);
+		}
+
+		switch ((buf[2] >> 0) & 0x03) {
+		case 0:
+			c->rolloff = ROLLOFF_35;
+			break;
+		case 1:
+			c->rolloff = ROLLOFF_25;
+			break;
+		case 2:
+			c->rolloff = ROLLOFF_20;
+			break;
+		default:
+			dev_dbg(&priv->i2c->dev, "%s: invalid rolloff\n",
+					__func__);
+		}
+		break;
+	default:
+		dev_dbg(&priv->i2c->dev, "%s: invalid delivery_system\n",
+				__func__);
+		ret = -EINVAL;
+		goto err;
+	}
+
+	ret = m88ds3103_rd_regs(priv, 0x6d, buf, 2);
+	if (ret)
+		goto err;
+
+	c->symbol_rate = 1ull * ((buf[1] << 8) | (buf[0] << 0)) *
+			M88DS3103_MCLK_KHZ * 1000 / 0x10000;
+
+	return 0;
+err:
+	dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+static int m88ds3103_read_snr(struct dvb_frontend *fe, u16 *snr)
+{
+	struct m88ds3103_priv *priv = fe->demodulator_priv;
+	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+	int ret, i, tmp;
+	u8 buf[3];
+	u16 noise, signal;
+	u32 noise_tot, signal_tot;
+	dev_dbg(&priv->i2c->dev, "%s:\n", __func__);
+	/* reports SNR in resolution of 0.1 dB */
+
+	/* more iterations for more accurate estimation */
+	#define M88DS3103_SNR_ITERATIONS 3
+
+	switch (c->delivery_system) {
+	case SYS_DVBS:
+		tmp = 0;
+
+		for (i = 0; i < M88DS3103_SNR_ITERATIONS; i++) {
+			ret = m88ds3103_rd_reg(priv, 0xff, &buf[0]);
+			if (ret)
+				goto err;
+
+			tmp += buf[0];
+		}
+
+		/* use of one register limits max value to 15 dB */
+		/* SNR(X) dB = 10 * ln(X) / ln(10) dB */
+		tmp = DIV_ROUND_CLOSEST(tmp, 8 * M88DS3103_SNR_ITERATIONS);
+		if (tmp)
+			*snr = 100ul * intlog2(tmp) / intlog2(10);
+		else
+			*snr = 0;
+		break;
+	case SYS_DVBS2:
+		noise_tot = 0;
+		signal_tot = 0;
+
+		for (i = 0; i < M88DS3103_SNR_ITERATIONS; i++) {
+			ret = m88ds3103_rd_regs(priv, 0x8c, buf, 3);
+			if (ret)
+				goto err;
+
+			noise = buf[1] << 6;    /* [13:6] */
+			noise |= buf[0] & 0x3f; /*  [5:0] */
+			noise >>= 2;
+			signal = buf[2] * buf[2];
+			signal >>= 1;
+
+			noise_tot += noise;
+			signal_tot += signal;
+		}
+
+		noise = noise_tot / M88DS3103_SNR_ITERATIONS;
+		signal = signal_tot / M88DS3103_SNR_ITERATIONS;
+
+		/* SNR(X) dB = 10 * log10(X) dB */
+		if (signal > noise) {
+			tmp = signal / noise;
+			*snr = 100ul * intlog10(tmp) / (1 << 24);
+		} else
+			*snr = 0;
+		break;
+	default:
+		dev_dbg(&priv->i2c->dev, "%s: invalid delivery_system\n",
+				__func__);
+		ret = -EINVAL;
+		goto err;
+	}
+
+	return 0;
+err:
+	dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+
+static int m88ds3103_set_tone(struct dvb_frontend *fe,
+	fe_sec_tone_mode_t fe_sec_tone_mode)
+{
+	struct m88ds3103_priv *priv = fe->demodulator_priv;
+	int ret;
+	u8 u8tmp, tone, reg_a1_mask;
+	dev_dbg(&priv->i2c->dev, "%s: fe_sec_tone_mode=%d\n", __func__,
+			fe_sec_tone_mode);
+
+	if (!priv->warm) {
+		ret = -EAGAIN;
+		goto err;
+	}
+
+	switch (fe_sec_tone_mode) {
+	case SEC_TONE_ON:
+		tone = 0;
+		reg_a1_mask = 0x87;
+		break;
+	case SEC_TONE_OFF:
+		tone = 1;
+		reg_a1_mask = 0x00;
+		break;
+	default:
+		dev_dbg(&priv->i2c->dev, "%s: invalid fe_sec_tone_mode\n",
+				__func__);
+		ret = -EINVAL;
+		goto err;
+	}
+
+	u8tmp = tone << 7 | priv->cfg->envelope_mode << 5;
+	ret = m88ds3103_wr_reg_mask(priv, 0xa2, u8tmp, 0xe0);
+	if (ret)
+		goto err;
+
+	u8tmp = 1 << 2;
+	ret = m88ds3103_wr_reg_mask(priv, 0xa1, u8tmp, reg_a1_mask);
+	if (ret)
+		goto err;
+
+	return 0;
+err:
+	dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+static int m88ds3103_diseqc_send_master_cmd(struct dvb_frontend *fe,
+		struct dvb_diseqc_master_cmd *diseqc_cmd)
+{
+	struct m88ds3103_priv *priv = fe->demodulator_priv;
+	int ret, i;
+	u8 u8tmp;
+	dev_dbg(&priv->i2c->dev, "%s: msg=%*ph\n", __func__,
+			diseqc_cmd->msg_len, diseqc_cmd->msg);
+
+	if (!priv->warm) {
+		ret = -EAGAIN;
+		goto err;
+	}
+
+	if (diseqc_cmd->msg_len < 3 || diseqc_cmd->msg_len > 6) {
+		ret = -EINVAL;
+		goto err;
+	}
+
+	u8tmp = priv->cfg->envelope_mode << 5;
+	ret = m88ds3103_wr_reg_mask(priv, 0xa2, u8tmp, 0xe0);
+	if (ret)
+		goto err;
+
+	ret = m88ds3103_wr_regs(priv, 0xa3, diseqc_cmd->msg,
+			diseqc_cmd->msg_len);
+	if (ret)
+		goto err;
+
+	ret = m88ds3103_wr_reg(priv, 0xa1,
+			(diseqc_cmd->msg_len - 1) << 3 | 0x07);
+	if (ret)
+		goto err;
+
+	/* DiSEqC message typical period is 54 ms */
+	usleep_range(40000, 60000);
+
+	/* wait DiSEqC TX ready */
+	for (i = 20, u8tmp = 1; i && u8tmp; i--) {
+		usleep_range(5000, 10000);
+
+		ret = m88ds3103_rd_reg_mask(priv, 0xa1, &u8tmp, 0x40);
+		if (ret)
+			goto err;
+	}
+
+	dev_dbg(&priv->i2c->dev, "%s: loop=%d\n", __func__, i);
+
+	if (i == 0) {
+		dev_dbg(&priv->i2c->dev, "%s: diseqc tx timeout\n", __func__);
+
+		ret = m88ds3103_wr_reg_mask(priv, 0xa1, 0x40, 0xc0);
+		if (ret)
+			goto err;
+	}
+
+	ret = m88ds3103_wr_reg_mask(priv, 0xa2, 0x80, 0xc0);
+	if (ret)
+		goto err;
+
+	if (i == 0) {
+		ret = -ETIMEDOUT;
+		goto err;
+	}
+
+	return 0;
+err:
+	dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+static int m88ds3103_diseqc_send_burst(struct dvb_frontend *fe,
+	fe_sec_mini_cmd_t fe_sec_mini_cmd)
+{
+	struct m88ds3103_priv *priv = fe->demodulator_priv;
+	int ret, i;
+	u8 u8tmp, burst;
+	dev_dbg(&priv->i2c->dev, "%s: fe_sec_mini_cmd=%d\n", __func__,
+			fe_sec_mini_cmd);
+
+	if (!priv->warm) {
+		ret = -EAGAIN;
+		goto err;
+	}
+
+	u8tmp = priv->cfg->envelope_mode << 5;
+	ret = m88ds3103_wr_reg_mask(priv, 0xa2, u8tmp, 0xe0);
+	if (ret)
+		goto err;
+
+	switch (fe_sec_mini_cmd) {
+	case SEC_MINI_A:
+		burst = 0x02;
+		break;
+	case SEC_MINI_B:
+		burst = 0x01;
+		break;
+	default:
+		dev_dbg(&priv->i2c->dev, "%s: invalid fe_sec_mini_cmd\n",
+				__func__);
+		ret = -EINVAL;
+		goto err;
+	}
+
+	ret = m88ds3103_wr_reg(priv, 0xa1, burst);
+	if (ret)
+		goto err;
+
+	/* DiSEqC ToneBurst period is 12.5 ms */
+	usleep_range(11000, 20000);
+
+	/* wait DiSEqC TX ready */
+	for (i = 5, u8tmp = 1; i && u8tmp; i--) {
+		usleep_range(800, 2000);
+
+		ret = m88ds3103_rd_reg_mask(priv, 0xa1, &u8tmp, 0x40);
+		if (ret)
+			goto err;
+	}
+
+	dev_dbg(&priv->i2c->dev, "%s: loop=%d\n", __func__, i);
+
+	ret = m88ds3103_wr_reg_mask(priv, 0xa2, 0x80, 0xc0);
+	if (ret)
+		goto err;
+
+	if (i == 0) {
+		dev_dbg(&priv->i2c->dev, "%s: diseqc tx timeout\n", __func__);
+		ret = -ETIMEDOUT;
+		goto err;
+	}
+
+	return 0;
+err:
+	dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+static int m88ds3103_get_tune_settings(struct dvb_frontend *fe,
+	struct dvb_frontend_tune_settings *s)
+{
+	s->min_delay_ms = 3000;
+
+	return 0;
+}
+
+static u32 m88ds3103_tuner_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static int m88ds3103_tuner_i2c_xfer(struct i2c_adapter *i2c_adap,
+		struct i2c_msg msg[], int num)
+{
+	struct m88ds3103_priv *priv = i2c_get_adapdata(i2c_adap);
+	int ret;
+	struct i2c_msg gate_open_msg[1] = {
+		{
+			.addr = priv->cfg->i2c_addr,
+			.flags = 0,
+			.len = 2,
+			.buf = "\x03\x11",
+		}
+	};
+	dev_dbg(&priv->i2c->dev, "%s: num=%d\n", __func__, num);
+
+	mutex_lock(&priv->i2c_mutex);
+
+	/* open i2c-gate */
+	ret = i2c_transfer(priv->i2c, gate_open_msg, 1);
+	if (ret != 1) {
+		mutex_unlock(&priv->i2c_mutex);
+		dev_warn(&priv->i2c->dev,
+				"%s: i2c wr failed=%d\n",
+				KBUILD_MODNAME, ret);
+		ret = -EREMOTEIO;
+		goto err;
+	}
+
+	ret = i2c_transfer(priv->i2c, msg, num);
+	mutex_unlock(&priv->i2c_mutex);
+	if (ret < 0)
+		dev_warn(&priv->i2c->dev, "%s: i2c failed=%d\n",
+				KBUILD_MODNAME, ret);
+
+	return ret;
+err:
+	dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+static struct i2c_algorithm m88ds3103_tuner_i2c_algo = {
+	.master_xfer   = m88ds3103_tuner_i2c_xfer,
+	.functionality = m88ds3103_tuner_i2c_func,
+};
+
+static void m88ds3103_release(struct dvb_frontend *fe)
+{
+	struct m88ds3103_priv *priv = fe->demodulator_priv;
+	i2c_del_adapter(&priv->i2c_adapter);
+	kfree(priv);
+}
+
+struct dvb_frontend *m88ds3103_attach(const struct m88ds3103_config *cfg,
+		struct i2c_adapter *i2c, struct i2c_adapter **tuner_i2c_adapter)
+{
+	int ret;
+	struct m88ds3103_priv *priv;
+	u8 chip_id, u8tmp;
+
+	/* allocate memory for the internal priv */
+	priv = kzalloc(sizeof(struct m88ds3103_priv), GFP_KERNEL);
+	if (!priv) {
+		ret = -ENOMEM;
+		dev_err(&i2c->dev, "%s: kzalloc() failed\n", KBUILD_MODNAME);
+		goto err;
+	}
+
+	priv->cfg = cfg;
+	priv->i2c = i2c;
+	mutex_init(&priv->i2c_mutex);
+
+	ret = m88ds3103_rd_reg(priv, 0x01, &chip_id);
+	if (ret)
+		goto err;
+
+	dev_dbg(&priv->i2c->dev, "%s: chip_id=%02x\n", __func__, chip_id);
+
+	switch (chip_id) {
+	case 0xd0:
+		break;
+	default:
+		goto err;
+	}
+
+	switch (priv->cfg->clock_out) {
+	case M88DS3103_CLOCK_OUT_DISABLED:
+		u8tmp = 0x80;
+		break;
+	case M88DS3103_CLOCK_OUT_ENABLED:
+		u8tmp = 0x00;
+		break;
+	case M88DS3103_CLOCK_OUT_ENABLED_DIV2:
+		u8tmp = 0x10;
+		break;
+	default:
+		goto err;
+	}
+
+	ret = m88ds3103_wr_reg(priv, 0x29, u8tmp);
+	if (ret)
+		goto err;
+
+	/* sleep */
+	ret = m88ds3103_wr_reg_mask(priv, 0x08, 0x00, 0x01);
+	if (ret)
+		goto err;
+
+	ret = m88ds3103_wr_reg_mask(priv, 0x04, 0x01, 0x01);
+	if (ret)
+		goto err;
+
+	ret = m88ds3103_wr_reg_mask(priv, 0x23, 0x10, 0x10);
+	if (ret)
+		goto err;
+
+	/* create dvb_frontend */
+	memcpy(&priv->fe.ops, &m88ds3103_ops, sizeof(struct dvb_frontend_ops));
+	priv->fe.demodulator_priv = priv;
+
+	/* create i2c adapter for tuner */
+	strlcpy(priv->i2c_adapter.name, KBUILD_MODNAME,
+			sizeof(priv->i2c_adapter.name));
+	priv->i2c_adapter.algo = &m88ds3103_tuner_i2c_algo;
+	priv->i2c_adapter.algo_data = NULL;
+	i2c_set_adapdata(&priv->i2c_adapter, priv);
+	ret = i2c_add_adapter(&priv->i2c_adapter);
+	if (ret) {
+		dev_err(&i2c->dev, "%s: i2c bus could not be initialized\n",
+				KBUILD_MODNAME);
+		goto err;
+	}
+	*tuner_i2c_adapter = &priv->i2c_adapter;
+
+	return &priv->fe;
+err:
+	dev_dbg(&i2c->dev, "%s: failed=%d\n", __func__, ret);
+	kfree(priv);
+	return NULL;
+}
+EXPORT_SYMBOL(m88ds3103_attach);
+
+static struct dvb_frontend_ops m88ds3103_ops = {
+	.delsys = { SYS_DVBS, SYS_DVBS2 },
+	.info = {
+		.name = "Montage M88DS3103",
+		.frequency_min =  950000,
+		.frequency_max = 2150000,
+		.frequency_tolerance = 5000,
+		.symbol_rate_min =  1000000,
+		.symbol_rate_max = 45000000,
+		.caps = FE_CAN_INVERSION_AUTO |
+			FE_CAN_FEC_1_2 |
+			FE_CAN_FEC_2_3 |
+			FE_CAN_FEC_3_4 |
+			FE_CAN_FEC_4_5 |
+			FE_CAN_FEC_5_6 |
+			FE_CAN_FEC_6_7 |
+			FE_CAN_FEC_7_8 |
+			FE_CAN_FEC_8_9 |
+			FE_CAN_FEC_AUTO |
+			FE_CAN_QPSK |
+			FE_CAN_RECOVER |
+			FE_CAN_2G_MODULATION
+	},
+
+	.release = m88ds3103_release,
+
+	.get_tune_settings = m88ds3103_get_tune_settings,
+
+	.init = m88ds3103_init,
+	.sleep = m88ds3103_sleep,
+
+	.set_frontend = m88ds3103_set_frontend,
+	.get_frontend = m88ds3103_get_frontend,
+
+	.read_status = m88ds3103_read_status,
+	.read_snr = m88ds3103_read_snr,
+
+	.diseqc_send_master_cmd = m88ds3103_diseqc_send_master_cmd,
+	.diseqc_send_burst = m88ds3103_diseqc_send_burst,
+
+	.set_tone = m88ds3103_set_tone,
+};
+
+MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
+MODULE_DESCRIPTION("Montage M88DS3103 DVB-S/S2 demodulator driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(M88DS3103_FIRMWARE);
diff --git a/drivers/media/dvb-frontends/m88ds3103.h b/drivers/media/dvb-frontends/m88ds3103.h
new file mode 100644
index 0000000..287d62a
--- /dev/null
+++ b/drivers/media/dvb-frontends/m88ds3103.h
@@ -0,0 +1,108 @@
+/*
+ * Montage M88DS3103 demodulator driver
+ *
+ * Copyright (C) 2013 Antti Palosaari <crope@iki.fi>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    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.,
+ *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef M88DS3103_H
+#define M88DS3103_H
+
+#include <linux/dvb/frontend.h>
+
+struct m88ds3103_config {
+	/*
+	 * I2C address
+	 * 0x68, ...
+	 */
+	u8 i2c_addr;
+
+	/*
+	 * clock
+	 * 27000000
+	 */
+	u32 clock;
+
+	/*
+	 * max bytes I2C provider is asked to write at once
+	 * Note: Buffer is taken from the stack currently!
+	 * Value must be set.
+	 * 33, 65, ...
+	 */
+	u16 i2c_wr_max;
+
+	/*
+	 * TS output mode
+	 */
+#define M88DS3103_TS_SERIAL             0 /* TS output pin D0, normal */
+#define M88DS3103_TS_SERIAL_D7          1 /* TS output pin D7 */
+#define M88DS3103_TS_PARALLEL           2 /* 24 MHz, normal */
+#define M88DS3103_TS_PARALLEL_12        3 /* 12 MHz */
+#define M88DS3103_TS_PARALLEL_16        4 /* 16 MHz */
+#define M88DS3103_TS_PARALLEL_19_2      5 /* 19.2 MHz */
+#define M88DS3103_TS_CI                 6 /* 6 MHz */
+	u8 ts_mode;
+
+	/*
+	 * spectrum inversion
+	 */
+	u8 spec_inv:1;
+
+	/*
+	 * AGC polarity
+	 */
+	u8 agc_inv:1;
+
+	/*
+	 * clock output
+	 */
+#define M88DS3103_CLOCK_OUT_DISABLED        0
+#define M88DS3103_CLOCK_OUT_ENABLED         1
+#define M88DS3103_CLOCK_OUT_ENABLED_DIV2    2
+	u8 clock_out;
+
+	/*
+	 * DiSEqC envelope mode
+	 */
+	u8 envelope_mode:1;
+
+	u8 agc;
+};
+
+/*
+ * Driver implements own I2C-adapter for tuner I2C access. That's since chip
+ * has I2C-gate control which closes gate automatically after I2C transfer.
+ * Using own I2C adapter we can workaround that.
+ */
+
+#if defined(CONFIG_DVB_M88DS3103) || \
+		(defined(CONFIG_DVB_M88DS3103_MODULE) && defined(MODULE))
+extern struct dvb_frontend *m88ds3103_attach(
+		const struct m88ds3103_config *config,
+		struct i2c_adapter *i2c,
+		struct i2c_adapter **tuner_i2c);
+#else
+static inline struct dvb_frontend *m88ds3103_attach(
+		const struct m88ds3103_config *config,
+		struct i2c_adapter *i2c,
+		struct i2c_adapter **tuner_i2c)
+{
+	pr_warn("%s: driver disabled by Kconfig\n", __func__);
+	return NULL;
+}
+#endif
+
+#endif
diff --git a/drivers/media/dvb-frontends/m88ds3103_priv.h b/drivers/media/dvb-frontends/m88ds3103_priv.h
new file mode 100644
index 0000000..f3d0867
--- /dev/null
+++ b/drivers/media/dvb-frontends/m88ds3103_priv.h
@@ -0,0 +1,218 @@
+/*
+ * Montage M88DS3103 demodulator driver
+ *
+ * Copyright (C) 2013 Antti Palosaari <crope@iki.fi>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    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.,
+ *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef M88DS3103_PRIV_H
+#define M88DS3103_PRIV_H
+
+#include "dvb_frontend.h"
+#include "m88ds3103.h"
+#include "dvb_math.h"
+#include <linux/firmware.h>
+
+#define M88DS3103_FIRMWARE "dvb-demod-m88ds3103.fw"
+#define M88DS3103_MCLK_KHZ 96000
+
+struct m88ds3103_priv {
+	struct i2c_adapter *i2c;
+	/* mutex needed due to own tuner I2C adapter */
+	struct mutex i2c_mutex;
+	const struct m88ds3103_config *cfg;
+	struct dvb_frontend fe;
+	fe_delivery_system_t delivery_system;
+	fe_status_t fe_status;
+	bool warm; /* FW running */
+	struct i2c_adapter i2c_adapter;
+};
+
+struct m88ds3103_reg_val {
+	u8 reg;
+	u8 val;
+};
+
+static const struct m88ds3103_reg_val m88ds3103_dvbs_init_reg_vals[] = {
+	{0x23, 0x07},
+	{0x08, 0x03},
+	{0x0c, 0x02},
+	{0x21, 0x54},
+	{0x25, 0x8a},
+	{0x27, 0x31},
+	{0x30, 0x08},
+	{0x31, 0x40},
+	{0x32, 0x32},
+	{0x33, 0x35},
+	{0x35, 0xff},
+	{0x3a, 0x00},
+	{0x37, 0x10},
+	{0x38, 0x10},
+	{0x39, 0x02},
+	{0x42, 0x60},
+	{0x4a, 0x80},
+	{0x4b, 0x04},
+	{0x4d, 0x91},
+	{0x5d, 0xc8},
+	{0x50, 0x36},
+	{0x51, 0x36},
+	{0x52, 0x36},
+	{0x53, 0x36},
+	{0x63, 0x0f},
+	{0x64, 0x30},
+	{0x65, 0x40},
+	{0x68, 0x26},
+	{0x69, 0x4c},
+	{0x70, 0x20},
+	{0x71, 0x70},
+	{0x72, 0x04},
+	{0x73, 0x00},
+	{0x70, 0x40},
+	{0x71, 0x70},
+	{0x72, 0x04},
+	{0x73, 0x00},
+	{0x70, 0x60},
+	{0x71, 0x70},
+	{0x72, 0x04},
+	{0x73, 0x00},
+	{0x70, 0x80},
+	{0x71, 0x70},
+	{0x72, 0x04},
+	{0x73, 0x00},
+	{0x70, 0xa0},
+	{0x71, 0x70},
+	{0x72, 0x04},
+	{0x73, 0x00},
+	{0x70, 0x1f},
+	{0x76, 0x38},
+	{0x77, 0xa6},
+	{0x78, 0x0c},
+	{0x79, 0x80},
+	{0x7f, 0x14},
+	{0x7c, 0x00},
+	{0xae, 0x82},
+	{0x80, 0x64},
+	{0x81, 0x66},
+	{0x82, 0x44},
+	{0x85, 0x04},
+	{0xcd, 0xf4},
+	{0x90, 0x33},
+	{0xa0, 0x44},
+	{0xc0, 0x08},
+	{0xc3, 0x10},
+	{0xc4, 0x08},
+	{0xc5, 0xf0},
+	{0xc6, 0xff},
+	{0xc7, 0x00},
+	{0xc8, 0x1a},
+	{0xc9, 0x80},
+	{0xe0, 0xf8},
+	{0xe6, 0x8b},
+	{0xd0, 0x40},
+	{0xf8, 0x20},
+	{0xfa, 0x0f},
+	{0x00, 0x00},
+	{0xbd, 0x01},
+	{0xb8, 0x00},
+};
+
+static const struct m88ds3103_reg_val m88ds3103_dvbs2_init_reg_vals[] = {
+	{0x23, 0x07},
+	{0x08, 0x07},
+	{0x0c, 0x02},
+	{0x21, 0x54},
+	{0x25, 0x8a},
+	{0x27, 0x31},
+	{0x30, 0x08},
+	{0x32, 0x32},
+	{0x33, 0x35},
+	{0x35, 0xff},
+	{0x3a, 0x00},
+	{0x37, 0x10},
+	{0x38, 0x10},
+	{0x39, 0x02},
+	{0x42, 0x60},
+	{0x4a, 0x80},
+	{0x4b, 0x04},
+	{0x4d, 0x91},
+	{0x5d, 0xc8},
+	{0x50, 0x36},
+	{0x51, 0x36},
+	{0x52, 0x36},
+	{0x53, 0x36},
+	{0x63, 0x0f},
+	{0x64, 0x10},
+	{0x65, 0x20},
+	{0x68, 0x46},
+	{0x69, 0xcd},
+	{0x70, 0x20},
+	{0x71, 0x70},
+	{0x72, 0x04},
+	{0x73, 0x00},
+	{0x70, 0x40},
+	{0x71, 0x70},
+	{0x72, 0x04},
+	{0x73, 0x00},
+	{0x70, 0x60},
+	{0x71, 0x70},
+	{0x72, 0x04},
+	{0x73, 0x00},
+	{0x70, 0x80},
+	{0x71, 0x70},
+	{0x72, 0x04},
+	{0x73, 0x00},
+	{0x70, 0xa0},
+	{0x71, 0x70},
+	{0x72, 0x04},
+	{0x73, 0x00},
+	{0x70, 0x1f},
+	{0x76, 0x38},
+	{0x77, 0xa6},
+	{0x78, 0x0c},
+	{0x79, 0x80},
+	{0x7f, 0x14},
+	{0x85, 0x08},
+	{0xcd, 0xf4},
+	{0x90, 0x33},
+	{0x86, 0x00},
+	{0x87, 0x0f},
+	{0x89, 0x00},
+	{0x8b, 0x44},
+	{0x8c, 0x66},
+	{0x9d, 0xc1},
+	{0x8a, 0x10},
+	{0xad, 0x40},
+	{0xa0, 0x44},
+	{0xc0, 0x08},
+	{0xc1, 0x10},
+	{0xc2, 0x08},
+	{0xc3, 0x10},
+	{0xc4, 0x08},
+	{0xc5, 0xf0},
+	{0xc6, 0xff},
+	{0xc7, 0x00},
+	{0xc8, 0x1a},
+	{0xc9, 0x80},
+	{0xca, 0x23},
+	{0xcb, 0x24},
+	{0xcc, 0xf4},
+	{0xce, 0x74},
+	{0x00, 0x00},
+	{0xbd, 0x01},
+	{0xb8, 0x00},
+};
+
+#endif
-- 
1.8.4.2


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

* [PATCH 4/8] Montage M88TS2022 silicon tuner driver
  2013-11-06 17:57 [PATCH 0/8] PCTV DVB-S2 Stick (461e) [2013:0258] driver Antti Palosaari
                   ` (2 preceding siblings ...)
  2013-11-06 17:57 ` [PATCH 3/8] Montage M88DS3103 DVB-S/S2 demodulator driver Antti Palosaari
@ 2013-11-06 17:57 ` Antti Palosaari
  2013-11-06 17:57 ` [PATCH 5/8] em28xx: add support for PCTV DVB-S2 Stick (461e) [2013:0258] Antti Palosaari
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 12+ messages in thread
From: Antti Palosaari @ 2013-11-06 17:57 UTC (permalink / raw)
  To: linux-media; +Cc: Antti Palosaari

---
 drivers/media/tuners/Kconfig          |   7 +
 drivers/media/tuners/Makefile         |   1 +
 drivers/media/tuners/m88ts2022.c      | 664 ++++++++++++++++++++++++++++++++++
 drivers/media/tuners/m88ts2022.h      |  72 ++++
 drivers/media/tuners/m88ts2022_priv.h |  38 ++
 5 files changed, 782 insertions(+)
 create mode 100644 drivers/media/tuners/m88ts2022.c
 create mode 100644 drivers/media/tuners/m88ts2022.h
 create mode 100644 drivers/media/tuners/m88ts2022_priv.h

diff --git a/drivers/media/tuners/Kconfig b/drivers/media/tuners/Kconfig
index 15665de..ba2e365 100644
--- a/drivers/media/tuners/Kconfig
+++ b/drivers/media/tuners/Kconfig
@@ -215,6 +215,13 @@ config MEDIA_TUNER_FC2580
 	help
 	  FCI FC2580 silicon tuner driver.
 
+config MEDIA_TUNER_M88TS2022
+	tristate "Montage M88TS2022 silicon tuner"
+	depends on MEDIA_SUPPORT && I2C
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Montage M88TS2022 silicon tuner driver.
+
 config MEDIA_TUNER_TUA9001
 	tristate "Infineon TUA 9001 silicon tuner"
 	depends on MEDIA_SUPPORT && I2C
diff --git a/drivers/media/tuners/Makefile b/drivers/media/tuners/Makefile
index 308f108..efe82a9 100644
--- a/drivers/media/tuners/Makefile
+++ b/drivers/media/tuners/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_MEDIA_TUNER_TDA18212) += tda18212.o
 obj-$(CONFIG_MEDIA_TUNER_E4000) += e4000.o
 obj-$(CONFIG_MEDIA_TUNER_FC2580) += fc2580.o
 obj-$(CONFIG_MEDIA_TUNER_TUA9001) += tua9001.o
+obj-$(CONFIG_MEDIA_TUNER_M88TS2022) += m88ts2022.o
 obj-$(CONFIG_MEDIA_TUNER_FC0011) += fc0011.o
 obj-$(CONFIG_MEDIA_TUNER_FC0012) += fc0012.o
 obj-$(CONFIG_MEDIA_TUNER_FC0013) += fc0013.o
diff --git a/drivers/media/tuners/m88ts2022.c b/drivers/media/tuners/m88ts2022.c
new file mode 100644
index 0000000..ef69db4
--- /dev/null
+++ b/drivers/media/tuners/m88ts2022.c
@@ -0,0 +1,664 @@
+/*
+ * Montage M88TS2022 silicon tuner driver
+ *
+ * Copyright (C) 2013 Antti Palosaari <crope@iki.fi>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    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.,
+ *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "m88ts2022_priv.h"
+
+/* write multiple registers */
+static int m88ts2022_wr_regs(struct m88ts2022_priv *priv,
+		u8 reg, const u8 *val, int len)
+{
+	int ret;
+	u8 buf[1 + len];
+	struct i2c_msg msg[1] = {
+		{
+			.addr = priv->cfg->i2c_addr,
+			.flags = 0,
+			.len = sizeof(buf),
+			.buf = buf,
+		}
+	};
+
+	buf[0] = reg;
+	memcpy(&buf[1], val, len);
+
+	ret = i2c_transfer(priv->i2c, msg, 1);
+	if (ret == 1) {
+		ret = 0;
+	} else {
+		dev_warn(&priv->i2c->dev,
+				"%s: i2c wr failed=%d reg=%02x len=%d\n",
+				KBUILD_MODNAME, ret, reg, len);
+		ret = -EREMOTEIO;
+	}
+
+	return ret;
+}
+
+/* read multiple registers */
+static int m88ts2022_rd_regs(struct m88ts2022_priv *priv, u8 reg,
+		u8 *val, int len)
+{
+	int ret;
+	u8 buf[len];
+	struct i2c_msg msg[2] = {
+		{
+			.addr = priv->cfg->i2c_addr,
+			.flags = 0,
+			.len = 1,
+			.buf = &reg,
+		}, {
+			.addr = priv->cfg->i2c_addr,
+			.flags = I2C_M_RD,
+			.len = sizeof(buf),
+			.buf = buf,
+		}
+	};
+
+	ret = i2c_transfer(priv->i2c, msg, 2);
+	if (ret == 2) {
+		memcpy(val, buf, len);
+		ret = 0;
+	} else {
+		dev_warn(&priv->i2c->dev,
+				"%s: i2c rd failed=%d reg=%02x len=%d\n",
+				KBUILD_MODNAME, ret, reg, len);
+		ret = -EREMOTEIO;
+	}
+
+	return ret;
+}
+
+/* write single register */
+static int m88ts2022_wr_reg(struct m88ts2022_priv *priv, u8 reg, u8 val)
+{
+	return m88ts2022_wr_regs(priv, reg, &val, 1);
+}
+
+/* read single register */
+static int m88ts2022_rd_reg(struct m88ts2022_priv *priv, u8 reg, u8 *val)
+{
+	return m88ts2022_rd_regs(priv, reg, val, 1);
+}
+
+/* write single register with mask */
+static int m88ts2022_wr_reg_mask(struct m88ts2022_priv *priv,
+		u8 reg, u8 val, u8 mask)
+{
+	int ret;
+	u8 u8tmp;
+
+	/* no need for read if whole reg is written */
+	if (mask != 0xff) {
+		ret = m88ts2022_rd_regs(priv, reg, &u8tmp, 1);
+		if (ret)
+			return ret;
+
+		val &= mask;
+		u8tmp &= ~mask;
+		val |= u8tmp;
+	}
+
+	return m88ts2022_wr_regs(priv, reg, &val, 1);
+}
+
+static int m88ts2022_cmd(struct dvb_frontend *fe,
+		int op, int sleep, u8 reg, u8 mask, u8 val, u8 *reg_val)
+{
+	struct m88ts2022_priv *priv = fe->tuner_priv;
+	int ret, i;
+	u8 u8tmp;
+	struct m88ts2022_reg_val reg_vals[] = {
+		{0x51, 0x1f - op},
+		{0x51, 0x1f},
+		{0x50, 0x00 + op},
+		{0x50, 0x00},
+	};
+
+	for (i = 0; i < 2; i++) {
+		dev_dbg(&priv->i2c->dev,
+				"%s: i=%d op=%02x reg=%02x mask=%02x val=%02x\n",
+				__func__, i, op, reg, mask, val);
+
+		for (i = 0; i < ARRAY_SIZE(reg_vals); i++) {
+			ret = m88ts2022_wr_reg(priv, reg_vals[i].reg,
+					reg_vals[i].val);
+			if (ret)
+				goto err;
+		}
+
+		usleep_range(sleep * 1000, sleep * 10000);
+
+		ret = m88ts2022_rd_reg(priv, reg, &u8tmp);
+		if (ret)
+			goto err;
+
+		if ((u8tmp & mask) != val)
+			break;
+	}
+
+	if (reg_val)
+		*reg_val = u8tmp;
+err:
+	return ret;
+}
+
+static int m88ts2022_set_params(struct dvb_frontend *fe)
+{
+	struct m88ts2022_priv *priv = fe->tuner_priv;
+	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+	int ret = 0, div;
+	u8 buf[3], u8tmp, cap_code, lpf_mxdiv, div_max, div_min;
+	u16 N_reg, N, K;
+	u32 lpf_gm, lpf_coeff, gdiv28, frequency_khz, frequency_offset;
+	u32 freq_3db;
+	dev_dbg(&priv->i2c->dev,
+			"%s: frequency=%d symbol_rate=%d rolloff=%d\n",
+			__func__, c->frequency, c->symbol_rate, c->rolloff);
+
+	if (c->symbol_rate < 5000000)
+		frequency_offset = 3000000; /* 3 MHz */
+	else
+		frequency_offset = 0;
+
+	frequency_khz = c->frequency + (frequency_offset / 1000);
+
+	if (frequency_khz < 1103000) {
+		div = 2;
+		u8tmp = 0x1b;
+	} else {
+		div = 1;
+		u8tmp = 0x0b;
+	}
+
+	buf[0] = u8tmp;
+	buf[1] = 0x40;
+	ret = m88ts2022_wr_regs(priv, 0x10, buf, 2);
+	if (ret)
+		goto err;
+
+	K = DIV_ROUND_CLOSEST((priv->cfg->clock / 2), 1000000);
+	N = 1ul * frequency_khz * K * div * 2 / (priv->cfg->clock / 1000);
+	N += N % 2;
+
+	if (N < 4095)
+		N_reg = N - 1024;
+	else if (N < 6143)
+		N_reg = N + 1024;
+	else
+		N_reg = N + 3072;
+
+	buf[0] = (N_reg >> 8) & 0x3f;
+	buf[1] = (N_reg >> 0) & 0xff;
+	buf[2] = K - 8;
+	ret = m88ts2022_wr_regs(priv, 0x01, buf, 3);
+	if (ret)
+		goto err;
+
+	priv->frequency_khz = 1ul * N * (priv->cfg->clock / 1000) / K / div / 2;
+
+	dev_dbg(&priv->i2c->dev,
+			"%s: frequency=%d offset=%d K=%d N=%d div=%d\n",
+			__func__, priv->frequency_khz,
+			priv->frequency_khz - c->frequency, K, N, div);
+
+	ret = m88ts2022_cmd(fe, 0x10, 5, 0x15, 0x40, 0x00, NULL);
+	if (ret)
+		goto err;
+
+	ret = m88ts2022_rd_reg(priv, 0x14, &u8tmp);
+	if (ret)
+		goto err;
+
+	u8tmp &= 0x7f;
+	if (u8tmp < 64) {
+		ret = m88ts2022_wr_reg_mask(priv, 0x10, 0x80, 0x80);
+		if (ret)
+			goto err;
+
+		ret = m88ts2022_wr_reg(priv, 0x11, 0x6f);
+		if (ret)
+			goto err;
+
+		ret = m88ts2022_cmd(fe, 0x10, 5, 0x15, 0x40, 0x00, NULL);
+		if (ret)
+			goto err;
+	}
+
+	ret = m88ts2022_rd_reg(priv, 0x14, &u8tmp);
+	if (ret)
+		goto err;
+
+	u8tmp &= 0x1f;
+	if (u8tmp > 19) {
+		ret = m88ts2022_wr_reg_mask(priv, 0x10, 0x00, 0x02);
+		if (ret)
+			goto err;
+	}
+
+	ret = m88ts2022_cmd(fe, 0x08, 5, 0x3c, 0xff, 0x00, NULL);
+	if (ret)
+		goto err;
+
+	ret = m88ts2022_wr_reg(priv, 0x25, 0x00);
+	if (ret)
+		goto err;
+
+	ret = m88ts2022_wr_reg(priv, 0x27, 0x70);
+	if (ret)
+		goto err;
+
+	ret = m88ts2022_wr_reg(priv, 0x41, 0x09);
+	if (ret)
+		goto err;
+
+	ret = m88ts2022_wr_reg(priv, 0x08, 0x0b);
+	if (ret)
+		goto err;
+
+	gdiv28 = DIV_ROUND_CLOSEST(priv->cfg->clock / 1000000 * 1694, 1000);
+
+	ret = m88ts2022_wr_reg(priv, 0x04, gdiv28);
+	if (ret)
+		goto err;
+
+	ret = m88ts2022_cmd(fe, 0x04, 2, 0x26, 0xff, 0x00, &u8tmp);
+	if (ret)
+		goto err;
+
+	cap_code = u8tmp & 0x3f;
+
+	ret = m88ts2022_wr_reg(priv, 0x41, 0x0d);
+	if (ret)
+		goto err;
+
+	ret = m88ts2022_cmd(fe, 0x04, 2, 0x26, 0xff, 0x00, &u8tmp);
+	if (ret)
+		goto err;
+
+	u8tmp &= 0x3f;
+	cap_code = (cap_code + u8tmp) / 2;
+	gdiv28 = gdiv28 * 207 / (cap_code * 2 + 151);
+	div_max = gdiv28 * 135 / 100;
+	div_min = gdiv28 * 78 / 100;
+	if (div_max > 63)
+		div_max = 63;
+
+	freq_3db = 1ul * c->symbol_rate * 135 / 200 + 2000000;
+	freq_3db += frequency_offset;
+	if (freq_3db < 7000000)
+		freq_3db = 7000000;
+	if (freq_3db > 40000000)
+		freq_3db = 40000000;
+
+	lpf_coeff = 3200;
+	lpf_gm = DIV_ROUND_CLOSEST(freq_3db * gdiv28, lpf_coeff *
+			(priv->cfg->clock / 1000));
+	if (lpf_gm > 23)
+		lpf_gm = 23;
+	if (lpf_gm < 1)
+		lpf_gm = 1;
+
+	lpf_mxdiv = DIV_ROUND_CLOSEST(lpf_gm * lpf_coeff *
+			(priv->cfg->clock / 1000), freq_3db);
+
+	if (lpf_mxdiv < div_min) {
+		lpf_gm++;
+		lpf_mxdiv = DIV_ROUND_CLOSEST(lpf_gm * lpf_coeff *
+				(priv->cfg->clock / 1000), freq_3db);
+	}
+
+	if (lpf_mxdiv > div_max)
+		lpf_mxdiv = div_max;
+
+	ret = m88ts2022_wr_reg(priv, 0x04, lpf_mxdiv);
+	if (ret)
+		goto err;
+
+	ret = m88ts2022_wr_reg(priv, 0x06, lpf_gm);
+	if (ret)
+		goto err;
+
+	ret = m88ts2022_cmd(fe, 0x04, 2, 0x26, 0xff, 0x00, &u8tmp);
+	if (ret)
+		goto err;
+
+	cap_code = u8tmp & 0x3f;
+
+	ret = m88ts2022_wr_reg(priv, 0x41, 0x09);
+	if (ret)
+		goto err;
+
+	ret = m88ts2022_cmd(fe, 0x04, 2, 0x26, 0xff, 0x00, &u8tmp);
+	if (ret)
+		goto err;
+
+	u8tmp &= 0x3f;
+	cap_code = (cap_code + u8tmp) / 2;
+
+	u8tmp = cap_code | 0x80;
+	ret = m88ts2022_wr_reg(priv, 0x25, u8tmp);
+	if (ret)
+		goto err;
+
+	ret = m88ts2022_wr_reg(priv, 0x27, 0x30);
+	if (ret)
+		goto err;
+
+	ret = m88ts2022_wr_reg(priv, 0x08, 0x09);
+	if (ret)
+		goto err;
+
+	ret = m88ts2022_cmd(fe, 0x01, 20, 0x21, 0xff, 0x00, NULL);
+	if (ret)
+		goto err;
+err:
+	if (ret)
+		dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
+
+	return ret;
+}
+
+static int m88ts2022_init(struct dvb_frontend *fe)
+{
+	struct m88ts2022_priv *priv = fe->tuner_priv;
+	int ret, i;
+	u8 u8tmp;
+	static const struct m88ts2022_reg_val reg_vals[] = {
+		{0x7d, 0x9d},
+		{0x7c, 0x9a},
+		{0x7a, 0x76},
+		{0x3b, 0x01},
+		{0x63, 0x88},
+		{0x61, 0x85},
+		{0x22, 0x30},
+		{0x30, 0x40},
+		{0x20, 0x23},
+		{0x24, 0x02},
+		{0x12, 0xa0},
+	};
+	dev_dbg(&priv->i2c->dev, "%s:\n", __func__);
+
+	ret = m88ts2022_wr_reg(priv, 0x00, 0x01);
+	if (ret)
+		goto err;
+
+	ret = m88ts2022_wr_reg(priv, 0x00, 0x03);
+	if (ret)
+		goto err;
+
+	switch (priv->cfg->clock_out) {
+	case M88TS2022_CLOCK_OUT_DISABLED:
+		u8tmp = 0x60;
+		break;
+	case M88TS2022_CLOCK_OUT_ENABLED:
+		u8tmp = 0x70;
+		ret = m88ts2022_wr_reg(priv, 0x05, priv->cfg->clock_out_div);
+		if (ret)
+			goto err;
+		break;
+	case M88TS2022_CLOCK_OUT_ENABLED_XTALOUT:
+		u8tmp = 0x6c;
+		break;
+	default:
+		goto err;
+	}
+
+	ret = m88ts2022_wr_reg(priv, 0x42, u8tmp);
+	if (ret)
+		goto err;
+
+	if (priv->cfg->loop_through)
+		u8tmp = 0xec;
+	else
+		u8tmp = 0x6c;
+
+	ret = m88ts2022_wr_reg(priv, 0x62, u8tmp);
+	if (ret)
+		goto err;
+
+	for (i = 0; i < ARRAY_SIZE(reg_vals); i++) {
+		ret = m88ts2022_wr_reg(priv, reg_vals[i].reg, reg_vals[i].val);
+		if (ret)
+			goto err;
+	}
+err:
+	if (ret)
+		dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+static int m88ts2022_sleep(struct dvb_frontend *fe)
+{
+	struct m88ts2022_priv *priv = fe->tuner_priv;
+	int ret;
+	dev_dbg(&priv->i2c->dev, "%s:\n", __func__);
+
+	ret = m88ts2022_wr_reg(priv, 0x00, 0x00);
+	if (ret)
+		goto err;
+err:
+	if (ret)
+		dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+static int m88ts2022_get_frequency(struct dvb_frontend *fe, u32 *frequency)
+{
+	struct m88ts2022_priv *priv = fe->tuner_priv;
+	dev_dbg(&priv->i2c->dev, "%s:\n", __func__);
+
+	*frequency = priv->frequency_khz;
+	return 0;
+}
+
+static int m88ts2022_get_if_frequency(struct dvb_frontend *fe, u32 *frequency)
+{
+	struct m88ts2022_priv *priv = fe->tuner_priv;
+	dev_dbg(&priv->i2c->dev, "%s:\n", __func__);
+
+	*frequency = 0; /* Zero-IF */
+	return 0;
+}
+
+static int m88ts2022_get_rf_strength(struct dvb_frontend *fe, u16 *strength)
+{
+	struct m88ts2022_priv *priv = fe->tuner_priv;
+	u8  u8tmp, gain1, gain2, gain3;
+	u16 gain, u16tmp;
+	int ret;
+
+	ret = m88ts2022_rd_reg(priv, 0x3d, &u8tmp);
+	if (ret)
+		goto err;
+
+	gain1 = (u8tmp >> 0) & 0x1f;
+	if (gain1 > 15)
+		gain1 = 15;
+
+	ret = m88ts2022_rd_reg(priv, 0x21, &u8tmp);
+	if (ret)
+		goto err;
+
+	gain2 = (u8tmp >> 0) & 0x1f;
+	if (gain2 < 2)
+		gain2 = 2;
+	if (gain2 > 16)
+		gain2 = 16;
+
+	ret = m88ts2022_rd_reg(priv, 0x66, &u8tmp);
+	if (ret)
+		goto err;
+
+	gain3 = (u8tmp >> 3) & 0x07;
+	if (gain3 > 6)
+		gain3 = 6;
+
+	gain = gain1 * 265 + gain2 * 338 + gain3 * 285;
+
+	/* scale value to 0x0000-0xffff */
+	u16tmp = (0xffff - gain);
+	if (u16tmp < 59000)
+		u16tmp = 59000;
+	else if (u16tmp > 61500)
+		u16tmp = 61500;
+
+	*strength = (u16tmp - 59000) * 0xffff / (61500 - 59000);
+err:
+	if (ret)
+		dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+static int m88ts2022_release(struct dvb_frontend *fe)
+{
+	struct m88ts2022_priv *priv = fe->tuner_priv;
+	dev_dbg(&priv->i2c->dev, "%s:\n", __func__);
+
+	kfree(fe->tuner_priv);
+	return 0;
+}
+
+static const struct dvb_tuner_ops m88ts2022_tuner_ops = {
+	.info = {
+		.name          = "Montage M88TS2022",
+		.frequency_min = 950000,
+		.frequency_max = 2150000,
+	},
+
+	.release = m88ts2022_release,
+
+	.init = m88ts2022_init,
+	.sleep = m88ts2022_sleep,
+	.set_params = m88ts2022_set_params,
+
+	.get_frequency = m88ts2022_get_frequency,
+	.get_if_frequency = m88ts2022_get_if_frequency,
+	.get_rf_strength = m88ts2022_get_rf_strength,
+};
+
+struct dvb_frontend *m88ts2022_attach(struct dvb_frontend *fe,
+		struct i2c_adapter *i2c, const struct m88ts2022_config *cfg)
+{
+	struct m88ts2022_priv *priv;
+	int ret;
+	u8 chip_id, u8tmp;
+
+	priv = kzalloc(sizeof(struct m88ts2022_priv), GFP_KERNEL);
+	if (!priv) {
+		ret = -ENOMEM;
+		dev_err(&i2c->dev, "%s: kzalloc() failed\n", KBUILD_MODNAME);
+		goto err;
+	}
+
+	priv->cfg = cfg;
+	priv->i2c = i2c;
+	priv->fe = fe;
+
+	/* check if the tuner is there */
+	ret = m88ts2022_rd_reg(priv, 0x00, &u8tmp);
+	if (ret)
+		goto err;
+
+	if ((u8tmp & 0x03) == 0x00) {
+		ret = m88ts2022_wr_reg(priv, 0x00, 0x01);
+		if (ret < 0)
+			goto err;
+
+		usleep_range(2000, 50000);
+	}
+
+	ret = m88ts2022_wr_reg(priv, 0x00, 0x03);
+	if (ret)
+		goto err;
+
+	usleep_range(2000, 50000);
+
+	ret = m88ts2022_rd_reg(priv, 0x00, &chip_id);
+	if (ret)
+		goto err;
+
+	dev_dbg(&priv->i2c->dev, "%s: chip_id=%02x\n", __func__, chip_id);
+
+	switch (chip_id) {
+	case 0xc3:
+	case 0x83:
+		break;
+	default:
+		goto err;
+	}
+
+	switch (priv->cfg->clock_out) {
+	case M88TS2022_CLOCK_OUT_DISABLED:
+		u8tmp = 0x60;
+		break;
+	case M88TS2022_CLOCK_OUT_ENABLED:
+		u8tmp = 0x70;
+		ret = m88ts2022_wr_reg(priv, 0x05, priv->cfg->clock_out_div);
+		if (ret)
+			goto err;
+		break;
+	case M88TS2022_CLOCK_OUT_ENABLED_XTALOUT:
+		u8tmp = 0x6c;
+		break;
+	default:
+		goto err;
+	}
+
+	ret = m88ts2022_wr_reg(priv, 0x42, u8tmp);
+	if (ret)
+		goto err;
+
+	if (priv->cfg->loop_through)
+		u8tmp = 0xec;
+	else
+		u8tmp = 0x6c;
+
+	ret = m88ts2022_wr_reg(priv, 0x62, u8tmp);
+	if (ret)
+		goto err;
+
+	/* sleep */
+	ret = m88ts2022_wr_reg(priv, 0x00, 0x00);
+	if (ret)
+		goto err;
+
+	dev_info(&priv->i2c->dev,
+			"%s: Montage M88TS2022 successfully identified\n",
+			KBUILD_MODNAME);
+
+	fe->tuner_priv = priv;
+	memcpy(&fe->ops.tuner_ops, &m88ts2022_tuner_ops,
+			sizeof(struct dvb_tuner_ops));
+err:
+	if (ret) {
+		dev_dbg(&i2c->dev, "%s: failed=%d\n", __func__, ret);
+		kfree(priv);
+		return NULL;
+	}
+
+	fe->ops.read_signal_strength = fe->ops.tuner_ops.get_rf_strength;
+
+	return fe;
+}
+EXPORT_SYMBOL(m88ts2022_attach);
+
+MODULE_DESCRIPTION("Montage M88TS2022 silicon tuner driver");
+MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/tuners/m88ts2022.h b/drivers/media/tuners/m88ts2022.h
new file mode 100644
index 0000000..fa1112c
--- /dev/null
+++ b/drivers/media/tuners/m88ts2022.h
@@ -0,0 +1,72 @@
+/*
+ * Montage M88TS2022 silicon tuner driver
+ *
+ * Copyright (C) 2013 Antti Palosaari <crope@iki.fi>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    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.,
+ *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef M88TS2022_H
+#define M88TS2022_H
+
+#include "dvb_frontend.h"
+
+struct m88ts2022_config {
+	/*
+	 * I2C address
+	 * 0x60, ...
+	 */
+	u8 i2c_addr;
+
+	/*
+	 * clock
+	 * 16000000 - 32000000
+	 */
+	u32 clock;
+
+	/*
+	 * RF loop-through
+	 */
+	u8 loop_through:1;
+
+	/*
+	 * clock output
+	 */
+#define M88TS2022_CLOCK_OUT_DISABLED        0
+#define M88TS2022_CLOCK_OUT_ENABLED         1
+#define M88TS2022_CLOCK_OUT_ENABLED_XTALOUT 2
+	u8 clock_out:2;
+
+	/*
+	 * clock output divider
+	 * 1 - 31
+	 */
+	u8 clock_out_div:5;
+};
+
+#if defined(CONFIG_MEDIA_TUNER_M88TS2022) || \
+	(defined(CONFIG_MEDIA_TUNER_M88TS2022_MODULE) && defined(MODULE))
+extern struct dvb_frontend *m88ts2022_attach(struct dvb_frontend *fe,
+	struct i2c_adapter *i2c, const struct m88ts2022_config *cfg);
+#else
+static inline struct dvb_frontend *m88ts2022_attach(struct dvb_frontend *fe,
+	struct i2c_adapter *i2c, const struct m88ts2022_config *cfg)
+{
+	pr_warn("%s: driver disabled by Kconfig\n", __func__);
+	return NULL;
+}
+#endif
+
+#endif
diff --git a/drivers/media/tuners/m88ts2022_priv.h b/drivers/media/tuners/m88ts2022_priv.h
new file mode 100644
index 0000000..190299a
--- /dev/null
+++ b/drivers/media/tuners/m88ts2022_priv.h
@@ -0,0 +1,38 @@
+/*
+ * Montage M88TS2022 silicon tuner driver
+ *
+ * Copyright (C) 2013 Antti Palosaari <crope@iki.fi>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    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.,
+ *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef M88TS2022_PRIV_H
+#define M88TS2022_PRIV_H
+
+#include "m88ts2022.h"
+
+struct m88ts2022_priv {
+	const struct m88ts2022_config *cfg;
+	struct i2c_adapter *i2c;
+	struct dvb_frontend *fe;
+	u32 frequency_khz;
+};
+
+struct m88ts2022_reg_val {
+	u8 reg;
+	u8 val;
+};
+
+#endif
-- 
1.8.4.2


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

* [PATCH 5/8] em28xx: add support for PCTV DVB-S2 Stick (461e) [2013:0258]
  2013-11-06 17:57 [PATCH 0/8] PCTV DVB-S2 Stick (461e) [2013:0258] driver Antti Palosaari
                   ` (3 preceding siblings ...)
  2013-11-06 17:57 ` [PATCH 4/8] Montage M88TS2022 silicon tuner driver Antti Palosaari
@ 2013-11-06 17:57 ` Antti Palosaari
  2013-11-06 17:57 ` [PATCH 6/8] m88ds3103: add parent for I2C adapter Antti Palosaari
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 12+ messages in thread
From: Antti Palosaari @ 2013-11-06 17:57 UTC (permalink / raw)
  To: linux-media; +Cc: Antti Palosaari

Device has following chips: Empia EM28178, Montage M88DS3103,
Montage M88TS2022, Allegro A8293.
---
 drivers/media/usb/em28xx/Kconfig        |  2 ++
 drivers/media/usb/em28xx/em28xx-cards.c | 35 +++++++++++++++++++++++
 drivers/media/usb/em28xx/em28xx-dvb.c   | 49 +++++++++++++++++++++++++++++++++
 drivers/media/usb/em28xx/em28xx.h       |  1 +
 4 files changed, 87 insertions(+)

diff --git a/drivers/media/usb/em28xx/Kconfig b/drivers/media/usb/em28xx/Kconfig
index ca5ee6a..d6ba514 100644
--- a/drivers/media/usb/em28xx/Kconfig
+++ b/drivers/media/usb/em28xx/Kconfig
@@ -49,6 +49,8 @@ config VIDEO_EM28XX_DVB
 	select DVB_MB86A20S if MEDIA_SUBDRV_AUTOSELECT
 	select MEDIA_TUNER_QT1010 if MEDIA_SUBDRV_AUTOSELECT
 	select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_M88DS3103 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_M88TS2022 if MEDIA_SUBDRV_AUTOSELECT
 	---help---
 	  This adds support for DVB cards based on the
 	  Empiatech em28xx chips.
diff --git a/drivers/media/usb/em28xx/em28xx-cards.c b/drivers/media/usb/em28xx/em28xx-cards.c
index 62332a6..4db5eab 100644
--- a/drivers/media/usb/em28xx/em28xx-cards.c
+++ b/drivers/media/usb/em28xx/em28xx-cards.c
@@ -356,6 +356,28 @@ static struct em28xx_reg_seq c3tech_digital_duo_digital[] = {
 	{	-1,			-1,	-1,	-1},
 };
 
+/*
+ * 2013:0258 PCTV DVB-S2 Stick (461e)
+ * GPIO 0 = POWER_ON
+ * GPIO 1 = BOOST
+ * GPIO 2 = VUV_LNB (red LED)
+ * GPIO 3 = #EXT_12V
+ * GPIO 4 = INT_DEM
+ * GPIO 5 = INT_LNB
+ * GPIO 6 = #RESET_DEM
+ * GPIO 7 = P07_LED (green LED)
+ */
+static struct em28xx_reg_seq pctv_461e[] = {
+	{EM2874_R80_GPIO_P0_CTRL,      0x7f, 0xff,    0},
+	{0x0d,                 0xff, 0xff,    0},
+	{EM2874_R80_GPIO_P0_CTRL,      0x3f, 0xff,  100}, /* reset demod */
+	{EM2874_R80_GPIO_P0_CTRL,      0x7f, 0xff,  200}, /* reset demod */
+	{0x0d,                 0x42, 0xff,    0},
+	{EM2874_R80_GPIO_P0_CTRL,      0xeb, 0xff,    0},
+	{EM2874_R5F_TS_ENABLE, 0x84, 0x84,    0}, /* parallel? | null discard */
+	{                  -1,   -1,   -1,   -1},
+};
+
 #if 0
 static struct em28xx_reg_seq hauppauge_930c_gpio[] = {
 	{EM2874_R80_GPIO_P0_CTRL,	0x6f,	0xff,	10},
@@ -2043,6 +2065,17 @@ struct em28xx_board em28xx_boards[] = {
 		.tuner_gpio	= default_tuner_gpio,
 		.def_i2c_bus	= 1,
 	},
+	/* 2013:0258 PCTV DVB-S2 Stick (461e)
+	 * Empia EM28178, Montage M88DS3103, Montage M88TS2022, Allegro A8293 */
+	[EM28178_BOARD_PCTV_461E] = {
+		.def_i2c_bus   = 1,
+		.i2c_speed     = EM28XX_I2C_CLK_WAIT_ENABLE | EM28XX_I2C_FREQ_400_KHZ,
+		.name          = "PCTV DVB-S2 Stick (461e)",
+		.tuner_type    = TUNER_ABSENT,
+		.tuner_gpio    = pctv_461e,
+		.has_dvb       = 1,
+		.ir_codes      = RC_MAP_PINNACLE_PCTV_HD,
+	},
 };
 const unsigned int em28xx_bcount = ARRAY_SIZE(em28xx_boards);
 
@@ -2208,6 +2241,8 @@ struct usb_device_id em28xx_id_table[] = {
 			.driver_info = EM2884_BOARD_PCTV_520E },
 	{ USB_DEVICE(0x1b80, 0xe1cc),
 			.driver_info = EM2874_BOARD_DELOCK_61959 },
+	{ USB_DEVICE(0x2013, 0x0258),
+			.driver_info = EM28178_BOARD_PCTV_461E },
 	{ },
 };
 MODULE_DEVICE_TABLE(usb, em28xx_id_table);
diff --git a/drivers/media/usb/em28xx/em28xx-dvb.c b/drivers/media/usb/em28xx/em28xx-dvb.c
index 344042b..929fbdb 100644
--- a/drivers/media/usb/em28xx/em28xx-dvb.c
+++ b/drivers/media/usb/em28xx/em28xx-dvb.c
@@ -51,6 +51,8 @@
 #include "a8293.h"
 #include "qt1010.h"
 #include "mb86a20s.h"
+#include "m88ds3103.h"
+#include "m88ts2022.h"
 
 MODULE_DESCRIPTION("driver for em28xx based DVB cards");
 MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@infradead.org>");
@@ -808,6 +810,19 @@ static struct tda18271_config c3tech_duo_tda18271_config = {
 	.small_i2c = TDA18271_03_BYTE_CHUNK_INIT,
 };
 
+static const struct m88ds3103_config pctv_461e_m88ds3103_config = {
+	.i2c_addr = 0x68,
+	.clock = 27000000,
+	.i2c_wr_max = 33,
+	.clock_out = 0,
+	.ts_mode = M88DS3103_TS_PARALLEL_16,
+	.agc = 0x99,
+};
+
+static const struct m88ts2022_config em28xx_m88ts2022_config = {
+	.i2c_addr = 0x60,
+	.clock = 27000000,
+};
 
 /* ------------------------------------------------------------------ */
 
@@ -1330,6 +1345,40 @@ static int em28xx_dvb_init(struct em28xx *dev)
 			goto out_free;
 		}
 		break;
+	case EM28178_BOARD_PCTV_461E:
+		{
+			/* demod I2C adapter */
+			struct i2c_adapter *i2c_adapter;
+
+			/* attach demod */
+			dvb->fe[0] = dvb_attach(m88ds3103_attach,
+					&pctv_461e_m88ds3103_config,
+					&dev->i2c_adap[dev->def_i2c_bus],
+					&i2c_adapter);
+			if (dvb->fe[0] == NULL) {
+				result = -ENODEV;
+				goto out_free;
+			}
+
+			/* attach tuner */
+			if (!dvb_attach(m88ts2022_attach, dvb->fe[0],
+					i2c_adapter,
+					&em28xx_m88ts2022_config)) {
+				dvb_frontend_detach(dvb->fe[0]);
+				result = -ENODEV;
+				goto out_free;
+			}
+
+			/* attach SEC */
+			if (!dvb_attach(a8293_attach, dvb->fe[0],
+					&dev->i2c_adap[dev->def_i2c_bus],
+					&em28xx_a8293_config)) {
+				dvb_frontend_detach(dvb->fe[0]);
+				result = -ENODEV;
+				goto out_free;
+			}
+		}
+		break;
 	default:
 		em28xx_errdev("/2: The frontend of your DVB/ATSC card"
 				" isn't supported yet\n");
diff --git a/drivers/media/usb/em28xx/em28xx.h b/drivers/media/usb/em28xx/em28xx.h
index f8726ad..ea058be 100644
--- a/drivers/media/usb/em28xx/em28xx.h
+++ b/drivers/media/usb/em28xx/em28xx.h
@@ -132,6 +132,7 @@
 #define EM2884_BOARD_C3TECH_DIGITAL_DUO		  88
 #define EM2874_BOARD_DELOCK_61959		  89
 #define EM2874_BOARD_KWORLD_UB435Q_V2		  90
+#define EM28178_BOARD_PCTV_461E                   91
 
 /* Limits minimum and default number of buffers */
 #define EM28XX_MIN_BUF 4
-- 
1.8.4.2


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

* [PATCH 6/8] m88ds3103: add parent for I2C adapter
  2013-11-06 17:57 [PATCH 0/8] PCTV DVB-S2 Stick (461e) [2013:0258] driver Antti Palosaari
                   ` (4 preceding siblings ...)
  2013-11-06 17:57 ` [PATCH 5/8] em28xx: add support for PCTV DVB-S2 Stick (461e) [2013:0258] Antti Palosaari
@ 2013-11-06 17:57 ` Antti Palosaari
  2013-11-06 17:57 ` [PATCH 7/8] MAINTAINERS: add M88DS3103 Antti Palosaari
  2013-11-06 17:57 ` [PATCH 8/8] MAINTAINERS: add M88TS2022 Antti Palosaari
  7 siblings, 0 replies; 12+ messages in thread
From: Antti Palosaari @ 2013-11-06 17:57 UTC (permalink / raw)
  To: linux-media; +Cc: Antti Palosaari

Every I2C adapter should have a parent.

Signed-off-by: Antti Palosaari <crope@iki.fi>
---
 drivers/media/dvb-frontends/m88ds3103.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/media/dvb-frontends/m88ds3103.c b/drivers/media/dvb-frontends/m88ds3103.c
index 91b3729..fe4a67e 100644
--- a/drivers/media/dvb-frontends/m88ds3103.c
+++ b/drivers/media/dvb-frontends/m88ds3103.c
@@ -1227,6 +1227,7 @@ struct dvb_frontend *m88ds3103_attach(const struct m88ds3103_config *cfg,
 			sizeof(priv->i2c_adapter.name));
 	priv->i2c_adapter.algo = &m88ds3103_tuner_i2c_algo;
 	priv->i2c_adapter.algo_data = NULL;
+	priv->i2c_adapter.dev.parent = &i2c->dev;
 	i2c_set_adapdata(&priv->i2c_adapter, priv);
 	ret = i2c_add_adapter(&priv->i2c_adapter);
 	if (ret) {
-- 
1.8.4.2


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

* [PATCH 7/8] MAINTAINERS: add M88DS3103
  2013-11-06 17:57 [PATCH 0/8] PCTV DVB-S2 Stick (461e) [2013:0258] driver Antti Palosaari
                   ` (5 preceding siblings ...)
  2013-11-06 17:57 ` [PATCH 6/8] m88ds3103: add parent for I2C adapter Antti Palosaari
@ 2013-11-06 17:57 ` Antti Palosaari
  2013-11-06 17:57 ` [PATCH 8/8] MAINTAINERS: add M88TS2022 Antti Palosaari
  7 siblings, 0 replies; 12+ messages in thread
From: Antti Palosaari @ 2013-11-06 17:57 UTC (permalink / raw)
  To: linux-media; +Cc: Antti Palosaari

It is Montage M88DS3103 DVB-S/S2 demodulator driver.

Signed-off-by: Antti Palosaari <crope@iki.fi>
---
 MAINTAINERS | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index e61c2e8..85cfc6f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5244,6 +5244,16 @@ W:	http://www.tazenda.demon.co.uk/phil/linux-hp
 S:	Maintained
 F:	arch/m68k/hp300/
 
+M88DS3103 MEDIA DRIVER
+M:	Antti Palosaari <crope@iki.fi>
+L:	linux-media@vger.kernel.org
+W:	http://linuxtv.org/
+W:	http://palosaari.fi/linux/
+Q:	http://patchwork.linuxtv.org/project/linux-media/list/
+T:	git git://linuxtv.org/anttip/media_tree.git
+S:	Maintained
+F:	drivers/media/dvb-frontends/m88ds3103*
+
 M88RS2000 MEDIA DRIVER
 M:	Malcolm Priestley <tvboxspy@gmail.com>
 L:	linux-media@vger.kernel.org
-- 
1.8.4.2


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

* [PATCH 8/8] MAINTAINERS: add M88TS2022
  2013-11-06 17:57 [PATCH 0/8] PCTV DVB-S2 Stick (461e) [2013:0258] driver Antti Palosaari
                   ` (6 preceding siblings ...)
  2013-11-06 17:57 ` [PATCH 7/8] MAINTAINERS: add M88DS3103 Antti Palosaari
@ 2013-11-06 17:57 ` Antti Palosaari
  7 siblings, 0 replies; 12+ messages in thread
From: Antti Palosaari @ 2013-11-06 17:57 UTC (permalink / raw)
  To: linux-media; +Cc: Antti Palosaari

It is Montage M88TS2022 DVB-S/S2 silicon tuner driver.

Signed-off-by: Antti Palosaari <crope@iki.fi>
---
 MAINTAINERS | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 85cfc6f..314b485 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5262,6 +5262,16 @@ Q:	http://patchwork.linuxtv.org/project/linux-media/list/
 S:	Maintained
 F:	drivers/media/dvb-frontends/m88rs2000*
 
+M88TS2022 MEDIA DRIVER
+M:	Antti Palosaari <crope@iki.fi>
+L:	linux-media@vger.kernel.org
+W:	http://linuxtv.org/
+W:	http://palosaari.fi/linux/
+Q:	http://patchwork.linuxtv.org/project/linux-media/list/
+T:	git git://linuxtv.org/anttip/media_tree.git
+S:	Maintained
+F:	drivers/media/tuners/m88ts2022*
+
 MA901 MASTERKIT USB FM RADIO DRIVER
 M:      Alexey Klimov <klimov.linux@gmail.com>
 L:      linux-media@vger.kernel.org
-- 
1.8.4.2


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

* Re: [PATCH 3/8] Montage M88DS3103 DVB-S/S2 demodulator driver
  2013-11-06 17:57 ` [PATCH 3/8] Montage M88DS3103 DVB-S/S2 demodulator driver Antti Palosaari
@ 2013-11-09  2:35   ` Manu Abraham
  2013-11-09  2:48     ` Antti Palosaari
  0 siblings, 1 reply; 12+ messages in thread
From: Manu Abraham @ 2013-11-09  2:35 UTC (permalink / raw)
  To: Antti Palosaari; +Cc: Linux Media Mailing List

On Wed, Nov 6, 2013 at 11:27 PM, Antti Palosaari <crope@iki.fi> wrote:
> ---
>  drivers/media/dvb-frontends/Kconfig          |    7 +
>  drivers/media/dvb-frontends/Makefile         |    1 +
>  drivers/media/dvb-frontends/m88ds3103.c      | 1293 ++++++++++++++++++++++++++
>  drivers/media/dvb-frontends/m88ds3103.h      |  108 +++
>  drivers/media/dvb-frontends/m88ds3103_priv.h |  218 +++++
>  5 files changed, 1627 insertions(+)
>  create mode 100644 drivers/media/dvb-frontends/m88ds3103.c
>  create mode 100644 drivers/media/dvb-frontends/m88ds3103.h
>  create mode 100644 drivers/media/dvb-frontends/m88ds3103_priv.h
>
> diff --git a/drivers/media/dvb-frontends/Kconfig b/drivers/media/dvb-frontends/Kconfig
> index bddbab4..6c46caf 100644
> --- a/drivers/media/dvb-frontends/Kconfig
> +++ b/drivers/media/dvb-frontends/Kconfig
> @@ -35,6 +35,13 @@ config DVB_STV6110x
>         help
>           A Silicon tuner that supports DVB-S and DVB-S2 modes
>
> +config DVB_M88DS3103
> +       tristate "Montage M88DS3103"
> +       depends on DVB_CORE && I2C
> +       default m if !MEDIA_SUBDRV_AUTOSELECT
> +       help
> +         Say Y when you want to support this frontend.
> +
>  comment "Multistandard (cable + terrestrial) frontends"
>         depends on DVB_CORE
>
> diff --git a/drivers/media/dvb-frontends/Makefile b/drivers/media/dvb-frontends/Makefile
> index f9cb43d..0c75a6a 100644
> --- a/drivers/media/dvb-frontends/Makefile
> +++ b/drivers/media/dvb-frontends/Makefile
> @@ -85,6 +85,7 @@ obj-$(CONFIG_DVB_STV6110) += stv6110.o
>  obj-$(CONFIG_DVB_STV0900) += stv0900.o
>  obj-$(CONFIG_DVB_STV090x) += stv090x.o
>  obj-$(CONFIG_DVB_STV6110x) += stv6110x.o
> +obj-$(CONFIG_DVB_M88DS3103) += m88ds3103.o
>  obj-$(CONFIG_DVB_ISL6423) += isl6423.o
>  obj-$(CONFIG_DVB_EC100) += ec100.o
>  obj-$(CONFIG_DVB_HD29L2) += hd29l2.o
> diff --git a/drivers/media/dvb-frontends/m88ds3103.c b/drivers/media/dvb-frontends/m88ds3103.c
> new file mode 100644
> index 0000000..91b3729
> --- /dev/null
> +++ b/drivers/media/dvb-frontends/m88ds3103.c
> @@ -0,0 +1,1293 @@
> +/*
> + * Montage M88DS3103 demodulator driver
> + *
> + * Copyright (C) 2013 Antti Palosaari <crope@iki.fi>
> + *
> + *    This program is free software; you can redistribute it and/or modify
> + *    it under the terms of the GNU General Public License as published by
> + *    the Free Software Foundation; either version 2 of the License, or
> + *    (at your option) any later version.
> + *
> + *    This program is distributed in the hope that it will be useful,
> + *    but WITHOUT ANY WARRANTY; without even the implied warranty of
> + *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *    GNU General Public License for more details.
> + *
> + *    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.,
> + *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
> + */
> +
> +#include "m88ds3103_priv.h"
> +
> +static struct dvb_frontend_ops m88ds3103_ops;
> +
> +/* write multiple registers */
> +static int m88ds3103_wr_regs(struct m88ds3103_priv *priv,
> +               u8 reg, const u8 *val, int len)
> +{
> +       int ret;
> +       u8 buf[1 + len];
> +       struct i2c_msg msg[1] = {
> +               {
> +                       .addr = priv->cfg->i2c_addr,
> +                       .flags = 0,
> +                       .len = sizeof(buf),
> +                       .buf = buf,
> +               }
> +       };
> +
> +       buf[0] = reg;
> +       memcpy(&buf[1], val, len);
> +
> +       mutex_lock(&priv->i2c_mutex);
> +       ret = i2c_transfer(priv->i2c, msg, 1);
> +       mutex_unlock(&priv->i2c_mutex);
> +       if (ret == 1) {
> +               ret = 0;
> +       } else {
> +               dev_warn(&priv->i2c->dev,
> +                               "%s: i2c wr failed=%d reg=%02x len=%d\n",
> +                               KBUILD_MODNAME, ret, reg, len);
> +               ret = -EREMOTEIO;
> +       }
> +
> +       return ret;
> +}
> +
> +/* read multiple registers */
> +static int m88ds3103_rd_regs(struct m88ds3103_priv *priv,
> +               u8 reg, u8 *val, int len)
> +{
> +       int ret;
> +       u8 buf[len];
> +       struct i2c_msg msg[2] = {
> +               {
> +                       .addr = priv->cfg->i2c_addr,
> +                       .flags = 0,
> +                       .len = 1,
> +                       .buf = &reg,
> +               }, {
> +                       .addr = priv->cfg->i2c_addr,
> +                       .flags = I2C_M_RD,
> +                       .len = sizeof(buf),
> +                       .buf = buf,
> +               }
> +       };
> +
> +       mutex_lock(&priv->i2c_mutex);
> +       ret = i2c_transfer(priv->i2c, msg, 2);
> +       mutex_unlock(&priv->i2c_mutex);
> +       if (ret == 2) {
> +               memcpy(val, buf, len);
> +               ret = 0;
> +       } else {
> +               dev_warn(&priv->i2c->dev,
> +                               "%s: i2c rd failed=%d reg=%02x len=%d\n",
> +                               KBUILD_MODNAME, ret, reg, len);
> +               ret = -EREMOTEIO;
> +       }
> +
> +       return ret;
> +}
> +
> +/* write single register */
> +static int m88ds3103_wr_reg(struct m88ds3103_priv *priv, u8 reg, u8 val)
> +{
> +       return m88ds3103_wr_regs(priv, reg, &val, 1);
> +}
> +
> +/* read single register */
> +static int m88ds3103_rd_reg(struct m88ds3103_priv *priv, u8 reg, u8 *val)
> +{
> +       return m88ds3103_rd_regs(priv, reg, val, 1);
> +}
> +
> +/* write single register with mask */
> +static int m88ds3103_wr_reg_mask(struct m88ds3103_priv *priv,
> +               u8 reg, u8 val, u8 mask)
> +{
> +       int ret;
> +       u8 u8tmp;
> +
> +       /* no need for read if whole reg is written */
> +       if (mask != 0xff) {
> +               ret = m88ds3103_rd_regs(priv, reg, &u8tmp, 1);
> +               if (ret)
> +                       return ret;
> +
> +               val &= mask;
> +               u8tmp &= ~mask;
> +               val |= u8tmp;
> +       }
> +
> +       return m88ds3103_wr_regs(priv, reg, &val, 1);
> +}
> +
> +/* read single register with mask */
> +static int m88ds3103_rd_reg_mask(struct m88ds3103_priv *priv,
> +               u8 reg, u8 *val, u8 mask)
> +{
> +       int ret, i;
> +       u8 u8tmp;
> +
> +       ret = m88ds3103_rd_regs(priv, reg, &u8tmp, 1);
> +       if (ret)
> +               return ret;
> +
> +       u8tmp &= mask;
> +
> +       /* find position of the first bit */
> +       for (i = 0; i < 8; i++) {
> +               if ((mask >> i) & 0x01)
> +                       break;
> +       }
> +       *val = u8tmp >> i;
> +
> +       return 0;
> +}
> +
> +static int m88ds3103_read_status(struct dvb_frontend *fe, fe_status_t *status)
> +{
> +       struct m88ds3103_priv *priv = fe->demodulator_priv;
> +       struct dtv_frontend_properties *c = &fe->dtv_property_cache;
> +       int ret;
> +       u8 u8tmp;
> +
> +       *status = 0;
> +
> +       if (!priv->warm) {
> +               ret = -EAGAIN;
> +               goto err;
> +       }
> +
> +       switch (c->delivery_system) {
> +       case SYS_DVBS:
> +               ret = m88ds3103_rd_reg_mask(priv, 0xd1, &u8tmp, 0x07);
> +               if (ret)
> +                       goto err;
> +
> +               if (u8tmp == 0x07)
> +                       *status = FE_HAS_SIGNAL | FE_HAS_CARRIER |
> +                                       FE_HAS_VITERBI | FE_HAS_SYNC |
> +                                       FE_HAS_LOCK;
> +               break;
> +       case SYS_DVBS2:
> +               ret = m88ds3103_rd_reg_mask(priv, 0x0d, &u8tmp, 0x8f);
> +               if (ret)
> +                       goto err;
> +
> +               if (u8tmp == 0x8f)
> +                       *status = FE_HAS_SIGNAL | FE_HAS_CARRIER |
> +                                       FE_HAS_VITERBI | FE_HAS_SYNC |
> +                                       FE_HAS_LOCK;
> +               break;
> +       default:
> +               dev_dbg(&priv->i2c->dev, "%s: invalid delivery_system\n",
> +                               __func__);
> +               ret = -EINVAL;
> +               goto err;
> +       }
> +
> +       priv->fe_status = *status;
> +
> +       dev_dbg(&priv->i2c->dev, "%s: lock=%02x status=%02x\n",
> +                       __func__, u8tmp, *status);
> +
> +       return 0;
> +err:
> +       dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
> +       return ret;
> +}
> +
> +static int m88ds3103_set_frontend(struct dvb_frontend *fe)
> +{
> +       struct m88ds3103_priv *priv = fe->demodulator_priv;
> +       struct dtv_frontend_properties *c = &fe->dtv_property_cache;
> +       int ret, i, len;
> +       const struct m88ds3103_reg_val *init;
> +       u8 u8tmp, u8tmp1, u8tmp2;
> +       u8 buf[2];
> +       u16 u16tmp, divide_ratio;
> +       u32 tuner_frequency, target_mclk, ts_clk;
> +       s32 s32tmp;
> +       dev_dbg(&priv->i2c->dev,
> +                       "%s: delivery_system=%d modulation=%d frequency=%d symbol_rate=%d inversion=%d pilot=%d rolloff=%d\n",
> +                       __func__, c->delivery_system,
> +                       c->modulation, c->frequency, c->symbol_rate,
> +                       c->inversion, c->pilot, c->rolloff);
> +
> +       if (!priv->warm) {
> +               ret = -EAGAIN;
> +               goto err;
> +       }
> +
> +       /* program tuner */
> +       if (fe->ops.tuner_ops.set_params) {
> +               ret = fe->ops.tuner_ops.set_params(fe);
> +               if (ret)
> +                       goto err;
> +       }
> +
> +       if (fe->ops.tuner_ops.get_frequency) {
> +               ret = fe->ops.tuner_ops.get_frequency(fe, &tuner_frequency);
> +               if (ret)
> +                       goto err;
> +       }
> +
> +       /* reset */
> +       ret = m88ds3103_wr_reg(priv, 0x07, 0x80);
> +       if (ret)
> +               goto err;
> +
> +       ret = m88ds3103_wr_reg(priv, 0x07, 0x00);
> +       if (ret)
> +               goto err;
> +
> +       ret = m88ds3103_wr_reg(priv, 0xb2, 0x01);
> +       if (ret)
> +               goto err;
> +
> +       ret = m88ds3103_wr_reg(priv, 0x00, 0x01);
> +       if (ret)
> +               goto err;
> +
> +       switch (c->delivery_system) {
> +       case SYS_DVBS:
> +               len = ARRAY_SIZE(m88ds3103_dvbs_init_reg_vals);
> +               init = m88ds3103_dvbs_init_reg_vals;
> +               target_mclk = 96000;
> +               break;
> +       case SYS_DVBS2:
> +               len = ARRAY_SIZE(m88ds3103_dvbs2_init_reg_vals);
> +               init = m88ds3103_dvbs2_init_reg_vals;
> +
> +               switch (priv->cfg->ts_mode) {
> +               case M88DS3103_TS_SERIAL:
> +               case M88DS3103_TS_SERIAL_D7:
> +                       if (c->symbol_rate < 18000000)
> +                               target_mclk = 96000;
> +                       else
> +                               target_mclk = 144000;
> +                       break;
> +               case M88DS3103_TS_PARALLEL:
> +               case M88DS3103_TS_PARALLEL_12:
> +               case M88DS3103_TS_PARALLEL_16:
> +               case M88DS3103_TS_PARALLEL_19_2:
> +               case M88DS3103_TS_CI:
> +                       if (c->symbol_rate < 18000000)
> +                               target_mclk = 96000;
> +                       else if (c->symbol_rate < 28000000)
> +                               target_mclk = 144000;
> +                       else
> +                               target_mclk = 192000;
> +                       break;
> +               default:
> +                       dev_dbg(&priv->i2c->dev, "%s: invalid ts_mode\n",
> +                                       __func__);
> +                       ret = -EINVAL;
> +                       goto err;
> +               }
> +               break;
> +       default:
> +               dev_dbg(&priv->i2c->dev, "%s: invalid delivery_system\n",
> +                               __func__);
> +               ret = -EINVAL;
> +               goto err;
> +       }
> +
> +       /* program init table */
> +       if (c->delivery_system != priv->delivery_system) {
> +               dev_dbg(&priv->i2c->dev, "%s: program init\n", __func__);
> +               for (i = 0; i < len; i++) {
> +                       ret = m88ds3103_wr_reg(priv, init[i].reg, init[i].val);
> +                       if (ret)
> +                               goto err;
> +               }
> +       }
> +
> +       u8tmp1 = 0; /* silence compiler warning */
> +       switch (priv->cfg->ts_mode) {
> +       case M88DS3103_TS_SERIAL:
> +               u8tmp1 = 0x00;
> +               ts_clk = 0;
> +               u8tmp = 0x04;
> +               break;
> +       case M88DS3103_TS_SERIAL_D7:
> +               u8tmp1 = 0x20;
> +               ts_clk = 0;
> +               u8tmp = 0x04;
> +               break;
> +       case M88DS3103_TS_PARALLEL:
> +               ts_clk = 24000;
> +               u8tmp = 0x00;
> +               break;
> +       case M88DS3103_TS_PARALLEL_12:
> +               ts_clk = 12000;
> +               u8tmp = 0x00;
> +               break;
> +       case M88DS3103_TS_PARALLEL_16:
> +               ts_clk = 16000;
> +               u8tmp = 0x00;
> +               break;
> +       case M88DS3103_TS_PARALLEL_19_2:
> +               ts_clk = 19200;
> +               u8tmp = 0x00;
> +               break;
> +       case M88DS3103_TS_CI:
> +               ts_clk = 6000;
> +               u8tmp = 0x01;
> +               break;
> +       default:
> +               dev_dbg(&priv->i2c->dev, "%s: invalid ts_mode\n", __func__);
> +               ret = -EINVAL;
> +               goto err;
> +       }
> +
> +       /* TS mode */
> +       ret = m88ds3103_wr_reg_mask(priv, 0xfd, u8tmp, 0x05);
> +       if (ret)
> +               goto err;
> +
> +       switch (priv->cfg->ts_mode) {
> +       case M88DS3103_TS_SERIAL:
> +       case M88DS3103_TS_SERIAL_D7:
> +               ret = m88ds3103_wr_reg_mask(priv, 0x29, u8tmp1, 0x20);
> +               if (ret)
> +                       goto err;
> +       }
> +
> +       if (ts_clk) {
> +               divide_ratio = DIV_ROUND_UP(target_mclk, ts_clk);
> +               u8tmp1 = divide_ratio / 2;
> +               u8tmp2 = DIV_ROUND_UP(divide_ratio, 2);
> +       } else {
> +               divide_ratio = 0;
> +               u8tmp1 = 0;
> +               u8tmp2 = 0;
> +       }
> +
> +       dev_dbg(&priv->i2c->dev,
> +                       "%s: target_mclk=%d ts_clk=%d divide_ratio=%d\n",
> +                       __func__, target_mclk, ts_clk, divide_ratio);
> +
> +       u8tmp1--;
> +       u8tmp2--;
> +       /* u8tmp1[5:2] => fe[3:0], u8tmp1[1:0] => ea[7:6] */
> +       u8tmp1 &= 0x3f;
> +       /* u8tmp2[5:0] => ea[5:0] */
> +       u8tmp2 &= 0x3f;
> +
> +       ret = m88ds3103_rd_reg(priv, 0xfe, &u8tmp);
> +       if (ret)
> +               goto err;
> +
> +       u8tmp = ((u8tmp  & 0xf0) << 0) | u8tmp1 >> 2;
> +       ret = m88ds3103_wr_reg(priv, 0xfe, u8tmp);
> +       if (ret)
> +               goto err;
> +
> +       u8tmp = ((u8tmp1 & 0x03) << 6) | u8tmp2 >> 0;
> +       ret = m88ds3103_wr_reg(priv, 0xea, u8tmp);
> +       if (ret)
> +               goto err;
> +
> +       switch (target_mclk) {
> +       case 72000:
> +               u8tmp1 = 0x00; /* 0b00 */
> +               u8tmp2 = 0x03; /* 0b11 */
> +               break;
> +       case 96000:
> +               u8tmp1 = 0x02; /* 0b10 */
> +               u8tmp2 = 0x01; /* 0b01 */
> +               break;
> +       case 115200:
> +               u8tmp1 = 0x01; /* 0b01 */
> +               u8tmp2 = 0x01; /* 0b01 */
> +               break;
> +       case 144000:
> +               u8tmp1 = 0x00; /* 0b00 */
> +               u8tmp2 = 0x01; /* 0b01 */
> +               break;
> +       case 192000:
> +               u8tmp1 = 0x03; /* 0b11 */
> +               u8tmp2 = 0x00; /* 0b00 */
> +               break;
> +       default:
> +               dev_dbg(&priv->i2c->dev, "%s: invalid target_mclk\n", __func__);
> +               ret = -EINVAL;
> +               goto err;
> +       }
> +
> +       ret = m88ds3103_wr_reg_mask(priv, 0x22, u8tmp1 << 6, 0xc0);
> +       if (ret)
> +               goto err;
> +
> +       ret = m88ds3103_wr_reg_mask(priv, 0x24, u8tmp2 << 6, 0xc0);
> +       if (ret)
> +               goto err;
> +
> +       if (c->symbol_rate <= 3000000)
> +               u8tmp = 0x20;
> +       else if (c->symbol_rate <= 10000000)
> +               u8tmp = 0x10;
> +       else
> +               u8tmp = 0x06;
> +
> +       ret = m88ds3103_wr_reg(priv, 0xc3, 0x08);
> +       if (ret)
> +               goto err;
> +
> +       ret = m88ds3103_wr_reg(priv, 0xc8, u8tmp);
> +       if (ret)
> +               goto err;
> +
> +       ret = m88ds3103_wr_reg(priv, 0xc4, 0x08);
> +       if (ret)
> +               goto err;
> +
> +       ret = m88ds3103_wr_reg(priv, 0xc7, 0x00);
> +       if (ret)
> +               goto err;
> +
> +       u16tmp = (((c->symbol_rate / 1000) << 15) + (M88DS3103_MCLK_KHZ / 4)) /
> +                       (M88DS3103_MCLK_KHZ / 2);
> +       buf[0] = (u16tmp >> 0) & 0xff;
> +       buf[1] = (u16tmp >> 8) & 0xff;
> +       ret = m88ds3103_wr_regs(priv, 0x61, buf, 2);
> +       if (ret)
> +               goto err;
> +
> +       ret = m88ds3103_wr_reg_mask(priv, 0x4d, priv->cfg->spec_inv << 1, 0x02);
> +       if (ret)
> +               goto err;
> +
> +       ret = m88ds3103_wr_reg_mask(priv, 0x30, priv->cfg->agc_inv << 4, 0x10);
> +       if (ret)
> +               goto err;
> +
> +       ret = m88ds3103_wr_reg(priv, 0x33, priv->cfg->agc);
> +       if (ret)
> +               goto err;
> +
> +       dev_dbg(&priv->i2c->dev, "%s: carrier offset=%d\n", __func__,
> +                       (tuner_frequency - c->frequency));
> +
> +       s32tmp = 0x10000 * (tuner_frequency - c->frequency);
> +       s32tmp = (2 * s32tmp + M88DS3103_MCLK_KHZ) / (2 * M88DS3103_MCLK_KHZ);
> +       if (s32tmp < 0)
> +               s32tmp += 0x10000;
> +
> +       buf[0] = (s32tmp >> 0) & 0xff;
> +       buf[1] = (s32tmp >> 8) & 0xff;
> +       ret = m88ds3103_wr_regs(priv, 0x5e, buf, 2);
> +       if (ret)
> +               goto err;
> +
> +       ret = m88ds3103_wr_reg(priv, 0x00, 0x00);
> +       if (ret)
> +               goto err;
> +
> +       ret = m88ds3103_wr_reg(priv, 0xb2, 0x00);
> +       if (ret)
> +               goto err;
> +
> +       priv->delivery_system = c->delivery_system;
> +
> +       return 0;
> +err:
> +       dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
> +       return ret;
> +}
> +
> +static int m88ds3103_init(struct dvb_frontend *fe)
> +{
> +       struct m88ds3103_priv *priv = fe->demodulator_priv;
> +       int ret, len, remaining;
> +       const struct firmware *fw = NULL;
> +       u8 *fw_file = M88DS3103_FIRMWARE;
> +       u8 u8tmp;
> +       dev_dbg(&priv->i2c->dev, "%s:\n", __func__);
> +
> +       /* set cold state by default */
> +       priv->warm = false;
> +
> +       /* wake up device from sleep */
> +       ret = m88ds3103_wr_reg_mask(priv, 0x08, 0x01, 0x01);
> +       if (ret)
> +               goto err;
> +
> +       ret = m88ds3103_wr_reg_mask(priv, 0x04, 0x00, 0x01);
> +       if (ret)
> +               goto err;
> +
> +       ret = m88ds3103_wr_reg_mask(priv, 0x23, 0x00, 0x10);
> +       if (ret)
> +               goto err;
> +
> +       /* reset */
> +       ret = m88ds3103_wr_reg(priv, 0x07, 0x60);
> +       if (ret)
> +               goto err;
> +
> +       ret = m88ds3103_wr_reg(priv, 0x07, 0x00);
> +       if (ret)
> +               goto err;
> +
> +       /* firmware status */
> +       ret = m88ds3103_rd_reg(priv, 0xb9, &u8tmp);
> +       if (ret)
> +               goto err;
> +
> +       dev_dbg(&priv->i2c->dev, "%s: firmware=%02x\n", __func__, u8tmp);
> +
> +       if (u8tmp)
> +               goto skip_fw_download;
> +
> +       /* cold state - try to download firmware */
> +       dev_info(&priv->i2c->dev, "%s: found a '%s' in cold state\n",
> +                       KBUILD_MODNAME, m88ds3103_ops.info.name);
> +
> +       /* request the firmware, this will block and timeout */
> +       ret = request_firmware(&fw, fw_file, priv->i2c->dev.parent);
> +       if (ret) {
> +               dev_err(&priv->i2c->dev, "%s: firmare file '%s' not found\n",
> +                               KBUILD_MODNAME, fw_file);
> +               goto err;
> +       }
> +
> +       dev_info(&priv->i2c->dev, "%s: downloading firmware from file '%s'\n",
> +                       KBUILD_MODNAME, fw_file);
> +
> +       ret = m88ds3103_wr_reg(priv, 0xb2, 0x01);
> +       if (ret)
> +               goto err;
> +
> +       for (remaining = fw->size; remaining > 0;
> +                       remaining -= (priv->cfg->i2c_wr_max - 1)) {
> +               len = remaining;
> +               if (len > (priv->cfg->i2c_wr_max - 1))
> +                       len = (priv->cfg->i2c_wr_max - 1);
> +
> +               ret = m88ds3103_wr_regs(priv, 0xb0,
> +                               &fw->data[fw->size - remaining], len);
> +               if (ret) {
> +                       dev_err(&priv->i2c->dev,
> +                                       "%s: firmware download failed=%d\n",
> +                                       KBUILD_MODNAME, ret);
> +                       goto err;
> +               }
> +       }
> +
> +       ret = m88ds3103_wr_reg(priv, 0xb2, 0x00);
> +       if (ret)
> +               goto err;
> +
> +       release_firmware(fw);
> +       fw = NULL;
> +
> +       ret = m88ds3103_rd_reg(priv, 0xb9, &u8tmp);
> +       if (ret)
> +               goto err;
> +
> +       if (!u8tmp) {
> +               dev_info(&priv->i2c->dev, "%s: firmware did not run\n",
> +                               KBUILD_MODNAME);
> +               ret = -EFAULT;
> +               goto err;
> +       }
> +
> +       dev_info(&priv->i2c->dev, "%s: found a '%s' in warm state\n",
> +                       KBUILD_MODNAME, m88ds3103_ops.info.name);
> +       dev_info(&priv->i2c->dev, "%s: firmware version %X.%X\n",
> +                       KBUILD_MODNAME, (u8tmp >> 4) & 0xf, (u8tmp >> 0 & 0xf));
> +
> +skip_fw_download:
> +       /* warm state */
> +       priv->warm = true;
> +
> +       return 0;
> +err:
> +       if (fw)
> +               release_firmware(fw);
> +
> +       dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
> +       return ret;
> +}
> +
> +static int m88ds3103_sleep(struct dvb_frontend *fe)
> +{
> +       struct m88ds3103_priv *priv = fe->demodulator_priv;
> +       int ret;
> +       dev_dbg(&priv->i2c->dev, "%s:\n", __func__);
> +
> +       priv->delivery_system = SYS_UNDEFINED;
> +
> +       /* TS Hi-Z */
> +       ret = m88ds3103_wr_reg_mask(priv, 0x27, 0x00, 0x01);
> +       if (ret)
> +               goto err;
> +
> +       /* sleep */
> +       ret = m88ds3103_wr_reg_mask(priv, 0x08, 0x00, 0x01);
> +       if (ret)
> +               goto err;
> +
> +       ret = m88ds3103_wr_reg_mask(priv, 0x04, 0x01, 0x01);
> +       if (ret)
> +               goto err;
> +
> +       ret = m88ds3103_wr_reg_mask(priv, 0x23, 0x10, 0x10);
> +       if (ret)
> +               goto err;
> +
> +       return 0;
> +err:
> +       dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
> +       return ret;
> +}
> +
> +static int m88ds3103_get_frontend(struct dvb_frontend *fe)
> +{
> +       struct m88ds3103_priv *priv = fe->demodulator_priv;
> +       struct dtv_frontend_properties *c = &fe->dtv_property_cache;
> +       int ret;
> +       u8 buf[3];
> +       dev_dbg(&priv->i2c->dev, "%s:\n", __func__);
> +
> +       if (!priv->warm || !(priv->fe_status & FE_HAS_LOCK)) {
> +               ret = -EAGAIN;
> +               goto err;
> +       }
> +
> +       switch (c->delivery_system) {
> +       case SYS_DVBS:
> +               ret = m88ds3103_rd_reg(priv, 0xe0, &buf[0]);
> +               if (ret)
> +                       goto err;
> +
> +               ret = m88ds3103_rd_reg(priv, 0xe6, &buf[1]);
> +               if (ret)
> +                       goto err;
> +
> +               switch ((buf[0] >> 2) & 0x01) {
> +               case 0:
> +                       c->inversion = INVERSION_OFF;
> +                       break;
> +               case 1:
> +                       c->inversion = INVERSION_ON;
> +                       break;
> +               default:
> +                       dev_dbg(&priv->i2c->dev, "%s: invalid inversion\n",
> +                                       __func__);
> +               }
> +
> +               switch ((buf[1] >> 5) & 0x07) {
> +               case 0:
> +                       c->fec_inner = FEC_7_8;
> +                       break;
> +               case 1:
> +                       c->fec_inner = FEC_5_6;
> +                       break;
> +               case 2:
> +                       c->fec_inner = FEC_3_4;
> +                       break;
> +               case 3:
> +                       c->fec_inner = FEC_2_3;
> +                       break;
> +               case 4:
> +                       c->fec_inner = FEC_1_2;
> +                       break;
> +               default:
> +                       dev_dbg(&priv->i2c->dev, "%s: invalid fec_inner\n",
> +                                       __func__);
> +               }
> +
> +               c->modulation = QPSK;
> +
> +               break;
> +       case SYS_DVBS2:
> +               ret = m88ds3103_rd_reg(priv, 0x7e, &buf[0]);
> +               if (ret)
> +                       goto err;
> +
> +               ret = m88ds3103_rd_reg(priv, 0x89, &buf[1]);
> +               if (ret)
> +                       goto err;
> +
> +               ret = m88ds3103_rd_reg(priv, 0xf2, &buf[2]);
> +               if (ret)
> +                       goto err;
> +
> +               switch ((buf[0] >> 0) & 0x0f) {
> +               case 2:
> +                       c->fec_inner = FEC_2_5;
> +                       break;
> +               case 3:
> +                       c->fec_inner = FEC_1_2;
> +                       break;
> +               case 4:
> +                       c->fec_inner = FEC_3_5;
> +                       break;
> +               case 5:
> +                       c->fec_inner = FEC_2_3;
> +                       break;
> +               case 6:
> +                       c->fec_inner = FEC_3_4;
> +                       break;
> +               case 7:
> +                       c->fec_inner = FEC_4_5;
> +                       break;
> +               case 8:
> +                       c->fec_inner = FEC_5_6;
> +                       break;
> +               case 9:
> +                       c->fec_inner = FEC_8_9;
> +                       break;
> +               case 10:
> +                       c->fec_inner = FEC_9_10;
> +                       break;
> +               default:
> +                       dev_dbg(&priv->i2c->dev, "%s: invalid fec_inner\n",
> +                                       __func__);
> +               }
> +
> +               switch ((buf[0] >> 5) & 0x01) {
> +               case 0:
> +                       c->pilot = PILOT_OFF;
> +                       break;
> +               case 1:
> +                       c->pilot = PILOT_ON;
> +                       break;
> +               default:
> +                       dev_dbg(&priv->i2c->dev, "%s: invalid pilot\n",
> +                                       __func__);
> +               }
> +
> +               switch ((buf[0] >> 6) & 0x07) {
> +               case 0:
> +                       c->modulation = QPSK;
> +                       break;
> +               case 1:
> +                       c->modulation = PSK_8;
> +                       break;
> +               case 2:
> +                       c->modulation = APSK_16;
> +                       break;
> +               case 3:
> +                       c->modulation = APSK_32;
> +                       break;
> +               default:
> +                       dev_dbg(&priv->i2c->dev, "%s: invalid modulation\n",
> +                                       __func__);
> +               }
> +
> +               switch ((buf[1] >> 7) & 0x01) {
> +               case 0:
> +                       c->inversion = INVERSION_OFF;
> +                       break;
> +               case 1:
> +                       c->inversion = INVERSION_ON;
> +                       break;
> +               default:
> +                       dev_dbg(&priv->i2c->dev, "%s: invalid inversion\n",
> +                                       __func__);
> +               }
> +
> +               switch ((buf[2] >> 0) & 0x03) {
> +               case 0:
> +                       c->rolloff = ROLLOFF_35;
> +                       break;
> +               case 1:
> +                       c->rolloff = ROLLOFF_25;
> +                       break;
> +               case 2:
> +                       c->rolloff = ROLLOFF_20;
> +                       break;
> +               default:
> +                       dev_dbg(&priv->i2c->dev, "%s: invalid rolloff\n",
> +                                       __func__);
> +               }
> +               break;
> +       default:
> +               dev_dbg(&priv->i2c->dev, "%s: invalid delivery_system\n",
> +                               __func__);
> +               ret = -EINVAL;
> +               goto err;
> +       }
> +
> +       ret = m88ds3103_rd_regs(priv, 0x6d, buf, 2);
> +       if (ret)
> +               goto err;
> +
> +       c->symbol_rate = 1ull * ((buf[1] << 8) | (buf[0] << 0)) *
> +                       M88DS3103_MCLK_KHZ * 1000 / 0x10000;
> +
> +       return 0;
> +err:
> +       dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
> +       return ret;
> +}
> +
> +static int m88ds3103_read_snr(struct dvb_frontend *fe, u16 *snr)
> +{
> +       struct m88ds3103_priv *priv = fe->demodulator_priv;
> +       struct dtv_frontend_properties *c = &fe->dtv_property_cache;
> +       int ret, i, tmp;
> +       u8 buf[3];
> +       u16 noise, signal;
> +       u32 noise_tot, signal_tot;
> +       dev_dbg(&priv->i2c->dev, "%s:\n", __func__);
> +       /* reports SNR in resolution of 0.1 dB */
> +
> +       /* more iterations for more accurate estimation */
> +       #define M88DS3103_SNR_ITERATIONS 3
> +
> +       switch (c->delivery_system) {
> +       case SYS_DVBS:
> +               tmp = 0;
> +
> +               for (i = 0; i < M88DS3103_SNR_ITERATIONS; i++) {
> +                       ret = m88ds3103_rd_reg(priv, 0xff, &buf[0]);
> +                       if (ret)
> +                               goto err;
> +
> +                       tmp += buf[0];
> +               }
> +
> +               /* use of one register limits max value to 15 dB */
> +               /* SNR(X) dB = 10 * ln(X) / ln(10) dB */
> +               tmp = DIV_ROUND_CLOSEST(tmp, 8 * M88DS3103_SNR_ITERATIONS);
> +               if (tmp)
> +                       *snr = 100ul * intlog2(tmp) / intlog2(10);
> +               else
> +                       *snr = 0;
> +               break;
> +       case SYS_DVBS2:
> +               noise_tot = 0;
> +               signal_tot = 0;
> +
> +               for (i = 0; i < M88DS3103_SNR_ITERATIONS; i++) {
> +                       ret = m88ds3103_rd_regs(priv, 0x8c, buf, 3);
> +                       if (ret)
> +                               goto err;
> +
> +                       noise = buf[1] << 6;    /* [13:6] */
> +                       noise |= buf[0] & 0x3f; /*  [5:0] */
> +                       noise >>= 2;
> +                       signal = buf[2] * buf[2];
> +                       signal >>= 1;
> +
> +                       noise_tot += noise;
> +                       signal_tot += signal;
> +               }
> +
> +               noise = noise_tot / M88DS3103_SNR_ITERATIONS;
> +               signal = signal_tot / M88DS3103_SNR_ITERATIONS;
> +
> +               /* SNR(X) dB = 10 * log10(X) dB */
> +               if (signal > noise) {
> +                       tmp = signal / noise;
> +                       *snr = 100ul * intlog10(tmp) / (1 << 24);
> +               } else
> +                       *snr = 0;
> +               break;
> +       default:
> +               dev_dbg(&priv->i2c->dev, "%s: invalid delivery_system\n",
> +                               __func__);
> +               ret = -EINVAL;
> +               goto err;
> +       }
> +
> +       return 0;
> +err:
> +       dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
> +       return ret;
> +}
> +
> +
> +static int m88ds3103_set_tone(struct dvb_frontend *fe,
> +       fe_sec_tone_mode_t fe_sec_tone_mode)
> +{
> +       struct m88ds3103_priv *priv = fe->demodulator_priv;
> +       int ret;
> +       u8 u8tmp, tone, reg_a1_mask;
> +       dev_dbg(&priv->i2c->dev, "%s: fe_sec_tone_mode=%d\n", __func__,
> +                       fe_sec_tone_mode);
> +
> +       if (!priv->warm) {
> +               ret = -EAGAIN;
> +               goto err;
> +       }
> +
> +       switch (fe_sec_tone_mode) {
> +       case SEC_TONE_ON:
> +               tone = 0;
> +               reg_a1_mask = 0x87;
> +               break;
> +       case SEC_TONE_OFF:
> +               tone = 1;
> +               reg_a1_mask = 0x00;
> +               break;
> +       default:
> +               dev_dbg(&priv->i2c->dev, "%s: invalid fe_sec_tone_mode\n",
> +                               __func__);
> +               ret = -EINVAL;
> +               goto err;
> +       }
> +
> +       u8tmp = tone << 7 | priv->cfg->envelope_mode << 5;
> +       ret = m88ds3103_wr_reg_mask(priv, 0xa2, u8tmp, 0xe0);
> +       if (ret)
> +               goto err;
> +
> +       u8tmp = 1 << 2;
> +       ret = m88ds3103_wr_reg_mask(priv, 0xa1, u8tmp, reg_a1_mask);
> +       if (ret)
> +               goto err;
> +
> +       return 0;
> +err:
> +       dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
> +       return ret;
> +}
> +
> +static int m88ds3103_diseqc_send_master_cmd(struct dvb_frontend *fe,
> +               struct dvb_diseqc_master_cmd *diseqc_cmd)
> +{
> +       struct m88ds3103_priv *priv = fe->demodulator_priv;
> +       int ret, i;
> +       u8 u8tmp;
> +       dev_dbg(&priv->i2c->dev, "%s: msg=%*ph\n", __func__,
> +                       diseqc_cmd->msg_len, diseqc_cmd->msg);
> +
> +       if (!priv->warm) {
> +               ret = -EAGAIN;
> +               goto err;
> +       }
> +
> +       if (diseqc_cmd->msg_len < 3 || diseqc_cmd->msg_len > 6) {
> +               ret = -EINVAL;
> +               goto err;
> +       }
> +
> +       u8tmp = priv->cfg->envelope_mode << 5;
> +       ret = m88ds3103_wr_reg_mask(priv, 0xa2, u8tmp, 0xe0);
> +       if (ret)
> +               goto err;
> +
> +       ret = m88ds3103_wr_regs(priv, 0xa3, diseqc_cmd->msg,
> +                       diseqc_cmd->msg_len);
> +       if (ret)
> +               goto err;
> +
> +       ret = m88ds3103_wr_reg(priv, 0xa1,
> +                       (diseqc_cmd->msg_len - 1) << 3 | 0x07);
> +       if (ret)
> +               goto err;
> +
> +       /* DiSEqC message typical period is 54 ms */
> +       usleep_range(40000, 60000);
> +
> +       /* wait DiSEqC TX ready */
> +       for (i = 20, u8tmp = 1; i && u8tmp; i--) {
> +               usleep_range(5000, 10000);
> +
> +               ret = m88ds3103_rd_reg_mask(priv, 0xa1, &u8tmp, 0x40);
> +               if (ret)
> +                       goto err;
> +       }
> +
> +       dev_dbg(&priv->i2c->dev, "%s: loop=%d\n", __func__, i);
> +
> +       if (i == 0) {
> +               dev_dbg(&priv->i2c->dev, "%s: diseqc tx timeout\n", __func__);
> +
> +               ret = m88ds3103_wr_reg_mask(priv, 0xa1, 0x40, 0xc0);
> +               if (ret)
> +                       goto err;
> +       }
> +
> +       ret = m88ds3103_wr_reg_mask(priv, 0xa2, 0x80, 0xc0);
> +       if (ret)
> +               goto err;
> +
> +       if (i == 0) {
> +               ret = -ETIMEDOUT;
> +               goto err;
> +       }
> +
> +       return 0;
> +err:
> +       dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
> +       return ret;
> +}
> +
> +static int m88ds3103_diseqc_send_burst(struct dvb_frontend *fe,
> +       fe_sec_mini_cmd_t fe_sec_mini_cmd)
> +{
> +       struct m88ds3103_priv *priv = fe->demodulator_priv;
> +       int ret, i;
> +       u8 u8tmp, burst;
> +       dev_dbg(&priv->i2c->dev, "%s: fe_sec_mini_cmd=%d\n", __func__,
> +                       fe_sec_mini_cmd);
> +
> +       if (!priv->warm) {
> +               ret = -EAGAIN;
> +               goto err;
> +       }
> +
> +       u8tmp = priv->cfg->envelope_mode << 5;
> +       ret = m88ds3103_wr_reg_mask(priv, 0xa2, u8tmp, 0xe0);
> +       if (ret)
> +               goto err;
> +
> +       switch (fe_sec_mini_cmd) {
> +       case SEC_MINI_A:
> +               burst = 0x02;
> +               break;
> +       case SEC_MINI_B:
> +               burst = 0x01;
> +               break;
> +       default:
> +               dev_dbg(&priv->i2c->dev, "%s: invalid fe_sec_mini_cmd\n",
> +                               __func__);
> +               ret = -EINVAL;
> +               goto err;
> +       }
> +
> +       ret = m88ds3103_wr_reg(priv, 0xa1, burst);
> +       if (ret)
> +               goto err;
> +
> +       /* DiSEqC ToneBurst period is 12.5 ms */
> +       usleep_range(11000, 20000);
> +
> +       /* wait DiSEqC TX ready */
> +       for (i = 5, u8tmp = 1; i && u8tmp; i--) {
> +               usleep_range(800, 2000);
> +
> +               ret = m88ds3103_rd_reg_mask(priv, 0xa1, &u8tmp, 0x40);
> +               if (ret)
> +                       goto err;
> +       }
> +
> +       dev_dbg(&priv->i2c->dev, "%s: loop=%d\n", __func__, i);
> +
> +       ret = m88ds3103_wr_reg_mask(priv, 0xa2, 0x80, 0xc0);
> +       if (ret)
> +               goto err;
> +
> +       if (i == 0) {
> +               dev_dbg(&priv->i2c->dev, "%s: diseqc tx timeout\n", __func__);
> +               ret = -ETIMEDOUT;
> +               goto err;
> +       }
> +
> +       return 0;
> +err:
> +       dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
> +       return ret;
> +}
> +
> +static int m88ds3103_get_tune_settings(struct dvb_frontend *fe,
> +       struct dvb_frontend_tune_settings *s)
> +{
> +       s->min_delay_ms = 3000;
> +
> +       return 0;
> +}
> +
> +static u32 m88ds3103_tuner_i2c_func(struct i2c_adapter *adapter)
> +{
> +       return I2C_FUNC_I2C;
> +}
> +
> +static int m88ds3103_tuner_i2c_xfer(struct i2c_adapter *i2c_adap,
> +               struct i2c_msg msg[], int num)
> +{
> +       struct m88ds3103_priv *priv = i2c_get_adapdata(i2c_adap);
> +       int ret;
> +       struct i2c_msg gate_open_msg[1] = {
> +               {
> +                       .addr = priv->cfg->i2c_addr,
> +                       .flags = 0,
> +                       .len = 2,
> +                       .buf = "\x03\x11",
> +               }
> +       };
> +       dev_dbg(&priv->i2c->dev, "%s: num=%d\n", __func__, num);
> +
> +       mutex_lock(&priv->i2c_mutex);
> +
> +       /* open i2c-gate */
> +       ret = i2c_transfer(priv->i2c, gate_open_msg, 1);
> +       if (ret != 1) {
> +               mutex_unlock(&priv->i2c_mutex);
> +               dev_warn(&priv->i2c->dev,
> +                               "%s: i2c wr failed=%d\n",
> +                               KBUILD_MODNAME, ret);
> +               ret = -EREMOTEIO;
> +               goto err;
> +       }
> +
> +       ret = i2c_transfer(priv->i2c, msg, num);
> +       mutex_unlock(&priv->i2c_mutex);
> +       if (ret < 0)
> +               dev_warn(&priv->i2c->dev, "%s: i2c failed=%d\n",
> +                               KBUILD_MODNAME, ret);
> +
> +       return ret;
> +err:
> +       dev_dbg(&priv->i2c->dev, "%s: failed=%d\n", __func__, ret);
> +       return ret;
> +}
> +
> +static struct i2c_algorithm m88ds3103_tuner_i2c_algo = {
> +       .master_xfer   = m88ds3103_tuner_i2c_xfer,
> +       .functionality = m88ds3103_tuner_i2c_func,
> +};
> +
> +static void m88ds3103_release(struct dvb_frontend *fe)
> +{
> +       struct m88ds3103_priv *priv = fe->demodulator_priv;
> +       i2c_del_adapter(&priv->i2c_adapter);
> +       kfree(priv);
> +}
> +
> +struct dvb_frontend *m88ds3103_attach(const struct m88ds3103_config *cfg,
> +               struct i2c_adapter *i2c, struct i2c_adapter **tuner_i2c_adapter)
> +{
> +       int ret;
> +       struct m88ds3103_priv *priv;
> +       u8 chip_id, u8tmp;
> +
> +       /* allocate memory for the internal priv */
> +       priv = kzalloc(sizeof(struct m88ds3103_priv), GFP_KERNEL);
> +       if (!priv) {
> +               ret = -ENOMEM;
> +               dev_err(&i2c->dev, "%s: kzalloc() failed\n", KBUILD_MODNAME);
> +               goto err;
> +       }
> +
> +       priv->cfg = cfg;
> +       priv->i2c = i2c;
> +       mutex_init(&priv->i2c_mutex);
> +
> +       ret = m88ds3103_rd_reg(priv, 0x01, &chip_id);
> +       if (ret)
> +               goto err;
> +
> +       dev_dbg(&priv->i2c->dev, "%s: chip_id=%02x\n", __func__, chip_id);
> +
> +       switch (chip_id) {
> +       case 0xd0:
> +               break;
> +       default:
> +               goto err;
> +       }
> +
> +       switch (priv->cfg->clock_out) {
> +       case M88DS3103_CLOCK_OUT_DISABLED:
> +               u8tmp = 0x80;
> +               break;
> +       case M88DS3103_CLOCK_OUT_ENABLED:
> +               u8tmp = 0x00;
> +               break;
> +       case M88DS3103_CLOCK_OUT_ENABLED_DIV2:
> +               u8tmp = 0x10;
> +               break;
> +       default:
> +               goto err;
> +       }
> +
> +       ret = m88ds3103_wr_reg(priv, 0x29, u8tmp);
> +       if (ret)
> +               goto err;
> +
> +       /* sleep */
> +       ret = m88ds3103_wr_reg_mask(priv, 0x08, 0x00, 0x01);
> +       if (ret)
> +               goto err;
> +
> +       ret = m88ds3103_wr_reg_mask(priv, 0x04, 0x01, 0x01);
> +       if (ret)
> +               goto err;
> +
> +       ret = m88ds3103_wr_reg_mask(priv, 0x23, 0x10, 0x10);
> +       if (ret)
> +               goto err;
> +
> +       /* create dvb_frontend */
> +       memcpy(&priv->fe.ops, &m88ds3103_ops, sizeof(struct dvb_frontend_ops));
> +       priv->fe.demodulator_priv = priv;
> +
> +       /* create i2c adapter for tuner */
> +       strlcpy(priv->i2c_adapter.name, KBUILD_MODNAME,
> +                       sizeof(priv->i2c_adapter.name));
> +       priv->i2c_adapter.algo = &m88ds3103_tuner_i2c_algo;
> +       priv->i2c_adapter.algo_data = NULL;
> +       i2c_set_adapdata(&priv->i2c_adapter, priv);
> +       ret = i2c_add_adapter(&priv->i2c_adapter);
> +       if (ret) {
> +               dev_err(&i2c->dev, "%s: i2c bus could not be initialized\n",
> +                               KBUILD_MODNAME);
> +               goto err;
> +       }
> +       *tuner_i2c_adapter = &priv->i2c_adapter;
> +
> +       return &priv->fe;
> +err:
> +       dev_dbg(&i2c->dev, "%s: failed=%d\n", __func__, ret);
> +       kfree(priv);
> +       return NULL;
> +}
> +EXPORT_SYMBOL(m88ds3103_attach);
> +
> +static struct dvb_frontend_ops m88ds3103_ops = {
> +       .delsys = { SYS_DVBS, SYS_DVBS2 },
> +       .info = {
> +               .name = "Montage M88DS3103",
> +               .frequency_min =  950000,
> +               .frequency_max = 2150000,
> +               .frequency_tolerance = 5000,
> +               .symbol_rate_min =  1000000,
> +               .symbol_rate_max = 45000000,
> +               .caps = FE_CAN_INVERSION_AUTO |
> +                       FE_CAN_FEC_1_2 |
> +                       FE_CAN_FEC_2_3 |
> +                       FE_CAN_FEC_3_4 |
> +                       FE_CAN_FEC_4_5 |
> +                       FE_CAN_FEC_5_6 |
> +                       FE_CAN_FEC_6_7 |
> +                       FE_CAN_FEC_7_8 |
> +                       FE_CAN_FEC_8_9 |
> +                       FE_CAN_FEC_AUTO |
> +                       FE_CAN_QPSK |
> +                       FE_CAN_RECOVER |
> +                       FE_CAN_2G_MODULATION
> +       },
> +
> +       .release = m88ds3103_release,
> +
> +       .get_tune_settings = m88ds3103_get_tune_settings,
> +
> +       .init = m88ds3103_init,
> +       .sleep = m88ds3103_sleep,
> +
> +       .set_frontend = m88ds3103_set_frontend,
> +       .get_frontend = m88ds3103_get_frontend,
> +
> +       .read_status = m88ds3103_read_status,
> +       .read_snr = m88ds3103_read_snr,
> +
> +       .diseqc_send_master_cmd = m88ds3103_diseqc_send_master_cmd,
> +       .diseqc_send_burst = m88ds3103_diseqc_send_burst,
> +
> +       .set_tone = m88ds3103_set_tone,
> +};
> +
> +MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
> +MODULE_DESCRIPTION("Montage M88DS3103 DVB-S/S2 demodulator driver");
> +MODULE_LICENSE("GPL");
> +MODULE_FIRMWARE(M88DS3103_FIRMWARE);
> diff --git a/drivers/media/dvb-frontends/m88ds3103.h b/drivers/media/dvb-frontends/m88ds3103.h
> new file mode 100644
> index 0000000..287d62a
> --- /dev/null
> +++ b/drivers/media/dvb-frontends/m88ds3103.h
> @@ -0,0 +1,108 @@
> +/*
> + * Montage M88DS3103 demodulator driver
> + *
> + * Copyright (C) 2013 Antti Palosaari <crope@iki.fi>
> + *
> + *    This program is free software; you can redistribute it and/or modify
> + *    it under the terms of the GNU General Public License as published by
> + *    the Free Software Foundation; either version 2 of the License, or
> + *    (at your option) any later version.
> + *
> + *    This program is distributed in the hope that it will be useful,
> + *    but WITHOUT ANY WARRANTY; without even the implied warranty of
> + *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *    GNU General Public License for more details.
> + *
> + *    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.,
> + *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
> + */
> +
> +#ifndef M88DS3103_H
> +#define M88DS3103_H
> +
> +#include <linux/dvb/frontend.h>
> +
> +struct m88ds3103_config {
> +       /*
> +        * I2C address
> +        * 0x68, ...
> +        */
> +       u8 i2c_addr;
> +
> +       /*
> +        * clock
> +        * 27000000
> +        */
> +       u32 clock;
> +
> +       /*
> +        * max bytes I2C provider is asked to write at once
> +        * Note: Buffer is taken from the stack currently!
> +        * Value must be set.
> +        * 33, 65, ...
> +        */
> +       u16 i2c_wr_max;
> +
> +       /*
> +        * TS output mode
> +        */
> +#define M88DS3103_TS_SERIAL             0 /* TS output pin D0, normal */
> +#define M88DS3103_TS_SERIAL_D7          1 /* TS output pin D7 */
> +#define M88DS3103_TS_PARALLEL           2 /* 24 MHz, normal */
> +#define M88DS3103_TS_PARALLEL_12        3 /* 12 MHz */
> +#define M88DS3103_TS_PARALLEL_16        4 /* 16 MHz */
> +#define M88DS3103_TS_PARALLEL_19_2      5 /* 19.2 MHz */
> +#define M88DS3103_TS_CI                 6 /* 6 MHz */
> +       u8 ts_mode;
> +
> +       /*
> +        * spectrum inversion
> +        */
> +       u8 spec_inv:1;
> +
> +       /*
> +        * AGC polarity
> +        */
> +       u8 agc_inv:1;
> +
> +       /*
> +        * clock output
> +        */
> +#define M88DS3103_CLOCK_OUT_DISABLED        0
> +#define M88DS3103_CLOCK_OUT_ENABLED         1
> +#define M88DS3103_CLOCK_OUT_ENABLED_DIV2    2
> +       u8 clock_out;
> +
> +       /*
> +        * DiSEqC envelope mode
> +        */
> +       u8 envelope_mode:1;
> +
> +       u8 agc;
> +};
> +
> +/*
> + * Driver implements own I2C-adapter for tuner I2C access. That's since chip
> + * has I2C-gate control which closes gate automatically after I2C transfer.
> + * Using own I2C adapter we can workaround that.
> + */


Why should the demodulator implement it's own adapter for tuner access ?

DS3103 is identical to DS3002, DS3000 which is similar to all other
dvb demodulators. Comparing datsheets of these demodulators
with others, I can't see any difference in the repeater setup, except
for an additional bit field to control the repeater block itself.

Also, from what I see, the vendor; Montage has a driver, which appears
to be more code complete looking at this url. http://goo.gl/biaPYu

Do you still think the DS3103 is much different in comparison ?

Manu

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

* Re: [PATCH 3/8] Montage M88DS3103 DVB-S/S2 demodulator driver
  2013-11-09  2:35   ` Manu Abraham
@ 2013-11-09  2:48     ` Antti Palosaari
  2013-11-09  3:25       ` Manu Abraham
  0 siblings, 1 reply; 12+ messages in thread
From: Antti Palosaari @ 2013-11-09  2:48 UTC (permalink / raw)
  To: Manu Abraham; +Cc: Linux Media Mailing List

On 09.11.2013 04:35, Manu Abraham wrote:
> On Wed, Nov 6, 2013 at 11:27 PM, Antti Palosaari <crope@iki.fi> wrote:


>> +/*
>> + * Driver implements own I2C-adapter for tuner I2C access. That's since chip
>> + * has I2C-gate control which closes gate automatically after I2C transfer.
>> + * Using own I2C adapter we can workaround that.
>> + */
>
>
> Why should the demodulator implement it's own adapter for tuner access ?

In order to implement it properly.


> DS3103 is identical to DS3002, DS3000 which is similar to all other
> dvb demodulators. Comparing datsheets of these demodulators
> with others, I can't see any difference in the repeater setup, except
> for an additional bit field to control the repeater block itself.
>
> Also, from what I see, the vendor; Montage has a driver, which appears
> to be more code complete looking at this url. http://goo.gl/biaPYu
>
> Do you still think the DS3103 is much different in comparison ?

There was even some patches, maybe 2 years, ago in order to mainline 
that but it never happened.

More complete is here 53 vs. 86 register writes, so yes it is more ~40 
more complete if you like to compare it like that.

regards
Antti

-- 
http://palosaari.fi/

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

* Re: [PATCH 3/8] Montage M88DS3103 DVB-S/S2 demodulator driver
  2013-11-09  2:48     ` Antti Palosaari
@ 2013-11-09  3:25       ` Manu Abraham
  0 siblings, 0 replies; 12+ messages in thread
From: Manu Abraham @ 2013-11-09  3:25 UTC (permalink / raw)
  To: Antti Palosaari; +Cc: Linux Media Mailing List

On Sat, Nov 9, 2013 at 8:18 AM, Antti Palosaari <crope@iki.fi> wrote:
> On 09.11.2013 04:35, Manu Abraham wrote:
>>
>> On Wed, Nov 6, 2013 at 11:27 PM, Antti Palosaari <crope@iki.fi> wrote:
>
>
>
>>> +/*
>>> + * Driver implements own I2C-adapter for tuner I2C access. That's since
>>> chip
>>> + * has I2C-gate control which closes gate automatically after I2C
>>> transfer.
>>> + * Using own I2C adapter we can workaround that.
>>> + */
>>
>>
>>
>> Why should the demodulator implement it's own adapter for tuner access ?
>
>
> In order to implement it properly.
>
>
>
>> DS3103 is identical to DS3002, DS3000 which is similar to all other
>> dvb demodulators. Comparing datsheets of these demodulators
>> with others, I can't see any difference in the repeater setup, except
>> for an additional bit field to control the repeater block itself.
>>
>> Also, from what I see, the vendor; Montage has a driver, which appears
>> to be more code complete looking at this url. http://goo.gl/biaPYu
>>
>> Do you still think the DS3103 is much different in comparison ?
>

DS3000 demodulator datasheet states:

To avoid unwanted noise disturbing the tuner performance, the
M88DS3000 offers a 2-wire bus repeater dedicated for tuner
control. The tuner is connected to the M88DS3000 through the
SCLT and SDAT pins. See Figure 11. Every time the 2-wire bus
master wants to access the tuner registers, it must enable the
repeater first. When the repeater is enabled, the SDAT and SCLT
pins are active. The messages on the SDA and SCL pins is
repeated on the SDAT and SCLT pins. The repeater will be
automatically disabled once the access times to the tuner
reaches the configured value. When disabled, the SCLT and
SDAT pins are completely isolated from the 2-wire bus and
become inactive (HIGH).

DS3002 demodulator datasheet states:

To avoid unwanted noise disturbing the tuner performance, the
M88DS3002B offers a 2-wire bus repeater dedicated for tuner
control. The tuner is connected to the M88DS3002B through
the SCLT and SDAT pins. See Figure 12. Every time the 2-wire
bus master wants to access the tuner registers, it must enable
the repeater first by configuring bit 2_WIRE_REP_EN (03H).
When the repeater is enabled, the SDAT and SCLT pins are
active. The messages on the SDA and SCL pins is repeated
on the SDAT and SCLT pins. The repeater will be automatically
disabled once the access times to the tuner reaches the
configured value set in bits 2_WIRE_REP_TM[2:0] (03H).
When disabled, the SCLT and SDAT pins are completely
isolated from the 2-wire bus and become inactive (HIGH).

DS3013 demodulator datasheet states:

To avoid unwanted noise disturbing the tuner performance, the
M88DS3103 offers a 2-wire bus repeater dedicated for tuner
control. The tuner is connected to the M88DS3103 through the
SCLT and SDAT pins. See Figure 12. Every time the 2-wire bus
master wants to access the tuner registers, it must enable the
repeater first by configuring bit 2_WIRE_REP_EN (03H). When
the repeater is enabled, the SDAT and SCLT pins are active.
The messages on the SDA and SCL pins is repeated on the
SDAT and SCLT pins. The repeater will be automatically
disabled once the access times to the tuner reaches the
configured value set in bits 2_WIRE_REP_TM[2:0] (03H).
When disabled, the SCLT and SDAT pins are completely
isolated from the 2-wire bus and become inactive (HIGH).

When you compare this with *almost* any other demodulator
that exists; This behaviour is much consistent with that which
exists in the mainline kernel source.


If you look at most DVB-S/S2 demodulator drivers almost all
of them do have an I2C repeater, which in some cases are
configurable for a) auto-manual close, b) auto close,
c) manual close. The majority of them do auto close,
unless bugs on the hardware implementation do exist.

What I don't understand why you need an I2C adapter to handle
the I2C repeater. All demodulator drivers use i2c_gate_ctl
to enable/disable the repeater.

ie, how is your i2c_adapter implementation for the ds3103
demodulator going to make things better than:

static int ds3103_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)
{
        struct ds3103_state *state = fe->demodulator_priv;

        if (enable)
                ds3103_writereg(state, 0x03, 0x12);
        else
                ds3103_writereg(state, 0x03, 0x02);

        return 0;
}

which is more common to all other DVB demodulator drivers.
Please don't make weird implementations for straight forward
stuff.

>
> There was even some patches, maybe 2 years, ago in order to mainline that
> but it never happened.

??

>
> More complete is here 53 vs. 86 register writes, so yes it is more ~40 more
> complete if you like to compare it like that.

What I would stress more, is that the driver at this URL

http://goo.gl/biaPYu

is from Montage themselves rather than a reverse engineered one;
rather than the number of lines of code, or number of registers.

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

end of thread, other threads:[~2013-11-09  3:25 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-11-06 17:57 [PATCH 0/8] PCTV DVB-S2 Stick (461e) [2013:0258] driver Antti Palosaari
2013-11-06 17:57 ` [PATCH 1/8] em28xx: add support for Empia EM28178 Antti Palosaari
2013-11-06 17:57 ` [PATCH 2/8] a8293: add small sleep in order to settle LNB voltage Antti Palosaari
2013-11-06 17:57 ` [PATCH 3/8] Montage M88DS3103 DVB-S/S2 demodulator driver Antti Palosaari
2013-11-09  2:35   ` Manu Abraham
2013-11-09  2:48     ` Antti Palosaari
2013-11-09  3:25       ` Manu Abraham
2013-11-06 17:57 ` [PATCH 4/8] Montage M88TS2022 silicon tuner driver Antti Palosaari
2013-11-06 17:57 ` [PATCH 5/8] em28xx: add support for PCTV DVB-S2 Stick (461e) [2013:0258] Antti Palosaari
2013-11-06 17:57 ` [PATCH 6/8] m88ds3103: add parent for I2C adapter Antti Palosaari
2013-11-06 17:57 ` [PATCH 7/8] MAINTAINERS: add M88DS3103 Antti Palosaari
2013-11-06 17:57 ` [PATCH 8/8] MAINTAINERS: add M88TS2022 Antti Palosaari

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).