diff options
author | Mauro Carvalho Chehab <mchehab@redhat.com> | 2013-01-16 13:12:05 -0500 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2013-01-23 16:10:49 -0500 |
commit | 25188bd0e66f244d8111c4459f2c2f262a13d272 (patch) | |
tree | c14fb32b183e2ab6246ebe34e0531d007e4b1c24 /drivers/media | |
parent | d01a8ee37afdeb1a00458a79854a1672ada5c9f0 (diff) |
[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>
Diffstat (limited to 'drivers/media')
-rw-r--r-- | drivers/media/dvb-frontends/mb86a20s.c | 336 |
1 files changed, 334 insertions, 2 deletions
diff --git a/drivers/media/dvb-frontends/mb86a20s.c b/drivers/media/dvb-frontends/mb86a20s.c index c68e4676e5fd..4f3e222a2bcf 100644 --- a/drivers/media/dvb-frontends/mb86a20s.c +++ b/drivers/media/dvb-frontends/mb86a20s.c | |||
@@ -118,10 +118,12 @@ static struct regdata mb86a20s_init[] = { | |||
118 | { 0x50, 0xb5 }, { 0x51, 0xff }, | 118 | { 0x50, 0xb5 }, { 0x51, 0xff }, |
119 | { 0x50, 0xb6 }, { 0x51, 0xff }, | 119 | { 0x50, 0xb6 }, { 0x51, 0xff }, |
120 | { 0x50, 0xb7 }, { 0x51, 0xff }, | 120 | { 0x50, 0xb7 }, { 0x51, 0xff }, |
121 | { 0x50, 0x50 }, { 0x51, 0x02 }, | 121 | |
122 | { 0x50, 0x50 }, { 0x51, 0x02 }, /* MER manual mode */ | ||
122 | { 0x50, 0x51 }, { 0x51, 0x04 }, /* MER symbol 4 */ | 123 | { 0x50, 0x51 }, { 0x51, 0x04 }, /* MER symbol 4 */ |
123 | { 0x45, 0x04 }, /* CN symbol 4 */ | 124 | { 0x45, 0x04 }, /* CN symbol 4 */ |
124 | { 0x48, 0x04 }, | 125 | { 0x48, 0x04 }, /* CN manual mode */ |
126 | |||
125 | { 0x50, 0xd5 }, { 0x51, 0x01 }, /* Serial */ | 127 | { 0x50, 0xd5 }, { 0x51, 0x01 }, /* Serial */ |
126 | { 0x50, 0xd6 }, { 0x51, 0x1f }, | 128 | { 0x50, 0xd6 }, { 0x51, 0x1f }, |
127 | { 0x50, 0xd2 }, { 0x51, 0x03 }, | 129 | { 0x50, 0xd2 }, { 0x51, 0x03 }, |
@@ -891,6 +893,330 @@ static int mb86a20s_get_ber_before_vterbi(struct dvb_frontend *fe, | |||
891 | return 0; | 893 | return 0; |
892 | } | 894 | } |
893 | 895 | ||
896 | struct linear_segments { | ||
897 | unsigned x, y; | ||
898 | }; | ||
899 | |||
900 | /* | ||
901 | * All tables below return a dB/1000 measurement | ||
902 | */ | ||
903 | |||
904 | static struct linear_segments cnr_to_db_table[] = { | ||
905 | { 19648, 0}, | ||
906 | { 18187, 1000}, | ||
907 | { 16534, 2000}, | ||
908 | { 14823, 3000}, | ||
909 | { 13161, 4000}, | ||
910 | { 11622, 5000}, | ||
911 | { 10279, 6000}, | ||
912 | { 9089, 7000}, | ||
913 | { 8042, 8000}, | ||
914 | { 7137, 9000}, | ||
915 | { 6342, 10000}, | ||
916 | { 5641, 11000}, | ||
917 | { 5030, 12000}, | ||
918 | { 4474, 13000}, | ||
919 | { 3988, 14000}, | ||
920 | { 3556, 15000}, | ||
921 | { 3180, 16000}, | ||
922 | { 2841, 17000}, | ||
923 | { 2541, 18000}, | ||
924 | { 2276, 19000}, | ||
925 | { 2038, 20000}, | ||
926 | { 1800, 21000}, | ||
927 | { 1625, 22000}, | ||
928 | { 1462, 23000}, | ||
929 | { 1324, 24000}, | ||
930 | { 1175, 25000}, | ||
931 | { 1063, 26000}, | ||
932 | { 980, 27000}, | ||
933 | { 907, 28000}, | ||
934 | { 840, 29000}, | ||
935 | { 788, 30000}, | ||
936 | }; | ||
937 | |||
938 | static struct linear_segments cnr_64qam_table[] = { | ||
939 | { 3922688, 0}, | ||
940 | { 3920384, 1000}, | ||
941 | { 3902720, 2000}, | ||
942 | { 3894784, 3000}, | ||
943 | { 3882496, 4000}, | ||
944 | { 3872768, 5000}, | ||
945 | { 3858944, 6000}, | ||
946 | { 3851520, 7000}, | ||
947 | { 3838976, 8000}, | ||
948 | { 3829248, 9000}, | ||
949 | { 3818240, 10000}, | ||
950 | { 3806976, 11000}, | ||
951 | { 3791872, 12000}, | ||
952 | { 3767040, 13000}, | ||
953 | { 3720960, 14000}, | ||
954 | { 3637504, 15000}, | ||
955 | { 3498496, 16000}, | ||
956 | { 3296000, 17000}, | ||
957 | { 3031040, 18000}, | ||
958 | { 2715392, 19000}, | ||
959 | { 2362624, 20000}, | ||
960 | { 1963264, 21000}, | ||
961 | { 1649664, 22000}, | ||
962 | { 1366784, 23000}, | ||
963 | { 1120768, 24000}, | ||
964 | { 890880, 25000}, | ||
965 | { 723456, 26000}, | ||
966 | { 612096, 27000}, | ||
967 | { 518912, 28000}, | ||
968 | { 448256, 29000}, | ||
969 | { 388864, 30000}, | ||
970 | }; | ||
971 | |||
972 | static struct linear_segments cnr_16qam_table[] = { | ||
973 | { 5314816, 0}, | ||
974 | { 5219072, 1000}, | ||
975 | { 5118720, 2000}, | ||
976 | { 4998912, 3000}, | ||
977 | { 4875520, 4000}, | ||
978 | { 4736000, 5000}, | ||
979 | { 4604160, 6000}, | ||
980 | { 4458752, 7000}, | ||
981 | { 4300288, 8000}, | ||
982 | { 4092928, 9000}, | ||
983 | { 3836160, 10000}, | ||
984 | { 3521024, 11000}, | ||
985 | { 3155968, 12000}, | ||
986 | { 2756864, 13000}, | ||
987 | { 2347008, 14000}, | ||
988 | { 1955072, 15000}, | ||
989 | { 1593600, 16000}, | ||
990 | { 1297920, 17000}, | ||
991 | { 1043968, 18000}, | ||
992 | { 839680, 19000}, | ||
993 | { 672256, 20000}, | ||
994 | { 523008, 21000}, | ||
995 | { 424704, 22000}, | ||
996 | { 345088, 23000}, | ||
997 | { 280064, 24000}, | ||
998 | { 221440, 25000}, | ||
999 | { 179712, 26000}, | ||
1000 | { 151040, 27000}, | ||
1001 | { 128512, 28000}, | ||
1002 | { 110080, 29000}, | ||
1003 | { 95744, 30000}, | ||
1004 | }; | ||
1005 | |||
1006 | struct linear_segments cnr_qpsk_table[] = { | ||
1007 | { 2834176, 0}, | ||
1008 | { 2683648, 1000}, | ||
1009 | { 2536960, 2000}, | ||
1010 | { 2391808, 3000}, | ||
1011 | { 2133248, 4000}, | ||
1012 | { 1906176, 5000}, | ||
1013 | { 1666560, 6000}, | ||
1014 | { 1422080, 7000}, | ||
1015 | { 1189632, 8000}, | ||
1016 | { 976384, 9000}, | ||
1017 | { 790272, 10000}, | ||
1018 | { 633344, 11000}, | ||
1019 | { 505600, 12000}, | ||
1020 | { 402944, 13000}, | ||
1021 | { 320768, 14000}, | ||
1022 | { 255488, 15000}, | ||
1023 | { 204032, 16000}, | ||
1024 | { 163072, 17000}, | ||
1025 | { 130304, 18000}, | ||
1026 | { 105216, 19000}, | ||
1027 | { 83456, 20000}, | ||
1028 | { 65024, 21000}, | ||
1029 | { 52480, 22000}, | ||
1030 | { 42752, 23000}, | ||
1031 | { 34560, 24000}, | ||
1032 | { 27136, 25000}, | ||
1033 | { 22016, 26000}, | ||
1034 | { 18432, 27000}, | ||
1035 | { 15616, 28000}, | ||
1036 | { 13312, 29000}, | ||
1037 | { 11520, 30000}, | ||
1038 | }; | ||
1039 | |||
1040 | static u32 interpolate_value(u32 value, struct linear_segments *segments, | ||
1041 | unsigned len) | ||
1042 | { | ||
1043 | u64 tmp64; | ||
1044 | u32 dx, dy; | ||
1045 | int i, ret; | ||
1046 | |||
1047 | if (value >= segments[0].x) | ||
1048 | return segments[0].y; | ||
1049 | if (value < segments[len-1].x) | ||
1050 | return segments[len-1].y; | ||
1051 | |||
1052 | for (i = 1; i < len - 1; i++) { | ||
1053 | /* If value is identical, no need to interpolate */ | ||
1054 | if (value == segments[i].x) | ||
1055 | return segments[i].y; | ||
1056 | if (value > segments[i].x) | ||
1057 | break; | ||
1058 | } | ||
1059 | |||
1060 | /* Linear interpolation between the two (x,y) points */ | ||
1061 | dy = segments[i].y - segments[i - 1].y; | ||
1062 | dx = segments[i - 1].x - segments[i].x; | ||
1063 | tmp64 = value - segments[i].x; | ||
1064 | tmp64 *= dy; | ||
1065 | do_div(tmp64, dx); | ||
1066 | ret = segments[i].y - tmp64; | ||
1067 | |||
1068 | return ret; | ||
1069 | } | ||
1070 | |||
1071 | static int mb86a20s_get_main_CNR(struct dvb_frontend *fe) | ||
1072 | { | ||
1073 | struct mb86a20s_state *state = fe->demodulator_priv; | ||
1074 | struct dtv_frontend_properties *c = &fe->dtv_property_cache; | ||
1075 | u32 cnr_linear, cnr; | ||
1076 | int rc, val; | ||
1077 | |||
1078 | /* Check if CNR is available */ | ||
1079 | rc = mb86a20s_readreg(state, 0x45); | ||
1080 | if (rc < 0) | ||
1081 | return rc; | ||
1082 | |||
1083 | if (!(rc & 0x40)) { | ||
1084 | dev_info(&state->i2c->dev, "%s: CNR is not available yet.\n", | ||
1085 | __func__); | ||
1086 | return -EBUSY; | ||
1087 | } | ||
1088 | val = rc; | ||
1089 | |||
1090 | rc = mb86a20s_readreg(state, 0x46); | ||
1091 | if (rc < 0) | ||
1092 | return rc; | ||
1093 | cnr_linear = rc << 8; | ||
1094 | |||
1095 | rc = mb86a20s_readreg(state, 0x46); | ||
1096 | if (rc < 0) | ||
1097 | return rc; | ||
1098 | cnr_linear |= rc; | ||
1099 | |||
1100 | cnr = interpolate_value(cnr_linear, | ||
1101 | cnr_to_db_table, ARRAY_SIZE(cnr_to_db_table)); | ||
1102 | |||
1103 | c->cnr.stat[0].scale = FE_SCALE_DECIBEL; | ||
1104 | c->cnr.stat[0].svalue = cnr; | ||
1105 | |||
1106 | dev_dbg(&state->i2c->dev, "%s: CNR is %d.%03d dB (%d)\n", | ||
1107 | __func__, cnr / 1000, cnr % 1000, cnr_linear); | ||
1108 | |||
1109 | /* CNR counter reset */ | ||
1110 | rc = mb86a20s_writereg(state, 0x45, val | 0x10); | ||
1111 | if (rc < 0) | ||
1112 | return rc; | ||
1113 | rc = mb86a20s_writereg(state, 0x45, val & 0x6f); | ||
1114 | |||
1115 | return rc; | ||
1116 | } | ||
1117 | |||
1118 | static int mb86a20s_get_per_layer_CNR(struct dvb_frontend *fe) | ||
1119 | { | ||
1120 | struct mb86a20s_state *state = fe->demodulator_priv; | ||
1121 | struct dtv_frontend_properties *c = &fe->dtv_property_cache; | ||
1122 | u32 mer, cnr; | ||
1123 | int rc, val, i; | ||
1124 | struct linear_segments *segs; | ||
1125 | unsigned segs_len; | ||
1126 | |||
1127 | dev_dbg(&state->i2c->dev, "%s called.\n", __func__); | ||
1128 | |||
1129 | /* Check if the measures are already available */ | ||
1130 | rc = mb86a20s_writereg(state, 0x50, 0x5b); | ||
1131 | if (rc < 0) | ||
1132 | return rc; | ||
1133 | rc = mb86a20s_readreg(state, 0x51); | ||
1134 | if (rc < 0) | ||
1135 | return rc; | ||
1136 | |||
1137 | /* Check if data is available */ | ||
1138 | if (!(rc & 0x01)) { | ||
1139 | dev_info(&state->i2c->dev, | ||
1140 | "%s: MER measures aren't available yet.\n", __func__); | ||
1141 | return -EBUSY; | ||
1142 | } | ||
1143 | |||
1144 | /* Read all layers */ | ||
1145 | for (i = 0; i < 3; i++) { | ||
1146 | if (!(c->isdbt_layer_enabled & (1 << i))) { | ||
1147 | c->cnr.stat[1 + i].scale = FE_SCALE_NOT_AVAILABLE; | ||
1148 | continue; | ||
1149 | } | ||
1150 | |||
1151 | rc = mb86a20s_writereg(state, 0x50, 0x52 + i * 3); | ||
1152 | if (rc < 0) | ||
1153 | return rc; | ||
1154 | rc = mb86a20s_readreg(state, 0x51); | ||
1155 | if (rc < 0) | ||
1156 | return rc; | ||
1157 | mer = rc << 16; | ||
1158 | rc = mb86a20s_writereg(state, 0x50, 0x53 + i * 3); | ||
1159 | if (rc < 0) | ||
1160 | return rc; | ||
1161 | rc = mb86a20s_readreg(state, 0x51); | ||
1162 | if (rc < 0) | ||
1163 | return rc; | ||
1164 | mer |= rc << 8; | ||
1165 | rc = mb86a20s_writereg(state, 0x50, 0x54 + i * 3); | ||
1166 | if (rc < 0) | ||
1167 | return rc; | ||
1168 | rc = mb86a20s_readreg(state, 0x51); | ||
1169 | if (rc < 0) | ||
1170 | return rc; | ||
1171 | mer |= rc; | ||
1172 | |||
1173 | switch (c->layer[i].modulation) { | ||
1174 | case DQPSK: | ||
1175 | case QPSK: | ||
1176 | segs = cnr_qpsk_table; | ||
1177 | segs_len = ARRAY_SIZE(cnr_qpsk_table); | ||
1178 | break; | ||
1179 | case QAM_16: | ||
1180 | segs = cnr_16qam_table; | ||
1181 | segs_len = ARRAY_SIZE(cnr_16qam_table); | ||
1182 | break; | ||
1183 | default: | ||
1184 | case QAM_64: | ||
1185 | segs = cnr_64qam_table; | ||
1186 | segs_len = ARRAY_SIZE(cnr_64qam_table); | ||
1187 | break; | ||
1188 | } | ||
1189 | cnr = interpolate_value(mer, segs, segs_len); | ||
1190 | |||
1191 | c->cnr.stat[1 + i].scale = FE_SCALE_DECIBEL; | ||
1192 | c->cnr.stat[1 + i].svalue = cnr; | ||
1193 | |||
1194 | dev_dbg(&state->i2c->dev, | ||
1195 | "%s: CNR for layer %c is %d.%03d dB (MER = %d).\n", | ||
1196 | __func__, 'A' + i, cnr / 1000, cnr % 1000, mer); | ||
1197 | |||
1198 | } | ||
1199 | |||
1200 | /* Start a new MER measurement */ | ||
1201 | /* MER counter reset */ | ||
1202 | rc = mb86a20s_writereg(state, 0x50, 0x50); | ||
1203 | if (rc < 0) | ||
1204 | return rc; | ||
1205 | rc = mb86a20s_readreg(state, 0x51); | ||
1206 | if (rc < 0) | ||
1207 | return rc; | ||
1208 | val = rc; | ||
1209 | |||
1210 | rc = mb86a20s_writereg(state, 0x51, val | 0x01); | ||
1211 | if (rc < 0) | ||
1212 | return rc; | ||
1213 | rc = mb86a20s_writereg(state, 0x51, val & 0x06); | ||
1214 | if (rc < 0) | ||
1215 | return rc; | ||
1216 | |||
1217 | return 0; | ||
1218 | } | ||
1219 | |||
894 | static void mb86a20s_stats_not_ready(struct dvb_frontend *fe) | 1220 | static void mb86a20s_stats_not_ready(struct dvb_frontend *fe) |
895 | { | 1221 | { |
896 | struct mb86a20s_state *state = fe->demodulator_priv; | 1222 | struct mb86a20s_state *state = fe->demodulator_priv; |
@@ -934,7 +1260,13 @@ static int mb86a20s_get_stats(struct dvb_frontend *fe) | |||
934 | u32 t_pre_bit_error = 0, t_pre_bit_count = 0; | 1260 | u32 t_pre_bit_error = 0, t_pre_bit_count = 0; |
935 | int active_layers = 0, ber_layers = 0; | 1261 | int active_layers = 0, ber_layers = 0; |
936 | 1262 | ||
1263 | dev_dbg(&state->i2c->dev, "%s called.\n", __func__); | ||
1264 | |||
1265 | mb86a20s_get_main_CNR(fe); | ||
1266 | |||
937 | /* Get per-layer stats */ | 1267 | /* Get per-layer stats */ |
1268 | mb86a20s_get_per_layer_CNR(fe); | ||
1269 | |||
938 | for (i = 0; i < 3; i++) { | 1270 | for (i = 0; i < 3; i++) { |
939 | if (c->isdbt_layer_enabled & (1 << i)) { | 1271 | if (c->isdbt_layer_enabled & (1 << i)) { |
940 | /* Layer is active and has rc segments */ | 1272 | /* Layer is active and has rc segments */ |