Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

[media] TS2020: Calculate tuner gain correctly

The TS2020 and TS2022 tuners take an input from the demodulator indicating the
AGC setting on that component that is then used to influence the tuner's own
gain. This should be taken into account when calculating the gain and signal
strength.

Further, the existing TS2020 driver miscalculates the signal strength as the
result of its calculations can exceed the storage capacity of the 16-bit word
used to return it to userspace.

To this end:

(1) Add a callback function (->get_agc_pwm()) in the ts2020_config struct that
the tuner can call to get the AGC PWM value from the demodulator.

(2) Modify the TS2020 driver to calculate the gain according to Montage's
specification with the adjustment that we produce a negative value and
scale it to 0.001dB units (which is what the DVBv5 API will require):

(a) Callback to the demodulator to retrieve the AGC PWM value and then
turn that into Vagc for incorporation in the calculations. If the
callback is unset, assume a Vagc of 0.

(b) Calculate the tuner gain from a combination of Vagc and the tuner's RF
gain and baseband gain settings.

(3) Turn this into a percentage signal strength as per Montage's
specification for return to userspace with the DVBv3 API.

(4) Provide a function in the M88DS3103 demodulator driver that can be used to
get the AGC PWM value on behalf of the tuner.

(5) The ts2020_config.get_agc_pwm function should be set by the code that
stitches together the drivers for each card.

For the DVBSky cards that use the M88DS3103 with the TS2020 or the TS2022,
set the get_agc_pwm function to point to m88ds3103_get_agc_pwm.

I have tested this with a DVBSky S952 card which has an M88DS3103 and a TS2022.

Thanks to Montage for providing access to information about the workings of
these parts.

Signed-off-by: David Howells <dhowells@redhat.com>
Signed-off-by: Antti Palosaari <crope@iki.fi>
Signed-off-by: Mauro Carvalho Chehab <mchehab@osg.samsung.com>

authored by

David Howells and committed by
Mauro Carvalho Chehab
0f91c9d6 76b91be3

