[PATCH] v4l: update for tuner cards and some V4L chips

Tuner improvements and additions.  TEA5767 FM tuner added.  Several small
fixes.

Signed-off-by: Mauro Carvalho Chehab <mchehab@brturbo.com.br>
Signed-off-by: Nickolay V Shmyrev <nshmyrev@yandex.ru>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
diff --git a/drivers/media/video/tuner-core.c b/drivers/media/video/tuner-core.c
index 71423ae..ba13bfa 100644
--- a/drivers/media/video/tuner-core.c
+++ b/drivers/media/video/tuner-core.c
@@ -1,5 +1,5 @@
 /*
- * $Id: tuner-core.c,v 1.7 2005/05/30 02:02:47 mchehab Exp $
+ * $Id: tuner-core.c,v 1.15 2005/06/12 01:36:14 mchehab Exp $
  *
  * i2c tv tuner chip device driver
  * core core, i.e. kernel interfaces, registering and so on
@@ -26,15 +26,17 @@
 /*
  * comment line bellow to return to old behavor, where only one I2C device is supported
  */
-/* #define CONFIG_TUNER_MULTI_I2C */
+#define CONFIG_TUNER_MULTI_I2C /**/
 
 #define UNSET (-1U)
 
 /* standard i2c insmod options */
 static unsigned short normal_i2c[] = {
 	0x4b, /* tda8290 */
-	0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
-	0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
+	I2C_CLIENT_END
+};
+static unsigned short normal_i2c_range[] = {
+	0x60, 0x6f,
 	I2C_CLIENT_END
 };
 I2C_CLIENT_INSMOD;
@@ -59,7 +61,7 @@
 
 static int this_adap;
 #ifdef CONFIG_TUNER_MULTI_I2C
-static unsigned short tv_tuner, radio_tuner;
+static unsigned short first_tuner, tv_tuner, radio_tuner;
 #endif
 
 static struct i2c_driver driver;
@@ -67,7 +69,7 @@
 
 /* ---------------------------------------------------------------------- */
 
-// Set tuner frequency,  freq in Units of 62.5kHz = 1/16MHz
+/* Set tuner frequency,  freq in Units of 62.5kHz = 1/16MHz */
 static void set_tv_freq(struct i2c_client *c, unsigned int freq)
 {
 	struct tuner *t = i2c_get_clientdata(c);
@@ -81,14 +83,26 @@
 		return;
 	}
 	if (freq < tv_range[0]*16 || freq > tv_range[1]*16) {
-		/* FIXME: better do that chip-specific, but
-		   right now we don't have that in the config
-		   struct and this way is still better than no
-		   check at all */
-		tuner_info("TV freq (%d.%02d) out of range (%d-%d)\n",
-			   freq/16,freq%16*100/16,tv_range[0],tv_range[1]);
-		return;
+
+		if (freq >= tv_range[0]*16364 && freq <= tv_range[1]*16384) {
+			/* V4L2_TUNER_CAP_LOW frequency */
+
+			tuner_dbg("V4L2_TUNER_CAP_LOW freq selected for TV. Tuners yet doesn't support converting it to valid freq.\n");
+
+			t->tv_freq(c,freq>>10);
+
+			return;
+                } else {
+			/* FIXME: better do that chip-specific, but
+			   right now we don't have that in the config
+			   struct and this way is still better than no
+			   check at all */
+			tuner_info("TV freq (%d.%02d) out of range (%d-%d)\n",
+				   freq/16,freq%16*100/16,tv_range[0],tv_range[1]);
+			return;
+		}
 	}
+	tuner_dbg("62.5 Khz freq step selected for TV.\n");
 	t->tv_freq(c,freq);
 }
 
