blob: 22ae79dae41e17d6584484c29ef1ff4add73fc1a [file] [log] [blame]
David Kilroyf482eb72008-08-21 23:27:51 +01001/*
2 * Hermes download helper driver.
3 *
4 * This could be entirely merged into hermes.c.
5 *
6 * I'm keeping it separate to minimise the amount of merging between
7 * kernel upgrades. It also means the memory overhead for drivers that
8 * don't need firmware download low.
9 *
10 * This driver:
11 * - is capable of writing to the volatile area of the hermes device
12 * - is currently not capable of writing to non-volatile areas
13 * - provide helpers to identify and update plugin data
14 * - is not capable of interpreting a fw image directly. That is up to
15 * the main card driver.
16 * - deals with Hermes I devices. It can probably be modified to deal
17 * with Hermes II devices
18 *
19 * Copyright (C) 2007, David Kilroy
20 *
21 * Plug data code slightly modified from spectrum_cs driver
22 * Copyright (C) 2002-2005 Pavel Roskin <proski@gnu.org>
23 * Portions based on information in wl_lkm_718 Agere driver
24 * COPYRIGHT (C) 2001-2004 by Agere Systems Inc. All Rights Reserved
25 *
26 * The contents of this file are subject to the Mozilla Public License
27 * Version 1.1 (the "License"); you may not use this file except in
28 * compliance with the License. You may obtain a copy of the License
29 * at http://www.mozilla.org/MPL/
30 *
31 * Software distributed under the License is distributed on an "AS IS"
32 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
33 * the License for the specific language governing rights and
34 * limitations under the License.
35 *
36 * Alternatively, the contents of this file may be used under the
37 * terms of the GNU General Public License version 2 (the "GPL"), in
38 * which case the provisions of the GPL are applicable instead of the
39 * above. If you wish to allow the use of your version of this file
40 * only under the terms of the GPL and not to allow others to use your
41 * version of this file under the MPL, indicate your decision by
42 * deleting the provisions above and replace them with the notice and
43 * other provisions required by the GPL. If you do not delete the
44 * provisions above, a recipient may use your version of this file
45 * under either the MPL or the GPL.
46 */
47
48#include <linux/module.h>
49#include <linux/delay.h>
50#include "hermes.h"
51#include "hermes_dld.h"
52
53MODULE_DESCRIPTION("Download helper for Lucent Hermes chipset");
54MODULE_AUTHOR("David Kilroy <kilroyd@gmail.com>");
55MODULE_LICENSE("Dual MPL/GPL");
56
57#define PFX "hermes_dld: "
58
59/*
60 * AUX port access. To unlock the AUX port write the access keys to the
61 * PARAM0-2 registers, then write HERMES_AUX_ENABLE to the HERMES_CONTROL
62 * register. Then read it and make sure it's HERMES_AUX_ENABLED.
63 */
64#define HERMES_AUX_ENABLE 0x8000 /* Enable auxiliary port access */
65#define HERMES_AUX_DISABLE 0x4000 /* Disable to auxiliary port access */
66#define HERMES_AUX_ENABLED 0xC000 /* Auxiliary port is open */
David Kilroye2334182008-08-21 23:27:52 +010067#define HERMES_AUX_DISABLED 0x0000 /* Auxiliary port is closed */
David Kilroyf482eb72008-08-21 23:27:51 +010068
69#define HERMES_AUX_PW0 0xFE01
70#define HERMES_AUX_PW1 0xDC23
71#define HERMES_AUX_PW2 0xBA45
72
David Kilroye2334182008-08-21 23:27:52 +010073/* End markers used in dblocks */
David Kilroyf482eb72008-08-21 23:27:51 +010074#define PDI_END 0x00000000 /* End of PDA */
75#define BLOCK_END 0xFFFFFFFF /* Last image block */
David Kilroye2334182008-08-21 23:27:52 +010076#define TEXT_END 0x1A /* End of text header */
77
78/*
79 * PDA == Production Data Area
80 *
81 * In principle, the max. size of the PDA is is 4096 words. Currently,
82 * however, only about 500 bytes of this area are used.
83 *
84 * Some USB implementations can't handle sizes in excess of 1016. Note
85 * that PDA is not actually used in those USB environments, but may be
86 * retrieved by common code.
87 */
88#define MAX_PDA_SIZE 1000
89
90/* Limit the amout we try to download in a single shot.
91 * Size is in bytes.
92 */
93#define MAX_DL_SIZE 1024
94#define LIMIT_PROGRAM_SIZE 0
David Kilroyf482eb72008-08-21 23:27:51 +010095
96/*
97 * The following structures have little-endian fields denoted by
98 * the leading underscore. Don't access them directly - use inline
99 * functions defined below.
100 */
101
102/*
103 * The binary image to be downloaded consists of series of data blocks.
104 * Each block has the following structure.
105 */
106struct dblock {
107 __le32 addr; /* adapter address where to write the block */
108 __le16 len; /* length of the data only, in bytes */
109 char data[0]; /* data to be written */
110} __attribute__ ((packed));
111
112/*
113 * Plug Data References are located in in the image after the last data
114 * block. They refer to areas in the adapter memory where the plug data
115 * items with matching ID should be written.
116 */
117struct pdr {
118 __le32 id; /* record ID */
119 __le32 addr; /* adapter address where to write the data */
120 __le32 len; /* expected length of the data, in bytes */
121 char next[0]; /* next PDR starts here */
122} __attribute__ ((packed));
123
124/*
125 * Plug Data Items are located in the EEPROM read from the adapter by
126 * primary firmware. They refer to the device-specific data that should
127 * be plugged into the secondary firmware.
128 */
129struct pdi {
130 __le16 len; /* length of ID and data, in words */
131 __le16 id; /* record ID */
132 char data[0]; /* plug data */
133} __attribute__ ((packed));
134
David Kilroye2334182008-08-21 23:27:52 +0100135/*** FW data block access functions ***/
136
David Kilroyf482eb72008-08-21 23:27:51 +0100137static inline u32
138dblock_addr(const struct dblock *blk)
139{
140 return le32_to_cpu(blk->addr);
141}
142
143static inline u32
144dblock_len(const struct dblock *blk)
145{
146 return le16_to_cpu(blk->len);
147}
148
David Kilroye2334182008-08-21 23:27:52 +0100149/*** PDR Access functions ***/
150
David Kilroyf482eb72008-08-21 23:27:51 +0100151static inline u32
152pdr_id(const struct pdr *pdr)
153{
154 return le32_to_cpu(pdr->id);
155}
156
157static inline u32
158pdr_addr(const struct pdr *pdr)
159{
160 return le32_to_cpu(pdr->addr);
161}
162
163static inline u32
164pdr_len(const struct pdr *pdr)
165{
166 return le32_to_cpu(pdr->len);
167}
168
David Kilroye2334182008-08-21 23:27:52 +0100169/*** PDI Access functions ***/
170
David Kilroyf482eb72008-08-21 23:27:51 +0100171static inline u32
172pdi_id(const struct pdi *pdi)
173{
174 return le16_to_cpu(pdi->id);
175}
176
177/* Return length of the data only, in bytes */
178static inline u32
179pdi_len(const struct pdi *pdi)
180{
181 return 2 * (le16_to_cpu(pdi->len) - 1);
182}
183
David Kilroye2334182008-08-21 23:27:52 +0100184/*** Hermes AUX control ***/
185
David Kilroyf482eb72008-08-21 23:27:51 +0100186static inline void
David Kilroye2334182008-08-21 23:27:52 +0100187hermes_aux_setaddr(hermes_t *hw, u32 addr)
David Kilroyf482eb72008-08-21 23:27:51 +0100188{
189 hermes_write_reg(hw, HERMES_AUXPAGE, (u16) (addr >> 7));
190 hermes_write_reg(hw, HERMES_AUXOFFSET, (u16) (addr & 0x7F));
191}
192
David Kilroye2334182008-08-21 23:27:52 +0100193static inline int
194hermes_aux_control(hermes_t *hw, int enabled)
David Kilroyf482eb72008-08-21 23:27:51 +0100195{
David Kilroye2334182008-08-21 23:27:52 +0100196 int desired_state = enabled ? HERMES_AUX_ENABLED : HERMES_AUX_DISABLED;
197 int action = enabled ? HERMES_AUX_ENABLE : HERMES_AUX_DISABLE;
David Kilroyf482eb72008-08-21 23:27:51 +0100198 int i;
199
200 /* Already open? */
David Kilroye2334182008-08-21 23:27:52 +0100201 if (hermes_read_reg(hw, HERMES_CONTROL) == desired_state)
David Kilroyf482eb72008-08-21 23:27:51 +0100202 return 0;
203
204 hermes_write_reg(hw, HERMES_PARAM0, HERMES_AUX_PW0);
205 hermes_write_reg(hw, HERMES_PARAM1, HERMES_AUX_PW1);
206 hermes_write_reg(hw, HERMES_PARAM2, HERMES_AUX_PW2);
David Kilroye2334182008-08-21 23:27:52 +0100207 hermes_write_reg(hw, HERMES_CONTROL, action);
David Kilroyf482eb72008-08-21 23:27:51 +0100208
209 for (i = 0; i < 20; i++) {
210 udelay(10);
211 if (hermes_read_reg(hw, HERMES_CONTROL) ==
David Kilroye2334182008-08-21 23:27:52 +0100212 desired_state)
David Kilroyf482eb72008-08-21 23:27:51 +0100213 return 0;
214 }
215
216 return -EBUSY;
217}
218
David Kilroye2334182008-08-21 23:27:52 +0100219/*** Plug Data Functions ***/
220
David Kilroyf482eb72008-08-21 23:27:51 +0100221/*
222 * Scan PDR for the record with the specified RECORD_ID.
223 * If it's not found, return NULL.
224 */
225static struct pdr *
David Kilroye2334182008-08-21 23:27:52 +0100226hermes_find_pdr(struct pdr *first_pdr, u32 record_id)
David Kilroyf482eb72008-08-21 23:27:51 +0100227{
228 struct pdr *pdr = first_pdr;
David Kilroye2334182008-08-21 23:27:52 +0100229 void *end = (void *)first_pdr + MAX_PDA_SIZE;
David Kilroyf482eb72008-08-21 23:27:51 +0100230
David Kilroye2334182008-08-21 23:27:52 +0100231 while (((void *)pdr < end) &&
232 (pdr_id(pdr) != PDI_END)) {
David Kilroyf482eb72008-08-21 23:27:51 +0100233 /*
234 * PDR area is currently not terminated by PDI_END.
235 * It's followed by CRC records, which have the type
236 * field where PDR has length. The type can be 0 or 1.
237 */
238 if (pdr_len(pdr) < 2)
239 return NULL;
240
241 /* If the record ID matches, we are done */
242 if (pdr_id(pdr) == record_id)
243 return pdr;
244
245 pdr = (struct pdr *) pdr->next;
246 }
247 return NULL;
248}
249
250/* Process one Plug Data Item - find corresponding PDR and plug it */
251static int
David Kilroye2334182008-08-21 23:27:52 +0100252hermes_plug_pdi(hermes_t *hw, struct pdr *first_pdr, const struct pdi *pdi)
David Kilroyf482eb72008-08-21 23:27:51 +0100253{
254 struct pdr *pdr;
255
David Kilroye2334182008-08-21 23:27:52 +0100256 /* Find the PDR corresponding to this PDI */
257 pdr = hermes_find_pdr(first_pdr, pdi_id(pdi));
David Kilroyf482eb72008-08-21 23:27:51 +0100258
259 /* No match is found, safe to ignore */
260 if (!pdr)
261 return 0;
262
263 /* Lengths of the data in PDI and PDR must match */
264 if (pdi_len(pdi) != pdr_len(pdr))
265 return -EINVAL;
266
267 /* do the actual plugging */
David Kilroye2334182008-08-21 23:27:52 +0100268 hermes_aux_setaddr(hw, pdr_addr(pdr));
David Kilroyf482eb72008-08-21 23:27:51 +0100269 hermes_write_bytes(hw, HERMES_AUXDATA, pdi->data, pdi_len(pdi));
270
271 return 0;
272}
273
274/* Read PDA from the adapter */
David Kilroye2334182008-08-21 23:27:52 +0100275int hermes_read_pda(hermes_t *hw,
276 __le16 *pda,
277 u32 pda_addr,
278 u16 pda_len,
279 int use_eeprom) /* can we get this into hw? */
David Kilroyf482eb72008-08-21 23:27:51 +0100280{
281 int ret;
David Kilroye2334182008-08-21 23:27:52 +0100282 u16 pda_size;
283 u16 data_len = pda_len;
284 __le16 *data = pda;
David Kilroyf482eb72008-08-21 23:27:51 +0100285
David Kilroye2334182008-08-21 23:27:52 +0100286 if (use_eeprom) {
287 /* PDA of spectrum symbol is in eeprom */
288
289 /* Issue command to read EEPROM */
290 ret = hermes_docmd_wait(hw, HERMES_CMD_READMIF, 0, NULL);
291 if (ret)
292 return ret;
293 }
David Kilroyf482eb72008-08-21 23:27:51 +0100294
295 /* Open auxiliary port */
David Kilroye2334182008-08-21 23:27:52 +0100296 ret = hermes_aux_control(hw, 1);
297 printk(KERN_DEBUG PFX "AUX enable returned %d\n", ret);
David Kilroyf482eb72008-08-21 23:27:51 +0100298 if (ret)
299 return ret;
300
301 /* read PDA from EEPROM */
David Kilroye2334182008-08-21 23:27:52 +0100302 hermes_aux_setaddr(hw, pda_addr);
303 hermes_read_words(hw, HERMES_AUXDATA, data, data_len / 2);
304
305 /* Close aux port */
306 ret = hermes_aux_control(hw, 0);
307 printk(KERN_DEBUG PFX "AUX disable returned %d\n", ret);
David Kilroyf482eb72008-08-21 23:27:51 +0100308
309 /* Check PDA length */
310 pda_size = le16_to_cpu(pda[0]);
David Kilroye2334182008-08-21 23:27:52 +0100311 printk(KERN_DEBUG PFX "Actual PDA length %d, Max allowed %d\n",
312 pda_size, pda_len);
David Kilroyf482eb72008-08-21 23:27:51 +0100313 if (pda_size > pda_len)
314 return -EINVAL;
315
316 return 0;
317}
David Kilroye2334182008-08-21 23:27:52 +0100318EXPORT_SYMBOL(hermes_read_pda);
David Kilroyf482eb72008-08-21 23:27:51 +0100319
David Kilroye2334182008-08-21 23:27:52 +0100320/* Parse PDA and write the records into the adapter
321 *
322 * Attempt to write every records that is in the specified pda
323 * which also has a valid production data record for the firmware.
324 */
325int hermes_apply_pda(hermes_t *hw,
326 const char *first_pdr,
327 const __le16 *pda)
David Kilroyf482eb72008-08-21 23:27:51 +0100328{
329 int ret;
David Kilroye2334182008-08-21 23:27:52 +0100330 const struct pdi *pdi;
331 struct pdr *pdr;
David Kilroyf482eb72008-08-21 23:27:51 +0100332
David Kilroye2334182008-08-21 23:27:52 +0100333 pdr = (struct pdr *) first_pdr;
David Kilroyf482eb72008-08-21 23:27:51 +0100334
335 /* Go through every PDI and plug them into the adapter */
David Kilroye2334182008-08-21 23:27:52 +0100336 pdi = (const struct pdi *) (pda + 2);
David Kilroyf482eb72008-08-21 23:27:51 +0100337 while (pdi_id(pdi) != PDI_END) {
David Kilroye2334182008-08-21 23:27:52 +0100338 ret = hermes_plug_pdi(hw, pdr, pdi);
David Kilroyf482eb72008-08-21 23:27:51 +0100339 if (ret)
340 return ret;
341
342 /* Increment to the next PDI */
David Kilroye2334182008-08-21 23:27:52 +0100343 pdi = (const struct pdi *) &pdi->data[pdi_len(pdi)];
David Kilroyf482eb72008-08-21 23:27:51 +0100344 }
345 return 0;
346}
David Kilroye2334182008-08-21 23:27:52 +0100347EXPORT_SYMBOL(hermes_apply_pda);
David Kilroyf482eb72008-08-21 23:27:51 +0100348
David Kilroye2334182008-08-21 23:27:52 +0100349/* Identify the total number of bytes in all blocks
350 * including the header data.
351 */
352size_t
353hermes_blocks_length(const char *first_block)
354{
355 const struct dblock *blk = (const struct dblock *) first_block;
356 int total_len = 0;
357 int len;
358
359 /* Skip all blocks to locate Plug Data References
360 * (Spectrum CS) */
361 while (dblock_addr(blk) != BLOCK_END) {
362 len = dblock_len(blk);
363 total_len += sizeof(*blk) + len;
364 blk = (struct dblock *) &blk->data[len];
365 }
366
367 return total_len;
368}
369EXPORT_SYMBOL(hermes_blocks_length);
370
371/*** Hermes programming ***/
372
373/* Program the data blocks */
374int hermes_program(hermes_t *hw, const char *first_block, const char *end)
David Kilroyf482eb72008-08-21 23:27:51 +0100375{
376 const struct dblock *blk;
377 u32 blkaddr;
378 u32 blklen;
David Kilroye2334182008-08-21 23:27:52 +0100379#if LIMIT_PROGRAM_SIZE
380 u32 addr;
381 u32 len;
382#endif
David Kilroyf482eb72008-08-21 23:27:51 +0100383
David Kilroye2334182008-08-21 23:27:52 +0100384 blk = (const struct dblock *) first_block;
385
386 if ((const char *) blk > (end - sizeof(*blk)))
387 return -EIO;
388
David Kilroyf482eb72008-08-21 23:27:51 +0100389 blkaddr = dblock_addr(blk);
390 blklen = dblock_len(blk);
391
David Kilroye2334182008-08-21 23:27:52 +0100392 while ((blkaddr != BLOCK_END) &&
393 (((const char *) blk + blklen) <= end)) {
394 printk(KERN_DEBUG PFX
395 "Programming block of length %d to address 0x%08x\n",
396 blklen, blkaddr);
397
398#if !LIMIT_PROGRAM_SIZE
399 /* wl_lkm driver splits this into writes of 2000 bytes */
400 hermes_aux_setaddr(hw, blkaddr);
David Kilroyf482eb72008-08-21 23:27:51 +0100401 hermes_write_bytes(hw, HERMES_AUXDATA, blk->data,
402 blklen);
David Kilroye2334182008-08-21 23:27:52 +0100403#else
404 len = (blklen < MAX_DL_SIZE) ? blklen : MAX_DL_SIZE;
405 addr = blkaddr;
David Kilroyf482eb72008-08-21 23:27:51 +0100406
David Kilroye2334182008-08-21 23:27:52 +0100407 while (addr < (blkaddr + blklen)) {
408 printk(KERN_DEBUG PFX
409 "Programming subblock of length %d "
410 "to address 0x%08x. Data @ %p\n",
411 len, addr, &blk->data[addr - blkaddr]);
412
413 hermes_aux_setaddr(hw, addr);
414 hermes_write_bytes(hw, HERMES_AUXDATA,
415 &blk->data[addr - blkaddr],
416 len);
417
418 addr += len;
419 len = ((blkaddr + blklen - addr) < MAX_DL_SIZE) ?
420 (blkaddr + blklen - addr) : MAX_DL_SIZE;
421 }
422#endif
423 blk = (const struct dblock *) &blk->data[blklen];
424
425 if ((const char *) blk > (end - sizeof(*blk)))
426 return -EIO;
427
David Kilroyf482eb72008-08-21 23:27:51 +0100428 blkaddr = dblock_addr(blk);
429 blklen = dblock_len(blk);
430 }
431 return 0;
432}
David Kilroye2334182008-08-21 23:27:52 +0100433EXPORT_SYMBOL(hermes_program);
David Kilroyf482eb72008-08-21 23:27:51 +0100434
435static int __init init_hermes_dld(void)
436{
437 return 0;
438}
439
440static void __exit exit_hermes_dld(void)
441{
442}
443
444module_init(init_hermes_dld);
445module_exit(exit_hermes_dld);