linux-media.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 0/5] dvb: Add support for PT3 ISDB-S/T card
@ 2014-08-27 15:29 tskd08
  2014-08-27 15:29 ` [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5 tskd08
                   ` (4 more replies)
  0 siblings, 5 replies; 40+ messages in thread
From: tskd08 @ 2014-08-27 15:29 UTC (permalink / raw)
  To: linux-media; +Cc: m.chehab

From: Akihiro Tsukada <tskd08@gmail.com>

This patch series adds support for "PT3" ISDB-S/T receiver cards.
It contains a PCI bridge driver, a dvb-frontend driver and
two tuner drivers.
I know "Bud R" had already posted ones to this mailing list,
(as in 1400629035-32487-1-git-send-email-knightrider@are.ma ), 
but it seems that he stopped updating his patch and does not agree
on splitting the single large patch into smaller, separate ones.
This series is another version written independently by me.

Changes in v2:
- added a new tuner ops (get_signal_strength_dbm()) for DVBv5
- improved kthread handling on remove,suspend,resume
- added support for suspend/resume
- moved static const tables out of function scope
- renamed badly named variables
- changed symbol_rate calculation
- other small fixes pointed out in the last review by mauro

Akihiro Tsukada (5):
  dvb-core: add a new tuner ops to dvb_frontend for APIv5
  mxl301rf: add driver for MaxLinear MxL301RF OFDM tuner
  qm1d1c0042: add driver for Sharp QM1D1C0042 ISDB-S tuner
  tc90522: add driver for Toshiba TC90522 quad demodulator
  pt3: add support for Earthsoft PT3 ISDB-S/T receiver card

 drivers/media/dvb-core/dvb_frontend.h |   2 +
 drivers/media/dvb-frontends/Kconfig   |   8 +
 drivers/media/dvb-frontends/Makefile  |   1 +
 drivers/media/dvb-frontends/tc90522.c | 857 ++++++++++++++++++++++++++++++++++
 drivers/media/dvb-frontends/tc90522.h |  63 +++
 drivers/media/pci/Kconfig             |   1 +
 drivers/media/pci/Makefile            |   1 +
 drivers/media/pci/pt3/Kconfig         |  10 +
 drivers/media/pci/pt3/Makefile        |   8 +
 drivers/media/pci/pt3/pt3.c           | 817 ++++++++++++++++++++++++++++++++
 drivers/media/pci/pt3/pt3.h           | 179 +++++++
 drivers/media/pci/pt3/pt3_dma.c       | 225 +++++++++
 drivers/media/pci/pt3/pt3_i2c.c       | 239 ++++++++++
 drivers/media/tuners/Kconfig          |  14 +
 drivers/media/tuners/Makefile         |   2 +
 drivers/media/tuners/mxl301rf.c       | 334 +++++++++++++
 drivers/media/tuners/mxl301rf.h       |  40 ++
 drivers/media/tuners/qm1d1c0042.c     | 422 +++++++++++++++++
 drivers/media/tuners/qm1d1c0042.h     |  50 ++
 19 files changed, 3273 insertions(+)
 create mode 100644 drivers/media/dvb-frontends/tc90522.c
 create mode 100644 drivers/media/dvb-frontends/tc90522.h
 create mode 100644 drivers/media/pci/pt3/Kconfig
 create mode 100644 drivers/media/pci/pt3/Makefile
 create mode 100644 drivers/media/pci/pt3/pt3.c
 create mode 100644 drivers/media/pci/pt3/pt3.h
 create mode 100644 drivers/media/pci/pt3/pt3_dma.c
 create mode 100644 drivers/media/pci/pt3/pt3_i2c.c
 create mode 100644 drivers/media/tuners/mxl301rf.c
 create mode 100644 drivers/media/tuners/mxl301rf.h
 create mode 100644 drivers/media/tuners/qm1d1c0042.c
 create mode 100644 drivers/media/tuners/qm1d1c0042.h

-- 
2.1.0


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