@@ -105,11 +119,29 @@
 		return;
 	}
 	if (freq < radio_range[0]*16 || freq > radio_range[1]*16) {
-		tuner_info("radio freq (%d.%02d) out of range (%d-%d)\n",
+		if (freq >= tv_range[0]*16364 && freq <= tv_range[1]*16384) {
+			/* V4L2_TUNER_CAP_LOW frequency */
+			if (t->type == TUNER_TEA5767) {
+				tuner_info("radio freq step 62.5Hz (%d.%06d)\n",(freq>>14),freq%(1<<14)*10000);
+				t->radio_freq(c,freq>>10);
+				return;
+			}
+
+			tuner_dbg("V4L2_TUNER_CAP_LOW freq selected for Radio. Tuners yet doesn't support converting it to valid freq.\n");
+
+			tuner_info("radio freq (%d.%06d)\n",(freq>>14),freq%(1<<14)*10000);
+
+			t->radio_freq(c,freq>>10);
+			return;
+
+                } else {
+			tuner_info("radio freq (%d.%02d) out of range (%d-%d)\n",
 			   freq/16,freq%16*100/16,
-			   radio_range[0],radio_range[1]);
-		return;
+				   radio_range[0],radio_range[1]);
+			return;
+		}
 	}
+	tuner_dbg("62.5 Khz freq step selected for Radio.\n");
 	t->radio_freq(c,freq);
 }
 
@@ -133,34 +165,13 @@
 	t->freq = freq;
 }
 
-#ifdef CONFIG_TUNER_MULTI_I2C
-static void set_addr(struct i2c_client *c, struct tuner_addr *tun_addr)
-{
-	struct tuner *t = i2c_get_clientdata(c);
-
-	switch (tun_addr->type) {
-	case V4L2_TUNER_RADIO:
- 		radio_tuner=tun_addr->addr;
-		tuner_dbg("radio tuner set to I2C address 0x%02x\n",radio_tuner<<1);
-
-		break;
-	default:
-		tv_tuner=tun_addr->addr;
-		tuner_dbg("TV tuner set to I2C address 0x%02x\n",tv_tuner<<1);
-		break;
-	}
-}
-#else
-#define set_addr(c,tun_addr) \
-		tuner_warn("It is recommended to enable CONFIG_TUNER_MULTI_I2C for this card.\n");
-#endif
-
 static void set_type(struct i2c_client *c, unsigned int type)
 {
 	struct tuner *t = i2c_get_clientdata(c);
 
+	tuner_dbg ("I2C addr 0x%02x with type %d\n",c->addr<<1,type);
 	/* sanity check */
-	if (type == UNSET  ||  type == TUNER_ABSENT)
+	if (type == UNSET || type == TUNER_ABSENT)
 		return;
 	if (type >= tuner_count)
 		return;
@@ -175,6 +186,7 @@
 		return;
 
 	t->initialized = 1;
+
 	t->type = type;
 	switch (t->type) {
 	case TUNER_MT2032:
@@ -189,6 +201,53 @@
 	}
 }
 
+#ifdef CONFIG_TUNER_MULTI_I2C
+#define CHECK_ADDR(tp,cmd,tun)	if (client->addr!=tp) { \
+			  return 0; } else \
+			  tuner_info ("Cmd %s accepted to "tun"\n",cmd);
+#define CHECK_MODE(cmd)	if (t->mode == V4L2_TUNER_RADIO) { \
+		 	CHECK_ADDR(radio_tuner,cmd,"radio") } else \
+			{ CHECK_ADDR(tv_tuner,cmd,"TV"); }
+#else
+#define CHECK_ADDR(tp,cmd,tun) tuner_info ("Cmd %s accepted to "tun"\n",cmd);
+#define CHECK_MODE(cmd) tuner_info ("Cmd %s accepted\n",cmd);
+#endif
+
+#ifdef CONFIG_TUNER_MULTI_I2C
+
+static void set_addr(struct i2c_client *c, struct tuner_addr *tun_addr)
+{
+	/* ADDR_UNSET defaults to first available tuner */
+	if ( tun_addr->addr == ADDR_UNSET ) {
+		if (first_tuner != c->addr)
+			return;
+		switch (tun_addr->v4l2_tuner) {
+		case V4L2_TUNER_RADIO:
+	 		radio_tuner=c->addr;
+			break;
+		default:
+			tv_tuner=c->addr;
+			break;
+		}
+	} else {
+		/* Sets tuner to its configured value */
+		switch (tun_addr->v4l2_tuner) {
+		case V4L2_TUNER_RADIO:
+ 			radio_tuner=tun_addr->addr;
+			if ( tun_addr->addr == c->addr ) set_type(c,tun_addr->type);
+			return;
+		default:
+			tv_tuner=tun_addr->addr;
+			if ( tun_addr->addr == c->addr ) set_type(c,tun_addr->type);
+			return;
+		}
+	}
+	set_type(c,tun_addr->type);
+}
+#else
+#define set_addr(c,tun_addr) set_type(c,(tun_addr)->type)
+#endif
+
 static char pal[] = "-";
 module_param_string(pal, pal, sizeof(pal), 0644);
 
