diff --git a/drivers/media/video/tuner-xc2028-types.h b/drivers/media/video/tuner-xc2028-types.h
index a9e2e0562d99f019c028a353994134fd0a64580d..6cee48193c471ae282464e4864def25070084eeb 100644
--- a/drivers/media/video/tuner-xc2028-types.h
+++ b/drivers/media/video/tuner-xc2028-types.h
@@ -8,6 +8,7 @@
 
 /* BASE firmware should be loaded before any other firmware */
 #define BASE		(1<<0)
+#define BASE_TYPES	(BASE|F8MHZ|MTS|FM|INPUT1|INPUT2|INIT1)
 
 /* F8MHZ marks BASE firmwares for 8 MHz Bandwidth */
 #define F8MHZ		(1<<1)
@@ -37,6 +38,8 @@
 #define DTV78		(1<<8)
 #define DTV8		(1<<9)
 
+#define DTV_TYPES	(D2620|D2633|DTV6|QAM|DTV7|DTV78|DTV8|ATSC)
+
 /* There's a FM | BASE firmware + FM specific firmware (std=0) */
 #define	FM		(1<<10)
 
@@ -60,6 +63,7 @@
 /* Old firmwares were broken into init0 and init1 */
 #define INIT1		(1<<14)
 
+/* SCODE firmware selects particular behaviours */
 #define MONO           (1 << 15)
 #define ATSC           (1 << 16)
 #define IF             (1 << 17)
@@ -76,6 +80,10 @@
 #define INPUT2         (1 << 28)
 #define SCODE          (1 << 29)
 
+#define SCODE_TYPES	(MTS|DTV6|QAM|DTV7|DTV78|DTV8|LCD|NOGD|MONO|ATSC|IF| \
+			 LG60|ATI638|OREN538|OREN36|TOYOTA388|TOYOTA794|     \
+			 DIBCOM52|ZARLINK456|CHINA|F6MHZ|SCODE)
+
 /* Newer types to be moved to videodev2.h */
 
 #define V4L2_STD_SECAM_K3	(0x04000000)
diff --git a/drivers/media/video/tuner-xc2028.c b/drivers/media/video/tuner-xc2028.c
index a5efd5f6e57c73390952d57683d66a5187a07627..8140d8ad0792b3b50cc8523ce2fd628db1c9c04c 100644
--- a/drivers/media/video/tuner-xc2028.c
+++ b/drivers/media/video/tuner-xc2028.c
@@ -54,6 +54,14 @@ struct firmware_description {
 	unsigned int  size;
 };
 
+struct firmware_properties {
+	unsigned int	type;
+	v4l2_std_id	id;
+	v4l2_std_id	std_req;
+	unsigned int	scode_table;
+	int 		scode_nr;
+};
+
 struct xc2028_data {
 	struct list_head        xc2028_list;
 	struct tuner_i2c_props  i2c_props;
@@ -69,14 +77,7 @@ struct xc2028_data {
 
 	struct xc2028_ctrl	ctrl;
 
-	v4l2_std_id		firm_type;	   /* video stds supported
-							by current firmware */
-	fe_bandwidth_t		bandwidth;	   /* Firmware bandwidth:
-							      6M, 7M or 8M */
-	int			need_load_generic; /* The generic firmware
-							      were loaded? */
-	enum tuner_mode	mode;
-	struct i2c_client	*i2c_client;
+	struct firmware_properties cur_fw;
 
 	struct mutex lock;
 };
@@ -234,7 +235,8 @@ static void free_firmware(struct xc2028_data *priv)
 
 	priv->firm = NULL;
 	priv->firm_size = 0;
-	priv->need_load_generic = 1;
+
+	memset(&priv->cur_fw, 0, sizeof(priv->cur_fw));
 }
 
 static int load_all_firmwares(struct dvb_frontend *fe)
