blob: 4a10b7aca043ba90ca4a184986636b6d35e0e6bd [file] [log] [blame]
David Kilroyf482eb72008-08-21 23:27:51 +01001/*
David Kilroyf90d8d42009-02-04 23:05:57 +00002 * Hermes download helper.
David Kilroyf482eb72008-08-21 23:27:51 +01003 *
David Kilroyf90d8d42009-02-04 23:05:57 +00004 * This helper:
David Kilroyf482eb72008-08-21 23:27:51 +01005 * - 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
David Kilroyf482eb72008-08-21 23:27:51 +010047#define PFX "hermes_dld: "
48
David Kilroye2334182008-08-21 23:27:52 +010049/* End markers used in dblocks */
David Kilroyf482eb72008-08-21 23:27:51 +010050#define PDI_END 0x00000000 /* End of PDA */
51#define BLOCK_END 0xFFFFFFFF /* Last image block */
David Kilroye2334182008-08-21 23:27:52 +010052#define TEXT_END 0x1A /* End of text header */
53
David Kilroyf482eb72008-08-21 23:27:51 +010054/*
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 */
Eric Dumazetba2d3582010-06-02 18:10:09 +000068} __packed;
David Kilroyf482eb72008-08-21 23:27:51 +010069
70/*
Walter Goldens77c20612010-05-18 04:44:54 -070071 * Plug Data References are located in the image after the last data
David Kilroyf482eb72008-08-21 23:27:51 +010072 * 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 */
Eric Dumazetba2d3582010-06-02 18:10:09 +000080} __packed;
David Kilroyf482eb72008-08-21 23:27:51 +010081
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 */
Eric Dumazetba2d3582010-06-02 18:10:09 +000091} __packed;
David Kilroyf482eb72008-08-21 23:27:51 +010092
David Kilroye2334182008-08-21 23:27:52 +010093/*** FW data block access functions ***/
94
David Kilroyf482eb72008-08-21 23:27:51 +010095static 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
David Kilroye2334182008-08-21 23:27:52 +0100107/*** PDR Access functions ***/
108
David Kilroyf482eb72008-08-21 23:27:51 +0100109static 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
David Kilroye2334182008-08-21 23:27:52 +0100127/*** PDI Access functions ***/
128
David Kilroyf482eb72008-08-21 23:27:51 +0100129static 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
David Kilroye2334182008-08-21 23:27:52 +0100142/*** Plug Data Functions ***/
143
David Kilroyf482eb72008-08-21 23:27:51 +0100144/*
145 * Scan PDR for the record with the specified RECORD_ID.
146 * If it's not found, return NULL.
147 */
David Kilroy3faa19c2009-02-21 16:52:54 +0000148static const struct pdr *
149hermes_find_pdr(const struct pdr *first_pdr, u32 record_id, const void *end)
David Kilroyf482eb72008-08-21 23:27:51 +0100150{
David Kilroy3faa19c2009-02-21 16:52:54 +0000151 const struct pdr *pdr = first_pdr;
David Kilroyf482eb72008-08-21 23:27:51 +0100152
David Kilroy3faa19c2009-02-21 16:52:54 +0000153 end -= sizeof(struct pdr);
154
155 while (((void *) pdr <= end) &&
David Kilroye2334182008-08-21 23:27:52 +0100156 (pdr_id(pdr) != PDI_END)) {
David Kilroyf482eb72008-08-21 23:27:51 +0100157 /*
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
David Kilroy8f5ae732008-08-21 23:27:53 +0100174/* Scan production data items for a particular entry */
David Kilroy3faa19c2009-02-21 16:52:54 +0000175static const struct pdi *
176hermes_find_pdi(const struct pdi *first_pdi, u32 record_id, const void *end)
David Kilroy8f5ae732008-08-21 23:27:53 +0100177{
David Kilroy3faa19c2009-02-21 16:52:54 +0000178 const struct pdi *pdi = first_pdi;
David Kilroy8f5ae732008-08-21 23:27:53 +0100179
David Kilroy3faa19c2009-02-21 16:52:54 +0000180 end -= sizeof(struct pdi);
181
182 while (((void *) pdi <= end) &&
183 (pdi_id(pdi) != PDI_END)) {
David Kilroy8f5ae732008-08-21 23:27:53 +0100184
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
David Kilroyf482eb72008-08-21 23:27:51 +0100194/* Process one Plug Data Item - find corresponding PDR and plug it */
195static int
Pavel Roskin933d5942011-07-13 11:19:57 -0400196hermes_plug_pdi(struct hermes *hw, const struct pdr *first_pdr,
David Kilroy3faa19c2009-02-21 16:52:54 +0000197 const struct pdi *pdi, const void *pdr_end)
David Kilroyf482eb72008-08-21 23:27:51 +0100198{
David Kilroy3faa19c2009-02-21 16:52:54 +0000199 const struct pdr *pdr;
David Kilroyf482eb72008-08-21 23:27:51 +0100200
David Kilroye2334182008-08-21 23:27:52 +0100201 /* Find the PDR corresponding to this PDI */
David Kilroy3faa19c2009-02-21 16:52:54 +0000202 pdr = hermes_find_pdr(first_pdr, pdi_id(pdi), pdr_end);
David Kilroyf482eb72008-08-21 23:27:51 +0100203
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 */
David Kilroy07cefe72010-05-01 14:05:43 +0100213 hw->ops->program(hw, pdi->data, pdr_addr(pdr), pdi_len(pdi));
David Kilroyf482eb72008-08-21 23:27:51 +0100214
215 return 0;
216}
David Kilroyf482eb72008-08-21 23:27:51 +0100217
David Kilroye2334182008-08-21 23:27:52 +0100218/* 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 */
Pavel Roskin933d5942011-07-13 11:19:57 -0400223int hermes_apply_pda(struct hermes *hw,
David Kilroye2334182008-08-21 23:27:52 +0100224 const char *first_pdr,
David Kilroy3faa19c2009-02-21 16:52:54 +0000225 const void *pdr_end,
226 const __le16 *pda,
227 const void *pda_end)
David Kilroyf482eb72008-08-21 23:27:51 +0100228{
229 int ret;
David Kilroye2334182008-08-21 23:27:52 +0100230 const struct pdi *pdi;
David Kilroy3faa19c2009-02-21 16:52:54 +0000231 const struct pdr *pdr;
David Kilroyf482eb72008-08-21 23:27:51 +0100232
David Kilroy3faa19c2009-02-21 16:52:54 +0000233 pdr = (const struct pdr *) first_pdr;
234 pda_end -= sizeof(struct pdi);
David Kilroyf482eb72008-08-21 23:27:51 +0100235
236 /* Go through every PDI and plug them into the adapter */
David Kilroye2334182008-08-21 23:27:52 +0100237 pdi = (const struct pdi *) (pda + 2);
David Kilroy3faa19c2009-02-21 16:52:54 +0000238 while (((void *) pdi <= pda_end) &&
239 (pdi_id(pdi) != PDI_END)) {
240 ret = hermes_plug_pdi(hw, pdr, pdi, pdr_end);
David Kilroyf482eb72008-08-21 23:27:51 +0100241 if (ret)
242 return ret;
243
244 /* Increment to the next PDI */
David Kilroye2334182008-08-21 23:27:52 +0100245 pdi = (const struct pdi *) &pdi->data[pdi_len(pdi)];
David Kilroyf482eb72008-08-21 23:27:51 +0100246 }
247 return 0;
248}
David Kilroyf482eb72008-08-21 23:27:51 +0100249
David Kilroye2334182008-08-21 23:27:52 +0100250/* Identify the total number of bytes in all blocks
251 * including the header data.
252 */
253size_t
David Kilroy3faa19c2009-02-21 16:52:54 +0000254hermes_blocks_length(const char *first_block, const void *end)
David Kilroye2334182008-08-21 23:27:52 +0100255{
256 const struct dblock *blk = (const struct dblock *) first_block;
257 int total_len = 0;
258 int len;
259
David Kilroy3faa19c2009-02-21 16:52:54 +0000260 end -= sizeof(*blk);
261
David Kilroye2334182008-08-21 23:27:52 +0100262 /* Skip all blocks to locate Plug Data References
263 * (Spectrum CS) */
David Kilroy3faa19c2009-02-21 16:52:54 +0000264 while (((void *) blk <= end) &&
265 (dblock_addr(blk) != BLOCK_END)) {
David Kilroye2334182008-08-21 23:27:52 +0100266 len = dblock_len(blk);
267 total_len += sizeof(*blk) + len;
268 blk = (struct dblock *) &blk->data[len];
269 }
270
271 return total_len;
272}
David Kilroye2334182008-08-21 23:27:52 +0100273
274/*** Hermes programming ***/
275
276/* Program the data blocks */
Pavel Roskin933d5942011-07-13 11:19:57 -0400277int hermes_program(struct hermes *hw, const char *first_block, const void *end)
David Kilroyf482eb72008-08-21 23:27:51 +0100278{
279 const struct dblock *blk;
280 u32 blkaddr;
281 u32 blklen;
David Kilroy07cefe72010-05-01 14:05:43 +0100282 int err = 0;
David Kilroyf482eb72008-08-21 23:27:51 +0100283
David Kilroye2334182008-08-21 23:27:52 +0100284 blk = (const struct dblock *) first_block;
285
David Kilroy3faa19c2009-02-21 16:52:54 +0000286 if ((void *) blk > (end - sizeof(*blk)))
David Kilroye2334182008-08-21 23:27:52 +0100287 return -EIO;
288
David Kilroyf482eb72008-08-21 23:27:51 +0100289 blkaddr = dblock_addr(blk);
290 blklen = dblock_len(blk);
291
David Kilroye2334182008-08-21 23:27:52 +0100292 while ((blkaddr != BLOCK_END) &&
David Kilroy3faa19c2009-02-21 16:52:54 +0000293 (((void *) blk + blklen) <= end)) {
David Kilroy35832c52009-06-18 23:21:27 +0100294 pr_debug(PFX "Programming block of length %d "
295 "to address 0x%08x\n", blklen, blkaddr);
David Kilroye2334182008-08-21 23:27:52 +0100296
David Kilroy07cefe72010-05-01 14:05:43 +0100297 err = hw->ops->program(hw, blk->data, blkaddr, blklen);
298 if (err)
299 break;
David Kilroyf482eb72008-08-21 23:27:51 +0100300
David Kilroye2334182008-08-21 23:27:52 +0100301 blk = (const struct dblock *) &blk->data[blklen];
302
David Kilroy3faa19c2009-02-21 16:52:54 +0000303 if ((void *) blk > (end - sizeof(*blk)))
David Kilroye2334182008-08-21 23:27:52 +0100304 return -EIO;
305
David Kilroyf482eb72008-08-21 23:27:51 +0100306 blkaddr = dblock_addr(blk);
307 blklen = dblock_len(blk);
308 }
David Kilroy07cefe72010-05-01 14:05:43 +0100309 return err;
David Kilroyf482eb72008-08-21 23:27:51 +0100310}
David Kilroy8f5ae732008-08-21 23:27:53 +0100311
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]; \
Eric Dumazetba2d3582010-06-02 18:10:09 +0000320} __packed default_pdr_data_##pid = { \
David Kilroy3faa19c2009-02-21 16:52:54 +0000321 cpu_to_le16((sizeof(default_pdr_data_##pid)/ \
David Kilroy8f5ae732008-08-21 23:27:53 +0100322 sizeof(__le16)) - 1), \
David Kilroy3faa19c2009-02-21 16:52:54 +0000323 cpu_to_le16(pid), \
David Kilroy8f5ae732008-08-21 23:27:53 +0100324 data \
325}
326
327#define DEFAULT_PDR(pid) default_pdr_data_##pid
328
Dirk Hohndel06fe9fb2009-09-28 21:43:57 -0400329/* HWIF Compatibility */
David Kilroy8f5ae732008-08-21 23:27:53 +0100330DEFINE_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 */
Pavel Roskin933d5942011-07-13 11:19:57 -0400390int hermes_apply_pda_with_defaults(struct hermes *hw,
David Kilroy8f5ae732008-08-21 23:27:53 +0100391 const char *first_pdr,
David Kilroy3faa19c2009-02-21 16:52:54 +0000392 const void *pdr_end,
393 const __le16 *pda,
394 const void *pda_end)
David Kilroy8f5ae732008-08-21 23:27:53 +0100395{
396 const struct pdr *pdr = (const struct pdr *) first_pdr;
David Kilroy3faa19c2009-02-21 16:52:54 +0000397 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;
David Kilroy8f5ae732008-08-21 23:27:53 +0100401 int record_id;
402
David Kilroy3faa19c2009-02-21 16:52:54 +0000403 pdr_end -= sizeof(struct pdr);
404
405 while (((void *) pdr <= pdr_end) &&
David Kilroy8f5ae732008-08-21 23:27:53 +0100406 (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
David Kilroy3faa19c2009-02-21 16:52:54 +0000417 pdi = hermes_find_pdi(first_pdi, record_id, pda_end);
David Kilroy8f5ae732008-08-21 23:27:53 +0100418 if (pdi)
David Kilroy35832c52009-06-18 23:21:27 +0100419 pr_debug(PFX "Found record 0x%04x at %p\n",
420 record_id, pdi);
David Kilroy8f5ae732008-08-21 23:27:53 +0100421
422 switch (record_id) {
423 case 0x110: /* Modem REFDAC values */
424 case 0x120: /* Modem VGDAC values */
David Kilroy3faa19c2009-02-21 16:52:54 +0000425 outdoor_pdi = hermes_find_pdi(first_pdi, record_id + 1,
426 pda_end);
David Kilroy8f5ae732008-08-21 23:27:53 +0100427 default_pdi = NULL;
428 if (outdoor_pdi) {
429 pdi = outdoor_pdi;
David Kilroy35832c52009-06-18 23:21:27 +0100430 pr_debug(PFX
431 "Using outdoor record 0x%04x at %p\n",
432 record_id + 1, pdi);
David Kilroy8f5ae732008-08-21 23:27:53 +0100433 }
434 break;
Dirk Hohndel06fe9fb2009-09-28 21:43:57 -0400435 case 0x5: /* HWIF Compatibility */
David Kilroy8f5ae732008-08-21 23:27:53 +0100436 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;
David Kilroy35832c52009-06-18 23:21:27 +0100460 pr_debug(PFX "Using default record 0x%04x at %p\n",
461 record_id, pdi);
David Kilroy8f5ae732008-08-21 23:27:53 +0100462 }
463
464 if (pdi) {
465 /* Lengths of the data in PDI and PDR must match */
David Kilroy3faa19c2009-02-21 16:52:54 +0000466 if ((pdi_len(pdi) == pdr_len(pdr)) &&
467 ((void *) pdi->data + pdi_len(pdi) < pda_end)) {
David Kilroy8f5ae732008-08-21 23:27:53 +0100468 /* do the actual plugging */
David Kilroy07cefe72010-05-01 14:05:43 +0100469 hw->ops->program(hw, pdi->data, pdr_addr(pdr),
470 pdi_len(pdi));
David Kilroy8f5ae732008-08-21 23:27:53 +0100471 }
472 }
473
474 pdr++;
475 }
476 return 0;
477}