diff mbox

Support for Legend Silicon LGS8913/LGS8GL5/LGS8GXX China DMB-TH digital demodulator

Message ID 15ed362e0903170855k2ec1e5afm613de692c237e34d@mail.gmail.com (mailing list archive)
State Changes Requested
Headers show

Commit Message

David Wong March 17, 2009, 3:55 p.m. UTC
This patch contains the unified driver for Legend Silicon LGS8913 and
LGS8GL5. It should replace lgs8gl5.c in media/dvb/frontends

David T.L. Wong

Comments

Mauro Carvalho Chehab March 27, 2009, 9:57 a.m. UTC | #1
On Tue, 17 Mar 2009 23:55:05 +0800
David Wong <davidtlwong@gmail.com> wrote:

> +#undef USE_FAKE_SIGNAL_STRENGTH

Hmm... why do you need this upstream? Is the signal strength working? If so,
just remove this test code.

> +
> +static void lgs8gxx_auto_lock(struct lgs8gxx_state *priv);

I don't see why do you need to prototype this function.

> +
> +static int debug = 0;

Don't initialize static vars to zero. Kernel already does this, and static
initialization requires eats some space.

> +static int lgs8gxx_set_fe(struct dvb_frontend *fe,
> +			  struct dvb_frontend_parameters *fe_params)
> +{
> +	struct lgs8gxx_state *priv = fe->demodulator_priv;
> +
> +	dprintk("%s\n", __func__);
> +
> +	/* set frequency */
> +	if (fe->ops.tuner_ops.set_params) {
> +		fe->ops.tuner_ops.set_params(fe, fe_params);
> +		if (fe->ops.i2c_gate_ctrl)
> +			fe->ops.i2c_gate_ctrl(fe, 0);
> +	}
> +
> +	/* Hardcoded to use auto as much as possible */
> +	fe_params->u.ofdm.code_rate_HP = FEC_AUTO;
> +	fe_params->u.ofdm.guard_interval = GUARD_INTERVAL_AUTO;
> +	fe_params->u.ofdm.transmission_mode = TRANSMISSION_MODE_AUTO;

Hmm... this is weird.

That's said, maybe you may need some DVBS2 API additions for DMB. You should
propose some API additions and provide a patch for it.

> +	/* FEC. No exact match for DMB-TH, pick approx. value */
> +	switch(t & LGS_FEC_MASK) {
> +	case  LGS_FEC_0_4: /* FEC 0.4 */
> +		translated_fec = FEC_1_2;
> +		break;
> +	case  LGS_FEC_0_6: /* FEC 0.6 */
> +		translated_fec = FEC_2_3;
> +		break;
> +	case  LGS_FEC_0_8: /* FEC 0.8 */
> +		translated_fec = FEC_5_6;
> +		break;
> +	default:
> +		translated_fec = FEC_1_2;
> +	}

Same here: if there's no exact match, we should first patch the core files to
improve the API, and then use the correct values.

> +	fe_params->u.ofdm.code_rate_HP =
> +	fe_params->u.ofdm.code_rate_LP = translated_fec;

The above seems weird. It would be better to do:

+	fe_params->u.ofdm.code_rate_HP = translated_fec;
+	fe_params->u.ofdm.code_rate_LP = translated_fec;

The gcc optimizer will produce the same code, but this way would be cleaner for
those who are reading the source code.

> +static
> +int lgs8gxx_get_tune_settings(struct dvb_frontend *fe,
> +			      struct dvb_frontend_tune_settings *fesettings)
> +{
> +	/* FIXME: copy from tda1004x.c */

It would be nice if you fix those FIXME's.

> +	fesettings->min_delay_ms = 800;
> +	/* Drift compensation makes no sense for DVB-T */

DVB-T???

> +static int lgs8gxx_read_snr(struct dvb_frontend *fe, u16 *snr)
> +{
> +	struct lgs8gxx_state *priv = fe->demodulator_priv;
> +	u8 t;
> +	*snr = 0;
> +
> +	lgs8gxx_read_reg(priv, 0x95, &t);
> +	dprintk("AVG Noise=0x%02X\n", t);
> +	*snr = 256 - t;
> +	*snr <<= 8;
> +	dprintk("snr=0x%x\n", *snr);
> +	
> +	return 0;
> +}

I dunno if you are following all those discussions about SNR. We're trying to
standardize the meaning for all those status reads (SNR, signal strength, etc.

Nothing were decided yet, but while we don't take a decision, the better is if
you provide some comments at the source code specifying what's the unit for
each of those status (dB? 0.1 dB steps? dB * 256 ?).

> +static struct dvb_frontend_ops lgs8gxx_ops = {
> +	.info = {
> +		.name = "Legend Silicon LGS8913/LGS8GXX DMB-TH",
> +		.type = FE_OFDM,
> +		.frequency_min = 474000000,
> +		.frequency_max = 858000000,
> +		.frequency_stepsize = 10000,
> +		.caps =
> +		    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
> +	},

Also here we should reflect the proper DMB parameters, after the API additions.

--- 

Before submitting patches, please check they with checkpatch.pl ( see
http://linuxtv.org/hg/v4l-dvb/raw-file/tip/README.patches for the submission
procedures). 

Please fix the CodingStyle errors detected by the tool:


ERROR: do not initialise statics to 0 or NULL
#91: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:43:
+static int debug = 0;

WARNING: printk() should include KERN_ facility level
#145: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:97:
+		printk("%s: reg=0x%02X, data=0x%02X\n", __func__, reg, b1[0]);

ERROR: do not use C99 // comments
#164: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:116:
+	if_conf = 0x10; // AGC output on;

ERROR: spaces required around that ':' (ctx:VxV)
#167: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:119:
+		((config->ext_adc) ? 0x80:0x00) |
 		                         ^

ERROR: spaces required around that ':' (ctx:VxV)
#168: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:120:
+		((config->if_neg_center) ? 0x04:0x00) |
 		                               ^

ERROR: spaces required around that ':' (ctx:VxV)
#169: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:121:
+		((config->if_freq == 0) ? 0x08:0x00) | /* Baseband */
 		                              ^

ERROR: spaces required around that ':' (ctx:VxV)
#170: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:122:
+		((config->ext_adc && config->adc_signed) ? 0x02:0x00) |
 		                                               ^

ERROR: spaces required around that ':' (ctx:VxV)
#171: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:123:
+		((config->ext_adc && config->if_neg_edge) ? 0x01:0x00);
 		                                                ^

WARNING: braces {} are not necessary for single statement blocks
#216: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:168:
+	if (priv->config->prod == LGS8GXX_PROD_LGS8913) {
+		lgs8gxx_write_reg(priv, 0xC6, 0x01);
+	}

ERROR: do not use C99 // comments
#223: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:175:
+	// clear FEC self reset

WARNING: braces {} are not necessary for single statement blocks
#244: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:196:
+	if (priv->config->prod == LGS8GXX_PROD_LGS8G52) {
+		lgs8gxx_write_reg(priv, 0xD9, 0x40);
+	}

ERROR: trailing whitespace
#300: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:252:
+^Iint err; $

ERROR: space required after that ',' (ctx:VxV)
#327: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:279:
+	int i,j;
 	     ^

ERROR: spaces required around that '=' (ctx:WxV)
#338: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:290:
+		for (j =0 ; j < 2; j++) {
 		       ^

ERROR: trailing statements should be on next line
#341: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:293:
+			if (err) goto out;
+			if (err) goto out;
ERROR: trailing statements should be on next line
#342: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:294:
+			if (locked) goto locked;
+			if (locked) goto locked;
ERROR: spaces required around that '=' (ctx:WxV)
#344: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:296:
+		for (j =0 ; j < 2; j++) {
 		       ^

ERROR: trailing statements should be on next line
#347: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:299:
+			if (err) goto out;
+			if (err) goto out;
ERROR: trailing statements should be on next line
#348: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:300:
+			if (locked) goto locked;
+			if (locked) goto locked;
ERROR: trailing statements should be on next line
#352: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:304:
+		if (err) goto out;
+		if (err) goto out;
ERROR: trailing statements should be on next line
#353: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:305:
+		if (locked) goto locked;
+		if (locked) goto locked;
ERROR: do not use C99 // comments
#381: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:333:
+	//u8 ctrl_frame = 0, mode = 0, rate = 0;

ERROR: trailing whitespace
#395: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:347:
+^I$

WARNING: braces {} are not necessary for single statement blocks
#404: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:356:
+	if (priv->config->prod == LGS8GXX_PROD_LGS8913) {
+		lgs8gxx_write_reg(priv, 0xC0, detected_param);
+	}

ERROR: do not use C99 // comments
#407: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:359:
+	//lgs8gxx_soft_reset(priv);

WARNING: suspect code indent for conditional statements (8, 8)
#412: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:364:
+	if (gi == 0x2)
+	switch(gi) {

ERROR: space required before the open parenthesis '('
#413: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:365:
+	switch(gi) {

ERROR: trailing whitespace
#467: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:419:
+^Ilgs8gxx_write_reg(priv, 0x2C, 0); $

WARNING: line over 80 characters
#477: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:429:
+	struct lgs8gxx_state *priv = (struct lgs8gxx_state *)fe->demodulator_priv;

WARNING: braces {} are not necessary for single statement blocks
#493: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:445:
+	if (config->prod == LGS8GXX_PROD_LGS8913) {
+		lgs8913_init(priv);
+	}

WARNING: suspect code indent for conditional statements (8, 8)
#550: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:502:
+	if ((fe_params->u.ofdm.code_rate_HP == FEC_AUTO) ||
[...]
+	} else {

ERROR: space required before the open parenthesis '('
#629: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:581:
+	switch(t & LGS_FEC_MASK) {

ERROR: space required before the open parenthesis '('
#646: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:598:
+	switch(t & SC_MASK) {

WARNING: line over 80 characters
#707: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:659:
+			*fe_status |= FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK;

ERROR: trailing whitespace
#724: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:676:
+^Idprintk("%s()\n", __func__);^I$

ERROR: space prohibited before that close parenthesis ')'
#734: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:686:
+	if (v < 0x100 )

ERROR: trailing whitespace
#748: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:700:
+^I^I$

ERROR: trailing whitespace
#818: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:770:
+^I$

ERROR: "foo* bar" should be "foo *bar"
#865: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:817:
+static int lgs8gxx_i2c_gate_ctrl(struct dvb_frontend* fe, int enable)

WARNING: braces {} are not necessary for any arm of this statement
#871: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:823:
+	if (enable) {
[...]
+	} else {
[...]

WARNING: line over 80 characters
#872: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:824:
+		return lgs8gxx_write_reg(priv, 0x01, 0x80 | priv->config->tuner_address);

ERROR: do not use C99 // comments
#896: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:848:
+	//.sleep = lgs8gxx_sleep,

ERROR: space required after that ',' (ctx:VxV)
#917: FILE: linux/drivers/media/dvb/frontends/lgs8gxx.c:869:
+	dprintk("%s()\n",__func__);
 	                ^

ERROR: trailing whitespace
#1111: FILE: linux/drivers/media/dvb/frontends/lgs8gxx_priv.h:58:
+#define GI_595^I0x01^I$



Cheers,
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
David Wong March 27, 2009, 1:02 p.m. UTC | #2
On Fri, Mar 27, 2009 at 5:57 PM, Mauro Carvalho Chehab
<mchehab@infradead.org> wrote:
> On Tue, 17 Mar 2009 23:55:05 +0800
> David Wong <davidtlwong@gmail.com> wrote:
>
>> +#undef USE_FAKE_SIGNAL_STRENGTH
>
> Hmm... why do you need this upstream? Is the signal strength working? If so,
> just remove this test code.

I don't know if I should remove the that signal strength code.
LGS8913 codes already use a very slow loop get get signal strength.
It loops from 1 to 915 (for 915 guard intervals), set a register and then read.
Such loop is very slow, that's why I add a fake signal strength.

for LGS8GL5 and newer chips, it reads two register to roughly get the
AGC output value.

seems LGS8913 can use the new method too. Perhaps we can remove the
fake signal strength code.

>
>> +
>> +static void lgs8gxx_auto_lock(struct lgs8gxx_state *priv);
>
> I don't see why do you need to prototype this function.

No problem, to be removed.

>
>> +
>> +static int debug = 0;
>
> Don't initialize static vars to zero. Kernel already does this, and static
> initialization requires eats some space.

No problem.

>
>> +static int lgs8gxx_set_fe(struct dvb_frontend *fe,
>> +                       struct dvb_frontend_parameters *fe_params)
>> +{
>> +     struct lgs8gxx_state *priv = fe->demodulator_priv;
>> +
>> +     dprintk("%s\n", __func__);
>> +
>> +     /* set frequency */
>> +     if (fe->ops.tuner_ops.set_params) {
>> +             fe->ops.tuner_ops.set_params(fe, fe_params);
>> +             if (fe->ops.i2c_gate_ctrl)
>> +                     fe->ops.i2c_gate_ctrl(fe, 0);
>> +     }
>> +
>> +     /* Hardcoded to use auto as much as possible */
>> +     fe_params->u.ofdm.code_rate_HP = FEC_AUTO;
>> +     fe_params->u.ofdm.guard_interval = GUARD_INTERVAL_AUTO;
>> +     fe_params->u.ofdm.transmission_mode = TRANSMISSION_MODE_AUTO;
>
> Hmm... this is weird.
>
> That's said, maybe you may need some DVBS2 API additions for DMB. You should
> propose some API additions and provide a patch for it.

That's the code copied from another frontend when I start the work.
But currently I would like to make it AUTO only.
Yes, I think there is a need for DMB-TH API.
FYI, DMB-TH is union of two modes, single carrier and multi-carrier.
The multi carrier mode is very DVB-T 8MHz alike.
The single carrier mode, I guess, is ATSC like.
I am not very familiar with RF and DTV technology and there is no
single carrier mode broadcast in Hong Kong.
It is very welcome to open a new thread to discuss proposal for DMB-TH API

> +       fe_params->u.ofdm.code_rate_HP = translated_fec;
> +       fe_params->u.ofdm.code_rate_LP = translated_fec;
>
> The gcc optimizer will produce the same code, but this way would be cleaner for
> those who are reading the source code.

OK.

>
>> +static
>> +int lgs8gxx_get_tune_settings(struct dvb_frontend *fe,
>> +                           struct dvb_frontend_tune_settings *fesettings)
>> +{
>> +     /* FIXME: copy from tda1004x.c */
>
> It would be nice if you fix those FIXME's.
>
>> +     fesettings->min_delay_ms = 800;
>> +     /* Drift compensation makes no sense for DVB-T */
>
> DVB-T???

That's the code copy from tda1004x. What is that delay for?

>
>> +static int lgs8gxx_read_snr(struct dvb_frontend *fe, u16 *snr)
>> +{
>> +     struct lgs8gxx_state *priv = fe->demodulator_priv;
>> +     u8 t;
>> +     *snr = 0;
>> +
>> +     lgs8gxx_read_reg(priv, 0x95, &t);
>> +     dprintk("AVG Noise=0x%02X\n", t);
>> +     *snr = 256 - t;
>> +     *snr <<= 8;
>> +     dprintk("snr=0x%x\n", *snr);
>> +
>> +     return 0;
>> +}
>
> I dunno if you are following all those discussions about SNR. We're trying to
> standardize the meaning for all those status reads (SNR, signal strength, etc.
>
> Nothing were decided yet, but while we don't take a decision, the better is if
> you provide some comments at the source code specifying what's the unit for
> each of those status (dB? 0.1 dB steps? dB * 256 ?).

Yes, I read your SNR discussion.
The register read is called average noise magnitude, but I don't know the unit.
There is no description from vendor.

>
>> +static struct dvb_frontend_ops lgs8gxx_ops = {
>> +     .info = {
>> +             .name = "Legend Silicon LGS8913/LGS8GXX DMB-TH",
>> +             .type = FE_OFDM,
>> +             .frequency_min = 474000000,
>> +             .frequency_max = 858000000,
>> +             .frequency_stepsize = 10000,
>> +             .caps =
>> +                 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
>> +     },
>
> Also here we should reflect the proper DMB parameters, after the API additions.
>
> ---
>
> Before submitting patches, please check they with checkpatch.pl ( see
> http://linuxtv.org/hg/v4l-dvb/raw-file/tip/README.patches for the submission
> procedures).
>
> Please fix the CodingStyle errors detected by the tool:
>
>

No problem.

Regards,
David
--
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
Devin Heitmueller March 31, 2009, 4:54 a.m. UTC | #3
On Tue, Mar 17, 2009 at 11:55 AM, David Wong <davidtlwong@gmail.com> wrote:
> This patch contains the unified driver for Legend Silicon LGS8913 and
> LGS8GL5. It should replace lgs8gl5.c in media/dvb/frontends
>
> David T.L. Wong

David,

The questions you posed tonight on a separate thread about making the
xc5000 work with this device prompts the question:

Do you know that this driver you submitted actually works?  Have you
successfully achieved lock with this driver and been able to view the
stream?

It is great to see the improvements and more generic support, but if
you don't have it working in at least one device, then it probably
shouldn't be submitted upstream yet, and it definitely should not be
replacing an existing driver.

Devin
David Wong March 31, 2009, 5:19 a.m. UTC | #4
Devin

The unified LGS8GXX driver surely work on some DMB-TH devices I have.
They are:
TECHGEAR HDTVC USB (also for MagicPro ProHDTV USB as they are the same
hardware with same USB ID)
ASUS U3100 Mini DMB-TH USB

Timothy Lee tested with this unified driver for his MagicPro ProHDTV USB too.
ASUS patch is sent to Alan Knowles, don't know his result yet.

David

On Tue, Mar 31, 2009 at 12:54 PM, Devin Heitmueller
<devin.heitmueller@gmail.com> wrote:
> On Tue, Mar 17, 2009 at 11:55 AM, David Wong <davidtlwong@gmail.com> wrote:
>> This patch contains the unified driver for Legend Silicon LGS8913 and
>> LGS8GL5. It should replace lgs8gl5.c in media/dvb/frontends
>>
>> David T.L. Wong
>
> David,
>
> The questions you posed tonight on a separate thread about making the
> xc5000 work with this device prompts the question:
>
> Do you know that this driver you submitted actually works?  Have you
> successfully achieved lock with this driver and been able to view the
> stream?
>
> It is great to see the improvements and more generic support, but if
> you don't have it working in at least one device, then it probably
> shouldn't be submitted upstream yet, and it definitely should not be
> replacing an existing driver.
>
> Devin
>
> --
> Devin J. Heitmueller
> http://www.devinheitmueller.com
> AIM: devinheitmueller
>
--
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 April 6, 2009, 5:44 p.m. UTC | #5
On Tue, 31 Mar 2009 00:54:49 -0400
Devin Heitmueller <devin.heitmueller@gmail.com> wrote:

> On Tue, Mar 17, 2009 at 11:55 AM, David Wong <davidtlwong@gmail.com> wrote:
> > This patch contains the unified driver for Legend Silicon LGS8913 and
> > LGS8GL5. It should replace lgs8gl5.c in media/dvb/frontends
> >
> > David T.L. Wong
> 
> David,
> 
> The questions you posed tonight on a separate thread about making the
> xc5000 work with this device prompts the question:
> 
> Do you know that this driver you submitted actually works?  Have you
> successfully achieved lock with this driver and been able to view the
> stream?
> 
> It is great to see the improvements and more generic support, but if
> you don't have it working in at least one device, then it probably
> shouldn't be submitted upstream yet, and it definitely should not be
> replacing an existing driver.

We need to do some tests before replacing the existing one. Yet, it is better
to have a generic device than specific ones. Do you have any card with lg8gl5?
If so, could you please test the new driver for us?

Anyway, I'm applying what we currently have.

Cheers,
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
Timothy Lee April 7, 2009, 12:54 a.m. UTC | #6
Dear Mauro,

I wrote the original lgs8gl5 driver by reverse-engineering my USB TV 
stick using UsbSnoop.

I've been working together with David to make sure his lgs8gxx driver 
works with my TV stick, so yes, the generic driver actually works.  :)

Regards,
Timothy Lee

On 04/07/2009 01:44 AM, Mauro Carvalho Chehab wrote:
> On Tue, 31 Mar 2009 00:54:49 -0400
> Devin Heitmueller<devin.heitmueller@gmail.com>  wrote:
>
>    
>> On Tue, Mar 17, 2009 at 11:55 AM, David Wong<davidtlwong@gmail.com>  wrote:
>>      
>>> This patch contains the unified driver for Legend Silicon LGS8913 and
>>> LGS8GL5. It should replace lgs8gl5.c in media/dvb/frontends
>>>
>>> David T.L. Wong
>>>        
>> David,
>>
>> The questions you posed tonight on a separate thread about making the
>> xc5000 work with this device prompts the question:
>>
>> Do you know that this driver you submitted actually works?  Have you
>> successfully achieved lock with this driver and been able to view the
>> stream?
>>
>> It is great to see the improvements and more generic support, but if
>> you don't have it working in at least one device, then it probably
>> shouldn't be submitted upstream yet, and it definitely should not be
>> replacing an existing driver.
>>      
> We need to do some tests before replacing the existing one. Yet, it is better
> to have a generic device than specific ones. Do you have any card with lg8gl5?
> If so, could you please test the new driver for us?
>
> Anyway, I'm applying what we currently have.
>
> Cheers,
> 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
>    

--
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 April 7, 2009, 2:17 a.m. UTC | #7
On Tue, 07 Apr 2009 08:54:04 +0800
Timothy Lee <timothy.lee@siriushk.com> wrote:

> Dear Mauro,
> 
> I wrote the original lgs8gl5 driver by reverse-engineering my USB TV 
> stick using UsbSnoop.
> 
> I've been working together with David to make sure his lgs8gxx driver 
> works with my TV stick, so yes, the generic driver actually works.  :)

Great! You should sign a patch together, removing the legacy module and using
the newer one instead. It would be better to have this merged at the
development tree for a while, to be sure that this won't cause regressions.

Cheers,
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
diff mbox

Patch

diff -r 626c136ec221 linux/drivers/media/dvb/frontends/Kconfig
--- a/linux/drivers/media/dvb/frontends/Kconfig	Fri Mar 13 14:35:14 2009 -0700
+++ b/linux/drivers/media/dvb/frontends/Kconfig	Tue Mar 17 23:07:57 2009 +0800
@@ -513,6 +513,13 @@ 
 	help
 	  A DMB-TH tuner module. Say Y when you want to support this frontend.
 
+config DVB_LGS8GXX
+	tristate "Legend Silicon LGS8913/LGS8GL5/LGS8GXX DMB-TH demodulator"
+	depends on DVB_CORE && I2C
+	default m if DVB_FE_CUSTOMISE
+	help
+	  A DMB-TH tuner module. Say Y when you want to support this frontend.
+
 comment "Tools to develop new frontends"
 
 config DVB_DUMMY_FE
diff -r 626c136ec221 linux/drivers/media/dvb/frontends/Makefile
--- a/linux/drivers/media/dvb/frontends/Makefile	Fri Mar 13 14:35:14 2009 -0700
+++ b/linux/drivers/media/dvb/frontends/Makefile	Tue Mar 17 23:07:57 2009 +0800
@@ -60,6 +60,7 @@ 
 obj-$(CONFIG_DVB_TUNER_CX24113) += cx24113.o
 obj-$(CONFIG_DVB_S5H1411) += s5h1411.o
 obj-$(CONFIG_DVB_LGS8GL5) += lgs8gl5.o
+obj-$(CONFIG_DVB_LGS8GXX) += lgs8gxx.o
 obj-$(CONFIG_DVB_DUMMY_FE) += dvb_dummy_fe.o
 obj-$(CONFIG_DVB_AF9013) += af9013.o
 obj-$(CONFIG_DVB_CX24116) += cx24116.o
diff -r 626c136ec221 linux/drivers/media/dvb/frontends/lgs8gxx.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/linux/drivers/media/dvb/frontends/lgs8gxx.c	Tue Mar 17 23:07:57 2009 +0800
@@ -0,0 +1,907 @@ 
+/*
+ *    Support for Legend Silicon DMB-TH demodulator
+ *    LGS8913, LGS8GL5
+ *    experimental support LGS8G42, LGS8G52
+ *
+ *    Copyright (C) 2007,2008 David T.L. Wong <davidtlwong@gmail.com>
+ *    Copyright (C) 2008 Sirius International (Hong Kong) Limited
+ *    Timothy Lee <timothy.lee@siriushk.com> (for initial work on LGS8GL5)
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License
+ *    along with this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <asm/div64.h>
+
+#include "dvb_frontend.h"
+
+#include "lgs8gxx.h"
+#include "lgs8gxx_priv.h"
+
+#define dprintk(args...) \
+	do { \
+		if (debug) \
+			printk(KERN_DEBUG "lgs8gxx: " args); \
+	} while (0)
+
+#undef USE_FAKE_SIGNAL_STRENGTH
+
+static void lgs8gxx_auto_lock(struct lgs8gxx_state *priv);
+
+static int debug = 0;
+
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off).");
+
+/* LGS8GXX internal helper functions */
+
+static int lgs8gxx_write_reg(struct lgs8gxx_state *priv, u8 reg, u8 data)
+{
+	int ret;
+	u8 buf[] = { reg, data };
+	struct i2c_msg msg = { .flags = 0, .buf = buf, .len = 2 };
+
+	msg.addr = priv->config->demod_address;
+	if (reg >= 0xC0)
+		msg.addr += 0x02;
+
+	if (debug >= 2)
+		printk("%s: reg=0x%02X, data=0x%02X\n", __func__, reg, data);
+
+	ret = i2c_transfer(priv->i2c, &msg, 1);
+
+	if (ret != 1)
+		dprintk("%s: error reg=0x%x, data=0x%x, ret=%i\n",
+			__func__, reg, data, ret);
+
+	return (ret != 1) ? -1 : 0;
+}
+
+static int lgs8gxx_read_reg(struct lgs8gxx_state *priv, u8 reg, u8 *p_data)
+{
+	int ret;
+	u8 dev_addr;
+
+	u8 b0[] = { reg };
+	u8 b1[] = { 0 };
+	struct i2c_msg msg[] = {
+		{ .flags = 0, .buf = b0, .len = 1 },
+		{ .flags = I2C_M_RD, .buf = b1, .len = 1 },
+	};
+
+	dev_addr = priv->config->demod_address;
+	if (reg >= 0xC0)
+		dev_addr += 0x02;
+	msg[1].addr =  msg[0].addr = dev_addr;
+
+	ret = i2c_transfer(priv->i2c, msg, 2);
+	if (ret != 2) {
+		dprintk("%s: error reg=0x%x, ret=%i\n", __func__, reg, ret);
+		return -1;
+	}
+
+	*p_data = b1[0];
+	if (debug >= 2)
+		printk("%s: reg=0x%02X, data=0x%02X\n", __func__, reg, b1[0]);
+	return 0;
+}
+
+static int lgs8gxx_soft_reset(struct lgs8gxx_state *priv)
+{
+	lgs8gxx_write_reg(priv, 0x02, 0x00);
+	msleep(1);
+	lgs8gxx_write_reg(priv, 0x02, 0x01);
+	msleep(100);
+
+	return 0;
+}
+
+static int lgs8gxx_set_ad_mode(struct lgs8gxx_state *priv)
+{
+	const struct lgs8gxx_config *config = priv->config;
+	u8 if_conf;
+
+	if_conf = 0x10; // AGC output on;
+
+	if_conf |=
+		((config->ext_adc) ? 0x80:0x00) |
+		((config->if_neg_center) ? 0x04:0x00) |
+		((config->if_freq == 0) ? 0x08:0x00) | /* Baseband */
+		((config->ext_adc && config->adc_signed) ? 0x02:0x00) |
+		((config->ext_adc && config->if_neg_edge) ? 0x01:0x00);
+
+	if (config->ext_adc &&
+		(config->prod == LGS8GXX_PROD_LGS8G52)) {
+		lgs8gxx_write_reg(priv, 0xBA, 0x40);
+	}
+
+	lgs8gxx_write_reg(priv, 0x07, if_conf);
+
+	return 0;
+}
+
+static int lgs8gxx_set_if_freq(struct lgs8gxx_state *priv, u32 freq /*in kHz*/)
+{
+	u64 val;
+	u32 v32;
+	u32 if_clk;
+
+	if_clk = priv->config->if_clk_freq;
+
+	val = freq;
+	if (freq != 0) {
+		val *= (u64)1 << 32;
+		if (if_clk != 0)
+			do_div(val, if_clk);
+		v32 = val & 0xFFFFFFFF;
+		dprintk("Set IF Freq to %dkHz\n", freq);
+	} else {
+		v32 = 0;
+		dprintk("Set IF Freq to baseband\n");
+	}
+	dprintk("AFC_INIT_FREQ = 0x%08X\n", v32);
+
+	lgs8gxx_write_reg(priv, 0x09, 0xFF & (v32));
+	lgs8gxx_write_reg(priv, 0x0A, 0xFF & (v32 >> 8));
+	lgs8gxx_write_reg(priv, 0x0B, 0xFF & (v32 >> 16));
+	lgs8gxx_write_reg(priv, 0x0C, 0xFF & (v32 >> 24));
+
+	return 0;
+}
+
+static int lgs8gxx_set_mode_auto(struct lgs8gxx_state *priv)
+{
+	u8 t;
+
+	if (priv->config->prod == LGS8GXX_PROD_LGS8913) {
+		lgs8gxx_write_reg(priv, 0xC6, 0x01);
+	}
+
+	lgs8gxx_read_reg(priv, 0x7E, &t);
+	lgs8gxx_write_reg(priv, 0x7E, t | 0x01);
+
+	// clear FEC self reset
+	lgs8gxx_read_reg(priv, 0xC5, &t);
+	lgs8gxx_write_reg(priv, 0xC5, t & 0xE0);
+
+	if (priv->config->prod == LGS8GXX_PROD_LGS8913) {
+		/* FEC auto detect */
+		lgs8gxx_write_reg(priv, 0xC1, 0x03);
+
+		lgs8gxx_read_reg(priv, 0x7C, &t);
+		t = (t & 0x8C) | 0x03;
+		lgs8gxx_write_reg(priv, 0x7C, t);
+	}
+
+
+	if (priv->config->prod == LGS8GXX_PROD_LGS8913) {
+		/* BER test mode */
+		lgs8gxx_read_reg(priv, 0xC3, &t);
+		t = (t & 0xEF) |  0x10;
+		lgs8gxx_write_reg(priv, 0xC3, t);
+	}
+
+	if (priv->config->prod == LGS8GXX_PROD_LGS8G52) {
+		lgs8gxx_write_reg(priv, 0xD9, 0x40);
+	}
+
+	return 0;
+}
+
+static int lgs8gxx_set_mode_manual(struct lgs8gxx_state *priv)
+{
+	int ret = 0;
+	u8 t;
+
+	/* turn off auto-detect; manual settings*/
+	lgs8gxx_write_reg(priv, 0x7E, 0);
+	if (priv->config->prod == LGS8GXX_PROD_LGS8913)
+		lgs8gxx_write_reg(priv, 0xC1, 0);
+
+	ret = lgs8gxx_read_reg(priv, 0xC5, &t);
+	t = (t & 0xE0) | 0x06;
+	lgs8gxx_write_reg(priv, 0xC5, t);
+
+	lgs8gxx_soft_reset(priv);
+
+	return 0;
+}
+
+static int lgs8gxx_is_locked(struct lgs8gxx_state *priv, u8 *locked)
+{
+	int ret = 0;
+	u8 t;
+
+	ret = lgs8gxx_read_reg(priv, 0x4B, &t);
+	if (ret != 0)
+		return ret;
+
+	*locked = ((t & 0xC0) == 0xC0) ? 1 : 0;
+	return 0;
+}
+
+static int lgs8gxx_is_autodetect_finished(struct lgs8gxx_state *priv,
+					  u8 *finished)
+{
+	int ret = 0;
+	u8 t;
+
+	ret = lgs8gxx_read_reg(priv, 0xA4, &t);
+	if (ret != 0)
+		return ret;
+
+	*finished = ((t & 0x3) == 0x1) ? 1 : 0;
+
+	return 0;
+}
+
+static int lgs8gxx_autolock_gi(struct lgs8gxx_state *priv, u8 gi, u8 *locked)
+{
+	int err; 
+	u8 ad_fini = 0;
+
+	if (gi == GI_945)
+		dprintk("try GI 945\n");
+	else if (gi == GI_595)
+		dprintk("try GI 595\n");
+	else if (gi == GI_420)
+		dprintk("try GI 420\n");
+	lgs8gxx_write_reg(priv, 0x04, gi);
+	lgs8gxx_soft_reset(priv);
+	msleep(50);
+	err = lgs8gxx_is_autodetect_finished(priv, &ad_fini);
+	if (err != 0)
+		return err;
+	if (ad_fini) {
+		err = lgs8gxx_is_locked(priv, locked);
+		if (err != 0)
+			return err;
+	}
+
+	return 0;
+}
+
+static int lgs8gxx_auto_detect(struct lgs8gxx_state *priv,
+			       u8 *detected_param, u8 *gi)
+{
+	int i,j;
+	int err = 0;
+	u8 locked = 0, tmp_gi;
+
+	dprintk("%s\n", __func__);
+
+	lgs8gxx_set_mode_auto(priv);
+	/* Guard Interval */
+	lgs8gxx_write_reg(priv, 0x03, 00);
+
+	for (i = 0; i < 2; i++) {
+		for (j =0 ; j < 2; j++) {
+			tmp_gi = GI_945;
+			err = lgs8gxx_autolock_gi(priv, GI_945, &locked);
+			if (err) goto out;
+			if (locked) goto locked;
+		}
+		for (j =0 ; j < 2; j++) {
+			tmp_gi = GI_420;
+			err = lgs8gxx_autolock_gi(priv, GI_420, &locked);
+			if (err) goto out;
+			if (locked) goto locked;
+		}
+		tmp_gi = GI_595;
+		err = lgs8gxx_autolock_gi(priv, GI_595, &locked);
+		if (err) goto out;
+		if (locked) goto locked;
+	}
+
+locked:
+	if ((err == 0) && (locked == 1)) {
+		u8 t;
+
+		lgs8gxx_read_reg(priv, 0xA2, &t);
+		*detected_param = t;
+
+		if (tmp_gi == GI_945)
+			dprintk("GI 945 locked\n");
+		else if (tmp_gi == GI_595)
+			dprintk("GI 595 locked\n");
+		else if (tmp_gi == GI_420)
+			dprintk("GI 420 locked\n");
+		*gi = tmp_gi;
+	}
+	if (!locked)
+		err = -1;
+
+out:
+	return err;
+}
+
+static void lgs8gxx_auto_lock(struct lgs8gxx_state *priv)
+{
+	s8 err;
+	//u8 ctrl_frame = 0, mode = 0, rate = 0;
+	u8 /*inter_leaver_len = 0,*/ gi = 0x2;
+	u8 detected_param = 0;
+
+	err = lgs8gxx_auto_detect(priv, &detected_param, &gi);
+
+	if (err != 0) {
+#if 0
+		/* Set auto guardinterval detection */
+		lgs8gxx_write_reg(priv, 0x03, 0x01);
+#endif
+		dprintk("lgs8gxx_auto_detect failed\n");
+	}
+
+	
+	/* Apply detected parameters */
+	if (priv->config->prod == LGS8GXX_PROD_LGS8913) {
+		u8 inter_leave_len = detected_param & TIM_MASK ;
+		inter_leave_len = (inter_leave_len == TIM_LONG) ? 0x60 : 0x40;
+		detected_param &= CF_MASK | SC_MASK  | LGS_FEC_MASK;
+		detected_param |= inter_leave_len;
+	}
+	lgs8gxx_write_reg(priv, 0x7D, detected_param);
+	if (priv->config->prod == LGS8GXX_PROD_LGS8913) {
+		lgs8gxx_write_reg(priv, 0xC0, detected_param);
+	}
+	//lgs8gxx_soft_reset(priv);
+
+	/* Enter manual mode */
+	lgs8gxx_set_mode_manual(priv);
+
+	if (gi == 0x2)
+	switch(gi) {
+	case GI_945:
+		priv->curr_gi = 945; break;
+	case GI_595:
+		priv->curr_gi = 595; break;
+	case GI_420:
+		priv->curr_gi = 420; break;
+	default:
+		priv->curr_gi = 945; break;
+	}
+}
+
+static int lgs8gxx_set_mpeg_mode(struct lgs8gxx_state *priv,
+				 u8 serial, u8 clk_pol, u8 clk_gated)
+{
+	int ret = 0;
+	u8 t;
+
+	ret = lgs8gxx_read_reg(priv, 0xC2, &t);
+	if (ret != 0)
+		return ret;
+
+	t &= 0xF8;
+	t |= serial ? TS_SERIAL : TS_PARALLEL;
+	t |= clk_pol ? TS_CLK_INVERTED : TS_CLK_NORMAL;
+	t |= clk_gated ? TS_CLK_GATED : TS_CLK_FREERUN;
+
+	ret = lgs8gxx_write_reg(priv, 0xC2, t);
+	if (ret != 0)
+		return ret;
+
+	return 0;
+}
+
+
+/* LGS8913 demod frontend functions */
+
+static int lgs8913_init(struct lgs8gxx_state *priv)
+{
+	u8 t;
+
+	/* LGS8913 specific */
+	lgs8gxx_write_reg(priv, 0xc1, 0x3);
+
+	lgs8gxx_read_reg(priv, 0x7c, &t);
+	lgs8gxx_write_reg(priv, 0x7c, (t&0x8c) | 0x3);
+
+	/* LGS8913 specific */
+	lgs8gxx_read_reg(priv, 0xc3, &t);
+	lgs8gxx_write_reg(priv, 0xc3, t&0x10);
+
+#if 0
+	/* set AGC ref */
+	/* FIXME better set from configuration per hardware */
+	lgs8gxx_write_reg(priv, 0x2C, 0); 
+	lgs8gxx_write_reg(priv, 0x2D, 0x18);
+	lgs8gxx_write_reg(priv, 0x2E, 0xA2);
+#endif
+
+	return 0;
+}
+
+static int lgs8gxx_init(struct dvb_frontend *fe)
+{
+	struct lgs8gxx_state *priv = (struct lgs8gxx_state *)fe->demodulator_priv;
+	const struct lgs8gxx_config *config = priv->config;
+	u8 data = 0;
+	s8 err;
+	dprintk("%s\n", __func__);
+
+	lgs8gxx_read_reg(priv, 0, &data);
+	dprintk("reg 0 = 0x%02X\n", data);
+
+	/* Setup MPEG output format */
+	err = lgs8gxx_set_mpeg_mode(priv, config->serial_ts,
+				    config->ts_clk_pol,
+				    config->ts_clk_gated);
+	if (err != 0)
+		return -EIO;
+
+	if (config->prod == LGS8GXX_PROD_LGS8913) {
+		lgs8913_init(priv);
+	}
+	lgs8gxx_set_if_freq(priv, priv->config->if_freq);
+	if (config->prod != LGS8GXX_PROD_LGS8913)
+		lgs8gxx_set_ad_mode(priv);
+
+	return 0;
+}
+
+static void lgs8gxx_release(struct dvb_frontend *fe)
+{
+	struct lgs8gxx_state *state = fe->demodulator_priv;
+	dprintk("%s\n", __func__);
+
+	kfree(state);
+}
+
+#if 0
+static int lgs8gxx_sleep(struct dvb_frontend *fe)
+{
+	dprintk("%s\n", __func__);
+
+	return 0;
+}
+#endif
+
+static int lgs8gxx_write(struct dvb_frontend *fe, u8 *buf, int len)
+{
+	struct lgs8gxx_state *priv = fe->demodulator_priv;
+
+	if (len != 2)
+		return -EINVAL;
+
+	return lgs8gxx_write_reg(priv, buf[0], buf[1]);
+}
+
+static int lgs8gxx_set_fe(struct dvb_frontend *fe,
+			  struct dvb_frontend_parameters *fe_params)
+{
+	struct lgs8gxx_state *priv = fe->demodulator_priv;
+
+	dprintk("%s\n", __func__);
+
+	/* set frequency */
+	if (fe->ops.tuner_ops.set_params) {
+		fe->ops.tuner_ops.set_params(fe, fe_params);
+		if (fe->ops.i2c_gate_ctrl)
+			fe->ops.i2c_gate_ctrl(fe, 0);
+	}
+
+	/* Hardcoded to use auto as much as possible */
+	fe_params->u.ofdm.code_rate_HP = FEC_AUTO;
+	fe_params->u.ofdm.guard_interval = GUARD_INTERVAL_AUTO;
+	fe_params->u.ofdm.transmission_mode = TRANSMISSION_MODE_AUTO;
+
+	/* Set standard params.. or put them to auto */
+	if ((fe_params->u.ofdm.code_rate_HP == FEC_AUTO) ||
+	    (fe_params->u.ofdm.code_rate_LP == FEC_AUTO) ||
+	    (fe_params->u.ofdm.constellation == QAM_AUTO) ||
+	    (fe_params->u.ofdm.hierarchy_information == HIERARCHY_AUTO)) {
+	} else {
+		/* set constellation */
+		switch (fe_params->u.ofdm.constellation) {
+		case QPSK:
+		case QAM_16:
+		case QAM_64:
+			break;
+
+		default:
+			return -EINVAL;
+		}
+
+		/* set hierarchy */
+		switch (fe_params->u.ofdm.hierarchy_information) {
+		case HIERARCHY_NONE:
+		case HIERARCHY_1:
+		case HIERARCHY_2:
+		case HIERARCHY_4:
+			break;
+
+		default:
+			return -EINVAL;
+		}
+	}
+
+	/* set guard interval */
+	switch (fe_params->u.ofdm.guard_interval) {
+	case GUARD_INTERVAL_1_32:
+	case GUARD_INTERVAL_1_16:
+	case GUARD_INTERVAL_1_8:
+	case GUARD_INTERVAL_1_4:
+	case GUARD_INTERVAL_AUTO:
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	/* set transmission mode */
+	switch (fe_params->u.ofdm.transmission_mode) {
+	case TRANSMISSION_MODE_2K:
+	case TRANSMISSION_MODE_8K:
+	case TRANSMISSION_MODE_AUTO:
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	/* start auto lock */
+	lgs8gxx_auto_lock(priv);
+
+	msleep(10);
+
+	return 0;
+}
+
+static int lgs8gxx_get_fe(struct dvb_frontend *fe,
+			  struct dvb_frontend_parameters *fe_params)
+{
+	struct lgs8gxx_state *priv = fe->demodulator_priv;
+	u8 t;
+	int translated_fec = FEC_1_2;
+
+	dprintk("%s\n", __func__);
+
+	/* TODO: get real readings from device */
+	/* inversion status */
+	fe_params->inversion = INVERSION_OFF;
+
+	/* bandwidth */
+	fe_params->u.ofdm.bandwidth = BANDWIDTH_8_MHZ;
+
+	lgs8gxx_read_reg(priv, 0x7D, &t);
+	/* FEC. No exact match for DMB-TH, pick approx. value */
+	switch(t & LGS_FEC_MASK) {
+	case  LGS_FEC_0_4: /* FEC 0.4 */
+		translated_fec = FEC_1_2;
+		break;
+	case  LGS_FEC_0_6: /* FEC 0.6 */
+		translated_fec = FEC_2_3;
+		break;
+	case  LGS_FEC_0_8: /* FEC 0.8 */
+		translated_fec = FEC_5_6;
+		break;
+	default:
+		translated_fec = FEC_1_2;
+	}
+	fe_params->u.ofdm.code_rate_HP =
+	fe_params->u.ofdm.code_rate_LP = translated_fec;
+
+	/* constellation */
+	switch(t & SC_MASK) {
+	case SC_QAM64:
+		fe_params->u.ofdm.constellation = QAM_64;
+		break;
+	case SC_QAM32:
+		fe_params->u.ofdm.constellation = QAM_32;
+		break;
+	case SC_QAM16:
+		fe_params->u.ofdm.constellation = QAM_16;
+		break;
+	case SC_QAM4:
+	case SC_QAM4NR:
+		fe_params->u.ofdm.constellation = QPSK;
+		break;
+	default:
+		fe_params->u.ofdm.constellation = QAM_64;
+	}
+
+	/* transmission mode */
+	fe_params->u.ofdm.transmission_mode = TRANSMISSION_MODE_8K;
+
+	/* guard interval */
+	fe_params->u.ofdm.guard_interval = GUARD_INTERVAL_1_16;
+
+	/* hierarchy */
+	fe_params->u.ofdm.hierarchy_information = HIERARCHY_NONE;
+
+	return 0;
+}
+
+static
+int lgs8gxx_get_tune_settings(struct dvb_frontend *fe,
+			      struct dvb_frontend_tune_settings *fesettings)
+{
+	/* FIXME: copy from tda1004x.c */
+	fesettings->min_delay_ms = 800;
+	/* Drift compensation makes no sense for DVB-T */
+	fesettings->step_size = 0;
+	fesettings->max_drift = 0;
+	return 0;
+}
+
+static int lgs8gxx_read_status(struct dvb_frontend *fe, fe_status_t *fe_status)
+{
+	struct lgs8gxx_state *priv = fe->demodulator_priv;
+	s8 ret;
+	u8 t;
+
+	dprintk("%s\n", __func__);
+
+	ret = lgs8gxx_read_reg(priv, 0x4B, &t);
+	if (ret != 0)
+		return -EIO;
+
+	dprintk("Reg 0x4B: 0x%02X\n", t);
+
+	*fe_status = 0;
+	if (priv->config->prod == LGS8GXX_PROD_LGS8913) {
+		if ((t & 0x40) == 0x40)
+			*fe_status |= FE_HAS_SIGNAL | FE_HAS_CARRIER;
+		if ((t & 0x80) == 0x80)
+			*fe_status |= FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK;
+	} else {
+		if ((t & 0x80) == 0x80)
+			*fe_status |= FE_HAS_SIGNAL | FE_HAS_CARRIER |
+				FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK;
+	}
+
+	/* success */
+	dprintk("%s: fe_status=0x%x\n", __func__, *fe_status);
+	return 0;
+}
+
+static int lgs8gxx_read_signal_agc(struct lgs8gxx_state *priv, u16 *signal)
+{
+	u16 v;
+	u8 agc_lvl[2], cat;
+
+	dprintk("%s()\n", __func__);	
+	lgs8gxx_read_reg(priv, 0x3F, &agc_lvl[0]);
+	lgs8gxx_read_reg(priv, 0x3E, &agc_lvl[1]);
+
+	v = agc_lvl[0];
+	v <<= 8;
+	v |= agc_lvl[1];
+
+	dprintk("agc_lvl: 0x%04X\n", v);
+
+	if (v < 0x100 )
+		cat = 0;
+	else if (v < 0x190)
+		cat = 5;
+	else if (v < 0x2A8)
+		cat = 4;
+	else if (v < 0x381)
+		cat = 3;
+	else if (v < 0x400)
+		cat = 2;
+	else if (v == 0x400)
+		cat = 1;
+	else
+		cat = 0;
+		
+	*signal = cat;
+
+	return 0;
+}
+
+static int lgs8913_read_signal_strength(struct lgs8gxx_state *priv, u16 *signal)
+{
+	u8 t; s8 ret;
+#ifndef USE_FAKE_SIGNAL_STRENGTH
+	s16 max_strength = 0;
+	u8 str;
+	u16 i, gi = priv->curr_gi;
+#endif
+
+	dprintk("%s\n", __func__);
+
+	ret = lgs8gxx_read_reg(priv, 0x4B, &t);
+	if (ret != 0)
+		return -EIO;
+
+#ifdef USE_FAKE_SIGNAL_STRENGTH
+	if ((t & 0xC0) == 0xC0) {
+		dprintk("Fake signal strength as 50\n");
+		*signal = 0x32;
+	}
+#else
+	dprintk("gi = %d\n", gi);
+	for (i = 0; i < gi; i++) {
+
+		if ((i & 0xFF) == 0)
+			lgs8gxx_write_reg(priv, 0x84, 0x03 & (i >> 8));
+		lgs8gxx_write_reg(priv, 0x83, i & 0xFF);
+
+		lgs8gxx_read_reg(priv, 0x94, &str);
+		if (max_strength < str)
+			max_strength = str;
+	}
+
+	*signal = max_strength;
+	dprintk("%s: signal=0x%02X\n", __func__, *signal);
+#endif
+
+	lgs8gxx_read_reg(priv, 0x95, &t);
+	dprintk("%s: AVG Noise=0x%02X\n", __func__, t);
+
+	return 0;
+}
+
+static int lgs8gxx_read_signal_strength(struct dvb_frontend *fe, u16 *signal)
+{
+	struct lgs8gxx_state *priv = fe->demodulator_priv;
+
+	if (priv->config->prod == LGS8GXX_PROD_LGS8913)
+		return lgs8913_read_signal_strength(priv, signal);
+	else
+		return lgs8gxx_read_signal_agc(priv, signal);
+}
+
+static int lgs8gxx_read_snr(struct dvb_frontend *fe, u16 *snr)
+{
+	struct lgs8gxx_state *priv = fe->demodulator_priv;
+	u8 t;
+	*snr = 0;
+
+	lgs8gxx_read_reg(priv, 0x95, &t);
+	dprintk("AVG Noise=0x%02X\n", t);
+	*snr = 256 - t;
+	*snr <<= 8;
+	dprintk("snr=0x%x\n", *snr);
+	
+	return 0;
+}
+
+static int lgs8gxx_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks)
+{
+	*ucblocks = 0;
+	dprintk("%s: ucblocks=0x%x\n", __func__, *ucblocks);
+	return 0;
+}
+
+static int lgs8gxx_read_ber(struct dvb_frontend *fe, u32 *ber)
+{
+	struct lgs8gxx_state *priv = fe->demodulator_priv;
+	u8 r0, r1, r2, r3;
+	u32 total_cnt, err_cnt;
+
+	dprintk("%s\n", __func__);
+
+	lgs8gxx_write_reg(priv, 0xc6, 0x01);
+	lgs8gxx_write_reg(priv, 0xc6, 0x41);
+	lgs8gxx_write_reg(priv, 0xc6, 0x01);
+
+	msleep(200);
+
+	lgs8gxx_write_reg(priv, 0xc6, 0x81);
+	lgs8gxx_read_reg(priv, 0xd0, &r0);
+	lgs8gxx_read_reg(priv, 0xd1, &r1);
+	lgs8gxx_read_reg(priv, 0xd2, &r2);
+	lgs8gxx_read_reg(priv, 0xd3, &r3);
+	total_cnt = (r3 << 24) | (r2 << 16) | (r1 << 8) | (r0);
+	lgs8gxx_read_reg(priv, 0xd4, &r0);
+	lgs8gxx_read_reg(priv, 0xd5, &r1);
+	lgs8gxx_read_reg(priv, 0xd6, &r2);
+	lgs8gxx_read_reg(priv, 0xd7, &r3);
+	err_cnt = (r3 << 24) | (r2 << 16) | (r1 << 8) | (r0);
+	dprintk("error=%d total=%d\n", err_cnt, total_cnt);
+
+	if (total_cnt == 0)
+		*ber = 0;
+	else
+		*ber = err_cnt * 100 / total_cnt;
+
+	dprintk("%s: ber=0x%x\n", __func__, *ber);
+	return 0;
+}
+
+static int lgs8gxx_i2c_gate_ctrl(struct dvb_frontend* fe, int enable)
+{
+	struct lgs8gxx_state *priv = fe->demodulator_priv;
+
+	if (priv->config->tuner_address == 0)
+		return 0;
+	if (enable) {
+		return lgs8gxx_write_reg(priv, 0x01, 0x80 | priv->config->tuner_address);
+	} else {
+		return lgs8gxx_write_reg(priv, 0x01, 0);
+	}
+}
+
+static struct dvb_frontend_ops lgs8gxx_ops = {
+	.info = {
+		.name = "Legend Silicon LGS8913/LGS8GXX DMB-TH",
+		.type = FE_OFDM,
+		.frequency_min = 474000000,
+		.frequency_max = 858000000,
+		.frequency_stepsize = 10000,
+		.caps =
+		    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
+	},
+
+	.release = lgs8gxx_release,
+
+	.init = lgs8gxx_init,
+	//.sleep = lgs8gxx_sleep,
+	.write = lgs8gxx_write,
+	.i2c_gate_ctrl = lgs8gxx_i2c_gate_ctrl,
+
+	.set_frontend = lgs8gxx_set_fe,
+	.get_frontend = lgs8gxx_get_fe,
+	.get_tune_settings = lgs8gxx_get_tune_settings,
+
+	.read_status = lgs8gxx_read_status,
+	.read_ber = lgs8gxx_read_ber,
+	.read_signal_strength = lgs8gxx_read_signal_strength,
+	.read_snr = lgs8gxx_read_snr,
+	.read_ucblocks = lgs8gxx_read_ucblocks,
+};
+
+struct dvb_frontend *lgs8gxx_attach(const struct lgs8gxx_config *config,
+				    struct i2c_adapter *i2c)
+{
+	struct lgs8gxx_state *priv = NULL;
+	u8 data = 0;
+
+	dprintk("%s()\n",__func__);
+
+	if (config == NULL || i2c == NULL)
+		return NULL;
+
+	priv = kzalloc(sizeof(struct lgs8gxx_state), GFP_KERNEL);
+	if (priv == NULL)
+		goto error_out;
+
+	priv->config = config;
+	priv->i2c = i2c;
+
+	/* check if the demod is there */
+	if (lgs8gxx_read_reg(priv, 0, &data) != 0 /*||
+		data != 0x0B*/) {
+		dprintk("%s lgs8gxx not found at i2c addr 0x%02X\n",
+			__func__, priv->config->demod_address);
+		goto error_out;
+	}
+
+	lgs8gxx_read_reg(priv, 1, &data);
+
+	memcpy(&priv->frontend.ops, &lgs8gxx_ops,
+	       sizeof(struct dvb_frontend_ops));
+	priv->frontend.demodulator_priv = priv;
+
+	return &priv->frontend;
+
+error_out:
+	dprintk("%s() error_out\n", __func__);
+	kfree(priv);
+	return NULL;
+
+}
+EXPORT_SYMBOL(lgs8gxx_attach);
+
+MODULE_DESCRIPTION("Legend Silicon LGS8913/LGS8GXX DMB-TH demodulator driver");
+MODULE_AUTHOR("David T. L. Wong <davidtlwong@gmail.com>");
+MODULE_LICENSE("GPL");
diff -r 626c136ec221 linux/drivers/media/dvb/frontends/lgs8gxx.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/linux/drivers/media/dvb/frontends/lgs8gxx.h	Tue Mar 17 23:07:57 2009 +0800
@@ -0,0 +1,90 @@ 
+/*
+ *    Support for Legend Silicon DMB-TH demodulator
+ *    LGS8913, LGS8GL5
+ *    experimental support LGS8G42, LGS8G52
+ *
+ *    Copyright (C) 2007,2008 David T.L. Wong <davidtlwong@gmail.com>
+ *    Copyright (C) 2008 Sirius International (Hong Kong) Limited
+ *    Timothy Lee <timothy.lee@siriushk.com> (for initial work on LGS8GL5)
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License
+ *    along with this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#ifndef __LGS8GXX_H__
+#define __LGS8GXX_H__
+
+#include <linux/dvb/frontend.h>
+#include <linux/i2c.h>
+
+#define LGS8GXX_PROD_LGS8913 0
+#define LGS8GXX_PROD_LGS8GL5 1
+#define LGS8GXX_PROD_LGS8G42 3
+#define LGS8GXX_PROD_LGS8G52 4
+#define LGS8GXX_PROD_LGS8G54 5
+
+struct lgs8gxx_config {
+
+	/* product type */
+	u8 prod;
+
+	/* the demodulator's i2c address */
+	u8 demod_address;
+
+	/* parallel or serial transport stream */
+	u8 serial_ts;
+
+	/* transport stream polarity*/
+	u8 ts_clk_pol;
+
+	/* transport stream clock gated by ts_valid */
+	u8 ts_clk_gated;
+
+	/* A/D Clock frequency */
+	u32 if_clk_freq; /* in kHz */
+
+	/* IF frequency */
+	u32 if_freq; /* in kHz */
+
+	/*Use External ADC*/
+	u8 ext_adc;
+
+	/*External ADC output two's complement*/
+	u8 adc_signed;
+
+	/*Sample IF data at falling edge of IF_CLK*/
+	u8 if_neg_edge;
+
+	/*IF use Negative center frequency*/
+	u8 if_neg_center;
+
+	/* slave address and configuration of the tuner */
+	u8 tuner_address;
+};
+
+#if defined(CONFIG_DVB_LGS8GXX) || \
+	(defined(CONFIG_DVB_LGS8GXX_MODULE) && defined(MODULE))
+extern struct dvb_frontend *lgs8gxx_attach(const struct lgs8gxx_config *config,
+					   struct i2c_adapter *i2c);
+#else
+static inline
+struct dvb_frontend *lgs8gxx_attach(const struct lgs8gxx_config *config,
+				    struct i2c_adapter *i2c) {
+	printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__);
+	return NULL;
+}
+#endif /* CONFIG_DVB_LGS8GXX */
+
+#endif /* __LGS8GXX_H__ */
diff -r 626c136ec221 linux/drivers/media/dvb/frontends/lgs8gxx_priv.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/linux/drivers/media/dvb/frontends/lgs8gxx_priv.h	Tue Mar 17 23:07:57 2009 +0800
@@ -0,0 +1,70 @@ 
+/*
+ *    Support for Legend Silicon DMB-TH demodulator
+ *    LGS8913, LGS8GL5
+ *    experimental support LGS8G42, LGS8G52
+ *
+ *    Copyright (C) 2007,2008 David T.L. Wong <davidtlwong@gmail.com>
+ *    Copyright (C) 2008 Sirius International (Hong Kong) Limited
+ *    Timothy Lee <timothy.lee@siriushk.com> (for initial work on LGS8GL5)
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License
+ *    along with this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#ifndef LGS8913_PRIV_H
+#define LGS8913_PRIV_H
+
+struct lgs8gxx_state {
+	struct i2c_adapter *i2c;
+	/* configuration settings */
+	const struct lgs8gxx_config *config;
+	struct dvb_frontend frontend;
+	u16 curr_gi; /* current guard interval */
+};
+
+#define SC_MASK		0x1C	/* Sub-Carrier Modulation Mask */
+#define SC_QAM64	0x10	/* 64QAM modulation */
+#define SC_QAM32	0x0C	/* 32QAM modulation */
+#define SC_QAM16	0x08	/* 16QAM modulation */
+#define SC_QAM4NR	0x04	/* 4QAM modulation */
+#define SC_QAM4		0x00	/* 4QAM modulation */
+
+#define LGS_FEC_MASK	0x03	/* FEC Rate Mask */
+#define LGS_FEC_0_4	0x00	/* FEC Rate 0.4 */
+#define LGS_FEC_0_6	0x01	/* FEC Rate 0.6 */
+#define LGS_FEC_0_8	0x02	/* FEC Rate 0.8 */
+
+#define TIM_MASK	  0x20	/* Time Interleave Length Mask */
+#define TIM_LONG	  0x00	/* Time Interleave Length = 720 */
+#define TIM_MIDDLE     0x20   /* Time Interleave Length = 240 */
+
+#define CF_MASK	0x80	/* Control Frame Mask */
+#define CF_EN	0x80	/* Control Frame On */
+
+#define GI_MASK	0x03	/* Guard Interval Mask */
+#define GI_420	0x00	/* 1/9 Guard Interval */
+#define GI_595	0x01	
+#define GI_945	0x02	/* 1/4 Guard Interval */
+
+
+#define TS_PARALLEL	0x00	/* Parallel TS Output a.k.a. SPI */
+#define TS_SERIAL	0x01	/* Serial TS Output a.k.a. SSI */
+#define TS_CLK_NORMAL		0x00	/* MPEG Clock Normal */
+#define TS_CLK_INVERTED		0x02	/* MPEG Clock Inverted */
+#define TS_CLK_GATED		0x00	/* MPEG clock gated */
+#define TS_CLK_FREERUN		0x04	/* MPEG clock free running*/
+
+
+#endif