[media] xc4000: add code to do standard and scode firmware loading

Add code to handle firmware blobs for the standard and scode.  Note there
appears to be some issue with loading the DTV8 standard firmware, probably
related to direct/indirect mode.

Signed-off-by: Devin Heitmueller <dheitmueller@kernellabs.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
diff --git a/drivers/media/common/tuners/xc4000.c b/drivers/media/common/tuners/xc4000.c
index 6d5ab59..1997388 100644
--- a/drivers/media/common/tuners/xc4000.c
+++ b/drivers/media/common/tuners/xc4000.c
@@ -36,7 +36,7 @@
 #include "tuner-i2c.h"
 #include "tuner-xc2028-types.h"
 
-static int debug;
+static int debug=1;
 module_param(debug, int, 0644);
 MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off).");
 
@@ -52,8 +52,8 @@
 #define dprintk(level, fmt, arg...) if (debug >= level) \
 	printk(KERN_INFO "%s: " fmt, "xc4000", ## arg)
 
-#define XC4000_DEFAULT_FIRMWARE "xc4000-01.fw"
-#define XC4000_DEFAULT_FIRMWARE_SIZE 8434
+#define XC4000_DEFAULT_FIRMWARE "xc4000-02.fw"
+#define XC4000_DEFAULT_FIRMWARE_SIZE 18643
 
 
 /* struct for storing firmware table */
@@ -85,6 +85,10 @@
 	u32 bandwidth;
 	u8  video_standard;
 	u8  rf_mode;
+//	struct xc2028_ctrl	ctrl;
+	struct firmware_properties cur_fw;
+	__u16			hwmodel;
+	__u16			hwvers;
 };
 
 /* Misc Defines */
@@ -568,6 +572,73 @@
 	return XC_RESULT_SUCCESS;
 }
 
+#define dump_firm_type(t) 	dump_firm_type_and_int_freq(t, 0)
+static void dump_firm_type_and_int_freq(unsigned int type, u16 int_freq)
+{
+	 if (type & BASE)
+		printk("BASE ");
+	 if (type & INIT1)
+		printk("INIT1 ");
+	 if (type & F8MHZ)
+		printk("F8MHZ ");
+	 if (type & MTS)
+		printk("MTS ");
+	 if (type & D2620)
+		printk("D2620 ");
+	 if (type & D2633)
+		printk("D2633 ");
+	 if (type & DTV6)
+		printk("DTV6 ");
+	 if (type & QAM)
+		printk("QAM ");
+	 if (type & DTV7)
+		printk("DTV7 ");
+	 if (type & DTV78)
+		printk("DTV78 ");
+	 if (type & DTV8)
+		printk("DTV8 ");
+	 if (type & FM)
+		printk("FM ");
+	 if (type & INPUT1)
+		printk("INPUT1 ");
+	 if (type & LCD)
+		printk("LCD ");
+	 if (type & NOGD)
+		printk("NOGD ");
+	 if (type & MONO)
+		printk("MONO ");
+	 if (type & ATSC)
+		printk("ATSC ");
+	 if (type & IF)
+		printk("IF ");
+	 if (type & LG60)
+		printk("LG60 ");
+	 if (type & ATI638)
+		printk("ATI638 ");
+	 if (type & OREN538)
+		printk("OREN538 ");
+	 if (type & OREN36)
+		printk("OREN36 ");
+	 if (type & TOYOTA388)
+		printk("TOYOTA388 ");
+	 if (type & TOYOTA794)
+		printk("TOYOTA794 ");
+	 if (type & DIBCOM52)
+		printk("DIBCOM52 ");
+	 if (type & ZARLINK456)
+		printk("ZARLINK456 ");
+	 if (type & CHINA)
+		printk("CHINA ");
+	 if (type & F6MHZ)
+		printk("F6MHZ ");
+	 if (type & INPUT2)
+		printk("INPUT2 ");
+	 if (type & SCODE)
+		printk("SCODE ");
+	 if (type & HAS_IF)
+		printk("HAS_IF_%d ", int_freq);
+}
+
 static int seek_firmware(struct dvb_frontend *fe, unsigned int type,
 			 v4l2_std_id *id)
 {
@@ -577,7 +648,7 @@
 
 	printk("%s called, want type=", __func__);
 	if (debug) {
-//		dump_firm_type(type);
+		dump_firm_type(type);
 		printk("(%x), id %016llx.\n", type, (unsigned long long)*id);
 	}
 
@@ -653,8 +724,10 @@
 ret:
 	printk("%s firmware for type=", (i < 0) ? "Can't find" : "Found");
 	if (debug) {
-//		dump_firm_type(type);
+		dump_firm_type(type);
 		printk("(%x), id %016llx.\n", type, (unsigned long long)*id);
+		if (i < 0)
+			dump_stack();
 	}
 	return i;
 }
@@ -680,7 +753,6 @@
 	p = priv->firm[pos].ptr;
 
 	rc = xc_load_i2c_sequence(fe, p);
