summaryrefslogtreecommitdiff
path: root/drivers/media/common
diff options
context:
space:
mode:
authorDevin Heitmueller <dheitmueller@kernellabs.com>2009-07-25 17:39:54 -0300
committerMauro Carvalho Chehab <mchehab@redhat.com>2011-07-27 17:52:29 -0300
commitd0962382cf6ca55e5a33413b39a10fe2f56bae78 (patch)
tree025b9110d48396b0059e2c1cb7fdf7d7e4725679 /drivers/media/common
parente7490d5962ad7959c0597cd55c97ff7eae3acf82 (diff)
[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>
Diffstat (limited to 'drivers/media/common')
-rw-r--r--drivers/media/common/tuners/xc4000.c341
1 files changed, 332 insertions, 9 deletions
diff --git a/drivers/media/common/tuners/xc4000.c b/drivers/media/common/tuners/xc4000.c
index 6d5ab599e83a..19973887312a 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 @@ static LIST_HEAD(hybrid_tuner_instance_list);
#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 @@ struct xc4000_priv {
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 @@ static int xc4000_readreg(struct xc4000_priv *priv, u16 reg, u16 *val)
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 @@ static int seek_firmware(struct dvb_frontend *fe, unsigned int type,
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 @@ found:
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 @@ static int load_firmware(struct dvb_frontend *fe, unsigned int type,
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 @@ static int xc4000_fwupload(struct dvb_frontend *fe)
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 @@ done:
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 @@ struct dvb_frontend *xc4000_attach(struct dvb_frontend *fe,
/* 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 @@ struct dvb_frontend *xc4000_attach(struct dvb_frontend *fe,
}
/* 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;