+150 -20
+16
drivers/media/dvb-frontends/m88ds3103.c
··· 52 52 return ret; 53 53 } 54 54 55 + /* 56 + * Get the demodulator AGC PWM voltage setting supplied to the tuner. 57 + */ 58 + int m88ds3103_get_agc_pwm(struct dvb_frontend *fe, u8 *_agc_pwm) 59 + { 60 + struct m88ds3103_dev *dev = fe->demodulator_priv; 61 + unsigned tmp; 62 + int ret; 63 + 64 + ret = regmap_read(dev->regmap, 0x3f, &tmp); 65 + if (ret == 0) 66 + *_agc_pwm = tmp; 67 + return ret; 68 + } 69 + EXPORT_SYMBOL(m88ds3103_get_agc_pwm); 70 + 55 71 static int m88ds3103_read_status(struct dvb_frontend *fe, 56 72 enum fe_status *status) 57 73 {
+2
drivers/media/dvb-frontends/m88ds3103.h
··· 176 176 const struct m88ds3103_config *config, 177 177 struct i2c_adapter *i2c, 178 178 struct i2c_adapter **tuner_i2c); 179 + extern int m88ds3103_get_agc_pwm(struct dvb_frontend *fe, u8 *_agc_pwm); 179 180 #else 180 181 static inline struct dvb_frontend *m88ds3103_attach( 181 182 const struct m88ds3103_config *config, ··· 186 185 pr_warn("%s: driver disabled by Kconfig\n", __func__); 187 186 return NULL; 188 187 } 188 + #define m88ds3103_get_agc_pwm NULL 189 189 #endif 190 190 191 191 #endif
+122 -20
drivers/media/dvb-frontends/ts2020.c
··· 32 32 struct regmap_config regmap_config; 33 33 struct regmap *regmap; 34 34 struct dvb_frontend *fe; 35 + int (*get_agc_pwm)(struct dvb_frontend *fe, u8 *_agc_pwm); 35 36 /* i2c details */ 36 37 int i2c_address; 37 38 struct i2c_adapter *i2c; ··· 314 313 return 0; 315 314 } 316 315 317 - /* read TS2020 signal strength */ 316 + /* 317 + * Get the tuner gain. 318 + * @fe: The front end for which we're determining the gain 319 + * @v_agc: The voltage of the AGC from the demodulator (0-2600mV) 320 + * @_gain: Where to store the gain (in 0.001dB units) 321 + * 322 + * Returns 0 or a negative error code. 323 + */ 324 + static int ts2020_read_tuner_gain(struct dvb_frontend *fe, unsigned v_agc, 325 + __s64 *_gain) 326 + { 327 + struct ts2020_priv *priv = fe->tuner_priv; 328 + unsigned long gain1, gain2, gain3; 329 + unsigned utmp; 330 + int ret; 331 + 332 + /* Read the RF gain */ 333 + ret = regmap_read(priv->regmap, 0x3d, &utmp); 334 + if (ret < 0) 335 + return ret; 336 + gain1 = utmp & 0x1f; 337 + 338 + /* Read the baseband gain */ 339 + ret = regmap_read(priv->regmap, 0x21, &utmp); 340 + if (ret < 0) 341 + return ret; 342 + gain2 = utmp & 0x1f; 343 + 344 + switch (priv->tuner) { 345 + case TS2020_M88TS2020: 346 + gain1 = clamp_t(long, gain1, 0, 15); 347 + gain2 = clamp_t(long, gain2, 0, 13); 348 + v_agc = clamp_t(long, v_agc, 400, 1100); 349 + 350 + *_gain = -(gain1 * 2330 + 351 + gain2 * 3500 + 352 + v_agc * 24 / 10 * 10 + 353 + 10000); 354 + /* gain in range -19600 to -116850 in units of 0.001dB */ 355 + break; 356 + 357 + case TS2020_M88TS2022: 358 + ret = regmap_read(priv->regmap, 0x66, &utmp); 359 + if (ret < 0) 360 + return ret; 361 + gain3 = (utmp >> 3) & 0x07; 362 + 363 + gain1 = clamp_t(long, gain1, 0, 15); 364 + gain2 = clamp_t(long, gain2, 2, 16); 365 + gain3 = clamp_t(long, gain3, 0, 6); 366 + v_agc = clamp_t(long, v_agc, 600, 1600); 367 + 368 + *_gain = -(gain1 * 2650 + 369 + gain2 * 3380 + 370 + gain3 * 2850 + 371 + v_agc * 176 / 100 * 10 - 372 + 30000); 373 + /* gain in range -47320 to -158950 in units of 0.001dB */ 374 + break; 375 + } 376 + 377 + return 0; 378 + } 379 + 380 + /* 381 + * Get the AGC information from the demodulator and use that to calculate the 382 + * tuner gain. 383 + */ 384 + static int ts2020_get_tuner_gain(struct dvb_frontend *fe, __s64 *_gain) 385 + { 386 + struct ts2020_priv *priv = fe->tuner_priv; 387 + int v_agc = 0, ret; 388 + u8 agc_pwm; 389 + 390 + /* Read the AGC PWM rate from the demodulator */ 391 + if (priv->get_agc_pwm) { 392 + ret = priv->get_agc_pwm(fe, &agc_pwm); 393 + if (ret < 0) 394 + return ret; 395 + 396 + switch (priv->tuner) { 397 + case TS2020_M88TS2020: 398 + v_agc = (int)agc_pwm * 20 - 1166; 399 + break; 400 + case TS2020_M88TS2022: 401 + v_agc = (int)agc_pwm * 16 - 670; 402 + break; 403 + } 404 + 405 + if (v_agc < 0) 406 + v_agc = 0; 407 + } 408 + 409 + return ts2020_read_tuner_gain(fe, v_agc, _gain); 410 + } 411 + 412 + /* 413 + * Read TS2020 signal strength in v3 format. 414 + */ 318 415 static int ts2020_read_signal_strength(struct dvb_frontend *fe, 319 416 u16 *signal_strength) 320 417 { 321 - struct ts2020_priv *priv = fe->tuner_priv; 322 - unsigned int utmp; 323 - u16 sig_reading, sig_strength; 324 - u8 rfgain, bbgain; 418 + unsigned strength; 419 + __s64 gain; 420 + int ret; 325 421 326 - regmap_read(priv->regmap, 0x3d, &utmp); 327 - rfgain = utmp & 0x1f; 328 - regmap_read(priv->regmap, 0x21, &utmp); 329 - bbgain = utmp & 0x1f; 422 + /* Determine the total gain of the tuner */ 423 + ret = ts2020_get_tuner_gain(fe, &gain); 424 + if (ret < 0) 425 + return ret; 330 426 331 - if (rfgain > 15) 332 - rfgain = 15; 333 - if (bbgain > 13) 334 - bbgain = 13; 427 + /* Calculate the signal strength based on the total gain of the tuner */ 428 + if (gain < -85000) 429 + /* 0%: no signal or weak signal */ 430 + strength = 0; 431 + else if (gain < -65000) 432 + /* 0% - 60%: weak signal */ 433 + strength = 0 + (85000 + gain) * 3 / 1000; 434 + else if (gain < -45000) 435 + /* 60% - 90%: normal signal */ 436 + strength = 60 + (65000 + gain) * 3 / 2000; 437 + else 438 + /* 90% - 99%: strong signal */ 439 + strength = 90 + (45000 + gain) / 5000; 335 440 336 - sig_reading = rfgain * 2 + bbgain * 3; 337 - 338 - sig_strength = 40 + (64 - sig_reading) * 50 / 64 ; 339 - 340 - /* cook the value to be suitable for szap-s2 human readable output */ 341 - *signal_strength = sig_strength * 1000; 342 - 441 + *signal_strength = strength * 65535 / 100; 343 442 return 0; 344 443 } 345 444 ··· 543 442 dev->clk_out_div = pdata->clk_out_div; 544 443 dev->frequency_div = pdata->frequency_div; 545 444 dev->fe = fe; 445 + dev->get_agc_pwm = pdata->get_agc_pwm; 546 446 fe->tuner_priv = dev; 547 447 dev->client = client; 548 448
+5
drivers/media/dvb-frontends/ts2020.h
··· 57 57 * driver private, do not set value 58 58 */ 59 59 u8 attach_in_use:1; 60 + 61 + /* Operation to be called by the ts2020 driver to get the value of the 62 + * AGC PWM tuner input as theoretically output by the demodulator. 63 + */ 64 + int (*get_agc_pwm)(struct dvb_frontend *fe, u8 *_agc_pwm); 60 65 }; 61 66 62 67 /* Do not add new ts2020_attach() users! Use I2C bindings instead. */
+3
drivers/media/pci/cx23885/cx23885-dvb.c
··· 1908 1908 /* attach tuner */ 1909 1909 memset(&ts2020_config, 0, sizeof(ts2020_config)); 1910 1910 ts2020_config.fe = fe0->dvb.frontend; 1911 + ts2020_config.get_agc_pwm = m88ds3103_get_agc_pwm; 1911 1912 memset(&info, 0, sizeof(struct i2c_board_info)); 1912 1913 strlcpy(info.type, "ts2020", I2C_NAME_SIZE); 1913 1914 info.addr = 0x60; ··· 2040 2039 /* attach tuner */ 2041 2040 memset(&ts2020_config, 0, sizeof(ts2020_config)); 2042 2041 ts2020_config.fe = fe0->dvb.frontend; 2042 + ts2020_config.get_agc_pwm = m88ds3103_get_agc_pwm; 2043 2043 memset(&info, 0, sizeof(struct i2c_board_info)); 2044 2044 strlcpy(info.type, "ts2020", I2C_NAME_SIZE); 2045 2045 info.addr = 0x60; ··· 2086 2084 /* attach tuner */ 2087 2085 memset(&ts2020_config, 0, sizeof(ts2020_config)); 2088 2086 ts2020_config.fe = fe0->dvb.frontend; 2087 + ts2020_config.get_agc_pwm = m88ds3103_get_agc_pwm; 2089 2088 memset(&info, 0, sizeof(struct i2c_board_info)); 2090 2089 strlcpy(info.type, "ts2020", I2C_NAME_SIZE); 2091 2090 info.addr = 0x60;
+2
drivers/media/usb/dvb-usb-v2/dvbsky.c
··· 332 332 333 333 /* attach tuner */ 334 334 ts2020_config.fe = adap->fe[0]; 335 + ts2020_config.get_agc_pwm = m88ds3103_get_agc_pwm; 335 336 strlcpy(info.type, "ts2020", I2C_NAME_SIZE); 336 337 info.addr = 0x60; 337 338 info.platform_data = &ts2020_config; ··· 455 454 456 455 /* attach tuner */ 457 456 ts2020_config.fe = adap->fe[0]; 457 + ts2020_config.get_agc_pwm = m88ds3103_get_agc_pwm; 458 458 strlcpy(info.type, "ts2020", I2C_NAME_SIZE); 459 459 info.addr = 0x60; 460 460 info.platform_data = &ts2020_config;