diff mbox

[2/5,v2] sony-tuner: Subdev conversion from wis-sony-tuner

Message ID 1266014180.4626.293.camel@pete-desktop (mailing list archive)
State Changes Requested
Headers show

Commit Message

Pete Eberlein Feb. 12, 2010, 10:36 p.m. UTC
None
diff mbox

Patch

diff -r 27ee8e515b18 -r 1cf1bf4e616f linux/Documentation/video4linux/CARDLIST.tuner
--- a/linux/Documentation/video4linux/CARDLIST.tuner	Wed Feb 10 11:25:59 2010 -0800
+++ b/linux/Documentation/video4linux/CARDLIST.tuner	Fri Feb 12 14:27:39 2010 -0800
@@ -81,3 +81,6 @@ 
 tuner=81 - Partsnic (Daewoo) PTI-5NF05
 tuner=82 - Philips CU1216L
 tuner=83 - NXP TDA18271
+tuner=84 - Sony PAL+SECAM (BTF-PG472Z)
+tuner=85 - Sony NTSC_JP (BTF-PK467Z)
+tuner=86 - Sony NTSC (BTF-PB463Z)
diff -r 27ee8e515b18 -r 1cf1bf4e616f linux/drivers/media/common/tuners/Kconfig
--- a/linux/drivers/media/common/tuners/Kconfig	Wed Feb 10 11:25:59 2010 -0800
+++ b/linux/drivers/media/common/tuners/Kconfig	Fri Feb 12 14:27:39 2010 -0800
@@ -179,4 +179,12 @@ 
 	help
 	  A driver for the silicon tuner MAX2165 from Maxim.
 
+config MEDIA_TUNER_SONY
+	tristate "Sony TV tuner"
+	depends on VIDEO_MEDIA && I2C
+	default m if MEDIA_TUNER_CUSTOMISE
+	help
+	  A driver for the Sony tuners BTF-PG472Z, BTF-PK467Z, BTF-PB463Z.
+
+
 endif # MEDIA_TUNER_CUSTOMISE
diff -r 27ee8e515b18 -r 1cf1bf4e616f linux/drivers/media/common/tuners/Makefile
--- a/linux/drivers/media/common/tuners/Makefile	Wed Feb 10 11:25:59 2010 -0800
+++ b/linux/drivers/media/common/tuners/Makefile	Fri Feb 12 14:27:39 2010 -0800
@@ -24,6 +24,7 @@ 
 obj-$(CONFIG_MEDIA_TUNER_MXL5007T) += mxl5007t.o
 obj-$(CONFIG_MEDIA_TUNER_MC44S803) += mc44s803.o
 obj-$(CONFIG_MEDIA_TUNER_MAX2165) += max2165.o
+obj-$(CONFIG_MEDIA_TUNER_SONY) += sony-tuner.o
 
 EXTRA_CFLAGS += -Idrivers/media/dvb/dvb-core
 EXTRA_CFLAGS += -Idrivers/media/dvb/frontends
