1c942fddfSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 2ccae7af2SMauro Carvalho Chehab /* 3ccae7af2SMauro Carvalho Chehab * Driver for Microtune MT2060 "Single chip dual conversion broadband tuner" 4ccae7af2SMauro Carvalho Chehab * 5ccae7af2SMauro Carvalho Chehab * Copyright (c) 2006 Olivier DANET <odanet@caramail.com> 6ccae7af2SMauro Carvalho Chehab */ 7ccae7af2SMauro Carvalho Chehab 8ccae7af2SMauro Carvalho Chehab /* In that file, frequencies are expressed in kiloHertz to avoid 32 bits overflows */ 9ccae7af2SMauro Carvalho Chehab 10ccae7af2SMauro Carvalho Chehab #include <linux/module.h> 11ccae7af2SMauro Carvalho Chehab #include <linux/delay.h> 12ccae7af2SMauro Carvalho Chehab #include <linux/dvb/frontend.h> 13ccae7af2SMauro Carvalho Chehab #include <linux/i2c.h> 14ccae7af2SMauro Carvalho Chehab #include <linux/slab.h> 15ccae7af2SMauro Carvalho Chehab 16fada1935SMauro Carvalho Chehab #include <media/dvb_frontend.h> 17ccae7af2SMauro Carvalho Chehab 18ccae7af2SMauro Carvalho Chehab #include "mt2060.h" 19ccae7af2SMauro Carvalho Chehab #include "mt2060_priv.h" 20ccae7af2SMauro Carvalho Chehab 21ccae7af2SMauro Carvalho Chehab static int debug; 22ccae7af2SMauro Carvalho Chehab module_param(debug, int, 0644); 23ccae7af2SMauro Carvalho Chehab MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off)."); 24ccae7af2SMauro Carvalho Chehab 25ccae7af2SMauro Carvalho Chehab #define dprintk(args...) do { if (debug) {printk(KERN_DEBUG "MT2060: " args); printk("\n"); }} while (0) 26ccae7af2SMauro Carvalho Chehab 27ccae7af2SMauro Carvalho Chehab // Reads a single register 28ccae7af2SMauro Carvalho Chehab static int mt2060_readreg(struct mt2060_priv *priv, u8 reg, u8 *val) 29ccae7af2SMauro Carvalho Chehab { 30ccae7af2SMauro Carvalho Chehab struct i2c_msg msg[2] = { 31b4756707SSean Young { .addr = priv->cfg->i2c_address, .flags = 0, .len = 1 }, 32b4756707SSean Young { .addr = priv->cfg->i2c_address, .flags = I2C_M_RD, .len = 1 }, 33ccae7af2SMauro Carvalho Chehab }; 34b4756707SSean Young int rc = 0; 35b4756707SSean Young u8 *b; 36b4756707SSean Young 37b4756707SSean Young b = kmalloc(2, GFP_KERNEL); 38b4756707SSean Young if (!b) 39b4756707SSean Young return -ENOMEM; 40b4756707SSean Young 41b4756707SSean Young b[0] = reg; 42b4756707SSean Young b[1] = 0; 43b4756707SSean Young 44b4756707SSean Young msg[0].buf = b; 45b4756707SSean Young msg[1].buf = b + 1; 46ccae7af2SMauro Carvalho Chehab 47ccae7af2SMauro Carvalho Chehab if (i2c_transfer(priv->i2c, msg, 2) != 2) { 48ccae7af2SMauro Carvalho Chehab printk(KERN_WARNING "mt2060 I2C read failed\n"); 49b4756707SSean Young rc = -EREMOTEIO; 50ccae7af2SMauro Carvalho Chehab } 51b4756707SSean Young *val = b[1]; 52b4756707SSean Young kfree(b); 53b4756707SSean Young 54b4756707SSean Young return rc; 55ccae7af2SMauro Carvalho Chehab } 56ccae7af2SMauro Carvalho Chehab 57ccae7af2SMauro Carvalho Chehab // Writes a single register 58ccae7af2SMauro Carvalho Chehab static int mt2060_writereg(struct mt2060_priv *priv, u8 reg, u8 val) 59ccae7af2SMauro Carvalho Chehab { 60ccae7af2SMauro Carvalho Chehab struct i2c_msg msg = { 61b4756707SSean Young .addr = priv->cfg->i2c_address, .flags = 0, .len = 2 62ccae7af2SMauro Carvalho Chehab }; 63b4756707SSean Young u8 *buf; 64b4756707SSean Young int rc = 0; 65b4756707SSean Young 66b4756707SSean Young buf = kmalloc(2, GFP_KERNEL); 67b4756707SSean Young if (!buf) 68b4756707SSean Young return -ENOMEM; 69b4756707SSean Young 70b4756707SSean Young buf[0] = reg; 71b4756707SSean Young buf[1] = val; 72b4756707SSean Young 73b4756707SSean Young msg.buf = buf; 74ccae7af2SMauro Carvalho Chehab 75ccae7af2SMauro Carvalho Chehab if (i2c_transfer(priv->i2c, &msg, 1) != 1) { 76ccae7af2SMauro Carvalho Chehab printk(KERN_WARNING "mt2060 I2C write failed\n"); 77b4756707SSean Young rc = -EREMOTEIO; 78ccae7af2SMauro Carvalho Chehab } 79b4756707SSean Young kfree(buf); 80b4756707SSean Young return rc; 81ccae7af2SMauro Carvalho Chehab } 82ccae7af2SMauro Carvalho Chehab 83ccae7af2SMauro Carvalho Chehab // Writes a set of consecutive registers 84ccae7af2SMauro Carvalho Chehab static int mt2060_writeregs(struct mt2060_priv *priv,u8 *buf, u8 len) 85ccae7af2SMauro Carvalho Chehab { 86433c4864SAntti Palosaari int rem, val_len; 87b4756707SSean Young u8 *xfer_buf; 88b4756707SSean Young int rc = 0; 89ccae7af2SMauro Carvalho Chehab struct i2c_msg msg = { 90b4756707SSean Young .addr = priv->cfg->i2c_address, .flags = 0 91ccae7af2SMauro Carvalho Chehab }; 92433c4864SAntti Palosaari 93b4756707SSean Young xfer_buf = kmalloc(16, GFP_KERNEL); 94b4756707SSean Young if (!xfer_buf) 95b4756707SSean Young return -ENOMEM; 96b4756707SSean Young 97b4756707SSean Young msg.buf = xfer_buf; 98b4756707SSean Young 99433c4864SAntti Palosaari for (rem = len - 1; rem > 0; rem -= priv->i2c_max_regs) { 100433c4864SAntti Palosaari val_len = min_t(int, rem, priv->i2c_max_regs); 101433c4864SAntti Palosaari msg.len = 1 + val_len; 102433c4864SAntti Palosaari xfer_buf[0] = buf[0] + len - 1 - rem; 103433c4864SAntti Palosaari memcpy(&xfer_buf[1], &buf[1 + len - 1 - rem], val_len); 104433c4864SAntti Palosaari 105ccae7af2SMauro Carvalho Chehab if (i2c_transfer(priv->i2c, &msg, 1) != 1) { 106433c4864SAntti Palosaari printk(KERN_WARNING "mt2060 I2C write failed (len=%i)\n", val_len); 107b4756707SSean Young rc = -EREMOTEIO; 108b4756707SSean Young break; 109ccae7af2SMauro Carvalho Chehab } 110433c4864SAntti Palosaari } 111433c4864SAntti Palosaari 112b4756707SSean Young kfree(xfer_buf); 113b4756707SSean Young return rc; 114ccae7af2SMauro Carvalho Chehab } 115ccae7af2SMauro Carvalho Chehab 116ccae7af2SMauro Carvalho Chehab // Initialisation sequences 117ccae7af2SMauro Carvalho Chehab // LNABAND=3, NUM1=0x3C, DIV1=0x74, NUM2=0x1080, DIV2=0x49 118ccae7af2SMauro Carvalho Chehab static u8 mt2060_config1[] = { 119ccae7af2SMauro Carvalho Chehab REG_LO1C1, 120ccae7af2SMauro Carvalho Chehab 0x3F, 0x74, 0x00, 0x08, 0x93 121ccae7af2SMauro Carvalho Chehab }; 122ccae7af2SMauro Carvalho Chehab 123ccae7af2SMauro Carvalho Chehab // FMCG=2, GP2=0, GP1=0 124ccae7af2SMauro Carvalho Chehab static u8 mt2060_config2[] = { 125ccae7af2SMauro Carvalho Chehab REG_MISC_CTRL, 126ccae7af2SMauro Carvalho Chehab 0x20, 0x1E, 0x30, 0xff, 0x80, 0xff, 0x00, 0x2c, 0x42 127ccae7af2SMauro Carvalho Chehab }; 128ccae7af2SMauro Carvalho Chehab 129ccae7af2SMauro Carvalho Chehab // VGAG=3, V1CSE=1 130ccae7af2SMauro Carvalho Chehab 131ccae7af2SMauro Carvalho Chehab #ifdef MT2060_SPURCHECK 132ccae7af2SMauro Carvalho Chehab /* The function below calculates the frequency offset between the output frequency if2 133ccae7af2SMauro Carvalho Chehab and the closer cross modulation subcarrier between lo1 and lo2 up to the tenth harmonic */ 134ccae7af2SMauro Carvalho Chehab static int mt2060_spurcalc(u32 lo1,u32 lo2,u32 if2) 135ccae7af2SMauro Carvalho Chehab { 136ccae7af2SMauro Carvalho Chehab int I,J; 137ccae7af2SMauro Carvalho Chehab int dia,diamin,diff; 138ccae7af2SMauro Carvalho Chehab diamin=1000000; 139ccae7af2SMauro Carvalho Chehab for (I = 1; I < 10; I++) { 140ccae7af2SMauro Carvalho Chehab J = ((2*I*lo1)/lo2+1)/2; 141ccae7af2SMauro Carvalho Chehab diff = I*(int)lo1-J*(int)lo2; 142ccae7af2SMauro Carvalho Chehab if (diff < 0) diff=-diff; 143ccae7af2SMauro Carvalho Chehab dia = (diff-(int)if2); 144ccae7af2SMauro Carvalho Chehab if (dia < 0) dia=-dia; 145ccae7af2SMauro Carvalho Chehab if (diamin > dia) diamin=dia; 146ccae7af2SMauro Carvalho Chehab } 147ccae7af2SMauro Carvalho Chehab return diamin; 148ccae7af2SMauro Carvalho Chehab } 149ccae7af2SMauro Carvalho Chehab 150ccae7af2SMauro Carvalho Chehab #define BANDWIDTH 4000 // kHz 151ccae7af2SMauro Carvalho Chehab 152ccae7af2SMauro Carvalho Chehab /* Calculates the frequency offset to add to avoid spurs. Returns 0 if no offset is needed */ 153ccae7af2SMauro Carvalho Chehab static int mt2060_spurcheck(u32 lo1,u32 lo2,u32 if2) 154ccae7af2SMauro Carvalho Chehab { 155ccae7af2SMauro Carvalho Chehab u32 Spur,Sp1,Sp2; 156ccae7af2SMauro Carvalho Chehab int I,J; 157ccae7af2SMauro Carvalho Chehab I=0; 158ccae7af2SMauro Carvalho Chehab J=1000; 159ccae7af2SMauro Carvalho Chehab 160ccae7af2SMauro Carvalho Chehab Spur=mt2060_spurcalc(lo1,lo2,if2); 161ccae7af2SMauro Carvalho Chehab if (Spur < BANDWIDTH) { 162ccae7af2SMauro Carvalho Chehab /* Potential spurs detected */ 163ccae7af2SMauro Carvalho Chehab dprintk("Spurs before : f_lo1: %d f_lo2: %d (kHz)", 164ccae7af2SMauro Carvalho Chehab (int)lo1,(int)lo2); 165ccae7af2SMauro Carvalho Chehab I=1000; 166ccae7af2SMauro Carvalho Chehab Sp1 = mt2060_spurcalc(lo1+I,lo2+I,if2); 167ccae7af2SMauro Carvalho Chehab Sp2 = mt2060_spurcalc(lo1-I,lo2-I,if2); 168ccae7af2SMauro Carvalho Chehab 169ccae7af2SMauro Carvalho Chehab if (Sp1 < Sp2) { 170ccae7af2SMauro Carvalho Chehab J=-J; I=-I; Spur=Sp2; 171ccae7af2SMauro Carvalho Chehab } else 172ccae7af2SMauro Carvalho Chehab Spur=Sp1; 173ccae7af2SMauro Carvalho Chehab 174ccae7af2SMauro Carvalho Chehab while (Spur < BANDWIDTH) { 175ccae7af2SMauro Carvalho Chehab I += J; 176ccae7af2SMauro Carvalho Chehab Spur = mt2060_spurcalc(lo1+I,lo2+I,if2); 177ccae7af2SMauro Carvalho Chehab } 178ccae7af2SMauro Carvalho Chehab dprintk("Spurs after : f_lo1: %d f_lo2: %d (kHz)", 179ccae7af2SMauro Carvalho Chehab (int)(lo1+I),(int)(lo2+I)); 180ccae7af2SMauro Carvalho Chehab } 181ccae7af2SMauro Carvalho Chehab return I; 182ccae7af2SMauro Carvalho Chehab } 183ccae7af2SMauro Carvalho Chehab #endif 184ccae7af2SMauro Carvalho Chehab 185ccae7af2SMauro Carvalho Chehab #define IF2 36150 // IF2 frequency = 36.150 MHz 186ccae7af2SMauro Carvalho Chehab #define FREF 16000 // Quartz oscillator 16 MHz 187ccae7af2SMauro Carvalho Chehab 188ccae7af2SMauro Carvalho Chehab static int mt2060_set_params(struct dvb_frontend *fe) 189ccae7af2SMauro Carvalho Chehab { 190ccae7af2SMauro Carvalho Chehab struct dtv_frontend_properties *c = &fe->dtv_property_cache; 191ccae7af2SMauro Carvalho Chehab struct mt2060_priv *priv; 192ccae7af2SMauro Carvalho Chehab int i=0; 193ccae7af2SMauro Carvalho Chehab u32 freq; 194ccae7af2SMauro Carvalho Chehab u8 lnaband; 195ccae7af2SMauro Carvalho Chehab u32 f_lo1,f_lo2; 196ccae7af2SMauro Carvalho Chehab u32 div1,num1,div2,num2; 197ccae7af2SMauro Carvalho Chehab u8 b[8]; 198ccae7af2SMauro Carvalho Chehab u32 if1; 199ccae7af2SMauro Carvalho Chehab 200ccae7af2SMauro Carvalho Chehab priv = fe->tuner_priv; 201ccae7af2SMauro Carvalho Chehab 202ccae7af2SMauro Carvalho Chehab if1 = priv->if1_freq; 203ccae7af2SMauro Carvalho Chehab b[0] = REG_LO1B1; 204ccae7af2SMauro Carvalho Chehab b[1] = 0xFF; 205ccae7af2SMauro Carvalho Chehab 206ccae7af2SMauro Carvalho Chehab if (fe->ops.i2c_gate_ctrl) 207ccae7af2SMauro Carvalho Chehab fe->ops.i2c_gate_ctrl(fe, 1); /* open i2c_gate */ 208ccae7af2SMauro Carvalho Chehab 209ccae7af2SMauro Carvalho Chehab mt2060_writeregs(priv,b,2); 210ccae7af2SMauro Carvalho Chehab 211ccae7af2SMauro Carvalho Chehab freq = c->frequency / 1000; /* Hz -> kHz */ 212ccae7af2SMauro Carvalho Chehab 213ccae7af2SMauro Carvalho Chehab f_lo1 = freq + if1 * 1000; 214ccae7af2SMauro Carvalho Chehab f_lo1 = (f_lo1 / 250) * 250; 215ccae7af2SMauro Carvalho Chehab f_lo2 = f_lo1 - freq - IF2; 216ccae7af2SMauro Carvalho Chehab // From the Comtech datasheet, the step used is 50kHz. The tuner chip could be more precise 217ccae7af2SMauro Carvalho Chehab f_lo2 = ((f_lo2 + 25) / 50) * 50; 218ef0d21e4SJulia Lawall priv->frequency = (f_lo1 - f_lo2 - IF2) * 1000; 219ccae7af2SMauro Carvalho Chehab 220ccae7af2SMauro Carvalho Chehab #ifdef MT2060_SPURCHECK 221ccae7af2SMauro Carvalho Chehab // LO-related spurs detection and correction 222ccae7af2SMauro Carvalho Chehab num1 = mt2060_spurcheck(f_lo1,f_lo2,IF2); 223ccae7af2SMauro Carvalho Chehab f_lo1 += num1; 224ccae7af2SMauro Carvalho Chehab f_lo2 += num1; 225ccae7af2SMauro Carvalho Chehab #endif 226ccae7af2SMauro Carvalho Chehab //Frequency LO1 = 16MHz * (DIV1 + NUM1/64 ) 227ccae7af2SMauro Carvalho Chehab num1 = f_lo1 / (FREF / 64); 228ccae7af2SMauro Carvalho Chehab div1 = num1 / 64; 229ccae7af2SMauro Carvalho Chehab num1 &= 0x3f; 230ccae7af2SMauro Carvalho Chehab 231ccae7af2SMauro Carvalho Chehab // Frequency LO2 = 16MHz * (DIV2 + NUM2/8192 ) 232ccae7af2SMauro Carvalho Chehab num2 = f_lo2 * 64 / (FREF / 128); 233ccae7af2SMauro Carvalho Chehab div2 = num2 / 8192; 234ccae7af2SMauro Carvalho Chehab num2 &= 0x1fff; 235ccae7af2SMauro Carvalho Chehab 236ccae7af2SMauro Carvalho Chehab if (freq <= 95000) lnaband = 0xB0; else 237ccae7af2SMauro Carvalho Chehab if (freq <= 180000) lnaband = 0xA0; else 238ccae7af2SMauro Carvalho Chehab if (freq <= 260000) lnaband = 0x90; else 239ccae7af2SMauro Carvalho Chehab if (freq <= 335000) lnaband = 0x80; else 240ccae7af2SMauro Carvalho Chehab if (freq <= 425000) lnaband = 0x70; else 241ccae7af2SMauro Carvalho Chehab if (freq <= 480000) lnaband = 0x60; else 242ccae7af2SMauro Carvalho Chehab if (freq <= 570000) lnaband = 0x50; else 243ccae7af2SMauro Carvalho Chehab if (freq <= 645000) lnaband = 0x40; else 244ccae7af2SMauro Carvalho Chehab if (freq <= 730000) lnaband = 0x30; else 245ccae7af2SMauro Carvalho Chehab if (freq <= 810000) lnaband = 0x20; else lnaband = 0x10; 246ccae7af2SMauro Carvalho Chehab 247ccae7af2SMauro Carvalho Chehab b[0] = REG_LO1C1; 248ccae7af2SMauro Carvalho Chehab b[1] = lnaband | ((num1 >>2) & 0x0F); 249ccae7af2SMauro Carvalho Chehab b[2] = div1; 250ccae7af2SMauro Carvalho Chehab b[3] = (num2 & 0x0F) | ((num1 & 3) << 4); 251ccae7af2SMauro Carvalho Chehab b[4] = num2 >> 4; 252ccae7af2SMauro Carvalho Chehab b[5] = ((num2 >>12) & 1) | (div2 << 1); 253ccae7af2SMauro Carvalho Chehab 254ccae7af2SMauro Carvalho Chehab dprintk("IF1: %dMHz",(int)if1); 255ccae7af2SMauro Carvalho Chehab dprintk("PLL freq=%dkHz f_lo1=%dkHz f_lo2=%dkHz",(int)freq,(int)f_lo1,(int)f_lo2); 256ccae7af2SMauro Carvalho Chehab dprintk("PLL div1=%d num1=%d div2=%d num2=%d",(int)div1,(int)num1,(int)div2,(int)num2); 257ccae7af2SMauro Carvalho Chehab dprintk("PLL [1..5]: %2x %2x %2x %2x %2x",(int)b[1],(int)b[2],(int)b[3],(int)b[4],(int)b[5]); 258ccae7af2SMauro Carvalho Chehab 259ccae7af2SMauro Carvalho Chehab mt2060_writeregs(priv,b,6); 260ccae7af2SMauro Carvalho Chehab 261ccae7af2SMauro Carvalho Chehab //Waits for pll lock or timeout 262ccae7af2SMauro Carvalho Chehab i = 0; 263ccae7af2SMauro Carvalho Chehab do { 264ccae7af2SMauro Carvalho Chehab mt2060_readreg(priv,REG_LO_STATUS,b); 265ccae7af2SMauro Carvalho Chehab if ((b[0] & 0x88)==0x88) 266ccae7af2SMauro Carvalho Chehab break; 267ccae7af2SMauro Carvalho Chehab msleep(4); 268ccae7af2SMauro Carvalho Chehab i++; 269ccae7af2SMauro Carvalho Chehab } while (i<10); 270ccae7af2SMauro Carvalho Chehab 271ccae7af2SMauro Carvalho Chehab if (fe->ops.i2c_gate_ctrl) 272ccae7af2SMauro Carvalho Chehab fe->ops.i2c_gate_ctrl(fe, 0); /* close i2c_gate */ 273ccae7af2SMauro Carvalho Chehab 2744539fc5cSMauro Carvalho Chehab return 0; 275ccae7af2SMauro Carvalho Chehab } 276ccae7af2SMauro Carvalho Chehab 277ccae7af2SMauro Carvalho Chehab static void mt2060_calibrate(struct mt2060_priv *priv) 278ccae7af2SMauro Carvalho Chehab { 279ccae7af2SMauro Carvalho Chehab u8 b = 0; 280ccae7af2SMauro Carvalho Chehab int i = 0; 281ccae7af2SMauro Carvalho Chehab 282ccae7af2SMauro Carvalho Chehab if (mt2060_writeregs(priv,mt2060_config1,sizeof(mt2060_config1))) 283ccae7af2SMauro Carvalho Chehab return; 284ccae7af2SMauro Carvalho Chehab if (mt2060_writeregs(priv,mt2060_config2,sizeof(mt2060_config2))) 285ccae7af2SMauro Carvalho Chehab return; 286ccae7af2SMauro Carvalho Chehab 287ccae7af2SMauro Carvalho Chehab /* initialize the clock output */ 288ccae7af2SMauro Carvalho Chehab mt2060_writereg(priv, REG_VGAG, (priv->cfg->clock_out << 6) | 0x30); 289ccae7af2SMauro Carvalho Chehab 290ccae7af2SMauro Carvalho Chehab do { 291ccae7af2SMauro Carvalho Chehab b |= (1 << 6); // FM1SS; 292ccae7af2SMauro Carvalho Chehab mt2060_writereg(priv, REG_LO2C1,b); 293ccae7af2SMauro Carvalho Chehab msleep(20); 294ccae7af2SMauro Carvalho Chehab 295ccae7af2SMauro Carvalho Chehab if (i == 0) { 296ccae7af2SMauro Carvalho Chehab b |= (1 << 7); // FM1CA; 297ccae7af2SMauro Carvalho Chehab mt2060_writereg(priv, REG_LO2C1,b); 298ccae7af2SMauro Carvalho Chehab b &= ~(1 << 7); // FM1CA; 299ccae7af2SMauro Carvalho Chehab msleep(20); 300ccae7af2SMauro Carvalho Chehab } 301ccae7af2SMauro Carvalho Chehab 302ccae7af2SMauro Carvalho Chehab b &= ~(1 << 6); // FM1SS 303ccae7af2SMauro Carvalho Chehab mt2060_writereg(priv, REG_LO2C1,b); 304ccae7af2SMauro Carvalho Chehab 305ccae7af2SMauro Carvalho Chehab msleep(20); 306ccae7af2SMauro Carvalho Chehab i++; 307ccae7af2SMauro Carvalho Chehab } while (i < 9); 308ccae7af2SMauro Carvalho Chehab 309ccae7af2SMauro Carvalho Chehab i = 0; 310ccae7af2SMauro Carvalho Chehab while (i++ < 10 && mt2060_readreg(priv, REG_MISC_STAT, &b) == 0 && (b & (1 << 6)) == 0) 311ccae7af2SMauro Carvalho Chehab msleep(20); 312ccae7af2SMauro Carvalho Chehab 313ccae7af2SMauro Carvalho Chehab if (i <= 10) { 314ccae7af2SMauro Carvalho Chehab mt2060_readreg(priv, REG_FM_FREQ, &priv->fmfreq); // now find out, what is fmreq used for :) 315ccae7af2SMauro Carvalho Chehab dprintk("calibration was successful: %d", (int)priv->fmfreq); 316ccae7af2SMauro Carvalho Chehab } else 317ccae7af2SMauro Carvalho Chehab dprintk("FMCAL timed out"); 318ccae7af2SMauro Carvalho Chehab } 319ccae7af2SMauro Carvalho Chehab 320ccae7af2SMauro Carvalho Chehab static int mt2060_get_frequency(struct dvb_frontend *fe, u32 *frequency) 321ccae7af2SMauro Carvalho Chehab { 322ccae7af2SMauro Carvalho Chehab struct mt2060_priv *priv = fe->tuner_priv; 323ccae7af2SMauro Carvalho Chehab *frequency = priv->frequency; 324ccae7af2SMauro Carvalho Chehab return 0; 325ccae7af2SMauro Carvalho Chehab } 326ccae7af2SMauro Carvalho Chehab 327ccae7af2SMauro Carvalho Chehab static int mt2060_get_if_frequency(struct dvb_frontend *fe, u32 *frequency) 328ccae7af2SMauro Carvalho Chehab { 329ccae7af2SMauro Carvalho Chehab *frequency = IF2 * 1000; 330ccae7af2SMauro Carvalho Chehab return 0; 331ccae7af2SMauro Carvalho Chehab } 332ccae7af2SMauro Carvalho Chehab 333ccae7af2SMauro Carvalho Chehab static int mt2060_init(struct dvb_frontend *fe) 334ccae7af2SMauro Carvalho Chehab { 335ccae7af2SMauro Carvalho Chehab struct mt2060_priv *priv = fe->tuner_priv; 336ccae7af2SMauro Carvalho Chehab int ret; 337ccae7af2SMauro Carvalho Chehab 338ccae7af2SMauro Carvalho Chehab if (fe->ops.i2c_gate_ctrl) 339ccae7af2SMauro Carvalho Chehab fe->ops.i2c_gate_ctrl(fe, 1); /* open i2c_gate */ 340ccae7af2SMauro Carvalho Chehab 341e11415c8SAntti Palosaari if (priv->sleep) { 342e11415c8SAntti Palosaari ret = mt2060_writereg(priv, REG_MISC_CTRL, 0x20); 343e11415c8SAntti Palosaari if (ret) 344e11415c8SAntti Palosaari goto err_i2c_gate_ctrl; 345e11415c8SAntti Palosaari } 346e11415c8SAntti Palosaari 347ccae7af2SMauro Carvalho Chehab ret = mt2060_writereg(priv, REG_VGAG, 348ccae7af2SMauro Carvalho Chehab (priv->cfg->clock_out << 6) | 0x33); 349ccae7af2SMauro Carvalho Chehab 350e11415c8SAntti Palosaari err_i2c_gate_ctrl: 351ccae7af2SMauro Carvalho Chehab if (fe->ops.i2c_gate_ctrl) 352ccae7af2SMauro Carvalho Chehab fe->ops.i2c_gate_ctrl(fe, 0); /* close i2c_gate */ 353ccae7af2SMauro Carvalho Chehab 354ccae7af2SMauro Carvalho Chehab return ret; 355ccae7af2SMauro Carvalho Chehab } 356ccae7af2SMauro Carvalho Chehab 357ccae7af2SMauro Carvalho Chehab static int mt2060_sleep(struct dvb_frontend *fe) 358ccae7af2SMauro Carvalho Chehab { 359ccae7af2SMauro Carvalho Chehab struct mt2060_priv *priv = fe->tuner_priv; 360ccae7af2SMauro Carvalho Chehab int ret; 361ccae7af2SMauro Carvalho Chehab 362ccae7af2SMauro Carvalho Chehab if (fe->ops.i2c_gate_ctrl) 363ccae7af2SMauro Carvalho Chehab fe->ops.i2c_gate_ctrl(fe, 1); /* open i2c_gate */ 364ccae7af2SMauro Carvalho Chehab 365ccae7af2SMauro Carvalho Chehab ret = mt2060_writereg(priv, REG_VGAG, 366ccae7af2SMauro Carvalho Chehab (priv->cfg->clock_out << 6) | 0x30); 367e11415c8SAntti Palosaari if (ret) 368e11415c8SAntti Palosaari goto err_i2c_gate_ctrl; 369ccae7af2SMauro Carvalho Chehab 370e11415c8SAntti Palosaari if (priv->sleep) 371e11415c8SAntti Palosaari ret = mt2060_writereg(priv, REG_MISC_CTRL, 0xe8); 372e11415c8SAntti Palosaari 373e11415c8SAntti Palosaari err_i2c_gate_ctrl: 374ccae7af2SMauro Carvalho Chehab if (fe->ops.i2c_gate_ctrl) 375ccae7af2SMauro Carvalho Chehab fe->ops.i2c_gate_ctrl(fe, 0); /* close i2c_gate */ 376ccae7af2SMauro Carvalho Chehab 377ccae7af2SMauro Carvalho Chehab return ret; 378ccae7af2SMauro Carvalho Chehab } 379ccae7af2SMauro Carvalho Chehab 380f2709c20SMauro Carvalho Chehab static void mt2060_release(struct dvb_frontend *fe) 381f2709c20SMauro Carvalho Chehab { 382f2709c20SMauro Carvalho Chehab kfree(fe->tuner_priv); 383f2709c20SMauro Carvalho Chehab fe->tuner_priv = NULL; 384f2709c20SMauro Carvalho Chehab } 385f2709c20SMauro Carvalho Chehab 386ccae7af2SMauro Carvalho Chehab static const struct dvb_tuner_ops mt2060_tuner_ops = { 387ccae7af2SMauro Carvalho Chehab .info = { 388ccae7af2SMauro Carvalho Chehab .name = "Microtune MT2060", 389a3f90c75SMauro Carvalho Chehab .frequency_min_hz = 48 * MHz, 390a3f90c75SMauro Carvalho Chehab .frequency_max_hz = 860 * MHz, 391a3f90c75SMauro Carvalho Chehab .frequency_step_hz = 50 * kHz, 392ccae7af2SMauro Carvalho Chehab }, 393ccae7af2SMauro Carvalho Chehab 394f2709c20SMauro Carvalho Chehab .release = mt2060_release, 395ccae7af2SMauro Carvalho Chehab 396ccae7af2SMauro Carvalho Chehab .init = mt2060_init, 397ccae7af2SMauro Carvalho Chehab .sleep = mt2060_sleep, 398ccae7af2SMauro Carvalho Chehab 399ccae7af2SMauro Carvalho Chehab .set_params = mt2060_set_params, 400ccae7af2SMauro Carvalho Chehab .get_frequency = mt2060_get_frequency, 401ccae7af2SMauro Carvalho Chehab .get_if_frequency = mt2060_get_if_frequency, 402ccae7af2SMauro Carvalho Chehab }; 403ccae7af2SMauro Carvalho Chehab 404ccae7af2SMauro Carvalho Chehab /* This functions tries to identify a MT2060 tuner by reading the PART/REV register. This is hasty. */ 405ccae7af2SMauro Carvalho Chehab struct dvb_frontend * mt2060_attach(struct dvb_frontend *fe, struct i2c_adapter *i2c, struct mt2060_config *cfg, u16 if1) 406ccae7af2SMauro Carvalho Chehab { 407ccae7af2SMauro Carvalho Chehab struct mt2060_priv *priv = NULL; 408ccae7af2SMauro Carvalho Chehab u8 id = 0; 409ccae7af2SMauro Carvalho Chehab 410ccae7af2SMauro Carvalho Chehab priv = kzalloc(sizeof(struct mt2060_priv), GFP_KERNEL); 411ccae7af2SMauro Carvalho Chehab if (priv == NULL) 412ccae7af2SMauro Carvalho Chehab return NULL; 413ccae7af2SMauro Carvalho Chehab 414ccae7af2SMauro Carvalho Chehab priv->cfg = cfg; 415ccae7af2SMauro Carvalho Chehab priv->i2c = i2c; 416ccae7af2SMauro Carvalho Chehab priv->if1_freq = if1; 417433c4864SAntti Palosaari priv->i2c_max_regs = ~0; 418ccae7af2SMauro Carvalho Chehab 419ccae7af2SMauro Carvalho Chehab if (fe->ops.i2c_gate_ctrl) 420ccae7af2SMauro Carvalho Chehab fe->ops.i2c_gate_ctrl(fe, 1); /* open i2c_gate */ 421ccae7af2SMauro Carvalho Chehab 422ccae7af2SMauro Carvalho Chehab if (mt2060_readreg(priv,REG_PART_REV,&id) != 0) { 423ccae7af2SMauro Carvalho Chehab kfree(priv); 424ccae7af2SMauro Carvalho Chehab return NULL; 425ccae7af2SMauro Carvalho Chehab } 426ccae7af2SMauro Carvalho Chehab 427ccae7af2SMauro Carvalho Chehab if (id != PART_REV) { 428ccae7af2SMauro Carvalho Chehab kfree(priv); 429ccae7af2SMauro Carvalho Chehab return NULL; 430ccae7af2SMauro Carvalho Chehab } 431ccae7af2SMauro Carvalho Chehab printk(KERN_INFO "MT2060: successfully identified (IF1 = %d)\n", if1); 432ccae7af2SMauro Carvalho Chehab memcpy(&fe->ops.tuner_ops, &mt2060_tuner_ops, sizeof(struct dvb_tuner_ops)); 433ccae7af2SMauro Carvalho Chehab 434ccae7af2SMauro Carvalho Chehab fe->tuner_priv = priv; 435ccae7af2SMauro Carvalho Chehab 436ccae7af2SMauro Carvalho Chehab mt2060_calibrate(priv); 437ccae7af2SMauro Carvalho Chehab 438ccae7af2SMauro Carvalho Chehab if (fe->ops.i2c_gate_ctrl) 439ccae7af2SMauro Carvalho Chehab fe->ops.i2c_gate_ctrl(fe, 0); /* close i2c_gate */ 440ccae7af2SMauro Carvalho Chehab 441ccae7af2SMauro Carvalho Chehab return fe; 442ccae7af2SMauro Carvalho Chehab } 443ccae7af2SMauro Carvalho Chehab EXPORT_SYMBOL(mt2060_attach); 444ccae7af2SMauro Carvalho Chehab 4459ebd0543SUwe Kleine-König static int mt2060_probe(struct i2c_client *client) 44659e8b7aaSAntti Palosaari { 44759e8b7aaSAntti Palosaari struct mt2060_platform_data *pdata = client->dev.platform_data; 44859e8b7aaSAntti Palosaari struct dvb_frontend *fe; 44959e8b7aaSAntti Palosaari struct mt2060_priv *dev; 45059e8b7aaSAntti Palosaari int ret; 45159e8b7aaSAntti Palosaari u8 chip_id; 45259e8b7aaSAntti Palosaari 45359e8b7aaSAntti Palosaari dev_dbg(&client->dev, "\n"); 45459e8b7aaSAntti Palosaari 45559e8b7aaSAntti Palosaari if (!pdata) { 45659e8b7aaSAntti Palosaari dev_err(&client->dev, "Cannot proceed without platform data\n"); 45759e8b7aaSAntti Palosaari ret = -EINVAL; 45859e8b7aaSAntti Palosaari goto err; 45959e8b7aaSAntti Palosaari } 46059e8b7aaSAntti Palosaari 46159e8b7aaSAntti Palosaari dev = devm_kzalloc(&client->dev, sizeof(*dev), GFP_KERNEL); 46259e8b7aaSAntti Palosaari if (!dev) { 46359e8b7aaSAntti Palosaari ret = -ENOMEM; 46459e8b7aaSAntti Palosaari goto err; 46559e8b7aaSAntti Palosaari } 46659e8b7aaSAntti Palosaari 46759e8b7aaSAntti Palosaari fe = pdata->dvb_frontend; 46859e8b7aaSAntti Palosaari dev->config.i2c_address = client->addr; 46959e8b7aaSAntti Palosaari dev->config.clock_out = pdata->clock_out; 47059e8b7aaSAntti Palosaari dev->cfg = &dev->config; 47159e8b7aaSAntti Palosaari dev->i2c = client->adapter; 47259e8b7aaSAntti Palosaari dev->if1_freq = pdata->if1 ? pdata->if1 : 1220; 47359e8b7aaSAntti Palosaari dev->client = client; 474433c4864SAntti Palosaari dev->i2c_max_regs = pdata->i2c_write_max ? pdata->i2c_write_max - 1 : ~0; 475e11415c8SAntti Palosaari dev->sleep = true; 47659e8b7aaSAntti Palosaari 47759e8b7aaSAntti Palosaari ret = mt2060_readreg(dev, REG_PART_REV, &chip_id); 47859e8b7aaSAntti Palosaari if (ret) { 47959e8b7aaSAntti Palosaari ret = -ENODEV; 48059e8b7aaSAntti Palosaari goto err; 48159e8b7aaSAntti Palosaari } 48259e8b7aaSAntti Palosaari 48359e8b7aaSAntti Palosaari dev_dbg(&client->dev, "chip id=%02x\n", chip_id); 48459e8b7aaSAntti Palosaari 48559e8b7aaSAntti Palosaari if (chip_id != PART_REV) { 48659e8b7aaSAntti Palosaari ret = -ENODEV; 48759e8b7aaSAntti Palosaari goto err; 48859e8b7aaSAntti Palosaari } 48959e8b7aaSAntti Palosaari 490e11415c8SAntti Palosaari /* Power on, calibrate, sleep */ 491e11415c8SAntti Palosaari ret = mt2060_writereg(dev, REG_MISC_CTRL, 0x20); 492e11415c8SAntti Palosaari if (ret) 493e11415c8SAntti Palosaari goto err; 494e11415c8SAntti Palosaari mt2060_calibrate(dev); 495e11415c8SAntti Palosaari ret = mt2060_writereg(dev, REG_MISC_CTRL, 0xe8); 496e11415c8SAntti Palosaari if (ret) 497e11415c8SAntti Palosaari goto err; 498e11415c8SAntti Palosaari 49959e8b7aaSAntti Palosaari dev_info(&client->dev, "Microtune MT2060 successfully identified\n"); 50059e8b7aaSAntti Palosaari memcpy(&fe->ops.tuner_ops, &mt2060_tuner_ops, sizeof(fe->ops.tuner_ops)); 50159e8b7aaSAntti Palosaari fe->ops.tuner_ops.release = NULL; 50259e8b7aaSAntti Palosaari fe->tuner_priv = dev; 50359e8b7aaSAntti Palosaari i2c_set_clientdata(client, dev); 50459e8b7aaSAntti Palosaari 50559e8b7aaSAntti Palosaari return 0; 50659e8b7aaSAntti Palosaari err: 50759e8b7aaSAntti Palosaari dev_dbg(&client->dev, "failed=%d\n", ret); 50859e8b7aaSAntti Palosaari return ret; 50959e8b7aaSAntti Palosaari } 51059e8b7aaSAntti Palosaari 511ed5c2f5fSUwe Kleine-König static void mt2060_remove(struct i2c_client *client) 51259e8b7aaSAntti Palosaari { 51359e8b7aaSAntti Palosaari dev_dbg(&client->dev, "\n"); 51459e8b7aaSAntti Palosaari } 51559e8b7aaSAntti Palosaari 51659e8b7aaSAntti Palosaari static const struct i2c_device_id mt2060_id_table[] = { 51759e8b7aaSAntti Palosaari {"mt2060", 0}, 51859e8b7aaSAntti Palosaari {} 51959e8b7aaSAntti Palosaari }; 52059e8b7aaSAntti Palosaari MODULE_DEVICE_TABLE(i2c, mt2060_id_table); 52159e8b7aaSAntti Palosaari 52259e8b7aaSAntti Palosaari static struct i2c_driver mt2060_driver = { 52359e8b7aaSAntti Palosaari .driver = { 52459e8b7aaSAntti Palosaari .name = "mt2060", 52559e8b7aaSAntti Palosaari .suppress_bind_attrs = true, 52659e8b7aaSAntti Palosaari }, 527*aaeb31c0SUwe Kleine-König .probe = mt2060_probe, 52859e8b7aaSAntti Palosaari .remove = mt2060_remove, 52959e8b7aaSAntti Palosaari .id_table = mt2060_id_table, 53059e8b7aaSAntti Palosaari }; 53159e8b7aaSAntti Palosaari 53259e8b7aaSAntti Palosaari module_i2c_driver(mt2060_driver); 53359e8b7aaSAntti Palosaari 534ccae7af2SMauro Carvalho Chehab MODULE_AUTHOR("Olivier DANET"); 535ccae7af2SMauro Carvalho Chehab MODULE_DESCRIPTION("Microtune MT2060 silicon tuner driver"); 536ccae7af2SMauro Carvalho Chehab MODULE_LICENSE("GPL"); 537