* [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5
  2014-08-27 15:29 [PATCH v2 0/5] dvb: Add support for PT3 ISDB-S/T card tskd08
@ 2014-08-27 15:29 ` tskd08
  2014-08-27 18:09   ` Antti Palosaari
  2014-08-27 15:29 ` [PATCH v2 2/5] mxl301rf: add driver for MaxLinear MxL301RF OFDM tuner tskd08
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 40+ messages in thread
From: tskd08 @ 2014-08-27 15:29 UTC (permalink / raw)
  To: linux-media; +Cc: m.chehab

From: Akihiro Tsukada <tskd08@gmail.com>

fe->ops.tuner_ops.get_rf_strength() reports its result in u16,
while in DVB APIv5 it should be reported in s64 and by 0.001dBm.

Signed-off-by: Akihiro Tsukada <tskd08@gmail.com>
---
 drivers/media/dvb-core/dvb_frontend.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/media/dvb-core/dvb_frontend.h b/drivers/media/dvb-core/dvb_frontend.h
index 816269e..f6222b5 100644
--- a/drivers/media/dvb-core/dvb_frontend.h
+++ b/drivers/media/dvb-core/dvb_frontend.h
@@ -222,6 +222,8 @@ struct dvb_tuner_ops {
 #define TUNER_STATUS_STEREO 2
 	int (*get_status)(struct dvb_frontend *fe, u32 *status);
 	int (*get_rf_strength)(struct dvb_frontend *fe, u16 *strength);
+	/** get signal strengh in 0.001dBm, in accordance with APIv5 */
+	int (*get_rf_strength_dbm)(struct dvb_frontend *fe, s64 *strength);
 	int (*get_afc)(struct dvb_frontend *fe, s32 *afc);
 
 	/** These are provided separately from set_params in order to facilitate silicon
-- 
2.1.0


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

* [PATCH v2 2/5] mxl301rf: add driver for MaxLinear MxL301RF OFDM tuner
  2014-08-27 15:29 [PATCH v2 0/5] dvb: Add support for PT3 ISDB-S/T card tskd08
  2014-08-27 15:29 ` [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5 tskd08
@ 2014-08-27 15:29 ` tskd08
  2014-08-27 15:29 ` [PATCH v2 3/5] qm1d1c0042: add driver for Sharp QM1D1C0042 ISDB-S tuner tskd08
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 40+ messages in thread
From: tskd08 @ 2014-08-27 15:29 UTC (permalink / raw)
  To: linux-media; +Cc: m.chehab

From: Akihiro Tsukada <tskd08@gmail.com>

This patch adds driver for mxl301rf OFDM tuner chips.
It is used as an ISDB-T tuner in earthsoft pt3 cards.

Note that this driver does not initilize the chip,
because the initilization sequence / register setting is not disclosed.
Thus, the driver assumes that the chips are initilized externally
by its parent board driver before tuner_ops->init() are called,
like in PT3 driver where the bridge chip contains the init sequence
in its private memory and provides a command to trigger the sequence.

Signed-off-by: Akihiro Tsukada <tskd08@gmail.com>
---
Changes in v2:
- use new tuner ops for get_signal_strengh() for DVBv5 API
- removed automatic frequency adjustment
- moved static const tables out of function scope
- extend max acceptable frequency to accept ISDB-Tb

 drivers/media/tuners/Kconfig    |   7 +
 drivers/media/tuners/Makefile   |   1 +
 drivers/media/tuners/mxl301rf.c | 334 ++++++++++++++++++++++++++++++++++++++++
 drivers/media/tuners/mxl301rf.h |  40 +++++
 4 files changed, 382 insertions(+)

diff --git a/drivers/media/tuners/Kconfig b/drivers/media/tuners/Kconfig
index d79fd1c..cd3f8ee 100644
--- a/drivers/media/tuners/Kconfig
+++ b/drivers/media/tuners/Kconfig
@@ -257,4 +257,11 @@ config MEDIA_TUNER_R820T
 	default m if !MEDIA_SUBDRV_AUTOSELECT
 	help
 	  Rafael Micro R820T silicon tuner driver.
+
+config MEDIA_TUNER_MXL301RF
+	tristate "MaxLinear MxL301RF tuner"
+	depends on MEDIA_SUPPORT && I2C
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  MaxLinear MxL301RF OFDM tuner driver.
 endmenu
diff --git a/drivers/media/tuners/Makefile b/drivers/media/tuners/Makefile
index 5591699..6d5bf48 100644
--- a/drivers/media/tuners/Makefile
+++ b/drivers/media/tuners/Makefile
@@ -39,6 +39,7 @@ obj-$(CONFIG_MEDIA_TUNER_FC0012) += fc0012.o
 obj-$(CONFIG_MEDIA_TUNER_FC0013) += fc0013.o
 obj-$(CONFIG_MEDIA_TUNER_IT913X) += tuner_it913x.o
 obj-$(CONFIG_MEDIA_TUNER_R820T) += r820t.o
+obj-$(CONFIG_MEDIA_TUNER_MXL301RF) += mxl301rf.o
 
 ccflags-y += -I$(srctree)/drivers/media/dvb-core
 ccflags-y += -I$(srctree)/drivers/media/dvb-frontends
diff --git a/drivers/media/tuners/mxl301rf.c b/drivers/media/tuners/mxl301rf.c
new file mode 100644
index 0000000..11ac7b8
--- /dev/null
+++ b/drivers/media/tuners/mxl301rf.c
@@ -0,0 +1,334 @@
+/*
+ * MaxLinear MxL301RF OFDM tuner driver
+ *
+ * Copyright (C) 2014 Akihiro Tsukada <tskd08@gmail.com>
+ *
+ * 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 version 2.
+ *
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "mxl301rf.h"
+
+struct mxl301rf_state {
+	struct mxl301rf_config cfg;
+	struct i2c_adapter *i2c;
+};
+
+static int data_write(struct mxl301rf_state *state, const u8 *buf, int len)
+{
+	struct i2c_msg msg = {
+		.addr = state->cfg.addr,
+		.flags = 0,
+		.buf = (u8 *)buf,
+		.len = len,
+	};
+
+	return i2c_transfer(state->i2c, &msg, 1);
+}
+
+static int reg_write(struct mxl301rf_state *state, u8 reg, u8 val)
+{
+	u8 buf[2] = { reg, val };
+
+	return data_write(state, buf, 2);
+}
+
+static int reg_read(struct mxl301rf_state *state, u8 reg, u8 *val)
+{
+	u8 wbuf[2] = { 0xfb, reg };
+	struct i2c_msg msgs[2] = {
+		{
+			.addr = state->cfg.addr,
+			.flags = 0,
+			.buf = wbuf,
+			.len = 2,
+		},
+		{
+			.addr = state->cfg.addr,
+			.flags = I2C_M_RD,
+			.buf = val,
+			.len = 1,
+		}
+	};
+
+	return i2c_transfer(state->i2c, msgs, ARRAY_SIZE(msgs));
+}
+
+/* tuner_ops */
+
+static int mxl301rf_get_status(struct dvb_frontend *fe, u32 *status)
+{
+	struct mxl301rf_state *state;
+	int ret;
+	u8 val;
+
+	*status = 0;
+	state = fe->tuner_priv;
+	ret = reg_read(state, 0x16, &val);  /* check RF Synthesizer lock */
+	if (ret < 0 || (val & 0x0c) != 0x0c)
+		return ret;
+	ret = reg_read(state, 0x16, &val);  /* check REF Synthesizer lock */
+	if (ret < 0 || (val & 0x03) != 0x03)
+		return ret;
+	*status = TUNER_STATUS_LOCKED;
+	return 0;
+}
+
+/* *strength : [0.001dBm] */
+static int mxl301rf_get_rf_strength(struct dvb_frontend *fe, s64 *strength)
+{
+	struct mxl301rf_state *state;
+	int ret;
+	u8 rf_in1, rf_in2, rf_off1, rf_off2;
+	u16 rf_in, rf_off;
+
+	*strength = 0;
+	state = fe->tuner_priv;
+	ret = reg_write(state, 0x14, 0x01);
+	if (ret < 0)
+		return ret;
+	usleep_range(1000, 2000);
+	ret = reg_read(state, 0x18, &rf_in1);
+	if (ret == 0)
+		ret = reg_read(state, 0x19, &rf_in2);
+	if (ret == 0)
+		ret = reg_read(state, 0xd6, &rf_off1);
+	if (ret == 0)
+		ret = reg_read(state, 0xd7, &rf_off2);
+	if (ret != 0)
+		return ret;
+
+	rf_in = (rf_in2 & 0x07) << 8 | rf_in1;
+	rf_off = (rf_off2 & 0x0f) << 5 | (rf_off1 >> 3);
+	*strength = rf_in - rf_off - (113 << 3); /* x8 dBm */
+	*strength = *strength * 1000 / 8;
+	return 0;
+}
+
+/* spur shift parameters */
+struct shf {
+	u32	freq;		/* Channel center frequency */
+	u32	ofst_th;	/* Offset frequency threshold */
+	u8	shf_val;	/* Spur shift value */
+	u8	shf_dir;	/* Spur shift direction */
+};
+
+static const struct shf shf_tab[] = {
+	{  64500, 500, 0x92, 0x07 },
+	{ 191500, 300, 0xe2, 0x07 },
+	{ 205500, 500, 0x2c, 0x04 },
+	{ 212500, 500, 0x1e, 0x04 },
+	{ 226500, 500, 0xd4, 0x07 },
+	{  99143, 500, 0x9c, 0x07 },
+	{ 173143, 500, 0xd4, 0x07 },
+	{ 191143, 300, 0xd4, 0x07 },
+	{ 207143, 500, 0xce, 0x07 },
+	{ 225143, 500, 0xce, 0x07 },
+	{ 243143, 500, 0xd4, 0x07 },
+	{ 261143, 500, 0xd4, 0x07 },
+	{ 291143, 500, 0xd4, 0x07 },
+	{ 339143, 500, 0x2c, 0x04 },
+	{ 117143, 500, 0x7a, 0x07 },
+	{ 135143, 300, 0x7a, 0x07 },
+	{ 153143, 500, 0x01, 0x07 }
+};
+
+static const u8 set_idac[] = {
+	0x0d, 0x00,
+	0x0c, 0x67,
+	0x6f, 0x89,
+	0x70, 0x0c,
+	0x6f, 0x8a,
+	0x70, 0x0e,
+	0x6f, 0x8b,
+	0x70, 0x1c,
+};
+
+static int mxl301rf_set_params(struct dvb_frontend *fe)
+{
+	u8 tune0[] = {
+		0x13, 0x00,		/* abort tuning */
+		0x3b, 0xc0,
+		0x3b, 0x80,
+		0x10, 0x95,		/* BW */
+		0x1a, 0x05,
+		0x61, 0x00,		/* spur shift value (placeholder) */
+		0x62, 0xa0		/* spur shift direction (placeholder) */
+	};
+	u8 tune1[] = {
+		0x11, 0x40,		/* RF frequency L (placeholder) */
+		0x12, 0x0e,		/* RF frequency H (placeholder) */
+		0x13, 0x01		/* start tune */
+	};
+
+	struct mxl301rf_state *state;
+	u32 freq;
+	u16 f;
+	u32 tmp, div;
+	int i, ret;
+
+	state = fe->tuner_priv;
+	freq = fe->dtv_property_cache.frequency;
+
+	/* spur shift function (for analog) */
+	for (i = 0; i < ARRAY_SIZE(shf_tab); i++) {
+		if (freq >= (shf_tab[i].freq - shf_tab[i].ofst_th) * 1000 &&
+		    freq <= (shf_tab[i].freq + shf_tab[i].ofst_th) * 1000) {
+			tune0[2 * 5 + 1] = shf_tab[i].shf_val;
+			tune0[2 * 6 + 1] = 0xa0 | shf_tab[i].shf_dir;
+			break;
+		}
+	}
+	ret = data_write(state, tune0, sizeof(tune0));
+	if (ret < 0)
+		goto failed;
+	usleep_range(3000, 4000);
+
+	/* convert freq to 10.6 fixed point float [MHz] */
+	f = freq / 1000000;
+	tmp = freq % 1000000;
+	div = 1000000;
+	for (i = 0; i < 6; i++) {
+		f <<= 1;
+		div >>= 1;
+		if (tmp > div) {
+			tmp -= div;
+			f |= 1;
+		}
+	}
+	if (tmp > 7812)
+		f++;
+	tune1[2 * 0 + 1] = f & 0xff;
+	tune1[2 * 1 + 1] = f >> 8;
+	ret = data_write(state, tune1, sizeof(tune1));
+	if (ret < 0)
+		goto failed;
+	msleep(31);
+
+	ret = reg_write(state, 0x1a, 0x0d);
+	if (ret < 0)
+		goto failed;
+	ret = data_write(state, set_idac, sizeof(set_idac));
+	if (ret < 0)
+		goto failed;
+	return 0;
+
+failed:
+	dev_warn(&state->i2c->dev, "(%s) failed. [adap%d-fe%d]\n",
+		__func__, fe->dvb->num, fe->id);
+	return ret;
+}
+
+static const u8 standby_data[] = {
+	0x01, 0x00, 0x13, 0x00
+};
+
+static int mxl301rf_sleep(struct dvb_frontend *fe)
+{
+	struct mxl301rf_state *state;
+	int ret;
+
+	state = fe->tuner_priv;
+	ret = data_write(fe->tuner_priv, standby_data, sizeof(standby_data));
+	if (ret < 0)
+		dev_warn(&state->i2c->dev, "(%s) failed. [adap%d-fe%d]\n",
+			__func__, fe->dvb->num, fe->id);
+	return ret;
+}
+
+static int mxl301rf_release(struct dvb_frontend *fe)
+{
+	struct mxl301rf_state *state;
+
+	state = fe->tuner_priv;
+	kfree(state);
+	fe->tuner_priv = NULL;
+	return 0;
+}
+
+
+/* init sequence is not public.
+ * the parent must have init'ed the device.
+ * just wake up here.
+ */
+static const u8 wakeup_data[] = {
+	0x01, 0x01
+};
+
+static int mxl301rf_init(struct dvb_frontend *fe)
+{
+	struct mxl301rf_state *state;
+	int ret;
+
+	state = fe->tuner_priv;
+
+	ret = data_write(state, wakeup_data, sizeof(wakeup_data));
+	if (ret < 0)
+		goto failed;
+
+	/* tune to the initial freq */
+	if (state->cfg.init_freq > 0) {
+		u32 f = fe->dtv_property_cache.frequency;
+
+		fe->dtv_property_cache.frequency = state->cfg.init_freq;
+		ret = mxl301rf_set_params(fe);
+		fe->dtv_property_cache.frequency = f;
+		if (ret < 0)
+			goto failed;
+	}
+	return 0;
+
+failed:
+	dev_warn(&state->i2c->dev, "(%s) failed. [adap%d-fe%d]\n",
+		__func__, fe->dvb->num, fe->id);
+	return ret;
+}
+
+/* exported functions */
+
+static const struct dvb_tuner_ops mxl301rf_ops = {
+	.info = {
+		.name = "MaxLinear MxL301RF",
+
+		.frequency_min =  93000000,
+		.frequency_max = 803142857,
+	},
+
+	.release = mxl301rf_release,
+	.init = mxl301rf_init,
+	.sleep = mxl301rf_sleep,
+
+	.set_params = mxl301rf_set_params,
+	.get_status = mxl301rf_get_status,
+	.get_rf_strength_dbm = mxl301rf_get_rf_strength,
+};
+
+
+struct dvb_frontend *mxl301rf_attach(struct dvb_frontend *fe,
+	struct i2c_adapter *i2c, const struct mxl301rf_config *cfg)
+{
+	struct mxl301rf_state *state;
+
+	state = kzalloc(sizeof(*state), GFP_KERNEL);
+	if (!state)
+		return NULL;
+
+	memcpy(&state->cfg, cfg, sizeof(*cfg));
+	state->i2c = i2c;
+	memcpy(&fe->ops.tuner_ops, &mxl301rf_ops, sizeof(mxl301rf_ops));
+	fe->tuner_priv = state;
+	dev_info(&i2c->dev, "MaxLinear MxL301RF attached.\n");
+	return fe;
+}
+EXPORT_SYMBOL(mxl301rf_attach);
+
+MODULE_DESCRIPTION("MaxLinear MXL301RF tuner");
+MODULE_AUTHOR("Akihiro TSUKADA");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/tuners/mxl301rf.h b/drivers/media/tuners/mxl301rf.h
new file mode 100644
index 0000000..8c47e55
--- /dev/null
+++ b/drivers/media/tuners/mxl301rf.h
@@ -0,0 +1,40 @@
+/*
+ * MaxLinear MxL301RF OFDM tuner driver
+ *
+ * Copyright (C) 2014 Akihiro Tsukada <tskd08@gmail.com>
+ *
+ * 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 version 2.
+ *
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef MXL301RF_H
+#define MXL301RF_H
+
+#include <linux/kconfig.h>
+#include "dvb_frontend.h"
+
+struct mxl301rf_config {
+	u8 addr;
+	u32 init_freq;
+};
+
+#if IS_ENABLED(CONFIG_MEDIA_TUNER_MXL301RF)
+extern struct dvb_frontend *mxl301rf_attach(struct dvb_frontend *fe,
+		struct i2c_adapter *i2c, const struct mxl301rf_config *cfg);
+#else
+static inline struct dvb_frontend *mxl301rf_attach(struct dvb_frontend *fe,
+		struct i2c_adapter *i2c, const struct mxl301rf_config *cfg)
+{
+	printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__);
+	return NULL;
+}
+#endif
+
+#endif /* MXL301RF_H */
-- 
2.1.0


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

* [PATCH v2 3/5] qm1d1c0042: add driver for Sharp QM1D1C0042 ISDB-S tuner
  2014-08-27 15:29 [PATCH v2 0/5] dvb: Add support for PT3 ISDB-S/T card tskd08
  2014-08-27 15:29 ` [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5 tskd08
  2014-08-27 15:29 ` [PATCH v2 2/5] mxl301rf: add driver for MaxLinear MxL301RF OFDM tuner tskd08
@ 2014-08-27 15:29 ` tskd08
  2014-08-27 15:29 ` [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator tskd08
  2014-08-27 15:29 ` [PATCH v2 5/5] pt3: add support for Earthsoft PT3 ISDB-S/T receiver card tskd08
  4 siblings, 0 replies; 40+ messages in thread
From: tskd08 @ 2014-08-27 15:29 UTC (permalink / raw)
  To: linux-media; +Cc: m.chehab

From: Akihiro Tsukada <tskd08@gmail.com>

This patch adds driver for qm1d1c0042 tuner chips.
It is used as an ISDB-S tuner in earthsoft pt3 cards.

Signed-off-by: Akihiro Tsukada <tskd08@gmail.com>
---
Changes in v2:
- moved a static const table out of function scope
- removed an unused config parameter
- improvement in _init() to support suspend/resume

 drivers/media/tuners/Kconfig      |   7 +
 drivers/media/tuners/Makefile     |   1 +
 drivers/media/tuners/qm1d1c0042.c | 422 ++++++++++++++++++++++++++++++++++++++
 drivers/media/tuners/qm1d1c0042.h |  50 +++++
 4 files changed, 480 insertions(+)

diff --git a/drivers/media/tuners/Kconfig b/drivers/media/tuners/Kconfig
index cd3f8ee..8125d1d 100644
--- a/drivers/media/tuners/Kconfig
+++ b/drivers/media/tuners/Kconfig
@@ -264,4 +264,11 @@ config MEDIA_TUNER_MXL301RF
 	default m if !MEDIA_SUBDRV_AUTOSELECT
 	help
 	  MaxLinear MxL301RF OFDM tuner driver.
+
+config MEDIA_TUNER_QM1D1C0042
+	tristate "Sharp QM1D1C0042 tuner"
+	depends on MEDIA_SUPPORT && I2C
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Sharp QM1D1C0042 trellis coded 8PSK tuner driver.
 endmenu
diff --git a/drivers/media/tuners/Makefile b/drivers/media/tuners/Makefile
index 6d5bf48..04d5efc 100644
--- a/drivers/media/tuners/Makefile
+++ b/drivers/media/tuners/Makefile
@@ -40,6 +40,7 @@ obj-$(CONFIG_MEDIA_TUNER_FC0013) += fc0013.o
 obj-$(CONFIG_MEDIA_TUNER_IT913X) += tuner_it913x.o
 obj-$(CONFIG_MEDIA_TUNER_R820T) += r820t.o
 obj-$(CONFIG_MEDIA_TUNER_MXL301RF) += mxl301rf.o
+obj-$(CONFIG_MEDIA_TUNER_QM1D1C0042) += qm1d1c0042.o
 
 ccflags-y += -I$(srctree)/drivers/media/dvb-core
 ccflags-y += -I$(srctree)/drivers/media/dvb-frontends
diff --git a/drivers/media/tuners/qm1d1c0042.c b/drivers/media/tuners/qm1d1c0042.c
new file mode 100644
index 0000000..ea6c245
--- /dev/null
+++ b/drivers/media/tuners/qm1d1c0042.c
@@ -0,0 +1,422 @@
+/*
+ * Sharp QM1D1C0042 8PSK tuner driver
+ *
+ * Copyright (C) 2014 Akihiro Tsukada <tskd08@gmail.com>
+ *
+ * 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 version 2.
+ *
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "qm1d1c0042.h"
+
+#define QM1D1C0042_NUM_REGS 0x20
+
+static const u8 reg_initval[QM1D1C0042_NUM_REGS] = {
+	0x48, 0x1c, 0xa0, 0x10, 0xbc, 0xc5, 0x20, 0x33,
+	0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+	0x00, 0xff, 0xf3, 0x00, 0x2a, 0x64, 0xa6, 0x86,
+	0x8c, 0xcf, 0xb8, 0xf1, 0xa8, 0xf2, 0x89, 0x00
+};
+
+static const struct qm1d1c0042_config default_cfg = {
+	.init_freq = 0,
+	.xtal_freq = 16000,
+	.lpf = 1,
+	.fast_srch = 0,
+	.lpf_wait = 20,
+	.fast_srch_wait = 4,
+	.normal_srch_wait = 15,
+};
+
+struct qm1d1c0042_state {
+	struct qm1d1c0042_config cfg;
+	struct i2c_adapter *i2c;
+	struct dvb_frontend *fe;
+	u8 regs[QM1D1C0042_NUM_REGS];
+};
+
+static int reg_write(struct qm1d1c0042_state *state, u8 reg, u8 val)
+{
+	u8 wbuf[2] = { reg, val };
+	struct i2c_msg msg = {
+		.addr = state->cfg.addr,
+		.flags = 0,
+		.buf = wbuf,
+		.len = 2,
+	};
+	return i2c_transfer(state->i2c, &msg, 1);
+}
+
+
+static int reg_read(struct qm1d1c0042_state *state, u8 reg, u8 *val)
+{
+	struct i2c_msg msgs[2] = {
+		{
+			.addr = state->cfg.addr,
+			.flags = 0,
+			.buf = &reg,
+			.len = 1,
+		},
+		{
+			.addr = state->cfg.addr,
+			.flags = I2C_M_RD,
+			.buf = val,
+			.len = 1,
+		},
+	};
+
+	return i2c_transfer(state->i2c, msgs, ARRAY_SIZE(msgs));
+}
+
+static int qm1d1c0042_set_srch_mode(struct qm1d1c0042_state *state, bool fast)
+{
+	if (fast)
+		state->regs[0x03] |= 0x01; /* set fast search mode */
+	else
+		state->regs[0x03] &= ~0x01 & 0xff;
+
+	return reg_write(state, 0x03, state->regs[0x03]);
+}
+
+static int qm1d1c0042_wakeup(struct qm1d1c0042_state *state)
+{
+	int ret;
+
+	state->regs[0x01] |= 1 << 3;             /* BB_Reg_enable */
+	state->regs[0x01] &= (~(1 << 0)) & 0xff; /* NORMAL (wake-up) */
+	state->regs[0x05] &= (~(1 << 3)) & 0xff; /* pfd_rst NORMAL */
+	ret = reg_write(state, 0x01, state->regs[0x01]);
+	if (ret == 0)
+		ret = reg_write(state, 0x05, state->regs[0x05]);
+
+	if (ret < 0)
+		dev_warn(&state->i2c->dev, "(%s) failed. [adap%d-fe%d]\n",
+			__func__, state->fe->dvb->num, state->fe->id);
+	return ret;
+}
+
+/* tuner_ops */
+
+static int qm1d1c0042_set_config(struct dvb_frontend *fe, void *priv_cfg)
+{
+	struct qm1d1c0042_state *state;
+	struct qm1d1c0042_config *cfg;
+
+	state = fe->tuner_priv;
+	cfg = priv_cfg;
+
+	state->cfg.init_freq = cfg->init_freq;
+	if (cfg->xtal_freq != QM1D1C0042_CFG_XTAL_DFLT)
+		dev_warn(&state->i2c->dev,
+			"(%s) changing xtal_freq not supported. "
+			"[adap%d-fe%d]\n", __func__, fe->dvb->num, fe->id);
+	state->cfg.lpf = cfg->lpf;
+	state->cfg.fast_srch = cfg->fast_srch;
+	if (cfg->lpf_wait != QM1D1C0042_CFG_WAIT_DFLT)
+		state->cfg.lpf_wait = cfg->lpf_wait;
+	else
+		state->cfg.lpf_wait = default_cfg.lpf_wait;
+	if (cfg->fast_srch_wait != QM1D1C0042_CFG_WAIT_DFLT)
+		state->cfg.fast_srch_wait = cfg->fast_srch_wait;
+	else
+		state->cfg.fast_srch_wait = default_cfg.fast_srch_wait;
+	if (cfg->normal_srch_wait != QM1D1C0042_CFG_WAIT_DFLT)
+		state->cfg.normal_srch_wait = cfg->normal_srch_wait;
+	else
+		state->cfg.normal_srch_wait = default_cfg.normal_srch_wait;
+	return 0;
+}
+
+/* divisor, vco_band parameters */
+/*  {maxfreq,  param1(band?), param2(div?) */
+static const u32 conv_table[9][3] = {
+	{ 2151000, 1, 7 },
+	{ 1950000, 1, 6 },
+	{ 1800000, 1, 5 },
+	{ 1600000, 1, 4 },
+	{ 1450000, 1, 3 },
+	{ 1250000, 1, 2 },
+	{ 1200000, 0, 7 },
+	{  975000, 0, 6 },
+	{  950000, 0, 0 }
+};
+
+static int qm1d1c0042_set_params(struct dvb_frontend *fe)
+{
+	struct qm1d1c0042_state *state;
+	u32 freq;
+	int i, ret;
+	u8 val, mask;
+	u32 a, sd;
+	s32 b;
+
+	state = fe->tuner_priv;
+	freq = fe->dtv_property_cache.frequency;
+
+	state->regs[0x08] &= 0xf0;
+	state->regs[0x08] |= 0x09;
+
+	state->regs[0x13] &= 0x9f;
+	state->regs[0x13] |= 0x20;
+
+	/* div2/vco_band */
+	val = state->regs[0x02] & 0x0f;
+	for (i = 0; i < 8; i++)
+		if (freq < conv_table[i][0] && freq >= conv_table[i + 1][0]) {
+			val |= conv_table[i][1] << 7;
+			val |= conv_table[i][2] << 4;
+			break;
+		}
+	ret = reg_write(state, 0x02, val);
+	if (ret < 0)
+		return ret;
+
+	a = (freq + state->cfg.xtal_freq / 2) / state->cfg.xtal_freq;
+
+	state->regs[0x06] &= 0x40;
+	state->regs[0x06] |= (a - 12) / 4;
+	ret = reg_write(state, 0x06, state->regs[0x06]);
+	if (ret < 0)
+		return ret;
+
+	state->regs[0x07] &= 0xf0;
+	state->regs[0x07] |= (a - 4 * ((a - 12) / 4 + 1) - 5) & 0x0f;
+	ret = reg_write(state, 0x07, state->regs[0x07]);
+	if (ret < 0)
+		return ret;
+
+	/* LPF */
+	val = state->regs[0x08];
+	if (state->cfg.lpf) {
+		/* LPF_CLK, LPF_FC */
+		val &= 0xf0;
+		val |= 0x02;
+	}
+	ret = reg_write(state, 0x08, val);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * b = (freq / state->cfg.xtal_freq - a) << 20;
+	 * sd = b          (b >= 0)
+	 *      1<<22 + b  (b < 0)
+	 */
+	b = (((s64) freq) << 20) / state->cfg.xtal_freq - (((s64) a) << 20);
+	if (b >= 0)
+		sd = b;
+	else
+		sd = (1 << 22) + b;
+
+	state->regs[0x09] &= 0xc0;
+	state->regs[0x09] |= (sd >> 16) & 0x3f;
+	state->regs[0x0a] = (sd >> 8) & 0xff;
+	state->regs[0x0b] = sd & 0xff;
+	ret = reg_write(state, 0x09, state->regs[0x09]);
+	ret |= reg_write(state, 0x0a, state->regs[0x0a]);
+	ret |= reg_write(state, 0x0b, state->regs[0x0b]);
+	if (ret != 0)
+		return ret;
+
+	if (!state->cfg.lpf) {
+		/* CSEL_Offset */
+		ret = reg_write(state, 0x13, state->regs[0x13]);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* VCO_TM, LPF_TM */
+	mask = state->cfg.lpf ? 0x3f : 0x7f;
+	val = state->regs[0x0c] & mask;
+	ret = reg_write(state, 0x0c, val);
+	if (ret < 0)
+		return ret;
+	usleep_range(2000, 3000);
+	val = state->regs[0x0c] | ~mask;
+	ret = reg_write(state, 0x0c, val);
+	if (ret < 0)
+		return ret;
+
+	if (state->cfg.lpf)
+		msleep(state->cfg.lpf_wait);
+	else if (state->regs[0x03] & 0x01)
+		msleep(state->cfg.fast_srch_wait);
+	else
+		msleep(state->cfg.normal_srch_wait);
+
+	if (state->cfg.lpf) {
+		/* LPF_FC */
+		ret = reg_write(state, 0x08, 0x09);
+		if (ret < 0)
+			return ret;
+
+		/* CSEL_Offset */
+		ret = reg_write(state, 0x13, state->regs[0x13]);
+		if (ret < 0)
+			return ret;
+	}
+	return 0;
+}
+
+static int qm1d1c0042_get_status(struct dvb_frontend *fe, u32 *status)
+{
+	struct qm1d1c0042_state *state;
+	int ret;
+
+	*status = 0;
+	state = fe->tuner_priv;
+	ret = reg_read(state, 0x0d, &state->regs[0x0d]);
+	if (ret == 0 && state->regs[0x0d] & 0x40)
+		*status = TUNER_STATUS_LOCKED;
+	return ret;
+}
+
+static int qm1d1c0042_sleep(struct dvb_frontend *fe)
+{
+	struct qm1d1c0042_state *state;
+	int ret;
+
+	state = fe->tuner_priv;
+	state->regs[0x01] &= (~(1 << 3)) & 0xff; /* BB_Reg_disable */
+	state->regs[0x01] |= 1 << 0;             /* STDBY */
+	state->regs[0x05] |= 1 << 3;             /* pfd_rst STANDBY */
+	ret = reg_write(state, 0x05, state->regs[0x05]);
+	if (ret == 0)
+		ret = reg_write(state, 0x01, state->regs[0x01]);
+	if (ret < 0)
+		dev_warn(&state->i2c->dev, "(%s) failed. [adap%d-fe%d]\n",
+			__func__, fe->dvb->num, fe->id);
+	return ret;
+}
+
+static int qm1d1c0042_init(struct dvb_frontend *fe)
+{
+	struct qm1d1c0042_state *state;
+	u8 val;
+	u8 i;
+	int ret;
+
+	state = fe->tuner_priv;
+	memcpy(state->regs, reg_initval, sizeof(reg_initval));
+
+	reg_write(state, 0x01, 0x0c);
+	reg_write(state, 0x01, 0x0c);
+
+	ret = reg_write(state, 0x01, 0x0c); /* soft reset on */
+	if (ret < 0)
+		goto failed;
+	usleep_range(2000, 3000);
+
+	val = state->regs[0x01] | 0x10;
+	ret = reg_write(state, 0x01, val); /* soft reset off */
+	if (ret < 0)
+		goto failed;
+
+	/* check ID */
+	ret = reg_read(state, 0x00, &val);
+	if (ret < 0 || val != 0x48)
+		goto failed;
+	usleep_range(2000, 3000);
+
+	state->regs[0x0c] |= 0x40;
+	ret = reg_write(state, 0x0c, state->regs[0x0c]);
+	if (ret < 0)
+		goto failed;
+	msleep(state->cfg.lpf_wait);
+
+	/* set all writable registers */
+	for (i = 1; i <= 0x0c ; i++) {
+		ret = reg_write(state, i, state->regs[i]);
+		if (ret < 0)
+			goto failed;
+	}
+	for (i = 0x11; i < QM1D1C0042_NUM_REGS; i++) {
+		ret = reg_write(state, i, state->regs[i]);
+		if (ret < 0)
+			goto failed;
+	}
+
+	ret = qm1d1c0042_wakeup(state);
+	if (ret < 0)
+		goto failed;
+
+	ret = qm1d1c0042_set_srch_mode(state, state->cfg.fast_srch);
+	if (ret < 0)
+		goto failed;
+
+	if (state->cfg.init_freq > 0) {
+		u32 f = fe->dtv_property_cache.frequency;
+
+		fe->dtv_property_cache.frequency = state->cfg.init_freq;
+		ret = qm1d1c0042_set_params(fe);
+		fe->dtv_property_cache.frequency = f;
+	}
+	return ret;
+
+failed:
+	dev_warn(&state->i2c->dev, "(%s) failed. [adap%d-fe%d]\n",
+		__func__, fe->dvb->num, fe->id);
+	return ret;
+}
+
+static int qm1d1c0042_release(struct dvb_frontend *fe)
+{
+	struct qm1d1c0042_state *state;
+
+	state = fe->tuner_priv;
+	kfree(state);
+	fe->tuner_priv = NULL;
+	return 0;
+}
+
+/* exported functions */
+
+static const struct dvb_tuner_ops qm1d1c0042_ops = {
+	.info = {
+		.name = "Sharp QM1D1C0042",
+
+		.frequency_min =  950000,
+		.frequency_max = 2150000,
+	},
+
+	.release = qm1d1c0042_release,
+	.init = qm1d1c0042_init,
+	.sleep = qm1d1c0042_sleep,
+	.set_config = qm1d1c0042_set_config,
+	.set_params = qm1d1c0042_set_params,
+	.get_status = qm1d1c0042_get_status,
+};
+
+
+struct dvb_frontend *qm1d1c0042_attach(struct dvb_frontend *fe,
+				       struct i2c_adapter *i2c,
+				       const struct qm1d1c0042_config *cfg)
+{
+	struct qm1d1c0042_state *state;
+
+	state = kzalloc(sizeof(*state), GFP_KERNEL);
+	if (!state)
+		return NULL;
+
+	state->fe = fe;
+	state->i2c = i2c;
+	memcpy(&state->cfg, &default_cfg, sizeof(default_cfg));
+	state->cfg.addr = cfg->addr;
+
+	memcpy(&fe->ops.tuner_ops, &qm1d1c0042_ops, sizeof(qm1d1c0042_ops));
+	fe->tuner_priv = state;
+	fe->ops.tuner_ops.set_config(fe, (void *)cfg);
+	dev_info(&i2c->dev, "Sharp QM1D1C0042 attached.\n");
+	return fe;
+}
+EXPORT_SYMBOL(qm1d1c0042_attach);
+
+MODULE_DESCRIPTION("Sharp QM1D1C0042 tuner");
+MODULE_AUTHOR("Akihiro TSUKADA");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/tuners/qm1d1c0042.h b/drivers/media/tuners/qm1d1c0042.h
new file mode 100644
index 0000000..5b76da8
--- /dev/null
+++ b/drivers/media/tuners/qm1d1c0042.h
@@ -0,0 +1,50 @@
+/*
+ * Sharp QM1D1C0042 8PSK tuner driver
+ *
+ * Copyright (C) 2014 Akihiro Tsukada <tskd08@gmail.com>
+ *
+ * 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 version 2.
+ *
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef QM1D1C0042_H
+#define QM1D1C0042_H
+
+#include <linux/kconfig.h>
+#include "dvb_frontend.h"
+
+
+struct qm1d1c0042_config {
+	u8   addr;
+	u32  init_freq;    /* initial frequency to be tuned. [kHz] */
+	u32  xtal_freq;    /* [kHz] */ /* currently ignored */
+	bool lpf;          /* enable LPF */
+	bool fast_srch;    /* enable fast search mode, no LPF */
+	u32  lpf_wait;         /* wait in tuning with LPF enabled. [ms] */
+	u32  fast_srch_wait;   /* with fast-search mode, no LPF. [ms] */
+	u32  normal_srch_wait; /* with no LPF/fast-search mode. [ms] */
+};
+/* special values indicating to use the default in qm1d1c0042_config */
+#define QM1D1C0042_CFG_XTAL_DFLT 0
+#define QM1D1C0042_CFG_WAIT_DFLT 0
+
+#if IS_ENABLED(CONFIG_MEDIA_TUNER_QM1D1C0042)
+extern struct dvb_frontend *qm1d1c0042_attach(struct dvb_frontend *fe,
+		struct i2c_adapter *i2c, const struct qm1d1c0042_config *cfg);
+#else
+static inline struct dvb_frontend *qm1d1c0042_attach(struct dvb_frontend *fe,
+		struct i2c_adapter *i2c, const struct qm1d1c0042_config *cfg)
+{
+	printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__);
+	return NULL;
+}
+#endif
+
+#endif /* QM1D1C0042_H */
-- 
2.1.0


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

* [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator
  2014-08-27 15:29 [PATCH v2 0/5] dvb: Add support for PT3 ISDB-S/T card tskd08
                   ` (2 preceding siblings ...)
  2014-08-27 15:29 ` [PATCH v2 3/5] qm1d1c0042: add driver for Sharp QM1D1C0042 ISDB-S tuner tskd08
@ 2014-08-27 15:29 ` tskd08
  2014-08-31 10:29   ` Matthias Schwarzott
  2014-08-27 15:29 ` [PATCH v2 5/5] pt3: add support for Earthsoft PT3 ISDB-S/T receiver card tskd08
  4 siblings, 1 reply; 40+ messages in thread
From: tskd08 @ 2014-08-27 15:29 UTC (permalink / raw)
  To: linux-media; +Cc: m.chehab

From: Akihiro Tsukada <tskd08@gmail.com>

This patch adds driver for tc90522 demodulator chips.
The chip contains 4 demod modules that run in parallel and are independently
controllable via separate I2C addresses.
Two of the modules are for ISDB-T and the rest for ISDB-S.
It is used in earthsoft pt3 cards.

Note that this driver does not init the chip,
because the initilization sequence / register setting is not disclosed.
Thus, the driver assumes that the chips are initilized externally
by its parent board driver before fe->ops->init() are called.
Earthsoft PT3 PCIe card, for example, contains the init sequence
in its private memory and provides a command to trigger the sequence.

Signed-off-by: Akihiro Tsukada <tskd08@gmail.com>
---
Changes in v2:
- renamed badly named variables
- moved static const tables out of function scope
- calculate symbol rate per output TS
- improve in _init() to support suspend/resume

 drivers/media/dvb-frontends/Kconfig   |   8 +
 drivers/media/dvb-frontends/Makefile  |   1 +
 drivers/media/dvb-frontends/tc90522.c | 857 ++++++++++++++++++++++++++++++++++
 drivers/media/dvb-frontends/tc90522.h |  63 +++
 4 files changed, 929 insertions(+)

diff --git a/drivers/media/dvb-frontends/Kconfig b/drivers/media/dvb-frontends/Kconfig
index aa5ae22..123adb2 100644
--- a/drivers/media/dvb-frontends/Kconfig
+++ b/drivers/media/dvb-frontends/Kconfig
@@ -648,6 +648,14 @@ config DVB_MB86A20S
 	  A driver for Fujitsu mb86a20s ISDB-T/ISDB-Tsb demodulator.
 	  Say Y when you want to support this frontend.
 
+config DVB_TC90522
+	tristate "Toshiba TC90522"
+	depends on DVB_CORE && I2C
+	default m if !MEDIA_SUBDRV_AUTOSELECT
+	help
+	  A Toshiba TC90522 2xISDB-T + 2xISDB-S demodulator.
+	  Say Y when you want to support this frontend.
+
 comment "Digital terrestrial only tuners/PLL"
 	depends on DVB_CORE
 
diff --git a/drivers/media/dvb-frontends/Makefile b/drivers/media/dvb-frontends/Makefile
index fc4e689..00cc299 100644
--- a/drivers/media/dvb-frontends/Makefile
+++ b/drivers/media/dvb-frontends/Makefile
@@ -114,3 +114,4 @@ obj-$(CONFIG_DVB_RTL2832_SDR) += rtl2832_sdr.o
 obj-$(CONFIG_DVB_M88RS2000) += m88rs2000.o
 obj-$(CONFIG_DVB_AF9033) += af9033.o
 obj-$(CONFIG_DVB_AS102_FE) += as102_fe.o
+obj-$(CONFIG_DVB_TC90522) += tc90522.o
diff --git a/drivers/media/dvb-frontends/tc90522.c b/drivers/media/dvb-frontends/tc90522.c
new file mode 100644
index 0000000..1f0f6f7
--- /dev/null
+++ b/drivers/media/dvb-frontends/tc90522.c
@@ -0,0 +1,857 @@
+/*
+ * Toshiba TC90522 Demodulator
+ *
+ * Copyright (C) 2014 Akihiro Tsukada <tskd08@gmail.com>
+ *
+ * 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 version 2.
+ *
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/dvb/frontend.h>
+#include "dvb_math.h"
+#include "tc90522.h"
+
+#define TC90522_I2C_THRU_REG 0xfe
+
+#define TC90522_MODULE_IDX(addr) (((u8)(addr) & 0x02U) >> 1)
+
+enum tc90522_tuning_status {
+	STATUS_IDLE,
+	STATUS_SET_FREQ,
+	STATUS_CHECK_TUNER,
+	STATUS_CHECK_DEMOD,
+	STATUS_TRACK,
+};
+
+struct tc90522_state {
+	struct dvb_frontend dvb_fe;
+
+	struct tc90522_config cfg;
+	struct i2c_adapter *i2c;
+	struct i2c_adapter tuner_i2c;
+	enum tc90522_tuning_status tuning_status;
+	int retry_count;
+
+	bool lna;
+};
+
+struct reg_val {
+	u8 reg;
+	u8 val;
+};
+
+
+static int
+reg_write(struct tc90522_state *state, const struct reg_val *regs, int num)
+{
+	int i, ret;
+	struct i2c_msg msg;
+
+	ret = 0;
+	msg.addr = state->cfg.addr;
+	msg.flags = 0;
+	msg.len = 2;
+	for (i = 0; i < num; i++) {
+		msg.buf = (u8 *)&regs[i];
+		ret = i2c_transfer(state->i2c, &msg, 1);
+		if (ret < 0)
+			break;
+	}
+	return ret;
+}
+
+static int reg_read(struct tc90522_state *state, u8 reg, u8 *val, u8 len)
+{
+	struct i2c_msg msgs[2] = {
+		{
+			.addr = state->cfg.addr,
+			.flags = 0,
+			.buf = &reg,
+			.len = 1,
+		},
+		{
+			.addr = state->cfg.addr,
+			.flags = I2C_M_RD,
+			.buf = val,
+			.len = len,
+		},
+	};
+
+	return i2c_transfer(state->i2c, msgs, ARRAY_SIZE(msgs));
+}
+
+static int enable_lna(struct dvb_frontend *fe, bool on)
+{
+	struct tc90522_state *state;
+
+	state = fe->demodulator_priv;
+	/* delegate to the parent board */
+	if (fe->callback)
+		fe->callback(fe, DVB_FRONTEND_COMPONENT_DEMOD,
+				TC90522T_CMD_SET_LNA, on);
+	state->lna = on;
+	return 0;
+}
+
+static int tc90522s_set_tsid(struct dvb_frontend *fe)
+{
+	struct reg_val set_tsid[] = {
+		{ 0x8f, 00 },
+		{ 0x90, 00 }
+	};
+
+	set_tsid[0].val = (fe->dtv_property_cache.stream_id & 0xff00) >> 8;
+	set_tsid[1].val = fe->dtv_property_cache.stream_id & 0xff;
+	return reg_write(fe->demodulator_priv, set_tsid, ARRAY_SIZE(set_tsid));
+}
+
+static int tc90522t_set_layers(struct dvb_frontend *fe)
+{
+	struct reg_val rv;
+	u8 laysel;
+
+	laysel = ~fe->dtv_property_cache.isdbt_layer_enabled & 0x07;
+	laysel = (laysel & 0x01) << 2 | (laysel & 0x02) | (laysel & 0x04) >> 2;
+	rv.reg = 0x71;
+	rv.val = laysel;
+	return reg_write(fe->demodulator_priv, &rv, 1);
+}
+
+/* frontend ops */
+
+static int tc90522s_read_status(struct dvb_frontend *fe, fe_status_t *status)
+{
+	int ret;
+	u8 reg;
+
+	*status = 0;
+	ret = reg_read(fe->demodulator_priv, 0xc3, &reg, 1);
+	if (ret < 0)
+		return ret;
+
+	if (reg & 0x80) /* input level under min ? */
+		return 0;
+	*status |= FE_HAS_SIGNAL;
+
+	if (reg & 0x60) /* carrier? */
+		return 0;
+	*status |= FE_HAS_CARRIER | FE_HAS_VITERBI | FE_HAS_SYNC;
+
+	if (reg & 0x10)
+		return 0;
+	if (reg_read(fe->demodulator_priv, 0xc5, &reg, 1) < 0 || !(reg & 0x03))
+		return 0;
+	*status |= FE_HAS_LOCK;
+	return 0;
+}
+
+static int tc90522t_read_status(struct dvb_frontend *fe, fe_status_t *status)
+{
+	int ret;
+	u8 reg;
+
+	*status = 0;
+	ret = reg_read(fe->demodulator_priv, 0x96, &reg, 1);
+	if (ret < 0)
+		return ret;
+
+	if (reg & 0xe0) {
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_VITERBI
+				| FE_HAS_SYNC | FE_HAS_LOCK;
+		return 0;
+	}
+
+	ret = reg_read(fe->demodulator_priv, 0x80, &reg, 1);
+	if (ret < 0)
+		return ret;
+
+	if (reg & 0xf0)
+		return 0;
+	*status |= FE_HAS_SIGNAL | FE_HAS_CARRIER;
+
+	if (reg & 0x0c)
+		return 0;
+	*status |= FE_HAS_SYNC | FE_HAS_VITERBI;
+
+	if (reg & 0x02)
+		return 0;
+	*status |= FE_HAS_LOCK;
+	return 0;
+}
+
+static const fe_code_rate_t fec_conv_sat[] = {
+	FEC_NONE, /* unused */
+	FEC_1_2, /* for BPSK */
+	FEC_1_2, FEC_2_3, FEC_3_4, FEC_5_6, FEC_7_8, /* for QPSK */
+	FEC_2_3, /* for 8PSK. (trellis code) */
+};
+
+static int tc90522s_get_frontend(struct dvb_frontend *fe)
+{
+	struct tc90522_state *state;
+	struct dtv_frontend_properties *c;
+	struct dtv_fe_stats *stats;
+	int ret, i;
+	int layers;
+	u8 val[10], v;
+	u32 cndat;
+
+	state = fe->demodulator_priv;
+	c = &fe->dtv_property_cache;
+	c->delivery_system = SYS_ISDBS;
+
+	layers = 0;
+	ret = reg_read(state, 0xe8, val, 3);
+	if (ret == 0) {
+		int slots;
+
+		/* high/single layer */
+		v = (val[0] & 0x70) >> 4;
+		c->modulation = (v == 7) ? PSK_8 : QPSK;
+		c->fec_inner = fec_conv_sat[v];
+		c->layer[0].fec = c->fec_inner;
+		c->layer[0].modulation = c->modulation;
+		c->layer[0].segment_count = val[1] & 0x3f; /* slots */
+
+		/* low layer */
+		v = (val[0] & 0x07);
+		c->layer[1].fec = fec_conv_sat[v];
+		if (v == 0)  /* no low layer */
+			c->layer[1].segment_count = 0;
+		else
+			c->layer[1].segment_count = val[2] & 0x3f; /* slots */
+		/* actually, BPSK if v==1, but not defined in fe_modulation_t */
+		c->layer[1].modulation = QPSK;
+		layers = (v > 0) ? 2 : 1;
+
+		slots =  c->layer[0].segment_count +  c->layer[1].segment_count;
+		c->symbol_rate = 28860000 * slots / 48;
+	}
+
+	/* statistics */
+
+	stats = &c->strength;
+	stats->len = 0;
+	if (fe->ops.tuner_ops.get_rf_strength_dbm) {
+		s64 str;
+
+		stats->len = 1;
+		ret = fe->ops.tuner_ops.get_rf_strength_dbm(fe, &str);
+		if (ret == 0) {
+			stats->stat[0].scale = FE_SCALE_DECIBEL;
+			stats->stat[0].svalue = str;
+		} else
+			stats->stat[0].scale = FE_SCALE_NOT_AVAILABLE;
+	}
+
+	stats = &c->cnr;
+	stats->len = 1;
+	stats->stat[0].scale = FE_SCALE_NOT_AVAILABLE;
+	cndat = 0;
+	ret = reg_read(state, 0xbc, val, 2);
+	if (ret == 0)
+		cndat = val[0] << 8 | val[1];
+	if (cndat >= 3000) {
+		u32 p, p4;
+		s64 cn;
+
+		cndat -= 3000;  /* cndat: 4.12 fixed point float */
+		/*
+		 * cnr[mdB] = -1634.6 * P^5 + 14341 * P^4 - 50259 * P^3
+		 *                 + 88977 * P^2 - 89565 * P + 58857
+		 *  (P = sqrt(cndat) / 64)
+		 */
+		/* p := sqrt(cndat) << 8 = P << 14, 2.14 fixed  point float */
+		/* cn = cnr << 3 */
+		p = int_sqrt(cndat << 16);
+		p4 = cndat * cndat;
+		cn = (-16346LL * p4 * p / 10) >> 35;
+		cn += (14341LL * p4) >> 21;
+		cn -= (50259LL * cndat * p) >> 23;
+		cn += (88977LL * cndat) >> 9;
+		cn -= (89565LL * p) >> 11;
+		cn += 58857  << 3;
+		stats->stat[0].svalue = cn >> 3;
+		stats->stat[0].scale = FE_SCALE_DECIBEL;
+	}
+
+	/* per-layer post viterbi BER (or PER? config dependent?) */
+	stats = &c->post_bit_error;
+	memset(stats, 0, sizeof(*stats));
+	stats->len = layers;
+	ret = reg_read(state, 0xeb, val, 10);
+	if (ret < 0)
+		for (i = 0; i < layers; i++)
+			stats->stat[i].scale = FE_SCALE_NOT_AVAILABLE;
+	else {
+		for (i = 0; i < layers; i++) {
+			stats->stat[i].scale = FE_SCALE_COUNTER;
+			stats->stat[i].uvalue = val[i * 5] << 16
+				| val[i * 5 + 1] << 8 | val[i * 5 + 2];
+		}
+	}
+	stats = &c->post_bit_count;
+	memset(stats, 0, sizeof(*stats));
+	stats->len = layers;
+	if (ret < 0)
+		for (i = 0; i < layers; i++)
+			stats->stat[i].scale = FE_SCALE_NOT_AVAILABLE;
+	else {
+		for (i = 0; i < layers; i++) {
+			stats->stat[i].scale = FE_SCALE_COUNTER;
+			stats->stat[i].uvalue =
+				val[i * 5 + 3] << 8 | val[i * 5 + 4];
+			stats->stat[i].uvalue *= 204 * 8;
+		}
+	}
+
+	return 0;
+}
+
+
+static const fe_transmit_mode_t tm_conv[] = {
+	TRANSMISSION_MODE_2K,
+	TRANSMISSION_MODE_4K,
+	TRANSMISSION_MODE_8K,
+	0
+};
+
+static const fe_code_rate_t fec_conv_ter[] = {
+	FEC_1_2, FEC_2_3, FEC_3_4, FEC_5_6, FEC_7_8, 0, 0, 0
+};
+
+static const fe_modulation_t mod_conv[] = {
+	DQPSK, QPSK, QAM_16, QAM_64, 0, 0, 0, 0
+};
+
+static int tc90522t_get_frontend(struct dvb_frontend *fe)
+{
+	struct tc90522_state *state;
+	struct dtv_frontend_properties *c;
+	struct dtv_fe_stats *stats;
+	int ret, i;
+	int layers;
+	u8 val[15], mode;
+	u32 cndat;
+
+	state = fe->demodulator_priv;
+	c = &fe->dtv_property_cache;
+	c->delivery_system = SYS_ISDBT;
+	c->bandwidth_hz = 6000000;
+	mode = 1;
+	ret = reg_read(state, 0xb0, val, 1);
+	if (ret == 0) {
+		mode = (val[0] & 0xc0) >> 2;
+		c->transmission_mode = tm_conv[mode];
+		c->guard_interval = (val[0] & 0x30) >> 4;
+	}
+
+	ret = reg_read(state, 0xb2, val, 6);
+	layers = 0;
+	if (ret == 0) {
+		u8 v;
+
+		c->isdbt_partial_reception = val[0] & 0x01;
+		c->isdbt_sb_mode = (val[0] & 0xc0) == 0x01;
+
+		/* layer A */
+		v = (val[2] & 0x78) >> 3;
+		if (v == 0x0f)
+			c->layer[0].segment_count = 0;
+		else {
+			layers++;
+			c->layer[0].segment_count = v;
+			c->layer[0].fec = fec_conv_ter[(val[1] & 0x1c) >> 2];
+			c->layer[0].modulation = mod_conv[(val[1] & 0xe0) >> 5];
+			v = (val[1] & 0x03) << 1 | (val[2] & 0x80) >> 7;
+			c->layer[0].interleaving = v;
+		}
+
+		/* layer B */
+		v = (val[3] & 0x03) << 1 | (val[4] & 0xc0) >> 6;
+		if (v == 0x0f)
+			c->layer[1].segment_count = 0;
+		else {
+			layers++;
+			c->layer[1].segment_count = v;
+			c->layer[1].fec = fec_conv_ter[(val[3] & 0xe0) >> 5];
+			c->layer[1].modulation = mod_conv[(val[2] & 0x07)];
+			c->layer[1].interleaving = (val[3] & 0x1c) >> 2;
+		}
+
+		/* layer C */
+		v = (val[5] & 0x1e) >> 1;
+		if (v == 0x0f)
+			c->layer[2].segment_count = 0;
+		else {
+			layers++;
+			c->layer[2].segment_count = v;
+			c->layer[2].fec = fec_conv_ter[(val[4] & 0x07)];
+			c->layer[2].modulation = mod_conv[(val[4] & 0x38) >> 3];
+			c->layer[2].interleaving = (val[5] & 0xe0) >> 5;
+		}
+	}
+
+	/* statistics */
+
+	stats = &c->strength;
+	stats->len = 0;
+	if (fe->ops.tuner_ops.get_rf_strength_dbm) {
+		s64 str;
+
+		stats->len = 1;
+		ret = fe->ops.tuner_ops.get_rf_strength_dbm(fe, &str);
+		if (ret == 0) {
+			stats->stat[0].scale = FE_SCALE_DECIBEL;
+			stats->stat[0].svalue = str;
+		} else
+			stats->stat[0].scale = FE_SCALE_NOT_AVAILABLE;
+	}
+
+	stats = &c->cnr;
+	stats->len = 1;
+	stats->stat[0].scale = FE_SCALE_NOT_AVAILABLE;
+	cndat = 0;
+	ret = reg_read(state, 0x8b, val, 3);
+	if (ret == 0)
+		cndat = val[0] << 16 | val[1] << 8 | val[2];
+	if (cndat != 0) {
+		u32 p, tmp;
+		s64 cn;
+
+		/*
+		 * cnr[mdB] = 0.024 P^4 - 1.6 P^3 + 39.8 P^2 + 549.1 P + 3096.5
+		 * (P = 10log10(5505024/cndat))
+		 */
+		/* cn = cnr << 3 (61.3 fixed point float */
+		/* p = 10log10(5505024/cndat) << 24  (8.24 fixed point float)*/
+		p = intlog10(5505024) - intlog10(cndat);
+		p *= 10;
+
+		cn = 24772;
+		cn += ((43827LL * p) / 10) >> 24;
+		tmp = p >> 8;
+		cn += ((3184LL * tmp * tmp) / 10) >> 32;
+		tmp = p >> 13;
+		cn -= ((128LL * tmp * tmp * tmp) / 10) >> 33;
+		tmp = p >> 18;
+		cn += ((192LL * tmp * tmp * tmp * tmp) / 1000) >> 24;
+
+		stats->stat[0].svalue = cn >> 3;
+		stats->stat[0].scale = FE_SCALE_DECIBEL;
+	}
+
+	/* per-layer post viterbi BER (or PER? config dependent?) */
+	stats = &c->post_bit_error;
+	memset(stats, 0, sizeof(*stats));
+	stats->len = layers;
+	ret = reg_read(state, 0x9d, val, 15);
+	if (ret < 0)
+		for (i = 0; i < layers; i++)
+			stats->stat[i].scale = FE_SCALE_NOT_AVAILABLE;
+	else {
+		for (i = 0; i < layers; i++) {
+			stats->stat[i].scale = FE_SCALE_COUNTER;
+			stats->stat[i].uvalue = val[i * 3] << 16
+				| val[i * 3 + 1] << 8 | val[i * 3 + 2];
+		}
+	}
+	stats = &c->post_bit_count;
+	memset(stats, 0, sizeof(*stats));
+	stats->len = layers;
+	if (ret < 0)
+		for (i = 0; i < layers; i++)
+			stats->stat[i].scale = FE_SCALE_NOT_AVAILABLE;
+	else {
+		for (i = 0; i < layers; i++) {
+			stats->stat[i].scale = FE_SCALE_COUNTER;
+			stats->stat[i].uvalue =
+				val[9 + i * 2] << 8 | val[9 + i * 2 + 1];
+			stats->stat[i].uvalue *= 204 * 8;
+		}
+	}
+
+	return 0;
+}
+
+static int tc90522s_set_voltage(struct dvb_frontend *fe, fe_sec_voltage_t volt)
+{
+	int ret;
+
+	ret = 0;
+	if (fe->callback)
+		ret = fe->callback(fe, DVB_FRONTEND_COMPONENT_DEMOD,
+				TC90522S_CMD_SET_LNB, volt);
+	return ret;
+}
+
+static int tc90522t_set_lna(struct dvb_frontend *fe)
+{
+	struct tc90522_state *state;
+	int ret;
+
+	ret = 0;
+	state = fe->demodulator_priv;
+	if (fe->dtv_property_cache.lna != LNA_AUTO &&
+	    fe->dtv_property_cache.lna != state->lna)
+		ret = enable_lna(fe, fe->dtv_property_cache.lna);
+	return ret;
+}
+
+static const struct reg_val reset_sat = { 0x03, 0x01 };
+static const struct reg_val reset_ter = { 0x01, 0x40 };
+
+static int tc90522_set_frontend(struct dvb_frontend *fe)
+{
+	struct tc90522_state *state;
+	int ret, i;
+	u32 status;
+
+	state = fe->demodulator_priv;
+
+	if (fe->ops.tuner_ops.set_params)
+		ret = fe->ops.tuner_ops.set_params(fe);
+	else
+		ret = -ENODEV;
+	if (ret < 0)
+		goto failed;
+
+	if (fe->ops.tuner_ops.get_status) {
+		for (i = 50; i > 0; i--) {
+			ret = fe->ops.tuner_ops.get_status(fe, &status);
+			if (ret < 0)
+				goto failed;
+			if (status & TUNER_STATUS_LOCKED)
+				break;
+			usleep_range(2000, 3000);
+		}
+		if (i <= 0) {
+			ret = -ETIMEDOUT;
+			goto failed;
+		}
+	}
+
+	if (fe->ops.delsys[0] == SYS_ISDBS) {
+		ret = tc90522s_set_tsid(fe);
+		if (ret < 0)
+			goto failed;
+		ret = reg_write(state, &reset_sat, 1);
+	} else {
+		ret = tc90522t_set_layers(fe);
+		if (ret < 0)
+			goto failed;
+		ret = reg_write(state, &reset_ter, 1);
+	}
+	if (ret < 0)
+		goto failed;
+
+	return 0;
+
+failed:
+	dev_warn(&state->tuner_i2c.dev, "(%s) failed. [adap%d-fe%d]\n",
+			__func__, fe->dvb->num, fe->id);
+	return ret;
+}
+
+static int tc90522_get_tune_settings(struct dvb_frontend *fe,
+	struct dvb_frontend_tune_settings *settings)
+{
+	if (fe->ops.delsys[0] == SYS_ISDBS) {
+		settings->min_delay_ms = 250;
+		settings->step_size = 1000;
+		settings->max_drift = settings->step_size * 2;
+	} else {
+		settings->min_delay_ms = 400;
+		settings->step_size = 142857;
+		settings->max_drift = settings->step_size;
+	}
+	return 0;
+}
+
+static int tc90522_set_if_agc(struct dvb_frontend *fe, bool on)
+{
+	struct reg_val sat_agc[] = {
+		{ 0x0a, 0x00 },
+		{ 0x10, 0x30 },
+		{ 0x11, 0x00 },
+		{ 0x03, 0x01 },
+	};
+	struct reg_val ter_agc[] = {
+		{ 0x25, 0x00 },
+		{ 0x23, 0x4c },
+		{ 0x01, 0x40 },
+	};
+	struct tc90522_state *state;
+	struct reg_val *rv;
+	int num;
+
+	state = fe->demodulator_priv;
+	if (fe->ops.delsys[0] == SYS_ISDBS) {
+		sat_agc[0].val = on ? 0xff : 0x00;
+		sat_agc[1].val |= 0x80;
+		sat_agc[1].val |= on ? 0x01 : 0x00;
+		sat_agc[2].val |= on ? 0x40 : 0x00;
+		rv = sat_agc;
+		num = ARRAY_SIZE(sat_agc);
+	} else {
+		ter_agc[0].val = on ? 0x40 : 0x00;
+		ter_agc[1].val |= on ? 0x00 : 0x01;
+		rv = ter_agc;
+		num = ARRAY_SIZE(ter_agc);
+	}
+	return reg_write(state, rv, num);
+}
+
+static const struct reg_val sleep_sat = { 0x17, 0x01 };
+static const struct reg_val sleep_ter = { 0x03, 0x90 };
+
+static int tc90522_sleep(struct dvb_frontend *fe)
+{
+	struct tc90522_state *state;
+	int ret;
+
+	state = fe->demodulator_priv;
+	if (fe->ops.delsys[0] == SYS_ISDBS)
+		ret = reg_write(state, &sleep_sat, 1);
+	else {
+		ret = reg_write(state, &sleep_ter, 1);
+		if (ret == 0 && fe->dtv_property_cache.lna == LNA_AUTO)
+			ret = enable_lna(fe, false);
+	}
+	if (ret < 0)
+		dev_warn(&state->tuner_i2c.dev,
+			"(%s) failed. [adap%d-fe%d]\n",
+			__func__, fe->dvb->num, fe->id);
+	return ret;
+}
+
+static void tc90522_release(struct dvb_frontend *fe)
+{
+	struct tc90522_state *state;
+
+	state = fe->demodulator_priv;
+	i2c_del_adapter(&state->tuner_i2c);
+	fe->demodulator_priv = NULL;
+	kfree(state);
+}
+
+
+static const struct reg_val wakeup_sat = { 0x17, 0x00 };
+static const struct reg_val wakeup_ter = { 0x03, 0x80 };
+
+static int tc90522_init(struct dvb_frontend *fe)
+{
+	struct tc90522_state *state;
+	int ret;
+
+	/*
+	 * Because the init sequence is not public,
+	 * the parent device/driver should have init'ed the device before.
+	 * just wake up the device here.
+	 */
+
+	state = fe->demodulator_priv;
+	if (fe->ops.delsys[0] == SYS_ISDBS)
+		ret = reg_write(state, &wakeup_sat, 1);
+	else {
+		ret = reg_write(state, &wakeup_ter, 1);
+		if (ret == 0 && fe->dtv_property_cache.lna == LNA_AUTO)
+			ret = enable_lna(fe, true);
+	}
+	if (ret < 0) {
+		dev_warn(&state->tuner_i2c.dev,
+			"(%s) failed. [adap%d-fe%d]\n",
+			__func__, fe->dvb->num, fe->id);
+		return ret;
+	}
+
+	/* prefer 'all-layers' to 'none' as a default */
+	if (fe->dtv_property_cache.isdbt_layer_enabled == 0)
+		fe->dtv_property_cache.isdbt_layer_enabled = 7;
+	return tc90522_set_if_agc(fe, true);
+}
+
+
+/*
+ * tuner I2C
+ */
+
+static int
+tc90522_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
+{
+	struct tc90522_state *state;
+	struct i2c_msg *new_msgs;
+	int i, j;
+	int ret, rd_num;
+	u8 wbuf[256];
+	u8 *p, *bufend;
+
+	rd_num = 0;
+	for (i = 0; i < num; i++)
+		if (msgs[i].flags & I2C_M_RD)
+			rd_num++;
+	new_msgs = kmalloc(sizeof(*new_msgs) * (num + rd_num), GFP_KERNEL);
+	if (!new_msgs)
+		return -ENOMEM;
+
+	state = i2c_get_adapdata(adap);
+	p = wbuf;
+	bufend = wbuf + sizeof(wbuf);
+	for (i = 0, j = 0; i < num; i++, j++) {
+		new_msgs[j].addr = state->cfg.addr;
+		new_msgs[j].flags = msgs[i].flags;
+
+		if (msgs[i].flags & I2C_M_RD) {
+			new_msgs[j].flags &= ~I2C_M_RD;
+			if (p + 2 > bufend)
+				break;
+			p[0] = TC90522_I2C_THRU_REG;
+			p[1] = msgs[i].addr << 1 | 0x01;
+			new_msgs[j].buf = p;
+			new_msgs[j].len = 2;
+			p += 2;
+			j++;
+			new_msgs[j].addr = state->cfg.addr;
+			new_msgs[j].flags = msgs[i].flags;
+			new_msgs[j].buf = msgs[i].buf;
+			new_msgs[j].len = msgs[i].len;
+			continue;
+		}
+
+		if (p + msgs[i].len + 2 > bufend)
+			break;
+		p[0] = TC90522_I2C_THRU_REG;
+		p[1] = msgs[i].addr << 1;
+		memcpy(p + 2, msgs[i].buf, msgs[i].len);
+		new_msgs[j].buf = p;
+		new_msgs[j].len = msgs[i].len + 2;
+		p += new_msgs[j].len;
+	}
+	if (i < num)
+		ret = -ENOMEM;
+	else
+		ret = i2c_transfer(state->i2c, new_msgs, j);
+	kfree(new_msgs);
+	return ret;
+}
+
+u32 tc90522_functionality(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C;
+}
+
+static const struct i2c_algorithm tc90522_tuner_i2c_algo = {
+	.master_xfer   = &tc90522_master_xfer,
+	.functionality = &tc90522_functionality,
+};
+
+
+/*
+ * exported functions
+ */
+
+static const struct dvb_frontend_ops tc90522_ops_sat = {
+	.delsys = { SYS_ISDBS },
+	.info = {
+		.name = "Toshiba TC90522 ISDB-S module",
+		.frequency_min =  950000,
+		.frequency_max = 2150000,
+		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_AUTO |
+			FE_CAN_QAM_AUTO | FE_CAN_TRANSMISSION_MODE_AUTO |
+			FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_HIERARCHY_AUTO,
+	},
+
+	.release = tc90522_release,
+	.init = tc90522_init,
+	.sleep = tc90522_sleep,
+	.set_frontend = tc90522_set_frontend,
+	.get_tune_settings = tc90522_get_tune_settings,
+
+	.get_frontend = tc90522s_get_frontend,
+	.read_status = tc90522s_read_status,
+	.set_voltage = tc90522s_set_voltage,
+};
+
+static const struct dvb_frontend_ops tc90522_ops_ter = {
+	.delsys = { SYS_ISDBT },
+	.info = {
+		.name = "Toshiba TC90522 ISDB-T module",
+		.frequency_min = 470000000,
+		.frequency_max = 770000000,
+		.frequency_stepsize = 142857,
+		.caps = FE_CAN_INVERSION_AUTO |
+			FE_CAN_FEC_1_2  | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 |
+			FE_CAN_FEC_5_6  | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO |
+			FE_CAN_QPSK     | FE_CAN_QAM_16 | FE_CAN_QAM_64 |
+			FE_CAN_QAM_AUTO | FE_CAN_TRANSMISSION_MODE_AUTO |
+			FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_RECOVER |
+			FE_CAN_HIERARCHY_AUTO,
+	},
+
+	.release = tc90522_release,
+	.init = tc90522_init,
+	.sleep = tc90522_sleep,
+	.set_frontend = tc90522_set_frontend,
+	.get_tune_settings = tc90522_get_tune_settings,
+
+	.get_frontend = tc90522t_get_frontend,
+	.read_status = tc90522t_read_status,
+	.set_lna = tc90522t_set_lna,
+};
+
+struct dvb_frontend *
+tc90522_attach(const struct tc90522_config *cfg, struct i2c_adapter *i2c)
+{
+	struct tc90522_state *state;
+	const struct dvb_frontend_ops *ops;
+	struct i2c_adapter *adap;
+	int ret;
+
+	state = kzalloc(sizeof(*state), GFP_KERNEL);
+	if (!state)
+		return NULL;
+
+	memcpy(&state->cfg, cfg, sizeof(*cfg));
+	state->i2c = i2c;
+	ops = TC90522_IS_ISDBS(cfg->addr) ? &tc90522_ops_sat : &tc90522_ops_ter;
+	memcpy(&state->dvb_fe.ops, ops, sizeof(*ops));
+	state->dvb_fe.demodulator_priv = state;
+
+	adap = &state->tuner_i2c;
+	adap->owner = i2c->owner;
+	adap->algo = &tc90522_tuner_i2c_algo;
+	adap->dev.parent = i2c->dev.parent;
+	strlcpy(adap->name, "tc90522 tuner", sizeof(adap->name));
+	i2c_set_adapdata(adap, state);
+	ret = i2c_add_adapter(adap);
+	if (ret < 0) {
+		kfree(state);
+		return NULL;
+	}
+	dev_info(&i2c->dev, "Toshiba TC90522 attached.\n");
+	return &state->dvb_fe;
+}
+EXPORT_SYMBOL(tc90522_attach);
+
+
+struct i2c_adapter *tc90522_get_tuner_i2c(struct dvb_frontend *fe)
+{
+	struct tc90522_state *state;
+
+	state = fe->demodulator_priv;
+	return &state->tuner_i2c;
+}
+EXPORT_SYMBOL(tc90522_get_tuner_i2c);
+
+
+MODULE_DESCRIPTION("Toshiba TC90522 frontend");
+MODULE_AUTHOR("Akihiro TSUKADA");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/dvb-frontends/tc90522.h b/drivers/media/dvb-frontends/tc90522.h
new file mode 100644
index 0000000..d55a6be
--- /dev/null
+++ b/drivers/media/dvb-frontends/tc90522.h
@@ -0,0 +1,63 @@
+/*
+ * Toshiba TC90522 Demodulator
+ *
+ * Copyright (C) 2014 Akihiro Tsukada <tskd08@gmail.com>
+ *
+ * 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 version 2.
+ *
+ *
+ * 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.
+ */
+
+/*
+ * The demod has 4 input (2xISDB-T and 2xISDB-S),
+ * and provides independent sub modules for each input.
+ * As the sub modules work in parallel and have the separate i2c addr's,
+ * this driver treats each sub module as one demod device.
+ */
+
+#ifndef TC90522_H
+#define TC90522_H
+
+#include <linux/i2c.h>
+#include <linux/kconfig.h>
+#include "dvb_frontend.h"
+
+#define TC90522_IS_ISDBS(addr) (addr & 1)
+
+#define TC90522T_CMD_SET_LNA 1
+#define TC90522S_CMD_SET_LNB 1
+
+struct tc90522_config {
+	u8 addr;
+};
+
+#if IS_ENABLED(CONFIG_DVB_TC90522)
+
+extern struct dvb_frontend *tc90522_attach(const struct tc90522_config *cfg,
+		struct i2c_adapter *i2c);
+
+extern struct i2c_adapter *tc90522_get_tuner_i2c(struct dvb_frontend *fe);
+
+#else
+static inline struct dvb_frontend *tc90522_attach(
+	const struct tc90522_config *cfg, struct i2c_adapter *i2c)
+{
+	printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__);
+	return NULL;
+}
+
+static inline struct i2c_adapter *tc90522_get_tuner_i2c(struct dvb_frontend *fe)
+{
+	printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__);
+	return NULL;
+}
+
+#endif
+
+#endif /* TC90522_H */
-- 
2.1.0


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