diff -r 27ee8e515b18 -r 1cf1bf4e616f linux/drivers/media/common/tuners/sony-tuner.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/linux/drivers/media/common/tuners/sony-tuner.c	Fri Feb 12 14:27:39 2010 -0800
@@ -0,0 +1,677 @@ 
+/*
+ * Copyright (C) 2005-2006 Micronas USA Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/videodev2.h>
+#include <media/tuner.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-i2c-drv.h>
+
+MODULE_DESCRIPTION("Sony TV Tuner driver");
+MODULE_LICENSE("GPL v2");
+
+static int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "debug level 0=off(default) 1=on\n");
+
+/* #define MPX_DEBUG */
+
+/* AS(IF/MPX) pin:      LOW      HIGH/OPEN
+ * IF/MPX address:   0x42/0x40   0x43/0x44
+ */
+#define IF_I2C_ADDR	0x43
+#define MPX_I2C_ADDR	0x44
+
+static v4l2_std_id force_band;
+static char force_band_str[] = "-";
+module_param_string(force_band, force_band_str, sizeof(force_band_str), 0644);
+static int force_mpx_mode = -1;
+module_param(force_mpx_mode, int, 0644);
+
+/* Store tuner info in the same format as tuner.c, so maybe we can put the
+ * Sony tuner support in there. */
+struct sony_tunertype {
+	const char *name;
+	unsigned char Vendor; /* unused here */
+	unsigned char Type; /* unused here */
+
+	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;
+};
+
+/* This array is indexed by (tuner_type - TUNER_SONY_BTF_PG472Z) */
+static const struct sony_tunertype sony_tuners[] = {
+	{ "Sony PAL+SECAM (BTF-PG472Z)", 0, 0,
+	  16*144.25, 16*427.25, 0x01, 0x02, 0x04, 0xc6, 623},
+	{ "Sony NTSC_JP (BTF-PK467Z)", 0, 0,
+	  16*220.25, 16*467.25, 0x01, 0x02, 0x04, 0xc6, 940},
+	{ "Sony NTSC (BTF-PB463Z)", 0, 0,
+	  16*130.25, 16*364.25, 0x01, 0x02, 0x04, 0xc6, 732},
+};
+
+struct sony_tuner {
+	struct v4l2_subdev sd;
+	int type;
+	v4l2_std_id std;
+	unsigned int freq;
+	int mpxmode;
+	u32 audmode;
+};
+
+static inline struct sony_tuner *to_state(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct sony_tuner, sd);
+}
+
+/* Basically the same as default_set_tv_freq() in tuner.c */
+static int set_freq(struct i2c_client *client, int freq)
+{
+	struct sony_tuner *t = to_state(i2c_get_clientdata(client));
+	const char *band_name;
+	int n;
+	int band_select;
+	const struct sony_tunertype *tun;
+	u8 buffer[4];
+
+	tun = &sony_tuners[t->type - TUNER_SONY_BTF_PG472Z];
+	if (freq < tun->thresh1) {
+		band_name = "VHF_L";
+		band_select = tun->VHF_L;
+	} else if (freq < tun->thresh2) {
+		band_name = "VHF_H";
+		band_select = tun->VHF_H;
+	} else {
+		band_name = "UHF";
+		band_select = tun->UHF;
+	}
+	v4l2_dbg(1, debug, client, "tuning to frequency %d.%04d (%s)\n",
+			freq / 16, (freq % 16) * 625, band_name);
+	n = freq + tun->IFPCoff;
+
+	buffer[0] = n >> 8;
+	buffer[1] = n & 0xff;
+	buffer[2] = tun->config;
+	buffer[3] = band_select;
+	i2c_master_send(client, buffer, 4);
+
+	return 0;
+}
+
+static int mpx_write(struct i2c_client *client, int dev, int addr, int val)
+{
+	u8 buffer[5];
+	struct i2c_msg msg;
+
+	buffer[0] = dev;
+	buffer[1] = addr >> 8;
+	buffer[2] = addr & 0xff;
+	buffer[3] = val >> 8;
+	buffer[4] = val & 0xff;
+	msg.addr = MPX_I2C_ADDR;
+	msg.flags = 0;
+	msg.len = 5;
+	msg.buf = buffer;
+	i2c_transfer(client->adapter, &msg, 1);
+	return 0;
+}
+
+/*
+ * MPX register values for the BTF-PG472Z:
+ *
+ *                                 FM_     NICAM_  SCART_
+ *          MODUS  SOURCE    ACB   PRESCAL PRESCAL PRESCAL SYSTEM  VOLUME
+ *         10/0030 12/0008 12/0013 12/000E 12/0010 12/0000 10/0020 12/0000
+ *         ---------------------------------------------------------------
+ * Auto     1003    0020    0100    2603    5000    XXXX    0001    7500
+ *
+ * B/G
+ *  Mono    1003    0020    0100    2603    5000    XXXX    0003    7500
+ *  A2      1003    0020    0100    2601    5000    XXXX    0003    7500
+ *  NICAM   1003    0120    0100    2603    5000    XXXX    0008    7500
+ *
+ * I
+ *  Mono    1003    0020    0100    2603    7900    XXXX    000A    7500
+ *  NICAM   1003    0120    0100    2603    7900    XXXX    000A    7500
+ *
+ * D/K
+ *  Mono    1003    0020    0100    2603    5000    XXXX    0004    7500
+ *  A2-1    1003    0020    0100    2601    5000    XXXX    0004    7500
+ *  A2-2    1003    0020    0100    2601    5000    XXXX    0005    7500
+ *  A2-3    1003    0020    0100    2601    5000    XXXX    0007    7500
+ *  NICAM   1003    0120    0100    2603    5000    XXXX    000B    7500
+ *
+ * L/L'
+ *  Mono    0003    0200    0100    7C03    5000    2200    0009    7500
+ *  NICAM   0003    0120    0100    7C03    5000    XXXX    0009    7500
+ *
+ * M
+ *  Mono    1003    0200    0100    2B03    5000    2B00    0002    7500
+ *
+ * For Asia, replace the 0x26XX in FM_PRESCALE with 0x14XX.
+ *
+ * Bilingual selection in A2/NICAM:
+ *
+ *         High byte of SOURCE     Left chan   Right chan
+ *                 0x01              MAIN         SUB
+ *                 0x03              MAIN         MAIN
+ *                 0x04              SUB          SUB
+ *
+ * Force mono in NICAM by setting the high byte of SOURCE to 0x02 (L/L') or
+ * 0x00 (all other bands).  Force mono in A2 with FMONO_A2:
+ *
+ *                      FMONO_A2
+ *                      10/0022
+ *                      --------
+ *     Forced mono ON     07F0
+ *     Forced mono OFF    0190
+ */
+
+static struct {
+	enum { AUD_MONO, AUD_A2, AUD_NICAM, AUD_NICAM_L } audio_mode;
+	u16 modus;
+	u16 source;
+	u16 acb;
+	u16 fm_prescale;
+	u16 nicam_prescale;
+	u16 scart_prescale;
+	u16 system;
+	u16 volume;
+} mpx_audio_modes[] = {
+	/* Auto */	{ AUD_MONO,	0x1003, 0x0020, 0x0100, 0x2603,
+					0x5000, 0x0000, 0x0001, 0x7500 },
+	/* B/G Mono */	{ AUD_MONO,	0x1003, 0x0020, 0x0100, 0x2603,
+					0x5000, 0x0000, 0x0003, 0x7500 },
+	/* B/G A2 */	{ AUD_A2,	0x1003, 0x0020, 0x0100, 0x2601,
+					0x5000, 0x0000, 0x0003, 0x7500 },
+	/* B/G NICAM */ { AUD_NICAM,	0x1003, 0x0120, 0x0100, 0x2603,
+					0x5000, 0x0000, 0x0008, 0x7500 },
+	/* I Mono */	{ AUD_MONO,	0x1003, 0x0020, 0x0100, 0x2603,
+					0x7900, 0x0000, 0x000A, 0x7500 },
+	/* I NICAM */	{ AUD_NICAM,	0x1003, 0x0120, 0x0100, 0x2603,
+					0x7900, 0x0000, 0x000A, 0x7500 },
+	/* D/K Mono */	{ AUD_MONO,	0x1003, 0x0020, 0x0100, 0x2603,
+					0x5000, 0x0000, 0x0004, 0x7500 },
+	/* D/K A2-1 */	{ AUD_A2,	0x1003, 0x0020, 0x0100, 0x2601,
+					0x5000, 0x0000, 0x0004, 0x7500 },
+	/* D/K A2-2 */	{ AUD_A2,	0x1003, 0x0020, 0x0100, 0x2601,
+					0x5000, 0x0000, 0x0005, 0x7500 },
+	/* D/K A2-3 */	{ AUD_A2,	0x1003, 0x0020, 0x0100, 0x2601,
+					0x5000, 0x0000, 0x0007, 0x7500 },
+	/* D/K NICAM */	{ AUD_NICAM,	0x1003, 0x0120, 0x0100, 0x2603,
+					0x5000, 0x0000, 0x000B, 0x7500 },
+	/* L/L' Mono */	{ AUD_MONO,	0x0003, 0x0200, 0x0100, 0x7C03,
+					0x5000, 0x2200, 0x0009, 0x7500 },
+	/* L/L' NICAM */{ AUD_NICAM_L,	0x0003, 0x0120, 0x0100, 0x7C03,
+					0x5000, 0x0000, 0x0009, 0x7500 },
+};
+
+#define MPX_NUM_MODES	ARRAY_SIZE(mpx_audio_modes)
+
+static int mpx_setup(struct i2c_client *client)
+{
+	struct sony_tuner *t = i2c_get_clientdata(client);
+	u16 source = 0;
+	u8 buffer[3];
+	struct i2c_msg msg;
+
+	/* reset MPX */
+	buffer[0] = 0x00;
+	buffer[1] = 0x80;
+	buffer[2] = 0x00;
+	msg.addr = MPX_I2C_ADDR;
+	msg.flags = 0;
+	msg.len = 3;
+	msg.buf = buffer;
+	i2c_transfer(client->adapter, &msg, 1);
+	buffer[1] = 0x00;
+	i2c_transfer(client->adapter, &msg, 1);
+
+	if (mpx_audio_modes[t->mpxmode].audio_mode != AUD_MONO) {
+		switch (t->audmode) {
+		case V4L2_TUNER_MODE_MONO:
+			switch (mpx_audio_modes[t->mpxmode].audio_mode) {
+			case AUD_A2:
+				source = mpx_audio_modes[t->mpxmode].source;
+				break;
+			case AUD_NICAM:
+				source = 0x0000;
+				break;
+			case AUD_NICAM_L:
+				source = 0x0200;
+				break;
+			default:
+				break;
+			}
+			break;
+		case V4L2_TUNER_MODE_STEREO:
+			source = mpx_audio_modes[t->mpxmode].source;
+			break;
+		case V4L2_TUNER_MODE_LANG1:
+			source = 0x0300;
+			break;
+		case V4L2_TUNER_MODE_LANG2:
+			source = 0x0400;
+			break;
+		}
+		source |= mpx_audio_modes[t->mpxmode].source & 0x00ff;
+	} else
+		source = mpx_audio_modes[t->mpxmode].source;
+
+	mpx_write(client, 0x10, 0x0030, mpx_audio_modes[t->mpxmode].modus);
+	mpx_write(client, 0x12, 0x0008, source);
+	mpx_write(client, 0x12, 0x0013, mpx_audio_modes[t->mpxmode].acb);
+	mpx_write(client, 0x12, 0x000e,
+			mpx_audio_modes[t->mpxmode].fm_prescale);
+	mpx_write(client, 0x12, 0x0010,
+			mpx_audio_modes[t->mpxmode].nicam_prescale);
+	mpx_write(client, 0x12, 0x000d,
+			mpx_audio_modes[t->mpxmode].scart_prescale);
+	mpx_write(client, 0x10, 0x0020, mpx_audio_modes[t->mpxmode].system);
+	mpx_write(client, 0x12, 0x0000, mpx_audio_modes[t->mpxmode].volume);
+	if (mpx_audio_modes[t->mpxmode].audio_mode == AUD_A2)
+		mpx_write(client, 0x10, 0x0022,
+			t->audmode == V4L2_TUNER_MODE_MONO ?  0x07f0 : 0x0190);
+
+#ifdef MPX_DEBUG
+	{
+		u8 buf1[3], buf2[2];
+		struct i2c_msg msgs[2];
+
+		v4l2_info(client, "MPX registers: %04x %04x "
+				"%04x %04x %04x %04x %04x %04x\n",
+				mpx_audio_modes[t->mpxmode].modus,
+				source,
+				mpx_audio_modes[t->mpxmode].acb,
+				mpx_audio_modes[t->mpxmode].fm_prescale,
+				mpx_audio_modes[t->mpxmode].nicam_prescale,
+				mpx_audio_modes[t->mpxmode].scart_prescale,
+				mpx_audio_modes[t->mpxmode].system,
+				mpx_audio_modes[t->mpxmode].volume);
+		buf1[0] = 0x11;
+		buf1[1] = 0x00;
+		buf1[2] = 0x7e;
+		msgs[0].addr = MPX_I2C_ADDR;
+		msgs[0].flags = 0;
+		msgs[0].len = 3;
+		msgs[0].buf = buf1;
+		msgs[1].addr = MPX_I2C_ADDR;
+		msgs[1].flags = I2C_M_RD;
+		msgs[1].len = 2;
+		msgs[1].buf = buf2;
+		i2c_transfer(client->adapter, msgs, 2);
+		v4l2_info(client, "MPX system: %02x%02x\n",
+				buf2[0], buf2[1]);
+		buf1[0] = 0x11;
+		buf1[1] = 0x02;
+		buf1[2] = 0x00;
+		i2c_transfer(client->adapter, msgs, 2);
+		v4l2_info(client, "MPX status: %02x%02x\n",
+				buf2[0], buf2[1]);
+	}
+#endif
+	return 0;
+}
+
+/*
+ * IF configuration values for the BTF-PG472Z:
+ *
+ *	B/G: 0x94 0x70 0x49
+ *	I:   0x14 0x70 0x4a
+ *	D/K: 0x14 0x70 0x4b
+ *	L:   0x04 0x70 0x4b
+ *	L':  0x44 0x70 0x53
+ *	M:   0x50 0x30 0x4c
+ */
+
+static int set_if(struct i2c_client *client)
+{
+	struct sony_tuner *t = i2c_get_clientdata(client);
+	u8 buffer[4];
+	struct i2c_msg msg;
+	int default_mpx_mode = 0;
+
+	/* configure IF */
+	buffer[0] = 0;
+	if (t->std & V4L2_STD_PAL_BG) {
+		buffer[1] = 0x94;
+		buffer[2] = 0x70;
+		buffer[3] = 0x49;
+		default_mpx_mode = 1;
+	} else if (t->std & V4L2_STD_PAL_I) {
+		buffer[1] = 0x14;
+		buffer[2] = 0x70;
+		buffer[3] = 0x4a;
+		default_mpx_mode = 4;
+	} else if (t->std & V4L2_STD_PAL_DK) {
+		buffer[1] = 0x14;
+		buffer[2] = 0x70;
+		buffer[3] = 0x4b;
+		default_mpx_mode = 6;
+	} else if (t->std & V4L2_STD_SECAM_L) {
+		buffer[1] = 0x04;
+		buffer[2] = 0x70;
+		buffer[3] = 0x4b;
+		default_mpx_mode = 11;
+	}
+	msg.addr = IF_I2C_ADDR;
+	msg.flags = 0;
+	msg.len = 4;
+	msg.buf = buffer;
+	i2c_transfer(client->adapter, &msg, 1);
+
+	/* Select MPX mode if not forced by the user */
+	if (force_mpx_mode >= 0 && force_mpx_mode < MPX_NUM_MODES)
+		t->mpxmode = force_mpx_mode;
+	else
+		t->mpxmode = default_mpx_mode;
+	v4l2_dbg(1, debug, client, "setting MPX to mode %d\n", t->mpxmode);
+	mpx_setup(client);
+
+	return 0;
+}
+
+static int sony_tuner_s_type_addr(struct v4l2_subdev *sd,
+				      struct tuner_setup *setup)
+{
+	struct sony_tuner *t = to_state(sd);
+
+	if (!(setup->mode_mask & T_ANALOG_TV))
+		return 0;
+
+	if (t->type >= 0) {
+		if (t->type != setup->type)
+			v4l2_dbg(1, debug, sd, "type already set to %d, "
+				"ignoring request for %d\n",
+				t->type, setup->type);
+		return 0;
+	}
+	switch (setup->type) {
+	case TUNER_SONY_BTF_PG472Z:
+		switch (force_band_str[0]) {
+		case 'b':
+		case 'B':
+		case 'g':
+		case 'G':
+			v4l2_dbg(1, debug, sd, "forcing tuner to PAL-B/G bands\n");
+			force_band = V4L2_STD_PAL_BG;
+			break;
+		case 'i':
+		case 'I':
+			v4l2_dbg(1, debug, sd, "forcing tuner to PAL-I band\n");
+			force_band = V4L2_STD_PAL_I;
+			break;
+		case 'd':
+		case 'D':
+		case 'k':
+		case 'K':
+			v4l2_dbg(1, debug, sd, "forcing tuner to PAL-D/K bands\n");
+			force_band = V4L2_STD_PAL_I;
+			break;
+		case 'l':
+		case 'L':
+			v4l2_dbg(1, debug, sd, "forcing tuner to SECAM-L band\n");
+			force_band = V4L2_STD_SECAM_L;
+			break;
+		default:
+			force_band = 0;
+			break;
+		}
+		if (force_band)
+			t->std = force_band;
+		else
+			t->std = V4L2_STD_PAL_BG;
+		set_if(v4l2_get_subdevdata(sd));
+		break;
+	case TUNER_SONY_BTF_PK467Z:
+		t->std = V4L2_STD_NTSC_M_JP;
+		break;
+	case TUNER_SONY_BTF_PB463Z:
+		t->std = V4L2_STD_NTSC_M;
+		break;
+	default:
+		v4l2_dbg(1, debug, sd,
+			"tuner type %d is not supported by this module\n",
+			setup->type);
+		return -EINVAL;
+	}
+	t->type = setup->type;
+	if (t->type >= 0)
+		v4l2_dbg(1, debug, sd, "type set to %d (%s)\n",
+			t->type,
+			sony_tuners[t->type - TUNER_SONY_BTF_PG472Z].name);
+	return 0;
+}
+
+static int sony_tuner_g_frequency(struct v4l2_subdev *sd,
+				      struct v4l2_frequency *freq)
+{
+	struct sony_tuner *t = to_state(sd);
+
+	if (freq->type != V4L2_TUNER_ANALOG_TV)
+		return -EINVAL;
+
+	freq->frequency = t->freq;
+	return 0;
+}
+
+static int sony_tuner_s_frequency(struct v4l2_subdev *sd,
+				      struct v4l2_frequency *freq)
+{
+	struct sony_tuner *t = to_state(sd);
+
+	if (freq->type != V4L2_TUNER_ANALOG_TV)
+		return -EINVAL;
+
+	t->freq = freq->frequency;
+	set_freq(v4l2_get_subdevdata(sd), t->freq);
+	return 0;
+}
+
+
+
+static int sony_tuner_s_std(struct v4l2_subdev *sd, v4l2_std_id norm)
+{
+	struct sony_tuner *t = to_state(sd);
+	v4l2_std_id old = t->std;
+
+	switch (t->type) {
+	case TUNER_SONY_BTF_PG472Z:
+		if (force_band && (norm & force_band) != norm &&
+				norm != V4L2_STD_PAL &&
+				norm != V4L2_STD_SECAM) {
+			v4l2_dbg(1, debug, sd, "ignoring requested TV standard in "
+					"favor of force_band value\n");
+			t->std = force_band;
+		} else if (norm & V4L2_STD_PAL_BG) { /* default */
+			t->std = V4L2_STD_PAL_BG;
+		} else if (norm & V4L2_STD_PAL_I) {
+			t->std = V4L2_STD_PAL_I;
+		} else if (norm & V4L2_STD_PAL_DK) {
+			t->std = V4L2_STD_PAL_DK;
+		} else if (norm & V4L2_STD_SECAM_L) {
+			t->std = V4L2_STD_SECAM_L;
+		} else {
+			v4l2_dbg(1, debug, sd, "TV standard not supported\n");
+			return -EINVAL;
+		}
+		if (old != t->std)
+			set_if(v4l2_get_subdevdata(sd));
+		break;
+	case TUNER_SONY_BTF_PK467Z:
+		if (!(norm & V4L2_STD_NTSC_M_JP)) {
+			v4l2_dbg(1, debug, sd, "TV standard not supported\n");
+			return -EINVAL;
+		}
+		break;
+	case TUNER_SONY_BTF_PB463Z:
+		if (!(norm & V4L2_STD_NTSC_M)) {
+			v4l2_dbg(1, debug, sd, "TV standard not supported\n");
+			return -EINVAL;
+		}
+		break;
+	}
+	return 0;
+}
+
+static int sony_tuner_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt)
+{
+	struct sony_tuner *t = to_state(sd);
+
+	strlcpy(vt->name, "Television", sizeof(vt->name));
+	vt->type = V4L2_TUNER_ANALOG_TV;
+	vt->rangelow = 16;		/* FIXME */
+	vt->rangehigh = 16 * 999.99;	/* FIXME */
+	switch (t->type) {
+	case TUNER_SONY_BTF_PG472Z:
+		vt->capability = V4L2_TUNER_CAP_NORM |
+			V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LANG1 |
+			V4L2_TUNER_CAP_LANG2;
+		vt->rxsubchans = V4L2_TUNER_SUB_MONO |
+			V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_LANG1 |
+			V4L2_TUNER_SUB_LANG2;
+		break;
+	case TUNER_SONY_BTF_PK467Z:
+	case TUNER_SONY_BTF_PB463Z:
+		vt->capability = V4L2_TUNER_CAP_STEREO;
+		vt->rxsubchans = V4L2_TUNER_SUB_MONO |
+					V4L2_TUNER_SUB_STEREO;
+		break;
+	}
+	vt->audmode = t->audmode;
+	return 0;
+}
+
+static int sony_tuner_s_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt)
+{
+	struct sony_tuner *t = to_state(sd);
+
+	if (vt->type != V4L2_TUNER_ANALOG_TV)
+		return -EINVAL;
+
+	switch (t->type) {
+	case TUNER_SONY_BTF_PG472Z:
+		if (vt->audmode != t->audmode) {
+			t->audmode = vt->audmode;
+			mpx_setup(v4l2_get_subdevdata(sd));
+		}
+		break;
+	case TUNER_SONY_BTF_PK467Z:
+	case TUNER_SONY_BTF_PB463Z:
+		break;
+	}
+	return 0;
+}
+
+static int sony_tuner_log_status(struct v4l2_subdev *sd)
+{
+	struct sony_tuner *t = to_state(sd);
+
+	v4l2_info(sd, "Standard: %s\n", t->std == V4L2_STD_NTSC ? "NTSC" :
+					t->std == V4L2_STD_PAL ? "PAL" :
+					t->std == V4L2_STD_SECAM ? "SECAM" :
+					"unknown");
+	v4l2_info(sd, "Frequency: %ud\n", t->freq);
+	return 0;
+}
+
+/* --------------------------------------------------------------------------*/
+
+static const struct v4l2_subdev_core_ops sony_tuner_core_ops = {
+	.log_status = sony_tuner_log_status,
+	.s_std = sony_tuner_s_std,
+};
+
+static const struct v4l2_subdev_tuner_ops sony_tuner_tuner_ops = {
+	.s_frequency = sony_tuner_s_frequency,
+	.g_frequency = sony_tuner_g_frequency,
+	.s_tuner = sony_tuner_s_tuner,
+	.g_tuner = sony_tuner_g_tuner,
+	.s_type_addr = sony_tuner_s_type_addr,
+};
+
+static const struct v4l2_subdev_ops sony_tuner_ops = {
+	.core = &sony_tuner_core_ops,
+	.tuner = &sony_tuner_tuner_ops,
+};
+
+/* --------------------------------------------------------------------------*/
+
+static int sony_tuner_probe(struct i2c_client *client,
+				const struct i2c_device_id *id)
+{
+	struct sony_tuner *t;
+	struct v4l2_subdev *sd;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK))
+		return -ENODEV;
+
+	v4l2_info(client, "initializing Sony TV Tuner at address 0x%x on %s\n",
+		client->addr << 1, client->adapter->name);
+
+	t = kzalloc(sizeof(struct sony_tuner), GFP_KERNEL);
+	if (t == NULL)
+		return -ENOMEM;
+
+	sd = &t->sd;
+	v4l2_i2c_subdev_init(sd, client, &sony_tuner_ops);
+
+	/* Initialize sony_tuner */
+	t->type = -1;
+	t->freq = 0;
+	t->mpxmode = 0;
+	t->audmode = V4L2_TUNER_MODE_STEREO;
+
+	return 0;
+}
+
+static int sony_tuner_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+
+	v4l2_device_unregister_subdev(sd);
+	kfree(to_state(sd));
+
+	return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26)
+static const struct i2c_device_id sony_tuner_id[] = {
+	{ "sony_tuner", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, sony_tuner_id);
+#endif
+
+static struct v4l2_i2c_driver_data v4l2_i2c_data = {
+	.name = "sony_tuner",
+	.probe = sony_tuner_probe,
+	.remove = sony_tuner_remove,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26)
+	.id_table = sony_tuner_id,
+#endif
+};
diff -r 27ee8e515b18 -r 1cf1bf4e616f linux/drivers/media/common/tuners/tuner-types.c
--- a/linux/drivers/media/common/tuners/tuner-types.c	Wed Feb 10 11:25:59 2010 -0800
+++ b/linux/drivers/media/common/tuners/tuner-types.c	Fri Feb 12 14:27:39 2010 -0800
@@ -1806,6 +1806,18 @@ 
 		.name   = "NXP TDA18271",
 		/* see tda18271-fe.c for details */
 	},
