Commit | Line | Data |
---|---|---|
f482eb79 DK |
1 | /* |
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 | ||
53 | MODULE_DESCRIPTION("Download helper for Lucent Hermes chipset"); | |
54 | MODULE_AUTHOR("David Kilroy <kilroyd@gmail.com>"); | |
55 | MODULE_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 */ | |
e2334180 | 67 | #define HERMES_AUX_DISABLED 0x0000 /* Auxiliary port is closed */ |
f482eb79 DK |
68 | |
69 | #define HERMES_AUX_PW0 0xFE01 | |
70 | #define HERMES_AUX_PW1 0xDC23 | |
71 | #define HERMES_AUX_PW2 0xBA45 | |
72 | ||
e2334180 | 73 | /* End markers used in dblocks */ |
f482eb79 DK |
74 | #define PDI_END 0x00000000 /* End of PDA */ |
75 | #define BLOCK_END 0xFFFFFFFF /* Last image block */ | |
e2334180 DK |
76 | #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 | |
f482eb79 DK |
95 | |
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 | */ | |
106 | struct 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 | */ | |
117 | struct 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 | */ | |
129 | struct 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 | ||
e2334180 DK |
135 | /*** FW data block access functions ***/ |
136 | ||
f482eb79 DK |
137 | static inline u32 |
138 | dblock_addr(const struct dblock *blk) | |
139 | { | |
140 | return le32_to_cpu(blk->addr); | |
141 | } | |
142 | ||
143 | static inline u32 | |
144 | dblock_len(const struct dblock *blk) | |
145 | { | |
146 | return le16_to_cpu(blk->len); | |
147 | } | |
148 | ||
e2334180 DK |
149 | /*** PDR Access functions ***/ |
150 | ||
f482eb79 DK |
151 | static inline u32 |
152 | pdr_id(const struct pdr *pdr) | |
153 | { | |
154 | return le32_to_cpu(pdr->id); | |
155 | } | |
156 | ||
157 | static inline u32 | |
158 | pdr_addr(const struct pdr *pdr) | |
159 | { | |
160 | return le32_to_cpu(pdr->addr); | |
161 | } | |
162 | ||
163 | static inline u32 | |
164 | pdr_len(const struct pdr *pdr) | |
165 | { | |
166 | return le32_to_cpu(pdr->len); | |
167 | } | |
168 | ||
e2334180 DK |
169 | /*** PDI Access functions ***/ |
170 | ||
f482eb79 DK |
171 | static inline u32 |
172 | pdi_id(const struct pdi *pdi) | |
173 | { | |
174 | return le16_to_cpu(pdi->id); | |
175 | } | |
176 | ||
177 | /* Return length of the data only, in bytes */ | |
178 | static inline u32 | |
179 | pdi_len(const struct pdi *pdi) | |
180 | { | |
181 | return 2 * (le16_to_cpu(pdi->len) - 1); | |
182 | } | |
183 | ||
e2334180 DK |
184 | /*** Hermes AUX control ***/ |
185 | ||
f482eb79 | 186 | static inline void |
e2334180 | 187 | hermes_aux_setaddr(hermes_t *hw, u32 addr) |
f482eb79 DK |
188 | { |
189 | hermes_write_reg(hw, HERMES_AUXPAGE, (u16) (addr >> 7)); | |
190 | hermes_write_reg(hw, HERMES_AUXOFFSET, (u16) (addr & 0x7F)); | |
191 | } | |
192 | ||
e2334180 DK |
193 | static inline int |
194 | hermes_aux_control(hermes_t *hw, int enabled) | |
f482eb79 | 195 | { |
e2334180 DK |
196 | int desired_state = enabled ? HERMES_AUX_ENABLED : HERMES_AUX_DISABLED; |
197 | int action = enabled ? HERMES_AUX_ENABLE : HERMES_AUX_DISABLE; | |
f482eb79 DK |
198 | int i; |
199 | ||
200 | /* Already open? */ | |
e2334180 | 201 | if (hermes_read_reg(hw, HERMES_CONTROL) == desired_state) |
f482eb79 DK |
202 | 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); | |
e2334180 | 207 | hermes_write_reg(hw, HERMES_CONTROL, action); |
f482eb79 DK |
208 | |
209 | for (i = 0; i < 20; i++) { | |
210 | udelay(10); | |
211 | if (hermes_read_reg(hw, HERMES_CONTROL) == | |
e2334180 | 212 | desired_state) |
f482eb79 DK |
213 | return 0; |
214 | } | |
215 | ||
216 | return -EBUSY; | |
217 | } | |
218 | ||
e2334180 DK |
219 | /*** Plug Data Functions ***/ |
220 | ||
f482eb79 DK |
221 | /* |
222 | * Scan PDR for the record with the specified RECORD_ID. | |
223 | * If it's not found, return NULL. | |
224 | */ | |
225 | static struct pdr * | |
e2334180 | 226 | hermes_find_pdr(struct pdr *first_pdr, u32 record_id) |
f482eb79 DK |
227 | { |
228 | struct pdr *pdr = first_pdr; | |
e2334180 | 229 | void *end = (void *)first_pdr + MAX_PDA_SIZE; |
f482eb79 | 230 | |
e2334180 DK |
231 | while (((void *)pdr < end) && |
232 | (pdr_id(pdr) != PDI_END)) { | |
f482eb79 DK |
233 | /* |
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 */ | |
251 | static int | |
e2334180 | 252 | hermes_plug_pdi(hermes_t *hw, struct pdr *first_pdr, const struct pdi *pdi) |
f482eb79 DK |
253 | { |
254 | struct pdr *pdr; | |
255 | ||
e2334180 DK |
256 | /* Find the PDR corresponding to this PDI */ |
257 | pdr = hermes_find_pdr(first_pdr, pdi_id(pdi)); | |
f482eb79 DK |
258 | |
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 */ | |
e2334180 | 268 | hermes_aux_setaddr(hw, pdr_addr(pdr)); |
f482eb79 DK |
269 | hermes_write_bytes(hw, HERMES_AUXDATA, pdi->data, pdi_len(pdi)); |
270 | ||
271 | return 0; | |
272 | } | |
273 | ||
274 | /* Read PDA from the adapter */ | |
e2334180 DK |
275 | int 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? */ | |
f482eb79 DK |
280 | { |
281 | int ret; | |
e2334180 DK |
282 | u16 pda_size; |
283 | u16 data_len = pda_len; | |
284 | __le16 *data = pda; | |
f482eb79 | 285 | |
e2334180 DK |
286 | 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 | } | |
f482eb79 DK |
294 | |
295 | /* Open auxiliary port */ | |
e2334180 DK |
296 | ret = hermes_aux_control(hw, 1); |
297 | printk(KERN_DEBUG PFX "AUX enable returned %d\n", ret); | |
f482eb79 DK |
298 | if (ret) |
299 | return ret; | |
300 | ||
301 | /* read PDA from EEPROM */ | |
e2334180 DK |
302 | 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); | |
f482eb79 DK |
308 | |
309 | /* Check PDA length */ | |
310 | pda_size = le16_to_cpu(pda[0]); | |
e2334180 DK |
311 | printk(KERN_DEBUG PFX "Actual PDA length %d, Max allowed %d\n", |
312 | pda_size, pda_len); | |
f482eb79 DK |
313 | if (pda_size > pda_len) |
314 | return -EINVAL; | |
315 | ||
316 | return 0; | |
317 | } | |
e2334180 | 318 | EXPORT_SYMBOL(hermes_read_pda); |
f482eb79 | 319 | |
e2334180 DK |
320 | /* 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 | */ | |
325 | int hermes_apply_pda(hermes_t *hw, | |
326 | const char *first_pdr, | |
327 | const __le16 *pda) | |
f482eb79 DK |
328 | { |
329 | int ret; | |
e2334180 DK |
330 | const struct pdi *pdi; |
331 | struct pdr *pdr; | |
f482eb79 | 332 | |
e2334180 | 333 | pdr = (struct pdr *) first_pdr; |
f482eb79 DK |
334 | |
335 | /* Go through every PDI and plug them into the adapter */ | |
e2334180 | 336 | pdi = (const struct pdi *) (pda + 2); |
f482eb79 | 337 | while (pdi_id(pdi) != PDI_END) { |
e2334180 | 338 | ret = hermes_plug_pdi(hw, pdr, pdi); |
f482eb79 DK |
339 | if (ret) |
340 | return ret; | |
341 | ||
342 | /* Increment to the next PDI */ | |
e2334180 | 343 | pdi = (const struct pdi *) &pdi->data[pdi_len(pdi)]; |
f482eb79 DK |
344 | } |
345 | return 0; | |
346 | } | |
e2334180 DK |
347 | EXPORT_SYMBOL(hermes_apply_pda); |
348 | ||
349 | /* Identify the total number of bytes in all blocks | |
350 | * including the header data. | |
351 | */ | |
352 | size_t | |
353 | hermes_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 | } | |
369 | EXPORT_SYMBOL(hermes_blocks_length); | |
370 | ||
371 | /*** Hermes programming ***/ | |
f482eb79 | 372 | |
e2334180 DK |
373 | /* Program the data blocks */ |
374 | int hermes_program(hermes_t *hw, const char *first_block, const char *end) | |
f482eb79 DK |
375 | { |
376 | const struct dblock *blk; | |
377 | u32 blkaddr; | |
378 | u32 blklen; | |
e2334180 DK |
379 | #if LIMIT_PROGRAM_SIZE |
380 | u32 addr; | |
381 | u32 len; | |
382 | #endif | |
383 | ||
384 | blk = (const struct dblock *) first_block; | |
385 | ||
386 | if ((const char *) blk > (end - sizeof(*blk))) | |
387 | return -EIO; | |
f482eb79 | 388 | |
f482eb79 DK |
389 | blkaddr = dblock_addr(blk); |
390 | blklen = dblock_len(blk); | |
391 | ||
e2334180 DK |
392 | 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); | |
f482eb79 DK |
401 | hermes_write_bytes(hw, HERMES_AUXDATA, blk->data, |
402 | blklen); | |
e2334180 DK |
403 | #else |
404 | len = (blklen < MAX_DL_SIZE) ? blklen : MAX_DL_SIZE; | |
405 | addr = blkaddr; | |
406 | ||
407 | 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; | |
f482eb79 | 427 | |
f482eb79 DK |
428 | blkaddr = dblock_addr(blk); |
429 | blklen = dblock_len(blk); | |
430 | } | |
431 | return 0; | |
432 | } | |
e2334180 | 433 | EXPORT_SYMBOL(hermes_program); |
f482eb79 DK |
434 | |
435 | static int __init init_hermes_dld(void) | |
436 | { | |
437 | return 0; | |
438 | } | |
439 | ||
440 | static void __exit exit_hermes_dld(void) | |
441 | { | |
442 | } | |
443 | ||
444 | module_init(init_hermes_dld); | |
445 | module_exit(exit_hermes_dld); |