linux/drivers/net/wireless/orinoco/hermes_dld.c
<<
>>
Prefs
   1/*
   2 * Hermes download helper.
   3 *
   4 * This helper:
   5 *  - is capable of writing to the volatile area of the hermes device
   6 *  - is currently not capable of writing to non-volatile areas
   7 *  - provide helpers to identify and update plugin data
   8 *  - is not capable of interpreting a fw image directly. That is up to
   9 *    the main card driver.
  10 *  - deals with Hermes I devices. It can probably be modified to deal
  11 *    with Hermes II devices
  12 *
  13 * Copyright (C) 2007, David Kilroy
  14 *
  15 * Plug data code slightly modified from spectrum_cs driver
  16 *    Copyright (C) 2002-2005 Pavel Roskin <proski@gnu.org>
  17 * Portions based on information in wl_lkm_718 Agere driver
  18 *    COPYRIGHT (C) 2001-2004 by Agere Systems Inc. All Rights Reserved
  19 *
  20 * The contents of this file are subject to the Mozilla Public License
  21 * Version 1.1 (the "License"); you may not use this file except in
  22 * compliance with the License. You may obtain a copy of the License
  23 * at http://www.mozilla.org/MPL/
  24 *
  25 * Software distributed under the License is distributed on an "AS IS"
  26 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
  27 * the License for the specific language governing rights and
  28 * limitations under the License.
  29 *
  30 * Alternatively, the contents of this file may be used under the
  31 * terms of the GNU General Public License version 2 (the "GPL"), in
  32 * which case the provisions of the GPL are applicable instead of the
  33 * above.  If you wish to allow the use of your version of this file
  34 * only under the terms of the GPL and not to allow others to use your
  35 * version of this file under the MPL, indicate your decision by
  36 * deleting the provisions above and replace them with the notice and
  37 * other provisions required by the GPL.  If you do not delete the
  38 * provisions above, a recipient may use your version of this file
  39 * under either the MPL or the GPL.
  40 */
  41
  42#include <linux/module.h>
  43#include <linux/delay.h>
  44#include "hermes.h"
  45#include "hermes_dld.h"
  46
  47#define PFX "hermes_dld: "
  48
  49/* End markers used in dblocks */
  50#define PDI_END         0x00000000      /* End of PDA */
  51#define BLOCK_END       0xFFFFFFFF      /* Last image block */
  52#define TEXT_END        0x1A            /* End of text header */
  53
  54/*
  55 * The following structures have little-endian fields denoted by
  56 * the leading underscore.  Don't access them directly - use inline
  57 * functions defined below.
  58 */
  59
  60/*
  61 * The binary image to be downloaded consists of series of data blocks.
  62 * Each block has the following structure.
  63 */
  64struct dblock {
  65        __le32 addr;            /* adapter address where to write the block */
  66        __le16 len;             /* length of the data only, in bytes */
  67        char data[0];           /* data to be written */
  68} __packed;
  69
  70/*
  71 * Plug Data References are located in the image after the last data
  72 * block.  They refer to areas in the adapter memory where the plug data
  73 * items with matching ID should be written.
  74 */
  75struct pdr {
  76        __le32 id;              /* record ID */
  77        __le32 addr;            /* adapter address where to write the data */
  78        __le32 len;             /* expected length of the data, in bytes */
  79        char next[0];           /* next PDR starts here */
  80} __packed;
  81
  82/*
  83 * Plug Data Items are located in the EEPROM read from the adapter by
  84 * primary firmware.  They refer to the device-specific data that should
  85 * be plugged into the secondary firmware.
  86 */
  87struct pdi {
  88        __le16 len;             /* length of ID and data, in words */
  89        __le16 id;              /* record ID */
  90        char data[0];           /* plug data */
  91} __packed;
  92
  93/*** FW data block access functions ***/
  94
  95static inline u32
  96dblock_addr(const struct dblock *blk)
  97{
  98        return le32_to_cpu(blk->addr);
  99}
 100
 101static inline u32
 102dblock_len(const struct dblock *blk)
 103{
 104        return le16_to_cpu(blk->len);
 105}
 106
 107/*** PDR Access functions ***/
 108
 109static inline u32
 110pdr_id(const struct pdr *pdr)
 111{
 112        return le32_to_cpu(pdr->id);
 113}
 114
 115static inline u32
 116pdr_addr(const struct pdr *pdr)
 117{
 118        return le32_to_cpu(pdr->addr);
 119}
 120
 121static inline u32
 122pdr_len(const struct pdr *pdr)
 123{
 124        return le32_to_cpu(pdr->len);
 125}
 126
 127/*** PDI Access functions ***/
 128
 129static inline u32
 130pdi_id(const struct pdi *pdi)
 131{
 132        return le16_to_cpu(pdi->id);
 133}
 134
 135/* Return length of the data only, in bytes */
 136static inline u32
 137pdi_len(const struct pdi *pdi)
 138{
 139        return 2 * (le16_to_cpu(pdi->len) - 1);
 140}
 141
 142/*** Plug Data Functions ***/
 143
 144/*
 145 * Scan PDR for the record with the specified RECORD_ID.
 146 * If it's not found, return NULL.
 147 */
 148static const struct pdr *
 149hermes_find_pdr(const struct pdr *first_pdr, u32 record_id, const void *end)
 150{
 151        const struct pdr *pdr = first_pdr;
 152
 153        end -= sizeof(struct pdr);
 154
 155        while (((void *) pdr <= end) &&
 156               (pdr_id(pdr) != PDI_END)) {
 157                /*
 158                 * PDR area is currently not terminated by PDI_END.
 159                 * It's followed by CRC records, which have the type
 160                 * field where PDR has length.  The type can be 0 or 1.
 161                 */
 162                if (pdr_len(pdr) < 2)
 163                        return NULL;
 164
 165                /* If the record ID matches, we are done */
 166                if (pdr_id(pdr) == record_id)
 167                        return pdr;
 168
 169                pdr = (struct pdr *) pdr->next;
 170        }
 171        return NULL;
 172}
 173
 174/* Scan production data items for a particular entry */
 175static const struct pdi *
 176hermes_find_pdi(const struct pdi *first_pdi, u32 record_id, const void *end)
 177{
 178        const struct pdi *pdi = first_pdi;
 179
 180        end -= sizeof(struct pdi);
 181
 182        while (((void *) pdi <= end) &&
 183               (pdi_id(pdi) != PDI_END)) {
 184
 185                /* If the record ID matches, we are done */
 186                if (pdi_id(pdi) == record_id)
 187                        return pdi;
 188
 189                pdi = (struct pdi *) &pdi->data[pdi_len(pdi)];
 190        }
 191        return NULL;
 192}
 193
 194/* Process one Plug Data Item - find corresponding PDR and plug it */
 195static int
 196hermes_plug_pdi(struct hermes *hw, const struct pdr *first_pdr,
 197                const struct pdi *pdi, const void *pdr_end)
 198{
 199        const struct pdr *pdr;
 200
 201        /* Find the PDR corresponding to this PDI */
 202        pdr = hermes_find_pdr(first_pdr, pdi_id(pdi), pdr_end);
 203
 204        /* No match is found, safe to ignore */
 205        if (!pdr)
 206                return 0;
 207
 208        /* Lengths of the data in PDI and PDR must match */
 209        if (pdi_len(pdi) != pdr_len(pdr))
 210                return -EINVAL;
 211
 212        /* do the actual plugging */
 213        hw->ops->program(hw, pdi->data, pdr_addr(pdr), pdi_len(pdi));
 214
 215        return 0;
 216}
 217
 218/* Parse PDA and write the records into the adapter
 219 *
 220 * Attempt to write every records that is in the specified pda
 221 * which also has a valid production data record for the firmware.
 222 */
 223int hermes_apply_pda(struct hermes *hw,
 224                     const char *first_pdr,
 225                     const void *pdr_end,
 226                     const __le16 *pda,
 227                     const void *pda_end)
 228{
 229        int ret;
 230        const struct pdi *pdi;
 231        const struct pdr *pdr;
 232
 233        pdr = (const struct pdr *) first_pdr;
 234        pda_end -= sizeof(struct pdi);
 235
 236        /* Go through every PDI and plug them into the adapter */
 237        pdi = (const struct pdi *) (pda + 2);
 238        while (((void *) pdi <= pda_end) &&
 239               (pdi_id(pdi) != PDI_END)) {
 240                ret = hermes_plug_pdi(hw, pdr, pdi, pdr_end);
 241                if (ret)
 242                        return ret;
 243
 244                /* Increment to the next PDI */
 245                pdi = (const struct pdi *) &pdi->data[pdi_len(pdi)];
 246        }
 247        return 0;
 248}
 249
 250/* Identify the total number of bytes in all blocks
 251 * including the header data.
 252 */
 253size_t
 254hermes_blocks_length(const char *first_block, const void *end)
 255{
 256        const struct dblock *blk = (const struct dblock *) first_block;
 257        int total_len = 0;
 258        int len;
 259
 260        end -= sizeof(*blk);
 261
 262        /* Skip all blocks to locate Plug Data References
 263         * (Spectrum CS) */
 264        while (((void *) blk <= end) &&
 265               (dblock_addr(blk) != BLOCK_END)) {
 266                len = dblock_len(blk);
 267                total_len += sizeof(*blk) + len;
 268                blk = (struct dblock *) &blk->data[len];
 269        }
 270
 271        return total_len;
 272}
 273
 274/*** Hermes programming ***/
 275
 276/* Program the data blocks */
 277int hermes_program(struct hermes *hw, const char *first_block, const void *end)
 278{
 279        const struct dblock *blk;
 280        u32 blkaddr;
 281        u32 blklen;
 282        int err = 0;
 283
 284        blk = (const struct dblock *) first_block;
 285
 286        if ((void *) blk > (end - sizeof(*blk)))
 287                return -EIO;
 288
 289        blkaddr = dblock_addr(blk);
 290        blklen = dblock_len(blk);
 291
 292        while ((blkaddr != BLOCK_END) &&
 293               (((void *) blk + blklen) <= end)) {
 294                pr_debug(PFX "Programming block of length %d "
 295                         "to address 0x%08x\n", blklen, blkaddr);
 296
 297                err = hw->ops->program(hw, blk->data, blkaddr, blklen);
 298                if (err)
 299                        break;
 300
 301                blk = (const struct dblock *) &blk->data[blklen];
 302
 303                if ((void *) blk > (end - sizeof(*blk)))
 304                        return -EIO;
 305
 306                blkaddr = dblock_addr(blk);
 307                blklen = dblock_len(blk);
 308        }
 309        return err;
 310}
 311
 312/*** Default plugging data for Hermes I ***/
 313/* Values from wl_lkm_718/hcf/dhf.c */
 314
 315#define DEFINE_DEFAULT_PDR(pid, length, data)                           \
 316static const struct {                                                   \
 317        __le16 len;                                                     \
 318        __le16 id;                                                      \
 319        u8 val[length];                                                 \
 320} __packed default_pdr_data_##pid = {                   \
 321        cpu_to_le16((sizeof(default_pdr_data_##pid)/                    \
 322                                sizeof(__le16)) - 1),                   \
 323        cpu_to_le16(pid),                                               \
 324        data                                                            \
 325}
 326
 327#define DEFAULT_PDR(pid) default_pdr_data_##pid
 328
 329/*  HWIF Compatibility */
 330DEFINE_DEFAULT_PDR(0x0005, 10, "\x00\x00\x06\x00\x01\x00\x01\x00\x01\x00");
 331
 332/* PPPPSign */
 333DEFINE_DEFAULT_PDR(0x0108, 4, "\x00\x00\x00\x00");
 334
 335/* PPPPProf */
 336DEFINE_DEFAULT_PDR(0x0109, 10, "\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00");
 337
 338/* Antenna diversity */
 339DEFINE_DEFAULT_PDR(0x0150, 2, "\x00\x3F");
 340
 341/* Modem VCO band Set-up */
 342DEFINE_DEFAULT_PDR(0x0160, 28,
 343                   "\x00\x00\x00\x00\x00\x00\x00\x00"
 344                   "\x00\x00\x00\x00\x00\x00\x00\x00"
 345                   "\x00\x00\x00\x00\x00\x00\x00\x00"
 346                   "\x00\x00\x00\x00");
 347
 348/* Modem Rx Gain Table Values */
 349DEFINE_DEFAULT_PDR(0x0161, 256,
 350                   "\x3F\x01\x3F\01\x3F\x01\x3F\x01"
 351                   "\x3F\x01\x3F\01\x3F\x01\x3F\x01"
 352                   "\x3F\x01\x3F\01\x3F\x01\x3F\x01"
 353                   "\x3F\x01\x3F\01\x3F\x01\x3F\x01"
 354                   "\x3F\x01\x3E\01\x3E\x01\x3D\x01"
 355                   "\x3D\x01\x3C\01\x3C\x01\x3B\x01"
 356                   "\x3B\x01\x3A\01\x3A\x01\x39\x01"
 357                   "\x39\x01\x38\01\x38\x01\x37\x01"
 358                   "\x37\x01\x36\01\x36\x01\x35\x01"
 359                   "\x35\x01\x34\01\x34\x01\x33\x01"
 360                   "\x33\x01\x32\x01\x32\x01\x31\x01"
 361                   "\x31\x01\x30\x01\x30\x01\x7B\x01"
 362                   "\x7B\x01\x7A\x01\x7A\x01\x79\x01"
 363                   "\x79\x01\x78\x01\x78\x01\x77\x01"
 364                   "\x77\x01\x76\x01\x76\x01\x75\x01"
 365                   "\x75\x01\x74\x01\x74\x01\x73\x01"
 366                   "\x73\x01\x72\x01\x72\x01\x71\x01"
 367                   "\x71\x01\x70\x01\x70\x01\x68\x01"
 368                   "\x68\x01\x67\x01\x67\x01\x66\x01"
 369                   "\x66\x01\x65\x01\x65\x01\x57\x01"
 370                   "\x57\x01\x56\x01\x56\x01\x55\x01"
 371                   "\x55\x01\x54\x01\x54\x01\x53\x01"
 372                   "\x53\x01\x52\x01\x52\x01\x51\x01"
 373                   "\x51\x01\x50\x01\x50\x01\x48\x01"
 374                   "\x48\x01\x47\x01\x47\x01\x46\x01"
 375                   "\x46\x01\x45\x01\x45\x01\x44\x01"
 376                   "\x44\x01\x43\x01\x43\x01\x42\x01"
 377                   "\x42\x01\x41\x01\x41\x01\x40\x01"
 378                   "\x40\x01\x40\x01\x40\x01\x40\x01"
 379                   "\x40\x01\x40\x01\x40\x01\x40\x01"
 380                   "\x40\x01\x40\x01\x40\x01\x40\x01"
 381                   "\x40\x01\x40\x01\x40\x01\x40\x01");
 382
 383/* Write PDA according to certain rules.
 384 *
 385 * For every production data record, look for a previous setting in
 386 * the pda, and use that.
 387 *
 388 * For certain records, use defaults if they are not found in pda.
 389 */
 390int hermes_apply_pda_with_defaults(struct hermes *hw,
 391                                   const char *first_pdr,
 392                                   const void *pdr_end,
 393                                   const __le16 *pda,
 394                                   const void *pda_end)
 395{
 396        const struct pdr *pdr = (const struct pdr *) first_pdr;
 397        const struct pdi *first_pdi = (const struct pdi *) &pda[2];
 398        const struct pdi *pdi;
 399        const struct pdi *default_pdi = NULL;
 400        const struct pdi *outdoor_pdi;
 401        int record_id;
 402
 403        pdr_end -= sizeof(struct pdr);
 404
 405        while (((void *) pdr <= pdr_end) &&
 406               (pdr_id(pdr) != PDI_END)) {
 407                /*
 408                 * For spectrum_cs firmwares,
 409                 * PDR area is currently not terminated by PDI_END.
 410                 * It's followed by CRC records, which have the type
 411                 * field where PDR has length.  The type can be 0 or 1.
 412                 */
 413                if (pdr_len(pdr) < 2)
 414                        break;
 415                record_id = pdr_id(pdr);
 416
 417                pdi = hermes_find_pdi(first_pdi, record_id, pda_end);
 418                if (pdi)
 419                        pr_debug(PFX "Found record 0x%04x at %p\n",
 420                                 record_id, pdi);
 421
 422                switch (record_id) {
 423                case 0x110: /* Modem REFDAC values */
 424                case 0x120: /* Modem VGDAC values */
 425                        outdoor_pdi = hermes_find_pdi(first_pdi, record_id + 1,
 426                                                      pda_end);
 427                        default_pdi = NULL;
 428                        if (outdoor_pdi) {
 429                                pdi = outdoor_pdi;
 430                                pr_debug(PFX
 431                                         "Using outdoor record 0x%04x at %p\n",
 432                                         record_id + 1, pdi);
 433                        }
 434                        break;
 435                case 0x5: /*  HWIF Compatibility */
 436                        default_pdi = (struct pdi *) &DEFAULT_PDR(0x0005);
 437                        break;
 438                case 0x108: /* PPPPSign */
 439                        default_pdi = (struct pdi *) &DEFAULT_PDR(0x0108);
 440                        break;
 441                case 0x109: /* PPPPProf */
 442                        default_pdi = (struct pdi *) &DEFAULT_PDR(0x0109);
 443                        break;
 444                case 0x150: /* Antenna diversity */
 445                        default_pdi = (struct pdi *) &DEFAULT_PDR(0x0150);
 446                        break;
 447                case 0x160: /* Modem VCO band Set-up */
 448                        default_pdi = (struct pdi *) &DEFAULT_PDR(0x0160);
 449                        break;
 450                case 0x161: /* Modem Rx Gain Table Values */
 451                        default_pdi = (struct pdi *) &DEFAULT_PDR(0x0161);
 452                        break;
 453                default:
 454                        default_pdi = NULL;
 455                        break;
 456                }
 457                if (!pdi && default_pdi) {
 458                        /* Use default */
 459                        pdi = default_pdi;
 460                        pr_debug(PFX "Using default record 0x%04x at %p\n",
 461                                 record_id, pdi);
 462                }
 463
 464                if (pdi) {
 465                        /* Lengths of the data in PDI and PDR must match */
 466                        if ((pdi_len(pdi) == pdr_len(pdr)) &&
 467                            ((void *) pdi->data + pdi_len(pdi) < pda_end)) {
 468                                /* do the actual plugging */
 469                                hw->ops->program(hw, pdi->data, pdr_addr(pdr),
 470                                                 pdi_len(pdi));
 471                        }
 472                }
 473
 474                pdr++;
 475        }
 476        return 0;
 477}
 478