* [PATCH v2 5/5] pt3: add support for Earthsoft PT3 ISDB-S/T receiver card
  2014-08-27 15:29 [PATCH v2 0/5] dvb: Add support for PT3 ISDB-S/T card tskd08
                   ` (3 preceding siblings ...)
  2014-08-27 15:29 ` [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator tskd08
@ 2014-08-27 15:29 ` tskd08
  4 siblings, 0 replies; 40+ messages in thread
From: tskd08 @ 2014-08-27 15:29 UTC (permalink / raw)
  To: linux-media; +Cc: m.chehab

From: Akihiro Tsukada <tskd08@gmail.com>

This patch adds support for PT3 PCIe cards.
PT3 has an FPGA PCIe bridge chip, a TC90522 demod chip and
a VA4M6JC2103 tuner module which contains two QM1D1C0042 chips for ISDB-S
and two MxL301RF's for ISDB-T.
It can receive and deliver 4 (2x ISDB-S, 2x ISDB-T) streams simultaneously,
and a kthread is used per stream to poll incoming data,
because PT3 does not have interrupts.

As an antenna input for each delivery system is split in the tuner module
and shared between the corresponding two tuner chips,
LNB/LNA controls that the FPGA chip provides are (naturally) shared as well.
The tuner chips also share the power line in the tuner module,
which is controlled on/off by a GPIO pin of the demod chip.

As with the demod chip and the ISDB-T tuner chip,
the init sequences/register settings for those chips are not disclosed
and stored in a private memory of the FPGA,
PT3 driver executes the init of those chips on behalf of their drivers.

Signed-off-by: Akihiro Tsukada <tskd08@gmail.com>
---
Changes in v2:
- improved handling of kthread, including support of suspend/resume
- added support of suspend/resume
- improved init sequence to support live (streaming) suspend/resume
- moved static const tables out of function scop

 drivers/media/pci/Kconfig       |   1 +
 drivers/media/pci/Makefile      |   1 +
 drivers/media/pci/pt3/Kconfig   |  10 +
 drivers/media/pci/pt3/Makefile  |   8 +
 drivers/media/pci/pt3/pt3.c     | 817 ++++++++++++++++++++++++++++++++++++++++
 drivers/media/pci/pt3/pt3.h     | 179 +++++++++
 drivers/media/pci/pt3/pt3_dma.c | 225 +++++++++++
 drivers/media/pci/pt3/pt3_i2c.c | 239 ++++++++++++
 8 files changed, 1480 insertions(+)

diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
index 5c16c9c..89bd2a5 100644
--- a/drivers/media/pci/Kconfig
+++ b/drivers/media/pci/Kconfig
@@ -41,6 +41,7 @@ source "drivers/media/pci/b2c2/Kconfig"
 source "drivers/media/pci/pluto2/Kconfig"
 source "drivers/media/pci/dm1105/Kconfig"
 source "drivers/media/pci/pt1/Kconfig"
+source "drivers/media/pci/pt3/Kconfig"
 source "drivers/media/pci/mantis/Kconfig"
 source "drivers/media/pci/ngene/Kconfig"
 source "drivers/media/pci/ddbridge/Kconfig"
diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
index dc2ebbe..9db6775 100644
--- a/drivers/media/pci/Makefile
+++ b/drivers/media/pci/Makefile
@@ -7,6 +7,7 @@ obj-y        +=	ttpci/		\
 		pluto2/		\
 		dm1105/		\
 		pt1/		\
+		pt3/		\
 		mantis/		\
 		ngene/		\
 		ddbridge/	\
diff --git a/drivers/media/pci/pt3/Kconfig b/drivers/media/pci/pt3/Kconfig
new file mode 100644
index 0000000..16c208a
--- /dev/null
+++ b/drivers/media/pci/pt3/Kconfig
@@ -0,0 +1,10 @@
+config DVB_PT3
+	tristate "Earthsoft PT3 cards"
+	depends on DVB_CORE && PCI && I2C
+	select DVB_TC90522 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_QM1D1C0042 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MXL301RF if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Support for Earthsoft PT3 PCIe cards.
+
+	  Say Y or M if you own such a device and want to use it.
diff --git a/drivers/media/pci/pt3/Makefile b/drivers/media/pci/pt3/Makefile
new file mode 100644
index 0000000..396f146
--- /dev/null
+++ b/drivers/media/pci/pt3/Makefile
@@ -0,0 +1,8 @@
+
+earth-pt3-objs += pt3.o pt3_i2c.o pt3_dma.o
+
+obj-$(CONFIG_DVB_PT3) += earth-pt3.o
+
+ccflags-y += -Idrivers/media/dvb-core
+ccflags-y += -Idrivers/media/dvb-frontends
+ccflags-y += -Idrivers/media/tuners
diff --git a/drivers/media/pci/pt3/pt3.c b/drivers/media/pci/pt3/pt3.c
new file mode 100644
index 0000000..902eca5
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3.c
@@ -0,0 +1,817 @@
+/*
+ * Earthsoft PT3 driver
+ *
+ * Copyright (C) 2014 Akihiro Tsukada <tskd08@gmail.com>
+ *
+ * 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 version 2.
+ *
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/freezer.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/string.h>
+
+#include "dmxdev.h"
+#include "dvbdev.h"
+#include "dvb_demux.h"
+#include "dvb_frontend.h"
+
+#include "pt3.h"
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static bool one_adapter;
+module_param(one_adapter, bool, 0444);
+MODULE_PARM_DESC(one_adapter, "Place FE's together under one adapter.");
+
+static int num_bufs = 4;
+module_param(num_bufs, int, 0444);
+MODULE_PARM_DESC(num_bufs, "Number of DMA buffer (188KiB) per FE.");
+
+
+static const struct i2c_algorithm pt3_i2c_algo = {
+	.master_xfer   = &pt3_i2c_master_xfer,
+	.functionality = &pt3_i2c_functionality,
+};
+
+static const struct pt3_adap_config adap_conf[PT3_NUM_FE] = {
+	{
+		.demod_cfg = {
+			.addr = 0x11,
+		},
+		.tuner_cfg.qm1d1c0042 = {
+			.addr = 0x63,
+			.init_freq = 1049480 - 300,
+			.lpf = 1,
+		}
+	},
+	{
+		.demod_cfg = {
+			.addr = 0x10,
+		},
+		.tuner_cfg.mxl301rf = {
+			.addr = 0x62,
+			.init_freq = 515142857,
+		}
+	},
+	{
+		.demod_cfg = {
+			.addr = 0x13,
+		},
+		.tuner_cfg.qm1d1c0042 = {
+			.addr = 0x60,
+			.init_freq = 1049480 + 300,
+			.lpf = 1,
+		}
+	},
+	{
+		.demod_cfg = {
+			.addr = 0x12,
+		},
+		.tuner_cfg.mxl301rf = {
+			.addr = 0x61,
+			.init_freq = 521142857,
+		}
+	},
+};
+
+struct reg_val {
+	u8 reg;
+	u8 val;
+};
+
+
+static int
+pt3_demod_write(struct pt3_adapter *adap, const struct reg_val *data, int num)
+{
+	struct pt3_board *pt3;
+	struct i2c_msg msg;
+	u8 wbuf[2];
+	int i, ret;
+
+	ret = 0;
+	pt3 = adap->dvb_adap.priv;
+	msg.addr = adap_conf[adap->adap_idx].demod_cfg.addr;
+	msg.flags = 0;
+	msg.buf = wbuf;
+	msg.len = 2;
+	for (i = 0; i < num; i++) {
+		wbuf[0] = data[i].reg;
+		wbuf[1] = data[i].val;
+		ret = i2c_transfer(&pt3->i2c_adap, &msg, 1);
+		if (ret < 0)
+			break;
+	}
+	return ret;
+}
+
+static inline void pt3_lnb_ctrl(struct pt3_board *pt3, bool on)
+{
+	iowrite32((on ? 0x0f : 0x0c), pt3->regs[0] + REG_SYSTEM_W);
+}
+
+static inline struct pt3_adapter *pt3_find_adapter(struct dvb_frontend *fe)
+{
+	struct pt3_board *pt3;
+	int i;
+
+	if (one_adapter) {
+		pt3 = fe->dvb->priv;
+		for (i = 0; i < PT3_NUM_FE; i++)
+			if (pt3->adaps[i]->fe == fe)
+				return pt3->adaps[i];
+	}
+	return container_of(fe->dvb, struct pt3_adapter, dvb_adap);
+}
+
+/*
+ * all 4 tuners in PT3 are packaged in a can module (Sharp VA4M6JC2103).
+ * it seems that they share the power lines and Amp power line and
+ * adaps[3] controls those powers.
+ */
+static int
+pt3_set_tuner_power(struct pt3_board *pt3, bool tuner_on, bool amp_on)
+{
+	struct reg_val reg = { 0x1e, 0x99 };
+
+	if (tuner_on)
+		reg.val |= 0x40;
+	if (amp_on)
+		reg.val |= 0x04;
+	return pt3_demod_write(pt3->adaps[PT3_NUM_FE - 1], &reg, 1);
+}
+
+
+static int pt3_fe_callback_s(void *priv, int component, int cmd, int arg);
+static int pt3_fe_callback_t(void *priv, int component, int cmd, int arg);
+
+static const struct reg_val init0_sat[] = {
+	{ 0x03, 0x01 },
+	{ 0x1e, 0x10 },
+};
+static const struct reg_val init0_ter[] = {
+	{ 0x01, 0x40 },
+	{ 0x1c, 0x10 },
+};
+static const struct reg_val cfg_sat[] = {
+	{ 0x1c, 0x15 },
+	{ 0x1f, 0x04 },
+};
+static const struct reg_val cfg_ter[] = {
+	{ 0x1d, 0x01 },
+};
+
+/*
+ * pt3_fe_init: initialize demod sub modules and ISDB-T tuners all at once.
+ *
+ * As for demod IC (TC90522) and ISDB-T tuners (MxL301RF),
+ * the i2c sequences for init'ing them are not public and hidden in a ROM,
+ * and include the board specific configurations as well.
+ * They are stored in a lump and cannot be taken out / accessed separately,
+ * thus cannot be moved to the FE/tuner driver.
+ */
+static int pt3_fe_init(struct pt3_board *pt3)
+{
+	int i, ret;
+	struct dvb_frontend *fe;
+	u32 f;
+
+	pt3_i2c_reset(pt3);
+	ret = pt3_init_all_demods(pt3);
+	if (ret < 0)
+		goto failed;
+
+	/* additional config? */
+	for (i = 0; i < PT3_NUM_FE; i++) {
+		fe = pt3->adaps[i]->fe;
+		/* temporary disable the callback */
+		fe->callback = NULL;
+
+		if (pt3->adaps[i]->fe->ops.delsys[0] == SYS_ISDBS)
+			ret = pt3_demod_write(pt3->adaps[i], init0_sat,
+						ARRAY_SIZE(init0_sat));
+		else
+			ret = pt3_demod_write(pt3->adaps[i], init0_ter,
+						ARRAY_SIZE(init0_ter));
+		if (ret < 0)
+			goto failed;
+		fe->ops.init(fe);
+	}
+
+	usleep_range(2000, 4000);
+	ret = pt3_set_tuner_power(pt3, true, false);
+	if (ret < 0)
+		goto failed;
+
+	/* output pin configuration */
+	for (i = 0; i < PT3_NUM_FE; i++) {
+		fe = pt3->adaps[i]->fe;
+		if (pt3->adaps[i]->fe->ops.delsys[0] == SYS_ISDBS)
+			ret = pt3_demod_write(pt3->adaps[i], cfg_sat,
+						ARRAY_SIZE(cfg_sat));
+		else
+			ret = pt3_demod_write(pt3->adaps[i], cfg_ter,
+						ARRAY_SIZE(cfg_ter));
+		if (ret < 0)
+			goto failed;
+	}
+	usleep_range(4000, 6000);
+
+	for (i = 0; i < PT3_NUM_FE; i++) {
+		fe = pt3->adaps[i]->fe;
+		if (fe->ops.delsys[0] != SYS_ISDBS)
+			continue;
+		/* init and wake-up ISDB-S tuners */
+		ret = fe->ops.tuner_ops.init(fe);
+		if (ret < 0)
+			goto failed;
+	}
+	ret = pt3_init_all_mxl301rf(pt3);
+	if (ret < 0)
+		goto failed;
+
+	ret = pt3_set_tuner_power(pt3, true, true);
+	if (ret < 0)
+		goto failed;
+
+	/* Wake up all tuners and/or make an initial tuning,
+	 * in order to avoid interference among the tuners in the module,
+	 * according to the doc from the manufacturer.
+	 */
+	for (i = 0; i < PT3_NUM_FE; i++) {
+		fe = pt3->adaps[i]->fe;
+		if (fe->ops.delsys[0] == SYS_ISDBS) {
+			f = fe->dtv_property_cache.frequency;
+			fe->dtv_property_cache.frequency =
+				adap_conf[i].tuner_cfg.qm1d1c0042.init_freq;
+			ret = fe->ops.tuner_ops.set_params(fe);
+			fe->dtv_property_cache.frequency = f;
+		} else
+			ret = fe->ops.tuner_ops.init(fe);
+		if (ret < 0)
+			goto failed;
+	}
+
+	/* and sleep again, waiting to be opened by users. */
+	for (i = 0; i < PT3_NUM_FE; i++) {
+		fe = pt3->adaps[i]->fe;
+		if (fe->ops.tuner_ops.sleep)
+			ret = fe->ops.tuner_ops.sleep(fe);
+		if (ret < 0)
+			goto failed;
+		if (fe->ops.sleep)
+			ret = fe->ops.sleep(fe);
+		if (ret < 0)
+			goto failed;
+		fe->callback = (fe->ops.delsys[0] == SYS_ISDBS) ?
+				pt3_fe_callback_s : pt3_fe_callback_t;
+	}
+	return ret;
+
+failed:
+	dev_warn(&pt3->pdev->dev, "Failed to init the tuner module.\n");
+	return ret;
+}
+
+/*  callback functions to control the shared resources:
+ *      LNB, tuner CAN module's power and LNA.
+ */
+static int pt3_fe_callback_t(void *priv, int component, int cmd, int arg)
+{
+	struct pt3_adapter *adap;
+	struct pt3_board *pt3;
+	int ret;
+
+	adap = pt3_find_adapter(priv);
+	switch (component) {
+	case DVB_FRONTEND_COMPONENT_DEMOD:
+		switch (cmd) {
+		case TC90522T_CMD_SET_LNA:
+			/* LNA is shared btw. 2 TERR-tuners */
+			pt3 = adap->dvb_adap.priv;
+			if (mutex_lock_interruptible(&pt3->lock))
+				return -ERESTARTSYS;
+			if (arg)
+				pt3->lna_on_cnt++;
+			else
+				pt3->lna_on_cnt--;
+
+			if (arg && pt3->lna_on_cnt <= 1) {
+				pt3->lna_on_cnt = 1;
+				ret = pt3_set_tuner_power(pt3, true, true);
+			} else if (!arg && pt3->lna_on_cnt <= 0) {
+				pt3->lna_on_cnt = 0;
+				ret = pt3_set_tuner_power(pt3, true, false);
+			} else
+				ret = 0;
+			mutex_unlock(&pt3->lock);
+			return ret;
+		}
+		break;
+	}
+	return 0;
+}
+
+static int pt3_fe_callback_s(void *priv, int component, int cmd, int arg)
+{
+	struct pt3_adapter *adap;
+	struct pt3_board *pt3;
+	bool on;
+
+	adap = pt3_find_adapter(priv);
+	switch (component) {
+	case DVB_FRONTEND_COMPONENT_DEMOD:
+		switch (cmd) {
+		case TC90522S_CMD_SET_LNB:
+			/* LNB power is shared btw. 2 SAT-tuners */
+			on = (arg != SEC_VOLTAGE_OFF);
+			if (on == adap->cur_lnb)
+				return 0;
+			adap->cur_lnb = on;
+			pt3 = adap->dvb_adap.priv;
+			if (mutex_lock_interruptible(&pt3->lock))
+				return -ERESTARTSYS;
+			if (on)
+				pt3->lnb_on_cnt++;
+			else
+				pt3->lnb_on_cnt--;
+
+			if (on && pt3->lnb_on_cnt <= 1) {
+				pt3->lnb_on_cnt = 1;
+				pt3_lnb_ctrl(pt3, true);
+			} else if (!on && pt3->lnb_on_cnt <= 0) {
+				pt3->lnb_on_cnt = 0;
+				pt3_lnb_ctrl(pt3, false);
+			}
+			mutex_unlock(&pt3->lock);
+			return 0;
+		}
+		break;
+	}
+	return 0;
+}
+
+
+static int pt3_attach_fe(struct pt3_board *pt3, struct i2c_adapter *i2c, int i)
+{
+	struct dvb_frontend *fe;
+	struct i2c_adapter *tuner_i2c;
+	struct dvb_adapter *dvb_adap;
+	void *ret;
+
+	fe = dvb_attach(tc90522_attach, &adap_conf[i].demod_cfg, i2c);
+	if (!fe)
+		return -ENOMEM;
+	pt3->adaps[i]->fe = fe;
+
+	tuner_i2c = tc90522_get_tuner_i2c(fe);
+	if (TC90522_IS_ISDBS(adap_conf[i].demod_cfg.addr)) {
+		fe->callback = &pt3_fe_callback_s;
+		ret = dvb_attach(qm1d1c0042_attach, fe, tuner_i2c,
+				&adap_conf[i].tuner_cfg.qm1d1c0042);
+	} else {
+		fe->callback = &pt3_fe_callback_t;
+		ret = dvb_attach(mxl301rf_attach, fe, tuner_i2c,
+				&adap_conf[i].tuner_cfg.mxl301rf);
+	}
+	if (!ret)
+		return -ENOMEM;
+
+	dvb_adap = &pt3->adaps[one_adapter ? 0 : i]->dvb_adap;
+	return dvb_register_frontend(dvb_adap, fe);
+}
+
+
+static int pt3_fetch_thread(void *data)
+{
+	struct pt3_adapter *adap = data;
+	ktime_t delay;
+	bool was_frozen;
+
+#define PT3_INITIAL_BUF_DROPS 4
+#define PT3_FETCH_DELAY 10
+#define PT3_FETCH_DELAY_DELTA 2
+
+	pt3_init_dmabuf(adap);
+	adap->num_discard = PT3_INITIAL_BUF_DROPS;
+
+	dev_dbg(adap->dvb_adap.device,
+		"PT3: [%s] started.\n", adap->thread->comm);
+	set_freezable();
+	while (!kthread_freezable_should_stop(&was_frozen)) {
+		if (was_frozen)
+			adap->num_discard = PT3_INITIAL_BUF_DROPS;
+
+		pt3_proc_dma(adap);
+
+		delay = ktime_set(0, PT3_FETCH_DELAY * NSEC_PER_MSEC);
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		freezable_schedule_hrtimeout_range(&delay,
+					PT3_FETCH_DELAY_DELTA * NSEC_PER_MSEC,
+					HRTIMER_MODE_REL);
+	}
+	dev_dbg(adap->dvb_adap.device,
+		"PT3: [%s] exited.\n", adap->thread->comm);
+	adap->thread = NULL;
+	return 0;
+}
+
+static int pt3_start_streaming(struct pt3_adapter *adap)
+{
+	struct task_struct *thread;
+
+	/* start fetching thread */
+	thread = kthread_run(pt3_fetch_thread, adap, "pt3-ad%i-dmx%i",
+				adap->dvb_adap.num, adap->dmxdev.dvbdev->id);
+	if (IS_ERR(thread)) {
+		int ret = PTR_ERR(thread);
+
+		dev_warn(adap->dvb_adap.device,
+			"PT3 (adap:%d, dmx:%d): failed to start kthread.\n",
+			adap->dvb_adap.num, adap->dmxdev.dvbdev->id);
+		return ret;
+	}
+	adap->thread = thread;
+
+	return pt3_start_dma(adap);
+}
+
+static int pt3_stop_streaming(struct pt3_adapter *adap)
+{
+	int ret;
+
+	ret = pt3_stop_dma(adap);
+	if (ret)
+		dev_warn(adap->dvb_adap.device,
+			"PT3: failed to stop streaming of adap:%d/FE:%d\n",
+			adap->dvb_adap.num, adap->fe->id);
+
+	/* kill the fetching thread */
+	ret = kthread_stop(adap->thread);
+	return ret;
+}
+
+static int pt3_start_feed(struct dvb_demux_feed *feed)
+{
+	struct pt3_adapter *adap;
+
+	if (signal_pending(current))
+		return -EINTR;
+
+	adap = container_of(feed->demux, struct pt3_adapter, demux);
+	adap->num_feeds++;
+	if (adap->thread)
+		return 0;
+	if (adap->num_feeds != 1) {
+		dev_warn(adap->dvb_adap.device,
+			"%s: unmatched start/stop_feed in adap:%i/dmx:%i.\n",
+			__func__, adap->dvb_adap.num, adap->dmxdev.dvbdev->id);
+		adap->num_feeds = 1;
+	}
+
+	return pt3_start_streaming(adap);
+
+}
+
+static int pt3_stop_feed(struct dvb_demux_feed *feed)
+{
+	struct pt3_adapter *adap;
+
+	adap = container_of(feed->demux, struct pt3_adapter, demux);
+
+	adap->num_feeds--;
+	if (adap->num_feeds > 0 || !adap->thread)
+		return 0;
+	adap->num_feeds = 0;
+
+	return pt3_stop_streaming(adap);
+}
+
+
+static int pt3_alloc_adapter(struct pt3_board *pt3, int index)
+{
+	int ret;
+	struct pt3_adapter *adap;
+	struct dvb_adapter *da;
+
+	adap = kzalloc(sizeof(*adap), GFP_KERNEL);
+	if (!adap) {
+		dev_err(&pt3->pdev->dev, "failed to alloc mem for adapter.\n");
+		return -ENOMEM;
+	}
+	pt3->adaps[index] = adap;
+	adap->adap_idx = index;
+
+	if (index == 0 || !one_adapter) {
+		ret = dvb_register_adapter(&adap->dvb_adap, "PT3 DVB",
+				THIS_MODULE, &pt3->pdev->dev, adapter_nr);
+		if (ret < 0) {
+			dev_err(&pt3->pdev->dev,
+				"failed to register adapter dev.\n");
+			goto err_mem;
+		}
+		da = &adap->dvb_adap;
+	} else
+		da = &pt3->adaps[0]->dvb_adap;
+
+	adap->dvb_adap.priv = pt3;
+	adap->demux.dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING;
+	adap->demux.priv = adap;
+	adap->demux.feednum = 256;
+	adap->demux.filternum = 256;
+	adap->demux.start_feed = pt3_start_feed;
+	adap->demux.stop_feed = pt3_stop_feed;
+	ret = dvb_dmx_init(&adap->demux);
+	if (ret < 0) {
+		dev_err(&pt3->pdev->dev, "failed to init dmx dev.\n");
+		goto err_adap;
+	}
+
+	adap->dmxdev.filternum = 256;
+	adap->dmxdev.demux = &adap->demux.dmx;
+	ret = dvb_dmxdev_init(&adap->dmxdev, da);
+	if (ret < 0) {
+		dev_err(&pt3->pdev->dev, "failed to init dmxdev.\n");
+		goto err_demux;
+	}
+
+	ret = pt3_alloc_dmabuf(adap);
+	if (ret) {
+		dev_err(&pt3->pdev->dev, "failed to alloc DMA buffers.\n");
+		goto err_dmabuf;
+	}
+
+	return 0;
+
+err_dmabuf:
+	pt3_free_dmabuf(adap);
+	dvb_dmxdev_release(&adap->dmxdev);
+err_demux:
+	dvb_dmx_release(&adap->demux);
+err_adap:
+	if (index == 0 || !one_adapter)
+		dvb_unregister_adapter(da);
+err_mem:
+	kfree(adap);
+	pt3->adaps[index] = NULL;
+	return ret;
+}
+
+static void pt3_cleanup_adapter(struct pt3_board *pt3, int index)
+{
+	struct pt3_adapter *adap;
+	struct dmx_demux *dmx;
+
+	adap = pt3->adaps[index];
+	if (adap == NULL)
+		return;
+
+	/* stop demux kthread */
+	if (adap->thread)
+		pt3_stop_streaming(adap);
+
+	dmx = &adap->demux.dmx;
+	dmx->close(dmx);
+	if (adap->fe) {
+		adap->fe->callback = NULL;
+		if (adap->fe->frontend_priv)
+			dvb_unregister_frontend(adap->fe);
+		dvb_frontend_detach(adap->fe);
+	}
+	pt3_free_dmabuf(adap);
+	dvb_dmxdev_release(&adap->dmxdev);
+	dvb_dmx_release(&adap->demux);
+	if (index == 0 || !one_adapter)
+		dvb_unregister_adapter(&adap->dvb_adap);
+	kfree(adap);
+	pt3->adaps[index] = NULL;
+}
+
+static int pt3_suspend(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+	struct pt3_board *pt3 = pci_get_drvdata(pdev);
+	int i;
+	struct pt3_adapter *adap;
+
+	for (i = 0; i < PT3_NUM_FE; i++) {
+		adap = pt3->adaps[i];
+		if (adap->num_feeds > 0)
+			pt3_stop_dma(adap);
+		dvb_frontend_suspend(adap->fe);
+		pt3_free_dmabuf(adap);
+	}
+
+	pt3_lnb_ctrl(pt3, false);
+	pt3_set_tuner_power(pt3, false, false);
+	return 0;
+}
+
+static int pt3_resume(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+	struct pt3_board *pt3 = pci_get_drvdata(pdev);
+	int i, ret;
+	struct pt3_adapter *adap;
+
+	ret = pt3_fe_init(pt3);
+	if (ret)
+		return ret;
+
+	if (pt3->lna_on_cnt > 0)
+		pt3_set_tuner_power(pt3, true, true);
+	if (pt3->lnb_on_cnt > 0)
+		pt3_lnb_ctrl(pt3, true);
+
+	for (i = 0; i < PT3_NUM_FE; i++) {
+		adap = pt3->adaps[i];
+		dvb_frontend_resume(adap->fe);
+		ret = pt3_alloc_dmabuf(adap);
+		if (ret) {
+			dev_err(&pt3->pdev->dev, "failed to alloc DMA bufs.\n");
+			continue;
+		}
+		if (adap->num_feeds > 0)
+			pt3_start_dma(adap);
+	}
+
+	return 0;
+}
+
+static void pt3_remove(struct pci_dev *pdev)
+{
+	struct pt3_board *pt3;
+	int i;
+
+	pt3 = pci_get_drvdata(pdev);
+	for (i = PT3_NUM_FE - 1; i >= 0; i--)
+		pt3_cleanup_adapter(pt3, i);
+	i2c_del_adapter(&pt3->i2c_adap);
+	kfree(pt3->i2c_buf);
+	pci_iounmap(pt3->pdev, pt3->regs[0]);
+	pci_iounmap(pt3->pdev, pt3->regs[1]);
+	pci_release_regions(pdev);
+	pci_disable_device(pdev);
+	kfree(pt3);
+}
+
+static int pt3_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+	u8 rev;
+	u32 ver;
+	int i, ret;
+	struct pt3_board *pt3;
+	struct i2c_adapter *i2c;
+
+	if (pci_read_config_byte(pdev, PCI_REVISION_ID, &rev) || rev != 1)
+		return -ENODEV;
+
+	ret = pci_enable_device(pdev);
+	if (ret < 0)
+		return -ENODEV;
+	pci_set_master(pdev);
+
+	ret = pci_request_regions(pdev, DRV_NAME);
+	if (ret < 0)
+		goto err_disable_device;
+
+	ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64));
+	if (ret == 0)
+		dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64));
+	else {
+		ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32));
+		if (ret == 0)
+			dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
+		else {
+			dev_err(&pdev->dev, "Failed to set DMA mask.\n");
+			goto err_release_regions;
+		}
+		dev_info(&pdev->dev, "Use 32bit DMA.\n");
+	}
+
+	pt3 = kzalloc(sizeof(*pt3), GFP_KERNEL);
+	if (!pt3) {
+		dev_err(&pdev->dev, "Failed to alloc mem for this dev.\n");
+		ret = -ENOMEM;
+		goto err_release_regions;
+	}
+	pci_set_drvdata(pdev, pt3);
+	pt3->pdev = pdev;
+	mutex_init(&pt3->lock);
+	pt3->regs[0] = pci_ioremap_bar(pdev, 0);
+	pt3->regs[1] = pci_ioremap_bar(pdev, 2);
+	if (pt3->regs[0] == NULL || pt3->regs[1] == NULL) {
+		dev_err(&pdev->dev, "Failed to ioremap.\n");
+		ret = -ENOMEM;
+		goto err_kfree;
+	}
+
+	ver = ioread32(pt3->regs[0] + REG_VERSION);
+	if ((ver >> 16) != 0x0301) {
+		dev_warn(&pdev->dev, "PT%d, I/F-ver.:%d not supported",
+			ver >> 24, (ver & 0x00ff0000) >> 16);
+		ret = -ENODEV;
+		goto err_iounmap;
+	}
+
+	pt3->num_bufs = clamp_val(num_bufs, MIN_DATA_BUFS, MAX_DATA_BUFS);
+
+	pt3->i2c_buf = kmalloc(sizeof(*pt3->i2c_buf), GFP_KERNEL);
+	if (pt3->i2c_buf == NULL) {
+		dev_err(&pdev->dev, "Failed to alloc mem for i2c.\n");
+		ret = -ENOMEM;
+		goto err_iounmap;
+	}
+	i2c = &pt3->i2c_adap;
+	i2c->owner = THIS_MODULE;
+	i2c->algo = &pt3_i2c_algo;
+	i2c->algo_data = NULL;
+	i2c->dev.parent = &pdev->dev;
+	strlcpy(i2c->name, DRV_NAME, sizeof(i2c->name));
+	i2c_set_adapdata(i2c, pt3);
+	ret = i2c_add_adapter(i2c);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Failed to add i2c adapter.\n");
+		goto err_i2cbuf;
+	}
+
+	for (i = 0; i < PT3_NUM_FE; i++) {
+		ret = pt3_alloc_adapter(pt3, i);
+		if (ret < 0)
+			break;
+
+		ret = pt3_attach_fe(pt3, i2c, i);
+		if (ret < 0)
+			break;
+	}
+	if (i < PT3_NUM_FE) {
+		dev_err(&pdev->dev, "Failed to create FE%d.\n", i);
+		goto err_cleanup_adapters;
+	}
+
+	ret = pt3_fe_init(pt3);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Failed to init frontends.\n");
+		i = PT3_NUM_FE - 1;
+		goto err_cleanup_adapters;
+	}
+
+	dev_info(&pdev->dev,
+		"successfully init'ed PT%d (fw:0x%02x, I/F:0x%02x).\n",
+		ver >> 24, (ver >> 8) & 0xff, (ver >> 16) & 0xff);
+	return 0;
+
+err_cleanup_adapters:
+	while (i)
+		pt3_cleanup_adapter(pt3, i--);
+	i2c_del_adapter(i2c);
+err_i2cbuf:
+	kfree(pt3->i2c_buf);
+err_iounmap:
+	if (pt3->regs[0])
+		pci_iounmap(pdev, pt3->regs[0]);
+	if (pt3->regs[1])
+		pci_iounmap(pdev, pt3->regs[1]);
+err_kfree:
+	kfree(pt3);
+err_release_regions:
+	pci_release_regions(pdev);
+err_disable_device:
+	pci_disable_device(pdev);
+	return ret;
+
+}
+
+static const struct pci_device_id pt3_id_table[] = {
+	{ PCI_DEVICE_SUB(0x1172, 0x4c15, 0xee8d, 0x0368) },
+	{ },
+};
+MODULE_DEVICE_TABLE(pci, pt3_id_table);
+
+SIMPLE_DEV_PM_OPS(pt3_pm_ops, pt3_suspend, pt3_resume);
+
+static struct pci_driver pt3_driver = {
+	.name		= DRV_NAME,
+	.probe		= pt3_probe,
+	.remove		= pt3_remove,
+	.id_table	= pt3_id_table,
+
+	.driver.pm	= &pt3_pm_ops,
+};
+
+module_pci_driver(pt3_driver);
+
+MODULE_DESCRIPTION("Earthsoft PT3 Driver");
+MODULE_AUTHOR("Akihiro TSUKADA");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/pci/pt3/pt3.h b/drivers/media/pci/pt3/pt3.h
new file mode 100644
index 0000000..cd7821d
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3.h
@@ -0,0 +1,179 @@
+/*
+ * Earthsoft PT3 driver
+ *
+ * Copyright (C) 2014 Akihiro Tsukada <tskd08@gmail.com>
+ *
+ * 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 version 2.
+ *
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef PT3_H
+#define PT3_H
+
+#include <linux/atomic.h>
+#include <linux/types.h>
+
+#include "dvb_demux.h"
+#include "dvb_frontend.h"
+#include "dmxdev.h"
+
+#include "tc90522.h"
+#include "mxl301rf.h"
+#include "qm1d1c0042.h"
+
+#define DRV_NAME KBUILD_MODNAME
+
+#define PT3_NUM_FE 4
+
+/*
+ * register index of the FPGA chip
+ */
+#define REG_VERSION	0x00
+#define REG_BUS		0x04
+#define REG_SYSTEM_W	0x08
+#define REG_SYSTEM_R	0x0c
+#define REG_I2C_W	0x10
+#define REG_I2C_R	0x14
+#define REG_RAM_W	0x18
+#define REG_RAM_R	0x1c
+#define REG_DMA_BASE	0x40	/* regs for FE[i] = REG_DMA_BASE + 0x18 * i */
+#define OFST_DMA_DESC_L	0x00
+#define OFST_DMA_DESC_H	0x04
+#define OFST_DMA_CTL	0x08
+#define OFST_TS_CTL	0x0c
+#define OFST_STATUS	0x10
+#define OFST_TS_ERR	0x14
+
+/*
+ * internal buffer for I2C
+ */
+#define PT3_I2C_MAX 4091
+struct pt3_i2cbuf {
+	u8  data[PT3_I2C_MAX];
+	u8  tmp;
+	u32 num_cmds;
+};
+
+/*
+ * DMA things
+ */
+#define TS_PACKET_SZ  188
+/* DMA transfers must not cross 4GiB, so use one page / transfer */
+#define DATA_XFER_SZ   4096
+#define DATA_BUF_XFERS 47
+/* (num_bufs * DATA_BUF_SZ) % TS_PACKET_SZ must be 0 */
+#define DATA_BUF_SZ    (DATA_BUF_XFERS * DATA_XFER_SZ)
+#define MAX_DATA_BUFS  16
+#define MIN_DATA_BUFS   2
+
+#define DESCS_IN_PAGE (PAGE_SIZE / sizeof(struct xfer_desc))
+#define MAX_NUM_XFERS (MAX_DATA_BUFS * DATA_BUF_XFERS)
+#define MAX_DESC_BUFS DIV_ROUND_UP(MAX_NUM_XFERS, DESCS_IN_PAGE)
+
+/* DMA transfer description.
+ * device is passed a pointer to this struct, dma-reads it,
+ * and gets the DMA buffer ring for storing TS data.
+ */
+struct xfer_desc {
+	u32 addr_l; /* bus address of target data buffer */
+	u32 addr_h;
+	u32 size;
+	u32 next_l; /* bus adddress of the next xfer_desc */
+	u32 next_h;
+};
+
+/* A DMA mapping of a page containing xfer_desc's */
+struct xfer_desc_buffer {
+	dma_addr_t b_addr;
+	struct xfer_desc *descs; /* PAGE_SIZE (xfer_desc[DESCS_IN_PAGE]) */
+};
+
+/* A DMA mapping of a data buffer */
+struct dma_data_buffer {
+	dma_addr_t b_addr;
+	u8 *data; /* size: u8[PAGE_SIZE] */
+};
+
+/*
+ * device things
+ */
+struct pt3_adap_config {
+	struct tc90522_config demod_cfg;
+	union tuner_config {
+		struct qm1d1c0042_config qm1d1c0042;
+		struct mxl301rf_config   mxl301rf;
+	} tuner_cfg;
+};
+
+struct pt3_adapter {
+	struct dvb_adapter  dvb_adap;  /* dvb_adap.priv => struct pt3_board */
+	int adap_idx;
+
+	struct dvb_demux    demux;
+	struct dmxdev       dmxdev;
+	struct dvb_frontend *fe;
+
+	/* data fetch thread */
+	struct task_struct *thread;
+	int num_feeds;
+
+	bool cur_lnb; /* current LNB power status (on/off) */
+
+	/* items below are for DMA */
+	struct dma_data_buffer buffer[MAX_DATA_BUFS];
+	int buf_idx;
+	int buf_ofs;
+	int num_bufs;  /* == pt3_board->num_bufs */
+	int num_discard; /* how many access units to discard initially */
+
+	struct xfer_desc_buffer desc_buf[MAX_DESC_BUFS];
+	int num_desc_bufs;  /* == num_bufs * DATA_BUF_XFERS / DESCS_IN_PAGE */
+};
+
+
+struct pt3_board {
+	struct pci_dev *pdev;
+	void __iomem *regs[2];
+	/* regs[0]: registers, regs[1]: internal memory, used for I2C */
+
+	struct mutex lock;
+
+	/* LNB power shared among sat-FEs */
+	int lnb_on_cnt; /* LNB power on count */
+
+	/* LNA shared among terr-FEs */
+	int lna_on_cnt; /* booster enabled count */
+
+	int num_bufs;  /* number of DMA buffers allocated/mapped per FE */
+
+	struct i2c_adapter i2c_adap;
+	struct pt3_i2cbuf *i2c_buf;
+
+	struct pt3_adapter *adaps[PT3_NUM_FE];
+};
+
+
+/*
+ * prototypes
+ */
+extern int  pt3_alloc_dmabuf(struct pt3_adapter *adap);
+extern void pt3_init_dmabuf(struct pt3_adapter *adap);
+extern void pt3_free_dmabuf(struct pt3_adapter *adap);
+extern int  pt3_start_dma(struct pt3_adapter *adap);
+extern int  pt3_stop_dma(struct pt3_adapter *adap);
+extern int  pt3_proc_dma(struct pt3_adapter *adap);
+
+extern int  pt3_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
+	int num);
+extern u32  pt3_i2c_functionality(struct i2c_adapter *adap);
+extern void pt3_i2c_reset(struct pt3_board *pt3);
+extern int  pt3_init_all_demods(struct pt3_board *pt3);
+extern int  pt3_init_all_mxl301rf(struct pt3_board *pt3);
+#endif /* PT3_H */
diff --git a/drivers/media/pci/pt3/pt3_dma.c b/drivers/media/pci/pt3/pt3_dma.c
new file mode 100644
index 0000000..f0ce904
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_dma.c
@@ -0,0 +1,225 @@
+/*
+ * Earthsoft PT3 driver
+ *
+ * Copyright (C) 2014 Akihiro Tsukada <tskd08@gmail.com>
+ *
+ * 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 version 2.
+ *
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include <linux/dma-mapping.h>
+#include <linux/kernel.h>
+#include <linux/pci.h>
+
+#include "pt3.h"
+
+#define PT3_ACCESS_UNIT (TS_PACKET_SZ * 128)
+#define PT3_BUF_CANARY  (0x74)
+
+static u32 get_dma_base(int idx)
+{
+	int i;
+
+	i = (idx == 1 || idx == 2) ? 3 - idx : idx;
+	return REG_DMA_BASE + 0x18 * i;
+}
+
+int pt3_stop_dma(struct pt3_adapter *adap)
+{
+	struct pt3_board *pt3 = adap->dvb_adap.priv;
+	u32 base;
+	u32 stat;
+	int retry;
+
+	base = get_dma_base(adap->adap_idx);
+	stat = ioread32(pt3->regs[0] + base + OFST_STATUS);
+	if (!(stat & 0x01))
+		return 0;
+
+	iowrite32(0x02, pt3->regs[0] + base + OFST_DMA_CTL);
+	for (retry = 0; retry < 5; retry++) {
+		stat = ioread32(pt3->regs[0] + base + OFST_STATUS);
+		if (!(stat & 0x01))
+			return 0;
+		msleep(50);
+	}
+	return -EIO;
+}
+
+int pt3_start_dma(struct pt3_adapter *adap)
+{
+	struct pt3_board *pt3 = adap->dvb_adap.priv;
+	u32 base = get_dma_base(adap->adap_idx);
+
+	iowrite32(0x02, pt3->regs[0] + base + OFST_DMA_CTL);
+	iowrite32(lower_32_bits(adap->desc_buf[0].b_addr),
+			pt3->regs[0] + base + OFST_DMA_DESC_L);
+	iowrite32(upper_32_bits(adap->desc_buf[0].b_addr),
+			pt3->regs[0] + base + OFST_DMA_DESC_H);
+	iowrite32(0x01, pt3->regs[0] + base + OFST_DMA_CTL);
+	return 0;
+}
+
+
+static u8 *next_unit(struct pt3_adapter *adap, int *idx, int *ofs)
+{
+	*ofs += PT3_ACCESS_UNIT;
+	if (*ofs >= DATA_BUF_SZ) {
+		*ofs -= DATA_BUF_SZ;
+		(*idx)++;
+		if (*idx == adap->num_bufs)
+			*idx = 0;
+	}
+	return &adap->buffer[*idx].data[*ofs];
+}
+
+int pt3_proc_dma(struct pt3_adapter *adap)
+{
+	int idx, ofs;
+
+	idx = adap->buf_idx;
+	ofs = adap->buf_ofs;
+
+	if (adap->buffer[idx].data[ofs] == PT3_BUF_CANARY)
+		return 0;
+
+	while (*next_unit(adap, &idx, &ofs) != PT3_BUF_CANARY) {
+		u8 *p;
+
+		p = &adap->buffer[adap->buf_idx].data[adap->buf_ofs];
+		if (adap->num_discard > 0)
+			adap->num_discard--;
+		else if (adap->buf_ofs + PT3_ACCESS_UNIT > DATA_BUF_SZ) {
+			dvb_dmx_swfilter_packets(&adap->demux, p,
+				(DATA_BUF_SZ - adap->buf_ofs) / TS_PACKET_SZ);
+			dvb_dmx_swfilter_packets(&adap->demux,
+				adap->buffer[idx].data, ofs / TS_PACKET_SZ);
+		} else
+			dvb_dmx_swfilter_packets(&adap->demux, p,
+				PT3_ACCESS_UNIT / TS_PACKET_SZ);
+
+		*p = PT3_BUF_CANARY;
+		adap->buf_idx = idx;
+		adap->buf_ofs = ofs;
+	}
+	return 0;
+}
+
+void pt3_init_dmabuf(struct pt3_adapter *adap)
+{
+	int idx, ofs;
+	u8 *p;
+
+	idx = 0;
+	ofs = 0;
+	p = adap->buffer[0].data;
+	/* mark the whole buffers as "not written yet" */
+	while (idx < adap->num_bufs) {
+		p[ofs] = PT3_BUF_CANARY;
+		ofs += PT3_ACCESS_UNIT;
+		if (ofs >= DATA_BUF_SZ) {
+			ofs -= DATA_BUF_SZ;
+			idx++;
+			p = adap->buffer[idx].data;
+		}
+	}
+	adap->buf_idx = 0;
+	adap->buf_ofs = 0;
+}
+
+void pt3_free_dmabuf(struct pt3_adapter *adap)
+{
+	struct pt3_board *pt3;
+	int i;
+
+	pt3 = adap->dvb_adap.priv;
+	for (i = 0; i < adap->num_bufs; i++)
+		dma_free_coherent(&pt3->pdev->dev, DATA_BUF_SZ,
+			adap->buffer[i].data, adap->buffer[i].b_addr);
+	adap->num_bufs = 0;
+
+	for (i = 0; i < adap->num_desc_bufs; i++)
+		dma_free_coherent(&pt3->pdev->dev, PAGE_SIZE,
+			adap->desc_buf[i].descs, adap->desc_buf[i].b_addr);
+	adap->num_desc_bufs = 0;
+}
+
+
+int pt3_alloc_dmabuf(struct pt3_adapter *adap)
+{
+	struct pt3_board *pt3;
+	void *p;
+	int i, j;
+	int idx, ofs;
+	int num_desc_bufs;
+	dma_addr_t data_addr, desc_addr;
+	struct xfer_desc *d;
+
+	pt3 = adap->dvb_adap.priv;
+	adap->num_bufs = 0;
+	adap->num_desc_bufs = 0;
+	for (i = 0; i < pt3->num_bufs; i++) {
+		p = dma_alloc_coherent(&pt3->pdev->dev, DATA_BUF_SZ,
+					&adap->buffer[i].b_addr, GFP_KERNEL);
+		if (p == NULL)
+			goto failed;
+		adap->buffer[i].data = p;
+		adap->num_bufs++;
+	}
+	pt3_init_dmabuf(adap);
+
+	/* build circular-linked pointers (xfer_desc) to the data buffers*/
+	idx = 0;
+	ofs = 0;
+	num_desc_bufs =
+		DIV_ROUND_UP(adap->num_bufs * DATA_BUF_XFERS, DESCS_IN_PAGE);
+	for (i = 0; i < num_desc_bufs; i++) {
+		p = dma_alloc_coherent(&pt3->pdev->dev, PAGE_SIZE,
+					&desc_addr, GFP_KERNEL);
+		if (p == NULL)
+			goto failed;
+		adap->num_desc_bufs++;
+		adap->desc_buf[i].descs = p;
+		adap->desc_buf[i].b_addr = desc_addr;
+
+		if (i > 0) {
+			d = &adap->desc_buf[i - 1].descs[DESCS_IN_PAGE - 1];
+			d->next_l = lower_32_bits(desc_addr);
+			d->next_h = upper_32_bits(desc_addr);
+		}
+		for (j = 0; j < DESCS_IN_PAGE; j++) {
+			data_addr = adap->buffer[idx].b_addr + ofs;
+			d = &adap->desc_buf[i].descs[j];
+			d->addr_l = lower_32_bits(data_addr);
+			d->addr_h = upper_32_bits(data_addr);
+			d->size = DATA_XFER_SZ;
+
+			desc_addr += sizeof(struct xfer_desc);
+			d->next_l = lower_32_bits(desc_addr);
+			d->next_h = upper_32_bits(desc_addr);
+
+			ofs += DATA_XFER_SZ;
+			if (ofs >= DATA_BUF_SZ) {
+				ofs -= DATA_BUF_SZ;
+				idx++;
+				if (idx >= adap->num_bufs) {
+					desc_addr = adap->desc_buf[0].b_addr;
+					d->next_l = lower_32_bits(desc_addr);
+					d->next_h = upper_32_bits(desc_addr);
+					return 0;
+				}
+			}
+		}
+	}
+	return 0;
+
+failed:
+	pt3_free_dmabuf(adap);
+	return -ENOMEM;
+}
diff --git a/drivers/media/pci/pt3/pt3_i2c.c b/drivers/media/pci/pt3/pt3_i2c.c
new file mode 100644
index 0000000..c66ddef
--- /dev/null
+++ b/drivers/media/pci/pt3/pt3_i2c.c
@@ -0,0 +1,239 @@
+/*
+ * Earthsoft PT3 driver
+ *
+ * Copyright (C) 2014 Akihiro Tsukada <tskd08@gmail.com>
+ *
+ * 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 version 2.
+ *
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/io.h>
+#include <linux/pci.h>
+
+#include "pt3.h"
+
+#define PT3_I2C_BASE  2048
+#define PT3_CMD_ADDR_NORMAL 0
+#define PT3_CMD_ADDR_INIT_DEMOD  4096
+#define PT3_CMD_ADDR_INIT_TUNER  (4096 + 2042)
+
+/* masks for I2C status register */
+#define STAT_SEQ_RUNNING 0x1
+#define STAT_SEQ_ERROR   0x6
+#define STAT_NO_SEQ      0x8
+
+#define PT3_I2C_RUN   (1 << 16)
+#define PT3_I2C_RESET (1 << 17)
+
+enum ctl_cmd {
+	I_END,
+	I_ADDRESS,
+	I_CLOCK_L,
+	I_CLOCK_H,
+	I_DATA_L,
+	I_DATA_H,
+	I_RESET,
+	I_SLEEP,
+	I_DATA_L_NOP  = 0x08,
+	I_DATA_H_NOP  = 0x0c,
+	I_DATA_H_READ = 0x0d,
+	I_DATA_H_ACK0 = 0x0e,
+	I_DATA_H_ACK1 = 0x0f,
+};
+
+
+static void cmdbuf_add(struct pt3_i2cbuf *cbuf, enum ctl_cmd cmd)
+{
+	int buf_idx;
+
+	if ((cbuf->num_cmds % 2) == 0)
+		cbuf->tmp = cmd;
+	else {
+		cbuf->tmp |= cmd << 4;
+		buf_idx = cbuf->num_cmds / 2;
+		if (buf_idx < ARRAY_SIZE(cbuf->data))
+			cbuf->data[buf_idx] = cbuf->tmp;
+	}
+	cbuf->num_cmds++;
+}
+
+static void put_end(struct pt3_i2cbuf *cbuf)
+{
+	cmdbuf_add(cbuf, I_END);
+	if (cbuf->num_cmds % 2)
+		cmdbuf_add(cbuf, I_END);
+}
+
+static void put_start(struct pt3_i2cbuf *cbuf)
+{
+	cmdbuf_add(cbuf, I_DATA_H);
+	cmdbuf_add(cbuf, I_CLOCK_H);
+	cmdbuf_add(cbuf, I_DATA_L);
+	cmdbuf_add(cbuf, I_CLOCK_L);
+}
+
+static void put_byte_write(struct pt3_i2cbuf *cbuf, u8 val)
+{
+	u8 mask;
+
+	mask = 0x80;
+	for (mask = 0x80; mask > 0; mask >>= 1)
+		cmdbuf_add(cbuf, (val & mask) ? I_DATA_H_NOP : I_DATA_L_NOP);
+	cmdbuf_add(cbuf, I_DATA_H_ACK0);
+}
+
+static void put_byte_read(struct pt3_i2cbuf *cbuf, u32 size)
+{
+	int i, j;
+
+	for (i = 0; i < size; i++) {
+		for (j = 0; j < 8; j++)
+			cmdbuf_add(cbuf, I_DATA_H_READ);
+		cmdbuf_add(cbuf, (i == size - 1) ? I_DATA_H_NOP : I_DATA_L_NOP);
+	}
+}
+
+static void put_stop(struct pt3_i2cbuf *cbuf)
+{
+	cmdbuf_add(cbuf, I_DATA_L);
+	cmdbuf_add(cbuf, I_CLOCK_H);
+	cmdbuf_add(cbuf, I_DATA_H);
+}
+
+
+/* translates msgs to internal commands for bit-banging */
+static void translate(struct pt3_i2cbuf *cbuf, struct i2c_msg *msgs, int num)
+{
+	int i, j;
+	bool rd;
+
+	cbuf->num_cmds = 0;
+	for (i = 0; i < num; i++) {
+		rd = !!(msgs[i].flags & I2C_M_RD);
+		put_start(cbuf);
+		put_byte_write(cbuf, msgs[i].addr << 1 | rd);
+		if (rd)
+			put_byte_read(cbuf, msgs[i].len);
+		else
+			for (j = 0; j < msgs[i].len; j++)
+				put_byte_write(cbuf, msgs[i].buf[j]);
+	}
+	if (num > 0)
+		put_stop(cbuf);
+	put_end(cbuf);
+}
+
+static int wait_i2c_result(struct pt3_board *pt3, u32 *result, int max_wait)
+{
+	int i;
+	u32 v;
+
+	for (i = 0; i < max_wait; i++) {
+		v = ioread32(pt3->regs[0] + REG_I2C_R);
+		if (!(v & STAT_SEQ_RUNNING))
+			break;
+		usleep_range(500, 750);
+	}
+	if (i >= max_wait)
+		return -EIO;
+	if (result)
+		*result = v;
+	return 0;
+}
+
+/* send [pre-]translated i2c msgs stored at addr */
+static int send_i2c_cmd(struct pt3_board *pt3, u32 addr)
+{
+	u32 ret;
+
+	/* make sure that previous transactions had finished */
+	if (wait_i2c_result(pt3, NULL, 50)) {
+		dev_warn(&pt3->pdev->dev, "(%s) prev. transaction stalled\n",
+				__func__);
+		return -EIO;
+	}
+
+	iowrite32(PT3_I2C_RUN | addr, pt3->regs[0] + REG_I2C_W);
+	usleep_range(200, 300);
+	/* wait for the current transaction to finish */
+	if (wait_i2c_result(pt3, &ret, 500) || (ret & STAT_SEQ_ERROR)) {
+		dev_warn(&pt3->pdev->dev, "(%s) failed.\n", __func__);
+		return -EIO;
+	}
+	return 0;
+}
+
+
+/* init commands for each demod are combined into one transaction
+ *  and hidden in ROM with the address PT3_CMD_ADDR_INIT_DEMOD.
+ */
+int  pt3_init_all_demods(struct pt3_board *pt3)
+{
+	ioread32(pt3->regs[0] + REG_I2C_R);
+	return send_i2c_cmd(pt3, PT3_CMD_ADDR_INIT_DEMOD);
+}
+
+/* init commands for two ISDB-T tuners are hidden in ROM. */
+int  pt3_init_all_mxl301rf(struct pt3_board *pt3)
+{
+	usleep_range(1000, 2000);
+	return send_i2c_cmd(pt3, PT3_CMD_ADDR_INIT_TUNER);
+}
+
+void pt3_i2c_reset(struct pt3_board *pt3)
+{
+	iowrite32(PT3_I2C_RESET, pt3->regs[0] + REG_I2C_W);
+}
+
+/*
+ * I2C algorithm
+ */
+int
+pt3_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
+{
+	struct pt3_board *pt3;
+	struct pt3_i2cbuf *cbuf;
+	int i;
+	void __iomem *p;
+
+	pt3 = i2c_get_adapdata(adap);
+	cbuf = pt3->i2c_buf;
+
+	for (i = 0; i < num; i++)
+		if (msgs[i].flags & I2C_M_RECV_LEN) {
+			dev_warn(&pt3->pdev->dev,
+				"(%s) I2C_M_RECV_LEN not supported.\n",
+				__func__);
+			return -EINVAL;
+		}
+
+	translate(cbuf, msgs, num);
+	memcpy_toio(pt3->regs[1] + PT3_I2C_BASE + PT3_CMD_ADDR_NORMAL / 2,
+			cbuf->data, cbuf->num_cmds);
+
+	if (send_i2c_cmd(pt3, PT3_CMD_ADDR_NORMAL) < 0)
+		return -EIO;
+
+	p = pt3->regs[1] + PT3_I2C_BASE;
+	for (i = 0; i < num; i++)
+		if ((msgs[i].flags & I2C_M_RD) && msgs[i].len > 0) {
+			memcpy_fromio(msgs[i].buf, p, msgs[i].len);
+			p += msgs[i].len;
+		}
+
+	return 0;
+}
+
+u32 pt3_i2c_functionality(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C;
+}
-- 
2.1.0


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

* Re: [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5
  2014-08-27 15:29 ` [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5 tskd08
@ 2014-08-27 18:09   ` Antti Palosaari
  2014-08-28  9:07     ` Akihiro TSUKADA
  0 siblings, 1 reply; 40+ messages in thread
From: Antti Palosaari @ 2014-08-27 18:09 UTC (permalink / raw)
  To: tskd08, linux-media; +Cc: m.chehab

Moikka
I have feeling DVBv5 API is aimed to transfer data via property cached. 
I haven't done much driver for DVBv5 statistics, but recently I 
implemented CNR (DVBv5 stats) to Si2168 driver and it just writes all 
the values directly to property cache. I expect RF strength (RSSI) is 
just similar.

Antti



On 08/27/2014 06:29 PM, tskd08@gmail.com wrote:
> From: Akihiro Tsukada <tskd08@gmail.com>
>
> fe->ops.tuner_ops.get_rf_strength() reports its result in u16,
> while in DVB APIv5 it should be reported in s64 and by 0.001dBm.
>
> Signed-off-by: Akihiro Tsukada <tskd08@gmail.com>
> ---
>   drivers/media/dvb-core/dvb_frontend.h | 2 ++
>   1 file changed, 2 insertions(+)
>
> diff --git a/drivers/media/dvb-core/dvb_frontend.h b/drivers/media/dvb-core/dvb_frontend.h
> index 816269e..f6222b5 100644
> --- a/drivers/media/dvb-core/dvb_frontend.h
> +++ b/drivers/media/dvb-core/dvb_frontend.h
> @@ -222,6 +222,8 @@ struct dvb_tuner_ops {
>   #define TUNER_STATUS_STEREO 2
>   	int (*get_status)(struct dvb_frontend *fe, u32 *status);
>   	int (*get_rf_strength)(struct dvb_frontend *fe, u16 *strength);
> +	/** get signal strengh in 0.001dBm, in accordance with APIv5 */
> +	int (*get_rf_strength_dbm)(struct dvb_frontend *fe, s64 *strength);
>   	int (*get_afc)(struct dvb_frontend *fe, s32 *afc);
>
>   	/** These are provided separately from set_params in order to facilitate silicon
>

-- 
http://palosaari.fi/

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

* Re: [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5
  2014-08-27 18:09   ` Antti Palosaari
@ 2014-08-28  9:07     ` Akihiro TSUKADA
  2014-08-29  1:05       ` Antti Palosaari
  0 siblings, 1 reply; 40+ messages in thread
From: Akihiro TSUKADA @ 2014-08-28  9:07 UTC (permalink / raw)
  To: Antti Palosaari, linux-media; +Cc: m.chehab

moikka,
thanks for the comment.

> I have feeling DVBv5 API is aimed to transfer data via property cached.
> I haven't done much driver for DVBv5 statistics, but recently I
> implemented CNR (DVBv5 stats) to Si2168 driver and it just writes all
> the values directly to property cache. I expect RF strength (RSSI) is
> just similar.

Currently, the demod of PT3 card (tc90522) gets RSSI data from
the connected tuner (mxl301rf) via tuner_ops.get_signal_strength_dbm()
and sets property cache in fe->ops.get_frontend() (which is called
before returning property cache value by dvb_frontend_ioctl_properties()). 
If the tuner driver should set property cache directly,
when is the right timing to do so?
In fe->ops.tuner_ops.get_status() ?
or in the old fe->ops.tuner_ops.get_signal_strength()?
or Should I change get_signal_strength_dbm(fe, s64 *) to
update_signal_strength(fe) and let the tuner driver set property cache there?

--
akihiro



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

* Re: [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5
  2014-08-28  9:07     ` Akihiro TSUKADA
@ 2014-08-29  1:05       ` Antti Palosaari
  2014-08-29 10:45         ` Akihiro TSUKADA
  0 siblings, 1 reply; 40+ messages in thread
From: Antti Palosaari @ 2014-08-29  1:05 UTC (permalink / raw)
  To: Akihiro TSUKADA, linux-media; +Cc: m.chehab

moikka!

On 08/28/2014 12:07 PM, Akihiro TSUKADA wrote:
> moikka,
> thanks for the comment.
>
>> I have feeling DVBv5 API is aimed to transfer data via property cached.
>> I haven't done much driver for DVBv5 statistics, but recently I
>> implemented CNR (DVBv5 stats) to Si2168 driver and it just writes all
>> the values directly to property cache. I expect RF strength (RSSI) is
>> just similar.
>
> Currently, the demod of PT3 card (tc90522) gets RSSI data from
> the connected tuner (mxl301rf) via tuner_ops.get_signal_strength_dbm()
> and sets property cache in fe->ops.get_frontend() (which is called
> before returning property cache value by dvb_frontend_ioctl_properties()).
> If the tuner driver should set property cache directly,
> when is the right timing to do so?
> In fe->ops.tuner_ops.get_status() ?
> or in the old fe->ops.tuner_ops.get_signal_strength()?
> or Should I change get_signal_strength_dbm(fe, s64 *) to
> update_signal_strength(fe) and let the tuner driver set property cache there?

I think tuner driver should set c->strength as own. Look 
drivers/media/dvb-core/dvb_frontend.c
	/* Fill quality measures */
	case DTV_STAT_SIGNAL_STRENGTH:
		tvp->u.st = c->strength;
		break;

So user-space just get info what is set to struct 
dtv_frontend_properties. That is similarly than CNR and all the other 
statistics.

Start polling thread, which polls once per 2 sec or so, which reads RSSI 
and writes value to struct dtv_frontend_properties. That it is, in my 
understanding. Same for all those DVBv5 stats. Mauro knows better as he 
designed that functionality.

regards
Antti

-- 
http://palosaari.fi/

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

* Re: [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5
  2014-08-29  1:05       ` Antti Palosaari
@ 2014-08-29 10:45         ` Akihiro TSUKADA
  2014-09-06  2:09           ` Antti Palosaari
  0 siblings, 1 reply; 40+ messages in thread
From: Akihiro TSUKADA @ 2014-08-29 10:45 UTC (permalink / raw)
  To: Antti Palosaari, linux-media, m.chehab

moikka,

> Start polling thread, which polls once per 2 sec or so, which reads RSSI
> and writes value to struct dtv_frontend_properties. That it is, in my
> understanding. Same for all those DVBv5 stats. Mauro knows better as he
> designed that functionality.

I understand that RSSI property should be set directly in the tuner driver,
but I'm afraid that creating a kthread just for updating RSSI would be
overkill and complicate matters.

Would you give me an advice? >> Mauro

regards,
akihiro

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

* Re: [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator
  2014-08-27 15:29 ` [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator tskd08
@ 2014-08-31 10:29   ` Matthias Schwarzott
  2014-08-31 13:32     ` Akihiro TSUKADA
  0 siblings, 1 reply; 40+ messages in thread
From: Matthias Schwarzott @ 2014-08-31 10:29 UTC (permalink / raw)
  To: tskd08, linux-media; +Cc: m.chehab

On 27.08.2014 17:29, tskd08@gmail.com wrote:
> From: Akihiro Tsukada <tskd08@gmail.com>
> 
Hi Akihiro,

> This patch adds driver for tc90522 demodulator chips.
> The chip contains 4 demod modules that run in parallel and are independently
> controllable via separate I2C addresses.
> Two of the modules are for ISDB-T and the rest for ISDB-S.
> It is used in earthsoft pt3 cards.
> 
> Note that this driver does not init the chip,
> because the initilization sequence / register setting is not disclosed.
> Thus, the driver assumes that the chips are initilized externally
> by its parent board driver before fe->ops->init() are called.
> Earthsoft PT3 PCIe card, for example, contains the init sequence
> in its private memory and provides a command to trigger the sequence.
> 
> Signed-off-by: Akihiro Tsukada <tskd08@gmail.com>

> +
> +struct dvb_frontend *
> +tc90522_attach(const struct tc90522_config *cfg, struct i2c_adapter *i2c)
> +{
> +	struct tc90522_state *state;
> +	const struct dvb_frontend_ops *ops;
> +	struct i2c_adapter *adap;
> +	int ret;
> +
> +	state = kzalloc(sizeof(*state), GFP_KERNEL);
> +	if (!state)
> +		return NULL;
> +
> +	memcpy(&state->cfg, cfg, sizeof(*cfg));
> +	state->i2c = i2c;
> +	ops = TC90522_IS_ISDBS(cfg->addr) ? &tc90522_ops_sat : &tc90522_ops_ter;
> +	memcpy(&state->dvb_fe.ops, ops, sizeof(*ops));
> +	state->dvb_fe.demodulator_priv = state;
> +
> +	adap = &state->tuner_i2c;
> +	adap->owner = i2c->owner;
> +	adap->algo = &tc90522_tuner_i2c_algo;
> +	adap->dev.parent = i2c->dev.parent;
> +	strlcpy(adap->name, "tc90522 tuner", sizeof(adap->name));
> +	i2c_set_adapdata(adap, state);
> +	ret = i2c_add_adapter(adap);
> +	if (ret < 0) {
> +		kfree(state);
> +		return NULL;
> +	}
> +	dev_info(&i2c->dev, "Toshiba TC90522 attached.\n");
> +	return &state->dvb_fe;
> +}
> +EXPORT_SYMBOL(tc90522_attach);
> +
> +
> +struct i2c_adapter *tc90522_get_tuner_i2c(struct dvb_frontend *fe)
> +{
> +	struct tc90522_state *state;
> +
> +	state = fe->demodulator_priv;
> +	return &state->tuner_i2c;
> +}
> +EXPORT_SYMBOL(tc90522_get_tuner_i2c);
> +
> +
> +MODULE_DESCRIPTION("Toshiba TC90522 frontend");
> +MODULE_AUTHOR("Akihiro TSUKADA");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/media/dvb-frontends/tc90522.h b/drivers/media/dvb-frontends/tc90522.h
> new file mode 100644
> index 0000000..d55a6be
> --- /dev/null
> +++ b/drivers/media/dvb-frontends/tc90522.h
> @@ -0,0 +1,63 @@
> +/*
> + * Toshiba TC90522 Demodulator
> + *
> + * Copyright (C) 2014 Akihiro Tsukada <tskd08@gmail.com>
> + *
> + * 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 version 2.
> + *
> + *
> + * 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.
> + */
> +
> +/*
> + * The demod has 4 input (2xISDB-T and 2xISDB-S),
> + * and provides independent sub modules for each input.
> + * As the sub modules work in parallel and have the separate i2c addr's,
> + * this driver treats each sub module as one demod device.
> + */
> +
> +#ifndef TC90522_H
> +#define TC90522_H
> +
> +#include <linux/i2c.h>
> +#include <linux/kconfig.h>
> +#include "dvb_frontend.h"
> +
> +#define TC90522_IS_ISDBS(addr) (addr & 1)
> +
> +#define TC90522T_CMD_SET_LNA 1
> +#define TC90522S_CMD_SET_LNB 1
> +
> +struct tc90522_config {
> +	u8 addr;
> +};
> +
> +#if IS_ENABLED(CONFIG_DVB_TC90522)
> +
> +extern struct dvb_frontend *tc90522_attach(const struct tc90522_config *cfg,
> +		struct i2c_adapter *i2c);
> +
> +extern struct i2c_adapter *tc90522_get_tuner_i2c(struct dvb_frontend *fe);
> +

it sounds wrong to export a second function besides tc90522_attach.
This way there is a hard dependency of the bridge driver to the demod
driver.
In this case it is the only possible demod, but in general it violates
the design of demod drivers and their connection to bridge drivers.

si2168_probe at least has a solution for this:
Write the pointer to the new i2c adapter into location stored in "struct
i2c_adapter **" in the config structure.

Regards
Matthias


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

* Re: [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator
  2014-08-31 10:29   ` Matthias Schwarzott
@ 2014-08-31 13:32     ` Akihiro TSUKADA
  2014-08-31 19:48       ` Antti Palosaari
  0 siblings, 1 reply; 40+ messages in thread
From: Akihiro TSUKADA @ 2014-08-31 13:32 UTC (permalink / raw)
  To: Matthias Schwarzott, linux-media; +Cc: m.chehab

Hi Matthias,
thanks for the comment.

> it sounds wrong to export a second function besides tc90522_attach.
> This way there is a hard dependency of the bridge driver to the demod
> driver.
> In this case it is the only possible demod, but in general it violates
> the design of demod drivers and their connection to bridge drivers.

I agree. I missed that point.

> 
> si2168_probe at least has a solution for this:
> Write the pointer to the new i2c adapter into location stored in "struct
> i2c_adapter **" in the config structure.

I'll look into the si2168 code and update tc90522 in v3.

regards,
akihiro


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

* Re: [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator
  2014-08-31 13:32     ` Akihiro TSUKADA
@ 2014-08-31 19:48       ` Antti Palosaari
  2014-09-01  9:54         ` Akihiro TSUKADA
  0 siblings, 1 reply; 40+ messages in thread
From: Antti Palosaari @ 2014-08-31 19:48 UTC (permalink / raw)
  To: Akihiro TSUKADA, Matthias Schwarzott, linux-media; +Cc: m.chehab

On 08/31/2014 04:32 PM, Akihiro TSUKADA wrote:
> Hi Matthias,
> thanks for the comment.
>
>> it sounds wrong to export a second function besides tc90522_attach.
>> This way there is a hard dependency of the bridge driver to the demod
>> driver.
>> In this case it is the only possible demod, but in general it violates
>> the design of demod drivers and their connection to bridge drivers.
>
> I agree. I missed that point.
>
>>
>> si2168_probe at least has a solution for this:
>> Write the pointer to the new i2c adapter into location stored in "struct
>> i2c_adapter **" in the config structure.
>
> I'll look into the si2168 code and update tc90522 in v3.

Also, I would like to see all new drivers (demod and tuner) implemented 
as a standard kernel I2C drivers (or any other bus). I have converted 
already quite many drivers, si2168, si2157, m88ds3103, m88ts2022, 
it913x, tda18212, ...
When drivers are using proper kernel driver models, it allows using 
kernel services. For example dev_ / pr_ logging (it does not work 
properly without), RegMap API, I2C client, I2C multiplex, and so...

Here is few recent examples:
https://patchwork.linuxtv.org/patch/25495/
https://patchwork.linuxtv.org/patch/25152/
https://patchwork.linuxtv.org/patch/25146/

regards
Antti

-- 
http://palosaari.fi/

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

* Re: [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator
  2014-08-31 19:48       ` Antti Palosaari
@ 2014-09-01  9:54         ` Akihiro TSUKADA
  2014-09-06  2:02           ` Antti Palosaari
  0 siblings, 1 reply; 40+ messages in thread
From: Akihiro TSUKADA @ 2014-09-01  9:54 UTC (permalink / raw)
  To: Antti Palosaari, linux-media; +Cc: Matthias Schwarzott, m.chehab

Hi,

> Also, I would like to see all new drivers (demod and tuner) implemented
> as a standard kernel I2C drivers (or any other bus). I have converted
> already quite many drivers, si2168, si2157, m88ds3103, m88ts2022,
> it913x, tda18212, ...

I wrote the code in the old style using dvb_attach()
because (I felt) it is simpler than using i2c_new_device() by
introducing new i2c-related data structures,
registering to both dvb and i2c, without any new practical
features that i2c client provides.

But if the use of dvb_attach() is (almost) deprecated and
i2c client driver is the standard/prefered way,
I'll convert my code.

regards,
akihiro

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

* Re: [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator
  2014-09-01  9:54         ` Akihiro TSUKADA
@ 2014-09-06  2:02           ` Antti Palosaari
  2014-09-06  2:27             ` Mauro Carvalho Chehab
  0 siblings, 1 reply; 40+ messages in thread
From: Antti Palosaari @ 2014-09-06  2:02 UTC (permalink / raw)
  To: Akihiro TSUKADA, linux-media; +Cc: Matthias Schwarzott, m.chehab

On 09/01/2014 12:54 PM, Akihiro TSUKADA wrote:
> Hi,
>
>> Also, I would like to see all new drivers (demod and tuner) implemented
>> as a standard kernel I2C drivers (or any other bus). I have converted
>> already quite many drivers, si2168, si2157, m88ds3103, m88ts2022,
>> it913x, tda18212, ...
>
> I wrote the code in the old style using dvb_attach()
> because (I felt) it is simpler than using i2c_new_device() by
> introducing new i2c-related data structures,
> registering to both dvb and i2c, without any new practical
> features that i2c client provides.

Of course it is simpler to do old style as you could copy & paste older 
drivers and so. However, for a long term we must get rid of all DVB 
specific hacks and use common kernel solutions. The gap between common 
kernel solutions and DVB proprietary is already too big, without any 
good reason - just a laziness of developers to find out proper solutions 
as adding hacks is easier.

I mentioned quite many reasons earlier and If you look that driver you 
will see you use dev_foo() logging, that does not even work properly 
unless you convert driver to some kernel binding model (I2C on that 
case) (as I explained earlier).

There is also review issues. For more people do own tricks and hacks the 
harder code is review and also maintain as you don't never know what 
breaks when you do small change, which due to some trick used causes 
some other error.

Here is one example I fixed recently:
https://patchwork.linuxtv.org/patch/25776/

Lets mention that I am not even now fully happy to solution, even it 
somehow now works. Proper solution is implement clock source and clock 
client. Then register client to that source. And when client needs a 
clock (or power) it makes call to enable clock.

> But if the use of dvb_attach() is (almost) deprecated and
> i2c client driver is the standard/prefered way,
> I'll convert my code.

regards
Antti

-- 
http://palosaari.fi/

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

* Re: [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5
  2014-08-29 10:45         ` Akihiro TSUKADA
@ 2014-09-06  2:09           ` Antti Palosaari
  2014-09-06  2:51             ` Mauro Carvalho Chehab
  0 siblings, 1 reply; 40+ messages in thread
From: Antti Palosaari @ 2014-09-06  2:09 UTC (permalink / raw)
  To: Akihiro TSUKADA, linux-media, m.chehab

Moro!

On 08/29/2014 01:45 PM, Akihiro TSUKADA wrote:
> moikka,
>
>> Start polling thread, which polls once per 2 sec or so, which reads RSSI
>> and writes value to struct dtv_frontend_properties. That it is, in my
>> understanding. Same for all those DVBv5 stats. Mauro knows better as he
>> designed that functionality.
>
> I understand that RSSI property should be set directly in the tuner driver,
> but I'm afraid that creating a kthread just for updating RSSI would be
> overkill and complicate matters.
>
> Would you give me an advice? >> Mauro

Now I know that as I implement it. I added kthread and it works 
correctly, just I though it is aimed to work. In my case signal strength 
is reported by demod, not tuner, because there is some logic in firmware 
to calculate it.

Here is patches you would like to look as a example:

af9033: implement DVBv5 statistic for signal strength
https://patchwork.linuxtv.org/patch/25748/

af9033: implement DVBv5 statistic for CNR
https://patchwork.linuxtv.org/patch/25744/

af9033: implement DVBv5 stat block counters
https://patchwork.linuxtv.org/patch/25749/

af9033: implement DVBv5 post-Viterbi BER
https://patchwork.linuxtv.org/patch/25750/

regards
Antti

-- 
http://palosaari.fi/

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

* Re: [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator
  2014-09-06  2:02           ` Antti Palosaari
@ 2014-09-06  2:27             ` Mauro Carvalho Chehab
  2014-09-06  3:00               ` DVB clock source (Re: [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator) Antti Palosaari
  2014-09-06  6:09               ` [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator Akihiro TSUKADA
  0 siblings, 2 replies; 40+ messages in thread
From: Mauro Carvalho Chehab @ 2014-09-06  2:27 UTC (permalink / raw)
  To: Antti Palosaari; +Cc: Akihiro TSUKADA, linux-media, Matthias Schwarzott

Em Sat, 06 Sep 2014 05:02:15 +0300
Antti Palosaari <crope@iki.fi> escreveu:

> On 09/01/2014 12:54 PM, Akihiro TSUKADA wrote:
> > Hi,
> >
> >> Also, I would like to see all new drivers (demod and tuner) implemented
> >> as a standard kernel I2C drivers (or any other bus). I have converted
> >> already quite many drivers, si2168, si2157, m88ds3103, m88ts2022,
> >> it913x, tda18212, ...
> >
> > I wrote the code in the old style using dvb_attach()
> > because (I felt) it is simpler than using i2c_new_device() by
> > introducing new i2c-related data structures,
> > registering to both dvb and i2c, without any new practical
> > features that i2c client provides.
> 
> Of course it is simpler to do old style as you could copy & paste older 
> drivers and so. However, for a long term we must get rid of all DVB 
> specific hacks and use common kernel solutions. The gap between common 
> kernel solutions and DVB proprietary is already too big, without any 
> good reason - just a laziness of developers to find out proper solutions 
> as adding hacks is easier.
> 
> I mentioned quite many reasons earlier and If you look that driver you 
> will see you use dev_foo() logging, that does not even work properly 
> unless you convert driver to some kernel binding model (I2C on that 
> case) (as I explained earlier).
> 
> There is also review issues. For more people do own tricks and hacks the 
> harder code is review and also maintain as you don't never know what 
> breaks when you do small change, which due to some trick used causes 
> some other error.
> 
> Here is one example I fixed recently:
> https://patchwork.linuxtv.org/patch/25776/

Yes, using the I2C binding way provides a better decoupling than using the
legacy way. The current dvb_attach() macros are hacks that were created
by the time where the I2C standard bind didn't work with DVB.

We need to move on.

> 
> Lets mention that I am not even now fully happy to solution, even it 
> somehow now works. Proper solution is implement clock source and clock 
> client. Then register client to that source. And when client needs a 
> clock (or power) it makes call to enable clock.

Well, we need to discuss more about that, because you need to convince
me first about that ;)

We had already some discussions about that related to V4L2 I2C devices.
The consensus we've reached is that it makes sense to use the clock
framework only for the cases where the bridge driver doesn't know anything
about the clock to be used by a given device, e. g. in the case where this
data comes from the Device Tree (embedded systems).

In the case where the bridge is the ownership of the information that will
be used by a given device model (clock, serial/parallel mode, etc), then
a series of data information should be passed by a call from the bridge driver
to the device at setup time, and doing it in an atomic way is the best
way to go.

Anyway, such discussions don't belong to this thread. For the PT3 and
tc90522 to be merged, the only pending stuff to be done is to use the
I2C binding.

Akihiro-san,

Please change the driver to use the I2C model as pointed by Antti.

Thank you!
Mauro

> > But if the use of dvb_attach() is (almost) deprecated and
> > i2c client driver is the standard/prefered way,
> > I'll convert my code.
> 
> regards
> Antti
> 

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

* Re: [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5
  2014-09-06  2:09           ` Antti Palosaari
@ 2014-09-06  2:51             ` Mauro Carvalho Chehab
  2014-09-06  2:54               ` Mauro Carvalho Chehab
  2014-09-06 16:24               ` Malcolm Priestley
  0 siblings, 2 replies; 40+ messages in thread
From: Mauro Carvalho Chehab @ 2014-09-06  2:51 UTC (permalink / raw)
  To: Antti Palosaari; +Cc: Akihiro TSUKADA, linux-media

Em Sat, 06 Sep 2014 05:09:55 +0300
Antti Palosaari <crope@iki.fi> escreveu:

> Moro!
> 
> On 08/29/2014 01:45 PM, Akihiro TSUKADA wrote:
> > moikka,
> >
> >> Start polling thread, which polls once per 2 sec or so, which reads RSSI
> >> and writes value to struct dtv_frontend_properties. That it is, in my
> >> understanding. Same for all those DVBv5 stats. Mauro knows better as he
> >> designed that functionality.
> >
> > I understand that RSSI property should be set directly in the tuner driver,
> > but I'm afraid that creating a kthread just for updating RSSI would be
> > overkill and complicate matters.
> >
> > Would you give me an advice? >> Mauro
> 
> Now I know that as I implement it. I added kthread and it works 
> correctly, just I though it is aimed to work. In my case signal strength 
> is reported by demod, not tuner, because there is some logic in firmware 
> to calculate it.
> 
> Here is patches you would like to look as a example:
> 
> af9033: implement DVBv5 statistic for signal strength
> https://patchwork.linuxtv.org/patch/25748/

Actually, you don't need to add a separate kthread to collect the stats.
The DVB frontend core already has a thread that calls the frontend status
on every 3 seconds (the time can actually be different, depending on
the value for fepriv->delay. So, if the device doesn't have any issues
on getting stats on this period, it could just hook the DVBv5 stats logic
at ops.read_status().

>From the last time I reviewed the code, the PT3 driver seems to be using
this approach already at the demod.

Getting this value at the tuner makes it a little more trickier,
as you need to use some tuner callback to update the demod cache.
The .get_rf_strength ops is meant to get the signal strength from the
tuner, but it doesn't allow the tuner to return a value in dBm.

It shouldn't be the demod's task to convert a raw value on a tuner client
into dBm. 

After reading this thread and its comments, I think that the best would be
to not add a new callback.

Instead, change the implementation at the .get_rf_strength callback in
a way that it will return an integer from 0 to 65535 that would represent
a "percentage" level, where 100% means the maximum signal that the device
can measure.

Inside the tuner driver (mxl301rf), a call to .get_rf_strength will
directly update the FE stats cache to reflect the signal measurements in
dBm.

So, from the bridge driver, it will just call .get_rf_strength() without
using the returned results. If, latter, we use this tuner on some other
configuration (for example, on an hybrid analog/digital or SDR/digital
board), the V4L2 part will use the "percentage" level, as the V4L2 API
doesn't support returning values in dBm.

Regards,
Mauro

> af9033: implement DVBv5 statistic for CNR
> https://patchwork.linuxtv.org/patch/25744/
> 
> af9033: implement DVBv5 stat block counters
> https://patchwork.linuxtv.org/patch/25749/
> 
> af9033: implement DVBv5 post-Viterbi BER
> https://patchwork.linuxtv.org/patch/25750/
> 
> regards
> Antti
> 

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

* Re: [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5
  2014-09-06  2:51             ` Mauro Carvalho Chehab
@ 2014-09-06  2:54               ` Mauro Carvalho Chehab
  2014-09-06  3:10                 ` Antti Palosaari
  2014-09-06 16:24               ` Malcolm Priestley
  1 sibling, 1 reply; 40+ messages in thread
From: Mauro Carvalho Chehab @ 2014-09-06  2:54 UTC (permalink / raw)
  To: Antti Palosaari; +Cc: Akihiro TSUKADA, linux-media

Em Fri, 5 Sep 2014 23:51:05 -0300
Mauro Carvalho Chehab <m.chehab@samsung.com> escreveu:

> Em Sat, 06 Sep 2014 05:09:55 +0300
> Antti Palosaari <crope@iki.fi> escreveu:
> 
> > Moro!
> > 
> > On 08/29/2014 01:45 PM, Akihiro TSUKADA wrote:
> > > moikka,
> > >
> > >> Start polling thread, which polls once per 2 sec or so, which reads RSSI
> > >> and writes value to struct dtv_frontend_properties. That it is, in my
> > >> understanding. Same for all those DVBv5 stats. Mauro knows better as he
> > >> designed that functionality.
> > >
> > > I understand that RSSI property should be set directly in the tuner driver,
> > > but I'm afraid that creating a kthread just for updating RSSI would be
> > > overkill and complicate matters.
> > >
> > > Would you give me an advice? >> Mauro
> > 
> > Now I know that as I implement it. I added kthread and it works 
> > correctly, just I though it is aimed to work. In my case signal strength 
> > is reported by demod, not tuner, because there is some logic in firmware 
> > to calculate it.
> > 
> > Here is patches you would like to look as a example:
> > 
> > af9033: implement DVBv5 statistic for signal strength
> > https://patchwork.linuxtv.org/patch/25748/
> 
> Actually, you don't need to add a separate kthread to collect the stats.
> The DVB frontend core already has a thread that calls the frontend status
> on every 3 seconds (the time can actually be different, depending on
> the value for fepriv->delay. So, if the device doesn't have any issues
> on getting stats on this period, it could just hook the DVBv5 stats logic
> at ops.read_status().

In time: not implementing its own thread has one drawback: the driver needs
to check if the minimal time needed to get a new stats were already archived.

Please see the mt86a20s driver and check for some examples on how to
properly do that.

There, we do things like:

static int mb86a20s_read_signal_strength(struct dvb_frontend *fe)
{
	struct mb86a20s_state *state = fe->demodulator_priv;
	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
	int rc;
	unsigned rf_max, rf_min, rf;

	if (state->get_strength_time &&
	   (!time_after(jiffies, state->get_strength_time)))
		return c->strength.stat[0].uvalue;

To prevent the stats to be called too fast.

Regards,
Mauro

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

* DVB clock source (Re: [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator)
  2014-09-06  2:27             ` Mauro Carvalho Chehab
@ 2014-09-06  3:00               ` Antti Palosaari
  2014-09-06  3:11                 ` Mauro Carvalho Chehab
  2014-09-06  6:09               ` [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator Akihiro TSUKADA
  1 sibling, 1 reply; 40+ messages in thread
From: Antti Palosaari @ 2014-09-06  3:00 UTC (permalink / raw)
  To: Mauro Carvalho Chehab; +Cc: Akihiro TSUKADA, linux-media, Matthias Schwarzott

On 09/06/2014 05:27 AM, Mauro Carvalho Chehab wrote:
> Em Sat, 06 Sep 2014 05:02:15 +0300
> Antti Palosaari <crope@iki.fi> escreveu:

>> Lets mention that I am not even now fully happy to solution, even it
>> somehow now works. Proper solution is implement clock source and clock
>> client. Then register client to that source. And when client needs a
>> clock (or power) it makes call to enable clock.
>
> Well, we need to discuss more about that, because you need to convince
> me first about that ;)
>
> We had already some discussions about that related to V4L2 I2C devices.
> The consensus we've reached is that it makes sense to use the clock
> framework only for the cases where the bridge driver doesn't know anything
> about the clock to be used by a given device, e. g. in the case where this
> data comes from the Device Tree (embedded systems).
>
> In the case where the bridge is the ownership of the information that will
> be used by a given device model (clock, serial/parallel mode, etc), then
> a series of data information should be passed by a call from the bridge driver
> to the device at setup time, and doing it in an atomic way is the best
> way to go.

For AF9033/IT9133 demod + tuner I resolved it like that:
https://patchwork.linuxtv.org/patch/25772/
https://patchwork.linuxtv.org/patch/25774/

It is demod which provides clock for tuner. It is very common situation 
nowadays, one or more clocks are shared. And clock sharing is routed via 
chips so that there is clock gates you have enable/disable for power 
management reasons.

Currently we just enable clocks always. Clock output is put on when 
driver is attached and it is never disabled after that, leaving power 
management partly broken.

Lets take a example, dual tuner case:
tuner#0 gets clock from Xtal
tuner#1 gets clock from #tuner0

All possible use cases are:
1) #tuner0 off & #tuner1 off
2) #tuner0 on & #tuner1 off
3) #tuner1 off & #tuner1 on
4) #tuner1 on & #tuner1 on

you will need, as per aforementioned use case:
1) #tuner0 clock out off & #tuner1 clock out off
2) #tuner0 clock out off & #tuner1 clock out off
3) #tuner0 clock out on & #tuner1 clock out off
4) #tuner0 clock out on & #tuner1 clock out off

Implementing that currently is simply impossible. But if you use clock 
framework (or what ever its name is) I think it is possible to implement 
that properly. When tuner#1 driver needs a clock, it calls "get clock" 
and that call is routed to #tuner0 which enables clock.

And that was not even the most complicated case, as many times clock is 
routed to demod and USB bridge too.

Quite same situation is for power on/off gpios (which should likely 
implemented as a regulator). Also there is many times reset gpio (for PM 
chip is powered off by switching power totally off *or* chip is put to 
reset using GPIO)

regards
Antti

-- 
http://palosaari.fi/

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

* Re: [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5
  2014-09-06  2:54               ` Mauro Carvalho Chehab
@ 2014-09-06  3:10                 ` Antti Palosaari
  2014-09-06  3:17                   ` Mauro Carvalho Chehab
  0 siblings, 1 reply; 40+ messages in thread
From: Antti Palosaari @ 2014-09-06  3:10 UTC (permalink / raw)
  To: Mauro Carvalho Chehab; +Cc: Akihiro TSUKADA, linux-media



On 09/06/2014 05:54 AM, Mauro Carvalho Chehab wrote:
> Em Fri, 5 Sep 2014 23:51:05 -0300
> Mauro Carvalho Chehab <m.chehab@samsung.com> escreveu:
>
>> Em Sat, 06 Sep 2014 05:09:55 +0300
>> Antti Palosaari <crope@iki.fi> escreveu:
>>
>>> Moro!
>>>
>>> On 08/29/2014 01:45 PM, Akihiro TSUKADA wrote:
>>>> moikka,
>>>>
>>>>> Start polling thread, which polls once per 2 sec or so, which reads RSSI
>>>>> and writes value to struct dtv_frontend_properties. That it is, in my
>>>>> understanding. Same for all those DVBv5 stats. Mauro knows better as he
>>>>> designed that functionality.
>>>>
>>>> I understand that RSSI property should be set directly in the tuner driver,
>>>> but I'm afraid that creating a kthread just for updating RSSI would be
>>>> overkill and complicate matters.
>>>>
>>>> Would you give me an advice? >> Mauro
>>>
>>> Now I know that as I implement it. I added kthread and it works
>>> correctly, just I though it is aimed to work. In my case signal strength
>>> is reported by demod, not tuner, because there is some logic in firmware
>>> to calculate it.
>>>
>>> Here is patches you would like to look as a example:
>>>
>>> af9033: implement DVBv5 statistic for signal strength
>>> https://patchwork.linuxtv.org/patch/25748/
>>
>> Actually, you don't need to add a separate kthread to collect the stats.
>> The DVB frontend core already has a thread that calls the frontend status
>> on every 3 seconds (the time can actually be different, depending on
>> the value for fepriv->delay. So, if the device doesn't have any issues
>> on getting stats on this period, it could just hook the DVBv5 stats logic
>> at ops.read_status().
>
> In time: not implementing its own thread has one drawback: the driver needs
> to check if the minimal time needed to get a new stats were already archived.
>
> Please see the mt86a20s driver and check for some examples on how to
> properly do that.
>
> There, we do things like:
>
> static int mb86a20s_read_signal_strength(struct dvb_frontend *fe)
> {
> 	struct mb86a20s_state *state = fe->demodulator_priv;
> 	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
> 	int rc;
> 	unsigned rf_max, rf_min, rf;
>
> 	if (state->get_strength_time &&
> 	   (!time_after(jiffies, state->get_strength_time)))
> 		return c->strength.stat[0].uvalue;
>
> To prevent the stats to be called too fast.

... I simply don't understand why you want hook that RF strength call 
via demod? The frontend cache is shared between demod and tuner. We use 
it for tuner driver as well demod driver. Let the tuner driver make RSSI 
calculation independently without any unneeded relation to demod driver.

regards
Antti

-- 
http://palosaari.fi/

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

* Re: DVB clock source (Re: [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator)
  2014-09-06  3:00               ` DVB clock source (Re: [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator) Antti Palosaari
@ 2014-09-06  3:11                 ` Mauro Carvalho Chehab
  2014-09-06  3:20                   ` Antti Palosaari
  0 siblings, 1 reply; 40+ messages in thread
From: Mauro Carvalho Chehab @ 2014-09-06  3:11 UTC (permalink / raw)
  To: Antti Palosaari; +Cc: Akihiro TSUKADA, linux-media, Matthias Schwarzott

Em Sat, 06 Sep 2014 06:00:28 +0300
Antti Palosaari <crope@iki.fi> escreveu:

> On 09/06/2014 05:27 AM, Mauro Carvalho Chehab wrote:
> > Em Sat, 06 Sep 2014 05:02:15 +0300
> > Antti Palosaari <crope@iki.fi> escreveu:
> 
> >> Lets mention that I am not even now fully happy to solution, even it
> >> somehow now works. Proper solution is implement clock source and clock
> >> client. Then register client to that source. And when client needs a
> >> clock (or power) it makes call to enable clock.
> >
> > Well, we need to discuss more about that, because you need to convince
> > me first about that ;)
> >
> > We had already some discussions about that related to V4L2 I2C devices.
> > The consensus we've reached is that it makes sense to use the clock
> > framework only for the cases where the bridge driver doesn't know anything
> > about the clock to be used by a given device, e. g. in the case where this
> > data comes from the Device Tree (embedded systems).
> >
> > In the case where the bridge is the ownership of the information that will
> > be used by a given device model (clock, serial/parallel mode, etc), then
> > a series of data information should be passed by a call from the bridge driver
> > to the device at setup time, and doing it in an atomic way is the best
> > way to go.
> 
> For AF9033/IT9133 demod + tuner I resolved it like that:
> https://patchwork.linuxtv.org/patch/25772/
> https://patchwork.linuxtv.org/patch/25774/
> 
> It is demod which provides clock for tuner. It is very common situation 
> nowadays, one or more clocks are shared. And clock sharing is routed via 
> chips so that there is clock gates you have enable/disable for power 
> management reasons.
> 
> Currently we just enable clocks always. Clock output is put on when 
> driver is attached and it is never disabled after that, leaving power 
> management partly broken.
> 
> Lets take a example, dual tuner case:
> tuner#0 gets clock from Xtal
> tuner#1 gets clock from #tuner0
> 
> All possible use cases are:
> 1) #tuner0 off & #tuner1 off
> 2) #tuner0 on & #tuner1 off
> 3) #tuner1 off & #tuner1 on
> 4) #tuner1 on & #tuner1 on
> 
> you will need, as per aforementioned use case:
> 1) #tuner0 clock out off & #tuner1 clock out off
> 2) #tuner0 clock out off & #tuner1 clock out off
> 3) #tuner0 clock out on & #tuner1 clock out off
> 4) #tuner0 clock out on & #tuner1 clock out off
> 
> Implementing that currently is simply impossible. But if you use clock 
> framework (or what ever its name is) I think it is possible to implement 
> that properly. When tuner#1 driver needs a clock, it calls "get clock" 
> and that call is routed to #tuner0 which enables clock.
> 
> And that was not even the most complicated case, as many times clock is 
> routed to demod and USB bridge too.
> 
> Quite same situation is for power on/off gpios (which should likely 
> implemented as a regulator). Also there is many times reset gpio (for PM 
> chip is powered off by switching power totally off *or* chip is put to 
> reset using GPIO)

Ok, in the above scenario, I agree that using the clock framework
makes sense, but, on devices where the clock is independent
(e. g. each chip has its on XTAL), I'm yet to see a scenario where
using the clock framework will simplify the code or bring some extra
benefit.

Regards,
Mauro

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

* Re: [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5
  2014-09-06  3:10                 ` Antti Palosaari
@ 2014-09-06  3:17                   ` Mauro Carvalho Chehab
  2014-09-06  3:34                     ` Antti Palosaari
  2014-09-06  4:08                     ` Akihiro TSUKADA
  0 siblings, 2 replies; 40+ messages in thread
From: Mauro Carvalho Chehab @ 2014-09-06  3:17 UTC (permalink / raw)
  To: Antti Palosaari; +Cc: Akihiro TSUKADA, linux-media

Em Sat, 06 Sep 2014 06:10:01 +0300
Antti Palosaari <crope@iki.fi> escreveu:

> 
> 
> On 09/06/2014 05:54 AM, Mauro Carvalho Chehab wrote:
> > Em Fri, 5 Sep 2014 23:51:05 -0300
> > Mauro Carvalho Chehab <m.chehab@samsung.com> escreveu:
> >
> >> Em Sat, 06 Sep 2014 05:09:55 +0300
> >> Antti Palosaari <crope@iki.fi> escreveu:
> >>
> >>> Moro!
> >>>
> >>> On 08/29/2014 01:45 PM, Akihiro TSUKADA wrote:
> >>>> moikka,
> >>>>
> >>>>> Start polling thread, which polls once per 2 sec or so, which reads RSSI
> >>>>> and writes value to struct dtv_frontend_properties. That it is, in my
> >>>>> understanding. Same for all those DVBv5 stats. Mauro knows better as he
> >>>>> designed that functionality.
> >>>>
> >>>> I understand that RSSI property should be set directly in the tuner driver,
> >>>> but I'm afraid that creating a kthread just for updating RSSI would be
> >>>> overkill and complicate matters.
> >>>>
> >>>> Would you give me an advice? >> Mauro
> >>>
> >>> Now I know that as I implement it. I added kthread and it works
> >>> correctly, just I though it is aimed to work. In my case signal strength
> >>> is reported by demod, not tuner, because there is some logic in firmware
> >>> to calculate it.
> >>>
> >>> Here is patches you would like to look as a example:
> >>>
> >>> af9033: implement DVBv5 statistic for signal strength
> >>> https://patchwork.linuxtv.org/patch/25748/
> >>
> >> Actually, you don't need to add a separate kthread to collect the stats.
> >> The DVB frontend core already has a thread that calls the frontend status
> >> on every 3 seconds (the time can actually be different, depending on
> >> the value for fepriv->delay. So, if the device doesn't have any issues
> >> on getting stats on this period, it could just hook the DVBv5 stats logic
> >> at ops.read_status().
> >
> > In time: not implementing its own thread has one drawback: the driver needs
> > to check if the minimal time needed to get a new stats were already archived.
> >
> > Please see the mt86a20s driver and check for some examples on how to
> > properly do that.
> >
> > There, we do things like:
> >
> > static int mb86a20s_read_signal_strength(struct dvb_frontend *fe)
> > {
> > 	struct mb86a20s_state *state = fe->demodulator_priv;
> > 	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
> > 	int rc;
> > 	unsigned rf_max, rf_min, rf;
> >
> > 	if (state->get_strength_time &&
> > 	   (!time_after(jiffies, state->get_strength_time)))
> > 		return c->strength.stat[0].uvalue;
> >
> > To prevent the stats to be called too fast.
> 
> ... I simply don't understand why you want hook that RF strength call 
> via demod? The frontend cache is shared between demod and tuner. We use 
> it for tuner driver as well demod driver. Let the tuner driver make RSSI 
> calculation independently without any unneeded relation to demod driver.

Well, adding kthreads has a cost, with is a way higher than just
calling a callback function.

Also, it makes a way more complicated to implement several tasks.

For example, devices with kthreads need to stop them during suspend,
and restart them at resume time, or otherwise suspend and/or resume
may not work.

Also, the power consumption increases with kthread, as the CPU need
to be periodically waken.

I'm not saying we shouldn't use kthreads at driver level, but
the best is to avoid when there are some other simpler ways of doing it.

Regards,
Mauro

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

* Re: DVB clock source (Re: [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator)
  2014-09-06  3:11                 ` Mauro Carvalho Chehab
@ 2014-09-06  3:20                   ` Antti Palosaari
  0 siblings, 0 replies; 40+ messages in thread
From: Antti Palosaari @ 2014-09-06  3:20 UTC (permalink / raw)
  To: Mauro Carvalho Chehab; +Cc: Akihiro TSUKADA, linux-media, Matthias Schwarzott



On 09/06/2014 06:11 AM, Mauro Carvalho Chehab wrote:
> Em Sat, 06 Sep 2014 06:00:28 +0300
> Antti Palosaari <crope@iki.fi> escreveu:
>
>> On 09/06/2014 05:27 AM, Mauro Carvalho Chehab wrote:
>>> Em Sat, 06 Sep 2014 05:02:15 +0300
>>> Antti Palosaari <crope@iki.fi> escreveu:
>>
>>>> Lets mention that I am not even now fully happy to solution, even it
>>>> somehow now works. Proper solution is implement clock source and clock
>>>> client. Then register client to that source. And when client needs a
>>>> clock (or power) it makes call to enable clock.
>>>
>>> Well, we need to discuss more about that, because you need to convince
>>> me first about that ;)
>>>
>>> We had already some discussions about that related to V4L2 I2C devices.
>>> The consensus we've reached is that it makes sense to use the clock
>>> framework only for the cases where the bridge driver doesn't know anything
>>> about the clock to be used by a given device, e. g. in the case where this
>>> data comes from the Device Tree (embedded systems).
>>>
>>> In the case where the bridge is the ownership of the information that will
>>> be used by a given device model (clock, serial/parallel mode, etc), then
>>> a series of data information should be passed by a call from the bridge driver
>>> to the device at setup time, and doing it in an atomic way is the best
>>> way to go.
>>
>> For AF9033/IT9133 demod + tuner I resolved it like that:
>> https://patchwork.linuxtv.org/patch/25772/
>> https://patchwork.linuxtv.org/patch/25774/
>>
>> It is demod which provides clock for tuner. It is very common situation
>> nowadays, one or more clocks are shared. And clock sharing is routed via
>> chips so that there is clock gates you have enable/disable for power
>> management reasons.
>>
>> Currently we just enable clocks always. Clock output is put on when
>> driver is attached and it is never disabled after that, leaving power
>> management partly broken.
>>
>> Lets take a example, dual tuner case:
>> tuner#0 gets clock from Xtal
>> tuner#1 gets clock from #tuner0
>>
>> All possible use cases are:
>> 1) #tuner0 off & #tuner1 off
>> 2) #tuner0 on & #tuner1 off
>> 3) #tuner1 off & #tuner1 on
>> 4) #tuner1 on & #tuner1 on
>>
>> you will need, as per aforementioned use case:
>> 1) #tuner0 clock out off & #tuner1 clock out off
>> 2) #tuner0 clock out off & #tuner1 clock out off
>> 3) #tuner0 clock out on & #tuner1 clock out off
>> 4) #tuner0 clock out on & #tuner1 clock out off
>>
>> Implementing that currently is simply impossible. But if you use clock
>> framework (or what ever its name is) I think it is possible to implement
>> that properly. When tuner#1 driver needs a clock, it calls "get clock"
>> and that call is routed to #tuner0 which enables clock.
>>
>> And that was not even the most complicated case, as many times clock is
>> routed to demod and USB bridge too.
>>
>> Quite same situation is for power on/off gpios (which should likely
>> implemented as a regulator). Also there is many times reset gpio (for PM
>> chip is powered off by switching power totally off *or* chip is put to
>> reset using GPIO)
>
> Ok, in the above scenario, I agree that using the clock framework
> makes sense, but, on devices where the clock is independent
> (e. g. each chip has its on XTAL), I'm yet to see a scenario where
> using the clock framework will simplify the code or bring some extra
> benefit.

And I resolved it like that for IT9135:
https://patchwork.linuxtv.org/patch/25763/

1) defined tuner role config parameter (master feeds a clock, slave does 
not)
2) master is then never put 100% deep sleep

Comment on code explains that:
/*
  * Writing '0x00' to master tuner register '0x80ec08' causes slave tuner
  * communication lost. Due to that, we cannot put master full sleep.
  */

but it will be much more elegant solution to use clock framework which 
allows implementing correct power management.

regards
Antti

-- 
http://palosaari.fi/

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

* Re: [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5
  2014-09-06  3:17                   ` Mauro Carvalho Chehab
@ 2014-09-06  3:34                     ` Antti Palosaari
  2014-09-06 12:35                       ` Mauro Carvalho Chehab
  2014-09-06  4:08                     ` Akihiro TSUKADA
  1 sibling, 1 reply; 40+ messages in thread
From: Antti Palosaari @ 2014-09-06  3:34 UTC (permalink / raw)
  To: Mauro Carvalho Chehab; +Cc: Akihiro TSUKADA, linux-media

On 09/06/2014 06:17 AM, Mauro Carvalho Chehab wrote:
> Em Sat, 06 Sep 2014 06:10:01 +0300
> Antti Palosaari <crope@iki.fi> escreveu:

>> ... I simply don't understand why you want hook that RF strength call
>> via demod? The frontend cache is shared between demod and tuner. We use
>> it for tuner driver as well demod driver. Let the tuner driver make RSSI
>> calculation independently without any unneeded relation to demod driver.
>
> Well, adding kthreads has a cost, with is a way higher than just
> calling a callback function.
>
> Also, it makes a way more complicated to implement several tasks.
>
> For example, devices with kthreads need to stop them during suspend,
> and restart them at resume time, or otherwise suspend and/or resume
> may not work.
>
> Also, the power consumption increases with kthread, as the CPU need
> to be periodically waken.
>
> I'm not saying we shouldn't use kthreads at driver level, but
> the best is to avoid when there are some other simpler ways of doing it.

And small reality check, how much you think one kthreads, that polls 
once per 2 second or so causes, in a case when you are *already 
streaming* 20-80 Mbit/sec data stream :) I think CPU does not need wake 
up to execute one thread as there is a lot of other interrupts happening 
in that use case anyway.

We have a remote controller which is polled often as 100ms and it 
happens even when device is "sleeping".

regards
Antti

-- 
http://palosaari.fi/

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

* Re: [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5
  2014-09-06  3:17                   ` Mauro Carvalho Chehab
  2014-09-06  3:34                     ` Antti Palosaari
@ 2014-09-06  4:08                     ` Akihiro TSUKADA
  2014-09-06 10:36                       ` Antti Palosaari
  1 sibling, 1 reply; 40+ messages in thread
From: Akihiro TSUKADA @ 2014-09-06  4:08 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Antti Palosaari; +Cc: linux-media

Moikka!,
thanks for the comments and advices.

I had been updating my code and during that, I also found that
updating property cache in tuner_ops.get_signal_strength() was
simple and (seemed to me) better than using a kthread,
so the current implementation (under testing) is just like
what Mauro proposed, but,

> In time: not implementing its own thread has one drawback: the driver needs
> to check if the minimal time needed to get a new stats were already archived.

since I don't know the minimal time and
whether there's a limit in the first place,
I'd like to let users take the responsibility.


>> ... I simply don't understand why you want hook that RF strength call 
>> via demod? The frontend cache is shared between demod and tuner. We use 
>> it for tuner drivr as well demod driver. Let the tuner driver make RSSI 
>> calculation independently without any unneeded relation to demod driver.

I think the main reason for the hook is because the dvb-core calls
ops.get_frontend() everytime before reading of any property cache,
so it is already a nice place to trigger property updates,
and reading any property involves demod (FE as a whole) anyway.

regards,
akihiro

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

* Re: [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator
  2014-09-06  2:27             ` Mauro Carvalho Chehab
  2014-09-06  3:00               ` DVB clock source (Re: [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator) Antti Palosaari
@ 2014-09-06  6:09               ` Akihiro TSUKADA
  2014-09-06  6:51                 ` Antti Palosaari
  2014-09-06  7:13                 ` Antti Palosaari
  1 sibling, 2 replies; 40+ messages in thread
From: Akihiro TSUKADA @ 2014-09-06  6:09 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Antti Palosaari; +Cc: linux-media, Matthias Schwarzott

Moi!

> Yes, using the I2C binding way provides a better decoupling than using the
> legacy way. The current dvb_attach() macros are hacks that were created
> by the time where the I2C standard bind didn't work with DVB.

I understand. I converted my code to use i2c binding model,
but I'm uncertain about a few things.

1. How to load the modules of i2c driver?
Currently I use request_module()/module_put()
like an example (ddbrige-core.c) from Antti does,
but I'd prefer implicit module loading/ref-counting
like in dvb_attach() if it exists.


2. Is there a standard way to pass around dvb_frontend*, i2c_client*,
regmap* between bridge(dvb_adapter) and demod/tuner drivers?
Currently I use config structure for the purpose, which is set to
dev.platform_data (via i2c_board_info/i2c_new_device()) or
dev.driver_data (via i2c_{get,set}_clientdata()),
but using config as both IN/OUT looks a bit hacky.

3. Should I also use RegMap API for register access?
I tried using it but gave up,
because it does not fit well to one of my use-case,
where (only) reads must be done via 0xfb register, like
   READ(reg, buf, len) -> [addr/w, 0xfb, reg], [addr/r, buf[0]...],
   WRITE(reg, buf, len) -> [addr/w, reg, buf[0]...],
and regmap looked to me overkill for 8bit-reg, 8bit-val cases
and did not simplify the code.
so I'd like to go without RegMap if possible,
since I'm already puzzled enough by I2C binding, regmap, clock source,
as well as dvb-core, PCI ;)

regards,
Akihiro

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

* Re: [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator
  2014-09-06  6:09               ` [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator Akihiro TSUKADA
@ 2014-09-06  6:51                 ` Antti Palosaari
  2014-09-06 19:35                   ` Akihiro TSUKADA
  2014-09-06  7:13                 ` Antti Palosaari
  1 sibling, 1 reply; 40+ messages in thread
From: Antti Palosaari @ 2014-09-06  6:51 UTC (permalink / raw)
  To: Akihiro TSUKADA, Mauro Carvalho Chehab; +Cc: linux-media, Matthias Schwarzott

moikka!

On 09/06/2014 09:09 AM, Akihiro TSUKADA wrote:
> Moi!
>
>> Yes, using the I2C binding way provides a better decoupling than using the
>> legacy way. The current dvb_attach() macros are hacks that were created
>> by the time where the I2C standard bind didn't work with DVB.
>
> I understand. I converted my code to use i2c binding model,
> but I'm uncertain about a few things.
>
> 1. How to load the modules of i2c driver?
> Currently I use request_module()/module_put()
> like an example (ddbrige-core.c) from Antti does,
> but I'd prefer implicit module loading/ref-counting
> like in dvb_attach() if it exists.

Maybe I haven't found the best way yet, but that was implementation I 
made for AF9035 driver:
https://patchwork.linuxtv.org/patch/25764/

Basically it is 2 functions, af9035_add_i2c_dev() and af9035_del_i2c_dev()

> 2. Is there a standard way to pass around dvb_frontend*, i2c_client*,
> regmap* between bridge(dvb_adapter) and demod/tuner drivers?
> Currently I use config structure for the purpose, which is set to
> dev.platform_data (via i2c_board_info/i2c_new_device()) or
> dev.driver_data (via i2c_{get,set}_clientdata()),
> but using config as both IN/OUT looks a bit hacky.

In my understanding, platform_data is place to pass environment specific 
config to driver. get/set client_data() is aimed to carry pointer for 
device specific instance "state" inside a driver. Is it possible to set 
I2C client data before calling i2c_new_device() and pass pointer to driver?

There is also IOCTL style command for I2C, but it is legacy and should 
not be used.

Documentation/i2c/busses/i2c-ocores

Yet, using config to OUT seems to be bit hacky for my eyes too. I though 
replacing all OUT with ops when converted af9033 driver. Currently 
caller fills struct af9033_ops and then af9033 I2C probe populates ops. 
See that patch:
https://patchwork.linuxtv.org/patch/25746/

Does this kind of ops sounds any better?

EXPORT_SYMBOL() is easiest way to offer outputs, like 
EXPORT_SYMBOL(get_frontend), EXPORT_SYMBOL(get_i2c_adapter). But we want 
avoid those exported symbols.

> 3. Should I also use RegMap API for register access?
> I tried using it but gave up,
> because it does not fit well to one of my use-case,
> where (only) reads must be done via 0xfb register, like
>     READ(reg, buf, len) -> [addr/w, 0xfb, reg], [addr/r, buf[0]...],
>     WRITE(reg, buf, len) -> [addr/w, reg, buf[0]...],
> and regmap looked to me overkill for 8bit-reg, 8bit-val cases
> and did not simplify the code.
> so I'd like to go without RegMap if possible,
> since I'm already puzzled enough by I2C binding, regmap, clock source,
> as well as dvb-core, PCI ;)

I prefer RegMap API, but I am only one who has started using it yet. And 
I haven't converted any demod driver having I2C bus/gate/repeater for 
tuner to that API. It is because of I2C locking with I2C mux adapter, 
you need use unlocked version of i2c_transfer() for I2C mux as I2C bus 
lock is already taken. RegMap API does not support that, but I think it 
should be possible if you implement own xfer callback for regmap. For RF 
tuners RegMap API should be trivial and it will reduce ~100 LOC from driver.

But if you decide avoid RegMap API, I ask you add least implementing 
those I2C read / write function parameters similarly than RegMap API 
does. Defining all kind of weird register write / read functions makes 
life harder. I converted recently IT913x driver to RegMap API and 
biggest pain was there very different register read / write routines. So 
I need to do a lot of work in order convert functions first some common 
style and then it was trivial to change RegMap API.

https://patchwork.linuxtv.org/patch/25766/
https://patchwork.linuxtv.org/patch/25757/

I quickly overlooked that demod driver and one which looked crazy was 
LNA stuff. You implement set_lna callback in demod, but it is then 
passed back to PCI driver using frontend callback. Is there some reason 
you hooked it via demod? You could implement set_lna in PCI driver too.

regards
Antti

-- 
http://palosaari.fi/

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

* Re: [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator
  2014-09-06  6:09               ` [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator Akihiro TSUKADA
  2014-09-06  6:51                 ` Antti Palosaari
@ 2014-09-06  7:13                 ` Antti Palosaari
  1 sibling, 0 replies; 40+ messages in thread
From: Antti Palosaari @ 2014-09-06  7:13 UTC (permalink / raw)
  To: Akihiro TSUKADA, Mauro Carvalho Chehab; +Cc: linux-media, Matthias Schwarzott

On 09/06/2014 09:09 AM, Akihiro TSUKADA wrote:
> 3. Should I also use RegMap API for register access?
> I tried using it but gave up,
> because it does not fit well to one of my use-case,
> where (only) reads must be done via 0xfb register, like
>     READ(reg, buf, len) -> [addr/w, 0xfb, reg], [addr/r, buf[0]...],
>     WRITE(reg, buf, len) -> [addr/w, reg, buf[0]...],
> and regmap looked to me overkill for 8bit-reg, 8bit-val cases
> and did not simplify the code.
> so I'd like to go without RegMap if possible,
> since I'm already puzzled enough by I2C binding, regmap, clock source,
> as well as dvb-core, PCI ;)

That is MaxLinear MxL301RF tuner I2C. Problem is there that it uses 
write + STOP + write, so you should not even do that as a one I2C 
i2c_transfer. All I2C messages send using i2c_transfer are send so 
called REPEATED START condition.

I ran that same problem ears ago in a case of, surprise, MxL5007 tuner.
https://patchwork.linuxtv.org/patch/17847/

I think you could just write wanted register to command register 0xfb. 
And after that all the reads are coming from that active register until 
you change it.

RegMap API cannot handle I2C command format like that, it relies 
repeated start for reads.

Si2157 / Si2168 are using I2C access with STOP condition - but it is 
otherwise bad example as there is firmware API, not register API. Look 
still as a example.

regards
Antti

-- 
http://palosaari.fi/

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

* Re: [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5
  2014-09-06  4:08                     ` Akihiro TSUKADA
@ 2014-09-06 10:36                       ` Antti Palosaari
  2014-09-06 12:49                         ` Mauro Carvalho Chehab
  0 siblings, 1 reply; 40+ messages in thread
From: Antti Palosaari @ 2014-09-06 10:36 UTC (permalink / raw)
  To: Akihiro TSUKADA, Mauro Carvalho Chehab; +Cc: linux-media

On 09/06/2014 07:08 AM, Akihiro TSUKADA wrote:
> Moikka!,
> thanks for the comments and advices.
>
> I had been updating my code and during that, I also found that
> updating property cache in tuner_ops.get_signal_strength() was
> simple and (seemed to me) better than using a kthread,
> so the current implementation (under testing) is just like
> what Mauro proposed, but,
>
>> In time: not implementing its own thread has one drawback: the driver needs
>> to check if the minimal time needed to get a new stats were already archived.
>
> since I don't know the minimal time and
> whether there's a limit in the first place,
> I'd like to let users take the responsibility.

You could add some simple jiffie (some kind of kernel global time) which 
limits calls to some reasonable level.

if (jiffies > jiffies_previous + 1 sec)
   return 0;
else
   jiffies_previous = jiffies;

... continue

>>> ... I simply don't understand why you want hook that RF strength call
>>> via demod? The frontend cache is shared between demod and tuner. We use
>>> it for tuner drivr as well demod driver. Let the tuner driver make RSSI
>>> calculation independently without any unneeded relation to demod driver.
>
> I think the main reason for the hook is because the dvb-core calls
> ops.get_frontend() everytime before reading of any property cache,
> so it is already a nice place to trigger property updates,
> and reading any property involves demod (FE as a whole) anyway.

That must be changed 'resently'. IIRC originally get_frontend() was 
called by dvb-core only once, just after demod lock was gained. Also 
userspace could call it using some IOCTL (GET_FRONTEND?).

But if it is not called periodically by dvb-core, you could not use it 
for bit error measurements, as you will usually need to start 
measurement, then wait complete, read values and return.

Signal strength and SNR are typically provided by chip without any waiting.

regards
Antti

-- 
http://palosaari.fi/

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

* Re: [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5
  2014-09-06  3:34                     ` Antti Palosaari
@ 2014-09-06 12:35                       ` Mauro Carvalho Chehab
  0 siblings, 0 replies; 40+ messages in thread
From: Mauro Carvalho Chehab @ 2014-09-06 12:35 UTC (permalink / raw)
  To: Antti Palosaari; +Cc: Akihiro TSUKADA, linux-media

Em Sat, 06 Sep 2014 06:34:33 +0300
Antti Palosaari <crope@iki.fi> escreveu:

> On 09/06/2014 06:17 AM, Mauro Carvalho Chehab wrote:
> > Em Sat, 06 Sep 2014 06:10:01 +0300
> > Antti Palosaari <crope@iki.fi> escreveu:
> 
> >> ... I simply don't understand why you want hook that RF strength call
> >> via demod? The frontend cache is shared between demod and tuner. We use
> >> it for tuner driver as well demod driver. Let the tuner driver make RSSI
> >> calculation independently without any unneeded relation to demod driver.
> >
> > Well, adding kthreads has a cost, with is a way higher than just
> > calling a callback function.
> >
> > Also, it makes a way more complicated to implement several tasks.
> >
> > For example, devices with kthreads need to stop them during suspend,
> > and restart them at resume time, or otherwise suspend and/or resume
> > may not work.
> >
> > Also, the power consumption increases with kthread, as the CPU need
> > to be periodically waken.
> >
> > I'm not saying we shouldn't use kthreads at driver level, but
> > the best is to avoid when there are some other simpler ways of doing it.
> 
> And small reality check, how much you think one kthreads, that polls 
> once per 2 second or so causes, in a case when you are *already 
> streaming* 20-80 Mbit/sec data stream :) I think CPU does not need wake 
> up to execute one thread as there is a lot of other interrupts happening 
> in that use case anyway.

You can't assume that all streams received by a tuner uses 20-80Mbps. 

It could be a sound broadcasting stream, for example, with uses a much
lower bandwidth.

> We have a remote controller which is polled often as 100ms and it 
> happens even when device is "sleeping".

And lots of drivers have a modprobe parameter to disable it, because
it causes too much power consumption.

Regards,
Mauro

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

* Re: [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5
  2014-09-06 10:36                       ` Antti Palosaari
@ 2014-09-06 12:49                         ` Mauro Carvalho Chehab
  0 siblings, 0 replies; 40+ messages in thread
From: Mauro Carvalho Chehab @ 2014-09-06 12:49 UTC (permalink / raw)
  To: Akihiro TSUKADA; +Cc: Antti Palosaari, linux-media

Em Sat, 06 Sep 2014 13:36:14 +0300
Antti Palosaari <crope@iki.fi> escreveu:

> On 09/06/2014 07:08 AM, Akihiro TSUKADA wrote:
> > Moikka!,
> > thanks for the comments and advices.
> >
> > I had been updating my code and during that, I also found that
> > updating property cache in tuner_ops.get_signal_strength() was
> > simple and (seemed to me) better than using a kthread,
> > so the current implementation (under testing) is just like
> > what Mauro proposed, but,
> >
> >> In time: not implementing its own thread has one drawback: the driver needs
> >> to check if the minimal time needed to get a new stats were already archived.
> >
> > since I don't know the minimal time and
> > whether there's a limit in the first place,
> > I'd like to let users take the responsibility.

For RSSI measurements, in general there's no minimal time, but for measures
like BER, PER, UCB, you'll need to wait for a while before the stats to be
updated. So, you'll need to at least track those.

> You could add some simple jiffie (some kind of kernel global time) which 
> limits calls to some reasonable level.
> 
> if (jiffies > jiffies_previous + 1 sec)
>    return 0;
> else
>    jiffies_previous = jiffies;

Don't use jiffies like that. jiffies can be overflowed and the update
will never occur. The right way is to use the macros time_after() and
time_before(), or, alternatively, time_is_after_jiffies() and
time_is_before_jiffies().

> 
> ... continue
> 
> >>> ... I simply don't understand why you want hook that RF strength call
> >>> via demod? The frontend cache is shared between demod and tuner. We use
> >>> it for tuner drivr as well demod driver. Let the tuner driver make RSSI
> >>> calculation independently without any unneeded relation to demod driver.
> >
> > I think the main reason for the hook is because the dvb-core calls
> > ops.get_frontend() everytime before reading of any property cache,
> > so it is already a nice place to trigger property updates,
> > and reading any property involves demod (FE as a whole) anyway.
> 
> That must be changed 'resently'. IIRC originally get_frontend() was 
> called by dvb-core only once, just after demod lock was gained. Also 
> userspace could call it using some IOCTL (GET_FRONTEND?).

No, get_frontend() is not automatically called by the dvb kthread after
lock has gained. Just double-checked.

> But if it is not called periodically by dvb-core, you could not use it 
> for bit error measurements, as you will usually need to start 
> measurement, then wait complete, read values and return.

Probably, the application you're using for tests are calling it
periodically.

What the core calls periodically for sure is read_status(). That's why
most drivers that provide DVBv5 stats hook the cache update there.

> Signal strength and SNR are typically provided by chip without any waiting.
> 
> regards
> Antti
> 

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

* Re: [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5
  2014-09-06  2:51             ` Mauro Carvalho Chehab
  2014-09-06  2:54               ` Mauro Carvalho Chehab
@ 2014-09-06 16:24               ` Malcolm Priestley
  2014-09-06 16:31                 ` Malcolm Priestley
  2014-09-06 21:37                 ` Malcolm Priestley
  1 sibling, 2 replies; 40+ messages in thread
From: Malcolm Priestley @ 2014-09-06 16:24 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Antti Palosaari; +Cc: Akihiro TSUKADA, linux-media

On 06/09/14 03:51, Mauro Carvalho Chehab wrote:
> Em Sat, 06 Sep 2014 05:09:55 +0300
> Antti Palosaari <crope@iki.fi> escreveu:
>
>> Moro!
>>
>> On 08/29/2014 01:45 PM, Akihiro TSUKADA wrote:
>>> moikka,
>>>
>>>> Start polling thread, which polls once per 2 sec or so, which reads RSSI
>>>> and writes value to struct dtv_frontend_properties. That it is, in my
>>>> understanding. Same for all those DVBv5 stats. Mauro knows better as he
>>>> designed that functionality.
>>>
>>> I understand that RSSI property should be set directly in the tuner driver,
>>> but I'm afraid that creating a kthread just for updating RSSI would be
>>> overkill and complicate matters.
>>>
>>> Would you give me an advice? >> Mauro
>>
>> Now I know that as I implement it. I added kthread and it works
>> correctly, just I though it is aimed to work. In my case signal strength
>> is reported by demod, not tuner, because there is some logic in firmware
>> to calculate it.
>>
>> Here is patches you would like to look as a example:
>>
>> af9033: implement DVBv5 statistic for signal strength
>> https://patchwork.linuxtv.org/patch/25748/
>
> Actually, you don't need to add a separate kthread to collect the stats.
> The DVB frontend core already has a thread that calls the frontend status
> on every 3 seconds (the time can actually be different, depending on
> the value for fepriv->delay. So, if the device doesn't have any issues
> on getting stats on this period, it could just hook the DVBv5 stats logic
> at ops.read_status().
>

Hmm, fepriv->delay missed that one, 3 seconds is far too long for lmedm04.

It would be good to hook stats on to this thread.

Regards


Malcolm







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

* Re: [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5
  2014-09-06 16:24               ` Malcolm Priestley
@ 2014-09-06 16:31                 ` Malcolm Priestley
  2014-09-06 21:37                 ` Malcolm Priestley
  1 sibling, 0 replies; 40+ messages in thread
From: Malcolm Priestley @ 2014-09-06 16:31 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Antti Palosaari; +Cc: Akihiro TSUKADA, linux-media

On 06/09/14 17:24, Malcolm Priestley wrote:
> On 06/09/14 03:51, Mauro Carvalho Chehab wrote:
>> Em Sat, 06 Sep 2014 05:09:55 +0300
>> Antti Palosaari <crope@iki.fi> escreveu:
>>
>>> Moro!
>>>
>>> On 08/29/2014 01:45 PM, Akihiro TSUKADA wrote:
>>>> moikka,
>>>>
>>>>> Start polling thread, which polls once per 2 sec or so, which reads
>>>>> RSSI
>>>>> and writes value to struct dtv_frontend_properties. That it is, in my
>>>>> understanding. Same for all those DVBv5 stats. Mauro knows better
>>>>> as he
>>>>> designed that functionality.
>>>>
>>>> I understand that RSSI property should be set directly in the tuner
>>>> driver,
>>>> but I'm afraid that creating a kthread just for updating RSSI would be
>>>> overkill and complicate matters.
>>>>
>>>> Would you give me an advice? >> Mauro
>>>
>>> Now I know that as I implement it. I added kthread and it works
>>> correctly, just I though it is aimed to work. In my case signal strength
>>> is reported by demod, not tuner, because there is some logic in firmware
>>> to calculate it.
>>>
>>> Here is patches you would like to look as a example:
>>>
>>> af9033: implement DVBv5 statistic for signal strength
>>> https://patchwork.linuxtv.org/patch/25748/
>>
>> Actually, you don't need to add a separate kthread to collect the stats.
>> The DVB frontend core already has a thread that calls the frontend status
>> on every 3 seconds (the time can actually be different, depending on
>> the value for fepriv->delay. So, if the device doesn't have any issues
>> on getting stats on this period, it could just hook the DVBv5 stats logic
>> at ops.read_status().
>>
>
> Hmm, fepriv->delay missed that one, 3 seconds is far too long for lmedm04.
>
> It would be good to hook stats on to this thread.
optional that is.

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

* Re: [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator
  2014-09-06  6:51                 ` Antti Palosaari
@ 2014-09-06 19:35                   ` Akihiro TSUKADA
  2014-09-07  1:05                     ` Antti Palosaari
  0 siblings, 1 reply; 40+ messages in thread
From: Akihiro TSUKADA @ 2014-09-06 19:35 UTC (permalink / raw)
  To: Antti Palosaari, Mauro Carvalho Chehab; +Cc: linux-media, Matthias Schwarzott

moikka!,

> Basically it is 2 functions, af9035_add_i2c_dev() and af9035_del_i2c_dev()

I used request_module()/try_module_get()/module_put()
just like the above example (and bridge-core.c).
It works, but when I unload bridge driver(earth_pt3),
its demod and tuner modules stay loaded, with the refcount of 0.
Is it ok that the auto loaded modules remain with 0 ref count?

> Yet, using config to OUT seems to be bit hacky for my eyes too. I though
> replacing all OUT with ops when converted af9033 driver. Currently
> caller fills struct af9033_ops and then af9033 I2C probe populates ops.
> See that patch:
> https://patchwork.linuxtv.org/patch/25746/
> 
> Does this kind of ops sounds any better?

Do you mean using ops in struct config?
if so, I don't find much difference with the current situation
where demod/tuner probe() sets dvb_frontend* to config->fe. 

> I quickly overlooked that demod driver and one which looked crazy was
> LNA stuff. You implement set_lna callback in demod, but it is then
> passed back to PCI driver using frontend callback. Is there some reason
> you hooked it via demod? You could implement set_lna in PCI driver too.

Stupidly I forgot that FE's ops can be set from the PCI driver.
I will remove those callbacks and set the corresponding ops instead.

regards,
akihiro


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

* Re: [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5
  2014-09-06 16:24               ` Malcolm Priestley
  2014-09-06 16:31                 ` Malcolm Priestley
@ 2014-09-06 21:37                 ` Malcolm Priestley
  2014-09-06 22:37                   ` Mauro Carvalho Chehab
  1 sibling, 1 reply; 40+ messages in thread
From: Malcolm Priestley @ 2014-09-06 21:37 UTC (permalink / raw)
  To: Mauro Carvalho Chehab; +Cc: Antti Palosaari, Akihiro TSUKADA, linux-media

On 06/09/14 17:24, Malcolm Priestley wrote:
> On 06/09/14 03:51, Mauro Carvalho Chehab wrote:
>> Em Sat, 06 Sep 2014 05:09:55 +0300
>> Antti Palosaari <crope@iki.fi> escreveu:
>>
>>> Moro!
>>>
>>> On 08/29/2014 01:45 PM, Akihiro TSUKADA wrote:
>>>> moikka,
>>>>
>>>>> Start polling thread, which polls once per 2 sec or so, which reads
>>>>> RSSI
>>>>> and writes value to struct dtv_frontend_properties. That it is, in my
>>>>> understanding. Same for all those DVBv5 stats. Mauro knows better
>>>>> as he
>>>>> designed that functionality.
>>>>
>>>> I understand that RSSI property should be set directly in the tuner
>>>> driver,
>>>> but I'm afraid that creating a kthread just for updating RSSI would be
>>>> overkill and complicate matters.
>>>>
>>>> Would you give me an advice? >> Mauro
>>>
>>> Now I know that as I implement it. I added kthread and it works
>>> correctly, just I though it is aimed to work. In my case signal strength
>>> is reported by demod, not tuner, because there is some logic in firmware
>>> to calculate it.
>>>
>>> Here is patches you would like to look as a example:
>>>
>>> af9033: implement DVBv5 statistic for signal strength
>>> https://patchwork.linuxtv.org/patch/25748/
>>
>> Actually, you don't need to add a separate kthread to collect the stats.
>> The DVB frontend core already has a thread that calls the frontend status
>> on every 3 seconds (the time can actually be different, depending on
>> the value for fepriv->delay. So, if the device doesn't have any issues
>> on getting stats on this period, it could just hook the DVBv5 stats logic
>> at ops.read_status().
>>
>
> Hmm, fepriv->delay missed that one, 3 seconds is far too long for lmedm04.

The only way change this is by using algo DVBFE_ALGO_HW using the 
frontend ops tune.

As most frontends are using dvb_frontend_swzigzag it could be 
implemented by patching the frontend ops tune code at the lock
return in this function or in dvb_frontend_swzigzag_update_delay.

Regards

Malcolm

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

* Re: [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5
  2014-09-06 21:37                 ` Malcolm Priestley
@ 2014-09-06 22:37                   ` Mauro Carvalho Chehab
  2014-09-06 23:38                     ` Antti Palosaari
  0 siblings, 1 reply; 40+ messages in thread
From: Mauro Carvalho Chehab @ 2014-09-06 22:37 UTC (permalink / raw)
  To: Malcolm Priestley; +Cc: Antti Palosaari, Akihiro TSUKADA, linux-media

Em Sat, 06 Sep 2014 22:37:21 +0100
Malcolm Priestley <tvboxspy@gmail.com> escreveu:

> On 06/09/14 17:24, Malcolm Priestley wrote:
> > On 06/09/14 03:51, Mauro Carvalho Chehab wrote:
> >> Em Sat, 06 Sep 2014 05:09:55 +0300
> >> Antti Palosaari <crope@iki.fi> escreveu:
> >>
> >>> Moro!
> >>>
> >>> On 08/29/2014 01:45 PM, Akihiro TSUKADA wrote:
> >>>> moikka,
> >>>>
> >>>>> Start polling thread, which polls once per 2 sec or so, which reads
> >>>>> RSSI
> >>>>> and writes value to struct dtv_frontend_properties. That it is, in my
> >>>>> understanding. Same for all those DVBv5 stats. Mauro knows better
> >>>>> as he
> >>>>> designed that functionality.
> >>>>
> >>>> I understand that RSSI property should be set directly in the tuner
> >>>> driver,
> >>>> but I'm afraid that creating a kthread just for updating RSSI would be
> >>>> overkill and complicate matters.
> >>>>
> >>>> Would you give me an advice? >> Mauro
> >>>
> >>> Now I know that as I implement it. I added kthread and it works
> >>> correctly, just I though it is aimed to work. In my case signal strength
> >>> is reported by demod, not tuner, because there is some logic in firmware
> >>> to calculate it.
> >>>
> >>> Here is patches you would like to look as a example:
> >>>
> >>> af9033: implement DVBv5 statistic for signal strength
> >>> https://patchwork.linuxtv.org/patch/25748/
> >>
> >> Actually, you don't need to add a separate kthread to collect the stats.
> >> The DVB frontend core already has a thread that calls the frontend status
> >> on every 3 seconds (the time can actually be different, depending on
> >> the value for fepriv->delay. So, if the device doesn't have any issues
> >> on getting stats on this period, it could just hook the DVBv5 stats logic
> >> at ops.read_status().
> >>
> >
> > Hmm, fepriv->delay missed that one, 3 seconds is far too long for lmedm04.
> 
> The only way change this is by using algo DVBFE_ALGO_HW using the 
> frontend ops tune.
> 
> As most frontends are using dvb_frontend_swzigzag it could be 
> implemented by patching the frontend ops tune code at the lock
> return in this function or in dvb_frontend_swzigzag_update_delay.

Well, if a different value is needed, it shouldn't be hard to add a
way to customize it, letting the demod to specify it, in the same way
as fe->ops.info.frequency_stepsize (and other similar demot properties)
are passed through the core.

Regards,
Mauro

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

* Re: [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5
  2014-09-06 22:37                   ` Mauro Carvalho Chehab
@ 2014-09-06 23:38                     ` Antti Palosaari
  2014-09-07  9:35                       ` Malcolm Priestley
  0 siblings, 1 reply; 40+ messages in thread
From: Antti Palosaari @ 2014-09-06 23:38 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Malcolm Priestley; +Cc: Akihiro TSUKADA, linux-media



On 09/07/2014 01:37 AM, Mauro Carvalho Chehab wrote:
> Em Sat, 06 Sep 2014 22:37:21 +0100
> Malcolm Priestley <tvboxspy@gmail.com> escreveu:
>
>> On 06/09/14 17:24, Malcolm Priestley wrote:
>>> On 06/09/14 03:51, Mauro Carvalho Chehab wrote:
>>>> Em Sat, 06 Sep 2014 05:09:55 +0300
>>>> Antti Palosaari <crope@iki.fi> escreveu:
>>>>
>>>>> Moro!
>>>>>
>>>>> On 08/29/2014 01:45 PM, Akihiro TSUKADA wrote:
>>>>>> moikka,
>>>>>>
>>>>>>> Start polling thread, which polls once per 2 sec or so, which reads
>>>>>>> RSSI
>>>>>>> and writes value to struct dtv_frontend_properties. That it is, in my
>>>>>>> understanding. Same for all those DVBv5 stats. Mauro knows better
>>>>>>> as he
>>>>>>> designed that functionality.
>>>>>>
>>>>>> I understand that RSSI property should be set directly in the tuner
>>>>>> driver,
>>>>>> but I'm afraid that creating a kthread just for updating RSSI would be
>>>>>> overkill and complicate matters.
>>>>>>
>>>>>> Would you give me an advice? >> Mauro
>>>>>
>>>>> Now I know that as I implement it. I added kthread and it works
>>>>> correctly, just I though it is aimed to work. In my case signal strength
>>>>> is reported by demod, not tuner, because there is some logic in firmware
>>>>> to calculate it.
>>>>>
>>>>> Here is patches you would like to look as a example:
>>>>>
>>>>> af9033: implement DVBv5 statistic for signal strength
>>>>> https://patchwork.linuxtv.org/patch/25748/
>>>>
>>>> Actually, you don't need to add a separate kthread to collect the stats.
>>>> The DVB frontend core already has a thread that calls the frontend status
>>>> on every 3 seconds (the time can actually be different, depending on
>>>> the value for fepriv->delay. So, if the device doesn't have any issues
>>>> on getting stats on this period, it could just hook the DVBv5 stats logic
>>>> at ops.read_status().
>>>>
>>>
>>> Hmm, fepriv->delay missed that one, 3 seconds is far too long for lmedm04.
>>
>> The only way change this is by using algo DVBFE_ALGO_HW using the
>> frontend ops tune.
>>
>> As most frontends are using dvb_frontend_swzigzag it could be
>> implemented by patching the frontend ops tune code at the lock
>> return in this function or in dvb_frontend_swzigzag_update_delay.
>
> Well, if a different value is needed, it shouldn't be hard to add a
> way to customize it, letting the demod to specify it, in the same way
> as fe->ops.info.frequency_stepsize (and other similar demot properties)
> are passed through the core.

DVBFE_ALGO_SW, which is used normally, polls read_status rather rapidly. 
For statics problem is that it is too rapid, not that it is too slow. If 
you want re-use that as a timer for statistics, you could simply make 
own ratelimit very easily using kernel jiffies.

Not going to implement details, but here is skeleton, which is almost as 
many lines of code as actual implementation:

if (jiffies < jiffies_prev + 2 sec)
   return 0; // limit on
else
   jiffies_prev = jiffies;

... statistics polling code here.

regards
Antti

-- 
http://palosaari.fi/

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

* Re: [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator
  2014-09-06 19:35                   ` Akihiro TSUKADA
@ 2014-09-07  1:05                     ` Antti Palosaari
  0 siblings, 0 replies; 40+ messages in thread
From: Antti Palosaari @ 2014-09-07  1:05 UTC (permalink / raw)
  To: Akihiro TSUKADA, Mauro Carvalho Chehab; +Cc: linux-media, Matthias Schwarzott



On 09/06/2014 10:35 PM, Akihiro TSUKADA wrote:
> moikka!,
>
>> Basically it is 2 functions, af9035_add_i2c_dev() and af9035_del_i2c_dev()
>
> I used request_module()/try_module_get()/module_put()
> just like the above example (and bridge-core.c).
> It works, but when I unload bridge driver(earth_pt3),
> its demod and tuner modules stay loaded, with the refcount of 0.
> Is it ok that the auto loaded modules remain with 0 ref count?

So there is no other problem than those modules were left loaded? If you 
could unload those using rmmod it is OK then. Ref counting is here to 
prevent unloading demod and tuner driver while those are used by some 
other module. So when bridge is loaded, you should not be able to unload 
demod or tuner. But when bridge is unloaded, you should be able to 
unload demod and tuner.

And your question, I think there is no way to unload modules 
automatically or at least no need.

>> Yet, using config to OUT seems to be bit hacky for my eyes too. I though
>> replacing all OUT with ops when converted af9033 driver. Currently
>> caller fills struct af9033_ops and then af9033 I2C probe populates ops.
>> See that patch:
>> https://patchwork.linuxtv.org/patch/25746/
>>
>> Does this kind of ops sounds any better?
>
> Do you mean using ops in struct config?
> if so, I don't find much difference with the current situation
> where demod/tuner probe() sets dvb_frontend* to config->fe.

Alloc driver specific ops in bridge driver, then put pointer to that ops 
to config struct. Driver fills ops during probe. Maybe that patch clears 
the idea:
af9033: Don't export functions for the hardware filter
https://patchwork.linuxtv.org/patch/23087/

Functionality is not much different than passing pointer to frontend 
pointer from bridge to I2C demod driver via platform_data...

Somehow you will need to transfer data during driver loading and there 
is not many alternatives:
* platform data pointer
* driver data pointer
* export function
* i2c_clients_command (legacy)
* device ID string (not very suitable)
* + the rest from i2c client, not related at all

regards
Antti

-- 
http://palosaari.fi/

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

* Re: [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5
  2014-09-06 23:38                     ` Antti Palosaari
@ 2014-09-07  9:35                       ` Malcolm Priestley
  0 siblings, 0 replies; 40+ messages in thread
From: Malcolm Priestley @ 2014-09-07  9:35 UTC (permalink / raw)
  To: Antti Palosaari, Mauro Carvalho Chehab; +Cc: Akihiro TSUKADA, linux-media

On 07/09/14 00:38, Antti Palosaari wrote:
>
>
> On 09/07/2014 01:37 AM, Mauro Carvalho Chehab wrote:
>> Em Sat, 06 Sep 2014 22:37:21 +0100
>> Malcolm Priestley <tvboxspy@gmail.com> escreveu:
>>
>>> On 06/09/14 17:24, Malcolm Priestley wrote:
>>>> On 06/09/14 03:51, Mauro Carvalho Chehab wrote:
>>>>> Em Sat, 06 Sep 2014 05:09:55 +0300
>>>>> Antti Palosaari <crope@iki.fi> escreveu:
>>>>>
>>>>>> Moro!
>>>>>>
>>>>>> On 08/29/2014 01:45 PM, Akihiro TSUKADA wrote:
>>>>>>> moikka,
>>>>>>>
>>>>>>>> Start polling thread, which polls once per 2 sec or so, which reads
>>>>>>>> RSSI
>>>>>>>> and writes value to struct dtv_frontend_properties. That it is,
>>>>>>>> in my
>>>>>>>> understanding. Same for all those DVBv5 stats. Mauro knows better
>>>>>>>> as he
>>>>>>>> designed that functionality.
>>>>>>>
>>>>>>> I understand that RSSI property should be set directly in the tuner
>>>>>>> driver,
>>>>>>> but I'm afraid that creating a kthread just for updating RSSI
>>>>>>> would be
>>>>>>> overkill and complicate matters.
>>>>>>>
>>>>>>> Would you give me an advice? >> Mauro
>>>>>>
>>>>>> Now I know that as I implement it. I added kthread and it works
>>>>>> correctly, just I though it is aimed to work. In my case signal
>>>>>> strength
>>>>>> is reported by demod, not tuner, because there is some logic in
>>>>>> firmware
>>>>>> to calculate it.
>>>>>>
>>>>>> Here is patches you would like to look as a example:
>>>>>>
>>>>>> af9033: implement DVBv5 statistic for signal strength
>>>>>> https://patchwork.linuxtv.org/patch/25748/
>>>>>
>>>>> Actually, you don't need to add a separate kthread to collect the
>>>>> stats.
>>>>> The DVB frontend core already has a thread that calls the frontend
>>>>> status
>>>>> on every 3 seconds (the time can actually be different, depending on
>>>>> the value for fepriv->delay. So, if the device doesn't have any issues
>>>>> on getting stats on this period, it could just hook the DVBv5 stats
>>>>> logic
>>>>> at ops.read_status().
>>>>>
>>>>
>>>> Hmm, fepriv->delay missed that one, 3 seconds is far too long for
>>>> lmedm04.
>>>
>>> The only way change this is by using algo DVBFE_ALGO_HW using the
>>> frontend ops tune.
>>>
>>> As most frontends are using dvb_frontend_swzigzag it could be
>>> implemented by patching the frontend ops tune code at the lock
>>> return in this function or in dvb_frontend_swzigzag_update_delay.
>>
>> Well, if a different value is needed, it shouldn't be hard to add a
>> way to customize it, letting the demod to specify it, in the same way
>> as fe->ops.info.frequency_stepsize (and other similar demot properties)
>> are passed through the core.
>
> DVBFE_ALGO_SW, which is used normally, polls read_status rather rapidly.
> For statics problem is that it is too rapid, not that it is too slow. If
> you want re-use that as a timer for statistics, you could simply make
> own ratelimit very easily using kernel jiffies.
The default starts off at 50msec and gradually rises to around 950msec.

There is another way to set fepriv->delay that is in 
ops->get_tune_settings  dvb_frontend_tune_settings->min_delay_ms.

The delay increases by around 900msec on top of the value set there.

In the case of lmedm04/m88rs2000 that why I am seeing 3 sec.

Regards


Malcolm





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

end of thread, other threads:[~2014-09-07  9:35 UTC | newest]

Thread overview: 40+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2014-08-27 15:29 [PATCH v2 0/5] dvb: Add support for PT3 ISDB-S/T card tskd08
2014-08-27 15:29 ` [PATCH v2 1/5] dvb-core: add a new tuner ops to dvb_frontend for APIv5 tskd08
2014-08-27 18:09   ` Antti Palosaari
2014-08-28  9:07     ` Akihiro TSUKADA
2014-08-29  1:05       ` Antti Palosaari
2014-08-29 10:45         ` Akihiro TSUKADA
2014-09-06  2:09           ` Antti Palosaari
2014-09-06  2:51             ` Mauro Carvalho Chehab
2014-09-06  2:54               ` Mauro Carvalho Chehab
2014-09-06  3:10                 ` Antti Palosaari
2014-09-06  3:17                   ` Mauro Carvalho Chehab
2014-09-06  3:34                     ` Antti Palosaari
2014-09-06 12:35                       ` Mauro Carvalho Chehab
2014-09-06  4:08                     ` Akihiro TSUKADA
2014-09-06 10:36                       ` Antti Palosaari
2014-09-06 12:49                         ` Mauro Carvalho Chehab
2014-09-06 16:24               ` Malcolm Priestley
2014-09-06 16:31                 ` Malcolm Priestley
2014-09-06 21:37                 ` Malcolm Priestley
2014-09-06 22:37                   ` Mauro Carvalho Chehab
2014-09-06 23:38                     ` Antti Palosaari
2014-09-07  9:35                       ` Malcolm Priestley
2014-08-27 15:29 ` [PATCH v2 2/5] mxl301rf: add driver for MaxLinear MxL301RF OFDM tuner tskd08
2014-08-27 15:29 ` [PATCH v2 3/5] qm1d1c0042: add driver for Sharp QM1D1C0042 ISDB-S tuner tskd08
2014-08-27 15:29 ` [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator tskd08
2014-08-31 10:29   ` Matthias Schwarzott
2014-08-31 13:32     ` Akihiro TSUKADA
2014-08-31 19:48       ` Antti Palosaari
2014-09-01  9:54         ` Akihiro TSUKADA
2014-09-06  2:02           ` Antti Palosaari
2014-09-06  2:27             ` Mauro Carvalho Chehab
2014-09-06  3:00               ` DVB clock source (Re: [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator) Antti Palosaari
2014-09-06  3:11                 ` Mauro Carvalho Chehab
2014-09-06  3:20                   ` Antti Palosaari
2014-09-06  6:09               ` [PATCH v2 4/5] tc90522: add driver for Toshiba TC90522 quad demodulator Akihiro TSUKADA
2014-09-06  6:51                 ` Antti Palosaari
2014-09-06 19:35                   ` Akihiro TSUKADA
2014-09-07  1:05                     ` Antti Palosaari
2014-09-06  7:13                 ` Antti Palosaari
2014-08-27 15:29 ` [PATCH v2 5/5] pt3: add support for Earthsoft PT3 ISDB-S/T receiver card tskd08

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).