174ba9207SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 26fef4fc7SKonstantin Dimitrov /* 36fef4fc7SKonstantin Dimitrov Montage Technology TS2020 - Silicon Tuner driver 46fef4fc7SKonstantin Dimitrov Copyright (C) 2009-2012 Konstantin Dimitrov <kosio.dimitrov@gmail.com> 56fef4fc7SKonstantin Dimitrov 66fef4fc7SKonstantin Dimitrov Copyright (C) 2009-2012 TurboSight.com 76fef4fc7SKonstantin Dimitrov 86fef4fc7SKonstantin Dimitrov */ 96fef4fc7SKonstantin Dimitrov 10fada1935SMauro Carvalho Chehab #include <media/dvb_frontend.h> 116fef4fc7SKonstantin Dimitrov #include "ts2020.h" 12f158cbceSAntti Palosaari #include <linux/regmap.h> 1387b09bd0SMauro Carvalho Chehab #include <linux/math64.h> 146fef4fc7SKonstantin Dimitrov 156fef4fc7SKonstantin Dimitrov #define TS2020_XTAL_FREQ 27000 /* in kHz */ 16b858c331SIgor M. Liplianin #define FREQ_OFFSET_LOW_SYM_RATE 3000 176fef4fc7SKonstantin Dimitrov 18b858c331SIgor M. Liplianin struct ts2020_priv { 19e6ad9ce3SAntti Palosaari struct i2c_client *client; 20f158cbceSAntti Palosaari struct mutex regmap_mutex; 21f158cbceSAntti Palosaari struct regmap_config regmap_config; 22f158cbceSAntti Palosaari struct regmap *regmap; 23dc245a5fSAntti Palosaari struct dvb_frontend *fe; 243366cd5dSDavid Howells struct delayed_work stat_work; 250f91c9d6SDavid Howells int (*get_agc_pwm)(struct dvb_frontend *fe, u8 *_agc_pwm); 26b858c331SIgor M. Liplianin /* i2c details */ 276fef4fc7SKonstantin Dimitrov struct i2c_adapter *i2c; 283366cd5dSDavid Howells int i2c_address; 290f20baadSDavid Howells bool loop_through:1; 30abd9025bSAntti Palosaari u8 clk_out:2; 31abd9025bSAntti Palosaari u8 clk_out_div:5; 32c7275ae1SDavid Howells bool dont_poll:1; 33af9d5255SAntti Palosaari u32 frequency_div; /* LO output divider switch frequency */ 34af9d5255SAntti Palosaari u32 frequency_khz; /* actual used LO frequency */ 35abd9025bSAntti Palosaari #define TS2020_M88TS2020 0 36abd9025bSAntti Palosaari #define TS2020_M88TS2022 1 37abd9025bSAntti Palosaari u8 tuner; 38abd9025bSAntti Palosaari }; 39abd9025bSAntti Palosaari 40abd9025bSAntti Palosaari struct ts2020_reg_val { 41abd9025bSAntti Palosaari u8 reg; 42abd9025bSAntti Palosaari u8 val; 436fef4fc7SKonstantin Dimitrov }; 446fef4fc7SKonstantin Dimitrov 45c7275ae1SDavid Howells static void ts2020_stat_work(struct work_struct *work); 46c7275ae1SDavid Howells 47194ced7aSMax Kellermann static void ts2020_release(struct dvb_frontend *fe) 48b858c331SIgor M. Liplianin { 49e6ad9ce3SAntti Palosaari struct ts2020_priv *priv = fe->tuner_priv; 50e6ad9ce3SAntti Palosaari struct i2c_client *client = priv->client; 51e6ad9ce3SAntti Palosaari 52e6ad9ce3SAntti Palosaari dev_dbg(&client->dev, "\n"); 53e6ad9ce3SAntti Palosaari 54e6ad9ce3SAntti Palosaari i2c_unregister_device(client); 55b858c331SIgor M. Liplianin } 56b858c331SIgor M. Liplianin 57b858c331SIgor M. Liplianin static int ts2020_sleep(struct dvb_frontend *fe) 586fef4fc7SKonstantin Dimitrov { 59b858c331SIgor M. Liplianin struct ts2020_priv *priv = fe->tuner_priv; 603366cd5dSDavid Howells int ret; 61b3226f96SAntti Palosaari u8 u8tmp; 626fef4fc7SKonstantin Dimitrov 63b3226f96SAntti Palosaari if (priv->tuner == TS2020_M88TS2020) 64b3226f96SAntti Palosaari u8tmp = 0x0a; /* XXX: probably wrong */ 65b3226f96SAntti Palosaari else 66b3226f96SAntti Palosaari u8tmp = 0x00; 67abd9025bSAntti Palosaari 683366cd5dSDavid Howells ret = regmap_write(priv->regmap, u8tmp, 0x00); 693366cd5dSDavid Howells if (ret < 0) 703366cd5dSDavid Howells return ret; 713366cd5dSDavid Howells 723366cd5dSDavid Howells /* stop statistics polling */ 73c7275ae1SDavid Howells if (!priv->dont_poll) 743366cd5dSDavid Howells cancel_delayed_work_sync(&priv->stat_work); 753366cd5dSDavid Howells return 0; 766fef4fc7SKonstantin Dimitrov } 776fef4fc7SKonstantin Dimitrov 786fef4fc7SKonstantin Dimitrov static int ts2020_init(struct dvb_frontend *fe) 796fef4fc7SKonstantin Dimitrov { 803366cd5dSDavid Howells struct dtv_frontend_properties *c = &fe->dtv_property_cache; 81b858c331SIgor M. Liplianin struct ts2020_priv *priv = fe->tuner_priv; 82abd9025bSAntti Palosaari int i; 83abd9025bSAntti Palosaari u8 u8tmp; 84b858c331SIgor M. Liplianin 85abd9025bSAntti Palosaari if (priv->tuner == TS2020_M88TS2020) { 86f158cbceSAntti Palosaari regmap_write(priv->regmap, 0x42, 0x73); 87f158cbceSAntti Palosaari regmap_write(priv->regmap, 0x05, priv->clk_out_div); 88f158cbceSAntti Palosaari regmap_write(priv->regmap, 0x20, 0x27); 89f158cbceSAntti Palosaari regmap_write(priv->regmap, 0x07, 0x02); 90f158cbceSAntti Palosaari regmap_write(priv->regmap, 0x11, 0xff); 91f158cbceSAntti Palosaari regmap_write(priv->regmap, 0x60, 0xf9); 92f158cbceSAntti Palosaari regmap_write(priv->regmap, 0x08, 0x01); 93f158cbceSAntti Palosaari regmap_write(priv->regmap, 0x00, 0x41); 94abd9025bSAntti Palosaari } else { 95abd9025bSAntti Palosaari static const struct ts2020_reg_val reg_vals[] = { 96abd9025bSAntti Palosaari {0x7d, 0x9d}, 97abd9025bSAntti Palosaari {0x7c, 0x9a}, 98abd9025bSAntti Palosaari {0x7a, 0x76}, 99abd9025bSAntti Palosaari {0x3b, 0x01}, 100abd9025bSAntti Palosaari {0x63, 0x88}, 101abd9025bSAntti Palosaari {0x61, 0x85}, 102abd9025bSAntti Palosaari {0x22, 0x30}, 103abd9025bSAntti Palosaari {0x30, 0x40}, 104abd9025bSAntti Palosaari {0x20, 0x23}, 105abd9025bSAntti Palosaari {0x24, 0x02}, 106abd9025bSAntti Palosaari {0x12, 0xa0}, 107abd9025bSAntti Palosaari }; 108abd9025bSAntti Palosaari 109f158cbceSAntti Palosaari regmap_write(priv->regmap, 0x00, 0x01); 110f158cbceSAntti Palosaari regmap_write(priv->regmap, 0x00, 0x03); 111abd9025bSAntti Palosaari 112abd9025bSAntti Palosaari switch (priv->clk_out) { 113abd9025bSAntti Palosaari case TS2020_CLK_OUT_DISABLED: 114abd9025bSAntti Palosaari u8tmp = 0x60; 115abd9025bSAntti Palosaari break; 116abd9025bSAntti Palosaari case TS2020_CLK_OUT_ENABLED: 117abd9025bSAntti Palosaari u8tmp = 0x70; 118f158cbceSAntti Palosaari regmap_write(priv->regmap, 0x05, priv->clk_out_div); 119abd9025bSAntti Palosaari break; 120abd9025bSAntti Palosaari case TS2020_CLK_OUT_ENABLED_XTALOUT: 121abd9025bSAntti Palosaari u8tmp = 0x6c; 122abd9025bSAntti Palosaari break; 123abd9025bSAntti Palosaari default: 124abd9025bSAntti Palosaari u8tmp = 0x60; 125abd9025bSAntti Palosaari break; 126abd9025bSAntti Palosaari } 127abd9025bSAntti Palosaari 128f158cbceSAntti Palosaari regmap_write(priv->regmap, 0x42, u8tmp); 129abd9025bSAntti Palosaari 130abd9025bSAntti Palosaari if (priv->loop_through) 131abd9025bSAntti Palosaari u8tmp = 0xec; 132abd9025bSAntti Palosaari else 133abd9025bSAntti Palosaari u8tmp = 0x6c; 134abd9025bSAntti Palosaari 135f158cbceSAntti Palosaari regmap_write(priv->regmap, 0x62, u8tmp); 136abd9025bSAntti Palosaari 137abd9025bSAntti Palosaari for (i = 0; i < ARRAY_SIZE(reg_vals); i++) 138f158cbceSAntti Palosaari regmap_write(priv->regmap, reg_vals[i].reg, 139f158cbceSAntti Palosaari reg_vals[i].val); 140abd9025bSAntti Palosaari } 141b858c331SIgor M. Liplianin 1423366cd5dSDavid Howells /* Initialise v5 stats here */ 1433366cd5dSDavid Howells c->strength.len = 1; 1443366cd5dSDavid Howells c->strength.stat[0].scale = FE_SCALE_DECIBEL; 1453366cd5dSDavid Howells c->strength.stat[0].uvalue = 0; 1463366cd5dSDavid Howells 147c7275ae1SDavid Howells /* Start statistics polling by invoking the work function */ 148c7275ae1SDavid Howells ts2020_stat_work(&priv->stat_work.work); 1496fef4fc7SKonstantin Dimitrov return 0; 1506fef4fc7SKonstantin Dimitrov } 1516fef4fc7SKonstantin Dimitrov 152b858c331SIgor M. Liplianin static int ts2020_tuner_gate_ctrl(struct dvb_frontend *fe, u8 offset) 1536fef4fc7SKonstantin Dimitrov { 154f158cbceSAntti Palosaari struct ts2020_priv *priv = fe->tuner_priv; 155b858c331SIgor M. Liplianin int ret; 156f158cbceSAntti Palosaari ret = regmap_write(priv->regmap, 0x51, 0x1f - offset); 157f158cbceSAntti Palosaari ret |= regmap_write(priv->regmap, 0x51, 0x1f); 158f158cbceSAntti Palosaari ret |= regmap_write(priv->regmap, 0x50, offset); 159f158cbceSAntti Palosaari ret |= regmap_write(priv->regmap, 0x50, 0x00); 160b858c331SIgor M. Liplianin msleep(20); 161b858c331SIgor M. Liplianin return ret; 162b858c331SIgor M. Liplianin } 1636fef4fc7SKonstantin Dimitrov 164b858c331SIgor M. Liplianin static int ts2020_set_tuner_rf(struct dvb_frontend *fe) 165b858c331SIgor M. Liplianin { 166f158cbceSAntti Palosaari struct ts2020_priv *dev = fe->tuner_priv; 167f158cbceSAntti Palosaari int ret; 168f158cbceSAntti Palosaari unsigned int utmp; 1696fef4fc7SKonstantin Dimitrov 170f158cbceSAntti Palosaari ret = regmap_read(dev->regmap, 0x3d, &utmp); 17137d1e62bSYizhuo if (ret) 17237d1e62bSYizhuo return ret; 17337d1e62bSYizhuo 174f158cbceSAntti Palosaari utmp &= 0x7f; 175f158cbceSAntti Palosaari if (utmp < 0x16) 176f158cbceSAntti Palosaari utmp = 0xa1; 177f158cbceSAntti Palosaari else if (utmp == 0x16) 178f158cbceSAntti Palosaari utmp = 0x99; 179b858c331SIgor M. Liplianin else 180f158cbceSAntti Palosaari utmp = 0xf9; 1816fef4fc7SKonstantin Dimitrov 182f158cbceSAntti Palosaari regmap_write(dev->regmap, 0x60, utmp); 183f158cbceSAntti Palosaari ret = ts2020_tuner_gate_ctrl(fe, 0x08); 1846fef4fc7SKonstantin Dimitrov 185f158cbceSAntti Palosaari return ret; 1866fef4fc7SKonstantin Dimitrov } 1876fef4fc7SKonstantin Dimitrov 1886fef4fc7SKonstantin Dimitrov static int ts2020_set_params(struct dvb_frontend *fe) 1896fef4fc7SKonstantin Dimitrov { 1906fef4fc7SKonstantin Dimitrov struct dtv_frontend_properties *c = &fe->dtv_property_cache; 1919898df64SMalcolm Priestley struct ts2020_priv *priv = fe->tuner_priv; 192b858c331SIgor M. Liplianin int ret; 193f158cbceSAntti Palosaari unsigned int utmp; 194b858c331SIgor M. Liplianin u32 f3db, gdiv28; 195af9d5255SAntti Palosaari u16 u16tmp, value, lpf_coeff; 196af9d5255SAntti Palosaari u8 buf[3], reg10, lpf_mxdiv, mlpf_max, mlpf_min, nlpf; 197af9d5255SAntti Palosaari unsigned int f_ref_khz, f_vco_khz, div_ref, div_out, pll_n; 198af9d5255SAntti Palosaari unsigned int frequency_khz = c->frequency; 1996fef4fc7SKonstantin Dimitrov 200af9d5255SAntti Palosaari /* 201af9d5255SAntti Palosaari * Integer-N PLL synthesizer 202af9d5255SAntti Palosaari * kHz is used for all calculations to keep calculations within 32-bit 203af9d5255SAntti Palosaari */ 204af9d5255SAntti Palosaari f_ref_khz = TS2020_XTAL_FREQ; 205af9d5255SAntti Palosaari div_ref = DIV_ROUND_CLOSEST(f_ref_khz, 2000); 206af9d5255SAntti Palosaari 207af9d5255SAntti Palosaari /* select LO output divider */ 208af9d5255SAntti Palosaari if (frequency_khz < priv->frequency_div) { 209af9d5255SAntti Palosaari div_out = 4; 210af9d5255SAntti Palosaari reg10 = 0x10; 211af9d5255SAntti Palosaari } else { 212af9d5255SAntti Palosaari div_out = 2; 213af9d5255SAntti Palosaari reg10 = 0x00; 214af9d5255SAntti Palosaari } 215af9d5255SAntti Palosaari 216af9d5255SAntti Palosaari f_vco_khz = frequency_khz * div_out; 217af9d5255SAntti Palosaari pll_n = f_vco_khz * div_ref / f_ref_khz; 218af9d5255SAntti Palosaari pll_n += pll_n % 2; 219af9d5255SAntti Palosaari priv->frequency_khz = pll_n * f_ref_khz / div_ref / div_out; 220af9d5255SAntti Palosaari 221af9d5255SAntti Palosaari pr_debug("frequency=%u offset=%d f_vco_khz=%u pll_n=%u div_ref=%u div_out=%u\n", 222af9d5255SAntti Palosaari priv->frequency_khz, priv->frequency_khz - c->frequency, 223af9d5255SAntti Palosaari f_vco_khz, pll_n, div_ref, div_out); 2246fef4fc7SKonstantin Dimitrov 225abd9025bSAntti Palosaari if (priv->tuner == TS2020_M88TS2020) { 226abd9025bSAntti Palosaari lpf_coeff = 2766; 227af9d5255SAntti Palosaari reg10 |= 0x01; 228f158cbceSAntti Palosaari ret = regmap_write(priv->regmap, 0x10, reg10); 229abd9025bSAntti Palosaari } else { 230abd9025bSAntti Palosaari lpf_coeff = 3200; 231af9d5255SAntti Palosaari reg10 |= 0x0b; 232f158cbceSAntti Palosaari ret = regmap_write(priv->regmap, 0x10, reg10); 233f158cbceSAntti Palosaari ret |= regmap_write(priv->regmap, 0x11, 0x40); 234abd9025bSAntti Palosaari } 2356fef4fc7SKonstantin Dimitrov 236af9d5255SAntti Palosaari u16tmp = pll_n - 1024; 237af9d5255SAntti Palosaari buf[0] = (u16tmp >> 8) & 0xff; 238af9d5255SAntti Palosaari buf[1] = (u16tmp >> 0) & 0xff; 239af9d5255SAntti Palosaari buf[2] = div_ref - 8; 2406fef4fc7SKonstantin Dimitrov 241f158cbceSAntti Palosaari ret |= regmap_write(priv->regmap, 0x01, buf[0]); 242f158cbceSAntti Palosaari ret |= regmap_write(priv->regmap, 0x02, buf[1]); 243f158cbceSAntti Palosaari ret |= regmap_write(priv->regmap, 0x03, buf[2]); 244af9d5255SAntti Palosaari 245b858c331SIgor M. Liplianin ret |= ts2020_tuner_gate_ctrl(fe, 0x10); 246b858c331SIgor M. Liplianin if (ret < 0) 247b858c331SIgor M. Liplianin return -ENODEV; 2486fef4fc7SKonstantin Dimitrov 249b858c331SIgor M. Liplianin ret |= ts2020_tuner_gate_ctrl(fe, 0x08); 2506fef4fc7SKonstantin Dimitrov 251b858c331SIgor M. Liplianin /* Tuner RF */ 252abd9025bSAntti Palosaari if (priv->tuner == TS2020_M88TS2020) 253b858c331SIgor M. Liplianin ret |= ts2020_set_tuner_rf(fe); 2546fef4fc7SKonstantin Dimitrov 255b858c331SIgor M. Liplianin gdiv28 = (TS2020_XTAL_FREQ / 1000 * 1694 + 500) / 1000; 256f158cbceSAntti Palosaari ret |= regmap_write(priv->regmap, 0x04, gdiv28 & 0xff); 257b858c331SIgor M. Liplianin ret |= ts2020_tuner_gate_ctrl(fe, 0x04); 258b858c331SIgor M. Liplianin if (ret < 0) 259b858c331SIgor M. Liplianin return -ENODEV; 2606fef4fc7SKonstantin Dimitrov 261abd9025bSAntti Palosaari if (priv->tuner == TS2020_M88TS2022) { 262f158cbceSAntti Palosaari ret = regmap_write(priv->regmap, 0x25, 0x00); 263f158cbceSAntti Palosaari ret |= regmap_write(priv->regmap, 0x27, 0x70); 264f158cbceSAntti Palosaari ret |= regmap_write(priv->regmap, 0x41, 0x09); 265f158cbceSAntti Palosaari ret |= regmap_write(priv->regmap, 0x08, 0x0b); 266abd9025bSAntti Palosaari if (ret < 0) 267abd9025bSAntti Palosaari return -ENODEV; 268abd9025bSAntti Palosaari } 269abd9025bSAntti Palosaari 270f158cbceSAntti Palosaari regmap_read(priv->regmap, 0x26, &utmp); 271f158cbceSAntti Palosaari value = utmp; 2726fef4fc7SKonstantin Dimitrov 2732ca58f45SAntti Palosaari f3db = (c->bandwidth_hz / 1000 / 2) + 2000; 2742ca58f45SAntti Palosaari f3db += FREQ_OFFSET_LOW_SYM_RATE; /* FIXME: ~always too wide filter */ 2752ca58f45SAntti Palosaari f3db = clamp(f3db, 7000U, 40000U); 2766fef4fc7SKonstantin Dimitrov 277b858c331SIgor M. Liplianin gdiv28 = gdiv28 * 207 / (value * 2 + 151); 278b858c331SIgor M. Liplianin mlpf_max = gdiv28 * 135 / 100; 279b858c331SIgor M. Liplianin mlpf_min = gdiv28 * 78 / 100; 2806fef4fc7SKonstantin Dimitrov if (mlpf_max > 63) 2816fef4fc7SKonstantin Dimitrov mlpf_max = 63; 2826fef4fc7SKonstantin Dimitrov 283b858c331SIgor M. Liplianin nlpf = (f3db * gdiv28 * 2 / lpf_coeff / 284b858c331SIgor M. Liplianin (TS2020_XTAL_FREQ / 1000) + 1) / 2; 2856fef4fc7SKonstantin Dimitrov if (nlpf > 23) 2866fef4fc7SKonstantin Dimitrov nlpf = 23; 2876fef4fc7SKonstantin Dimitrov if (nlpf < 1) 2886fef4fc7SKonstantin Dimitrov nlpf = 1; 2896fef4fc7SKonstantin Dimitrov 290b858c331SIgor M. Liplianin lpf_mxdiv = (nlpf * (TS2020_XTAL_FREQ / 1000) 291b858c331SIgor M. Liplianin * lpf_coeff * 2 / f3db + 1) / 2; 2926fef4fc7SKonstantin Dimitrov 293b858c331SIgor M. Liplianin if (lpf_mxdiv < mlpf_min) { 2946fef4fc7SKonstantin Dimitrov nlpf++; 295b858c331SIgor M. Liplianin lpf_mxdiv = (nlpf * (TS2020_XTAL_FREQ / 1000) 296b858c331SIgor M. Liplianin * lpf_coeff * 2 / f3db + 1) / 2; 2976fef4fc7SKonstantin Dimitrov } 2986fef4fc7SKonstantin Dimitrov 299b858c331SIgor M. Liplianin if (lpf_mxdiv > mlpf_max) 300b858c331SIgor M. Liplianin lpf_mxdiv = mlpf_max; 3016fef4fc7SKonstantin Dimitrov 302f158cbceSAntti Palosaari ret = regmap_write(priv->regmap, 0x04, lpf_mxdiv); 303f158cbceSAntti Palosaari ret |= regmap_write(priv->regmap, 0x06, nlpf); 3046fef4fc7SKonstantin Dimitrov 305b858c331SIgor M. Liplianin ret |= ts2020_tuner_gate_ctrl(fe, 0x04); 3066fef4fc7SKonstantin Dimitrov 307b858c331SIgor M. Liplianin ret |= ts2020_tuner_gate_ctrl(fe, 0x01); 308b858c331SIgor M. Liplianin 309b858c331SIgor M. Liplianin msleep(80); 310b858c331SIgor M. Liplianin 311b858c331SIgor M. Liplianin return (ret < 0) ? -EINVAL : 0; 3126fef4fc7SKonstantin Dimitrov } 3136fef4fc7SKonstantin Dimitrov 314b858c331SIgor M. Liplianin static int ts2020_get_frequency(struct dvb_frontend *fe, u32 *frequency) 3156fef4fc7SKonstantin Dimitrov { 316b858c331SIgor M. Liplianin struct ts2020_priv *priv = fe->tuner_priv; 317abd9025bSAntti Palosaari 318af9d5255SAntti Palosaari *frequency = priv->frequency_khz; 319abd9025bSAntti Palosaari return 0; 320abd9025bSAntti Palosaari } 321abd9025bSAntti Palosaari 322abd9025bSAntti Palosaari static int ts2020_get_if_frequency(struct dvb_frontend *fe, u32 *frequency) 323abd9025bSAntti Palosaari { 324abd9025bSAntti Palosaari *frequency = 0; /* Zero-IF */ 3256fef4fc7SKonstantin Dimitrov return 0; 3266fef4fc7SKonstantin Dimitrov } 3276fef4fc7SKonstantin Dimitrov 3280f91c9d6SDavid Howells /* 3290f91c9d6SDavid Howells * Get the tuner gain. 3300f91c9d6SDavid Howells * @fe: The front end for which we're determining the gain 3310f91c9d6SDavid Howells * @v_agc: The voltage of the AGC from the demodulator (0-2600mV) 3320f91c9d6SDavid Howells * @_gain: Where to store the gain (in 0.001dB units) 3330f91c9d6SDavid Howells * 3340f91c9d6SDavid Howells * Returns 0 or a negative error code. 3350f91c9d6SDavid Howells */ 3360f91c9d6SDavid Howells static int ts2020_read_tuner_gain(struct dvb_frontend *fe, unsigned v_agc, 3370f91c9d6SDavid Howells __s64 *_gain) 3380f91c9d6SDavid Howells { 3390f91c9d6SDavid Howells struct ts2020_priv *priv = fe->tuner_priv; 3400f91c9d6SDavid Howells unsigned long gain1, gain2, gain3; 3410f91c9d6SDavid Howells unsigned utmp; 3420f91c9d6SDavid Howells int ret; 3430f91c9d6SDavid Howells 3440f91c9d6SDavid Howells /* Read the RF gain */ 3450f91c9d6SDavid Howells ret = regmap_read(priv->regmap, 0x3d, &utmp); 3460f91c9d6SDavid Howells if (ret < 0) 3470f91c9d6SDavid Howells return ret; 3480f91c9d6SDavid Howells gain1 = utmp & 0x1f; 3490f91c9d6SDavid Howells 3500f91c9d6SDavid Howells /* Read the baseband gain */ 3510f91c9d6SDavid Howells ret = regmap_read(priv->regmap, 0x21, &utmp); 3520f91c9d6SDavid Howells if (ret < 0) 3530f91c9d6SDavid Howells return ret; 3540f91c9d6SDavid Howells gain2 = utmp & 0x1f; 3550f91c9d6SDavid Howells 3560f91c9d6SDavid Howells switch (priv->tuner) { 3570f91c9d6SDavid Howells case TS2020_M88TS2020: 3580f91c9d6SDavid Howells gain1 = clamp_t(long, gain1, 0, 15); 3590f91c9d6SDavid Howells gain2 = clamp_t(long, gain2, 0, 13); 3600f91c9d6SDavid Howells v_agc = clamp_t(long, v_agc, 400, 1100); 3610f91c9d6SDavid Howells 36281742be1SMauro Carvalho Chehab *_gain = -((__s64)gain1 * 2330 + 3630f91c9d6SDavid Howells gain2 * 3500 + 3640f91c9d6SDavid Howells v_agc * 24 / 10 * 10 + 3650f91c9d6SDavid Howells 10000); 3660f91c9d6SDavid Howells /* gain in range -19600 to -116850 in units of 0.001dB */ 3670f91c9d6SDavid Howells break; 3680f91c9d6SDavid Howells 3690f91c9d6SDavid Howells case TS2020_M88TS2022: 3700f91c9d6SDavid Howells ret = regmap_read(priv->regmap, 0x66, &utmp); 3710f91c9d6SDavid Howells if (ret < 0) 3720f91c9d6SDavid Howells return ret; 3730f91c9d6SDavid Howells gain3 = (utmp >> 3) & 0x07; 3740f91c9d6SDavid Howells 3750f91c9d6SDavid Howells gain1 = clamp_t(long, gain1, 0, 15); 3760f91c9d6SDavid Howells gain2 = clamp_t(long, gain2, 2, 16); 3770f91c9d6SDavid Howells gain3 = clamp_t(long, gain3, 0, 6); 3780f91c9d6SDavid Howells v_agc = clamp_t(long, v_agc, 600, 1600); 3790f91c9d6SDavid Howells 38081742be1SMauro Carvalho Chehab *_gain = -((__s64)gain1 * 2650 + 3810f91c9d6SDavid Howells gain2 * 3380 + 3820f91c9d6SDavid Howells gain3 * 2850 + 3830f91c9d6SDavid Howells v_agc * 176 / 100 * 10 - 3840f91c9d6SDavid Howells 30000); 3850f91c9d6SDavid Howells /* gain in range -47320 to -158950 in units of 0.001dB */ 3860f91c9d6SDavid Howells break; 3870f91c9d6SDavid Howells } 3880f91c9d6SDavid Howells 3890f91c9d6SDavid Howells return 0; 3900f91c9d6SDavid Howells } 3910f91c9d6SDavid Howells 3920f91c9d6SDavid Howells /* 3930f91c9d6SDavid Howells * Get the AGC information from the demodulator and use that to calculate the 3940f91c9d6SDavid Howells * tuner gain. 3950f91c9d6SDavid Howells */ 3960f91c9d6SDavid Howells static int ts2020_get_tuner_gain(struct dvb_frontend *fe, __s64 *_gain) 3970f91c9d6SDavid Howells { 3980f91c9d6SDavid Howells struct ts2020_priv *priv = fe->tuner_priv; 3990f91c9d6SDavid Howells int v_agc = 0, ret; 4000f91c9d6SDavid Howells u8 agc_pwm; 4010f91c9d6SDavid Howells 4020f91c9d6SDavid Howells /* Read the AGC PWM rate from the demodulator */ 4030f91c9d6SDavid Howells if (priv->get_agc_pwm) { 4040f91c9d6SDavid Howells ret = priv->get_agc_pwm(fe, &agc_pwm); 4050f91c9d6SDavid Howells if (ret < 0) 4060f91c9d6SDavid Howells return ret; 4070f91c9d6SDavid Howells 4080f91c9d6SDavid Howells switch (priv->tuner) { 4090f91c9d6SDavid Howells case TS2020_M88TS2020: 4100f91c9d6SDavid Howells v_agc = (int)agc_pwm * 20 - 1166; 4110f91c9d6SDavid Howells break; 4120f91c9d6SDavid Howells case TS2020_M88TS2022: 4130f91c9d6SDavid Howells v_agc = (int)agc_pwm * 16 - 670; 4140f91c9d6SDavid Howells break; 4150f91c9d6SDavid Howells } 4160f91c9d6SDavid Howells 4170f91c9d6SDavid Howells if (v_agc < 0) 4180f91c9d6SDavid Howells v_agc = 0; 4190f91c9d6SDavid Howells } 4200f91c9d6SDavid Howells 4210f91c9d6SDavid Howells return ts2020_read_tuner_gain(fe, v_agc, _gain); 4220f91c9d6SDavid Howells } 4230f91c9d6SDavid Howells 4240f91c9d6SDavid Howells /* 4253366cd5dSDavid Howells * Gather statistics on a regular basis 4263366cd5dSDavid Howells */ 4273366cd5dSDavid Howells static void ts2020_stat_work(struct work_struct *work) 4283366cd5dSDavid Howells { 4293366cd5dSDavid Howells struct ts2020_priv *priv = container_of(work, struct ts2020_priv, 4303366cd5dSDavid Howells stat_work.work); 4313366cd5dSDavid Howells struct i2c_client *client = priv->client; 4323366cd5dSDavid Howells struct dtv_frontend_properties *c = &priv->fe->dtv_property_cache; 4333366cd5dSDavid Howells int ret; 4343366cd5dSDavid Howells 4353366cd5dSDavid Howells dev_dbg(&client->dev, "\n"); 4363366cd5dSDavid Howells 4373366cd5dSDavid Howells ret = ts2020_get_tuner_gain(priv->fe, &c->strength.stat[0].svalue); 4383366cd5dSDavid Howells if (ret < 0) 4393366cd5dSDavid Howells goto err; 4403366cd5dSDavid Howells 4413366cd5dSDavid Howells c->strength.stat[0].scale = FE_SCALE_DECIBEL; 4423366cd5dSDavid Howells 443c7275ae1SDavid Howells if (!priv->dont_poll) 4443366cd5dSDavid Howells schedule_delayed_work(&priv->stat_work, msecs_to_jiffies(2000)); 4453366cd5dSDavid Howells return; 4463366cd5dSDavid Howells err: 4473366cd5dSDavid Howells dev_dbg(&client->dev, "failed=%d\n", ret); 4483366cd5dSDavid Howells } 4493366cd5dSDavid Howells 4503366cd5dSDavid Howells /* 4510f91c9d6SDavid Howells * Read TS2020 signal strength in v3 format. 4520f91c9d6SDavid Howells */ 453b858c331SIgor M. Liplianin static int ts2020_read_signal_strength(struct dvb_frontend *fe, 4543366cd5dSDavid Howells u16 *_signal_strength) 4556fef4fc7SKonstantin Dimitrov { 4563366cd5dSDavid Howells struct dtv_frontend_properties *c = &fe->dtv_property_cache; 457c7275ae1SDavid Howells struct ts2020_priv *priv = fe->tuner_priv; 4580f91c9d6SDavid Howells unsigned strength; 4590f91c9d6SDavid Howells __s64 gain; 4606fef4fc7SKonstantin Dimitrov 461c7275ae1SDavid Howells if (priv->dont_poll) 462c7275ae1SDavid Howells ts2020_stat_work(&priv->stat_work.work); 463c7275ae1SDavid Howells 4643366cd5dSDavid Howells if (c->strength.stat[0].scale == FE_SCALE_NOT_AVAILABLE) { 4653366cd5dSDavid Howells *_signal_strength = 0; 4663366cd5dSDavid Howells return 0; 4673366cd5dSDavid Howells } 4683366cd5dSDavid Howells 4693366cd5dSDavid Howells gain = c->strength.stat[0].svalue; 4706fef4fc7SKonstantin Dimitrov 4710f91c9d6SDavid Howells /* Calculate the signal strength based on the total gain of the tuner */ 4720f91c9d6SDavid Howells if (gain < -85000) 4730f91c9d6SDavid Howells /* 0%: no signal or weak signal */ 4740f91c9d6SDavid Howells strength = 0; 4750f91c9d6SDavid Howells else if (gain < -65000) 4760f91c9d6SDavid Howells /* 0% - 60%: weak signal */ 47787b09bd0SMauro Carvalho Chehab strength = 0 + div64_s64((85000 + gain) * 3, 1000); 4780f91c9d6SDavid Howells else if (gain < -45000) 4790f91c9d6SDavid Howells /* 60% - 90%: normal signal */ 48087b09bd0SMauro Carvalho Chehab strength = 60 + div64_s64((65000 + gain) * 3, 2000); 4810f91c9d6SDavid Howells else 4820f91c9d6SDavid Howells /* 90% - 99%: strong signal */ 48387b09bd0SMauro Carvalho Chehab strength = 90 + div64_s64((45000 + gain), 5000); 4846fef4fc7SKonstantin Dimitrov 4853366cd5dSDavid Howells *_signal_strength = strength * 65535 / 100; 4866fef4fc7SKonstantin Dimitrov return 0; 4876fef4fc7SKonstantin Dimitrov } 4886fef4fc7SKonstantin Dimitrov 48914c4bf3cSJulia Lawall static const struct dvb_tuner_ops ts2020_tuner_ops = { 4906fef4fc7SKonstantin Dimitrov .info = { 491b858c331SIgor M. Liplianin .name = "TS2020", 492a3f90c75SMauro Carvalho Chehab .frequency_min_hz = 950 * MHz, 493a3f90c75SMauro Carvalho Chehab .frequency_max_hz = 2150 * MHz 4946fef4fc7SKonstantin Dimitrov }, 4956fef4fc7SKonstantin Dimitrov .init = ts2020_init, 4966fef4fc7SKonstantin Dimitrov .release = ts2020_release, 497b858c331SIgor M. Liplianin .sleep = ts2020_sleep, 4986fef4fc7SKonstantin Dimitrov .set_params = ts2020_set_params, 4996fef4fc7SKonstantin Dimitrov .get_frequency = ts2020_get_frequency, 500abd9025bSAntti Palosaari .get_if_frequency = ts2020_get_if_frequency, 501b858c331SIgor M. Liplianin .get_rf_strength = ts2020_read_signal_strength, 5026fef4fc7SKonstantin Dimitrov }; 5036fef4fc7SKonstantin Dimitrov 5046fef4fc7SKonstantin Dimitrov struct dvb_frontend *ts2020_attach(struct dvb_frontend *fe, 505b858c331SIgor M. Liplianin const struct ts2020_config *config, 506b858c331SIgor M. Liplianin struct i2c_adapter *i2c) 5076fef4fc7SKonstantin Dimitrov { 508e6ad9ce3SAntti Palosaari struct i2c_client *client; 509e6ad9ce3SAntti Palosaari struct i2c_board_info board_info; 51080868c8eSDavid Howells 51180868c8eSDavid Howells /* This is only used by ts2020_probe() so can be on the stack */ 512e6ad9ce3SAntti Palosaari struct ts2020_config pdata; 5136fef4fc7SKonstantin Dimitrov 514e6ad9ce3SAntti Palosaari memcpy(&pdata, config, sizeof(pdata)); 515e6ad9ce3SAntti Palosaari pdata.fe = fe; 516e6ad9ce3SAntti Palosaari pdata.attach_in_use = true; 517e6ad9ce3SAntti Palosaari 518e6ad9ce3SAntti Palosaari memset(&board_info, 0, sizeof(board_info)); 519c0decac1SMauro Carvalho Chehab strscpy(board_info.type, "ts2020", I2C_NAME_SIZE); 520e6ad9ce3SAntti Palosaari board_info.addr = config->tuner_address; 521e6ad9ce3SAntti Palosaari board_info.platform_data = &pdata; 5222f507ffaSWolfram Sang client = i2c_new_client_device(i2c, &board_info); 5232f507ffaSWolfram Sang if (!i2c_client_has_driver(client)) 5246fef4fc7SKonstantin Dimitrov return NULL; 5256fef4fc7SKonstantin Dimitrov 5266fef4fc7SKonstantin Dimitrov return fe; 5276fef4fc7SKonstantin Dimitrov } 528*86495af1SGreg Kroah-Hartman EXPORT_SYMBOL_GPL(ts2020_attach); 5296fef4fc7SKonstantin Dimitrov 530f158cbceSAntti Palosaari /* 531f158cbceSAntti Palosaari * We implement own regmap locking due to legacy DVB attach which uses frontend 532f158cbceSAntti Palosaari * gate control callback to control I2C bus access. We can open / close gate and 533f158cbceSAntti Palosaari * serialize whole open / I2C-operation / close sequence at the same. 534f158cbceSAntti Palosaari */ 535f158cbceSAntti Palosaari static void ts2020_regmap_lock(void *__dev) 536f158cbceSAntti Palosaari { 537f158cbceSAntti Palosaari struct ts2020_priv *dev = __dev; 538f158cbceSAntti Palosaari 539f158cbceSAntti Palosaari mutex_lock(&dev->regmap_mutex); 540f158cbceSAntti Palosaari if (dev->fe->ops.i2c_gate_ctrl) 541f158cbceSAntti Palosaari dev->fe->ops.i2c_gate_ctrl(dev->fe, 1); 542f158cbceSAntti Palosaari } 543f158cbceSAntti Palosaari 544f158cbceSAntti Palosaari static void ts2020_regmap_unlock(void *__dev) 545f158cbceSAntti Palosaari { 546f158cbceSAntti Palosaari struct ts2020_priv *dev = __dev; 547f158cbceSAntti Palosaari 548f158cbceSAntti Palosaari if (dev->fe->ops.i2c_gate_ctrl) 549f158cbceSAntti Palosaari dev->fe->ops.i2c_gate_ctrl(dev->fe, 0); 550f158cbceSAntti Palosaari mutex_unlock(&dev->regmap_mutex); 551f158cbceSAntti Palosaari } 552f158cbceSAntti Palosaari 55349a7233fSUwe Kleine-König static int ts2020_probe(struct i2c_client *client) 554dc245a5fSAntti Palosaari { 555dc245a5fSAntti Palosaari struct ts2020_config *pdata = client->dev.platform_data; 556dc245a5fSAntti Palosaari struct dvb_frontend *fe = pdata->fe; 557dc245a5fSAntti Palosaari struct ts2020_priv *dev; 558dc245a5fSAntti Palosaari int ret; 559dc245a5fSAntti Palosaari u8 u8tmp; 560dc245a5fSAntti Palosaari unsigned int utmp; 561dc245a5fSAntti Palosaari char *chip_str; 562dc245a5fSAntti Palosaari 563dc245a5fSAntti Palosaari dev = kzalloc(sizeof(*dev), GFP_KERNEL); 564dc245a5fSAntti Palosaari if (!dev) { 565dc245a5fSAntti Palosaari ret = -ENOMEM; 566dc245a5fSAntti Palosaari goto err; 567dc245a5fSAntti Palosaari } 568dc245a5fSAntti Palosaari 569f158cbceSAntti Palosaari /* create regmap */ 570f158cbceSAntti Palosaari mutex_init(&dev->regmap_mutex); 571fa71ae71SJulia Lawall dev->regmap_config.reg_bits = 8; 572fa71ae71SJulia Lawall dev->regmap_config.val_bits = 8; 573fa71ae71SJulia Lawall dev->regmap_config.lock = ts2020_regmap_lock; 574fa71ae71SJulia Lawall dev->regmap_config.unlock = ts2020_regmap_unlock; 575fa71ae71SJulia Lawall dev->regmap_config.lock_arg = dev; 576f158cbceSAntti Palosaari dev->regmap = regmap_init_i2c(client, &dev->regmap_config); 577f158cbceSAntti Palosaari if (IS_ERR(dev->regmap)) { 578f158cbceSAntti Palosaari ret = PTR_ERR(dev->regmap); 579f158cbceSAntti Palosaari goto err_kfree; 580f158cbceSAntti Palosaari } 581f158cbceSAntti Palosaari 582dc245a5fSAntti Palosaari dev->i2c = client->adapter; 583dc245a5fSAntti Palosaari dev->i2c_address = client->addr; 5840f20baadSDavid Howells dev->loop_through = pdata->loop_through; 585dc245a5fSAntti Palosaari dev->clk_out = pdata->clk_out; 586dc245a5fSAntti Palosaari dev->clk_out_div = pdata->clk_out_div; 587c7275ae1SDavid Howells dev->dont_poll = pdata->dont_poll; 588dc245a5fSAntti Palosaari dev->frequency_div = pdata->frequency_div; 589dc245a5fSAntti Palosaari dev->fe = fe; 5900f91c9d6SDavid Howells dev->get_agc_pwm = pdata->get_agc_pwm; 591dc245a5fSAntti Palosaari fe->tuner_priv = dev; 592e6ad9ce3SAntti Palosaari dev->client = client; 5933366cd5dSDavid Howells INIT_DELAYED_WORK(&dev->stat_work, ts2020_stat_work); 594dc245a5fSAntti Palosaari 595dc245a5fSAntti Palosaari /* check if the tuner is there */ 596f158cbceSAntti Palosaari ret = regmap_read(dev->regmap, 0x00, &utmp); 597f158cbceSAntti Palosaari if (ret) 598f158cbceSAntti Palosaari goto err_regmap_exit; 599dc245a5fSAntti Palosaari 600dc245a5fSAntti Palosaari if ((utmp & 0x03) == 0x00) { 601f158cbceSAntti Palosaari ret = regmap_write(dev->regmap, 0x00, 0x01); 602dc245a5fSAntti Palosaari if (ret) 603f158cbceSAntti Palosaari goto err_regmap_exit; 604dc245a5fSAntti Palosaari 605dc245a5fSAntti Palosaari usleep_range(2000, 50000); 606dc245a5fSAntti Palosaari } 607dc245a5fSAntti Palosaari 608f158cbceSAntti Palosaari ret = regmap_write(dev->regmap, 0x00, 0x03); 609dc245a5fSAntti Palosaari if (ret) 610f158cbceSAntti Palosaari goto err_regmap_exit; 611dc245a5fSAntti Palosaari 612dc245a5fSAntti Palosaari usleep_range(2000, 50000); 613dc245a5fSAntti Palosaari 614f158cbceSAntti Palosaari ret = regmap_read(dev->regmap, 0x00, &utmp); 615f158cbceSAntti Palosaari if (ret) 616f158cbceSAntti Palosaari goto err_regmap_exit; 617dc245a5fSAntti Palosaari 618dc245a5fSAntti Palosaari dev_dbg(&client->dev, "chip_id=%02x\n", utmp); 619dc245a5fSAntti Palosaari 620dc245a5fSAntti Palosaari switch (utmp) { 621dc245a5fSAntti Palosaari case 0x01: 622dc245a5fSAntti Palosaari case 0x41: 623dc245a5fSAntti Palosaari case 0x81: 624dc245a5fSAntti Palosaari dev->tuner = TS2020_M88TS2020; 625dc245a5fSAntti Palosaari chip_str = "TS2020"; 626dc245a5fSAntti Palosaari if (!dev->frequency_div) 627dc245a5fSAntti Palosaari dev->frequency_div = 1060000; 628dc245a5fSAntti Palosaari break; 629dc245a5fSAntti Palosaari case 0xc3: 630dc245a5fSAntti Palosaari case 0x83: 631dc245a5fSAntti Palosaari dev->tuner = TS2020_M88TS2022; 632dc245a5fSAntti Palosaari chip_str = "TS2022"; 633dc245a5fSAntti Palosaari if (!dev->frequency_div) 634dc245a5fSAntti Palosaari dev->frequency_div = 1103000; 635dc245a5fSAntti Palosaari break; 636dc245a5fSAntti Palosaari default: 637dc245a5fSAntti Palosaari ret = -ENODEV; 638f158cbceSAntti Palosaari goto err_regmap_exit; 639dc245a5fSAntti Palosaari } 640dc245a5fSAntti Palosaari 641dc245a5fSAntti Palosaari if (dev->tuner == TS2020_M88TS2022) { 642dc245a5fSAntti Palosaari switch (dev->clk_out) { 643dc245a5fSAntti Palosaari case TS2020_CLK_OUT_DISABLED: 644dc245a5fSAntti Palosaari u8tmp = 0x60; 645dc245a5fSAntti Palosaari break; 646dc245a5fSAntti Palosaari case TS2020_CLK_OUT_ENABLED: 647dc245a5fSAntti Palosaari u8tmp = 0x70; 648f158cbceSAntti Palosaari ret = regmap_write(dev->regmap, 0x05, dev->clk_out_div); 649dc245a5fSAntti Palosaari if (ret) 650f158cbceSAntti Palosaari goto err_regmap_exit; 651dc245a5fSAntti Palosaari break; 652dc245a5fSAntti Palosaari case TS2020_CLK_OUT_ENABLED_XTALOUT: 653dc245a5fSAntti Palosaari u8tmp = 0x6c; 654dc245a5fSAntti Palosaari break; 655dc245a5fSAntti Palosaari default: 656dc245a5fSAntti Palosaari ret = -EINVAL; 657f158cbceSAntti Palosaari goto err_regmap_exit; 658dc245a5fSAntti Palosaari } 659dc245a5fSAntti Palosaari 660f158cbceSAntti Palosaari ret = regmap_write(dev->regmap, 0x42, u8tmp); 661dc245a5fSAntti Palosaari if (ret) 662f158cbceSAntti Palosaari goto err_regmap_exit; 663dc245a5fSAntti Palosaari 664dc245a5fSAntti Palosaari if (dev->loop_through) 665dc245a5fSAntti Palosaari u8tmp = 0xec; 666dc245a5fSAntti Palosaari else 667dc245a5fSAntti Palosaari u8tmp = 0x6c; 668dc245a5fSAntti Palosaari 669f158cbceSAntti Palosaari ret = regmap_write(dev->regmap, 0x62, u8tmp); 670dc245a5fSAntti Palosaari if (ret) 671f158cbceSAntti Palosaari goto err_regmap_exit; 672dc245a5fSAntti Palosaari } 673dc245a5fSAntti Palosaari 674dc245a5fSAntti Palosaari /* sleep */ 675f158cbceSAntti Palosaari ret = regmap_write(dev->regmap, 0x00, 0x00); 676dc245a5fSAntti Palosaari if (ret) 677f158cbceSAntti Palosaari goto err_regmap_exit; 678dc245a5fSAntti Palosaari 679dc245a5fSAntti Palosaari dev_info(&client->dev, 680dc245a5fSAntti Palosaari "Montage Technology %s successfully identified\n", chip_str); 681dc245a5fSAntti Palosaari 682dc245a5fSAntti Palosaari memcpy(&fe->ops.tuner_ops, &ts2020_tuner_ops, 683dc245a5fSAntti Palosaari sizeof(struct dvb_tuner_ops)); 684e6ad9ce3SAntti Palosaari if (!pdata->attach_in_use) 685dc245a5fSAntti Palosaari fe->ops.tuner_ops.release = NULL; 686dc245a5fSAntti Palosaari 687dc245a5fSAntti Palosaari i2c_set_clientdata(client, dev); 688dc245a5fSAntti Palosaari return 0; 689f158cbceSAntti Palosaari err_regmap_exit: 690f158cbceSAntti Palosaari regmap_exit(dev->regmap); 691f158cbceSAntti Palosaari err_kfree: 692f158cbceSAntti Palosaari kfree(dev); 693dc245a5fSAntti Palosaari err: 694dc245a5fSAntti Palosaari dev_dbg(&client->dev, "failed=%d\n", ret); 695dc245a5fSAntti Palosaari return ret; 696dc245a5fSAntti Palosaari } 697dc245a5fSAntti Palosaari 698ed5c2f5fSUwe Kleine-König static void ts2020_remove(struct i2c_client *client) 699dc245a5fSAntti Palosaari { 700dc245a5fSAntti Palosaari struct ts2020_priv *dev = i2c_get_clientdata(client); 701dc245a5fSAntti Palosaari 702dc245a5fSAntti Palosaari dev_dbg(&client->dev, "\n"); 703dc245a5fSAntti Palosaari 70441ff9142SErnst Martin Witte /* stop statistics polling */ 70541ff9142SErnst Martin Witte if (!dev->dont_poll) 70641ff9142SErnst Martin Witte cancel_delayed_work_sync(&dev->stat_work); 70741ff9142SErnst Martin Witte 708f158cbceSAntti Palosaari regmap_exit(dev->regmap); 709dc245a5fSAntti Palosaari kfree(dev); 710dc245a5fSAntti Palosaari } 711dc245a5fSAntti Palosaari 712dc245a5fSAntti Palosaari static const struct i2c_device_id ts2020_id_table[] = { 713dc245a5fSAntti Palosaari {"ts2020", 0}, 714dc245a5fSAntti Palosaari {"ts2022", 0}, 715dc245a5fSAntti Palosaari {} 716dc245a5fSAntti Palosaari }; 717dc245a5fSAntti Palosaari MODULE_DEVICE_TABLE(i2c, ts2020_id_table); 718dc245a5fSAntti Palosaari 719dc245a5fSAntti Palosaari static struct i2c_driver ts2020_driver = { 720dc245a5fSAntti Palosaari .driver = { 721dc245a5fSAntti Palosaari .name = "ts2020", 722dc245a5fSAntti Palosaari }, 723aaeb31c0SUwe Kleine-König .probe = ts2020_probe, 724dc245a5fSAntti Palosaari .remove = ts2020_remove, 725dc245a5fSAntti Palosaari .id_table = ts2020_id_table, 726dc245a5fSAntti Palosaari }; 727dc245a5fSAntti Palosaari 728dc245a5fSAntti Palosaari module_i2c_driver(ts2020_driver); 729dc245a5fSAntti Palosaari 7306fef4fc7SKonstantin Dimitrov MODULE_AUTHOR("Konstantin Dimitrov <kosio.dimitrov@gmail.com>"); 7316fef4fc7SKonstantin Dimitrov MODULE_DESCRIPTION("Montage Technology TS2020 - Silicon tuner driver module"); 7326fef4fc7SKonstantin Dimitrov MODULE_LICENSE("GPL"); 733