#define HERMES_AUX_ENABLE      0x8000  /* Enable auxiliary port access */
 #define HERMES_AUX_DISABLE     0x4000  /* Disable to auxiliary port access */
 #define HERMES_AUX_ENABLED     0xC000  /* Auxiliary port is open */
+#define HERMES_AUX_DISABLED    0x0000  /* Auxiliary port is closed */
 
 #define HERMES_AUX_PW0 0xFE01
 #define HERMES_AUX_PW1 0xDC23
 #define HERMES_AUX_PW2 0xBA45
 
-/* End markers */
+/* End markers used in dblocks */
 #define PDI_END                0x00000000      /* End of PDA */
 #define BLOCK_END      0xFFFFFFFF      /* Last image block */
+#define TEXT_END       0x1A            /* End of text header */
+
+/*
+ * PDA == Production Data Area
+ *
+ * In principle, the max. size of the PDA is is 4096 words. Currently,
+ * however, only about 500 bytes of this area are used.
+ *
+ * Some USB implementations can't handle sizes in excess of 1016. Note
+ * that PDA is not actually used in those USB environments, but may be
+ * retrieved by common code.
+ */
+#define MAX_PDA_SIZE   1000
+
+/* Limit the amout we try to download in a single shot.
+ * Size is in bytes.
+ */
+#define MAX_DL_SIZE 1024
+#define LIMIT_PROGRAM_SIZE 0
 
 /*
  * The following structures have little-endian fields denoted by
        char data[0];           /* plug data */
 } __attribute__ ((packed));
 
-/* Functions for access to little-endian data */
+/*** FW data block access functions ***/
+
 static inline u32
 dblock_addr(const struct dblock *blk)
 {
        return le16_to_cpu(blk->len);
 }
 
+/*** PDR Access functions ***/
+
 static inline u32
 pdr_id(const struct pdr *pdr)
 {
        return le32_to_cpu(pdr->len);
 }
 
+/*** PDI Access functions ***/
+
 static inline u32
 pdi_id(const struct pdi *pdi)
 {
        return 2 * (le16_to_cpu(pdi->len) - 1);
 }
 
-/* Set address of the auxiliary port */
+/*** Hermes AUX control ***/
+
 static inline void
-spectrum_aux_setaddr(hermes_t *hw, u32 addr)
+hermes_aux_setaddr(hermes_t *hw, u32 addr)
 {
        hermes_write_reg(hw, HERMES_AUXPAGE, (u16) (addr >> 7));
        hermes_write_reg(hw, HERMES_AUXOFFSET, (u16) (addr & 0x7F));
 }
 
-/* Open access to the auxiliary port */
-static int
-spectrum_aux_open(hermes_t *hw)
+static inline int
+hermes_aux_control(hermes_t *hw, int enabled)
 {
+       int desired_state = enabled ? HERMES_AUX_ENABLED : HERMES_AUX_DISABLED;
+       int action = enabled ? HERMES_AUX_ENABLE : HERMES_AUX_DISABLE;
        int i;
 
        /* Already open? */
-       if (hermes_read_reg(hw, HERMES_CONTROL) == HERMES_AUX_ENABLED)
+       if (hermes_read_reg(hw, HERMES_CONTROL) == desired_state)
                return 0;
 
        hermes_write_reg(hw, HERMES_PARAM0, HERMES_AUX_PW0);
        hermes_write_reg(hw, HERMES_PARAM1, HERMES_AUX_PW1);
        hermes_write_reg(hw, HERMES_PARAM2, HERMES_AUX_PW2);
-       hermes_write_reg(hw, HERMES_CONTROL, HERMES_AUX_ENABLE);
+       hermes_write_reg(hw, HERMES_CONTROL, action);
 
        for (i = 0; i < 20; i++) {
                udelay(10);
                if (hermes_read_reg(hw, HERMES_CONTROL) ==
-                   HERMES_AUX_ENABLED)
+                   desired_state)
                        return 0;
        }
 
        return -EBUSY;
 }
 
+/*** Plug Data Functions ***/
+
 /*
  * Scan PDR for the record with the specified RECORD_ID.
  * If it's not found, return NULL.
  */
 static struct pdr *