@@ -233,6 +292,7 @@
 #else
 	/* by default, first I2C card is both tv and radio tuner */
 	if (this_adap == 0) {
+		first_tuner = addr;
 		tv_tuner = addr;
 		radio_tuner = addr;
 	}
@@ -249,11 +309,12 @@
         memcpy(&t->i2c,&client_template,sizeof(struct i2c_client));
 	i2c_set_clientdata(&t->i2c, t);
 	t->type       = UNSET;
-	t->radio_if2  = 10700*1000; // 10.7MHz - FM radio
+	t->radio_if2  = 10700*1000; /* 10.7MHz - FM radio */
 
         i2c_attach_client(&t->i2c);
 	tuner_info("chip found @ 0x%x (%s)\n",
 		   addr << 1, adap->name);
+
 	set_type(&t->i2c, t->type);
 	return 0;
 }
@@ -261,12 +322,14 @@
 static int tuner_probe(struct i2c_adapter *adap)
 {
 	if (0 != addr) {
-		normal_i2c[0] = addr;
-		normal_i2c[1] = I2C_CLIENT_END;
+		normal_i2c[0]       = addr;
+		normal_i2c_range[0] = addr;
+		normal_i2c_range[1] = addr;
 	}
 	this_adap = 0;
 
 #ifdef CONFIG_TUNER_MULTI_I2C
+	first_tuner = 0;
 	tv_tuner = 0;
 	radio_tuner = 0;
 #endif
@@ -298,17 +361,6 @@
 			  tuner_info("ignore v4l1 call\n"); \
 		          return 0; }
 
