[media] mb86a20s: add CNR measurement
authorMauro Carvalho Chehab <mchehab@redhat.com>
Wed, 16 Jan 2013 18:12:05 +0000 (15:12 -0300)
committerMauro Carvalho Chehab <mchehab@redhat.com>
Wed, 23 Jan 2013 21:10:49 +0000 (19:10 -0200)
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>
drivers/media/dvb-frontends/mb86a20s.c

index c68e467..4f3e222 100644 (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 */