-spectrum_find_pdr(struct pdr *first_pdr, u32 record_id)
+hermes_find_pdr(struct pdr *first_pdr, u32 record_id)
 {
        struct pdr *pdr = first_pdr;
+       void *end = (void *)first_pdr + MAX_PDA_SIZE;
 
-       while (pdr_id(pdr) != PDI_END) {
+       while (((void *)pdr < end) &&
+              (pdr_id(pdr) != PDI_END)) {
                /*
                 * PDR area is currently not terminated by PDI_END.
                 * It's followed by CRC records, which have the type
 
 /* Process one Plug Data Item - find corresponding PDR and plug it */
 static int
-spectrum_plug_pdi(hermes_t *hw, struct pdr *first_pdr, struct pdi *pdi)
+hermes_plug_pdi(hermes_t *hw, struct pdr *first_pdr, const struct pdi *pdi)
 {
        struct pdr *pdr;
 
-       /* Find the PDI corresponding to this PDR */
-       pdr = spectrum_find_pdr(first_pdr, pdi_id(pdi));
+       /* Find the PDR corresponding to this PDI */
+       pdr = hermes_find_pdr(first_pdr, pdi_id(pdi));
 
        /* No match is found, safe to ignore */
        if (!pdr)
                return -EINVAL;
 
        /* do the actual plugging */
-       spectrum_aux_setaddr(hw, pdr_addr(pdr));
+       hermes_aux_setaddr(hw, pdr_addr(pdr));
        hermes_write_bytes(hw, HERMES_AUXDATA, pdi->data, pdi_len(pdi));
 
        return 0;
 }
 
 /* Read PDA from the adapter */
-int
-spectrum_read_pda(hermes_t *hw, __le16 *pda, int pda_len)
+int hermes_read_pda(hermes_t *hw,
+                   __le16 *pda,
+                   u32 pda_addr,
+                   u16 pda_len,
+                   int use_eeprom) /* can we get this into hw? */
 {
        int ret;
-       int pda_size;
+       u16 pda_size;
+       u16 data_len = pda_len;
+       __le16 *data = pda;
 
-       /* Issue command to read EEPROM */
-       ret = hermes_docmd_wait(hw, HERMES_CMD_READMIF, 0, NULL);
-       if (ret)
-               return ret;
+       if (use_eeprom) {
+               /* PDA of spectrum symbol is in eeprom */
+
+               /* Issue command to read EEPROM */
+               ret = hermes_docmd_wait(hw, HERMES_CMD_READMIF, 0, NULL);
+               if (ret)
+                       return ret;
+       }
 
        /* Open auxiliary port */
-       ret = spectrum_aux_open(hw);
+       ret = hermes_aux_control(hw, 1);
+       printk(KERN_DEBUG PFX "AUX enable returned %d\n", ret);
        if (ret)
                return ret;
 
        /* read PDA from EEPROM */
-       spectrum_aux_setaddr(hw, PDA_ADDR);
-       hermes_read_words(hw, HERMES_AUXDATA, pda, pda_len / 2);
+       hermes_aux_setaddr(hw, pda_addr);
+       hermes_read_words(hw, HERMES_AUXDATA, data, data_len / 2);
+
+       /* Close aux port */
+       ret = hermes_aux_control(hw, 0);
+       printk(KERN_DEBUG PFX "AUX disable returned %d\n", ret);
 
        /* Check PDA length */
        pda_size = le16_to_cpu(pda[0]);
+       printk(KERN_DEBUG PFX "Actual PDA length %d, Max allowed %d\n",
+              pda_size, pda_len);
        if (pda_size > pda_len)
                return -EINVAL;
 
        return 0;
 }
-EXPORT_SYMBOL(spectrum_read_pda);
+EXPORT_SYMBOL(hermes_read_pda);
 
-/* Parse PDA and write the records into the adapter */
-int
-spectrum_apply_pda(hermes_t *hw, const struct dblock *first_block,
-                  __le16 *pda)
+/* Parse PDA and write the records into the adapter
+ *
+ * Attempt to write every records that is in the specified pda
+ * which also has a valid production data record for the firmware.
+ */
+int hermes_apply_pda(hermes_t *hw,
+                    const char *first_pdr,
+                    const __le16 *pda)
 {
        int ret;
-       struct pdi *pdi;
-       struct pdr *first_pdr;
-       const struct dblock *blk = first_block;
-
-       /* Skip all blocks to locate Plug Data References */
-       while (dblock_addr(blk) != BLOCK_END)
-               blk = (struct dblock *) &blk->data[dblock_len(blk)];
+       const struct pdi *pdi;
+       struct pdr *pdr;
 
-       first_pdr = (struct pdr *) blk;
+       pdr = (struct pdr *) first_pdr;
 
        /* Go through every PDI and plug them into the adapter */
-       pdi = (struct pdi *) (pda + 2);
+       pdi = (const struct pdi *) (pda + 2);
        while (pdi_id(pdi) != PDI_END) {
-               ret = spectrum_plug_pdi(hw, first_pdr, pdi);
+               ret = hermes_plug_pdi(hw, pdr, pdi);
                if (ret)
                        return ret;
 
                /* Increment to the next PDI */
-               pdi = (struct pdi *) &pdi->data[pdi_len(pdi)];
+               pdi = (const struct pdi *) &pdi->data[pdi_len(pdi)];
        }
        return 0;
 }
-EXPORT_SYMBOL(spectrum_apply_pda);
+EXPORT_SYMBOL(hermes_apply_pda);
+
+/* Identify the total number of bytes in all blocks
+ * including the header data.
+ */
+size_t
+hermes_blocks_length(const char *first_block)
+{
+       const struct dblock *blk = (const struct dblock *) first_block;
+       int total_len = 0;
+       int len;
+
+       /* Skip all blocks to locate Plug Data References
+        * (Spectrum CS) */
+       while (dblock_addr(blk) != BLOCK_END) {
+               len = dblock_len(blk);
+               total_len += sizeof(*blk) + len;
+               blk = (struct dblock *) &blk->data[len];
+       }
+
+       return total_len;
+}
+EXPORT_SYMBOL(hermes_blocks_length);
+
+/*** Hermes programming ***/
 
-/* Load firmware blocks into the adapter */
-int
-spectrum_load_blocks(hermes_t *hw, const struct dblock *first_block)
+/* Program the data blocks */
+int hermes_program(hermes_t *hw, const char *first_block, const char *end)
 {
        const struct dblock *blk;
        u32 blkaddr;
        u32 blklen;
+#if LIMIT_PROGRAM_SIZE
+       u32 addr;
+       u32 len;
+#endif
+
+       blk = (const struct dblock *) first_block;
+
+       if ((const char *) blk > (end - sizeof(*blk)))
+               return -EIO;
 
-       blk = first_block;
        blkaddr = dblock_addr(blk);
        blklen = dblock_len(blk);
 
-       while (dblock_addr(blk) != BLOCK_END) {
-               spectrum_aux_setaddr(hw, blkaddr);
+       while ((blkaddr != BLOCK_END) &&
+              (((const char *) blk + blklen) <= end)) {
+               printk(KERN_DEBUG PFX
+                      "Programming block of length %d to address 0x%08x\n",
+                      blklen, blkaddr);
+
+#if !LIMIT_PROGRAM_SIZE
+               /* wl_lkm driver splits this into writes of 2000 bytes */
+               hermes_aux_setaddr(hw, blkaddr);
                hermes_write_bytes(hw, HERMES_AUXDATA, blk->data,
                                   blklen);
+#else
+               len = (blklen < MAX_DL_SIZE) ? blklen : MAX_DL_SIZE;
+               addr = blkaddr;
+
+               while (addr < (blkaddr + blklen)) {
+                       printk(KERN_DEBUG PFX
+                              "Programming subblock of length %d "
+                              "to address 0x%08x. Data @ %p\n",
+                              len, addr, &blk->data[addr - blkaddr]);
+
+                       hermes_aux_setaddr(hw, addr);
+                       hermes_write_bytes(hw, HERMES_AUXDATA,
+                                          &blk->data[addr - blkaddr],
+                                          len);
+
+                       addr += len;
+                       len = ((blkaddr + blklen - addr) < MAX_DL_SIZE) ?
+                               (blkaddr + blklen - addr) : MAX_DL_SIZE;
+               }
+#endif
+               blk = (const struct dblock *) &blk->data[blklen];
+
+               if ((const char *) blk > (end - sizeof(*blk)))
+                       return -EIO;
 
-               blk = (struct dblock *) &blk->data[blklen];
                blkaddr = dblock_addr(blk);
                blklen = dblock_len(blk);
        }
        return 0;
 }
-EXPORT_SYMBOL(spectrum_load_blocks);
+EXPORT_SYMBOL(hermes_program);
 
 static int __init init_hermes_dld(void)
 {
 
  */
 static int
 spectrum_dl_image(hermes_t *hw, struct pcmcia_device *link,
-                 const unsigned char *image, int secondary)
+                 const unsigned char *image, const unsigned char *end,
+                 int secondary)
 {
        int ret;
        const unsigned char *ptr;
-       const struct dblock *first_block;
+       const unsigned char *first_block;
 
        /* Plug Data Area (PDA) */
        __le16 pda[PDA_WORDS];
        /* Binary block begins after the 0x1A marker */
        ptr = image;
        while (*ptr++ != TEXT_END);
-       first_block = (const struct dblock *) ptr;
+       first_block = ptr;
 
-       /* Read the PDA */
+       /* Read the PDA from EEPROM */
        if (secondary) {
-               ret = spectrum_read_pda(hw, pda, sizeof(pda));
+               ret = hermes_read_pda(hw, pda, PDA_ADDR, sizeof(pda), 1);
                if (ret)
                        return ret;
        }
                return ret;
 
        /* Program the adapter with new firmware */
-       ret = spectrum_load_blocks(hw, first_block);
+       ret = hermes_program(hw, first_block, end);
        if (ret)
                return ret;
 
        /* Write the PDA to the adapter */
        if (secondary) {
-               ret = spectrum_apply_pda(hw, first_block, pda);
+               size_t len = hermes_blocks_length(first_block);
+               ptr = first_block + len;
+               ret = hermes_apply_pda(hw, ptr, pda);
                if (ret)
                        return ret;
        }
        }
 
        /* Load primary firmware */
-       ret = spectrum_dl_image(hw, link, fw_entry->data, 0);
+       ret = spectrum_dl_image(hw, link, fw_entry->data,
+                               fw_entry->data + fw_entry->size, 0);
        release_firmware(fw_entry);
        if (ret) {
                printk(KERN_ERR PFX "Primary firmware download failed\n");
        }
 
        /* Load secondary firmware */
-       ret = spectrum_dl_image(hw, link, fw_entry->data, 1);
+       ret = spectrum_dl_image(hw, link, fw_entry->data,
+                               fw_entry->data + fw_entry->size, 1);
        release_firmware(fw_entry);
        if (ret) {
                printk(KERN_ERR PFX "Secondary firmware download failed\n");