@@ -393,6 +395,13 @@ static int seek_firmware(struct dvb_frontend *fe, unsigned int type,
 	if (((type & ~SCODE) == 0) && (*id == 0))
 		*id = V4L2_STD_PAL;
 
+	if (type & BASE)
+		type &= BASE_TYPES;
+	else if (type & SCODE)
+		type &= SCODE_TYPES;
+	else if (type & DTV_TYPES)
+		type = type & DTV_TYPES;
+
 	/* Seek for exact match */
 	for (i = 0; i < priv->firm_size; i++) {
 		if ((type == priv->firm[i].type) && (*id == priv->firm[i].id))
@@ -598,11 +607,10 @@ static int check_firmware(struct dvb_frontend *fe, enum tuner_mode new_mode,
 			  v4l2_std_id std, fe_bandwidth_t bandwidth)
 {
 	struct xc2028_data      *priv = fe->tuner_priv;
-	int			rc;
+	int			rc = 0;
+	unsigned int		type = 0;
+	struct firmware_properties new_fw;
 	u16			version, hwmodel;
-	v4l2_std_id		std0 = 0;
-	unsigned int		type0 = 0, type = 0;
-	int			change_digital_bandwidth;
 
 	tuner_dbg("%s called\n", __FUNCTION__);
 
@@ -617,61 +625,19 @@ static int check_firmware(struct dvb_frontend *fe, enum tuner_mode new_mode,
 			return rc;
 	}
 
-	tuner_dbg("I am in mode %u and I should switch to mode %i\n",
-		   priv->mode, new_mode);
-
-	/* first of all, determine whether we have switched the mode */
-	if (new_mode != priv->mode) {
-		priv->mode = new_mode;
-		priv->need_load_generic = 1;
-	}
-
-	change_digital_bandwidth = (priv->mode == T_DIGITAL_TV
-				    && bandwidth != priv->bandwidth) ? 1 : 0;
-	tuner_dbg("old bandwidth %u, new bandwidth %u\n", priv->bandwidth,
-		   bandwidth);
-
-	if (priv->need_load_generic) {
-		/* Reset is needed before loading firmware */
-		rc = priv->tuner_callback(priv->video_dev,
-					  XC2028_TUNER_RESET, 0);
-		if (rc < 0)
-			return rc;
-
-		type0 = BASE;
-
-		if (priv->ctrl.type == XC2028_FIRM_MTS)
-			type0 |= MTS;
-
-		if (bandwidth == BANDWIDTH_7_MHZ ||
-		    bandwidth == BANDWIDTH_8_MHZ)
-			type0 |= F8MHZ;
-
-		/* FIXME: How to load FM and FM|INPUT1 firmwares? */
-
-		rc = load_firmware(fe, type0, &std0);
-		if (rc < 0) {
-			tuner_err("Error %d while loading generic firmware\n",
-				  rc);
-			return rc;
-		}
-
-		priv->need_load_generic = 0;
-		priv->firm_type = 0;
-		if (priv->mode == T_DIGITAL_TV)
-			change_digital_bandwidth = 1;
-	}
+	if (priv->ctrl.type == XC2028_FIRM_MTS)
+		type |= MTS;
+	if (bandwidth == BANDWIDTH_7_MHZ || bandwidth == BANDWIDTH_8_MHZ)
+		type |= F8MHZ;
 
-	tuner_dbg("I should change bandwidth %u\n", change_digital_bandwidth);
+	/* FIXME: How to load FM and FM|INPUT1 firmwares? */
 
-	if (change_digital_bandwidth) {
+	if (new_mode == T_DIGITAL_TV) {
 		if (priv->ctrl.d2633)
 			type |= D2633;
 		else
 			type |= D2620;
 
-		/* FIXME: When should select a DTV78 firmware?
-		 */
 		switch (bandwidth) {
 		case BANDWIDTH_8_MHZ:
 			type |= DTV8;
@@ -683,49 +649,96 @@ static int check_firmware(struct dvb_frontend *fe, enum tuner_mode new_mode,
 			/* FIXME: Should allow select also ATSC */
 			type |= DTV6 | QAM;
 			break;
-
 		default:
 			tuner_err("error: bandwidth not supported.\n");
 		};
-		priv->bandwidth = bandwidth;
 	}
 
-	if (!change_digital_bandwidth && priv->mode == T_DIGITAL_TV)
-		return 0;
+	new_fw.type = type;
+	new_fw.id = std;
+	new_fw.std_req = std;
+	new_fw.scode_table = SCODE | priv->ctrl.scode_table;
+	new_fw.scode_nr = 0;
+
+	tuner_dbg("checking firmware, user requested type=");
+	if (debug) {
+		dump_firm_type(new_fw.type);
+		printk("(%x), id %016llx, scode_tbl ", new_fw.type,
+		       (unsigned long long)new_fw.std_req);
+		dump_firm_type(priv->ctrl.scode_table);
+		printk("(%x), scode_nr %d\n", priv->ctrl.scode_table,
+		       new_fw.scode_nr);
+	}
+
+	/* No need to reload base firmware if it matches */
+	if (((BASE | new_fw.type) & BASE_TYPES) ==
+	    (priv->cur_fw.type & BASE_TYPES)) {
+		tuner_dbg("BASE firmware not changed.\n");
+		goto skip_base;
+	}
+
+	/* Updating BASE - forget about all currently loaded firmware */
+	memset(&priv->cur_fw, 0, sizeof(priv->cur_fw));
+
+	/* Reset is needed before loading firmware */
+	rc = priv->tuner_callback(priv->video_dev,
+				  XC2028_TUNER_RESET, 0);
+	if (rc < 0)
+		goto fail;
+
+	rc = load_firmware(fe, BASE | new_fw.type, &new_fw.id);
+	if (rc < 0) {
+		tuner_err("Error %d while loading base firmware\n",
+			  rc);
+		goto fail;
+	}
 
 	/* Load INIT1, if needed */
 	tuner_dbg("Load init1 firmware, if exists\n");
-	type0 = BASE | INIT1;
-	if (priv->ctrl.type == XC2028_FIRM_MTS)
-		type0 |= MTS;
 
-	/* FIXME: Should handle errors - if INIT1 found */
-	rc = load_firmware(fe, type0, &std0);
+	rc = load_firmware(fe, BASE | INIT1 | new_fw.type, &new_fw.id);
+	if (rc < 0 && rc != -ENOENT) {
+		tuner_err("Error %d while loading init1 firmware\n",
+			  rc);
+		goto fail;
+	}
 
-	/* FIXME: Should add support for FM radio
+skip_base:
+	/*
+	 * No need to reload standard specific firmware if base firmware
+	 * was not reloaded and requested video standards have not changed.
 	 */
-
-	if (priv->ctrl.type == XC2028_FIRM_MTS)
-		type |= MTS;
-
-	if (priv->firm_type & std) {
+	if (priv->cur_fw.type == (BASE | new_fw.type) &&
+	    priv->cur_fw.std_req == std) {
 		tuner_dbg("Std-specific firmware already loaded.\n");
-		return 0;
+		goto skip_std_specific;
 	}
 
+	/* Reloading std-specific firmware forces a SCODE update */
+	priv->cur_fw.scode_table = 0;
+
 	/* Add audio hack to std mask */
-	std |= parse_audio_std_option();
+	if (new_mode == T_ANALOG_TV)
+		new_fw.id |= parse_audio_std_option();
 
-	rc = load_firmware(fe, type, &std);
+	rc = load_firmware(fe, new_fw.type, &new_fw.id);
 	if (rc < 0)
-		return rc;
+		goto fail;
+
+skip_std_specific:
+	if (priv->cur_fw.scode_table == new_fw.scode_table &&
+	    priv->cur_fw.scode_nr == new_fw.scode_nr) {
+		tuner_dbg("SCODE firmware already loaded.\n");
+		goto check_device;
+	}
 
 	/* Load SCODE firmware, if exists */
-	tuner_dbg("Trying to load scode 0\n");
-	type |= SCODE;
+	tuner_dbg("Trying to load scode %d\n", new_fw.scode_nr);
 
-	rc = load_scode(fe, type, &std, 0);
+	rc = load_scode(fe, new_fw.type | new_fw.scode_table,
+			&new_fw.id, new_fw.scode_nr);
 
+check_device:
 	xc2028_get_reg(priv, 0x0004, &version);
 	xc2028_get_reg(priv, 0x0008, &hwmodel);
 
@@ -734,9 +747,23 @@ static int check_firmware(struct dvb_frontend *fe, enum tuner_mode new_mode,
 		   hwmodel, (version & 0xf000) >> 12, (version & 0xf00) >> 8,
 		   (version & 0xf0) >> 4, version & 0xf);
 
-	priv->firm_type = std;
+	memcpy(&priv->cur_fw, &new_fw, sizeof(priv->cur_fw));
+
+	/*
+	 * By setting BASE in cur_fw.type only after successfully loading all
+	 * firmwares, we can:
+	 * 1. Identify that BASE firmware with type=0 has been loaded;
+	 * 2. Tell whether BASE firmware was just changed the next time through.
+	 */
+	priv->cur_fw.type |= BASE;
 
 	return 0;
+
+fail:
+	memset(&priv->cur_fw, 0, sizeof(priv->cur_fw));
+	if (rc == -ENOENT)
+		rc = -EINVAL;
+	return rc;
 }
 
 static int xc2028_signal(struct dvb_frontend *fe, u16 *strength)
@@ -785,16 +812,10 @@ static int generic_set_tv_freq(struct dvb_frontend *fe, u32 freq /* in Hz */ ,
 	mutex_lock(&priv->lock);
 
 	/* HACK: It seems that specific firmware need to be reloaded
-	   when freq is changed */
-
-	priv->firm_type = 0;
-
-	/* Reset GPIO 1 */
-	rc = priv->tuner_callback(priv->video_dev, XC2028_TUNER_RESET, 0);
-	if (rc < 0)
-		goto ret;
+	   when watching analog TV and freq is changed */
+	if (new_mode != T_DIGITAL_TV)
+		priv->cur_fw.type = 0;
 
-	msleep(10);
 	tuner_dbg("should set frequency %d kHz\n", freq / 1000);
 
 	if (check_firmware(fe, new_mode, std, bandwidth) < 0)
@@ -802,7 +823,7 @@ static int generic_set_tv_freq(struct dvb_frontend *fe, u32 freq /* in Hz */ ,
 
 	if (new_mode == T_DIGITAL_TV) {
 		offset = 2750000;
-		if (priv->bandwidth == BANDWIDTH_7_MHZ)
+		if (priv->cur_fw.type & DTV7)
 			offset -= 500000;
 	}
 
@@ -994,9 +1015,6 @@ void *xc2028_attach(struct dvb_frontend *fe, struct xc2028_config *cfg)
 			return NULL;
 		}
 
-		priv->bandwidth = BANDWIDTH_6_MHZ;
-		priv->need_load_generic = 1;
-		priv->mode = T_UNINITIALIZED;
 		priv->i2c_props.addr = cfg->i2c_addr;
 		priv->i2c_props.adap = cfg->i2c_adap;
 		priv->video_dev = video_dev;
diff --git a/drivers/media/video/tuner-xc2028.h b/drivers/media/video/tuner-xc2028.h
index 4edc4b735c84506fc6a3a8f91b3a531eac4178db..16259b14ce904099922d813a445d0f4f601fb782 100644
--- a/drivers/media/video/tuner-xc2028.h
+++ b/drivers/media/video/tuner-xc2028.h
@@ -21,6 +21,7 @@ struct xc2028_ctrl {
 	char			*fname;
 	int			max_len;
 	int			d2633:1;
+	unsigned int		scode_table;
 };
 
 struct xc2028_config {