-	printk("load i2c sequence result=%d\n", rc);
 
 	return rc;
 }
@@ -791,9 +863,10 @@
 			rc = -ENOMEM;
 			goto err;
 		}
-		printk("Reading firmware type ");
+
 		if (debug) {
-//			dump_firm_type_and_int_freq(type, int_freq);
+			printk("Reading firmware type ");
+			dump_firm_type_and_int_freq(type, int_freq);
 			printk("(%x), id %llx, size=%d.\n",
 			       type, (unsigned long long)id, size);
 		}
@@ -832,6 +905,253 @@
 	return rc;
 }
 
+static int load_scode(struct dvb_frontend *fe, unsigned int type,
+			 v4l2_std_id *id, __u16 int_freq, int scode)
+{
+	struct xc4000_priv *priv = fe->tuner_priv;
+	int                pos, rc;
+	unsigned char	   *p;
+	u8 direct_mode[4];
+	u8 indirect_mode[5];
+
+	dprintk(1, "%s called\n", __func__);
+
+	if (!int_freq) {
+		pos = seek_firmware(fe, type, id);
+		if (pos < 0)
+			return pos;
+	} else {
+		for (pos = 0; pos < priv->firm_size; pos++) {
+			if ((priv->firm[pos].int_freq == int_freq) &&
+			    (priv->firm[pos].type & HAS_IF))
+				break;
+		}
+		if (pos == priv->firm_size)
+			return -ENOENT;
+	}
+
+	p = priv->firm[pos].ptr;
+
+	if (priv->firm[pos].type & HAS_IF) {
+		if (priv->firm[pos].size != 12 * 16 || scode >= 16)
+			return -EINVAL;
+		p += 12 * scode;
+	} else {
+		/* 16 SCODE entries per file; each SCODE entry is 12 bytes and
+		 * has a 2-byte size header in the firmware format. */
+		if (priv->firm[pos].size != 14 * 16 || scode >= 16 ||
+		    le16_to_cpu(*(__u16 *)(p + 14 * scode)) != 12)
+			return -EINVAL;
+		p += 14 * scode + 2;
+	}
+
+	tuner_info("Loading SCODE for type=");
+	dump_firm_type_and_int_freq(priv->firm[pos].type,
+				    priv->firm[pos].int_freq);
+	printk("(%x), id %016llx.\n", priv->firm[pos].type,
+	       (unsigned long long)*id);
+
+
+	/* Enter direct-mode */
+	memset(direct_mode, 0, sizeof(direct_mode));
+	direct_mode[1] = 0x05;
+	rc = xc_send_i2c_data(priv, direct_mode, sizeof(direct_mode));
+	if (rc < 0)
+		return -EIO;
+
+	rc = xc_send_i2c_data(priv, p, 12);
+	if (rc != XC_RESULT_SUCCESS)
+		return -EIO;
+
+	/* Switch back to indirect-mode */
+	memset(indirect_mode, 0, sizeof(indirect_mode));
+	indirect_mode[4] = 0x88;
+	rc = xc_send_i2c_data(priv, indirect_mode, sizeof(indirect_mode));
+	if (rc < 0)
+		return -EIO;
+
+	return 0;
+}
+
+static int check_firmware(struct dvb_frontend *fe, unsigned int type,
+			  v4l2_std_id std, __u16 int_freq)
+{
+	struct xc4000_priv         *priv = fe->tuner_priv;
+	struct firmware_properties new_fw;
+	int			   rc = 0, is_retry = 0;
+	u16			   version, hwmodel;
+	v4l2_std_id		   std0;
+	u8 		   	   hw_major, hw_minor, fw_major, fw_minor;
+
+	dprintk(1, "%s called\n", __func__);
+
+	if (!priv->firm) {
+		rc = xc4000_fwupload(fe);
+		if (rc < 0)
+			return rc;
+	}
+
+#ifdef DJH_DEBUG
+	if (priv->ctrl.mts && !(type & FM))
+		type |= MTS;
+#endif
+
+retry:
+	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_table = SCODE;
+	new_fw.scode_nr = 0;
+	new_fw.int_freq = int_freq;
+
+	dprintk(1, "checking firmware, user requested type=");
+	if (debug) {
+		dump_firm_type(new_fw.type);
+		printk("(%x), id %016llx, ", new_fw.type,
+		       (unsigned long long)new_fw.std_req);
+		if (!int_freq) {
+			printk("scode_tbl ");
+#ifdef DJH_DEBUG
+			dump_firm_type(priv->ctrl.scode_table);
+			printk("(%x), ", priv->ctrl.scode_table);
+#endif
+		} else
+			printk("int_freq %d, ", new_fw.int_freq);
+		printk("scode_nr %d\n", 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)) {
+		dprintk(1, "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 = xc4000_TunerReset(fe);
+	if (rc < 0)
+		goto fail;
+
+	/* BASE firmwares are all std0 */
+	std0 = 0;
+	rc = load_firmware(fe, BASE | new_fw.type, &std0);
+	if (rc < 0) {
+		printk("Error %d while loading base firmware\n", rc);
+		goto fail;
+	}
+
+	/* Load INIT1, if needed */
+	dprintk(1, "Load init1 firmware, if exists\n");
+
+	rc = load_firmware(fe, BASE | INIT1 | new_fw.type, &std0);
+	if (rc == -ENOENT)
+		rc = load_firmware(fe, (BASE | INIT1 | new_fw.type) & ~F8MHZ,
+				   &std0);
+	if (rc < 0 && rc != -ENOENT) {
+		tuner_err("Error %d while loading init1 firmware\n",
+			  rc);
+		goto fail;
+	}
+
+skip_base:
+	/*
+	 * No need to reload standard specific firmware if base firmware
+	 * was not reloaded and requested video standards have not changed.
+	 */
+	if (priv->cur_fw.type == (BASE | new_fw.type) &&
+	    priv->cur_fw.std_req == std) {
+		dprintk(1, "Std-specific firmware already loaded.\n");
+		goto skip_std_specific;
+	}
+
+	/* Reloading std-specific firmware forces a SCODE update */
+	priv->cur_fw.scode_table = 0;
+
+	rc = load_firmware(fe, new_fw.type, &new_fw.id);
+	if (rc == -ENOENT)
+		rc = load_firmware(fe, new_fw.type & ~F8MHZ, &new_fw.id);
+
+	if (rc < 0)
+		goto fail;
+
+skip_std_specific:
+	if (priv->cur_fw.scode_table == new_fw.scode_table &&
+	    priv->cur_fw.scode_nr == new_fw.scode_nr) {
+		dprintk(1, "SCODE firmware already loaded.\n");
+		goto check_device;
+	}
+
+	if (new_fw.type & FM)
+		goto check_device;
+
+	/* Load SCODE firmware, if exists */
+	dprintk(1, "Trying to load scode %d\n", new_fw.scode_nr);
+
+	rc = load_scode(fe, new_fw.type | new_fw.scode_table, &new_fw.id,
+			new_fw.int_freq, new_fw.scode_nr);
+
+check_device:
+	rc = xc4000_readreg(priv, XREG_PRODUCT_ID, &hwmodel);
+
+	if (xc_get_version(priv, &hw_major, &hw_minor, &fw_major, 
+			   &fw_minor) != XC_RESULT_SUCCESS) {
+		printk("Unable to read tuner registers.\n");
+		goto fail;
+	}
+
+	dprintk(1, "Device is Xceive %d version %d.%d, "
+		"firmware version %d.%d\n",
+		hwmodel, hw_major, hw_minor, fw_major, fw_minor);
+
+	/* Check firmware version against what we downloaded. */
+#ifdef DJH_DEBUG
+	if (priv->firm_version != ((version & 0xf0) << 4 | (version & 0x0f))) {
+		printk("Incorrect readback of firmware version %x.\n",
+		       (version & 0xff));
+		goto fail;
+	}
+#endif
+
+	/* Check that the tuner hardware model remains consistent over time. */
+	if (priv->hwmodel == 0 && hwmodel == 4000) {
+		priv->hwmodel = hwmodel;
+		priv->hwvers  = version & 0xff00;
+	} else if (priv->hwmodel == 0 || priv->hwmodel != hwmodel ||
+		   priv->hwvers != (version & 0xff00)) {
+		printk("Read invalid device hardware information - tuner "
+			  "hung?\n");
+		goto fail;
+	}
+
+	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 (!is_retry) {
+		msleep(50);
+		is_retry = 1;
+		dprintk(1, "Retrying firmware load\n");
+		goto retry;
+	}
+
+	if (rc == -ENOENT)
+		rc = -EINVAL;
+	return rc;
+}
 
 static void xc_debug_dump(struct xc4000_priv *priv)
 {
@@ -1295,6 +1615,7 @@
 
 	/* FIXME: For now, load the firmware at startup.  We will remove this
 	   before the code goes to production... */
+#ifdef DJH_DEBUG
 	xc4000_fwupload(fe);
 	printk("xc4000_fwupload done\n");
 
@@ -1308,11 +1629,13 @@
 	}
 
 	/* Load INIT1, if needed */
-	tuner_dbg("Load init1 firmware, if exists\n");
+	dprintk("Load init1 firmware, if exists\n");
 
 //	rc = load_firmware(fe, BASE | INIT1 | new_fw.type, &std0);
 	rc = load_firmware(fe, BASE | INIT1, &std0);
 	printk("init1 load result %x\n", rc);
+#endif
+	check_firmware(fe, DTV8, 0, 5400);
 
 	if (xc4000_readreg(priv, XREG_PRODUCT_ID, &id) != XC_RESULT_SUCCESS)
 			goto fail;