diff mbox

[v4,3/4] tc90522: add driver for Toshiba TC90522 quad demodulator

Message ID 1410196843-26168-4-git-send-email-tskd08@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Akihiro TSUKADA Sept. 8, 2014, 5:20 p.m. UTC
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 v4:
- removed cheking tuner lock in set_frontend()
- added a comment to notice incompleteness of the driver

 drivers/media/dvb-frontends/Kconfig   |   8 +
 drivers/media/dvb-frontends/Makefile  |   1 +
 drivers/media/dvb-frontends/tc90522.c | 839 ++++++++++++++++++++++++++++++++++
 drivers/media/dvb-frontends/tc90522.h |  42 ++
 4 files changed, 890 insertions(+)
 create mode 100644 drivers/media/dvb-frontends/tc90522.c
 create mode 100644 drivers/media/dvb-frontends/tc90522.h

Comments

Mauro Carvalho Chehab Sept. 23, 2014, 8:07 p.m. UTC | #1
Em Tue,  9 Sep 2014 02:20:42 +0900
tskd08@gmail.com escreveu:

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

I applied this series, as we're discussing it already for a long time,
and it seems in a good shape...

Yet, I got some minor issues with FE status. See below.

PS.: could you also test (and send us patches as needed) for ISDB-S
support at libdvbv5 and dvbv5-utils[1]?

[1] http://linuxtv.org/wiki/index.php/DVBv5_Tools

Thanks!
Mauro


