+/*
+ * $Id: tuner.c,v 1.36 2005/01/14 13:29:40 kraxel Exp $
+ */
+
#include <linux/module.h>
+#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <media/tuner.h>
#include <media/audiochip.h>
-/* Addresses to scan */
+#define UNSET (-1U)
+
+/* standard i2c insmod options */
static unsigned short normal_i2c[] = {I2C_CLIENT_END};
static unsigned short normal_i2c_range[] = {0x60,0x6f,I2C_CLIENT_END};
I2C_CLIENT_INSMOD;
-#define UNSET (-1U)
-
-/* insmod options */
-static unsigned int debug = 0;
+/* insmod options used at init time => read/only */
static unsigned int type = UNSET;
static unsigned int addr = 0;
+module_param(type, int, 0444);
+module_param(addr, int, 0444);
+
+/* insmod options used at runtime => read/write */
+static unsigned int debug = 0;
+static unsigned int tv_antenna = 1;
+static unsigned int radio_antenna = 0;
+static unsigned int optimize_vco = 1;
+module_param(debug, int, 0644);
+module_param(tv_antenna, int, 0644);
+module_param(radio_antenna, int, 0644);
+module_param(optimize_vco, int, 0644);
+
static unsigned int tv_range[2] = { 44, 958 };
static unsigned int radio_range[2] = { 65, 108 };
-static unsigned int tv_antenna = 1;
-static unsigned int radio_antenna = 0;
-MODULE_PARM(debug,"i");
-MODULE_PARM(type,"i");
-MODULE_PARM(addr,"i");
-MODULE_PARM(tv_range,"2i");
-MODULE_PARM(radio_range,"2i");
-MODULE_PARM(tv_antenna,"i");
-MODULE_PARM(radio_antenna,"i");
-#define optimize_vco 1
+module_param_array(tv_range, int, NULL, 0644);
+module_param_array(radio_range, int, NULL, 0644);
MODULE_DESCRIPTION("device driver for various TV and TV+FM radio tuners");
MODULE_AUTHOR("Ralph Metzler, Gerd Knorr, Gunther Mayer");
unsigned int freq; /* keep track of the current settings */
v4l2_std_id std;
int using_v4l2;
-
- unsigned int radio;
+
+ enum v4l2_tuner_type mode;
unsigned int input;
-
+
// only for MT2032
unsigned int xogc;
unsigned int radio_if2;
/* tv standard selection for Temic 4046 FM5
this value takes the low bits of control byte 2
- from datasheet Rev.01, Feb.00
+ from datasheet Rev.01, Feb.00
standard BG I L L2 D
picture IF 38.9 38.9 38.9 33.95 38.9
sound 1 33.4 32.9 32.4 40.45 32.4
- sound 2 33.16
+ sound 2 33.16
NICAM 33.05 32.348 33.05 33.05
*/
#define TEMIC_SET_PAL_I 0x05
#define PHILIPS_SET_PAL_I 0x01 /* Bit 2 always zero !*/
#define PHILIPS_SET_PAL_BGDK 0x09
#define PHILIPS_SET_PAL_L2 0x0a
-#define PHILIPS_SET_PAL_L 0x0b
+#define PHILIPS_SET_PAL_L 0x0b
/* system switching for Philips FI1216MF MK2
from datasheet "1996 Jul 09",
/* ---------------------------------------------------------------------- */
-struct tunertype
+struct tunertype
{
char *name;
unsigned char Vendor;
unsigned char Type;
-
+
unsigned short thresh1; /* band switch VHF_LO <=> VHF_HI */
unsigned short thresh2; /* band switch VHF_HI <=> UHF */
unsigned char VHF_L;
unsigned char VHF_H;
unsigned char UHF;
- unsigned char config;
- unsigned short IFPCoff; /* 622.4=16*38.90 MHz PAL,
- 732 =16*45.75 NTSCi,
- 940 =58.75 NTSC-Japan */
+ unsigned char config;
+ unsigned short IFPCoff; /* 622.4=16*38.90 MHz PAL,
+ 732 =16*45.75 NTSCi,
+ 940 =16*58.75 NTSC-Japan
+ 704 =16*44 ATSC */
};
/*
{ "Alps TSBC5", Alps, PAL, /* untested - data sheet guess. Only IF differs. */
16*133.25,16*351.25,0x01,0x02,0x08,0x8e,608},
{ "Temic PAL_BG (4006FH5)", TEMIC, PAL,
- 16*170.00,16*450.00,0xa0,0x90,0x30,0x8e,623},
+ 16*170.00,16*450.00,0xa0,0x90,0x30,0x8e,623},
{ "Alps TSCH6",Alps,NTSC,
16*137.25,16*385.25,0x14,0x12,0x11,0x8e,732},
{ "Temic PAL* auto + FM (4009 FN5)", TEMIC, PAL,
16*141.00, 16*464.00, 0xa0,0x90,0x30,0x8e,623},
{ "SHARP NTSC_JP (2U5JF5540)", SHARP, NTSC, /* 940=16*58.75 NTSC@Japan */
- 16*137.25,16*317.25,0x01,0x02,0x08,0x8e,732 }, // Corrected to NTSC=732 (was:940)
+ 16*137.25,16*317.25,0x01,0x02,0x08,0x8e,940 },
{ "Samsung PAL TCPM9091PD27", Samsung, PAL, /* from sourceforge v3tv */
16*169,16*464,0xA0,0x90,0x30,0x8e,623},
16*170.00, 16*450.00, 0x01,0x02,0x08,0x8e,732},
{ "HITACHI V7-J180AT", HITACHI, NTSC,
- 16*170.00, 16*450.00, 0x01,0x02,0x00,0x8e,940 },
+ 16*170.00, 16*450.00, 0x01,0x02,0x08,0x8e,940 },
{ "Philips PAL_MK (FI1216 MK)", Philips, PAL,
16*140.25,16*463.25,0x01,0xc2,0xcf,0x8e,623},
{ "Philips 1236D ATSC/NTSC daul in",Philips,ATSC,
16*160.00,16*442.00,0x01,0x02,0x04,0x8e,732},
{ "Microtune 4049 FM5",Microtune,PAL,
16*141.00,16*464.00,0xa0,0x90,0x30,0x8e,623},
+ { "Panasonic VP27s/ENGE4324D", Panasonic, NTSC,
+ 16*160.00,16*454.00,0x01,0x02,0x08,0xce,940},
+ { "LG NTSC (TAPE series)", LGINNOTEK, NTSC,
+ 16*160.00,16*442.00,0x01,0x02,0x04,0x8e,732 },
+
+ { "Tenna TNF 8831 BGFF)", Philips, PAL,
+ 16*161.25,16*463.25,0xa0,0x90,0x30,0x8e,623},
+ { "Microtune 4042 FI5 ATSC/NTSC dual in", Microtune, NTSC,
+ 16*162.00,16*457.00,0xa2,0x94,0x31,0x8e,732},
+ { "TCL 2002N", TCL, NTSC,
+ 16*172.00,16*448.00,0x01,0x02,0x08,0x8e,732},
+ { "Philips PAL/SECAM_D (FM 1256 I-H3)", Philips, PAL,
+ 16*160.00,16*442.00,0x01,0x02,0x04,0x8e,623 },
+
+ { "Thomson DDT 7610 ATSC/NTSC)", THOMSON, ATSC,
+ 16*157.25,16*454.00,0x39,0x3a,0x3c,0x8e,732},
+ { "Philips FQ1286", Philips, NTSC,
+ 16*160.00,16*454.00,0x41,0x42,0x04,0x8e,940}, // UHF band untested
};
#define TUNERS ARRAY_SIZE(tuners)
if(rfin >400*1000*1000)
buf[6]=0xe4;
else
- buf[6]=0xf4; // set PKEN per rev 1.2
+ buf[6]=0xf4; // set PKEN per rev 1.2
buf[7]=8+xogc;
buf[8]=0xc3; //reserved
buf[9]=0x4e; //reserved
i2c_master_recv(c,buf,1);
dprintk("mt2032 Reg.E=0x%02x\n",buf[0]);
lock=buf[0] &0x06;
-
+
if (lock==6)
break;
-
+
dprintk("mt2032: pll wait 1ms for lock (0x%2x)\n",buf[0]);
udelay(1000);
}
if(tad1 ==1) return lock;
if(tad1==2) {
- if(sel==0)
+ if(sel==0)
return lock;
else sel--;
}
// wait for PLLs to lock (per manual), retry LINT if not.
for(lint_try=0; lint_try<2; lint_try++) {
lock=mt2032_check_lo_lock(c);
-
+
if(optimize_vco)
lock=mt2032_optimize_vco(c,sel,lock);
if(lock==6) break;
-
- printk("mt2032: re-init PLLs by LINT\n");
- buf[0]=7;
+
+ printk("mt2032: re-init PLLs by LINT\n");
+ buf[0]=7;
buf[1]=0x80 +8+t->xogc; // set LINT to re-init PLLs
i2c_master_send(c,buf,2);
mdelay(10);
unsigned int f_lo1,f_lo2,lo1,lo2,f_lo1_modulo,f_lo2_modulo,num1,num2,div1a,div1b,div2a,div2b;
int ret;
unsigned char buf[6];
-
- dprintk("mt2050_set_if_freq freq=%d\n",freq);
-
+
+ dprintk("mt2050_set_if_freq freq=%d if1=%d if2=%d\n",
+ freq,if1,if2);
+
f_lo1=freq+if1;
f_lo1=(f_lo1/1000000)*1000000;
-
+
f_lo2=f_lo1-freq-if2;
f_lo2=(f_lo2/50000)*50000;
-
+
lo1=f_lo1/4000000;
lo2=f_lo2/4000000;
-
+
f_lo1_modulo= f_lo1-(lo1*4000000);
f_lo2_modulo= f_lo2-(lo2*4000000);
-
+
num1=4*f_lo1_modulo/4000000;
num2=4096*(f_lo2_modulo/1000)/4000;
-
+
// todo spurchecks
-
+
div1a=(lo1/12)-1;
div1b=lo1-(div1a+1)*12;
-
+
div2a=(lo2/8)-1;
div2b=lo2-(div2a+1)*8;
-
- dprintk("lo1 lo2 = %d %d\n", lo1, lo2);
- dprintk("num1 num2 div1a div1b div2a div2b= %x %x %x %x %x %x\n",num1,num2,div1a,div1b,div2a,div2b);
-
-
+
+ if (debug > 1) {
+ printk("lo1 lo2 = %d %d\n", lo1, lo2);
+ printk("num1 num2 div1a div1b div2a div2b= %x %x %x %x %x %x\n",num1,num2,div1a,div1b,div2a,div2b);
+ }
+
buf[0]=1;
buf[1]= 4*div1b + num1;
if(freq<275*1000*1000) buf[1] = buf[1]|0x80;
-
+
buf[2]=div1a;
buf[3]=32*div2b + num2/256;
buf[4]=num2-(num2/256)*256;
buf[5]=div2a;
if(num2!=0) buf[5]=buf[5]|0x40;
-
- if(debug) {
+
+ if (debug > 1) {
int i;
printk("bufs is: ");
for(i=0;i<6;i++)
printk("%x ",buf[i]);
printk("\n");
}
-
+
ret=i2c_master_send(c,buf,6);
if (ret!=6)
printk("mt2050_set_if_freq failed with %d\n",ret);
{
struct tuner *t = i2c_get_clientdata(c);
unsigned int if2;
-
+
if (t->std & V4L2_STD_525_60) {
// NTSC
if2 = 45750*1000;
// PAL
if2 = 38900*1000;
}
+ if (V4L2_TUNER_DIGITAL_TV == t->mode) {
+ // testing for DVB ...
+ if2 = 36150*1000;
+ }
mt2050_set_if_freq(c, freq*62500, if2);
mt2050_set_antenna(c, tv_antenna);
}
{
struct tuner *t = i2c_get_clientdata(c);
int if2 = t->radio_if2;
-
+
mt2050_set_if_freq(c, freq*62500, if2);
mt2050_set_antenna(c, radio_antenna);
}
struct tuner *t = i2c_get_clientdata(c);
unsigned char buf[2];
int ret;
-
+
buf[0]=6;
buf[1]=0x10;
ret=i2c_master_send(c,buf,2); // power
-
+
buf[0]=0x0f;
buf[1]=0x0f;
ret=i2c_master_send(c,buf,2); // m1lo
-
+
buf[0]=0x0d;
ret=i2c_master_send(c,buf,1);
i2c_master_recv(c,buf,1);
-
+
dprintk("mt2050: sro is %x\n",buf[0]);
t->tv_freq = mt2050_set_tv_freq;
t->radio_freq = mt2050_set_radio_freq;
char *name;
unsigned char buf[21];
int company_code;
-
+
memset(buf,0,sizeof(buf));
t->tv_freq = NULL;
t->radio_freq = NULL;
unsigned char buffer[4];
int rc;
- tun=&tuners[t->type];
- if (freq < tun->thresh1)
+ tun = &tuners[t->type];
+ if (freq < tun->thresh1) {
config = tun->VHF_L;
- else if (freq < tun->thresh2)
+ dprintk("tv: VHF lowrange\n");
+ } else if (freq < tun->thresh2) {
config = tun->VHF_H;
- else
+ dprintk("tv: VHF high range\n");
+ } else {
config = tun->UHF;
+ dprintk("tv: UHF range\n");
+ }
/* tv norm specific stuff for multi-norm tuners */
/* 0x02 -> NTSC antenna input 1 */
/* 0x03 -> NTSC antenna input 2 */
config &= ~0x03;
- if (t->std & V4L2_STD_ATSC)
+ if (!(t->std & V4L2_STD_ATSC))
config |= 2;
/* FIXME: input */
break;
+
+ case TUNER_MICROTUNE_4042FI5:
+ /* Set the charge pump for fast tuning */
+ tun->config |= 0x40;
+ break;
}
-
/*
* Philips FI1216MK2 remark from specification :
* for channel selection involving band switching, and to ensure
if (4 != (rc = i2c_master_send(c,buffer,4)))
printk("tuner: i2c i/o error: rc == %d (should be 4)\n",rc);
+ if (t->type == TUNER_MICROTUNE_4042FI5) {
+ // FIXME - this may also work for other tuners
+ unsigned long timeout = jiffies + msecs_to_jiffies(1);
+ u8 status_byte = 0;
+
+ /* Wait until the PLL locks */
+ for (;;) {
+ if (time_after(jiffies,timeout))
+ return;
+ if (1 != (rc = i2c_master_recv(c,&status_byte,1))) {
+ dprintk("tuner: i2c i/o read error: rc == %d (should be 1)\n",rc);
+ break;
+ }
+ /* bit 6 is PLL locked indicator */
+ if (status_byte & 0x40)
+ break;
+ udelay(10);
+ }
+
+ /* Set the charge pump for optimized phase noise figure */
+ tun->config &= ~0x40;
+ buffer[0] = (div>>8) & 0x7f;
+ buffer[1] = div & 0xff;
+ buffer[2] = tun->config;
+ buffer[3] = config;
+ dprintk("tuner: tv 0x%02x 0x%02x 0x%02x 0x%02x\n",
+ buffer[0],buffer[1],buffer[2],buffer[3]);
+
+ if (4 != (rc = i2c_master_send(c,buffer,4)))
+ dprintk("tuner: i2c i/o error: rc == %d (should be 4)\n",rc);
+ }
}
static void default_set_radio_freq(struct i2c_client *c, unsigned int freq)
tun=&tuners[t->type];
div = freq + (int)(16*10.7);
- buffer[0] = (div>>8) & 0x7f;
- buffer[1] = div & 0xff;
buffer[2] = tun->config;
+
switch (t->type) {
case TUNER_PHILIPS_FM1216ME_MK3:
case TUNER_PHILIPS_FM1236_MK3:
buffer[3] = 0x19;
break;
+ case TUNER_PHILIPS_FM1256_IH3:
+ div = (20 * freq)/16 + 333 * 2;
+ buffer[2] = 0x80;
+ buffer[3] = 0x19;
+ break;
+ case TUNER_LG_PAL_FM:
+ buffer[3] = 0xa5;
+ break;
default:
buffer[3] = 0xa4;
break;
}
+ buffer[0] = (div>>8) & 0x7f;
+ buffer[1] = div & 0xff;
dprintk("tuner: radio 0x%02x 0x%02x 0x%02x 0x%02x\n",
buffer[0],buffer[1],buffer[2],buffer[3]);
{
struct tuner *t = i2c_get_clientdata(c);
- if (t->radio) {
+ switch (t->mode) {
+ case V4L2_TUNER_RADIO:
dprintk("tuner: radio freq set to %lu.%02lu\n",
freq/16,freq%16*100/16);
set_radio_freq(c,freq);
- } else {
+ break;
+ case V4L2_TUNER_ANALOG_TV:
+ case V4L2_TUNER_DIGITAL_TV:
dprintk("tuner: tv freq set to %lu.%02lu\n",
freq/16,freq%16*100/16);
set_tv_freq(c, freq);
+ break;
}
t->freq = freq;
}
{
struct tuner *t = i2c_get_clientdata(c);
- if (t->type != UNSET) {
+ if (t->type != UNSET && t->type != TUNER_ABSENT) {
if (t->type != type)
printk("tuner: type already set to %d, "
"ignoring request for %d\n", t->type, type);
}
}
-static char *pal = "-";
-MODULE_PARM(pal,"s");
+static char pal[] = "-";
+module_param_string(pal, pal, 0644, sizeof(pal));
static int tuner_fixup_std(struct tuner *t)
{
if (this_adap > 0)
return -1;
this_adap++;
-
+
client_template.adapter = adap;
client_template.addr = addr;
set_type(client,*iarg,client->adapter->name);
break;
case AUDC_SET_RADIO:
- if (!t->radio) {
+ if (V4L2_TUNER_RADIO != t->mode) {
set_tv_freq(client,400 * 16);
- t->radio = 1;
+ t->mode = V4L2_TUNER_RADIO;
}
break;
case AUDC_CONFIG_PINNACLE:
break;
}
break;
-
+
/* --- v4l ioctls --- */
/* take care: bttv does userspace copying, we'll get a
kernel pointer here... */
struct video_channel *vc = arg;
CHECK_V4L2;
- t->radio = 0;
+ t->mode = V4L2_TUNER_ANALOG_TV;
if (vc->norm < ARRAY_SIZE(map))
t->std = map[vc->norm];
tuner_fixup_std(t);
struct video_tuner *vt = arg;
CHECK_V4L2;
- if (t->radio)
+ if (V4L2_TUNER_RADIO == t->mode)
vt->signal = tuner_signal(client);
return 0;
}
struct video_audio *va = arg;
CHECK_V4L2;
- if (t->radio)
+ if (V4L2_TUNER_RADIO == t->mode)
va->mode = (tuner_stereo(client) ? VIDEO_SOUND_STEREO : VIDEO_SOUND_MONO);
return 0;
}
v4l2_std_id *id = arg;
SWITCH_V4L2;
- t->radio = 0;
+ t->mode = V4L2_TUNER_ANALOG_TV;
t->std = *id;
tuner_fixup_std(t);
if (t->freq)
struct v4l2_frequency *f = arg;
SWITCH_V4L2;
- if (V4L2_TUNER_ANALOG_TV == f->type) {
- t->radio = 0;
- }
- if (V4L2_TUNER_RADIO == f->type) {
- if (!t->radio) {
- set_tv_freq(client,400*16);
- t->radio = 1;
- }
- }
+ if (V4L2_TUNER_RADIO == f->type &&
+ V4L2_TUNER_RADIO != t->mode)
+ set_tv_freq(client,400*16);
+ t->mode = f->type;
t->freq = f->frequency;
set_freq(client,t->freq);
break;
struct v4l2_tuner *tuner = arg;
SWITCH_V4L2;
- if (t->radio)
+ if (V4L2_TUNER_RADIO == t->mode)
tuner->signal = tuner_signal(client);
break;
}
/* nothing */
break;
}
-
+
+ return 0;
+}
+
+static int tuner_suspend(struct device * dev, u32 state, u32 level)
+{
+ dprintk("tuner: suspend\n");
+ /* FIXME: power down ??? */
+ return 0;
+}
+
+static int tuner_resume(struct device * dev, u32 level)
+{
+ struct i2c_client *c = container_of(dev, struct i2c_client, dev);
+ struct tuner *t = i2c_get_clientdata(c);
+
+ dprintk("tuner: resume\n");
+ if (t->freq)
+ set_freq(c,t->freq);
return 0;
}
.attach_adapter = tuner_probe,
.detach_client = tuner_detach,
.command = tuner_command,
+ .driver = {
+ .suspend = tuner_suspend,
+ .resume = tuner_resume,
+ },
};
static struct i2c_client client_template =
{
.driver = &driver,
};
-static int tuner_init_module(void)
+static int __init tuner_init_module(void)
{
- i2c_add_driver(&driver);
- return 0;
+ return i2c_add_driver(&driver);
}
-static void tuner_cleanup_module(void)
+static void __exit tuner_cleanup_module(void)
{
i2c_del_driver(&driver);
}