-#ifdef CONFIG_TUNER_MULTI_I2C
-#define CHECK_ADDR(tp,cmd)	if (client->addr!=tp) { \
-			  tuner_info ("Cmd %s to addr 0x%02x rejected.\n",cmd,client->addr<<1); \
-			  return 0; }
-#define CHECK_MODE(cmd)	if (t->mode == V4L2_TUNER_RADIO) { \
-			  CHECK_ADDR(radio_tuner,cmd) } else { CHECK_ADDR(tv_tuner,cmd); }
-#else
-#define CHECK_ADDR(tp,cmd)
-#define CHECK_MODE(cmd)
-#endif
-
 static int
 tuner_command(struct i2c_client *client, unsigned int cmd, void *arg)
 {
@@ -320,19 +372,19 @@
 	case TUNER_SET_TYPE:
 		set_type(client,*iarg);
 		break;
-	case TUNER_SET_ADDR:
+	case TUNER_SET_TYPE_ADDR:
 		set_addr(client,(struct tuner_addr *)arg);
 		break;
 	case AUDC_SET_RADIO:
-		CHECK_ADDR(radio_tuner,"AUDC_SET_RADIO");
+		t->mode = V4L2_TUNER_RADIO;
+		CHECK_ADDR(tv_tuner,"AUDC_SET_RADIO","TV");
 
 		if (V4L2_TUNER_RADIO != t->mode) {
 			set_tv_freq(client,400 * 16);
-			t->mode = V4L2_TUNER_RADIO;
 		}
 		break;
 	case AUDC_CONFIG_PINNACLE:
-		CHECK_ADDR(tv_tuner,"AUDC_CONFIG_PINNACLE");
+		CHECK_ADDR(tv_tuner,"AUDC_CONFIG_PINNACLE","TV");
 		switch (*iarg) {
 		case 2:
 			tuner_dbg("pinnacle pal\n");
@@ -360,9 +412,10 @@
 		};
 		struct video_channel *vc = arg;
 
-		CHECK_ADDR(tv_tuner,"VIDIOCSCHAN");
 		CHECK_V4L2;
 		t->mode = V4L2_TUNER_ANALOG_TV;
+		CHECK_ADDR(tv_tuner,"VIDIOCSCHAN","TV");
+
 		if (vc->norm < ARRAY_SIZE(map))
 			t->std = map[vc->norm];
 		tuner_fixup_std(t);
@@ -383,17 +436,27 @@
 	{
 		struct video_tuner *vt = arg;
 
-		CHECK_ADDR(radio_tuner,"VIDIOCGTUNER:");
+		CHECK_ADDR(radio_tuner,"VIDIOCGTUNER","radio");
 		CHECK_V4L2;
-		if (V4L2_TUNER_RADIO == t->mode  &&  t->has_signal)
-			vt->signal = t->has_signal(client);
+		if (V4L2_TUNER_RADIO == t->mode) {
+			if (t->has_signal)
+				vt->signal = t->has_signal(client);
+			if (t->is_stereo) {
+				if (t->is_stereo(client))
+					vt-> flags |= VIDEO_TUNER_STEREO_ON;
+				else
+					vt-> flags &= 0xffff ^ VIDEO_TUNER_STEREO_ON;
+			}
+			vt->flags |= V4L2_TUNER_CAP_LOW; /* Allow freqs at 62.5 Hz */
+		}
+
 		return 0;
 	}
 	case VIDIOCGAUDIO:
 	{
 		struct video_audio *va = arg;
 
-		CHECK_ADDR(radio_tuner,"VIDIOCGAUDIO");
+		CHECK_ADDR(radio_tuner,"VIDIOCGAUDIO","radio");
 		CHECK_V4L2;
 		if (V4L2_TUNER_RADIO == t->mode  &&  t->is_stereo)
 			va->mode = t->is_stereo(client)
@@ -406,9 +469,10 @@
 	{
 		v4l2_std_id *id = arg;
 
-		CHECK_ADDR(tv_tuner,"VIDIOC_S_STD");
 		SWITCH_V4L2;
 		t->mode = V4L2_TUNER_ANALOG_TV;
+		CHECK_ADDR(tv_tuner,"VIDIOC_S_STD","TV");
+
 		t->std = *id;
 		tuner_fixup_std(t);
 		if (t->freq)
@@ -444,13 +508,27 @@
 
 		CHECK_MODE("VIDIOC_G_TUNER");
 		SWITCH_V4L2;
-		if (V4L2_TUNER_RADIO == t->mode  &&  t->has_signal)
-			tuner->signal = t->has_signal(client);
+		if (V4L2_TUNER_RADIO == t->mode) {
+			if (t->has_signal)
+				tuner -> signal = t->has_signal(client);
+			if (t->is_stereo) {
+				if (t->is_stereo(client)) {
+					tuner -> capability |= V4L2_TUNER_CAP_STEREO;
+					tuner -> rxsubchans |= V4L2_TUNER_SUB_STEREO;
+				} else {
+					tuner -> rxsubchans &= 0xffff ^ V4L2_TUNER_SUB_STEREO;
+				}
+			}
+		}
+		/* Wow to deal with V4L2_TUNER_CAP_LOW ? For now, it accepts from low at 62.5KHz step  to high at 62.5 Hz */
 		tuner->rangelow = tv_range[0] * 16;
-		tuner->rangehigh = tv_range[1] * 16;
+//		tuner->rangehigh = tv_range[1] * 16;
+//		tuner->rangelow = tv_range[0] * 16384;
+		tuner->rangehigh = tv_range[1] * 16384;
 		break;
 	}
 	default:
+		tuner_dbg ("Unimplemented IOCTL 0x%08x called to tuner.\n", cmd);
 		/* nothing */
 		break;
 	}
@@ -458,7 +536,7 @@
 	return 0;
 }
 
-static int tuner_suspend(struct device * dev, pm_message_t state, u32 level)
+static int tuner_suspend(struct device * dev, u32 state, u32 level)
 {
 	struct i2c_client *c = container_of(dev, struct i2c_client, dev);
 	struct tuner *t = i2c_get_clientdata(c);