> +static int tc90522s_read_status(struct dvb_frontend *fe, fe_status_t *status)
> +{
> +	struct tc90522_state *state;
> +	int ret;
> +	u8 reg;
> +
> +	state = fe->demodulator_priv;
> +	ret = reg_read(state, 0xc3, &reg, 1);
> +	if (ret < 0)
> +		return ret;
> +
> +	*status = 0;
> +	if (reg & 0x80) /* input level under min ? */
> +		return 0;
> +	*status |= FE_HAS_SIGNAL;
> +
> +	if (reg & 0x60) /* carrier? */
> +		return 0;

Sure about that? Wouldn't it be, instead, reg & 0x60 == 0x60?

> +	*status |= FE_HAS_CARRIER | FE_HAS_VITERBI | FE_HAS_SYNC;
> +
> +	if (reg & 0x10)
> +		return 0;
> +	if (reg_read(state, 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)
> +{
> +	struct tc90522_state *state;
> +	int ret;
> +	u8 reg;
> +
> +	state = fe->demodulator_priv;
> +	ret = reg_read(state, 0x96, &reg, 1);
> +	if (ret < 0)
> +		return ret;
> +
> +	*status = 0;
> +	if (reg & 0xe0) {
> +		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_VITERBI
> +				| FE_HAS_SYNC | FE_HAS_LOCK;
> +		return 0;
> +	}
> +
> +	ret = reg_read(state, 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;

The entire series of checks above seems wrong on my eyes too.

For example, if reg = 0x20 or 0x40 or 0x80 or ..., it will return
FE_HAS_LOCK.

Regards,
Mauro
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Akihiro TSUKADA Sept. 24, 2014, 3 a.m. UTC | #2
Hi,

On 2014?09?24? 05:07, Mauro Carvalho Chehab wrote:
> I applied this series, as we're discussing it already for a long time,
> and it seems in a good shape...

thanks for your reviews and advices.

>> +static int tc90522s_read_status(struct dvb_frontend *fe, fe_status_t *status)
...........
>> +	if (reg & 0x60) /* carrier? */
>> +		return 0;
> 
> Sure about that? Wouldn't it be, instead, reg & 0x60 == 0x60?

Yes, I'm pretty sure about that.
The register indicates errors in the various demod stages,
and if all go well, the reg should be 0.

>> +static int tc90522t_read_status(struct dvb_frontend *fe, fe_status_t *status)
..............
> The entire series of checks above seems wrong on my eyes too.
> 
> For example, if reg = 0x20 or 0x40 or 0x80 or ..., it will return
> FE_HAS_LOCK.

This register 0x96 should indicates "lock" status for each layers,
and since layer config can vary in ISDB-T, the driver checks that
any of the three bits is set, for faster lock detection.
and the register 0x80 is the same kind of the one in the above ISDB-S case.

> PS.: could you also test (and send us patches as needed) for ISDB-S
> support at libdvbv5 and dvbv5-utils[1]?
I'll have a try.

regards,
akihiro

  

--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Mauro Carvalho Chehab Sept. 24, 2014, 9:28 a.m. UTC | #3
Em Wed, 24 Sep 2014 12:00:53 +0900
Akihiro TSUKADA <tskd08@gmail.com> escreveu:

> 
> Hi,
> 
> On 2014?09?24? 05:07, Mauro Carvalho Chehab wrote:
> > I applied this series, as we're discussing it already for a long time,
> > and it seems in a good shape...
> 
> thanks for your reviews and advices.
> 
> >> +static int tc90522s_read_status(struct dvb_frontend *fe, fe_status_t *status)
> ...........
> >> +	if (reg & 0x60) /* carrier? */
> >> +		return 0;
> > 
> > Sure about that? Wouldn't it be, instead, reg & 0x60 == 0x60?
> 
> Yes, I'm pretty sure about that.
> The register indicates errors in the various demod stages,
> and if all go well, the reg should be 0.
> 
> >> +static int tc90522t_read_status(struct dvb_frontend *fe, fe_status_t *status)
> ..............
> > The entire series of checks above seems wrong on my eyes too.
> > 
> > For example, if reg = 0x20 or 0x40 or 0x80 or ..., it will return
> > FE_HAS_LOCK.
> 
> This register 0x96 should indicates "lock" status for each layers,
> and since layer config can vary in ISDB-T, the driver checks that
> any of the three bits is set, for faster lock detection.
> and the register 0x80 is the same kind of the one in the above ISDB-S case.

Ah, ok. It could be useful to document it, and eventually to add some
debug printk's that would print the locks for each layer. Such printk
were very useful for me to discover why dib8000 driver were not locking
to a layer encoded using interleave equal to 4:

commit 34ba2e65bab3693cdd7c2ee0b8ba6477bd8c366b
Author: Mauro Carvalho Chehab <m.chehab@samsung.com>
Date:   Fri Jul 4 14:15:27 2014 -0300

    [media] dib8000: Fix handling of interleave bigger than 2
    
    If interleave is bigger than 2, the code will set it to 0, as
    dib8000 registers use a log2(). So, change the code to handle
    it accordingly.
    
    Acked-By: Patrick Boettcher <pboettcher@kernellabs.com>
    Signed-off-by: Mauro Carvalho Chehab <m.chehab@samsung.com>


Btw, please check if your driver is handling it well, as the valid
values for interleave are 0, 1, 2, 4 (actually, dib8000 also
supports interleaving equal to 8, if sound_broadcast).

> 
> > PS.: could you also test (and send us patches as needed) for ISDB-S
> > support at libdvbv5 and dvbv5-utils[1]?
> I'll have a try.

Ok, thanks!

Regards,
Mauro
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Akihiro TSUKADA Sept. 24, 2014, 11:14 a.m. UTC | #4
> Btw, please check if your driver is handling it well, as the valid
> values for interleave are 0, 1, 2, 4 (actually, dib8000 also
> supports interleaving equal to 8, if sound_broadcast).

I have no info on how to set time interleaving parameters,
although I can read them as a part of TMCC info.
I guess I can also set them in the same registers, but I'm not sure.

Since the demod is intelligent and automatically reads TMCC and
sets the related parameters accordingly,
I think it is safe to ignore user settings and let the demod set it.
If someone would get more info in the future,
[s]he could set those parameters in advance and make a lock faster.

regards,
akihiro
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Mauro Carvalho Chehab Sept. 24, 2014, 11:30 a.m. UTC | #5
Em Wed, 24 Sep 2014 20:14:01 +0900
Akihiro TSUKADA <tskd08@gmail.com> escreveu:

> > Btw, please check if your driver is handling it well, as the valid
> > values for interleave are 0, 1, 2, 4 (actually, dib8000 also
> > supports interleaving equal to 8, if sound_broadcast).
> 
> I have no info on how to set time interleaving parameters,
> although I can read them as a part of TMCC info.

And TMCC returns it as 0, 1, 2, 3, 4, instead of 0, 1, 2, 4, 8.

> I guess I can also set them in the same registers, but I'm not sure.
> 
> Since the demod is intelligent and automatically reads TMCC and
> sets the related parameters accordingly,
> I think it is safe to ignore user settings and let the demod set it.

AFAIKT, that doesn't work in Sound Broadcast mode, as the demod won't
be able to read the TMCC (at least on most demods - don't have any
details about tc90522).

> If someone would get more info in the future,
> [s]he could set those parameters in advance and make a lock faster.
> 
> regards,
> akihiro
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Akihiro TSUKADA Sept. 24, 2014, 12:06 p.m. UTC | #6
> And TMCC returns it as 0, 1, 2, 3, 4, instead of 0, 1, 2, 4, 8.

According to ARIB STD B31 3.15.6.7 (table 3.30),
The value is encoded and depends on the "mode".
so demods have to encode/decode anyway.

> AFAIKT, that doesn't work in Sound Broadcast mode, as the demod won't
> be able to read the TMCC (at least on most demods - don't have any
> details about tc90522).

I forgat ISDB-Tsb;P, as in Japan terrestrial digital sound broadcast
was cancelled in 2011 and it was unpopular.

--
akihiro
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Mauro Carvalho Chehab Sept. 24, 2014, 1:32 p.m. UTC | #7
Em Wed, 24 Sep 2014 21:06:43 +0900
Akihiro TSUKADA <tskd08@gmail.com> escreveu:

> > And TMCC returns it as 0, 1, 2, 3, 4, instead of 0, 1, 2, 4, 8.
> 
> According to ARIB STD B31 3.15.6.7 (table 3.30),
> The value is encoded and depends on the "mode".
> so demods have to encode/decode anyway.

Ok, but see the definition at the API spec:

	http://linuxtv.org/downloads/v4l-dvb-apis/FE_GET_SET_PROPERTY.html#isdb-hierq-layers

Basically, we use 0, 1, 2, 4 no matter what mode, e. g.

DTV_ISDBT_LAYERA_TIME_INTERLEAVING	1

means:
	Interleave = 4 for mode 1
	Interleave = 2 for mode 2
	Interleave = 1 for mode 3

> > AFAIKT, that doesn't work in Sound Broadcast mode, as the demod won't
> > be able to read the TMCC (at least on most demods - don't have any
> > details about tc90522).
> 
> I forgat ISDB-Tsb;P, as in Japan terrestrial digital sound broadcast
> was cancelled in 2011 and it was unpopular.

Ah, I wasn't aware that ISDB-Tsb was cancelled in Japan.

Regards,
Mauro
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Akihiro TSUKADA Sept. 24, 2014, 2:26 p.m. UTC | #8
> Ok, but see the definition at the API spec:
> 
> 	http://linuxtv.org/downloads/v4l-dvb-apis/FE_GET_SET_PROPERTY.html#isdb-hierq-layers
> 
> Basically, we use 0, 1, 2, 4 no matter what mode, e. g.

I understand.
(though I wonder why "4" is assigned instead of "3"
when it is not a flag bit that can be OR'ed
 and a kind of "raw" values are used anyway).

> Ah, I wasn't aware that ISDB-Tsb was cancelled in Japan.

The band used by ISDB-Tsb is now assigned to ISDB-Tmm
(which is also very unpopular),
and there is a plan to use ISDB-Tsb in another freq band
in the future, but no concrete one yet.

--
akihiro
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

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..f4760dd
--- /dev/null
+++ b/drivers/media/dvb-frontends/tc90522.c
@@ -0,0 +1,839 @@ 
+/*
+ * 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.
+ */
+
+/*
+ * NOTICE:
+ * This driver is incomplete and lacks init/config of the chips,
+ * as the necessary info is not disclosed.
+ * It assumes that users of this driver (such as a PCI bridge of
+ * DTV receiver cards) properly init and configure the chip
+ * via I2C *before* calling this driver's init() function.
+ *
+ * Currently, PT3 driver is the only one that uses this driver,
+ * and contains init/config code in its firmware.
+ * Thus some part of the code might be dependent on PT3 specific config.
+ */
+
+#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)
+
+struct tc90522_state {
+	struct tc90522_config cfg;
+	struct dvb_frontend fe;
+	struct i2c_client *i2c_client;
+	struct i2c_adapter tuner_i2c;
+
+	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->i2c_client->addr;
+	msg.flags = 0;
+	msg.len = 2;
+	for (i = 0; i < num; i++) {
+		msg.buf = (u8 *)&regs[i];
+		ret = i2c_transfer(state->i2c_client->adapter, &msg, 1);
+		if (ret == 0)
+			ret = -EIO;
+		if (ret < 0)
+			return ret;
+	}
+	return 0;
+}
+
+static int reg_read(struct tc90522_state *state, u8 reg, u8 *val, u8 len)
+{
+	struct i2c_msg msgs[2] = {
+		{
+			.addr = state->i2c_client->addr,
+			.flags = 0,
+			.buf = &reg,
+			.len = 1,
+		},
+		{
+			.addr = state->i2c_client->addr,
+			.flags = I2C_M_RD,
+			.buf = val,
+			.len = len,
+		},
+	};
+	int ret;
+
+	ret = i2c_transfer(state->i2c_client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret == ARRAY_SIZE(msgs))
+		ret = 0;
+	else if (ret >= 0)
+		ret = -EIO;
+	return ret;
+}
+
+static struct tc90522_state *cfg_to_state(struct tc90522_config *c)
+{
+	return container_of(c, struct tc90522_state, cfg);
+}
+
+
+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)
+{
+	struct tc90522_state *state;
+	int ret;
+	u8 reg;
+
+	state = fe->demodulator_priv;
+	ret = reg_read(state, 0xc3, &reg, 1);
+	if (ret < 0)
+		return ret;
+
+	*status = 0;
+	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(state, 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)
+{
+	struct tc90522_state *state;
+	int ret;
+	u8 reg;
+
+	state = fe->demodulator_priv;
+	ret = reg_read(state, 0x96, &reg, 1);
+	if (ret < 0)
+		return ret;
+
+	*status = 0;
+	if (reg & 0xe0) {
+		*status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_VITERBI
+				| FE_HAS_SYNC | FE_HAS_LOCK;
+		return 0;
+	}
+
+	ret = reg_read(state, 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];
+	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;
+		u8 v;
+
+		/* 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;
+	/* let the connected tuner set RSSI property cache */
+	if (fe->ops.tuner_ops.get_rf_strength) {
+		u16 dummy;
+
+		fe->ops.tuner_ops.get_rf_strength(fe, &dummy);
+	}
+
+	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;
+	/* let the connected tuner set RSSI property cache */
+	if (fe->ops.tuner_ops.get_rf_strength) {
+		u16 dummy;
+
+		fe->ops.tuner_ops.get_rf_strength(fe, &dummy);
+	}
+
+	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 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;
+
+	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.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 agc_sat[] = {
+		{ 0x0a, 0x00 },
+		{ 0x10, 0x30 },
+		{ 0x11, 0x00 },
+		{ 0x03, 0x01 },
+	};
+	struct reg_val agc_ter[] = {
+		{ 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) {
+		agc_sat[0].val = on ? 0xff : 0x00;
+		agc_sat[1].val |= 0x80;
+		agc_sat[1].val |= on ? 0x01 : 0x00;
+		agc_sat[2].val |= on ? 0x40 : 0x00;
+		rv = agc_sat;
+		num = ARRAY_SIZE(agc_sat);
+	} else {
+		agc_ter[0].val = on ? 0x40 : 0x00;
+		agc_ter[1].val |= on ? 0x00 : 0x01;
+		rv = agc_ter;
+		num = ARRAY_SIZE(agc_ter);
+	}
+	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->ops.set_lna &&
+		    fe->dtv_property_cache.lna == LNA_AUTO) {
+			fe->dtv_property_cache.lna = 0;
+			ret = fe->ops.set_lna(fe);
+			fe->dtv_property_cache.lna = LNA_AUTO;
+		}
+	}
+	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 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->ops.set_lna &&
+		    fe->dtv_property_cache.lna == LNA_AUTO) {
+			fe->dtv_property_cache.lna = 1;
+			ret = fe->ops.set_lna(fe);
+			fe->dtv_property_cache.lna = LNA_AUTO;
+		}
+	}
+	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 adapter functions
+ */
+
+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;
+
+	if (num <= 0)
+		return -EINVAL;
+
+	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->i2c_client->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->i2c_client->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_client->adapter, new_msgs, j);
+	if (ret >= 0 && ret < j)
+		ret = -EIO;
+	kfree(new_msgs);
+	return (ret == j) ? num : 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,
+};
+
+
+/*
+ * I2C driver 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,
+	},
+
+	.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,
+};
+
+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,
+	},
+
+	.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,
+};
+
+
+static int tc90522_probe(struct i2c_client *client,
+			 const struct i2c_device_id *id)
+{
+	struct tc90522_state *state;
+	struct tc90522_config *cfg;
+	const struct dvb_frontend_ops *ops;
+	struct i2c_adapter *adap;
+	int ret;
+
+	state = kzalloc(sizeof(*state), GFP_KERNEL);
+	if (!state)
+		return -ENOMEM;
+	state->i2c_client = client;
+
+	cfg = client->dev.platform_data;
+	memcpy(&state->cfg, cfg, sizeof(state->cfg));
+	cfg->fe = state->cfg.fe = &state->fe;
+	ops =  id->driver_data == 0 ? &tc90522_ops_sat : &tc90522_ops_ter;
+	memcpy(&state->fe.ops, ops, sizeof(*ops));
+	state->fe.demodulator_priv = state;
+
+	adap = &state->tuner_i2c;
+	adap->owner = THIS_MODULE;
+	adap->algo = &tc90522_tuner_i2c_algo;
+	adap->dev.parent = &client->dev;
+	strlcpy(adap->name, "tc90522_sub", sizeof(adap->name));
+	i2c_set_adapdata(adap, state);
+	ret = i2c_add_adapter(adap);
+	if (ret < 0)
+		goto err;
+	cfg->tuner_i2c = state->cfg.tuner_i2c = adap;
+
+	i2c_set_clientdata(client, &state->cfg);
+	dev_info(&client->dev, "Toshiba TC90522 attached.\n");
+	return 0;
+
+err:
+	kfree(state);
+	return ret;
+}
+
+static int tc90522_remove(struct i2c_client *client)
+{
+	struct tc90522_state *state;
+
+	state = cfg_to_state(i2c_get_clientdata(client));
+	i2c_del_adapter(&state->tuner_i2c);
+	kfree(state);
+	return 0;
+}
+
+
+static const struct i2c_device_id tc90522_id[] = {
+	{ TC90522_I2C_DEV_SAT, 0 },
+	{ TC90522_I2C_DEV_TER, 1 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, tc90522_id);
+
+static struct i2c_driver tc90522_driver = {
+	.driver = {
+		.name	= "tc90522",
+	},
+	.probe		= tc90522_probe,
+	.remove		= tc90522_remove,
+	.id_table	= tc90522_id,
+};
+
+module_i2c_driver(tc90522_driver);
+
+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..b1cbddf
--- /dev/null
+++ b/drivers/media/dvb-frontends/tc90522.h
@@ -0,0 +1,42 @@ 
+/*
+ * 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 "dvb_frontend.h"
+
+/* I2C device types */
+#define TC90522_I2C_DEV_SAT "tc90522sat"
+#define TC90522_I2C_DEV_TER "tc90522ter"
+
+struct tc90522_config {
+	/* [OUT] frontend returned by driver */
+	struct dvb_frontend *fe;
+
+	/* [OUT] tuner I2C adapter returned by driver */
+	struct i2c_adapter *tuner_i2c;
+};
+
+#endif /* TC90522_H */