1 /*
2 * Copyright (c) 2008, Google Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in
12 * the documentation and/or other materials provided with the
13 * distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
18 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
19 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
22 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
25 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 #include <boot/boot.h>
30 #include <boot/flash.h>
31
32 #include <msm7k/dmov.h>
33 #include <msm7k/nand.h>
34 #include <msm7k/shared.h>
35
36 #define VERBOSE 0
37
38 typedef struct dmov_ch dmov_ch;
39 struct dmov_ch
40 {
41 volatile unsigned cmd;
42 volatile unsigned result;
43 volatile unsigned status;
44 volatile unsigned config;
45 };
46
dmov_prep_ch(dmov_ch * ch,unsigned id)47 static void dmov_prep_ch(dmov_ch *ch, unsigned id)
48 {
49 ch->cmd = DMOV_CMD_PTR(id);
50 ch->result = DMOV_RSLT(id);
51 ch->status = DMOV_STATUS(id);
52 ch->config = DMOV_CONFIG(id);
53 }
54
55 #define SRC_CRCI_NAND_CMD CMD_SRC_CRCI(DMOV_NAND_CRCI_CMD)
56 #define DST_CRCI_NAND_CMD CMD_DST_CRCI(DMOV_NAND_CRCI_CMD)
57 #define SRC_CRCI_NAND_DATA CMD_SRC_CRCI(DMOV_NAND_CRCI_DATA)
58 #define DST_CRCI_NAND_DATA CMD_DST_CRCI(DMOV_NAND_CRCI_DATA)
59
60 static unsigned CFG0, CFG1;
61
62 #define CFG1_WIDE_FLASH (1U << 1)
63
64 #define paddr(n) ((unsigned) (n))
65
dmov_exec_cmdptr(unsigned id,unsigned * ptr)66 static int dmov_exec_cmdptr(unsigned id, unsigned *ptr)
67 {
68 dmov_ch ch;
69 unsigned n;
70
71 dmov_prep_ch(&ch, id);
72
73 writel(DMOV_CMD_PTR_LIST | DMOV_CMD_ADDR(paddr(ptr)), ch.cmd);
74
75 while(!(readl(ch.status) & DMOV_STATUS_RSLT_VALID)) ;
76
77 n = readl(ch.status);
78 while(DMOV_STATUS_RSLT_COUNT(n)) {
79 n = readl(ch.result);
80 if(n != 0x80000002) {
81 dprintf("ERROR: result: %x\n", n);
82 dprintf("ERROR: flush: %x %x %x %x\n",
83 readl(DMOV_FLUSH0(DMOV_NAND_CHAN)),
84 readl(DMOV_FLUSH1(DMOV_NAND_CHAN)),
85 readl(DMOV_FLUSH2(DMOV_NAND_CHAN)),
86 readl(DMOV_FLUSH3(DMOV_NAND_CHAN)));
87 }
88 n = readl(ch.status);
89 }
90
91 return 0;
92 }
93
94 static unsigned flash_maker = 0;
95 static unsigned flash_device = 0;
96
flash_read_id(dmov_s * cmdlist,unsigned * ptrlist)97 static void flash_read_id(dmov_s *cmdlist, unsigned *ptrlist)
98 {
99 dmov_s *cmd = cmdlist;
100 unsigned *ptr = ptrlist;
101 unsigned *data = ptrlist + 4;
102
103 data[0] = 0 | 4;
104 data[1] = NAND_CMD_FETCH_ID;
105 data[2] = 1;
106 data[3] = 0;
107 data[4] = 0;
108
109 cmd[0].cmd = 0 | CMD_OCB;
110 cmd[0].src = paddr(&data[0]);
111 cmd[0].dst = NAND_FLASH_CHIP_SELECT;
112 cmd[0].len = 4;
113
114 cmd[1].cmd = DST_CRCI_NAND_CMD;
115 cmd[1].src = paddr(&data[1]);
116 cmd[1].dst = NAND_FLASH_CMD;
117 cmd[1].len = 4;
118
119 cmd[2].cmd = 0;
120 cmd[2].src = paddr(&data[2]);
121 cmd[2].dst = NAND_EXEC_CMD;
122 cmd[2].len = 4;
123
124 cmd[3].cmd = SRC_CRCI_NAND_DATA;
125 cmd[3].src = NAND_FLASH_STATUS;
126 cmd[3].dst = paddr(&data[3]);
127 cmd[3].len = 4;
128
129 cmd[4].cmd = CMD_OCU | CMD_LC;
130 cmd[4].src = NAND_READ_ID;
131 cmd[4].dst = paddr(&data[4]);
132 cmd[4].len = 4;
133
134 ptr[0] = (paddr(cmd) >> 3) | CMD_PTR_LP;
135
136 dmov_exec_cmdptr(DMOV_NAND_CHAN, ptr);
137
138 #if VERBOSE
139 dprintf("status: %x\n", data[3]);
140 #endif
141 dprintf("nandid: %x maker %b device %b\n",
142 data[4], data[4] & 0xff, (data[4] >> 8) & 0xff);
143
144 flash_maker = data[4] & 0xff;
145 flash_device = (data[4] >> 8) & 0xff;
146 }
147
flash_erase_block(dmov_s * cmdlist,unsigned * ptrlist,unsigned page)148 static int flash_erase_block(dmov_s *cmdlist, unsigned *ptrlist, unsigned page)
149 {
150 dmov_s *cmd = cmdlist;
151 unsigned *ptr = ptrlist;
152 unsigned *data = ptrlist + 4;
153
154 /* only allow erasing on block boundaries */
155 if(page & 63) return -1;
156
157 data[0] = NAND_CMD_BLOCK_ERASE;
158 data[1] = page;
159 data[2] = 0;
160 data[3] = 0 | 4;
161 data[4] = 1;
162 data[5] = 0xeeeeeeee;
163 data[6] = CFG0 & (~(7 << 6)); /* CW_PER_PAGE = 0 */
164 data[7] = CFG1;
165
166 cmd[0].cmd = DST_CRCI_NAND_CMD | CMD_OCB;
167 cmd[0].src = paddr(&data[0]);
168 cmd[0].dst = NAND_FLASH_CMD;
169 cmd[0].len = 16;
170
171 cmd[1].cmd = 0;
172 cmd[1].src = paddr(&data[6]);
173 cmd[1].dst = NAND_DEV0_CFG0;
174 cmd[1].len = 8;
175
176 cmd[2].cmd = 0;
177 cmd[2].src = paddr(&data[4]);
178 cmd[2].dst = NAND_EXEC_CMD;
179 cmd[2].len = 4;
180
181 cmd[3].cmd = SRC_CRCI_NAND_DATA | CMD_OCU | CMD_LC;
182 cmd[3].src = NAND_FLASH_STATUS;
183 cmd[3].dst = paddr(&data[5]);
184 cmd[3].len = 4;
185
186 ptr[0] = (paddr(cmd) >> 3) | CMD_PTR_LP;
187
188 dmov_exec_cmdptr(DMOV_NAND_CHAN, ptr);
189
190 #if VERBOSE
191 dprintf("status: %x\n", data[5]);
192 #endif
193
194 /* we fail if there was an operation error, a mpu error, or the
195 ** erase success bit was not set.
196 */
197 if(data[5] & 0x110) return -1;
198 if(!(data[5] & 0x80)) return -1;
199
200 return 0;
201 }
202
203 struct data_flash_io {
204 unsigned cmd;
205 unsigned addr0;
206 unsigned addr1;
207 unsigned chipsel;
208 unsigned cfg0;
209 unsigned cfg1;
210 unsigned exec;
211 unsigned ecc_cfg;
212 unsigned ecc_cfg_save;
213 struct {
214 unsigned flash_status;
215 unsigned buffer_status;
216 } result[4];
217 };
218
_flash_read_page(dmov_s * cmdlist,unsigned * ptrlist,unsigned page,void * _addr,void * _spareaddr)219 static int _flash_read_page(dmov_s *cmdlist, unsigned *ptrlist, unsigned page, void *_addr, void *_spareaddr)
220 {
221 dmov_s *cmd = cmdlist;
222 unsigned *ptr = ptrlist;
223 struct data_flash_io *data = (void*) (ptrlist + 4);
224 unsigned addr = (unsigned) _addr;
225 unsigned spareaddr = (unsigned) _spareaddr;
226 unsigned n;
227
228 data->cmd = NAND_CMD_PAGE_READ_ECC;
229 data->addr0 = page << 16;
230 data->addr1 = (page >> 16) & 0xff;
231 data->chipsel = 0 | 4; /* flash0 + undoc bit */
232
233 /* GO bit for the EXEC register */
234 data->exec = 1;
235
236 data->cfg0 = CFG0;
237 data->cfg1 = CFG1;
238
239 data->ecc_cfg = 0x203;
240
241 /* save existing ecc config */
242 cmd->cmd = CMD_OCB;
243 cmd->src = NAND_EBI2_ECC_BUF_CFG;
244 cmd->dst = paddr(&data->ecc_cfg_save);
245 cmd->len = 4;
246 cmd++;
247
248 for(n = 0; n < 4; n++) {
249 /* write CMD / ADDR0 / ADDR1 / CHIPSEL regs in a burst */
250 cmd->cmd = DST_CRCI_NAND_CMD;
251 cmd->src = paddr(&data->cmd);
252 cmd->dst = NAND_FLASH_CMD;
253 cmd->len = ((n == 0) ? 16 : 4);
254 cmd++;
255
256 if (n == 0) {
257 /* block on cmd ready, set configuration */
258 cmd->cmd = 0;
259 cmd->src = paddr(&data->cfg0);
260 cmd->dst = NAND_DEV0_CFG0;
261 cmd->len = 8;
262 cmd++;
263
264 /* set our ecc config */
265 cmd->cmd = 0;
266 cmd->src = paddr(&data->ecc_cfg);
267 cmd->dst = NAND_EBI2_ECC_BUF_CFG;
268 cmd->len = 4;
269 cmd++;
270 }
271 /* kick the execute register */
272 cmd->cmd = 0;
273 cmd->src = paddr(&data->exec);
274 cmd->dst = NAND_EXEC_CMD;
275 cmd->len = 4;
276 cmd++;
277
278 /* block on data ready, then read the status register */
279 cmd->cmd = SRC_CRCI_NAND_DATA;
280 cmd->src = NAND_FLASH_STATUS;
281 cmd->dst = paddr(&data->result[n]);
282 cmd->len = 8;
283 cmd++;
284
285 /* read data block */
286 cmd->cmd = 0;
287 cmd->src = NAND_FLASH_BUFFER;
288 cmd->dst = addr + n * 516;
289 cmd->len = ((n < 3) ? 516 : 500);
290 cmd++;
291 }
292
293 /* read extra data */
294 cmd->cmd = 0;
295 cmd->src = NAND_FLASH_BUFFER + 500;
296 cmd->dst = spareaddr;
297 cmd->len = 16;
298 cmd++;
299
300 /* restore saved ecc config */
301 cmd->cmd = CMD_OCU | CMD_LC;
302 cmd->src = paddr(&data->ecc_cfg_save);
303 cmd->dst = NAND_EBI2_ECC_BUF_CFG;
304 cmd->len = 4;
305
306 ptr[0] = (paddr(cmdlist) >> 3) | CMD_PTR_LP;
307
308 dmov_exec_cmdptr(DMOV_NAND_CHAN, ptr);
309
310 #if VERBOSE
311 dprintf("read page %d: status: %x %x %x %x\n",
312 page, data[5], data[6], data[7], data[8]);
313 for(n = 0; n < 4; n++) {
314 ptr = (unsigned*)(addr + 512 * n);
315 dprintf("data%d: %x %x %x %x\n", n, ptr[0], ptr[1], ptr[2], ptr[3]);
316 ptr = (unsigned*)(spareaddr + 16 * n);
317 dprintf("spare data%d %x %x %x %x\n", n, ptr[0], ptr[1], ptr[2], ptr[3]);
318 }
319 #endif
320
321 /* if any of the writes failed (0x10), or there was a
322 ** protection violation (0x100), we lose
323 */
324 for(n = 0; n < 4; n++) {
325 if (data->result[n].flash_status & 0x110) {
326 return -1;
327 }
328 }
329
330 return 0;
331 }
332
_flash_write_page(dmov_s * cmdlist,unsigned * ptrlist,unsigned page,const void * _addr,const void * _spareaddr)333 static int _flash_write_page(dmov_s *cmdlist, unsigned *ptrlist, unsigned page,
334 const void *_addr, const void *_spareaddr)
335 {
336 dmov_s *cmd = cmdlist;
337 unsigned *ptr = ptrlist;
338 struct data_flash_io *data = (void*) (ptrlist + 4);
339 unsigned addr = (unsigned) _addr;
340 unsigned spareaddr = (unsigned) _spareaddr;
341 unsigned n;
342
343 data->cmd = NAND_CMD_PRG_PAGE;
344 data->addr0 = page << 16;
345 data->addr1 = (page >> 16) & 0xff;
346 data->chipsel = 0 | 4; /* flash0 + undoc bit */
347
348 data->cfg0 = CFG0;
349 data->cfg1 = CFG1;
350
351 /* GO bit for the EXEC register */
352 data->exec = 1;
353
354 data->ecc_cfg = 0x203;
355
356 /* save existing ecc config */
357 cmd->cmd = CMD_OCB;
358 cmd->src = NAND_EBI2_ECC_BUF_CFG;
359 cmd->dst = paddr(&data->ecc_cfg_save);
360 cmd->len = 4;
361 cmd++;
362
363 for(n = 0; n < 4; n++) {
364 /* write CMD / ADDR0 / ADDR1 / CHIPSEL regs in a burst */
365 cmd->cmd = DST_CRCI_NAND_CMD;
366 cmd->src = paddr(&data->cmd);
367 cmd->dst = NAND_FLASH_CMD;
368 cmd->len = ((n == 0) ? 16 : 4);
369 cmd++;
370
371 if (n == 0) {
372 /* set configuration */
373 cmd->cmd = 0;
374 cmd->src = paddr(&data->cfg0);
375 cmd->dst = NAND_DEV0_CFG0;
376 cmd->len = 8;
377 cmd++;
378
379 /* set our ecc config */
380 cmd->cmd = 0;
381 cmd->src = paddr(&data->ecc_cfg);
382 cmd->dst = NAND_EBI2_ECC_BUF_CFG;
383 cmd->len = 4;
384 cmd++;
385 }
386
387 /* write data block */
388 cmd->cmd = 0;
389 cmd->src = addr + n * 516;
390 cmd->dst = NAND_FLASH_BUFFER;
391 cmd->len = ((n < 3) ? 516 : 510);
392 cmd++;
393
394 if (n == 3) {
395 /* write extra data */
396 cmd->cmd = 0;
397 cmd->src = spareaddr;
398 cmd->dst = NAND_FLASH_BUFFER + 500;
399 cmd->len = 16;
400 cmd++;
401 }
402
403 /* kick the execute register */
404 cmd->cmd = 0;
405 cmd->src = paddr(&data->exec);
406 cmd->dst = NAND_EXEC_CMD;
407 cmd->len = 4;
408 cmd++;
409
410 /* block on data ready, then read the status register */
411 cmd->cmd = SRC_CRCI_NAND_DATA;
412 cmd->src = NAND_FLASH_STATUS;
413 cmd->dst = paddr(&data->result[n]);
414 cmd->len = 8;
415 cmd++;
416 }
417
418 /* restore saved ecc config */
419 cmd->cmd = CMD_OCU | CMD_LC;
420 cmd->src = paddr(&data->ecc_cfg_save);
421 cmd->dst = NAND_EBI2_ECC_BUF_CFG;
422 cmd->len = 4;
423
424 ptr[0] = (paddr(cmdlist) >> 3) | CMD_PTR_LP;
425
426 dmov_exec_cmdptr(DMOV_NAND_CHAN, ptr);
427
428 #if VERBOSE
429 dprintf("write page %d: status: %x %x %x %x\n",
430 page, data[5], data[6], data[7], data[8]);
431 #endif
432
433 /* if any of the writes failed (0x10), or there was a
434 ** protection violation (0x100), or the program success
435 ** bit (0x80) is unset, we lose
436 */
437 for(n = 0; n < 4; n++) {
438 if(data->result[n].flash_status & 0x110) return -1;
439 if(!(data->result[n].flash_status & 0x80)) return -1;
440 }
441
442 return 0;
443 }
444
445 unsigned nand_cfg0;
446 unsigned nand_cfg1;
447
flash_read_config(dmov_s * cmdlist,unsigned * ptrlist)448 static int flash_read_config(dmov_s *cmdlist, unsigned *ptrlist)
449 {
450 cmdlist[0].cmd = CMD_OCB;
451 cmdlist[0].src = NAND_DEV0_CFG0;
452 cmdlist[0].dst = paddr(&CFG0);
453 cmdlist[0].len = 4;
454
455 cmdlist[1].cmd = CMD_OCU | CMD_LC;
456 cmdlist[1].src = NAND_DEV0_CFG1;
457 cmdlist[1].dst = paddr(&CFG1);
458 cmdlist[1].len = 4;
459
460 *ptrlist = (paddr(cmdlist) >> 3) | CMD_PTR_LP;
461
462 dmov_exec_cmdptr(DMOV_NAND_CHAN, ptrlist);
463
464 if((CFG0 == 0) || (CFG1 == 0)) {
465 return -1;
466 }
467
468 dprintf("nandcfg: %x %x (initial)\n", CFG0, CFG1);
469
470 CFG0 = (3 << 6) /* 4 codeword per page for 2k nand */
471 | (516 << 9) /* 516 user data bytes */
472 | (10 << 19) /* 10 parity bytes */
473 | (5 << 27) /* 5 address cycles */
474 | (1 << 30) /* Read status before data */
475 | (1 << 31) /* Send read cmd */
476 /* 0 spare bytes for 16 bit nand or 1 spare bytes for 8 bit */
477 | ((nand_cfg1 & CFG1_WIDE_FLASH) ? (0 << 23) : (1 << 23));
478 CFG1 = (0 << 0) /* Enable ecc */
479 | (7 << 2) /* 8 recovery cycles */
480 | (0 << 5) /* Allow CS deassertion */
481 | (465 << 6) /* Bad block marker location */
482 | (0 << 16) /* Bad block in user data area */
483 | (2 << 17) /* 6 cycle tWB/tRB */
484 | (nand_cfg1 & CFG1_WIDE_FLASH); /* preserve wide flash flag */
485
486 dprintf("nandcfg: %x %x (used)\n", CFG0, CFG1);
487
488 return 0;
489 }
490
491 static unsigned *flash_ptrlist;
492 static dmov_s *flash_cmdlist;
493 static void *flash_spare;
494 static void *flash_data;
495
flash_init(void)496 int flash_init(void)
497 {
498 flash_ptrlist = alloc(1024);
499 flash_cmdlist = alloc(1024);
500 flash_data = alloc(2048);
501 flash_spare = alloc(64);
502
503 if(flash_read_config(flash_cmdlist, flash_ptrlist)) {
504 dprintf("ERROR: could not read CFG0/CFG1 state\n");
505 for(;;);
506 }
507
508 flash_read_id(flash_cmdlist, flash_ptrlist);
509
510 return 0;
511 }
512
flash_erase(ptentry * ptn)513 int flash_erase(ptentry *ptn)
514 {
515 unsigned block = ptn->start;
516 unsigned count = ptn->length;
517
518 while(count-- > 0) {
519 if(flash_erase_block(flash_cmdlist, flash_ptrlist, block * 64)) {
520 dprintf("cannot erase @ %d (bad block?)\n", block);
521 }
522 block++;
523 }
524 return 0;
525 }
526
flash_read_ext(ptentry * ptn,unsigned extra_per_page,unsigned offset,void * data,unsigned bytes)527 int flash_read_ext(ptentry *ptn, unsigned extra_per_page, unsigned offset, void *data, unsigned bytes)
528 {
529 unsigned page = (ptn->start * 64) + (offset / 2048);
530 unsigned lastpage = (ptn->start + ptn->length) * 64;
531 unsigned count = (bytes + 2047 + extra_per_page) / (2048 + extra_per_page);
532 unsigned *spare = (unsigned*) flash_spare;
533 unsigned errors = 0;
534 unsigned char *image = data;
535
536 if(offset & 2047)
537 return -1;
538
539 while(page < lastpage) {
540 if(count == 0) {
541 dprintf("flash_read_image: success (%d errors)\n", errors);
542 return 0;
543 }
544
545 if(_flash_read_page(flash_cmdlist, flash_ptrlist, page++, image, spare)) {
546 errors++;
547 continue;
548 }
549 image += 2048;
550 memcpy(image, spare, extra_per_page);
551 image += extra_per_page;
552 count -= 1;
553 }
554
555 /* could not find enough valid pages before we hit the end */
556 dprintf("flash_read_image: failed (%d errors)\n", errors);
557 return 0xffffffff;
558 }
559
flash_write(ptentry * ptn,unsigned extra_per_page,const void * data,unsigned bytes)560 int flash_write(ptentry *ptn, unsigned extra_per_page, const void *data, unsigned bytes)
561 {
562 unsigned page = ptn->start * 64;
563 unsigned lastpage = (ptn->start + ptn->length) * 64;
564 unsigned *spare = (unsigned*) flash_spare;
565 const unsigned char *image = data;
566 unsigned wsize = 2048 + extra_per_page;
567 unsigned n;
568 int r;
569
570 for(n = 0; n < 16; n++) spare[n] = 0xffffffff;
571
572 while(bytes > 0) {
573 if(bytes < wsize) {
574 dprintf("flash_write_image: image undersized (%d < %d)\n", bytes, wsize);
575 return -1;
576 }
577 if(page >= lastpage) {
578 dprintf("flash_write_image: out of space\n");
579 return -1;
580 }
581
582 if((page & 63) == 0) {
583 if(flash_erase_block(flash_cmdlist, flash_ptrlist, page)) {
584 dprintf("flash_write_image: bad block @ %d\n", page >> 6);
585 page += 64;
586 continue;
587 }
588 }
589
590 if(extra_per_page) {
591 r = _flash_write_page(flash_cmdlist, flash_ptrlist, page++, image, image + 2048);
592 } else {
593 r = _flash_write_page(flash_cmdlist, flash_ptrlist, page++, image, spare);
594 }
595 if(r) {
596 dprintf("flash_write_image: write failure @ page %d (src %d)\n", page, image - (const unsigned char *)data);
597 image -= (page & 63) * wsize;
598 bytes += (page & 63) * wsize;
599 page &= ~63;
600 if(flash_erase_block(flash_cmdlist, flash_ptrlist, page)) {
601 dprintf("flash_write_image: erase failure @ page %d\n", page);
602 }
603 dprintf("flash_write_image: restart write @ page %d (src %d)\n", page, image - (const unsigned char *)data);
604 page += 64;
605 continue;
606 }
607
608 image += wsize;
609 bytes -= wsize;
610 }
611
612 /* erase any remaining pages in the partition */
613 page = (page + 63) & (~63);
614 while(page < lastpage){
615 if(flash_erase_block(flash_cmdlist, flash_ptrlist, page)) {
616 dprintf("flash_write_image: bad block @ %d\n", page >> 6);
617 }
618 page += 64;
619 }
620
621 dprintf("flash_write_image: success\n");
622 return 0;
623 }
624
flash_read_page(unsigned page,void * data,void * extra)625 static int flash_read_page(unsigned page, void *data, void *extra)
626 {
627 return _flash_read_page(flash_cmdlist, flash_ptrlist,
628 page, data, extra);
629 }
630
631