+	[TUNER_SONY_BTF_PG472Z] = {
+		.name   = "Sony PAL+SECAM (BTF-PG472Z)",
+		/* see sony-tuner.c for details */
+	},
+	[TUNER_SONY_BTF_PK467Z] = {
+		.name   = "Sony NTSC_JP (BTF-PK467Z)",
+		/* see sony-tuner.c for details */
+	},
+	[TUNER_SONY_BTF_PB463Z] = {
+		.name   = "Sony NTSC (BTF-PB463Z)",
+		/* see sony-tuner.c for details */
+	},
 };
 EXPORT_SYMBOL(tuners);
 
diff -r 27ee8e515b18 -r 1cf1bf4e616f linux/drivers/staging/go7007/go7007-driver.c
--- a/linux/drivers/staging/go7007/go7007-driver.c	Wed Feb 10 11:25:59 2010 -0800
+++ b/linux/drivers/staging/go7007/go7007-driver.c	Fri Feb 12 14:27:39 2010 -0800
@@ -243,6 +243,16 @@ 
 			init_i2c_module(&go->i2c_adapter,
 					go->board_info->i2c_devs[i].type,
 					go->board_info->i2c_devs[i].addr);
+
+		if (go->tuner_type >= 0) {
+			struct tuner_setup setup = {
+				.addr = ADDR_UNSET,
+				.type = go->tuner_type,
+				.mode_mask = T_ANALOG_TV,
+			};
+			v4l2_device_call_all(&go->v4l2_dev, 0, tuner,
+				s_type_addr, &setup);
+		}
 		if (go->board_id == GO7007_BOARDID_ADLINK_MPG24) {
 			int channel = go->channel_number;
 			struct v4l2_priv_tun_config config = {
diff -r 27ee8e515b18 -r 1cf1bf4e616f linux/drivers/staging/go7007/go7007-priv.h
--- a/linux/drivers/staging/go7007/go7007-priv.h	Wed Feb 10 11:25:59 2010 -0800
+++ b/linux/drivers/staging/go7007/go7007-priv.h	Fri Feb 12 14:27:39 2010 -0800
@@ -292,9 +292,3 @@ 
 /* We re-use the I2C_M_TEN value so the flag passes through the masks in the
  * core I2C code.  Major kludge, but the I2C layer ain't exactly flexible. */
 #define	I2C_CLIENT_SCCB			0x10
-
-/* Sony tuner types */
-
-#define TUNER_SONY_BTF_PG472Z		200
-#define TUNER_SONY_BTF_PK467Z		201
-#define TUNER_SONY_BTF_PB463Z		202
diff -r 27ee8e515b18 -r 1cf1bf4e616f linux/drivers/staging/go7007/go7007-usb.c
--- a/linux/drivers/staging/go7007/go7007-usb.c	Wed Feb 10 11:25:59 2010 -0800
+++ b/linux/drivers/staging/go7007/go7007-usb.c	Fri Feb 12 14:27:39 2010 -0800
@@ -27,6 +27,7 @@ 
 #include <linux/i2c.h>
 #include <asm/byteorder.h>
 #include <media/tvaudio.h>
+#include <media/tuner.h>
 
 #include "go7007-priv.h"
 
@@ -217,7 +218,7 @@ 
 				.addr	= 0x1a,
 			},
 			{
-				.type	= "wis_sony_tuner",
+				.type	= "sony_tuner",
 				.addr	= 0x60,
 			},
 		},
diff -r 27ee8e515b18 -r 1cf1bf4e616f linux/include/media/tuner.h
--- a/linux/include/media/tuner.h	Wed Feb 10 11:25:59 2010 -0800
+++ b/linux/include/media/tuner.h	Fri Feb 12 14:27:39 2010 -0800
@@ -130,6 +130,10 @@ 
 #define TUNER_PHILIPS_CU1216L           82
 #define TUNER_NXP_TDA18271		83
 
+#define TUNER_SONY_BTF_PG472Z		84	/* PAL+SECAM */
+#define TUNER_SONY_BTF_PK467Z		85	/* NTSC_JP */
+#define TUNER_SONY_BTF_PB463Z		86	/* NTSC */
+
 /* tv card specific */
 #define TDA9887_PRESENT 		(1<<0)
 #define TDA9887_PORT1_INACTIVE 		(1<<1)