[media] mb86a20s: add CNR measurement

Add Signal/Noise ratio measurement. On this device, a global measure
is taken by the demod. It also provides per-layer CNR measurements,
based on Modulation Error measures (MER).

Reviewed-by: Antti Palosaari <crope@iki.fi>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
This commit is contained in:
Mauro Carvalho Chehab 2013-01-16 15:12:05 -03:00
parent d01a8ee37a
commit 25188bd0e6

View File

@ -118,10 +118,12 @@ static struct regdata mb86a20s_init[] = {
{ 0x50, 0xb5 }, { 0x51, 0xff },
{ 0x50, 0xb6 }, { 0x51, 0xff },
{ 0x50, 0xb7 }, { 0x51, 0xff },
{ 0x50, 0x50 }, { 0x51, 0x02 },
{ 0x50, 0x50 }, { 0x51, 0x02 }, /* MER manual mode */
{ 0x50, 0x51 }, { 0x51, 0x04 }, /* MER symbol 4 */
{ 0x45, 0x04 }, /* CN symbol 4 */
{ 0x48, 0x04 },
{ 0x48, 0x04 }, /* CN manual mode */
{ 0x50, 0xd5 }, { 0x51, 0x01 }, /* Serial */
{ 0x50, 0xd6 }, { 0x51, 0x1f },
{ 0x50, 0xd2 }, { 0x51, 0x03 },
@ -891,6 +893,330 @@ static int mb86a20s_get_ber_before_vterbi(struct dvb_frontend *fe,
return 0;
}
struct linear_segments {
unsigned x, y;
};
/*
* All tables below return a dB/1000 measurement
*/
static struct linear_segments cnr_to_db_table[] = {
{ 19648, 0},
{ 18187, 1000},
{ 16534, 2000},
{ 14823, 3000},
{ 13161, 4000},
{ 11622, 5000},
{ 10279, 6000},
{ 9089, 7000},
{ 8042, 8000},
{ 7137, 9000},
{ 6342, 10000},
{ 5641, 11000},
{ 5030, 12000},
{ 4474, 13000},
{ 3988, 14000},
{ 3556, 15000},
{ 3180, 16000},
{ 2841, 17000},
{ 2541, 18000},
{ 2276, 19000},
{ 2038, 20000},
{ 1800, 21000},
{ 1625, 22000},
{ 1462, 23000},
{ 1324, 24000},
{ 1175, 25000},
{ 1063, 26000},
{ 980, 27000},
{ 907, 28000},
{ 840, 29000},
{ 788, 30000},
};
static struct linear_segments cnr_64qam_table[] = {
{ 3922688, 0},
{ 3920384, 1000},
{ 3902720, 2000},
{ 3894784, 3000},
{ 3882496, 4000},
{ 3872768, 5000},
{ 3858944, 6000},
{ 3851520, 7000},
{ 3838976, 8000},
{ 3829248, 9000},
{ 3818240, 10000},
{ 3806976, 11000},
{ 3791872, 12000},
{ 3767040, 13000},
{ 3720960, 14000},
{ 3637504, 15000},
{ 3498496, 16000},
{ 3296000, 17000},
{ 3031040, 18000},
{ 2715392, 19000},
{ 2362624, 20000},
{ 1963264, 21000},
{ 1649664, 22000},
{ 1366784, 23000},
{ 1120768, 24000},
{ 890880, 25000},
{ 723456, 26000},
{ 612096, 27000},
{ 518912, 28000},
{ 448256, 29000},
{ 388864, 30000},
};
static struct linear_segments cnr_16qam_table[] = {
{ 5314816, 0},
{ 5219072, 1000},
{ 5118720, 2000},
{ 4998912, 3000},
{ 4875520, 4000},
{ 4736000, 5000},
{ 4604160, 6000},
{ 4458752, 7000},
{ 4300288, 8000},
{ 4092928, 9000},
{ 3836160, 10000},
{ 3521024, 11000},
{ 3155968, 12000},
{ 2756864, 13000},
{ 2347008, 14000},
{ 1955072, 15000},
{ 1593600, 16000},
{ 1297920, 17000},
{ 1043968, 18000},
{ 839680, 19000},
{ 672256, 20000},
{ 523008, 21000},
{ 424704, 22000},
{ 345088, 23000},
{ 280064, 24000},
{ 221440, 25000},
{ 179712, 26000},
{ 151040, 27000},
{ 128512, 28000},
{ 110080, 29000},
{ 95744, 30000},
};
struct linear_segments cnr_qpsk_table[] = {
{ 2834176, 0},
{ 2683648, 1000},
{ 2536960, 2000},
{ 2391808, 3000},
{ 2133248, 4000},
{ 1906176, 5000},
{ 1666560, 6000},
{ 1422080, 7000},
{ 1189632, 8000},
{ 976384, 9000},
{ 790272, 10000},
{ 633344, 11000},
{ 505600, 12000},
{ 402944, 13000},
{ 320768, 14000},
{ 255488, 15000},
{ 204032, 16000},
{ 163072, 17000},
{ 130304, 18000},
{ 105216, 19000},
{ 83456, 20000},
{ 65024, 21000},
{ 52480, 22000},
{ 42752, 23000},
{ 34560, 24000},
{ 27136, 25000},
{ 22016, 26000},
{ 18432, 27000},
{ 15616, 28000},
{ 13312, 29000},
{ 11520, 30000},
};
static u32 interpolate_value(u32 value, struct linear_segments *segments,
unsigned len)
{
u64 tmp64;
u32 dx, dy;
int i, ret;
if (value >= segments[0].x)
return segments[0].y;
if (value < segments[len-1].x)
return segments[len-1].y;
for (i = 1; i < len - 1; i++) {
/* If value is identical, no need to interpolate */
if (value == segments[i].x)
return segments[i].y;
if (value > segments[i].x)
break;
}
/* Linear interpolation between the two (x,y) points */
dy = segments[i].y - segments[i - 1].y;
dx = segments[i - 1].x - segments[i].x;
tmp64 = value - segments[i].x;
tmp64 *= dy;
do_div(tmp64, dx);
ret = segments[i].y - tmp64;
return ret;
}
static int mb86a20s_get_main_CNR(struct dvb_frontend *fe)
{
struct mb86a20s_state *state = fe->demodulator_priv;
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
u32 cnr_linear, cnr;
int rc, val;
/* Check if CNR is available */
rc = mb86a20s_readreg(state, 0x45);
if (rc < 0)
return rc;
if (!(rc & 0x40)) {
dev_info(&state->i2c->dev, "%s: CNR is not available yet.\n",
__func__);
return -EBUSY;
}
val = rc;
rc = mb86a20s_readreg(state, 0x46);
if (rc < 0)
return rc;
cnr_linear = rc << 8;
rc = mb86a20s_readreg(state, 0x46);
if (rc < 0)
return rc;
cnr_linear |= rc;
cnr = interpolate_value(cnr_linear,
cnr_to_db_table, ARRAY_SIZE(cnr_to_db_table));
c->cnr.stat[0].scale = FE_SCALE_DECIBEL;
c->cnr.stat[0].svalue = cnr;
dev_dbg(&state->i2c->dev, "%s: CNR is %d.%03d dB (%d)\n",
__func__, cnr / 1000, cnr % 1000, cnr_linear);
/* CNR counter reset */
rc = mb86a20s_writereg(state, 0x45, val | 0x10);
if (rc < 0)
return rc;
rc = mb86a20s_writereg(state, 0x45, val & 0x6f);
return rc;
}
static int mb86a20s_get_per_layer_CNR(struct dvb_frontend *fe)
{
struct mb86a20s_state *state = fe->demodulator_priv;
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
u32 mer, cnr;
int rc, val, i;
struct linear_segments *segs;
unsigned segs_len;
dev_dbg(&state->i2c->dev, "%s called.\n", __func__);
/* Check if the measures are already available */
rc = mb86a20s_writereg(state, 0x50, 0x5b);
if (rc < 0)
return rc;
rc = mb86a20s_readreg(state, 0x51);
if (rc < 0)
return rc;
/* Check if data is available */
if (!(rc & 0x01)) {
dev_info(&state->i2c->dev,
"%s: MER measures aren't available yet.\n", __func__);
return -EBUSY;
}
/* Read all layers */
for (i = 0; i < 3; i++) {
if (!(c->isdbt_layer_enabled & (1 << i))) {
c->cnr.stat[1 + i].scale = FE_SCALE_NOT_AVAILABLE;
continue;
}
rc = mb86a20s_writereg(state, 0x50, 0x52 + i * 3);
if (rc < 0)
return rc;
rc = mb86a20s_readreg(state, 0x51);
if (rc < 0)
return rc;
mer = rc << 16;
rc = mb86a20s_writereg(state, 0x50, 0x53 + i * 3);
if (rc < 0)
return rc;
rc = mb86a20s_readreg(state, 0x51);
if (rc < 0)
return rc;
mer |= rc << 8;
rc = mb86a20s_writereg(state, 0x50, 0x54 + i * 3);
if (rc < 0)
return rc;
rc = mb86a20s_readreg(state, 0x51);
if (rc < 0)
return rc;
mer |= rc;
switch (c->layer[i].modulation) {
case DQPSK:
case QPSK:
segs = cnr_qpsk_table;
segs_len = ARRAY_SIZE(cnr_qpsk_table);
break;
case QAM_16:
segs = cnr_16qam_table;
segs_len = ARRAY_SIZE(cnr_16qam_table);
break;
default:
case QAM_64:
segs = cnr_64qam_table;
segs_len = ARRAY_SIZE(cnr_64qam_table);
break;
}
cnr = interpolate_value(mer, segs, segs_len);
c->cnr.stat[1 + i].scale = FE_SCALE_DECIBEL;
c->cnr.stat[1 + i].svalue = cnr;
dev_dbg(&state->i2c->dev,
"%s: CNR for layer %c is %d.%03d dB (MER = %d).\n",
__func__, 'A' + i, cnr / 1000, cnr % 1000, mer);
}
/* Start a new MER measurement */
/* MER counter reset */
rc = mb86a20s_writereg(state, 0x50, 0x50);
if (rc < 0)
return rc;
rc = mb86a20s_readreg(state, 0x51);
if (rc < 0)
return rc;
val = rc;
rc = mb86a20s_writereg(state, 0x51, val | 0x01);
if (rc < 0)
return rc;
rc = mb86a20s_writereg(state, 0x51, val & 0x06);
if (rc < 0)
return rc;
return 0;
}
static void mb86a20s_stats_not_ready(struct dvb_frontend *fe)
{
struct mb86a20s_state *state = fe->demodulator_priv;
@ -934,7 +1260,13 @@ static int mb86a20s_get_stats(struct dvb_frontend *fe)
u32 t_pre_bit_error = 0, t_pre_bit_count = 0;
int active_layers = 0, ber_layers = 0;
dev_dbg(&state->i2c->dev, "%s called.\n", __func__);
mb86a20s_get_main_CNR(fe);
/* Get per-layer stats */
mb86a20s_get_per_layer_CNR(fe);
for (i = 0; i < 3; i++) {
if (c->isdbt_layer_enabled & (1 << i)) {
/* Layer is active and has rc segments */