[media] xc4000: pull in firmware management code from xc3028
authorDevin Heitmueller <dheitmueller@kernellabs.com>
Mon, 20 Jul 2009 03:54:57 +0000 (00:54 -0300)
committerMauro Carvalho Chehab <mchehab@redhat.com>
Wed, 27 Jul 2011 20:52:28 +0000 (17:52 -0300)
Switch over to using the firmware management routines from the tuner-xc2028,
since that has support for scodes, etc.

This code still requires signficant cleanup, and at this point the base
firmware does not load properly (i2c write errors about 300 bytes in).

Signed-off-by: Devin Heitmueller <dheitmueller@kernellabs.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
drivers/media/common/tuners/xc4000.c

index 68f5e2beee1ec5910cf3fd046bdb3a2726a6888c..98ec80abb0477e8d5e2a949b599df8598475f1c7 100644 (file)
 #include <linux/delay.h>
 #include <linux/dvb/frontend.h>
 #include <linux/i2c.h>
+#include <asm/unaligned.h>
 
 #include "dvb_frontend.h"
 
 #include "xc4000.h"
 #include "tuner-i2c.h"
+#include "tuner-xc2028-types.h"
 
 static int debug;
 module_param(debug, int, 0644);
@@ -50,13 +52,34 @@ 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 "dvb-fe-xc4000-1.4.26.fw"
-#define XC4000_DEFAULT_FIRMWARE_SIZE 8236
+#define XC4000_DEFAULT_FIRMWARE "xc4000-01.fw"
+#define XC4000_DEFAULT_FIRMWARE_SIZE 8434
+
+
+/* struct for storing firmware table */
+struct firmware_description {
+       unsigned int  type;
+       v4l2_std_id   id;
+       __u16         int_freq;
+       unsigned char *ptr;
+       unsigned int  size;
+};
+
+struct firmware_properties {
+       unsigned int    type;
+       v4l2_std_id     id;
+       v4l2_std_id     std_req;
+       __u16           int_freq;
+       unsigned int    scode_table;
+       int             scode_nr;
+};
 
 struct xc4000_priv {
        struct tuner_i2c_props i2c_props;
        struct list_head hybrid_tuner_instance_list;
-
+       struct firmware_description *firm;
+       int                     firm_size;
+       __u16                   firm_version;
        u32 if_khz;
        u32 freq_hz;
        u32 bandwidth;
@@ -167,10 +190,6 @@ struct XC_TV_STANDARD {
 #define DK_SECAM_A2MONO        14
 #define L_SECAM_NICAM          15
 #define LC_SECAM_NICAM         16
-#define DTV6                   17
-#define DTV8                   18
-#define DTV7_8                 19
-#define DTV7                   20
 #define FM_Radio_INPUT2        21
 #define FM_Radio_INPUT1        22
 
@@ -575,42 +594,358 @@ static int xc4000_readreg(struct xc4000_priv *priv, u16 reg, u16 *val)
        return XC_RESULT_SUCCESS;
 }
 
+
+static int seek_firmware(struct dvb_frontend *fe, unsigned int type,
+                        v4l2_std_id *id)
+{
+       struct xc4000_priv *priv = fe->tuner_priv;
+       int                 i, best_i = -1, best_nr_matches = 0;
+       unsigned int        type_mask = 0;
+
+       printk("%s called, want type=", __func__);
+       if (debug) {
+//             dump_firm_type(type);
+               printk("(%x), id %016llx.\n", type, (unsigned long long)*id);
+       }
+
+       if (!priv->firm) {
+               printk("Error! firmware not loaded\n");
+               return -EINVAL;
+       }
+
+       if (((type & ~SCODE) == 0) && (*id == 0))
+               *id = V4L2_STD_PAL;
+
+       if (type & BASE)
+               type_mask = BASE_TYPES;
+       else if (type & SCODE) {
+               type &= SCODE_TYPES;
+               type_mask = SCODE_TYPES & ~HAS_IF;
+       } else if (type & DTV_TYPES)
+               type_mask = DTV_TYPES;
+       else if (type & STD_SPECIFIC_TYPES)
+               type_mask = STD_SPECIFIC_TYPES;
+
+       type &= type_mask;
+
+       if (!(type & SCODE))
+               type_mask = ~0;
+
+       /* Seek for exact match */
+       for (i = 0; i < priv->firm_size; i++) {
+               if ((type == (priv->firm[i].type & type_mask)) &&
+                   (*id == priv->firm[i].id))
+                       goto found;
+       }
+
+       /* Seek for generic video standard match */
+       for (i = 0; i < priv->firm_size; i++) {
+               v4l2_std_id match_mask;
+               int nr_matches;
+
+               if (type != (priv->firm[i].type & type_mask))
+                       continue;
+
+               match_mask = *id & priv->firm[i].id;
+               if (!match_mask)
+                       continue;
+
+               if ((*id & match_mask) == *id)
+                       goto found; /* Supports all the requested standards */
+
+               nr_matches = hweight64(match_mask);
+               if (nr_matches > best_nr_matches) {
+                       best_nr_matches = nr_matches;
+                       best_i = i;
+               }
+       }
+
+       if (best_nr_matches > 0) {
+               printk("Selecting best matching firmware (%d bits) for "
+                         "type=", best_nr_matches);
+//             dump_firm_type(type);
+               printk("(%x), id %016llx:\n", type, (unsigned long long)*id);
+               i = best_i;
+               goto found;
+       }
+
+       /*FIXME: Would make sense to seek for type "hint" match ? */
+
+       i = -ENOENT;
+       goto ret;
+
+found:
+       *id = priv->firm[i].id;
+
+ret:
+       printk("%s firmware for type=", (i < 0) ? "Can't find" : "Found");
+       if (debug) {
+//             dump_firm_type(type);
+               printk("(%x), id %016llx.\n", type, (unsigned long long)*id);
+       }
+       return i;
+}
+
+static int load_firmware(struct dvb_frontend *fe, unsigned int type,
+                        v4l2_std_id *id)
+{
+       struct xc4000_priv *priv = fe->tuner_priv;
+       int                pos, rc;
+       unsigned char      *p, *endp, buf[XC_MAX_I2C_WRITE_LENGTH];
+
+       printk("%s called\n", __func__);
+
+       pos = seek_firmware(fe, type, id);
+       if (pos < 0)
+               return pos;
+
+       printk("Loading firmware for type=");
+//     dump_firm_type(priv->firm[pos].type);
+       printk("(%x), id %016llx.\n", priv->firm[pos].type,
+              (unsigned long long)*id);
+
+       p = priv->firm[pos].ptr;
+       endp = p + priv->firm[pos].size;
+
+       while (p < endp) {
+               __u16 size;
+
+               printk("block %02x %02x %02x %02x %02x %02x\n", p[0], p[1], p[2], p[3], p[4], p[5]);
+
+               /* Checks if there's enough bytes to read */
+               if (p + sizeof(size) > endp) {
+                       printk("Firmware chunk size is wrong\n");
+                       return -EINVAL;
+               }
+
+               size = be16_to_cpu(*(__u16 *) p);
+               p += sizeof(size);
+
+               printk("djh size=%x\n", size);
+
+               if (size == 0xffff)
+                       return 0;
+
+               if (!size) {
+                       /* Special callback command received */
+                       rc = xc4000_TunerReset(fe);
+                       if (rc != XC_RESULT_SUCCESS) {
+                               printk("Error at RESET code %d\n",
+                                      (*p) & 0x7f);
+                               return -EINVAL;
+                       }
+                       continue;
+               }
+               if (size >= 0xff00) {
+                       switch (size) {
+#ifdef DJH_XXX
+                       case 0xff00:
+                               rc = do_tuner_callback(fe, XC2028_RESET_CLK, 0);
+                               if (rc < 0) {
+                                       printk("Error at RESET code %d\n",
+                                                 (*p) & 0x7f);
+                                       return -EINVAL;
+                               }
+                               break;
+#endif
+                       default:
+                               printk("Invalid RESET code %d\n",
+                                          size & 0x7f);
+                               return -EINVAL;
+
+                       }
+                       continue;
+               }
+
+               /* Checks for a sleep command */
+               if (size & 0x8000) {
+                       printk("djh doing msleep for %x\n", (size & 0x7fff));
+                       msleep(size & 0x7fff);
+                       continue;
+               }
+
+               if ((size + p > endp)) {
+                       printk("missing bytes: need %d, have %d\n",
+                                  size, (int)(endp - p));
+                       return -EINVAL;
+               }
+
+               buf[0] = *p;
+               p++;
+               size--;
+
+               /* Sends message chunks */
+               printk("djh final size %d\n", size);
+               while (size > 0) {
+                       int len = (size < XC_MAX_I2C_WRITE_LENGTH - 1) ?
+                                  size : XC_MAX_I2C_WRITE_LENGTH - 1;
+
+                       memcpy(buf + 1, p, len);
+
+//                     rc = i2c_send(priv, buf, len + 1);
+                       printk("djh sending %d\n", len + 1);
+                       rc = xc_send_i2c_data(priv, buf, len + 1);
+                       if (rc < 0) {
+                               printk("%d returned from send\n", rc);
+                               return -EINVAL;
+                       }
+
+                       p += len;
+                       size -= len;
+               }
+       }
+       return 0;
+}
+
+//static int load_all_firmwares(struct dvb_frontend *fe)
 static int xc4000_fwupload(struct dvb_frontend *fe)
 {
        struct xc4000_priv *priv = fe->tuner_priv;
-       const struct firmware *fw;
-       int ret;
+       const struct firmware *fw   = NULL;
+       const unsigned char   *p, *endp;
+       int                   rc = 0;
+       int                   n, n_array;
+       char                  name[33];
+       char                  *fname;
+
+       printk("%s called\n", __func__);
+
+       fname = XC4000_DEFAULT_FIRMWARE;
+
+       printk("Reading firmware %s\n",  fname);
+       rc = request_firmware(&fw, fname, priv->i2c_props.adap->dev.parent);
+       if (rc < 0) {
+               if (rc == -ENOENT)
+                       printk("Error: firmware %s not found.\n",
+                                  fname);
+               else
+                       printk("Error %d while requesting firmware %s \n",
+                                  rc, fname);
 
-       /* request the firmware, this will block and timeout */
-       printk(KERN_INFO "xc4000: waiting for firmware upload (%s)...\n",
-               XC4000_DEFAULT_FIRMWARE);
+               return rc;
+       }
+       p = fw->data;
+       endp = p + fw->size;
 
-       ret = request_firmware(&fw, XC4000_DEFAULT_FIRMWARE,
-               priv->i2c_props.adap->dev.parent);
-       if (ret) {
-               printk(KERN_ERR "xc4000: Upload failed. (file not found?)\n");
-               ret = XC_RESULT_RESET_FAILURE;
-               goto out;
-       } else {
-               printk(KERN_DEBUG "xc4000: firmware read %Zu bytes.\n",
-                      fw->size);
-               ret = XC_RESULT_SUCCESS;
+       if (fw->size < sizeof(name) - 1 + 2 + 2) {
+               printk("Error: firmware file %s has invalid size!\n",
+                         fname);
+               goto corrupt;
        }
 
-       if (fw->size != XC4000_DEFAULT_FIRMWARE_SIZE) {
-               printk(KERN_ERR "xc4000: firmware incorrect size\n");
-               ret = XC_RESULT_RESET_FAILURE;
-       } else {
-               printk(KERN_INFO "xc4000: firmware uploading...\n");
-               ret = xc_load_i2c_sequence(fe,  fw->data);
-               printk(KERN_INFO "xc4000: firmware upload complete...\n");
+       memcpy(name, p, sizeof(name) - 1);
+       name[sizeof(name) - 1] = 0;
+       p += sizeof(name) - 1;
+
+       priv->firm_version = get_unaligned_le16(p);
+       p += 2;
+
+       n_array = get_unaligned_le16(p);
+       p += 2;
+
+       printk("Loading %d firmware images from %s, type: %s, ver %d.%d\n",
+                  n_array, fname, name,
+                  priv->firm_version >> 8, priv->firm_version & 0xff);
+
+       priv->firm = kzalloc(sizeof(*priv->firm) * n_array, GFP_KERNEL);
+       if (priv->firm == NULL) {
+               printk("Not enough memory to load firmware file.\n");
+               rc = -ENOMEM;
+               goto err;
+       }
+       priv->firm_size = n_array;
+
+       n = -1;
+       while (p < endp) {
+               __u32 type, size;
+               v4l2_std_id id;
+               __u16 int_freq = 0;
+
+               n++;
+               if (n >= n_array) {
+                       printk("More firmware images in file than "
+                                 "were expected!\n");
+                       goto corrupt;
+               }
+
+               /* Checks if there's enough bytes to read */
+               if (endp - p < sizeof(type) + sizeof(id) + sizeof(size))
+                       goto header;
+
+               type = get_unaligned_le32(p);
+               p += sizeof(type);
+
+               id = get_unaligned_le64(p);
+               p += sizeof(id);
+
+               if (type & HAS_IF) {
+                       int_freq = get_unaligned_le16(p);
+                       p += sizeof(int_freq);
+                       if (endp - p < sizeof(size))
+                               goto header;
+               }
+
+               size = get_unaligned_le32(p);
+               p += sizeof(size);
+
+               if (!size || size > endp - p) {
+                       printk("Firmware type ");
+//                     dump_firm_type(type);
+                       printk("(%x), id %llx is corrupted "
+                              "(size=%d, expected %d)\n",
+                              type, (unsigned long long)id,
+                              (unsigned)(endp - p), size);
+                       goto corrupt;
+               }
+
+               priv->firm[n].ptr = kzalloc(size, GFP_KERNEL);
+               if (priv->firm[n].ptr == NULL) {
+                       printk("Not enough memory to load firmware file.\n");
+                       rc = -ENOMEM;
+                       goto err;
+               }
+               printk("Reading firmware type ");
+               if (debug) {
+//                     dump_firm_type_and_int_freq(type, int_freq);
+                       printk("(%x), id %llx, size=%d.\n",
+                              type, (unsigned long long)id, size);
+               }
+
+               memcpy(priv->firm[n].ptr, p, size);
+               priv->firm[n].type = type;
+               priv->firm[n].id   = id;
+               priv->firm[n].size = size;
+               priv->firm[n].int_freq = int_freq;
+
+               p += size;
        }
 
-out:
+       if (n + 1 != priv->firm_size) {
+               printk("Firmware file is incomplete!\n");
+               goto corrupt;
+       }
+
+       goto done;
+
+header:
+       printk("Firmware header is incomplete!\n");
+corrupt:
+       rc = -EINVAL;
+       printk("Error: firmware file is corrupted!\n");
+
+err:
+       printk("Releasing partially loaded firmware file.\n");
+//     free_firmware(priv);
+
+done:
        release_firmware(fw);
-       return ret;
+       if (rc == 0)
+               printk("Firmware files loaded.\n");
+
+       return rc;
 }
 
+
 static void xc_debug_dump(struct xc4000_priv *priv)
 {
        u16 adc_envelope;
@@ -1002,7 +1337,9 @@ struct dvb_frontend *xc4000_attach(struct dvb_frontend *fe,
 {
        struct xc4000_priv *priv = NULL;
        int instance;
+       v4l2_std_id std0;
        u16 id = 0;
+       int rc;
 
        dprintk(1, "%s(%d-%04x)\n", __func__,
                i2c ? i2c_adapter_id(i2c) : -1,
@@ -1069,6 +1406,31 @@ struct dvb_frontend *xc4000_attach(struct dvb_frontend *fe,
        memcpy(&fe->ops.tuner_ops, &xc4000_tuner_ops,
                sizeof(struct dvb_tuner_ops));
 
+       /* FIXME: For now, load the firmware at startup.  We will remove this
+          before the code goes to production... */
+       xc4000_fwupload(fe);
+       printk("xc4000_fwupload done\n");
+
+       std0 = 0;
+//     rc = load_firmware(fe, BASE | new_fw.type, &std0);
+       rc = load_firmware(fe, BASE, &std0);
+       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");
+
+//     rc = load_firmware(fe, BASE | INIT1 | new_fw.type, &std0);
+       rc = load_firmware(fe, INIT1, &std0);
+       printk("init1 load result %x\n", rc);
+
+       if (xc4000_readreg(priv, XREG_PRODUCT_ID, &id) != XC_RESULT_SUCCESS)
+                       goto fail;
+       printk("djh id is now %x\n", id);
+
        return fe;
 fail:
        mutex_unlock(&xc4000_list_mutex);